Browse Source

Merge branch 'master' of github.com:yiisoft/yii2 into 3232-var-export

tags/2.0.0-rc
Klimov Paul 10 years ago
parent
commit
6f3ffae7d6
  1. 1
      .travis.yml
  2. 3
      apps/basic/config/web.php
  3. 25
      docs/guide/README.md
  4. 30
      docs/guide/caching-content.md
  5. 364
      docs/guide/caching-data.md
  6. 3
      docs/guide/caching-http.md
  7. 212
      docs/guide/caching-overview.md
  8. 15
      docs/guide/concept-aliases.md
  9. 12
      docs/guide/concept-autoloading.md
  10. 21
      docs/guide/concept-behaviors.md
  11. 21
      docs/guide/concept-configurations.md
  12. 29
      docs/guide/concept-di-container.md
  13. 18
      docs/guide/concept-events.md
  14. 4
      docs/guide/db-active-record.md
  15. 401
      docs/guide/images/application-lifecycle.graphml
  16. BIN
      docs/guide/images/application-lifecycle.png
  17. BIN
      docs/guide/images/start-app-installed.png
  18. BIN
      docs/guide/images/start-country-list.png
  19. BIN
      docs/guide/images/start-entry-confirmation.png
  20. BIN
      docs/guide/images/start-form-validation.png
  21. BIN
      docs/guide/images/start-gii-country-grid.png
  22. BIN
      docs/guide/images/start-gii-country-update.png
  23. BIN
      docs/guide/images/start-gii-crud.png
  24. BIN
      docs/guide/images/start-gii-model-preview.png
  25. BIN
      docs/guide/images/start-gii-model.png
  26. BIN
      docs/guide/images/start-gii.png
  27. BIN
      docs/guide/images/start-hello-world.png
  28. 5
      docs/guide/intro-upgrade-from-v1.md
  29. 9
      docs/guide/output-data-providers.md
  30. 53
      docs/guide/output-data-widgets.md
  31. 109
      docs/guide/rest-authentication.md
  32. 152
      docs/guide/rest-controllers.md
  33. 44
      docs/guide/rest-error-handling.md
  34. 848
      docs/guide/rest-quick-start.md
  35. 44
      docs/guide/rest-rate-limiting.md
  36. 218
      docs/guide/rest-resources.md
  37. 150
      docs/guide/rest-response-formatting.md
  38. 78
      docs/guide/rest-routing.md
  39. 107
      docs/guide/rest-versioning.md
  40. 255
      docs/guide/start-databases.md
  41. 31
      docs/guide/start-forms.md
  42. 126
      docs/guide/start-gii.md
  43. 18
      docs/guide/start-hello.md
  44. 25
      docs/guide/start-installation.md
  45. 31
      docs/guide/start-looking-head.md
  46. 67
      docs/guide/start-workflow.md
  47. 117
      docs/guide/structure-entry-scripts.md
  48. 10
      docs/guide/tool-debugger.md
  49. 8
      extensions/apidoc/models/Context.php
  50. 2
      extensions/authclient/clients/VKontakte.php
  51. 3
      extensions/composer/CHANGELOG.md
  52. 6
      extensions/composer/Installer.php
  53. 1
      extensions/debug/Module.php
  54. 2
      extensions/debug/controllers/DefaultController.php
  55. 16
      extensions/gii/assets/gii.js
  56. 2
      extensions/gii/generators/crud/Generator.php
  57. 4
      extensions/gii/generators/model/Generator.php
  58. 1
      extensions/gii/views/default/view/files.php
  59. 8
      framework/CHANGELOG.md
  60. 4
      framework/UPGRADE.md
  61. 3
      framework/base/Application.php
  62. 9
      framework/base/ErrorHandler.php
  63. 19
      framework/caching/Cache.php
  64. 3
      framework/captcha/CaptchaAction.php
  65. 12
      framework/console/Application.php
  66. 7
      framework/console/ErrorHandler.php
  67. 11
      framework/console/controllers/MigrateController.php
  68. 14
      framework/db/ActiveRecordInterface.php
  69. 2
      framework/db/QueryBuilder.php
  70. 2
      framework/di/ServiceLocator.php
  71. 4
      framework/filters/ContentNegotiator.php
  72. 2
      framework/helpers/HtmlPurifier.php
  73. 4
      framework/messages/config.php
  74. 82
      framework/messages/ko/yii.php
  75. 1
      framework/requirements/YiiRequirementChecker.php
  76. 17
      framework/rest/ActiveController.php
  77. 17
      framework/rest/Controller.php
  78. 12
      framework/rest/Serializer.php
  79. 36
      framework/validators/ExistValidator.php
  80. 2
      framework/validators/UniqueValidator.php
  81. 1
      framework/validators/Validator.php
  82. 15
      framework/views/errorHandler/exception.php
  83. 3
      framework/views/errorHandler/previousException.php
  84. 12
      framework/web/Application.php
  85. 2
      framework/web/Controller.php
  86. 2
      framework/web/ErrorAction.php
  87. 5
      framework/web/ErrorHandler.php
  88. 2
      framework/web/Response.php
  89. 32
      framework/web/Session.php
  90. 16
      framework/widgets/ActiveForm.php
  91. 10
      tests/unit/framework/caching/CacheTestCase.php
  92. 1
      tests/unit/framework/validators/BooleanValidatorTest.php
  93. 35
      tests/unit/framework/validators/ExistValidatorTest.php

1
.travis.yml

@ -9,6 +9,7 @@ php:
# run build against PHP 5.6 and hhvm but allow them to fail
# http://docs.travis-ci.com/user/build-configuration/#Rows-That-are-Allowed-To-Fail
matrix:
fast_finish: true
allow_failures:
- php: hhvm
- php: 5.6

3
apps/basic/config/web.php

@ -1,7 +1,6 @@
<?php
$params = require(__DIR__ . '/params.php');
$db = require(__DIR__ . '/db.php');
$config = [
'id' => 'basic',
@ -32,7 +31,7 @@ $config = [
],
],
],
'db' => $db,
'db' => require(__DIR__ . '/db.php'),
],
'params' => $params,
];

25
docs/guide/README.md

@ -30,7 +30,7 @@ Getting Started
Application Structure
---------------------
* **TBD** [Entry Scripts](structure-entry-scripts.md)
* [Entry Scripts](structure-entry-scripts.md)
* **TBD** [Applications](structure-applications.md)
* [Controllers and Actions](structure-controllers.md)
* [Views](structure-views.md)
@ -113,24 +113,23 @@ Caching
-------
* [Overview](caching-overview.md)
* **TBD** [Data Caching](caching-data.md)
* **TBD** [Fragment and Page Caching](caching-fragment.md)
* **TBD** [HTTP Caching](caching-http.md)
* [Data Caching](caching-data.md)
* [Content Caching](caching-content.md)
* [HTTP Caching](caching-http.md)
RESTful Web Services
--------------------
* [Quick Start](rest-quick-start.md)
* **TBD** [Resources](rest-resources.md)
* **TBD** [Routing](rest-routing.md)
* **TBD** [Data Formatting](rest-data-formatting.md)
* **TBD** [Authentication](rest-authentication.md)
* **TBD** [Rate Limiting](rest-rate-limiting.md)
* **TBD** [Versioning](rest-versioning.md)
* **TBD** [Caching](rest-caching.md)
* **TBD** [Error Handling](rest-error-handling.md)
* **TBD** [Testing](rest-testing.md)
* [Resources](rest-resources.md)
* [Controllers](rest-controllers.md)
* [Routing](rest-routing.md)
* [Response Formatting](rest-response-formatting.md)
* [Authentication](rest-authentication.md)
* [Rate Limiting](rest-rate-limiting.md)
* [Versioning](rest-versioning.md)
* [Error Handling](rest-error-handling.md)
Development Tools

30
docs/guide/caching-content.md

@ -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

364
docs/guide/caching-data.md

@ -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).

3
docs/guide/caching-http.md

@ -0,0 +1,3 @@
### HTTP Caching
TBD: http://www.yiiframework.com/doc/guide/1.1/en/caching.page#http-caching

212
docs/guide/caching-overview.md

@ -1,24 +1,41 @@
Caching
=======
> Note: This section is under development.
Caching is a cheap and effective way to improve the performance of a Web application. By storing relatively
static data in cache and serving it from cache when requested, the application saves the time that would be
required to generate the data from scratch.
Caching is a cheap and effective way to improve the performance of a web application. By storing relatively
static data in cache and serving it from cache when requested, the application saves the time required to generate the data from scratch. Caching is one of the best ways to improve the performance of your application, almost mandatory on any large-scale site.
Caching can occur at different levels and places in a Web application. On the server side, at the lower level,
cache may be used to store basic data, such as a list of most recent article information fetched from database;
and at the higher level, cache may be used to store the page content, such as the rendering result of the most
recent articles. On the client side, HTTP caching may be used to keep most recently visited page content in
the browser cache.
Yii supports all these caching mechanisms which are described in the following sections:
Base Concepts
-------------
* [Data caching](caching-data.md)
* [Content caching](caching-content.md)
* [HTTP caching](caching-http.md)
Using cache in Yii involves configuring and accessing a cache application component. The following
application configuration specifies a cache component that uses [memcached](http://memcached.org/) with
two cache servers. Note, this configuration should be done in file located at `@app/config/web.php` alias
in case you're using basic sample application.
## Cache Components
Server-side caching (data caching and content caching) relies on the so-called *cache components*.
Each cache component represents a caching storage and provides a common set of APIs
that may be called to store data in the cache and retrieve it later.
Cache components are usually registered as application components so that they can be globally
configurable and accessible. You may register multiple cache components in a single application.
In most cases you would configure at least the [[yii\base\Application::getCache()|cache]] component
because it is the default cache component being used by most cache-dependent classes, such as [[yii\web\UrlManager]].
The following code shows how to configure the `cache` application component to
use [memcached](http://memcached.org/) with two cache servers:
```php
'components' => [
'cache' => [
'class' => '\yii\caching\MemCache',
'class' => 'yii\caching\MemCache',
'servers' => [
[
'host' => 'server1',
@ -35,28 +52,27 @@ in case you're using basic sample application.
],
```
When the application is running, the cache component can be accessed through `Yii::$app->cache` call.
You may access the `cache` component by using the expression `Yii::$app->cache`.
Yii provides various cache components that can store cached data in different media. The following
is a summary of the available cache components:
The following is a summary of the built-in cache components supported by Yii:
* [[yii\caching\ApcCache]]: uses PHP [APC](http://php.net/manual/en/book.apc.php) extension. This option can be
considered as the fastest one when dealing with cache for a centralized thick application (e.g. one
server, no dedicated load balancers, etc.).
* [[yii\caching\DbCache]]: uses a database table to store cached data. By default, it will create and use a
[SQLite3](http://sqlite.org/) database under the runtime directory. You can explicitly specify a database for
it to use by setting its `db` property.
* [[yii\caching\DbCache]]: uses a database table to store cached data. To use this cache, you must
create a table as specified in [[yii\caching\DbCache::cacheTable]].
* [[yii\caching\DummyCache]]: presents dummy cache that does no caching at all. The purpose of this component
is to simplify the code that needs to check the availability of cache. For example, during development or if
the server doesn't have actual cache support, we can use this cache component. When an actual cache support
is enabled, we can switch to use the corresponding cache component. In both cases, we can use the same
code `Yii::$app->cache->get($key)` to attempt retrieving a piece of data without worrying that
* [[yii\caching\DummyCache]]: serves as a cache placeholder which does no real caching.
The purpose of this component is to simplify the code that needs to check the availability of cache.
For example, during development or if the server doesn't have actual cache support, you may configure
a cache component to use this cache. When an actual cache support is enabled, you can switch to use
the corresponding cache component. In both cases, you may use the same code
`Yii::$app->cache->get($key)` to attempt retrieving data from the cache without worrying that
`Yii::$app->cache` might be `null`.
* [[yii\caching\FileCache]]: uses standard files to store cached data. This is particular suitable
to cache large chunk of data (such as pages).
to cache large chunk of data, such as page content.
* [[yii\caching\MemCache]]: uses PHP [memcache](http://php.net/manual/en/book.memcache.php)
and [memcached](http://php.net/manual/en/book.memcached.php) extensions. This option can be considered as
@ -75,154 +91,6 @@ is a summary of the available cache components:
[Zend Data Cache](http://files.zend.com/help/Zend-Server-6/zend-server.htm#data_cache_component.htm)
as the underlying caching medium.
Tip: because all these cache components extend from the same base class [[yii\caching\Cache]], one can switch to use
a different type of cache without modifying the code that uses cache.
Caching can be used at different levels. At the lowest level, we use cache to store a single piece of data,
such as a variable, and we call this data caching. At the next level, we store in cache a page fragment which
is generated by a portion of a view script. And at the highest level, we store a whole page in cache and serve
it from cache as needed.
In the next few subsections, we elaborate how to use cache at these levels.
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).
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();
```
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
### HTTP Caching
> Because all cache components extend from the same base class [[yii\caching\Cache]], you can switch to use
a different type of cache without modifying the code that uses a cache.
TBD: http://www.yiiframework.com/doc/guide/1.1/en/caching.page#http-caching

15
docs/guide/concept-aliases.md

@ -7,8 +7,7 @@ For example, the alias `@yii` represents the installation path of the Yii framew
the base URL for the currently running Web application.
<a name="defining-aliases"></a>
Defining Aliases
Defining Aliases <a name="defining-aliases"></a>
----------------
You can call [[Yii::setAlias()]] to define an alias for a given file path or URL. For example,
@ -50,8 +49,7 @@ return [
```
<a name="resolving-aliases"></a>
Resolving Aliases
Resolving Aliases <a name="resolving-aliases"></a>
-----------------
You can call [[Yii::getAlias()]] to resolve a root alias into the file path or URL it is representing.
@ -83,8 +81,7 @@ Yii::getAlias('@foo/bar/file.php'); // displays: /path2/bar/file.php
If `@foo/bar` is not defined as a root alias, the last statement would display `/path/to/foo/bar/file.php`.
<a name="using-aliases"></a>
Using Aliases
Using Aliases <a name="using-aliases"></a>
-------------
Aliases are recognized in many places in Yii without the need of calling [[Yii::getAlias()]] to convert
@ -103,8 +100,7 @@ $cache = new FileCache([
Please pay attention to the API documentation to see if a property or method parameter supports aliases.
<a name="predefined-aliases"></a>
Predefined Aliases
Predefined Aliases <a name="predefined-aliases"></a>
------------------
Yii predefines a set of aliases to ease the need of referencing commonly used file paths and URLs.
@ -122,8 +118,7 @@ while the rest of the aliases are defined in the application constructor when ap
[configuration](concept-configurations.md).
<a name="extension-aliases"></a>
Extension Aliases
Extension Aliases <a name="extension-aliases"></a>
-----------------
An alias is automatically defined for each [extension](structure-extensions.md) that is installed via Composer.

12
docs/guide/concept-autoloading.md

@ -10,8 +10,7 @@ The autoloader is installed when you include the `Yii.php` file.
mind that the content we are describing here applies to autoloading of interfaces and traits as well.
<a name="using-yii-autoloader"></a>
Using the Yii Autoloader
Using the Yii Autoloader <a name="using-yii-autoloader"></a>
------------------------
To make use of the Yii class autoloader, you should follow two simple rules when creating and naming your classes:
@ -39,8 +38,7 @@ put the front-end classes under the namespace `frontend` while the back-end clas
allow these classes to be autoloaded by the Yii autoloader.
<a name="class-map"></a>
Class Map
Class Map <a name="class-map"></a>
---------
The Yii class autoloader supports the *class map* feature which maps class names to the corresponding class file paths.
@ -58,8 +56,7 @@ Yii::$classMap['foo\bar\MyClass'] = 'path/to/MyClass.php';
[bootstrapping](runtime-bootstrapping.md) process so that the map is ready before your classes are used.
<a name="using-other-autoloaders"></a>
Using Other Autoloaders
Using Other Autoloaders <a name="using-other-autoloaders"></a>
-----------------------
Because Yii embraces Composer as a package dependency manager, it is recommended that you also install
@ -85,8 +82,7 @@ to be autoloadable.
and include it in your [entry script](structure-entry-scripts.md).
<a name="autoloading-extension-classes"></a>
Autoloading Extension Classes
Autoloading Extension Classes <a name="autoloading-extension-classes"></a>
-----------------------------
The Yii autoloader is capable of autoloading [extension](structure-extensions.md) classes. The sole requirement

21
docs/guide/concept-behaviors.md

@ -10,8 +10,7 @@ can respond to the [events](concept-events.md) triggered by the component so tha
code execution of the component.
<a name="using-behaviors"></a>
Using Behaviors
Using Behaviors <a name="using-behaviors"></a>
---------------
To use a behavior, you first need to attach it to a [[yii\base\Component|component]]. We will describe how to
@ -55,8 +54,7 @@ $behaviors = $component->getBehaviors();
```
<a name="attaching-behaviors"></a>
Attaching Behaviors
Attaching Behaviors <a name="attaching-behaviors"></a>
-------------------
You can attach a behavior to a [[yii\base\Component|component]] either statically or dynamically. The former
@ -132,8 +130,7 @@ You may also attach behaviors through [configurations](concept-configurations.md
refer to the [Configurations](concept-configurations.md#configuration-format) section.
<a name="detaching-behaviors"></a>
Detaching Behaviors
Detaching Behaviors <a name="detaching-behaviors"></a>
-------------------
To detach a behavior, you can call [[yii\base\Component::detachBehavior()]] with the name associated with the behavior:
@ -149,8 +146,7 @@ $component->detachBehaviors();
```
<a name="defining-behaviors"></a>
Defining Behaviors
Defining Behaviors <a name="defining-behaviors"></a>
------------------
To define a behavior, create a class by extending from [[yii\base\Behavior]] or its child class. For example,
@ -235,8 +231,7 @@ function ($event) {
```
<a name="using-timestamp-behavior"></a>
Using `TimestampBehavior`
Using `TimestampBehavior` <a name="using-timestamp-behavior"></a>
-------------------------
To wrap up, let's take a look at [[yii\behaviors\TimestampBehavior]] - a behavior that supports automatically
@ -294,8 +289,7 @@ $user->touch('login_time');
```
<a name="comparison-with-traits"></a>
Comparison with Traits
Comparison with Traits <a name="comparison-with-traits"></a>
----------------------
While behaviors are similar to [traits](http://www.php.net/traits) in that they both "inject" their
@ -322,8 +316,7 @@ Name conflict caused by different traits requires you to manually resolve it by
properties or methods.
<a name="pros-for-traits"></a>
### Pros for Traits
### Pros for Traits <a name="pros-for-traits"></a>
Traits are much more efficient than behaviors because behaviors are objects which take both time and memory.

21
docs/guide/concept-configurations.md

@ -35,8 +35,7 @@ Yii::configure($object, $config);
Note that in this case, the configuration should not contain the `class` element.
<a name="configuration-format"></a>
Configuration Format
Configuration Format <a name="configuration-format"></a>
--------------------
The format of a configuration can be formally described as follows,
@ -80,8 +79,7 @@ Below is an example showing a configuration with property initial values, event
```
<a name="using-configurations"></a>
Using Configurations
Using Configurations <a name="using-configurations"></a>
--------------------
Configurations are used in many places in Yii. At the beginning of this section, we have shown how to use
@ -89,8 +87,7 @@ create an object according to a configuration by using [[Yii::createObject()]].
describe application configurations and widget configurations - two major usages of configurations.
<a name="application-configurations"></a>
### Application Configurations
### Application Configurations <a name="application-configurations"></a>
Configuration for an [application](structure-applications.md) is probably one of the most complex configurations.
This is because the [[yii\web\Application|application]] class has a lot of configurable properties and events.
@ -141,8 +138,7 @@ For more details about configuring the `components` property of an application c
in the [Applications](structure-applications.md) section and the [Service Locator](concept-service-locator.md) section.
<a name="widget-configurations"></a>
### Widget Configurations
### Widget Configurations <a name="widget-configurations"></a>
When using [widgets](structure-widgets.md), you often need to use configurations to customize the widget properties.
Both of the [[yii\base\Widget::widget()]] and [[yii\base\Widget::beginWidget()]] methods can be used to create
@ -167,8 +163,7 @@ The `items` property is also configured with menu items to be displayed.
Note that because the class name is already given, the configuration array should NOT have the `class` key.
<a name="configuration-files"></a>
Configuration Files
Configuration Files <a name="configuration-files"></a>
-------------------
When a configuration is very complex, a common practice is to store it in one or multiple PHP files, known as
@ -222,8 +217,7 @@ $config = require('path/to/web.php');
```
<a name="default-configurations"></a>
Default Configurations
Default Configurations <a name="default-configurations"></a>
----------------------
The [[Yii::createObject()]] method is implemented based on a [dependency injection container](concept-di-container.md).
@ -244,8 +238,7 @@ Without using default configurations, you would have to configure `maxButtonCoun
link pagers.
<a name="environment-constants"></a>
Environment Constants
Environment Constants <a name="environment-constants"></a>
---------------------
Configurations often vary according to the environment in which an application runs. For example,

29
docs/guide/concept-di-container.md

@ -6,8 +6,7 @@ all their dependent objects. [Martin's article](http://martinfowler.com/articles
explained why DI container is useful. Here we will mainly explain the usage of the DI container provided by Yii.
<a name="dependency-injection"></a>
Dependency Injection
Dependency Injection <a name="dependency-injection"></a>
--------------------
Yii provides the DI container feature through the class [[yii\di\Container]]. It supports the following kinds of
@ -18,8 +17,7 @@ dependency injection:
* PHP callable injection.
<a name="constructor-injection"></a>
### Constructor Injection
### Constructor Injection <a name="constructor-injection"></a>
The DI container supports constructor injection with the help of type hints for constructor parameters.
The type hints tell the container which classes or interfaces are dependent when it is used to create a new object.
@ -41,8 +39,7 @@ $foo = new Foo($bar);
```
<a name="setter-and-property-injection"></a>
### Setter and Property Injection
### Setter and Property Injection <a name="setter-and-property-injection"></a>
Setter and property injection is supported through [configurations](concept-configurations.md).
When registering a dependency or when creating a new object, you can provide a configuration which
@ -76,8 +73,7 @@ $container->get('Foo', [], [
```
<a name="php-callable-injection"></a>
### PHP Callable Injection
### PHP Callable Injection <a name="php-callable-injection"></a>
In this case, the container will use a registered PHP callable to build new instances of a class.
The callable is responsible to resolve the dependencies and inject them appropriately to the newly
@ -92,8 +88,7 @@ $foo = $container->get('Foo');
```
<a name="registering-dependencies"></a>
Registering Dependencies
Registering Dependencies <a name="registering-dependencies"></a>
------------------------
You can use [[yii\di\Container::set()]] to register dependencies. The registration requires a dependency name
@ -162,8 +157,7 @@ $container->setSingleton('yii\db\Connection', [
```
<a name="resolving-dependencies"></a>
Resolving Dependencies
Resolving Dependencies <a name="resolving-dependencies"></a>
----------------------
Once you have registered dependencies, you can use the DI container to create new objects,
@ -252,9 +246,8 @@ $lister = new UserLister($finder);
```
<a name="practical-usages"></a>
Practical Usages
----------------
Practical Usage <a name="practical-usage"></a>
---------------
Yii creates a DI container when you include the `Yii.php` file in the [entry script](structure-entry-scripts.md)
of your application. The DI container is accessible via [[Yii::$container]]. When you call [[Yii::createObject()]],
@ -315,8 +308,7 @@ Now if you access the controller again, an instance of `app\components\BookingSe
created and injected as the 3rd parameter to the controller's constructor.
<a name="when-to-register-dependencies"></a>
When to Register Dependencies
When to Register Dependencies <a name="when-to-register-dependencies"></a>
-----------------------------
Because dependencies are needed when new objects are being created, their registration should be done
@ -328,8 +320,7 @@ as early as possible. The followings are the recommended practices:
in the bootstrap class of the extension.
<a name="summary"></a>
Summary
Summary <a name="summary"></a>
-------
Both dependency injection and [service locator](concept-service-locator.md) are popular design patterns

18
docs/guide/concept-events.md

@ -10,8 +10,7 @@ Yii introduces a base class called [[yii\base\Component]] to support events. If
events, it should extend from [[yii\base\Component]] or its child class.
<a name="triggering-events"></a>
Triggering Events
Triggering Events <a name="triggering-events"></a>
-----------------
Events are triggered by calling the [[yii\base\Component::trigger()]] method. The method requires an *event name*
@ -77,8 +76,7 @@ When the [[yii\base\Component::trigger()]] method is called, it will call handle
the named event.
<a name="event-handlers"></a>
Event Handlers
Event Handlers <a name="event-handlers"></a>
--------------
An event handler is a [PHP callback](http://www.php.net/manual/en/language.types.callable.php) that gets executed
@ -104,8 +102,7 @@ Through the `$event` parameter, an event handler may get the following informati
- [[yii\base\Event::data|custom data]]: the data that is provided when attaching the event handler (to be explained shortly).
<a name="attaching-event-handlers"></a>
Attaching Event Handlers
Attaching Event Handlers <a name="attaching-event-handlers"></a>
------------------------
You can attach a handler to an event by calling the [[yii\base\Component::on()]] method. For example,
@ -166,8 +163,7 @@ $foo->on(Foo::EVENT_HELLO, function ($event) {
```
<a name="detaching-event-handlers"></a>
Detaching Event Handlers
Detaching Event Handlers <a name="detaching-event-handlers"></a>
------------------------
To detach a handler from an event, call the [[yii\base\Component::off()]] method. For example,
@ -197,8 +193,7 @@ $foo->off(Foo::EVENT_HELLO);
```
<a name="class-level-event-handlers"></a>
Class-Level Event Handlers
Class-Level Event Handlers <a name="class-level-event-handlers"></a>
--------------------------
In the above subsections, we have described how to attach a handler to an event at *instance level*.
@ -256,8 +251,7 @@ Event::off(Foo::className(), Foo::EVENT_HELLO);
```
<a name="global-events"></a>
Global Events
Global Events <a name="global-events"></a>
-------------
The so-called *global event* is actually a trick based on the event mechanism described above.

4
docs/guide/db-active-record.md

@ -930,7 +930,7 @@ class Feature extends \yii\db\ActiveRecord
public function getProduct()
{
return $this->hasOne(Product::className(), ['product_id' => 'id']);
return $this->hasOne(Product::className(), ['id' => 'product_id']);
}
}
@ -940,7 +940,7 @@ class Product extends \yii\db\ActiveRecord
public function getFeatures()
{
return $this->hasMany(Feature::className(), ['id' => 'product_id']);
return $this->hasMany(Feature::className(), ['product_id' => 'id']);
}
}
```

401
docs/guide/images/application-lifecycle.graphml

@ -15,7 +15,6 @@
<graph edgedefault="directed" id="G">
<data key="d7"/>
<node id="n0">
<data key="d5"/>
<data key="d6">
<y:SVGNode>
<y:Geometry height="70.13700103759766" width="56.558998107910156" x="198.38633947503078" y="411.5661153793335"/>
@ -29,7 +28,7 @@
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="-0.5" nodeRatioX="0.0" nodeRatioY="0.5" offsetX="0.0" offsetY="4.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="custom" textColor="#000000" visible="true" width="81.6484375" x="-85.6484375" y="25.717914581298828">user's browser<y:LabelModel>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="29.16015625" x="-33.16015625" y="26.002094268798828">user<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
@ -44,13 +43,12 @@
</data>
</node>
<node id="n1">
<data key="d5"/>
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="119.0" x="890.844499300604" y="604.0870225429535"/>
<y:Geometry height="30.0" width="119.0" x="815.0653619766235" y="644.6271178722382"/>
<y:Fill color="#FF99CC" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="custom" textColor="#000000" visible="true" width="36.68359375" x="41.158203125" y="5.6494140625">model<y:LabelModel>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="40.28125" x="39.359375" y="5.93359375">model<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
@ -62,13 +60,12 @@
</data>
</node>
<node id="n2">
<data key="d5"/>
<data key="d6">
<y:SVGNode>
<y:Geometry height="46.887996673583984" width="39.527000427246094" x="930.5809990869809" y="513.1990258693695"/>
<y:Geometry height="46.887996673583984" width="39.527000427246094" x="854.8018617630005" y="544.9831195354463"/>
<y:Fill color="#CCCCFF" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="custom" textColor="#000000" visible="true" width="55.369140625" x="-7.921070098876953" y="-30.723247528076172">Database<y:LabelModel>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="56.27734375" x="-8.375171661376953" y="-30.154888153076172">database<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
@ -91,13 +88,12 @@
</data>
</node>
<node id="n3">
<data key="d5"/>
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="119.0" x="890.844499300604" y="653.5870225429535"/>
<y:Geometry height="30.0" width="119.0" x="815.0653619766235" y="721.1271178722382"/>
<y:Fill color="#ADF4A6" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="custom" textColor="#000000" visible="true" width="28.005859375" x="45.4970703125" y="5.6494140625">view<y:LabelModel>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="29.611328125" x="44.6943359375" y="5.93359375">view<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
@ -108,45 +104,26 @@
</y:ShapeNode>
</data>
</node>
<node id="n4">
<data key="d5"/>
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="119.0" x="890.844499300604" y="738.0870225429535"/>
<y:Fill color="#99CCFF" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="custom" textColor="#000000" visible="true" width="44.6875" x="37.15625" y="5.6494140625">widgets<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:Shape type="roundrectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n5" yfiles.foldertype="group">
<node id="n4" yfiles.foldertype="group">
<data key="d4"/>
<data key="d5"/>
<data key="d6">
<y:ProxyAutoBoundsNode>
<y:Realizers active="0">
<y:GroupNode>
<y:Geometry height="368.8577250242233" width="519.1941337585449" x="313.2978515625" y="411.5661153793335"/>
<y:Geometry height="376.3268349409104" width="219.27949905395508" x="560.4919853210449" y="408.6371007919311"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#99CCFF" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="22.37646484375" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="519.1941337585449" x="0.0" y="0.0">Controller</y:NodeLabel>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#99CCFF" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="219.27949905395508" x="0.0" y="0.0">controller</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="27" leftF="26.788239002227783" right="13" rightF="13.0" top="0" topF="0.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="2" leftF="1.7335329055786133" right="0" rightF="0.0" top="0" topF="0.0"/>
</y:GroupNode>
<y:GroupNode>
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
<y:Geometry height="50.0" width="50.0" x="313.2978515625" y="412.2765645980835"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="22.37646484375" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="59.02685546875" x="-4.513427734375" y="0.0">Folder 1</y:NodeLabel>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 1</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
@ -155,15 +132,14 @@
</y:Realizers>
</y:ProxyAutoBoundsNode>
</data>
<graph edgedefault="directed" id="n5:">
<node id="n5::n0">
<data key="d5"/>
<graph edgedefault="directed" id="n4:">
<node id="n4::n0">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="249.5270004272461" x="466.68780612945557" y="448.9425802230835"/>
<y:Geometry height="30.0" width="150.55899810791016" x="596.2124862670898" y="445.3031164169311"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="custom" textColor="#000000" visible="true" width="192.126953125" x="28.700023651123047" y="5.6494140625">which method should be executed?<y:LabelModel>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="78.888671875" x="35.83516311645508" y="5.93359375">create action<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
@ -174,14 +150,13 @@
</y:ShapeNode>
</data>
</node>
<node id="n5::n1">
<data key="d5"/>
<node id="n4::n1">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="45.0" width="159.91864204406738" x="511.4919853210449" y="504.9133868217468"/>
<y:Geometry height="45.0" width="159.91864204406738" x="591.5326642990112" y="521.8166599273682"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="custom" textColor="#000000" visible="true" width="78.033203125" x="40.94271945953369" y="13.1494140625">execute filters<y:LabelModel>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="88.392578125" x="35.76303195953369" y="13.43359375">perform filters<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
@ -192,27 +167,26 @@
</y:ShapeNode>
</data>
</node>
<node id="n5::n2" yfiles.foldertype="group">
<node id="n4::n2" yfiles.foldertype="group">
<data key="d4"/>
<data key="d5"/>
<data key="d6">
<y:ProxyAutoBoundsNode>
<y:Realizers active="0">
<y:GroupNode>
<y:Geometry height="198.71328270435333" width="278.0" x="526.4919853210449" y="566.7105576992035"/>
<y:Geometry height="162.00283348560333" width="187.54596614837646" x="577.2255182266235" y="607.9611022472382"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#99CCFF" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="22.37646484375" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="278.0" x="0.0" y="0.0">action</y:NodeLabel>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#99CCFF" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="187.54596614837646" x="0.0" y="0.0">action</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="24" leftF="24.195820808410645" right="18" rightF="17.804179191589355" top="0" topF="0.0"/>
<y:BorderInsets bottom="4" bottomF="3.8368178606033325" left="4" leftF="3.9869680404663086" right="3" rightF="3.0" top="0" topF="0.0"/>
</y:GroupNode>
<y:GroupNode>
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="22.37646484375" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="59.02685546875" x="-4.513427734375" y="0.0">Folder 3</y:NodeLabel>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 3</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
@ -221,15 +195,14 @@
</y:Realizers>
</y:ProxyAutoBoundsNode>
</data>
<graph edgedefault="directed" id="n5::n2:">
<node id="n5::n2::n0">
<data key="d5"/>
<graph edgedefault="directed" id="n4::n2:">
<node id="n4::n2::n0">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="206.0" x="565.6878061294556" y="604.0870225429535"/>
<y:Geometry height="30.0" width="150.55899810791016" x="596.2124862670898" y="644.6271178722382"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="custom" textColor="#000000" visible="true" width="107.388671875" x="49.3056640625" y="5.6494140625">Get model instance<y:LabelModel>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="69.09765625" x="40.73067092895508" y="5.93359375">load model<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
@ -240,14 +213,13 @@
</y:ShapeNode>
</data>
</node>
<node id="n5::n2::n1">
<data key="d5"/>
<node id="n4::n2::n1">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="206.0" x="565.6878061294556" y="653.5870225429535"/>
<y:Geometry height="30.0" width="150.55899810791016" x="596.2124862670898" y="721.1271178722382"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="custom" textColor="#000000" visible="true" width="179.41796875" x="13.291015625" y="5.6494140625">Render view with model instance<y:LabelModel>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="71.599609375" x="39.47969436645508" y="5.93359375">render view<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
@ -258,54 +230,17 @@
</y:ShapeNode>
</data>
</node>
<node id="n5::n2::n2">
<data key="d5"/>
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="206.0" x="565.6878061294556" y="720.4238404035568"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="custom" textColor="#000000" visible="true" width="186.771484375" x="9.6142578125" y="7.298828125">Embed rendering result into layout<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.5" nodeRatioX="0.0" nodeRatioY="0.5" offsetX="0.0" offsetY="-4.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
</graph>
</node>
<node id="n5::n3">
<data key="d5"/>
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="140.99198532104492" x="355.0860905647278" y="720.4238404035568"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="custom" textColor="#000000" visible="true" width="84.6953125" x="28.14833641052246" y="5.6494140625">Form response<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
</graph>
</node>
<node id="n6">
<data key="d5"/>
<node id="n5">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="45.0" width="96.0" x="189.47636032104492" y="720.4238404035568"/>
<y:Geometry height="45.0" width="96.0" x="189.47636032104495" y="713.6271178722382"/>
<y:Fill color="#FFCC99" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="custom" textColor="#000000" visible="true" width="53.365234375" x="21.3173828125" y="13.1494140625">response<y:LabelModel>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="56.88671875" x="19.556640625" y="13.43359375">response<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
@ -316,40 +251,13 @@
</y:ShapeNode>
</data>
</node>
<node id="n7">
<data key="d5"/>
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="119.0" x="890.844499300604" y="262.00096702575684"/>
<y:Fill color="#FFCC99" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="custom" textColor="#000000" visible="true" width="44.025390625" x="37.4873046875" y="5.6494140625">request<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:Shape type="roundrectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n8">
<data key="d5"/>
<node id="n6">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="119.0" x="890.844499300604" y="322.00096702575684"/>
<y:Geometry height="30.0" width="119.0" x="815.0653619766235" y="262.00096702575684"/>
<y:Fill color="#FFCC99" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="custom" textColor="#000000" visible="true" width="64.697265625" x="27.1513671875" y="5.6494140625">urlManager<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="57.5" y="13.0">
<y:LabelModel>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="47.88671875" x="35.556640625" y="5.93359375">request<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
@ -360,27 +268,26 @@
</y:ShapeNode>
</data>
</node>
<node id="n9" yfiles.foldertype="group">
<node id="n7" yfiles.foldertype="group">
<data key="d4"/>
<data key="d5"/>
<data key="d6">
<y:ProxyAutoBoundsNode>
<y:Realizers active="0">
<y:GroupNode>
<y:Geometry height="144.132230758667" width="321.0" x="511.4919853210449" y="222.86873626708984"/>
<y:Geometry height="143.421781539917" width="219.27949905395508" x="560.4919853210449" y="223.57918548583984"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#FFCC00" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="22.37646484375" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="321.0" x="0.0" y="0.0">Application</y:NodeLabel>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#FFCC00" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="219.27949905395508" x="0.0" y="0.0">application</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="13" rightF="13.0" top="2" topF="1.7557659149169922"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="21" leftF="20.720500946044922" right="18" rightF="18.0" top="2" topF="1.7557659149169922"/>
</y:GroupNode>
<y:GroupNode>
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="22.37646484375" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="59.02685546875" x="-4.513427734375" y="0.0">Folder 2</y:NodeLabel>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 2</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
@ -389,15 +296,14 @@
</y:Realizers>
</y:ProxyAutoBoundsNode>
</data>
<graph edgedefault="directed" id="n9:">
<node id="n9::n0">
<data key="d5"/>
<graph edgedefault="directed" id="n7:">
<node id="n7::n0">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="150.55899810791016" x="590.2124862670898" y="262.00096702575684"/>
<y:Geometry height="30.0" width="150.55899810791016" x="596.2124862670898" y="262.00096702575684"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="custom" textColor="#000000" visible="true" width="89.3828125" x="30.588092803955078" y="5.6494140625">Get request info<y:LabelModel>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="80.1484375" x="35.20528030395508" y="5.93359375">resolve route<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
@ -408,14 +314,13 @@
</y:ShapeNode>
</data>
</node>
<node id="n9::n1">
<data key="d5"/>
<node id="n7::n1">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="278.0" x="526.4919853210449" y="322.00096702575684"/>
<y:Geometry height="30.0" width="150.55899810791016" x="596.2124862670898" y="322.00096702575684"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="custom" textColor="#000000" visible="true" width="248.119140625" x="14.9404296875" y="5.6494140625">Which controller/action was requested? Run it<y:LabelModel>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="99.607421875" x="25.475788116455078" y="5.93359375">create controller<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
@ -428,17 +333,16 @@
</node>
</graph>
</node>
<node id="n10" yfiles.foldertype="group">
<node id="n8" yfiles.foldertype="group">
<data key="d4"/>
<data key="d5"/>
<data key="d6">
<y:ProxyAutoBoundsNode>
<y:Realizers active="0">
<y:GroupNode>
<y:Geometry height="142.37646484375" width="159.91864204406738" x="313.2978515625" y="224.62450218200684"/>
<y:Geometry height="141.666015625" width="159.91864204406738" x="313.2978515625" y="225.33495140075684"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#FFCC00" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="22.37646484375" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="159.91864204406738" x="0.0" y="0.0">index.php</y:NodeLabel>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#FFCC00" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="159.91864204406738" x="0.0" y="0.0">index.php</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
@ -448,7 +352,7 @@
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="22.37646484375" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="59.02685546875" x="-4.513427734375" y="0.0">Folder 4</y:NodeLabel>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 4</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
@ -457,15 +361,14 @@
</y:Realizers>
</y:ProxyAutoBoundsNode>
</data>
<graph edgedefault="directed" id="n10:">
<node id="n10::n0">
<data key="d5"/>
<graph edgedefault="directed" id="n8:">
<node id="n8::n0">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="129.0" x="329.2164936065674" y="262.00096702575684"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="custom" textColor="#000000" visible="true" width="66.05078125" x="31.474609375" y="5.6494140625">Load config<y:LabelModel>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="94.673828125" x="17.1630859375" y="5.93359375">load app config<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
@ -476,14 +379,13 @@
</y:ShapeNode>
</data>
</node>
<node id="n10::n1">
<data key="d5"/>
<node id="n8::n1">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="129.0" x="329.2164936065674" y="322.00096702575684"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="custom" textColor="#000000" visible="true" width="121.404296875" x="3.7978515625" y="5.6494140625">Create/run application<y:LabelModel>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="91.8203125" x="18.58984375" y="5.93359375">run application<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
@ -497,7 +399,6 @@
</graph>
</node>
<edge id="e0" source="n2" target="n1">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
@ -507,33 +408,13 @@
</y:PolyLineEdge>
</data>
</edge>
<edge id="e1" source="n4" target="n3">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" backgroundColor="#FFFFFF" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasLineColor="false" height="18.701171875" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="10.673828125" x="-5.336897183771043" y="-36.62330460548401">9<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="center" ratio="0.5" segment="0"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e2" source="n6" target="n0">
<data key="d9"/>
<edge id="e1" source="n5" target="n0">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="-10.81052179205907" sy="-22.46484374999998" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="4.0" x="28.00000600945461" y="-121.36211973428726">
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="4.0" x="28.000006009454637" y="-117.9645825624466">
<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
@ -542,7 +423,7 @@
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
<y:EdgeLabel alignment="center" backgroundColor="#FFFFFF" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasLineColor="false" height="18.701171875" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="17.34765625" x="-8.673822115545391" y="-128.71270567178726">11<y:LabelModel>
<y:EdgeLabel alignment="center" backgroundColor="#FFFFFF" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="19.17578125" x="-9.587884615545363" y="-125.0309888124466">11<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
@ -554,18 +435,17 @@
</y:PolyLineEdge>
</data>
</edge>
<edge id="e3" source="n7" target="n9::n0">
<data key="d9"/>
<edge id="e2" source="n6" target="n7::n0">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" backgroundColor="#FFFFFF" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasLineColor="false" height="18.701171875" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="10.673828125" x="-48.37847056454177" y="-9.350595474243164">3<y:LabelModel>
<y:EdgeLabel alignment="center" backgroundColor="#FFFFFF" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="11.587890625" x="-39.89712858200073" y="-9.066415786743164">3<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="center" ratio="0.2527032372366451" segment="-1"/>
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="center" ratio="0.5" segment="0"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
@ -573,50 +453,29 @@
</y:PolyLineEdge>
</data>
</edge>
<edge id="n9::e0" source="n9::n0" target="n9::n1">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e4" source="n8" target="n9::n1">
<data key="d9"/>
<edge id="n7::e0" source="n7::n0" target="n7::n1">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" backgroundColor="#FFFFFF" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasLineColor="false" height="18.701171875" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="10.673828125" x="-48.51822009151931" y="-9.350595474243164">4<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="center" ratio="0.5" segment="0"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="n5::e0" source="n5::n1" target="n5::n2">
<data key="d9"/>
<edge id="e3" source="n0" target="n8">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="79.98824393609539" sy="0.0" tx="26.96733570098877" ty="-99.38389617204666">
<y:Point x="692.4593210220337" y="527.4133868217468"/>
<y:Path sx="0.0" sy="0.0" tx="-79.98824393609539" ty="0.0">
<y:Point x="226.66583852898586" y="295.81273460388184"/>
</y:Path>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" backgroundColor="#F5F5F5" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasLineColor="false" height="18.701171875" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="10.673828125" x="15.682831764221191" y="3.1227022409439087">6<y:LabelModel>
<y:EdgeLabel alignment="center" backgroundColor="#FFFFFF" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="11.587890625" x="-5.793939303045391" y="-76.08932256698608">1<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="center" ratio="0.5" segment="-1"/>
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="center" ratio="0.5" segment="0"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
@ -624,20 +483,17 @@
</y:PolyLineEdge>
</data>
</edge>
<edge id="e5" source="n5::n1" target="n6">
<data key="d9"/>
<edge id="e4" source="n7::n1" target="n4">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="-79.98824393609539" sy="0.0" tx="24.24112858184396" ty="-1.4999999999999991">
<y:Point x="261.7174889028889" y="530.8841934204102"/>
</y:Path>
<y:Path sx="0.0" sy="0.0" tx="2.408647025755731" ty="-181.21658369302781"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" backgroundColor="#F7F7F7" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasLineColor="false" height="18.701171875" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="80.716796875" x="-131.52712170688778" y="-8.083584922098225">access denied<y:LabelModel>
<y:EdgeLabel alignment="center" backgroundColor="#FFFFFF" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="11.587890625" x="-5.416119621118469" y="19.25165109634395">4<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="center" ratio="0.22424474821352752" segment="0"/>
<y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="30.0" distanceToCenter="false" position="center" ratio="0.5" segment="0"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
@ -645,8 +501,7 @@
</y:PolyLineEdge>
</data>
</edge>
<edge id="n5::e1" source="n5::n0" target="n5::n1">
<data key="d9"/>
<edge id="n8::e0" source="n8::n0" target="n8::n1">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
@ -656,16 +511,13 @@
</y:PolyLineEdge>
</data>
</edge>
<edge id="e6" source="n0" target="n10">
<data key="d9"/>
<edge id="e5" source="n3" target="n4::n2::n1">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="-79.98824393609539" ty="0.0">
<y:Point x="226.66583852898586" y="295.81273460388184"/>
</y:Path>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" backgroundColor="#FFFFFF" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasLineColor="false" height="18.701171875" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="10.673828125" x="-5.336908053045391" y="-76.51559209823608">1<y:LabelModel>
<y:EdgeLabel alignment="center" backgroundColor="#FFFFFF" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="11.587890625" x="-39.89712858200073" y="-9.06642460823059">9<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
@ -677,18 +529,17 @@
</y:PolyLineEdge>
</data>
</edge>
<edge id="e7" source="n9::n1" target="n5">
<data key="d9"/>
<edge id="e6" source="n4::n2::n1" target="n5">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="92.59706687927246" ty="-184.39778308760094"/>
<y:Path sx="0.0" sy="-2.2737367544323206E-13" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" backgroundColor="#FFFFFF" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasLineColor="false" height="18.701171875" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="10.673828125" x="-5.336933135986214" y="20.431978702545166">5<y:LabelModel>
<y:EdgeLabel alignment="center" backgroundColor="#FFFFFF" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="19.17578125" x="-102.64279838562021" y="-9.066424608230818">10<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="center" ratio="0.5" segment="0"/>
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="center" ratio="0.2786124840137136" segment="-1"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
@ -696,47 +547,69 @@
</y:PolyLineEdge>
</data>
</edge>
<edge id="n10::e0" source="n10::n0" target="n10::n1">
<data key="d9"/>
<edge id="e7" source="n8::n1" target="n7">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:Path sx="0.0" sy="0.0" tx="-109.63961141576266" ty="42.066115379333496"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" backgroundColor="#FFFFFF" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="11.587890625" x="45.3438024520874" y="-8.957472195132482">2<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="center" ratio="0.5" segment="0"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e8" source="n1" target="n5::n2::n0">
<data key="d9"/>
<edge id="e8" source="n1" target="n4::n2::n0">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" backgroundColor="#FFFFFF" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="11.587890625" x="-39.89712858200073" y="-9.06642460823059">8<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="center" ratio="0.5" segment="0"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="n5::n2::e0" source="n5::n2::n0" target="n5::n2::n1">
<data key="d9"/>
<edge id="e9" source="n4::n1" target="n5">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:Path sx="-79.9882439360951" sy="2.2737367544323206E-13" tx="24.24112858184396" ty="-1.4999999999999991">
<y:Point x="261.71748890288893" y="544.3166599273684"/>
</y:Path>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" backgroundColor="#FFFFFF" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="11.587890625" x="-91.39898898324532" y="-9.066396713256609">6<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="30.0" distanceToCenter="true" position="center" ratio="0.23459266172695797" segment="0"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e9" source="n3" target="n5::n2::n1">
<data key="d9"/>
<edge id="n4::e0" source="n4::n0" target="n4::n1">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" backgroundColor="#FFFFFF" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasLineColor="false" height="18.701171875" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="24.015625" x="-29.000532437677293" y="-9.350599527359009">7, 8<y:LabelModel>
<y:EdgeLabel alignment="center" backgroundColor="#F5F5F5" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="11.587890625" x="-5.793964385986328" y="4.999985313415493">5<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
@ -748,18 +621,17 @@
</y:PolyLineEdge>
</data>
</edge>
<edge id="n5::n2::e1" source="n5::n2::n1" target="n5::n2::n2">
<data key="d9"/>
<edge id="n4::e1" source="n4::n1" target="n4::n2">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="-81.01828954355172"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" backgroundColor="#E1E1E1" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasLineColor="false" height="18.701171875" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="17.34765625" x="-8.673827171325684" y="4.999986410140991">10<y:LabelModel>
<y:EdgeLabel alignment="center" backgroundColor="#F5F5F5" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="11.587890625" x="-5.903311505104057" y="5.037729263305664">7<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="center" ratio="0.0" segment="0"/>
<y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="30.0" distanceToCenter="false" position="center" ratio="0.0" segment="0"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
@ -767,8 +639,7 @@
</y:PolyLineEdge>
</data>
</edge>
<edge id="n5::e2" source="n5::n2::n2" target="n5::n3">
<data key="d9"/>
<edge id="n4::n2::e0" source="n4::n2::n0" target="n4::n2::n1">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
@ -778,36 +649,6 @@
</y:PolyLineEdge>
</data>
</edge>
<edge id="e10" source="n5::n3" target="n6">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="47.98457717895508" ty="-7.5"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e11" source="n10::n1" target="n9">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="-160.49979782104492" ty="42.066115379333496"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" backgroundColor="#FFFFFF" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasLineColor="false" height="18.701171875" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="10.673828125" x="21.300833702087402" y="-9.350595474243164">2<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="center" ratio="0.5" segment="0"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
</graph>
<data key="d0">
<y:Resources>

BIN
docs/guide/images/application-lifecycle.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 39 KiB

BIN
docs/guide/images/start-app-installed.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

BIN
docs/guide/images/start-country-list.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
docs/guide/images/start-entry-confirmation.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
docs/guide/images/start-form-validation.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
docs/guide/images/start-gii-country-grid.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

BIN
docs/guide/images/start-gii-country-update.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

BIN
docs/guide/images/start-gii-crud.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
docs/guide/images/start-gii-model-preview.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

BIN
docs/guide/images/start-gii-model.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
docs/guide/images/start-gii.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

BIN
docs/guide/images/start-hello-world.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

5
docs/guide/intro-upgrade-from-v1.md

@ -1,8 +1,9 @@
Upgrading from Version 1.1
==========================
There are many differences between versions 1.1 and 2.0 of the Yii as the framework was completely rewritten for 2.0.
As a result, upgrading from version 1.1 is not as trivial as upgrading between minor versions. In this guide you'll find the major differences between the two versions.
There are many differences between versions 1.1 and 2.0 of Yii as the framework was completely rewritten for 2.0.
As a result, upgrading from version 1.1 is not as trivial as upgrading between minor versions. In this guide you'll
find the major differences between the two versions.
Please note that Yii 2.0 introduces more new features than are covered in this summary. It is highly recommended
that you read through the whole definitive guide to learn about them all. Chances are that

9
docs/guide/output-data-providers.md

@ -4,7 +4,7 @@ Data providers
> Note: This section is under development.
Data provider abstracts data set via [[yii\data\DataProviderInterface]] and handles pagination and sorting.
It can be used by [grids](data-grid.md), [lists and other data widgets](data-widgets.md).
It can be used by [grids, lists and other data widgets](output-data-widgets.md).
In Yii there are three built-in data providers: [[yii\data\ActiveDataProvider]], [[yii\data\ArrayDataProvider]] and
[[yii\data\SqlDataProvider]].
@ -34,6 +34,13 @@ And the following example shows how to use ActiveDataProvider without ActiveReco
$query = new Query();
$provider = new ActiveDataProvider([
'query' => $query->from('post'),
'sort' => [
// Set the default sort by name ASC and created_at DESC.
'defaultOrder' => [
'name' => SORT_ASC,
'created_at' => SORT_DESC
]
],
'pagination' => [
'pageSize' => 20,
],

53
docs/guide/output-data-widgets.md

@ -68,7 +68,7 @@ echo GridView::widget([
```
The above code first creates a data provider and then uses GridView to display every attribute in every row taken from
data provider. The displayed table is equiped with sorting and pagination functionality.
data provider. The displayed table is equipped with sorting and pagination functionality.
### Grid columns
@ -218,18 +218,18 @@ echo GridView::widget([
'dataProvider' => $dataProvider,
'columns' => [
['class' => 'yii\grid\SerialColumn'], // <-- here
// ...
```
Sorting data
------------
### Sorting data
- https://github.com/yiisoft/yii2/issues/1576
Filtering data
--------------
### Filtering data
For filtering data the GridView needs a [model](model.md) that takes the input from the filtering
form and adjusts the query of the dataprovider to respect the search criteria.
form and adjusts the query of the dataProvider to respect the search criteria.
A common practice when using [active records](active-record.md) is to create a search Model class
that extends from the active record class. This class then defines the validation rules for the search
and provides a `search()` method that will return the data provider.
@ -308,15 +308,14 @@ echo GridView::widget([
```
Working with model relations
----------------------------
### Working with model relations
When displaying active records in a GridView you might encounter the case where you display values of related
columns such as the post's author's name instead of just his `id`.
You do this by defining the attribute name in columns as `author.name` when the `Post` model
has a relation named `author` and the author model has an attribute `name`.
The GridView will then display the name of the author but sorting and filtering are not enabled by default.
You have to adjust the `PostSearch` model that has been introduced in the last section to add this functionallity.
You have to adjust the `PostSearch` model that has been introduced in the last section to add this functionality.
To enable sorting on a related column you have to join the related table and add the sorting rule
to the Sort component of the data provider:
@ -362,3 +361,39 @@ In `search()` you then just add another filter condition with `$query->andFilter
> Info: For more information on `joinWith` and the queries performed in the background, check the
> [active record docs on eager and lazy loading](active-record.md#lazy-and-eager-loading).
### Multiple GridViews on one page
You can use more than one GridView on a single page but some additional configuration is needed so that
they do not interfere.
When using multiple instances of GridView you have to configure different parameter names for
the generated sort and pagination links so that each GridView has its individual sorting and pagination.
You do so by setting the [[yii\data\Sort::sortParam|sortParam]] and [[yii\data\Pagination::pageParam|pageParam]]
of the dataProviders [[yii\data\BaseDataProvider::$sort|sort]] and [[yii\data\BaseDataProvider::$pagination|pagination]]
instance.
Assume we want to list `Post` and `User` models for which we have already prepared two data providers
in `$userProvider` and `$postProvider`:
```php
use yii\grid\GridView;
$userProvider->pagination->pageParam = 'user-page';
$userProvider->sort->sortParam = 'user-sort';
$postProvider->pagination->pageParam = 'post-page';
$postProvider->sort->sortParam = 'post-sort';
echo '<h1>Users</h1>';
echo GridView::widget([
'dataProvider' => $userProvider,
]);
echo '<h1>Posts</h1>';
echo GridView::widget([
'dataProvider' => $postProvider,
]);
```
### Using GridView with Pjax
TBD

109
docs/guide/rest-authentication.md

@ -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]].

152
docs/guide/rest-controllers.md

@ -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).

44
docs/guide/rest-error-handling.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.

848
docs/guide/rest-quick-start.md

@ -1,33 +1,28 @@
Implementing RESTful Web Service APIs
=====================================
Quick Start
===========
> Note: This section is under development.
Yii provides a whole set of tools to simplify the task of implementing RESTful Web Service APIs.
In particular, Yii supports the following features about RESTful APIs:
Yii provides a whole set of tools to greatly simplify the task of implementing RESTful Web Service APIs.
In particular, Yii provides support for the following aspects regarding RESTful APIs:
* Quick prototyping with support for common APIs for ActiveRecord;
* Quick prototyping with support for common APIs for [Active Record](db-active-record.md);
* Response format (supporting JSON and XML by default) negotiation;
* Customizable object serialization with support for selectable output fields;
* Proper formatting of collection data and validation errors;
* Support for [HATEOAS](http://en.wikipedia.org/wiki/HATEOAS);
* Efficient routing with proper HTTP verb check;
* Support `OPTIONS` and `HEAD` verbs;
* Authentication;
* Authorization;
* Support for HATEOAS;
* Caching via `yii\filters\HttpCache`;
* Built-in support for the `OPTIONS` and `HEAD` verbs;
* Authentication and authorization;
* Data caching and HTTP caching;
* Rate limiting;
* Searching and filtering: TBD
* Testing: TBD
* Automatic generation of API documentation: TBD
A Quick Example
---------------
In the following, we use an example to illustrate how you can build a set of RESTful APIs with some minimal coding effort.
Let's use a quick example to show how to build a set of RESTful APIs using Yii.
Assume you want to expose the user data via RESTful APIs. The user data are stored in the user DB table,
and you have already created the ActiveRecord class `app\models\User` to access the user data.
and you have already created the [[yii\db\ActiveRecord|ActiveRecord]] class `app\models\User` to access the user data.
## Creating a Controller <a name="creating-controller"></a>
First, create a controller class `app\controllers\UserController` as follows,
@ -42,6 +37,12 @@ class UserController extends ActiveController
}
```
The controller class extends from [[yii\rest\ActiveController]]. By specifying [[yii\rest\ActiveController::modelClass|modelClass]]
as `app\models\User`, the controller knows what model can be used for fetching and manipulating data.
## Configuring URL Rules <a name="configuring-url-rules"></a>
Then, modify the configuration about the `urlManager` component in your application configuration:
```php
@ -55,6 +56,12 @@ Then, modify the configuration about the `urlManager` component in your applicat
]
```
The above configuration mainly adds a URL rule for the `user` controller so that the user data
can be accessed and manipulated with pretty URLs and meaningful HTTP verbs.
## Trying it Out <a name="trying-it-out"></a>
With the above minimal amount of effort, you have already finished your task of creating the RESTful APIs
for accessing the user data. The APIs you have created include:
@ -71,12 +78,8 @@ for accessing the user data. The APIs you have created include:
You may access your APIs with the `curl` command like the following,
```
curl -i -H "Accept:application/json" "http://localhost/users"
```
$ curl -i -H "Accept:application/json" "http://localhost/users"
which may give the following output:
```
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
@ -108,10 +111,8 @@ Try changing the acceptable content type to be `application/xml`, and you will s
is returned in XML format:
```
curl -i -H "Accept:application/xml" "http://localhost/users"
```
$ curl -i -H "Accept:application/xml" "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
@ -141,807 +142,32 @@ Content-Type: application/xml
```
> Tip: You may also access your APIs via Web browser by entering the URL `http://localhost/users`.
However, you may need some browser plugins to send specific request headers.
As you can see, in the response headers, there are information about the total count, page count, etc.
There are also links that allow you to navigate to other pages of data. For example, `http://localhost/users?page=2`
would give you the next page of the user data.
Using the `fields` and `expand` parameters, you may also request to return a subset of the fields in the result.
For example, the URL `http://localhost/users?fields=id,email` will only return the `id` and `email` fields in the result:
Using the `fields` and `expand` parameters, you may also specify which fields should be included in the result.
For example, the URL `http://localhost/users?fields=id,email` will only return the `id` and `email` fields.
> Info: You may have noticed that the result of `http://localhost/users` includes some sensitive fields,
> such as `password_hash`, `auth_key`. You certainly do not want these to appear in your API result.
> You can/should filter out these fields as described in the following sections.
> You can and should filter out these fields as described in the [Response Formatting](rest-response-formatting.md) section.
In the following sections, we will explain in more details about implementing RESTful APIs.
General Architecture
--------------------
## Summary <a name="summary"></a>
Using the Yii RESTful API framework, you implement an API endpoint in terms of a controller action, and you use
a controller to organize the actions that implement the endpoints for a single type of resource.
Resources are represented as data models which extend from the [[yii\base\Model]] class.
If you are working with databases (relational or NoSQL), it is recommended you use ActiveRecord to represent resources.
If you are working with databases (relational or NoSQL), it is recommended you use [[yii\db\ActiveRecord|ActiveRecord]]
to represent resources.
You may use [[yii\rest\UrlRule]] to simplify the routing to your API endpoints.
While not required, it is recommended that you develop your RESTful APIs as an application, separated from
your Web front end and back end.
Creating Resource Classes
-------------------------
RESTful APIs are all about accessing and manipulating resources. In Yii, a resource can be an object of any class.
However, if your resource classes extend from [[yii\base\Model]] or its child classes (e.g. [[yii\db\ActiveRecord]]),
you may enjoy the following benefits:
* Input data validation;
* Query, create, update and delete data, if extending from [[yii\db\ActiveRecord]];
* Customizable data formatting (to be explained in the next section).
Formatting Response Data
------------------------
By default, Yii supports two response formats for RESTful APIs: JSON and XML. If you want to support
other formats, you should configure the `contentNegotiator` behavior in your REST controller classes as follows,
```php
use yii\helpers\ArrayHelper;
public function behaviors()
{
return ArrayHelper::merge(parent::behaviors(), [
'contentNegotiator' => [
'formats' => [
// ... other supported formats ...
],
],
]);
}
```
Formatting response data in general involves two steps:
1. The objects (including embedded objects) in the response data are converted into arrays by [[yii\rest\Serializer]];
2. The array data are converted into different formats (e.g. JSON, XML) by [[yii\web\ResponseFormatterInterface|response formatters]].
Step 2 is usually a very mechanical data conversion process and can be well handled by the built-in response formatters.
Step 1 involves some major development effort as explained below.
When the [[yii\rest\Serializer|serializer]] converts an object into an array, it will call the `toArray()` method
of the object if it implements [[yii\base\Arrayable]]. If an object does not implement this interface,
its public properties will be returned instead.
For classes extending from [[yii\base\Model]] or [[yii\db\ActiveRecord]], besides directly overriding `toArray()`,
you may also override the `fields()` method and/or the `extraFields()` method to customize the data being returned.
The method [[yii\base\Model::fields()]] declares a set of *fields* that should be included in the result.
A field is simply a named data item. In a result array, the array keys are the field names, and the array values
are the corresponding field values. The default implementation of [[yii\base\Model::fields()]] is to return
all attributes of a model as the output fields; for [[yii\db\ActiveRecord::fields()]], by default it will return
the names of the attributes whose values have been populated into the object.
You can override the `fields()` method to add, remove, rename or redefine fields. For example,
```php
// 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;
}
```
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.
> 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()` or `toArray()` to filter them out. In the above example, we choose
> to filter out `auth_key`, `password_hash` and `password_reset_token`.
You may use the `fields` query parameter to specify which fields in `fields()` should be included in the result.
If this parameter is not specified, all fields returned by `fields()` will be returned.
The method [[yii\base\Model::extraFields()]] is very similar to [[yii\base\Model::fields()]].
The difference between these methods is that the latter declares the fields that should be returned by default,
while the former declares the fields that should only be returned when the user specifies them in the `expand` query parameter.
For example, `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,
}
},
...
]
```
You may wonder who triggers the conversion from objects to arrays when an action returns an object or object collection.
The answer is that this is done by [[yii\rest\Controller::serializer]] in the [[yii\base\Controller::afterAction()|afterAction()]]
method. By default, [[yii\rest\Serializer]] is used as the serializer that can recognize resource objects extending from
[[yii\base\Model]] and collection objects implementing [[yii\data\DataProviderInterface]]. The serializer
will call the `toArray()` method of these objects and pass the `fields` and `expand` user parameters to the method.
If there are any embedded objects, they will also be converted into arrays recursively.
If all your resource objects are of [[yii\base\Model]] or its child classes, such as [[yii\db\ActiveRecord]],
and you only use [[yii\data\DataProviderInterface]] as resource collections, the default data formatting
implementation should work very well. However, if you want to introduce some new resource classes that do not
extend from [[yii\base\Model]], or if you want to use some new collection classes, you will need to
customize the serializer class and configure [[yii\rest\Controller::serializer]] to use it.
You new resource classes may use the trait [[yii\base\ArrayableTrait]] to support selective field output
as explained above.
### Pagination
For API endpoints about resource collections, pagination is supported out-of-box if you use
[[yii\data\DataProviderInterface|data provider]] to serve the response data. In particular,
through query parameters `page` and `per-page`, an API consumer may specify which page of data
to return and how many data items should be included in each page. The corresponding response
will include the pagination information by the following HTTP headers (please also refer to the first example
in this section):
* `X-Pagination-Total-Count`: The total number of data items;
* `X-Pagination-Page-Count`: The number of pages;
* `X-Pagination-Current-Page`: The current page (1-based);
* `X-Pagination-Per-Page`: The number of data items in each page;
* `Link`: A set of navigational links allowing client to traverse the data page by page.
The response body will contain a list of data items in the requested page.
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
}
}
```
### HATEOAS Support
[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 APIs.
You may let your model classes to implement the [[yii\web\Linkable]] interface to support HATEOAS. By implementing
this interface, a class is required to return a list of [[yii\web\Link|links]]. Typically, you should return at least
the `self` link, 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"
]
}
```
Creating Controllers and Actions
--------------------------------
So you have the resource data and you have specified how the resource data should be formatted, the next thing
to do is to create controller actions to expose the resource data to end users.
Yii provides two base controller classes to simplify your work of creating RESTful actions:
[[yii\rest\Controller]] and [[yii\rest\ActiveController]]. The difference between these two controllers
is that the latter provides a default set of actions that are specified designed to deal with
resources represented as ActiveRecord. So if you are using ActiveRecord and you are comfortable with
the provided built-in actions, you may consider creating your controller class by extending from
the latter. Otherwise, extending from [[yii\rest\Controller]] will allow you to develop actions
from scratch.
Both [[yii\rest\Controller]] and [[yii\rest\ActiveController]] provide the following features which will
be described in detail in the next few sections:
* Response format negotiation;
* API version negotiation;
* HTTP method validation;
* User authentication;
* Rate limiting.
[[yii\rest\ActiveController]] in addition provides the following features specifically for working
with ActiveRecord:
* A set of commonly used actions: `index`, `view`, `create`, `update`, `delete`, `options`;
* User authorization in regard to the requested action and resource.
When creating a new controller class, a convention in naming the controller class is to use
the type name of the resource and use singular form. For example, to serve user information,
the controller may be named as `UserController`.
Creating a new action is similar to creating an action for a Web application. The only difference
is that instead of rendering the result using a view by calling the `render()` method, for RESTful actions
you directly return the data. The [[yii\rest\Controller::serializer|serializer]] and the
[[yii\web\Response|response object]] will handle the conversion from the original data to the requested
format. For example,
```php
public function actionSearch($keyword)
{
$result = SolrService::search($keyword);
return $result;
}
```
If your controller class extends from [[yii\rest\ActiveController]], you should set
its [[yii\rest\ActiveController::modelClass||modelClass]] property to be the name of the resource class
that you plan to serve through this controller. The class must implement [[yii\db\ActiveRecordInterface]].
With [[yii\rest\ActiveController]], you may want to disable some of the built-in actions or customize them.
To do so, override the `actions()` method like the following:
```php
public function actions()
{
$actions = parent::actions();
// disable the "delete" and "create" actions
unset($actions['delete'], $actions['create']);
// customize the data provider preparation with the "prepareDataProvider()" method
$actions['index']['prepareDataProvider'] = [$this, 'prepareDataProvider'];
return $actions;
}
public function prepareDataProvider()
{
// prepare and return a data provider for the "index" action
}
```
The following list summarizes the built-in actions supported by [[yii\rest\ActiveController]]:
* [[yii\rest\IndexAction|index]]: list resources page by page;
* [[yii\rest\ViewAction|view]]: return the details of a specified resource;
* [[yii\rest\CreateAction|create]]: create a new resource;
* [[yii\rest\UpdateAction|update]]: update an existing resource;
* [[yii\rest\DeleteAction|delete]]: delete the specified resource;
* [[yii\rest\OptionsAction|options]]: return the supported HTTP methods.
Routing
-------
With resource and controller classes ready, you can access the resources using the URL like
`http://localhost/index.php?r=user/create`. As you can see, the format of the URL is the same as that
for Web applications.
In practice, you usually want to enable pretty URLs and take advantage of HTTP verbs.
For example, a request `POST /users` would mean accessing the `user/create` action.
This can be done easily by configuring the `urlManager` application component in the application
configuration like the following:
```php
'urlManager' => [
'enablePrettyUrl' => true,
'enableStrictParsing' => true,
'showScriptName' => false,
'rules' => [
['class' => 'yii\rest\UrlRule', 'controller' => 'user'],
],
]
```
Compared to the URL management for Web applications, the main new thing above is the use of
[[yii\rest\UrlRule]] for routing RESTful API requests. This special URL rule class will
create a whole set of child URL rules to support routing and URL creation for the specified controller(s).
For example, the above code is roughly equivalent to the following rules:
```php
[
'PUT,PATCH users/<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.
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\helpers\ArrayHelper;
use yii\filters\auth\HttpBasicAuth;
public function behaviors()
{
return ArrayHelper::merge(parent::behaviors(), [
'authenticator' => [
'class' => HttpBasicAuth::className(),
],
]);
}
```
If you want to support all three authentication methods explained above, you can use `CompositeAuth` like the following,
```php
use yii\helpers\ArrayHelper;
use yii\filters\auth\CompositeAuth;
use yii\filters\auth\HttpBasicAuth;
use yii\filters\auth\HttpBearerAuth;
use yii\filters\auth\QueryParamAuth;
public function behaviors()
{
return ArrayHelper::merge(parent::behaviors(), [
'authenticator' => [
'class' => CompositeAuth::className(),
'authMethods' => [
HttpBasicAuth::className(),
HttpBearerAuth::className(),
QueryParamAuth::className(),
],
],
]);
}
```
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
-------------
After a user is authenticated, you probably want to check if he has the permission to perform the requested
action for the requested resource. This process is called *authorization* which is covered in detail in
the [Authorization section](authorization.md).
You may use the Role-Based Access Control (RBAC) component to implementation authorization.
To simplify the authorization check, you may also override the [[yii\rest\Controller::checkAccess()]] method
and then call this method in places where authorization is needed. By default, the built-in actions provided
by [[yii\rest\ActiveController]] will call this method when they are about to run.
```php
/**
* Checks the privilege of the current user.
*
* This method should be overridden to check whether the current user has the privilege
* to run the specified action against the specified data model.
* If the user does not have access, a [[ForbiddenHttpException]] should be thrown.
*
* @param string $action the ID of the action to be executed
* @param \yii\base\Model $model the model to be accessed. If null, it means no specific model is being accessed.
* @param array $params additional parameters
* @throws ForbiddenHttpException if the user does not have access
*/
public function checkAccess($action, $model = null, $params = [])
{
}
```
Rate Limiting
-------------
To prevent abuse, you should consider adding rate limiting to your APIs. For example, you may limit the API usage
of each user to be at most 100 API calls within a period of 10 minutes. If too many requests are received from a user
within the period of the time, a response with status code 429 (meaning Too Many Requests) should be returned.
To enable rate limiting, the [[yii\web\User::identityClass|user identity class]] should implement [[yii\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
use yii\helpers\ArrayHelper;
use yii\filters\RateLimiter;
public function behaviors()
{
return ArrayHelper::merge(parent::behaviors(), [
'rateLimiter' => [
'class' => RateLimiter::className(),
'enableRateLimitHeaders' => false,
],
]);
}
```
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.
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 something wrong 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.
API 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.
Caching
-------
Documentation
-------------
Testing
-------
While not required, it is recommended that you develop your RESTful APIs as a separate application, different from
your Web front end and back end for easier maintenance.

44
docs/guide/rest-rate-limiting.md

@ -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.

218
docs/guide/rest-resources.md

@ -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.

150
docs/guide/rest-response-formatting.md

@ -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
}
}
```

78
docs/guide/rest-routing.md

@ -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.

107
docs/guide/rest-versioning.md

@ -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.

255
docs/guide/start-databases.md

@ -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.

31
docs/guide/start-forms.md

@ -1,8 +1,6 @@
Working with Forms
==================
> Note: This section is under development.
In this section, we will describe how to create a new page to get data from users.
The page will display a form with a name input field and an email input field.
After getting these data from a user, the page will echo them back to the user for confirmation.
@ -17,8 +15,7 @@ Through this tutorial, you will learn
* How to build an HTML form in a [view](structure-views.md).
<a name="creating-model"></a>
Creating a Model
Creating a Model <a name="creating-model"></a>
----------------
To represent the data entered by a user, create an `EntryForm` model class as shown below and
@ -26,6 +23,8 @@ save the class in the file `models/EntryForm.php`. Please refer to the [Class Au
section for more details about the class file naming convention.
```php
<?php
namespace app\models;
use yii\base\Model;
@ -61,13 +60,14 @@ failure will turn on the [[yii\base\Model::hasErrors|hasErrors]] property, and t
[[yii\base\Model::getErrors|errors]] you may learn what validation errors the model has.
<a name="creating-action"></a>
Creating an Action
Creating an Action <a name="creating-action"></a>
------------------
Next, create an `entry` action in the `site` controller, like you did in the previous section.
```php
<?php
namespace app\controllers;
use Yii;
@ -112,8 +112,7 @@ be rendered, which will show the HTML form together with the validation error me
In the above code, the `request` component is used to access the `$_POST` data.
<a name="creating-views"></a>
Creating Views
Creating Views <a name="creating-views"></a>
--------------
Finally, create two views named `entry-confirm` and `entry` that are rendered by the `entry` action,
@ -161,9 +160,8 @@ and the second the "email" data. After the input fields, the [[yii\helpers\Html:
is called to generate a submit button.
<a name="how-it-works"></a>
How It Works
------------
Trying it Out <a name="trying-it-out"></a>
-------------
To see how it works, use your browser to access the following URL:
@ -176,12 +174,16 @@ is also displayed indicating what data you need to enter. If you click the submi
entering anything, or if you do not provide a valid email address, you will see an error message that
is displayed next to each problematic input field.
![Form with Validation Errors](images/start-form-validation.png)
After entering a valid name and email address and clicking the submit button, you will see a new page
displaying the data that you just entered.
![Confirmation of Data Entry](images/start-entry-confirmation.png)
<a name="magic-explained"></a>
### Magic Explained
### Magic Explained <a name="magic-explained"></a>
You may wonder how the HTML form works behind the scene, because it seems almost magical that it can
display a label for each input field and show error messages if you do not enter the data correctly
@ -207,8 +209,7 @@ the following code:
view code into reusable widgets to simplify view development in future.
<a name="summary"></a>
Summary
Summary <a name="summary"></a>
-------
In this section, you have touched every part in the MVC design pattern. You have learned how

126
docs/guide/start-gii.md

@ -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.

18
docs/guide/start-hello.md

@ -15,8 +15,7 @@ Through this tutorial, you will learn
* How an application dispatches requests to [actions](structure-controllers.md).
<a name="creating-action"></a>
Creating an Action
Creating an Action <a name="creating-action"></a>
------------------
For the "Hello" task, you will create a `say` [action](structure-controllers.md) which reads
@ -32,6 +31,8 @@ declare the `say` action in the existing controller `SiteController` which is de
in the class file `controllers/SiteController.php`:
```php
<?php
namespace app\controllers;
use yii\web\Controller;
@ -66,8 +67,7 @@ so that it can be echoed there. The rendering result is returned by the action m
by the application and displayed to the end user.
<a name="creating-view"></a>
Creating a View
Creating a View <a name="creating-view"></a>
---------------
[Views](structure-views.md) are scripts that you write to compose response content.
@ -93,9 +93,8 @@ In fact, the `say` view is just a PHP script which is executed by the [[yii\web\
The content echoed by the view script will be forwarded by the application as the response to the end user.
<a name="how-it-works"></a>
How It Works
------------
Trying it Out <a name="trying-it-out"></a>
-------------
After creating the action and the view, you may access the new page by the following URL:
@ -103,6 +102,8 @@ After creating the action and the view, you may access the new page by the follo
http://hostname/index.php?r=site/say&message=Hello+World
```
![Hello World](images/start-hello-world.png)
This will show a page displaying "Hello World". The page shares the same header and footer as other pages of
the application. If you omit the `message` parameter in the URL, you would see the page displays "Hello".
This is because `message` is passed as a parameter to the `actionSay()` method, and when it is omitted,
@ -126,8 +127,7 @@ the `SiteController::actionSay()` method will be called to handle the request.
to the controller class name `PostCommentController`.
<a name="summary"></a>
Summary
Summary <a name="summary"></a>
-------
In this section, you have touched the controller part and the view part in the MVC design pattern.

25
docs/guide/start-installation.md

@ -6,8 +6,7 @@ The former is the preferred way as it allows you to install new [extensions](str
or update Yii by running a single command.
<a name="installing-via-composer"></a>
Installing via Composer
Installing via Composer <a name="installing-via-composer"></a>
-----------------------
If you do not already have Composer installed, you may get it by following the instructions at
@ -40,8 +39,7 @@ composer create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic bas
Note that the development version of Yii should not be used for production as it may break your running code.
<a name="installing-from-archive-file"></a>
Installing from an Archive File
Installing from an Archive File <a name="installing-from-archive-file"></a>
-------------------------------
Installing Yii from an archive file involves two steps:
@ -50,8 +48,7 @@ Installing Yii from an archive file involves two steps:
2. Unpack the downloaded file to a Web accessible folder.
<a name="other-installation-options"></a>
Other Installation Options
Other Installation Options <a name="other-installation-options"></a>
--------------------------
The above installation instructions show how to install Yii in terms of a basic Web application that works out of box.
@ -65,8 +62,7 @@ There are other installation options available:
you may consider [Advanced Application Template](tutorial-advanced-app.md).
<a name="verifying-installation"></a>
Verifying Installation
Verifying Installation <a name="verifying-installation"></a>
----------------------
After installation, you can use your browser to access the installed Yii application with the following URL,
@ -77,7 +73,9 @@ and the server name is `hostname`,
http://hostname/basic/web/index.php
```
You should see a "Congratulations!" page in your browser. If not, please check if your PHP installation satisfies
![Successful Installation of Yii](images/start-app-installed.png)
You should see the above "Congratulations!" page in your browser. If not, please check if your PHP installation satisfies
Yii's requirements by using one of the following approaches:
* Use a browser to access the URL `http://hostname/basic/requirements.php`
@ -94,8 +92,7 @@ the [PDO PHP Extension](http://www.php.net/manual/en/pdo.installation.php) and a
(such as `pdo_mysql` for MySQL databases), if your application needs a database.
<a name="configuring-web-servers"></a>
Configuring Web Servers
Configuring Web Servers <a name="configuring-web-servers"></a>
-----------------------
> Info: You may skip this sub-section for now if you are just testing driving Yii with no intention
@ -120,8 +117,7 @@ to modify its Web server setting, you may adjust the structure of your applicati
the [Shared Hosting Environment](tutorial-shared-hosting.md) section for more details.
<a name="recommended-apache-configuration"></a>
### Recommended Apache Configuration
### Recommended Apache Configuration <a name="recommended-apache-configuration"></a>
Use the following configuration in Apache's `httpd.conf` file or within a virtual host configuration. Note that you
should replace `path/to/basic/web` with the actual path of `basic/web`.
@ -144,8 +140,7 @@ DocumentRoot "path/to/basic/web"
```
<a name="recommended-nginx-configuration"></a>
### Recommended Nginx Configuration
### Recommended Nginx Configuration <a name="recommended-nginx-configuration"></a>
You should have installed PHP as an [FPM SAPI](http://php.net/install.fpm) for [Nginx](http://wiki.nginx.org/).
Use the following Nginx configuration and replace `path/to/basic/web` with the actual path of `basic/web`.

31
docs/guide/start-looking-head.md

@ -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)

67
docs/guide/start-workflow.md

@ -11,10 +11,10 @@ and how the application handles requests in general.
Please adjust the URLs accordingly in our descriptions.
Functionalities
Functionalities <a name="functionalities"></a>
---------------
The application that you have installed has four pages:
The application that you have installed contains four pages:
* the homepage is the page displayed when you access the URL `http://hostname/index.php`;
* the "About" page;
@ -30,7 +30,7 @@ This is a useful [debugger tool](tool-debugger.md) provided by Yii to help you c
about the application execution, such as log messages, response status, database queries, and so on.
Application Structure
Application Structure <a name="application-structure"></a>
---------------------
The following is a list of the most important directories and files in your application,
@ -54,45 +54,42 @@ basic/ application base path
```
In general, the files in the application can be divided into two parts: those under `basic/web` and those
under other directories. The former can be directly accessed from Web, while the latter can/should not.
under other directories. The former can be directly accessed from Web, while the latter can not and should not.
Your application uses a single entry `web/index.php`. It is the only PHP script that is directly accessible from Web.
It takes ALL Web requests, creates an [application](structure-applications.md) instance to handle the requests,
and then sends back the responses.
Yii implements the [model-view-controller (MVC)](http://wikipedia.org/wiki/Model-view-controller) design pattern,
as reflected in the above directory organization. The `models` directory contains all [model classes](structure-models.md),
Yii implements the [model-view-controller (MVC)](http://wikipedia.org/wiki/Model-view-controller) design pattern
which is reflected in the above directory organization. The `models` directory contains all [model classes](structure-models.md),
the `views` directory contains all [view scripts](structure-views.md), and the `controllers` directory contains
all [controller classes](structure-controllers.md).
The following diagram shows the static structure of an application:
The following diagram shows the static structure of an application.
![Static Structure of Application](images/application-structure.png)
![Static structure of Yii application](images/application-structure.png)
Each application has an entry script `web/index.php` which is the only Web accessible PHP script in the application.
The entry script takes an incoming request and creates an [application](structure-applications.md) instance to handle it.
The [application](structure-applications.md) resolves the request with the help of its [components](concept-components.md)
and dispatches the request to MVC. [Widgets](structure-widgets.md) are used in the [views](structure-views.md)
to help build complex and dynamic user interface elements.
Request Lifecycle
Request Lifecycle <a name="request-lifecycle"></a>
-----------------
The following diagram shows a typical workflow of a Yii application handling a user request:
![Typical workflow of a Yii application](images/application-lifecycle.png)
1. A user makes a request of the URL `http://www.example.com/index.php?r=post/show&id=1`.
The Web server handles the request by executing the bootstrap script `index.php`.
2. The bootstrap script creates an [[yii\web\Application|Application]] instance and runs it.
3. The Application instance obtains the detailed user request information from an application component named `request`.
4. The application determines which [controller](controller.md) and which action of that controller was requested.
This is accomplished with the help of an application component named `urlManager`.
For this example, the controller is `post`, which refers to the `PostController` class, and the action is `show`,
whose actual meaning is determined by the controller.
5. The application creates an instance of the requested controller to further handle the user's request.
The controller determines that the action `show` refers to a method named `actionShow` in the controller class.
The controller then creates and executes any filters associated with this action (e.g. access control or benchmarking).
The action is then executed, if execution is allowed by the filters (e.g., if the user has permission to execute that action).
6. The action creates a `Post` [model](model.md) instance, using the underlying database table, where the ID value of the corresponding record is `1`.
7. The action renders a [view](view.md) named `show`, providing to the view the `Post` model instance.
8. The view reads the attributes of the `Post` model instance and displays the values of those attributes.
9. The view executes some [widgets](view.md#widgets).
10. The view rendering result--the output from the previous steps--is embedded within a [layout](view.md#layout) to create a complete HTML page.
11. The action completes the view rendering and displays the result to the user.
The following diagram shows how an application handles a request.
![Request Lifecycle](images/application-lifecycle.png)
1. A user makes a request to the [entry script](structure-entry-scripts.md) `web/index.php`.
2. The entry script loads the application [configuration](concept-configurations.md) and creates
an [application](structure-applications.md) instance to handle the request.
3. The application resolves the requested [route](runtime-routing.md) with the help of
the [request](runtime-requests.md) application component.
4. The application creates a [controller](structure-controllers.md) instance to handle the request.
5. The controller creates an [action](structure-controllers.md) instance and performs the filters for the action.
6. If any filter fails, the action is cancelled.
7. If all filters pass, the action is being executed.
8. The action loads a data model, possibly from a database.
9. The action renders a view with the data model.
10. The rendering result is to the [response](runtime-responses.md) application component.
11. The response component sends the rendering result to the user.

117
docs/guide/structure-entry-scripts.md

@ -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.

10
docs/guide/tool-debugger.md

@ -19,6 +19,7 @@ Out of the box these tools allow you to:
All of this information will be available per request, allowing you to revisit the information for past requests as well.
Installing and configuring
--------------------------
@ -31,7 +32,8 @@ To enable these features, add these lines to your configuration file to enable t
]
```
By default, the debug module only works when browsing the website from localhost. If you want to use it on a remote (staging) server, add the parameter `allowedIPs` to the configuration to whitelist your IP:
By default, the debug module only works when browsing the website from localhost. If you want to use it on a remote (staging)
server, add the parameter `allowedIPs` to the configuration to whitelist your IP:
```php
'bootstrap' => ['debug'],
@ -55,6 +57,11 @@ If you are using `enableStrictParsing` URL manager option, add the following to
],
```
> Note: the debugger stores information about each request in the `@runtime/debug` directory. If you have problems using
> The debugger such as weird error messages when using it or the toolbar not showing up or not showing any requests, check
> whether the web server has enough permissions to access this directory and the files located inside.
### Extra configuration for logging and profiling
Logging and profiling are simple but powerful tools that may help you to understand the execution flow of both the
@ -84,6 +91,7 @@ defined('YII_DEBUG') or define('YII_DEBUG', true);
> Note: Make sure to disable debug mode in production environments since it may have a significant and adverse performance effect. Further, the debug mode may expose sensitive information to end users.
Creating your own panels
------------------------

8
extensions/apidoc/models/Context.php

@ -166,6 +166,14 @@ class Context extends Component
foreach ($class->methods as $m) {
if ($m->hasTag('inheritdoc')) {
$inheritedMethod = $this->inheritMethodRecursive($m, $class);
if (!$inheritedMethod) {
$this->errors[] = [
'line' => $m->startLine,
'file' => $class->sourceFile,
'message' => "Method {$m->name} has no parent to inherit from in {$class->name}.",
];
continue;
}
foreach (['shortDescription', 'description', 'return', 'returnType', 'returnTypes', 'exceptions'] as $property) {
if (empty($m->$property) || is_string($m->$property) && trim($m->$property) === '') {
$m->$property = $inheritedMethod->$property;

2
extensions/authclient/clients/VKontakte.php

@ -21,7 +21,7 @@ use yii\authclient\OAuth2;
* 'authClientCollection' => [
* 'class' => 'yii\authclient\Collection',
* 'clients' => [
* 'google' => [
* 'vkontakte' => [
* 'class' => 'yii\authclient\clients\VKontakte',
* 'clientId' => 'vkontakte_client_id',
* 'clientSecret' => 'vkontakte_client_secret',

3
extensions/composer/CHANGELOG.md

@ -4,8 +4,7 @@ Yii Framework 2 composer extension Change Log
2.0.0-rc under development
--------------------------
- no changes in this release.
- Bug #3438: Fixed support for non-lowercase package names (cebe)
2.0.0-beta April 13, 2014
-------------------------

6
extensions/composer/Installer.php

@ -89,7 +89,7 @@ class Installer extends LibraryInstaller
$extension['alias'] = $alias;
}
$extra = $package->getExtra();
if (isset($extra[self::EXTRA_BOOTSTRAP]) && is_string($extra[self::EXTRA_BOOTSTRAP])) {
if (isset($extra[self::EXTRA_BOOTSTRAP])) {
$extension['bootstrap'] = $extra[self::EXTRA_BOOTSTRAP];
}
@ -110,7 +110,7 @@ class Installer extends LibraryInstaller
foreach ($autoload['psr-0'] as $name => $path) {
$name = str_replace('\\', '/', trim($name, '\\'));
if (!$fs->isAbsolutePath($path)) {
$path = $this->vendorDir . '/' . $package->getName() . '/' . $path;
$path = $this->vendorDir . '/' . $package->getPrettyName() . '/' . $path;
}
$path = $fs->normalizePath($path);
if (strpos($path . '/', $vendorDir . '/') === 0) {
@ -125,7 +125,7 @@ class Installer extends LibraryInstaller
foreach ($autoload['psr-4'] as $name => $path) {
$name = str_replace('\\', '/', trim($name, '\\'));
if (!$fs->isAbsolutePath($path)) {
$path = $this->vendorDir . '/' . $package->getName() . '/' . $path;
$path = $this->vendorDir . '/' . $package->getPrettyName() . '/' . $path;
}
$path = $fs->normalizePath($path);
if (strpos($path . '/', $vendorDir . '/') === 0) {

1
extensions/debug/Module.php

@ -61,6 +61,7 @@ class Module extends \yii\base\Module implements BootstrapInterface
*/
public $enableDebugLogs = false;
/**
* Returns Yii logo ready to use in `<img src="`
*

2
extensions/debug/controllers/DefaultController.php

@ -116,7 +116,7 @@ class DefaultController extends Controller
clearstatcache();
}
$indexFile = $this->module->dataPath . '/index.data';
if (is_file($indexFile)) {
if (is_file($indexFile) && is_readable($indexFile)) {
$this->_manifest = array_reverse(unserialize(file_get_contents($indexFile)), true);
} else {
$this->_manifest = [];

16
extensions/gii/assets/gii.js

@ -45,6 +45,8 @@ yii.gii = (function ($) {
$modal.find('.modal-title').text($link.data('title'));
$modal.find('.modal-body').html('Loading ...');
$modal.modal('show');
var checked = $('a.' + $modal.data('action') + '[href="' + $link.attr('href') + '"]').closest('tr').find('input').get(0).checked;
$modal.find('.modal-checkbox span').toggleClass('glyphicon-check', checked).toggleClass('glyphicon-unchecked', !checked);
$.ajax({
type: 'POST',
cache: false,
@ -57,6 +59,7 @@ yii.gii = (function ($) {
var index = $files.filter('[href="' + $link.attr('href') + '"]').index(filesSelector);
var $prev = $files.eq(index - 1);
var $next = $files.eq((index + 1 == $files.length ? 0 : index + 1));
$modal.data('current', $files.eq(index));
$modal.find('.modal-previous').attr('href', $prev.attr('href')).data('title', $prev.data('title'));
$modal.find('.modal-next').attr('href', $next.attr('href')).data('title', $next.data('title'));
}
@ -77,8 +80,21 @@ yii.gii = (function ($) {
$('.modal-next').trigger('click');
} else if (e.keyCode === 82) {
$('.modal-refresh').trigger('click');
} else if (e.keyCode === 32) {
$('.modal-checkbox').trigger('click');
}
});
$('.modal-checkbox').on('click', checkFileToggle);
};
var checkFileToggle = function () {
var $modal = $('#preview-modal');
var $checkbox = $modal.data('current').closest('tr').find('input');
var checked = !$checkbox.prop('checked');
$checkbox.prop('checked', checked);
$modal.find('.modal-checkbox span').toggleClass('glyphicon-check', checked).toggleClass('glyphicon-unchecked', !checked);
return false;
};
var checkAllToggle = function () {

2
extensions/gii/generators/crud/Generator.php

@ -110,7 +110,7 @@ class Generator extends \yii\gii\Generator
'indexWidgetType' => 'This is the widget type to be used in the index page to display list of the models.
You may choose either <code>GridView</code> or <code>ListView</code>',
'searchModelClass' => 'This is the name of the search model class to be generated. You should provide a fully
qualified namespaced class name, e.g., <code>app\models\search\PostSearch</code>.',
qualified namespaced class name, e.g., <code>app\models\PostSearch</code>.',
]);
}

4
extensions/gii/generators/model/Generator.php

@ -111,6 +111,10 @@ class Generator extends \yii\gii\Generator
you may want to uncheck this option to accelerate the code generation process.',
'generateLabelsFromComments' => 'This indicates whether the generator should generate attribute labels
by using the comments of the corresponding DB columns.',
'useTablePrefix' => 'This indicates whether the table name returned by the generated ActiveRecord class
should consider the <code>tablePrefix</code> setting of the DB connection. For example, if the
table name is <code>tbl_post</code> and <code>tablePrefix=tbl_</code>, the ActiveRecord class
will return the table name as <code>{{%post}}</code>.',
]);
}

1
extensions/gii/views/default/view/files.php

@ -98,6 +98,7 @@ use yii\gii\CodeFile;
<a class="modal-previous btn btn-xs btn-default" href="#" title="Previous File (Left Arrow)"><span class="glyphicon glyphicon-arrow-left"></span></a>
<a class="modal-next btn btn-xs btn-default" href="#" title="Next File (Right Arrow)"><span class="glyphicon glyphicon-arrow-right"></span></a>
<a class="modal-refresh btn btn-xs btn-default" href="#" title="Refresh File (R)"><span class="glyphicon glyphicon-refresh"></span></a>
<a class="modal-checkbox btn btn-xs btn-default" href="#" title="Check This File (Space)"><span class="glyphicon"></span></a>
&nbsp;
</div>
<strong class="modal-title pull-left">Modal title</strong>

8
framework/CHANGELOG.md

@ -25,6 +25,9 @@ Yii Framework 2 Change Log
- Bug #3311: Fixed the bug that `yii\di\Container::has()` did not return correct value (mgrechanik, qiangxue)
- Bug #3327: Fixed "Unable to find debug data" when logging objects with circular references (jarekkozak, samdark)
- Bug #3368: Fix for comparing numeric attributes in JavaScript (technixp)
- Bug #3431: Allow using extended ErrorHandler class from the app namespace (cebe)
- Bug #3436: Fixed the issue that `ServiceLocator` still returns the old component after calling `set()` with a new definition (qiangxue)
- Bug #3458: Fixed the bug that the image rendered by `CaptchaAction` was using a wrong content type (MDMunir, qiangxue)
- Bug: Fixed inconsistent return of `\yii\console\Application::runAction()` (samdark)
- Enh #2264: `CookieCollection::has()` will return false for expired or removed cookies (qiangxue)
- Enh #2435: `yii\db\IntegrityException` is now thrown on database integrity errors instead of general `yii\db\Exception` (samdark)
@ -49,6 +52,9 @@ Yii Framework 2 Change Log
- Enh: Added support to insert an event handler at the beginning of class-level event handler queue (qiangxue)
- Enh: Added `yii\console\Controller::EXIT_CODE_NORMAL` and `yii\console\Controller::EXIT_CODE_ERROR` constants (samdark)
- Enh: `yii\console\MigrateController` now returns `yii\console\Controller::EXIT_CODE_ERROR` in case of failed migration (samdark)
- Enh: Added method ErrorHandler::unregister() for unregistering the ErrorHandler (cebe)
- Enh: Added `all` option to `MigrateController::actionDown()` action (creocoder, umneeq)
- Enh: Added support for array attributes in `exist` validator (creocoder)
- Chg #2913: RBAC `DbManager` is now initialized via migration (samdark)
- Chg #3036: Upgraded Twitter Bootstrap to 3.1.x (qiangxue)
- Chg #3175: InvalidCallException, InvalidParamException, UnknownMethodException are now extended from SPL BadMethodCallException (samdark)
@ -58,7 +64,7 @@ Yii Framework 2 Change Log
- Chg: `yii\grid\DataColumn::getDataCellValue()` visibility is now `public` to allow accessing the value from a GridView directly (cebe)
- Chg: `yii\data\ActiveDataProvider::$query` will not be modified directly with pagination and sorting anymore so it will be reuseable (cebe)
- Chg: Removed `yii\rest\ActiveController::$transactional` property and connected functionality (samdark)
- Chg: Changed the default value of the `keyPrefix` property of cache components to be null (qiangxue)
2.0.0-beta April 13, 2014
-------------------------

4
framework/UPGRADE.md

@ -29,3 +29,7 @@ Upgrade from Yii 2.0 Beta
`yii\filters\auth\HttpBearerAuth` authentication method, the value of this parameter will be
`yii\filters\auth\HttpBearerAuth`. This allows you to differentiate access tokens taken by
different authentication methods.
* If you are sharing the same cache across different applications, you should configure
the `keyPrefix` property of the cache component to use some unique string.
Previously, this property was automatically assigned with a unique string.

3
framework/base/Application.php

@ -184,9 +184,10 @@ abstract class Application extends Module
$this->state = self::STATE_BEGIN;
$this->registerErrorHandler($config);
$this->preInit($config);
$this->registerErrorHandler($config);
Component::__construct($config);
}

9
framework/base/ErrorHandler.php

@ -60,6 +60,15 @@ abstract class ErrorHandler extends Component
}
/**
* Unregisters this error handler by restoring the PHP error and exception handlers.
*/
public function unregister()
{
restore_error_handler();
restore_exception_handler();
}
/**
* Handles uncaught PHP exceptions.
*
* This method is implemented as a PHP exception handler.

19
framework/caching/Cache.php

@ -7,7 +7,6 @@
namespace yii\caching;
use Yii;
use yii\base\Component;
use yii\helpers\StringHelper;
@ -52,10 +51,9 @@ use yii\helpers\StringHelper;
abstract class Cache extends Component implements \ArrayAccess
{
/**
* @var string a string prefixed to every cache key so that it is unique. If not set,
* it will use a prefix generated from [[\yii\base\Application::id]]. You may set this property to be an empty string
* if you don't want to use key prefix. It is recommended that you explicitly set this property to some
* static value if the cached data needs to be shared among multiple applications.
* @var string a string prefixed to every cache key so that it is unique globally in the whole cache storage.
* It is recommended that you set a unique cache key prefix for each application if the same cache
* storage is being used by different applications.
*
* To ensure interoperability, only alphanumeric characters should be used.
*/
@ -71,17 +69,6 @@ abstract class Cache extends Component implements \ArrayAccess
*/
public $serializer;
/**
* Initializes the application component.
* This method overrides the parent implementation by setting default cache key prefix.
*/
public function init()
{
parent::init();
if ($this->keyPrefix === null) {
$this->keyPrefix = substr(md5(Yii::$app->id), 0, 5);
}
}
/**
* Builds a normalized cache key from a given key.

3
framework/captcha/CaptchaAction.php

@ -11,6 +11,7 @@ use Yii;
use yii\base\Action;
use yii\base\InvalidConfigException;
use yii\helpers\Url;
use yii\web\Response;
/**
* CaptchaAction renders a CAPTCHA image.
@ -127,7 +128,7 @@ class CaptchaAction extends Action
]);
} else {
$this->setHttpHeaders();
Yii::$app->response->format = Response::FORMAT_RAW;
return $this->renderImage($this->getVerifyCode());
}
}

12
framework/console/Application.php

@ -187,17 +187,7 @@ class Application extends \yii\base\Application
return array_merge(parent::coreComponents(), [
'request' => ['class' => 'yii\console\Request'],
'response' => ['class' => 'yii\console\Response'],
'errorHandler' => ['class' => 'yii\console\ErrorHandler'],
]);
}
/**
* Registers the errorHandler component as a PHP error handler.
*/
protected function registerErrorHandler(&$config)
{
if (!isset($config['components']['errorHandler']['class'])) {
$config['components']['errorHandler']['class'] = 'yii\\console\\ErrorHandler';
}
parent::registerErrorHandler($config);
}
}

7
framework/console/ErrorHandler.php

@ -42,8 +42,11 @@ class ErrorHandler extends \yii\base\ErrorHandler
$message .= $this->formatMessage(" '" . get_class($exception) . "'", [Console::BOLD, Console::FG_BLUE])
. " with message " . $this->formatMessage("'{$exception->getMessage()}'", [Console::BOLD]) //. "\n"
. "\n\nin " . dirname($exception->getFile()) . DIRECTORY_SEPARATOR . $this->formatMessage(basename($exception->getFile()), [Console::BOLD])
. ':' . $this->formatMessage($exception->getLine(), [Console::BOLD, Console::FG_YELLOW]) . "\n\n"
. $this->formatMessage("Stack trace:\n", [Console::BOLD]) . $exception->getTraceAsString();
. ':' . $this->formatMessage($exception->getLine(), [Console::BOLD, Console::FG_YELLOW]) . "\n";
if ($exception instanceof \yii\db\Exception && !empty($exception->errorInfo)) {
$message .= "\n" . $this->formatMessage("Error Info:\n", [Console::BOLD]) . print_r($exception->errorInfo, true);
}
$message .= "\n" . $this->formatMessage("Stack trace:\n", [Console::BOLD]) . $exception->getTraceAsString();
} else {
$message = $this->formatMessage('Error: ') . $exception->getMessage();
}

11
framework/console/controllers/MigrateController.php

@ -193,6 +193,7 @@ class MigrateController extends Controller
* ~~~
* yii migrate/down # revert the last migration
* yii migrate/down 3 # revert the last 3 migrations
* yii migrate/down all # revert all migrations
* ~~~
*
* @param integer $limit the number of migrations to be reverted. Defaults to 1,
@ -203,9 +204,13 @@ class MigrateController extends Controller
*/
public function actionDown($limit = 1)
{
$limit = (int) $limit;
if ($limit < 1) {
throw new Exception("The step argument must be greater than 0.");
if ($limit === 'all') {
$limit = null;
} else {
$limit = (int) $limit;
if ($limit < 1) {
throw new Exception("The step argument must be greater than 0.");
}
}
$migrations = $this->getMigrationHistory($limit);

14
framework/db/ActiveRecordInterface.php

@ -183,14 +183,18 @@ interface ActiveRecordInterface
public static function findOne($condition);
/**
* Returns a list of active record models that match the specified primary key value or a set of column values.
* Returns a list of active record models that match the specified primary key value(s) or a set of column values.
*
* The method accepts:
*
* - a scalar value (integer or string): query by a single primary key value and return the
* corresponding record (or null if not found).
* - an array of name-value pairs: query by a set of attribute values and return a single record
* matching all of them (or null if not found).
* - a scalar value (integer or string): query by a single primary key value and return an array containing the
* corresponding record (or an empty array if not found).
* - an array of scalar values (integer or string): query by a list of primary key values and return the
* corresponding records (or an empty array if none was found).
* Note that an empty condition will result in an empty result as it will be interpreted as a search for
* primary keys and not an empty `WHERE` condition.
* - an array of name-value pairs: query by a set of attribute values and return an array of records
* matching all of them (or an empty array if none was found).
*
* Note that this method will automatically call the `all()` method and return an array of
* [[ActiveRecordInterface|ActiveRecord]] instances. For example,

2
framework/db/QueryBuilder.php

@ -576,7 +576,7 @@ class QueryBuilder extends \yii\base\Object
* For example, 'string NOT NULL' is converted to 'varchar(255) NOT NULL'.
*
* For some of the abstract types you can also specify a length or precision constraint
* by prepending it in round brackets directly to the type.
* by appending it in round brackets directly to the type.
* For example `string(32)` will be converted into "varchar(32)" on a MySQL database.
* If the underlying DBMS does not support these kind of constraints for a type it will
* be ignored.

2
framework/di/ServiceLocator.php

@ -188,6 +188,8 @@ class ServiceLocator extends Component
return;
}
unset($this->_components[$id]);
if (is_object($definition) || is_callable($definition, true)) {
// an object, a class name, or a PHP callable
$this->_definitions[$id] = $definition;

4
framework/filters/ContentNegotiator.php

@ -89,7 +89,7 @@ class ContentNegotiator extends ActionFilter implements BootstrapInterface
* @var string the name of the GET parameter that specifies the response format.
* Note that if the specified format does not exist in [[formats]], a [[UnsupportedMediaTypeHttpException]]
* exception will be thrown. If the parameter value is empty or if this property is null,
* the response format will be determined based on the `Accept` HTTP header.
* the response format will be determined based on the `Accept` HTTP header only.
* @see formats
*/
public $formatParam = '_format';
@ -97,7 +97,7 @@ class ContentNegotiator extends ActionFilter implements BootstrapInterface
* @var string the name of the GET parameter that specifies the [[\yii\base\Application::language|application language]].
* Note that if the specified language does not match any of [[languages]], the first language in [[languages]]
* will be used. If the parameter value is empty or if this property is null,
* the application language will be determined based on the `Accept-Language` HTTP header.
* the application language will be determined based on the `Accept-Language` HTTP header only.
* @see languages
*/
public $languageParam = '_lang';

2
framework/helpers/HtmlPurifier.php

@ -24,7 +24,7 @@ namespace yii\helpers;
* ]);
* ```
*
* For more details please refer to HTMLPurifier documentation](http://htmlpurifier.org/).
* For more details please refer to [HTMLPurifier documentation](http://htmlpurifier.org/).
*
* Note that you should add `ezyang/htmlpurifier` to your composer.json `require` section and run `composer install`
* before using it.

4
framework/messages/config.php

@ -7,7 +7,7 @@ return [
'messagePath' => __DIR__,
// array, required, list of language codes that the extracted messages
// should be translated to. For example, ['zh-CN', 'de'].
'languages' => ['ar', 'bg', 'ca', 'da', 'de', 'el', 'es', 'et', 'fa-IR', 'fi', 'fr', 'hu', 'it', 'ja', 'kk', 'lt', 'lv', 'nl', 'pl', 'pt-BR', 'pt-PT', 'ro', 'ru', 'sk', 'sr', 'sr-Latn', 'uk', 'vi', 'zh-CN'],
'languages' => ['ar', 'bg', 'ca', 'da', 'de', 'el', 'es', 'et', 'fa-IR', 'fi', 'fr', 'hu', 'it', 'ja', 'kk', 'ko', 'lt', 'lv', 'nl', 'pl', 'pt-BR', 'pt-PT', 'ro', 'ru', 'sk', 'sr', 'sr-Latn', 'uk', 'vi', 'zh-CN'],
// string, the name of the function for translating messages.
// Defaults to 'Yii::t'. This is used as a mark to find the messages to be
// translated. You may use a string for single function name or an array for
@ -48,4 +48,4 @@ return [
'format' => 'php',
// Connection component ID for "db" format.
//'db' => 'db',
];
];

82
framework/messages/ko/yii.php

@ -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}자 이어야합니다.',
);

1
framework/requirements/YiiRequirementChecker.php

@ -287,7 +287,6 @@ class YiiRequirementChecker
$minCheckResult = true;
}
if ($max !== null) {
var_dump($postMaxSize, $uploadMaxFileSize, $max);
$maxCheckResult = $this->compareByteSize($postMaxSize, $max, '<=') && $this->compareByteSize($uploadMaxFileSize, $max, '<=');
} else {
$maxCheckResult = true;

17
framework/rest/ActiveController.php

@ -9,6 +9,7 @@ namespace yii\rest;
use yii\base\InvalidConfigException;
use yii\base\Model;
use yii\web\ForbiddenHttpException;
/**
* ActiveController implements a common set of actions for supporting RESTful access to ActiveRecord.
@ -114,4 +115,20 @@ class ActiveController extends Controller
'delete' => ['DELETE'],
];
}
/**
* Checks the privilege of the current user.
*
* This method should be overridden to check whether the current user has the privilege
* to run the specified action against the specified data model.
* If the user does not have access, a [[ForbiddenHttpException]] should be thrown.
*
* @param string $action the ID of the action to be executed
* @param object $model the model to be accessed. If null, it means no specific model is being accessed.
* @param array $params additional parameters
* @throws ForbiddenHttpException if the user does not have access
*/
public function checkAccess($action, $model = null, $params = [])
{
}
}

17
framework/rest/Controller.php

@ -13,7 +13,6 @@ use yii\filters\ContentNegotiator;
use yii\filters\RateLimiter;
use yii\web\Response;
use yii\filters\VerbFilter;
use yii\web\ForbiddenHttpException;
/**
* Controller is the base class for RESTful API controller classes.
@ -97,20 +96,4 @@ class Controller extends \yii\web\Controller
{
return Yii::createObject($this->serializer)->serialize($data);
}
/**
* Checks the privilege of the current user.
*
* This method should be overridden to check whether the current user has the privilege
* to run the specified action against the specified data model.
* If the user does not have access, a [[ForbiddenHttpException]] should be thrown.
*
* @param string $action the ID of the action to be executed
* @param object $model the model to be accessed. If null, it means no specific model is being accessed.
* @param array $params additional parameters
* @throws ForbiddenHttpException if the user does not have access
*/
public function checkAccess($action, $model = null, $params = [])
{
}
}

12
framework/rest/Serializer.php

@ -8,6 +8,7 @@
namespace yii\rest;
use Yii;
use yii\base\Arrayable;
use yii\base\Component;
use yii\base\Model;
use yii\data\DataProviderInterface;
@ -121,8 +122,10 @@ class Serializer extends Component
*/
public function serialize($data)
{
if ($data instanceof Model) {
return $data->hasErrors() ? $this->serializeModelErrors($data) : $this->serializeModel($data);
if ($data instanceof Model && $data->hasErrors()) {
return $this->serializeModelErrors($data);
} elseif ($data instanceof Arrayable) {
return $this->serializeModel($data);
} elseif ($data instanceof DataProviderInterface) {
return $this->serializeDataProvider($data);
} else {
@ -217,7 +220,7 @@ class Serializer extends Component
/**
* Serializes a model object.
* @param Model $model
* @param Arrayable $model
* @return array the array representation of the model
*/
protected function serializeModel($model)
@ -226,7 +229,6 @@ class Serializer extends Component
return null;
} else {
list ($fields, $expand) = $this->getRequestedFields();
return $model->toArray($fields, $expand);
}
}
@ -259,7 +261,7 @@ class Serializer extends Component
{
list ($fields, $expand) = $this->getRequestedFields();
foreach ($models as $i => $model) {
if ($model instanceof Model) {
if ($model instanceof Arrayable) {
$models[$i] = $model->toArray($fields, $expand);
} elseif (is_array($model)) {
$models[$i] = ArrayHelper::toArray($model);

36
framework/validators/ExistValidator.php

@ -63,6 +63,11 @@ class ExistValidator extends Validator
public $filter;
/**
* @var boolean whether to allow array type attribute.
*/
public $allowArray = false;
/**
* @inheritdoc
*/
public function init()
@ -81,6 +86,9 @@ class ExistValidator extends Validator
$targetAttribute = $this->targetAttribute === null ? $attribute : $this->targetAttribute;
if (is_array($targetAttribute)) {
if ($this->allowArray) {
throw new InvalidConfigException('The "targetAttribute" property must be configured as a string.');
}
$params = [];
foreach ($targetAttribute as $k => $v) {
$params[$v] = is_integer($k) ? $object->$v : $object->$k;
@ -89,18 +97,24 @@ class ExistValidator extends Validator
$params = [$targetAttribute => $object->$attribute];
}
foreach ($params as $value) {
if (is_array($value)) {
$this->addError($object, $attribute, Yii::t('yii', '{attribute} is invalid.'));
if (!$this->allowArray) {
foreach ($params as $value) {
if (is_array($value)) {
$this->addError($object, $attribute, Yii::t('yii', '{attribute} is invalid.'));
return;
return;
}
}
}
$targetClass = $this->targetClass === null ? get_class($object) : $this->targetClass;
$query = $this->createQuery($targetClass, $params);
if (!$query->exists()) {
if (is_array($object->$attribute)) {
if ($query->count("DISTINCT [[$targetAttribute]]") != count($object->$attribute)) {
$this->addError($object, $attribute, $this->message);
}
} elseif (!$query->exists()) {
$this->addError($object, $attribute, $this->message);
}
}
@ -110,9 +124,6 @@ class ExistValidator extends Validator
*/
protected function validateValue($value)
{
if (is_array($value)) {
return [$this->message, []];
}
if ($this->targetClass === null) {
throw new InvalidConfigException('The "targetClass" property must be set.');
}
@ -122,7 +133,14 @@ class ExistValidator extends Validator
$query = $this->createQuery($this->targetClass, [$this->targetAttribute => $value]);
return $query->exists() ? null : [$this->message, []];
if (is_array($value)) {
if (!$this->allowArray) {
return [$this->message, []];
}
return $query->count("DISTINCT [[$this->targetAttribute]]") == count($value) ? null : [$this->message, []];
} else {
return $query->exists() ? null : [$this->message, []];
}
}
/**

2
framework/validators/UniqueValidator.php

@ -38,7 +38,7 @@ class UniqueValidator extends Validator
{
/**
* @var string the name of the ActiveRecord class that should be used to validate the uniqueness
* of the current attribute value. It not set, it will use the ActiveRecord class of the attribute being validated.
* of the current attribute value. If not set, it will use the ActiveRecord class of the attribute being validated.
* @see targetAttribute
*/
public $targetClass;

1
framework/validators/Validator.php

@ -10,6 +10,7 @@ namespace yii\validators;
use Yii;
use yii\base\Component;
use yii\base\NotSupportedException;
use yii\base\InvalidConfigException;
/**
* Validator is the base class for all validators.

15
framework/views/errorHandler/exception.php

@ -87,6 +87,9 @@ html,body{
font-size: 20px;
line-height: 1.25;
}
.header pre{
margin: 10px 0;
}
/* previous exceptions */
.header .previous{
@ -104,8 +107,8 @@ html,body{
filter: progid:DXImageTransform.Microsoft.BasicImage(mirror=1);
font-size: 26px;
position: absolute;
margin-top: -5px;
margin-left: -25px;
margin-top: -3px;
margin-left: -30px;
color: #e51717;
}
.header .previous h2{
@ -130,6 +133,10 @@ html,body{
font-size: 14px;
color: #aaa;
}
.header .previous pre{
font-size: 14px;
margin: 10px 0;
}
/* call stack */
.call-stack{
@ -348,6 +355,10 @@ html,body{
<?php endif; ?>
<h2><?= nl2br($handler->htmlEncode($exception->getMessage())) ?></h2>
<?php if ($exception instanceof \yii\db\Exception && !empty($exception->errorInfo)) {
echo '<pre>Error Info: ' . print_r($exception->errorInfo, true) . '</pre>';
} ?>
<?= $handler->renderPreviousExceptions($exception) ?>
</div>

3
framework/views/errorHandler/previousException.php

@ -18,5 +18,8 @@
</h2>
<h3><?= nl2br($handler->htmlEncode($exception->getMessage())) ?></h3>
<p>in <span class="file"><?= $exception->getFile() ?></span> at line <span class="line"><?= $exception->getLine() ?></span></p>
<?php if ($exception instanceof \yii\db\Exception && !empty($exception->errorInfo)) {
echo '<pre>Error Info: ' . print_r($exception->errorInfo, true) . '</pre>';
} ?>
<?= $handler->renderPreviousExceptions($exception) ?>
</div>

12
framework/web/Application.php

@ -149,17 +149,7 @@ class Application extends \yii\base\Application
'response' => ['class' => 'yii\web\Response'],
'session' => ['class' => 'yii\web\Session'],
'user' => ['class' => 'yii\web\User'],
'errorHandler' => ['class' => 'yii\web\ErrorHandler'],
]);
}
/**
* Registers the errorHandler component as a PHP error handler.
*/
protected function registerErrorHandler(&$config)
{
if (!isset($config['components']['errorHandler']['class'])) {
$config['components']['errorHandler']['class'] = 'yii\\web\\ErrorHandler';
}
parent::registerErrorHandler($config);
}
}

2
framework/web/Controller.php

@ -105,7 +105,7 @@ class Controller extends \yii\base\Controller
public function beforeAction($action)
{
if (parent::beforeAction($action)) {
if ($this->enableCsrfValidation && Yii::$app->errorHandler->exception === null && !Yii::$app->getRequest()->validateCsrfToken()) {
if ($this->enableCsrfValidation && Yii::$app->getErrorHandler()->exception === null && !Yii::$app->getRequest()->validateCsrfToken()) {
throw new BadRequestHttpException(Yii::t('yii', 'Unable to verify your data submission.'));
}
return true;

2
framework/web/ErrorAction.php

@ -68,7 +68,7 @@ class ErrorAction extends Action
public function run()
{
if (($exception = Yii::$app->errorHandler->exception) === null) {
if (($exception = Yii::$app->getErrorHandler()->exception) === null) {
return '';
}

5
framework/web/ErrorHandler.php

@ -127,6 +127,9 @@ class ErrorHandler extends \yii\base\ErrorHandler
}
if (YII_DEBUG) {
$array['stack-trace'] = explode("\n", $exception->getTraceAsString());
if ($exception instanceof \yii\db\Exception) {
$array['error-info'] = $this->errorInfo;
}
}
if (($prev = $exception->getPrevious()) !== null) {
$array['previous'] = $this->convertExceptionToArray($prev);
@ -169,7 +172,7 @@ class ErrorHandler extends \yii\base\ErrorHandler
$page = $this->htmlEncode(strtolower(str_replace('\\', '-', $class)));
$url = "http://www.yiiframework.com/doc-2.0/$page.html";
if (isset($method)) {
$url .= "#$method-detail";
$url .= "#$method()-detail";
}
return '<a href="' . $url . '" target="_blank">' . $text . '</a>';

2
framework/web/Response.php

@ -609,8 +609,8 @@ class Response extends \yii\base\Response
* ~~~
*
* @param string $filePath file name with full path
* @param string $mimeType the MIME type of the file. If null, it will be determined based on `$filePath`.
* @param string $attachmentName file name shown to the user. If null, it will be determined from `$filePath`.
* @param string $mimeType the MIME type of the file. If null, it will be determined based on `$filePath`.
* @param string $xHeader the name of the x-sendfile header.
* @return static the response object itself
*/

32
framework/web/Session.php

@ -591,7 +591,6 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
public function has($key)
{
$this->open();
return isset($_SESSION[$key]);
}
@ -625,6 +624,10 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
* @param boolean $delete whether to delete this flash message right after this method is called.
* If false, the flash message will be automatically deleted after the next request.
* @return mixed the flash message
* @see setFlash()
* @see hasFlash()
* @see getAllFlashes()
* @see removeFlash()
*/
public function getFlash($key, $defaultValue = null, $delete = false)
{
@ -643,7 +646,26 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
/**
* Returns all flash messages.
*
* You may use this method to display all the flash messages in a view file:
*
* ```php
* <?php
* foreach(Yii::$app->session->getAllFlashes() as $key => $message) {
* echo '<div class="alert alert-' . $key . '">' . $message . '</div>';
* } ?>
* ```
*
* With the above code you can use the [bootstrap alert][] classes such as `success`, `info`, `danger`
* as the flash message key to influence the color of the div.
*
* [bootstrap alert]: http://getbootstrap.com/components/#alerts
*
* @return array flash messages (key => message).
* @see setFlash()
* @see getFlash()
* @see hasFlash()
* @see removeFlash()
*/
public function getAllFlashes()
{
@ -665,6 +687,8 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
* and normal session variables share the same name space. If you have a normal
* session variable using the same name, its value will be overwritten by this method.
* @param mixed $value flash message
* @see getFlash()
* @see removeFlash()
*/
public function setFlash($key, $value = true)
{
@ -681,6 +705,9 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
* and normal session variables share the same name space. If you have a normal
* session variable using the same name, it will be removed by this method.
* @return mixed the removed flash message. Null if the flash message does not exist.
* @see getFlash()
* @see setFlash()
* @see removeAllFlashes()
*/
public function removeFlash($key)
{
@ -697,6 +724,9 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
* Note that flash messages and normal session variables share the same name space.
* If you have a normal session variable using the same name, it will be removed
* by this method.
* @see getFlash()
* @see setFlash()
* @see removeFlash()
*/
public function removeAllFlashes()
{

16
framework/widgets/ActiveForm.php

@ -25,11 +25,23 @@ class ActiveForm extends Widget
{
/**
* @param array|string $action the form action URL. This parameter will be processed by [[\yii\helpers\Url::to()]].
* @see method for specifying the HTTP method for this form.
*/
public $action = '';
/**
* @var string the form submission method. This should be either 'post' or 'get'.
* Defaults to 'post'.
* @var string the form submission method. This should be either 'post' or 'get'. Defaults to 'post'.
*
* When you set this to 'get' you may see the url parameters repeated on each request.
* This is because the default value of [[action]] is set to be the current request url and each submit
* will add new parameters instead of replacing existing ones.
* You may set [[action]] explicitly to avoid this:
*
* ```php
* $form = ActiveForm::begin([
* 'method' => 'get',
* 'action' => ['controller/action'],
* ]);
* ```
*/
public $method = 'post';
/**

10
tests/unit/framework/caching/CacheTestCase.php

@ -58,16 +58,6 @@ abstract class CacheTestCase extends TestCase
return $cache;
}
/**
* default value of cache prefix is application id
*/
public function testKeyPrefix()
{
$cache = $this->getCacheInstance();
$this->assertNotNull(\Yii::$app->id);
$this->assertNotNull($cache->keyPrefix);
}
public function testSet()
{
$cache = $this->getCacheInstance();

1
tests/unit/framework/validators/BooleanValidatorTest.php

@ -23,6 +23,7 @@ class BooleanValidatorTest extends TestCase
$this->assertTrue($val->validate(false));
$this->assertTrue($val->validate('0'));
$this->assertTrue($val->validate('1'));
$this->assertFalse($val->validate('5'));
$this->assertFalse($val->validate(null));
$this->assertFalse($val->validate([]));
$val->strict = true;

35
tests/unit/framework/validators/ExistValidatorTest.php

@ -89,10 +89,39 @@ class ExistValidatorTest extends DatabaseTestCase
$m->a_field = 'some new value';
$val->validateAttribute($m, 'a_field');
$this->assertTrue($m->hasErrors('a_field'));
// check array
// existing array
$val = new ExistValidator(['targetAttribute' => 'ref']);
$m = ValidatorTestRefModel::findOne(['id' => 2]);
$m->test_val = [1,2,3];
$val->allowArray = true;
$m = new ValidatorTestRefModel();
$m->test_val = [2, 3, 4, 5];
$val->validateAttribute($m, 'test_val');
$this->assertFalse($m->hasErrors('test_val'));
// non-existing array
$val = new ExistValidator(['targetAttribute' => 'ref']);
$val->allowArray = true;
$m = new ValidatorTestRefModel();
$m->test_val = [95, 96, 97, 98];
$val->validateAttribute($m, 'test_val');
$this->assertTrue($m->hasErrors('test_val'));
// partial-existing array
$val = new ExistValidator(['targetAttribute' => 'ref']);
$val->allowArray = true;
$m = new ValidatorTestRefModel();
$m->test_val = [2, 97, 3, 98];
$val->validateAttribute($m, 'test_val');
$this->assertTrue($m->hasErrors('test_val'));
// existing array (allowArray = false)
$val = new ExistValidator(['targetAttribute' => 'ref']);
$val->allowArray = false;
$m = new ValidatorTestRefModel();
$m->test_val = [2, 3, 4, 5];
$val->validateAttribute($m, 'test_val');
$this->assertTrue($m->hasErrors('test_val'));
// non-existing array (allowArray = false)
$val = new ExistValidator(['targetAttribute' => 'ref']);
$val->allowArray = false;
$m = new ValidatorTestRefModel();
$m->test_val = [95, 96, 97, 98];
$val->validateAttribute($m, 'test_val');
$this->assertTrue($m->hasErrors('test_val'));
}

Loading…
Cancel
Save