Browse Source

Merge branch 'master' of github.com:yiisoft/yii2

tags/2.0.0-rc
RichWeber 10 years ago
parent
commit
9e925f6a54
  1. 4
      .travis.yml
  2. 4
      apps/advanced/README.md
  3. 5
      apps/advanced/backend/codeception.yml
  4. 13
      apps/advanced/backend/tests/_bootstrap.php
  5. 1
      apps/advanced/backend/tests/acceptance/LoginCept.php
  6. 1
      apps/advanced/backend/tests/functional/LoginCept.php
  7. 4
      apps/advanced/backend/tests/functional/_config.php
  8. 11
      apps/advanced/codeception.yml
  9. 1
      apps/advanced/common/codeception.yml
  10. 9
      apps/advanced/common/tests/_bootstrap.php
  11. 1
      apps/advanced/console/codeception.yml
  12. 9
      apps/advanced/console/tests/_bootstrap.php
  13. 5
      apps/advanced/frontend/codeception.yml
  14. 13
      apps/advanced/frontend/tests/_bootstrap.php
  15. 1
      apps/advanced/frontend/tests/acceptance/AboutCept.php
  16. 1
      apps/advanced/frontend/tests/acceptance/ContactCept.php
  17. 2
      apps/advanced/frontend/tests/acceptance/HomeCept.php
  18. 1
      apps/advanced/frontend/tests/acceptance/LoginCept.php
  19. 1
      apps/advanced/frontend/tests/functional/AboutCept.php
  20. 1
      apps/advanced/frontend/tests/functional/ContactCept.php
  21. 2
      apps/advanced/frontend/tests/functional/HomeCept.php
  22. 1
      apps/advanced/frontend/tests/functional/LoginCept.php
  23. 4
      apps/advanced/frontend/tests/functional/_config.php
  24. 2
      apps/advanced/tests/_log/.gitignore
  25. 124
      docs/guide-ru/rest-authentication.md
  26. 152
      docs/guide-ru/rest-controllers.md
  27. 78
      docs/guide-ru/rest-routing.md
  28. 2
      docs/guide/db-active-record.md
  29. 47
      docs/guide/output-data-widgets.md
  30. 19
      docs/guide/test-acceptance.md
  31. 1
      extensions/composer/CHANGELOG.md
  32. 12
      extensions/composer/Installer.php
  33. 3
      extensions/smarty/Extension.php
  34. 2
      extensions/smarty/README.md
  35. 10
      framework/CHANGELOG.md
  36. 117
      framework/behaviors/SluggableBehavior.php
  37. 86
      framework/caching/ArrayCache.php
  38. 16
      framework/db/Schema.php
  39. 2
      framework/db/cubrid/Schema.php
  40. 2
      framework/db/mssql/Schema.php
  41. 2
      framework/db/mysql/Schema.php
  42. 2
      framework/db/oci/Schema.php
  43. 40
      framework/db/pgsql/QueryBuilder.php
  44. 4
      framework/db/pgsql/Schema.php
  45. 2
      framework/db/sqlite/Schema.php
  46. 18
      framework/helpers/BaseHtml.php
  47. 30
      framework/helpers/BaseHtmlPurifier.php
  48. 2
      framework/widgets/ActiveField.php
  49. 12
      tests/README.md
  50. 3
      tests/unit/.gitignore
  51. 22
      tests/unit/data/config.php
  52. 8
      tests/unit/data/postgres.sql
  53. 9
      tests/unit/extensions/smarty/ViewRendererTest.php
  54. 6
      tests/unit/extensions/sphinx/SphinxTestCase.php
  55. 7
      tests/unit/extensions/twig/ViewRendererTest.php
  56. 193
      tests/unit/framework/behaviors/SluggableBehaviorTest.php
  57. 49
      tests/unit/framework/caching/ArrayCacheTest.php
  58. 18
      tests/unit/framework/caching/CacheTestCase.php
  59. 5
      tests/unit/framework/caching/MemCacheTest.php
  60. 5
      tests/unit/framework/caching/MemCachedTest.php
  61. 50
      tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php
  62. 35
      tests/unit/framework/db/pgsql/PostgreSQLCommandTest.php
  63. 24
      tests/unit/framework/db/pgsql/PostgreSQLQueryTest.php
  64. 10
      tests/unit/framework/db/pgsql/PostgreSQLSchemaTest.php
  65. 2
      tests/unit/framework/db/sqlite/SqliteConnectionTest.php

4
.travis.yml

@ -21,6 +21,10 @@ services:
- elasticsearch
- mongodb
# try running against postgres 9.3
addons:
postgresql: "9.3"
install:
- composer self-update && composer --version
# core framework:

4
apps/advanced/README.md

@ -113,7 +113,7 @@ it will upgrade your database to the last state according migrations.
To be able to run acceptance tests you need a running webserver. For this you can use the php builtin server and run it in the directory where your main project folder is located. For example if your application is located in `/www/advanced` all you need to is:
`cd /www` and then `php -S 127.0.0.1:8080` because the default configuration of acceptance tests expects the url of the application to be `/advanced/`.
If you already have a server configured or your application is not located in a folder called `advanced`, you may need to adjust the `TEST_ENTRY_URL` in `frontend/tests/_bootstrap.php` and `backend/tests/_bootstrap.php`.
If you already have a server configured or your application is not located in a folder called `advanced`, you may need to adjust the `test_entry_url` in `backend/codeception.yml` and `frontend/codeception.yml`.
After that is done you should be able to run your tests, for example to run `frontend` tests do:
@ -123,5 +123,7 @@ After that is done you should be able to run your tests, for example to run `fro
In similar way you can run tests for other application tiers - `backend`, `console`, `common`.
If you already have run `../vendor/bin/codecept build` for each application, you can run all tests by one command: `vendor/bin/codecept run`
You also can adjust you application suite configs and `_bootstrap.php` settings to use other urls and files, as it is can be done in `yii2-basic`.
Current template also includes [yii2-faker](https://github.com/yiisoft/yii2/tree/master/extensions/faker) extension, that is correctly setup for each application tier.

5
apps/advanced/backend/codeception.yml

@ -1,3 +1,4 @@
namespace: backend
actor: Tester
paths:
tests: tests
@ -17,3 +18,7 @@ modules:
user: ''
password: ''
dump: tests/_data/dump.sql
config:
# the entry script URL (without host info) for functional and acceptance tests
# PLEASE ADJUST IT TO THE ACTUAL ENTRY SCRIPT URL
test_entry_url: /advanced/backend/web/index-test.php

13
apps/advanced/backend/tests/_bootstrap.php

@ -1,12 +1,5 @@
<?php
// the entry script URL (without host info) for functional and acceptance tests
// PLEASE ADJUST IT TO THE ACTUAL ENTRY SCRIPT URL
defined('TEST_ENTRY_URL') or define('TEST_ENTRY_URL', '/advanced/backend/web/index-test.php');
// the entry script file path for functional and acceptance tests
defined('TEST_ENTRY_FILE') or define('TEST_ENTRY_FILE', dirname(__DIR__) . '/web/index-test.php');
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'test');
@ -18,6 +11,8 @@ require_once(__DIR__ . '/../../vendor/yiisoft/yii2/Yii.php');
require(__DIR__ . '/../../common/config/aliases.php');
// set correct script paths
$_SERVER['SCRIPT_FILENAME'] = TEST_ENTRY_FILE;
$_SERVER['SCRIPT_NAME'] = TEST_ENTRY_URL;
// the entry script file path for functional and acceptance tests
$_SERVER['SCRIPT_FILENAME'] = dirname(__DIR__) . '/web/index-test.php';
$_SERVER['SCRIPT_NAME'] = \Codeception\Configuration::config()['config']['test_entry_url'];
$_SERVER['SERVER_NAME'] = 'localhost';

1
apps/advanced/backend/tests/acceptance/LoginCept.php

@ -1,6 +1,7 @@
<?php
use common\tests\_pages\LoginPage;
use backend\WebGuy;
$I = new WebGuy($scenario);
$I->wantTo('ensure login page works');

1
apps/advanced/backend/tests/functional/LoginCept.php

@ -1,6 +1,7 @@
<?php
use common\tests\_pages\LoginPage;
use backend\TestGuy;
$I = new TestGuy($scenario);
$I->wantTo('ensure login page works');

4
apps/advanced/backend/tests/functional/_config.php

@ -1,8 +1,8 @@
<?php
// set correct script paths
$_SERVER['SCRIPT_FILENAME'] = TEST_ENTRY_FILE;
$_SERVER['SCRIPT_NAME'] = TEST_ENTRY_URL;
$_SERVER['SCRIPT_FILENAME'] = dirname(dirname(__DIR__)) . '/web/index-test.php';
$_SERVER['SCRIPT_NAME'] = \Codeception\Configuration::config()['config']['test_entry_url'];;
return yii\helpers\ArrayHelper::merge(
require(__DIR__ . '/../../config/main.php'),

11
apps/advanced/codeception.yml

@ -0,0 +1,11 @@
include:
- common
- console
- backend
- frontend
paths:
log: tests/_log
settings:
colors: true

1
apps/advanced/common/codeception.yml

@ -1,3 +1,4 @@
namespace: common
actor: Tester
paths:
tests: tests

9
apps/advanced/common/tests/_bootstrap.php

@ -1,12 +1,5 @@
<?php
// the entry script URL (without host info) for functional and acceptance tests
// PLEASE ADJUST IT TO THE ACTUAL ENTRY SCRIPT URL
defined('TEST_ENTRY_URL') or define('TEST_ENTRY_URL', '/index-test.php');
// the entry script file path for functional and acceptance tests
defined('TEST_ENTRY_FILE') or define('TEST_ENTRY_FILE', dirname(__DIR__) . '/index-test.php');
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'test');
@ -18,6 +11,4 @@ require_once(__DIR__ . '/../../vendor/yiisoft/yii2/Yii.php');
require(__DIR__ . '/../../common/config/aliases.php');
// set correct script paths
$_SERVER['SCRIPT_FILENAME'] = TEST_ENTRY_FILE;
$_SERVER['SCRIPT_NAME'] = TEST_ENTRY_URL;
$_SERVER['SERVER_NAME'] = 'localhost';

1
apps/advanced/console/codeception.yml

@ -1,3 +1,4 @@
namespace: console
actor: Tester
paths:
tests: tests

9
apps/advanced/console/tests/_bootstrap.php

@ -1,12 +1,5 @@
<?php
// the entry script URL (without host info) for functional and acceptance tests
// PLEASE ADJUST IT TO THE ACTUAL ENTRY SCRIPT URL
defined('TEST_ENTRY_URL') or define('TEST_ENTRY_URL', '/index-test.php');
// the entry script file path for functional and acceptance tests
defined('TEST_ENTRY_FILE') or define('TEST_ENTRY_FILE', dirname(__DIR__) . '/index-test.php');
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'test');
@ -18,6 +11,4 @@ require_once(__DIR__ . '/../../vendor/yiisoft/yii2/Yii.php');
require(__DIR__ . '/../../common/config/aliases.php');
// set correct script paths
$_SERVER['SCRIPT_FILENAME'] = TEST_ENTRY_FILE;
$_SERVER['SCRIPT_NAME'] = TEST_ENTRY_URL;
$_SERVER['SERVER_NAME'] = 'localhost';

5
apps/advanced/frontend/codeception.yml

@ -1,3 +1,4 @@
namespace: frontend
actor: Tester
paths:
tests: tests
@ -17,3 +18,7 @@ modules:
user: ''
password: ''
dump: tests/_data/dump.sql
config:
# the entry script URL (without host info) for functional and acceptance tests
# PLEASE ADJUST IT TO THE ACTUAL ENTRY SCRIPT URL
test_entry_url: /advanced/frontend/web/index-test.php

13
apps/advanced/frontend/tests/_bootstrap.php

@ -1,12 +1,5 @@
<?php
// the entry script URL (without host info) for functional and acceptance tests
// PLEASE ADJUST IT TO THE ACTUAL ENTRY SCRIPT URL
defined('TEST_ENTRY_URL') or define('TEST_ENTRY_URL', '/advanced/frontend/web/index-test.php');
// the entry script file path for functional and acceptance tests
defined('TEST_ENTRY_FILE') or define('TEST_ENTRY_FILE', dirname(__DIR__) . '/web/index-test.php');
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'test');
@ -18,6 +11,8 @@ require_once(__DIR__ . '/../../vendor/yiisoft/yii2/Yii.php');
require(__DIR__ . '/../../common/config/aliases.php');
// set correct script paths
$_SERVER['SCRIPT_FILENAME'] = TEST_ENTRY_FILE;
$_SERVER['SCRIPT_NAME'] = TEST_ENTRY_URL;
// the entry script file path for functional and acceptance tests
$_SERVER['SCRIPT_FILENAME'] = dirname(__DIR__) . '/web/index-test.php';
$_SERVER['SCRIPT_NAME'] = \Codeception\Configuration::config()['config']['test_entry_url'];
$_SERVER['SERVER_NAME'] = 'localhost';

1
apps/advanced/frontend/tests/acceptance/AboutCept.php

@ -1,6 +1,7 @@
<?php
use frontend\tests\_pages\AboutPage;
use frontend\WebGuy;
$I = new WebGuy($scenario);
$I->wantTo('ensure that about works');

1
apps/advanced/frontend/tests/acceptance/ContactCept.php

@ -1,6 +1,7 @@
<?php
use frontend\tests\_pages\ContactPage;
use frontend\WebGuy;
$I = new WebGuy($scenario);
$I->wantTo('ensure that contact works');

2
apps/advanced/frontend/tests/acceptance/HomeCept.php

@ -1,5 +1,7 @@
<?php
use frontend\WebGuy;
$I = new WebGuy($scenario);
$I->wantTo('ensure that home page works');
$I->amOnPage(Yii::$app->homeUrl);

1
apps/advanced/frontend/tests/acceptance/LoginCept.php

@ -1,6 +1,7 @@
<?php
use common\tests\_pages\LoginPage;
use frontend\WebGuy;
$I = new WebGuy($scenario);
$I->wantTo('ensure login page works');

1
apps/advanced/frontend/tests/functional/AboutCept.php

@ -1,6 +1,7 @@
<?php
use frontend\tests\_pages\AboutPage;
use frontend\TestGuy;
$I = new TestGuy($scenario);
$I->wantTo('ensure that about works');

1
apps/advanced/frontend/tests/functional/ContactCept.php

@ -1,6 +1,7 @@
<?php
use frontend\tests\_pages\ContactPage;
use frontend\TestGuy;
$I = new TestGuy($scenario);
$I->wantTo('ensure that contact works');

2
apps/advanced/frontend/tests/functional/HomeCept.php

@ -1,5 +1,7 @@
<?php
use frontend\TestGuy;
$I = new TestGuy($scenario);
$I->wantTo('ensure that home page works');
$I->amOnPage(Yii::$app->homeUrl);

1
apps/advanced/frontend/tests/functional/LoginCept.php

@ -1,6 +1,7 @@
<?php
use common\tests\_pages\LoginPage;
use frontend\TestGuy;
$I = new TestGuy($scenario);
$I->wantTo('ensure login page works');

4
apps/advanced/frontend/tests/functional/_config.php

@ -1,8 +1,8 @@
<?php
// set correct script paths
$_SERVER['SCRIPT_FILENAME'] = TEST_ENTRY_FILE;
$_SERVER['SCRIPT_NAME'] = TEST_ENTRY_URL;
$_SERVER['SCRIPT_FILENAME'] = dirname(dirname(__DIR__)) . '/web/index-test.php';
$_SERVER['SCRIPT_NAME'] = \Codeception\Configuration::config()['config']['test_entry_url'];;
return yii\helpers\ArrayHelper::merge(
require(__DIR__ . '/../../config/main.php'),

2
apps/advanced/tests/_log/.gitignore vendored

@ -0,0 +1,2 @@
*
!.gitignore

124
docs/guide-ru/rest-authentication.md

@ -0,0 +1,124 @@
Аутентификация
==============
В отличие от Web-приложений, RESTful API обычно не сохраняют информацию о состоянии, а это означает, что сессии и куки
использовать не следует. Следовательно, раз состояние аутентификации пользователя не может быть сохранено в сессиях или куках,
каждый запрос должен приходить вместе с определенным видом параметров аутентификации. Общепринятая практика состоит в том,
что для аутентификации пользователя с каждый запросом отправляется секретный токен доступа. Так как токен доступа
может использоваться для уникальной идентификации и аутентификации пользователя, **запросы к API всегда должны отсылаться
через протокол HTTPS, чтобы предотвратить атаки «человек посередине» (англ. "man-in-the-middle", MitM)**.
Есть различные способы отправки токена доступа:
* [HTTP Basic Auth](http://en.wikipedia.org/wiki/Basic_access_authentication): токен доступа
отправляется как имя пользователя. Такой подход следует использовать только в том случае, когда токен доступа может быть безопасно сохранен
на стороне абонента API. Например, если API используется программой, запущенной на сервере.
* Параметр запроса: токен доступа отправляется как параметр запроса в URL-адресе API, т.е. примерно таким образом:
`https://example.com/users?access-token=xxxxxxxx`. Так как большинство Web-серверов сохраняют параметры запроса в своих логах,
такой подход следует применять только при работе с `JSONP`-запросами, которые не могут отправлять токены доступа
в HTTP-заголовках.
* [OAuth 2](http://oauth.net/2/): токен доступа выдается абоненту API сервером авторизации
и отправляется API-серверу через [HTTP Bearer Tokens](http://tools.ietf.org/html/rfc6750),
в соответствии с протоколом OAuth2.
Yii поддерживает все выше перечисленные методы аутентификации. Вы также можете легко создавать новые методы аутентификации.
Чтобы включить аутентификацию для ваших API, выполните следующие шаги:
1. У компонента приложения `user` установите свойство [[yii\web\User::enableSession|enableSession]] равным false.
2. Укажите, какие методы аутентификации вы планируете использовать, настроив поведение `authenticator`
в ваших классах REST-контроллеров.
3. Реализуйте метод [[yii\web\IdentityInterface::findIdentityByAccessToken()]] *в вашем [[yii\web\User::identityClass|классе UserIdentity]]*.
Шаг 1 не обязателен, но рекомендуется его все-таки выполнить, так как RESTful API не должны сохранять информацию о состоянии клиента. Когда свойство [[yii\web\User::enableSession|enableSession]]
установлено в false, состояние аутентификации пользователя НЕ БУДЕТ постоянно
сохраняться между запросами с использованием сессий. Вместо этого аутентификация будет выполняться для каждого запроса, что достигается шагами 2 и 3.
> Подсказка: если вы разрабатываете RESTful API в пределах приложения, вы можете настроить свойство
[[yii\web\User::enableSession|enableSession]] компонента приложения `user` в конфигурации приложения. Если вы разрабатываете
RESTful API как модуль, можете добавить следующую строчку в метод `init()` модуля:
> ```php
public function init()
{
parent::init();
\Yii::$app->user->enableSession = false;
}
```
Например, для использования HTTP Basic Auth, вы можете настроить свойство `authenticator` следующим образом:
```php
use yii\filters\auth\HttpBasicAuth;
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
'class' => HttpBasicAuth::className(),
];
return $behaviors;
}
```
Если вы хотите включить поддержку всех трех описанных выше методов аутентификации, можете использовать `CompositeAuth`:
```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;
}
```
Каждый элемент в массиве `authMethods` должен быть названием класса метода аутентификации или массивом настроек.
Реализация метода `findIdentityByAccessToken()` определяется особенностями приложения. Например, в простом варианте,
когда у каждого пользователя есть только один токен доступа, вы можете хранить этот токен в поле `access_token`
таблицы пользователей. В этом случае метод `findIdentityByAccessToken()` может быть легко реализован в классе `User` следующим образом:
```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]);
}
}
```
После включения аутентификации описанным выше способом при каждом запросе к API запрашиваемый контроллер
будет пытаться аутентифицировать пользователя в своем методе `beforeAction()`.
Если аутентификация прошла успешно, контроллер выполнит другие проверки (ограничение на количество запросов, авторизация)
и затем выполнит действие. *Информация о подлинности аутентифицированного пользователя может быть получена из объекта `Yii::$app->user->identity`*.
Если аутентификация прошла неудачно, будет возвращен ответ с HTTP-кодом состояния 401 вместе с другими необходимыми заголовками
(такими, как заголовок `WWW-Authenticate` для HTTP Basic Auth).
## Авторизация <a name="authorization"></a>
После аутентификации пользователя вы, вероятно, захотите проверить, есть ли у него или у нее разрешение на выполнение запрошенного
действия с запрошенным ресурсом. Этот процесс называется *авторизацией* и подробно описан
в разделе [Авторизация](security-authorization.md).
Если ваши контроллеры унаследованы от [[yii\rest\ActiveController]], вы можете переопределить
метод [[yii\rest\Controller::checkAccess()|checkAccess()]] для выполнения авторизации. Этот метод будет вызываться
встроенными действиями, предоставляемыми контроллером [[yii\rest\ActiveController]].

152
docs/guide-ru/rest-controllers.md

@ -0,0 +1,152 @@
Контроллеры
===========
После создания классов ресурсов и настройки способа форматирования ресурсных данных следующим шагом
является создание действий контроллеров для предоставления ресурсов конечным пользователям через RESTful API.
В Yii есть два базовых класса контроллеров для упрощения вашей работы по созданию RESTful-действий:
[[yii\rest\Controller]] и [[yii\rest\ActiveController]]. Разница между этими двумя контроллерами в том,
что у последнего есть набор действий по умолчанию, который специально создан для работы с ресурсами,
представленными [Active Record](db-active-record.md). Так что если вы используете [Active Record](db-active-record.md)
и вас устраивает предоставленный набор встроенных действий, вы можете унаследовать классы ваших контроллеров
от [[yii\rest\ActiveController]], что позволит вам создать полноценные RESTful API, написав минимум кода.
[[yii\rest\Controller]] и [[yii\rest\ActiveController]] имеют следующие возможности, некоторые из которых
будут подробно описаны в следующих нескольких разделах:
* Проверка HTTP-метода;
* [Согласование содержимого и форматирование данных](rest-response-formatting.md);
* [Аутентификация](rest-authentication.md);
* [Ограничение частоты запросов](rest-rate-limiting.md).
[[yii\rest\ActiveController]], кроме того, предоставляет следующие возможности:
* Набор наиболее употребительных действий: `index`, `view`, `create`, `update`, `delete`, `options`;
* Авторизация пользователя для запрашиваемых действия и ресурса.
## Создание классов контроллеров <a name="creating-controller"></a>
При создании нового класса контроллера в имени класса обычно используется
название типа ресурса в единственном числе. Например, контроллер, отвечающий за предоставление информации о пользователях,
можно назвать `UserController`.
Создание нового действия похоже на создание действия для Web-приложения. Единственное отличие в том,
что в RESTful-действиях вместо рендера результата в представлении с помощью вызова метода `render()`
вы просто возвращает данные. Выполнение преобразования исходных данных в запрошенный формат ложится на
[[yii\rest\Controller::serializer|сериализатор]] и [[yii\web\Response|объект ответа]].
Например:
```php
public function actionView($id)
{
return User::findOne($id);
}
```
## Фильтры <a name="filters"></a>
Большинство возможностей RESTful API, предоставляемых [[yii\rest\Controller]], реализовано на основе [фильтров](structure-filters.md).
В частности, следующие фильтры будут выполняться в том порядке, в котором они перечислены:
* [[yii\filters\ContentNegotiator|contentNegotiator]]: обеспечивает согласование содержимого, более подробно описан
в разделе [Форматирование ответа](rest-response-formatting.md);
* [[yii\filters\VerbFilter|verbFilter]]: обеспечивает проверку HTTP-метода;
* [[yii\filters\AuthMethod|authenticator]]: обеспечивает аутентификацию пользователя, более подробно описан
в разделе [Аутентификация](rest-authentication.md);
* [[yii\filters\RateLimiter|rateLimiter]]: обеспечивает ограничение частоты запросов, более подробно описан
в разделе [Ограничение частоты запросов](rest-rate-limiting.md).
Эти именованные фильтры объявлены в методе [[yii\rest\Controller::behaviors()|behaviors()]].
Вы можете переопределить этот метод для настройки отдельных фильтров, отключения каких-то из них или для добавления ваших собственных фильтров.
Например, если вы хотите использовать только базовую HTTP-аутентификацию, вы можете написать такой код:
```php
use yii\filters\auth\HttpBasicAuth;
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
'class' => HttpBasicAuth::className(),
];
return $behaviors;
}
```
## Наследование от `ActiveController` <a name="extending-active-controller"></a>
Если ваш класс контроллера наследуется от [[yii\rest\ActiveController]], вам следует установить
значение его свойства [[yii\rest\ActiveController::modelClass||modelClass]] равным имени класса ресурса,
который вы планируете обслуживать с помощью этого контроллера. Класс ресурса должен быть унаследован от [[yii\db\ActiveRecord]].
### Настройка действий <a name="customizing-actions"></a>
По умолчанию [[yii\rest\ActiveController]] предоставляет набор из следующих действий:
* [[yii\rest\IndexAction|index]]: постраничный список ресурсов;
* [[yii\rest\ViewAction|view]]: возвращает подробную информацию об указанном ресурсе;
* [[yii\rest\CreateAction|create]]: создание нового ресурса;
* [[yii\rest\UpdateAction|update]]: обновление существующего ресурса;
* [[yii\rest\DeleteAction|delete]]: удаление указанного ресурса;
* [[yii\rest\OptionsAction|options]]: возвращает поддерживаемые HTTP-методы.
Все эти действия объявляются в методе [[yii\rest\ActiveController::actions()|actions()]].
Вы можете настроить эти действия или отключить какие-то из них, переопределив метод `actions()`, как показано ниже:
```php
public function actions()
{
$actions = parent::actions();
// отключить действия "delete" и "create"
unset($actions['delete'], $actions['create']);
// настроить подготовку провайдера данных с помощью метода "prepareDataProvider()"
$actions['index']['prepareDataProvider'] = [$this, 'prepareDataProvider'];
return $actions;
}
public function prepareDataProvider()
{
// подготовить и вернуть провайдер данных для действия "index"
}
```
Чтобы узнать, какие опции доступны для настройки классов отдельных действий, обратитесь к соответствующим разделам справочника классов.
### Выполнение контроля доступа <a name="performing-access-check"></a>
При предоставлении ресурсов через RESTful API часто бывает нужно проверять, имеет ли текущий пользователь разрешение
на доступ к запрошенному ресурсу (или ресурсам) и манипуляцию им (ими). Для [[yii\rest\ActiveController]] эта задача может быть решена
переопределением метода [[yii\rest\ActiveController::checkAccess()|checkAccess()]] следующим образом:
```php
/**
* Проверяет права текущего пользователя.
*
* Этот метод должен быть переопределен, чтобы проверить, имеет ли текущий пользователь
* право выполнения указанного действия над указанной моделью данных.
* Если у пользователя нет доступа, следует выбросить исключение [[ForbiddenHttpException]].
*
* @param string $action ID действия, которое надо выполнить
* @param \yii\base\Model $model модель, к которой нужно получить доступ. Если null, это означает, что модель, к которой нужно получить доступ, отсутствует.
* @param array $params дополнительные параметры
* @throws ForbiddenHttpException если у пользователя нет доступа
*/
public function checkAccess($action, $model = null, $params = [])
{
// проверить, имеет ли пользователь доступ к $action и $model
// выбросить ForbiddenHttpException, если доступ следует запретить
}
```
Метод `checkAccess()` будет вызван действиями по умолчанию контроллера [[yii\rest\ActiveController]]. Если вы создаете
новые действия и хотите в них выполнять контроль доступа, вы должны вызвать этот метод явно в своих новых действиях.
> Подсказка: вы можете реализовать метод `checkAccess()` с помощью ["Контроля доступа на основе ролей" (RBAC)](security-authorization.md).

78
docs/guide-ru/rest-routing.md

@ -0,0 +1,78 @@
Маршрутизация
=============
Имея готовые классы ресурсов и контроллеров, можно получить доступ к ресурсам, используя URL вроде
`http://localhost/index.php?r=user/create`, подобно тому, как вы это делаете с обычными Web-приложениями.
На деле вам обычно хочется включить «красивые» URL-адреса и использовать все преимущества HTTP-методов (HTTP-verbs).
Например, чтобы запрос `POST /users` означал обращение к действию `user/create`.
Это может быть легко сделано с помощью настройки компонента приложения `urlManager` в
конфигурации приложения следующим образом:
```php
'urlManager' => [
'enablePrettyUrl' => true,
'enableStrictParsing' => true,
'showScriptName' => false,
'rules' => [
['class' => 'yii\rest\UrlRule', 'controller' => 'user'],
],
]
```
Главная новинка в коде выше по сравнению с управлением URL-адресами в Web-приложениях состоит в использовании
[[yii\rest\UrlRule]] для маршрутизации запросов к RESTful API. Этот особый класс URL-правил будет
создавать целый набор дочерних URL-правил для поддержки маршрутизации и создания URL-адресов для указанного контроллера (или контроллеров).
Например, приведенный выше код является приближенным аналогом следующего набора правил:
```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',
]
```
Этим правилом поддерживаются следующие точки входа в API:
* `GET /users`: разбитый на страницы список всех пользователей;
* `HEAD /users`: общая информация по списку пользователей;
* `POST /users`: создание нового пользователя;
* `GET /users/123`: подробная информация о пользователе 123;
* `HEAD /users/123`: общая информация о пользователе 123;
* `PATCH /users/123` и `PUT /users/123`: обновление пользователя 123;
* `DELETE /users/123`: удаление пользователя 123;
* `OPTIONS /users`: список HTTP-методов, поддерживаемые точкой входа `/users`;
* `OPTIONS /users/123`: список HTTP-методов, поддерживаемые точкой входа `/users/123`.
Вы можете настроить опции `only` и `except`, явно указав для них список действий, которые поддерживаются или
которые должны быть отключены, соответственно. Например:
```php
[
'class' => 'yii\rest\UrlRule',
'controller' => 'user',
'except' => ['delete', 'create', 'update'],
],
```
Вы также можете настроить опции `patterns` или `extraPatterns` для переопределения существующих шаблонов или добавления новых шаблонов, поддерживаемых этим правилом.
Например, для включения нового действия `search` в точке входа `GET /users/search` настройте опцию `extraPatterns` следующим образом:
```php
[
'class' => 'yii\rest\UrlRule',
'controller' => 'user',
'extraPatterns' => [
'GET search' => 'search',
],
```
Как вы могли заметить, ID контроллера `user` в этих точках входа используется в форме множественного числа (как `users`).
Это происходит потому, что [[yii\rest\UrlRule]] автоматически приводит идентификаторы контроллеров к множественной форме для использования в точках входа.
Вы можете отключить такое поведение, назначив свойству [[yii\rest\UrlRule::pluralize]] значение false, или, если вы хотите использовать
какие-то особые имена, вы можете настроить свойство [[yii\rest\UrlRule::controller]].

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

@ -20,7 +20,7 @@ $customer->save();
```
The above code is equivalent to using the following raw SQL statement, which is less
intuitive, more error prone, and may have compatibility problem for different DBMS:
intuitive, more error prone, and may have compatibility problems for different DBMS:
```php
$db->createCommand('INSERT INTO customer (name) VALUES (:name)', [

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

@ -140,7 +140,52 @@ You may specify various container HTML options passing arrays to:
Data column is for displaying and sorting data. It is default column type so specifying class could be omitted when
using it.
TBD
The main setting of the data column is its format. It could be specified via `format` attribute. Its values are
corresponding to methods in `format` application component that is [[\yii\base\Formatter|Formatter]] by default:
```php
<?= GridView::widget([
'columns' => [
[
'attribute' => 'name',
'format' => 'text'
],
[
'attribute' => 'birthday',
'format' => ['date', 'Y-m-d']
],
],
]); ?>
```
In the above `text` corresponds to [[\yii\base\Formatter::asText()]]. The value of the column is passed as the first
argument. In the second column definition `date` corresponds to [[\yii\base\Formatter::asDate()]]. The value of the
column is, again, passed as the first argument while 'Y-m-d' is used as the second argument value.
Here's the bundled formatters list:
- [[\yii\base\Formatter::asRaw()|raw]] - the value is outputted as is.
- [[\yii\base\Formatter::asText()|text]] - the value is HTML-encoded. This format is used by default.
- [[\yii\base\Formatter::asNtext()|ntext]] - the value is formatted as an HTML-encoded plain text with newlines converted
into line breaks.
- [[\yii\base\Formatter::asParagraphs()|paragraphs]] - the value is formatted as HTML-encoded text paragraphs wrapped
into `<p>` tags.
- [[\yii\base\Formatter::asHtml()|html]] - the value is purified using [[HtmlPurifier]] to avoid XSS attacks. You can
pass additional options such as `['html', ['Attr.AllowedFrameTargets' => ['_blank']]]`.
- [[\yii\base\Formatter::asEmail()|email]] - the value is formatted as a mailto link.
- [[\yii\base\Formatter::asImage()|image]] - the value is formatted as an image tag.
- [[\yii\base\Formatter::asUrl()|url]] - the value is formatted as a hyperlink.
- [[\yii\base\Formatter::asBoolean()|boolean]] - the value is formatted as a boolean. You can set what's rendered for
true and false values by calling `Yii::$app->formatter->booleanFormat = ['No', 'Yes'];` before outputting GridView.
- [[\yii\base\Formatter::asDate()|date]] - the value is formatted as date.
- [[\yii\base\Formatter::asTime()|time]] - the value is formatted as time.
- [[\yii\base\Formatter::asDatetime()|datetime]] - the value is formatted as datetime.
- [[\yii\base\Formatter::asInteger()|integer]] - the value is formatted as an integer.
- [[\yii\base\Formatter::asDouble()|double]] - the value is formatted as a double number.
- [[\yii\base\Formatter::asNumber()|number]] - the value is formatted as a number with decimal and thousand separators.
- [[\yii\base\Formatter::asSize()|size]] - the value that is a number of bytes is formatted as a human readable size.
- [[\yii\base\Formatter::asRelativeTime()|relativeTime]] - the value is formatted as the time interval between a date
and now in human readable form.
#### Action column

19
docs/guide/test-acceptance.md

@ -7,5 +7,20 @@ Acceptance Tests
- https://github.com/yiisoft/yii2/blob/master/apps/advanced/README.md#testing
- https://github.com/yiisoft/yii2/blob/master/apps/basic/tests/README.md
How to run php-server
---------------------
How to run webserver
--------------------
In order to perform acceptance tests you need a web server. Since PHP 5.4 has built-in one, we can use it. For the basic
application template it would be:
```
cd web
php -S localhost:8080
```
In order for the tests to work correctly you need to adjust `TEST_ENTRY_URL` in `_bootstrap.php` file. It should point
to `index-test.php` of your webserver. Since we're running directly from its directory the line would be:
```php
defined('TEST_ENTRY_URL') or define('TEST_ENTRY_URL', '/index-test.php');
```

1
extensions/composer/CHANGELOG.md

@ -5,6 +5,7 @@ Yii Framework 2 composer extension Change Log
--------------------------
- Bug #3438: Fixed support for non-lowercase package names (cebe)
- Enh #4597: `yii\composer\Installer::setPermission()` supports setting permission for both directories and files now (qiangxue)
2.0.0-beta April 13, 2014
-------------------------

12
extensions/composer/Installer.php

@ -237,24 +237,22 @@ EOF
foreach ((array) $options[self::EXTRA_WRITABLE] as $path) {
echo "Setting writable: $path ...";
if (is_dir($path)) {
chmod($path, 0777);
if (is_dir($path) || is_file($path)) {
chmod($path, is_file($path) ? 0666 : 0777);
echo "done\n";
} else {
echo "The directory was not found: " . getcwd() . DIRECTORY_SEPARATOR . $path;
echo "The directory or file was not found: " . getcwd() . DIRECTORY_SEPARATOR . $path;
return;
}
}
foreach ((array) $options[self::EXTRA_EXECUTABLE] as $path) {
echo "Setting executable: $path ...";
if (is_file($path)) {
if (is_dir($path) || is_file($path)) {
chmod($path, 0755);
echo "done\n";
} else {
echo "\n\tThe file was not found: " . getcwd() . DIRECTORY_SEPARATOR . $path . "\n";
echo "\n\tThe directory or file was not found: " . getcwd() . DIRECTORY_SEPARATOR . $path . "\n";
return;
}
}

3
extensions/smarty/Extension.php

@ -10,6 +10,7 @@ namespace yii\smarty;
use Smarty;
use Yii;
use yii\helpers\ArrayHelper;
use yii\helpers\StringHelper;
use yii\helpers\Url;
use yii\web\View;
@ -133,7 +134,7 @@ class Extension
}
$class = $params['class'];
$alias = ArrayHelper::getValue($params, 'as', basename($params['class']));
$alias = ArrayHelper::getValue($params, 'as', StringHelper::basename($params['class']));
$type = ArrayHelper::getValue($params, 'type', 'static');
// Register the class during compile time

2
extensions/smarty/README.md

@ -39,3 +39,5 @@ or add
```
to the require section of your composer.json.
Note that the smarty composer package is distributed using subversion so you may need to install subversion.

10
framework/CHANGELOG.md

@ -57,6 +57,7 @@ Yii Framework 2 Change Log
- Bug #3863: Fixed incorrect js selector for `\yii\widgets\ActiveForm::errorSummaryCssClass` when it contains multiple classes (creocoder, umneeq)
- Bug #3893: Headers did not overwrite default setting by webserver (cebe)
- Bug #3909: `Html::to()` should not prefix base URL to URLs that already contain scheme (qiangxue)
- Bug #3920: Fixed issue with loading default values of PostgreSQL boolean columns (cebe)
- Bug #3934: yii.handleAction() in yii.js does not correctly detect if a hyperlink contains useful URL or not (joni-jones, qiangxue)
- Bug #3968: Messages logged in shutdown functions are not handled (qiangxue)
- Bug #3989: Fixed yii\log\FileTarget::$rotateByCopy to avoid any rename (cebe)
@ -69,6 +70,7 @@ Yii Framework 2 Change Log
- Bug #4241: `yii\widgets\Pjax` was incorrectly setting container id (mitalcoi)
- Bug #4276: Added check for UPLOAD_ERR_NO_FILE in `yii\web\UploadedFile` and return null if no file was uploaded (OmgDef)
- Bug #4342: mssql (dblib) driver does not support getting attributes (tof06)
- Bug #4371: Active form client validation wasn't working in case of two models having same named fields (abrahamy)
- Bug #4409: Upper case letters in subdirectory prefixes of controller IDs were not properly handled (qiangxue)
- Bug #4412: Formatter used SI Prefixes for base 1024, now uses binary prefixes (kmindi)
- Bug #4427: Formatter could do one division too much (kmindi)
@ -79,6 +81,7 @@ Yii Framework 2 Change Log
- Bug #4514: Fixed Request class crashing when empty CSRF token value is sent in cookie (cebe)
- Bug #4519: `yii\base\Model::isAttributeRequired()` should check if the `when` option of the validator is set (thiagotalma)
- Bug #4592: Fixed `yii help` command was listing incorrect action names for methods like `actionSayNO` (samdark)
- Bug #4654: Fixed issue with PostgreSQL and inserting boolean values with batch insert (cebe)
- Bug: Fixed inconsistent return of `\yii\console\Application::runAction()` (samdark)
- Bug: URL encoding for the route parameter added to `\yii\web\UrlManager` (klimov-paul)
- Bug: Fixed the bug that requesting protected or private action methods would cause 500 error instead of 404 (qiangxue)
@ -157,6 +160,7 @@ Yii Framework 2 Change Log
- Enh #4080: Added proper handling and support of the symlinked directories in `FileHelper`, added $options parameter in `FileHelper::removeDirectory()` (resurtm)
- Enh #4086: changedAttributes of afterSave Event now contain old values (dizews)
- Enh #4114: Added `Security::generateRandomBytes()`, improved tests (samdark)
- Enh #4122: `Html::error()` and `Html::errorSummary()` are now accepting `encode` option. If set to false it prevents encoding of error messages (samdark)
- Enh #4131: Security adjustments (tom--)
- Added HKDF to `yii\base\Security`.
- Reverted auto fallback to PHP PBKDF2.
@ -172,7 +176,11 @@ Yii Framework 2 Change Log
- Enh #4559: Added `beforeValidateAll` and `afterValidateAll` callbacks to `ActiveForm` (Alex-Code)
- Enh #4566: Added client validation support for image validator (Skysplit, qiangxue)
- Enh #4581: Added ability to disable url encoding in `UrlRule` (tadaszelvys)
- Enh #4597: `yii\composer\Installer::setPermission()` supports setting permission for both directories and files now (qiangxue)
- Enh #4602: Added $key param in ActionColumn buttons Closure call (disem)
- Enh #4630: Added automatic generating of unique slug value to `yii\behaviors\Sluggable` (klimov-paul)
- Enh #4644: Added `\yii\db\Schema::createColumnSchema()` to be able to customize column schema used (mcd-php)
- Enh #4656: HtmlPurifier helper config can now be a closure to change the purifier config object after it was created (Alex-Code)
- Enh: Added support for using sub-queries when building a DB query with `IN` condition (qiangxue)
- Enh: Supported adding a new response formatter without the need to reconfigure existing formatters (qiangxue)
- Enh: Added `yii\web\UrlManager::addRules()` to simplify adding new URL rules (qiangxue)
@ -218,6 +226,7 @@ Yii Framework 2 Change Log
- Chg #4310: Removed `$data` from signature of `yii\rbac\ManagerInterface` (samdark)
- Chg #4318: `yii\helpers\Html::ul()` and `ol()` will return an empty list tag if an empty item array is given (qiangxue)
- Chg #4331: `yii\helpers\Url` now uses `UrlManager` to determine base URL when generating URLs (qiangxue)
- Chg #4586: Signed bigint and unsigned int will be converted into integers when they are loaded from DB by AR (qiangxue)
- Chg #4591: `yii\helpers\Url::to()` will no longer prefix relative URLs with the base URL (qiangxue)
- Chg #4595: `yii\widgets\LinkPager`'s `nextPageLabel`, `prevPageLabel`, `firstPageLabel`, `lastPageLabel` are now taking `false` instead of `null` for "no label" (samdark)
- Chg: Replaced `clearAll()` and `clearAllAssignments()` in `yii\rbac\ManagerInterface` with `removeAll()`, `removeAllRoles()`, `removeAllPermissions()`, `removeAllRules()` and `removeAllAssignments()` (qiangxue)
@ -232,6 +241,7 @@ Yii Framework 2 Change Log
- New #3911: Added `yii\behaviors\SluggableBehavior` that fills the specified model attribute with the transliterated and adjusted version to use in URLs (creocoder)
- New #4193: Added `yii\filters\Cors` CORS filter to allow Cross Origin Resource Sharing (pgaultier)
- New: Added `yii\base\InvalidValueException` (qiangxue)
- New: Added `yii\caching\ArrayCache` (cebe)
2.0.0-beta April 13, 2014

117
framework/behaviors/SluggableBehavior.php

@ -10,6 +10,8 @@ namespace yii\behaviors;
use yii\base\InvalidConfigException;
use yii\db\BaseActiveRecord;
use yii\helpers\Inflector;
use yii\validators\UniqueValidator;
use Yii;
/**
* SluggableBehavior automatically fills the specified attribute with a value that can be used a slug in a URL.
@ -46,7 +48,9 @@ use yii\helpers\Inflector;
* ];
* }
* ```
*
* @author Alexander Kochetov <creocoder@gmail.com>
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class SluggableBehavior extends AttributeBehavior
@ -56,7 +60,7 @@ class SluggableBehavior extends AttributeBehavior
*/
public $slugAttribute = 'slug';
/**
* @var string the attribute whose value will be converted into a slug
* @var string|array the attribute or list of attributes whose value will be converted into a slug
*/
public $attribute;
/**
@ -72,6 +76,40 @@ class SluggableBehavior extends AttributeBehavior
* ```
*/
public $value;
/**
* @var boolean whether to ensure generated slug value to be unique among owner class records.
* If enabled behavior will validate slug uniqueness automatically. If validation fails it will attempt
* generating unique slug value from based one until success.
*/
public $ensureUnique = false;
/**
* @var array configuration for slug uniqueness validator. Parameter 'class' may be omitted - by default
* [[UniqueValidator]] will be used.
* For example:
*
* ```php
* [
* 'filter' => ['type' => 1, 'status' => 2]
* ]
* ```
*
* @see UniqueValidator
*/
public $uniqueValidator = [];
/**
* @var callable slug unique value generator. It is used in case [[ensureUnique]] enabled and generated
* slug is not unique. This should be a PHP callable with following signature:
*
* ```php
* function ($baseSlug, $iteration)
* {
* // return uniqueSlug
* }
* ```
*
* If not set unique slug will be generated adding incrementing suffix to the base slug.
*/
public $uniqueSlugGenerator;
/**
@ -95,10 +133,83 @@ class SluggableBehavior extends AttributeBehavior
*/
protected function getValue($event)
{
$isNewSlug = true;
if ($this->attribute !== null) {
$this->value = Inflector::slug($this->owner->{$this->attribute});
$attributes = (array)$this->attribute;
/* @var $owner BaseActiveRecord */
$owner = $this->owner;
if (!$owner->getIsNewRecord() && !empty($owner->{$this->slugAttribute})) {
$isNewSlug = false;
foreach ($attributes as $attribute) {
if ($owner->isAttributeChanged($attribute)) {
$isNewSlug = true;
break;
}
}
}
if ($isNewSlug) {
$slugParts = [];
foreach ($attributes as $attribute) {
$slugParts[] = $owner->{$attribute};
}
$slug = Inflector::slug(implode('-', $slugParts));
} else {
$slug = $owner->{$this->slugAttribute};
}
} else {
$slug = parent::getValue($event);
}
if ($this->ensureUnique && $isNewSlug) {
$baseSlug = $slug;
$iteration = 0;
while (!$this->validateSlug($slug)) {
$iteration++;
$slug = $this->generateUniqueSlug($baseSlug, $iteration);
}
}
return $slug;
}
/**
* Checks if given slug value is unique.
* @param string $slug slug value
* @return boolean whether slug is unique.
*/
private function validateSlug($slug)
{
/* @var $validator UniqueValidator */
/* @var $model BaseActiveRecord */
$validator = Yii::createObject(array_merge(
[
'class' => UniqueValidator::className()
],
$this->uniqueValidator
));
$model = clone $this->owner;
$model->clearErrors();
$model->{$this->slugAttribute} = $slug;
$validator->validateAttribute($model, $this->slugAttribute);
return !$model->hasErrors();
}
return parent::getValue($event);
/**
* Generates slug using configured callback or increment of iteration.
* @param string $baseSlug base slug value
* @param integer $iteration iteration number
* @return string new slug value
* @throws \yii\base\InvalidConfigException
*/
private function generateUniqueSlug($baseSlug, $iteration)
{
if (is_callable($this->uniqueSlugGenerator)) {
return call_user_func($this->uniqueSlugGenerator, $baseSlug, $iteration);
} else {
return $baseSlug . '-' . ($iteration + 1);
}
}
}

86
framework/caching/ArrayCache.php

@ -0,0 +1,86 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\caching;
/**
* ArrayCache provides caching for the current request only by storing the values in an array.
*
* See [[Cache]] for common cache operations that ArrayCache supports.
*
* Unlike the [[Cache]], ArrayCache allows the expire parameter of [[set]], [[add]], [[mset]] and [[madd]] to
* be a floating point number, so you may specify the time in milliseconds (e.g. 0.1 will be 100 milliseconds).
*
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class ArrayCache extends Cache
{
private $_cache;
/**
* @inheritdoc
*/
public function exists($key)
{
$key = $this->buildKey($key);
return isset($this->_cache[$key]) && ($this->_cache[$key][1] === 0 || $this->_cache[$key][1] > microtime(true));
}
/**
* @inheritdoc
*/
protected function getValue($key)
{
if (isset($this->_cache[$key]) && ($this->_cache[$key][1] === 0 || $this->_cache[$key][1] > microtime(true))) {
return $this->_cache[$key][0];
} else {
return false;
}
}
/**
* @inheritdoc
*/
protected function setValue($key, $value, $duration)
{
$this->_cache[$key] = [$value, $duration === 0 ? 0 : microtime(true) + $duration];
return true;
}
/**
* @inheritdoc
*/
protected function addValue($key, $value, $duration)
{
if (isset($this->_cache[$key]) && ($this->_cache[$key][1] === 0 || $this->_cache[$key][1] > microtime(true))) {
return false;
} else {
$this->_cache[$key] = [$value, $duration === 0 ? 0 : microtime(true) + $duration];
return true;
}
}
/**
* @inheritdoc
*/
protected function deleteValue($key)
{
unset($this->_cache[$key]);
return true;
}
/**
* @inheritdoc
*/
protected function flushValues()
{
$this->_cache = [];
return true;
}
}

16
framework/db/Schema.php

@ -86,6 +86,15 @@ abstract class Schema extends Object
/**
* @return \yii\db\ColumnSchema
* @throws \yii\base\InvalidConfigException
*/
protected function createColumnSchema()
{
return Yii::createObject('yii\db\ColumnSchema');
}
/**
* Loads the metadata for the specified table.
* @param string $name table name
* @return TableSchema DBMS-dependent table metadata, null if the table does not exist.
@ -481,13 +490,16 @@ abstract class Schema extends Object
// abstract type => php type
'smallint' => 'integer',
'integer' => 'integer',
'bigint' => 'integer',
'boolean' => 'boolean',
'float' => 'double',
'binary' => 'resource',
];
if (isset($typeMap[$column->type])) {
if ($column->type === 'integer') {
return $column->unsigned ? 'string' : 'integer';
if ($column->type === 'bigint') {
return PHP_INT_SIZE == 8 && !$column->unsigned ? 'integer' : 'string';
} elseif ($column->type === 'integer') {
return PHP_INT_SIZE == 4 && $column->unsigned ? 'string' : 'integer';
} else {
return $typeMap[$column->type];
}

2
framework/db/cubrid/Schema.php

@ -195,7 +195,7 @@ class Schema extends \yii\db\Schema
*/
protected function loadColumnSchema($info)
{
$column = new ColumnSchema();
$column = $this->createColumnSchema();
$column->name = $info['Field'];
$column->allowNull = $info['Null'] === 'YES';

2
framework/db/mssql/Schema.php

@ -176,7 +176,7 @@ class Schema extends \yii\db\Schema
*/
protected function loadColumnSchema($info)
{
$column = new ColumnSchema();
$column = $this->createColumnSchema();
$column->name = $info['column_name'];
$column->allowNull = $info['is_nullable'] == 'YES';

2
framework/db/mysql/Schema.php

@ -127,7 +127,7 @@ class Schema extends \yii\db\Schema
*/
protected function loadColumnSchema($info)
{
$column = new ColumnSchema;
$column = $this->createColumnSchema();
$column->name = $info['Field'];
$column->allowNull = $info['Null'] === 'YES';

2
framework/db/oci/Schema.php

@ -200,7 +200,7 @@ EOD;
*/
protected function createColumn($column)
{
$c = new ColumnSchema();
$c = $this->createColumnSchema();
$c->name = $column['COLUMN_NAME'];
$c->allowNull = $column['NULLABLE'] === 'Y';
$c->isPrimaryKey = strpos($column['KEY'], 'P') !== false;

40
framework/db/pgsql/QueryBuilder.php

@ -159,4 +159,44 @@ class QueryBuilder extends \yii\db\QueryBuilder
. $this->db->quoteColumnName($column) . ' TYPE '
. $this->getColumnType($type);
}
/**
* @inheritdoc
*/
public function batchInsert($table, $columns, $rows)
{
if (($tableSchema = $this->db->getTableSchema($table)) !== null) {
$columnSchemas = $tableSchema->columns;
} else {
$columnSchemas = [];
}
$values = [];
foreach ($rows as $row) {
$vs = [];
foreach ($row as $i => $value) {
if (!is_array($value) && isset($columnSchemas[$columns[$i]])) {
$value = $columnSchemas[$columns[$i]]->dbTypecast($value);
}
if (is_string($value)) {
$value = $this->db->quoteValue($value);
} elseif ($value === true) {
$value = 'TRUE';
} elseif ($value === false) {
$value = 'FALSE';
} elseif ($value === null) {
$value = 'NULL';
}
$vs[] = $value;
}
$values[] = '(' . implode(', ', $vs) . ')';
}
foreach ($columns as $i => $name) {
$columns[$i] = $this->db->quoteColumnName($name);
}
return 'INSERT INTO ' . $this->db->quoteTableName($table)
. ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values);
}
}

4
framework/db/pgsql/Schema.php

@ -412,6 +412,8 @@ SQL;
} elseif ($column->defaultValue) {
if ($column->type === 'timestamp' && $column->defaultValue === 'now()') {
$column->defaultValue = new Expression($column->defaultValue);
} elseif ($column->type === 'boolean') {
$column->defaultValue = ($column->defaultValue === 'true');
} elseif (stripos($column->dbType, 'bit') === 0 || stripos($column->dbType, 'varbit') === 0) {
$column->defaultValue = bindec(trim($column->defaultValue, 'B\''));
} elseif (preg_match("/^'(.*?)'::/", $column->defaultValue, $matches)) {
@ -434,7 +436,7 @@ SQL;
*/
protected function loadColumnSchema($info)
{
$column = new ColumnSchema();
$column = $this->createColumnSchema();
$column->allowNull = $info['is_nullable'];
$column->autoIncrement = $info['is_autoinc'];
$column->comment = $info['column_comment'];

2
framework/db/sqlite/Schema.php

@ -212,7 +212,7 @@ class Schema extends \yii\db\Schema
*/
protected function loadColumnSchema($info)
{
$column = new ColumnSchema;
$column = $this->createColumnSchema();
$column->name = $info['name'];
$column->allowNull = !$info['notnull'];
$column->isPrimaryKey = $info['pk'] != 0;

18
framework/helpers/BaseHtml.php

@ -1067,6 +1067,7 @@ class BaseHtml
*
* - header: string, the header HTML for the error summary. If not set, a default prompt string will be used.
* - footer: string, the footer HTML for the error summary.
* - encode: boolean, if set to false then value won't be encoded.
*
* The rest of the options will be rendered as the attributes of the container tag. The values will
* be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered.
@ -1074,6 +1075,11 @@ class BaseHtml
*/
public static function errorSummary($models, $options = [])
{
$header = isset($options['header']) ? $options['header'] : '<p>' . Yii::t('yii', 'Please fix the following errors:') . '</p>';
$footer = isset($options['footer']) ? $options['footer'] : '';
$encode = !isset($options['encode']) || $options['encode'] !== false;
unset($options['header'], $options['footer'], $options['encode']);
$lines = [];
if (!is_array($models)) {
$models = [$models];
@ -1081,14 +1087,10 @@ class BaseHtml
foreach ($models as $model) {
/* @var $model Model */
foreach ($model->getFirstErrors() as $error) {
$lines[] = Html::encode($error);
$lines[] = $encode ? Html::encode($error) : $error;
}
}
$header = isset($options['header']) ? $options['header'] : '<p>' . Yii::t('yii', 'Please fix the following errors:') . '</p>';
$footer = isset($options['footer']) ? $options['footer'] : '';
unset($options['header'], $options['footer']);
if (empty($lines)) {
// still render the placeholder for client-side validation use
$content = "<ul></ul>";
@ -1111,6 +1113,7 @@ class BaseHtml
* The following options are specially handled:
*
* - tag: this specifies the tag name. If not set, "div" will be used.
* - encode: boolean, if set to false then value won't be encoded.
*
* See [[renderTagAttributes()]] for details on how attributes are being rendered.
*
@ -1121,8 +1124,9 @@ class BaseHtml
$attribute = static::getAttributeName($attribute);
$error = $model->getFirstError($attribute);
$tag = isset($options['tag']) ? $options['tag'] : 'div';
unset($options['tag']);
return Html::tag($tag, Html::encode($error), $options);
$encode = !isset($options['encode']) || $options['encode'] !== false;
unset($options['tag'], $options['encode']);
return Html::tag($tag, $encode ? Html::encode($error) : $error, $options);
}
/**

30
framework/helpers/BaseHtmlPurifier.php

@ -20,17 +20,39 @@ class BaseHtmlPurifier
/**
* Passes markup through HTMLPurifier making it safe to output to end user
*
* @param string $content
* @param array|null $config
* @return string
* @param string $content The HTML content to purify
* @param array|\Closure|null $config The config to use for HtmlPurifier.
* If not specified or `null` the default config will be used.
* You can use an array or an anonymous function to provide configuration options:
*
* - An array will be passed to the `HTMLPurifier_Config::create()` method.
* - An anonymous function will be called after the config was created.
* The signature should be: `function($config)` where `$config` will be an
* instance of `HTMLPurifier_Config`.
*
* Here is a usage example of such a function:
*
* ~~~
* // Allow the HTML5 data attribute `data-type` on `img` elements.
* $content = HtmlPurifier::process($content, function($config) {
* $config->getHTMLDefinition(true)
* ->addAttribute('img', 'data-type', 'Text');
* });
* ~~~
*
* @return string the purified HTML content.
*/
public static function process($content, $config = null)
{
$configInstance = \HTMLPurifier_Config::create($config);
$configInstance = \HTMLPurifier_Config::create($config instanceof \Closure ? null : $config);
$configInstance->autoFinalize = false;
$purifier=\HTMLPurifier::instance($configInstance);
$purifier->config->set('Cache.SerializerPath', \Yii::$app->getRuntimePath());
if ($config instanceof \Closure) {
call_user_func($config, $configInstance);
}
return $purifier->purify($content);
}
}

2
framework/widgets/ActiveField.php

@ -195,7 +195,7 @@ class ActiveField extends Component
{
$clientOptions = $this->getClientOptions();
if (!empty($clientOptions)) {
$this->form->attributes[$this->attribute] = $clientOptions;
$this->form->attributes[] = $clientOptions;
}
$inputID = Html::getInputId($this->model, $this->attribute);

12
tests/README.md

@ -15,7 +15,7 @@ DIRECTORY STRUCTURE
HOW TO RUN THE TESTS
--------------------
Make sure you have PHPUnit installed.
Make sure you have PHPUnit installed and that you installed all composer dependencies (run `composer update` in the repo base directory).
Run PHPUnit in the yii repo base directory.
@ -39,3 +39,13 @@ You can create your own phpunit.xml to override dist config.
Database and other backend system configuration can be found in `unit/data/config.php`
adjust them to your needs to allow testing databases and caching in your environment.
You can override configuration values by creating a `config.local.php` file
and manipulate the `$config` variable.
For example to change MySQL username and password your `config.local.php` should
contain the following:
```php
<?php
$config['databases']['mysql']['username'] = 'yiitest';
$config['databases']['mysql']['password'] = 'changeme';
```

3
tests/unit/.gitignore vendored

@ -1 +1,2 @@
runtime/cache/*
/runtime/cache/*
/data/config.local.php

22
tests/unit/data/config.php

@ -1,5 +1,19 @@
<?php
return [
/**
* This is the configuration file for the Yii2 unit tests.
* You can override configuration values by creating a `config.local.php` file
* and manipulate the `$config` variable.
* For example to change MySQL username and password your `config.local.php` should
* contain the following:
*
<?php
$config['databases']['mysql']['username'] = 'yiitest';
$config['databases']['mysql']['password'] = 'changeme';
*/
$config = [
'databases' => [
'cubrid' => [
'dsn' => 'cubrid:dbname=demodb;host=localhost;port=33000',
@ -58,3 +72,9 @@ return [
'options' => [],
]
];
if (is_file(__DIR__ . '/config.local.php')) {
include(__DIR__ . '/config.local.php');
}
return $config;

8
tests/unit/data/postgres.sql

@ -16,6 +16,7 @@ DROP TABLE IF EXISTS "profile" CASCADE;
DROP TABLE IF EXISTS "type" CASCADE;
DROP TABLE IF EXISTS "null_values" CASCADE;
DROP TABLE IF EXISTS "constraints" CASCADE;
DROP TABLE IF EXISTS "bool_values" CASCADE;
CREATE TABLE "constraints"
(
@ -113,6 +114,13 @@ CREATE TABLE "type" (
bit_col BIT(8) NOT NULL DEFAULT B'10000010'
);
CREATE TABLE "bool_values" (
id serial not null primary key,
bool_col bool,
default_true bool not null default true,
default_false boolean not null default false
);
INSERT INTO "profile" (description) VALUES ('profile customer 1');
INSERT INTO "profile" (description) VALUES ('profile customer 3');

9
tests/unit/extensions/smarty/ViewRendererTest.php

@ -7,6 +7,7 @@
namespace yiiunit\extensions\smarty;
use yii\helpers\FileHelper;
use yii\web\AssetManager;
use yii\web\View;
use Yii;
@ -20,9 +21,17 @@ class ViewRendererTest extends TestCase
{
protected function setUp()
{
parent::setUp();
$this->mockApplication();
}
protected function tearDown()
{
parent::tearDown();
FileHelper::removeDirectory(Yii::getAlias('@runtime/assets'));
FileHelper::removeDirectory(Yii::getAlias('@runtime/Smarty'));
}
/**
* https://github.com/yiisoft/yii2/issues/2265
*/

6
tests/unit/extensions/sphinx/SphinxTestCase.php

@ -48,6 +48,12 @@ class SphinxTestCase extends TestCase
if (!extension_loaded('pdo') || !extension_loaded('pdo_mysql')) {
$this->markTestSkipped('pdo and pdo_mysql extension are required.');
}
// check whether sphinx is running and skip tests if not.
if (preg_match('/host=([\w\d.]+)/i', $this->sphinxConfig['dsn'], $hm) && preg_match('/port=(\d+)/i', $this->sphinxConfig['dsn'], $pm)) {
if (!@stream_socket_client($hm[1] . ':' . $pm[1], $errorNumber, $errorDescription, 0.5)) {
$this->markTestSkipped('No redis server running at ' . $hm[1] . ':' . $pm[1] . ' : ' . $errorNumber . ' - ' . $errorDescription);
}
}
$config = self::getParam('sphinx');
if (!empty($config)) {
$this->sphinxConfig = $config['sphinx'];

7
tests/unit/extensions/twig/ViewRendererTest.php

@ -1,6 +1,7 @@
<?php
namespace yiiunit\extensions\twig;
use yii\helpers\FileHelper;
use yii\web\AssetManager;
use yii\web\View;
use Yii;
@ -24,6 +25,12 @@ class ViewRendererTest extends DatabaseTestCase
$this->mockApplication();
}
protected function tearDown()
{
parent::tearDown();
FileHelper::removeDirectory(Yii::getAlias('@runtime/assets'));
}
/**
* https://github.com/yiisoft/yii2/issues/1755
*/

193
tests/unit/framework/behaviors/SluggableBehaviorTest.php

@ -0,0 +1,193 @@
<?php
namespace yiiunit\framework\behaviors;
use Yii;
use yiiunit\TestCase;
use yii\db\Connection;
use yii\db\ActiveRecord;
use yii\behaviors\SluggableBehavior;
/**
* Unit test for [[\yii\behaviors\SluggableBehavior]].
* @see SluggableBehavior
*
* @group behaviors
*/
class SluggableBehaviorTest extends TestCase
{
/**
* @var Connection test db connection
*/
protected $dbConnection;
public static function setUpBeforeClass()
{
if (!extension_loaded('pdo') || !extension_loaded('pdo_sqlite')) {
static::markTestSkipped('PDO and SQLite extensions are required.');
}
}
public function setUp()
{
$this->mockApplication([
'components' => [
'db' => [
'class' => '\yii\db\Connection',
'dsn' => 'sqlite::memory:',
]
]
]);
$columns = [
'id' => 'pk',
'name' => 'string',
'slug' => 'string',
'category_id' => 'integer',
];
Yii::$app->getDb()->createCommand()->createTable('test_slug', $columns)->execute();
}
public function tearDown()
{
Yii::$app->getDb()->close();
parent::tearDown();
}
// Tests :
public function testSlug()
{
$model = new ActiveRecordSluggable();
$model->name = 'test name';
$model->validate();
$this->assertEquals('test-name', $model->slug);
}
/**
* @depends testSlug
*/
public function testSlugSeveralAttributes()
{
$model = new ActiveRecordSluggable();
$model->getBehavior('sluggable')->attribute = array('name', 'category_id');
$model->name = 'test';
$model->category_id = 10;
$model->validate();
$this->assertEquals('test-10', $model->slug);
}
/**
* @depends testSlug
*/
public function testUniqueByIncrement()
{
$name = 'test name';
$model = new ActiveRecordSluggableUnique();
$model->name = $name;
$model->save();
$model = new ActiveRecordSluggableUnique();
$model->sluggable->uniqueSlugGenerator = 'increment';
$model->name = $name;
$model->save();
$this->assertEquals('test-name-2', $model->slug);
}
/**
* @depends testUniqueByIncrement
*/
public function testUniqueByCallback()
{
$name = 'test name';
$model = new ActiveRecordSluggableUnique();
$model->name = $name;
$model->save();
$model = new ActiveRecordSluggableUnique();
$model->sluggable->uniqueSlugGenerator = function($baseSlug, $iteration) {return $baseSlug . '-callback';};
$model->name = $name;
$model->save();
$this->assertEquals('test-name-callback', $model->slug);
}
/**
* @depends testSlug
*/
public function testUpdateUnique()
{
$name = 'test name';
$model = new ActiveRecordSluggableUnique();
$model->name = $name;
$model->save();
$model->save();
$this->assertEquals('test-name', $model->slug);
$model = ActiveRecordSluggableUnique::find()->one();
$model->save();
$this->assertEquals('test-name', $model->slug);
$model->name = 'test-name';
$model->save();
$this->assertEquals('test-name', $model->slug);
}
}
/**
* Test Active Record class with [[SluggableBehavior]] behavior attached.
*
* @property integer $id
* @property string $name
* @property string $slug
* @property integer $category_id
*
* @property SluggableBehavior $sluggable
*/
class ActiveRecordSluggable extends ActiveRecord
{
public function behaviors()
{
return [
'sluggable' => [
'class' => SluggableBehavior::className(),
'attribute' => 'name',
],
];
}
public static function tableName()
{
return 'test_slug';
}
/**
* @return SluggableBehavior
*/
public function getSluggable()
{
return $this->getBehavior('sluggable');
}
}
class ActiveRecordSluggableUnique extends ActiveRecordSluggable
{
public function behaviors()
{
return [
'sluggable' => [
'class' => SluggableBehavior::className(),
'attribute' => 'name',
'ensureUnique' => true,
],
];
}
}

49
tests/unit/framework/caching/ArrayCacheTest.php

@ -0,0 +1,49 @@
<?php
namespace yiiunit\framework\caching;
use yii\caching\ArrayCache;
/**
* Class for testing file cache backend
* @group caching
*/
class ArrayCacheTest extends CacheTestCase
{
private $_cacheInstance = null;
/**
* @return ArrayCache
*/
protected function getCacheInstance()
{
if ($this->_cacheInstance === null) {
$this->_cacheInstance = new ArrayCache();
}
return $this->_cacheInstance;
}
public function testExpire()
{
$cache = $this->getCacheInstance();
static::$microtime = \microtime(true);
$this->assertTrue($cache->set('expire_test', 'expire_test', 2));
static::$microtime++;
$this->assertEquals('expire_test', $cache->get('expire_test'));
static::$microtime++;
$this->assertFalse($cache->get('expire_test'));
}
public function testExpireAdd()
{
$cache = $this->getCacheInstance();
static::$microtime = \microtime(true);
$this->assertTrue($cache->add('expire_testa', 'expire_testa', 2));
static::$microtime++;
$this->assertEquals('expire_testa', $cache->get('expire_testa'));
static::$microtime++;
$this->assertFalse($cache->get('expire_testa'));
}
}

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

@ -11,8 +11,19 @@ function time()
return \yiiunit\framework\caching\CacheTestCase::$time ?: \time();
}
/**
* Mock for the microtime() function for caching classes
* @param bool $float
* @return float
*/
function microtime($float = false)
{
return \yiiunit\framework\caching\CacheTestCase::$microtime ?: \microtime($float);
}
namespace yiiunit\framework\caching;
use yii\caching\Cache;
use yiiunit\TestCase;
/**
@ -25,6 +36,12 @@ abstract class CacheTestCase extends TestCase
* Null means normal time() behavior.
*/
public static $time;
/**
* @var float virtual time to be returned by mocked microtime() function.
* Null means normal microtime() behavior.
*/
public static $microtime;
/**
* @return Cache
@ -40,6 +57,7 @@ abstract class CacheTestCase extends TestCase
protected function tearDown()
{
static::$time = null;
static::$microtime = null;
}
/**

5
tests/unit/framework/caching/MemCacheTest.php

@ -21,6 +21,11 @@ class MemCacheTest extends CacheTestCase
$this->markTestSkipped("memcache not installed. Skipping.");
}
// check whether memcached is running and skip tests if not.
if (!@stream_socket_client('127.0.0.1:11211', $errorNumber, $errorDescription, 0.5)) {
$this->markTestSkipped('No redis server running at ' . '127.0.0.1:11211' . ' : ' . $errorNumber . ' - ' . $errorDescription);
}
if ($this->_cacheInstance === null) {
$this->_cacheInstance = new MemCache();
}

5
tests/unit/framework/caching/MemCachedTest.php

@ -21,6 +21,11 @@ class MemCachedTest extends CacheTestCase
$this->markTestSkipped("memcached not installed. Skipping.");
}
// check whether memcached is running and skip tests if not.
if (!@stream_socket_client('127.0.0.1:11211', $errorNumber, $errorDescription, 0.5)) {
$this->markTestSkipped('No redis server running at ' . '127.0.0.1:11211' . ' : ' . $errorNumber . ' - ' . $errorDescription);
}
if ($this->_cacheInstance === null) {
$this->_cacheInstance = new MemCache(['useMemcached' => true]);
}

50
tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php

@ -2,6 +2,7 @@
namespace yiiunit\framework\db\pgsql;
use yiiunit\data\ar\ActiveRecord;
use yiiunit\framework\db\ActiveRecordTest;
/**
@ -11,4 +12,53 @@ use yiiunit\framework\db\ActiveRecordTest;
class PostgreSQLActiveRecordTest extends ActiveRecordTest
{
protected $driverName = 'pgsql';
public function testBooleanValues()
{
$db = $this->getConnection();
$command = $db->createCommand();
$command->batchInsert('bool_values',
['bool_col'], [
[true],
[false],
]
)->execute();
$this->assertEquals(1, BoolAR::find()->where('bool_col = TRUE')->count('*', $db));
$this->assertEquals(1, BoolAR::find()->where('bool_col = FALSE')->count('*', $db));
$this->assertEquals(2, BoolAR::find()->where('bool_col IN (TRUE, FALSE)')->count('*', $db));
$this->assertEquals(1, BoolAR::find()->where(['bool_col' => true])->count('*', $db));
$this->assertEquals(1, BoolAR::find()->where(['bool_col' => false])->count('*', $db));
$this->assertEquals(2, BoolAR::find()->where(['bool_col' => [true, false]])->count('*', $db));
$this->assertEquals(1, BoolAR::find()->where('bool_col = :bool_col', ['bool_col' => true])->count('*', $db));
$this->assertEquals(1, BoolAR::find()->where('bool_col = :bool_col', ['bool_col' => false])->count('*', $db));
$this->assertSame(true, BoolAR::find()->where(['bool_col' => true])->one($db)->bool_col);
$this->assertSame(false, BoolAR::find()->where(['bool_col' => false])->one($db)->bool_col);
}
public function testBooleanDefaultValues()
{
$model = new BoolAR();
$this->assertNull($model->bool_col);
$this->assertNull($model->default_true);
$this->assertNull($model->default_false);
$model->loadDefaultValues();
$this->assertNull($model->bool_col);
$this->assertSame(true, $model->default_true);
$this->assertSame(false, $model->default_false);
$this->assertTrue($model->save(false));
}
}
class BoolAR extends ActiveRecord
{
public static function tableName()
{
return 'bool_values';
}
}

35
tests/unit/framework/db/pgsql/PostgreSQLCommandTest.php

@ -19,4 +19,39 @@ class PostgreSQLCommandTest extends CommandTest
$command = $db->createCommand($sql);
$this->assertEquals('SELECT "id", "t"."name" FROM "customer" t', $command->sql);
}
public function testBooleanValuesInsert()
{
$db = $this->getConnection();
$command = $db->createCommand();
$command->insert('bool_values', ['bool_col' => true]);
$this->assertEquals(1, $command->execute());
$command = $db->createCommand();
$command->insert('bool_values', ['bool_col' => false]);
$this->assertEquals(1, $command->execute());
$command = $db->createCommand('SELECT COUNT(*) FROM "bool_values" WHERE bool_col = TRUE;');
$this->assertEquals(1, $command->queryScalar());
$command = $db->createCommand('SELECT COUNT(*) FROM "bool_values" WHERE bool_col = FALSE;');
$this->assertEquals(1, $command->queryScalar());
}
public function testBooleanValuesBatchInsert()
{
$db = $this->getConnection();
$command = $db->createCommand();
$command->batchInsert('bool_values',
['bool_col'], [
[true],
[false],
]
);
$this->assertEquals(2, $command->execute());
$command = $db->createCommand('SELECT COUNT(*) FROM "bool_values" WHERE bool_col = TRUE;');
$this->assertEquals(1, $command->queryScalar());
$command = $db->createCommand('SELECT COUNT(*) FROM "bool_values" WHERE bool_col = FALSE;');
$this->assertEquals(1, $command->queryScalar());
}
}

24
tests/unit/framework/db/pgsql/PostgreSQLQueryTest.php

@ -3,6 +3,7 @@
namespace yiiunit\framework\db\pgsql;
use yii\db\pgsql\Schema;
use yii\db\Query;
use yiiunit\framework\db\QueryTest;
use yiiunit\framework\db\SchemaTest;
@ -13,4 +14,27 @@ use yiiunit\framework\db\SchemaTest;
class PostgreSQLQueryTest extends QueryTest
{
public $driverName = 'pgsql';
public function testBooleanValues()
{
$db = $this->getConnection();
$command = $db->createCommand();
$command->batchInsert('bool_values',
['bool_col'], [
[true],
[false],
]
)->execute();
$this->assertEquals(1, (new Query())->from('bool_values')->where('bool_col = TRUE')->count('*', $db));
$this->assertEquals(1, (new Query())->from('bool_values')->where('bool_col = FALSE')->count('*', $db));
$this->assertEquals(2, (new Query())->from('bool_values')->where('bool_col IN (TRUE, FALSE)')->count('*', $db));
$this->assertEquals(1, (new Query())->from('bool_values')->where(['bool_col' => true])->count('*', $db));
$this->assertEquals(1, (new Query())->from('bool_values')->where(['bool_col' => false])->count('*', $db));
$this->assertEquals(2, (new Query())->from('bool_values')->where(['bool_col' => [true, false]])->count('*', $db));
$this->assertEquals(1, (new Query())->from('bool_values')->where('bool_col = :bool_col', ['bool_col' => true])->count('*', $db));
$this->assertEquals(1, (new Query())->from('bool_values')->where('bool_col = :bool_col', ['bool_col' => false])->count('*', $db));
}
}

10
tests/unit/framework/db/pgsql/PostgreSQLSchemaTest.php

@ -80,4 +80,14 @@ class PostgreSQLSchemaTest extends SchemaTest
}
fclose($fp);
}
public function testBooleanDefaultValues()
{
/* @var $schema Schema */
$schema = $this->getConnection()->schema;
$table = $schema->getTableSchema('bool_values');
$this->assertSame(true, $table->getColumn('default_true')->defaultValue);
$this->assertSame(false, $table->getColumn('default_false')->defaultValue);
}
}

2
tests/unit/framework/db/sqlite/SqliteConnectionTest.php

@ -122,7 +122,7 @@ class SqliteConnectionTest extends ConnectionTest
$config = [
'class' => 'yii\db\Connection',
'dsn' => 'sqlite:memory:',
'dsn' => "sqlite:$basePath/yii2test.sq3",
];
$this->prepareDatabase($config, $fixture)->close();

Loading…
Cancel
Save