I am new to CakePHP. Is this site menu sensible and conventional? - php

I need a bit of feedback to make sure I'm not completely missing the point of CakePHP/MVC.
I'm designing a mini-cms in CakePHP--essentially a photo album. I'm looking to have a standard, boiler-plate drop down menu on every page. Naturally, the menu needs to be dynamic as the user deletes and adds albums.
My goal: Build something that moves in the opposite direction of View::actionRequest() (ie, instead of view calling back to the controller, have the controller push a set variable to the View class before it renders.) My understanding is that View::requestAction() is not very graceful and is quite slow.
This is my model for the table containing the menu keywords. app/Model/ModelItem.php
class MenuItem extends AppModel {
public function buildMainMenu() {
return $this->find('all');
}
}
Since I want it everywhere, I put the call in the AppController
class AppController extends Controller {
public function beforeFilter() {
$this->loadModel('MenuItem');
$this->set('mainMenuItems',$this->MenuItem->buildMainMenu());
}
}
And this is an element that gets dumped into the top of my default layout for the CSS
- Element File: app/View/Element/navigation.ctp
- Layout: app/View/Element/Layout/default.ctp
<ul id="navigation">
<?php
foreach($mainMenuItems as $item) {
echo "<li>".$item['MenuItem']['name']."</li>";
}
?>
</ul>
I end up with a lovely, bullet list of all the items in the table. Am I completely mucking this up? Do I have it all wrong? I have no idea.

I don't see anything wrong with your approach. Here are a few notes though, completely optional:
I'd move the menu fetching code from beforeFilter() to beforeRender(). There no reason to do it that early, you may throw an exception before the menu is needed, or you may redirect your user to another page. Additionally, you have more information about the request there, so you can do things like highlight the current page/item/user in the menu.
Move the element rendering code into some sort of a MenuHelper, which will make your layour cleaner (e.g. $this->Menu->render() in the layout). You could also use the HtmlHelper to render your items through cake, and let cake take care of markup correctness.
If you move the rendering into a helper, implement some sort of caching scheme for the whole thing, to speed things up - if it's necessary. Whether the caching is possible or not, or whether you should do it in your controller or your helper is entirely up to you and your app :)
I guess the overall advice is to try to isolate bits of functionality from the rest of the app; it's easier to read, maintain and modify.

I'd parcel it up into an element and use request action to fetch the data when you output the menu in the layout/view:
/app/View/Elements/main_menu.ctp:
$mainMenuItems = $this->requestAction('/menu_items/buildMainMenu');
<ul id="navigation">
<?php
foreach($mainMenuItems as $item) {
echo "<li>".$item['MenuItem']['name']."</li>";
}
?>
</ul>
and include it in your layout/view with:
echo $this->Element('main_menu');
This way, if you have a view/layout (e.g. Ajax) that doesn't need the menu you wont waste a database query.

Related

add values to cakephp layout from model app wide

thanks for reading, I know the question might sound fairly common, and well I wont deny the fact that maybe I'm just formulating what i want wrongly.
Lets start by shooting it straight, then specifying.
I have a cakephp app with 2 layouts, one layout renders the whole "public" page, and lets call it admin "admin" layout that will render only actions to authenticated users.
in my admin layout, I am printing an element which is the navigation bar.
what I want to do is, without setting on every single controller, the options to set a variable containing the navigation bar values (yes dynamically filled from a specific model)
I want to be able, to set a variable, which will contain a list of values gotten from a model.
The Model is called "Section", which is a table, that contains a list of "sections" of the application.
this navigation bar, needs to print the values of each section, so I need them to be dynamic, hence, I think (again I might be wrong) i need to set that variable somewhere, to make it available to the element, so when the layout "admin" is rendered, the menu bar, is actually filled with the available values of the sections.
I tried doing a Configure::write on AppController, but no dice, it just allowes me to use a variable on controllers, when what I want to do is, loop through the arrah "sections_for_menu" or whatever we call it, and then print the options avaliable on the menu bar.
so you are a bit more comfortable with the idea, the nav bar is a bootstrap based "navbar" with "dropdowns".
i should be able to
<ul class="mycoolclass">
<?php
foreach($sections as $section) {
echo '<li>" . $section . "</li>';
}
</ul>
and thus, printing each value on a new list with its link, and whatnot.
I have been reading with no luck, and have to admin that I am myself fairly new to cakephp, been using it for no longer than 2 weeks.
Any help reaching a solution to this need, is highly appreciated.
UPDATE:
Hi #nunser, thank you very much for your reply.
indeed I'm using an element
this is my layout "base"
<body>
<?php echo $this->element('admin_navbar'); ?>
<!-- container -->
<div class="container-fluid">
<!-- , array('element' => 'flash') -->
<?php echo $this->Session->flash('flash'); ?>
<?php /*echo $this->fetch('content');*/echo $content_for_layout; ?>
</div>
</body>
I'll try your suggestion and see how it goes
I have a controller "SectionsController" which is in charge of well all Section related actions on the app, what I need is to set in the global variable, a list of sections, so I can print the link inside my navbar!
lets assume the following scenario
$this->set('sections_for_navigation', $this->Section->find('list', array('fields' => array('id', 'name'))));
so then i can access the variable $sections_for_navigation from my element, and render the list of sections.
[19-02-2014 - update] tried it.
based on what #nunser suggested, im actually able to, beforeFilter, setting the value, as if i var_dump it, it actually gives me the array as i expected.
public function beforeFilter(){
$this->loadModel('Section');
$secciones = $this->Section->find(
'list', array(
'fields' => array(
'id',
'name'
)
)
);
$this->set('navigation', $secciones);
Then call
public function beforeFilter() {
parent::beforeFilter();
}
from the controllers, did the trick!, now, do i have to add a beforeFilter to every controller to share the navigation through all the app?, is there a way to avoid having to add that method to every single controller? (not that it bothers me, so far it does what i need,which is actually great)
For those kind of navigation things, I do it in beforeFilter or beforeRender in the AppController.
public function beforeFilter() {
//queries and stuff that gets the array for navigation
$this->set('mainNavigationBar', $navigation)
//remember to pass that variable to the element in the view
}
As of to where, beforeFilter or beforeRender, it depends on what type of rule you want to stablish. For example, if you will have same elements for navigation everywhere, be it that the user has permission or not, or if you might want to alter the navigation variable depending on the action being executed, I'd go with doing it in beforeFilter. That way you can tweak things in navigation if, for example, the user doesn't have permission to access any Section (totally making up the model relation here). Though for that, you might want to keep access to the navigation array in the controller.
Tweaked example
protected $_mainNav = array();
public function beforeFilter() {
//queries and stuff that gets the array for navigation
$this->set('mainNavigationBar', $navigation)
//remember to pass that variable to the element in the view
$_mainNav = $navigation;
}
//other controller
public function randomAction() {
//some reason that makes you modify the navigation
unset($_mainNav[0]); //making that up...
$this->set('mainNavigationBar', $navigation)
}
Now, if you won't change the navigation values (once you've added them dynamically), then go with beforeRender, that way you can check permissions and other stuff before bothering with queries for navigation (example to follow would be the first one).
If values of the navigation change per controller, overwrite the function like
RandomController
public function beforeFilter() {
parent::beforeFilter();
$_mainNav = array('no nav'); //example
}
And that's it. Don't know if you need more detail than that, if so, please explain you problem a little more. Oh, and you mention "to make it available to the element", so I'm guessing you're using an element for the navigation bar. If not, please do.

Should loops with logic and html output go in the model or the view?

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.

Correct way to deal with application-wide data needed on every pageview

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?

How should partials be loaded when they are dependent on business logic?

I'm using the term "partial" to refer to a small section of presentational code which is repeated on many views. For example, a sidebar. In vanilla PHP, where the business and presentation logic is mixed, including a sidebar is no trouble:
if($someCondition) {
include('sidebar.php');
}
However, in an MVC design pattern, the presentational logic must be kept in the view whilst the business logic must be kept in the controller. If I wish to include a partial unconditionally, then this is unproblematic since I can just have include('sidebar.php') in my view. However, I can no longer do so conditionally because that if logic is banned from my view.
I have attempted a number of solutions but they all have problems. I am currently using Solution 2:
Solution 1
Create an include function in my view class which could conditionally include content from my controller. So in my controller I could have the following logic:
if($someCondition) {
$this->view->include('sidebar.php');
}
$this->view->show('index.php');
Problems: sidebar.php will need to be included into index.php at a specific point requiring the include method on the view object to do some sort of parsing.
Solution 2
Move control of the partials out of the view and put them into the controller:
if($someCondition) {
$this->view->show('header.php', 'sidebar.php', 'index.php', 'footer.php');
}
else {
$this->view->show('header.php', 'index.php', 'footer.php');
}
Problems: Moves a large portion of the presentational logic into the realm of the controller. It seems to be more natural to me for the view to decide whether or not to include the header. Indeed, every PHP MVC tutorial I can find, has partials under the control of the view and not the controller.
Solution 3
Duplicate the view and alter the clone so that it includes the sidebar. Then I could conditionally load one or the other in the controller:
if($someCondition) {
$this->view->show('indexWithSidebar.php');
}
else {
$this->view->show('index.php');
}
Problems: Duplication of code. Consider what would happen if I had 2 sidebars which I needed to be conditionally loaded. Then I would need index.php, indexWithSidebar1.php, indexWithSidebar2.php, indexWithSidebar1And2.php. This only gets worse with every condition. Remember that the entire point of taking the sidebar out as a partial was to avoid replicating it anyway and this approach seems to defeat the point.
Are any of these solutions the "right" solution and if so, how can I overcome their problems? Is there a better approach out there?
However, in an MVC design pattern, the
presentational logic must be kept in
the view whilst the business logic
must be kept in the controller.
IMHO: From an architecture standpoint, I push my business logic further back, out of the controller. We use services to handle all the business logic and repositories for data retrieval. The services call the repositories and then pass back our data model with all the business logic decided for us. Any logic outside that is really UI logic (show this, hide that), as our returned data could be (should be able to be) used in any kind of application, whether it's a mobile app, windows app, or web app.
You could use an extension helper method for your control, and in the model for the partial you can return EmptyResult() if you don't wish to render the sidebar. Or, more succintly:
<% Html.RenderAction<MyController>(x => x.Sidebar({params})); %>
And then in the controller:
public ViewResult Sidebar({params})
{
SidebarModel model = new SidebarModel();
//...get/build model
if ({someCondition})
{
return View("MySidebarPartialView", model);
}
return new EmptyResult();
}
Have your controller evaluate the condition and pass the result to your view. Then, the view can decide whether to include the partial.
For example, the controller can check whether a variable, $foo, isn't null. It passes the result of the comparison to the view via the model's property, $model->isFooed. In this case, the view can display the sidebar based on the value of $model->isFooed.

Admin panel - what is the best way to display "static" data in the layout?

I'm about to write a admin panel for my CMS written in CodeIgniter. There will be some user information visible at all time - both in the layout's header section and the sidebar. I'm used to do it in a way that I personally hope and think could be done a lot easier, since I'm tired of sending the same parametres to the view over and over again, when it's dynamic data that needs to be displayed on every page anyways (such as unread messages, username, name, status, etc).
I'll need controllers and models, I know that, but do I have to pass, just for an example, the user's username, unread messages etc. every time I need to load a view? Should I do some kind of library for this?
Now my question is: How would I do it when it comes to best practice and for making it easy to maintain in the future?
I hope my question is understandable :)
Personally, I would extend the Controller library (create a MY_Controller by following the guidance at the bottom of Creating Libraries at codeigniter.com).
You would use your model etc as normal. Then you would create a private function in your MY_Controller class to get the relevant "global" data and call
$this->load->vars('everywhere_data', $data_from_relevant_models);
which would make the data available to all views called from that point on as $everywhere_data. Then add a reference to that function in the constructor of MY_Controller, perhaps with a conditional checking for the user to be actually logged in.
If it's complex to collect and get all that data, you might write a library to handle it for you, but the 'controller' part would still be done by MY_Controller: i.e. to get the data and then use load->vars() to publish it to the view.
As a quick and untested example, MY_Controller would start something like as follows:
<?php
class MY_Controller extends Controller
{
private $logged_in_user;
function MY_Controller()
{
parent::Controller();
if( $this->_logged_in_userid() > 0 )
{
$this->logged_in_user = $this->_get_user( $this->logged_in_userid() );
$this->load->vars('logged_in_username', $this->logged_in_user->username );
} else {
$this->logged_in_user = false;
}
}
...
}
Note that things like _logged_in_userid() would access the session for you (e.g. return $this->session->userdata('logged_in_userid');), and _get_user() would access the relevant models.
Finally, you would have a view that accesses $logged_in_username (or everywhere_data in my first example) which you would call into your headers etc. This leaves your normal controllers uncluttered so that they can focus on delivering their specific functionality, stops you rewriting your code several times AND maintains the MVC ideals.
You could create a View just to hold the information and get it from a $_SESSION variable in the View itself if you want to keep it all in one place.

Categories