The Pope Is Dead ::

Main Navigation

The pope is dead. Long live the pope! Just another blog about web development. If you’re interested in web stuff you may find this blog useful.

blog latest entries

Zend Entity Notes On Rich Domain Objects

I have been testing out Zend_Entity and was wondering how to inject dependencies into the Domain Objects it creates, after some reading I finally came up with the following prototype. Any suggestions on a better solution are welcome :)

The Problem

Ok so the problem is that a Domain Object has a dependency on another Domain Object (Not one produced by Zend_Entity, those dependencies are already handled) or Service, Zend_Entity should not know about the Dependencies as it is not a DI Container. So how can we get these dependencies into the Domain Object?

Why Solve it?

Well if we can not have other dependencies to our Domain Objects we are going to end up using our Domain Objects like simple DAO's which they are not, this will walk us into the Anemic Domain Model

Example Domain Object
© 2010 Keith Pope
  1. class My_Model_Product
  2. {
  3.    public $id;
  4.    public $name;
  5.  
  6.    protected $_mail;
  7.  
  8.    public function getState()
  9.    {
  10.        return array(
  11.            'id' => $this->id,
  12.            'name' => $this->name,
  13.        );
  14.    }
  15.  
  16.    public function setState(array $state)
  17.    {
  18.        foreach($state AS $k => $v) {
  19.            $this->$k = $v;
  20.        }
  21.    }
  22.  
  23.    public function setMail($m)
  24.    {
  25.        $this->_mail = $m;
  26.    }
  27. }
Parsed in 0.024 seconds, using GeSHi 1.0.7.21

We can see that our Domain Object needs to use the Mail Service, so we need to fulfill this dependency.

A possible solution

Ok here's my prototype :)

Changes to Yadif

I am using Yadif (http://github.com/beberlei/yadif) for Dependency Injection, but I need to add the ability to inject into an already created instance.

© 2010 Keith Pope
  1. /**
  2.      * Injects dependents into an instance
  3.      *
  4.      * @param mixed $instance
  5.      * @return mixed
  6.      */
  7.     public function inject($instance)
  8.     {
  9.         if (!is_object($instance)) {
  10.             throw new Yadif_Exception("Must be a class instance");
  11.         }
  12.  
  13.         $name = strtolower(get_class($instance));
  14.  
  15.         if(!array_key_exists($name, $this->_container)) {
  16.             throw new Yadif_Exception("Component '".$name."' does not exist in container.");
  17.         }
  18.  
  19.         $component = $this->_container[$name];
  20.         $scope = $component[self::CONFIG_SCOPE];
  21.  
  22.         $componentReflection = new ReflectionClass($component[ self::CONFIG_CLASS ]);
  23.  
  24.         $constructorArguments = $component[self::CONFIG_ARGUMENTS];
  25.         $setterMethods        = $component[self::CONFIG_METHODS];
  26.  
  27.         if(!empty($constructorArguments)) {
  28.             throw new Yadif_Exception("Component '".$name."' has constructor injector and instance is already created.");
  29.         }
  30.  
  31.         foreach ($setterMethods as $method) {
  32.             $methodName = $method[self::CONFIG_METHOD];
  33.             $params = $method[self::CONFIG_PARAMETERS];
  34.  
  35.             $injection = array();
  36.             if(isset($method[self::CONFIG_ARGUMENTS])) {
  37.                 $argsName = $method[self::CONFIG_ARGUMENTS];
  38.                 $injection = $this->injectParameters($argsName, null, $params);
  39.             }
  40.  
  41.             if ($componentReflection->getMethod($methodName)->isConstructor()) {
  42.                 throw new Yadif_Exception("Cannot use constructor in 'methods' setter injection list. Use 'arguments' key instead.");
  43.             } else {
  44.                 call_user_func_array(array($instance, $methodName), $injection);
  45.             }
  46.         }
  47.  
  48.         return $instance;
  49.     }
Parsed in 0.072 seconds, using GeSHi 1.0.7.21

So this is a very rough addition to Yadif and simply uses the current container implementation to inject dependents via setters, obviously construct injection is out of the question here.

Zend_Entity Event

Next we need to add a new event into Zend_Entity.

© 2010 Keith Pope
  1. class My_Entity_Event_Test extends Zend_Entity_Event_EventAbstract
  2. {
  3.     public function postLoad($entity)
  4.     {
  5.         $f = Zend_Controller_Front::getInstance();
  6.         $c = $f->getParam('bootstrap')->getContainer();
  7.  
  8.         $name = get_class($entity);
  9.         if (isset($c->$name)) {
  10.             $c->inject($entity);
  11.         }
  12.     }
  13. }
Parsed in 0.017 seconds, using GeSHi 1.0.7.21

This class is then registered to Zend_Entity and postLoad is called every time an entity is created.

Here we get the container from the bootsrap and then use the new inject method to populate the entities dependencies. This is a pretty lazy way to do this, it's probably better to inject the container.

Example Object Wiring
© 2010 Keith Pope
  1. class My_Objects extends Yadif_Module
  2. {
  3.     protected function configure()
  4.     {
  5.         $this->bind("My_Model_Bug")->to("My_Model_Bug");
  6.         $this->bind("My_Model_Product")
  7.             ->to("My_Model_Product")
  8.             ->method('setMail')->args('My_Service_Mail');
  9.         $this->bind("My_Service_Mail");
  10.     }
  11. }
Parsed in 0.021 seconds, using GeSHi 1.0.7.21

So here we have our object wiring where we setup the dependencies.

Example usage

Finally here is an example usage:

© 2010 Keith Pope
  1. $sqlQueryBuilder = new Zend_Db_Mapper_SqlQueryBuilder($c->Max_Entity_Manager);
  2. $sqlQueryBuilder->fromEntity("My_Model_Bug")
  3.                 ->where("assigned_to = ?", (int)1)
  4.                 ->order("bug_created DESC");
  5.  
  6. $bugs = $sqlQueryBuilder->getResultList();
  7.  
  8. foreach($bugs AS $bug) {
  9.     Zend_Debug::dump($bug->getProducts());
  10.     foreach($bug->getProducts() as $product) {
  11.         Zend_Debug::dump($product);
  12.     }
  13. }
Parsed in 0.020 seconds, using GeSHi 1.0.7.21
Conclusion

Well this has been a very quick blog post but I hope these notes either spark debate or help someone, any suggestions are welcome.

I am still working on my Dependency Injection article, hopefully have that done soon ish and hopefully i will get some time to cover more on Zend_Entity...

Posted on 28/10/2009 at 09:46 PM

about me

I am a web developer/ project manager from Birmingham Uk, currently I am working for inflight productions as a technical project manager. Hopefully I will be posting here as regularly as possible on my experiences with various technologies that I am using and maybe the odd random thing..

the book