koseduhemak
koseduhemak

Reputation: 551

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()

Upvotes: 1

Views: 184

Answers (1)

AlexP
AlexP

Reputation: 9857

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);
    } 
}

Upvotes: 3

Related Questions