CakePHP and Layout – secret of data passing through

In my current project I need to deal with the Layout more than adding some header, static menu and footer in it. I need to have dynamic menu loaded from the database plus additional blocks which will contain some data related to the main content. For example YouTube.com /and many other sites/ has some blocks which appear all over the site like related movies block from the right depending from the tags related to the main movie. There are a lot examples.

Well, searching in the CakePHP Manual, CakePHP Google Group or Bakery doesn’t satisfy my understanding how to solve the case. Apart from $anything_for_layout: Making HTML from the View available to the layout article I didnt find anything on this topic. And this one sounds like a hack rather than a complex solution.

Then I decided to investigate my self the situation and here is my solution:

Let’s go through complete example. Let’s imagine that we have to load a dynamic menu from the database in all pages. Menu is in the database.

Before starting with the example I want to resume the process:
1. Component fetch the data from the database
2. in the beforeRenter() function in the AppController the component provide the fetched data to the $this->data variable
3. In the helper the specified node in the $this->data variable is manipulated in order to create a valid html block /or whatever it’s required/
4. Including the helper in the default layout or other one will provide the desired functionality on the site

That’s it. It’s nothing more by wrapping the data with helper so it would be easy to be changed.

Ok, let’s continue with first step – to create a menu component which will fetch the data:

<?php
//Get the menu from the database
class MenuComponent extends Object {
    function fetchData(&$controller){
        //Dynamically Loading the model of the menu, because it's not necessary to have it on every controller.
        App::import('Model', 'Menu');
        $this->Menu = new Menu();
        return $this->Menu->find('all');
    }
}
?>

The component just fetch all data from the Menu table and return it as array. It’s possible to skip this step and to get the data directly with a function in a Controller, but I think this way is more clear.

Once we have this component we need to include it in our application. If we want to be loaded for all controllers the best place is app_controller.php

<?php
class AppController extends Controller {
   ...
   var $components = array(..., 'Menu');
   ...
 
   function beforeRender(){
     $this->data = am($this->data, array('Menu'=>$this->Menu->fetchData(&$this)));
   }
   ...
?>

So, what we add in the AppController class? The callback beforeRender() is executed always after all Controller’s actions. This small trick allow us to put extra node in the $this->data variable. For our example this node is called “Menu”, but you should set name which wont be in a conflict with other variables /as example I could mention if there is interface for editing the Menu/, so I would suggest array node with name something like ‘MenuBlock’ or similar, but for this example it’s not needed. Let’s keep it simple.

We have the data from the database and it is associated properly to $this->data array. Let’s make the Helper:

<?php
class MenuHelper extends Helper {
   function display(&$data) {
      //This extracting only the name of the menu from the multidimentional array.
      $content = Set::extract('/Menu/name', $data['Menu']);
      $ret = '<li>'.implode('</li><li>', $content).'</li>';
      $ret = '<ul>'.$ret.'</ul>';
      return $ret;
   }
}
?>

Basically what this helper do is to extract the name and create a simple unsorted list.

Now whats left is to add this in the layout. Layouts are in app/views/layouts directory. The default one is called default.ctp. If you don’t have default one in that directory, you can get a copy from cake/libs/view/templates/layouts/default.ctp, put it in the app/views/layouts and modify it by adding your code and functionality. Once you have it then just add the following code in it in the proper position.

<html>
  <head></head>
   <body>
      ...
      <?php
      //Check if the Helper is loaded in the memory /if it's not, then loading it/
      App::import('Helper', 'Menu');
      //Create instance of the Helper class
      $Menu = new MenuHelper();
      echo $Menu->display($this->data);?>
      ...
      <?php echo $content_for_layout;?>
   </body>
</html>

That’s it, now this menu list will be populated in all pages.

36 thoughts on “CakePHP and Layout – secret of data passing through

  1. Pingback: links for 2007-10-11 « Richard@Home

  2. Alfredo

    I’m coming from Rails and was really missing the content_for method, I still do, but this is a great workaround, thanks for sharing.

  3. DannyC

    Thanks! It really works.

    There’s just one issue in the helper: the $ret variable is not $ret .= ”… but $ret = ”…

  4. vipin

    I think nice tutorial but unfortunalty it not working for its shows undefined variable ; i can’t figureout the exact problem anybody help me why this shows undefined varialbe how we can rectify this ,thanks in adavnce

  5. Nik Chankov Post author

    @All – thanks for your comments!

    DannyC – you are completely right, with .= it will take wrong string and it will become mess. I will fix in the article right now.

    @vipin – I just want to warn you that the example is writen for CakePHP 1.2, and as DannyC said – it’s working, but there is no guarantee that it would work in v1.1

  6. conankun

    loadModel is deprecated. Please use App::import instead.

    Another question, can you share abit on what does your database look like to use this code? Is it just menu table with id, name, and pages?

  7. Nik Chankov Post author

    @conankun – true, loadModel() is deprecated, but the article was written before the deprecation, so I will leave as it is.

    About db model this example is made with one table with 2 fields in it – id and name. This is because I simplify the example as much as possible.

  8. Stacey Kingwill

    Hi,

    I keep getting a
    Fatal error: Class ‘Menu’ not found in /var/www/cake/app/controllers/components/calendar.php on line 8

    What is wrong, Version 1.2.0.6311 beta

  9. Nik Chankov Post author

    @Stacey Kingwill – I think you need to have create a model named Menu. because as far I can see look like there is no such class. This article is just prove of concept and is not tend to be full working demo.

  10. Gabe Chaney

    i’m having trouble implementing this. i think i’ve got everything set except i cannot get the line of code in the layout to work. i can’t figure out how i can have access to the helper within the layout. any help would be appreciated.

  11. Nik Chankov Post author

    Apologize to all guys commented here. I miss the part which explain how to get instance of the helper in layout.

    Just small clarification: Properly because of sequence of the class inheritance, but it’s not possible to load helper in the usual way in AppController class. The $helpers variable get overwritten from the default helpers which are Html and Form.
    there are 2 approaches to sort this out:
    1. You need to add desired helper in every controller /but this is time consuming and not convenient/
    2. The approach which I just added in the post.

    So, hopefully this issue finally get sorted.

  12. Valerie

    To help those that find this article, but can’t quite get it working. It think that they deprecated the ‘App::…’ part. Replace these with loadModel(‘NameofMenu’). And things will be good.

  13. Nik Chankov Post author

    Valerie, you are totally wrong πŸ™‚ it’s upside down – loadModel() is the deprecated one and the App::import is the new way to load different classes in the memory. In fact it was written with loadModel in the beginning, but I change this to meet new convention in cake.

  14. Pingback: Signets remarquables du 29/09/2008 au 02/10/2008 | Cherry on the...

  15. Thomas

    Hi,
    thank you for your job. You have a small mistake in your script in the MenuComponent:
    return $this->Menu->find(‘all’);
    You have to write all in lower-case charakter.

    Regards Thomas

  16. Pingback: pshhqvmi

  17. Pingback: xldztoek

  18. Tuan Tran

    I couldn’t express my thank to you with my English.

    It’s a great work you’ve done here. It took me out of the endless sleepless-night.

    Thank you very much.

  19. Micke Fyhr

    Hi!

    It seems i can’t get this working, i’m beginner with cake and think it is only something i have done wrong.

    When i add the
    function beforeRender()
    {
    $this->data = am($this->data, array(‘Menu’=>$this->Menu->fetchData(&$this)));
    }
    to my app/app_controller.php i only get a blank webpage

    my db name is menus and i have the id and name field in it.

    Hope someone has any help for me with this.

    Would be very happy for all help i can get

    Regards
    // Mify

  20. Nik Chankov Post author

    try to replace the row:

    $this->data = am($this->data, array(’Menu’=>$this->Menu->fetchData(&$this)));

    with:

    $this->data = am($this->data, array(’Menu’=>$this->Menu->find('all')));

    This way you skipping the component part.

  21. Peter James

    @Nik Chankov
    Nik,

    This causes problems on mysetup when passing data to and from edit methods of controllers, e.g. if you were editing a menu using your example above this data will be overwritten when passed through to the edit method?

  22. Nik Chankov Post author

    Peter, it should not if you using $this->data[‘show_menu’] or some unique string which doesn’t match Menu controller.

    In fact if you have Menu form and you are using my method – yes it will be a problem. But my example is proov of concept. I would use PublicMenu and AdminMenu as $this->data nodes instead of Menu only.

  23. Peter James

    Right – I get you πŸ™‚

    With regards to my previous comment I worked it out – it was the old notation that threw me somewhat as I am used to XPath.

    function display(&amp;$layoutData) {
              //This extracting only the name of the menu from the multidimentional array.
              //$contents = Set::extract('/User/username', $data['User']);
              $avatars = Set::extract('/UserProfile/avatar', $layoutData['UserProfile']);
              $usernames = Set::extract('/User/username', $layoutData['UserProfile']);
             
              $ret = "";
             
              for($i; $i&lt;=count($usernames); $i++) {
               
                    $ret.="".$usernames[$i]."";
              }
              $ret .= "";
              return $ret;
             
           }
  24. Pingback: Widgets et contenu modulaire avec CakePHP - Pierre MARTIN

  25. gurel

    had problems uploading png’s too. you can edit line 302 from:
    imagepng($newImage, $dstimg, $quality);
    to:
    imagepng($newImage, $dstimg, 9);

  26. render_da_pain

    I think using components with requestAction() is much more better πŸ˜€
    true MVC style FTW πŸ˜€

  27. Pingback: How to deal with Layouts in CakePHP | Vietnam Online Marketing Addict | Nganhtuan.com

Leave a Reply

Your email address will not be published. Required fields are marked *