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.
Related
Goal: understand whether this implemented order/logic of operation indeed belongs to the C part of MVC.
Situation: I have nearly finished a simple note-taking site with markdown files. No database is used except for authentication. The lack of database, however, makes it difficult to know if I am neglecting the M of MVC.
Because I did not want to have the .md extension as part of the pretty url, I am heavily relying on the PageController to settle the order of operation.
From the Controller, PageController inherits the constructed filesystem/Flysystem(fs), twig, and the "$app" that processes any of the three scenarios.
It first checks to see if a param.md exists and then whether param is a directory. If none of the above, then it's a new note. In each case, it uses a corresponding method set by the Application ($app) that processes and returns an array (title, breadcrumbs, content/directory listing, new template etc).
<?php
namespace App\Controller;
class PageController extends Controller {
public function Page($param){
$file=$param.'.md';
if ($this->fs->has($file)) {
$data=$this->app->setNote($file);
return $this->app->render('note.twig',$data)
}
elseif ($this->fs->has($param)) {
$data=$this->app->setFolder($param);
return $this->app->render('folder.twig',$data)
}
else {
$data=$this->app->setNew($param);
return $this->app->render('new.twig',$data)
}
}
}
Per "PHP the Right Way":
Controllers handle the request, process the data returned from models and load views to send in the response.
Although my code works and gets the job done, it does not appear to the be right way because the main App is doing the processing. I guess I could just move the Application.php in the Models folder, but would that make it adhere to "right way"? Should I use a Middleware before the PageController gets to work?
It may seem silly to ask about a code that just works, but my goal is to better understand/learn the current wisdom's ways when dealing with flat-files.
Regardless of whether you are 'database-less', the data is being stored / accessed in the .md files.
Access to them should be abstracted to a Model. You should create a File.find object + method, and/or a File.find_or_create. Then
$file = File.find_or_create($param);
$render_type = $file.type . '.twig';
return $this->app->render($render_type, $file.data);
Put all your if logic in the Model.
I am currently involved in the development of a larger webapplication written in PHP and based upon a MVC-framework sharing a wide range of similarities with the Zend Framework in terms of architecture.
When the user has logged in I have a place that is supposed to display the balance of the current users virtual points. This display needs to be on every page across every single controller.
Where do you put code for fetching sidewide modeldata, that isn't controller specific but needs to go in the sitewide layout on every pageview, independently of the current controller? How would the MVC or ZF-heads do this? And how about the rest of you?
I thought about loading the balance when the user logs in and storing it in the session, but as the balance is frequently altered this doesn't seem right - it needs to be checked and updated pretty much on every page load. I also thought about doing it by adding the fetching routine to every controller, but that didn't seem right either as it would result in code-duplication.
Well, you're right, having routines to every controller would be a code-duplication and wouldn't make your code reusable.
Unlike suggested in your question comments, I wouldn't go for a a base controller, since base controllers aren't a good practice (in most cases) and Zend Framework implements Action Helpers in order to to avoid them.
If your partial view is site-wide, why don't you just write your own custom View Helper and fetch the data in your model from your view helper? Then you could call this view helper directly from your layout. In my opinion, fetching data through a model from the view doesn't break the MVC design pattern at all, as long as you don't update/edit these data.
You can add your view helpers in /view/helpers/ or in your library (then you would have to register your view helper path too):
class Zend_View_Helper_Balance extends Zend_View_Helper_Abstract
{
public function balance()
{
$html = '';
if (Zend_Auth::getInstance()->hasIdentity()) {
// pull data from your model
$html .= ...;
}
return $html;
}
}
Note that you view helper could also call a partial view (render(), partial(), partialLoop()) if you need to format your code in a specific way.
This is a pretty simple example, but to me it's enough is your case. If you want to have more control on these data and be able to modify it (or not) depending on a particular view (or controller), then I recommend you to take a look on Placeholders. Zend has a really good example about them here on the online documentation.
More information about custom view helpers here.
When you perform such a task, consider using the Zend_Cache component too, so you won't have to query the database after each request but let's say, every minute (depending on your needs).
What you are looking for is Zend_Registry. This is the component you should use when you think you need some form of global variable. If you need this on EVERY page, then you are best adding it to your bootstrap, if you only need it in certain places add it in init method of relavent controllers.
application/Bootstrap.php
public _initUserBalance()
{
$userId = Zend_Auth::getInstance()->getIdentity()->userId;
$user = UserService::getUser($userId);
Zend_Registry::set('balance', $user->getBalance());
}
application/layouts/default.phtml
echo 'Balance = ' . Zend_Registry::get('balance');
That wee snippet should give you the right idea!
In this case, I usually go with a front controller plugin with a dispatchLoopShutdown() hook that performs the required data access and adds the data to the view/layout. The layout script then renders that data.
More details available on request.
[UPDATE]
Suppose you wanted to display inside your layout the last X news items from your db (or web service or an RSS feed), independent of which controller was requested.
Your front-controller plugin could look something like this in application/plugins/SidebarNews.php:
class My_Plugin_SidebarNews
{
public function dispatchLoopShutdown()
{
$front = Zend_Controller_Front::getInstance();
$view = $front->getParam('bootstrap')->getResource('view');
$view->sidebarNews = $this->getNewsItems();
}
protected function getNewsItems()
{
// Access your datasource (db, web service, RSS feed, etc)
// and return an iterable collection of news items
}
}
Make sure you register your plugin with the front controller, typically in application/configs/application.ini:
resource.frontController.plugins.sidebarNews = "My_Plugin_SidebarNews"
Then in your layout, just render as usual, perhaps in application/layouts/scripts/layout.phtml:
<?php if (isset($this->sidebarNews) && is_array($this->sidebarNews) && count($this->sidebarNews) > 0): ?>
<div id="sidebarNews">
<?php foreach ($this->sidebarNews as $newsItem): ?>
<div class="sidebarNewsItem">
<h3><?= $this->escape($newsItem['headline']) ?></h3>
<p><?= $this->escape($newsItem['blurb']) ?></p>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
See what I mean?
I am in the process of learning the MVC pattern and building my own lightweight one in PHP
Below is a basic example of what I have right now.
I am a little confused on how I should handle AJAX requests/responses though.
In my example user controller below, If I went to www.domain.com/user/friends/page-14 in the browser, it would create a User object and call the friends method of that object
The friends method would then get the data needed for the content portion of my page.
My app would load a template file with a header/footer and insert the content from the object above into the middle of the page.
Now here is where I am confused, if a request is made using AJAX then it will call a page that will do the process over, including loading the template file. IF an AJAX call is made, I think it should somehow, just return the body/content portion for my page and not build the header/footer stuff.
So in my MVC where should I build/load this template file which will have the header/footer stuff? ANd where should I detect if an AJAX request is made so I can avoid loading the template?
I hope I am making sense, I really need help in figuring out how to do this in my MVC I am building. IUf you can help, please use some sample code
/**
* Extend this class with your Controllers
* Reference to the model wrapper / loader functions via $this->model
* Reference to the view functions via $this->view
*/
abstract class Core_Controller {
protected $view;
protected $model;
function __construct(DependencyContainer $dependencyContainer){
$this->view = new Core_View();
//$this->view = $dependencyContainer->get(view);
}
public function load($model){
//load model
//this part under construction and un-tested
$this->$model = new $model;
}
}
user controller
/**
* Example Controller
*/
class User_Controller extends Core_Controller {
// domain.com/user/id-53463463
function profile($userId)
{
//GET data from a Model
$profileData = $this->model->getProfile($userId);
$this->view->load('userProfile', $profileData);
}
// domain.com/user/friends/page-14
function friends()
{
//GET data from a Model
$friendsData = $this->model->getFriends();
$this->view->load('userFriends', $friendsData);
}
}
For me, I developed a separate object that handles all template display methods. This is good because you can then ensure that all the resources you need to display your UI is contained in one object. It looks like you've isolated this in Core_View.
Then, when an AJAX call is made, simply detect that it is an AJAX call. This can be done by either making the AJAX call through an AJAX object, which then references other objects, or you can take an easy approach and simply set an extra POST or GET field which indicates an AJAX call.
Once you've detected if it's an AJAX call, define a constant in your MVC such as AJAX_REQUEST. Then, in your template/UI object, you can specify that if it's an AJAX call, only output your response text. If it isn't, proceed with including your template files.
For me, I send it through an AJAX object. That way I don't have to worry about making a single output work for both cases. When it's ready to send a response, I just do something to the manner of print( json_encode( ...[Response]... ) ).
well, it would all start with normal request which would load the initial page. there are many options as to handle this but let's say that you start with /users/friends page which would list all your friends. then each of the friends should have link to specific friend's profile -- now this is the moment where ajax could kick in and you could ajaxify links to your friend profiles - this means that instead of normal you would instead use let's say jQuery and setup click handler in a such way that
$("a").click(function(){$.post($(this).attr("href"), null, function(data){$("#content").html(data);}});
this would use "href", and upon click would make post request to your backend. at backend, if you see that it's post, then you would just return the content for that particular friend. alternatively, if you have get request, you return all - header - content - footer.
if you use technique above, make sure to properly handle the data you receive. e.g. if there are further actions that should be done via ajax, make sure to "ajaxify" the data you get back. e.g. after updating html of the content, again apply the $("a").click routine.
this is just trivial example, to kick you off, but there are many more sophisticated ways of doing that. if you have time, I suggest reading some of agiletoolkit.org, it has nice mvc + ajax support.
You will need to use a different view. Maybe something like:
funciton friends() {
$this->view = new Ajax_Request_View();
$friendsData = $this->model->getFriends();
$this->view->load($friendsData);
}
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.
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.