mirror of
https://github.com/yiisoft/yii2.git
synced 2025-08-26 06:15:19 +08:00
Improved action filter and action execution flow by supporting installing action filters at controller, module and application levels
This commit is contained in:
@ -40,42 +40,6 @@ The return value will be handled by the `response` application
|
||||
component which can convert the output to different formats such as JSON for example. The default behavior
|
||||
is to output the value unchanged though.
|
||||
|
||||
You also can disable CSRF validation per controller and/or action, by setting its property:
|
||||
|
||||
```php
|
||||
namespace app\controllers;
|
||||
|
||||
use yii\web\Controller;
|
||||
|
||||
class SiteController extends Controller
|
||||
{
|
||||
public $enableCsrfValidation = false;
|
||||
|
||||
public function actionIndex()
|
||||
{
|
||||
// CSRF validation will not be applied to this and other actions
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
To disable CSRF validation per custom actions you can do:
|
||||
|
||||
```php
|
||||
namespace app\controllers;
|
||||
|
||||
use yii\web\Controller;
|
||||
|
||||
class SiteController extends Controller
|
||||
{
|
||||
public function beforeAction($action)
|
||||
{
|
||||
// ...set `$this->enableCsrfValidation` here based on some conditions...
|
||||
// call parent method that will check CSRF if such property is true.
|
||||
return parent::beforeAction($action);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Routes
|
||||
------
|
||||
@ -222,27 +186,49 @@ After doing so you can access your action as `http://example.com/?r=site/about`.
|
||||
Action Filters
|
||||
--------------
|
||||
|
||||
Action filters are implemented via behaviors. You should extend from `ActionFilter` to
|
||||
define a new filter. To use a filter, you should attach the filter class to the controller
|
||||
as a behavior. For example, to use the [[yii\web\AccessControl]] filter, you should have the following
|
||||
code in a controller:
|
||||
You may apply some action filters to controller actions to accomplish tasks such as determining
|
||||
who can access the current action, decorating the result of the action, etc.
|
||||
|
||||
An action filter is an instance of a class extending [[yii\base\ActionFilter]].
|
||||
|
||||
To use an action filter, attach it as a behavior to a controller or a module. The following
|
||||
example shows how to enable HTTP caching for the `index` action:
|
||||
|
||||
```php
|
||||
public function behaviors()
|
||||
{
|
||||
return [
|
||||
'access' => [
|
||||
'class' => 'yii\web\AccessControl',
|
||||
'rules' => [
|
||||
['allow' => true, 'actions' => ['admin'], 'roles' => ['@']],
|
||||
],
|
||||
'httpCache' => [
|
||||
'class' => \yii\web\HttpCache::className(),
|
||||
'only' => ['index'],
|
||||
'lastModified' => function ($action, $params) {
|
||||
$q = new \yii\db\Query();
|
||||
return $q->from('user')->max('updated_at');
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
In order to learn more about access control check the [authorization](authorization.md) section of the guide.
|
||||
Two other filters, [[yii\web\PageCache]] and [[yii\web\HttpCache]] are described in the [caching](caching.md) section of the guide.
|
||||
You may use multiple action filters at the same time. These filters will be applied in the
|
||||
order they are declared in `behaviors()`. If any of the filter cancels the action execution,
|
||||
the filters after it will be skipped.
|
||||
|
||||
When you attach a filter to a controller, it can be applied to all actions of that controller;
|
||||
If you attach a filter to a module (or application), it can be applied to the actions of any controller
|
||||
within that module (or application).
|
||||
|
||||
To create a new action filter, extend from [[yii\base\ActionFilter]] and override the
|
||||
[[yii\base\ActionFilter::beforeAction()|beforeAction()]] and [[yii\base\ActionFilter::afterAction()|afterAction()]]
|
||||
methods. The former will be executed before an action runs while the latter after an action runs.
|
||||
The return value of [[yii\base\ActionFilter::beforeAction()|beforeAction()]] determines whether
|
||||
an action should be executed or not. If `beforeAction()` of a filter returns false, the filters after this one
|
||||
will be skipped and the action will not be executed.
|
||||
|
||||
The [authorization](authorization.md) section of this guide shows how to use the [[yii\web\AccessControl]] filter,
|
||||
and the [caching](caching.md) section gives more details about the [[yii\web\PageCache]] and [[yii\web\HttpCache]] filters.
|
||||
These built-in filters are also good references when you learn to create your own filters.
|
||||
|
||||
|
||||
Catching all incoming requests
|
||||
------------------------------
|
||||
@ -252,7 +238,7 @@ when website is in maintenance mode. In order to do it you should configure web
|
||||
dynamically or via application config:
|
||||
|
||||
```php
|
||||
$config = [
|
||||
return [
|
||||
'id' => 'basic',
|
||||
'basePath' => dirname(__DIR__),
|
||||
// ...
|
||||
@ -261,6 +247,7 @@ $config = [
|
||||
'param1' => 'value1',
|
||||
'param2' => 'value2',
|
||||
],
|
||||
]
|
||||
```
|
||||
|
||||
In the above `offline/notice` refer to `OfflineController::actionNotice()`. `param1` and `param2` are parameters passed
|
||||
|
@ -3,6 +3,7 @@ Security
|
||||
|
||||
Good security is vital to the health and success of any application. Unfortunately, many developers cut corners when it comes to security, either due to a lack of understanding or because implementation is too much of a hurdle. To make your Yii powered application as secure as possible, Yii has included several excellent and easy to use security features.
|
||||
|
||||
|
||||
Hashing and verifying passwords
|
||||
-------------------------------
|
||||
|
||||
@ -86,6 +87,45 @@ $data = \yii\helpers\Security::validateData($data, $secretKey);
|
||||
```
|
||||
|
||||
|
||||
todo: XSS prevention, CSRF prevention, cookie protection, refer to 1.1 guide
|
||||
|
||||
You also can disable CSRF validation per controller and/or action, by setting its property:
|
||||
|
||||
```php
|
||||
namespace app\controllers;
|
||||
|
||||
use yii\web\Controller;
|
||||
|
||||
class SiteController extends Controller
|
||||
{
|
||||
public $enableCsrfValidation = false;
|
||||
|
||||
public function actionIndex()
|
||||
{
|
||||
// CSRF validation will not be applied to this and other actions
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
To disable CSRF validation per custom actions you can do:
|
||||
|
||||
```php
|
||||
namespace app\controllers;
|
||||
|
||||
use yii\web\Controller;
|
||||
|
||||
class SiteController extends Controller
|
||||
{
|
||||
public function beforeAction($action)
|
||||
{
|
||||
// ...set `$this->enableCsrfValidation` here based on some conditions...
|
||||
// call parent method that will check CSRF if such property is true.
|
||||
return parent::beforeAction($action);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Securing Cookies
|
||||
----------------
|
||||
|
||||
|
@ -187,6 +187,7 @@ Yii Framework 2 Change Log
|
||||
- Enh: Added summaryOptions and emptyTextOptions to BaseListView (johonunu)
|
||||
- Enh: Implemented Oracle column comment reading from another schema (gureedo, samdark)
|
||||
- Enh: Added support to allow an event handler to be inserted at the beginning of the existing event handler list (qiangxue)
|
||||
- Enh: Improved action filter and action execution flow by supporting installing action filters at controller, module and application levels (qiangxue)
|
||||
- Chg #47: Changed Markdown library to cebe/markdown and adjusted Markdown helper API (cebe)
|
||||
- Chg #735: Added back `ActiveField::hiddenInput()` (qiangxue)
|
||||
- Chg #1186: Changed `Sort` to use comma to separate multiple sort fields and use negative sign to indicate descending sort (qiangxue)
|
||||
|
@ -8,8 +8,10 @@
|
||||
namespace yii\base;
|
||||
|
||||
/**
|
||||
* ActionFilter provides a base implementation for action filters that can be added to a controller
|
||||
* to handle the `beforeAction` event.
|
||||
* ActionFilter is the base class for action filters.
|
||||
*
|
||||
* An action filter will participate in the action execution workflow by responding to
|
||||
* the `beforeAction` and `afterAction` events triggered by modules and controllers.
|
||||
*
|
||||
* Check implementation of [[\yii\web\AccessControl]], [[\yii\web\PageCache]] and [[\yii\web\HttpCache]] as examples on how to use it.
|
||||
*
|
||||
@ -31,43 +33,54 @@ class ActionFilter extends Behavior
|
||||
*/
|
||||
public $except = [];
|
||||
|
||||
|
||||
/**
|
||||
* Declares event handlers for the [[owner]]'s events.
|
||||
* @return array events (array keys) and the corresponding event handler methods (array values).
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function events()
|
||||
public function attach($owner)
|
||||
{
|
||||
return [
|
||||
Controller::EVENT_BEFORE_ACTION => 'beforeFilter',
|
||||
Controller::EVENT_AFTER_ACTION => 'afterFilter',
|
||||
];
|
||||
$this->owner = $owner;
|
||||
$owner->on(Controller::EVENT_BEFORE_ACTION, [$this, 'beforeFilter']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function detach()
|
||||
{
|
||||
if ($this->owner) {
|
||||
$this->owner->off(Controller::EVENT_BEFORE_ACTION, [$this, 'beforeFilter']);
|
||||
$this->owner->off(Controller::EVENT_AFTER_ACTION, [$this, 'afterFilter']);
|
||||
$this->owner = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ActionEvent $event
|
||||
* @return boolean
|
||||
*/
|
||||
public function beforeFilter($event)
|
||||
{
|
||||
if ($this->isActive($event->action)) {
|
||||
$event->isValid = $this->beforeAction($event->action);
|
||||
if (!$event->isValid) {
|
||||
$event->handled = true;
|
||||
}
|
||||
if (!$this->isActive($event->action)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return $event->isValid;
|
||||
$event->isValid = $this->beforeAction($event->action);
|
||||
if ($event->isValid) {
|
||||
// call afterFilter only if beforeFilter succeeds
|
||||
// beforeFilter and afterFilter should be properly nested
|
||||
$this->owner->on(Controller::EVENT_AFTER_ACTION, [$this, 'afterFilter'], null, false);
|
||||
} else {
|
||||
$event->handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ActionEvent $event
|
||||
* @return boolean
|
||||
*/
|
||||
public function afterFilter($event)
|
||||
{
|
||||
if ($this->isActive($event->action)) {
|
||||
$event->result = $this->afterAction($event->action, $event->result);
|
||||
}
|
||||
$event->result = $this->afterAction($event->action, $event->result);
|
||||
$this->owner->off(Controller::EVENT_AFTER_ACTION, [$this, 'afterFilter']);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -100,6 +113,16 @@ class ActionFilter extends Behavior
|
||||
*/
|
||||
protected function isActive($action)
|
||||
{
|
||||
return !in_array($action->id, $this->except, true) && (empty($this->only) || in_array($action->id, $this->only, true));
|
||||
if ($this->owner instanceof Module) {
|
||||
// convert action uniqueId into an ID relative to the module
|
||||
$mid = $this->owner->getUniqueId();
|
||||
$id = $action->getUniqueId();
|
||||
if ($mid !== '' && strpos($id, $mid) === 0) {
|
||||
$id = substr($id, strlen($mid) + 1);
|
||||
}
|
||||
} else {
|
||||
$id = $action->id;
|
||||
}
|
||||
return !in_array($id, $this->except, true) && (empty($this->only) || in_array($id, $this->only, true));
|
||||
}
|
||||
}
|
||||
|
@ -49,16 +49,6 @@ abstract class Application extends Module
|
||||
* @event Event an event raised after the application successfully handles a request (before the response is sent out).
|
||||
*/
|
||||
const EVENT_AFTER_REQUEST = 'afterRequest';
|
||||
/**
|
||||
* @event ActionEvent an event raised before executing a controller action.
|
||||
* You may set [[ActionEvent::isValid]] to be false to cancel the action execution.
|
||||
*/
|
||||
const EVENT_BEFORE_ACTION = 'beforeAction';
|
||||
/**
|
||||
* @event ActionEvent an event raised after executing a controller action.
|
||||
*/
|
||||
const EVENT_AFTER_ACTION = 'afterAction';
|
||||
|
||||
/**
|
||||
* Application state used by [[state]]: application just started.
|
||||
*/
|
||||
|
@ -113,31 +113,48 @@ class Controller extends Component implements ViewContextInterface
|
||||
public function runAction($id, $params = [])
|
||||
{
|
||||
$action = $this->createAction($id);
|
||||
if ($action !== null) {
|
||||
Yii::trace("Route to run: " . $action->getUniqueId(), __METHOD__);
|
||||
if (Yii::$app->requestedAction === null) {
|
||||
Yii::$app->requestedAction = $action;
|
||||
}
|
||||
$oldAction = $this->action;
|
||||
$this->action = $action;
|
||||
$result = null;
|
||||
$event = new ActionEvent($action);
|
||||
Yii::$app->trigger(Application::EVENT_BEFORE_ACTION, $event);
|
||||
if ($event->isValid && $this->module->beforeAction($action) && $this->beforeAction($action)) {
|
||||
$result = $action->runWithParams($params);
|
||||
$result = $this->afterAction($action, $result);
|
||||
$result = $this->module->afterAction($action, $result);
|
||||
$event = new ActionEvent($action);
|
||||
$event->result = $result;
|
||||
Yii::$app->trigger(Application::EVENT_AFTER_ACTION, $event);
|
||||
$result = $event->result;
|
||||
}
|
||||
$this->action = $oldAction;
|
||||
|
||||
return $result;
|
||||
} else {
|
||||
if ($action === null) {
|
||||
throw new InvalidRouteException('Unable to resolve the request: ' . $this->getUniqueId() . '/' . $id);
|
||||
}
|
||||
|
||||
Yii::trace("Route to run: " . $action->getUniqueId(), __METHOD__);
|
||||
|
||||
if (Yii::$app->requestedAction === null) {
|
||||
Yii::$app->requestedAction = $action;
|
||||
}
|
||||
|
||||
$oldAction = $this->action;
|
||||
$this->action = $action;
|
||||
|
||||
$modules = [];
|
||||
$runAction = true;
|
||||
|
||||
foreach ($this->getModules() as $module) {
|
||||
if ($module->beforeAction($action)) {
|
||||
array_unshift($modules, $module);
|
||||
} else {
|
||||
$runAction = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$result = null;
|
||||
|
||||
if ($runAction) {
|
||||
if ($this->beforeAction($action)) {
|
||||
$result = $action->runWithParams($params);
|
||||
$result = $this->afterAction($action, $result);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($modules as $module) {
|
||||
/** @var Module $module */
|
||||
$result = $module->afterAction($action, $result);
|
||||
}
|
||||
|
||||
$this->action = $oldAction;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -207,25 +224,52 @@ class Controller extends Component implements ViewContextInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is invoked right before an action is to be executed (after all possible filters).
|
||||
* You may override this method to do last-minute preparation for the action.
|
||||
* If you override this method, please make sure you call the parent implementation first.
|
||||
* This method is invoked right before an action is executed.
|
||||
*
|
||||
* The method will trigger the [[EVENT_BEFORE_ACTION]] event. The return value of the method
|
||||
* will determine whether the action should continue to run.
|
||||
*
|
||||
* If you override this method, your code should look like the following:
|
||||
*
|
||||
* ```php
|
||||
* public function beforeAction($action)
|
||||
* {
|
||||
* if (parent::beforeAction($action)) {
|
||||
* // your custom code here
|
||||
* return true; // or false if needed
|
||||
* } else {
|
||||
* return false;
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param Action $action the action to be executed.
|
||||
* @return boolean whether the action should continue to be executed.
|
||||
* @return boolean whether the action should continue to run.
|
||||
*/
|
||||
public function beforeAction($action)
|
||||
{
|
||||
$event = new ActionEvent($action);
|
||||
$this->trigger(self::EVENT_BEFORE_ACTION, $event);
|
||||
|
||||
return $event->isValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is invoked right after an action is executed.
|
||||
* You may override this method to do some postprocessing for the action.
|
||||
* If you override this method, please make sure you call the parent implementation first.
|
||||
* Also make sure you return the action result, whether it is processed or not.
|
||||
*
|
||||
* The method will trigger the [[EVENT_AFTER_ACTION]] event. The return value of the method
|
||||
* will be used as the action return value.
|
||||
*
|
||||
* If you override this method, your code should look like the following:
|
||||
*
|
||||
* ```php
|
||||
* public function afterAction($action, $result)
|
||||
* {
|
||||
* $result = parent::afterAction($action, $result);
|
||||
* // your custom code here
|
||||
* return $result;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param Action $action the action just executed.
|
||||
* @param mixed $result the action return result.
|
||||
* @return mixed the processed action result.
|
||||
@ -235,10 +279,26 @@ class Controller extends Component implements ViewContextInterface
|
||||
$event = new ActionEvent($action);
|
||||
$event->result = $result;
|
||||
$this->trigger(self::EVENT_AFTER_ACTION, $event);
|
||||
|
||||
return $event->result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all ancestor modules of this controller.
|
||||
* The first module in the array is the outermost one (i.e., the application instance),
|
||||
* while the last is the innermost one.
|
||||
* @return Module[] all ancestor modules that this controller is located within.
|
||||
*/
|
||||
public function getModules()
|
||||
{
|
||||
$modules = [$this->module];
|
||||
$module = $this->module;
|
||||
while ($module->module !== null) {
|
||||
array_unshift($modules, $module->module);
|
||||
$module = $module->module;
|
||||
}
|
||||
return $modules;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string the controller ID that is prefixed with the module ID (if any).
|
||||
*/
|
||||
|
@ -37,6 +37,16 @@ use yii\di\ServiceLocator;
|
||||
*/
|
||||
class Module extends ServiceLocator
|
||||
{
|
||||
/**
|
||||
* @event ActionEvent an event raised before executing a controller action.
|
||||
* You may set [[ActionEvent::isValid]] to be false to cancel the action execution.
|
||||
*/
|
||||
const EVENT_BEFORE_ACTION = 'beforeAction';
|
||||
/**
|
||||
* @event ActionEvent an event raised after executing a controller action.
|
||||
*/
|
||||
const EVENT_AFTER_ACTION = 'afterAction';
|
||||
|
||||
/**
|
||||
* @var array custom module parameters (name => value).
|
||||
*/
|
||||
@ -551,28 +561,61 @@ class Module extends ServiceLocator
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is invoked right before an action of this module is to be executed (after all possible filters.)
|
||||
* You may override this method to do last-minute preparation for the action.
|
||||
* Make sure you call the parent implementation so that the relevant event is triggered.
|
||||
* This method is invoked right before an action within this module is executed.
|
||||
*
|
||||
* The method will trigger the [[EVENT_BEFORE_ACTION]] event. The return value of the method
|
||||
* will determine whether the action should continue to run.
|
||||
*
|
||||
* If you override this method, your code should look like the following:
|
||||
*
|
||||
* ```php
|
||||
* public function beforeAction($action)
|
||||
* {
|
||||
* if (parent::beforeAction($action)) {
|
||||
* // your custom code here
|
||||
* return true; // or false if needed
|
||||
* } else {
|
||||
* return false;
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param Action $action the action to be executed.
|
||||
* @return boolean whether the action should continue to be executed.
|
||||
*/
|
||||
public function beforeAction($action)
|
||||
{
|
||||
return true;
|
||||
$event = new ActionEvent($action);
|
||||
$this->trigger(self::EVENT_BEFORE_ACTION, $event);
|
||||
return $event->isValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is invoked right after an action of this module has been executed.
|
||||
* You may override this method to do some postprocessing for the action.
|
||||
* Make sure you call the parent implementation so that the relevant event is triggered.
|
||||
* Also make sure you return the action result, whether it is processed or not.
|
||||
* This method is invoked right after an action within this module is executed.
|
||||
*
|
||||
* The method will trigger the [[EVENT_AFTER_ACTION]] event. The return value of the method
|
||||
* will be used as the action return value.
|
||||
*
|
||||
* If you override this method, your code should look like the following:
|
||||
*
|
||||
* ```php
|
||||
* public function afterAction($action, $result)
|
||||
* {
|
||||
* $result = parent::afterAction($action, $result);
|
||||
* // your custom code here
|
||||
* return $result;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param Action $action the action just executed.
|
||||
* @param mixed $result the action return result.
|
||||
* @return mixed the processed action result.
|
||||
*/
|
||||
public function afterAction($action, $result)
|
||||
{
|
||||
return $result;
|
||||
$event = new ActionEvent($action);
|
||||
$event->result = $result;
|
||||
$this->trigger(self::EVENT_AFTER_ACTION, $event);
|
||||
return $event->result;
|
||||
}
|
||||
}
|
||||
|
@ -106,12 +106,8 @@ class Controller extends \yii\web\Controller
|
||||
public function beforeAction($action)
|
||||
{
|
||||
$this->authenticate($action);
|
||||
if (parent::beforeAction($action)) {
|
||||
$this->checkRateLimit($action);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
$this->checkRateLimit($action);
|
||||
return parent::beforeAction($action);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -26,10 +26,10 @@ use yii\base\Action;
|
||||
* return [
|
||||
* 'httpCache' => [
|
||||
* 'class' => \yii\web\HttpCache::className(),
|
||||
* 'only' => ['list'],
|
||||
* 'only' => ['index'],
|
||||
* 'lastModified' => function ($action, $params) {
|
||||
* $q = new Query();
|
||||
* return strtotime($q->from('users')->max('updated_timestamp'));
|
||||
* $q = new \yii\db\Query();
|
||||
* return $q->from('user')->max('updated_at');
|
||||
* },
|
||||
* // 'etagSeed' => function ($action, $params) {
|
||||
* // return // generate etag seed here
|
||||
|
@ -31,7 +31,7 @@ use yii\caching\Dependency;
|
||||
* 'class' => \yii\web\PageCache::className(),
|
||||
* 'only' => ['list'],
|
||||
* 'duration' => 60,
|
||||
* 'dependecy' => [
|
||||
* 'dependency' => [
|
||||
* 'class' => 'yii\caching\DbDependency',
|
||||
* 'sql' => 'SELECT COUNT(*) FROM post',
|
||||
* ],
|
||||
|
164
tests/unit/framework/base/ActionFilterTest.php
Normal file
164
tests/unit/framework/base/ActionFilterTest.php
Normal file
@ -0,0 +1,164 @@
|
||||
<?php
|
||||
/**
|
||||
* @link http://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license http://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace yiiunit\framework\base;
|
||||
|
||||
use Yii;
|
||||
use yii\base\ActionFilter;
|
||||
use yii\base\Controller;
|
||||
use yiiunit\TestCase;
|
||||
|
||||
|
||||
/**
|
||||
* @group base
|
||||
*/
|
||||
class ActionFilterTest extends TestCase
|
||||
{
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
$this->mockApplication();
|
||||
}
|
||||
|
||||
public function testFilter()
|
||||
{
|
||||
// no filters
|
||||
$controller = new FakeController('fake', Yii::$app);
|
||||
$this->assertNull($controller->result);
|
||||
$result = $controller->runAction('test');
|
||||
$this->assertEquals('x', $result);
|
||||
$this->assertNull($controller->result);
|
||||
|
||||
// all filters pass
|
||||
$controller = new FakeController('fake', Yii::$app, [
|
||||
'behaviors' => [
|
||||
'filter1' => Filter1::className(),
|
||||
'filter3' => Filter3::className(),
|
||||
],
|
||||
]);
|
||||
$this->assertNull($controller->result);
|
||||
$result = $controller->runAction('test');
|
||||
$this->assertEquals('x-3-1', $result);
|
||||
$this->assertEquals([1, 3], $controller->result);
|
||||
|
||||
// a filter stops in the middle
|
||||
$controller = new FakeController('fake', Yii::$app, [
|
||||
'behaviors' => [
|
||||
'filter1' => Filter1::className(),
|
||||
'filter2' => Filter2::className(),
|
||||
'filter3' => Filter3::className(),
|
||||
],
|
||||
]);
|
||||
$this->assertNull($controller->result);
|
||||
$result = $controller->runAction('test');
|
||||
$this->assertNull($result);
|
||||
$this->assertEquals([1, 2], $controller->result);
|
||||
|
||||
// the first filter stops
|
||||
$controller = new FakeController('fake', Yii::$app, [
|
||||
'behaviors' => [
|
||||
'filter2' => Filter2::className(),
|
||||
'filter1' => Filter1::className(),
|
||||
'filter3' => Filter3::className(),
|
||||
],
|
||||
]);
|
||||
$this->assertNull($controller->result);
|
||||
$result = $controller->runAction('test');
|
||||
$this->assertNull($result);
|
||||
$this->assertEquals([2], $controller->result);
|
||||
|
||||
// the last filter stops
|
||||
$controller = new FakeController('fake', Yii::$app, [
|
||||
'behaviors' => [
|
||||
'filter1' => Filter1::className(),
|
||||
'filter3' => Filter3::className(),
|
||||
'filter2' => Filter2::className(),
|
||||
],
|
||||
]);
|
||||
$this->assertNull($controller->result);
|
||||
$result = $controller->runAction('test');
|
||||
$this->assertNull($result);
|
||||
$this->assertEquals([1, 3, 2], $controller->result);
|
||||
}
|
||||
}
|
||||
|
||||
class FakeController extends Controller
|
||||
{
|
||||
public $result;
|
||||
public $behaviors = [];
|
||||
|
||||
public function behaviors()
|
||||
{
|
||||
return $this->behaviors;
|
||||
}
|
||||
|
||||
public function actionTest()
|
||||
{
|
||||
return 'x';
|
||||
}
|
||||
}
|
||||
|
||||
class Filter1 extends ActionFilter
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function beforeAction($action)
|
||||
{
|
||||
$action->controller->result[] = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function afterAction($action, $result)
|
||||
{
|
||||
return $result . '-1';
|
||||
}
|
||||
}
|
||||
|
||||
class Filter2 extends ActionFilter
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function beforeAction($action)
|
||||
{
|
||||
$action->controller->result[] = 2;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function afterAction($action, $result)
|
||||
{
|
||||
return $result . '-2';
|
||||
}
|
||||
}
|
||||
|
||||
class Filter3 extends ActionFilter
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function beforeAction($action)
|
||||
{
|
||||
$action->controller->result[] = 3;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function afterAction($action, $result)
|
||||
{
|
||||
return $result . '-3';
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user