views:

242

answers:

2

I have a PHP MVC application using Zend Framework. As presented in the quickstart, I use 3 layers for the model part :

  • Model (business logic)
  • Data mapper
  • Table data gateway (or data access object, i.e. one class per SQL table)

The model is UML designed and totally independent of the DB.

My problem is : I can't have multiple instances of the same "instance/record".

For example : if I get, for example, the user "Chuck Norris" with id=5, this will create a new model instance wich members will be filled by the data mapper (the data mapper query the table data gateway that query the DB). Then, if I change the name to "Duck Norras", don't save it in DB right away, and re-load the same user in another variable, I have "synchronisation" problems... (different instances for the same "record")

Right now, I use the Multiton / Identity Map pattern : like Singleton, but multiple instances indexed by a key (wich is the user ID in our example). But this is complicating my developpement a lot, and my testings too.

How to do it right ?

+1  A: 

Keep all loaded model instances in "live model pool". When you load/query a model, first check if it has been already loaded into pool (use primary key or similar concept). If so, return the object (or a reference) from pool. This way all your references point to the same object. My terminology may be incorrect but hopefully you get the idea. Basically the pool acts as a cache between business logic and database.

jholster
Yes I get the idea, this is exactly what I've been doing. But the problem is when you want, for example, the list of all users, you can't use the "pool", so you have to do a SQL query, but then you will have multiple instances for the same user. The solution would be to always check the pool, but this is a lot of work, not practical... And you have to always keep that in mind or you'll have a debugging nightmare
Matthieu
So basically we are talking about advantages / disadvantes of an ORM. It's not necessary a debugging nightmare if implemented properly, but there will always be a performance hit. The pool checking part is of course done transparently in your ORM layer, so you don't have to worry about that in the business logic layer. A related [question][1]. [1]: http://stackoverflow.com/questions/108699/good-php-orm-library
jholster
Your link directed me to Xyster (http://xyster.libreworks.net/documentation/guide/xyster.orm.relation.html), wich is EXACTLY what I am trying to develop, but, of course, in better. THANK YOU
Matthieu
I'm glad to hear that. Personally I've been searching for the Holy Grail of PHP ORM for ages and been developing few ones, but still haven't found it ;)
jholster
Well after looking at Xyster, it seems to be a "one-man" project. But I read that Doctrine 2 will do exactly that, so i'll wait...
Matthieu
+1  A: 

Identity Map

Edit

In response to this comment:

If I have a "select * from X", how can I skip getting the already loaded records ?

You can't in the query itself, but you can in the logic that loads the rows into entity objects. In pseudo-code:

class Person {}

class PersonMapper {
  protected $identity_map = array();
  function load($row) {
    if (!isset($this->identity_map[$row['id']])) {
      $person = new Person();
      foreach ($row as $key => $value) {
        $person->$key = $value;
      }
      $this->identity_map[$row['id']] = $person;
    }
    return $this->identity_map[$row['id']];
  }
}

class MappingIterator {
  function __construct($resultset, $mapper) {
    $this->resultset = $resultset;
    $this->mapper = $mapper;
  }
  function next() {
    $row = next($this->resultset);
    if ($row) {
      return $this->mapper->load($row);
    }
  }
}

In practice, you'd probably want your MappingIterator to implement Iterator, but I skipped it for brevity.

troelskn
this is what I use actually
Matthieu
... and the problem is when I need to execute specific queries (select where ...), I have to bypass the Identity map wich can cause multiple instance for the same "identity"
Matthieu
Sounds like you're implementing it in an odd way then. If you use the identity map in the hydration phase, you can just skip those records that are already in the map.
troelskn
If I have a "select * from X", how can I skip getting the already loaded records ?
Matthieu