Commit 2a3c87b7 by Thiago Talma

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

parents 16724d2f 87b49414
Database Fixtures Managing Fixtures
================= =================
// todo: this tutorial may be merged into test-fixture.md
Fixtures are important part of testing. Their main purpose is to populate you with data that needed by testing Fixtures are important part of testing. Their main purpose is to populate you with data that needed by testing
different cases. With this data using your tests becoming more efficient and useful. different cases. With this data using your tests becoming more efficient and useful.
Yii supports database fixtures via the `yii fixture` command line tool. This tool supports: Yii supports fixtures via the `yii fixture` command line tool. This tool supports:
* Applying new fixtures to database tables; * Loading fixtures to different storage such as: RDBMS, NoSQL, etc;
* Clearing, database tables (with sequences); * Unloading fixtures in different ways (usually it is clearing storage);
* Auto-generating fixtures and populating it with random data. * Auto-generating fixtures and populating it with random data.
Fixtures format Fixtures format
--------------- ---------------
Fixtures are just plain php files returning array. These files are usually stored under `@tests/unit/fixtures` path, but it Fixtures are objects with different methods and configurations, refer to official [documentation](https://github.com/yiisoft/yii2/blob/master/docs/guide/test-fixture.md) on them.
can be [configured](#configure-command-globally) in other way. Example of fixture file: Lets assume we have fixtures data to load:
``` ```
#users.php file under fixtures path #users.php file under fixtures data path, by default @tests\unit\fixtures\data
return [ return [
[ [
...@@ -36,31 +38,35 @@ return [ ...@@ -36,31 +38,35 @@ return [
], ],
]; ];
``` ```
If we are using fixture that loads data into database then these rows will be applied to `users` table. If we are using nosql fixtures, for example `mongodb`
This data will be loaded to the `users`, but before it will be loaded table `users` will be cleared: all data deleted, sequence reset. fixture, then this data will be applied to `users` mongodb collection. In order to learn about implementing various loading strategies and more, refer to official [documentation](https://github.com/yiisoft/yii2/blob/master/docs/guide/test-fixture.md).
Above fixture example was auto-generated by `yii2-faker` extension, read more about it in these [section](#auto-generating-fixtures). Above fixture example was auto-generated by `yii2-faker` extension, read more about it in these [section](#auto-generating-fixtures).
Fixture classes name should not be plural.
Loading fixtures
----------------
Applying fixtures Fixture classes should be suffixed by `Fixture` class. By default fixtures will be searched under `tests\unit\fixtures` namespace, you can
----------------- change this behavior with config or command options.
To apply fixture to the table, run the following command: To apply fixture, run the following command:
``` ```
yii fixture/apply <tbl_name> yii fixture/apply <fixture_name>
``` ```
The required `tbl_name` parameter specifies a database table to which data will be loaded. You can load data to several tables at once. The required `fixture_name` parameter specifies a fixture name which data will be loaded. You can load several fixtures at once.
Below are correct formats of this command: Below are correct formats of this command:
``` ```
// apply fixtures to the "users" table of database // apply `users` fixture
yii fixture/apply users yii fixture/apply User
// same as above, because default action of "fixture" command is "apply" // same as above, because default action of "fixture" command is "apply"
yii fixture users yii fixture User
// apply several fixtures to several tables. Note that there should not be any whitespace between ",", it should be one string. // apply several fixtures. Note that there should not be any whitespace between ",", it should be one string.
yii fixture users,users_profiles yii fixture User,UserProfile
// apply all fixtures // apply all fixtures
yii fixture/apply all yii fixture/apply all
...@@ -68,29 +74,31 @@ yii fixture/apply all ...@@ -68,29 +74,31 @@ yii fixture/apply all
// same as above // same as above
yii fixture all yii fixture all
// apply fixtures to the table users, but fixtures will be taken from different path. // apply fixtures, but for other database connection.
yii fixture users --fixturePath='@app/my/custom/path/to/fixtures' yii fixtures User --db='customDbConnectionId'
// apply fixtures to the table users, but for other database connection. // apply fixtures, but search them in different namespace. By default namespace is: tests\unit\fixtures.
yii fixtures users --db='customDbConnectionId' yii fixtures User --namespace='alias\my\custom\namespace'
``` ```
Clearing tables Unloading fixtures
--------------- ------------------
To clear table, run the following command: To unload fixture, run the following command:
``` ```
// clear given table: delete all data and reset sequence. // unload Users fixture, by default it will clear fixture storage (for example "users" table, or "users" collection if this is mongodb fixture).
yii fixture/clear users yii fixture/clear User
// clear several tables. Note that there should not be any whitespace between ",", it should be one string. // Unload several fixtures. Note that there should not be any whitespace between ",", it should be one string.
yii fixture/clear users,users_profile yii fixture/clear User,UserProfile
// clear all tables of current connection in current schema // unload all fixtures
yii fixture/clear all yii fixture/clear all
``` ```
Same command options like: `db`, `namespace` also can be applied to this command.
Configure Command Globally Configure Command Globally
-------------------------- --------------------------
While command line options allow us to configure the migration command While command line options allow us to configure the migration command
...@@ -100,9 +108,9 @@ different migration path as follows: ...@@ -100,9 +108,9 @@ different migration path as follows:
``` ```
'controllerMap' => [ 'controllerMap' => [
'fixture' => [ 'fixture' => [
'class' => 'yii\console\FixtureController', 'class' => 'yii\console\controllers\FixtureController',
'fixturePath' => '@app/my/custom/path/to/fixtures',
'db' => 'customDbConnectionId', 'db' => 'customDbConnectionId',
'namespace' => 'myalias\some\custom\namespace',
], ],
] ]
``` ```
......
...@@ -91,6 +91,9 @@ to a console command. The method should return a list of public property names o ...@@ -91,6 +91,9 @@ to a console command. The method should return a list of public property names o
When running a command, you may specify the value of an option using the syntax `--OptionName=OptionValue`. When running a command, you may specify the value of an option using the syntax `--OptionName=OptionValue`.
This will assign `OptionValue` to the `OptionName` property of the controller class. This will assign `OptionValue` to the `OptionName` property of the controller class.
If the default value of an option is of array type, then if you set this option while running the command,
the option value will be converted into an array by splitting the input string by commas.
### Arguments ### Arguments
Besides options, a command can also receive arguments. The arguments will be passed as the parameters to the action Besides options, a command can also receive arguments. The arguments will be passed as the parameters to the action
......
Database basics Database basics
=============== ===============
Yii has a database access layer built on top of PHP's [PDO](http://www.php.net/manual/en/ref.pdo.php). It provides Yii has a database access layer built on top of PHP's [PDO](http://www.php.net/manual/en/book.pdo.php). It provides
uniform API and solves some inconsistencies between different DBMS. By default Yii supports the following DBMS: uniform API and solves some inconsistencies between different DBMS. By default Yii supports the following DBMS:
- [MySQL](http://www.mysql.com/) - [MySQL](http://www.mysql.com/)
......
...@@ -3,36 +3,95 @@ Events ...@@ -3,36 +3,95 @@ Events
TBD, see also [Component.md](../api/base/Component.md). TBD, see also [Component.md](../api/base/Component.md).
There is no longer the need to define an `on`-method in order to define an event in Yii 2.0. [[ADD INTRODUCTION]]
Instead, you can use whatever event names. To attach a handler to an event, you should
use the `on` method now: Creating Event Handlers
-----------------------
In Yii 1, events were defined using the `onEventName` method syntax, such as `onBeforeSave`. This is no longer necessary in Yii 2, as event handling is now assigned using the `on` method. The method's first argument is the name of the event to watch for; the second is the handling method to be called when that event occurs:
```php ```php
$component->on($eventName, $handler); $component->on($eventName, $handler);
// To detach the handler, use: ```
[[LINK TO LIST OF EVENTS]]
The handler must be a valid PHP callback. This could be represented as:
* The name of a global function
* An array consisting of a model name and method name
* An array consisting of an object and a method name
* An anonymous function
```php
// Global function:
$component->on($eventName, 'functionName');
// Model and method names:
$component->on($eventName, ['Modelname', 'functionName']);
// Object and method name:
$component->on($eventName, [$obj, 'functionName']);
// Anonymous function:
$component->on($eventName, function($event) {
// Use $event.
});
```
As shown in the anonymous function example, the event handling function must be defined so that it takes one argument. This will be an [[Event]] object.
Removing Event Handlers
-----------------------
The correspondoing `off` method removes an event handler:
```php
// $component->off($eventName);
```
Yii supports the ability to associate multiple handlers with the same event. When using `off` as in the above, every handler is removed. To remove only a specific handler, provide that as the second argument to `off`:
```php
// $component->off($eventName, $handler); // $component->off($eventName, $handler);
``` ```
The `$handler` should be presented in the `off` method in the same way as was presented in `on` in order to remove it.
Event Parameters
----------------
When you attach a handler, you can now associate it with some parameters which can be later You can make your event handlers easier to work with and more powerful by passing additional values as parameters.
accessed via the event parameter by the handler:
```php ```php
$component->on($eventName, $handler, $params); $component->on($eventName, $handler, $params);
``` ```
The passed parameters will be available in the event handler through `$event->data`, which will be an array.
Because of this change, you can now use "global" events. Simply trigger and attach handlers to [[NEED TO CONFIRM THE ABOVE]]
an event of the application instance:
Global Events
-------------
Thanks to the change in Yii 2 as to how event handlers are created, you can now use "global" events. To create a global event, simply attach handlers to an event on the application instance:
```php ```php
Yii::$app->on($eventName, $handler); Yii::$app->on($eventName, $handler);
.... ```
// this will trigger the event and cause $handler to be invoked.
You can use the `trigger` method to trigger these events manually:
```php
// this will trigger the event and cause $handler to be invoked:
Yii::$app->trigger($eventName); Yii::$app->trigger($eventName);
``` ```
If you need to handle all instances of a class instead of the object you can attach a handler like the following: Class Events
------------
You can also attach event handlers to all instances of a class instead of individual instances. To do so, use the static `Event::on` method:
```php ```php
Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) { Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) {
......
...@@ -15,8 +15,8 @@ PHP 5.4.0 or greater. ...@@ -15,8 +15,8 @@ PHP 5.4.0 or greater.
For developers who want to use Yii, understanding object-oriented For developers who want to use Yii, understanding object-oriented
programming (OOP) is very helpful, because Yii is a pure OOP framework. programming (OOP) is very helpful, because Yii is a pure OOP framework.
Yii 2.0 also makes use of the latest features of PHP such as [namespaces](http://www.php.net/manual/en/language.namespaces.php) Yii 2.0 also makes use of the latest features of PHP such as [namespaces](http://www.php.net/manual/en/language.namespaces.php),
so you should be familiar with how they work. so you should be familiar with how they work, too.
What is Yii Best for? What is Yii Best for?
...@@ -24,16 +24,16 @@ What is Yii Best for? ...@@ -24,16 +24,16 @@ What is Yii Best for?
Yii is a generic Web programming framework that can be used for developing Yii is a generic Web programming framework that can be used for developing
virtually any type of Web application. Because it is light-weight and virtually any type of Web application. Because it is light-weight and
equipped with sophisticated caching mechanisms, it is especially suited equipped with sophisticated caching mechanisms, Yii is especially suited
to high-traffic applications, such as portals, forums, content to high-traffic applications such as portals, forums, content
management systems (CMS), e-commerce projects, etc. management systems (CMS), e-commerce projects, and so on.
How does Yii Compare with Other Frameworks? How does Yii Compare with Other Frameworks?
------------------------------------------- -------------------------------------------
- Like most PHP frameworks, Yii is uses the MVC (Model-View-Controller) design approach. - Like most PHP frameworks, Yii is uses the MVC (Model-View-Controller) design approach.
- Yii is a fullstack framework providing many solutions and components, such as logging, session management, caching etc. - Yii is a fullstack framework providing many solutions and components, such as logging, session management, caching, etc.
- Yii strikes a good balance between simplicity and features. - Yii strikes a good balance between simplicity and features.
- Syntax and overall development usability are taken seriously by the Yii development team. - Syntax and overall development usability are taken seriously by the Yii development team.
- Performance is one of the key goals for the Yii framework. - Performance is one of the key goals for the Yii framework.
......
...@@ -116,7 +116,7 @@ use app\tests\fixtures\UserProfileFixture; ...@@ -116,7 +116,7 @@ use app\tests\fixtures\UserProfileFixture;
class UserProfileTest extends DbTestCase class UserProfileTest extends DbTestCase
{ {
protected function fixtures() public function fixtures()
{ {
return [ return [
'profiles' => UserProfileFixture::className(), 'profiles' => UserProfileFixture::className(),
...@@ -175,6 +175,43 @@ This means you only need to work with `@app/tests/fixtures/initdb.php` if you wa ...@@ -175,6 +175,43 @@ This means you only need to work with `@app/tests/fixtures/initdb.php` if you wa
before each test. You may otherwise simply focus on developing each individual test case and the corresponding fixtures. before each test. You may otherwise simply focus on developing each individual test case and the corresponding fixtures.
Organizing Fixture Classes and Data Files
-----------------------------------------
By default, fixture classes look for the corresponding data files under the `data` folder which is a sub-folder
of the folder containing the fixture class files. You can follow this convention when working with simple projects.
For big projects, chances are that you often need to switch different data files for the same fixture class for
different tests. We thus recommend that you organize the data files in a hierarchical way that is similar to
your class namespaces. For example,
```
# under folder tests\unit\fixtures
data\
components\
fixture_data_file1.php
fixture_data_file2.php
...
fixture_data_fileN.php
models\
fixture_data_file1.php
fixture_data_file2.php
...
fixture_data_fileN.php
# and so on
```
In this way you will avoid collision of fixture data files between tests and use them as you need.
> Note: In the example above fixture files are named only for example purpose. In real life you should name them
> according to which fixture class your fixture classes are extending from. For example, if you are extending
> from [[\yii\test\ActiveFixture]] for DB fixtures, you should use DB table names as the fixture data file names;
> If you are extending for [[\yii\mongodb\ActiveFixture]] for MongoDB fixtures, you should use collection names as the file names.
The similar hierarchy can be used to organize fixture class files. Instead of using `data` as the root directory, you may
want to use `fixtures` as the root directory to avoid conflict with the data files.
Summary Summary
------- -------
...@@ -186,5 +223,5 @@ of running unit tests related with DB: ...@@ -186,5 +223,5 @@ of running unit tests related with DB:
- Load fixtures: clean up the relevant DB tables and populate them with fixture data; - Load fixtures: clean up the relevant DB tables and populate them with fixture data;
- Perform the actual test; - Perform the actual test;
- Unload fixtures. - Unload fixtures.
3. Repeat 2 until all tests finish. 3. Repeat Step 2 until all tests finish.
...@@ -186,7 +186,7 @@ class Renderer extends \yii\apidoc\templates\html\Renderer ...@@ -186,7 +186,7 @@ class Renderer extends \yii\apidoc\templates\html\Renderer
protected function fixMarkdownLinks($content) protected function fixMarkdownLinks($content)
{ {
$content = preg_replace('/href\s*=\s*"([^"]+)\.md(#.*)?"/i', 'href="guide_\1.html\2"', $content); $content = preg_replace('/href\s*=\s*"([^"\/]+)\.md(#.*)?"/i', 'href="guide_\1.html\2"', $content);
return $content; return $content;
} }
} }
\ No newline at end of file
...@@ -29,6 +29,8 @@ class Dropdown extends Widget ...@@ -29,6 +29,8 @@ class Dropdown extends Widget
* - visible: boolean, optional, whether this menu item is visible. Defaults to true. * - visible: boolean, optional, whether this menu item is visible. Defaults to true.
* - linkOptions: array, optional, the HTML attributes of the item link. * - linkOptions: array, optional, the HTML attributes of the item link.
* - options: array, optional, the HTML attributes of the item. * - options: array, optional, the HTML attributes of the item.
* - items: array, optional, the submenu items. The structure is the same as this property.
* Note that Bootstrap doesn't support dropdown submenu. You have to add your own CSS styles to support it.
* *
* To insert divider use `<li role="presentation" class="divider"></li>`. * To insert divider use `<li role="presentation" class="divider"></li>`.
*/ */
......
...@@ -18,7 +18,7 @@ class DbTestCase extends TestCase ...@@ -18,7 +18,7 @@ class DbTestCase extends TestCase
/** /**
* @inheritdoc * @inheritdoc
*/ */
protected function globalFixtures() public function globalFixtures()
{ {
return [ return [
InitDbFixture::className(), InitDbFixture::className(),
......
...@@ -5,6 +5,9 @@ namespace yii\codeception; ...@@ -5,6 +5,9 @@ namespace yii\codeception;
use Yii; use Yii;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
use Codeception\TestCase\Test; use Codeception\TestCase\Test;
use yii\base\UnknownMethodException;
use yii\base\UnknownPropertyException;
use yii\test\ActiveFixture;
use yii\test\FixtureTrait; use yii\test\FixtureTrait;
/** /**
...@@ -26,12 +29,52 @@ class TestCase extends Test ...@@ -26,12 +29,52 @@ class TestCase extends Test
public $appConfig = '@tests/unit/_config.php'; public $appConfig = '@tests/unit/_config.php';
/** /**
* Returns the value of an object property.
*
* Do not call this method directly as it is a PHP magic method that
* will be implicitly called when executing `$value = $object->property;`.
* @param string $name the property name
* @return mixed the property value
* @throws UnknownPropertyException if the property is not defined
*/
public function __get($name)
{
$fixture = $this->getFixture($name);
if ($fixture !== null) {
return $fixture;
} else {
throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
}
}
/**
* Calls the named method which is not a class method.
*
* Do not call this method directly as it is a PHP magic method that
* will be implicitly called when an unknown method is being invoked.
* @param string $name the method name
* @param array $params method parameters
* @throws UnknownMethodException when calling unknown method
* @return mixed the method return value
*/
public function __call($name, $params)
{
$fixture = $this->getFixture($name);
if ($fixture instanceof ActiveFixture) {
return $fixture->getModel(reset($params));
} else {
throw new UnknownMethodException('Unknown method: ' . get_class($this) . "::$name()");
}
}
/**
* @inheritdoc * @inheritdoc
*/ */
protected function setUp() protected function setUp()
{ {
parent::setUp(); parent::setUp();
$this->mockApplication(); $this->mockApplication();
$this->unloadFixtures();
$this->loadFixtures(); $this->loadFixtures();
} }
...@@ -40,7 +83,6 @@ class TestCase extends Test ...@@ -40,7 +83,6 @@ class TestCase extends Test
*/ */
protected function tearDown() protected function tearDown()
{ {
$this->unloadFixtures();
$this->destroyApplication(); $this->destroyApplication();
parent::tearDown(); parent::tearDown();
} }
......
...@@ -8,6 +8,7 @@ Yii Framework 2 debug extension Change Log ...@@ -8,6 +8,7 @@ Yii Framework 2 debug extension Change Log
- Bug #1504: Debug toolbar isn't loaded successfully in some environments when xdebug is enabled (qiangxue) - Bug #1504: Debug toolbar isn't loaded successfully in some environments when xdebug is enabled (qiangxue)
- Bug #1747: Fixed problems with displaying toolbar on small screens (cebe) - Bug #1747: Fixed problems with displaying toolbar on small screens (cebe)
- Bug #1827: Debugger toolbar is loaded twice if an action is calling `run()` to execute another action (qiangxue) - Bug #1827: Debugger toolbar is loaded twice if an action is calling `run()` to execute another action (qiangxue)
- Enh #2006: Added total queries count monitoring (o-rey, Ragazzo)
2.0.0 alpha, December 1, 2013 2.0.0 alpha, December 1, 2013
----------------------------- -----------------------------
......
...@@ -11,6 +11,7 @@ use Yii; ...@@ -11,6 +11,7 @@ use Yii;
use yii\base\Application; use yii\base\Application;
use yii\web\View; use yii\web\View;
use yii\web\ForbiddenHttpException; use yii\web\ForbiddenHttpException;
use yii\helpers\ArrayHelper;
/** /**
* The Yii Debug Module provides the debug toolbar and debugger * The Yii Debug Module provides the debug toolbar and debugger
...@@ -63,7 +64,7 @@ class Module extends \yii\base\Module ...@@ -63,7 +64,7 @@ class Module extends \yii\base\Module
Yii::$app->getView()->on(View::EVENT_END_BODY, [$this, 'renderToolbar']); Yii::$app->getView()->on(View::EVENT_END_BODY, [$this, 'renderToolbar']);
}); });
$this->panels = array_merge($this->corePanels(), $this->panels); $this->panels = ArrayHelper::merge($this->corePanels(), $this->panels);
foreach ($this->panels as $id => $config) { foreach ($this->panels as $id => $config) {
$config['module'] = $this; $config['module'] = $this;
$config['id'] = $id; $config['id'] = $id;
......
...@@ -82,7 +82,7 @@ class Debug extends Base ...@@ -82,7 +82,7 @@ class Debug extends Base
'ajax' => 'Ajax', 'ajax' => 'Ajax',
'url' => 'url', 'url' => 'url',
'statusCode' => 'Status code', 'statusCode' => 'Status code',
'sqlCount' => 'Total queries count', 'sqlCount' => 'Total queries',
]; ];
} }
......
...@@ -21,6 +21,12 @@ use yii\debug\models\search\Db; ...@@ -21,6 +21,12 @@ use yii\debug\models\search\Db;
class DbPanel extends Panel class DbPanel extends Panel
{ {
/** /**
* @var integer the threshold for determining whether the request has involved
* critical number of DB queries. If the number of queries exceeds this number,
* the execution is considered taking critical number of DB queries.
*/
public $criticalQueryThreshold;
/**
* @var array db queries info extracted to array as models, to use with data provider. * @var array db queries info extracted to array as models, to use with data provider.
*/ */
private $_models; private $_models;
...@@ -147,4 +153,16 @@ class DbPanel extends Panel ...@@ -147,4 +153,16 @@ class DbPanel extends Panel
preg_match('/^([a-zA-z]*)/', $timing, $matches); preg_match('/^([a-zA-z]*)/', $timing, $matches);
return count($matches) ? $matches[0] : ''; return count($matches) ? $matches[0] : '';
} }
/**
* Check if given queries count is critical according settings.
*
* @param integer $count queries count
* @return boolean
*/
public function isQueryCountCritical($count)
{
return (($this->criticalQueryThreshold !== null) && ($count > $this->criticalQueryThreshold));
}
} }
...@@ -32,7 +32,9 @@ echo GridView::widget([ ...@@ -32,7 +32,9 @@ echo GridView::widget([
'dataProvider' => $dataProvider, 'dataProvider' => $dataProvider,
'filterModel' => $searchModel, 'filterModel' => $searchModel,
'rowOptions' => function ($model, $key, $index, $grid) use ($searchModel) { 'rowOptions' => function ($model, $key, $index, $grid) use ($searchModel) {
if ($searchModel->isCodeCritical($model['statusCode'])) { $dbPanel = $this->context->module->panels['db'];
if ($searchModel->isCodeCritical($model['statusCode']) || $dbPanel->isQueryCountCritical($model['sqlCount'])) {
return ['class'=>'danger']; return ['class'=>'danger'];
} else { } else {
return []; return [];
...@@ -58,7 +60,22 @@ echo GridView::widget([ ...@@ -58,7 +60,22 @@ echo GridView::widget([
'ip', 'ip',
[ [
'attribute' => 'sqlCount', 'attribute' => 'sqlCount',
'label' => 'Total queries count' 'label' => 'Total queries',
'value' => function ($data) {
$dbPanel = $this->context->module->panels['db'];
if ($dbPanel->isQueryCountCritical($data['sqlCount'])) {
$content = Html::tag('b', $data['sqlCount']) . ' ' . Html::tag('span','',['class' => 'glyphicon glyphicon-exclamation-sign']);
return Html::a($content, $dbPanel->getUrl(), [
'title' => 'Too many queries. Allowed count is ' . $dbPanel->criticalQueryThreshold,
]);
} else {
return $data['sqlCount'];
}
},
'format' => 'html',
], ],
[ [
'attribute' => 'method', 'attribute' => 'method',
......
...@@ -152,7 +152,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface ...@@ -152,7 +152,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface
} else { } else {
/** @var ActiveRecord $class */ /** @var ActiveRecord $class */
$class = $this->modelClass; $class = $this->modelClass;
$model = $class::create($result); $model = $class::instantiate($result);
$class::populateRecord($model, $result);
} }
if (!empty($this->with)) { if (!empty($this->with)) {
$models = [$model]; $models = [$model];
......
...@@ -94,7 +94,8 @@ class ActiveRecord extends BaseActiveRecord ...@@ -94,7 +94,8 @@ class ActiveRecord extends BaseActiveRecord
$command = static::getDb()->createCommand(); $command = static::getDb()->createCommand();
$result = $command->get(static::index(), static::type(), $primaryKey, $options); $result = $command->get(static::index(), static::type(), $primaryKey, $options);
if ($result['exists']) { if ($result['exists']) {
$model = static::create($result); $model = static::instantiate($result);
static::populateRecord($model, $result);
$model->afterFind(); $model->afterFind();
return $model; return $model;
} }
...@@ -123,7 +124,8 @@ class ActiveRecord extends BaseActiveRecord ...@@ -123,7 +124,8 @@ class ActiveRecord extends BaseActiveRecord
$models = []; $models = [];
foreach($result['docs'] as $doc) { foreach($result['docs'] as $doc) {
if ($doc['exists']) { if ($doc['exists']) {
$model = static::create($doc); $model = static::instantiate($doc);
static::populateRecord($model, $doc);
$model->afterFind(); $model->afterFind();
$models[] = $model; $models[] = $model;
} }
...@@ -264,22 +266,38 @@ class ActiveRecord extends BaseActiveRecord ...@@ -264,22 +266,38 @@ class ActiveRecord extends BaseActiveRecord
} }
/** /**
* Creates an active record object using a row of data. * @inheritdoc
* This method is called by [[ActiveQuery]] to populate the query results
* into Active Records. It is not meant to be used to create new records.
* @param array $row attribute values (name => value)
* @return ActiveRecord the newly created active record.
*/ */
public static function create($row) public static function populateRecord($record, $row)
{ {
$record = parent::create($row['_source']); parent::populateRecord($record, $row['_source']);
$pk = static::primaryKey()[0]; $pk = static::primaryKey()[0];
if ($pk === '_id') { if ($pk === '_id') {
$record->$pk = $row['_id']; $record->_id = $row['_id'];
} }
$record->_score = isset($row['_score']) ? $row['_score'] : null; $record->_score = isset($row['_score']) ? $row['_score'] : null;
$record->_version = isset($row['_version']) ? $row['_version'] : null; // TODO version should always be available... $record->_version = isset($row['_version']) ? $row['_version'] : null; // TODO version should always be available...
return $record; }
/**
* Creates an active record instance.
*
* This method is called together with [[populateRecord()]] by [[ActiveQuery]].
*
* You may override this method if the instance being created
* depends on the row data to be populated into the record.
* For example, by creating a record based on the value of a column,
* you may implement the so-called single-table inheritance mapping.
* @param array $row row data to be populated into the record.
* This array consists of the following keys:
* - `_source`: refers to the attributes of the record.
* - `_type`: the type this record is stored in.
* - `_index`: the index this record is stored in.
* @return static the newly created active record
*/
public static function instantiate($row)
{
return new static;
} }
/** /**
......
...@@ -5,9 +5,11 @@ Yii Framework 2 elasticsearch extension Change Log ...@@ -5,9 +5,11 @@ Yii Framework 2 elasticsearch extension Change Log
---------------------------- ----------------------------
- Bug #1993: afterFind event in AR is now called after relations have been populated (cebe, creocoder) - Bug #1993: afterFind event in AR is now called after relations have been populated (cebe, creocoder)
- Enh #1313: made index and type available in `ActiveRecord::instantiate()` to allow creating records based on elasticsearch type when doing cross index/type search (cebe)
- Enh #1382: Added a debug toolbar panel for elasticsearch (cebe) - Enh #1382: Added a debug toolbar panel for elasticsearch (cebe)
- Enh #1765: Added support for primary key path mapping, pk can now be part of the attributes when mapping is defined (cebe) - Enh #1765: Added support for primary key path mapping, pk can now be part of the attributes when mapping is defined (cebe)
- Chg #1765: Changed handling of ActiveRecord primary keys, removed getId(), use getPrimaryKey() instead (cebe) - Chg #1765: Changed handling of ActiveRecord primary keys, removed getId(), use getPrimaryKey() instead (cebe)
- Chg #2281: Renamed `ActiveRecord::create()` to `populateRecord()` and changed signature. This method will not call instantiate() anymore (cebe)
2.0.0 alpha, December 1, 2013 2.0.0 alpha, December 1, 2013
----------------------------- -----------------------------
......
...@@ -9,8 +9,8 @@ namespace yii\faker; ...@@ -9,8 +9,8 @@ namespace yii\faker;
use Yii; use Yii;
use yii\console\Exception; use yii\console\Exception;
use yii\helpers\FileHelper;
use yii\helpers\Console; use yii\helpers\Console;
use yii\helpers\FileHelper;
/** /**
* This command manage fixtures creations based on given template. * This command manage fixtures creations based on given template.
...@@ -69,7 +69,7 @@ use yii\helpers\Console; ...@@ -69,7 +69,7 @@ use yii\helpers\Console;
* ~~~ * ~~~
* *
* In the code above "users" is template name, after this command run, new file named same as template * In the code above "users" is template name, after this command run, new file named same as template
* will be created under the `$fixturePath` folder. * will be created under the `$fixtureDataPath` folder.
* You can generate fixtures for all templates by specifying keyword "all" * You can generate fixtures for all templates by specifying keyword "all"
* *
* ~~~ * ~~~
...@@ -77,7 +77,7 @@ use yii\helpers\Console; ...@@ -77,7 +77,7 @@ use yii\helpers\Console;
* ~~~ * ~~~
* *
* This command will generate fixtures for all template files that are stored under $templatePath and * This command will generate fixtures for all template files that are stored under $templatePath and
* store fixtures under $fixturePath with file names same as templates names. * store fixtures under `$fixtureDataPath` with file names same as templates names.
* *
* You can specify how many fixtures per file you need by the second parameter. In the code below we generate * You can specify how many fixtures per file you need by the second parameter. In the code below we generate
* all fixtures and in each file there will be 3 rows (fixtures). * all fixtures and in each file there will be 3 rows (fixtures).
...@@ -95,8 +95,8 @@ use yii\helpers\Console; ...@@ -95,8 +95,8 @@ use yii\helpers\Console;
* //read templates from the other path * //read templates from the other path
* yii fixture/generate all --templatePath=@app/path/to/my/custom/templates * yii fixture/generate all --templatePath=@app/path/to/my/custom/templates
* *
* //generate fixtures into other folders, but be sure that this folders exists or you will get notice about that. * //generate fixtures into other folders
* yii fixture/generate all --fixturePath=@tests/unit/fixtures/subfolder1/subfolder2/subfolder3 * yii fixture/generate all --fixtureDataPath=@tests/unit/fixtures/subfolder1/subfolder2/subfolder3
* ~~~ * ~~~
* *
* You also can create your own data providers for custom tables fields, see Faker library guide for more info (https://github.com/fzaninotto/Faker); * You also can create your own data providers for custom tables fields, see Faker library guide for more info (https://github.com/fzaninotto/Faker);
...@@ -148,24 +148,24 @@ class FixtureController extends \yii\console\controllers\FixtureController ...@@ -148,24 +148,24 @@ class FixtureController extends \yii\console\controllers\FixtureController
*/ */
public $defaultAction = 'generate'; public $defaultAction = 'generate';
/** /**
* Alias to the template path, where all tables templates are stored. * @var string Alias to the template path, where all tables templates are stored.
* @var string
*/ */
public $templatePath = '@tests/unit/templates/fixtures'; public $templatePath = '@tests/unit/templates/fixtures';
/** /**
* Language to use when generating fixtures data. * @var string Alias to the fixture data path, where data files should be written.
* @var string */
public $fixtureDataPath = '@tests/unit/fixtures/data';
/**
* @var string Language to use when generating fixtures data.
*/ */
public $language; public $language;
/** /**
* Additional data providers that can be created by user and will be added to the Faker generator. * @var array Additional data providers that can be created by user and will be added to the Faker generator.
* More info in [Faker](https://github.com/fzaninotto/Faker.) library docs. * More info in [Faker](https://github.com/fzaninotto/Faker.) library docs.
* @var array
*/ */
public $providers = []; public $providers = [];
/** /**
* Faker generator instance * @var \Faker\Generator Faker generator instance
* @var \Faker\Generator
*/ */
private $_generator; private $_generator;
...@@ -177,7 +177,7 @@ class FixtureController extends \yii\console\controllers\FixtureController ...@@ -177,7 +177,7 @@ class FixtureController extends \yii\console\controllers\FixtureController
public function globalOptions() public function globalOptions()
{ {
return array_merge(parent::globalOptions(), [ return array_merge(parent::globalOptions(), [
'templatePath', 'language' 'templatePath', 'language', 'fixtureDataPath'
]); ]);
} }
...@@ -201,7 +201,7 @@ class FixtureController extends \yii\console\controllers\FixtureController ...@@ -201,7 +201,7 @@ class FixtureController extends \yii\console\controllers\FixtureController
public function actionGenerate(array $file, $times = 2) public function actionGenerate(array $file, $times = 2)
{ {
$templatePath = Yii::getAlias($this->templatePath); $templatePath = Yii::getAlias($this->templatePath);
$fixturePath = Yii::getAlias($this->fixturePath); $fixtureDataPath = Yii::getAlias($this->fixtureDataPath);
if ($this->needToGenerateAll($file[0])) { if ($this->needToGenerateAll($file[0])) {
$files = FileHelper::findFiles($templatePath, ['only' => ['*.php']]); $files = FileHelper::findFiles($templatePath, ['only' => ['*.php']]);
...@@ -233,9 +233,10 @@ class FixtureController extends \yii\console\controllers\FixtureController ...@@ -233,9 +233,10 @@ class FixtureController extends \yii\console\controllers\FixtureController
} }
$content = $this->exportFixtures($fixtures); $content = $this->exportFixtures($fixtures);
$filePath = realpath($fixturePath . '/' . $fixtureFileName); FileHelper::createDirectory($fixtureDataPath);
file_put_contents($filePath, $content); file_put_contents($fixtureDataPath . '/'. $fixtureFileName, $content);
$this->stdout("Fixture file was generated under: $filePath\n", Console::FG_GREEN);
$this->stdout("Fixture file was generated under: $fixtureDataPath\n", Console::FG_GREEN);
} }
} }
...@@ -357,9 +358,9 @@ class FixtureController extends \yii\console\controllers\FixtureController ...@@ -357,9 +358,9 @@ class FixtureController extends \yii\console\controllers\FixtureController
public function confirmGeneration($files) public function confirmGeneration($files)
{ {
$this->stdout("Fixtures will be generated under the path: \n", Console::FG_YELLOW); $this->stdout("Fixtures will be generated under the path: \n", Console::FG_YELLOW);
$this->stdout(realpath(Yii::getAlias($this->fixturePath)) . "\n\n", Console::FG_GREEN); $this->stdout("\t" . Yii::getAlias($this->fixtureDataPath) . "\n\n", Console::FG_GREEN);
$this->stdout("Templates will be taken from path: \n", Console::FG_YELLOW); $this->stdout("Templates will be taken from path: \n", Console::FG_YELLOW);
$this->stdout(realpath(Yii::getAlias($this->templatePath)) . "\n\n", Console::FG_GREEN); $this->stdout("\t" . Yii::getAlias($this->templatePath) . "\n\n", Console::FG_GREEN);
foreach ($files as $index => $fileName) { foreach ($files as $index => $fileName) {
$this->stdout(" " . ($index + 1) . ". " . basename($fileName) . "\n", Console::FG_GREEN); $this->stdout(" " . ($index + 1) . ". " . basename($fileName) . "\n", Console::FG_GREEN);
......
...@@ -95,13 +95,14 @@ php yii fixture/generate users ...@@ -95,13 +95,14 @@ php yii fixture/generate users
//also a short version of this command ("generate" action is default) //also a short version of this command ("generate" action is default)
php yii fixture users php yii fixture users
//to generate fixtures for several tables, use "," as a separator, for example: //to generate several fixtures data files, use "," as a separator, for example:
php yii fixture users,profile,some_other_table php yii fixture users,profile,some_other_name
``` ```
In the code above "users" is template name, after this command run, new file named same as template In the code above "users" is template name, after this command run, new file named same as template
will be created under the fixtures path (by default ```@tests/unit/fixtures```) folder. will be created under the fixtures path (by default ```@tests/unit/fixtures```) folder.
You can generate fixtures for all templates by specifying keyword ```all```. You can generate fixtures for all templates by specifying keyword ```all```. You dont need to worry about if data file
directory already created or not, if not - it will be created by these command.
```php ```php
php yii fixture/generate all php yii fixture/generate all
...@@ -124,8 +125,8 @@ php yii fixture/generate users 5 --language='ru_RU' ...@@ -124,8 +125,8 @@ php yii fixture/generate users 5 --language='ru_RU'
//read templates from the other path //read templates from the other path
php yii fixture/generate all --templatePath='@app/path/to/my/custom/templates' php yii fixture/generate all --templatePath='@app/path/to/my/custom/templates'
//generate fixtures into other folders, but be sure that this folders exists or you will get notice about that. //generate fixtures into other directory.
php yii fixture/generate all --fixturePath='@tests/unit/fixtures/subfolder1/subfolder2/subfolder3' php yii fixture/generate all --fixtureDataPath='@tests/acceptance/fixtures/data'
``` ```
You also can create your own data providers for custom tables fields, see [Faker]((https://github.com/fzaninotto/Faker)) library guide for more info; You also can create your own data providers for custom tables fields, see [Faker]((https://github.com/fzaninotto/Faker)) library guide for more info;
......
...@@ -5,6 +5,7 @@ Yii Framework 2 gii extension Change Log ...@@ -5,6 +5,7 @@ Yii Framework 2 gii extension Change Log
---------------------------- ----------------------------
- Bug #1405: fixed disambiguation of relation names generated by gii (qiangxue) - Bug #1405: fixed disambiguation of relation names generated by gii (qiangxue)
- Bug #2298: Fixed the bug that Gii controller generator did not allow digit in the controller ID (qiangxue)
- Bug: fixed controller in crud template to avoid returning query in findModel() (cebe) - Bug: fixed controller in crud template to avoid returning query in findModel() (cebe)
- Enh #1624: generate rules for unique indexes (lucianobaraglia) - Enh #1624: generate rules for unique indexes (lucianobaraglia)
- Enh #1818: Do not display checkbox column if all rows are empty (johonunu) - Enh #1818: Do not display checkbox column if all rows are empty (johonunu)
......
...@@ -80,8 +80,8 @@ class Generator extends \yii\gii\Generator ...@@ -80,8 +80,8 @@ class Generator extends \yii\gii\Generator
return array_merge(parent::rules(), [ return array_merge(parent::rules(), [
[['controller', 'actions', 'baseClass', 'ns'], 'filter', 'filter' => 'trim'], [['controller', 'actions', 'baseClass', 'ns'], 'filter', 'filter' => 'trim'],
[['controller', 'baseClass'], 'required'], [['controller', 'baseClass'], 'required'],
[['controller'], 'match', 'pattern' => '/^[a-z\\-\\/]*$/', 'message' => 'Only a-z, dashes (-) and slashes (/) are allowed.'], [['controller'], 'match', 'pattern' => '/^[a-z][a-z0-9\\-\\/]*$/', 'message' => 'Only a-z, 0-9, dashes (-) and slashes (/) are allowed.'],
[['actions'], 'match', 'pattern' => '/^[a-z\\-,\\s]*$/', 'message' => 'Only a-z, dashes (-), spaces and commas are allowed.'], [['actions'], 'match', 'pattern' => '/^[a-z][a-z0-9\\-,\\s]*$/', 'message' => 'Only a-z, 0-9, dashes (-), spaces and commas are allowed.'],
[['baseClass'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], [['baseClass'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'],
[['ns'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], [['ns'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'],
]); ]);
......
...@@ -53,12 +53,12 @@ class ActiveFixture extends BaseActiveFixture ...@@ -53,12 +53,12 @@ class ActiveFixture extends BaseActiveFixture
/** /**
* Loads the fixture data. * Loads the fixture data.
* The default implementation will first reset the DB table and then populate it with the data * Data will be batch inserted into the given collection.
* returned by [[getData()]].
*/ */
public function load() public function load()
{ {
$this->resetCollection(); parent::load();
$data = $this->getData(); $data = $this->getData();
$this->getCollection()->batchInsert($data); $this->getCollection()->batchInsert($data);
foreach ($data as $alias => $row) { foreach ($data as $alias => $row) {
...@@ -66,6 +66,17 @@ class ActiveFixture extends BaseActiveFixture ...@@ -66,6 +66,17 @@ class ActiveFixture extends BaseActiveFixture
} }
} }
/**
* Unloads the fixture.
*
* The default implementation will clean up the colection by calling [[resetCollection()]].
*/
public function unload()
{
$this->resetCollection();
parent::unload();
}
protected function getCollection() protected function getCollection()
{ {
return $this->db->getCollection($this->getCollectionName()); return $this->db->getCollection($this->getCollectionName());
......
...@@ -81,7 +81,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface ...@@ -81,7 +81,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface
} else { } else {
/** @var ActiveRecord $class */ /** @var ActiveRecord $class */
$class = $this->modelClass; $class = $this->modelClass;
$model = $class::create($row); $model = $class::instantiate($row);
$class::populateRecord($model, $row);
} }
if (!empty($this->with)) { if (!empty($this->with)) {
$models = [$model]; $models = [$model];
......
...@@ -81,7 +81,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface ...@@ -81,7 +81,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface
} else { } else {
/** @var ActiveRecord $class */ /** @var ActiveRecord $class */
$class = $this->modelClass; $class = $this->modelClass;
$model = $class::create($row); $model = $class::instantiate($row);
$class::populateRecord($model, $row);
} }
if (!empty($this->with)) { if (!empty($this->with)) {
$models = [$model]; $models = [$model];
......
...@@ -112,7 +112,8 @@ class ActiveQuery extends \yii\base\Component implements ActiveQueryInterface ...@@ -112,7 +112,8 @@ class ActiveQuery extends \yii\base\Component implements ActiveQueryInterface
} else { } else {
/** @var ActiveRecord $class */ /** @var ActiveRecord $class */
$class = $this->modelClass; $class = $this->modelClass;
$model = $class::create($row); $model = $class::instantiate($row);
$class::populateRecord($model, $row);
} }
if (!empty($this->with)) { if (!empty($this->with)) {
$models = [$model]; $models = [$model];
......
...@@ -6,6 +6,7 @@ Yii Framework 2 redis extension Change Log ...@@ -6,6 +6,7 @@ Yii Framework 2 redis extension Change Log
- Bug #1993: afterFind event in AR is now called after relations have been populated (cebe, creocoder) - Bug #1993: afterFind event in AR is now called after relations have been populated (cebe, creocoder)
- Enh #1773: keyPrefix property of Session and Cache is not restricted to alnum characters anymore (cebe) - Enh #1773: keyPrefix property of Session and Cache is not restricted to alnum characters anymore (cebe)
- Chg #2281: Renamed `ActiveRecord::create()` to `populateRecord()` and changed signature. This method will not call instantiate() anymore (cebe)
2.0.0 alpha, December 1, 2013 2.0.0 alpha, December 1, 2013
----------------------------- -----------------------------
......
...@@ -139,7 +139,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface ...@@ -139,7 +139,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface
} else { } else {
/** @var $class ActiveRecord */ /** @var $class ActiveRecord */
$class = $this->modelClass; $class = $this->modelClass;
$model = $class::create($row); $model = $class::instantiate($row);
$class::populateRecord($model, $row);
} }
if (!empty($this->with)) { if (!empty($this->with)) {
$models = [$model]; $models = [$model];
......
...@@ -619,29 +619,17 @@ abstract class ActiveRecord extends BaseActiveRecord ...@@ -619,29 +619,17 @@ abstract class ActiveRecord extends BaseActiveRecord
} }
/** /**
* Creates an active record object using a row of data. * @inheritdoc
* This method is called by [[ActiveQuery]] to populate the query results
* into Active Records. It is not meant to be used to create new records.
* @param array $row attribute values (name => value)
* @return ActiveRecord the newly created active record.
*/ */
public static function create($row) public static function populateRecord($record, $row)
{ {
$record = static::instantiate($row);
$columns = static::getIndexSchema()->columns; $columns = static::getIndexSchema()->columns;
foreach ($row as $name => $value) { foreach ($row as $name => $value) {
if (isset($columns[$name])) { if (isset($columns[$name]) && $columns[$name]->isMva) {
$column = $columns[$name]; $row[$name] = explode(',', $value);
if ($column->isMva) {
$value = explode(',', $value);
}
$record->setAttribute($name, $value);
} else {
$record->$name = $value;
} }
} }
$record->setOldAttributes($record->getAttributes()); parent::populateRecord($record, $row);
return $record;
} }
/** /**
......
...@@ -7,6 +7,7 @@ Yii Framework 2 sphinx extension Change Log ...@@ -7,6 +7,7 @@ Yii Framework 2 sphinx extension Change Log
- Bug #1993: afterFind event in AR is now called after relations have been populated (cebe, creocoder) - Bug #1993: afterFind event in AR is now called after relations have been populated (cebe, creocoder)
- Bug #2160: SphinxQL does not support OFFSET (qiangxue, romeo7) - Bug #2160: SphinxQL does not support OFFSET (qiangxue, romeo7)
- Enh #1398: Refactor ActiveRecord to use BaseActiveRecord class of the framework (klimov-paul) - Enh #1398: Refactor ActiveRecord to use BaseActiveRecord class of the framework (klimov-paul)
- Chg #2281: Renamed `ActiveRecord::create()` to `populateRecord()` and changed signature. This method will not call instantiate() anymore (cebe)
2.0.0 alpha, December 1, 2013 2.0.0 alpha, December 1, 2013
----------------------------- -----------------------------
......
...@@ -41,6 +41,8 @@ Yii Framework 2 Change Log ...@@ -41,6 +41,8 @@ Yii Framework 2 Change Log
- Bug #2091: `QueryBuilder::buildInCondition()` fails to handle array not starting with index 0 (qiangxue) - Bug #2091: `QueryBuilder::buildInCondition()` fails to handle array not starting with index 0 (qiangxue)
- Bug #2160: SphinxQL does not support OFFSET (qiangxue, romeo7) - Bug #2160: SphinxQL does not support OFFSET (qiangxue, romeo7)
- Bug #2212: `yii\gridview\DataColumn` generates incorrect labels when used with nosql DB and there is no data (qiangxue) - Bug #2212: `yii\gridview\DataColumn` generates incorrect labels when used with nosql DB and there is no data (qiangxue)
- Bug #2298: Fixed the bug that Gii controller generator did not allow digit in the controller ID (qiangxue)
- Bug #2303: Fixed the bug that `yii\base\Theme::pathMap` did not support dynamic update with path aliases (qiangxue)
- Bug: Fixed `Call to a member function registerAssetFiles() on a non-object` in case of wrong `sourcePath` for an asset bundle (samdark) - Bug: Fixed `Call to a member function registerAssetFiles() on a non-object` in case of wrong `sourcePath` for an asset bundle (samdark)
- Bug: Fixed incorrect event name for `yii\jui\Spinner` (samdark) - Bug: Fixed incorrect event name for `yii\jui\Spinner` (samdark)
- Bug: Json::encode() did not handle objects that implement JsonSerializable interface correctly (cebe) - Bug: Json::encode() did not handle objects that implement JsonSerializable interface correctly (cebe)
...@@ -99,6 +101,7 @@ Yii Framework 2 Change Log ...@@ -99,6 +101,7 @@ Yii Framework 2 Change Log
- Enh #2132: Allow url of CSS and JS files registered in yii\web\View to be url alias (cebe) - Enh #2132: Allow url of CSS and JS files registered in yii\web\View to be url alias (cebe)
- Enh #2144: `Html` helper now supports rendering "data" attributes (qiangxue) - Enh #2144: `Html` helper now supports rendering "data" attributes (qiangxue)
- Enh #2156: `yii migrate` now automatically creates `migrations` directory if it does not exist (samdark) - Enh #2156: `yii migrate` now automatically creates `migrations` directory if it does not exist (samdark)
- Enh: Added support for using arrays as option values for console commands (qiangxue)
- Enh: Added `favicon.ico` and `robots.txt` to default application templates (samdark) - Enh: Added `favicon.ico` and `robots.txt` to default application templates (samdark)
- Enh: Added `Widget::autoIdPrefix` to support prefixing automatically generated widget IDs (qiangxue) - Enh: Added `Widget::autoIdPrefix` to support prefixing automatically generated widget IDs (qiangxue)
- Enh: Support for file aliases in console command 'message' (omnilight) - Enh: Support for file aliases in console command 'message' (omnilight)
...@@ -142,6 +145,7 @@ Yii Framework 2 Change Log ...@@ -142,6 +145,7 @@ Yii Framework 2 Change Log
- Chg #2175: QueryBuilder will now append UNION statements at the end of the primary SQL (qiangxue) - Chg #2175: QueryBuilder will now append UNION statements at the end of the primary SQL (qiangxue)
- Chg #2210: Mysql driver will now treat `tinyint(1)` as integer instead of boolean (qiangxue) - Chg #2210: Mysql driver will now treat `tinyint(1)` as integer instead of boolean (qiangxue)
- Chg #2248: Renamed `yii\base\Model::DEFAULT_SCENARIO` to `yii\base\Model::SCENARIO_DEFAULT` (samdark) - Chg #2248: Renamed `yii\base\Model::DEFAULT_SCENARIO` to `yii\base\Model::SCENARIO_DEFAULT` (samdark)
- Chg #2281: Renamed `ActiveRecord::create()` to `populateRecord()` and changed signature. This method will not call instantiate() anymore (cebe)
- Chg: Renamed `yii\jui\Widget::clientEventsMap` to `clientEventMap` (qiangxue) - Chg: Renamed `yii\jui\Widget::clientEventsMap` to `clientEventMap` (qiangxue)
- Chg: Renamed `ActiveRecord::getPopulatedRelations()` to `getRelatedRecords()` (qiangxue) - Chg: Renamed `ActiveRecord::getPopulatedRelations()` to `getRelatedRecords()` (qiangxue)
- Chg: Renamed `attributeName` and `className` to `targetAttribute` and `targetClass` for `UniqueValidator` and `ExistValidator` (qiangxue) - Chg: Renamed `attributeName` and `className` to `targetAttribute` and `targetClass` for `UniqueValidator` and `ExistValidator` (qiangxue)
......
...@@ -93,6 +93,13 @@ class Theme extends Component ...@@ -93,6 +93,13 @@ class Theme extends Component
public function init() public function init()
{ {
parent::init(); parent::init();
if ($this->baseUrl === null) {
throw new InvalidConfigException('The "baseUrl" property must be set.');
} else {
$this->baseUrl = rtrim(Yii::getAlias($this->baseUrl), '/');
}
if (empty($this->pathMap)) { if (empty($this->pathMap)) {
if ($this->basePath !== null) { if ($this->basePath !== null) {
$this->basePath = Yii::getAlias($this->basePath); $this->basePath = Yii::getAlias($this->basePath);
...@@ -101,20 +108,6 @@ class Theme extends Component ...@@ -101,20 +108,6 @@ class Theme extends Component
throw new InvalidConfigException('The "basePath" property must be set.'); throw new InvalidConfigException('The "basePath" property must be set.');
} }
} }
$paths = [];
foreach ($this->pathMap as $from => $tos) {
$from = FileHelper::normalizePath(Yii::getAlias($from));
foreach ((array)$tos as $to) {
$to = FileHelper::normalizePath(Yii::getAlias($to));
$paths[$from . DIRECTORY_SEPARATOR][] = $to . DIRECTORY_SEPARATOR;
}
}
$this->pathMap = $paths;
if ($this->baseUrl === null) {
throw new InvalidConfigException('The "baseUrl" property must be set.');
} else {
$this->baseUrl = rtrim(Yii::getAlias($this->baseUrl), '/');
}
} }
/** /**
...@@ -127,9 +120,11 @@ class Theme extends Component ...@@ -127,9 +120,11 @@ class Theme extends Component
{ {
$path = FileHelper::normalizePath($path); $path = FileHelper::normalizePath($path);
foreach ($this->pathMap as $from => $tos) { foreach ($this->pathMap as $from => $tos) {
$from = FileHelper::normalizePath(Yii::getAlias($from)) . DIRECTORY_SEPARATOR;
if (strpos($path, $from) === 0) { if (strpos($path, $from) === 0) {
$n = strlen($from); $n = strlen($from);
foreach ($tos as $to) { foreach ((array)$tos as $to) {
$to = FileHelper::normalizePath(Yii::getAlias($to)) . DIRECTORY_SEPARATOR;
$file = $to . substr($path, $n); $file = $to . substr($path, $n);
if (is_file($file)) { if (is_file($file)) {
return $file; return $file;
......
...@@ -61,16 +61,21 @@ class Controller extends \yii\base\Controller ...@@ -61,16 +61,21 @@ class Controller extends \yii\base\Controller
* @param array $params the parameters (name-value pairs) to be passed to the action. * @param array $params the parameters (name-value pairs) to be passed to the action.
* @return integer the status of the action execution. 0 means normal, other values mean abnormal. * @return integer the status of the action execution. 0 means normal, other values mean abnormal.
* @throws InvalidRouteException if the requested action ID cannot be resolved into an action successfully. * @throws InvalidRouteException if the requested action ID cannot be resolved into an action successfully.
* @throws Exception if there are unknown options or missing arguments
* @see createAction * @see createAction
*/ */
public function runAction($id, $params = []) public function runAction($id, $params = [])
{ {
if (!empty($params)) { if (!empty($params)) {
// populate global options here so that they are available in beforeAction().
$options = $this->globalOptions(); $options = $this->globalOptions();
foreach ($params as $name => $value) { foreach ($params as $name => $value) {
if (in_array($name, $options, true)) { if (in_array($name, $options, true)) {
$this->$name = $value; $default = $this->$name;
$this->$name = is_array($default) ? preg_split('/\s*,\s*/', $value) : $value;
unset($params[$name]); unset($params[$name]);
} elseif (!is_int($name)) {
throw new Exception(Yii::t('yii', 'Unknown option: --{name}', ['name' => $name]));
} }
} }
} }
...@@ -89,26 +94,14 @@ class Controller extends \yii\base\Controller ...@@ -89,26 +94,14 @@ class Controller extends \yii\base\Controller
*/ */
public function bindActionParams($action, $params) public function bindActionParams($action, $params)
{ {
$args = [];
if (!empty($params)) {
$options = $this->globalOptions();
foreach ($params as $name => $value) {
if (in_array($name, $options, true)) {
$this->$name = $value;
} elseif (is_int($name)) {
$args[] = $value;
} else {
throw new Exception(Yii::t('yii', 'Unknown option: --{name}', ['name' => $name]));
}
}
}
if ($action instanceof InlineAction) { if ($action instanceof InlineAction) {
$method = new \ReflectionMethod($this, $action->actionMethod); $method = new \ReflectionMethod($this, $action->actionMethod);
} else { } else {
$method = new \ReflectionMethod($action, 'run'); $method = new \ReflectionMethod($action, 'run');
} }
$args = array_values($params);
$missing = []; $missing = [];
foreach ($method->getParameters() as $i => $param) { foreach ($method->getParameters() as $i => $param) {
if ($param->isArray() && isset($args[$i])) { if ($param->isArray() && isset($args[$i])) {
......
...@@ -144,7 +144,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface ...@@ -144,7 +144,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface
} else { } else {
/** @var ActiveRecord $class */ /** @var ActiveRecord $class */
$class = $this->modelClass; $class = $this->modelClass;
$model = $class::create($row); $model = $class::instantiate($row);
$class::populateRecord($model, $row);
} }
if (!empty($this->with)) { if (!empty($this->with)) {
$models = [$model]; $models = [$model];
......
...@@ -128,11 +128,14 @@ trait ActiveQueryTrait ...@@ -128,11 +128,14 @@ trait ActiveQueryTrait
$class = $this->modelClass; $class = $this->modelClass;
if ($this->indexBy === null) { if ($this->indexBy === null) {
foreach ($rows as $row) { foreach ($rows as $row) {
$models[] = $class::create($row); $model = $class::instantiate($row);
$class::populateRecord($model, $row);
$models[] = $model;
} }
} else { } else {
foreach ($rows as $row) { foreach ($rows as $row) {
$model = $class::create($row); $model = $class::instantiate($row);
$class::populateRecord($model, $row);
if (is_string($this->indexBy)) { if (is_string($this->indexBy)) {
$key = $model->{$this->indexBy}; $key = $model->{$this->indexBy};
} else { } else {
......
...@@ -277,23 +277,15 @@ class ActiveRecord extends BaseActiveRecord ...@@ -277,23 +277,15 @@ class ActiveRecord extends BaseActiveRecord
/** /**
* @inheritdoc * @inheritdoc
*/ */
public static function create($row) public static function populateRecord($record, $row)
{ {
$record = static::instantiate($row);
$attributes = array_flip($record->attributes());
$columns = static::getTableSchema()->columns; $columns = static::getTableSchema()->columns;
foreach ($row as $name => $value) { foreach ($row as $name => $value) {
if (isset($columns[$name])) { if (isset($columns[$name])) {
$value = $columns[$name]->typecast($value); $row[$name] = $columns[$name]->typecast($value);
}
if (isset($attributes[$name])) {
$record->setAttribute($name, $value);
} else {
$record->$name = $value;
} }
} }
$record->setOldAttributes($record->getAttributes()); parent::populateRecord($record, $row);
return $record;
} }
/** /**
......
...@@ -984,23 +984,21 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface ...@@ -984,23 +984,21 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
} }
/** /**
* Creates an active record object using a row of data from the database/storage. * Populates an active record object using a row of data from the database/storage.
* *
* This method is *not* meant to be used to create new records. * This is an internal method meant to be called to create active record objects after
*
* It is an internal method meant to be called to create active record objects after
* fetching data from the database. It is mainly used by [[ActiveQuery]] to populate * fetching data from the database. It is mainly used by [[ActiveQuery]] to populate
* the query results into Active Records. * the query results into active records.
* *
* When calling this method manually you should call [[afterFind()]] on the created * When calling this method manually you should call [[afterFind()]] on the created
* record to trigger the [[EVENT_AFTER_FIND|afterFind Event]]. * record to trigger the [[EVENT_AFTER_FIND|afterFind Event]].
* *
* @param BaseActiveRecord $record the record to be populated. In most cases this will be an instance
* created by [[instantiate()]] beforehand.
* @param array $row attribute values (name => value) * @param array $row attribute values (name => value)
* @return static the newly created active record.
*/ */
public static function create($row) public static function populateRecord($record, $row)
{ {
$record = static::instantiate($row);
$columns = array_flip($record->attributes()); $columns = array_flip($record->attributes());
foreach ($row as $name => $value) { foreach ($row as $name => $value) {
if (isset($columns[$name])) { if (isset($columns[$name])) {
...@@ -1010,12 +1008,13 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface ...@@ -1010,12 +1008,13 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
} }
} }
$record->_oldAttributes = $record->_attributes; $record->_oldAttributes = $record->_attributes;
return $record;
} }
/** /**
* Creates an active record instance. * Creates an active record instance.
* This method is called by [[create()]]. *
* This method is called together with [[populateRecord()]] by [[ActiveQuery]].
*
* You may override this method if the instance being created * You may override this method if the instance being created
* depends on the row data to be populated into the record. * depends on the row data to be populated into the record.
* For example, by creating a record based on the value of a column, * For example, by creating a record based on the value of a column,
......
...@@ -65,7 +65,6 @@ class ActiveFixture extends BaseActiveFixture ...@@ -65,7 +65,6 @@ class ActiveFixture extends BaseActiveFixture
/** /**
* Loads the fixture. * Loads the fixture.
* *
* The default implementation will first clean up the table by calling [[resetTable()]].
* It will then populate the table with the data returned by [[getData()]]. * It will then populate the table with the data returned by [[getData()]].
* *
* If you override this method, you should consider calling the parent implementation * If you override this method, you should consider calling the parent implementation
...@@ -73,7 +72,7 @@ class ActiveFixture extends BaseActiveFixture ...@@ -73,7 +72,7 @@ class ActiveFixture extends BaseActiveFixture
*/ */
public function load() public function load()
{ {
$this->resetTable(); parent::load();
$table = $this->getTableSchema(); $table = $this->getTableSchema();
...@@ -92,6 +91,17 @@ class ActiveFixture extends BaseActiveFixture ...@@ -92,6 +91,17 @@ class ActiveFixture extends BaseActiveFixture
} }
/** /**
* Unloads the fixture.
*
* The default implementation will clean up the table by calling [[resetTable()]].
*/
public function unload()
{
$this->resetTable();
parent::unload();
}
/**
* Returns the fixture data. * Returns the fixture data.
* *
* The default implementation will try to return the fixture data by including the external file specified by [[dataFile]]. * The default implementation will try to return the fixture data by including the external file specified by [[dataFile]].
......
...@@ -67,5 +67,19 @@ class Fixture extends Component ...@@ -67,5 +67,19 @@ class Fixture extends Component
public function unload() public function unload()
{ {
} }
/**
* This method is called BEFORE any fixture data is unloaded for the current test.
*/
public function beforeUnload()
{
}
/**
* This method is called AFTER all fixture data have been unloaded for the current test.
*/
public function afterUnload()
{
}
} }
...@@ -9,8 +9,6 @@ namespace yii\test; ...@@ -9,8 +9,6 @@ namespace yii\test;
use Yii; use Yii;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
use yii\base\UnknownMethodException;
use yii\base\UnknownPropertyException;
/** /**
* FixtureTrait provides functionalities for loading, unloading and accessing fixtures for a test case. * FixtureTrait provides functionalities for loading, unloading and accessing fixtures for a test case.
...@@ -33,50 +31,6 @@ trait FixtureTrait ...@@ -33,50 +31,6 @@ trait FixtureTrait
* if B depends on A. * if B depends on A.
*/ */
private $_fixtures; private $_fixtures;
/**
* @var array the fixture class names indexed by the corresponding fixture names (aliases).
*/
private $_fixtureAliases;
/**
* Returns the value of an object property.
*
* Do not call this method directly as it is a PHP magic method that
* will be implicitly called when executing `$value = $object->property;`.
* @param string $name the property name
* @return mixed the property value
* @throws UnknownPropertyException if the property is not defined
*/
public function __get($name)
{
$fixture = $this->getFixture($name);
if ($fixture !== null) {
return $fixture;
} else {
throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
}
}
/**
* Calls the named method which is not a class method.
*
* Do not call this method directly as it is a PHP magic method that
* will be implicitly called when an unknown method is being invoked.
* @param string $name the method name
* @param array $params method parameters
* @throws UnknownMethodException when calling unknown method
* @return mixed the method return value
*/
public function __call($name, $params)
{
$fixture = $this->getFixture($name);
if ($fixture instanceof ActiveFixture) {
return $fixture->getModel(reset($params));
} else {
throw new UnknownMethodException('Unknown method: ' . get_class($this) . "::$name()");
}
}
/** /**
* Declares the fixtures that are needed by the current test case. * Declares the fixtures that are needed by the current test case.
...@@ -101,7 +55,7 @@ trait FixtureTrait ...@@ -101,7 +55,7 @@ trait FixtureTrait
* *
* @return array the fixtures needed by the current test case * @return array the fixtures needed by the current test case
*/ */
protected function fixtures() public function fixtures()
{ {
return []; return [];
} }
...@@ -113,100 +67,139 @@ trait FixtureTrait ...@@ -113,100 +67,139 @@ trait FixtureTrait
* @return array the fixtures shared and required by different test cases. * @return array the fixtures shared and required by different test cases.
* @see fixtures() * @see fixtures()
*/ */
protected function globalFixtures() public function globalFixtures()
{ {
return []; return [];
} }
/** /**
* Loads the fixtures. * Loads the specified fixtures.
* This method will load the fixtures specified by `$fixtures` or [[globalFixtures()]] and [[fixtures()]]. * This method will call [[Fixture::load()]] for every fixture object.
* @param array $fixtures the fixtures to loaded. If not set, [[fixtures()]] will be loaded instead. * @param Fixture[] $fixtures the fixtures to be loaded. If this parameter is not specified,
* @throws InvalidConfigException if fixtures are not properly configured or if a circular dependency among * the return value of [[getFixtures()]] will be used.
* the fixtures is detected.
*/ */
protected function loadFixtures($fixtures = null) public function loadFixtures($fixtures = null)
{ {
if ($fixtures === null) { if ($fixtures === null) {
$fixtures = array_merge($this->globalFixtures(), $this->fixtures()); $fixtures = $this->getFixtures();
}
// normalize fixture configurations
$config = []; // configuration provided in test case
$this->_fixtureAliases = [];
foreach ($fixtures as $name => $fixture) {
if (!is_array($fixture)) {
$fixtures[$name] = $fixture = ['class' => $fixture];
} elseif (!isset($fixture['class'])) {
throw new InvalidConfigException("You must specify 'class' for the fixture '$name'.");
}
$config[$fixture['class']] = $fixture;
$this->_fixtureAliases[$name] = $fixture['class'];
}
// create fixture instances
$this->_fixtures = [];
$stack = array_reverse($fixtures);
while (($fixture = array_pop($stack)) !== null) {
if ($fixture instanceof Fixture) {
$class = get_class($fixture);
unset($this->_fixtures[$class]); // unset so that the fixture is added to the last in the next line
$this->_fixtures[$class] = $fixture;
} else {
$class = $fixture['class'];
if (!isset($this->_fixtures[$class])) {
$this->_fixtures[$class] = false;
$stack[] = $fixture = Yii::createObject($fixture);
foreach ($fixture->depends as $dep) {
// need to use the configuration provided in test case
$stack[] = isset($config[$dep]) ? $config[$dep] : ['class' => $dep];
}
} elseif ($this->_fixtures[$class] === false) {
throw new InvalidConfigException("A circular dependency is detected for fixture '$class'.");
}
}
} }
// load fixtures
/** @var Fixture $fixture */ /** @var Fixture $fixture */
foreach ($this->_fixtures as $fixture) { foreach ($fixtures as $fixture) {
$fixture->beforeLoad(); $fixture->beforeLoad();
} }
foreach ($this->_fixtures as $fixture) { foreach ($fixtures as $fixture) {
$fixture->load(); $fixture->load();
} }
foreach ($this->_fixtures as $fixture) { foreach (array_reverse($fixtures) as $fixture) {
$fixture->afterLoad(); $fixture->afterLoad();
} }
} }
/** /**
* Unloads all existing fixtures. * Unloads the specified fixtures.
* This method will call [[Fixture::unload()]] for every fixture object.
* @param Fixture[] $fixtures the fixtures to be loaded. If this parameter is not specified,
* the return value of [[getFixtures()]] will be used.
*/ */
protected function unloadFixtures() public function unloadFixtures($fixtures = null)
{ {
if ($fixtures === null) {
$fixtures = $this->getFixtures();
}
/** @var Fixture $fixture */ /** @var Fixture $fixture */
foreach (array_reverse($this->_fixtures) as $fixture) { foreach ($fixtures as $fixture) {
$fixture->beforeUnload();
}
$fixtures = array_reverse($fixtures);
foreach ($fixtures as $fixture) {
$fixture->unload(); $fixture->unload();
} }
foreach ($fixtures as $fixture) {
$fixture->afterUnload();
}
} }
/** /**
* @return array the loaded fixtures for the current test case * Returns the fixture objects as specified in [[globalFixtures()]] and [[fixtures()]].
* @return Fixture[] the loaded fixtures for the current test case
*/ */
protected function getFixtures() public function getFixtures()
{ {
if ($this->_fixtures === null) {
$this->_fixtures = $this->createFixtures(array_merge($this->globalFixtures(), $this->fixtures()));
}
return $this->_fixtures; return $this->_fixtures;
} }
/** /**
* Returns the named fixture. * Returns the named fixture.
* @param string $name the fixture alias or class name * @param string $name the fixture name. This can be either the fixture alias name, or the class name if the alias is not used.
* @return Fixture the fixture object, or null if the named fixture does not exist. * @return Fixture the fixture object, or null if the named fixture does not exist.
*/ */
protected function getFixture($name) public function getFixture($name)
{ {
$class = isset($this->_fixtureAliases[$name]) ? $this->_fixtureAliases[$name] : $name; if ($this->_fixtures === null) {
return isset($this->_fixtures[$class]) ? $this->_fixtures[$class] : null; $this->_fixtures = $this->createFixtures(array_merge($this->globalFixtures(), $this->fixtures()));
}
$name = ltrim($name, '\\');
return isset($this->_fixtures[$name]) ? $this->_fixtures[$name] : null;
}
/**
* Creates the specified fixture instances.
* All dependent fixtures will also be created.
* @param array $fixtures the fixtures to be created. You may provide fixture names or fixture configurations.
* If this parameter is not provided, the fixtures specified in [[globalFixtures()]] and [[fixtures()]] will be created.
* @return Fixture[] the created fixture instances
* @throws InvalidConfigException if fixtures are not properly configured or if a circular dependency among
* the fixtures is detected.
*/
protected function createFixtures(array $fixtures)
{
// normalize fixture configurations
$config = []; // configuration provided in test case
$aliases = []; // class name => alias or class name
foreach ($fixtures as $name => $fixture) {
if (!is_array($fixture)) {
$class = ltrim($fixture, '\\');
$fixtures[$name] = ['class' => $class];
$aliases[$class] = is_integer($name) ? $class : $name;
} elseif (isset($fixture['class'])) {
$class = ltrim($fixture['class'], '\\');
$config[$class] = $fixture;
$aliases[$class] = $name;
} else {
throw new InvalidConfigException("You must specify 'class' for the fixture '$name'.");
}
}
// create fixture instances
$instances = [];
$stack = array_reverse($fixtures);
while (($fixture = array_pop($stack)) !== null) {
if ($fixture instanceof Fixture) {
$class = get_class($fixture);
$name = isset($aliases[$class]) ? $aliases[$class] : $class;
unset($instances[$name]); // unset so that the fixture is added to the last in the next line
$instances[$name] = $fixture;
} else {
$class = ltrim($fixture['class'], '\\');
$name = isset($aliases[$class]) ? $aliases[$class] : $class;
if (!isset($instances[$name])) {
$instances[$name] = false;
$stack[] = $fixture = Yii::createObject($fixture);
foreach ($fixture->depends as $dep) {
// need to use the configuration provided in test case
$stack[] = isset($config[$dep]) ? $config[$dep] : ['class' => $dep];
}
} elseif ($instances[$name] === false) {
throw new InvalidConfigException("A circular dependency is detected for fixture '$class'.");
}
}
}
return $instances;
} }
} }
...@@ -46,9 +46,7 @@ class InitDbFixture extends DbFixture ...@@ -46,9 +46,7 @@ class InitDbFixture extends DbFixture
*/ */
public function beforeLoad() public function beforeLoad()
{ {
foreach ($this->schemas as $schema) { $this->checkIntegrity(false);
$this->checkIntegrity(false, $schema);
}
} }
/** /**
...@@ -56,9 +54,7 @@ class InitDbFixture extends DbFixture ...@@ -56,9 +54,7 @@ class InitDbFixture extends DbFixture
*/ */
public function afterLoad() public function afterLoad()
{ {
foreach ($this->schemas as $schema) { $this->checkIntegrity(true);
$this->checkIntegrity(true, $schema);
}
} }
/** /**
...@@ -73,6 +69,22 @@ class InitDbFixture extends DbFixture ...@@ -73,6 +69,22 @@ class InitDbFixture extends DbFixture
} }
/** /**
* @inheritdoc
*/
public function beforeUnload()
{
$this->checkIntegrity(false);
}
/**
* @inheritdoc
*/
public function afterUnload()
{
$this->checkIntegrity(true);
}
/**
* Toggles the DB integrity check. * Toggles the DB integrity check.
* @param boolean $check whether to turn on or off the integrity check. * @param boolean $check whether to turn on or off the integrity check.
*/ */
......
...@@ -25,22 +25,22 @@ class MyDbTestCase ...@@ -25,22 +25,22 @@ class MyDbTestCase
public function setUp() public function setUp()
{ {
$this->unloadFixtures();
$this->loadFixtures(); $this->loadFixtures();
} }
public function tearDown() public function tearDown()
{ {
$this->unloadFixtures();
} }
protected function fixtures() public function fixtures()
{ {
return [ return [
'customers' => CustomerFixture::className(), 'customers' => CustomerFixture::className(),
]; ];
} }
protected function globalFixtures() public function globalFixtures()
{ {
return [ return [
InitDbFixture::className(), InitDbFixture::className(),
...@@ -71,24 +71,26 @@ class ActiveFixtureTest extends DatabaseTestCase ...@@ -71,24 +71,26 @@ class ActiveFixtureTest extends DatabaseTestCase
{ {
$test = new MyDbTestCase(); $test = new MyDbTestCase();
$test->setUp(); $test->setUp();
$fixture = $test->customers; $fixture = $test->getFixture('customers');
$this->assertEquals(CustomerFixture::className(), get_class($fixture)); $this->assertEquals(CustomerFixture::className(), get_class($fixture));
$this->assertEquals(2, count($fixture)); $this->assertEquals(2, count($fixture));
$this->assertEquals(1, $fixture['customer1']['id']); $this->assertEquals(1, $fixture['customer1']['id']);
$this->assertEquals('customer1@example.com', $fixture['customer1']['email']); $this->assertEquals('customer1@example.com', $fixture['customer1']['email']);
$this->assertEquals(2, $fixture['customer2']['id']); $this->assertEquals(2, $fixture['customer2']['id']);
$this->assertEquals('customer2@example.com', $fixture['customer2']['email']); $this->assertEquals('customer2@example.com', $fixture['customer2']['email']);
$test->tearDown();
} }
public function testGetModel() public function testGetModel()
{ {
$test = new MyDbTestCase(); $test = new MyDbTestCase();
$test->setUp(); $test->setUp();
$fixture = $test->customers; $fixture = $test->getFixture('customers');
$this->assertEquals(Customer::className(), get_class($fixture->getModel('customer1'))); $this->assertEquals(Customer::className(), get_class($fixture->getModel('customer1')));
$this->assertEquals(1, $fixture->getModel('customer1')->id); $this->assertEquals(1, $fixture->getModel('customer1')->id);
$this->assertEquals('customer1@example.com', $fixture->getModel('customer1')->email); $this->assertEquals('customer1@example.com', $fixture->getModel('customer1')->email);
$this->assertEquals(2, $fixture->getModel('customer2')->id); $this->assertEquals(2, $fixture->getModel('customer2')->id);
$this->assertEquals('customer2@example.com', $fixture->getModel('customer2')->email); $this->assertEquals('customer2@example.com', $fixture->getModel('customer2')->email);
$test->tearDown();
} }
} }
...@@ -78,7 +78,7 @@ class MyTestCase ...@@ -78,7 +78,7 @@ class MyTestCase
return $this->getFixture($name); return $this->getFixture($name);
} }
protected function fixtures() public function fixtures()
{ {
switch ($this->scenario) { switch ($this->scenario) {
case 0: return []; case 0: return [];
......
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