Gjero Krsteski http://krsteski.de ...schreibt über PHP-Enterprise, Core-Themen und für die PHP-Community Mon, 12 Mar 2012 10:19:39 +0000 http://wordpress.org/?v=2.9 en hourly 1 Anomalie oder Einlesefehler der PHP Syntax? http://krsteski.de/php-tricks-und-tipps/anomalie-oder-einlesefehler-der-php-syntax.html http://krsteski.de/php-tricks-und-tipps/anomalie-oder-einlesefehler-der-php-syntax.html#comments Mon, 12 Mar 2012 10:19:39 +0000 Gjero Krsteski http://krsteski.de/?p=757 Refactoring macht mir viel Spaß. Das hat viele Gründe: man sieht wie andere Programmierer bestimmte Probleme gelöst haben, man sieht wie Sachen gemacht oder besser nicht gemacht werden sollen, man arbeitet mit altem Code. Dabei sind mir schon einige kuriose Codefragmente aufgefallen wo ich schmunzeln musste. Aktuell stehe ich vor einem Codefragment wo ich mich erst fragen muss – ob so was überhaupt ge-parsed werden kann. Hier das Codefragment in einer vereinfachten Art und Weise:

error_reporting(E_ALL | E_STRICT);
ini_set('display_errors', true);

//try
{

  $conan = 1;
  $power = 1;

  print ($conan + $power).PHP_EOL;
}
//catch (Exception $e)
{
  // do nothing ...
}

In das oben aufgeführte Beispiel, hat man nur das „try“ und „catch“ auskommentiert. Häää?! Ich verstehe leider nicht, was hier die Absicht des Programmierers war – aber gut. Habe den Script mit „php -l“ nach PHP Einlesefehler geprüft. Ergebnis – alles OK!!! Habe das Script ausgeführt. Ergebnis – wieder alles OK!!! Wie ist es möglich, dass solch eine Syntax funktioniert? Woher kommt das? Ist das eine Anomalie oder Absicht im PHP?

]]>
http://krsteski.de/php-tricks-und-tipps/anomalie-oder-einlesefehler-der-php-syntax.html/feed 15
Die Wiederbelebung der DBM Dateien http://krsteski.de/php-tricks-und-tipps/die-wiederbelebung-der-dbm-dateien.html http://krsteski.de/php-tricks-und-tipps/die-wiederbelebung-der-dbm-dateien.html#comments Mon, 06 Feb 2012 20:06:56 +0000 Gjero Krsteski http://krsteski.de/?p=744 In den Artikel von 14 Mai 2010 „Cachen mit DBM Dateien“ haben PHPGangster und ich darüber diskutiert wie ein Caching Verfahren mit DBM Dateien etwa aussehen könnte. Damals war das noch eine grobe Idee und nicht jeder konnte sich was darunter vorstellen, ob das gut funktionieren könnte. Da man eben die DBA Extension in PHP neu konfigurieren muss. Sprich, das PHP musste neu Gebacken werden. Also, durch die Verwendung der Konfigurationsoption –enable-dba=shared wird ein dynamisch ladbares Modul erstellt, mit dem PHP eine grundlegende Unterstützung von dbm-style-Datenbanken bietet. Es muss außerdem die Unterstützung für mindestens einen der folgenden Handler hinzugefügt werden, indem der configure-Schalter –with-[dba-handler] im PHP Konfigurationsaufruf angegeben wird. Das erschreckte mich nicht, in Gegenteil, ich nahm das als Ansporn und erstellte kurzerhand die erste Version der „CacheDba“ Library. Nun, seit dann bis heute ist einiges passiert. Hier mein Update für euch.

DbaCache

Die DbaCache-Library war die erste Entwicklung. Es ist eine unabhängige PHP Library. Eigentlich sollte das ein Prototyp werden. Jedoch zeigte sich der Einsatz als sehr leicht und vor allem sehr schnell. Andere Entwickler und mir war wichtig ein Caching Mechanismus zu haben, das genauso schnell wie Memcache ist und nie überlaufen kann. Mittlerweile ist die DbaCache-Library auf diverse Open-Source Portale zur download bereitgestellt und wird gerne runter geladen sowie aktiv eingesetzt. Mehr freut es mich, dass es doch noch Jungs gibt die gerne bereit sind ihr PHP auf der Produktiv-Umgebung neu mit DBM zu konfigurieren bzw. umzustellen. Ich weiß, dass es nicht immer ohne ist. :-)
Link: https://github.com/gjerokrsteski/php-dba-cache

Zend_Cache_Backend_Dba bei ZF1

Kurz danach, quasi über Nacht, habe ich die Komponente Zend_Cache_Backend_Dba entwickelt und ebenfalls auf GitHub bereitgestellt. Es dauerte nicht lange, dann würde der Marc Bennewitz von ZF darauf aufmerksam und bat mich die Komponente zu refaktorisieren und ein Backport auf ZF1 zu machen. An dieser Stelle – Marc vielen Dank für das tolle Code-Review. Deine Tipps habe ich ebenfalls in der DbaCache-Library nachgezogen.
Link: https://github.com/gjerokrsteski/zf1

Zend\Cache\Storage\Adapter\Dba bei ZF2

Nun, wie ihr bereits wisst ist dass ZF2 komplett auf Namespaces umgestellt und bietet eine Menge an Scafolding Mechanismen, die den Alltag um einiges erleichtern. In ZF2 hat der Marc Bennewitz basierend auf ZF1 das DBA Caching als ein Cache\Storage\Adapter implementiert. Das ganze befindet sich noch in der Entwicklung – sieht aber bereits jetzt ganz solide aus. Bitte fühlt euch frei ein ZF Contributor zu werden und diese Komponente weiter zu entwickeln.
Link: https://github.com/gjerokrsteski/zf2

Ja, das war’s eigentlich. Es würde mich sehr freuen, wenn der eine oder andere die oben genannten Komponenten auf GitHub forkt und sie ebenfalls ausprobiert und mir ein Feedback gibt. Rock on!!!

]]>
http://krsteski.de/php-tricks-und-tipps/die-wiederbelebung-der-dbm-dateien.html/feed 1
Objekte abrufen und mehrere Instanzen vermeiden http://krsteski.de/php-tricks-und-tipps/objekten-abrufen-und-mehrere-instanzen-vermeiden.html http://krsteski.de/php-tricks-und-tipps/objekten-abrufen-und-mehrere-instanzen-vermeiden.html#comments Fri, 18 Nov 2011 08:37:48 +0000 Gjero Krsteski http://krsteski.de/?p=700 Auf Englisch würde das etwa als „Retrieve objects avoiding multiple instances” geschrieben werden. Den Artikel habe ich auf Englisch verfasst, da ich von phpclasses.org gebeten wurde paar Zeilen Dokumentation zum Projekt „Building an Identity Map in PHP” zu schreiben. Das Projekt zeigt auf wie eine Identity-Map im PHP Projekt implementiert wird und was für Vorteile das Ganze mit sich bringt. So, ab heir ist der Artikel auf Englisch verfasst: The „Building an Identity Map in PHP” package can store and retrieve objects in persistent storage containers avoiding to have multiple instances of the same object in memory. It can use a mapper class to store objects of a class in a container like for instance a database table. It can also retrieve the objects from the container assuring that only one instance of the same object is retrieved into memory.

This package requires

- PDO a lightweight, consistent interface for accessing databases in PHP.
- PHPUnit a unit testing framework for PHP projects.

This package implements

- Data-Mapper Pattern
- Identity-Map Pattern

Why identity mapping?

By using Data-Mapper pattern without an identity map, you can easily run into problems because you may have more than one object that references the same domain entity.

Data-Mapper without identity map

      $userMapper = new UserMapper($pdo);

      $user1 = $userMapper->find(1); // creates new object
      $user2 = $userMapper->find(1); // creates new object

      echo $user1->getNickname(); // joe123
      echo $user2->getNickname(); // joe123

      $user1->setNickname('bob78');

      echo $user1->getNickname(); // bob78
      echo $user2->getNickname(); // joe123 -> ?!?

Data-Mapper with identity map

The identity map solves this problem by acting as a registry for all loaded domain instances.

      $userMapper = new UserMapper($pdo);

      $user1 = $userMapper->find(1); // creates new object
      $user2 = $userMapper->find(1); // returns same object

      echo $user1->getNickname(); // joe123
      echo $user2->getNickname(); // joe123

      $user1->setNickname('bob78');

      echo $user1->getNickname(); // bob78
      echo $user2->getNickname(); // bob78 -> yes, much better

By using an identity map you can be confident that your domain entity is shared throughout your application for the duration of the request. Note that using an identity map is not the same as adding a cache layer to your mappers. Although caching is useful and encouraged, it can still produce duplicate objects for the same domain entity.

Source & UML

Take a look at the php-identity-map repository on GitHub
php-identity-map-with-one-2-many-uml

Process Workflow

Martin Fowler says: “If you load the same data more than once you’re incurring an expensive cost in remote calls. Thus, not loading the same data twice doesn’t just help correctness, but can also speed up your application. An Identity Map keeps a record of all objects that have been read from the database in a single business transaction. Whenever you want an object, you check the Identity Map first to see if you already have it.”

identity map workflow

Presentation

]]>
http://krsteski.de/php-tricks-und-tipps/objekten-abrufen-und-mehrere-instanzen-vermeiden.html/feed 11
MySQL-Functions vs. PDO vs. Doctrine2 http://krsteski.de/php-tricks-und-tipps/mysql-functions-vs-pdo-vs-doctrine2.html http://krsteski.de/php-tricks-und-tipps/mysql-functions-vs-pdo-vs-doctrine2.html#comments Mon, 26 Sep 2011 14:56:36 +0000 Gjero Krsteski http://krsteski.de/?p=637 Neulich bin ich in ein komplett neues Projekt eingestiegen. Das Team diskutierte über ein mögliches ORM Framework. Die Anforderungen an da ORM-Framework waren: „Wir möchten keine SQL-Statement mehr schreiben“, „Wir möchten nur mit Objekten arbeiten“, „Ein Scaffolding oder ein Reverse-Engineering wäre super, ist aber kein muss“ und „Wir möchten Vor- und Nachteile von Doctrine2 gegenüber MySQL-Functions und PDO haben“. Zuletzt auch noch diese: „Wenn möglich, dann soll es mit nicht relationalen Datenbanken sowie mit relationalen Datenbanken zusammen arbeiten können“.

Für dieses Mamut-Benchmarking habe ich die TestSuite von Roman Borschel [Benchmark of the major PHP ORMs] verwendet. Die TestSuite ist auch eine Art Benchmarking verschiedener ORM-Frameworks. Daraus habe ich mir jeweils das Benchmark für Doctrine2 und PDO rausgezogen und die Tests für meine Testzwecke erweitert. Anschließend habe ich ein neues Benchmark mit den nativen MySQL-Funktionen hinzugefügt. Das PDO- und das MySQL- Benchmark wurden als Active-Record implementiert.

Als Datenbank habe ich MySQL verwendet. Dort habe ich zwei Tabellen „Author“ und „Book“ mit einer 1:n Beziehung, Fremdschlüssel und Referenz-Optionen erstellt. Also ein Autor kann viele Bücher haben. Hier die Tabellendefinition und die SQL-Statements:


Author
+-----------+--------------+------+-----+---------+----------------+
| Field     | Type         | Null | Key | Default | Extra          |
+-----------+--------------+------+-----+---------+----------------+
| id        | int(11)      | NO   | PRI | NULL    | auto_increment |
| firstName | varchar(128) | NO   |     | NULL    |                |
| lastName  | varchar(128) | NO   |     | NULL    |                |
| email     | varchar(128) | YES  |     | NULL    |                |
+-----------+--------------+------+-----+---------+----------------+

Book
+-----------+---------------+------+-----+---------+----------------+
| Field     | Type          | Null | Key | Default | Extra          |
+-----------+---------------+------+-----+---------+----------------+
| id        | int(11)       | NO   | PRI | NULL    | auto_increment |
| author_id | int(11)       | YES  | MUL | NULL    |                |
| title     | varchar(255)  | NO   |     | NULL    |                |
| isbn      | varchar(24)   | NO   |     | NULL    |                |
| price     | decimal(10,0) | NO   |     | NULL    |                |
+-----------+---------------+------+-----+---------+----------------+

Hier die CREATE TABLE Statements, die von Doctrine2 generiert werden.

CREATE TABLE `Author` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `firstName` varchar(128) NOT NULL,
  `lastName` varchar(128) NOT NULL,
  `email` varchar(128) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1701 DEFAULT CHARSET=utf8;

CREATE TABLE `Book` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `author_id` int(11) DEFAULT NULL,
  `title` varchar(255) NOT NULL,
  `isbn` varchar(24) NOT NULL,
  `price` decimal(10,0) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `IDX_6BD70C0FF675F31B` (`author_id`),
  CONSTRAINT `Book_ibfk_1` FOREIGN KEY (`author_id`) REFERENCES `Author` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1701 DEFAULT CHARSET=utf8;

Danach habe ich folgende Testszenarien definiert und pro Benchmark entsprechend umgesetzt:

Test-Szenario 1:
Erstellen eines Modell-Objekts, Setzen und Speichern von Attributen. Hier wurde
die Model-Objekt-Geschwindigkeit und die INSERT-Statement-Generierung gemessen. Je 1700 Mal ausgeführt

Test-Szenario 2:
Sucht einen Eintrag anhand des Primärschlüssels. Hier wurden das grundlegende Abfrage-Prinzip und die Objekt-Hydratation gemessen. Je 1700 Mal ausgeführt.

Test-Szenario 3:
Sucht einen Datensatz mit einer komplexen Abfrage. Hier wurde die Objektabfrage-Geschwindigkeit gemessen. Je 1900 Mal ausgeführt.

Test-Szenario 4:
Liefert fünf Datensätze für ein einfaches Kriterium, etwa Book.price=???. Hier wurde die Hydratations Geschwindigkeit gemessen. Je 190 Mal ausgeführt.

Test-Szenario 5:
Liefert einen Datensatz zusammen mit dem dazugehörigen Hydrat-Datensatz aus seiner Referenz-Tabelle. Hier wurde die Geschwindigkeit beim JOIN-Abfragen gemessen. Je 700 Mal ausgeführt.

Jede Abfrage-Möglichkeit wurde isoliert durch die oben beschriebenen Test-Szenarien ausgeführt. Dabei habe ich versucht, einen Zustand wie im Echt-Betrieb zu simulieren.

Testergebnisse und harte Fakten

1 Durchlauf zum aufwärmen:

             | insert | find   | complex| hydrate| join   |  memory MB
             |--------|--------|--------|--------|--------|----------------
   MySQL	  |    471 |    374 |    228 |    292 |    275 |     0.25559375
   PDO  	  |    495 |    369 |    220 |    312 |    276 |  0.30880859375
   Doctrine2 |    293 |   1260 |    298 |   1478 |    883 |  6.73025390625
             |--------|--------|--------|--------|--------|----------------

2 Durchlauf:

             | insert | find   | complex| hydrate| join   |  memory MB
             |--------|--------|--------|--------|--------|----------------
   MySQL     |    398 |    295 |    196 |    271 |    250 |     0.25559375
   PDO       |    454 |    336 |    205 |    285 |    269 |  0.30880859375
   Doctrine2 |    271 |   1110 |    291 |   1324 |   3390 |  6.67366796875
             |--------|--------|--------|--------|--------|----------------

3 Durchlauf:

             | insert | find   | complex| hydrate| join   |  memory MB
             |--------|--------|--------|--------|--------|----------------
   MySQL     |    411 |    304 |    202 |    275 |    244 |     0.25559375
   PDO       |    441 |    333 |    208 |    284 |    260 |  0.30880859375
   Doctrine2 |    276 |   1158 |    297 |   1341 |    839 |  6.73586328125
             |--------|--------|--------|--------|--------|----------------

Hier die jeweiligen SQL-Statements in abgekürzter Form, um sehen zu können, was die obigen Test-Probanden an SQL im Hintergrund erzeugen

SQL-Statements erstellt von MySQL während des Benchmarks
SQL-Statements erstellt von PDO während des Benchmarks
SQL-Statements erstellt von Doctrine2 während des Benchmarks

Erkenntnisse

Für die unten aufgeführten Erkenntnisse habe ich mich mehr auf Doctrine2 beschränkt.

INSERT-Statements

Alle INSERT-Statements werden immer gebündelt und gemeinsam in einer Transaktion ausgeführt. Man bekommt hierfür in etwa ein Gefühl dafür, wie die Geschwindigkeit sich bei einem Massen-Import verhält

Suche anhand des Primärschlüssels

Ohne es mit einen nativen PHP-MySql Abfrage-Art zu vergleichen, möchte ich behaupten, dass Doctrin2 langsamer ist. Hier haben wir jedoch ein ORM mit viel Abstraktion. Letztendlich ging das Abfragen mit DOctrine2 ganz bequem und das erwartete Reslut-Objekt wurde ausgeliefert. Doctrine2 bietet mehrere Möglichkeiten für das Ausliefern eines Abfrage-Ergebnisses: Als Modell-Objekt, als Array-Hydrat, als Scalar-Hydrat, als Modell-Objekt mit Verwendung des internen Array-Cache, als Modell-Objekt ohne Verwendung der internen Proxy-Klassen (zuständig für das Überladen) und als Iterator-Hydrat.

Suche anhand einer komplexen Abfrage

Hierfür wurde ein Statement mit Bedingungen WHERE+OR+CONCAT kombiniert verwendet. Die Suchergebnisse wurden ebenfalls schnell ausgeliefert. Dieselbe Abfrage mitunter Verwendung des internen Array-Cache war wie erwartet – schneller.

Auslieferung von Suchergebnissen als nicht Doctrine2-Model-Objekte

Wie oben im Text beschrieben, können die Suchergebnisse als Array oder Scalar-Object ausgeliefert werden. Diese können wiederum mit Verwendung vom internen Caching optional mitArray, Memory oder APC ausgeführt werden. Zudem hat man die Möglichkeit, das Ausliefern der Suchergebnisse ohne das interne Überladen (lazy-loading) durchzuführen. Diese Möglichkeit (also lazy-loading, überschreiben) sollte man jedoch auslassen, da sich die ausgelieferten Objektmodelle instabil zu ihren Entitäten verhalten können. Nichtsdestotrotz, alle drei Kombinationen haben sich als ähnlich schnell erwiesen, nur bei den JOIN-Statements war ein kleiner, jedoch unwesentlicher, Unterschied zu vermerken.

Caching und Cache-Validation

Doctrine2 bringt mehrere Caching Mechanismen: APC, Memcache, Xcache und ArrayCache. Es bietet die gängigen Caching Operationen. Das Finden und Löschen kann nach Angabe eines Suffixes, Präfixes, Regulären-Ausdrucks und Namespaces erfolgen. Es werden also viele Möglichkeiten geboten, nach bestimmten Kriterien eine Gruppe aus dem Cache zu identifizieren und oder sie zu löschen.

Hierfür habe ich einen ganz einfachen Test ausgeführt:

1. Gib mir das Modellobjekt aus dem Repository mit der id=1
2. Speichere diese in den Cache unter der cacheId=cache_id123 für 3sek.
3. Hole das Modell-Objekt aus dem Cache und vergleiche es mit dem Model-Oobjekt aus dem Repository.
4. Halte den Prozess für vier Sekunden an und versuche, das Objekt aus dem Cache zu laden.

Hier die Ausgabe in der Konsole:

---------- Repository before Cache ----------
Attribute: 1 color blue
Attribute: 1 name dog
Content: hallo wie geht es dir?
---------- Repository == Cache -------------
bool(false)
---------- Repository ---------------------
Attribute: 1 color blue
Attribute: 1 name dog
Content: hallo wie geht es dir?
---------- Cache --------------------------
Attribute: 1 color blue
Attribute: 1 name dog
Content: hallo wie geht es dir?
---------- Sleep 4sec, get data Cache ------
bool(false)

Beide Objekte sind von derselben Instanz und haben denselben Inhalt, werden jedoch nicht als gleich identifiziert. Doctrin2 biete also das gängige Caching-Verhalten und sorgt nicht dafür, dass auch die Objekte im Cache konsistent zur Datenbank bleiben.

Weitere und detaillierte Information über den Cache gibt es hier: http://www.doctrine-project.org/docs/orm/2.0/en/reference/caching.html

Massen Inserts & Massen Object Processing

Doctrine2 unterstützt keine multi-INSERTs wie „(insert into (…) values (…), (…), (…),..“. Dafür werden die INSERT-Statements jeweils einzeln ausgeführt. Laut Doctrine2 soll das perfomanter sein. Zudem sind die Single-INSERT-Statements für Doctrine2 leichter in anderen DB-Systemen zu übertragen, als multi-INSERT-Statements. Für eine initiale Massen-Migration ist das Doctrine2 eher nicht gedacht. Jedoch bietet es eine Art von “Batch Processing”. Damit ist es möglich, die Massen-Migration von Daten in fest definierten Chargen abzuarbeiten bzw. in die Datenbank zu migrieren. Die Ausführungszeiten und der Speicherverbrauch sind mehr als akzeptabel. Wenn man oft mit sehr vielen Daten arbeiten muss, dann gibt es die Möglichkeit, sich die Daten als “Iterable-Result” ausliefern zu lassen und darüber zu iterieren. In diesem Fall ist das schneller, als wenn man sich die Daten als Hydrat-Objekt ausliefern lässt. Wem das aber immer noch nicht schnell genug ist, dem rät Doctrine2, interne Datenbank-Optionen für Massen-Importe zu verwenden. Wie bei MySQL “LOAD DATA INFILE”.

Mehr Information und einen ausführlichen Bericht hier:
-http://www.doctrine-project.org/blog/doctrine2-batch-processing
-http://www.doctrine-project.org/docs/orm/2.0/en/reference/batch-processing.html

Arbeiten mit nicht relationalen Datenbanken

Der Vorteil von NoSQL Datenbanken gegenüber MySQL oder jeder anderen RDBMS ist ganz klar die Performance. Für Doctrine2 gibt es PlungIns auf GitHub zum Herunterladen. Diese können laut Dokumentation auch alle gängigen Entitäten abbilden. Der Unterschied liegt in der Deklaration der Annotationen in den Modell-Klassen. Für ein Umsteigen von SQL auf NoSQL Datenbank ist also eine umfassende Anpassung an der Projekt-Konfiguration und der Modell-Klasse notwendig. Abfrage-Methoden wie find*(…) oder getRepository(…) müssen nicht angepasst werden.

Mehr Information:
- http://www.doctrine-project.org/docs/mongodb_odm/1.0/en/index.html
- http://www.doctrine-project.org/blog/mongodb-for-ecommerce
- http://www.doctrine-project.org/docs/couchdb_odm/1.0/en/index.html
- mongodb-odm https://github.com/doctrine/mongodb-odm
- couchdb-odm https://github.com/doctrine/couchdb-odm

Erstellen von Custom Mapping Types

In Doctrine2 ist es möglich, auch eigene Spalten-Typen zu definieren. Ich habe es mit dem Typ “email” getestet. Dafür muss im Projekt ein neues Verzeichnis “Types” erstellt werden. In der Konfigurationsdatei muss der neue Typ bekannt gemacht werden:

//...
use Doctrine\DBAL\Types\Type;
//...
Type::addType('email', 'Types\Email');
//...
$conn = $em->getConnection();
$conn->getDatabasePlatform()->registerDoctrineTypeMapping('email', 'email');

Und so sieht die Klasse des neuen email-Types aus. Eine einfache E-Mail-Validierung ist auch eingebaut.

namespace Types;
use Doctrine\DBAL\Types\ConversionException,
    Doctrine\DBAL\Types\Type,
    Doctrine\DBAL\Platforms\AbstractPlatform;
class Email extends Type
{
    const MYTYPE = 'email';
    /** @override */
    public function convertToDatabaseValue($value, \Doctrine\DBAL\Platforms\AbstractPlatform $platform)
    {
      if (false === filter_var($value, FILTER_VALIDATE_EMAIL))
      {
        throw ConversionException::conversionFailed($value, $this->getName());
      }
      return $value;
    }
    /** @override */
    public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
    {
      return $platform->getVarcharTypeDeclarationSQL($fieldDeclaration);
    }
    /** @override */
    public function getDefaultLength(AbstractPlatform $platform)
    {
      return $platform->getVarcharDefaultLength();
    }
    public function getName()
    {
        return self::MYTYPE;
    }
}

Nach dem Setup, wurde die Tabelle Author wie erartet ohne Probleme erweitert und gültige E-Mails konnten abgespeichert werden.

Alle Features in einer Tabelle

ORM Prepared Statements Transactions Caching Exceptions Mass insert Object Hydration Scaffolding Reverse Engineering Drivers
MySQL & Active Record   +   ++ +     MySQL
PDO & Active Record ++ + +- ++ + ++ +-   MySQL, Cubrid, FreeTDS, Firebird, DB2, Informix, Oracle, ODBC, PostgreSQL, SQLite, 4D, MS SQL
Doctrine2 +++ ++ +++ +++ +++ +++ ++ +++ MySQL, Cubrid, FreeTDS, Firebird, DB2, Informix, Oracle, ODBC, PostgreSQL,SQLite, 4D, MS SQL, MongoDB, CouchDB

Prepared Statements: bei der MySQL Lösung muss man leider selber Methoden schreiben, um Prepared Statements ausführen zu können. PDO und DOctrine2 bringen das von Haus aus mit.

Transactions: alle Test-Probanden unterstützen das. Wobei bei MySQL die Methoden erst geschrieben werden müssen. Lustig wird das bei Nested-Transactions. Doctrine2 kann das, für PDO und MySQL muss das implementiert werden.

Caching: bei dem MySQL- und PDO-Probanden muss das Caching implementiert werden, Doctrine2 hingegen bringt das mit von Haus aus mit.

Mass insert: das unterstützen alle drei Probanden.

Exceptions: bei MySQL muss man die Exceptions implementieren. PDO und Doctrine2 bringen diese von Haus aus mit.

Object Hydration: alle drei Probanden können ein Result-Set als Object zurückliefen. Doctrine2 kann jedoch noch mehr: als Array-Object, Iretable-Object und Model-Object.

Scaffolding: bei den MySQL- und PDO-Probanden muss das erst implementiert werden, Doctrine2 bringt das von Haus aus mit.

Reverse Engineering: nur der Doctrin2-Proband kann das. Ein paar mögliche Operationen wären: SQL-Datei in die Datenbank importieren, internes Caching Operationen, konvertieren von diversen Datenbank-Shema ins PHP, Model- und Mapper-Klassen aus der Datenbank generieren.

Drivers: bei MySQL ist es klar, nur MySQL Datenbank. PDO kann viele SQL basierende Datenbanksysteme unterstützen. Bei Doctrine2 ist es genauso wie bei PDO, da es auf PDO basisiert. Für Doctrine2 gibt es zusätzlich noch Driver als Add-ons, die MongoDB oder CouchDB unterstützen.

Gedanke

Wenn ein Team zum ersten mal mit Modellen/Objekten arbeiten möchte, dann sollte es sich zuerst mit dem Active-Record-Pattern gut vertraut machen. Ist auch leicht zum umsetzen, kontrollieren, erweitern, optimieren und debuggen. Ein typisches ORM hingegen basiert auf das Repository-Pattern. Dieses macht viel Magie unter der Haube. Ist daher nicht einfach zu kontrollieren und debuggen. Man macht sich von ein ORM abhängig. Mann muss das ORM auch nicht komplett verwenden. Es reicht oft auch, wenn man nur das manipulieren der Datenbank dem ORM überlässt. Bei diesem Punkt möchte ich es nicht verallgemeinern, den der genaue Einsatz eines ORM hängt von der Anforderung des Projektes und Teams ab.

]]>
http://krsteski.de/php-tricks-und-tipps/mysql-functions-vs-pdo-vs-doctrine2.html/feed 5
PCRE – der Stackspeicher Fresser http://krsteski.de/php-tricks-und-tipps/pcre-der-stack-speicher-fresser.html http://krsteski.de/php-tricks-und-tipps/pcre-der-stack-speicher-fresser.html#comments Thu, 28 Jul 2011 08:37:03 +0000 Gjero Krsteski http://krsteski.de/?p=628 Als erstes möchte ich euch mitteilen, dass ich mich sehr freue diesen Beitrag zu schreiben. Die letzten drei Monate war ich in Elternzeit und bin nicht zum Schreiben geschweige zum Programmieren gekommen. Es war aber eine sehr schöne Zeit für mich und die Familie. Nach dieser langen Programmier-Abstinenz wollte ich eine neue LAMP Entwicklungsumgebung Installieren. Kurz darauf habe ich einen in den Jahren gekommenen Laptop platt gemacht und eine neue Version von Ubuntu installiert. Das ging ganz schnell und einfach. Danach habe ich mittels „aptitude“, eine Erweiterung der Paketverwaltung APT welche auf allen Debian-basierten Systemen zum Einsatz komm, einen Apache2 Server, eine MySQL Datenbank, PHP, PEAR und PHPUnit installiert. Um das ganze zu testen habe ich ein im PHP5 erstelltes Projekt in der neuen LAMP Umgebung kopiert und die PHPUnit Tests gestartet. Alles lief recht schnell und ohne Probleme. Die Tests waren erfolgreich. Nun bin ich auch neugierig geworden und habe ein etwas älteres Projekt, das von PHP4 auf PHP5 umgeschrieben habe, ausprobiert. Hier handelt es sich um eine Schnittstelle die sehr viele Text-Dateien verarbeitet um den Inhalt anschließend in der Datenbank zu importiert. So, ab hier fangen die Probleme an.

Kurz nach dem ich den Test gestartet habe, meldete sich PHP mit einem PCRE Rekursionslimit-Fehler. Das Rekursionslimit sei 128 und darf nicht überschritten werden. Nach einer kurzen Recherche im Internet, habe ich erfahren, dass seit PHP5.2.0 zwei neue Einstellungen „pcre.recursion_limit“ und „pcre.backtrack_limit“ eingeführt worden sind. Diese sollen PHP vor komplexe oder fehlerhafte reguläre Ausdrücke schützen. Da sie zu viel Stackspeicher benötigen und PHP zum Absturz bringen können.

Lösungsansatz 1:
Da es sich hier um eine Einstellungen im PHP handelt, können diese direkt in der php.ini oder gleich mit ini_set() auf einen höheren Wert gesetzt werden. Die php.ini lässt man besser in Ruhe, denn der Standartwert von hunderttausend ist ganz gut gewählt. Dafür sollten man besser darauf achten, keine Stackspeicher-Fresser zu programmieren. Eventuell nach Alternativen suchen oder native PHP String-Funktionen kombinieren. Nun, zurück zum Thema. Je nach dem, ist es auch möglich den Wert auch auf zehn Millionen zu setzen, aber nicht bevor man sich mit dem Teamkollegen oder Administrator konsultiert hat. Man sollte eben nicht den ganzen Speicher für PCRE Operationen reservieren.

ini_set('pcre.recursion_limit',10000000);
ini_set('pcre.backtrack_limit',10000000);

Lösungsansatz 2:
Ich gebe es zu, der erste Lösungsansatz war nicht der beste. Wenn man die Einstellungen zur Scriptlaufzeit ändert, riskiert man dadurch einen PHP Absturz, wenn der reguläre Ausdruck doch zu viel Speicher verbraucht. Eleganter ist es, wenn man es durch eine Fehlerprüfung absichert. Ganz besonders wenn man mit komplexen regulären Ausdrücken arbeitet oder reguläre Ausdrücke auf große Datenmengen anwendet. Der Abbruch im Script passiert also wenn ein regulärer Ausdruck das gesetzte Limit überschreitet. PHP liefert keine Fehlermeldung indem Fall, und der reguläre Ausdruck liefert NULL als Ergebnis. Dabei muss man unmittelbar nach der PCRE-Funktion mittels preg_last_error() prüfen, welcher Fehler aufgetreten ist. Dabei wird ein integer-Wert zurück geliefert. Die Werte sind ebenfalls als PHP-Konstanten vertreten.

< ?php

preg_match('/(?:\D+|<\d+>)*[!?]/', '...big text....');

if (preg_last_error() == PREG_BACKTRACK_LIMIT_ERROR
	|| preg_last_error() == PREG_RECURSION_LIMIT_ERROR)
{
    throw new Exception('Regular Expression limit was exhausted!');
}

?>

Im den oberen Beispiel, wirft PHP eine Exception wenn der Speicher-Limit erreicht ist. So kann man besser den Fehler abfangen um entsprechend reagieren. Das Problem und die Folgefehler wurden somit behoben.

]]>
http://krsteski.de/php-tricks-und-tipps/pcre-der-stack-speicher-fresser.html/feed 4
Dependency-Injection ist nicht Inversion-of-Control http://krsteski.de/php-tricks-und-tipps/dependency-injection-ist-nicht-inversion-of-control.html http://krsteski.de/php-tricks-und-tipps/dependency-injection-ist-nicht-inversion-of-control.html#comments Mon, 11 Apr 2011 09:26:40 +0000 Gjero Krsteski http://krsteski.de/?p=612 Eine der Regel für gutes Software-Design besagt, dass es wichtig ist, eine feste Kopplung zwischen einzelnen Klassen Ihrer Anwendung zu vermeiden. Um dies erreichen zu können, muss man die Abhängigkeiten einer Klasse von außen in die Klasse injizieren. Hier sprechen wir also von der „Dependency-Injection“. Eine Injizierung kann ebenso über den Constructor oder über eine Setter-Methode erfolgen. Hier ein Quellcode-Beispiel.

Vorher: feste Kopplung

class Storage
{
    public function store()
    {
        // store it to a XML file.
    }

    // ....
}

class User
{
    protected $_name;

    public function save()
    {
        $storage = new Storage();
        $storage->store();

        // ....
    }
}

Nachher: lose Kopplung

interface Storage
{
    public function store();
}

class XmlStorage implements Storage
{
    public function store()
    {
        // store data to XML file.
    }

    // ....
}

class MySqlStorage implements Storage
{
    public function store()
    {
        // store data to MySQL.
    }

    // ....
}

class User
{
    protected $_name;

    public function save(Storage $storage)
    {
        $storage->store();

        // ....
    }
}

Wir haben somit eine sehr lose Kopplung zwischen den einzelnen Komponenten erreicht. Die Klasse User weiß nicht welche Implementierung sie des Storage-Interface verwendet. Sie verlässt sich vollkommen darauf, dass die gewünschte Implementierung an die Klasse übergeben wird. Man nennt es auch die Umkehrung der Abhängigkeiten oder auch das Inversion-of-Control Prinzip. Die Klasse User selbst steuert nicht mehr, wie sie an ihre Abhängigkeiten kommt, der Kontrollfluss wird von außerhalb der Klasse gesteuert.

Trotzdem darf man das Inversion-of-Cointrol Prinzip mit der Dependency-Injection nicht gleich setzen. Inversion-of-Controll ist ein Prinzip was definiert, dass der Kontrollfluss nicht bei der Hauptanwendung liegt, sondern dieser an ein Framework abgegeben wird. Das Framework kümmert sich dann, dass innerhalb des Kontrollflusses die von der Anwendung bereitgestellten Funktionen und Anhängigkeiten aufgerufen werden. Die Klasse wartet darauf, dass sie von der Applikation an- bzw. aufgerufen wird und die Abhängigkeiten übergeben bekommt.

]]>
http://krsteski.de/php-tricks-und-tipps/dependency-injection-ist-nicht-inversion-of-control.html/feed 4
Static-Variablen in Methoden http://krsteski.de/php-tricks-und-tipps/static-variablen-in-methoden.html http://krsteski.de/php-tricks-und-tipps/static-variablen-in-methoden.html#comments Thu, 10 Mar 2011 16:34:49 +0000 Gjero Krsteski http://krsteski.de/?p=593 Seit paar Tagen optimiere und refactoriere ich eine Komponente die bereits vor einem Jahr von PHP4 auf PHP5 umgestellt und refactoriert wurde. Dabei bin ich auf viele lustige Codefragmente gestoßen. Eins hat bei mir für viel Aufmerksamkeit gesorgt. Und zwar: innerhalb einer Methode wurden Variablen die nur in dieser Methode verwendet werden als „static“ Deklariert. Hmmm…, was soll das den bitte? Ist es ein Feature? Wenn ja, wo ist der Verteil? So habe ich mich auf der Suche gemacht. Hier ein Beispiel aus der Komponente:

class SelectBox
{
    public function getTimeOptions()
    {
        static $hours, $mins, $options;

        $hours = range(0, 23, 1);
        $mins  = range(0, 45, 15);

        foreach ($hours as $hour)
        {
            foreach ($mins as $min)
            {
                $hour = ($hour == 0) ? '00' : $hour;
                $hour = (mb_strlen($hour) == 1) ? '0'.$hour : $hour;
                $min = ($min == 0) ? '00' : $min;

                $options[] = $hour.':'.$min;
            }
        }

      return $options;
    }
}

In den oben gezeigtem Beispiel ist es deutlich zu sehen, dass die alte Deklaration „global“ einfach durch „static“ ersetzt worden ist. Ob an diese Stelle richtig refactoriert worden ist, sei es mal hingestellt. Die Methode „getTimeOptions“ soll ein Array zurück liefern das alle Uhrzeiten zwischen 00 Uhr und 23 Uhr in 15 Minuten Schritte beinhaltet. Also, ein Array wie das hier:

Array
(
    [0] => 00:00
    [1] => 00:15
    [2] => 00:30
    [3] => 00:45
    [4] => 01:00
    [5] => 01:15
    [6] => 01:30
    [7] => 01:45
    [8] => 02:00
    [9] => 02:15
    [10] => 02:30
    [11] => 02:45
    [12] => 03:00
    [13] => 03:15
    [14] => 03:30
    [15] => 03:45
    [16] => 04:00
    ...
    ..
    .

Nun habe ich mich genau über die „static“ Deklaration informiert und erfahren, dass auch in der Programmierung eine Variable als ein Akkumulator verwendet werden kann. Man kann sie also aufladen und entladen. Ist also besonders gut geeignet um Zwischenergebnisse, innerhalb der Laufzeit eines Prozesses abzulegen. Solche Akkumulatoren sollen auch in Schleifen innerhalb von PHP selbst verwendet werden. Hmm…, das klingt spannend! Also eine Art Cache bzw. Zwischenspeicher. Kurzerhand habe ich die Klasse umgebaut. Hier die Klasse nach dem ersten Umbau:

class SelectBox
{
    public function getTimeOptions()
    {
        static $options;

        var_dump(empty($options));

        if (true === empty($options))
        {
            $hours = range(0, 23, 1);
            $mins  = range(0, 45, 15);

            foreach ($hours as $hour)
            {
                foreach ($mins as $min)
                {
                    $hour = ($hour == 0) ? '00' : $hour;
                    $hour = (mb_strlen($hour) == 1) ? '0'.$hour : $hour;
                    $min = ($min == 0) ? '00' : $min;

                    $options[] = $hour.':'.$min;
                }
            }
        }

        return $options;
    }

}

class CheckBox
extends SelectBox
{

}

Den Aufruf „var_dump(empty($options));“ habe ich absichtlich eingebaut um die Auswirkung von unseren Akkumulator deutlicher zu machen. Zudem habe ich die Klasse mit der „CheckBox“ Klasse erweitert. Einfach für testzwecke. Ich weiß ja nicht was mich erwartet. Hier der Test:

$selectBox = new SelectBox();
print 'Instance of SelectBox'.PHP_EOL;
print 'CALL 1:';$selectBox->getTimeOptions();
print 'CALL 2:';$selectBox->getTimeOptions();
print 'CALL 3:';$selectBox->getTimeOptions();
print '----------------------------'.PHP_EOL;

$selectBox = new SelectBox();
print 'Instance of SelectBox'.PHP_EOL;
print 'CALL 1:';$selectBox->getTimeOptions();
print 'CALL 2:';$selectBox->getTimeOptions();
print 'CALL 3:';$selectBox->getTimeOptions();
print '----------------------------'.PHP_EOL;

$checkBox = new CheckBox();
print 'Instance of CheckBox'.PHP_EOL;
print 'CALL 1:';$checkBox->getTimeOptions();
print 'CALL 2:';$checkBox->getTimeOptions();
print 'CALL 3:';$checkBox->getTimeOptions();
print '----------------------------'.PHP_EOL;

Hier die Ausgabe:

Instance of SelectBox
CALL 1:bool(true)
CALL 2:bool(false)
CALL 3:bool(false)
----------------------------
Instance of SelectBox
CALL 1:bool(false)
CALL 2:bool(false)
CALL 3:bool(false)
----------------------------
Instance of CheckBox
CALL 1:bool(true)
CALL 2:bool(false)
CALL 3:bool(false)
----------------------------

Das Ergebnis ist nicht verblüffend. Beim aller ersten Aufruf der Methode wird die Berechnung der Zeit-Optionen ausgeführt und in dem Akkumulator gespeichert. Bei jedem weiterem Aufruf der Methode wird direkt auf dem Akkumulator zugegriffen. Auch wenn die Instanz von SelectBox neu erzeugt wurde. Bei der Instanz von CheckBox, die SelectBox erweitert, müssen die Zeit-Optionen wiederum neu berechnet werden. Ein Vorteil hat das nur wenn man sicher ist, dass eine Berechnung lange dauert und dasselbe Ergebnis im gesamten Prozess zurück liefert. Den Einsatz von der „static“ Deklaration betrachte ich mit gemischten Gefühlen. Einerseits hat das nichts mehr in der Objekt-Orientierten-Programmierung zu suchen, andererseits kann es doch nützlich sein, wenn man genau weiß wie es eingesetzt werden kann.

Hier habe ich ein endgültiges refactoring vollzogen. Denn mir war wichtig, dass die Methode flexibler wird, und mit anderen Uhrzeit- und Minuten-Abstände arbeiten kann.

class SelectBox
{
    public function getTimeOptions(array $hours, array $mins)
    {
        static $options;

        if (true === empty($options))
        {
            foreach ($hours as $hour)
            {
                foreach ($mins as $min)
                {
                    $hour = (mb_strlen($hour) == 1) ? '0'.$hour : $hour;
                    $min = ($min == 0) ? '00' : $min;

                    $options[] = $hour.':'.$min;
                }
            }
        }

        return $options;
    }
}

$hours = range(0, 23, 1);
$mins  = range(0, 45, 15);
$selectBox = new SelectBox();
$selectBox->getTimeOptions($hours, $mins);
]]>
http://krsteski.de/php-tricks-und-tipps/static-variablen-in-methoden.html/feed 16
strncmp() ist schneller als substr() http://krsteski.de/php-tricks-und-tipps/strncmp-ist-schneller-als-substr.html http://krsteski.de/php-tricks-und-tipps/strncmp-ist-schneller-als-substr.html#comments Wed, 23 Feb 2011 09:50:32 +0000 Gjero Krsteski http://krsteski.de/?p=573 In diversen Foren und Blogs wird schon seit 2007 geschrieben, dass strncmp() schneller als substr() sei. Und zwar, stolze 20%!!! Ist das denn so? Stimmt die Berechnung auch? Nun, wie wir alle es wissen, Codefragmente die viel mit string-Typen zu tun haben, sind nicht Ressourcen-schonend und können den gesamten Prozess sehr verlangsamen. Um das Ganze besser vergleichen zu können, habe ich diese einfache Klasse „Benchmark_Substr_vs_Strncmp“ geschrieben. Mittels der oben genannten Funktionen wird versucht aus einem Test-Array, mit zwanzig Einträgen, den Array-Key „GETME__“ zu ermitteln. Diese Suche wird mit beiden Funktionen je 10.000-mal ausgeführt. Bei jedem Testlauf wird die Dauer für die Suche in eine CSV Datei festgehalten.

< ?php
class Benchmark_Substr_vs_Strncmp
{
    const LOOP = 10000;

    protected $_resultSubstr = 0;
    protected $_resultStrncmp = 0;

    protected $_testData = array(
        'GETME__3' => 3,
        'GETME__4' => 4,
        'DONT_GETME__1' => 1,
        'DONT_GETME__2' => 2,
        'DONT_GETME__3' => 3,
        'DONT_GETME__4' => 4,
        'GETME__5' => 5,
        'GETME__6' => 6,
        'DONT_GETME__6' => 6,
        'DONT_GETME__7' => 7,
        'DONT_GETME__8' => 8,
        'GETME__7' => 7,
        'GETME__8' => 8,
        'GETME__9' => 9,
        'DONT_GETME__5' => 5,
        'DONT_GETME__9' => 9,
        'GETME__1' => 1,
        'GETME__2' => 2,
		'GETME__1234' => 1,
        'GETME__2234' => 2,
    );

    protected function _substr_data()
    {
        $out = array();

        foreach ($this->_testData as $key => $val)
        {
            if (substr($key, 0, 7) == 'GETME__')
            {
                $out[$key] = $val;
            }
        }
    }

    protected function _strncmp_data()
    {
        $out = array();

        foreach ($this->_testData as $key => $val)
        {
            if (!strncmp($key, 'GETME__', 7))
            {
                $out[$key] = $val;
            }
        }
    }

    public function runBenchmarkOfStrncmp()
    {
        $beginn = microtime(true);

        $i = 0;

        while($i < self::LOOP)
        {
            $this->_strncmp_data();

            ++$i;
        }

        $this->_resultStrncmp = sprintf('%.3f', microtime(true) - $beginn);
    }

    public function runBenchmarkOfSubstr()
    {
        $beginn = microtime(true);

        $i = 0;

        while($i < self::LOOP)
        {
            $this->_substr_data();

            ++$i;
        }

        $this->_resultSubstr = sprintf('%.3f', microtime(true) - $beginn);
    }

    public function saveData()
    {
        $fp = fopen(dirname(__FILE__).'benchmark_substr_vs_strncmp.csv', 'a');

        fputcsv($fp, array($this->_resultSubstr, $this->_resultStrncmp), ';');
        fclose($fp);

        $this->_resultSubstr = 0;
        $this->_resultStrncmp = 0;

        print '.';
    }
}

Hier ein Testszenario:

$benchmark = new Benchmark_Substr_vs_Strncmp();
$benchmark->runBenchmarkOfSubstr();
$benchmark->runBenchmarkOfStrncmp();
$benchmark->saveData();

Für den Test habe ich auf das System dafür gesorgt, dass dieser Test die einzige Klasse im System ist die was mit PHP macht. Hier das Ergebnis nach zwanzig Testszenarien.

Substr	Strncmp		Differenz in Sek.	Differenz in %
--------------------------------------------------------------
0,227	0,2		0,027				11,89
0,222	0,199		0,023				10,36
0,216	0,198		0,018				8,33
0,217	0,199		0,018				8,29
0,232	0,213		0,019				8,19
0,217	0,201		0,016				7,37
0,223	0,207		0,016				7,17
0,213	0,199		0,014				6,57
0,21	0,198		0,012				5,71
0,215	0,204		0,011				5,12
0,207	0,198		0,009				4,35
0,205	0,198		0,007				3,41
0,207	0,202		0,005				2,42
0,2	0,198		0,002				1,00
0,209	0,207		0,002				0,96
0,199	0,198		0,001				0,50
0,202	0,201		0,001				0,50
0,206	0,205		0,001				0,49
0,199	0,199		0				0,00

Der Aufruf von substr() ist zwar relativ schnell, aber die widerholende Ausführung summiert sich. Ich habe bei meiner Berechnung die besagten 20% nicht erreicht, jedoch stolze 5%. Liegt es eventuell daran dass PHP5 schneller als PHP4 ist? Wie auch immer, ich bin mir sicher, dass man gut behaupten darf, dass schlecht implementierte Zeichenkettenanalysen in umfassenden Anwendungen oder Frameworks einen großen Teil der Zeit auf diese Funktion verwenden. Die strncmp() ist nur dann ein natürlicher Ersatz für substr(), wenn es um Zeichenvergleich am Anfang von Zeichenketten, handelt.

Die substr() Funktion muss erst ihren Rückgabewert zuweisen und schreiben, und dann einen Vergleich durchführen, während strncmp() nur Zeichen für Zeichen vergleicht. Wie die Speicherverwaltung bei PHP im Einzelnen funktioniert, das bleibt uns verborgen, dennoch werden wir die Zeit für die Zuweisung immer deutlich spüren.

Frage: Im Zend-Framework wird substr() alleine 209-mal verwendet. Der Großteil davon wird verwendet um Zeichenketten am Anfang der Zeichenkette zu vergleichen. Und strncmp(), hingegen wird nur 2-mal verwendet. Was meint Ihr, würde ein Umbau auf strncmp(), eine Anwendung die komplett auf Zend-Framework aufbaut, beschleunigen?

]]>
http://krsteski.de/php-tricks-und-tipps/strncmp-ist-schneller-als-substr.html/feed 5
Ein Prozess im Unternehmen implementieren http://krsteski.de/projektmanagement/ein-prozess-im-unternehmen-implementieren.html http://krsteski.de/projektmanagement/ein-prozess-im-unternehmen-implementieren.html#comments Fri, 04 Feb 2011 10:44:37 +0000 Gjero Krsteski http://krsteski.de/?p=561 In unseren Berufsalttag entwickeln wir eine Software. Wir erzeugen kein Gegenstand, sondern verrichten eine Tätigkeit, also ein Prozess, bei dem was hergestellt wird. Dabei ist es sehr wichtig sich auf dem Prozess und auf das Produkt zu konzentrieren. Kein Kunde wird sich freuen wenn sein Produkt nicht genau das leistet was er erwartet hat, oder wenn er das Produkt nicht rechtzeitig ausgeliefert bekommt.Erfreulicherweise haben sich viele kluge Köpfe, schon vor langer Zeit, darüber Gedanke gemacht, wie man Software mit guter Qualität herstellt, und diese Erkenntnisse in sogenannte Methodologien festgehalten.

In der Realität schaut das anders aus, und zwar – die meisten Entwicklungsteams kommen kaum auf diese Erkenntnisse und Informationen. Für die meisten Teams ist ein Prozess ein banaler Gedanke, oder etwas was vom Management oder Vorstand aufgedrückt wird. Unter einer Methodologie stellen sich die meisten lange Konferenzen und viel Papier-Kram vor. Oft werden Methodologien von Managern aufgesetzt und Manager müssen oft einen Prozess verfolgen. Dabei handelt es sich um Prozesse die in den 80er definiert worden sind und in der heutigen Zeit nicht mehr passen. Diese alten Prozesse werden einfach neu aufbereitet, werden mit diversen Mode-Wörtern wie „agil“ aufgepeppt und an das Team aufgedrückt. Das lustige daran ist, dass neu angehende Manager die aus dem Team stammen, oft diese Prozesse einfach übernehmen und das ohne sie zu hinterfragen.

Doch es gibt bessere Wege für Teams gute Software zu entwickeln. Man arbeitet in Team vermutlich als Software-Designer, Programmierer oder Tester und man denkt, dass der Entwicklungsprozess nicht in seiner Verantwortung liegt. Das stimmt auch – für den Prozess ist leider niemand zuständig. Soll aber ein neues Entwicklungsprozess im Unternehmen erfolgreich implementiert werden, dann kann das nur der implementieren der ihn auch anwendet und versteht – also der Programmierer.

Nun, stellt sich die Frage: Wie spürt man die Verantwortung über einen Prozess? Die Antwort ist: Man hilft bei der Implementierung diesen Prozesses in eigenen Unternehmen bzw. Team. Wenn das Unternehmen keinen klaren Prozess anwendet, dann findet man heraus welche Methodologien für das Unternehmen passen würden. Also: SCRUM, V-Modell, Testgetriebenes-Entwickeln oder sonst was. Dafür diskutiert man am besten mit dem Team über die aktuellen Entwicklungsprobleme und Möglichkeiten, sie durch Übernahme eines Standartprozesses zu beseitigen. Danach erstellt man alleine oder besser mit dem Management einen Plan auf, um den gewählten Prozess in Unternehmen einzuführen. Dafür ist wichtig, dass man von jedem Teammitglied die Bereitschaft zugesichert bekommt, bei der Umsetzung des Plans, mitzumachen.

Es steht eine Vielzahl an Methodologien zur Verfügung. Trotzdem wird man nie ein Unternehmen vorfinden, das diese vollständig implementiert hat. Ja, das ist vollkommen in Ordnung! Der beste Prozess ist immer der, der das Team am produktivsten macht und die beste Software ausliefert. Es ist also möglich zwei Prozess-Typen zu finden. Dabei ist es wichtig, einzelne Komponenten auszuwählen, die in Team sinnvoll zu sein erscheinen, und fortlaufend anhand der Erfahrung zu verfeinern.

Letztendlich kann man, wen man den Prozess nicht beherrscht, das Produkt nicht herstellen. Kenntnisse über den Software-Entwicklungsprozess sind in diesem Fall nützlich. Denn, es ist viel einfacher jemanden zu finden der eine Software erstellen und zu laufen bringen kann, als jemanden zu finden, der den Prozess der Erstellung von Software erfolgreich im Unternehmen implementieren kann.

]]>
http://krsteski.de/projektmanagement/ein-prozess-im-unternehmen-implementieren.html/feed 1
cURL vs. Fopen vs. ZF http://krsteski.de/php-tricks-und-tipps/curl-vs-fopen-vs-zf.html http://krsteski.de/php-tricks-und-tipps/curl-vs-fopen-vs-zf.html#comments Thu, 27 Jan 2011 08:54:13 +0000 Gjero Krsteski http://krsteski.de/?p=552 In Moment ist ein sehr gefährlicher Infekt in der PHP-Szene namens “ PhpBenchmarkItis” in Umlauf. Wenn es erwischt hat, derjenige ist gezwungen Dinge in PHP miteinander auf Leistung und Schnelligkeit gegeneinander zu prüfen. Mich hat die “ PhpBenchmarkItis” nun auch erwischt. Ich musste unbedingt erfahren welche Methode von PHP und ZF eine schnellere Verbindung zu einem Server aufbaut und dessen Antwort bzw. Ergebnis ausliefert. Hintergrund des Ganzen ist, eine Klasse zu erstellen die auf dem schnellsten Wege verschiedene RSS-Feeds auslesen und ausliefern kann.

Die Testumgebung
Server = Debian 5.0.4
Processors = Intel(R) Xeon(R) CPU E5335 @ 2.00GHz
PHP Version = 5.3.3-0.dotdeb.1
allow_url_fopen = On
cURL Version = 7.18.2
ZendFramework Version = 1.10.0

Das Test-Scenario
Als Testumgebung dient PHPUnit, da jeder Test isoliert ausgeführt werden kann. Jede Methode ruft den RSS-Feed (http://krsteski.de/feed/rss) auf dieses Blog 50-mal auf und liefert ebenso 50-mal den Inhalt zurück. Gut, dieser Blog ist eine WordPress Instanz und braucht etwas zu antworten, jedoch alles in akzeptablen Zeitrahmen. Getestet werden hier: file_get_contents, fopen, Zend_Http_Client, Zend_Feed_Reader und cURL.

< ?php
class Feed_PhpBenchmarkItis extends PHPUnit_Framework_TestCase
{
    const LOOP = 50;
    const FEED = 'http://krsteski.de/feed/rss';

    public function testZendFeed()
    {
        $beginn = microtime(true);

        $i = 0;
        while($i < self::LOOP)
        {
            $feed = Zend_Feed_Reader::importFile(self::FEED);
            $feed->getFeedLink();

            ++$i;
        }

        $dauer = sprintf('%.3f', microtime(true) - $beginn);

        echo self::LOOP." ZENDFEED Anfragen in: ".
                $dauer." Sek. durchschnittlich ".
                ($dauer / self::LOOP)." Sek. pro Anfrage".PHP_EOL;
    }

    public function testFILE_GET_CONTENTS()
    {
        $beginn = microtime(true);

        $i = 0;
        while($i < self::LOOP)
        {
            $contents = file_get_contents(self::FEED);

            ++$i;
        }

        $dauer = sprintf('%.3f', microtime(true) - $beginn);

        echo self::LOOP." FILE_GET_CONTENTS Anfragen in: ".
                $dauer." Sek. durchschnittlich ".
                ($dauer / self::LOOP)." Sek. pro Anfrage".PHP_EOL;

    }

    public function testFOPEN ()
    {
        $beginn = microtime(true);

        $i = 0;
        while($i < self::LOOP)
        {
            $handle = fopen(self::FEED, "r");
            $contents = stream_get_contents($handle, -1);
            fclose($handle);

            ++$i;
        }

        $dauer = sprintf('%.3f', microtime(true) - $beginn);

        echo self::LOOP." FOPEN Anfragen in: ".
                $dauer." Sek. durchschnittlich ".
                ($dauer / self::LOOP)." Sek. pro Anfrage".PHP_EOL;

    }

    public function testCURL()
    {
        $beginn = microtime(true);

        $i = 0;
        while($i < self::LOOP)
        {
            $handle = curl_init(self::FEED);
            curl_setopt($handle, CURLOPT_HEADER, false);
            curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
            $contents = curl_exec($handle);
            curl_close($handle);

            ++$i;
        }

        $dauer = sprintf('%.3f', microtime(true) - $beginn);

        echo self::LOOP." CURL Anfragen in: ".
            $dauer." Sek. durchschnittlich ".
            ($dauer / self::LOOP)." Sek. pro Anfrage".PHP_EOL;
    }

    public function testZendHttpClient()
    {
        $beginn = microtime(true);

        $i = 0;
        while($i < self::LOOP)
        {
            $urlClient = new Zend_Http_Client(self::FEED);
            $contents = $urlClient->request()->getRawBody();

            ++$i;
        }

        $dauer = sprintf('%.3f', microtime(true) - $beginn);

        echo self::LOOP." ZEND_HTTP_CLIENT Anfragen in: ".
            $dauer." Sek. durchschnittlich ".
            ($dauer / self::LOOP)." Sek. pro Anfrage".PHP_EOL;
    }
}

Das Testergebnis

50 ZENDFEED Anfragen in: 4.007 Sek. durchschnittlich 0.08014 Sek. pro Anfrage
50 FILE_GET_CONTENTS Anfragen in: 4.016 Sek. durchschnittlich 0.08032 Sek. pro Anfrage
50 FOPEN Anfragen in: 6.295 Sek. durchschnittlich 0.1259 Sek. pro Anfrage
50 CURL Anfragen in: 3.564 Sek. durchschnittlich 0.07128 Sek. pro Anfrage
50 ZEND_HTTP_CLIENT Anfragen in: 3.999 Sek. durchschnittlich 0.07998 Sek. pro Anfrage
Time: 23 seconds, Memory: 9.75Mb

Wie erwartet ist hier der Aufruf mit cURL schneller. Bei cURL habe ich jedoch das Problem, dass ich hier nicht genau weiß ob es sich um ein valides RSS-Feed handelt. Zudem muss ich das Ergebnis das cURL zurück liefert, irgendwie parsen um es weiter verarbeiten zu können. Diese Logik muss also zusätzlich implementiert werden. Ich habe mich für Zend_Feed_Reader entschieden und verzichte gerne auf bisschen Zeit und bekomme dafür mehr Sicherheit. Zudem der Zend_Feed_Reader kann das RSS-Feed validieren, löst entsprechenden Exceptions falls es irgendwelche Probleme mit dem RSS-Feed gibt und liefert das Ergebnis als eine DOMDocument Instanz zurück. Und das ist ja spitze! Anhand der DOMDocument Instanz kann ich nun ganz bequem über den Datenbestand iterieren.

]]>
http://krsteski.de/php-tricks-und-tipps/curl-vs-fopen-vs-zf.html/feed 10