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);
}
Related
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'm working with a PHP MVC Framework. Works really well. I like the separation of the business layer (model) with the business logic (controller). But i just stumbled upon a problem. Here's the thing:
Suppose i navigate to the following url:
http://localhost/user/showall/
In this case the userController.php is called and within that file there is a method showallAction() which gets executed.
In the showallAction() method i simply do a request to a model which gets all the users for me. Something like this:
public function showallAction()
{
// create userModel object
$users = new userModel();
// get all users and assign the data to a variable which can be accessed in the view
$this->view->users = $users->getAllUsers();
// render views
$this->view->render();
}
So this method gets all the users, assigns the data returned from the userModel to a variable and i can easily work with the returned data in my view. Just a typical MVC thing.
Now here comes the problem.
I also need to create a native iphone variant. Ofcourse the looks will be totally different. So all i actually want to do is to request this url:
http://localhost/user/showall/
And that it just gives me the array (in json format) back. So i can use that for the mobile development.
But this obviously can't be done right now because the showallAction() method assumes that it is for web browser display. It doesn't echo JSON formatted, instead it simply assings the array of users to a variable.
So that means i have to create another method "showallMobileAction()" in order to get the data, but specifically for the mobile device. But this is not an elegant solution. I'm sure that are better ways...
Anyone any idea how can i solve this problem??
In your situation i would modify the routing mechanism.
It would be useful, if you could add extension at the end of URL, which represents the format you expect, like :
http://foo.bar/news/latest >> HTML document
http://foo.bar/news/latest.html >> HTML document
http://foo.bar/news/latest.rss >> you RSS feed
http://foo.bar/news/latest.json >> data in JSON format
It's a simple pattern to recognize. And you can later expand this to add .. dunno .. pdf output, or Atom feeds.
Additionally , two comments :
Model is not a type of objects. Instead it is a layer, containing objects responsible for business logic, and objects responsible for data storage/retrieval.
View should be a full blown object, to which you bind the domain objects (objects responsible for business logic).
You could pass parameters to your url:
/user/showall/json
and get the third URL segment with a custom function or a built-in one. For instance, with CodeIgniter: $this->uri->segment(3).
Some frameworks will pass the additional parameters to your method. Just try this with the URL I wrote above:
public function showallAction()
{
print_r(func_get_args());
}
I'm not familiar with PHP MVC but in general terms I'd use the "accepts" HTML header field to request the response in either "text/html" or "text/json", the controller would check for the accepts type and return the response accordingly.
Here is the code using CodeIgniter:
The problem I encounter:
The controller will have some functions call view, and it
separated, but it is still very close with the logic itself, if the
controller change to return in JSON or XML to display result, it seems
very trouble.
Seems many method, but each one is depends another.
I think it is difficult to track the code.
Please give some suggestions thank you.
*Please reminded that, it is only the controller class. the load view is actually prepare the data for the view, won't render the page. also the doXXX function call model is only use the model method, it won't have any SQL statement. The MVC is separated, but the controller also have the functions related to the view or model, make it quite messy.
class User extends CI_Controller
{
public function register()
{
//check is logged in or not
//if not logged in , show the register page
}
public function show_register_page()
{
//generate the UI needed data , and call the view to render, and will the user will post back a valid_register function
}
public function valid_register()
{
//do all the valid logic, if success,
//do the do_register
//if fail, valid_register_fail
}
public function valid_register_fail()
{
//check is logged in or not
//show the valid register fail page
}
public function show_valid_register_fail_page()
{
//generate the UI needed data , and call the view to render
}
public function do_register()
{
//insert data in the db, the Model will be called
//if something go wrong in db, show the error page
//if everything is success, show the register success
}
public function show_db_error_page()
{
//generate the UI needed data , and call the view to render
}
public function show_register_success()
{
//generate the UI needed data , and call the view to render
}
}
1. The controller will have some functions call view, and it
separated, but it is still very close with the logic itself, if the
controller change to return in JSON or XML to display result, it seems
very trouble.
Depends on how you organized your code and what you actually pass into the view (template). If that's well structured, you can have one view for HTML, one for XML and one for json, where-as json normally just encodes the view variable's (see json_encodeDocs).
2. Seems many method, but each one is depends another.
Well, just don't do it :) The names look like you wanted to "code that into". Keep it apart. Make those function actually actions that a user performs:
register - that action handles the registration process
Make a login controller out of it that handles anything you need:
login - the login action
lost_password - the lost password action
register - the registration action
activate - the registration activation action
Everything else does not belong in there. There is no need for an action to display some page - the controller itself can decide which view to pick.
Next to that you don't need to display database errors. CI takes care of that. Just put only in what's needed and keep things simple. That should help you to reduce the number of methods and the code therein as well.
3. I think it is difficult to track the code.
Sure. Too many functions with not really speaking names. Keep things simple. It's not easy, but give naming and reducing the overall logic some love.
I am new to Zend FW. I am looking to write a simple feedparser in a controller named Feedparsercontroller's indexAction. but i want to display the parsed feed output as a widget on my index page. how can i drive the output/ variable data to my indexview?
The below is my parser.
class FeedparserController extends Zend_Controller_Action {
public function init() {
/* Initialize action controller here */
}
public function indexAction() {
$feedUrl = 'http://feeds.feedburner.com/ZendScreencastsVideoTutorialsAboutTheZendPhpFrameworkForDesktop';
$feed = Zend_Feed_Reader::import ( $feedUrl );
$this->view->gettingStarted = array ();
foreach ( $feed as $entry ) {
if (array_search ( 'Getting Started', $entry->getCategories ()->getValues () )) {
$this->view->gettingStarted [$entry->getLink ()] = $entry->getTitle ();
}
}
}
}
i want to implement the same with my login , register controllers as well.
Perhaps I'm not understanding your question fully.
But, it seems the best approach here would be to create a separate feed controller that is solely responsible for the business logic associated with feeds (retrieving, massaging, setting to view, etc).
Then, create a partial which contains javascript code to call the feed controller, which then outputs the widget you're desiring. This does a few things very well.
It centralizes feed-related logic
It allows you to place the feed widget wherever you want
It is a SOA approach which is generally a good thing
Hope this helps!
I think the best logic with widgets is ajax.
Use some js widgets libraries (maybe jQuery ui for example), then make these widgets be loaded by some ajax queries, returning HTML, this allow you as well simple widgets reloading behviours (without relaoding the whole page).
In the server Side you'll need to allow your controller/Action to be called via ajax requests and to send only html snippets (not a whole page with all the layout).
To do that check ContextSwitch and AjaxContext Action Helpers. You will tell your FeedparserController that the index action can be called with /format/html in an XMLHHTTPRequest, and that in this case the view helper will be index.
In the init part you will say the indexAction can be called in ajax mode, rendering html snippets ('html'):
$Ajaxcontext = $this->_helper->getHelper('AjaxContext');
$Ajaxcontext->addActionContext('index', 'html')
->initContext();
Now simply rename your view script feedparser/index.phtml to feedparser/index.ajax.phtml
In the indexAction, do your stuff and output what you want on your view script, do not think about layout composition problems, you're working alone with your own layout part and the composition is done on the js side.
In the javascript part, ensure you're calling via ajax ($.load or $.ajax with jQuery maybe) the url with format/html added as parameters (so http://example.com/feedparser/index/format/html)
Note that in my opinion you should use json responses and not html, maybe json with some html inside. But that's a matter on how you want to control your ajax communication (and handle errors, redirection and such, and it's another subject).
What about a view helper ?
You can read about it View Helpers in Zend Framework
I am currently building a small admin section for a website using Zend Framework, this is only my second time of using the framework so I am a little unsure on something things. for example are I have an archive option for news articles where the user will hopefully click a link and the article will be archived however I cannot work out how to get this to run without having a view?
this is my controller
public function archiveNewsAction()
{
//die(var_dump($this->_request->getParam('news_id')));
$oNews = new news();
$this->_request->getParam('news_id');
$oNews->archiveNewsArticle($news_id);
//die(var_dump($oNews));
$this->_redirect('/admin/list-all');
}
and this is my model
public function archiveNewsArticle($news_id)
{
//die($news_id);
$db = Zend_Registry::get('db');
$sql = "UPDATE $this->_name SET live = '0' WHERE news_id = '$news_id' LIMIT 1";
die($sql);
$query = $db->query($sql);
$row = $query->fetch();
return $row;
}
I would appreciate any help any one can give.
Thanks
Sico
I use this with calls to AJAX-only actions that I either don't want output or I'm using some other output, like XML or JSON:
// Disable the main layout renderer
$this->_helper->layout->disableLayout();
// Do not even attempt to render a view
$this->_helper->viewRenderer->setNoRender(true);
This has the added benefit of no overhead of redirection if what you are doing has no output/non-HTML output.
To disable view rendering in an action (put this in the specific action. If you want it for the entire controller put it in the init method):
$this->_helper->viewRenderer->setNoRender();
If you are using the layout component of ZF also add this:
$this->_helper->layout->disableLayout();
I could not figure out your code there. in your model you are calling die(). why?
it will stop the execution. are you sure about that line? anyway, if you have a controller in Zend Framework and do not need any view, you can turn the view off by this line:
// code in your controller
$this->_helper->viewRenderer->setNoRender(true);
// the rest of the controller
now the controller will not search for a view script to show to the user.
make sure you will call
$this->_redirect()
after all of your controller job is done.
Orignal Answer:
Your call to:
$this->_redirect();
Calls the Redirector action helper, which (unless you've configured it not to) will automatically exit the script as soon as the headers are written, so the view will never be called or rendered, there's no need for a view script.
Follow-up Answer:
In order to call the action without sending the user to the other "page" and then redirecting back again you'll need to use an XMLHttpRequest (AJAX) call. These links should provide the information you need:
http://developer.mozilla.org/en/AJAX
http://www.ibm.com/developerworks/xml/library/wa-ajaxintro1.html
http://www.oracle.com/technology/pub/articles/schalk-ajax.html
Also take a look at some JS frameworks that make using XMLHttpRequest cross-browser much easier:
http://www.prototypejs.org/
http://mootools.net/
Zend Framework actually has built in support for the Dojo JS framework, which you may find easier:
http://framework.zend.com/manual/en/zend.dojo.html
http://www.dojotoolkit.org/