|
Hi all!
I'm writing my first ZF2 controller, and am wondering about accessing other objects (models, services...) in a controller. As I see it now, there are 2 ways to do this, and both ways seem perfectly fine to me... But maybe I'm missing something I don't see, and in the long run, one way is better than the other. One way is by injecting the dependencies like this: https://github.com/akrabat/zf2-tutorial/blob/master/module/Album/Module.php#L23 and then the controller has the setAlbumTable method, etc... The other way is by configuring the service locator/manager/whatever it's called, and then getting the object in the controller via $this->getServiceLocator()->get('album-table') ... Personally, I'm leaning towards the second way: - I know that the object is being called from the service locator - As long as the called object implements the ServiceLocatorAwareInterface, it gets the service locator injected (this maybe happens with the first way too, don't know, haven't checked.) - From the unit tests PoV, it's... the same. Well, almost, the second way requires a bit more configuring of the service locator in the tests, but other than that, can't see any other difference. You'll need to configure the SL for the first way too, if you want to access the controller plugin manager/controller plugins. - My brain reads it/parses it/understands it easier. So, is my brain completely wrong and the first way is better, or am I correct going with the second option? Or maybe in the end it doesn't matter, as it boils down to the same? OR, is it on a case-by-case basis? Thanks :) -- ~Robert Basic; http://robertbasic.com/ -- List: [hidden email] Info: http://framework.zend.com/archives Unsubscribe: [hidden email] |
|
Administrator
|
-- Robert Basic <[hidden email]> wrote
(on Monday, 17 September 2012, 04:33 PM +0200): > I'm writing my first ZF2 controller, and am wondering about accessing > other objects (models, services...) in a controller. As I see it now, > there are 2 ways to do this, and both ways seem perfectly fine to > me... But maybe I'm missing something I don't see, and in the long > run, one way is better than the other. > > One way is by injecting the dependencies like this: > https://github.com/akrabat/zf2-tutorial/blob/master/module/Album/Module.php#L23 > and then the controller has the setAlbumTable method, etc... > > The other way is by configuring the service locator/manager/whatever > it's called, and then getting the object in the controller via > $this->getServiceLocator()->get('album-table') ... > > Personally, I'm leaning towards the second way: > - I know that the object is being called from the service locator > - As long as the called object implements the > ServiceLocatorAwareInterface, it gets the service locator injected > (this maybe happens with the first way too, don't know, haven't > checked.) > - From the unit tests PoV, it's... the same. Well, almost, the second > way requires a bit more configuring of the service locator in the > tests, but other than that, can't see any other difference. You'll > need to configure the SL for the first way too, if you want to access > the controller plugin manager/controller plugins. > - My brain reads it/parses it/understands it easier. > > So, is my brain completely wrong and the first way is better, or am I > correct going with the second option? Or maybe in the end it doesn't > matter, as it boils down to the same? OR, is it on a case-by-case > basis? I personally have been recommending the first way. The primary reasons are: * It makes your code self-documenting. You can look at API docs of your classes, and know immediately what dependencies they have. If you instead inject a Service Locator, and pull objects from that, it's anybody's guess what dependencies are needed unless you scan the code itself. The only hard dependency is on the Service Locator -- which is at best a mediator between your class and the objects it consumes. * It makes substitutions easier. Want to see if a different service that provides the same implementations can work? Modify the factory quickly, and test. This is harder when you use a Service Locator inside your class, as you're also hard-coding the service name within your code. With a factory, the consuming code is de-coupled from the service name. * As you noted, it makes testing easier. You can look at the factory you use in your application to get an idea of how to construct the object, and then simply re-create that in your test setup. If you rely on a service locator, you have to look through your code to see what services are pulled from it, which is extra effort. Basically, it's the difference between using a registry, and using an Inversion of Control (IoC) container. Registries are easy, fast, and seductive, but have side effects (undocumented dependencies, dependencies updated by other classes elsewhere, etc.); IoC containers require you to flip your thinking around, but make the code self-documenting, and dependencies easier to identify and provide substitutions for. We _do_ use the ServiceLocatorAwareInterface within the framework. I was loathe to add it to the controllers by default, but did so primarily to enable plugins access to workflow-related objects (look at the url() and forward() plugins for examples). Otherwise, I highly recommend passing your dependencies _to_ your objects, rather than fetching them _from_ the SL. -- Matthew Weier O'Phinney Project Lead | [hidden email] Zend Framework | http://framework.zend.com/ PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc -- List: [hidden email] Info: http://framework.zend.com/archives Unsubscribe: [hidden email] |
|
On 17 September 2012 17:14, Matthew Weier O'Phinney <[hidden email]> wrote:
> > I personally have been recommending the first way. Any example for getting controllers via factories? I've been through the docs, code, tests, examples, but fail to figure it out :( I can get anything else to work via factories, but not controllers. The only way I can get controllers is using the 'controllers' => array( 'invokables' => array( ... )) stuff, but that doesn't allow me to inject dependencies. -- List: [hidden email] Info: http://framework.zend.com/archives Unsubscribe: [hidden email] |
|
In reply to this post by weierophinney
Hi Matthew,
> I personally have been recommending the first way. The primary reasons > are: Yes, meanwhile I prefer the first way as well. I guess the quick start should be changed, because at the moment it shows the second way for the action controller: http://zf2.readthedocs.org/en/latest/user-guide/database-and-models.html#using-servicemanager-to-configure-the-database-credentials-and-inject-into-the-controller Regards, Ralf -- List: [hidden email] Info: http://framework.zend.com/archives Unsubscribe: [hidden email] |
|
In reply to this post by robertbasic
@Robert:
array( 'controllers' => array( 'factories' => array( 'my_controller' => function (ServiceLocator $sl) { return new MyController($sl->get('a_dependency'), $sl->get('another_dependency')); }, ), ), ); This works also with Zend\Di for development purposes, as I've blogged at http://ocramius.github.com/blog/zend-framework-2-controllers-and-dependency-injection-with-zend-di/ Marco Pivetta http://twitter.com/Ocramius http://marco-pivetta.com On 18 September 2012 11:40, Robert Basic <[hidden email]> wrote: > On 17 September 2012 17:14, Matthew Weier O'Phinney <[hidden email]> > wrote: > > > > I personally have been recommending the first way. > > Any example for getting controllers via factories? I've been through > the docs, code, tests, examples, but fail to figure it out :( I can > get anything else to work via factories, but not controllers. The only > way I can get controllers is using the 'controllers' => array( > 'invokables' => array( ... )) stuff, but that doesn't allow me to > inject dependencies. > > -- > List: [hidden email] > Info: http://framework.zend.com/archives > Unsubscribe: [hidden email] > > > |
|
In reply to this post by robertbasic
2012/9/18 Robert Basic <[hidden email]>
> On 17 September 2012 17:14, Matthew Weier O'Phinney <[hidden email]> > wrote: > > > > I personally have been recommending the first way. > > Any example for getting controllers via factories? I've been through > the docs, code, tests, examples, but fail to figure it out :( I can > get anything else to work via factories, but not controllers. The only > way I can get controllers is using the 'controllers' => array( > 'invokables' => array( ... )) stuff, but that doesn't allow me to > inject dependencies. I agree with Matthew, I prefer injecting dependencies in my controllers too. If you have module class with a getControllerConfig() method, you could return factory configuration as well. I usually include the controller factories from a separate file: public function getControllerConfig() { return include __DIR__ . '/config/controller.config.php'; } Such controller.config.php can look like this: <?php use MyModule\Controller; return array( 'factories' => array( 'MyModule\Controller\FooController' => function($sm) { $service = $sm->getServiceLocator()->get('MyModule\Service\DependencyClass'); $controller = new Controller\FooController($service); return $controller; }, ), ); -- Jurian Sluiman |
|
Marco, Jurian,
thanks guys. Odd, I tried exactly that, and didn't work, and now it does. I'll just file this under the "did not get enough sleep last night" issues :( Thanks again. -- List: [hidden email] Info: http://framework.zend.com/archives Unsubscribe: [hidden email] |
|
In reply to this post by robertbasic
Hi Robert,
if you want to use a factory class you can do it like this: /module/User/config/module.config.php --------------------------------------------------------------------- 'controllers' => array( 'invokables' => array( 'user-admin' => 'User\Controller\AdminController', ), 'factories' => array( 'user' => 'User\Controller\UserControllerFactory', ), ), --------------------------------------------------------------------- /module/User/src/Controller/UserControllerFactory.php --------------------------------------------------------------------- namespace User\Controller; use Zend\ServiceManager\FactoryInterface; use Zend\ServiceManager\ServiceLocatorInterface; class UserControllerFactory implements FactoryInterface { public function createService( ServiceLocatorInterface $serviceLocator ) { $service = $serviceLocator->getServiceLocator() ->get('User\Service\User'); $controller = new UserController(); $controller->setUserService($service); return $controller; } } --------------------------------------------------------------------- My UserController could also use the constructor to accept dependencies. Regards, Ralf -- List: [hidden email] Info: http://framework.zend.com/archives Unsubscribe: [hidden email] |
|
In reply to this post by Jurian Sluiman-3
Hi,
the only problem I have with these solutions is about injecting form objects to a controller or service. I have about five forms for a controller and don't need all of them on every page. So injecting them in a factory is no solution. Matthew suggested to use the event manager to trigger them on the fly but I did not get that working. Any suggestions? Regards, Ralf -- List: [hidden email] Info: http://framework.zend.com/archives Unsubscribe: [hidden email] |
|
In reply to this post by robertbasic
On 18 September 2012 11:54, Robert Basic <[hidden email]> wrote:
> Marco, Jurian, > > thanks guys. Odd, I tried exactly that, and didn't work, and now it > does. I'll just file this under the "did not get enough sleep last > night" issues :( > > Thanks again. Think I had it enough for today with this... The error I was getting is that the controller could not be mapped, where as the real problem was that the service manager was throwing Zend\ServiceManager\Exception\ServiceNotFoundException -- ~Robert Basic; http://robertbasic.com/ -- List: [hidden email] Info: http://framework.zend.com/archives Unsubscribe: [hidden email] |
|
In reply to this post by Ralf Eggert
Hi again,
ok, forget it. I missed to implement this line: $eventManager->setIdentifiers(array(__CLASS__, get_called_class())); within the method setEventManager() in the class that implements the EventManagerAwareInterface. Regards, Ralf -- List: [hidden email] Info: http://framework.zend.com/archives Unsubscribe: [hidden email] |
|
Administrator
|
In reply to this post by robertbasic
-- Robert Basic <[hidden email]> wrote
(on Tuesday, 18 September 2012, 11:40 AM +0200): > On 17 September 2012 17:14, Matthew Weier O'Phinney <[hidden email]> wrote: > > > > I personally have been recommending the first way. > > Any example for getting controllers via factories? I've been through > the docs, code, tests, examples, but fail to figure it out :( I can > get anything else to work via factories, but not controllers. The only > way I can get controllers is using the 'controllers' => array( > 'invokables' => array( ... )) stuff, but that doesn't allow me to > inject dependencies. https://github.com/weierophinney/PhlyPeep/blob/master/src/PhlyPeep/Service/PeepControllerFactory.php The trick is remembering that if the service is managed by a plugin manager, you need to pull the app service locator instance from that plugin manager. So, in that example, you'll see that in the factory, I named the argument "controllers" -- that reminds me that this is managed by the ControllerManager, and I have to pull the app service locator from it -- which is what the first line of that factory does. Then I can pull deps from there, and inject them in my newly created controller. I do the same with view helpers in that project: https://github.com/weierophinney/PhlyPeep/blob/master/src/PhlyPeep/Service/PeepViewFormFactory.php Notice in that example I use the verbiage "helpers" to refer to the helper manager. -- Matthew Weier O'Phinney Project Lead | [hidden email] Zend Framework | http://framework.zend.com/ PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc -- List: [hidden email] Info: http://framework.zend.com/archives Unsubscribe: [hidden email] |
|
Administrator
|
In reply to this post by Ralf Eggert
-- Ralf Eggert <[hidden email]> wrote
(on Tuesday, 18 September 2012, 12:01 PM +0200): > the only problem I have with these solutions is about injecting form > objects to a controller or service. I have about five forms for a > controller and don't need all of them on every page. So injecting them > in a factory is no solution. Then you need to break your controller into multiple controllers, as it's managing too much. The rule of thumb I have is: more than 5-7 actions in a controller: refactor. More than 1-2 forms: refactor. Otherwise, the workflow of the controller becomes too difficult to follow easily. -- Matthew Weier O'Phinney Project Lead | [hidden email] Zend Framework | http://framework.zend.com/ PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc -- List: [hidden email] Info: http://framework.zend.com/archives Unsubscribe: [hidden email] |
|
Hi Matthew,
> Then you need to break your controller into multiple controllers, as > it's managing too much. > > The rule of thumb I have is: more than 5-7 actions in a controller: > refactor. More than 1-2 forms: refactor. Otherwise, the workflow of the > controller becomes too difficult to follow easily. Interesting stuff. I am thinking about this, but the rules of thumb might not fit on every case I think. Here is an example controller I use for managing users by an admin: --------------------------------------------------------------------- class AdminController { public function indexAction() { // uses select form } public function addAction() { // uses create form } public function updateAction() { // uses update form } public function deleteAction() { // uses delete form } } --------------------------------------------------------------------- These are 4 actions and 4 forms. Would you really split this up in more than one controller? Here is another controller I have --------------------------------------------------------------------- class UserController { public function indexAction() { } public function registerAction() { // uses create form } public function updateAction() { // uses update form } public function showAction() { } public function activateAction() { } public function loginAction() { // uses login form } public function logoutAction() { } public function passwordAction() { // uses password form } public function forbiddenAction() { } } --------------------------------------------------------------------- This controller has 9 actions which use 4 forms. Well, I could move loginAction(), logoutAction() and forbiddenAction() to an AuthController and maybe registerAction() and activateAction() to a RegistrationController. But does that really make sense? Regards, Ralf -- List: [hidden email] Info: http://framework.zend.com/archives Unsubscribe: [hidden email] |
|
Administrator
|
-- Ralf Eggert <[hidden email]> wrote
(on Tuesday, 18 September 2012, 04:29 PM +0200): > Hi Matthew, > > > Then you need to break your controller into multiple controllers, as > > it's managing too much. > > > > The rule of thumb I have is: more than 5-7 actions in a controller: > > refactor. More than 1-2 forms: refactor. Otherwise, the workflow of the > > controller becomes too difficult to follow easily. > > Interesting stuff. I am thinking about this, but the rules of thumb > might not fit on every case I think. > > Here is an example controller I use for managing users by an admin: > > --------------------------------------------------------------------- > class AdminController > { > public function indexAction() > { > // uses select form > } > > public function addAction() > { > // uses create form > } > > public function updateAction() > { > // uses update form > } > > public function deleteAction() > { > // uses delete form > } > } > --------------------------------------------------------------------- > > These are 4 actions and 4 forms. Would you really split this up in more > than one controller? Well, one question I have is: is it really 4 forms? or 1 form with 4 views? There's a difference. :) By using setValidationGroup(), you can have a single form representing a user, but validate different pieces of it based on the action. For the "add" action, you'd have one set of fields, for update, another, and for delete, likely simply the user identifier. Your view scripts would determine how much or little of the form needs to be displayed. As such, the above would fit into the criteria I outlined quite well > Here is another controller I have > > --------------------------------------------------------------------- > class UserController > { > public function indexAction() > { > } > > public function registerAction() > { > // uses create form > } > > public function updateAction() > { > // uses update form > } > > public function showAction() > { > } > > public function activateAction() > { > } > > public function loginAction() > { > // uses login form > } > > public function logoutAction() > { > } > > public function passwordAction() > { > // uses password form > } > > public function forbiddenAction() > { > } > } > --------------------------------------------------------------------- > > This controller has 9 actions which use 4 forms. Well, I could move > loginAction(), logoutAction() and forbiddenAction() to an AuthController > and maybe registerAction() and activateAction() to a > RegistrationController. Exactly -- and I'd do this, as this more semantically groups the actions. > But does that really make sense? Yes -- at least in my opinion. :) -- Matthew Weier O'Phinney Project Lead | [hidden email] Zend Framework | http://framework.zend.com/ PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc -- List: [hidden email] Info: http://framework.zend.com/archives Unsubscribe: [hidden email] |
|
In reply to this post by Ralf Eggert
2012/9/18 Ralf Eggert <[hidden email]>
> Interesting stuff. I am thinking about this, but the rules of thumb > might not fit on every case I think. > > Here is an example controller I use for managing users by an admin: > > --------------------------------------------------------------------- > class AdminController > { > public function indexAction() > { > // uses select form > } > > public function addAction() > { > // uses create form > } > > public function updateAction() > { > // uses update form > } > > public function deleteAction() > { > // uses delete form > } > } > --------------------------------------------------------------------- > > These are 4 actions and 4 forms. Would you really split this up in more > than one controller? > Usually I use 1 controller per entity. Most of the time I have an Index (list), View and CRUD (Create, Update, Delete) actions. For the View and Delete, a route param for the id is required. This also means I can delete the entity "Foo" without a form. You just POST to /my/foo/delete/12. If you want to have a CSRF check, use the FooForm with only the CSRF element in your validation group. The same holds for create and update: they exactly use the same form FooForm. The create is a POST to /my/foo/create and the update a POST to /my/foo/update/12. I see no need in using different forms for every action here :) -- Jurian Sluiman |
|
In reply to this post by weierophinney
Hi Matthew and Jurian,
> Well, one question I have is: is it really 4 forms? or 1 form with 4 > views? There's a difference. :) Well, after really thinking about it, currently there is only three forms. addAction and updateAction use the same saveForm with different validation groups. The deleteAction form is not really needed, so I can refactor that. Just the selectForm works different than the saveForm, since the email field takes incomplete emails in the selectForm for searching. > Exactly -- and I'd do this, as this more semantically groups the > actions. > >> But does that really make sense? > > Yes -- at least in my opinion. :) Ok, sounds reasonable. Regards, Ralf -- List: [hidden email] Info: http://framework.zend.com/archives Unsubscribe: [hidden email] |
|
In reply to this post by robertbasic
Very good conversation, helped me a lot, thx!
I always saw the controllers as not a part of the whole DI thing. But it makes sense :) -----Ursprüngliche Nachricht----- Von: Ralf Eggert [mailto:[hidden email]] Gesendet: Dienstag, 18. September 2012 20:32 An: [hidden email] Betreff: Re: [fw-general] [ZF2] Injecting objects to a controller, or getting objects from the service locator? Hi Matthew and Jurian, > Well, one question I have is: is it really 4 forms? or 1 form with 4 > views? There's a difference. :) Well, after really thinking about it, currently there is only three forms. addAction and updateAction use the same saveForm with different validation groups. The deleteAction form is not really needed, so I can refactor that. Just the selectForm works different than the saveForm, since the email field takes incomplete emails in the selectForm for searching. > Exactly -- and I'd do this, as this more semantically groups the > actions. > >> But does that really make sense? > > Yes -- at least in my opinion. :) Ok, sounds reasonable. Regards, Ralf -- List: [hidden email] Info: http://framework.zend.com/archives Unsubscribe: [hidden email] -- List: [hidden email] Info: http://framework.zend.com/archives Unsubscribe: [hidden email] |
|
In reply to this post by robertbasic
Hi Ralf,
i´m at that point now too. Did you inject all three forms? Because they are all constructed, how can I avoid that? Checking the route and inject only the correct one? Greetings Marc -----Ursprüngliche Nachricht----- Von: Ralf Eggert [mailto:[hidden email]] Gesendet: Dienstag, 18. September 2012 20:32 An: [hidden email] Betreff: Re: [fw-general] [ZF2] Injecting objects to a controller, or getting objects from the service locator? Hi Matthew and Jurian, > Well, one question I have is: is it really 4 forms? or 1 form with 4 > views? There's a difference. :) Well, after really thinking about it, currently there is only three forms. addAction and updateAction use the same saveForm with different validation groups. The deleteAction form is not really needed, so I can refactor that. Just the selectForm works different than the saveForm, since the email field takes incomplete emails in the selectForm for searching. > Exactly -- and I'd do this, as this more semantically groups the > actions. > >> But does that really make sense? > > Yes -- at least in my opinion. :) Ok, sounds reasonable. Regards, Ralf -- List: [hidden email] Info: http://framework.zend.com/archives Unsubscribe: [hidden email] -- List: [hidden email] Info: http://framework.zend.com/archives Unsubscribe: [hidden email] |
|
Hi Marc,
you can use the shared event manager like this (reduced to one form): -------------------------------------------------------------------------- /module/User/Module.php class Module { protected $serviceLocator = null; public function onBootstrap(EventInterface $e) { [...] $this->serviceLocator = $e->getApplication() ->getServiceManager(); $sharedEventManager = $this->serviceLocator ->get('SharedEventManager'); $sharedEventManager->attach( 'User\Service\User', 'set-user-register-form', array($this, 'onRegisterForm') ); } public function getServiceConfig() { return array( 'factories' => array( 'User\Form\UserRegister' => 'User\Form\UserRegister', 'User\Service\User' => 'User\Service\UserFactory', ), ); } public function onRegisterForm(EventInterface $e) { $service = $this->serviceLocator->get('User\Service\User'); $form = $this->serviceLocator->get('User\Form\UserRegister'); $service->setRegisterForm($form); } } -------------------------------------------------------------------------- In the onBootstrap() method I attach the event 'set-user-register-form' to my service 'User\Service\User'. When this event is triggered, then the onRegisterForm() method of the Module object should be called. The onRegisterForm() method just takes the user service and the form and calls the setRegisterForm() method of my user service. -------------------------------------------------------------------------- /module/User/src/User/Service/User.php class User implements EventManagerAwareInterface { protected $eventManager = null; protected $registerForm; public function setEventManager(EventManagerInterface $eventManager) { $eventManager->setIdentifiers(array(__CLASS__)); $this->eventManager = $eventManager; } public function getEventManager() { return $this->eventManager; } public function getRegisterForm() { if (null === $this->registerForm) { $this->getEventManager()->trigger('set-user-register-form'); $this->setMessage('user_message_info_user_add'); } return $this->registerForm; } public function setRegisterForm(UserSaveForm $form) { $this->registerForm = $form; } } -------------------------------------------------------------------------- With the implementation of EventManagerAwareInterface the Event Manager is injected to my user service automatically. When I call the getRegisterForm() method of my user service for the first time, the event 'set-user-register-form' is triggered. Now the event manager calls Module::onRegisterForm() which creates my register form and passes it to my user service. You can build this for all forms you have to just create form instances when you really need it. Regards, Ralf -- List: [hidden email] Info: http://framework.zend.com/archives Unsubscribe: [hidden email] |
| Powered by Nabble | Edit this page |
