1

The latest update of zend-mvc has caused a break in compatibility due to phasing out ServiceLocatorAwareInterface. I use the servicelocator within a controller to dynamically load dependencies, eg:

class IndexController extends AbstractActionController
/**
 *
 * @return \UserManagement\Form\User\Details
 */
protected function getUserDetailsForm(){
    return $this->getFormManager()->get('User\Details');
}

/**
 *
 * @return FormElementManager
 */
protected function getFormManager(){
    return $this->getServiceLocator()->get('FormElementManager');
}
}

This is now raising an exception (E_USER_DEPRECEATED) with the following message:

You are retrieving the service locator from within the class User\Controller\IndexController. Please be aware that ServiceLocatorAwareInterface is deprecated and will be removed in version 3.0, along with the ServiceLocatorAwareInitializer. You will need to update your class to accept all dependencies at creation, either via constructor arguments or setters, and use a factory to perform the injections.

My question is, what is the best way of getting the forms into the controller? My service layer and other controller-specific dependencies are injected into the constructor, but i don't really want to polluate a constructor with all the forms that a controller may need, nor do i want the overhead of creating form objects that will not be used. The forms cannot be created in the controller ie $form = new Form() as they are also created dynamically eg: Module.php

public function getFormElementConfig ()
{
    return array(
            'factories' => array(
                    'User\Details' => function($sm){
                        $userMapper = $sm->getServiceLocator()->get('Model\User\Mapper');
                        $form = new \User\Form\Details($userMapper);
                        return $form;               
                    }
                )
            );
}
John Crest
  • 201
  • 1
  • 4
  • 24
  • Personally based on your example I would just inject the form manager and then call that when you need a form. There isn't a huge difference between `$this->getUserDetailsForm()` and `$this->formManager->get('User\Details')` – Tim Fountain Mar 16 '16 at 22:43
  • Possible duplicate of [PHP Deprecated: You are retrieving the service locator from within the class ZFTool\Controller\ModuleController](http://stackoverflow.com/questions/35933113/php-deprecated-you-are-retrieving-the-service-locator-from-within-the-class-zft) – Oleg Abrazhaev May 14 '16 at 03:35

3 Answers3

1

Have more and more specific controllers.

That way you can instantiate one controller and then have to inject exactly all the objects that are definitely needed to perform any task you may need.

There is no use to combine actions into one single controller class if all they share is a common URL path fragment. From the software design point of view, a class that does plenty of independent things in different methods is just doing too much. And doing too much is reflected in the number of dependencies you have to inject.

Sven
  • 69,403
  • 10
  • 107
  • 109
  • I think i understand what you are saying - so instead of a UserController, with add/edit/delete/search actions, there is a controller for each - UserAddController, UserEditController etc... - one controller per (url) action? Otherwise, i think something about injecting the form as a constructor argument isn't right? – John Crest Mar 16 '16 at 22:36
  • 1
    Why do you need multiple forms in a UserController for add/edit/view? Adding and editing basically is the same. Another thought: Why should the form be independent of the model? The model is responsible for persisting and reading from the storage - it should also be responsible for validation rules. The model could be asked to provide an InputFilter object or even a form (which only adds plenty of HTML rendering abilities, which are not needed for my daily tasks dealing with APIs) - and then only injecting the UserModel into the UserController makes more sense than injecting 1 form per action. – Sven Mar 17 '16 at 00:22
0

There are few solutions.

  • Make controllers smaller. Less methods, less dependencies. However, more factories.

  • Use the ZF2 proxy manager. It essentially replaces expensive object instantiation with proxy objects.

  • Another option would be to add a wrapper or container that lazy loads the forms via the form element manager. You can then inject this container into the controller or service layer. Using a service locator in this way isn't "ideal" as you loose the explicitly defined class dependencies.

I have a FormElementLazyProvider class and its associated factory that might be worth checking out.

For example.

$elementConfig = [
    'create' => 'MyModule\Form\CreateForm',
    'edit'   => 'MyModule\Form\EditForm',
    'delete' => 'MyModule\Form\DeleteForm',
];

$factory = function($serviceName, $elementName) use ($formElementManager) {
    if ($formElementManager->has($serviceName)) {
        return $formElementManager->get($serviceName);
    }
    return false;
};

$provider = new \ArpForm\Service\FormElementLazyProvider($factory, $elementConfig);

$provider->hasElement('create'); // true
$provider->getElement('edit');   // Callback executed and form object return
AlexP
  • 9,906
  • 1
  • 24
  • 43
0

The crux of the problem is the service locator hiding the dependencies of the controller class.

The Zend framework is instructing you to move away from the ServiceRepository pattern and use proper dependency injection using DI containers or using constructor injection or setter injection. You can use a factory to inject the dependencies as well.

Please read about Service Repository which many see it as an anti-pattern.

Pradeep
  • 2,469
  • 1
  • 18
  • 27