Love Fuel?    Donate

FuelPHP Forums

Ask your question about FuelPHP in the appropriate forum, or help others by answering their questions.
Using HMVC with the theme class?
  • Hello all, I am trying to create a setup where I have a set of page layout templates, and then a partial template for each module view.
    The architecture I would like is like this: app controllers control the browser requests, and assign module view output to partial areas in the theme template.
    module controllers determine what partial view template to use for the request, and the assigns all variables to that partial template.
    I am also using Smarty. So what I have tried so far is something like this:
    //in the app controller
        public function action_index()
        {
          $this->theme = \Theme::instance();
          $this->theme->set_template( 'templates/default' );
          $this->tpl = $this->theme->get_template( 'templates/default' );
    
          $this->tpl->set( 'title', 'Test Title' );
          $this->tpl->set( 'content', Request::forge( 'pages/pages/index', false )->execute() );
    
    
        }
    
    //in the module controller
        public function action_index()
        {
          $this->theme = \Theme::instance();
    
          $this->tpl = $this->theme->view( 'pages/test' );
    
          $this->tpl->set( 'name', 'Brian Jeffries' );
    
    
        }
    

    I get a 404 error. If I remove the execute() part in the app controller, I get the layout template, but no partial.
    I am pretty certain it is simply a matter of me not understanding something fundamental, but I've spent hours trying to figure it out with no luck.
    Thanks!
  • OK I've figured out that I need to load the module first, and define the path in config, but now smarty can't access the variables.
    New app controller code:
    //app controller
        public function action_index()
        {
          $this->theme = \Theme::instance();
          $this->theme->set_template( 'templates/default' );
          $this->tpl = $this->theme->get_template( 'templates/default' );
    
          $this->tpl->set( 'title', 'Test Title' );
          Fuel::add_module( 'test' );
          try
          {
            $widget = Request::forge( 'test/page/test' )->execute();
          }
          catch ( HttpNotFoundException $e )
          {
            $widget = 'Module Not Found';
          }
    
          $this->tpl->set( 'content', $widget );
    
    
        }
    
    //module controller
        public function action_test()
        {
          return 'hello';
    
        }
    

    which gets me the errors:
    Notice!
    
    ErrorException [ Notice ]: Undefined index: content
    
    APPPATH/tmp/Smarty/templates_c/f6e533553ae37904936623049a99dca5a798682a.file.default.html.php @ line 35:
    
    34: <body>
    35: <?php echo $_smarty_tpl->tpl_vars['content']->value;?>
    36: 
    Notice!
    
    ErrorException [ Notice ]: Trying to get property of non-object
    
    APPPATH/tmp/Smarty/templates_c/f6e533553ae37904936623049a99dca5a798682a.file.default.html.php @ line 35:
    
    34: <body>
    35: <?php echo $_smarty_tpl->tpl_vars['content']->value;?>
    

    Now I am stumped, because when I var dump the request object I get a huge page full of stuff and I don't know how to render it properly. :-(
  • You don't need to load the module first for a secondary request. If the request is for a module controller, the request will load the module for you. If you get a 404 on a request, something couldn't be found. You do need to add the 'false' flag on the Request::forge() if your test module isn't routable from the URL. I haven't worked with Smarty on a FuelPHP project, are you sure that a set() call on a view would result in setting a Smarty variable (i.e. an assign call)? What happens if you just do
    <?php echo $content; ?>
    
  • Yep set() does indeed map to the assign method in smarty. The problem seems to be putting a request into the set() method. Theme appears to be meant for the other way around, where the browser requests a module or controller which then sets the templates and the partials directly. I have been looking at the fuel depot project which is using modules as app compartmentalisation, and I think I'll just go that route. I just need to get my head away from the cms mentality and think more along the lines of a purpose built app. Although it would be fantastic to be able to just write {area name="nav"} in the layout template, and then in the controller write $this->theme->set('nav', 'module_name/method'), and then have that module_name/method set its own partial template and variables. I will eventually write something that does that but for now I'll do what I can easily replicate. Thanks for responding though.
  • I don't know what you mean by "the other way around". The purpose of the Theme class is that you select a theme, then set a page template from the theme. This page template contains the different sections (header, footer, sidebar, body, etc...) which are just variables echo'd out. From the template (which is just a view) point of view, it doesn't matter if you echo out $partial which has been set by calling set_partial() on the theme instance, or if you echo out $body which has been set by a direct call to set() on the theme template. The end result should be the same. And what you set can be anything that can be cast to string, and that includes a secondary request that returns a view (I use that mechanism a lot when I have modules providing widgets). So what you want should work without problems, and it does in some of my apps, with standard views. It is a very logical approach if you want loose coupling between the core of your app and it's modules. Perhaps it is Smarty related? What happens if you set a string instead of a request object? Is that displayed? Maybe Smarty doesn't like getting an object passed? I assume this works as in your example you set 'title' too. If it is indeed the object that's the issue, you can try casting it to string:
    $widget = Request::forge( 'test/page/test' )->execute();
    $this->tpl->set( 'content', (string) $widget );
    
  • Whew! I finally figured it out, thanks to your insistence that it was possible. It all revolves around the naming of the theme instances. :-) Now I have everything working nicely so far, including Smarty, and multiple modules per area in the template. Here is what I ended up with:
    //
    //This is the app controller that calls modules into the main layout template.
    //
    class Controller_Page extends Controller
      {
    
        public function before()
        {
    
          // call the parent controller
          parent::before();
        }
    
        /**
         * The index page.
         *
         * @access  public
         * @return  Response
         */
        public function action_index()
        {
          //name the theme instance here so you can pass it into the after() method for rendering
          $this->page_name = 'index';
    
          $data[ 'title' ]                      = 'Test Page';
          $data[ 'content_area' ][ 'module_1' ] = static::set_module( 'test/page/test' );
          $data[ 'content_area' ][ 'module_2' ] = static::set_module( 'test/page/test2' );
    
          //set main layout template here, since this is the app controller.
          \Theme::instance( $this->page_name )->set_template( 'templates/default' )->set( $data );
    
        }
    
        public static function set_module( $uri )
        {
          try
          {
            return Request::forge( $uri, false )->execute();
          }
          catch ( HttpNotFoundException $e )
          {
            return 'Module Not Found';
          }
        }
    
        /**
         * After controller method has run, render the theme template
         *
         * @param  Response  $response
         */
        public function after( $response )
        {
          // If nothing was returned render the defined template
          if ( empty( $response ) )
          {
            $response = \Theme::instance( $this->page_name )->render();
          }
    
          return parent::after( $response );
        }
      }
    
    //
    //This is the module controller  that sets its own template views, and variables. In this controller I create two separate views using the same template partial view.
    //
    namespace Test;
    
      class Controller_Page extends \Controller
      {
    
        /**
         * The home page.
         *
         * @access  public
         * @return  Response
         */
    
        public function before()
        {
    
          // call the parent controller
          parent::before();
        }
    
        public function action_test()
        {
          $this->view_name = 'test';
    
          $data['title'] = 'Test Page';
          $data[ 'name' ] = 'Brian';
    
          //set view partial here.
          \Theme::instance( $this->view_name )->set_template( 'pages/test' )->set( $data );
    
        }
    
        public function action_test2()
        {
          $this->view_name = 'test2';
    
          $data[ 'title' ] = 'Test Page 2';
          $data[ 'name' ]  = 'Brian Jeffries';
    
          //set view partial here.
          \Theme::instance( $this->view_name )->set_template( 'pages/test' )->set( $data );
    
        }
    
        /**
         * After controller method has run, render the theme template
         *
         * @param  Response  $response
         */
        public function after( $response )
        {
          // If nothing was returned render the defined template
          if ( empty( $response ) )
          {
            $response = \Theme::instance( $this->view_name )->render();
          }
    
          return parent::after( $response );
        }
      }
    
    //
    //This is the main template syntax. (default.html)
    //
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
      "http://www.w3.org/TR/html4/loose.dtd">;
    <html>
    <head>
     <title>{$title}</title>
    </head>
    <body>
    {foreach $content_area as $module}
    <br />
    {$module}
    {/foreach}
    </body>
    </html>
    
    //
    //And this is the syntax in the partial view template (test.html)
    //
    {$name}
    
    {$title}
    
  • Good to hear you've got it working. Although I don't understand what the problem was. If you have only one theme instance, the default should do fine, which is accessable through Theme::instance(). And you don't need to use render() under normal cicumstances. You can just pass the objects around, the framework will render them automatically if and when needed. This might save some processing if your application flow changes and you need to alter already created partial objects, or you run into a 403 or 404 situation.

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

In this Discussion