|
I’m trying to figure out a problem where ZF2 is rendering my templates twice even though it is only displaying them once. This duplicate processing is causing some weird, subtle side effects in my code. From a bit of digging, I eventually figured out that Zend\Mvc\View\Http\InjectViewModelListener::injectViewModel() was getting called twice, once from inside the forward() controller plugin, and once from outside it. I’m beginning to wonder if perhaps I’m misunderstanding the purpose of the forward() plugin. I thought this was basically the equivalent of a redirect, but without requiring an extra HTTP request on the part of the user. I’ve been using
it like this: // Not logged in? Force user to log in: if (!$this->getAuthManager()->isLoggedIn()) { return $this->forward() ->dispatch('MyResearch', array('action' => 'Login')); } In general, it seems to work… except for this double-rendering thing. Am I abusing the forward plug-in, or does it have some kind of bug that is causing unnecessary duplicate rendering? (Obviously, a third possibility is that some other
event set up by my application is interacting badly with the plug-in… though I don’t
think that’s the case). - Demian |
|
Administrator
|
-- Demian Katz <[hidden email]> wrote
(on Thursday, 02 August 2012, 07:30 PM +0000): > I’m trying to figure out a problem where ZF2 is rendering my templates twice > even though it is only displaying them once. This duplicate processing is > causing some weird, subtle side effects in my code. > > From a bit of digging, I eventually figured out that Zend\Mvc\View\Http\ > InjectViewModelListener::injectViewModel() was getting called twice, once from > inside the forward() controller plugin, and once from outside it. > > I’m beginning to wonder if perhaps I’m misunderstanding the purpose of the > forward() plugin. I thought this was basically the equivalent of a redirect, > but without requiring an extra HTTP request on the part of the user. I’ve been > using it like this: > > // Not logged in? Force user to log in: > > if (!$this->getAuthManager()->isLoggedIn()) { > return $this->forward() > ->dispatch('MyResearch', array('action' => 'Login')); > } > > In general, it seems to work… except for this double-rendering thing. Am I > abusing the forward plug-in, or does it have some kind of bug that is causing > unnecessary duplicate rendering? (Obviously, a third possibility is that some > other event set up by my application is interacting badly with the plug-in… > though I don’t think that’s the case). I'd argue it's likely the latter -- some event is interacting badly. Rendering happens after dispatch -- which is after forward() occurs. So, at most, you end up with multiple view models attached to your layout, or a view model with information from two actions (depending on what you return). -- 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 |
|
This post has NOT been accepted by the mailing list yet.
In reply to this post by demiankatz
I have the same issue, for some reason my meta tags rendered twice |
|
In reply to this post by weierophinney
> I'd argue it's likely the latter -- some event is interacting badly.
> > Rendering happens after dispatch -- which is after forward() occurs. So, > at most, you end up with multiple view models attached to your layout, > or a view model with information from two actions (depending on what you > return). As you say, the problem was that I ended up with multiple view models attached to my layout... and then both of them got rendered, which caused weird side effects. I've written a blog post about my problem in case anyone is interested: http://blog.library.villanova.edu/libtech/2012/08/13/moving-vufind-to-zend-framework-2-part-5-the-dreaded-forward-bug/ The short version: to forward from one action to another, DO NOT DO THIS: return $this->forward()->dispatch('controllerName', array('action' => 'actionName')); Instead, DO THIS: $this->forward()->dispatch('controllerName', array('action' => 'actionName')); return false; I'm sure I'm not the only person who will make this mistake -- the ZF1 habit of calling "return $this->_forward(...)" makes it easy to assume that simply returning the output of dispatch() will work correctly. I suggest that at least one of two things is necessary: 1.) The documentation should explicitly address this use case. 2.) A convenience method should be added to the forward() plug-in that calls dispatch and returns false, making it easier to do a one-line forward where appropriate (perhaps $this->forward()->toController() or something similar). I can spend a little time on either or both of these, but I'm interested to hear other people's thoughts first. Also, once again, thanks to mpinkston on #zftalk.2 for putting in a lot of time to help me sort this out. - Demian |
|
Administrator
|
-- Demian Katz <[hidden email]> wrote
(on Monday, 13 August 2012, 08:04 PM +0000): > > I'd argue it's likely the latter -- some event is interacting badly. > > > > Rendering happens after dispatch -- which is after forward() occurs. So, > > at most, you end up with multiple view models attached to your layout, > > or a view model with information from two actions (depending on what you > > return). > > As you say, the problem was that I ended up with multiple view models > attached to my layout... and then both of them got rendered, which > caused weird side effects. I've written a blog post about my problem > in case anyone is interested: > > http://blog.library.villanova.edu/libtech/2012/08/13/moving-vufind-to-zend-framework-2-part-5-the-dreaded-forward-bug/ > > The short version: to forward from one action to another, DO NOT DO THIS: > > return $this->forward()->dispatch('controllerName', array('action' => 'actionName')); > > Instead, DO THIS: > > $this->forward()->dispatch('controllerName', array('action' => 'actionName')); > return false; > > I'm sure I'm not the only person who will make this mistake -- the ZF1 > habit of calling "return $this->_forward(...)" makes it easy to assume > that simply returning the output of dispatch() will work correctly. > > I suggest that at least one of two things is necessary: > > 1.) The documentation should explicitly address this use case. > > 2.) A convenience method should be added to the forward() plug-in that calls dispatch and returns false, making it easier to do a one-line forward where appropriate (perhaps $this->forward()->toController() or something similar). > > I can spend a little time on either or both of these, but I'm > interested to hear other people's thoughts first. I think having forward() returning null or false makes sense. -- 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 |
|
> I think having forward() returning null or false makes sense.
For my use case, it definitely makes more sense for forward()->dispatch() to return null or false. However, the example in the documentation (http://packages.zendframework.com/docs/latest/manual/en/modules/zend.mvc.plugins.html) suggests that a use case exists where you might actually want to work with the return value: $foo = $this->forward()->dispatch('foo', array('action' => 'process')); return array( 'somekey' => $somevalue, 'foo' => $foo, ); I'm not sure of the best way to address both of these possible situations without causing confusion. For my part, I just created a convenience function in my base controller class that calls dispatch() and returns false. For my own needs, that works well enough for now. - Demian |
|
Demian,
Have you tried adding the result of a forward as a child of a ViewModel? $foo = $this->forward()->dispatch('foo', array('action' => 'process')); $viewModel = new ViewModel(); $viewModel->setVariable('somekey', $somevalue); $viewModel->addChild($foo, 'foo'); return $viewModel; -Nick On Wed, Aug 15, 2012 at 9:12 AM, Demian Katz <[hidden email]> wrote:
|
|
Nick, I’m not sure why I would want to do this. Calling $this->forward()->dispatch() automatically causes the dispatched action’s output to get attached to the layout.
Attaching a copy as a child of a view model seems unnecessary. The problem that started this whole conversation is that when I used the return value of dispatch(), it caused all of my templates to get processed twice (once for the automatically attached copy,
and once for the copy attached when I returned a value). Sorry if I’m missing the point of your question.
J - Demian From: Nicholas Calugar [mailto:[hidden email]]
Demian, On Wed, Aug 15, 2012 at 9:12 AM, Demian Katz <[hidden email]> wrote: > I think having forward() returning null or false makes sense. For my use case, it definitely makes more sense for forward()->dispatch() to return null or false. However, the example in the documentation (http://packages.zendframework.com/docs/latest/manual/en/modules/zend.mvc.plugins.html)
suggests that a use case exists where you might actually want to work with the return value: |
|
Sorry, did not understand the question. Reason I asked is I have a custom plugin that can add the result of a forward as a child of the action's view model. The part I forgot was that when it wraps the forward call, it detaches the InjectViewModelListener before forwarding and reattaches after.
-Nick On Wed, Aug 15, 2012 at 11:56 AM, Demian Katz <[hidden email]> wrote:
|
|
Administrator
|
-- Nicholas Calugar <[hidden email]> wrote
(on Wednesday, 15 August 2012, 01:17 PM -0700): > Sorry, did not understand the question. Reason I asked is I have a custom > plugin that can add the result of a forward as a child of the action's view > model. The part I forgot was that when it wraps the forward call, it detaches > the InjectViewModelListener before forwarding and reattaches after. This may be the more appropriate route, actually -- having the forward() plugin detach that listener and re-attach on completion. > On Wed, Aug 15, 2012 at 11:56 AM, Demian Katz <[hidden email]> > wrote: > > > Nick, > > > > I’m not sure why I would want to do this. Calling $this->forward()-> > dispatch() automatically causes the dispatched action’s output to get > attached to the layout. Attaching a copy as a child of a view model seems > unnecessary. The problem that started this whole conversation is that when > I used the return value of dispatch(), it caused all of my templates to get > processed twice (once for the automatically attached copy, and once for the > copy attached when I returned a value). > > > > Sorry if I’m missing the point of your question. J > > > > - Demian > > > > From: Nicholas Calugar [mailto:[hidden email]] > Sent: Wednesday, August 15, 2012 1:05 PM > To: Demian Katz > Cc: Matthew Weier O'Phinney; [hidden email] > Subject: Re: [zf-contributors] Double-rendering with forward() plugin? > > > > Demian, > > Have you tried adding the result of a forward as a child of a ViewModel? > > $foo = $this->forward()->dispatch('foo', array('action' => 'process')); > $viewModel = new ViewModel(); > $viewModel->setVariable('somekey', $somevalue); > $viewModel->addChild($foo, 'foo'); > return $viewModel; > > > -Nick > > > On Wed, Aug 15, 2012 at 9:12 AM, Demian Katz <[hidden email]> > wrote: > > > I think having forward() returning null or false makes sense. > > For my use case, it definitely makes more sense for forward()->dispatch() > to return null or false. However, the example in the documentation (http:/ > /packages.zendframework.com/docs/latest/manual/en/modules/ > zend.mvc.plugins.html) suggests that a use case exists where you might > actually want to work with the return value: > > $foo = $this->forward()->dispatch('foo', array('action' => 'process')); > return array( > 'somekey' => $somevalue, > 'foo' => $foo, > ); > > I'm not sure of the best way to address both of these possible situations > without causing confusion. For my part, I just created a convenience > function in my base controller class that calls dispatch() and returns > false. For my own needs, that works well enough for now. > > - Demian > > > > -- 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 |
|
In reply to this post by demiankatz
On 8/13/12 1:04 PM, Demian Katz wrote:
> The short version: to forward from one action to another, DO NOT DO THIS: > > return $this->forward()->dispatch('controllerName', array('action' => 'actionName')); > > Instead, DO THIS: > > $this->forward()->dispatch('controllerName', array('action' => 'actionName')); > return false; Thanks Demian, this is quite helpful! On a related note, are you able to forward to a controller class name? I am only able to forward to a controller class alias. I am trying to do something like this: $this->forward()->dispatch(__CLASS__, array('action' => 'login')); According to the docs, it should be possible to forward to a fully qualified class name, but this isn't working for me. Cheers, Stew |
|
> On 8/13/12 1:04 PM, Demian Katz wrote:
> > The short version: to forward from one action to another, DO NOT DO THIS: > > > > return $this->forward()->dispatch('controllerName', array('action' => > 'actionName')); > > > > Instead, DO THIS: > > > > $this->forward()->dispatch('controllerName', array('action' => > 'actionName')); > > return false; > > Thanks Demian, this is quite helpful! Glad you found it useful... but now, an important update: I submitted a PR to solve the double-rendering problem (https://github.com/zendframework/zf2/pull/2206) and it has been accepted, so I assume it will be part of RC5. Once that happens, the forward() plug-in will behave more like you would expect it to... you will want to start returning the output of $this->forward()->dispatch() instead of returning false. It's a small BC break between RC4 and RC5, but hopefully it will save a lot of people from getting confused in the future. > On a related note, are you able to > forward to a controller class name? I am only able to forward to a > controller class alias. I am trying to do something like this: > > $this->forward()->dispatch(__CLASS__, array('action' => 'login')); > > According to the docs, it should be possible to forward to a fully > qualified class name, but this isn't working for me. I've only ever tried forwarding to an alias in the past. I just did a quick test to see if a FQCN would work, and it failed for me as well -- so at least you're not alone! I'm not sure if this is a documentation error or a bug, though. It looks like the forward plug-in is just using the service locator to find the controller; I haven't spent much time learning about service locators yet, so I'm not sure how they're supposed to deal with FQCNs. Maybe someone with more experience in this area can comment. - Demian |
|
Administrator
|
-- Demian Katz <[hidden email]> wrote
(on Wednesday, 22 August 2012, 12:34 PM +0000): > > On 8/13/12 1:04 PM, Demian Katz wrote: > > > The short version: to forward from one action to another, DO NOT DO THIS: > > > > > > return $this->forward()->dispatch('controllerName', array('action' => > > 'actionName')); > > > > > > Instead, DO THIS: > > > > > > $this->forward()->dispatch('controllerName', array('action' => > > 'actionName')); > > > return false; > > > > Thanks Demian, this is quite helpful! > > Glad you found it useful... but now, an important update: I submitted > a PR to solve the double-rendering problem > (https://github.com/zendframework/zf2/pull/2206) and it has been > accepted, so I assume it will be part of RC5. Once that happens, the > forward() plug-in will behave more like you would expect it to... you > will want to start returning the output of > $this->forward()->dispatch() instead of returning false. It's a small > BC break between RC4 and RC5, but hopefully it will save a lot of > people from getting confused in the future. > > > On a related note, are you able to > > forward to a controller class name? I am only able to forward to a > > controller class alias. I am trying to do something like this: > > > > $this->forward()->dispatch(__CLASS__, array('action' => 'login')); > > > > According to the docs, it should be possible to forward to a fully > > qualified class name, but this isn't working for me. > > I've only ever tried forwarding to an alias in the past. I just did a > quick test to see if a FQCN would work, and it failed for me as well > -- so at least you're not alone! I'm not sure if this is a > documentation error or a bug, though. It looks like the forward > plug-in is just using the service locator to find the controller; Indeed. And part of how the ControllerLoader works is that it will only load controllers it knows about -- i.e. those defined in its service configuration. As such, usage of FQCN does not work, unless you've defined an alias. -- 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 |
|
> > I've only ever tried forwarding to an alias in the past. I just did a
> > quick test to see if a FQCN would work, and it failed for me as well > > -- so at least you're not alone! I'm not sure if this is a > > documentation error or a bug, though. It looks like the forward > > plug-in is just using the service locator to find the controller; > > Indeed. And part of how the ControllerLoader works is that it will only > load controllers it knows about -- i.e. those defined in its service > configuration. As such, usage of FQCN does not work, unless you've > defined an alias. So should we update the documentation, or should the plug-in be supported to allow arbitrary controllers? (I'm thinking update the documentation is probably better, though less flexible). - Demian |
|
On 8/22/12 7:56 AM, Demian Katz wrote:
>>> I've only ever tried forwarding to an alias in the past. I just did a >>> quick test to see if a FQCN would work, and it failed for me as well >>> -- so at least you're not alone! I'm not sure if this is a >>> documentation error or a bug, though. It looks like the forward >>> plug-in is just using the service locator to find the controller; >> >> Indeed. And part of how the ControllerLoader works is that it will only >> load controllers it knows about -- i.e. those defined in its service >> configuration. As such, usage of FQCN does not work, unless you've >> defined an alias. > > So should we update the documentation, or should the plug-in be supported to allow arbitrary controllers? > > (I'm thinking update the documentation is probably better, though less flexible). > > - Demian > Note that both the manual and the API docs would need updating. When redirecting to another action in the same controller, being able to use __CLASS__ (or perhaps just null) for the first arg would be nice. Stew |
|
> Note that both the manual and the API docs would need updating.
> > When redirecting to another action in the same controller, being able to > use __CLASS__ (or perhaps just null) for the first arg would be nice. I agree that it would be nice to be able to specify "current controller" without having to explicitly reference the alias over and over... though I can live without it if need be. Also, should the service manager be smart enough to recognize FQCNs that are set up as aliases in the invokables array? That is, if I have: 'controllers' => array( 'invokables' => array( 'admin' => 'VuFind\Controller\AdminController', ) ) Should $sm->get('admin') and $sm->get('VuFind\Controller\AdminController') return the same thing? In this particular use case, it would certainly be convenient, and it doesn't seem like something that would be hard to implement... but I suspect that there are other use cases that make it undesirable (both because of potential clashes between aliases and FQCNs, and also because of the other ways service locators can be set up -- not everything is an invokable). Anyway, my understanding of this area of code is still hazy, so apologies if I'm talking nonsense. :-) - Demian |
|
Administrator
|
In reply to this post by Stewart Lord
-- Stewart Lord <[hidden email]> wrote
(on Wednesday, 22 August 2012, 08:35 AM -0700): > On 8/22/12 7:56 AM, Demian Katz wrote: > >>>I've only ever tried forwarding to an alias in the past. I just did a > >>>quick test to see if a FQCN would work, and it failed for me as well > >>>-- so at least you're not alone! I'm not sure if this is a > >>>documentation error or a bug, though. It looks like the forward > >>>plug-in is just using the service locator to find the controller; > >> > >>Indeed. And part of how the ControllerLoader works is that it will only > >>load controllers it knows about -- i.e. those defined in its service > >>configuration. As such, usage of FQCN does not work, unless you've > >>defined an alias. > > > >So should we update the documentation, or should the plug-in be supported to allow arbitrary controllers? > > > >(I'm thinking update the documentation is probably better, though less flexible). > > > >- Demian > > > > > Note that both the manual and the API docs would need updating. > > When redirecting to another action in the same controller, being > able to use __CLASS__ (or perhaps just null) for the first arg would > be nice. Why would you do a forward() in this instance, instead of simply calling the method itself: public function someAction() { // do some work // invoke another action in this controller $this->otherAction(); // do more work } Just curious... -- 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 |
|
Administrator
|
In reply to this post by demiankatz
-- Demian Katz <[hidden email]> wrote
(on Wednesday, 22 August 2012, 03:50 PM +0000): > > Note that both the manual and the API docs would need updating. > > > > When redirecting to another action in the same controller, being able to > > use __CLASS__ (or perhaps just null) for the first arg would be nice. > > I agree that it would be nice to be able to specify "current > controller" without having to explicitly reference the alias over and > over... though I can live without it if need be. > > Also, should the service manager be smart enough to recognize FQCNs > that are set up as aliases in the invokables array? That is, if I > have: > > 'controllers' => array( > 'invokables' => array( > 'admin' => 'VuFind\Controller\AdminController', > ) > ) > > Should $sm->get('admin') and $sm->get('VuFind\Controller\AdminController') return the same thing? > > In this particular use case, it would certainly be convenient, and it > doesn't seem like something that would be hard to implement... but I > suspect that there are other use cases that make it undesirable (both > because of potential clashes between aliases and FQCNs, and also > because of the other ways service locators can be set up -- not > everything is an invokable). We made an explicit choice _not_ to allow arbitrary class names: https://github.com/zendframework/zf2/blob/master/library/Zend/Mvc/Controller/ControllerManager.php#L32-37 The reason for this is security. A lot of folks want to specify the controller to use via the command line, and typically provide no constraints on the value: '/foo/:controller/:action' As such, if we allowed the ControllerManager to use FQCN as invokables (which is what the linked setting above is for), we'd basically allow a somebody with knowledge of the system to invoke a class that had side effects in the constructor. While it seems like a remote possibility, it's still very real, and we felt it was better to err on the side of caution. As such, controllers must be registered explicitly with the ControllerManager -- as a service, invokable, factory, or via abstract factory. You can alias a controller's FQCN to a service name, or use the FQCN as the service name, if you want to retrieve it as such. -- 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 |
|
In reply to this post by weierophinney
On 12-08-22 9:17 AM, Matthew Weier O'Phinney wrote: > Why would you do a forward() in this instance, instead of simply calling > the method itself: > > public function someAction() > { > // do some work > > // invoke another action in this controller > $this->otherAction(); > > // do more work > } > > Just curious... Mainly because I didn't know that was a viable option. I (mistakenly?) assumed I would be missing out on dispatch events and other magic. One legit reason might be to pass additional params. Stew |
|
Administrator
|
-- Stewart Lord <[hidden email]> wrote
(on Wednesday, 22 August 2012, 10:16 AM -0700): > On 12-08-22 9:17 AM, Matthew Weier O'Phinney wrote: > > Why would you do a forward() in this instance, instead of simply calling > > the method itself: > > > > public function someAction() > > { > > // do some work > > > > // invoke another action in this controller > > $this-> otherAction(); > > > > // do more work > > } > > > > Just curious... > > > Mainly because I didn't know that was a viable option. I > (mistakenly?) assumed I would be missing out on dispatch events and > other magic. > > One legit reason might be to pass additional params. I was just curious. :) I typically rarely forward(), and if the action is in the same controller, usually just call it directly. (This is true for me in both ZF1 and ZF2.). I was just curious if there were parts of the dispatch cycle that would vary for you enough to warrant the forward() call. -- 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 |
| Powered by Nabble | Edit this page |
