2

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?

shukshin.ivan
  • 11,075
  • 4
  • 53
  • 69

2 Answers2

5

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.

AlexP
  • 9,906
  • 1
  • 24
  • 43
  • Thank you, let me think about it a while. – shukshin.ivan Apr 11 '14 at 01:39
  • The upper text is quite clear, but code - not. Why do you use events? Its hard to understand code with a lot of events. What is the benefits of them for architecture? Why do you inject via __construct, not setter? – shukshin.ivan Apr 11 '14 at 08:07
  • @shukshin.ivan The events in `AbstractEntityService` allow any extending service class to reuse the `create` method; without having to modify/overload it. If I wanted to do something specific for any entity service (say in the list service) I can attach an event int the `init()` method as I have done for `createList(EventInterface $event)`. I inject into the constructor ([dependency injection](http://stackoverflow.com/questions/130794/what-is-dependency-injection)) as the class has *hard* dependencies rather than *soft* dependencies (which would be when you should use setters). – AlexP Apr 11 '14 at 08:48
  • 1
    Hey Alex. Do you have a more worked out example of this code somewhere on the net? – sanders May 11 '14 at 15:46
  • Nice answer! One doubt i have, should i create one service for each module's controller or should i use one service injecting in all controllers? – Arthur Mastropietro Feb 22 '16 at 23:15
2

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

Greco Jonathan
  • 2,517
  • 2
  • 29
  • 54
  • So, every Entity should have its own Service, which get entitymanager via injection in module.config.php, right? But what is the reason to have yet another program layer? Why is EntityManager not enough? To change ORM-layer, if needed? – shukshin.ivan Apr 10 '14 at 09:39
  • 2
    The reason is : respect MVC, controller are not made for interact with DAO And Service Layer, plus, in zf3 it's almost plan to remove the access to SM in controllers. https://github.com/zendframework/zf2/issues/5168 This is a bad practice and for simplify tutorials, each demonstration are done in controllers wich is not what we have to do. EntityManager is enought, service manager get the entityManager in Service Layer. That's all. – Greco Jonathan Apr 10 '14 at 09:52
  • this link helped me to understand all these stuff : http://blog.astrumfutura.com/2008/12/the-m-in-mvc-why-models-are-misunderstood-and-unappreciated/ i advise you to read it, it's really nice – Greco Jonathan Apr 10 '14 at 09:54
  • Thanks, I completely forgot, that thick controller = bad architecture – shukshin.ivan Apr 11 '14 at 01:35
  • What is the difference between AbstractService in the other answer and yours ServiceManagerAwareInterface? I'm confused a little with a lot of services' classes. – shukshin.ivan Apr 11 '14 at 08:11
  • Service managerAwareInterface give you in your service's classes the injection of service manager for get EM, and any factories you want and declared. The other answer is doing the same, but in a different way and implements other class for retriving same instances, you can choose one of these two different architecture, the one which you prefer. – Greco Jonathan Apr 11 '14 at 08:31