diff --git a/docs/guide/rest.md b/docs/guide/rest.md index eb6cca7..c11d029 100644 --- a/docs/guide/rest.md +++ b/docs/guide/rest.md @@ -147,54 +147,16 @@ While not required, it is recommended that you develop your RESTful APIs as an a your Web front end and back end. -Adding or Removing Endpoints ----------------------------- +Creating Resource Classes +------------------------- -As explained above, controllers and actions are used to implement API endpoints. +RESTful APIs are all about accessing and manipulating resources. In Yii, a resource can be an object of any class. +However, if your resource classes extend from [[yii\base\Model]] or its child classes (e.g. [[yii\db\ActiveRecord]]), +you may enjoy the following benefits: -To add an API endpoint servicing a new kind of model (resource), create a new controller class by extending -[[yii\rest\ActiveController]] or [[yii\rest\Controller]]. The difference between these two base controller -classes is that the former is a subclass of the latter and implements a commonly needed actions to deal -with ActiveRecord. The controller class should be named after the model class with the `Controller` suffix. -For example, for the `Post` model, you would create a `PostController` class. - -If your new controller class extends from [[yii\rest\ActiveController]], you already have a whole set of -endpoints available out of box, as shown in the quick example. You may want to disable some of the actions -or customize them. This can be easily done by overriding the `actions()` method, like the following, - -```php -public function actions() -{ - $actions = parent::actions(); - - // disable the "delete" and "create" actions - unset($actions['delete'], $actions['create']); - - // customize the data provider preparation with the "prepareDataProvider()" method - $actions['index']['prepareDataProvider'] = [$this, 'prepareDataProvider']; - - return $actions; -} - -public function prepareDataProvider() -{ - // prepare and return a data provider for the "index" action -} -``` - -You can certainly create new actions like you do with regular controllers. The only difference is that -instead of calling [[yii\base\Controller::render()]] to render views, you directly return the data -in your action. For example, - -```php -public function actionSearch($keyword) -{ - $result = SolrService::search($keyword); - return $result; -} -``` - -The data will be automatically formatted and sent to the client, as we will explain in the next section. +* Input data validation; +* Query, create, update and delete data, if extending from [[yii\db\ActiveRecord]]; +* Customizable data formatting (to be explained in the next section). Formatting Response Data @@ -203,7 +165,7 @@ Formatting Response Data By default, Yii supports two response formats for RESTful APIs: JSON and XML. If you want to support other formats, you should configure [[yii\rest\Controller::supportedFormats]] and also [[yii\web\Response::formatters]]. -The data formatting is in general a two-step process: +Formatting response data in general involves two steps: 1. The objects (including embedded objects) in the response data are converted into arrays by [[yii\rest\Serializer]]; 2. The array data are converted into different formats (e.g. JSON, XML) by [[yii\web\ResponseFormatterInterface|response formatters]]. @@ -213,32 +175,46 @@ Step 1 involves some major development effort as explained below. When the [[yii\rest\Serializer|serializer]] converts an object into an array, it will call the `toArray()` method of the object if it implements [[yii\base\ArrayableInterface]]. If an object does not implement this interface, -an array consisting of all its public properties will be returned. +its public properties will be returned instead. For classes extending from [[yii\base\Model]] or [[yii\db\ActiveRecord]], besides directly overriding `toArray()`, -you may also override the `fields()` method and/or the `extraFields()` method to customize the data to be returned. +you may also override the `fields()` method and/or the `extraFields()` method to customize the data being returned. -The method [[yii\base\Model::fields()]] declares a set of fields of an object that should be included in the result. -The default implementation returns all attributes of a model as the output fields. You can customize it to add, -remove, rename or reformat the fields. For example, +The method [[yii\base\Model::fields()]] declares a set of *fields* that should be included in the result. +A field is simply a named data item. In a result array, the array keys are the field names, and the array values +are the corresponding field values. The default implementation of [[yii\base\Model::fields()]] is to return +all attributes of a model as the output fields; for [[yii\db\ActiveRecord::fields()]], by default it will return +the names of the attributes whose values have been populated into the object. + +You can override the `fields()` method to add, remove, rename or redefine fields. For example, ```php -class User extends \yii\db\ActiveRecord +// explicitly list every field, best used when you want to make sure the changes +// in your DB table or model attributes do not cause your field changes (to keep API backward compatibility). +public function fields() { - public function fields() - { - $fields = parent::fields(); + return [ + // field name is the same as the attribute name + 'id', + // field name is "email", the corresponding attribute name is "email_address" + 'email' => 'email_address', + // field name is "name", its value is defined by a PHP callback + 'name' => function () { + return $this->first_name . ' ' . $this->last_name; + }, + ]; +} - // remove fields that contain sensitive information - unset($fields['auth_key'], $fields['password_hash'], $fields['password_reset_token']); +// filter out some fields, best used when you want to inherit the parent implementation +// and blacklist some sensitive fields. +public function fields() +{ + $fields = parent::fields(); - // add a new field "full_name" defined as the concatenation of "first_name" and "last_name" - $fields['full_name'] = function () { - return $this->first_name . ' ' . $this->last_name; - }; + // remove fields that contain sensitive information + unset($fields['auth_key'], $fields['password_hash'], $fields['password_reset_token']); - return $fields; - } + return $fields; } ``` @@ -274,30 +250,335 @@ For example, `http://localhost/users?fields=id,email&expand=profile` may return ] ``` +You may wonder who triggers the conversion from objects to arrays when an action returns an object or object collection. +The answer is that this is done by [[yii\rest\Controller::serializer]] in the [[yii\base\Controller::afterAction()|afterAction()]] +method. By default, [[yii\rest\Serializer]] is used as the serializer that can recognize resource objects extending from +[[yii\base\Model]] and collection objects implementing [[yii\data\DataProviderInterface]]. The serializer +will call the `toArray()` method of these objects and pass the `fields` and `expand` user parameters to the method. +If there are any embedded objects, they will also be converted into arrays recursively. + +If all your resource objects are of [[yii\base\Model]] or its child classes, such as [[yii\db\ActiveRecord]], +and you only use [[yii\data\DataProviderInterface]] as resource collections, the default data formatting +implementation should work very well. However, if you want to introduce some new resource classes that do not +extend from [[yii\base\Model]], or if you want to use some new collection classes, you will need to +customize the serializer class and configure [[yii\rest\Controller::serializer]] to use it. +You new resource classes may use the trait [[yii\base\ArrayableTrait]] to support selective field output +as explained above. + + +### HATEOAS Support + +TBD + + +Creating Controllers and Actions +-------------------------------- + +So you have the resource data and you have specified how the resource data should be formatted, the next thing +to do is to create controller actions to expose the resource data to end users. + +Yii provides two base controller classes to simplify your work of creating RESTful actions: +[[yii\rest\Controller]] and [[yii\rest\ActiveController]]. The difference between these two controllers +is that the latter provides a default set of actions that are specified designed to deal with +resources represented as ActiveRecord. So if you are using ActiveRecord and you are comfortable with +the provided built-in actions, you may consider creating your controller class by extending from +the latter. Otherwise, extending from [[yii\rest\Controller]] will allow you to develop actions +from scratch. + +Both [[yii\rest\Controller]] and [[yii\rest\ActiveController]] provide the following features which will +be described in detail in the next few sections: + +* Response format negotiation; +* API version negotiation; +* HTTP method validation; +* User authentication; +* Rate limiting. + +[[yii\rest\ActiveController]] in addition provides the following features specifically for working +with ActiveRecord: + +* A set of commonly used actions: `index`, `view`, `create`, `update`, `delete`, `options`; +* User authorization in regard to the requested action and resource. + +When creating a new controller class, a convention in naming the controller class is to use +the type name of the resource and use singular form. For example, to serve user information, +the controller may be named as `UserController`. + +Creating a new action is similar to creating an action for a Web application. The only difference +is that instead of rendering the result using a view by calling the `render()` method, for RESTful actions +you directly return the data. The [[yii\rest\Controller::serializer|serializer]] and the +[[yii\web\Response|response object]] will handle the conversion from the original data to the requested +format. For example, + +```php +public function actionSearch($keyword) +{ + $result = SolrService::search($keyword); + return $result; +} +``` + +If your controller class extends from [[yii\rest\ActiveController]], you should set +its [[yii\rest\ActiveController::modelClass||modelClass]] property to be the name of the resource class +that you plan to serve through this controller. The class must implement [[yii\db\ActiveRecordInterface]]. + +With [[yii\rest\ActiveController]], you may want to disable some of the built-in actions or customize them. +To do so, override the `actions()` method like the following: + +```php +public function actions() +{ + $actions = parent::actions(); + + // disable the "delete" and "create" actions + unset($actions['delete'], $actions['create']); + + // customize the data provider preparation with the "prepareDataProvider()" method + $actions['index']['prepareDataProvider'] = [$this, 'prepareDataProvider']; + + return $actions; +} + +public function prepareDataProvider() +{ + // prepare and return a data provider for the "index" action +} +``` + +The following list summarizes the built-in actions supported by [[yii\rest\ActiveController]]: + +* [[yii\rest\IndexAction|index]]: list resources page by page; +* [[yii\rest\ViewAction|view]]: return the details of a specified resource; +* [[yii\rest\CreateAction|create]]: create a new resource; +* [[yii\rest\UpdateAction|update]]: update an existing resource; +* [[yii\rest\DeleteAction|delete]]: delete the specified resource; +* [[yii\rest\OptionsAction|options]]: return the supported HTTP methods. + Routing ------- +With resource and controller classes ready, you can access the resources using the URL like +`http://localhost/index.php?r=user/create`. As you can see, the format of the URL is the same as that +for Web applications. + +In practice, you usually want to enable pretty URLs and take advantage of HTTP verbs. +For example, a request `POST /users` would mean accessing the `user/create` action. +This can be done easily by configuring the `urlManager` application component in the application +configuration like the following: + +```php +'urlManager' => [ + 'enablePrettyUrl' => true, + 'enableStrictParsing' => true, + 'showScriptName' => false, + 'rules' => [ + ['class' => 'yii\rest\UrlRule', 'controller' => 'user'], + ], +] +``` + +Compared to the URL management for Web applications, the main new thing above is the use of +[[yii\rest\UrlRule]] for routing RESTful API requests. This special URL rule class will +create a whole set of child URL rules to support routing and URL creation for the specified controller(s). +For example, the above code is roughly equivalent to the following rules: + +```php +[ + 'PUT,PATCH users/' => 'user/update', + 'DELETE users/' => 'user/delete', + 'GET,HEAD users/' => 'user/view', + 'POST users' => 'user/create', + 'GET,HEAD users' => 'user/index', + 'users/' => 'user/options', + 'users' => 'user/options', +] +``` + +And the following API endpoints are supported by this rule: + +* `GET /users`: list all users page by page; +* `HEAD /users`: show the overview information of user listing; +* `POST /users`: create a new user; +* `GET /users/123`: return the details of the user 123; +* `HEAD /users/123`: show the overview information of user 123; +* `PATCH /users/123` and `PUT /users/123`: update the user 123; +* `DELETE /users/123`: delete the user 123; +* `OPTIONS /users`: show the supported verbs regarding endpoint `/users`; +* `OPTIONS /users/123`: show the supported verbs regarding endpoint `/users/123`. + +You may configure the `only` and `except` options to explicitly list which actions to support or which +actions should be disabled, respectively. For example, + +```php +[ + 'class' => 'yii\rest\UrlRule', + 'controller' => 'user', + 'except' => ['delete', 'create', 'update'], +], +``` + +You may also configure `patterns` or `extra` to redefine existing patterns or add new patterns supported by this rule. +For example, to support a new action `search` by the endpoint `GET /users/search`, configure the `extra` option as follows, + +```php +[ + 'class' => 'yii\rest\UrlRule', + 'controller' => 'user', + 'extra' => [ + 'GET search' => 'search', + ], +``` + +You may have noticed that the controller ID `user` appears in plural form as `users` in the endpoints. +This is because [[yii\rest\UrlRule]] automatically pluralizes controller IDs for them to use in endpoints. +You may disable this behavior by setting [[yii\rest\UrlRule::pluralize]] to be false, or if you want +to use some special names you may configure the [[yii\rest\UrlRule::controller]] property. + Authentication -------------- +Unlike Web applications, RESTful APIs should be stateless, which means sessions or cookies should not +be used. Therefore, each request should come with some sort of authentication credentials because +the user authentication status may not be maintained by sessions or cookies. A common practice is +to send a secret access token with each request to authenticate the user. Since an access token +can be used to uniquely identify and authenticate a user, **the API requests should always be sent +via HTTPS to prevent from man-in-the-middle (MitM) attacks**. + +There are different ways to send an access token: + +* [HTTP Basic Auth](http://en.wikipedia.org/wiki/Basic_access_authentication): the access token + is sent as the username. This is should only be used when an access token can be safely stored + on the API consumer side. For example, the API consumer is a program running on a server. +* Query parameter: the access token is sent as a query parameter in the API URL, e.g., + `https://example.com/users?access-token=xxxxxxxx`. Because most Web servers will keep query + parameters in server logs, this approach should be mainly used to serve `JSONP` requests which + cannot use HTTP headers to send access tokens. +* [OAuth 2](http://oauth.net/2/): the access token is obtained by the consumer from an authorization + server and sent to the API server via [HTTP Bearer Tokens](http://tools.ietf.org/html/rfc6750), + according to the OAuth2 protocol. + +Yii supports all of the above authentication methods and can be further extended to support other methods. + +To enable authentication for your APIs, do the following two steps: + +1. Configure [[yii\rest\Controller::authMethods]] with the authentication methods you plan to use. +2. Implement [[yii\web\IdentityInterface::findIdentityByAccessToken()]] in your [[yii\web\User::identityClass|user identity class]]. + +For example, to enable all three authentication methods explained above, you would configure `authMethods` +as follows, + +```php +class UserController extends ActiveController +{ + public $authMethods = [ + 'yii\rest\HttpBasicAuth', + 'yii\rest\QueryParamAuth', + 'yii\rest\HttpBearerAuth', + ]; +} +``` + +Each element in `authMethods` should be an auth class name or a configuration array. An auth class +must implement [[yii\rest\AuthInterface]]. + +Implementation of `findIdentityByAccessToken()` is application specific. For example, in simple scenarios +when each user can only have one access token, you may store the access token in an `access_token` column +in the user table. The method can then be readily implemented in the `User` class as follows, + +```php +use yii\db\ActiveRecord; +use yii\web\IdentityInterface; + +class User extends ActiveRecord implements IdentityInterface +{ + public static function findIdentityByAccessToken($token) + { + return static::find(['access_token' => $token]); + } +} +``` + +After authentication is enabled as described above, for every API request, the requested controller +will try to authenticate the user in its `beforeAction()` step. + +If authentication succeeds, the controller will perform other checks (such as rate limiting, authorization) +and then run the action. The authenticated user identity information can be retrieved via `Yii::$app->user->identity`. + +If authentication fails, a response with HTTP status 401 will be sent back together with other appropriate headers +(such as a `WWW-Authenticate` header for HTTP Basic Auth). + Authorization ------------- +After a user is authenticated, you probably want to check if he has the permission to perform the requested +action for the requested resource. This process is called *authorization* which is covered in detail in +the [Authorization chapter](authorization.md). -Versioning ----------- +You may use the [[yii\web\AccessControl]] filter and/or the Role-Based Access Control (RBAC) component +to implementation authorization. +To simplify the authorization check, you may also override the [[yii\rest\Controller::checkAccess()]] method +and then call this method in places where authorization is needed. By default, the built-in actions provided +by [[yii\rest\ActiveController]] will call this method when they are about to run. -Caching -------- +```php +/** + * Checks the privilege of the current user. + * + * This method should be overridden to check whether the current user has the privilege + * to run the specified action against the specified data model. + * If the user does not have access, a [[ForbiddenHttpException]] should be thrown. + * + * @param string $action the ID of the action to be executed + * @param \yii\base\Model $model the model to be accessed. If null, it means no specific model is being accessed. + * @param array $params additional parameters + * @throws ForbiddenHttpException if the user does not have access + */ +public function checkAccess($action, $model = null, $params = []) +{ +} +``` Rate Limiting ------------- +To prevent abuse, you should consider adding rate limiting to your APIs. For example, you may limit the API usage +of each user to be at most 100 API calls within a period of 10 minutes. If too many requests are received from a user +within the period of the time, a response with status code 429 (meaning Too Many Requests) should be returned. + +To enable rate limiting, the [[yii\web\User::identityClass|user identity class]] should implement [[yii\rest\RateLimitInterface]]. +This interface requires implementation of the following three methods: + +* `getRateLimit()`: returns the maximum number of allowed requests and the time period, e.g., `[100, 600]` means + at most 100 API calls within 600 seconds. +* `loadAllowance()`: returns the number of remaining requests allowed and the corresponding UNIX timestamp + when the rate limit is checked last time. +* `saveAllowance()`: saves the number of remaining requests allowed and the current UNIX timestamp. + +You may use two columns in the user table to record the allowance and timestamp information. +And `loadAllowance()` and `saveAllowance()` can then be implementation by reading and saving the values +of the two columns corresponding to the current authenticated user. To improve performance, you may also +consider storing these information in cache or some NoSQL storage. + +Once the identity class implements the required interface, Yii will automatically use the rate limiter +as specified by [[yii\rest\Controller::rateLimiter]] to perform rate limiting check. The rate limiter +will thrown a [[yii\web\TooManyRequestsHttpException]] if rate limit is exceeded. + +When rate limiting is enabled, every response will be sent with the following HTTP headers containing +the current rate limiting information: + +* `X-Rate-Limit-Limit`: The maximum number of requests allowed with a time period; +* `X-Rate-Limit-Remaining`: The number of remaining requests in the current time period; +* `X-Rate-Limit-Reset`: The number of seconds to wait in order to get the maximum number of allowed requests. + + +Caching +------- + Error Handling -------------- @@ -309,7 +590,7 @@ Error Handling * `304`: Resource was not modified. You can use the cached version. * `400`: Bad request. This could be caused by various reasons from the user side, such as invalid JSON data in the request body, invalid action parameters, etc. -* `401`: No valid API access token is provided. +* `401`: Authentication failed. * `403`: The authenticated user is not allowed to access the specified API endpoint. * `404`: The requested resource does not exist. * `405`: Method not allowed. Please check the `Allow` header for allowed HTTP methods. @@ -319,6 +600,10 @@ Error Handling * `500`: Internal server error. This could be caused by internal program errors. +Versioning +---------- + + Documentation ------------- diff --git a/framework/rest/ActiveController.php b/framework/rest/ActiveController.php index a29def2..75a4f55 100644 --- a/framework/rest/ActiveController.php +++ b/framework/rest/ActiveController.php @@ -9,7 +9,6 @@ namespace yii\rest; use yii\base\InvalidConfigException; use yii\base\Model; -use yii\web\ForbiddenHttpException; /** * ActiveController implements a common set of actions for supporting RESTful access to ActiveRecord. @@ -124,20 +123,4 @@ class ActiveController extends Controller 'delete' => ['DELETE'], ]; } - - /** - * Checks the privilege of the current user. - * - * This method should be overridden to check whether the current user has the privilege - * to run the specified action against the specified data model. - * If the user does not have access, a [[ForbiddenHttpException]] should be thrown. - * - * @param \yii\base\Action $action the action to be executed - * @param \yii\base\Model $model the model to be accessed. If null, it means no specific model is being accessed. - * @param array $params additional parameters - * @throws ForbiddenHttpException if the user does not have access - */ - public function checkAccess($action, $model = null, $params = []) - { - } } diff --git a/framework/rest/Controller.php b/framework/rest/Controller.php index cbeb98f..7900b15 100644 --- a/framework/rest/Controller.php +++ b/framework/rest/Controller.php @@ -14,6 +14,7 @@ use yii\web\UnauthorizedHttpException; use yii\web\UnsupportedMediaTypeHttpException; use yii\web\TooManyRequestsHttpException; use yii\web\VerbFilter; +use yii\web\ForbiddenHttpException; /** * Controller is the base class for RESTful API controller classes. @@ -227,4 +228,20 @@ class Controller extends \yii\web\Controller { return Yii::createObject($this->serializer)->serialize($data); } + + /** + * Checks the privilege of the current user. + * + * This method should be overridden to check whether the current user has the privilege + * to run the specified action against the specified data model. + * If the user does not have access, a [[ForbiddenHttpException]] should be thrown. + * + * @param string $action the ID of the action to be executed + * @param object $model the model to be accessed. If null, it means no specific model is being accessed. + * @param array $params additional parameters + * @throws ForbiddenHttpException if the user does not have access + */ + public function checkAccess($action, $model = null, $params = []) + { + } } diff --git a/framework/rest/CreateAction.php b/framework/rest/CreateAction.php index ba9701f..fa818c2 100644 --- a/framework/rest/CreateAction.php +++ b/framework/rest/CreateAction.php @@ -41,7 +41,7 @@ class CreateAction extends Action public function run() { if ($this->checkAccess) { - call_user_func($this->checkAccess, $this); + call_user_func($this->checkAccess, $this->id); } /** diff --git a/framework/rest/DeleteAction.php b/framework/rest/DeleteAction.php index 4b71aaf..a0355c8 100644 --- a/framework/rest/DeleteAction.php +++ b/framework/rest/DeleteAction.php @@ -32,7 +32,7 @@ class DeleteAction extends Action $model = $this->findModel($id); if ($this->checkAccess) { - call_user_func($this->checkAccess, $this, $model); + call_user_func($this->checkAccess, $this->id, $model); } if ($this->transactional && $model instanceof ActiveRecord) { diff --git a/framework/rest/IndexAction.php b/framework/rest/IndexAction.php index 3984e0a..ca30220 100644 --- a/framework/rest/IndexAction.php +++ b/framework/rest/IndexAction.php @@ -38,7 +38,7 @@ class IndexAction extends Action public function run() { if ($this->checkAccess) { - call_user_func($this->checkAccess, $this); + call_user_func($this->checkAccess, $this->id); } return $this->prepareDataProvider(); diff --git a/framework/rest/UpdateAction.php b/framework/rest/UpdateAction.php index b5f2000..7a14a0a 100644 --- a/framework/rest/UpdateAction.php +++ b/framework/rest/UpdateAction.php @@ -41,7 +41,7 @@ class UpdateAction extends Action $model = $this->findModel($id); if ($this->checkAccess) { - call_user_func($this->checkAccess, $this, $model); + call_user_func($this->checkAccess, $this->id, $model); } $model->scenario = $this->scenario; diff --git a/framework/rest/UrlRule.php b/framework/rest/UrlRule.php index 9f86bb2..a78e90c 100644 --- a/framework/rest/UrlRule.php +++ b/framework/rest/UrlRule.php @@ -93,6 +93,12 @@ class UrlRule extends CompositeUrlRule */ public $except = []; /** + * @var array patterns for supporting extra actions in addition to those listed in [[patterns]]. + * The keys are the patterns and the values are the corresponding action IDs. + * These extra patterns will take precedence over [[patterns]]. + */ + public $extra = []; + /** * @var array list of tokens that should be replaced for each pattern. The keys are the token names, * and the values are the corresponding replacements. * @see patterns @@ -117,9 +123,19 @@ class UrlRule extends CompositeUrlRule '{id}' => 'options', '' => 'options', ]; + /** + * @var array the default configuration for creating each URL rule contained by this rule. + */ public $ruleConfig = [ 'class' => 'yii\web\UrlRule', ]; + /** + * @var boolean whether to automatically pluralize the URL names for controllers. + * If true, a controller ID will appear in plural form in URLs. For example, `user` controller + * will appear as `users` in URLs. + * @see controllers + */ + public $pluralize = true; /** @@ -134,7 +150,7 @@ class UrlRule extends CompositeUrlRule $controllers = []; foreach ((array)$this->controller as $urlName => $controller) { if (is_integer($urlName)) { - $urlName = Inflector::pluralize($controller); + $urlName = $this->pluralize ? Inflector::pluralize($controller) : $controller; } $controllers[$urlName] = $controller; } @@ -152,10 +168,11 @@ class UrlRule extends CompositeUrlRule { $only = array_flip($this->only); $except = array_flip($this->except); + $patterns = array_merge($this->patterns, $this->extra); $rules = []; foreach ($this->controller as $urlName => $controller) { $prefix = trim($this->prefix . '/' . $urlName, '/'); - foreach ($this->patterns as $pattern => $action) { + foreach ($patterns as $pattern => $action) { if (!isset($except[$action]) && (empty($only) || isset($only[$action]))) { $rules[$urlName][] = $this->createRule($pattern, $prefix, $controller . '/' . $action); } diff --git a/framework/rest/ViewAction.php b/framework/rest/ViewAction.php index 8fb03c5..c37522f 100644 --- a/framework/rest/ViewAction.php +++ b/framework/rest/ViewAction.php @@ -26,7 +26,7 @@ class ViewAction extends Action { $model = $this->findModel($id); if ($this->checkAccess) { - call_user_func($this->checkAccess, $this, $model); + call_user_func($this->checkAccess, $this->id, $model); } return $model; }