Search code examples
zend-framework2dependenciesfactoryinstantiationstrategy-pattern

Zend Framework 2 - Controller instantiate Service via (modified?) Factory


I have an abstract service class SAbstract which is inherited by ConcreteServiceA and ConcreteServiceB. Now I am instantiating ConcreteServiceA in the factory class of my controller and inject the service in my controller.

In a specific action in my controller I want to exchange ConcreteServiceA with ConcreteServiceB to change behavior. Because they have same interface (abstract class SAbstract) I could inject it in my controller as well (the services are a Strategy-Pattern).

But I don't want to instantiate ConcreteServiceB directly in my controller to keep my code clean (for easy refactoring and exchanging behavior).

A possible solution is to create a second factory for my controller which injects ConcreteServiceB instead of ConcreteServiceA but then I have duplicated lots of code which is not good... Another solution would be to inject both services in my controller (but this "smells" like bad code).

Is a delegator factory the right way to do this? Then I have to implement setters in my controller...

Is there a better way?

I tried to schematically visualize my class relationships.

AbstractService <|--<inherit>- ConcreteServiceA
AbstractService <|--<inherit>- ConcreteServiceB
Controller -<use>--> AbstractService
Controller:ActionA -<use>--> ConcreteServiceA:exportAction()
Controller:ActionB -<use>--> ConcreteServiceB:exportAction()

Solution

  • In a specific action in my controller I want to exchange ConcreteServiceA with ConcreteServiceB to change behavior. Because they have same interface.

    You can configure the route to use a different controller service name for each action; then configure a controller factory to inject the required service using configuration.

    The route config could look like this.

    'router' => [
        'routes' => [
            'foo' => [
                'type' => 'literal',
                'options' => [
                    'route' => '/foo',
                    'defaults' => [
                        'controller' => 'MyControllerWithFooService',
                        'action' => 'actionThatNeedsFooService',
                    ],
                ],
            ],
            'bar' => [
                'type' => 'literal',
                'options' => [
                    'route' => '/bar',
                    'defaults' => [
                        'controller' => 'MyControllerWithBarService',
                        'action' => 'actionThatNeedsBarService',
                    ],
                ],
            ],
        ],
    ]
    

    Then add the config for the services and controllers.

    'app_config' => [
        'MyControllerWithFooService' => [
            'service_name' => 'FooService',
        ],
        'MyControllerWithFooService' => [
            'service_name' => 'BarService',
        ],
    ],
    'service_manager' => [
        'factories' => [
            'FooService' => 'FooServiceFactory'
            'BarService' => 'BarServiceFactory'
        ],
    ],
    'controllers' => [
        'factories' => [
            'MyControllerWithFooService' => 'MyControllerServiceFactory'
            'MyControllerWithBarService' => 'MyControllerServiceFactory'
        ],
    ]
    

    The MyControllerServiceFactory could be very simple.

    class MyControllerServiceFactory
    {
        public function __invoke($controllerManager, $name, $requestedName)
        {
            $sm = $controllerManager->getServiceLocator();
    
            $config = $sm->get('config');
    
            if (empty($config['app_config'][$requestedName])) {
                throw new ServiceNotCreatedException('No config set!');
            }
    
            $serviceName = $config['app_config'][$requestedName]['service_name'];
            $service = $sm->get($serviceName);
    
            return new MyController($service);
        } 
    }