Matthew, thanks for this elaborate answer. Makes a lot of things clearer
for me.

So, in that case I stick to injecting everything I need. This really bloats
my module.config.php with a lot of redundant stuff and slows me down quite
a bit, but I see your point.


On Tue, Feb 19, 2013 at 5:08 PM, Matthew Weier O'Phinney
<[email protected]>wrote:

> On Tue, Feb 19, 2013 at 3:37 AM, roberto blanko <[email protected]>
> wrote:
> > one problem drives me nuts, since I started working with ZF2 (never
> worked
> > with Version 1):
> >
> > I'm in need of the service manager all the time. E.g. to access my config
> > in config/autoload/local.php via $sm->get('Config'). I need the service
> > manager everywhere. Controllers, models, you name it. And I don’t want to
> > pass it around by hand, which would make everything ugly.
> >
> > Now I’ve started to implement ServiceLocatorAwareInterface in most
> classes.
>
> Don't.
>
> The best option is to define factories for your classes that inject
> the dependencies for you. When you do that, you no longer have hidden
> dependencies, and you have everything you need up front. When you have
> an instance of the object, it's fully configured.
>
> If you need configuration, you're doing it wrong -- that configuration
> should either be injected, or the objects the configuration
> defines/configures should be injected.
>
> As an example, let's consider a common controller. Let's say it makes
> use of a TableGateway, and you have one or more methods in that
> TableGateway that return a paginator instance. You want to be able to
> specify how many results per page the paginator should use. And for
> insert()/update() operations, you want this information tied to a form
> so that the validation is done correctly; however, you want a
> different form based on the operation (new vs. edit). One element of
> the form needs a DB instance in order to validate.
>
> In ZF1, you'd likely do the following:
>
> * Pull the "db" resource from the bootstrap (actually, a DB adapter).
> * Create a TableGateway instance, and inject the DB adapter.
> * Pull configuration from the registry or the bootstrap.
> * Use that configuration to tell the TableGateway how many items per
> page to return for Paginator instances.
> * You'd create a different form for each operation, and inject the DB
> adapter you pulled.
>
> This is a fair bit of work. And it really, really doesn't belong in
> your controller, any of it.
>
> Let's look at how to do it in ZF2.
>
> First, I'd create a factory for the TableGateway.
>
>     'my-table-gateway' => function ($services) {
>         $db = $services->get('db');
>         $config = $services->get('config');
>         $perPage = isset($config['per_page']) ? $config['per_page'] : 10;
>         $tableGateway = new MyTableGateway($db, $perPage);
>         return $tableGateway;
>     }
>
> Note that in the _factory_ I'm pulling the configuration, but then I'm
> using the values I retrieve from that in order to construct my table
> gateway -- this decouples the TableGateway from my configuration. Also
> note that I'm retrieving the adapter from the service manager --
> dependencies are defined as additional services, and I consume them in
> my factories. How is that dependency defined? As a factory:
>
>     'db' => 'Zend\Db\Adapter\AdapterFactoryService',
>
> Next, let's consider my form and validators. In 2.1, we added the
> ability to define form elements, filters, and validators via plugin
> managers which are managed via the application service manager. This
> means that I can create factories for my forms that consume these. By
> default, if you
>
>     'validators' => array('factories' => array(
>         'MyRecordExists' => function ($validators) {
>             $services = $validators->getServiceLocator();
>             $db          = $services->get('db');
>             return new \Zend\Validator\Db\RecordExists(array(
>                 'adapter' => $db,
>                 'table' => 'some_table',
>                 'field' => 'some_field',
>             ));
>         },
>     )),
>     'services' => array('factories' => array(
>         'MyCustomForm' => function($services) {
>             $validators = $services->get('ValidatorPluginManager');
>             $validatorChain = new \Zend\Validator\ValidatorChain();
>             $validatorChain->setPluginManager($validators);
>
>             $inputFilterFactory = new \Zend\InputFilter\Factory();
>             $inputFilterFactory->setDefaultValidatorChain($validatorChain);
>             $inputFilter = new \Zend\InputFilter\InputFilter();
>             $inputFilter->setFactory($inputFilterFactory);
>
>             return new MyCustomForm('my-custom-form',
> array('input_filter' => $inputFilter));
>         },
>     )),
>
> In the first case, we've provided a factory for
> Zend\Validator\Db\RecordExists that ensures that it is configured with
> a DB adapter, and the table and field names we require; note that it
> uses its own service name, which allows us to have multiple instances
> of the RecordExists validator with different configurations. In the
> second case, we create a factory for our form. In there, we create an
> input filter instance that has an InputFilter factory passed to it;
> that factory is seeded with a validator chain that has our custom
> validator plugins in it.
>
> Note that all of this is decoupled from our controller. This allows us
> to test any piece of it individually, as well as to re-use it in areas
> outside our controller if desired.
>
> Now, for the controller:
>
>     'controllers' => array('factories' => array(
>         'MyController' => function ($controllers) {
>             $services = $controllers->getServiceLocator();
>             $tableGateway = $services->get('my-table-gateway');
>             $form = $services->get('MyCustomForm');
>             $controller = new MyController();
>             $controller->setTableGateway($tableGateway);
>             $controller->setForm($form);
>             return $controller;
>         },
>     )),
>
> We grab dependencies, instantiate our controller, and inject the
> dependencies. Nice and clean.
>
> Inside our controller, we simply use those dependencies:
>
>     public function newAction()
>     {
>         $this->form->setValidationGroup('username', 'password',
> 'confirmation');
>         $this->form->setData($this->getRequest()->getPost()->toArray());
>         if (!$this->form->isValid()) {
>             return new ViewModel(array(
>                 'form' => $this->form,
>                 'success' => false,
>             ));
>         }
>         $this->table->insert($this->form->getData());
>         $this->redirect()->toRoute('user/profile');
>     }
>
> The controller contains no logic for creating the dependencies, or
> even fetching them; it simply has setters:
>
>     protected $table;
>
>     public function setTableGateway(TableGateway $tableGateway)
>     {
>         $this->table = $tableGateway;
>     }
>
> This allows the controller to be tested easily, and keeps the messy
> logic of obtaining dependencies where it should be -- in factories,
> elsewhere.
>
> Notice that I never pass around the service manager or service
> locator. If a class needs a dependency, I create a factory for that
> class, and use that factory to fetch dependencies and inject them.
> This keeps the logic clean inside the individual classes, as they are
> only operating on the dependencies passed to them; they don't worry
> about instantiating dependencies, or about fetching them. They simply
> assume they have them.
>
> > But this has the two downsides for me:
> >
> > 1. The classes which make use of my ServiceLocatorAware classes need to
> > have acces to the service locater, as well, in order to instantiate the
> > objects. So even more ServiceLocatorAware classes and even more
> invokables
> > to be added to module.config.php.
>
> As noted, don't use ServiceLocatorAware. Always pass in dependencies.
>
> Second, you _should_ define services for the service manager. This is
> a good practice. Those services will only be instantiated if the
> current request needs them. Furthermore, defining them means that
> someone later can provide _substitutions_ for them. This is
> tremendously powerful -- it allows developers to extend your class,
> and still have it injected where it needs to be. (I've done this to
> work around issues I've found in the past!)
>
> > 2. The service manager is a property of all ServiceLocatorAware classes,
> > which can be difficult if you want to persist a ServiceLocatorAware model
> > e.g. to the session.
>
> Again, don't make things ServiceLocatorAware. And if you do, don't
> persist them to the session. You should persist very little to the
> session, and your models typically should be plain old PHP objects
> anyways, without knowledge of persistence. If you're tying them to the
> persistence layer directly, use hydrators to extract information as
> well as hydrate them, if you need to serialize them into the session.
>
> > Now I'm starting to get the feeling the I haven’t really understood ZF2
> and
> > the service manager. Injecting everything cannot be the solution, can it?
>
> Yes, it is. Because it solves the problems of re-usability,
> substitution, and dependency resolution -- all of which were problems
> in ZF1.
>
> > Should I really inject e.g. my global config into all classes?
>
> No. Extract the configuration you need inside a factory, and use that
> to create an instance. Your objects should only get exactly what they
> need, no more, no less.
>
> > And if you
> > don’t want to have plain entity models but business models with some
> > process logic in them, you cannot abstain from the service manager
> either.
>
> There are ways to do this, too - service layers can compose
> context-specific service managers, or be injected with the
> services/configuration that the domain layer may need in order to
> operate. You can then use this information when constructing domain
> object instances, to create prototypes of domain objects, or to seed
> factories. There are many options here.
>
>
> --
> Matthew Weier O'Phinney
> Project Lead            | [email protected]
> Zend Framework          | http://framework.zend.com/
> PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc
>

Reply via email to