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

Routing issue

Hello,

I seem to have a problem with my routing. I have cleared the default routes and added two routes for testing. The '/' and '/test' are added. But when I go to the website and use something like '/bla' I end up with the index controller and index action. Instead of that I should have received an exception with a forward to a error controller with a route404 action. The action itself is nothing more then a die( 'here' ); at the moment.

I have my controllers, views, plug-ins in the root of my application.

The goal that I want to reach is to display the errorController::route404() endpoint when a none existing route is being requested. '/bla' for example

Below is the definition of the router

  $di->set( 'router', function()
  {
    $router =new Router( false );

    $router->removeExtraSlashes( false );  // Allow for trailing slash

    $router->add( '[/]{0,1}', [ 'controller' => 'index', 'action' => 'index' ]);
    $router->add( '/test[/]{0,1}', [ 'controller' => 'index', 'action' => 'test' ]);

    return $router;
  });

Below is the definition of the dispatcher

  $di->setShared( 'dispatcher', function()
  {
    // Create a new dispatcher
    $dispatcher =new Dispatcher();
    $dispatcher->setDefaultNamespace( 'Controllers' );

    // Create an EventsManager
    $eventsManager =new EventsManager();

    // Attach an exception listener
    $eventsManager->attach( 'dispatch:beforeException', new DispatchPlugin( ));

    // Bind the EventsManager to the dispatcher
    $dispatcher->setEventsManager( $eventsManager );

    return $dispatcher;
  });

Below is the definition of the DispatchPlugin class

<?php
  namespace Plugins;

  use Phalcon\Events\Event;
  use Phalcon\Mvc\Dispatcher;
  use Phalcon\Mvc\User\Plugin;
  use Phalcon\Mvc\Dispatcher\Exception;

  class DispatchPlugin extends Plugin
  {
    public function beforeException( Event $event, Dispatcher $dispatcher, Exception $exception )
    {
      // Check stuff
      switch( $exception->getCode( ))
      {
        // Handle 404 exceptions
        case Dispatcher::EXCEPTION_ACTION_NOT_FOUND:
        case Dispatcher::EXCEPTION_HANDLER_NOT_FOUND:
          $action ='404';
          break;

        // All other errors
        default:
          $action ='503';
          break;
      }

      $dispatcher->forward([
        'action'     => "error{$action}",
        'controller' => 'error',
        'namespace'  => '\\Controllers',
      ]);

      return false;
    }
  }


4.5k
edited Nov '18

The before exception event should be called when a route can not be found, but instead the route for / is called. I want the page that is shown as a 404 be based on the route that is requested. if it is the backend then another 404 should be shown then when the route is for the front of the application. Which is why I want to use the before exception event and in the event itself use forwarding to the correct module, controller and action.

edited Dec '18

Your setup is almost functionally equivalent to how I set up my 404s:

$DI->set('dispatcher',function(){
    $EM = new \Phalcon\Events\Manager();

    $EM->attach('dispatch:beforeException',
        function($event,$dispatcher,$exception){
            if( in_array(   $exception->getCode(), 
                            [
                                \Phalcon\Mvc\Dispatcher::EXCEPTION_HANDLER_NOT_FOUND,
                                \Phalcon\Mvc\Dispatcher::EXCEPTION_ACTION_NOT_FOUND
                            ]
                        )
                ){

                $dispatcher->forward(['controller'=>'index','action'=>'notFound']);
                return FALSE;
            }
        }
    );

    $Dispatcher = new \Phalcon\Mvc\Dispatcher();
    $Dispatcher->setDefaultNamespace('Controller');
    $Dispatcher->setEventsManager($EM);

    return $Dispatcher;
});

The one difference I can see is that I don't define the namespace in the $dispatcher->forward() method - I just do it with setDefaultNamespace() (as you do also). Maybe that extra declaration is causing a problem?

Also, I haven't disabled default routing in my router - though I'm not 100% sure what affect that has on this particular situation.



4.5k
edited Dec '18

I have disabled and enabled the names space, but that did not make a difference.

If you use the default routing then the default 'https://domain.ext/:module/:controller/:action' route is active. So nearly any route that you supply is found.

In my case I removed all routes with a false supplied to the constructor of the router definition and only added the '/' and '/test' routes. I have changed this a little to supplying the default route instead of using the '/' as a route, leaving the '/test' route. I added an event handler to the router (RouterPlugin) to see what is beeing called.

I noticed that the "notMatchedRoute" is called as one would expect when using a route that does not exist. What I do not understand is why is he does not throw an exception or something instead of just showing the default route. This puzzles me.

I'll add a forward to the 404 action of the error controller to see if that does work like I intended.

Let you know when I have tested a little more.



4.5k
Accepted
answer

I found a solution, but not the one I'm looking for. The code below will display a 404 page once a certain route can't be found. The notFound() witin the router is the key to preventing the default action to be displayed when a route does not match.

There are two parts in this. One the Router and the second the Dispatcher. The router will use the notFound() route to indicate that the route can not be found (matched). While the exception within the dispatcher will only fire once a route is found, but can not be matched to the specified module/controller/action. This will create an exception and that will trigger the beforeException on the dispatcher.

For now this will do. I could always write my own Router. But I do not have time for that now.

Thnaks for thinking with me.

The router definition:

      $di->set( 'router', function()
      {
        $router =new Router( false );  // Disable default routes

        // Allow for training slash
        $router->removeExtraSlashes( false );

        // Add not found action
        $router->notFound([ 'controller' => 'error', 'action' => 'error404']);

        // Add default route ('/')
        $router->add( '/', [ 'action' => 'index', 'controller' => 'index' ]);

        return $router;
      });

The dispatcher definition:

      $di->setShared( 'dispatcher', function()
      {
        // Create a new dispatcher
        $dispatcher =new Dispatcher();
        $dispatcher->setDefaultNamespace( 'Controllers' );

        // Create an EventsManager
        $eventsManager =new EventsManager();

        // Attach an exception listener
       $eventsManager->attach( 'dispatch:beforeException', new DispatchPlugin( ));

        // Bind the EventsManager to the dispatcher
        $dispatcher->setEventsManager( $eventsManager );

        return $dispatcher;
      });

The dispatchPlugin:

<?php
  namespace Plugins;

  use Phalcon\Events\Event;
  use Phalcon\Mvc\Dispatcher;
  use Phalcon\Mvc\User\Plugin;
  use Phalcon\Mvc\Dispatcher\Exception;

  class DispatchPlugin extends Plugin
  {
    public function beforeException( Event $event, Dispatcher $dispatcher, Exception $exception )
    {
      // Check stuff
      switch( $exception->getCode( ))
      {
        // Handle 404 exceptions
        case Dispatcher::EXCEPTION_ACTION_NOT_FOUND:
        case Dispatcher::EXCEPTION_HANDLER_NOT_FOUND:
          $action ='404';
          break;

        // All other errors
        default:
          $action ='503';
          break;
      }

      $dispatcher->forward([
        'action'     => "error{$action}",
        'controller' => 'error',
      ]);

      return false;
    }
  }


4.5k

Hmm, actually the problem seems to have a rather simple solution. Or so it seems. If you change the definition of the router to shared within the di then you are able to obtain the router within the dispatch plugin (event) and do a forward there to the right controller. Going to try that out.