Question

What is the best way to use Doctrine ObjectManager? I inject it into controller at module.config.php

'Telecom\Controller\Users' => function($sm){
    $ctr = new Telecom\Controller\UsersController();
    $ctr->setEntityManager(
               $sm->getServiceLocator()
                    ->get('Doctrine\ORM\EntityManager')
           );

    return $ctr;
},

Then I use it in my controller as follows

$this->getEntityManager()->persist($entity);
$this->getEntityManager()->flush();

But Marco Pivetta (Doctrine team, zf2 contributor) teaches "If you inject objectmanager in your controllers, you're gonna have a bad architecture" http://marco-pivetta.com/doctrine-orm-zf2-tutorial/#/39/11.

So please help me, what is the best architecture way to use Entity Manager. Should I use another layer like my own service to deal with entity manager?

Was it helpful?

Solution

If you have the entity manager within the controller you introduce a coupling of your "domain logic" (your database queries) where they become inseparable form the application logic (the controller should only read the request and return the correct response). This coupling makes it considerably harder to reuse and maintain code.

One solution would be to create "services" that are injected into the controller. Services encapsulate the business logic (such as database queries) and provide a well defined API to the controller. The nice thing is that should the business logic change at any point (it always will); you would only need to change the implementation of said services and the controller will continue to function.

ZF2 is very flexible and there are many ways to accomplish the same task. Personally I do the following:

Services

Services are not mean to encapsulate just one entity; they should encapsulate all entities that are required to perform that specific task. This completely depends on what the service is trying to do. More complex services could require other services for instance.

In my implementation I have an abstract class called AbstractEntityService this class is extended for all services require persistence (anything that needs the database).

The class is quite long for here, however the key bits are bellow.

abstract class AbstractEntityService 
  extends Service\AbstractService 
  implements EntityMapperAwareInterface, FormProviderInterface
{ 

public function __construct(
  EntityMapper $entityMapper, 
  FormElementManager $formElementManager)
{
    $this->entityMapper = $entityMapper;
    $this->formElementManager = $formElementManager;

    $this->getEventManager()->addIdentifiers(array(__CLASS__));

    parent::__construct();
}

public function getForm($name)
{
   return $this->formElementManager->get($name);
}

public function create(Entity\EntityInterface $entity)
{
    $this->triggerEvent(static::EVENT_CREATE, array('entity' => $entity));

    $entity = $this->entityMapper->insert($entity);

    $this->triggerEvent(static::EVENT_CREATE_POST, array('entity' => $entity));

    return $entity;
}

Notice the EntityMapper and FormElementManager are injected - No ObjectManager or ServiceLocator here.

EntityMapper

Is simply a thin layer around the object manager; this allows us to swap out the EntityManager for the DocumentManager if we do from MySQL to MongoDB for example.

interface MapperInterface
{
   public function getObjectManager();

   public function setObjectManager(ObjectManager $objectManager);

   public function getRepository($className);

   public function insert(EntityInterface $entity);

   public function save(EntityInterface $entity);

   public function delete(EntityInterface $entity);

   public function flush();

 }

So an example of a concrete service would be:

class ListService extends AbstractEntityService
{
    public function __construct(
      EntityMapper $entityMapper,
      FormElementManager $formElementManager,
      ListRepository $listRepository
    ){
      parent::__construct($entityMapper, $formElementManager);
      $this->listRepository = $listRepository;
  }

  protected function init(EventManagerInterface $eventManager){
    parent::init($eventManager);
    $eventManager->attach(static::EVENT_CREATE, array($this, 'createList'));
  }

  public function createList(EventInterface $event)
  {
    $list = $event->getParam('entity');

    if (! $list instanceof Entity\ValueList) return;
    $name =  $list->getName();
    if (empty($name)) {
        $name = $this->formatName($list->getTitle());
        $list->setName($name);
    }
   }

ListController

The controller then simply uses the service (in the above example it is a "ListService")

class ListController extends AbstractActionController {

public function __construct(
    ListService $listService
){
    $this->listService = $listService;
}

public function createAction(){
   // validate request/form data...
   $form = $this->listService->getListCreateForm();

   $list = $this->listService->create($form->getData());

   // return view...
}

Wow; bit longer than planned but hope it helps.

OTHER TIPS

You can do something like this for each module:

| | - App
| | | - Controller
| | | | -IndexController.php
| | | - Entity
| | | | - User.php
| | | - Factory
| | | - Service
| | | | - UserService.php

In this kind of architecture, you have to inject in your Service's class your entity manager for doing that your services must implements ServiceManagerAwareInterface.

use Zend\ServiceManager\ServiceManagerAwareInterface;

class UsersService implements ServiceManagerAwareInterface {

    protected $sm;
/**
 * Retrouver l'entityManager voulue
 * @var [Object EntityManager]
 */
protected $em;

public function setServiceManager(ServiceManager $serviceManager)
{
    $this->sm = $serviceManager;
    return $this;
}

/*
 * Retrieve service manager instance
 *
 * @return ServiceManager
 */
public function getServiceManager()
{
    return $this->sm;
}

// do your crazy stuff here
...
}

Your entities like User, has a UserService in this example. And User is the mapped class for Doctrine.

You architecture looks like this from low level to hight level : Model (Entity) < DAO(Via Service and EntityManager) < Service < Controller < View

Edit :

'invokables' => array(
        'UsersService' => 'Application\Service\UsersService',
    ),

You can inject each service in module.config.php yes. If you have any question i listen. (sorry for my english) i hope i'm clear enought

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top