Love Fuel?    Donate

FuelPHP Forums

Ask your question about FuelPHP in the appropriate forum, or help others by answering their questions.
Modules routing nightmare
  • Hello,

    I'm trying to setup a modular structure for an application in the way of this thread:

    my app routes are:

    '_root_' => 'front/index',
    '_404_' => 'errors/404',
    '(:any) => 'front/index/$1'

    I have a module called 'dashboard' supposed to manage all front side user functions like profile, orders etc.
    Then, it is meant to call others modules like "user" or "orders" and then to have a catch all route also.

    URIs:

    /dashboard/ should goes to the main menu of the dashboard
    /dashboard/user/ to the main menu of the user module displayed inside the dashboard's content part.

    Controller_Front action_index function is:

    $uri = implode('/', \Request::active()->method_params);
    if (!empty($uri))
    {
      $document = \Request::forge($uri, false)->execute()->response()->body();
      $this->theme->set_partial('document', $document);
    }

    if i call /dashboard/
    with no routes in module 'dashboard' it works
    but /dashboard/user throw a 404... logical

    if i put routes in the module:
    return array(
      '(:any)' => 'dashboard/index/$1'
    );

    /dashboard/ works
    /dashboard/user goes directly to the module without being routed first to front/index route, thus dashboard is displayed alone without to be included in the general template.

    I can't figure out where is the logic in that case.

    Best regards
  • Module routes can only be used for routes inside that module. In other words, the lefthand site must define a route that starts with the module name, so (:any) will not work in a module, you can to define that in the global (=app) routes.php.

    In our webengine the front controller also handles the backend (your dashboard), for us that is just another uri within the application. So we only have a catch all route, no others needed.

    If your design has a separate front and backend controller, you need two catch-alls:

    'dashboard(:any) => 'dashboard/back/index$1'
    '(:any) => 'front/index/$1'
  • hi Harro,

    I think we don't understand each other.

    I speak only for the frontend here.

    I need to have all URIs routed to front/index because this controller manage the main frontend template.

    this catch all route:
    return array(
      '(:any)' => 'dashboard/index/$1'
    );
    IS in the Dashboard module's routes.php where dashboard is the controller in the module

    Or do I have to set those app routes ?

    'dashboard(:any) => 'front/index$1'
    '(:any) => 'front/index/$1'

    But the module 'Dashboard' will have precedence too isn't it? then the first route will never match?

    Edit: With those routes in app, the Dashboard module is called alone... not rendered in the main tamplate.
    I'm lost...
  • Me to, I have no clue what you're on about.

    If you have a front controller that handles ALL requests as you write, routing "(:any)" is all you need to do, no requirement for any other route.

    Routes have precedence over controller action detection (app or module).
  • HarroHarro
    Accepted Answer
    This is the route config in all our apps:

    /**
     * Global application routes.
     */
    return array(
        '_root_'  => 'webengine/loader',        // The default route

        '_403_'   => 'webengine/loader/403',    // The main 403 route
        '_404_'   => 'webengine/loader/404',    // The main 404 route
        '_500_'   => 'webengine/loader/500',    // The main 500 route

        // download front controller
        'download(:any)' => 'download$1',

        // rest api front controller
        'api(:any)' => 'api/index$1',

        // all other requests to the web front controller
        ':any'    => 'webengine/loader'
    );

  • That's exactly the kind of global routes I thought to have!!

    you said "Routes have precedence over controller action detection (app or module)."

    How my dashboard module can be invoked without Controller_front->action_index invoked first in my case? because it has routes?

    But i need routes in my modules as they can acts themselves as container of other modules...
  • HarroHarro
    Accepted Answer
    It must be a route, otherwise your catch-all would have caught the request for your dashboard.

    I can't judge your architecture, but either your controller has a route and can act autonomous (being called directly) or it doesn't, but you can't have both.

    Module routes are autoloaded, and are only loaded when the router detects it's a potential request for a module, in other words, if the first segment in the request URI matches a module name.

    And if you have a module route like "dashboard/(:any)", then all of a sudden none of your dashboard requests will go to your front controller anymore, as this route will match all URI's starting with "dashboard/".
  • well,

    I modified the Controller_Front index method code:

    $uri = '';
    $method_params = \Request::active()->method_params;
    if (!empty($method_params))
    {
    $uri = array_shift($method_params);
    $module_params = implode('/', $method_params);
    }
    if (!empty($uri))
    {
    $document = \Request::forge($uri, false)->execute(array('params' => $module_params))- >response()->body();
    $ this->theme->set_partial('document', $document);
    }

    then I invoke the module without anything except his name and i pass the rest as 'param' to the index method as I noticed that if no action were detected it fallback to 'index' method, so i have my catch all with no routes in the module.

    some remarks about my little piece of code? no drawback?
    Of course i have to make some checks about module exists and write some code for when no module were invoked..
  • we wrote at same time ;)

    you said 'It must be a route', yes that's it, when i remove routes from module nothing like that happens.
  • HarroHarro
    Accepted Answer
    Only route to self-sufficient controller methods.

    If you have a mix between controller actions that can be called directly and actions that produce widgets and need to be called by your front controller, make sure your routes are as specific as possible.

    You could also come up with an architecture that maps all these controller actions to a single entry point, like we do with the download or api routes.

    Or do like we do in our webengine, record in config what type of data a defined endpoint returns (a full page, a widget, a menu structure, a json response, etc), and have your front controller deal with all requests.
  • Thank you Harro!

    I have to say that one of the best point of Fuelphp is its team, having a team member playing the role of the third eye is very valuable.

    PS: i don't know if in english 'third eye' is known.

    Have a great day,
    François
  • Don't know, I'm not a native speaker either. ;-). We use the phase "a second pair of eyes".

    I personally find it very important to provide as much support as you can, when you maintain an open source product other people rely on. Too many open source products die an early death because the developer(s) abandon it or can't be bothered to respond to questions or bug reports.

    Thanks for the kudos!
  • You are welcome Harro, it's just honest ;)
  • Hello Harry,

    I found one drawback using this kind of structure : how to know if a call is hmvc or not as all call are hmvc?

    Best regards.
  • Use \Request::is_hmvc() to check?
  • Hi Harro and sorry for the delay, i didn't receive the email notification of your answer, i have to check spam.

    No, i can't as all calls are Hmvc, \Request::is_hmvc() checks this 'static::active() !== static::main()'
    But in that kind of structure, it's always true since all requests come from the front loader...

    The only hack i've found is to add this function to module's controllers :

    public function is_hmvc()
    {
    return \Request::active()->uri->get() !== \Request::main()->uri->get();
    }

    At the end I'm a bit confused about this kind of architecture as it breaks some of the framework build-in functions. I don't know...

    What caused me wanting to setup this kind of things is what you wrote in the thread i mentioned at beginning of this talk :

    "What doesn't work is:
    - call cross-module (or app to module) the PHP way, like $result = \Module\Some_Controller::some_action();
    - do an HMVC call using Request but don't use the called action in isolation, but attempt to do Theme stuff in there"

    The 2nd point was what i was doing. Then i tried to start the right way. But it cause me more drawbacks than i thought, maybe I'm wrong and my old way was worst.
  • Requests from the frontloader aren't HMVC calls, HMVC is a controller requesting a secondairy controller. So maybe you are talking about something else?

    Perhaps you should do one step back, and first explain what you want to do at the functional level, so I can explain how you would go about that?
  • hmmm... But the frontloader IS a controller isnt'it?

    In your exemple:
    // all other requests to the web front controller
        ':any'    => 'webengine/loader'

    webengine is a controller and loader is a method or i'm wrong?

    from there you call your modules with Hmvc calls, no?
  • In MVC terms, the index.php is referred to as the front controller, so that is what confused me.

    That is what we use in our app framework, yes, and that is a regular controller. And that calls other controller actions via a secondary request, so yes, those are HMVC calls.

    And in all cases, Request::is_hmvc() returns true in those controllers, so what is your problem?
  • The problem is that some methods of module's controllers don't have to be accessed by browser, or even a whole module. but no way to prevent that except my small hack.

    Read again my first post of today please.

    When you have autonomous modules you know which one can ( and have to ) be called directly, and which have to be called by other module. But then your autonomous module do themes things, and you said this is not a good thing to do...

    to say short, I'm trying to figure out what is the best way to use Fuelphp, to figure out which architecture it is meant to work best with.
  • I don't have a clue what your on about.

    In the basics, every controller action is callable either by the browser or by an HMVC call. The only way to prevent a direct call is via routing (i.e. do not allow any routing to the method).

    Ideally, this is the path you should take, if you have an application architecture that uses a widget engine, no controllers should be called directly, possibly with the exception of api controllers out output controllers (for downloads, pdf or image generation, etc). Ideally, these will have a frontcontroller too, but you could use routes to limit access, for example by using /api/module/controller/method, and route these to /module/api/controller/method (and group all your api controllers).

    When an action is called, you can always use Request::is_hmvc(). It will return false when called by a client, and true when called by a Fuel request.

    If you want to mix hmvc and non-hmvc usage in a single controller (which I think from a design and architecture perspective is a bad idea), you can always use

    // only allow HMVC calls
    if ( ! \Request::is_hmvc())
    {
        throw new \HttpNotFoundException();
    }

    which would return a 404 if a client would request it.
  • you said:

    " if you have an application architecture that uses a widget engine, no controllers should be called directly"

    Of course because they are all called from the front loader controller.

    but you might want to have controllers you don't want to be called from front loader but from another module controller only, because they build only a part of a page, a component, like a form that can be used in multiple places. Unfortunately if you try to type the route for them in the browser, it works, the front loader will call them.

    And \Request::is_hmvc() will NOT help you in that case, it will return true.

  • I'm still lost.

    If you have an application architecture that uses a frontcontroller to assemble the page, it should receive ALL requests (except for non-HTML reponses like API or downloads), there are no controllers that can be called directly?

    And if you have this, your frontcontroller knows exactly what to call, it should not need or use a route.

    Your front controller captures all browser requests, so how a user could type in something that would route to and load one of these component controllers is beyond me...

  • Hi Harro!

    I have refined my thought in my previous post, of course they will not be called without the front loader.

    But, the front controller is in charge of building the page by a template, lets say this template:

    <header><?php echo $header; ?></header>
    <nav><?php echo $navbar; ?></nav>
    <div id="main"><?php echo $document; ?></div>
    <footer><?php echo $footer; ?></footer>

    For the document part ($document in my example) it will call a module depending of the uri (the $1 param in the ':any' route:
    $document = \Request::forge($uri)->execute()->response->body(); right?

    lets say this uri is http://example.com/blog
    it will call: $document = \Request::forge('blog')->execute()->response->body(); right?
    which will render the blog index content
    This content will probably have a 'sidebar' part, which will be loaded by the blog module by a call to a 'sidebar' module, lets say the route to the sidebar for the blog is 'sidebar/blog'.
    your blog module will call: $sidebar = \Request::forge('sidebar/blog')->execute()->response->body();
    to fill in the sidebar part of the blog index.
    and return it to the front loader as the main document part, am I still right?

    The sidebar part should never been returned to the front loader as a document for the main template, isn't it?

    But what if i call this uri : http://example.com/sidebar/blog ?

    the front loader will call: $document = \Request::forge('sidebar/blog')->execute()->response->body(); right?

    The sidebar module is not aware of which purpose it is called for, then it will return its content to the front loader as document part of the main template : fail!

    Indeed, the front loader cannot have the responsibility to know  what a module return is good or not.
    Then it's the sidebar module business to "know" if it can be called that way or not.
    That was the purpose of \Request::is_hmvc() isn't it? but in that case it will return 'true' and the poor sidebar module will do its job and return a content instead of a beautiful 404 error.

    Is that more clear for you? I know it's not easy sometimes to be understood on a forum ;)

    I repeat, I'm trying to figure out what is the best way to use Fuelphp, to figure out which architecture it is meant to work best with.
  • HarroHarro
    Accepted Answer
    That is poor design.

    How would the frontloader know what a valid page is and what not? The frontloader is in charge of creating your output, it should know!

    How would you deal with additional information on a page? Some could have a different menu (for example logged-in or not), some could have multiple widgets in the page body or the sidebar, etc.

    So you need an internal mapping, which defines the components that should be fetched for each of your page sections when a user requests /blog. And if you don't have such a mapping, have the frontloader throw a 404. A user should never be able to request any controller directly.

    Our frontloader goes much further than this, as we also store theme information (so you can have different themes for frontend and backend pages, or custom user themes), page templates (so you can define repeating items like headers and footers in a template,. and don't have to define them all for very page), symlinks to other pages or other site trees (our engine has multi-site support) so you an have a different url for frontend and backend, but still expose a single backend page on the frontend site, etc.
  • You said "That is poor design. " I agree, I didn't feel good with that design, that's why i posted here ;)

    At first i was in the way of autonomous modules like 'blog', 'dashboard', 'products' etc...

    All modules inherited from a front controller class that populate the main template except the document part, then each module was intended to populate this document part, eventually calling small modules like sidebar or what ever.

    But I've read your post saying that doing theme stuff in a module isn't a good idea:
    "What doesn't work is:
    - call cross-module (or app to module) the PHP way, like $result = \Module\Some_Controller::some_action();
    - do an HMVC call using Request but don't use the called action in isolation, but attempt to do Theme stuff in there"

    What is bad doing that?

    Thanks for your time btw...

Howdy, Stranger!

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

In this Discussion