Quick question about general MVC design principle in PHP, using CodeIgniter or Kohana (I'm actually using Kohana).
I'm new to MVC and don't want to get this wrong... so I'm wondering if i have tables:
categories, pages, notes
I create a separate controller and view for each one...? So people can go to
/category/#
/page/#
/note/#
But then lets say I want to also be able to display multiple notes per page, it would be bad to call the note view in a loop from the page view. So should I create some kind of a function that draws the notes and pass variables to that function from the note view and from a loop in the page view? Would this be the best way to go about it, if not how else should I do it...?
Thanks,
Serhiy
Yes, instead of just passing 1 entity (category, page, note) to your view, pass a list of entities. With a loop inside the view, you can display the whole list.
That view may call another one (or a function) that know how to display one entry.
I would personally have a "show" method for one item and a "list" method for multiple. In your controller you can say something like $page_data['note'] = get_note(cat_id,page_id) for the "show" method and $page_data['notes'] = get_all_notes(cat_id) for the "list" method.
Then in your view, you loop over the $page_data['notes'] and display HTML for each one. If the list view is using the same "note" HTML as the "show" view, create a template or function to spit out the HTML given a note:
// In your "list" view
foreach($n in $page_data['notes']){
print_note_html($n)
}
//In your "show" view
print_note_html($n)
The print_note_html function can be a helper method accessible by all views for Notes. Make sense?
You can loop in the View. The View is allowed can also access the model in MVC. See: http://www.phpwact.org/pattern/model_view_controller
You don't need to have a controller (or model) for each table.
In CodeIgniter I create a separate helper file where I put functions that return the markup for UI elements that may need to be included multiple times in the one view.
In your example, I would create a function to return the markup for a note.
application/helpers/view_helper.php
function note($note)
{
return '<div class="note">' .
'<h2>' . $note->title . '</h2>' .
'<p>' . $note->contents . '</p></div>';
}
I would normally auto-load this helper file. And then in the view I would do something like this.
echo note($note);
For a list of notes in a view, I would iterate the list calling this function.
<div class="note-list">
<?php foreach ($notes as $note) : ?>
<?php echo note($note); ?>
<?php endforeach; ?>
</div>
I found that including a view many times in another view was slow. Thats why I did it this way.
Edit
I just dug into the CodeIgniter Loader class and sure enough a PHP include is being done every time you call
$this->load->view('view_name');
This means that if you use this method to display a list of 20 notes, you're going to be doing 20 separate includes.
Related
I'm new to OOP and MVC and currently building a website using CodeIgniter.
There seems to be a lot of contrasting information about whether loops should be in the view or the model.
On the one hand I'm trying to keep all my html markup inside the views, but on the other hand I want to keep my messy PHP logic outside of the view. Plus I also need to format the data inside my loops using functions located in my model.
What's the best way to go about organising this?
Here is a simplified version of my current implementation:
View
<section>
<ul>
<?php echo $albumTracklistHtml ?>
</ul>
</section>
Controller
$data = [
'$albumTracklistHtml' => $this->MyModel->getAlbumTracklistHtml()
];
$this->load->view('myPage', $data);
Model
public function getAlbumTracklistHtml()
{
//$this->tracklisting returned from db call in other function
foreach($this->tracklisting as $song) {
$mp3 = $this->convertToAmazonUrl($song['mp3']);
$art = $this->formatArtUrl($song['art']);
$name = $this->formatTrackName($song['name']);
$class = 'mp3';
$btn ='';
if(substr($name, 0, 1) == '*') {
$class = 'load mp3';
$btn = '<span class="playBtn"></span>';
}
<li class="'.$class.'" '.$mp3.'>'.$btn.$name.'</li>';
}
}
Very generally speaking, and keep in mind that this isn't a hard and fast rule and if you ask ten different people you'll get ten slightly different answers, but the job of the model view and controller are essentially:
The model provides a way for the controller & view to access data from another source (a database, for instance). It's basically an abstraction on whatever your data is stored in.
The views simply display data they are given.
Controllers connect the model's data with the view, so it can display the data.
I would argue that the example code you've posted is just fine, and fits these definitions. Your model retrieves the data (or processes it), the controller hands the resulting data to the view, and the view simply displays it.
However, I also think it's fine (and generally I prefer this) for the model to simply return a list of items, and then for the view to loop through them and display each one. Of course, the view "shouldn't" be doing a lot of processing, but outputting HTML for each item seems like exactly what it should be doing. The reason I prefer this is purely for separating concerns - your models should be fairly HTML-agnostic. As in, if you ever wrote a non-web-based application to interact with the same data, it could use the same models. Because of this, I would put any HTML-rendering code in my views. Even though it requires some looping logic.
At the end of the day, though, I don't think it matters that much in your case. If you strongly prefer putting the loop in the model, go with that. The most important thing is just to develop your own conventions, and then stick to them.
Here's how I would do the view:
<section>
<ul>
<?php foreach($album->getTracks() as $track): ?>
<li
class="<?php echo $track->isPlayable() ? 'load mp3' : '' ?>"
>
<span class="playBtn">
<?php echo $track->getName() ?>
</span>
</li>
<?php endforeach ?>
</ul>
</section>
This assumes that you've passed a variable called $album, and that a method offered therein returns an array of type Track.
You can return arrays if you like as well, however I prefer objects as you can convert complex conditions to simple, meaningful names. Thus, rather than your '*' test, the programmer calls $track->isPlayable(), which makes much more sense, and doesn't need commenting in the template.
In my experience it depends on what the loop is doing. One can have view specific loops. You can loop through html tags that display elements dynamically and that should go in the view.
for($controller_sent_array as $element)
{
echo "<h4>$element</h4>";
}
However things like looping through file input should go in the controller/model side. I make a point of being vague here because the choice of what should be in models verses controllers depends on framework optimizations. However what is important is that the model and controller are not sending html to be rendered. Rather, they should process data either from a database or user input or network connection, and package that data for the view to figure out. To this end you also need loops.
// This is not a safe way to do this in real life...
for($_POST as $post_input)
{
$this->your_database_library->save_input($post_input);
}
Consider your framework selection with respect to what loops are processing what where, but loops should be used when needed in the model, view, or controller.
I´m working on a project using lithium framework and I need to be able to have different views in a "MAIN" view.
For example. I have to be able to see the post and events forms (add a new event and a new post) in the principal view.
I actually have the view for add a new post and a new event. I´m looking the way to include this views in the main one.
Any idea of how to solve this?
Thanks in advance
$this->_render() is used within the views to include elements. Any variables passed from the controller to the parent view are also available in the element. The third argument of $this->_render() can be used to pass additional variables.
<?php
// renders app/views/elements/nav.html.php
echo $this->_render('element', 'nav');
?>
Relative pathing works, so if you want to reuse a template from say app/views/events/add.html.php, you can do this:
<?=$this->_render('element', '../events/add'); ?>
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?
Is there a way to load a controller from a view ?
Here is what i am affter..
I want to use one view multiple times, but this view is being loaded by separate controller that gives the view, information from the db.So becouse of that information from the model i can't just set $this-load->view(); and etc. Is there a way to do this thing, or it has a better way ?
I think a lot of sites face similar challenges, including one I'm working on that loads the same db content into the sidebar on almost every page in the site. I implemented this with the combination of a library and a helper:
Put the data logic into the library (mine is named common.php). In addition to interfacing with the database, you may want the library to store the data in a local variable in case you want to reference it multiple times on a single load.public function get_total_items()
{
if ($this->_total_items === NULL)
{
$row = $this->ci->db->query("SELECT COUNT(*) FROM items")->row();
$this->_total_items = $row[0];
}
return $this->_total_items;
}
Create a helper to load the library. (Don't load libraries within a view!) I have MY_text_helper that loads the library and returns the data:function total_items()
{
$CI =& get_instance();
return $CI->common->get_total_items();
}
Call the helper function from within the view.<p> Total items: <?php echo total_items(); ?> </p>
Simply put, you can't and shouldn't load a controller from a view. That sad, I understand your frustration because you want to re-use the model-pulling/acting logic in the controller across multiples views.
There are various ways of doing this;
Re-use the models. Your models should be very simple to select data from, and should be sleek, but if you're doing the same thing over and over it does seem stupid. In which case...
Use a controller as a "main container" and extend upon it from any logic you need. So your basically using the controller as a template, which pulls data down from the model, loads the appropriate view.
MVC doesn't work that way ... Just re-use the model - that's why it's separate from the controller. If that doesn't fit your needs, you should probably implement a library that does the logic.
I would use a library.
That way you can wrap up the data retrieval in a reusable package that you can call from any controller you like.
just do this
if you controller named controller1
put a link in view just like that
http://your-site.com/index.php/controller1/
if you want specific function add it to your url
http://your-site.com/index.php/controller1/myfunction
that's it
I have a CakePHP application that in some moment will show a view with product media (pictures or videos) I want to know if, there is someway to include another view that threats the video or threats the pictures, depending on a flag. I want to use those "small views" to several other purposes, so It should be "like" a cake component, for reutilization.
What you guys suggest to use to be in Cake conventions (and not using a raw include('') command)
In the interest of having the information here in case someone stumbles upon this, it is important to note that the solution varies depending on the CakePHP version.
For CakePHP 1.1
$this->renderElement('display', array('flag' => 'value'));
in your view, and then in /app/views/elements/ you can make a file called display.thtml, where $flag will have the value of whatever you pass to it.
For CakePHP 1.2
$this->element('display', array('flag' => 'value'));
in your view, and then in /app/views/elements/ you can make a file called display.ctp, where $flag will have the value of whatever you pass to it.
In both versions the element will have access to all the data the view has access to + any values you pass to it. Furthermore, as someone pointed out, requestAction() is also an option, but it can take a heavy toll in performance if done without using cache, since it has to go through all the steps a normal action would.
In your controller (in this example the posts controller).
function something() {
return $this->Post->find('all');
}
In your elements directory (app/views/element) create a file called posts.ctp.
In posts.ctp:
$posts = $this->requestAction('posts/something');
foreach($posts as $post):
echo $post['Post']['title'];
endforeach;
Then in your view:
<?php echo $this->element('posts'); ?>
This is mostly taken from the CakePHP book here:
Creating Reusable Elements with requestAction
I do believe that using requestAction is quite expensive, so you will want to look into caching.
Simply use:
<?php include('/<other_view>.ctp'); ?>
in the .ctp your action ends up in.
For example, build an archived function
function archived() {
// do some stuff
// you can even hook the index() function
$myscope = array("archived = 1");
$this->index($myscope);
// coming back, so the archived view will be launched
$this->set("is_archived", true); // e.g. use this in your index.ctp for customization
}
Possibly adjust your index action:
function index($scope = array()) {
// ...
$this->set(items, $this->paginate($scope));
}
Your archive.ctp will be:
<?php include('/index.ctp'); ?>
Ideal reuse of code of controller actions and views.
For CakePHP 2.x
New for Cake 2.x is the abilty to extend a given view. So while elements are great for having little bits of reusable code, extending a view allows you to reuse whole views.
See the manual for more/better information
http://book.cakephp.org/2.0/en/views.html#extending-views
Elements work if you want them to have access to the same data that the calling view has access to.
If you want your embedded view to have access to its own set of data, you might want to use something like requestAction(). This allows you to embed a full-fledged view that would otherwise be stand-alone.
I want to use those "small views" to
several other purposes, so It should
be "like" a cake component, for
reutilization.
This is done with "Helpers", as described here. But I'm not sure that's really what you want. The "Elements" suggestion seems correct too. It heavily depends of what you're trying to accomplish. My two cents...
In CakePHP 3.x you can simple use:
$this->render('view')
This will render the view from the same directory as parent view.