Love Fuel?    Donate

FuelPHP Forums

Ask your question about FuelPHP in the appropriate forum, or help others by answering their questions.
controller inside a controller
  • hi everybody! i'm searching a framework that permits to invoke a controller (not just a controller method) inside a controller.
    example:
    class controller_page extends controller
    {
     public action_index()
    {
     $other_controller = new Controller_Cart_block();
     $other_controller->anyMethod();
     ........
    }
    }
    

    i need this beacuse from my point of view a controller is the logic that call the model and the view but not always the page requested by a URI. i tried to do this with FUEL and i had to put a require/include right before the class (maybe an elegant solution would involve autoload functionality) and i needed to pass two parameter to the default constructor of class Controller. while i don't have any idea about how fuel works, i don't know if the parameters that i passed was right or not (i passed the request and response passed to controller_page). so in conclusion: it is possible to use a controller inside another one in Fuel without invole hacking and ugly-faulty practices? and if it isn't possible, what is the best/beautiful/elegant solution to reuse controllers code inside another controller (e.g.: for build html-rendered blocks of code to put on every page)? code the block as a library? thank you.
  • well this is exactly the "hack" that every other frameworks have, with the exception that (maybe) the others can't determine if it is an "HMVC call" or a "normal call". it seems there is no way to implement hmvc in a different way. thank you.
  • HMVC isn't a "hack" in Fuel, it's part of Fuel's design.
  • yes, but it's not implemented as a normally instance of a controller. it's something *magic* like call_user_func(). maybe magic doesn't fit well.. let's call it *not so linear*. however i don't have anything against fuelphp nor the others frameworks.. i just want to know if there would be a more linear way, and if not, why, so i could rest in peace :) can i meet you over the net (i read that there is an irc channel where fuel developers met) to ask some more explanation about this topic? i'll be glad to learn something for people with a lot of experience! thanks.
  • What is your definition of "a normal instance of a controller"? I haven't got a clue what your issue is, so maybe you could explain yourself better? The IRC channel is on freenode, called #fuelphp.
  • Valerio Riva wrote on Sunday 27th of November 2011:
    class controller_page extends controller
    {
     public action_index()
    {
     $other_controller = new Controller_Cart_block();
     $other_controller->anyMethod();
     ........
    }
    }
    

    One reason you have to put an include/require is that your class names do not conform to FuelPHP's naming conventions so the Autoloader can't find the controller. http://docs.fuelphp.com/general/coding_standards.html#/naming_conventions
    http://docs.fuelphp.com/general/controllers/base.html#/controller_in_subdir "Controller_page" should be "Controller_Page" and should be located in app/classes/controller/page.php and "Controller_Cart_block" should be "Controller_Cart_Block" and be located in app/classes/controller/cart/block.php. Or if want "Controller_Cart_block" to be located at app/classes/controller/cartblock.php, then you need to name the class like so: "Controller_CartBlock". If you follow the class naming conventions of Fuel, you should never have to manually write an include or require. Hope that helps!
  • @WanWizard sorry i missed some words! i wanted to say "instantiate the controller as a normal object"
    @Johnny Freeman when i tried to load controllers, i used require because i didn't used naming convention. however i will try it with naming convention, and post the code here so maybe it would be more clear what is my intent! so expect a reply in the evenening! here it's 8.33 AM :)
  • You don't instantiate a controller yourself. The controller is part of the MVC framework, in that sense it is a special type of class. The same is true for the naming convention, this is how the framework works. If you need a "normal" class object, write a standard class (goes in /app/classes) and you can do whatever you want with it. See http://docs.fuelphp.com/general/classes.html for (a bit) more info.
  • here it is an example of what i was meaning
    file app/controller/welcome.php
    class Controller_Welcome extends Controller
    {
    
     /**
      * The basic welcome message
      *
      * @access  public
      * @return  Response
      */
     public function action_index($param1)
     {
      $widget = new Controller_Widget($this->request, $this->response);
      $widget->setSomething('This');
      $widget->var2 = 'is an';
      if ($param1 AND $widget->var2 == 'is an')
      {
       $widget->var3 = 'example';
      }
      $data['widget'] = $widget->action_index();
      return Response::forge(View::forge('welcome/index', $data));
     }
    }
    

    file app/controller/widget.php
    class Controller_Widget extends Controller
    {
     public $var1 = null;
     public $var2 = null;
     public $var3 = null;
    
     public function action_index()
     {
    
      return Response::forge(View::forge('welcome/index'));
     }
    
     public function setSomething($var)
     {
      /**
       * Some code here
       *
       *
       * */
      $this->var1 = $var;
     }
    }
    

    With this example i want to explain that a simple called made with
    $widget = Request::forge('mycontroller/mymethod/parms')->execute();
    
    doesn't fit my needs because it's just a call of one method with some params and i can't use the 2nd controller as a full object. with "full object" i mean that i don't have to write widget method to act as a "function" when all parameters must be passed within the call of the method. with "full object" i mean that i can have the widget as an object that "lives" inside the caller. The example i wrote, actually works.. or well, it doesn't return errors or warnings. maybe there will be some conflicts between libraries (like database library or something else).. i don't know because i don't develop with fuelphp. What do you think about my solution? would be compatible with other components of fuelphp? thank you.
  • If you don't want an HMVC call, don't make one. HMVC calls aren't meant to call other class methods, it's a complete framework request, which is supposed to return the output related to the call (in most cases, html, xml or json). You need to get your head around the way FuelPHP works, and design your application accordingly. In this case, widgets should be self-contained entities, producing a chunk of HTML which you can incorporate into a page template. Ideal to handle via an HMVC call. If you have information you need to pass to the widget controller, use parameters when forging the request. It is very bad practice to access the internals of another class, you should strive for loose coupling. If you absolutely need to work like this, use a container class:
    // store in app/classes/widget.php
    class Widget
    {
        public $var1  = null;
    
        public function __construct($request)
        {
            $this->request = $request;
        }
    
        public function render()
        {
            return Request::forge($this->request, array('widget' => $this))->execute();
        }
    
    }
    

    You can then do
    $widget = new Widget("mycontroller/mymethod");
    $widget->var1 = 'blah';
    $result = $widget->render();
    
  • that's ok. i already wrote widgets that return html chunk with the call of one method. i already use successful the module "codeigniter-modular-extensions-hmvc" to do it with CI. the question are
    - why i need to do a "a complete framework request" when i just need to one ore more method from another class
    - what if i need to use the same widget multiple times inside the same page (examples: login box on top bar and on right column block). i would make n+1 "complete framework request" instead of 1.
    - why i have to use a "container class" when a "widget" fits completely a normal controller class that must have access to models and views?
    - am i missing something structural or hmvc/mvc pattern related? i will repeat myself: i still think there must be another way to implement hmvc.. but i'm not a framework developer.. so if someone says "no", i will stop complain
  • You don't HAVE to do anything. You can do whatever you want. FuelPHP is flexible enough to accomodate you.
    // this works too
    $result = \Module\Controller_This::that($parms);
    
    // or this
    $object = new \Module\Controller_This();
    $result = $object->that($parms);
    

    My point there was that if you want to go that route, don't use controllers, as they are used by the framework to define request actions. Use standard classes instead. If you create a folder called 'widget', you can call your classes 'Widget_Name' which makes it's purpose clear. The idea behind MVC, and in relation HMVC is abstraction, preferably combined with loose coupling. If you call class methods directly, you loose all that. You also loose the abstraction within the framework, if you modify something request related (for example search paths), you modify it for the entire request. HMVC requests also allow you to scale by converting an internal request to an external request, which means your original app becomes the web-tier, and your HMVC modules your application-tier, running on different servers.
  • Harro Verton wrote on Tuesday 29th of November 2011:
    The idea behind MVC, and in relation HMVC is abstraction, preferably combined with loose coupling. If you call class methods directly, you loose all that. You also loose the abstraction within the framework, if you modify something request related (for example search paths), you modify it for the entire request. HMVC requests also allow you to scale by converting an internal request to an external request, which means your original app becomes the web-tier, and your HMVC modules your application-tier, running on different servers.

    those are exactly the answers i was looking for. thank you so much. now i understood why fuelphp and other frameworks in general, use this approach.
  • Harro Verton wrote on Tuesday 29th of November 2011:
    You don't HAVE to do anything. You can do whatever you want. FuelPHP is flexible enough to accomodate you.
    // this works too
    $result = \Module\Controller_This::that($parms);
    
    // or this
    $object = new \Module\Controller_This();
    $result = $object->that($parms);
    

    It does not work. My code is: http://clip2net.com/s/25zDo
    http://clip2net.com/s/25zEb
    http://clip2net.com/s/25zEX or
    namespace Substartmodule;
    use Fuel\Core\Request;
    use Fuel\Core\View;
    
    class Controller_Substartmodule extends \Fuel\Core\Controller {
    
    
    
        public function getSubMod( $d ){
    
            if( Request::is_hmvc() ){ // HMVC
    
                $view = View::forge('top/subtop',$d);
                return $view;
    
            }
            else { Request::show_404(); }
    
        }
    
    
    }
    
    namespace Decorator;
    
    use Request;
    
    class Controller_Decorator extends \Fuel\Core\Controller {
    
        public $height;
        public $width;
        public $name;
    
        public function action_test(){
    
            $d = array(
                'height'=>500,
                'width'=>600,
                'name'=>'Dsf'
            );
            $obj = new \Substartmodule\Controller_Substartmodule();
            $res = $obj->getSubMod($d);
    
            $this->response->body = $res;
    
        }
    
    }
    
    4096!
    
    ErrorException [ 4096 ]: Argument 1 passed to Fuel\Core\Controller::__construct() must be an instance of Fuel\Core\Request, none given, called in V:\home\fuelphp\fuel\app\modules\decorator\classes\controller\decorator.php on line 27 and defined
    COREPATH/classes/controller.php @ line 35
    

    Please show me how to do it.
  • Read the message carefully. This has nothing to do with calling other controller method, it's about the fact that the __construct() prototype in your class is not correct. If you extend \Fuel\Core\Controller and you overload methods, you have to make sure the method prototype at least contains the same arguments of the same type. That's a PHP language rule.
  • Harro is correct in that you can't overload methods and change parameters. However, (with all due respect) I don't think that is what's happening here. It looks like you just need to pass the Request and Response instance to the Controller_Substartmodule() constructor. So change this: $obj = new \Substartmodule\Controller_Substartmodule(); to this: $obj = new \Substartmodule\Controller_Substartmodule($this->request, $this->response); That should do it.
  • You are right, they are also required. But having said that, someone who wants to instantiate controllers like this imho has some serious flaws in their application design. You should not do this, like you should not call one controller from another. Even though FuelPHP is flexible enough to actually let you do that.
  • Harro Verton wrote on Wednesday 4th of July 2012:
    You are right, they are also required. But having said that, someone who wants to instantiate controllers like this imho has some serious flaws in their application design. You should not do this, like you should not call one controller from another. Even though FuelPHP is flexible enough to actually let you do that.
    I agree with you, but it is sometimes necessary to invoke methods on a single controller in the other. What do you think about this decision?
    namespace Testbase;
    
    
    class Controller_Testbase extends \Controller {
    
        public static $instance = null;
    
        public static function instance(){
    
           if(!self::$instance){
               self::$instance = new \Testbase\Controller_Testbase();
           }
    
              return self::$instance;
    
        }
    
        public function getMe(){
    
            return $hello = 'Me!';
    
        }
    
        public function getAny($d){
    
            return $d;
    
        }
    
    }
    
    
    
    
    namespace Test;
    
    
    class Controller_Test extends \Controller {
    
        public function get(){
    
            \Module::load('testbase');
    
            $obj = \Testbase\Controller_Testbase::instance();
    
            $me = $obj->getMe();
            $any = $obj->getAny('Oh yes)!');
    
            $view =
                'Hi! '
                .$me
                .$any;
    
            return $view;
    
    
        }
    
        public function action_index(){
    
            return $this->get();
    
        }
    
    }
    
    
    
    \fuel\core\classes\controller.php - mod
    
     public function __construct(\Request $request, \Response $response)
    at
     public function __construct(\Request $request = null, \Response $response = null)
    
    
  • I think that is bad design. If you need functionality in multiple controllers, split it off into a separate class (not a controller), and call that, like you would call a model. Alternatively, you can do "Controller_Test extends Controller_Testbase" and get access to the methods that way, which is quite common (use base controllers that is).
  • -> Alternatively, you can do "Controller_Test extends Controller_Testbase" and get access to the methods that way, which is quite common (use base controllers that is). Access to methods is one thing, but what if I need a context?
    
    \fuel\app\modules\base\classes\controller\base.php
    \fuel\app\modules\base\views\indexbase.php
    namespace Base;
    
    class Controller_Base extends \Controller_Template {
    
        public $template = 'indexbase';
    
    }
    
    
    
    \fuel\app\modules\module1\classes\controller\base.php
    \fuel\app\modules\module1\views\index.php
    
    
    namespace Module1;
    
    class Controller_Base extends \Base\Controller_Base {
    
        public function before($data = null)
        {
            parent::before(); 
    
            $this->auto_render = false; 
        }
    
        public function action_index(){
    
            $this->template->content = \View::forge('index');
    
            $this->response->body = $this->template;
    
        }
    
    }
    
    Error!
    
    Fuel\Core\FuelException [ Error ]: The requested view could not be found: indexbase
    COREPATH/classes/view.php @ line 381
    

    In this case, inheritance is not helping. Or am I mistaken?
  • Interitance works fine. The issue here is that your $template is set in the Base namespace, but defined 'relative'. So your Module1 controller will search for it within it's namespace (as that is the current context) and if not found in app/views, but it will not search other modules. In this case you have to define the view as being located in the Base module:
    public $template = 'Base::indexbase';
    
  • Harro Verton wrote on Friday 6th of July 2012:
    Interitance works fine. The issue here is that your $template is set in the Base namespace, but defined 'relative'. So your Module1 controller will search for it within it's namespace (as that is the current context) and if not found in app/views, but it will not search other modules. In this case you have to define the view as being located in the Base module:
    public $template = 'Base::indexbase';
    

    Spaces names I knew, I just wanted to show an example. I do not know about the 'Base :: indexbase' - it's very good). But this is not reflected in the documentation. Thanks a lot for your answers.
  • It is not specifically mentioned in the Controller_Template because it's standard behaviour in FuelPHP. All file locate activities are done by the Finder class, which allows you to search:
    - using a basepath, a folder and a file (APPPATH, 'views', 'viewname.php')
    - using a fully qualified path (APPPATH.'views/viewname.php')
    - using a module prefix (APPPATH', 'views', 'module::viewname.php') Also, the Finder by default searches the current context first (for example a module), then all custom paths added to the request, then all loaded packages, then APPPATH, then COREPATH. In case of a module prefix, that specific module will be searched only in the module specified, and if not found, in the custom paths added.

Howdy, Stranger!

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

In this Discussion