New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dispatch to another module in a multi-module app #121

Closed
adrlil opened this Issue Oct 13, 2012 · 19 comments

Comments

Projects
None yet
@adrlil

adrlil commented Oct 13, 2012

I tried to modify the file apps/library/Acl.php in my test HMVC app base on https://github.com/phalcon/mvc/tree/master/multiple, changed the dispatcher code as followed:

$dispatcher->forward(
    [
        'module'     => 'backend',
        'controller' => 'user',
        'action'     => 'login'
    ]
);

Thus want to dispatch to backend/user/login as module/controller/action in some conditions.
It doesn't work, just dispatch to the same module where the responding controller is. Any solution? Or should it be a new feature?

@ghost

This comment has been minimized.

Show comment
Hide comment
@ghost

ghost Oct 13, 2012

Collaborator

Hi Lee, a dispatcher can't forward to modules outside the current module. This happens because controllers might be located at any directory and have any namespace, suffix, prefix, etc. If you want to forward to other module you may use a temporary HTTP redirection:

$response->redirect("backend/user/login");

http://docs.phalconphp.com/en/latest/reference/response.html#making-redirections

Collaborator

ghost commented Oct 13, 2012

Hi Lee, a dispatcher can't forward to modules outside the current module. This happens because controllers might be located at any directory and have any namespace, suffix, prefix, etc. If you want to forward to other module you may use a temporary HTTP redirection:

$response->redirect("backend/user/login");

http://docs.phalconphp.com/en/latest/reference/response.html#making-redirections

@adrlil

This comment has been minimized.

Show comment
Hide comment
@adrlil

adrlil Oct 18, 2012

That's ok I think.

adrlil commented Oct 18, 2012

That's ok I think.

@adrlil adrlil closed this Oct 18, 2012

@DaanBiesterbos

This comment has been minimized.

Show comment
Hide comment
@DaanBiesterbos

DaanBiesterbos Oct 6, 2013

I really love Phalcon. Please don't get me wrong. I'm very happy with most of the framework.

However, it might be nice to offer some functionality to at least facilitate the possibility. Multi module apps very useful but it would be so much better if you would not have to hack the pieces together. As a developer being used to Zend Framework it feels a little unnatural to not being able to share such functionality accross my modules. The code used above should - in my opinion - just work. Assuming all configurations are correct. That should be up to the developer. Using different folder structures should not break functionality. It should just require another kind of configuration.

Zend framework solves this by firing an init event for each module being loaded.( It should not be cluttered with all kinds of operations. But it is a nice possibility to inject lazy loading services, attach events etc on a module to module basis. ) And zf basically collects a 'cachable' configuration object on bootstrap. (Routes, Services etc) Optionally overriding configurations/services in a logical order. Global config->Module config->User config. Also, by moving module configurations to the module itself it is much easier to reuse the module. Personally I believe it is good design practice to completely separate the module from the application. ZF uses a single autoload folder in the global config folder where you can put a modulename.config.php file allowing developers to override module configurations on a application to application basis to finetune the module if needed. This means that you would not have to change a well designed module in new applications. Just override the default module configuration. It may be a hassle to set it up. Especially for developers with little experience with frameworks. But then again. It gets easier pretty quick andm how great is it to be able to reuse essential modules accross different projects. Keeping them identical and separated is a real timesaver when it comes to maintainance.

Again; The configuration should be cached in production. But I really believe it is a very elegant way to use modules.

DaanBiesterbos commented Oct 6, 2013

I really love Phalcon. Please don't get me wrong. I'm very happy with most of the framework.

However, it might be nice to offer some functionality to at least facilitate the possibility. Multi module apps very useful but it would be so much better if you would not have to hack the pieces together. As a developer being used to Zend Framework it feels a little unnatural to not being able to share such functionality accross my modules. The code used above should - in my opinion - just work. Assuming all configurations are correct. That should be up to the developer. Using different folder structures should not break functionality. It should just require another kind of configuration.

Zend framework solves this by firing an init event for each module being loaded.( It should not be cluttered with all kinds of operations. But it is a nice possibility to inject lazy loading services, attach events etc on a module to module basis. ) And zf basically collects a 'cachable' configuration object on bootstrap. (Routes, Services etc) Optionally overriding configurations/services in a logical order. Global config->Module config->User config. Also, by moving module configurations to the module itself it is much easier to reuse the module. Personally I believe it is good design practice to completely separate the module from the application. ZF uses a single autoload folder in the global config folder where you can put a modulename.config.php file allowing developers to override module configurations on a application to application basis to finetune the module if needed. This means that you would not have to change a well designed module in new applications. Just override the default module configuration. It may be a hassle to set it up. Especially for developers with little experience with frameworks. But then again. It gets easier pretty quick andm how great is it to be able to reuse essential modules accross different projects. Keeping them identical and separated is a real timesaver when it comes to maintainance.

Again; The configuration should be cached in production. But I really believe it is a very elegant way to use modules.

@tugrul

This comment has been minimized.

Show comment
Hide comment
@tugrul

tugrul Oct 6, 2013

Contributor

@DaanBiesterbos I have used ZF 1. ZF bootstrap approach looks like good design but not useful if you try to make extreme things. I looked up ZF 2 and looks like too many complex and there are too many unnecessary configurations. I left using ZF rewriting my projects with Phalcon.

Phalcon has got very simple and efficient design. There are some missing features but I believe the Phalcon will be better and mature ongoing time.

Contributor

tugrul commented Oct 6, 2013

@DaanBiesterbos I have used ZF 1. ZF bootstrap approach looks like good design but not useful if you try to make extreme things. I looked up ZF 2 and looks like too many complex and there are too many unnecessary configurations. I left using ZF rewriting my projects with Phalcon.

Phalcon has got very simple and efficient design. There are some missing features but I believe the Phalcon will be better and mature ongoing time.

@DaanBiesterbos

This comment has been minimized.

Show comment
Hide comment
@DaanBiesterbos

DaanBiesterbos Oct 6, 2013

@tugrul I could'nt agree more. And I prefer Phalcon above ZF2 as well. Although the complexity in ZF is not so bad. It just takes a while to get used to. I not trying to say to solve phalcon issues the ZF way. Just the basic things that glues the components together would be nice. I'm sure phalcon will mature over time. Perhaps it is possible to get some beautiful shiny things from matured frameworks and only the things that has something to do with basic functionality. Like forwarding to other modules :p

That would be golden. And all I could possibly ask from a great framework with performance in mind.

DaanBiesterbos commented Oct 6, 2013

@tugrul I could'nt agree more. And I prefer Phalcon above ZF2 as well. Although the complexity in ZF is not so bad. It just takes a while to get used to. I not trying to say to solve phalcon issues the ZF way. Just the basic things that glues the components together would be nice. I'm sure phalcon will mature over time. Perhaps it is possible to get some beautiful shiny things from matured frameworks and only the things that has something to do with basic functionality. Like forwarding to other modules :p

That would be golden. And all I could possibly ask from a great framework with performance in mind.

@tugrul

This comment has been minimized.

Show comment
Hide comment
@tugrul

tugrul Oct 6, 2013

Contributor

@DaanBiesterbos Access to component context is very hard in Zend because there is unnecessary encapsulation. For example i need to access Dispatcher instance and traversing is very hard generally and it forced me to use Zend_Registry.

Phalcon DI is everywhere for example I can access the dispatcher parameter in view like $this->dispatcher->getParam('param')

Another problem is performance. I used Zend_Navigation with 4k nodes and its terrible because generates tree every request and after find active node. I tried to store tree on the apc cache there is no use. I'm making my solution with mongodb now.

Contributor

tugrul commented Oct 6, 2013

@DaanBiesterbos Access to component context is very hard in Zend because there is unnecessary encapsulation. For example i need to access Dispatcher instance and traversing is very hard generally and it forced me to use Zend_Registry.

Phalcon DI is everywhere for example I can access the dispatcher parameter in view like $this->dispatcher->getParam('param')

Another problem is performance. I used Zend_Navigation with 4k nodes and its terrible because generates tree every request and after find active node. I tried to store tree on the apc cache there is no use. I'm making my solution with mongodb now.

@DaanBiesterbos

This comment has been minimized.

Show comment
Hide comment
@DaanBiesterbos

DaanBiesterbos Oct 6, 2013

Hmmmm.. I'm not very familiar with zend framework 1. I guess you are right. In zend framework 2 its not so bad.
You can get route params or request values from $this->params() within the action controller.

Solved the forward issue btw...I've not tested it very well. But for now it seems to do what I want.
Perhaps somebody can use it.

  1. Extend Phalcon\Mvc\Dispatcher and add a dispatch:beforeForward event.

    class MyDispatcher extends Phalcon\Mvc\Dispatcher
    {
         public function forward($forward)
         {
              $this->getEventsManager()->fire("dispatch:beforeForward", $this, $forward);
              parent::forward($forward);
         }
    }
  2. I've added some metadata in modules.php and attached to the event.

/**
 * Register application modules
 */
$application->registerModules(array(

    'moduleA' => array(
        'className' => 'MyApp\ModuleA\Module',
        'path' => __DIR__ . '/../apps/moduleA/Module.php',
        'metadata' => array(
            // // Enable forwarding to other modules    
            'controllersNamespace' => 'MyApp\ModuleA\Controllers',
            'controllerSuffix' => '',
            'actionSuffix' => '',
        ),
    ),
    'moduleB' => array(
        'className' => 'MyApp\ModuleB\Module',
        'path' => __DIR__ . '/../apps/moduleB/Module.php',
        'metadata' => array(
            // // Enable forwarding to other modules    
            'controllersNamespace' => 'MyApp\ModuleB\Controllers',
            'controllerSuffix' => '',
            'actionSuffix' => '',
        ),
    ),
));


// Attach to dispatch:beforeForward event
$modules = $application->getModules();
$di->getShared('eventsManager')->attach('dispatch:beforeForward', function($event, $dispatcher, array $forward) use($modules)
{

    if(isset($forward['module'])){

        // Check whether the module is registered
        if(!isset($modules[ $forward['module'] ])){
            throw new \Phalcon\Mvc\Dispatcher\Exception('Module ' . $forward['module'] . ' does not exist.');
        }

        // Check whether module contains meta data
        $moduleData = $modules[ $forward['module'] ];
        if(!isset($moduleData['metadata']) || !isset($moduleData['metadata']['controllersNamespace'])){
            // @todo think of something nice to automatically get controller dir from existing config?
            throw new \Phalcon\Mvc\Dispatcher\Exception('Module ' . $forward['module'] . ' does not have meta data. Controller namespace must be specified.');
        }

        // Set controller namespace
        $metadata = $moduleData['metadata'];
        $dispatcher->setNamespaceName($metadata['controllersNamespace']);

        // Set controller suffix
        if(isset($metadata['controllerSuffix'])){
            $dispatcher->setControllerSuffix($metadata['controllerSuffix']);
        }

        // Set action suffix
        if(isset($metadata['actionSuffix'])){
            $dispatcher->setActionSuffix($metadata['actionSuffix']);
        }
    }
});

DaanBiesterbos commented Oct 6, 2013

Hmmmm.. I'm not very familiar with zend framework 1. I guess you are right. In zend framework 2 its not so bad.
You can get route params or request values from $this->params() within the action controller.

Solved the forward issue btw...I've not tested it very well. But for now it seems to do what I want.
Perhaps somebody can use it.

  1. Extend Phalcon\Mvc\Dispatcher and add a dispatch:beforeForward event.

    class MyDispatcher extends Phalcon\Mvc\Dispatcher
    {
         public function forward($forward)
         {
              $this->getEventsManager()->fire("dispatch:beforeForward", $this, $forward);
              parent::forward($forward);
         }
    }
  2. I've added some metadata in modules.php and attached to the event.

/**
 * Register application modules
 */
$application->registerModules(array(

    'moduleA' => array(
        'className' => 'MyApp\ModuleA\Module',
        'path' => __DIR__ . '/../apps/moduleA/Module.php',
        'metadata' => array(
            // // Enable forwarding to other modules    
            'controllersNamespace' => 'MyApp\ModuleA\Controllers',
            'controllerSuffix' => '',
            'actionSuffix' => '',
        ),
    ),
    'moduleB' => array(
        'className' => 'MyApp\ModuleB\Module',
        'path' => __DIR__ . '/../apps/moduleB/Module.php',
        'metadata' => array(
            // // Enable forwarding to other modules    
            'controllersNamespace' => 'MyApp\ModuleB\Controllers',
            'controllerSuffix' => '',
            'actionSuffix' => '',
        ),
    ),
));


// Attach to dispatch:beforeForward event
$modules = $application->getModules();
$di->getShared('eventsManager')->attach('dispatch:beforeForward', function($event, $dispatcher, array $forward) use($modules)
{

    if(isset($forward['module'])){

        // Check whether the module is registered
        if(!isset($modules[ $forward['module'] ])){
            throw new \Phalcon\Mvc\Dispatcher\Exception('Module ' . $forward['module'] . ' does not exist.');
        }

        // Check whether module contains meta data
        $moduleData = $modules[ $forward['module'] ];
        if(!isset($moduleData['metadata']) || !isset($moduleData['metadata']['controllersNamespace'])){
            // @todo think of something nice to automatically get controller dir from existing config?
            throw new \Phalcon\Mvc\Dispatcher\Exception('Module ' . $forward['module'] . ' does not have meta data. Controller namespace must be specified.');
        }

        // Set controller namespace
        $metadata = $moduleData['metadata'];
        $dispatcher->setNamespaceName($metadata['controllersNamespace']);

        // Set controller suffix
        if(isset($metadata['controllerSuffix'])){
            $dispatcher->setControllerSuffix($metadata['controllerSuffix']);
        }

        // Set action suffix
        if(isset($metadata['actionSuffix'])){
            $dispatcher->setActionSuffix($metadata['actionSuffix']);
        }
    }
});
@DaanBiesterbos

This comment has been minimized.

Show comment
Hide comment
@DaanBiesterbos

DaanBiesterbos Oct 7, 2013

Ohw, and for clearity. I would use this to forward from module A to module B:

$this->dispatcher->forward(array(
    'module' => 'moduleB ',
    'controller' => 'index',
    'action' => 'index'
));

DaanBiesterbos commented Oct 7, 2013

Ohw, and for clearity. I would use this to forward from module A to module B:

$this->dispatcher->forward(array(
    'module' => 'moduleB ',
    'controller' => 'index',
    'action' => 'index'
));
@DaanBiesterbos

This comment has been minimized.

Show comment
Hide comment
@DaanBiesterbos

DaanBiesterbos Oct 7, 2013

Hmmm. There is a little gotcha with the view. Don't forget to set all paths for the view you are forwarding to. Personally I use the initialize() method on the base controller to set module specific configurations. ( registerServices in module.php does not seem to run when forwarding to other modules. The init event on the controllerbase does.

I used this to set view paths. Works like a charm. ( For me ;-) )

$this->view->setBasePath(__DIR__ . '/../');
$this->view->setViewsDir('/views/');
// Layout template is located in view folder core module.
$this->view->setLayoutsDir('/../../../apps/core/views/layouts/');
$this->view->setPartialsDir('/partials/');
$this->view->setMainView('/../../../apps/core/views/index');

DaanBiesterbos commented Oct 7, 2013

Hmmm. There is a little gotcha with the view. Don't forget to set all paths for the view you are forwarding to. Personally I use the initialize() method on the base controller to set module specific configurations. ( registerServices in module.php does not seem to run when forwarding to other modules. The init event on the controllerbase does.

I used this to set view paths. Works like a charm. ( For me ;-) )

$this->view->setBasePath(__DIR__ . '/../');
$this->view->setViewsDir('/views/');
// Layout template is located in view folder core module.
$this->view->setLayoutsDir('/../../../apps/core/views/layouts/');
$this->view->setPartialsDir('/partials/');
$this->view->setMainView('/../../../apps/core/views/index');
@dphn

This comment has been minimized.

Show comment
Hide comment
@dphn

dphn Jan 9, 2014

Maybe I'm wrong, but in version 1.2.4, you can specify the namespace for the "forward"?

dphn commented Jan 9, 2014

Maybe I'm wrong, but in version 1.2.4, you can specify the namespace for the "forward"?

@fabfuel

This comment has been minimized.

Show comment
Hide comment
@fabfuel

fabfuel Jan 26, 2014

Hi everyone!

So it's not possible to forward to another module, right?

But also Phalcon\CLI\Console::handle() behaves a bit strange/unexpected.
When I try to forward, as described in the docs:

$this->console->handle(array(
    'module' => 'other',
    'task' => 'lorem',
    'action' => 'ipsum'
));

This does not work (as described in this thread), the dispatcher tries to find controller LoremTask with IpsumAction in the same module. BUT: the bootstrap file of the "other" module is loaded!

My question is: Is it possible to use e.g. public service classes from another module and if yes, what would be the best way to lazy bootstrap another module?

Best
Fabian

fabfuel commented Jan 26, 2014

Hi everyone!

So it's not possible to forward to another module, right?

But also Phalcon\CLI\Console::handle() behaves a bit strange/unexpected.
When I try to forward, as described in the docs:

$this->console->handle(array(
    'module' => 'other',
    'task' => 'lorem',
    'action' => 'ipsum'
));

This does not work (as described in this thread), the dispatcher tries to find controller LoremTask with IpsumAction in the same module. BUT: the bootstrap file of the "other" module is loaded!

My question is: Is it possible to use e.g. public service classes from another module and if yes, what would be the best way to lazy bootstrap another module?

Best
Fabian

@dphn

This comment has been minimized.

Show comment
Hide comment
@dphn

dphn Jan 26, 2014

Make the lazy loading is quite simple. But there is still a number of issues.
dphn/SkeletonApplication#1
dphn/SkeletonApplication#2

dphn commented Jan 26, 2014

Make the lazy loading is quite simple. But there is still a number of issues.
dphn/SkeletonApplication#1
dphn/SkeletonApplication#2

@fabfuel

This comment has been minimized.

Show comment
Hide comment
@fabfuel

fabfuel Jan 28, 2014

Thanks a lot for links to your example!
Just to make sure, you bootstrap all modules on every request, right?

fabfuel commented Jan 28, 2014

Thanks a lot for links to your example!
Just to make sure, you bootstrap all modules on every request, right?

@dphn

This comment has been minimized.

Show comment
Hide comment
@dphn

dphn Jan 28, 2014

It is inevitable if you want multimodular. Otherwise your modules - just directories on the disk and nothing more.
I prefer a scheme similar zf2. Here modules like OOP classes. They can inherit the behavior and may use the services of another module.
Configuration of all modules are merged. That it does not affect the performance, if you have several dozen modules provide automatic caching of merged configuration.

dphn commented Jan 28, 2014

It is inevitable if you want multimodular. Otherwise your modules - just directories on the disk and nothing more.
I prefer a scheme similar zf2. Here modules like OOP classes. They can inherit the behavior and may use the services of another module.
Configuration of all modules are merged. That it does not affect the performance, if you have several dozen modules provide automatic caching of merged configuration.

@MarcusSjolin

This comment has been minimized.

Show comment
Hide comment
@MarcusSjolin

MarcusSjolin Jun 11, 2014

I'd like to see this implemented for the reason of 40_, 50_ pages, because I want to forward to an error controller/action which is global, rather than having one for each module. Right now I have a controller in each module which refers to the same view and extends the main error controller to solve this.

MarcusSjolin commented Jun 11, 2014

I'd like to see this implemented for the reason of 40_, 50_ pages, because I want to forward to an error controller/action which is global, rather than having one for each module. Right now I have a controller in each module which refers to the same view and extends the main error controller to solve this.

@sergeyklay

This comment has been minimized.

Show comment
Hide comment
@sergeyklay

sergeyklay May 21, 2017

Member

Implemented in the 3.2.x branch.

Member

sergeyklay commented May 21, 2017

Implemented in the 3.2.x branch.

@temuri416

This comment has been minimized.

Show comment
Hide comment
@temuri416

temuri416 May 23, 2017

Contributor

@sergeyklay wow awesome!

Contributor

temuri416 commented May 23, 2017

@sergeyklay wow awesome!

@Jurigag

This comment has been minimized.

Show comment
Hide comment
@Jurigag

Jurigag May 23, 2017

Member

Though it's implemented just using event as i saw in test. Like now there is now clean and easy way to do it, but not out of box like forward(['module' =>'xyz']);?

Member

Jurigag commented May 23, 2017

Though it's implemented just using event as i saw in test. Like now there is now clean and easy way to do it, but not out of box like forward(['module' =>'xyz']);?

@virgofx

This comment has been minimized.

Show comment
Hide comment
@virgofx

virgofx May 23, 2017

Member

Clarification ... it's not implemented. There is now a forward event that doesn't add anything new as any of the other events could easily be hooked. Module forwarding is a huge refactor to do it properly as a few API changes need to be made to the modules to register and de-register separate dispatchers and DI services cleanly all while keeping the same scope for the end user.

Member

virgofx commented May 23, 2017

Clarification ... it's not implemented. There is now a forward event that doesn't add anything new as any of the other events could easily be hooked. Module forwarding is a huge refactor to do it properly as a few API changes need to be made to the modules to register and de-register separate dispatchers and DI services cleanly all while keeping the same scope for the end user.

@phalcon phalcon locked and limited conversation to collaborators May 23, 2017

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.