Matthieu Napoli
Matthieu Napoli

Reputation: 49533

A PHP design pattern for the model part [PHP Zend Framework]

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

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 ?

Upvotes: 0

Views: 1236

Answers (3)

Fezan Qadir Bhatti
Fezan Qadir Bhatti

Reputation: 1

Multiton

Best option if you want to use a variety of singletons in your project.

<?php
abstract class FactoryAbstract {
    protected static $instances = array();
    public static function getInstance() {
        $className = static::getClassName();
        if (!(self::$instances[$className] instanceof $className)) {
            self::$instances[$className] = new $className();
        }
        return self::$instances[$className];
    }
    public static function removeInstance() {
        $className = static::getClassName();
        if (array_key_exists($className, self::$instances)) {
            unset(self::$instances[$className]);
        }
    }
    final protected static function getClassName() {
        return get_called_class();
    }
    protected function __construct() { }
    final protected function __clone() { }
}
abstract class Factory extends FactoryAbstract {
    final public static function getInstance() {
        return parent::getInstance();
    }
    final public static function removeInstance() {
        parent::removeInstance();
    }
}
// using:
class FirstProduct extends Factory {
    public $a = [];
}
class SecondProduct extends FirstProduct {
}
FirstProduct::getInstance()->a[] = 1;
SecondProduct::getInstance()->a[] = 2;
FirstProduct::getInstance()->a[] = 3;
SecondProduct::getInstance()->a[] = 4;
print_r(FirstProduct::getInstance()->a);
// array(1, 3)
print_r(SecondProduct::getInstance()->a);
// array(2, 4)

Upvotes: 0

troelskn
troelskn

Reputation: 117427

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.

Upvotes: 1

jholster
jholster

Reputation: 5136

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.

Upvotes: 0

Related Questions