zend-framework-mvczend-framework3

Can I have a ZF3 MVC Framework Controller with parameters in the constructor?


I have a Zend Framework 3 MVC app with a controller with two end points. Both need to access the same class. It seems like the best way to do this would be to would be to make an instance of this class a property of the controller class like this:

class IndexController extends AbstractActionController
{
    /**
     * var Utility $utility
     */
    protected $utility;


    public function __construct(Utility $utility) 
    {
        $this->utility = $utility;
    }

    public function indexAction()
    {
        $this->utility->doA('param1');
        return new ViewModel();
    }

    public function otherAction()
    {
        $results = $this->utility->validateRequest($this->request);
        if ($results) 
        {
            return new ViewModel();
        } 
        else 
        {
            throw new Exception('Invalid request');
        }
    }
}

However, I don't know how to pass paramaters to the constructor since I don't know where Zend Framework "makes" it.


Solution

  • Zend Framework uses a concept called Dependency Injection. This is based on the D in SOLID, dependency inversion. Theory aside, you need to make a custom factory for your controller in modules.config.php. You also need to make a factory for the class calld Utility.

    So first of all, you probably made your project with a command similar to composer create-project -sdev zendframework/skeleton-application. If you did that you probably don't have the latest version of Service Manager. See if the file vendor/bin/generate-factory-for-class exists. If not, execute composer update zendframework/zend-servicemanager to add it there.

    Now lets make a factory for the utility class. Lets assume its in module/Application/src/Service/Utility.php and has the namespace Application\Service. You just type vendor/bin/generate-factory-for-class Application\\Service\\Utility > module/Application/src/Service/UtilityFactory.php. If you look in that file you can see:

    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        return new Utility();
    }
    

    Now lets do the same for the controller with vendor/bin/generate-factory-for-class Application\\Controller\\IndexController > module/Application/src/Controller/IndexControllerFactory.php. Open this factory and see its a little more complex.

    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        return new IndexController($container->get(\Application\Service\Utility::class));
    }
    

    $container is your dependency injection container. It executes the __invoke command in these factories when called.

    One more thing left to do. you need to edit your module.config.php. Replace the line

    'controllers' => [
        'factories' => [
            Controller\IndexController::class => InvokableFactory::class,
        ],
    ],
    

    with

    'controllers' => [
        'factories' => [
            Controller\IndexController::class => Controller\IndexControllerFactory::class,
        ],
    ],
    

    Now add the following section to the config:

    'service_manager' => [
        'factories' => [
            Service\Utility::class => InvokableFactory::class,
        ],
    ],
    

    Then your controller should work.