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

How to work with routing in multimodule applications?

Hello again :)

Is there simple way to produce links in multimodule application with 2 or more modules?

I mean: I want to make simple links with the function \Phalcon\Tag::linkTo(array("auth/login", "Login")). And then, if this function was executed in some controller of the "Admin" module, it produces the link: "https://site.com/admin/auth/login". So this link routes to the "Auth" controller of the Admin module...

Now I need to use:
    $router->add('/admin/:controller/:action/:params', array(
            'module' => "backend",
            'controller' => 1,
            'action' => 2,
            'params' => 3
        ));

And in every link of the "Admin" module I need to write \Phalcon\Tag::linkTo(array("admin/auth/login", "Login"))... but it still not work ^)

Dear Phalcon, maybe you have guidlines how to organize the multimodule application with the front-end and the back-end (without describing each route in \Phalcon\Mvc\Router() and with sharing model's structure for both modules)?



36.0k

My answer is:

$router->add('/admin', array(
        'module' => "backend",
        'action' => "index",
        'params' => "index"
));

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

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

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

And I need to produce my own \Tag class that extends \Phalcon\Tag.



4.1k

https://github.com/phalcon/cphalcon/issues/365 У меня была похожая проблема. К сожалению нормально сконфигурировать не вышло. Я вынес в подпапку /public/admin/ файл index.php с инициализацией и немного другими параметрами, к сожалению настроить нормальный редирект так и не получилось.



98.9k

Check the Album O' Rama example, it has a common models directory:

https://github.com/phalcon/album-o-rama/tree/master/common/models

Just point the autoloader in the module autoloader to this directory,

Your routes seems to be fine, if you name them you can use the URL-reverse generator to produce urls from them:

https://docs.phalcon.io/en/latest/reference/url.html#generating-uris



36.0k

Добрый Вам! Спасибо!

Почитал, понял что:

  1. Нужно УРЛ для ДИ определять в модулях приложения, тогда с адресами будет все норм.

  2. Нужно не забывать о чудесной штуке redirect() - я че-то увлекся форвардами сильно :)


36.0k

Ok, to create the multimodule application with dividing the frontend and the backend we need to do the next: Folder's structure:

app
  frontend
    Module.php
  backend
    Module.php
  models
public
...trash...

In every Module.php we need to put such strings:

    class Module implements \Phalcon\Mvc\ModuleDefinitionInterface
    {
        public function registerAutoloaders()
        {
            $loader = new \Phalcon\Loader();
            $loader->registerNamespaces(
                array(
                    'Backend\Controllers' => '../app/backend/controllers/',
// For frontend's module.php: 'Frontend\Controllers' => '../app/backend/controllers/',
                )
            );
            $loader->register();
        }
        public function registerServices($di)
        {
                   $di->set('dispatcher', function() use ($di){
                    $dispatcher = new \Phalcon\Mvc\Dispatcher();
                    $dispatcher->setDefaultNamespace("Backend\Controllers\\");
// For frontend's module.php: $dispatcher->setDefaultNamespace("Frontend\Controllers\\");
                     return $dispatcher;
            });
            $di->set('view', function() {
                    $view = new \Phalcon\Mvc\View();
                    $view->setViewsDir('../app/backend/views/');
// For frontend's module.php: $view->setViewsDir('../app/frontend/views/');
                    return $view;
            });

// This function will 'divide' parts of the application with the correct url:
            $di->set('url', function() use ($di) {
                    $url = new \Phalcon\Mvc\Url();
                    $url->setBaseUri("/adminko/");
// For frontend module.php:  $url->setBaseUri("/");
                    return $url;
            });

and dont forget to instance $router:

$router->add('/adminko', array(
'module' => "backend",
'action' => "index",
'params' => "index"
));

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

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

And now the link "https://site.com/adminko" will forward us to backend, where all links on the page will be prefixed with "adminko". The link "https://site.com" will forward us to frontend, where all links on the page will be not prefixed. It is very good behavior. But, unfortunately, in this case \Phalcon\Tag::stylesheetLink() and \Phalcon\Tag::javascriptInclude() will not work correctly.

But I have an another problem: How to redirect the user to the frontend from the backend when the user pressed the link in the frontend with the backend's prefix? And how to not redirect the user to the frontend from the backend when the user pressed the same link in the backend?

It's all about authorization. I want to authorize my users in the frontend and in the backend with single AuthController that situated in the backend. Now if authorization was success the user forwards to the backend's index page:

...
if ($this->security->checkHash($password, $user->password)) 
      {
                    $this->session->set('auth', 1);
                    return $this->response->redirect('index/index');
                    // return $this->dispatcher->forward(array('controller'=>'index')); // the same behavior as redirect
      }

Help me, please :)



98.9k

I think that the base URI must be / for both modules:

$di['url'] = function(){
     $url = new \Phalcon\Mvc\Url();
     $url->setBaseUri("/");
    return $url;
};

Create your URLs with names:


$router = new Phalcon\Mvc\Router(false);

$router->add('/', array(
    'module' => "frontend",
    'controller' => "index",
    'action' => "index"
))->setName('front-index');

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

$router->add('/:controller/:action/:params', array(
    'module' => "frontend",
    'controller' => 1,
    'action' => 2,
    'params' => 3
))->setName('front-full');

$router->add('/adminko', array(
    'module' => "backend",
    'controller' => "index",
    'action' => "index"
))->setName('back-index');

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

$router->add('/adminko/:controller/:action/:params', array(
    'module' => "backend",
    'controller' => 1,
    'action' => 2,
    'params' => 3
))->setName('back-full');

Then you can generate URLs from them:

echo $this->url->get(array('for' => 'back-full', 'controller' => 'users', 'action' => 'edit')), PHP_EOL;
echo $this->url->get(array('for' => 'front-controller', 'controller' => 'users', 'action' => 'edit')), PHP_EOL;
echo $this->url->get(array('for' => 'front-index')), PHP_EOL;

Every route is generated by the URL service in Phalcon:

$this->response->redirect(array('for' => 'back-full', 'controller' => 'users', 'action' => 'edit', 'params' => 150)); // /adminko/users/edit/150
Phalcon\Tag::form(array('for' => 'back-full', 'controller' => 'users', 'action' => 'save')); // /adminko/users/save


36.0k
  1. Why I can't use baseUri for the module if it solves my problem with prefixing all links on the page? I really don't want to write such huge links in my app.
  2. Using named url can solve problem with forwarding user from the backend controller to the frontend controller. But how not to forward the user from the backend to the frontend if authorization was performed in the backend?


98.9k

The baseUri is also used to generate the paths to static content, if you set your baseUri to /adminko all the javascript and css resources are prepended with the baseUri, /adminko/css/ while they're probably in /css/



36.0k

Sure it is. So, for static content I just use HTML tags :) There is some logic: for non-static contend - use Phalcon tags, for static content - use HTML tags.



36.0k

And... hello again! When I trying to redirect user from the backend to the frontend - Phalcon doesn't redirecting, I wrote

$url = array("for" => "frontend-full", "controller" => "spunchbob", "action" => "sit");
return $this->response->redirect($url);

but the $dispather's namespace is Backend\Controllers

  protected '_activeHandler' => null
  protected '_finished' => boolean true
  protected '_namespaceName' => string 'Backend\Controllers\' (length=20)
  protected '_handlerName' => string 'spunchbob' (length=9)
  protected '_actionName' => string 'sit' (length=3)

So, Phalcon redirects user to the backend's controller "spunchbob". What I doing wrong?



36.0k

Ok. I was wrong - "baseURI" must be set to "/" for both modules... else it may crash $dispatcher (example above).

Dear Phalcon, tell me please, am I correct in its conclusions?: If I need to use 2 modules than I must:

  • Use the same "baseURI" for all modules.
  • Set for each module 3 named routes (for index, controller and full)
  • In every module (in every file of module) use URI generated with using the named route (to generate correct links). ???


98.9k

I'm running this gist https://gist.github.com/4677808 here https://test.phalcon.io/z.php

And yes, you must use "/" for both modules, a global router for all the modules must be set in the application bootstrap,

Generating URLs from the named routes is optional, in fact, this is a strategy to change your routes at any moment without affecting the application, since all the routes are generated from them. If some day you change the prefix /adminko, it will be changed in the whole application.

But that is up to you, If you don't plan to implement that, you can use the static string routes too:

$this->response->redirect('adminko/users/edit');

Or override the redirect method in the response component to prepend the custom base uri:

class MyResponse extends \Phalcon\Http\Response
{
    public function redirect($uri, $externalRedirect=false, $statusCode=302)
    {
        if ($this->getDI()->getRouter()->getModuleName() == 'backend') {
            return parent::redirect('/adminko/' . $uri, $externalRedirect, $statusCode);
        }
        return parent::redirect($uri, $externalRedirect, $statusCode);
    }
}
$di['response'] = function(){
    return MyResponse();    
};

Or create your own redirect method in the Controller:

<?php

class SessionController extends Phalcon\Mvc\Controller
{
    protected function redirect($uri)
    {
        if ($this->router->getModuleName() == 'backend') {
            return $this->response->redirect('/adminko/' . $uri);
        }
        return $this->response->redirect($uri);
    }
}


36.0k

Thank you very much. It's very helpfull!

Redirect now works correctly, but there is problem with $this->dispatcher->forward(array("for" = "front-index")). Phalcon redirects me to the backend's index page instead of the frontend's index page. I don't use "redirect" in this way because I want to "flash" some messages to the user. Tell me please how to output "smthng" to user in backend and forward him to the frontend?



98.9k

Dispatcher cannot forward between modules, the dispatcher is unique for each module so it only can forward in the current module. You can use the session flasher to send flash messages between modules: https://docs.phalcon.io/en/latest/reference/flash.html#implicit-flush-vs-session



36.0k

Sure! I forgot about session flasher :) And I forgot about instancing dispather in Module.php, sorry.

Now it's all clear. Can I ask you one last question?

Dear Phalcon, how do you think an authorization controller must be implemented in every module or only in one module (for ex. backend)?

  • If only in one - what module must contain an authorization module (front or back)?
  • If in every module - maybe there is sense to create AuthorizationBase class that will be the base class of each Authorization controller?


98.9k

It depends, it seems more logical in the backend, I assume that administration options are implemented there, I'm not clear about how the application is currently structured.

The Acl plugin could be registered on both modules or just one:

This shows how to register an Acl plugin in a module definition: https://github.com/phalcon/mvc/blob/master/multiple/apps/backend/Module.php#L25



36.0k

Thank you! I tried to do so - to put athorization controller in the backend. But in most cases users must log in to application from the frontend (guests) and from the backend (admins). Algorithms of authorization are identical to both groups of users. So I tried to put in the frontend only forms for authorization with routing links to the backend module. Then I had troubles with "back redirection" to the frontend from the backend's actions...



3.0k

Hi! If you want an extended multimodal, watch this: https://github.com/dphn/SkeletonApplication

Here everything is as you would expect, and forward to other modules, and everything else. Here authorization and authentication can be done as individual modules. All can be configured using configuration files. Nothing hardcoded.

@Phalcon Look. perhaps this code will be useful for the next version. I think it's no problem to add 9 listeners to standard phalcon code.