Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 870
trante Avatar asked Aug 24 '15 07:08

trante


1 Answers

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.

like image 131
Holt Avatar answered Sep 23 '22 01:09

Holt