Commit 00232f7a by Qiang Xue

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

Conflicts: framework/CHANGELOG.md
parents 3b01f48c c6274acf
......@@ -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.
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
<?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';
<?php
use common\tests\_pages\LoginPage;
use backend\WebGuy;
$I = new WebGuy($scenario);
$I->wantTo('ensure login page works');
......
<?php
use common\tests\_pages\LoginPage;
use backend\TestGuy;
$I = new TestGuy($scenario);
$I->wantTo('ensure login page works');
......
<?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'),
......
include:
- common
- console
- backend
- frontend
paths:
log: tests/_log
settings:
colors: true
namespace: common
actor: Tester
paths:
tests: tests
......
<?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';
namespace: console
actor: Tester
paths:
tests: tests
......
<?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';
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
<?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';
<?php
use frontend\tests\_pages\AboutPage;
use frontend\WebGuy;
$I = new WebGuy($scenario);
$I->wantTo('ensure that about works');
......
<?php
use frontend\tests\_pages\ContactPage;
use frontend\WebGuy;
$I = new WebGuy($scenario);
$I->wantTo('ensure that contact works');
......
<?php
use frontend\WebGuy;
$I = new WebGuy($scenario);
$I->wantTo('ensure that home page works');
$I->amOnPage(Yii::$app->homeUrl);
......
<?php
use common\tests\_pages\LoginPage;
use frontend\WebGuy;
$I = new WebGuy($scenario);
$I->wantTo('ensure login page works');
......
<?php
use frontend\tests\_pages\AboutPage;
use frontend\TestGuy;
$I = new TestGuy($scenario);
$I->wantTo('ensure that about works');
......
<?php
use frontend\tests\_pages\ContactPage;
use frontend\TestGuy;
$I = new TestGuy($scenario);
$I->wantTo('ensure that contact works');
......
<?php
use frontend\TestGuy;
$I = new TestGuy($scenario);
$I->wantTo('ensure that home page works');
$I->amOnPage(Yii::$app->homeUrl);
......
<?php
use common\tests\_pages\LoginPage;
use frontend\TestGuy;
$I = new TestGuy($scenario);
$I->wantTo('ensure login page works');
......
<?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'),
......
Контроллеры
===========
После создания классов ресурсов и настройки способа форматирования ресурсных данных следующим шагом
является создание действий контроллеров для предоставления ресурсов конечным пользователям через 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).
Маршрутизация
=============
Имея готовые классы ресурсов и контроллеров, можно получить доступ к ресурсам, используя 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]].
......@@ -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)', [
......
......@@ -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
......
......@@ -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');
```
......@@ -106,9 +106,9 @@ Aliased class import:
{{ use({'alias' => '/app/widgets/MyWidget'}) }}
```
#### Referencing other views
#### Referencing other templates
There are two ways of referencing views in `include` and `extends` statements:
There are two ways of referencing templates in `include` and `extends` statements:
```
{% include "comment.twig" %}
......@@ -118,8 +118,8 @@ There are two ways of referencing views in `include` and `extends` statements:
{% extends "@app/views/layouts/2columns.twig" %}
```
In the first case the view will be searched relatively to the path current view is in. For `comment.twig` and `post.twig`
that means these will be searched in the same directory as the view that's rendered currently.
In the first case the view will be searched relatively to the current template path. For `comment.twig` and `post.twig`
that means these will be searched in the same directory as the currently rendered template.
In the second case we're using path aliases. All the Yii aliases such as `@app` are available by default.
......@@ -264,8 +264,9 @@ Then in the template you can apply filter using the following syntax:
Smarty
------
To use Smarty, you need to create templates in files that have the `.tpl` extension (or use another file extension but configure the component accordingly). Unlike standard view files, when using Smarty you must include the extension in your `$this->render()`
or `$this->renderPartial()` controller calls:
To use Smarty, you need to create templates in files that have the `.tpl` extension (or use another file extension but
configure the component accordingly). Unlike standard view files, when using Smarty you must include the extension in
your `$this->render()` or `$this->renderPartial()` controller calls:
```php
return $this->render('renderer.tpl', ['username' => 'Alex']);
......@@ -277,20 +278,173 @@ The best resource to learn Smarty template syntax is its official documentation
[www.smarty.net](http://www.smarty.net/docs/en/). Additionally there are Yii-specific syntax extensions
described below.
#### Additional functions
#### Setting object properties
There's a special function called `set` that allows you to set common properties of the view and controller. Currently
available properties are `title`, `theme` and `layout`:
```
{set title="My Page"}
{set theme="frontend"}
{set layout="main.tpl"}
```
For title there's dedicated block as well:
```
{title}My Page{/title}
```
#### Setting meta tags
Meta tags could be set like to following:
```
{meta keywords="Yii,PHP,Smarty,framework"}
```
There's also dedicated block for description:
```
{description}This is my page about Smarty extension{/description}
```
#### Calling object methods
Sometimes you need calling
#### Importing static classes, using widgets as functions and blocks
You can import additional static classes right in the template:
```
{use class="yii\helpers\Html"}
{Html::mailto('eugenia@example.com')}
```
If you want you can set custom alias:
```
{use class="yii\helpers\Html" as="Markup"}
{Markup::mailto('eugenia@example.com')}
```
Extension helps using widgets in convenient way converting their syntax to function calls or blocks. For regular widgets
function could be used like the following:
```
{use class='@yii\grid\GridView' type='function'}
{GridView dataProvider=$provider}
```
For widgets with `begin` and `end` methods such as ActiveForm it's better to use block:
```
{use class='yii\widgets\ActiveForm' type='block'}
{ActiveForm assign='form' id='login-form' action='/form-handler' options=['class' => 'form-horizontal']}
{$form->field($model, 'firstName')}
<div class="form-group">
<div class="col-lg-offset-1 col-lg-11">
<input type="submit" value="Login" class="btn btn-primary" />
</div>
</div>
{/ActiveForm}
```
If you're using particular widget a lot, it is a good idea to declare it in application config and remove `{use class`
call from templates:
Yii adds the following construct to the standard Smarty syntax:
```php
'components' => [
'view' => [
// ...
'renderers' => [
'tpl' => [
'class' => 'yii\smarty\ViewRenderer',
'widgets' => [
'blocks' => [
'ActiveForm' => '\yii\widgets\ActiveForm',
],
],
],
],
],
],
```
#### Referencing other templates
There are two main ways of referencing templates in `include` and `extends` statements:
```
{include 'comment.tpl'}
{extends 'post.tpl'}
{include '@app/views/snippets/avatar.tpl'}
{extends '@app/views/layouts/2columns.tpl'}
```
In the first case the view will be searched relatively to the current template path. For `comment.tpl` and `post.tpl`
that means these will be searched in the same directory as the currently rendered template.
In the second case we're using path aliases. All the Yii aliases such as `@app` are available by default.
#### CSS, JavaScript and asset bundles
In order to register JavaScript and CSS files the following syntax could be used:
```
{registerJsFile url='http://maps.google.com/maps/api/js?sensor=false' position='POS_END'}
{registerCssFile url='@assets/css/normalizer.css'}
```
If you need JavaScript and CSS directly in the template there are convenient blocks:
```
{registerJs key='show' position='POS_LOAD'}
$("span.show").replaceWith('<div class="show">');
{/registerJs}
{registerCss}
div.header {
background-color: #3366bd;
color: white;
}
{/registerCss}
```
Asset bundles could be registered the following way:
```
{use class="yii\web\JqueryAsset"}
{JqueryAsset::register($this)|void}
```
Here we're using `void` modifier because we don't need method call result.
#### URLs
There are two functions you can use for building URLs:
```php
<a href="{path route='blog/view' alias=$post.alias}">{$post.title}</a>
<a href="{url route='blog/view' alias=$post.alias}">{$post.title}</a>
```
Internally, the `path()` function calls Yii's `Url::to()` method.
`path` generates relative URL while `url` generates absolute one. Internally both are using [[\yii\helpers\Url]].
#### Additional variables
Within Smarty templates, you can also make use of these variables:
Within Smarty templates the following variables are always defined:
- `$app`, which equates to `\Yii::$app`
- `$this`, which equates to the current `View` object
#### Accessing config params
Yii parameters that are available in your application through `Yii::$app->params->something` could be used the following
way:
```
`{#something#}`
```
......@@ -4,8 +4,26 @@ Yii Framework 2 smarty extension Change Log
2.0.0-rc under development
--------------------------
- no changes in this release.
- Enh #4619 (samdark, hwmaier)
- New functions:
- `url` generates absolute URL.
- `set` allows setting commonly used view paramters: `title`, `theme` and `layout`.
- `meta` registers meta tag.
- `registerJsFile` registers JavaScript file.
- `registerCssFile` registers CSS file.
- `use` allows importing classes to the template and optionally provides these as functions and blocks.
- New blocks:
- `title`.
- `description`.
- `registerJs`.
- `registerCss`.
- New modifier `void` that allows calling functions and ignoring result.
- Moved most of Yii custom syntax into `\yii\smarty\Extension` class that could be extended via `extensionClass` property.
- Added ability to set Smarty options via config using `options`.
- Added `imports` property that accepts an array of classes imported into template namespace.
- Added `widgets` property that can be used to import widgets as Smarty tags.
- `Yii::$app->params['paramKey']` values are now accessible as Smarty config variables `{#paramKey#}`.
- Added ability to use Yii aliases in `extends` and `require`.
2.0.0-beta April 13, 2014
-------------------------
......
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\smarty;
use Smarty;
use Yii;
use yii\helpers\ArrayHelper;
use yii\helpers\StringHelper;
use yii\helpers\Url;
use yii\web\View;
/**
* Extension provides Yii-specific syntax for Smarty templates.
*
* @author Alexander Makarov <sam@rmcreative.ru>
* @author Henrik Maier <hwmaier@gmail.com>
*/
class Extension
{
/**
* @var ViewRenderer
*/
protected $viewRenderer;
/**
* @var Smarty
*/
protected $smarty;
/**
* @param ViewRenderer $viewRenderer
* @param Smarty $smarty
*/
public function __construct($viewRenderer, $smarty)
{
$this->viewRenderer = $viewRenderer;
$smarty = $this->smarty = $smarty;
$smarty->registerPlugin('function', 'path', [$this, 'functionPath']);
$smarty->registerPlugin('function', 'url', [$this, 'functionUrl']);
$smarty->registerPlugin('function', 'set', [$this, 'functionSet']);
$smarty->registerPlugin('function', 'meta', [$this, 'functionMeta']);
$smarty->registerPlugin('function', 'registerJsFile', [$this, 'functionRegisterJsFile']);
$smarty->registerPlugin('function', 'registerCssFile', [$this, 'functionRegisterCssFile']);
$smarty->registerPlugin('block', 'title', [$this, 'blockTitle']);
$smarty->registerPlugin('block', 'description', [$this, 'blockDescription']);
$smarty->registerPlugin('block', 'registerJs', [$this, 'blockJavaScript']);
$smarty->registerPlugin('block', 'registerCss', [$this, 'blockCss']);
$smarty->registerPlugin('compiler', 'use', [$this, 'compilerUse']);
$smarty->registerPlugin('modifier', 'void', [$this, 'modifierVoid']);
}
/**
* Smarty template function to get relative URL for using in links
*
* Usage is the following:
*
* {path route='blog/view' alias=$post.alias user=$user.id}
*
* where route is Yii route and the rest of parameters are passed as is.
*
* @param array $params
* @param \Smarty_Internal_Template $template
*
* @return string
*/
public function functionPath($params, \Smarty_Internal_Template $template)
{
if (!isset($params['route'])) {
trigger_error("path: missing 'route' parameter");
}
array_unshift($params, $params['route']) ;
unset($params['route']);
return Url::to($params, true);
}
/**
* Smarty template function to get absolute URL for using in links
*
* Usage is the following:
*
* {path route='blog/view' alias=$post.alias user=$user.id}
*
* where route is Yii route and the rest of parameters are passed as is.
*
* @param array $params
* @param \Smarty_Internal_Template $template
*
* @return string
*/
public function functionUrl($params, \Smarty_Internal_Template $template)
{
if (!isset($params['route'])) {
trigger_error("path: missing 'route' parameter");
}
array_unshift($params, $params['route']) ;
unset($params['route']);
return Url::to($params, true);
}
/**
* Smarty compiler function plugin
* Usage is the following:
*
* {use class="app\assets\AppAsset"}
* {use class="yii\helpers\Html"}
* {use class='yii\widgets\ActiveForm' type='block'}
* {use class='@app\widgets\MyWidget' as='my_widget' type='function'}
*
* Supported attributes: class, as, type. Type defaults to 'static'.
*
* @param $params
* @param \Smarty_Internal_Template $template
* @return string
* @note Even though this method is public it should not be called directly.
*/
public function compilerUse($params, $template)
{
if (!isset($params['class'])) {
trigger_error("use: missing 'class' parameter");
}
// Compiler plugin parameters may include quotes, so remove them
foreach ($params as $key => $value) {
$params[$key] = trim($value, '\'""');
}
$class = $params['class'];
$alias = ArrayHelper::getValue($params, 'as', StringHelper::basename($params['class']));
$type = ArrayHelper::getValue($params, 'type', 'static');
// Register the class during compile time
$this->smarty->registerClass($alias, $class);
if ($type === 'block') {
// Register widget tag during compile time
$this->viewRenderer->widgets['blocks'][$alias] = $class;
$this->smarty->registerPlugin('block', $alias, [$this->viewRenderer, '_widget_block__' . $alias]);
// Inject code to re-register widget tag during run-time
return <<<PHP
<?php
\$_smarty_tpl->getGlobal('_viewRenderer')->widgets['blocks']['$alias'] = '$class';
try {
\$_smarty_tpl->registerPlugin('block', '$alias', [\$_smarty_tpl->getGlobal('_viewRenderer'), '_widget_block__$alias']);
}
catch (SmartyException \$e) {
/* Ignore already registered exception during first execution after compilation */
}
?>
PHP;
} elseif ($type === 'function') {
// Register widget tag during compile time
$this->viewRenderer->widgets['functions'][$alias] = $class;
$this->smarty->registerPlugin('function', $alias, [$this->viewRenderer, '_widget_function__' . $alias]);
// Inject code to re-register widget tag during run-time
return <<<PHP
<?php
\$_smarty_tpl->getGlobal('_viewRenderer')->widgets['functions']['$alias'] = '$class';
try {
\$_smarty_tpl->registerPlugin('function', '$alias', [\$_smarty_tpl->getGlobal('_viewRenderer'), '_widget_function__$alias']);
}
catch (SmartyException \$e) {
/* Ignore already registered exception during first execution after compilation */
}
?>
PHP;
}
}
/**
* Smarty modifier plugin
* Converts any output to void
* @param mixed $arg
* @return string
* @note Even though this method is public it should not be called directly.
*/
public function modifierVoid($arg)
{
return;
}
/**
* Smarty function plugin
* Usage is the following:
*
* {set title="My Page"}
* {set theme="frontend"}
* {set layout="main.tpl"}
*
* Supported attributes: title, theme, layout
*
* @param $params
* @param \Smarty_Internal_Template $template
* @return string
* @note Even though this method is public it should not be called directly.
*/
public function functionSet($params, $template)
{
if (isset($params['title'])) {
$template->tpl_vars['this']->value->title = Yii::$app->getView()->title = ArrayHelper::remove($params, 'title');
}
if (isset($params['theme'])) {
$template->tpl_vars['this']->value->theme = Yii::$app->getView()->theme = ArrayHelper::remove($params, 'theme');
}
if (isset($params['layout'])) {
Yii::$app->controller->layout = ArrayHelper::remove($params, 'layout');
}
// We must have consumed all allowed parameters now, otherwise raise error
if (!empty($params)) {
trigger_error('set: Unsupported parameter attribute');
}
}
/**
* Smarty function plugin
* Usage is the following:
*
* {meta keywords="Yii,PHP,Smarty,framework"}
*
* Supported attributes: any; all attributes are passed as
* parameter array to Yii's registerMetaTag function.
*
* @param $params
* @param \Smarty_Internal_Template $template
* @return string
* @note Even though this method is public it should not be called directly.
*/
public function functionMeta($params, $template)
{
$key = isset($params['name']) ? $params['name'] : null;
Yii::$app->getView()->registerMetaTag($params, $key);
}
/**
* Smarty block function plugin
* Usage is the following:
*
* {title} Web Site Login {/title}
*
* Supported attributes: none.
*
* @param $params
* @param $content
* @param \Smarty_Internal_Template $template
* @param $repeat
* @return string
* @note Even though this method is public it should not be called directly.
*/
public function blockTitle($params, $content, $template, &$repeat)
{
if ($content !== null) {
Yii::$app->getView()->title = $content;
}
}
/**
* Smarty block function plugin
* Usage is the following:
*
* {description}
* The text between the opening and closing tags is added as
* meta description tag to the page output.
* {/description}
*
* Supported attributes: none.
*
* @param $params
* @param $content
* @param \Smarty_Internal_Template $template
* @param $repeat
* @return string
* @note Even though this method is public it should not be called directly.
*/
public function blockDescription($params, $content, $template, &$repeat)
{
if ($content !== null) {
// Clean-up whitespace and newlines
$content = preg_replace('/\s+/', ' ', trim($content));
Yii::$app->getView()->registerMetaTag(['name' => 'description',
'content' => $content],
'description');
}
}
/**
* Smarty function plugin
* Usage is the following:
*
* {registerJsFile url='http://maps.google.com/maps/api/js?sensor=false' position='POS_END'}
*
* Supported attributes: url, key, depends, position and valid HTML attributes for the script tag.
* Refer to Yii documentation for details.
* The position attribute is passed as text without the class prefix.
* Default is 'POS_END'.
*
* @param $params
* @param \Smarty_Internal_Template $template
* @return string
* @note Even though this method is public it should not be called directly.
*/
public function functionRegisterJsFile($params, $template)
{
if (!isset($params['url'])) {
trigger_error("registerJsFile: missing 'url' parameter");
}
$url = ArrayHelper::remove($params, 'url');
$key = ArrayHelper::remove($params, 'key', null);
$depends = ArrayHelper::remove($params, 'depends', null);
if (isset($params['position']))
$params['position'] = $this->getViewConstVal($params['position'], View::POS_END);
Yii::$app->getView()->registerJsFile($url, $depends, $params, $key);
}
/**
* Smarty block function plugin
* Usage is the following:
*
* {registerJs key='show' position='POS_LOAD'}
* $("span.show").replaceWith('<div class="show">');
* {/registerJs}
*
* Supported attributes: key, position. Refer to Yii documentation for details.
* The position attribute is passed as text without the class prefix.
* Default is 'POS_READY'.
*
* @param $params
* @param $content
* @param \Smarty_Internal_Template $template
* @param $repeat
* @return string
* @note Even though this method is public it should not be called directly.
*/
public function blockJavaScript($params, $content, $template, &$repeat)
{
if ($content !== null) {
$key = isset($params['key']) ? $params['key'] : null;
$position = isset($params['position']) ? $params['position'] : null;
Yii::$app->getView()->registerJs($content,
$this->getViewConstVal($position, View::POS_READY),
$key);
}
}
/**
* Smarty function plugin
* Usage is the following:
*
* {registerCssFile url='@assets/css/normalizer.css'}
*
* Supported attributes: url, key, depends and valid HTML attributes for the link tag.
* Refer to Yii documentation for details.
*
* @param $params
* @param \Smarty_Internal_Template $template
* @return string
* @note Even though this method is public it should not be called directly.
*/
public function functionRegisterCssFile($params, $template)
{
if (!isset($params['url'])) {
trigger_error("registerCssFile: missing 'url' parameter");
}
$url = ArrayHelper::remove($params, 'url');
$key = ArrayHelper::remove($params, 'key', null);
$depends = ArrayHelper::remove($params, 'depends', null);
Yii::$app->getView()->registerCssFile($url, $depends, $params, $key);
}
/**
* Smarty block function plugin
* Usage is the following:
*
* {registerCss}
* div.header {
* background-color: #3366bd;
* color: white;
* }
* {/registerCss}
*
* Supported attributes: key and valid HTML attributes for the style tag.
* Refer to Yii documentation for details.
*
* @param $params
* @param $content
* @param \Smarty_Internal_Template $template
* @param $repeat
* @return string
* @note Even though this method is public it should not be called directly.
*/
public function blockCss($params, $content, $template, &$repeat)
{
if ($content !== null) {
$key = isset($params['key']) ? $params['key'] : null;
Yii::$app->getView()->registerCss($content, $params, $key);
}
}
/**
* Helper function to convert a textual constant identifier to a View class
* integer constant value.
*
* @param string $string Constant identifier name
* @param integer $default Default value
* @return mixed
*/
protected function getViewConstVal($string, $default)
{
$val = @constant('yii\web\View::' . $string);
return isset($val) ? $val : $default;
}
}
\ No newline at end of file
......@@ -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.
......@@ -9,14 +9,17 @@ namespace yii\smarty;
use Yii;
use Smarty;
use yii\base\View;
use yii\web\View;
use yii\base\Widget;
use yii\base\ViewRenderer as BaseViewRenderer;
use yii\helpers\Url;
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
/**
* SmartyViewRenderer allows you to use Smarty templates in views.
*
* @author Alexander Makarov <sam@rmcreative.ru>
* @author Henrik Maier <hwmaier@gmail.com>
* @since 2.0
*/
class ViewRenderer extends BaseViewRenderer
......@@ -29,45 +32,202 @@ class ViewRenderer extends BaseViewRenderer
* @var string the directory or path alias pointing to where Smarty compiled templates will be stored.
*/
public $compilePath = '@runtime/Smarty/compile';
/**
* @var array Add additional directories to Smarty's search path for plugins.
*/
public $pluginDirs = [];
/**
* @var array Class imports similar to the use tag
*/
public $imports = [];
/**
* @var array Widget declarations
*/
public $widgets = ['functions' => [], 'blocks' => []];
/**
* @var Smarty
* @var Smarty The Smarty object used for rendering
*/
public $smarty;
protected $smarty;
/**
* @var array additional Smarty options
* @see http://www.smarty.net/docs/en/api.variables.tpl
*/
public $options = [];
/**
* @var string extension class name
*/
public $extensionClass = '\yii\smarty\Extension';
/**
* Instantiates and configures the Smarty object.
*/
public function init()
{
$this->smarty = new Smarty();
$this->smarty->setCompileDir(Yii::getAlias($this->compilePath));
$this->smarty->setCacheDir(Yii::getAlias($this->cachePath));
$this->smarty->registerPlugin('function', 'path', [$this, 'smarty_function_path']);
foreach ($this->options as $key => $value) {
$this->smarty->$key = $value;
}
$this->smarty->setTemplateDir([
dirname(Yii::$app->getView()->getViewFile()),
Yii::$app->getViewPath(),
]);
// Add additional plugin dirs from configuration array, apply Yii's dir convention
foreach ($this->pluginDirs as &$dir) {
$dir = $this->resolveTemplateDir($dir);
}
$this->smarty->addPluginsDir($this->pluginDirs);
if (isset($this->imports)) {
foreach(($this->imports) as $tag => $class) {
$this->smarty->registerClass($tag, $class);
}
}
// Register block widgets specified in configuration array
if (isset($this->widgets['blocks'])) {
foreach(($this->widgets['blocks']) as $tag => $class) {
$this->smarty->registerPlugin('block', $tag, [$this, '_widget_block__' . $tag]);
$this->smarty->registerClass($tag, $class);
}
}
// Register function widgets specified in configuration array
if (isset($this->widgets['functions'])) {
foreach(($this->widgets['functions']) as $tag => $class) {
$this->smarty->registerPlugin('function', $tag, [$this, '_widget_func__' . $tag]);
$this->smarty->registerClass($tag, $class);
}
}
new $this->extensionClass($this, $this->smarty);
$this->smarty->default_template_handler_func = [$this, 'aliasHandler'];
}
/**
* Smarty template function to get a path for using in links
*
* Usage is the following:
* The directory can be specified in Yii's standard convention
* using @, // and / prefixes or no prefix for view relative directories.
*
* {path route='blog/view' alias=$post.alias user=$user.id}
*
* where route is Yii route and the rest of parameters are passed as is.
* @param string $dir directory name to be resolved
* @return string the resolved directory name
*/
protected function resolveTemplateDir($dir)
{
if (strncmp($dir, '@', 1) === 0) {
// e.g. "@app/views/dir"
$dir = Yii::getAlias($dir);
} elseif (strncmp($dir, '//', 2) === 0) {
// e.g. "//layouts/dir"
$dir = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . ltrim($dir, '/');
} elseif (strncmp($dir, '/', 1) === 0) {
// e.g. "/site/dir"
if (Yii::$app->controller !== null) {
$dir = Yii::$app->controller->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($dir, '/');
} else {
// No controller, what to do?
}
} else {
// relative to view file
$dir = dirname(Yii::$app->getView()->getViewFile()) . DIRECTORY_SEPARATOR . $dir;
}
return $dir;
}
/**
* Mechanism to pass a widget's tag name to the callback function.
*
* @param $params
* @param \Smarty_Internal_Template $template
* Using a magic function call would not be necessary if Smarty would
* support closures. Smarty closure support is announced for 3.2,
* until its release magic function calls are used to pass the
* tag name to the callback.
*
* @param string $method
* @param array $args
* @throws InvalidConfigException
* @throws \BadMethodCallException
* @return string
*/
public function smarty_function_path($params, \Smarty_Internal_Template $template)
public function __call($method, $args)
{
if (!isset($params['route'])) {
trigger_error("path: missing 'route' parameter");
$methodInfo = explode('__', $method);
if (count($methodInfo) === 2) {
$alias = $methodInfo[1];
if (isset($this->widgets['functions'][$alias])) {
if (($methodInfo[0] === '_widget_func') && (count($args) === 2)) {
return $this->widgetFunction($this->widgets['functions'][$alias], $args[0], $args[1]);
}
} elseif (isset($this->widgets['blocks'][$alias])) {
if (($methodInfo[0] === '_widget_block') && (count($args) === 4)) {
return $this->widgetBlock($this->widgets['blocks'][$alias], $args[0], $args[1], $args[2], $args[3]);
}
} else {
throw new InvalidConfigException('Widget "' . $alias . '" not declared.');
}
}
array_unshift($params, $params['route']) ;
unset($params['route']);
throw new \BadMethodCallException('Method does not exist: ' . $method);
}
/**
* Smarty plugin callback function to support widget as Smarty blocks.
* This function is not called directly by Smarty but through a
* magic __call wrapper.
*
* Example usage is the following:
*
* {ActiveForm assign='form' id='login-form'}
* {$form->field($model, 'username')}
* {$form->field($model, 'password')->passwordInput()}
* <div class="form-group">
* <input type="submit" value="Login" class="btn btn-primary" />
* </div>
* {/ActiveForm}
*/
private function widgetBlock($class, $params, $content, \Smarty_Internal_Template $template, &$repeat)
{
// Check if this is the opening ($content is null) or closing tag.
if ($content === null) {
$params['class'] = $class;
// Figure out where to put the result of the widget call, if any
$assign = ArrayHelper::remove($params, 'assign', false);
ob_start();
ob_implicit_flush(false);
$widget = Yii::createObject($params);
Widget::$stack[] = $widget;
if ($assign) {
$template->assign($assign, $widget);
}
} else {
$widget = array_pop(Widget::$stack);
echo $content;
$out = $widget->run();
return ob_get_clean() . $out;
}
}
return Url::to($params);
/**
* Smarty plugin callback function to support widgets as Smarty functions.
* This function is not called directly by Smarty but through a
* magic __call wrapper.
*
* Example usage is the following:
*
* {GridView dataProvider=$provider}
*
*/
private function widgetFunction($class, $params, \Smarty_Internal_Template $template)
{
$repeat = false;
$this->widgetBlock($class, $params, null, $template, $repeat); // $widget->init(...)
return $this->widgetBlock($class, $params, '', $template, $repeat); // $widget->run()
}
/**
......@@ -79,17 +239,35 @@ class ViewRenderer extends BaseViewRenderer
* @param View $view the view object used for rendering the file.
* @param string $file the view file.
* @param array $params the parameters to be passed to the view file.
*
* @return string the rendering result
*/
public function render($view, $file, $params)
{
/* @var $template \Smarty_Internal_Template */
$template = $this->smarty->createTemplate($file, null, null, empty($params) ? null : $params, true);
$template = $this->smarty->createTemplate($file, null, null, empty($params) ? null : $params, false);
// Make Yii params available as smarty config variables
$template->config_vars = Yii::$app->params;
$template->assign('app', \Yii::$app);
$template->assign('this', $view);
return $template->fetch();
}
/**
* Resolves Yii alias into file path
*
* @param string $type
* @param string $name
* @param string $content
* @param string $modified
* @param Smarty $smarty
* @return bool|string path to file or false if it's not found
*/
public function aliasHandler($type, $name, &$content, &$modified, Smarty $smarty)
{
$file = Yii::getAlias($name);
return is_file($file) ? $file : false;
}
}
......@@ -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.
......@@ -174,6 +178,8 @@ Yii Framework 2 Change Log
- Enh #4581: Added ability to disable url encoding in `UrlRule` (tadaszelvys)
- Enh #4602: Added $key param in ActionColumn buttons Closure call (disem)
- Enh #4597: `yii\composer\Installer::setPermission()` supports setting permission for both directories and files now (qiangxue)
- 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)
......
......@@ -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.
......
......@@ -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';
......
......@@ -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';
......
......@@ -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';
......
......@@ -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;
......
......@@ -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);
}
}
......@@ -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'];
......
......@@ -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;
......
......@@ -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);
}
/**
......
......@@ -19,17 +19,39 @@ class BaseHtmlPurifier
{
/**
* Passes markup through HTMLPurifier making it safe to output to end user
*
* @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:
*
* @param string $content
* @param array|null $config
* @return string
* - 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);
}
......
......@@ -195,7 +195,7 @@ class ActiveField extends Component
{
$clientOptions = $this->getClientOptions();
if (!empty($clientOptions)) {
$this->form->attributes[$this->attribute] = $clientOptions;
$this->form->attributes[Html::getInputId($this->model, $this->attribute)] = $clientOptions;
}
$inputID = Html::getInputId($this->model, $this->attribute);
......
......@@ -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');
......
......@@ -7,9 +7,11 @@
namespace yiiunit\extensions\smarty;
use yii\helpers\FileHelper;
use yii\web\AssetManager;
use yii\web\View;
use Yii;
use yiiunit\data\base\Singer;
use yiiunit\TestCase;
/**
......@@ -19,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
*/
......@@ -41,6 +51,47 @@ class ViewRendererTest extends TestCase
$this->assertEquals('test view Hello World!.', $content);
}
public function testLayoutAssets()
{
$view = $this->mockView();
$content = $view->renderFile('@yiiunit/extensions/smarty/views/layout.tpl');
$this->assertEquals(1, preg_match('#<script src="/assets/[0-9a-z]+/jquery\\.js"></script>\s*</body>#', $content), 'Content does not contain the jquery js:' . $content);
}
public function testChangeTitle()
{
$view = $this->mockView();
$view->title = 'Original title';
$content = $view->renderFile('@yiiunit/extensions/smarty/views/changeTitle.tpl');
$this->assertTrue(strpos($content, 'New title') !== false, 'New title should be there:' . $content);
$this->assertFalse(strpos($content, 'Original title') !== false, 'Original title should not be there:' . $content);
}
public function testForm()
{
$view = $this->mockView();
$model = new Singer();
$content = $view->renderFile('@yiiunit/extensions/smarty/views/form.tpl', ['model' => $model]);
$this->assertEquals(1, preg_match('#<form id="login-form" class="form-horizontal" action="/form-handler" method="post">.*?</form>#s', $content), 'Content does not contain form:' . $content);
}
public function testInheritance()
{
$view = $this->mockView();
$content = $view->renderFile('@yiiunit/extensions/smarty/views/extends2.tpl');
$this->assertTrue(strpos($content, 'Hello, I\'m inheritance test!') !== false, 'Hello, I\'m inheritance test! should be there:' . $content);
$this->assertTrue(strpos($content, 'extends2 block') !== false, 'extends2 block should be there:' . $content);
$this->assertFalse(strpos($content, 'extends1 block') !== false, 'extends1 block should not be there:' . $content);
$content = $view->renderFile('@yiiunit/extensions/smarty/views/extends3.tpl');
$this->assertTrue(strpos($content, 'Hello, I\'m inheritance test!') !== false, 'Hello, I\'m inheritance test! should be there:' . $content);
$this->assertTrue(strpos($content, 'extends3 block') !== false, 'extends3 block should be there:' . $content);
$this->assertFalse(strpos($content, 'extends1 block') !== false, 'extends1 block should not be there:' . $content);
}
/**
* @return View
*/
......@@ -50,6 +101,9 @@ class ViewRendererTest extends TestCase
'renderers' => [
'tpl' => [
'class' => 'yii\smarty\ViewRenderer',
'options' => [
'force_compile' => true, // always recompile templates, don't do it in production
],
],
],
'assetManager' => $this->mockAssetManager(),
......
{set title='New title'}
<title>{$this->title}</title>
\ No newline at end of file
Hello, I'm inheritance test!
{block name=test}
extends1 block
{/block}
\ No newline at end of file
{extends file="@yiiunit/extensions/smarty/views/extends1.tpl"}
{block name=test}
extends2 block
{/block}
\ No newline at end of file
{extends file="@yiiunit/extensions/smarty/views/extends1.tpl"}
{block name=test}
extends3 block
{/block}
\ No newline at end of file
{use class='yii\widgets\ActiveForm' type='block'}
{ActiveForm assign='form' id='login-form' action='/form-handler' options=['class' => 'form-horizontal']}
{$form->field($model, 'firstName')}
<div class="form-group">
<div class="col-lg-offset-1 col-lg-11">
<input type="submit" value="Login" class="btn btn-primary" />
</div>
</div>
{/ActiveForm}
\ No newline at end of file
{use class="yii\web\JqueryAsset"}
{JqueryAsset::register($this)|void}
{$this->beginPage()}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="{$app->charset}"/>
<title>{$this->title|escape}</title>
{$this->head()}
</head>
<body>
{$this->beginBody()}
body
{$this->endBody()}
</body>
{$this->endPage()}
\ No newline at end of file
<?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
*/
......
......@@ -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';
}
}
\ No newline at end of file
......@@ -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());
}
}
\ No newline at end of file
......@@ -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));
}
}
......@@ -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);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment