On Tue, Feb 19, 2013 at 3:37 AM, roberto blanko <robertobla...@gmail.com> 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 | matt...@zend.com Zend Framework | http://framework.zend.com/ PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc -- List: fw-general@lists.zend.com Info: http://framework.zend.com/archives Unsubscribe: fw-general-unsubscr...@lists.zend.com