Zend Framework 2 : Using Doctrine Extension with DoctrineModule and DoctrineORMModule
In ZF2, by using DoctrineModule, we can manage the extension that implements
Doctrine\Common\EventSubscriber
in configuration. For example, we have TablePrefix
subcsriber to set table prefix query result like the following :
namespace Application\DoctrineExtension; use Doctrine\ORM\Event\LoadClassMetadataEventArgs; use Doctrine\Common\EventSubscriber; class TablePrefix implements EventSubscriber { protected $prefix = ''; public function __construct($prefix) { $this->prefix = (string) $prefix; } public function getSubscribedEvents() { return ['loadClassMetadata']; } public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs) { $classMetadata = $eventArgs->getClassMetadata(); $classMetadata->setTableName($this->prefix . $classMetadata->getTableName()); foreach ($classMetadata->getAssociationMappings() as $fieldName => $mapping) { if ($mapping['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_MANY) { $mappedTableName = $classMetadata->associationMappings[$fieldName]['joinTable']['name']; $classMetadata->associationMappings[$fieldName]['joinTable']['name'] = $this->prefix . $mappedTableName; } } } }
Because above class need a dependency at __construct part, we need to build the instance via service manager factory :
use Application\DoctrineExtension\TablePrefix; return [ 'service_manager' => [ 'factories' => [ 'tablePrefix' => function($sm) { // for cache-able config, use factory instead! return new TablePrefix('app.'); }, ], ], //... ];
And now, we can register to the ‘subscribers’ key under ‘orm_default’ ( if we use ‘orm_default’ as connection ).
return [ // ... 'doctrine' => [ // ... common doctrine driver config here 'eventmanager' => [ 'orm_default' => [ 'subscribers' => [ 'tablePrefixService', ], ], ], // ... ], //... ];
And when we call it :
$albumQb = $this->getServiceLocator() ->get('Doctrine\ORM\EntityManager')->getRepository('Application\Entity\Album') ->createQueryBuilder('a'); echo $albumQb->getQuery()->getSql();
It will produce :
SELECT a0_.id AS id0, a0_.artist AS artist1, a0_.title AS title2 FROM app.album a0_
Now… What if the Extension doesn’t extends the Doctrine\Common\EventSubscriber
?
We need to register it in the EventManager of Doctrine Connection as new event listener with utilize DoctrineORMModule, so we have to override the DoctrineORMModule\Service\EntityManagerFactory
:
namespace Application\Factory\Service; use DoctrineORMModule\Service\EntityManagerFactory as BaseEntityManagerFactory; use Doctrine\ORM\EntityManager; use DoctrineModule\Service\AbstractFactory; use Zend\ServiceManager\ServiceLocatorInterface; class EntityManagerFactory extends BaseEntityManagerFactory { /** * {@inheritDoc} * @return EntityManager */ public function createService(ServiceLocatorInterface $sl) { /* @var $options \DoctrineORMModule\Options\EntityManager */ $options = $this->getOptions($sl, 'entitymanager'); $connection = $sl->get($options->getConnection()); $config = $sl->get($options->getConfiguration()); $sl->get($options->getEntityResolver()); // add Table Prefix $evm = $connection->getEventManager(); // assumed 'tablePrefixService' already registered in service_manager before... $evm->addEventListener(\Doctrine\ORM\Events::loadClassMetadata, $sl->get('tablePrefixService')); return EntityManager::create($connection, $config, $evm); } }
Now, we override the ‘doctrine_factories’ config in our config :
return [ 'doctrine' => [ // ... common doctrine driver config here ], 'doctrine_factories' => [ 'entitymanager' => 'Application\Factory\Service\EntityManagerFactory', ], ];
To make it works, The "doctrine_factories"
override config need to be placed after the doctrine
config.
That’s it. I hope it useful 😉
References :
1. http://marco-pivetta.com/doctrine-orm-zf2-tutorial/
2. http://stackoverflow.com/questions/12841102/how-to-configure-doctrine-extensions-in-zend-framework-2
3. https://gist.github.com/samsonasik/90f041f049d509161d61
4. https://github.com/doctrine/DoctrineModule
5. https://github.com/doctrine/DoctrineORMModule
Zend Framework 2 : Build application Using PHP 5.4 – 5.6 Features
Zend Framework 2 ( 2.3.3 ) requires PHP 5.3.23 or above. If we use PHP 5.4 or above, we can use their features when building module(s). There are many feature, I will only show you some of them that are useful.
1. PHP 5.4 features
a. Traits
ZF2 have several component that provide traits that ready to be used in our application. For example : Zend\EventManager\EventManagerAwareTrait
. Instead of implements Zend\EventManager\EventManagerInterface
, we can just use this traits.
This is the old way :
namespace Sample\Model; use Zend\EventManager\EventManager; use Zend\EventManager\EventManagerAwareInterface; use Zend\EventManager\EventManagerInterface; class Foo implements EventManagerAwareInterface { protected $events; public function setEventManager(EventManagerInterface $events) { $this->events = $events; return $this; } public function getEventManager() { if (!$this->events) { $this->setEventManager(new EventManager(__CLASS__)); } return $this->events; } public function bar($baz, $bat = null) { $params = compact('baz', 'bat'); $this->getEventManager()->trigger(__FUNCTION__, $this, $params); } }
This is the new way :
namespace Sample\Model; use Zend\EventManager\EventManagerAwareInterface; use Zend\EventManager\EventManagerAwareTrait; class Foo implements EventManagerAwareInterface { use EventManagerAwareTrait; public function bar($baz, $bat = null) { $params = compact('baz', 'bat'); $this->getEventManager()->trigger(__FUNCTION__, $this, $params); } }
Why not extends ? Because extends feature can already been used, and we need another things to be used, for example in the form class that already extends Zend\Form.
We can use many trait in one class.
b. Short array syntax
This is my favourite feature from PHP 5.4 that change array()
to []
. Lesser code that well be used.
This is the old way :
return array( 'router' => array( 'routes' => array( 'home' => array( 'type' => 'Zend\Mvc\Router\Http\Literal', 'options' => array( 'route' => '/', 'defaults' => array( 'controller' => 'Application\Controller\Index', 'action' => 'index', ), ), ), ), ), // ... );
This is the new way :
return [ 'router' => [ 'routes' => [ 'home' => [ 'type' => 'Zend\Mvc\Router\Http\Literal', 'options' => [ 'route' => '/', 'defaults' => [ 'controller' => 'Application\Controller\Index', 'action' => 'index', ], ], ], ], ], // ... ];
You don’t want to manually change existing project ? You can use this tool to convert everything : https://github.com/thomasbachem/php-short-array-syntax-converter .
c. array dereferencing
In old way, when you want to access value of array from function that return array, you need to make it variable first.
For example, when you want to use one of config file.
This is the old way :
$config = $this->getServiceLocator()->get('Config'); echo $config['myconfig'];
This is the new way :
$myconfig = $this->getServiceLocator()->get('Config')['myconfig'];
d. Closures now support $this.
This is the old way : ( grabbed from https://mwop.net/blog/2012-07-30-the-new-init.html )
public function setEventManager(EventManagerInterface $events) { parent::setEventManager($events); $controller = $this; $events->attach('dispatch', function ($e) use ($controller) { $request = $e->getRequest(); $method = $request->getMethod(); if (!in_array($method, array('PUT', 'DELETE', 'PATCH'))) { // nothing to do return; } if ($controller->params()->fromRoute('id', false)) { // nothing to do return; } // Missing identifier! Redirect. return $controller->redirect()->toRoute(/* ... */); }, 100); // execute before executing action logic }
This is the new way :
Now, we don’t need to pass $this that assigned to variable anymore, just use it inside the closure.
public function setEventManager(EventManagerInterface $events) { parent::setEventManager($events); $events->attach('dispatch', function ($e) { $request = $e->getRequest(); $method = $request->getMethod(); if (!in_array($method, ['PUT', 'DELETE', 'PATCH'])) { // nothing to do return; } if ($this->params()->fromRoute('id', false)) { // nothing to do return; } // Missing identifier! Redirect. return $this->redirect()->toRoute(/* ... */); }, 100); // execute before executing action logic }
e. Class member access on instantiation has been added
This is the old way :
use Zend\Db\TableGateway\TableGateway; $selectedtable = new TableGateway('tableneedtobeselected', $adapterSelect); $select = $selectedtable->getSql()->select()->where(array('field' => 'value'));
This is the new way :
use Zend\Db\TableGateway\TableGateway; $select = (new TableGateway('tableneedtobeselected', $adapterSelect)) ->getSql()->select()->where(['field' => 'value']);
2. PHP 5.5 features
a. Class name resolution via ::class
It is very useful when we call service that has name same as its Namespace\ClassName.
This is the old way :
$serviceLocator->get('My\Very\Long\Service\Name');
This is the new way :
use My\Very\Long\Service\Name; $serviceLocator->get(Name::class);
It will reduce of re-type long servicename when the service call many times in our file, for example : in tests file. It will be useful when we want to call full class name in our module.config.php like the following :
use Application\Controller\IndexController; return[ 'router' => [ 'routes' => [ 'home' => [ 'type' => 'Zend\Mvc\Router\Http\Literal', 'options' => [ 'route' => '/', 'defaults' => [ 'controller' => IndexController::class, 'action' => 'index', ], ], ], ], ], 'controllers' => [ 'invokables' => [ IndexController::class => IndexController::class ], ], // ... ];
b. finally keyword
Code within the finally block will always be executed after the try and catch blocks, regardless of whether an exception has been thrown, and before normal execution resumes.
try{ $adapter->getDriver()->getConnection()->beginTransaction(); $adapter->query("INSERT INTO album(artist, title) values('Ray', 'Love')")->execute(); $lastInsertedId = $this->getAdapter()->getDriver()->getConnection()->getLastGeneratedValue(); $adapter->query("INSERT INTO track(title, album_id) values('New Title', 'Foo')")->execute(); $adapter->getDriver()->getConnection()->commit(); } catch(\Exception $e) { $adapter->getDriver()->getConnection()->rollback(); throw $e; } finally { $adapter->getDriver()->getConnection()->disconnect(); }
3. PHP 5.6 features
Managing function parameters is now easier with Variadic and Argument Unpacking.
This is the old way :
class Bar {} class Foo { public function __construct(Bar $bar) { var_dump($bar); //getting argument list $numargs = func_num_args(); if ($numargs > 1) { $args = func_get_args(); $options = array(); foreach($args as $key => $row) { if ($key > 0 ) { $options[] = $row; } } var_dump($options); } } } $foo = new Foo(new Bar, 'a', 'b', 'c', 'd');
This is the new way :
class Bar {} class Foo { public function __construct(Bar $bar, ...$options) { var_dump($bar); var_dump($options); } } $foo = new Foo(new Bar, 'a', 'b', 'c', 'd');
We can pack the argument list by : … too.
$options = [ 'a', 'b', 'c', 'd' ]; $foo = new Foo(new Bar, ...$options); // OR collect all of them in one array $options = [ new Bar, 'a', 'b', 'c', 'd' ]; $foo = new Foo(...$options);
This feature can be useful when managing service dependencies, for example :
namespace MyModule\Factory\Service; use MyModule\Service\Bar; use Zend\ServiceManager\FactoryInterface; use Zend\ServiceManager\ServiceLocatorInterface; class FooServiceFactory implements FactoryInterface { public function createService(ServiceLocatorInterface $serviceLocator) { $options = [ $serviceLocator->get('Bar'), 'a', 'b', 'c', 'd', ]; $foo = new Foo(...$options); return $foo; } }
Ok, I hope my post is useful. What PHP version you’re using ? 🙂
References :
1. http://php.net/manual/en/migration54.new-features.php
2. http://php.net/manual/en/migration55.new-features.php
3. http://php.net/manual/en/migration56.new-features.php
4. https://mwop.net/blog/2012-07-30-the-new-init.html
5. https://github.com/thomasbachem/php-short-array-syntax-converter
6 comments