trante
trante

Reputation: 34006

Cakephp 3 redirect in beforeFilter of parent class

In our CakePHP 3 application we found a different behaviour. We're sure that it worked well in CakePHP 2, so I suppose something changed in new version.

When user visits this url: /b2controller/myMethod, these methods run:

AppController::beforeFilter()  
BController::beforeFilter()  
B2Controller::beforeFilter()  
B2Controller::myMethod()  
B2Controller::myMethod2()    

then user is redirected to this url /ccontroller/myMethod10/

But we need this:

When user visits /b2controller/myMethod and $isOk condition is true, then redirect user to /ccontroller/myMethod10/, without running BController::beforeFilter(), B2Controller::beforeFilter(), B2Controller::myMethod() and BController::MyMethod2().

Our minimal code is like this:

class AppController {
  function beforeFilter(Event $event) {
        // set $isOk variable
        if ($isOk == TRUE) {
           return $this->redirect('/ccontroller/myMethod10/');
        }
        $aa=1;
        $ab=2;
  }
}

class BController extends AppController {
  function beforeFilter(Event $event) {
    parent::beforeFilter($event);

    $a=1;
    $b=2;
  }

  function myOtherMethod() {
      myOtherMethod2();
  }

  function myOtherMethod2() {
      ...
      ...
  }
}

class B2Controller extends BController {
  function beforeFilter(Event $event) {
    parent::beforeFilter($event);

    $m1=1;
    $m2=2;
  }

  function myMethod() {
      myMethod2();
  }

  function myMethod2() {
      ...
      ...
  }
}

class CController extends AppController {
  function beforeFilter(Event $event) {
    parent::beforeFilter($event);
  }

  function myMethod10() {
      ...
      ...
      ...
  }
}

How can I make user to redirect to another controller action, from the beforeFilter of main class ? Note that redirect occurs. But user is redirected after calling myMethod() and myMethod2().

Also note that there is other controllers like CController that uses beforeFilter redirect behaviour.

Upvotes: 14

Views: 5852

Answers (1)

Holt
Holt

Reputation: 37606

Here are 3 methods that works:

Method 1 - Override startupProcess in your controller(s)

Override the startupProcess method of AppController:

// In your AppController
public function startupProcess() {
    // Compute $isOk
    if ($isOk) {
        return $this->redirect('/c/myMethod10');
    }
    return parent::startupProcess();
}

This is a short and quite clean method, so I would go for this one if you can. If this does not fit your needs, see below.

Note: If you use this method, your components may not be initialized when you compute $isOk since the initialization is done by parent::startupProcess.

Method 2 - Send the response from AppController:

One easy but not really clean way may be to send the response from AppController::beforeFilter:

public function beforeFilter(\Cake\Event\Event $event) {
    // Compute $isOk
    if ($isOk) {
        $this->response = $this->redirect('/c/myMethod10');
        $this->response->send();
        die();
    }
}

Method 3 - Use Dispatcher Filters

A more "clean" way would be to use Dispatcher Filters:

In src/Routing/Filter/RedirectFilter.php:

<?php

namespace App\Routing\Filter;

use Cake\Event\Event;
use Cake\Routing\DispatcherFilter;

class RedirectFilter extends DispatcherFilter {

    public function beforeDispatch(Event $event) {
        // Compute $isOk
        if ($isOk) {
            $response = $event->data['response'];
            // The code bellow mainly comes from the source of Controller.php
            $response->statusCode(302);
            $response->location(\Cake\Routing\Router::url('/c/myMethod10', true));
            return $response;
        }
    }
}

In config/bootstrap.php:

DispatcherFactory::add('Redirect');

And you can remove the redirection in your AppController. This may be the cleanest way if you are able to compute $isOk from the DispatcherFilter.

Note that if you have beforeRedirect event, these will not be triggered with this method.


Edit: This was my previous answer which does not work very well if you have multiple B-like controllers.

You need to return the Response object returned by $this->redirect(). One way of achieving this is by doing the following:

class BController extends AppController {

    public function beforeFilter(\Cake\Event\Event $event) {
        $result = parent::beforeFilter($event);
        if ($result instanceof \Cake\Network\Response) {
            return $result;
        } 
        // Your stuff
    }

}

The code bellow the if is executed only if there was no redirection (parent::beforeFilter($event) did not return a Response object).

Note: I do not know how you compute isOk, but be careful of infinite redirection loop if you call $this->redirect() when calling /ccontroller/mymethod10.

Upvotes: 6

Related Questions