Solved thread

This post is marked as solved. If you think the information contained on this thread must be part of the official documentation, please contribute submitting a pull request to its repository.

Error: Dispatcher has detected a cyclic routing causing stability problems whilst trying to show 404 page

Hi all!

I'm having a little problem with the following error appearing whenever I try and test my page not found excpetion: Dispatcher has detected a cyclic routing causing stability problems

Now I understand that this is becuase the dispatcher has clearly got stuck in a loop and exceeded its max iterations. However, I'm at a loss as to why it's getting stuck in the loop to start with.

I have a multi-module setup with namespaces. I have a fairly large routing to accommodate the various modules. What I'm trying to achieve is upon a controller or action not being found, it redirects to the controller and action to show the 404 page.

My services file includes the following:

    $di->set('dispatcher', function ()
    {
        // Create an EventsManager
        $eventsManager = new EventsManager();

      // Attach a listener
      $eventsManager->attach("dispatch:beforeException", function ($event, $dispatcher, $exception)
      {

          // Controller or action doesn't exist
          if ($event->getType() == 'beforeException')
          {
              switch ($exception->getCode())
              {
                  case Dispatcher::EXCEPTION_HANDLER_NOT_FOUND:
                  case Dispatcher::EXCEPTION_ACTION_NOT_FOUND:
                      $dispatcher->forward(array(
                          'module' => 'frontend',
                          'controller' => 'index',
                          'action' => 'show404',
                          'namespace' => 'app\Frontend\Controllers'
                      ));
                      return false;
              }
          }
      });

      $dispatcher = new MvcDispatcher();

      // Bind the EventsManager to the dispatcher
      $dispatcher->setEventsManager($eventsManager);
      $dispatcher->setDefaultNamespace('app\Frontend\Controllers');

      return $dispatcher;

    }, true);

Then I also have the following:

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

    $router = new Router(FALSE);

    // This is the default route. Update to suit as needed.
    $router->add("/", array(
        "namespace"     => "app\CRM\Controllers",
        "module"        => "crm",
        "controller"    => "customers",
        "action"        => "index"
        ));

    // frontend
    $frontend = new Router\Group(array(
        "module"        => "frontend",
        "controller"    => "index",
        "action"        => "index",
        "namespace"     => "app\frontend\Controllers"
        ));

    $frontend->setPrefix("/frontend");

    $frontend->add("", array(
        "controller"  => 'index',
        'action'      => 'index'
        ));

    $frontend->add("/:controller", array(
        "controller"    => 1
        ));

    $frontend->add("/:controller/:action", array(
        "controller"    => 1,
        "action"        => 2
        ));

    $frontend->add("/:controller/:action/:params", array(
        "controller"    => 1,
        "action"        => 2,
        "params"        => 3
        ));

    $router->mount($frontend);

    // What about not found?
    $router->notFound(array(
        "namespace"       => "app\Frontend\Controllers",
        "module"      => "frontend",
        "controller"  => "index",
        "action"      => "show404"
        ));

    // Remove trailing slashes:
    $router->removeExtraSlashes(true);

    return $router;
    };

In the controller IndexController I have the show404Action function which sets the header as follows:

    public function show404Action()
    {
        $this->response->setHeader('HTTP/1.0 404','Not Found');
        $this->view->setVar('body_class', "error-page");
    }

If I browse to the controller and action manually, it works great. However, if I try an invalid URL as part of another module i the error from above i.e. I have a module called core so if I go to http://localhost/core/undefined, I get the error.

Can anyone help me?

As always your advice is greatly appreciated, and I thanks you in advance.

This means a rediection to the same combination of controller and actions is being made again and again. Before doing the forward:

$dispatcher->forward(array(
    'module' => 'frontend',
    'controller' => 'index',
    'action' => 'show404',
    'namespace' => 'app\Frontend\Controllers'
));

So you have to check first if the controller $dispatcher->getControllerName() != 'index' and $dispatcher->getActionName() != 'show404'.

Also, Mvc\Dispatcher is unique per module, so pass the module name there will not redirect to other module. You have to make a response->redirect.



4.8k
Accepted
answer
edited Jul '15

Thanks Andres for your help. I think I get what you're saying, but please correct me if I'm wrong.

If I try and access module2/test which doesn't exist the forward would only try to route to module2/index/show404 and would not go to frontend/index/show404 as per the code because the dispatcher is unique to the module. Therefore it ignores the namespace and the module parameters in this instance. This means that the redirect is the only method to get the 404 to show everytime.

With that in mind, I was able to find the following post: here

Based on that post I have modified the code to get it to work. I haven't tried all use cases, but for the majority of them it seems to work. So, in case anyone else has the problem in the future, this is the code that I am now using:

    **
     * Handle Exceptions:
     */
    $di['dispatcher'] = function () use ($di)
    {
      // Create an EventsManager
      $eventsManager = new EventsManager();

      // Attach a listener
      $eventsManager->attach("dispatch:beforeException", function ($event, $dispatcher, $exception) use ($di)
      {

          if ($event->getType() == 'beforeNotFoundAction')
          {
              $di->get('response')->redirect('site/error/notFound');
              return false;
          }

          // Controller or action doesn't exist
          if ($event->getType() == 'beforeException')
          {
              switch ($exception->getCode())
              {
                  case Dispatcher::EXCEPTION_HANDLER_NOT_FOUND:
                  case Dispatcher::EXCEPTION_ACTION_NOT_FOUND:
                      $di->get('response')->redirect('/show404');
                      return false;
                  default:
                      $di->get('response')->redirect('/show500');
                      return false;
              }
          }
      });

      $dispatcher = new MvcDispatcher();

      // Bind the EventsManager to the dispatcher
      $dispatcher->setEventsManager($eventsManager);
      $dispatcher->setDefaultNamespace('app\Frontend\Controllers');

      return $dispatcher;
    };