Intercepting not found errors in multi-module applications

Hi all! I have multi-module app like in 'mvc\multiple-shared-layouts' and have two modules, frontend and backend. I'm trying to intercept not found events by setting up dispatcher service inside every module's registerServices() method, but it seems application doesn't even run this method. The idea is to have different not found pages for frontend and backend. How can I achieve it? Here is example of my Frontend module, backend is the same except 'module' definitions.

public function registerServices($di)
    {
        $di->set('dispatcher', function() {
                $eventsManager = new \Phalcon\Events\Manager();
                $eventsManager->attach('dispatch', function($event, $dispatcher, $exception){
                        //The controller exists but the action not
                        if($event->getType() == 'beforeNotFoundAction') {
                            $dispatcher->forward(array(
                                    'module'=>'frontend',
                                    'controller'=>'index',
                                    'action'=>'show404'
                                ));
                            return false;
                        }
                        if($event->getType() == 'beforeException') {
                            switch ($exception->getCode()) {
                                case \Phalcon\Dispatcher::EXCEPTION_HANDLER_NOT_FOUND:
                                case \Phalcon\Dispatcher::EXCEPTION_ACTION_NOT_FOUND:
                                    $dispatcher->forward(array(
                                            'module'=>'frontend',
                                            'controller'=>'index',
                                            'action'=>'show404'
                                        ));
                                    return false;
                            }
                        }
                    });
                $dispatcher = new \Phalcon\Mvc\Dispatcher();
                $dispatcher->setEventsManager($eventsManager);
                $dispatcher->setDefaultNamespace('Modules\Frontend\Controllers');

                return $dispatcher;

            });


        $di['view'] = function() {
            $view = new \Phalcon\Mvc\View();

            $view->setViewsDir(__DIR__ . '/views/');
            $view->setLayoutsDir('../../common/layouts/');
            $view->setTemplateAfter('main');

            return $view;
        };
    }

Thanks.



81.8k
edited Feb '14

There are 3 not-found events you need to be aware of:

  1. The current URI is not matched by any route set in the router
  2. The current controller cannot be loaded by the dispatcher
  3. The current action is not part of the active controller

Using the code you posted before you can handle the cases 2 and 3. Additionally you need to set some not-found routes (http://docs.phalconphp.com/en/latest/reference/routing.html#not-found-paths) up to tell the application what module needs to be loaded in case 1.

Btw, the code to handle 2 and 3 is:

public function registerServices($di)
{
    $di->['dispatcher'] = function() {

        $eventsManager = new \Phalcon\Events\Manager();

        $eventsManager->attach('dispatch:beforeException', function($event, $dispatcher, $exception) {

            switch ($exception->getCode()) {
                case \Phalcon\Dispatcher::EXCEPTION_HANDLER_NOT_FOUND:
                case \Phalcon\Dispatcher::EXCEPTION_ACTION_NOT_FOUND:
                    $dispatcher->forward(array(
                        'module'=>'frontend',
                        'controller'=>'index',
                        'action'=>'show404'
                    ));
                    return false;
            }

        });

        $dispatcher = new \Phalcon\Mvc\Dispatcher();
        $dispatcher->setEventsManager($eventsManager);
        $dispatcher->setDefaultNamespace('Modules\Frontend\Controllers');

        return $dispatcher;

    };

    $di['view'] = function() {

        $view = new \Phalcon\Mvc\View();

        $view->setViewsDir(__DIR__ . '/views/');
        $view->setLayoutsDir('../../common/layouts/');
        $view->setTemplateAfter('main');

        return $view;
    };
}
edited Feb '14

Thanks for the answer. In this case, I always should set router inside of bootstrap file, where I need to choose module that should be able to handle all future errors\exceptions (index.php)

$di->set('router', function() use ($routes){
            $router = new Phalcon\Mvc\Router(false);
            array_walk($routes->toArray(), function($value, $key) use (&$router){
                    $router->add($key, $value);
                });
            $router->removeExtraSlashes(true);
            $router->notFound(array(
               'module' => 'frontend',
               'controller' => 'index',
                'action' => 'show404'
            ));

            return $router;
        });

Okay, that's clear. But how can I show different 404 pages for different modules? Should I create new module, 'common', where to route all notFound actions and check there what module should run in this case, right?

edited May '14

You don't need new module for that. if You set dispacher on each module.php with custom 404 pages and it's should work. I have similar setup and it's work with different error 404 pages for each module.

edited Nov '14

I have the same problem, I have a project using 2 modules, "www" and "api". I can't find how to have a 404 error page in HTML for my "www" module and a JSON format 404 error for the API module.

index.php

    $di->set('router', function () {
        $router = new Router(false);
        $router->setDefaultModule('www');
        $router->add('/', ['module' => 'www', 'controller' => 'index', 'action' => 'index']);
        $router->add('/api', ['module' => 'api', 'controller' => 'index', 'action' => 'index']);
        $router->notFound(['module' => 'www', 'controller' => 'error', 'action' => 'show404']);
        return $router;
    });

Www - Module.php

    public function registerServices($di)
    {
        $di->set('dispatcher', function() use ($di) {
            $eventManager = $di->getShared('eventsManager');
            $eventManager->attach('dispatch:beforeException', function($event, Dispatcher $dispatcher, $exception) {
                switch ($exception->getCode()) {
                    case Dispatcher::EXCEPTION_HANDLER_NOT_FOUND:
                    case Dispatcher::EXCEPTION_ACTION_NOT_FOUND:
                        $dispatcher->forward(['controller' => 'error', 'action' => 'show404']);
                        return false;
                }
            });

            $dispatcher = new Dispatcher();
            $dispatcher->setDefaultNamespace('MonPartenaire\\Www\\Controllers');
            return $dispatcher;
        });
    }

Api - Module.php is the same

When $router->notFound() is defined, it intercepts everything, including /api/* calls and route them to www module error controller. If I remove $router->notFound(), any call to /api/* is routed to default controller (home page).

Any idea ?

Thank you and keep going on with this great framework.



3.2k

My is not working. I can't make not found error page.

router.php

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

$router->removeExtraSlashes(true);

// required for modules.
$router->setDefaultModule('core');

$router->add(
    '/{lang:[a-z]{2}}/index/:action/:params',
    array(
        'module' => 'core',
        'controller' => 'index',
        'action' => 2,
        'params' => 3,
    )
);
$router->add(
    '/{lang:[a-z]{2}}', 
    array(
        'module' => 'core',
        'controller' => 'index',
        'action' => 'index',
    )
);

services.php

// set dispatcher
$di->set('dispatcher', function() use ($di) {
    $evManager = $di->getShared('eventsManager');

    $evManager->attach('dispatch:beforeException', function($event, $dispatcher, $exception) {
        switch ($exception->getCode()) {
            case PhDispatcher::EXCEPTION_HANDLER_NOT_FOUND:
            case PhDispatcher::EXCEPTION_ACTION_NOT_FOUND:
                $dispatcher->forward(
                    array(
                        'module' => 'core',
                        'controller' => 'error',
                        'action' => 'e404',
                    )
                );
                return false;
        }
    });

    $dispatcher = new PhDispatcher();
    $dispatcher->setEventsManager($evManager);
    $dispatcher->setDefaultNamespace('Core\\Controllers');
    return $dispatcher;
});

index.php

$application = new \Phalcon\Mvc\Application($di);
    $application->registerModules(
        array(
            'core' => array(
                'className' => 'Core\\Module',
                'path' => APPFULLPATH . '/Module.php',
            ),
            'contact' => array(
                'className' => 'Modules\\Contact\\Module',
                'path' => ROOTFULLPATH . '/modules/contact/Module.php',
            )
        )
    );

Request the page to /index/action-not-exists action-not-exists is not exists in the index controller. but it is not forward to error page.

"Action 'action-not-exists' was not found on handler 'index'"

This is what i got instead of my 404 page.

Can you help me please?

I had a similar problem. My application was in modules and was having two equal services, one in the global file services and other inside the module. A service was overwriting the other.

edited 7d ago

Please fix that!!! I have a similar problem.

Any workaround for this? I want to intercept the NotFound for my API too. But the notFound is redirecting me to the Web not Found route.