Must I move data dependency out of my Controllers

2019-07-17 16:25发布

This question can be viewed through a prism of ZF2 + Doctrine + MVC programming practices, or it can be viewed through just an OOP perspective.

My concern is about Separation of Concerns, and on removing dependencies.

I am using code in my controllers that goes something like this:

class MyController
{ 
    private $em; //entityManager 

    function __construct()
    {
        $this->em = DoctrineConnector::getEntityManager();
    }

    function indexAction()
    {
        //Input
        $inputParameter = filter_input(...);

        //request for Data
        $queryBuilder = $this->em->createQuery(...)
                             ->setParameter('param', $inputParameter);
        $query = $queryBuilder->getQuery();
        //$services is the user-defined data type requested
        $services = $query->getResult();

        //use data to produce a view model
        $view = new ViewModel();
        $view->setVariables(array('services' => $services));
        return $view;
    }
}

I am not entirely comfortable with the above and wanted a second opinion. For one, my EntityManager is part of the class, so my class is cognizant of the entity manager construct, when I think it should not be a part of the controller. Do I perhaps use a Factory or Builder design pattern to help me create MyController class?

If I do, I can move my em (entityManager) construct into the Factory pattern and create and populate my MyController inside the Factory. Then, the MyController can have a private variable $services instead.

i.e.

class MyController
{ 
    private $services;

    function setServices($services)
    {
        $this->services = $services;
    }

    function indexAction()
    {
        //use data to produce a view model
        $view = new ViewModel();
        $view->setVariables(array('services' => $this->services));
        return $view;
    }
}

class MyFactoryMethod
{
    function createMyController()
    {
        //Input
        $inputParameter = filter_input(INPUT_GET...);

        //request for Data
        $queryBuilder = $this->em->createQuery(...)
                             ->setParameter('param', $inputParameter);
        $query = $queryBuilder->getQuery();
        //$services is the user-defined data type requested
        $services = $query->getResult();

        //create and return MyController instance
        $controller = new MyController();
        $controller->setServices($services);
        return $controller;
    }
}

I typically tried to do this PHP's mysql extension to remove dependency on data out of my various objects. I am using Doctrine2 now which is an ORM, and wondering if I should keep doing the same thing (namely preferring 2nd example rather than the first...

Question:

I can write code both ways. It works essentially the same. My question is -- is the code, as it is written in my 2nd example preferred more than the code as it is written in my first?

Notes / Clarifications:

In my case variable $services is a domain-specific variable (not ZF2's ServiceLocator). i.e. think of MyController as a controller for business-specific "services".

I am not harnessing full power of ZF2 with configs, routers, events, and everything. I am using ZF2 modules on an existing legacy codebase on as-needed basis.

2条回答
走好不送
2楼-- · 2019-07-17 16:53

There are two different approaches to this problem that are provided by ZF2.

  1. Use the ServiceLocator to retrieve the EntityManager via a Factory.

    In Module.php, add an anonymous function or Factory.

    public function getServiceConfig()
    {
        return [
            'factories' => [
                'Doctrine\ORM\EntityManager' => function (ServiceManager $sm) {
                    $entityManager = $sm->get('doctrine.entitymanager.orm_default');
                    return $entityManager;
                }
            ],
        ],
    }
    

    In your Controller

    $em = $this->getServiceLocator()->get('Doctrine\ORM\EntityManager');
    
  2. Create an Initializer and AwareInterface to inject the EntityManger into your controllers.

    The AwareInterface can be added to any class which is initialized by the ServiceManager.

    interface EntityManagerAwareInterface
    {
        /**
         * Set EntityManager locator
         *
         * @param EntityManager $entityManager
         */
         public function setEntityManager(EntityManager $entityManager);
    
         /**
          * Get service locator
          *
          * @return EntityManager
          */
         public function getServiceLocator();
     }
    

    The Initializer is run when services are initialized by the ServiceManager. A check is performed to so if $instance is a EntityManagerAwareInterface.

    use Application\EntityManager\EntityManagerAwareInterface;
    use Zend\ServiceManager\InitializerInterface;
    use Zend\ServiceManager\ServiceLocatorInterface;
    
    class EntityManagerInitializer implements InitializerInterface
    {
        /**
         * Initialize
         *
         * @param $instance
         * @param ServiceLocatorInterface $serviceLocator
         * @return mixed
         */
        public function initialize($instance, ServiceLocatorInterface $serviceLocator)
        {
            if ($instance instanceof EntityManagerAwareInterface) {            
                $entityManager = $serviceLocator->get('doctrine.entitymanager.orm_default');
                $instance->setEntityManager($entityManager);
            }
        }
    }
    

    Next add the Initializer to Module.php

    public function getServiceConfig()
    {
        return [
            'initializers' => [
                'entityManager' => new EntityManagerInitializer(),
            ],
        ],
    }
    

The advantage of going the Initializer route is there is a one time setup. Any class that implements the EntityManagerAwareInterface will have the EntityManager injected when the class is initialized.

查看更多
smile是对你的礼貌
3楼-- · 2019-07-17 16:56

When your controller has hard dependencies I would suggest to use the common ZF2 solution by creating the controller and injecting the dependency in a factory instance and registering the controller under the 'factories' key in your 'controllers' config array.

In your module.config.php

'controllers' => array(
    'factories' => array(
        'Application\Controller\MyController' => 'Application\Controller\MyControllerFactory'
    )
)

In your controller I would set hard dependency in the __construct method. Like this you prevent the controller from ever being instantiated without your dependencies (it will throw an exception).

Never inject something like $services (if this is a ServiceLocator) from which you will pull the actual dependencies since it is not clear what the class actually needs. It will be harder to understand for other developers and it is also hard to test since you cannot set mocks for your individual dependencies so easily.

Your Controller class:

<?php
namespace Application\Controller;

use Doctrine\ORM\EntityManager;

class MyController
{ 
    /**
     * @var EntityManager
     */
    private $entityManager;

    /**
     * @param EntityManager $entityManager
     */
    public function __construct(EntityManager $entityManager)
    {
        $this->entityManager = $entityManager;
    }

    /**
     *
     */
    function indexAction()
    {
        //Do stuff
        $entityManager = $this->getEntityManager();
    }

    /**
     * @return EntityManager
     */
    public function getEntityManager()
    {
        return $this->entityManager;
    }
}

Your Factory:

<?php
namespace Application\Controller;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Doctrine\ORM\EntityManager;

class MyControllerFactory implements FactoryInterface
{
    /**
     * @param  ServiceLocatorInterface $serviceLocator
     * @return MyController
     */
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        /** @var EntityManager $entityManager */
        $serviceManager = $serviceLocator->getServiceLocator()
        $entityManager = $serviceManager->get('doctrine.entitymanager.orm_default');
        $myController = new MyController($entityManager);
        return $myController;
    }
}
查看更多
登录 后发表回答