Objekte abrufen und mehrere Instanzen vermeiden

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

15 thoughts on “Objekte abrufen und mehrere Instanzen vermeiden

  1. Versteh ich das nun richtig?
    In der Methode find() des userMappers passiert nicht anderes als zB in einem static array zu schauen ob es das Objekt schon gibt, wenn ja zurück liefert, wenn nicht ein neues Objekt [$user = new user(1)] erzeugt, es in einem (zB) array speichert und dann zurück gibt?

    In wie weit unterscheidet sich dies denn vom (Anti-) Pattern Singleton?

    LG,
    Rook

  2. Hm, also ein Singleton ohne statisches getInstance()?
    Ich bin irgendwie mäßig beeindruckt, aber möglicherweise übersehe ich ja auch den Witz an der Sache?

  3. @all:
    Ich glaube nicht, dass der Ziel dieses Artikels ist, jemanden schwer zu beeindrucken. Hier geht es um das Identity Map Pattern. Für Proga die viel mit Active Record Pattern arbeiten ist es wichtig auch eine Identity Map als eine Zwischen Schicht zu der Persistenz Schicht zu verwenden. Das Identity Map macht eine Applikation schneller. Wieso das? Ja, eine Applikation die nur auf Active Record basiert, macht jedes mal eine neue Datenbankabfrage wenn mehrere Mapper-Objekte von der selben Instanzen innerhalb eines Requests erzeugt werden sollen. Das Identity Map hingegen sorgt dafür, das die Instanz nur einmal aus der Datenbank abgerufen wird. http://martinfowler.com/eaaCatalog/identityMap.html

  4. Durch dieses Pattern hat man auch den Vorteil, dass wenn Komponente A ein Objekt mit Informationen anreichert oder manipuliert, kann Komponente B genau auf diesen neuen Status zugreifen. Das kann Komponente B indem sie einfach auf den Pool zugreift und sich das Objekt holt. Man muss nix umständlich durch die Gegend schleppen.

    Und der Unterschied zu einer statischen Instanz ist ganz einfach der, dass die Instanzen nicht global gültig sind. Sprich, wenn du aus irgendeinem Grund die originalen Objekte bräuchtest, könntest du dir einen zweiten Pool intanziieren und dieser hat wieder seine eigene IdentityMap mit eigenen Model-Instanzen.

    Zudem natürlich der schon erwähnte Performance-Boost. Ich habe in einem Projekt Models die nicht eine einzige Datenbankzeile repräsentieren, sondern aus mehreren Selects –die auch teilweise mehrere Hundert Zeilen zurück liefern– zusammengebaut werden. Wenn dies jedes Mal geschehen würde, wenn ich find() aufrufe, würde das ziemlich an der Performance ziehen.
    An dieser Stelle habe ich dann sogar noch einen Cache eingebunden. Der Mapper schaut also in die IdentityMap, ist das Objekt dort nicht zu finden, versucht er es im Cache und wenn da auch nix ist, macht er die Datenbankrequests.

  5. Benötigt man hierfür nicht Container mit schwachen Referenzen, so dass Entities freigegeben werden können, wenn der Benutzercode bestimmte Entities nicht mehr referenzieren sollte?

    1. @maz: Ich verstehe deine Frage nicht ganz? Meinst du Entities zwischen den Klassen oder der Datenbank-Tabellen? p.s. Ich habe das Beispiel um die Tabelle „Article“ erweitert um eine Implementierung einer One-2-Many Beziehung zwischen zwei Datenbank-Tabellen zu zeigen. Also ein „User“ kann viele „Article“ haben. Wenn ein „User“ gelöscht wird, dann sollen alle seine „Article“ ebenfalls gelöscht werden.

  6. Hallo,
    wie kann ich die Mapper auch in einer großen Klasse ablegen und auf diese wieder zugreifen?

    Ich bräuchte sowas
    $userMapper1 = new UserMapper($pdo);
    $userMapper2 = new UserMapper($pdo);
    „$userMapper1“ ist gleich „$userMapper2“

    $pdo müsste da eigentlich auch nur einmal übergeben werden, oder noch besser in eine Extended Klasse auslagern.
    Hoffe ihr könnt mir helfen.

    1. @Gast: habe mal kurzerhand eine Repository Klasse für die DataMapper implementiert. Nun, sollte die Repository Klasse als zentraler Manager für das laden der DataMapper verwendet werden. Hier ein Beispiel:

      $repository = new Repository($this->db);
      $userMapper = $repository->load('User');
      $insertId = $userMapper->insert(new User('billy', 'gatter'));

  7. Guten Morgen,

    vielen Dank für die ID-Map. Sehr inspirierend und gleichzeitig allumfassend.
    Ich habe dennoch mal eine Frage zur Methode UserMapper::delete().
    Ist es richtig, dass es beabsichtigt ist, dass zuerst das Objekt aus der Datenbank geholt wird und dann erst gelöscht wird (also 2 Datenbankzugriffe)?
    Wenn ich es sehe, dann wird der folgende Aufruf gemacht:

    $userMapper = new UserMapper($pdo);
    $user1 = $userMapper->find(1);
    $userMapper->delete($user1);

    Würde es nicht Sinn machen, dass gleich in delete() die ID, wie in find() übergeben wird, um den Datensatz gleich aus der DB zu löschen?

    1. Hallo M. Fische, bei einer Identity-Map handelt es sich um eine Identitäts-Liste eines Objektes innerhalb eines Requests. Daher wird beim löschen das Objekt als solches übergeben. Existiert das Objekt in der Identity-Map, dann kann es aus der Datenbank gelöscht werden. Das schont die Datenbank-Operationen nur innerhalb eines Requests.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.