We have moved our forum to GitHub Discussions. For questions about Phalcon v3/v4/v5 you can visit here and for Phalcon v6 here.

Multi Module Routing default behaviour

Hi all,

At multi-module docs I can see examples of how to route them, as well in the mvc repository in github. But I can't find an example with a generic solution. All the examples I saw define specific routers for the modules. What I really want is the router to handle it, as it does with controllers/actions but with modules.

At the moment I already have a multimodule skeleton, the loader is working ok with different namespaces across modules etc, but I can't get the router to reach other modules than the default one.

This is what I have:

$di['router'] = function () {
    $router = new \Phalcon\Mvc\Router();
    $router->setDefaultNamespace('App\Base\Controllers');
    $router->setDefaultModule("base");

    $router->add('/:module/:controller/:action',
        array(
            'module' => 1,
            'controller' => 2,
            'action' => 3,
        )
    );
    return $router;
};

with the bootstrap application as follows

$application->registerModules( array(
    'base' => array(
        'className' => 'App\Base\Module',
        'path' => $config->loader->modules_path.'Base'.DS.'Module.php'
    ),
    'user' => array(
        'className' => 'App\User\Module',
        'path' => $config->loader->modules_path.'User'.DS.'Module.php'
    )
));

if I access to route / it goes to base module, IndexController, indexAction, but it does the same whatever the route I write. What I'm doing wrong?

I'm so sorry for writing this since there is a lot of routing questions related to autoloading of multi module applications but I couln't find anything.

This is what I use

$modules = [
    'frontend' => '',
    'backend' => '/admin',
];
foreach ($modules as $module => $prefix) {
    $router->add("{$prefix}(/)?", [
        'module'     => $module,
        'controller' => 'index',
        'action'     => 'index',
    ]);
    $router->add("{$prefix}/:controller(/)?", [
        'module'     => $module,
        'controller' => 1,
        'action'     => 'index',
    ]);
    $router->add("{$prefix}/:controller/:action(/)?", [
        'module'     => $module,
        'controller' => 1,
        'action'     => 2,
    ]);
    $router->add("{$prefix}/:controller/:action/:params(/)?", [
        'module'     => $module,
        'controller' => 1,
        'action'     => 2,
        'params'     => 3,
    ]);
}

The reason it's not matching is because it treats the route as a regular expression, and the :controller etc are just shorthand for a regular expression.

Hope this helps.



16.0k
Accepted
answer

Hello,

If the modules and namespaces are loaded properly, what you did should work fine; but it will only work as long your url contains module/controller/action, otherway it will load the default module. If you want to access only by module name or module/controller, you need to add other routes.

$router->add('/:module/:controller',
    array(
        'module' => 1,
        'controller' => 2,
        'action' => 'index',
    )
);
$router->add('/:module',
    array(
        'module' => 1,
        'controller' => 'index',
        'action' => 'index',
    )
);
edited Apr '15

Yeah @Stefan solved it. I did many changes and I forget to set the default behaviour when no action, controller is present. Although I needed to set those for each of my Modules, due to namespaces. It does not work the generic way:

$di['router'] = function () use ($application) {
    $router = new \Phalcon\Mvc\Router();

    foreach ($application->getModules() as $moduleName => $module) {
        $namespaceName = str_replace('Module', 'Controllers', $module['className']).'\\';
        $router->add("$moduleName/:controller/:action/:params", array(
            'namespace'  => $namespaceName,
            'module'     => $moduleName,
            'controller' => 1,
            'action'     => 2,
            'params'     => 3
        ));
        $router->add("$moduleName/:controller", array(
            'namespace'  => $namespaceName,
            'module'     => $moduleName,
            'controller' => 1,
            'action'     => 'index',
        ));
        $router->add("$moduleName", array(
            'namespace'  => $namespaceName,
            'module'     => $moduleName,
            'controller' => 'index',
            'action'     => 'index',
        ));
    }
    $router->removeExtraSlashes(true);

    return $router;
};

Why it does not work with :module wildcard?

edited Apr '15

If the namespaces don't work the generic way, that means it does not follow the PSR-0 rules.

Make sure the folder names are written exactly as the namespace, Also the file names are the same as the class name. Everything is case sensitive.

Just defining a generic route (:module/:controller/:action, :module/:controller/, :module) will work for all modules. Define new routes only if you need something custom.

They follow the psr0. All my modules are defined on the vendor\NamespaceName\Etc... ex: NamespaceName\ModuleName\Controller\IndexController.php with namespace NamespaceName\ModuleName\Controller they are loaded with composer, all of them are accesible after autoload.

But if I set the generic routes (:module/:controller/:action, :module/:controller/, :module) or just start the router with default routes they don't work. In any case they are working with my foreach getModules since they are defined for each module.

During the weekend I will try to reproduce it on a clean skeleton so I can post it here. Don't know what I'm doing wrong.

edited Apr '15

Here is the test. I'm using phalcon 2.0 branch

<?php

//These routes simulate real URIs
$testRoutes = array(
    '/',
    '/index',
    '/index/index',
    '/base',
    '/base/index/',
    '/base/index/index',
    '/admin/user'
);

$router = new \Phalcon\Mvc\Router(true);
$router->setDefaultModule('base');
$router->setDefaultController('index');
$router->setDefaultAction('index');

// FRONT END ROUTES
$router->add("/:module/:controller/:action/:params", array(
    'module'     => 1,
    'controller' => 2,
    'action'     => 3,
    'params'     => 4
));

$router->add("/:module/:controller", array(
    'module'     => 1,
    'controller' => 2,
    'action'     => 'index',
));

$router->add("/:module", array(
    'module'     => 1,
    'controller' => 'index',
    'action'     => 'index',
));

// ADMIN ROUTES
$router->add("/admin/:module/:controller/:action/:params", array(
    'module'     => 1,
    'controller' => 2,
    'action'     => 3,
    'params'     => 4,
    'admin'      => true
));

$router->add("/admin/:module/:controller", array(
    'module'     => 1,
    'controller' => 2,
    'action'     => 'index',
    'admin'      => true
));

$router->add("/admin/:module", array(
    'module'     => 1,
    'controller' => 'index',
    'action'     => 'index',
    'admin'      => true
));

// API ROUTES
$router->add("/api/:module/:controller/:action/:params", array(
    'module'     => 1,
    'controller' => 2,
    'action'     => 3,
    'params'     => 4,
    'api'        => true
));

$router->add("/api/:module/:controller", array(
    'module'     => 1,
    'controller' => 2,
    'action'     => 'index',
    'api'        => true
));

$router->add("/api/:module", array(
    'module'     => 1,
    'controller' => 'index',
    'action'     => 'index',
    'api'        => true
));

//Testing each route
foreach ($testRoutes as $testRoute) {

    //Handle the route
    $router->handle($testRoute);

    echo 'Testing ', $testRoute, "\n";

    //Check if some route was matched
    if ($router->wasMatched()) {
        echo 'Module: ', $router->getModuleName(), "\n";
        echo 'Controller: ', $router->getControllerName(), "\n";
        echo 'Action: ', $router->getActionName(), "\n";
        if ($router->getParams()) {
            echo 'Params: ';
            print_r($router->getParams());
            echo "\n";
        }

    } else {
        echo "The route wasn\\'t matched by any route\n";
    }
    echo "\n";

}

The results are as follow:

// php routerTest.php

[BAD] Testing / The route wasn\'t matched by any route

Testing /index Module: index Controller: index Action: index

Testing /index/index Module: index Controller: index Action: index

Testing /base Module: base Controller: index Action: index

[BAD] Testing /base/index/ Module: base Controller: base Action: index

Testing /base/index/index Module: base Controller: index Action: index

[BAD] Testing /admin/user Module: user Controller: index Action: index Params: Array ( [admin] => user )

I tested your routes on my application and work fine, also the results seems to be according to the routes. Nothing wrong there. Also add

$router->removeExtraSlashes(true);

since /base/index/ is taken as another route that wasn't defined and gives you that BAD result