I would like someone to explain me what _forward is exactly doing, I cannot see if _forward is also rendering the attached view to the action or just executing the action.
Also is it possible to pass argument to $this->action in a view script ?
More generally my problem is how to code a confirmation page, let's say the user input some stuff and you want to show him confirmation, is forward is mean for that case ?
_forward is an internal redirect. Where as _redirect sends a header that tells the client's browser to go to some other URL, _forward tells the Dispatcher to internally redirect the request somewhere else.
If you consider the normal dispatch order of:
preDispatch()
someAction()
postDispatch()
Calling _forward at any point in that progression will cause the following steps to not be executed. So if you call _forward in preDispatch(), someAction() will not be called and so on. If you _forward() in someAction() and you are using the viewRenderer action helper to render your views (you are letting the framework choose what view script to render), then no view script will be rendered in someAction().
When the request is forwarded to the new Controller / Module the entire dispatch process will be repeated there.
You can find out what action is being dispatched by using:
$action = $this->getRequest()->getParam('action');
$action will be the url form of the action so if the method is name 'someKindOfAction', $action will contain 'some-kind-of'. You can do this as well for controllers and modules.
My experience with Zend is limited and I hope I'm not showing you something you've already seen but according to the docs (12.7.6. Utility Methods):
_forward($action, $controller = null, $module = null, array $params = null): perform another action. If called in preDispatch(), the currently requested action will be skipped in favor of the new one. Otherwise, after the current action is processed, the action requested in _forward() will be executed.
So it sounds like the context of when it's called matters. In the latter case it will first execute the action from which it's been called then execute the forwarded action. The exception is when it's being called from the preDispatch handler
I think it's important to note that _forward is very inefficient, and you should always call your method directly. When you do a _forward, the init(), pre and post dispatch run again. Depending on what you have in your init, you can run (and insert) the same database record twice.
It is easy to use but wasteful. If you profile your code, and are banging your head to why everything is being called twice, _forward is the reason. If your like me and you instantiate a few objects in the init() for use throughout the class, you wind up instantiating everything twice! I did load testing on my code and I got better performance by calling the action name directly, like foo(), instead of _forward('foo');
Another off topic tip I think most people know, is it use single quotes wherever possible, sine the PHP parser has to check a string for embedded variables. I don't know how much real world performance this will give, especially if you are using an opcode cache, but it's a best practice.
Forward is ment to be used when external redirect is not the right options.
Use case (bit ankward, but best i can make up):
You have a form that can add your pet (either dog or cat). You have different models for each. You include a select in your form to select dog / cat. Then in your action you do:
if($form->isValid($_POST)){
switch($form->select->getValue()){
case "dog":
$this->_forward('add-dog','pets','default');
break;
case "cat":
$this->_forward('add-cat','pets','default');
break;
}
}
And you handle different things for cats and dogs in separate actions. The advantage of this is that ALL the parameters are sent along. In constrast when you'd used $this->_redirect() the POST parameters will be lost. That is in some cases intended behaviour (for example after adding a comment you make a redirect to comments list page to avoid double posts and the message "page needs to send data again...".
A part of the Framework docs I swear used to be there explained the dispatch workflow at a general level. Theres this diagram, but its awefully complicated to explain what _forward does.
When in an action _forward will set $request->isDispatched = false, and set up the request to call the controller/action specified in _forward. During postDispatch, the isDispatched is checked - if its false, the whole thing runs again using the new request.
So... If in your action you're manually rendering views, they'll still get rendered. Everything in the action will still happen, its just another action will ALSO happen afterwards.
[edit after question edit]
Forward is not meant for the response/confirm-after-post - use a redirect for that. $this->_helper->redirector->gotoUrl() etc.
Related
I'd like to save a log access in my database with all links that the user visits.
The problem is that I can't save when is ajax request because I don't visit the link, for example when I delete a register, it's made by ajax call.
How can I globally detect when is the ajax call and save in my database?
My code to save the normal page registers (url that I visit) is:
$ip = CHttpRequest::getUserHostAddress(); // get ip
$id_user = Yii::app()->user->getId(); //get user
$url = Yii::app()->request->requestUri; //get url
$sql = "INSERT INTO log(id_user, date, hour, url, ip) VALUES (:id_user, 'now', 'now', :url, :ip)";
$rawData = Yii::app()->db->createCommand($sql)->queryAll(true, array(':id_user'=>$id_user,':url'=>$url,':ip'=>$ip));
If you override the beforeAction in your base controller (you are extending CController?) and put your code in it, it will be run on every action
If you want to differentiate if it's an ajax request you can use
Yii::app()->request->isAjaxRequest
http://www.yiiframework.com/doc/api/1.1/CHttpRequest#isAjaxRequest-detail
As mentioned in my comment and Rowan's answer, this is likely easiest to do by extending your base controller.
In the testdrive sample app, you'll notice there's a protected/components/Controller.php. If you look at the source, you'll see that that extends CController.
You'll also notice that the controllers at protected/controllers extend Controller, meaning that they'll include everything that Controller has, which by definition has everything that CController has.
So, the advice is to follows this idiom, and then in protected/components/Controller.php, override beforeAction(), which by default does nothing except return true. So in protected/components/Controller.php, just redefine beforeAction() to include your logging statements and any logic necessary.
Oh, and in response to your comment that "no url is visited", you do realize that an AJAX call is still visiting a URL of some form right? Namely the one specified via the url key? So if you really wanted, you could add the logging to whatever url that your AJAX is calling, but this is going to quickly become cumbersome, and you'll be violating the DRY principle.
I am currently reading "Zend Framework 1.8
Web Application Development" written by "Keith Pope". In that he tells us to use 'ActionStack' so that the Controller for the category top-level menu will be called on every request. The source code for the the plugin is :
class SF_Plugin_Action extends Zend_Controller_Plugin_Abstract
{
protected $_stack;
public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request)
{
$stack = $this->getStack();
// category menu
$categoryRequest = new Zend_Controller_Request_Simple();
$categoryRequest->setControllerName('category')
->setActionName('index')
->setParam(
'responseSegment',
'categoryMain'
);
// push requests into the stack
$stack->pushStack($categoryRequest);
}
public function getStack()
{
if (null === $this->_stack) {
$front = Zend_Controller_Front::getInstance();
if (!$front->hasPlugin(
'Zend_Controller_Plugin_ActionStack'
)) {
$stack = new Zend_Controller_Plugin_ActionStack();
$front->registerPlugin($stack);
} else {
$stack = $front->getPlugin('ActionStack');
}
$this->_stack = $stack;
}
return $this->_stack;
}
}
I have read the code for 'ActionStack' plugin. In the 'postDispatch' function it saves the current request and then in the 'forward' function it changes the current request's controller, action and also set parameters. Then what will happen to the current request ? How it will be executed ?
Also I heard ActionStack is evil. As I am a newbie I didn't understand most of it, as he did not explained it(for newbies). Why ActionStack is evil ?
ActionStack is evil as it promotes a bad practice: tying view-related logic to controllers. Additionally, it has a huge, negative impact on performance.
Typically, ActionStack is used to develop "widgetized" sites. You set up a list of widgets you need, and map them to individual controller actions, and then loop through the stack. The design flaw with this is that you're now executing N different controllers -- when, really, ONE controller is all you should use. Each individual controller should be able to map the incoming request to the necessary view and models. Instead, you're now basically executing an MVC triad simply to get back a bit of content.
The performance implications come from the fact that you now have to store the previous results in memory, and then re-dispatch -- which means running all pre/post dispatch plugins again, potentially having conflicts in state, and more.
The better approach is to have model-aware view helpers. You can use action helpers to marshal the appropriate models and inject the helpers, and then in your view scripts and/or layout, you simply invoke them. This honors an appropriate separation of concerns, and does not have the same performance implications.
(In ZF2, this marshaling is far easier, as you can define factories for individual helpers -- as such, you can simply use them in your view scripts, and not have to do anything special in the controllers at all in order to deliver widgetized content.)
It is the answer to my first question. As the action stack will be executed last (in the post dispatch) the current response object will be holding all content that got rendered for the request the user made, and the action stack will append the data to it. Hence the user will get content that he asked for + content that got rendered due to action stack
In your example. The front controller will start the execution of the current request and fire the routeStartup, routeShutdown and dispatchLoopStartup events. The dispatchLoopStartup event will call your plugin and your plugin will add a request object to the action stack.
Now the front controller will dispatch the current request, set the isDispatched flag of the current request to true and fire the postDispatch event. Now the Action stack plugin will get called. The front controller will pass the current request object to the Action Stack plugin as an argument and the Action stack plugin will update the controller, module, action properties of the current request object and set its isDispatched flag to false (Forward method).
Now the front controller will check the isDispatched flag of the current request object and since it was reset by the Action Stack plugin start the dispatch process again. And now your new request will get executed.
In short the front controller dispatches the current request, the action stack plugin resets the values of current request and the dispatch loop stars again.
symfony 1.4 passing variables between templates and actions
I've got an index page which includes a call to a series of partials through a switch statement; and it works. I now need to restrict access to the partial dependent upon the user's type; furthermore, I believe my switch statement should be in the actions class according to MVC, but I can't get that to work either. This might be better explained through example:
Here's my file structure for the dashboard module:
..dashboard
..actions
..config
..templates
_admins.php
_employers.php
_employees.php
_guest.php
indexSuccess.php
Here is my current indexSuccess template (which currently works... but without restricting access if the logged user's type doesn't match the page type):
$type = sfContext::getInstance()->getUser()->getGuardUser()->getProfile()->getType()->getName();
switch($type)
{
case ('Employer'):
include_partial('dashboard/employers');
$page_user_type = "employer"; //this example line currently does not exist, it's for example purpose below
$break;
case ('Employee'):
include_partial('dashboard/employees');
break;
case ('Administrator'):
include_partial('dashboard/admins');
break;
default: include_partial('dashboard/guest');
break;
}
Here's my actions class (currently empty):
public function executeIndex(sfWebRequest $request)
{
}
Basically, what I need is the switch statement moved to the action (I think), and a forward404Unless() method added that does the following:
$logged_user = sfContext::getInstance()->getUser()->getGuardUser()->getId();
$this->forward404Unless($logged_user == $page_user_type); //where the $page_user_type variable is retrieved by the switch statement in the example line above.
I've tried using the getAttribute() and setAttribute() with no success... and I'd rather not share attempts due to embarrassment. Just a beginner here...
Any help would be appreciated. Thanks in advance.
UPDATE:
Here's more information about the switch and the different partials:
The switch renders a different partial based upon the user's type. What it doesn't do is keep other logged-in users of a different type from accessing all the other partials... which in my design, is very bad. For example: logged-in users of type "employer" may not view the partial of type "employee". Currently they can (by explicitly typing in the other url), even though they are being redirected to the appropriate page during the the index action.
The 404 page should be called when a user of the wrong type tries to access the other partial by explicitly typing in the url. That's why I was attempting to add a variable to the switch statment when the appropriate partial is called and then passing that variable to the index action which would then evaluate it and either permit the partial to be rendered, or if the user_type and partial_type did not match -> forward to a 404 page. Make sense? I hope I explained that thouroughly enough. I'm sure there is an easier way... I'm just not schooled enough to know what that might be.
I sure do appreciate your response and attempt to resolve my issue.
You should play with the credential system to block not authorized user to access a ressource.
The 'type' of your user can become the name of a credential. Then you just have to create the security.yml to handle that.
I'm having a little trouble understanding when the 404 should happen. Does this handle it?
Action:
public function executeIndex(sfWebRequest $request)
{
$this->profileType = $this->getUser()->getGuardUser()->getProfile()->getType()->getName();
$this->forward404Unless(in_array($this->profileType, array('type1', 'type2')), 'Invalid profile type');
}
It's perfectly acceptable to have a switch statement in a veiw, though if that is the entirety of indexSuccess.php you may wish to call sfAction::setTemplate, instead.
Okay, I figured this one out on my own. Here's what I did to get the desired result:
Changed the route so that it cannot be explicitly typed and accessed. Problem solved.
Why do controller action predispatch events not fire if the controller is rewritten? Here is a snippet of store/app/code/core/Mage/Core/Controller/Varien/Action.php:
abstract class Mage_Core_Controller_Varien_Action
{
// [...]
public function preDispatch()
{
// [...]
if ($this->_rewrite()) {
return; // [What is the purpose if this?]
}
// [...]
// [This is where my event needs to be firing, but this code never gets
// executed because the controller is rewritten]
Mage::dispatchEvent(
'controller_action_predispatch_'.$this->getFullActionName(),
array('controller_action'=>$this)
);
}
// [...]
}
I don't know where to start fixing this problem. Anyone out there ever dealt with this before?
No time to test if the the behavior you're describing is accurate, but if it is I imagine it's what happens in the _rewrite function duplicates the actions of other non-event code after that call, and allowing preDispatch to continue after the rewrite would have made "bad things" happen.
In other words, it's a bug in the implementation of controller re-writing that's been ignored, because the preferred way of handling this is now at the routing level. In general, when a system level bug like this makes it into Magento, it tends to stay there, because cart owners start to rely on the broken behavior and scream loudly when anything changes, even if it's a bug fix.
If you can't re-factor your solution as described in the link above, you can still fire the event yourself in the controller class with old fashion Object Oriented Programming. Add the following to your custom controller (the one you're rewriting to)
protected function _rewrite()
{
//call the parent rewrite method so every that needs
//to happen happens
$original_result = parent::_rewrite();
//fire the event ourselve, since magento isn't firing it
Mage::dispatchEvent(
'controller_action_predispatch_'.$this->getFullActionName(),
array('controller_action'=>$this)
);
//return the original result in case another method is relying on it
return $original_result;
}
Hey, I'm not sure (as I'm not very familiar with the inner workings of Magento), but it occurs to me that _rewrite() checks whether a call to this specific action is being redirected (rewritten in a mod_rewrite kind of way) to a different controller/action. In this light, it would make sense that events for the original action would not be fired, since the whole request gets handled by a different action.
Rather than using controller/action/key1/value1/key2/value2 as my URL, I'd like to use controller/action/value1/value2. I think I could do this by defining a custom route in my Bootstrap class, but I want my entire application to behave this way, so adding a custom route for each action is out of the question.
Is this possible? If so, how would I then access valueN? I'd like to be able to define the parameters in my action method's signature. e.x.:
// PostsController.php
public function view($postID) {
echo 'post ID: ' . $postID;
}
I'm using Zend Framework 1.9.3
Thanks!
While I don't think it's possible with the current router to allow N values (a fixed number would work) you could write a custom router that would do it for you.
I would question this approach, however, and suggest that actually listing all of your routes won't take long and will be easier in the long run. A route designed as you've suggested would mean that either your named parameters are always in the same order, i.e.
/controller/action/id/title/colour
or that they are almost anonymous
/controller/action/value1/value2/value3
With code like
$this->getRequest()->getParam('value2'); //fairly meaningless
Does it have to be N or can you say some finite value? For instance can you imagine that you'll never need more than say 5 params? If so you can set up a route:
/:controller/:action/:param0/:param1/:param2/:param3/:param4
Which will work even if you don't specify all 5 params for every action. If you ever need 6 somewhere else you can just add another /:paramN onto the route.
Another solution I've worked with before is to write a plugin which parses the REQUEST_URI and puts all the extra params in the request object in the dispatchLoopStartup() method. I like the first method better as it makes it more obvious where the params are coming from.