@ -0,0 +1,30 @@
|
||||
|
||||
|
||||
|
||||
Fragment Caching |
||||
---------------- |
||||
|
||||
TBD: http://www.yiiframework.com/doc/guide/1.1/en/caching.fragment |
||||
|
||||
### Caching Options |
||||
|
||||
TBD: http://www.yiiframework.com/doc/guide/1.1/en/caching.fragment#caching-options |
||||
|
||||
### Nested Caching |
||||
|
||||
TBD: http://www.yiiframework.com/doc/guide/1.1/en/caching.fragment#nested-caching |
||||
|
||||
Dynamic Content |
||||
--------------- |
||||
|
||||
TBD: http://www.yiiframework.com/doc/guide/1.1/en/caching.dynamic |
||||
|
||||
Page Caching |
||||
------------ |
||||
|
||||
TBD: http://www.yiiframework.com/doc/guide/1.1/en/caching.page |
||||
|
||||
### Output Caching |
||||
|
||||
TBD: http://www.yiiframework.com/doc/guide/1.1/en/caching.page#output-caching |
||||
|
@ -0,0 +1,364 @@
|
||||
Data Caching |
||||
============ |
||||
|
||||
Data caching is about storing some PHP variable in cache and retrieving it later from cache. For this purpose, |
||||
the cache component base class [[yii\caching\Cache]] provides two methods that are used most of the time: |
||||
[[yii\caching\Cache::set()|set()]] and [[yii\caching\Cache::get()|get()]]. Note, only serializable variables and objects could be cached successfully. |
||||
|
||||
To store a variable `$value` in cache, we choose a unique `$key` and call [[yii\caching\Cache::set()|set()]] to store it: |
||||
|
||||
```php |
||||
Yii::$app->cache->set($key, $value); |
||||
``` |
||||
|
||||
The cached data will remain in the cache forever unless it is removed because of some caching policy |
||||
(e.g. caching space is full and the oldest data are removed). To change this behavior, we can also supply |
||||
an expiration parameter when calling [[yii\caching\Cache::set()|set()]] so that the data will be removed from the cache after |
||||
a certain period of time: |
||||
|
||||
```php |
||||
// keep the value in cache for at most 45 seconds |
||||
Yii::$app->cache->set($key, $value, 45); |
||||
``` |
||||
|
||||
Later when we need to access this variable (in either the same or a different web request), we call [[yii\caching\Cache::get()|get()]] |
||||
with the key to retrieve it from cache. If the value returned is `false`, it means the value is not available |
||||
in cache and we should regenerate it: |
||||
|
||||
```php |
||||
public function getCachedData() |
||||
{ |
||||
$key = /* generate unique key here */; |
||||
$value = Yii::$app->cache->get($key); |
||||
if ($value === false) { |
||||
$value = /* regenerate value because it is not found in cache and then save it in cache for later use */; |
||||
Yii::$app->cache->set($key, $value); |
||||
} |
||||
return $value; |
||||
} |
||||
``` |
||||
|
||||
This is the common pattern of arbitrary data caching for general use. |
||||
|
||||
When choosing the key for a variable to be cached, make sure the key is unique among all other variables that |
||||
may be cached in the application. It is **NOT** required that the key is unique across applications because |
||||
the cache component is intelligent enough to differentiate keys for different applications. |
||||
|
||||
Some cache storages, such as MemCache, APC, support retrieving multiple cached values in a batch mode, |
||||
which may reduce the overhead involved in retrieving cached data. A method named [[yii\caching\Cache::mget()|mget()]] is provided |
||||
to exploit this feature. In case the underlying cache storage does not support this feature, |
||||
[[yii\caching\Cache::mget()|mget()]] will still simulate it. |
||||
|
||||
To remove a cached value from cache, call [[yii\caching\Cache::delete()|delete()]]; and to remove everything from cache, call |
||||
[[yii\caching\Cache::flush()|flush()]]. |
||||
Be very careful when calling [[yii\caching\Cache::flush()|flush()]] because it also removes cached data that are from |
||||
other applications if the cache is shared among different applications. |
||||
|
||||
Note, because [[yii\caching\Cache]] implements `ArrayAccess`, a cache component can be used liked an array. The followings |
||||
are some examples: |
||||
|
||||
```php |
||||
$cache = Yii::$app->cache; |
||||
$cache['var1'] = $value1; // equivalent to: $cache->set('var1', $value1); |
||||
$value2 = $cache['var2']; // equivalent to: $value2 = $cache->get('var2'); |
||||
``` |
||||
|
||||
### Cache Dependency |
||||
|
||||
Besides expiration setting, cached data may also be invalidated according to some dependency changes. For example, if we |
||||
are caching the content of some file and the file is changed, we should invalidate the cached copy and read the latest |
||||
content from the file instead of the cache. |
||||
|
||||
We represent a dependency as an instance of [[yii\caching\Dependency]] or its child class. We pass the dependency |
||||
instance along with the data to be cached when calling [[yii\caching\Cache::set()|set()]]. |
||||
|
||||
```php |
||||
use yii\caching\FileDependency; |
||||
|
||||
// the value will expire in 30 seconds |
||||
// it may also be invalidated earlier if the dependent file is changed |
||||
Yii::$app->cache->set($id, $value, 30, new FileDependency(['fileName' => 'example.txt'])); |
||||
``` |
||||
|
||||
Now if we retrieve $value from cache by calling `get()`, the dependency will be evaluated and if it is changed, we will |
||||
get a false value, indicating the data needs to be regenerated. |
||||
|
||||
Below is a summary of the available cache dependencies: |
||||
|
||||
- [[yii\caching\FileDependency]]: the dependency is changed if the file's last modification time is changed. |
||||
- [[yii\caching\GroupDependency]]: marks a cached data item with a group name. You may invalidate the cached data items |
||||
with the same group name all at once by calling [[yii\caching\GroupDependency::invalidate()]]. |
||||
- [[yii\caching\DbDependency]]: the dependency is changed if the query result of the specified SQL statement is changed. |
||||
- [[yii\caching\ChainedDependency]]: the dependency is changed if any of the dependencies on the chain is changed. |
||||
- [[yii\caching\ExpressionDependency]]: the dependency is changed if the result of the specified PHP expression is |
||||
changed. |
||||
|
||||
### Query Caching |
||||
|
||||
For caching the result of database queries you can wrap them in calls to [[yii\db\Connection::beginCache()]] |
||||
and [[yii\db\Connection::endCache()]]: |
||||
|
||||
```php |
||||
$connection->beginCache(60); // cache all query results for 60 seconds. |
||||
// your db query code here... |
||||
$connection->endCache(); |
||||
``` |
||||
|
||||
|
||||
Data Caching |
||||
============ |
||||
|
||||
Data caching is about storing some PHP variable in cache and retrieving it |
||||
later from cache. For this purpose, the cache component base class [CCache] |
||||
provides two methods that are used most of the time: [set()|CCache::set] |
||||
and [get()|CCache::get]. |
||||
|
||||
To store a variable `$value` in cache, we choose a unique ID and call |
||||
[set()|CCache::set] to store it: |
||||
|
||||
~~~ |
||||
[php] |
||||
Yii::app()->cache->set($id, $value); |
||||
~~~ |
||||
|
||||
The cached data will remain in the cache forever unless it is removed |
||||
because of some caching policy (e.g. caching space is full and the oldest |
||||
data are removed). To change this behavior, we can also supply an |
||||
expiration parameter when calling [set()|CCache::set] so that the data will |
||||
be removed from the cache after, at most, that period of time: |
||||
|
||||
~~~ |
||||
[php] |
||||
// keep the value in cache for at most 30 seconds |
||||
Yii::app()->cache->set($id, $value, 30); |
||||
~~~ |
||||
|
||||
Later when we need to access this variable (in either the same or a |
||||
different Web request), we call [get()|CCache::get] with the ID to retrieve |
||||
it from cache. If the returned value is false, it means the value is not |
||||
available in cache and we have to regenerate it. |
||||
|
||||
~~~ |
||||
[php] |
||||
$value=Yii::app()->cache->get($id); |
||||
if($value===false) |
||||
{ |
||||
// regenerate $value because it is not found in cache |
||||
// and save it in cache for later use: |
||||
// Yii::app()->cache->set($id,$value); |
||||
} |
||||
~~~ |
||||
|
||||
When choosing the ID for a variable to be cached, make sure the ID is |
||||
unique among all other variables that may be cached in the application. It |
||||
is NOT required that the ID is unique across applications because the cache |
||||
component is intelligent enough to differentiate IDs for different |
||||
applications. |
||||
|
||||
Some cache storages, such as MemCache, APC, support retrieving |
||||
multiple cached values in a batch mode, which may reduce the overhead involved |
||||
in retrieving cached data. A method named |
||||
[mget()|CCache::mget] is provided to achieve this feature. In case the underlying |
||||
cache storage does not support this feature, [mget()|CCache::mget] will still |
||||
simulate it. |
||||
|
||||
To remove a cached value from cache, call [delete()|CCache::delete]; and |
||||
to remove everything from cache, call [flush()|CCache::flush]. Be very |
||||
careful when calling [flush()|CCache::flush] because it also removes cached |
||||
data that are from other applications. |
||||
|
||||
> Tip: Because [CCache] implements `ArrayAccess`, a cache component can be |
||||
> used liked an array. The followings are some examples: |
||||
> ~~~ |
||||
> [php] |
||||
> $cache=Yii::app()->cache; |
||||
> $cache['var1']=$value1; // equivalent to: $cache->set('var1',$value1); |
||||
> $value2=$cache['var2']; // equivalent to: $value2=$cache->get('var2'); |
||||
> ~~~ |
||||
|
||||
Cache Dependency |
||||
---------------- |
||||
|
||||
Besides expiration setting, cached data may also be invalidated according |
||||
to some dependency changes. For example, if we are caching the content of |
||||
some file and the file is changed, we should invalidate the cached copy and |
||||
read the latest content from the file instead of the cache. |
||||
|
||||
We represent a dependency as an instance of [CCacheDependency] or its |
||||
child class. We pass the dependency instance along with the data to be |
||||
cached when calling [set()|CCache::set]. |
||||
|
||||
~~~ |
||||
[php] |
||||
// the value will expire in 30 seconds |
||||
// it may also be invalidated earlier if the dependent file is changed |
||||
Yii::app()->cache->set($id, $value, 30, new CFileCacheDependency('FileName')); |
||||
~~~ |
||||
|
||||
Now if we retrieve `$value` from cache by calling [get()|CCache::get], the |
||||
dependency will be evaluated and if it is changed, we will get a false |
||||
value, indicating the data needs to be regenerated. |
||||
|
||||
Below is a summary of the available cache dependencies: |
||||
|
||||
- [CFileCacheDependency]: the dependency is changed if the file's last |
||||
modification time is changed. |
||||
|
||||
- [CDirectoryCacheDependency]: the dependency is changed if any of the |
||||
files under the directory and its subdirectories is changed. |
||||
|
||||
- [CDbCacheDependency]: the dependency is changed if the query result |
||||
of the specified SQL statement is changed. |
||||
|
||||
- [CGlobalStateCacheDependency]: the dependency is changed if the value |
||||
of the specified global state is changed. A global state is a variable that |
||||
is persistent across multiple requests and multiple sessions in an |
||||
application. It is defined via [CApplication::setGlobalState()]. |
||||
|
||||
- [CChainedCacheDependency]: the dependency is changed if any of the |
||||
dependencies on the chain is changed. |
||||
|
||||
- [CExpressionDependency]: the dependency is changed if the result of |
||||
the specified PHP expression is changed. |
||||
|
||||
|
||||
Query Caching |
||||
------------- |
||||
|
||||
Since version 1.1.7, Yii has added support for query caching. |
||||
Built on top of data caching, query caching stores the result of a DB query |
||||
in cache and may thus save the DB query execution time if the same query is requested |
||||
in future, as the result can be directly served from the cache. |
||||
|
||||
> Info: Some DBMS (e.g. [MySQL](http://dev.mysql.com/doc/refman/5.1/en/query-cache.html)) |
||||
> also support query caching on the DB server side. Compared with the server-side |
||||
> query caching, the same feature we support here offers more flexibility and |
||||
> may be potentially more efficient. |
||||
|
||||
|
||||
### Enabling Query Caching |
||||
|
||||
To enable query caching, make sure [CDbConnection::queryCacheID] refers to the ID of a valid |
||||
cache application component (it defaults to `cache`). |
||||
|
||||
|
||||
### Using Query Caching with DAO |
||||
|
||||
To use query caching, we call the [CDbConnection::cache()] method when we perform DB queries. |
||||
The following is an example: |
||||
|
||||
~~~ |
||||
[php] |
||||
$sql = 'SELECT * FROM tbl_post LIMIT 20'; |
||||
$dependency = new CDbCacheDependency('SELECT MAX(update_time) FROM tbl_post'); |
||||
$rows = Yii::app()->db->cache(1000, $dependency)->createCommand($sql)->queryAll(); |
||||
~~~ |
||||
|
||||
When running the above statements, Yii will first check if the cache contains a valid |
||||
result for the SQL statement to be executed. This is done by checking the following three conditions: |
||||
|
||||
- if the cache contains an entry indexed by the SQL statement. |
||||
- if the entry is not expired (less than 1000 seconds since it was first saved in the cache). |
||||
- if the dependency has not changed (the maximum `update_time` value is the same as when |
||||
the query result was saved in the cache). |
||||
|
||||
If all of the above conditions are satisfied, the cached result will be returned directly from the cache. |
||||
Otherwise, the SQL statement will be sent to the DB server for execution, and the corresponding |
||||
result will be saved in the cache and returned. |
||||
|
||||
|
||||
### Using Query Caching with ActiveRecord |
||||
|
||||
Query caching can also be used with [Active Record](/doc/guide/database.ar). |
||||
To do so, we call a similar [CActiveRecord::cache()] method like the following: |
||||
|
||||
~~~ |
||||
[php] |
||||
$dependency = new CDbCacheDependency('SELECT MAX(update_time) FROM tbl_post'); |
||||
$posts = Post::model()->cache(1000, $dependency)->findAll(); |
||||
// relational AR query |
||||
$posts = Post::model()->cache(1000, $dependency)->with('author')->findAll(); |
||||
~~~ |
||||
|
||||
The `cache()` method here is essentially a shortcut to [CDbConnection::cache()]. |
||||
Internally, when executing the SQL statement generated by ActiveRecord, Yii will |
||||
attempt to use query caching as we described in the last subsection. |
||||
|
||||
|
||||
### Caching Multiple Queries |
||||
|
||||
By default, each time we call the `cache()` method (of either [CDbConnection] or [CActiveRecord]), |
||||
it will mark the next SQL query to be cached. Any other SQL queries will NOT be cached |
||||
unless we call `cache()` again. For example, |
||||
|
||||
~~~ |
||||
[php] |
||||
$sql = 'SELECT * FROM tbl_post LIMIT 20'; |
||||
$dependency = new CDbCacheDependency('SELECT MAX(update_time) FROM tbl_post'); |
||||
|
||||
$rows = Yii::app()->db->cache(1000, $dependency)->createCommand($sql)->queryAll(); |
||||
// query caching will NOT be used |
||||
$rows = Yii::app()->db->createCommand($sql)->queryAll(); |
||||
~~~ |
||||
|
||||
By supplying an extra `$queryCount` parameter to the `cache()` method, we can enforce |
||||
multiple queries to use query caching. In the following example, when we call `cache()`, |
||||
we specify that query caching should be used for the next 2 queries: |
||||
|
||||
~~~ |
||||
[php] |
||||
// ... |
||||
$rows = Yii::app()->db->cache(1000, $dependency, 2)->createCommand($sql)->queryAll(); |
||||
// query caching WILL be used |
||||
$rows = Yii::app()->db->createCommand($sql)->queryAll(); |
||||
~~~ |
||||
|
||||
As we know, when performing a relational AR query, it is possible several SQL queries will |
||||
be executed (by checking the [log messages](/doc/guide/topics.logging)). |
||||
For example, if the relationship between `Post` and `Comment` is `HAS_MANY`, |
||||
then the following code will actually execute two DB queries: |
||||
|
||||
- it first selects the posts limited by 20; |
||||
- it then selects the comments for the previously selected posts. |
||||
|
||||
~~~ |
||||
[php] |
||||
$posts = Post::model()->with('comments')->findAll(array( |
||||
'limit'=>20, |
||||
)); |
||||
~~~ |
||||
|
||||
If we use query caching as follows, only the first DB query will be cached: |
||||
|
||||
~~~ |
||||
[php] |
||||
$posts = Post::model()->cache(1000, $dependency)->with('comments')->findAll(array( |
||||
'limit'=>20, |
||||
)); |
||||
~~~ |
||||
|
||||
In order to cache both DB queries, we need supply the extra parameter indicating how |
||||
many DB queries we want to cache next: |
||||
|
||||
~~~ |
||||
[php] |
||||
$posts = Post::model()->cache(1000, $dependency, 2)->with('comments')->findAll(array( |
||||
'limit'=>20, |
||||
)); |
||||
~~~ |
||||
|
||||
|
||||
### Limitations |
||||
|
||||
Query caching does not work with query results that contain resource handles. For example, |
||||
when using the `BLOB` column type in some DBMS, the query result will return a resource |
||||
handle for the column data. |
||||
|
||||
Some caching storage has size limitation. For example, memcache limits the maximum size |
||||
of each entry to be 1MB. Therefore, if the size of a query result exceeds this limit, |
||||
the caching will fail. |
||||
|
||||
|
||||
Note, by definition, cache is a volatile storage medium. It does not ensure the existence of the cached |
||||
data even if it does not expire. Therefore, do not use cache as a persistent storage (e.g. do not use cache |
||||
to store session data or other valuable information). |
@ -0,0 +1,3 @@
|
||||
### HTTP Caching |
||||
|
||||
TBD: http://www.yiiframework.com/doc/guide/1.1/en/caching.page#http-caching |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 77 KiB |
After Width: | Height: | Size: 47 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 84 KiB |
After Width: | Height: | Size: 53 KiB |
After Width: | Height: | Size: 94 KiB |
After Width: | Height: | Size: 102 KiB |
After Width: | Height: | Size: 92 KiB |
After Width: | Height: | Size: 89 KiB |
After Width: | Height: | Size: 32 KiB |
@ -0,0 +1,109 @@
|
||||
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. You can also easily create new authentication methods. |
||||
|
||||
To enable authentication for your APIs, do the following two steps: |
||||
|
||||
1. Specify which authentication methods you plan to use by configuring the `authenticator` behavior |
||||
in your REST controller classes. |
||||
2. Implement [[yii\web\IdentityInterface::findIdentityByAccessToken()]] in your [[yii\web\User::identityClass|user identity class]]. |
||||
|
||||
|
||||
For example, to use HTTP Basic Auth, you may configure `authenticator` as follows, |
||||
|
||||
```php |
||||
use yii\filters\auth\HttpBasicAuth; |
||||
|
||||
public function behaviors() |
||||
{ |
||||
$behaviors = parent::behaviors(); |
||||
$behaviors['authenticator'] = [ |
||||
'class' => HttpBasicAuth::className(), |
||||
]; |
||||
return $behaviors; |
||||
} |
||||
``` |
||||
|
||||
If you want to support all three authentication methods explained above, you can use `CompositeAuth` like the following, |
||||
|
||||
```php |
||||
use yii\filters\auth\CompositeAuth; |
||||
use yii\filters\auth\HttpBasicAuth; |
||||
use yii\filters\auth\HttpBearerAuth; |
||||
use yii\filters\auth\QueryParamAuth; |
||||
|
||||
public function behaviors() |
||||
{ |
||||
$behaviors = parent::behaviors(); |
||||
$behaviors['authenticator'] = [ |
||||
'class' => CompositeAuth::className(), |
||||
'authMethods' => [ |
||||
HttpBasicAuth::className(), |
||||
HttpBearerAuth::className(), |
||||
QueryParamAuth::className(), |
||||
], |
||||
]; |
||||
return $behaviors; |
||||
} |
||||
``` |
||||
|
||||
Each element in `authMethods` should be an auth method class name or a configuration array. |
||||
|
||||
|
||||
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, $type = null) |
||||
{ |
||||
return static::findOne(['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 <a name="authorization"></a> |
||||
|
||||
After a user is authenticated, you probably want to check if he or she 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 section](authorization.md). |
||||
|
||||
If your controllers extend from [[yii\rest\ActiveController]], you may override |
||||
the [[yii\rest\Controller::checkAccess()|checkAccess()]] method to perform authorization check. The method |
||||
will be called by the built-in actions provided by [[yii\rest\ActiveController]]. |
@ -0,0 +1,152 @@
|
||||
Controllers |
||||
=========== |
||||
|
||||
After creating the resource classes and specifying how resource data should be formatted, the next thing |
||||
to do is to create controller actions to expose the resources to end users through RESTful APIs. |
||||
|
||||
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 specifically designed to deal with |
||||
resources represented as [Active Record](db-active-record.md). So if you are using [Active Record](db-active-record.md) |
||||
and are comfortable with the provided built-in actions, you may consider extending your controller classes |
||||
from [[yii\rest\ActiveController]], which will allow you to create powerful RESTful APIs with minimal code. |
||||
|
||||
Both [[yii\rest\Controller]] and [[yii\rest\ActiveController]] provide the following features, some of which |
||||
will be described in detail in the next few sections: |
||||
|
||||
* HTTP method validation; |
||||
* [Content negotiation and Data formatting](rest-response-formatting.md); |
||||
* [Authentication](rest-authentication.md); |
||||
* [Rate limiting](rest-rate-limiting.md). |
||||
|
||||
[[yii\rest\ActiveController]] in addition provides the following features: |
||||
|
||||
* A set of commonly needed actions: `index`, `view`, `create`, `update`, `delete`, `options`; |
||||
* User authorization in regarding to the requested action and resource. |
||||
|
||||
|
||||
## Creating Controller Classes <a name="creating-controller"></a> |
||||
|
||||
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 actionView($id) |
||||
{ |
||||
return User::findOne($id); |
||||
} |
||||
``` |
||||
|
||||
|
||||
## Filters <a name="filters"></a> |
||||
|
||||
Most RESTful API features provided by [[yii\rest\Controller]] are implemented in terms of [filters](runtime-filtering.md). |
||||
In particular, the following filters will be executed in the order they are listed: |
||||
|
||||
* [[yii\filters\ContentNegotiator|contentNegotiator]]: supports content negotiation, to be explained in |
||||
the [Response Formatting](rest-response-formatting.md) section; |
||||
* [[yii\filters\VerbFilter|verbFilter]]: supports HTTP method validation; |
||||
* [[yii\filters\AuthMethod|authenticator]]: supports user authentication, to be explained in |
||||
the [Authentication](rest-authentication.md) section; |
||||
* [[yii\filters\RateLimiter|rateLimiter]]: supports rate limiting, to be explained in |
||||
the [Rate Limiting](rest-rate-limiting.md) section. |
||||
|
||||
These named filters are declared in the [[yii\rest\Controller::behaviors()|behaviors()]] method. |
||||
You may override this method to configure individual filters, disable some of them, or add your own filters. |
||||
For example, if you only want to use HTTP basic authentication, you may write the following code: |
||||
|
||||
```php |
||||
use yii\filters\auth\HttpBasicAuth; |
||||
|
||||
public function behaviors() |
||||
{ |
||||
$behaviors = parent::behaviors(); |
||||
$behaviors['authenticator'] = [ |
||||
'class' => HttpBasicAuth::className(), |
||||
]; |
||||
return $behaviors; |
||||
} |
||||
``` |
||||
|
||||
|
||||
## Extending `ActiveController` <a name="extending-active-controller"></a> |
||||
|
||||
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 extend from [[yii\db\ActiveRecord]]. |
||||
|
||||
|
||||
### Customizing Actions <a name="customizing-actions"></a> |
||||
|
||||
By default, [[yii\rest\ActiveController]] provides the following actions: |
||||
|
||||
* [[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. |
||||
|
||||
All these actions are declared through the [[yii\rest\ActiveController::actions()|actions()]] method. |
||||
You may configure these actions or disable some of them by overriding the `actions()` method, like shown 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 |
||||
} |
||||
``` |
||||
|
||||
Please refer to the class references for individual action classes to learn what configuration options are available. |
||||
|
||||
|
||||
### Performing Access Check <a name="performing-access-check"></a> |
||||
|
||||
When exposing resources through RESTful APIs, you often need to check if the current user has the permission |
||||
to access and manipulate the requested resource(s). With [[yii\rest\ActiveController]], this can be done |
||||
by overriding the [[yii\rest\ActiveController::checkAccess()|checkAccess()]] method like the following, |
||||
|
||||
```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 = []) |
||||
{ |
||||
// check if the user can access $action and $model |
||||
// throw ForbiddenHttpException if access should be denied |
||||
} |
||||
``` |
||||
|
||||
The `checkAccess()` method will be called by the default actions of [[yii\rest\ActiveController]]. If you create |
||||
new actions and also want to perform access check, you should call this method explicitly in the new actions. |
||||
|
||||
> Tip: You may implement `checkAccess()` by using the [Role-Based Access Control (RBAC) component](security-authorization.md). |
@ -0,0 +1,44 @@
|
||||
Error Handling |
||||
============== |
||||
|
||||
When handling a RESTful API request, if there is an error in the user request or if something unexpected |
||||
happens on the server, you may simply throw an exception to notify the user that something wrong has happened. |
||||
If you can identify the cause of the error (e.g. the requested resource does not exist), you should |
||||
consider throwing an exception with a proper HTTP status code (e.g. [[yii\web\NotFoundHttpException]] |
||||
representing a 404 HTTP status code). Yii will send the response with the corresponding HTTP status |
||||
code and text. It will also include in the response body the serialized representation of the |
||||
exception. For example, |
||||
|
||||
``` |
||||
HTTP/1.1 404 Not Found |
||||
Date: Sun, 02 Mar 2014 05:31:43 GMT |
||||
Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y |
||||
Transfer-Encoding: chunked |
||||
Content-Type: application/json; charset=UTF-8 |
||||
|
||||
{ |
||||
"type": "yii\\web\\NotFoundHttpException", |
||||
"name": "Not Found Exception", |
||||
"message": "The requested resource was not found.", |
||||
"code": 0, |
||||
"status": 404 |
||||
} |
||||
``` |
||||
|
||||
The following list summarizes the HTTP status code that are used by the Yii REST framework: |
||||
|
||||
* `200`: OK. Everything worked as expected. |
||||
* `201`: A resource was successfully created in response to a `POST` request. The `Location` header |
||||
contains the URL pointing to the newly created resource. |
||||
* `204`: The request is handled successfully and the response contains no body content (like a `DELETE` request). |
||||
* `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`: 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. |
||||
* `415`: Unsupported media type. The requested content type or version number is invalid. |
||||
* `422`: Data validation failed (in response to a `POST` request, for example). Please check the response body for detailed error messages. |
||||
* `429`: Too many requests. The request is rejected due to rate limiting. |
||||
* `500`: Internal server error. This could be caused by internal program errors. |
@ -0,0 +1,44 @@
|
||||
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\filters\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 [[yii\filters\RateLimiter]] |
||||
configured as an action filter for [[yii\rest\Controller]] to perform rate limiting check. The rate limiter |
||||
will thrown a [[yii\web\TooManyRequestsHttpException]] if rate limit is exceeded. You may configure the rate limiter |
||||
as follows in your REST controller classes, |
||||
|
||||
```php |
||||
public function behaviors() |
||||
{ |
||||
$behaviors = parent::behaviors(); |
||||
$behaviors['rateLimiter']['enableRateLimitHeaders'] = false; |
||||
return $behaviors; |
||||
} |
||||
``` |
||||
|
||||
When rate limiting is enabled, by default 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. |
||||
|
||||
You may disable these headers by configuring [[yii\filters\RateLimiter::enableRateLimitHeaders]] to be false, |
||||
like shown in the above code example. |
@ -0,0 +1,218 @@
|
||||
Resources |
||||
========= |
||||
|
||||
RESTful APIs are all about accessing and manipulating *resources*. You may view resources as |
||||
[models](structure-models.md) in the MVC paradigm. |
||||
|
||||
While there is no restriction in how to represent a resource, in Yii you usually would represent resources |
||||
in terms of objects of [[yii\base\Model]] or its child classes (e.g. [[yii\db\ActiveRecord]]), for the |
||||
following reasons: |
||||
|
||||
* [[yii\base\Model]] implements the [[yii\base\Arrayable]] interface, which allows you to |
||||
customize how you want to expose resource data through RESTful APIs. |
||||
* [[yii\base\Model]] supports [input validation](input-validation.md), which is useful if your RESTful APIs |
||||
need to support data input. |
||||
* [[yii\db\ActiveRecord]] provides powerful DB data access and manipulation support, which makes it |
||||
a perfect fit if your resource data is stored in databases. |
||||
|
||||
In this section, we will mainly describe how a resource class extending from [[yii\base\Model]] (or its child classes) |
||||
can specify what data may be returned via RESTful APIs. If the resource class does not extend from [[yii\base\Model]], |
||||
then all its public member variables will be returned. |
||||
|
||||
|
||||
## Fields <a name="fields"></a> |
||||
|
||||
When including a resource in a RESTful API response, the resource needs to be serialized into a string. |
||||
Yii breaks this process into two steps. First, the resource is converted into an array by [[yii\rest\Serializer]]. |
||||
Second, the array is serialized into a string in a requested format (e.g. JSON, XML) by |
||||
[[yii\web\ResponseFormatterInterface|response formatters]]. The first step is what you should mainly focus when |
||||
developing a resource class. |
||||
|
||||
By overriding [[yii\base\Model::fields()|fields()]] and/or [[yii\base\Model::extraFields()|extraFields()]], |
||||
you may specify what data, called *fields*, in the resource can be put into its array representation. |
||||
The difference between these two methods is that the former specifies the default set of fields which should |
||||
be included in the array representation, while the latter specifies additional fields which may be included |
||||
in the array if an end user requests for them via the `expand` query parameter. For example, |
||||
|
||||
``` |
||||
// returns all fields as declared in fields() |
||||
http://localhost/users |
||||
|
||||
// only returns field id and email, provided they are declared in fields() |
||||
http://localhost/users?fields=id,email |
||||
|
||||
// returns all fields in fields() and field profile if it is in extraFields() |
||||
http://localhost/users?expand=profile |
||||
|
||||
// only returns field id, email and profile, provided they are in fields() and extraFields() |
||||
http://localhost/users?fields=id,email&expand=profile |
||||
``` |
||||
|
||||
|
||||
### Overriding `fields()` <a name="overriding-fields"></a> |
||||
|
||||
By default, [[yii\base\Model::fields()]] returns all model attributes as fields, while |
||||
[[yii\db\ActiveRecord::fields()]] only returns the attributes which have been populated from DB. |
||||
|
||||
You can override `fields()` to add, remove, rename or redefine fields. The return value of `fields()` |
||||
should be an array. The array keys are the field names, and the array values are the corresponding |
||||
field definitions which can be either property/attribute names or anonymous functions returning the |
||||
corresponding field values. In the special case when a field name is the same as its defining attribute |
||||
name, you can omit the array key. For example, |
||||
|
||||
```php |
||||
// 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() |
||||
{ |
||||
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; |
||||
}, |
||||
]; |
||||
} |
||||
|
||||
// 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(); |
||||
|
||||
// remove fields that contain sensitive information |
||||
unset($fields['auth_key'], $fields['password_hash'], $fields['password_reset_token']); |
||||
|
||||
return $fields; |
||||
} |
||||
``` |
||||
|
||||
> Warning: Because by default all attributes of a model will be included in the API result, you should |
||||
> examine your data to make sure they do not contain sensitive information. If there is such information, |
||||
> you should override `fields()` to filter them out. In the above example, we choose |
||||
> to filter out `auth_key`, `password_hash` and `password_reset_token`. |
||||
|
||||
|
||||
### Overriding `extraFields()` <a name="overriding-extra-fields"></a> |
||||
|
||||
By default, [[yii\base\Model::extraFields()]] returns nothing, while [[yii\db\ActiveRecord::extraFields()]] |
||||
returns the names of the relations that have been populated from DB. |
||||
|
||||
The return data format of `extraFields()` is the same as that of `fields()`. Usually, `extraFields()` |
||||
is mainly used to specify fields whose values are objects. For example, given the following field |
||||
declaration, |
||||
|
||||
```php |
||||
public function fields() |
||||
{ |
||||
return ['id', 'email']; |
||||
} |
||||
|
||||
public function extraFields() |
||||
{ |
||||
return ['profile']; |
||||
} |
||||
``` |
||||
|
||||
the request with `http://localhost/users?fields=id,email&expand=profile` may return the following JSON data: |
||||
|
||||
```php |
||||
[ |
||||
{ |
||||
"id": 100, |
||||
"email": "100@example.com", |
||||
"profile": { |
||||
"id": 100, |
||||
"age": 30, |
||||
} |
||||
}, |
||||
... |
||||
] |
||||
``` |
||||
|
||||
|
||||
## Links <a name="links"></a> |
||||
|
||||
[HATEOAS](http://en.wikipedia.org/wiki/HATEOAS), an abbreviation for Hypermedia as the Engine of Application State, |
||||
promotes that RESTful APIs should return information that allow clients to discover actions supported for the returned |
||||
resources. The key of HATEOAS is to return a set of hyperlinks with relation information when resource data are served |
||||
by the APIs. |
||||
|
||||
Your resource classes may support HATEOAS by implementing the [[yii\web\Linkable]] interface. The interface |
||||
contains a single method [[yii\web\Linkable::getLinks()|getLinks()]] which should return a list of [[yii\web\Link|links]]. |
||||
Typically, you should return at least the `self` link representing the URL to the resource object itself. For example, |
||||
|
||||
```php |
||||
use yii\db\ActiveRecord; |
||||
use yii\web\Link; |
||||
use yii\web\Linkable; |
||||
use yii\helpers\Url; |
||||
|
||||
class User extends ActiveRecord implements Linkable |
||||
{ |
||||
public function getLinks() |
||||
{ |
||||
return [ |
||||
Link::REL_SELF => Url::to(['user', 'id' => $this->id], true), |
||||
]; |
||||
} |
||||
} |
||||
``` |
||||
|
||||
When a `User` object is returned in a response, it will contain a `_links` element representing the links related |
||||
to the user, for example, |
||||
|
||||
``` |
||||
{ |
||||
"id": 100, |
||||
"email": "user@example.com", |
||||
// ... |
||||
"_links" => [ |
||||
"self": "https://example.com/users/100" |
||||
] |
||||
} |
||||
``` |
||||
|
||||
|
||||
## Collections <a name="collections"></a> |
||||
|
||||
Resource objects can be grouped into *collections*. Each collection contains a list of resource objects |
||||
of the same type. |
||||
|
||||
While collections can be represented as arrays, it is usually more desirable to represent them |
||||
as [data providers](output-data-providers.md). This is because data providers support sorting and pagination |
||||
of resources, which is a commonly needed feature for RESTful APIs returning collections. For example, |
||||
the following action returns a data provider about the post resources: |
||||
|
||||
```php |
||||
namespace app\controllers; |
||||
|
||||
use yii\rest\Controller; |
||||
use yii\data\ActiveDataProvider; |
||||
use app\models\Post; |
||||
|
||||
class PostController extends Controller |
||||
{ |
||||
public function actionIndex() |
||||
{ |
||||
return new ActiveDataProvider([ |
||||
'query' => Post::find(), |
||||
]); |
||||
} |
||||
} |
||||
``` |
||||
|
||||
When a data provider is being sent in a RESTful API response, [[yii\rest\Serializer]] will take out the current |
||||
page of resources and serialize them as an array of resource objects. Additionally, [[yii\rest\Serializer]] |
||||
will also include the pagination information by the following HTTP headers: |
||||
|
||||
* `X-Pagination-Total-Count`: The total number of resources; |
||||
* `X-Pagination-Page-Count`: The number of pages; |
||||
* `X-Pagination-Current-Page`: The current page (1-based); |
||||
* `X-Pagination-Per-Page`: The number of resources in each page; |
||||
* `Link`: A set of navigational links allowing client to traverse the resources page by page. |
||||
|
||||
An example may be found in the [Quick Start](rest-quick-start.md#trying-it-out) section. |
@ -0,0 +1,150 @@
|
||||
Response Formatting |
||||
=================== |
||||
|
||||
When handling a RESTful API request, an application usually takes the following steps that are related |
||||
with response formatting: |
||||
|
||||
1. Determine various factors that may affect the response format, such as media type, language, version, etc. |
||||
This process is also known as [content negotiation](http://en.wikipedia.org/wiki/Content_negotiation). |
||||
2. Convert resource objects into arrays, as described in the [Resources](rest-resources.md) section. |
||||
This is done by [[yii\rest\Serializer]]. |
||||
3. Convert arrays into a string in the format as determined by the content negotiation step. This is |
||||
done by [[yii\web\ResponseFormatterInterface|response formatters]] registered with |
||||
the [[yii\web\Response::formatters|response]] application component. |
||||
|
||||
|
||||
## Content Negotiation <a name="content-negotiation"></a> |
||||
|
||||
Yii supports content negotiation via the [[yii\filters\ContentNegotiator]] filter. The the RESTful API base |
||||
controller class [[yii\rest\Controller]] is equipped with this filter under the name of `contentNegotiator`. |
||||
The filer provides response format negotiation as well as language negotiation. For example, if a RESTful |
||||
API request contains the following header, |
||||
|
||||
``` |
||||
Accept: application/json; q=1.0, */*; q=0.1 |
||||
``` |
||||
|
||||
it will get a response in JSON format, like the following: |
||||
|
||||
``` |
||||
$ curl -i -H "Accept: application/json; q=1.0, */*; q=0.1" "http://localhost/users" |
||||
|
||||
HTTP/1.1 200 OK |
||||
Date: Sun, 02 Mar 2014 05:31:43 GMT |
||||
Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y |
||||
X-Powered-By: PHP/5.4.20 |
||||
X-Pagination-Total-Count: 1000 |
||||
X-Pagination-Page-Count: 50 |
||||
X-Pagination-Current-Page: 1 |
||||
X-Pagination-Per-Page: 20 |
||||
Link: <http://localhost/users?page=1>; rel=self, |
||||
<http://localhost/users?page=2>; rel=next, |
||||
<http://localhost/users?page=50>; rel=last |
||||
Transfer-Encoding: chunked |
||||
Content-Type: application/json; charset=UTF-8 |
||||
|
||||
[ |
||||
{ |
||||
"id": 1, |
||||
... |
||||
}, |
||||
{ |
||||
"id": 2, |
||||
... |
||||
}, |
||||
... |
||||
] |
||||
``` |
||||
|
||||
Behind the scene, before a RESTful API controller action is executed, the [[yii\filters\ContentNegotiator]] |
||||
filter will check the `Accept` HTTP header in the request and set the [[yii\web\Response::format|response format]] |
||||
to be `'json'`. After the action is executed and returns the resulting resource object or collection, |
||||
[[yii\rest\Serializer]] will convert the result into an array. And finally, [[yii\web\JsonResponseFormatter]] |
||||
will serialize the array into a JSON string and include it in the response body. |
||||
|
||||
By default, RESTful APIs support both JSON and XML formats. To support a new format, you should configure |
||||
the [[yii\filters\ContentNegotiator::formats|formats]] property of the `contentNegotiator` filter like |
||||
the following in your API controller classes: |
||||
|
||||
```php |
||||
use yii\web\Response; |
||||
|
||||
public function behaviors() |
||||
{ |
||||
$behaviors = parent::behaviors(); |
||||
$behaviors['contentNegotiator']['formats']['text/html'] = Response::FORMAT_HTML; |
||||
return $behaviors; |
||||
} |
||||
``` |
||||
|
||||
The keys of the `formats` property are the supported MIME types, while the values are the corresponding |
||||
response format names which must be supported in [[yii\web\Response::formatters]]. |
||||
|
||||
|
||||
## Data Serializing <a name="data-serializing"></a> |
||||
|
||||
As we have described above, [[yii\rest\Serializer]] is the central piece responsible for converting resource |
||||
objects or collections into arrays. It recognizes objects implementing [[yii\base\ArrayableInterface]] as |
||||
well as [[yii\data\DataProviderInterface]]. The former is mainly implemented by resource objects, while |
||||
the latter resource collections. |
||||
|
||||
You may configure the serializer by setting the [[yii\rest\Controller::serializer]] property with a configuration array. |
||||
For example, sometimes you may want to help simplify the client development work by including pagination information |
||||
directly in the response body. To do so, configure the [[yii\rest\Serializer::collectionEnvelope]] property |
||||
as follows: |
||||
|
||||
```php |
||||
use yii\rest\ActiveController; |
||||
|
||||
class UserController extends ActiveController |
||||
{ |
||||
public $modelClass = 'app\models\User'; |
||||
public $serializer = [ |
||||
'class' => 'yii\rest\Serializer', |
||||
'collectionEnvelope' => 'items', |
||||
]; |
||||
} |
||||
``` |
||||
|
||||
You may then get the following response for request `http://localhost/users`: |
||||
|
||||
``` |
||||
HTTP/1.1 200 OK |
||||
Date: Sun, 02 Mar 2014 05:31:43 GMT |
||||
Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y |
||||
X-Powered-By: PHP/5.4.20 |
||||
X-Pagination-Total-Count: 1000 |
||||
X-Pagination-Page-Count: 50 |
||||
X-Pagination-Current-Page: 1 |
||||
X-Pagination-Per-Page: 20 |
||||
Link: <http://localhost/users?page=1>; rel=self, |
||||
<http://localhost/users?page=2>; rel=next, |
||||
<http://localhost/users?page=50>; rel=last |
||||
Transfer-Encoding: chunked |
||||
Content-Type: application/json; charset=UTF-8 |
||||
|
||||
{ |
||||
"items": [ |
||||
{ |
||||
"id": 1, |
||||
... |
||||
}, |
||||
{ |
||||
"id": 2, |
||||
... |
||||
}, |
||||
... |
||||
], |
||||
"_links": { |
||||
"self": "http://localhost/users?page=1", |
||||
"next": "http://localhost/users?page=2", |
||||
"last": "http://localhost/users?page=50" |
||||
}, |
||||
"_meta": { |
||||
"totalCount": 1000, |
||||
"pageCount": 50, |
||||
"currentPage": 1, |
||||
"perPage": 20 |
||||
} |
||||
} |
||||
``` |
@ -0,0 +1,78 @@
|
||||
Routing |
||||
======= |
||||
|
||||
With resource and controller classes ready, you can access the resources using the URL like |
||||
`http://localhost/index.php?r=user/create`, similar to what you can do with normal 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/<id>' => 'user/update', |
||||
'DELETE users/<id>' => 'user/delete', |
||||
'GET,HEAD users/<id>' => 'user/view', |
||||
'POST users' => 'user/create', |
||||
'GET,HEAD users' => 'user/index', |
||||
'users/<id>' => '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 `extraPatterns` 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 `extraPatterns` option as follows, |
||||
|
||||
```php |
||||
[ |
||||
'class' => 'yii\rest\UrlRule', |
||||
'controller' => 'user', |
||||
'extraPatterns' => [ |
||||
'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. |
@ -0,0 +1,107 @@
|
||||
Versioning |
||||
========== |
||||
|
||||
Your APIs should be versioned. Unlike Web applications which you have full control on both client side and server side |
||||
code, for APIs you usually do not have control of the client code that consumes the APIs. Therefore, backward |
||||
compatibility (BC) of the APIs should be maintained whenever possible, and if some BC-breaking changes must be |
||||
introduced to the APIs, you should bump up the version number. You may refer to [Semantic Versioning](http://semver.org/) |
||||
for more information about designing the version numbers of your APIs. |
||||
|
||||
Regarding how to implement API versioning, a common practice is to embed the version number in the API URLs. |
||||
For example, `http://example.com/v1/users` stands for `/users` API of version 1. Another method of API versioning |
||||
which gains momentum recently is to put version numbers in the HTTP request headers, typically through the `Accept` header, |
||||
like the following: |
||||
|
||||
``` |
||||
// via a parameter |
||||
Accept: application/json; version=v1 |
||||
// via a vendor content type |
||||
Accept: application/vnd.company.myapp-v1+json |
||||
``` |
||||
|
||||
Both methods have pros and cons, and there are a lot of debates about them. Below we describe a practical strategy |
||||
of API versioning that is kind of a mix of these two methods: |
||||
|
||||
* Put each major version of API implementation in a separate module whose ID is the major version number (e.g. `v1`, `v2`). |
||||
Naturally, the API URLs will contain major version numbers. |
||||
* Within each major version (and thus within the corresponding module), use the `Accept` HTTP request header |
||||
to determine the minor version number and write conditional code to respond to the minor versions accordingly. |
||||
|
||||
For each module serving a major version, it should include the resource classes and the controller classes |
||||
serving for that specific version. To better separate code responsibility, you may keep a common set of |
||||
base resource and controller classes, and subclass them in each individual version module. Within the subclasses, |
||||
implement the concrete code such as `Model::fields()`. |
||||
|
||||
Your code may be organized like the following: |
||||
|
||||
``` |
||||
api/ |
||||
common/ |
||||
controllers/ |
||||
UserController.php |
||||
PostController.php |
||||
models/ |
||||
User.php |
||||
Post.php |
||||
modules/ |
||||
v1/ |
||||
controllers/ |
||||
UserController.php |
||||
PostController.php |
||||
models/ |
||||
User.php |
||||
Post.php |
||||
v2/ |
||||
controllers/ |
||||
UserController.php |
||||
PostController.php |
||||
models/ |
||||
User.php |
||||
Post.php |
||||
``` |
||||
|
||||
Your application configuration would look like: |
||||
|
||||
```php |
||||
return [ |
||||
'modules' => [ |
||||
'v1' => [ |
||||
'basePath' => '@app/modules/v1', |
||||
], |
||||
'v2' => [ |
||||
'basePath' => '@app/modules/v2', |
||||
], |
||||
], |
||||
'components' => [ |
||||
'urlManager' => [ |
||||
'enablePrettyUrl' => true, |
||||
'enableStrictParsing' => true, |
||||
'showScriptName' => false, |
||||
'rules' => [ |
||||
['class' => 'yii\rest\UrlRule', 'controller' => ['v1/user', 'v1/post']], |
||||
['class' => 'yii\rest\UrlRule', 'controller' => ['v2/user', 'v2/post']], |
||||
], |
||||
], |
||||
], |
||||
]; |
||||
``` |
||||
|
||||
As a result, `http://example.com/v1/users` will return the list of users in version 1, while |
||||
`http://example.com/v2/users` will return version 2 users. |
||||
|
||||
Using modules, code for different major versions can be well isolated. And it is still possible |
||||
to reuse code across modules via common base classes and other shared classes. |
||||
|
||||
To deal with minor version numbers, you may take advantage of the content negotiation |
||||
feature provided by the [[yii\filters\ContentNegotiator|contentNegotiator]] behavior. The `contentNegotiator` |
||||
behavior will set the [[yii\web\Response::acceptParams]] property when it determines which |
||||
content type to support. |
||||
|
||||
For example, if a request is sent with the HTTP header `Accept: application/json; version=v1`, |
||||
after content negotiation, [[yii\web\Response::acceptParams]] will contain the value `['version' => 'v1']`. |
||||
|
||||
Based on the version information in `acceptParams`, you may write conditional code in places |
||||
such as actions, resource classes, serializers, etc. |
||||
|
||||
Since minor versions require maintaining backward compatibility, hopefully there are not much |
||||
version checks in your code. Otherwise, chances are that you may need to create a new major version. |
@ -1,31 +1,260 @@
|
||||
Working with Databases |
||||
====================== |
||||
|
||||
> Note: This section is under development. |
||||
|
||||
In this section, we will describe how to create a new page to display data fetched from a database table. |
||||
To achieve this goal, you will create an [action](structure-controllers.md), a [view](structure-views.md), |
||||
and an [Active Record](db-active-record.md) model that can be used to fetch and represent database data. |
||||
In this section, we will describe how to create a new page to display the country data fetched from |
||||
from a database table `country`. To achieve this goal, you will configure a database connection, |
||||
create an [Active Record](db-active-record.md) class, and then create an [action](structure-controllers.md) |
||||
and a [view](structure-views.md). |
||||
|
||||
Through this tutorial, you will learn |
||||
|
||||
* How to configure database connections; |
||||
* How to configure a DB connection; |
||||
* How to define an Active Record class; |
||||
* How to query data using the Active Record class; |
||||
* How to display data in a view in a paginated fashion. |
||||
|
||||
Note that in order to finish this section, you should have basic knowledge and experience about databases. |
||||
In particular, you should know how to create a database and how to execute SQL statements using a DB client tool. |
||||
|
||||
|
||||
Preparing a Database <a name="preparing-database"></a> |
||||
-------------------- |
||||
|
||||
To begin with, create a database named `yii2basic` from which you will fetch data in your application. |
||||
You may create a SQLite, MySQL, PostgreSQL, MSSQL or Oracle database. For simplicity, we will use MySQL |
||||
in the following description. |
||||
|
||||
Create a table named `country` in the database and insert some sample data. You may run the following SQL statements. |
||||
|
||||
```sql |
||||
CREATE TABLE `country` ( |
||||
`code` char(2) NOT NULL PRIMARY KEY, |
||||
`name` char(52) NOT NULL, |
||||
`population` int(11) NOT NULL DEFAULT '0' |
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
||||
|
||||
INSERT INTO `Country` VALUES ('AU','Australia',18886000); |
||||
INSERT INTO `Country` VALUES ('BR','Brazil',170115000); |
||||
INSERT INTO `Country` VALUES ('CA','Canada',1147000); |
||||
INSERT INTO `Country` VALUES ('CN','China',1277558000); |
||||
INSERT INTO `Country` VALUES ('DE','Germany',82164700); |
||||
INSERT INTO `Country` VALUES ('FR','France',59225700); |
||||
INSERT INTO `Country` VALUES ('GB','United Kingdom',59623400); |
||||
INSERT INTO `Country` VALUES ('IN','India',1013662000); |
||||
INSERT INTO `Country` VALUES ('RU','Russia',146934000); |
||||
INSERT INTO `Country` VALUES ('US','United States',278357000); |
||||
``` |
||||
|
||||
To this end, you have a database named `yii2basic`, and within this database there is a `country` table |
||||
with ten rows of data. |
||||
|
||||
|
||||
Configuring a DB Connection <a name="configuring-db-connection"></a> |
||||
--------------------------- |
||||
|
||||
Make sure you have installed the [PDO](http://www.php.net/manual/en/book.pdo.php) PHP extension and |
||||
the PDO driver for the database you are using (e.g. `pdo_mysql` for MySQL). This is a basic requirement |
||||
if your application uses a relational database. |
||||
|
||||
Open the file `config/db.php` and adjust the content based on your database information. By default, |
||||
the file contains the following content: |
||||
|
||||
```php |
||||
<?php |
||||
|
||||
return [ |
||||
'class' => 'yii\db\Connection', |
||||
'dsn' => 'mysql:host=localhost;dbname=yii2basic', |
||||
'username' => 'root', |
||||
'password' => '', |
||||
'charset' => 'utf8', |
||||
]; |
||||
``` |
||||
|
||||
This is a typical file-based [configuration](concept-configurations.md). It specifies the parameters |
||||
needed to create and initialize a [[yii\db\Connection]] instance through which you can make SQL queries |
||||
against the underlying database. |
||||
|
||||
The DB connection configured above can be accessed in the code via the expression `Yii::$app->db`. |
||||
|
||||
> Info: The `config/db.php` file will be included in the main application configuration `config/web.php` |
||||
which specifies how the [application](structure-applications.md) instance should be initialized. |
||||
For more information, please refer to the [Configurations](concept-configurations.md) section. |
||||
|
||||
|
||||
Creating an Active Record <a name="creating-active-record"></a> |
||||
------------------------- |
||||
|
||||
To represent and fetch the data in the `country` table, create an [Active Record](db-active-record.md) |
||||
class named `Country` and save it in the file `models/Country.php`. |
||||
|
||||
```php |
||||
<?php |
||||
|
||||
namespace app\models; |
||||
|
||||
use yii\db\ActiveRecord; |
||||
|
||||
class Country extends ActiveRecord |
||||
{ |
||||
} |
||||
``` |
||||
|
||||
The `Country` class extends from [[yii\db\ActiveRecord]]. You do not need to write any code inside of it. |
||||
Yii will guess the associated table name from the class name. In case this does not work, you may |
||||
override the [[yii\db\ActiveRecord::tableName()]] method to explicitly specify the associated table name. |
||||
|
||||
Using the `Country` class, you can manipulate the data in the `country` table easily. Below are some |
||||
code snippets showing how you can make use of the `Country` class. |
||||
|
||||
```php |
||||
use app\models\Country; |
||||
|
||||
// get all rows from the country table and order them by "name" |
||||
$countries = Country::find()->orderBy('name')->all(); |
||||
|
||||
// get the row whose primary key is "US" |
||||
$country = Country::findOne('US'); |
||||
|
||||
// displays "United States" |
||||
echo $country->name; |
||||
|
||||
Creating a Model |
||||
---------------- |
||||
// modifies the country name to be "U.S.A." and save it to database |
||||
$country->name = 'U.S.A.'; |
||||
$country->save(); |
||||
``` |
||||
|
||||
Creating an Action |
||||
> Info: Active Record is a powerful way of accessing and manipulating database data in an object-oriented fashion. |
||||
You may find more detailed information in the [Active Record](db-active-record.md). Besides Active Record, you may also |
||||
use a lower-level data accessing method called [Data Access Objects](db-dao.md). |
||||
|
||||
|
||||
Creating an Action <a name="creating-action"></a> |
||||
------------------ |
||||
|
||||
Creating a View |
||||
To expose the country data to end users, you need to create a new action. Instead of doing this in the `site` |
||||
controller like you did in the previous sections, it makes more sense to create a new controller specifically |
||||
for all actions about manipulating country data. Name this new controller as `CountryController` and create |
||||
an `index` action in it, as shown in the following, |
||||
|
||||
```php |
||||
<?php |
||||
|
||||
namespace app\controllers; |
||||
|
||||
use yii\web\Controller; |
||||
use yii\data\Pagination; |
||||
use app\models\Country; |
||||
|
||||
class CountryController extends Controller |
||||
{ |
||||
public function actionIndex() |
||||
{ |
||||
$query = Country::find(); |
||||
|
||||
$pagination = new Pagination([ |
||||
'defaultPageSize' => 5, |
||||
'totalCount' => $query->count(), |
||||
]); |
||||
|
||||
$countries = $query->orderBy('name') |
||||
->offset($pagination->offset) |
||||
->limit($pagination->limit) |
||||
->all(); |
||||
|
||||
return $this->render('index', [ |
||||
'countries' => $countries, |
||||
'pagination' => $pagination, |
||||
]); |
||||
} |
||||
} |
||||
``` |
||||
|
||||
Save the above code in the file `controllers/CountryController.php`. |
||||
|
||||
The `index` action calls `Country::find()` to build a DB query and retrieve all data from the `country` table. |
||||
To limit the number of countries returned in each request, the query is paginated with the help of a |
||||
[[yii\data\Pagination]] object. The `Pagination` object serves for two purposes: |
||||
|
||||
* Sets the `offset` and `limit` clauses for the SQL statement represented by the query so that it only |
||||
returns a single page of data (at most 5 rows in a page). |
||||
* Being used in the view to display a pager consisting of a list of page buttons, as will be explained in |
||||
the next subsection. |
||||
|
||||
At the end, the `index` action renders a view named `index` and passes the country data as well as the pagination |
||||
information to it. |
||||
|
||||
|
||||
Creating a View <a name="creating-view"></a> |
||||
--------------- |
||||
|
||||
How It Works |
||||
------------ |
||||
Under the `views` directory, first create a sub-directory named `country`. This will used to hold all |
||||
views rendered by the `country` controller. Within the `views/country` directory, create a file named `index.php` |
||||
with the following content: |
||||
|
||||
Summary |
||||
```php |
||||
<?php |
||||
use yii\helpers\Html; |
||||
use yii\widgets\LinkPager; |
||||
?> |
||||
<h1>Countries</h1> |
||||
<ul> |
||||
<?php foreach ($countries as $country): ?> |
||||
<li> |
||||
<?= Html::encode("{$country->name} ({$country->code})") ?>: |
||||
<?= $country->population ?> |
||||
</li> |
||||
<?php endforeach; ?> |
||||
</ul> |
||||
|
||||
<?= LinkPager::widget(['pagination' => $pagination]) ?> |
||||
``` |
||||
|
||||
The view consists of two parts. In the first part, the country data is traversed and rendered as an unordered HTML list. |
||||
In the second part, a [[yii\widgets\LinkPager]] widget is rendered using the pagination information passed from the action. |
||||
The `LinkPager` widget displays a list of page buttons. Clicking on any of them will refresh the country data |
||||
in the corresponding page. |
||||
|
||||
|
||||
Trying it Out <a name="trying-it-out"></a> |
||||
------------- |
||||
|
||||
To see how it works, use your browser to access the following URL: |
||||
|
||||
``` |
||||
http://hostname/index.php?r=country/index |
||||
``` |
||||
|
||||
![Country List](images/start-country-list.png) |
||||
|
||||
You will see a page showing five countries. And below the countries, you will see a pager with four buttons. |
||||
If you click on the button "2", you will see that the page displays another five countries in the database. |
||||
Observe more carefully and you will find the URL in the browser changes to |
||||
|
||||
``` |
||||
http://hostname/index.php?r=country/index&page=2 |
||||
``` |
||||
|
||||
Behind the scene, [[yii\data\Pagination|Pagination]] is playing the magic. |
||||
|
||||
* Initially, [[yii\data\Pagination|Pagination]] represents the first page, which sets the country query |
||||
with the clause `LIMIT 5 OFFSET 0`. As a result, the first five countries will be fetched and displayed. |
||||
* The [[yii\widgets\LinkPager|LinkPager]] widget renders the page buttons using the URLs |
||||
created by [[yii\data\Pagination::createUrl()|Pagination]]. The URLs will contain the query parameter `page` |
||||
representing different page numbers. |
||||
* If you click the page button "2", a new request for the route `country/index` will be triggered and handled. |
||||
[[yii\data\Pagination|Pagination]] reads the `page` query parameter and sets the current page number 2. |
||||
The new country query will thus have the clause `LIMIT 5 OFFSET 5` and return back the next five countries |
||||
for display. |
||||
|
||||
|
||||
Summary <a name="summary"></a> |
||||
------- |
||||
|
||||
In this section, you have learned how to work with a database. You have also learned how to fetch and display |
||||
data in pages with the help of [[yii\data\Pagination]] and [[yii\widgets\LinkPager]]. |
||||
|
||||
In the next section, you will learn how to use the powerful code generation tool, called [Gii](tool-gii.md), |
||||
to help you rapidly implement some commonly required features, such as the Create-Read-Update-Delete (CRUD) |
||||
operations about the data in a DB table. As a matter of fact, the code you have just written can all |
||||
be automatically generated using this tool. |
||||
|
@ -1,4 +1,128 @@
|
||||
Generating Code with Gii |
||||
======================== |
||||
|
||||
> Note: This section is under development. |
||||
In this section, we will describe how to use [Gii](tool-gii.md) to automatically generate the code |
||||
that implements some common features. To achieve this goal, all you need is just to enter the needed |
||||
information according to the instructions showing on the Gii Web pages. |
||||
|
||||
Through this tutorial, you will learn |
||||
|
||||
* How to enable Gii in your application; |
||||
* How to use Gii to generate an Active Record class; |
||||
* How to use Gii to generate the code implementing the CRUD operations for a DB table. |
||||
* How to customize the code generated by Gii. |
||||
|
||||
|
||||
Starting Gii <a name="starting-gii"></a> |
||||
------------ |
||||
|
||||
[Gii](tool-gii.md) is provided by Yii in terms of a [module](structure-modules.md). You can enable Gii |
||||
by configuring it in the [[yii\base\Application::modules|modules]] property of the application. In particular, |
||||
you may find the following code is already given in the `config/web.php` file - the application configuration, |
||||
|
||||
```php |
||||
$config = [ ... ]; |
||||
|
||||
if (YII_ENV_DEV) { |
||||
$config['bootstrap'][] = 'gii'; |
||||
$config['modules']['gii'] = 'yii\gii\Module'; |
||||
} |
||||
``` |
||||
|
||||
The above configuration states that when in [development environment](concept-configurations.md#environment-constants), |
||||
the application should include a module named `gii` which is of class [[yii\gii\Module]]. |
||||
|
||||
If you check the [entry script](structure-entry-scripts.md) `web/index.php` of your application, you will |
||||
find the following line which essentially makes `YII_ENV_DEV` to be true. |
||||
|
||||
```php |
||||
defined('YII_ENV') or define('YII_ENV', 'dev'); |
||||
``` |
||||
|
||||
Therefore, your application has already enabled Gii, and you can access it via the following URL: |
||||
|
||||
``` |
||||
http://hostname/index.php?r=gii |
||||
``` |
||||
|
||||
![Gii](images/start-gii.png) |
||||
|
||||
|
||||
Generating an Active Record Class <a name="generating-ar"></a> |
||||
--------------------------------- |
||||
|
||||
To use Gii to generate an Active Record class, select the "Model Generator" and fill out the form as follows, |
||||
|
||||
* Table Name: `country` |
||||
* Model Class: `Country` |
||||
|
||||
![Model Generator](images/start-gii-model.png) |
||||
|
||||
Click on the "Preview" button. You will see `models/Country.php` is listed in the result. |
||||
You may click on it to preview its content. |
||||
|
||||
Because in the last section, you have already created the same file `models/Country.php`, if you click |
||||
the `diff` button next to the file name, you will see the difference between the code to be generated |
||||
and the code that you have already written. |
||||
|
||||
![Model Generator Preview](images/start-gii-model-preview.png) |
||||
|
||||
Check the checkbox next to "overwrite" and then click on the "Generate" button. You will see |
||||
a confirmation page indicating the code has been successfully generated and your existing `models/Country.php` |
||||
is overwritten with the newly generated code. |
||||
|
||||
|
||||
Generating CRUD Code <a name="generating-crud"></a> |
||||
-------------------- |
||||
|
||||
To create CRUD code, select the "CRUD Generator". Fill out the form as follows: |
||||
|
||||
* Model Class: `app\models\Country` |
||||
* Search Model Class: `app\models\CountrySearch` |
||||
* Controller Class: `app\controllers\CountryController` |
||||
|
||||
![CRUD Generator](images/start-gii-crud.png) |
||||
|
||||
Click on the "Preview" button. You will see a list of files to be generated, as shown below. |
||||
|
||||
Make sure you have checked the overwrite checkboxes for both `controllers/CountryController.php` and |
||||
`views/country/index.php` files. This is needed because you have already created these files |
||||
in the previous section and you want to have them overwritten to have full CRUD support. |
||||
|
||||
|
||||
Trying it Out <a name="trying-it-out"></a> |
||||
------------- |
||||
|
||||
To see how it works, use your browser to access the following URL: |
||||
|
||||
``` |
||||
http://hostname/index.php?r=country/index |
||||
``` |
||||
|
||||
You will see a data grid showing the countries in the database table. You may sort the grid |
||||
or filter it by entering filter conditions in the column headers. |
||||
|
||||
For each country displayed in the grid, you may choose to view its detail, update it or delete it. |
||||
You may also click on the "Create Country" button on top of the grid to create a new country. |
||||
|
||||
![Data Grid of Countries](images/start-gii-country-grid.png) |
||||
|
||||
![Updating a Country](images/start-gii-country-update.png) |
||||
|
||||
The following is the list of the generated files in case you want to dig out how these features are implemented, |
||||
or if you want to customize them. |
||||
|
||||
* Controller: `controllers/CountryController.php` |
||||
* Models: `models/Country.php` and `models/CountrySearch.php` |
||||
* Views: `views/country/*.php` |
||||
|
||||
> Info: Gii is designed to be a highly customizable and extensible code generation tool. Using it wisely |
||||
can greatly accelerate your application development speed. For more details, please refer to |
||||
the [Gii](tool-gii.md) section. |
||||
|
||||
|
||||
Summary <a name="summary"></a> |
||||
------- |
||||
|
||||
In this section, you have learned how to use Gii to generate the code that implements a complete |
||||
set of CRUD features regarding a database table. |
||||
|
@ -1,4 +1,33 @@
|
||||
Looking Ahead |
||||
============= |
||||
|
||||
> Note: This section is under development. |
||||
To this end, you have created a complete Yii application, and you have learned how to implement some commonly |
||||
needed features, such as getting data from users using an HTML form, fetching data from database and |
||||
displaying it in a paginated fashion. You have also learned how to use [Gii](tool-gii.md) to generate |
||||
code automatically, which turns programming into a task as simple as just filling out some forms. In this |
||||
section, we will summarize the resources about Yii that help you be more productive when using Yii. |
||||
|
||||
* Documentation |
||||
- The Definitive Guide: |
||||
As the name indicates, the guide precisely defines how Yii should work and gives you a general guidance |
||||
about using Yii. It is the single most important Yii tutorial that you should read through |
||||
before writing any Yii code. |
||||
- The Class Reference: |
||||
This specifies the usage of every class provided by Yii. It should be mainly used when you are writing |
||||
code and want to understand the usage of a particular class, method, property. |
||||
- The Wiki Articles: |
||||
The wiki articles are written by Yii users based on their own experiences. Most of them are written |
||||
like cookbook recipes which show how to solve particular problems using Yii. While the quality of these |
||||
articles may be as good as the Definitive Guide, they are useful in that they cover broader topics |
||||
and can often provide to you ready-to-use solutions. |
||||
- Books |
||||
* [Extensions](http://www.yiiframework.com/extensions/): |
||||
Yii boasts a library of thousands of user-contributed extensions that can be easily plugged into your applications |
||||
and make your application development even faster and easier. |
||||
* Community |
||||
- [Forum](http://www.yiiframework.com/forum/) |
||||
- [GitHub](https://github.com/yiisoft/yii2) |
||||
- [Facebook](https://www.facebook.com/groups/yiitalk/) |
||||
- [Twitter](https://twitter.com/yiiframework) |
||||
- [LinkedIn](https://www.linkedin.com/groups/yii-framework-1483367) |
||||
|
||||
|
@ -1,31 +1,116 @@
|
||||
Entry Scripts |
||||
============= |
||||
|
||||
> Note: This section is under development. |
||||
Entry scripts are the first chain in the application bootstrapping process. An application (either |
||||
Web application or console application) has a single entry script. End users make requests to |
||||
entry scripts which instantiate application instances and forward the requests to them. |
||||
|
||||
Configuring options in the bootstrap file |
||||
----------------------------------------- |
||||
Entry scripts for Web applications must be stored under Web accessible directories so that they |
||||
can be accessed by end users. They are often named as `index.php`, but can also use any other names, |
||||
provided Web servers can locate them. |
||||
|
||||
For each application in Yii there is at least one bootstrap file: a PHP script through which all requests are handled. For web applications, the bootstrap file is typically `index.php`; for |
||||
console applications, the bootstrap file is `yii`. Both bootstrap files perform nearly the same job: |
||||
Entry scripts for console applications are usually stored under the [base path](structure-applications.md) |
||||
of applications and are named as `yii` (with the `.php` suffix). They should be made executable |
||||
so that users can run console applications through the command `./yii <route> [arguments] [options]`. |
||||
|
||||
1. Setting common constants. |
||||
2. Including the Yii framework itself. |
||||
3. Including [Composer autoloader](http://getcomposer.org/doc/01-basic-usage.md#autoloading). |
||||
4. Reading the configuration file into `$config`. |
||||
5. Creating a new application instance, configured via `$config`, and running that instance. |
||||
Entry scripts mainly do the following work: |
||||
|
||||
Like any resource in your Yii application, the bootstrap file can be edited to fit your needs. A typical change is to the value of `YII_DEBUG`. This constant should be `true` during development, but always `false` on production sites. |
||||
* Define global constants; |
||||
* Register [Composer autoloader](http://getcomposer.org/doc/01-basic-usage.md#autoloading); |
||||
* Include the [[Yii]] class file; |
||||
* Load application configuration; |
||||
* Create and configure an [application](structure-applications.md) instance; |
||||
* Call [[yii\base\Application::run()]] to process the incoming request. |
||||
|
||||
The default bootstrap structure sets `YII_DEBUG` to `false` if not defined: |
||||
|
||||
## Web Applications <a name="web-applications"></a> |
||||
|
||||
The following is the code in the entry script for the [Basic Web Application Template](start-installation.md). |
||||
|
||||
```php |
||||
defined('YII_DEBUG') or define('YII_DEBUG', false); |
||||
<?php |
||||
|
||||
defined('YII_DEBUG') or define('YII_DEBUG', true); |
||||
defined('YII_ENV') or define('YII_ENV', 'dev'); |
||||
|
||||
// register Composer autoloader |
||||
require(__DIR__ . '/../vendor/autoload.php'); |
||||
|
||||
// include Yii class file |
||||
require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php'); |
||||
|
||||
// load application configuration |
||||
$config = require(__DIR__ . '/../config/web.php'); |
||||
|
||||
// create, configure and run application |
||||
(new yii\web\Application($config))->run(); |
||||
``` |
||||
|
||||
During development, you can change this to `true`: |
||||
|
||||
## Console Applications <a name="console-applications"></a> |
||||
|
||||
Similarly, the following is the code for the entry script of a console application: |
||||
|
||||
```php |
||||
define('YII_DEBUG', true); // Development only |
||||
defined('YII_DEBUG') or define('YII_DEBUG', false); |
||||
#!/usr/bin/env php |
||||
<?php |
||||
/** |
||||
* Yii console bootstrap file. |
||||
* |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
defined('YII_DEBUG') or define('YII_DEBUG', true); |
||||
|
||||
// fcgi doesn't have STDIN and STDOUT defined by default |
||||
defined('STDIN') or define('STDIN', fopen('php://stdin', 'r')); |
||||
defined('STDOUT') or define('STDOUT', fopen('php://stdout', 'w')); |
||||
|
||||
// register Composer autoloader |
||||
require(__DIR__ . '/vendor/autoload.php'); |
||||
|
||||
// load application configuration |
||||
require(__DIR__ . '/vendor/yiisoft/yii2/Yii.php'); |
||||
|
||||
// load application configuration |
||||
$config = require(__DIR__ . '/config/console.php'); |
||||
|
||||
$application = new yii\console\Application($config); |
||||
$exitCode = $application->run(); |
||||
exit($exitCode); |
||||
``` |
||||
|
||||
|
||||
## Defining Constants <a name="defining-constants"></a> |
||||
|
||||
Entry scripts are the best place for defining global constants. Yii supports the following three constants: |
||||
|
||||
* `YII_DEBUG`: specifies whether the application is running in debug mode. When in debug mode, an application |
||||
will keep more log information, and will reveal detailed error call stacks if exceptions are thrown. For this |
||||
reason, debug mode should be used mainly during development. The default value of `YII_DEBUG` is false. |
||||
* `YII_ENV`: specifies which environment the application is running in. This has been described in |
||||
more detail in the [Configurations](concept-configurations.md#environment-constants) section. |
||||
The default value of `YII_ENV` is `'prod'`, meaning the application is running in production environment. |
||||
* `YII_ENABLE_ERROR_HANDLER`: specifies whether to enable the error handler provided by Yii. The default |
||||
value of this constant is true. |
||||
|
||||
When defining a constant, we often use the code like the following: |
||||
|
||||
```php |
||||
defined('YII_DEBUG') or define('YII_DEBUG', true); |
||||
``` |
||||
|
||||
which is equivalent to the following code: |
||||
|
||||
```php |
||||
if (!defined('YII_DEBUG')) { |
||||
define('YII_DEBUG', true); |
||||
} |
||||
``` |
||||
|
||||
Clearly the former is more succinct and easier to understand. |
||||
|
||||
Constant definitions should be done at the very beginning of an entry script so that they can take effect |
||||
when other PHP files are being included. |
||||
|
@ -0,0 +1,82 @@
|
||||
|
||||
<?php |
||||
/** |
||||
* Message translations. |
||||
* |
||||
* This file is automatically generated by 'yii message' command. |
||||
* It contains the localizable messages extracted from source code. |
||||
* You may modify this file by translating the extracted messages. |
||||
* |
||||
* Each array element represents the translation (value) of a message (key). |
||||
* If the value is empty, the message is considered as not translated. |
||||
* Messages that no longer need translation will have their translations |
||||
* enclosed between a pair of '@@' marks. |
||||
* |
||||
* Message string can be used with plural forms format. Check i18n section |
||||
* of the guide for details. |
||||
* |
||||
* NOTE: this file must be saved in UTF-8 encoding. |
||||
*/ |
||||
return array ( |
||||
'(not set)' => '(설정되어있지않습니다)', |
||||
'An internal server error occurred.' => '서버 오류가 발생하였습니다.', |
||||
'Are you sure to delete this item?' => '이 항목을 삭제하시겠습니까?', |
||||
'Delete' => '삭제', |
||||
'Error' => '오류', |
||||
'File upload failed.' => '파일 업로드 실패하였습니다.', |
||||
'Home' => '홈', |
||||
'Invalid data received for parameter "{param}".' => '매개변수"{param}"를 위한 데이터가 잘못된 데이터입니다.', |
||||
'Login Required' => '로그인이 필요합니다.', |
||||
'Missing required arguments: {params}' => '필요한 인수가 없습니다: {params}', |
||||
'Missing required parameters: {params}' => '필요한 매개변수가 없습니다: {params}', |
||||
'No' => '아니오', |
||||
'No help for unknown command "{command}".' => '알 수 없는 명령 "{command}"에 대한 도움말이 없습니다.', |
||||
'No help for unknown sub-command "{command}".' => '알 수 없는 하위명령 "{command}"에 대한 도움말이 없습니다.', |
||||
'No results found.' => '결과가 없습니다.', |
||||
'Only files with these extensions are allowed: {extensions}.' => '다음의 확장명을 가진 파일만 허용됩니다: {extensions}', |
||||
'Only files with these mimeTypes are allowed: {mimeTypes}.' => '다음의 mimeType만 허용됩니다: {mimeTypes}', |
||||
'Page not found.' => '페이지를 찾을 수 없습니다.', |
||||
'Please fix the following errors:' => '다음 오류를 수정하십시오:', |
||||
'Please upload a file.' => '파일을 업로드하십시오.', |
||||
'Showing <b>{begin, number}-{end, number}</b> of <b>{totalCount, number}</b> {totalCount, plural, one{item} other{items}}.' => '{totalCount} 중 {begin} 에서 {end} 까지 표시하고 있습니다.', |
||||
'The file "{file}" is not an image.' => '파일 "{file}"은 이미지 파일이 아닙니다.', |
||||
'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => '파일 "{file}"는 크기가 너무 큽니다. 파일 크기는 {limit} 바이트를 초과할 수 없습니다.', |
||||
'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => '파일 "{file}"는 크기가 너무 작습니다. 크기는 {limit} 바이트 보다 작을 수 없습니다.', |
||||
'The format of {attribute} is invalid.' => '{attribute}의 형식이 올바르지 않습니다.', |
||||
'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '이미지 "{file}"가 너무 큽니다. 높이는 {limit} 보다 클 수 없습니다.', |
||||
'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '이미지 "{file}"가 너무 큽니다. 넓이는 {limit} 보다 클 수 없습니다.', |
||||
'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '이미지 "{file}"가 너무 작습니다. 높이는 {limit} 보다 작을 수 없습니다.', |
||||
'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '이미지 "{file}"가 너무 작습니다. 넒이는 {limit} 보다 작을 수 없습니다.', |
||||
'The verification code is incorrect.' => '확인코드가 올바르지않습니다.', |
||||
'Total <b>{count, number}</b> {count, plural, one{item} other{items}}.' => '모두 {count} 개', |
||||
'Unable to verify your data submission.' => '데이터 전송을 확인하지 못했습니다.', |
||||
'Unknown command "{command}".' => '알 수 없는 명령 "{command}".', |
||||
'Unknown option: --{name}' => '알 수 없는 옵션: --{name}', |
||||
'Update' => '갱신', |
||||
'View' => '보기', |
||||
'Yes' => '예', |
||||
'You are not allowed to perform this action.' => '이 작업의 실행을 허가받지못하였습니다.', |
||||
'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => '최대 {limit} 개의 파일을 업로드할 수 있습니다.', |
||||
'the input value' => '입력 값', |
||||
'{attribute} "{value}" has already been taken.' => '{attribute}에 "{value}"이 이미 사용되고 있습니다.', |
||||
'{attribute} cannot be blank.' => '{attribute}는 공백일 수 없습니다.', |
||||
'{attribute} is invalid.' => '{attribute}가 잘못되었습니다.', |
||||
'{attribute} is not a valid URL.' => '{attribute}는 올바른 URL 형식이 아닙니다.', |
||||
'{attribute} is not a valid email address.' => '{attribute}는 올바른 이메일 주소 형식이 아닙니다.', |
||||
'{attribute} must be "{requiredValue}".' => '{attribute}는 {value}이어야 합니다.', |
||||
'{attribute} must be a number.' => '{attribute}는 반드시 숫자이어야 합니다.', |
||||
'{attribute} must be a string.' => '{attribute}는 반드시 문자이어야 합니다.', |
||||
'{attribute} must be an integer.' => '{attribute}는 반드시 정수이어야 합니다.', |
||||
'{attribute} must be either "{true}" or "{false}".' => '{attribute}는 {true} 또는 {false} 이어야 합니다.', |
||||
'{attribute} must be greater than "{compareValue}".' => '{attribute}는 "{compareValue}" 보다 커야 합니다.', |
||||
'{attribute} must be greater than or equal to "{compareValue}".' => '{attribute}는 "{compareValue}" 보다 크거나 같아야 합니다.', |
||||
'{attribute} must be less than "{compareValue}".' => '{attribute}는 "{compareValue}" 보다 작아야 합니다.', |
||||
'{attribute} must be less than or equal to "{compareValue}".' => '{attribute}는 "{compareValue}" 보다 작거나 같아야 합니다.', |
||||
'{attribute} must be no greater than {max}.' => '{attribute}는 "{compareValue}" 보다 클 수 없습니다.', |
||||
'{attribute} must be no less than {min}.' => '{attribute}는 "{compareValue}" 보다 작을 수 없습니다.', |
||||
'{attribute} must be repeated exactly.' => '{attribute}는 정확하게 반복합니다.', |
||||
'{attribute} must not be equal to "{compareValue}".' => '{attribute}는 "{compareValue}"와 같을 수 없습니다.', |
||||
'{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute}는 최소 {min}자 이어야합니다.', |
||||
'{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute}는 최대 {max}자 이어야합니다.', |
||||
'{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute}는 {length}자 이어야합니다.', |
||||
); |