Search code examples
phpoopdependency-injectionzend-framework2factory-pattern

Can I read input from GET inside a Controller Factory?


This question is not explicitly about ZF2, but I often take ques from ZF2 for my code. That said, most ZF2 examples I have seen process input inside a Controller Action.

Example:

class YourController extends AbstractActionController
{

    public function doStuffAction()
    {
        // ZF2's way to get input from $_GET variable
        $product =  $this->getEvent()->getRouteMatch()->getParam('product');

        // Process    
        $processor = (new ProcessorFactory())->getProcessor($product);
        $output = $processor->processInput($data);
    }
}

Now, I would like to inject a Processor into my Controller. Not create it inside the controller like I am doing above. But since Processor depends on knowing the $product, which is only gotten from $_GET, I do not see any other way.

If I want to inject Processor into Controller, I have to move the line that populates $product variable outside of the Controller as well.

How can I do so without breaking OOP, ZF2, design patterns badly? As in, I am under the impression that anything to do with $_GET is to be done inside a Controller, and not inside a ControllerFactory. Unless perhaps I can break this pattern?


Solution

  • If you just want to apply the Dependency Inversion principle. Applying the D of SOLID acronym, only a few changes are needed.

    class YourController
    {
    
        /**
         * @var ProcessorFactory
         */
        protected $processorFactory;
    
        public function __construct(ProcessorFactory $processorFactory)
        {
            $this->processorFactory = $processorFactory;
        }
    
        public function doStuffAction()
        {
            $product =  $this->getEvent()->getRouteMatch()->getParam('product');
            $processor = $this->processorFactory->getProcessor($product);
        }
    }
    

    You could improve by typehinting to an Interface (SOLID)

    class YourController
    {
    
        /**
         * @var ProcessorFactoryInterface
         */
        protected $processorFactory;
    
        public function __construct(ProcessorFactoryInterface $processorFactory)
        {
            $this->processorFactory = $processorFactory;
        }
    
        public function doStuffAction()
        {
            $product =  $this->getEvent()->getRouteMatch()->getParam('product');
            $processor = $this->processorFactory->getProcessor($product);
        }
    }
    

    Now, if you want don't want your Controller to be responsible of initiating the creating process (SOLID), you can split it up some more.

    class YourController
    {
    
        /**
         * @var ProcessorInterface
         */
        protected $processor;
    
        public function __construct(ProcessorInterface $processor)
        {
            $this->processor = $processor;
        }
    
        public function doStuffAction()
        {
            $processor = $this->processor;
        }
    }
    
    class ControllerFactory
    {
        /**
         * @var ProcessorFactory
         */
        protected $processorFactory;
    
        public function  __construct(ProcessorFactory $processorFactory)
        {
            $this->processorFactory = $processorFactory;
        }
    
        public function create()
        {
            return new YourController($this->processorFactory->getProcessor());
        }
    }
    
    class ProcessorFactory
    {
        /**
         * @var RouteMatch
         */
        protected $routeMatch;
    
        public function __construct(RouteMatch $routeMatch)
        {
            $this->routeMatch = $routeMatch;
        }
    
        public function getProcessor()
        {
            $processor = $this->createProcessor();
            // do stuff
            return $processor;
        }
    
        protected function createProcessor()
        {
            $product =  $this->routeMatch->getParam('product');
    
            // create processor
    
            return $processor;
        }
    }
    

    The following code would get you your controller.

    $controllerFactory = new ControllerFactory(new ProcessorFactory(new RouteMatch()));
    $yourController = $controllerFactory->create();
    

    Now above code is more general code and not adapted for ZF2. A good move would then to involve the ZF2's servicemanager.

    class YourController extends AbstractActionController
    {
    
        /**
         * @var ProcessorInterface
         */
        protected $processor;
    
        public function __construct(ProcessorInterface $processor)
        {
            $this->processor = $processor;
        }
    
        public function doStuffAction()
        {
            $processor = $this->processor;
        }
    }
    
    
    class YourControllerFactory implements FactoryInterface
    {
    
        public function createService(ServiceLocatorInterface $controllers)
        {
            $services = $controllers->getServiceLocator();
            $processorFactory = $services->get('ProcessorFactory');
            return new YourController($processorFactory->getProcessor());
        }
    }
    
    class ProcessorFactory
    {
        /**
         * @var RouteMatch
         */
        protected $routeMatch;
    
        public function __construct(RouteMatch $routeMatch)
        {
            $this->routeMatch = $routeMatch;
        }
    
        public function getProcessor()
        {
            $processor = $this->createProcessor();
            // do stuff
            return $processor;
        }
    
        protected function createProcessor()
        {
            $product =  $this->routeMatch->getParam('product');
    
            // create processor
    
            return $processor;
        }
    }
    
    class ProcessorFactoryFactory implements FactoryInterface
    {
    
        public function createService(ServiceLocatorInterface $services)
        {
            return new ProcessorFactory($services->get('RouteMatch'));
        }
    }
    

    Above services/controllers and their factories should be registered with their ServiceManager/ControllerManager

    $config = [
        'controllers' = [
            'factories' [
                 'YourController' => 'YourControllerFactory',
            ],
        ],
        'service_manager' = [
            'factories' [
                 'ProcessorFactory' => 'ProcessorFactoryFactory',
            ],
        ],
    ];
    

    When a request gets dispatch to YourController, the ControllerManager returns a YourController instance with a Processor injected. Which Processor it gets depends on the request (a parameter inside RouteMatch).