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
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;
* Clearing, database tables (with sequences);
* Loading fixtures to different storage such as: RDBMS, NoSQL, etc;
* Unloading fixtures in different ways (usually it is clearing storage);
* Auto-generating fixtures and populating it with random data.
Fixtures format
---------------
Fixtures are just plain php files returning array. These files are usually stored under `@tests/unit/fixtures` path, but it
can be [configured](#configure-command-globally) in other way. Example of fixture file:
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.
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 [
[
......@@ -36,31 +38,35 @@ return [
],
];
```
This data will be loaded to the `users`, but before it will be loaded table `users` will be cleared: all data deleted, sequence reset.
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`
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).
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:
```
// apply fixtures to the "users" table of database
yii fixture/apply users
// apply `users` fixture
yii fixture/apply User
// 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.
yii fixture users,users_profiles
// apply several fixtures. Note that there should not be any whitespace between ",", it should be one string.
yii fixture User,UserProfile
// apply all fixtures
yii fixture/apply all
......@@ -68,29 +74,31 @@ yii fixture/apply all
// same as above
yii fixture all
// apply fixtures to the table users, but fixtures will be taken from different path.
yii fixture users --fixturePath='@app/my/custom/path/to/fixtures'
// apply fixtures, but for other database connection.
yii fixtures User --db='customDbConnectionId'
// apply fixtures to the table users, but for other database connection.
yii fixtures users --db='customDbConnectionId'
// apply fixtures, but search them in different namespace. By default namespace is: tests\unit\fixtures.
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.
yii fixture/clear users
// 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 User
// clear several tables. Note that there should not be any whitespace between ",", it should be one string.
yii fixture/clear users,users_profile
// Unload several fixtures. Note that there should not be any whitespace between ",", it should be one string.
yii fixture/clear User,UserProfile
// clear all tables of current connection in current schema
// unload all fixtures
yii fixture/clear all
```
Same command options like: `db`, `namespace` also can be applied to this command.
Configure Command Globally
--------------------------
While command line options allow us to configure the migration command
......@@ -100,9 +108,9 @@ different migration path as follows:
```
'controllerMap' => [
'fixture' => [
'class' => 'yii\console\FixtureController',
'fixturePath' => '@app/my/custom/path/to/fixtures',
'class' => 'yii\console\controllers\FixtureController',
'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
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.
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
Besides options, a command can also receive arguments. The arguments will be passed as the parameters to the action
......
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:
- [MySQL](http://www.mysql.com/)
......
......@@ -3,36 +3,95 @@ Events
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.
Instead, you can use whatever event names. To attach a handler to an event, you should
use the `on` method now:
[[ADD INTRODUCTION]]
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
$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);
```
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
accessed via the event parameter by the handler:
You can make your event handlers easier to work with and more powerful by passing additional values as parameters.
```php
$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
an event of the application instance:
[[NEED TO CONFIRM THE ABOVE]]
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
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);
```
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
Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) {
......
......@@ -15,8 +15,8 @@ PHP 5.4.0 or greater.
For developers who want to use Yii, understanding object-oriented
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)
so you should be familiar with how they work.
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, too.
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
virtually any type of Web application. Because it is light-weight and
equipped with sophisticated caching mechanisms, it is especially suited
to high-traffic applications, such as portals, forums, content
management systems (CMS), e-commerce projects, etc.
equipped with sophisticated caching mechanisms, Yii is especially suited
to high-traffic applications such as portals, forums, content
management systems (CMS), e-commerce projects, and so on.
How does Yii Compare with Other Frameworks?
-------------------------------------------
- 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.
- Syntax and overall development usability are taken seriously by the Yii development team.
- Performance is one of the key goals for the Yii framework.
......
......@@ -116,7 +116,7 @@ use app\tests\fixtures\UserProfileFixture;
class UserProfileTest extends DbTestCase
{
protected function fixtures()
public function fixtures()
{
return [
'profiles' => UserProfileFixture::className(),
......@@ -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.
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
-------
......@@ -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;
- Perform the actual test;
- 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
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;
}
}
\ No newline at end of file
......@@ -29,6 +29,8 @@ class Dropdown extends Widget
* - visible: boolean, optional, whether this menu item is visible. Defaults to true.
* - linkOptions: array, optional, the HTML attributes of the item link.
* - 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>`.
*/
......
......@@ -18,7 +18,7 @@ class DbTestCase extends TestCase
/**
* @inheritdoc
*/
protected function globalFixtures()
public function globalFixtures()
{
return [
InitDbFixture::className(),
......
......@@ -5,6 +5,9 @@ namespace yii\codeception;
use Yii;
use yii\base\InvalidConfigException;
use Codeception\TestCase\Test;
use yii\base\UnknownMethodException;
use yii\base\UnknownPropertyException;
use yii\test\ActiveFixture;
use yii\test\FixtureTrait;
/**
......@@ -26,12 +29,52 @@ class TestCase extends Test
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
*/
protected function setUp()
{
parent::setUp();
$this->mockApplication();
$this->unloadFixtures();
$this->loadFixtures();
}
......@@ -40,7 +83,6 @@ class TestCase extends Test
*/
protected function tearDown()
{
$this->unloadFixtures();
$this->destroyApplication();
parent::tearDown();
}
......
......@@ -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 #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)
- Enh #2006: Added total queries count monitoring (o-rey, Ragazzo)
2.0.0 alpha, December 1, 2013
-----------------------------
......
......@@ -11,6 +11,7 @@ use Yii;
use yii\base\Application;
use yii\web\View;
use yii\web\ForbiddenHttpException;
use yii\helpers\ArrayHelper;
/**
* The Yii Debug Module provides the debug toolbar and debugger
......@@ -63,7 +64,7 @@ class Module extends \yii\base\Module
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) {
$config['module'] = $this;
$config['id'] = $id;
......
......@@ -82,7 +82,7 @@ class Debug extends Base
'ajax' => 'Ajax',
'url' => 'url',
'statusCode' => 'Status code',
'sqlCount' => 'Total queries count',
'sqlCount' => 'Total queries',
];
}
......
......@@ -21,6 +21,12 @@ use yii\debug\models\search\Db;
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.
*/
private $_models;
......@@ -147,4 +153,16 @@ class DbPanel extends Panel
preg_match('/^([a-zA-z]*)/', $timing, $matches);
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([
'dataProvider' => $dataProvider,
'filterModel' => $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'];
} else {
return [];
......@@ -58,7 +60,22 @@ echo GridView::widget([
'ip',
[
'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',
......
......@@ -152,7 +152,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface
} else {
/** @var ActiveRecord $class */
$class = $this->modelClass;
$model = $class::create($result);
$model = $class::instantiate($result);
$class::populateRecord($model, $result);
}
if (!empty($this->with)) {
$models = [$model];
......
......@@ -94,7 +94,8 @@ class ActiveRecord extends BaseActiveRecord
$command = static::getDb()->createCommand();
$result = $command->get(static::index(), static::type(), $primaryKey, $options);
if ($result['exists']) {
$model = static::create($result);
$model = static::instantiate($result);
static::populateRecord($model, $result);
$model->afterFind();
return $model;
}
......@@ -123,7 +124,8 @@ class ActiveRecord extends BaseActiveRecord
$models = [];
foreach($result['docs'] as $doc) {
if ($doc['exists']) {
$model = static::create($doc);
$model = static::instantiate($doc);
static::populateRecord($model, $doc);
$model->afterFind();
$models[] = $model;
}
......@@ -264,22 +266,38 @@ class ActiveRecord extends BaseActiveRecord
}
/**
* Creates an active record object using a row of data.
* 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.
* @inheritdoc
*/
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];
if ($pk === '_id') {
$record->$pk = $row['_id'];
$record->_id = $row['_id'];
}
$record->_score = isset($row['_score']) ? $row['_score'] : null;
$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
----------------------------
- 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 #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 #2281: Renamed `ActiveRecord::create()` to `populateRecord()` and changed signature. This method will not call instantiate() anymore (cebe)
2.0.0 alpha, December 1, 2013
-----------------------------
......
......@@ -9,8 +9,8 @@ namespace yii\faker;
use Yii;
use yii\console\Exception;
use yii\helpers\FileHelper;
use yii\helpers\Console;
use yii\helpers\FileHelper;
/**
* This command manage fixtures creations based on given template.
......@@ -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
* 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"
*
* ~~~
......@@ -77,7 +77,7 @@ use yii\helpers\Console;
* ~~~
*
* 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
* all fixtures and in each file there will be 3 rows (fixtures).
......@@ -95,8 +95,8 @@ use yii\helpers\Console;
* //read templates from the other path
* 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.
* yii fixture/generate all --fixturePath=@tests/unit/fixtures/subfolder1/subfolder2/subfolder3
* //generate fixtures into other folders
* 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);
......@@ -148,24 +148,24 @@ class FixtureController extends \yii\console\controllers\FixtureController
*/
public $defaultAction = 'generate';
/**
* Alias to the template path, where all tables templates are stored.
* @var string
* @var string Alias to the template path, where all tables templates are stored.
*/
public $templatePath = '@tests/unit/templates/fixtures';
/**
* Language to use when generating fixtures data.
* @var string
* @var string Alias to the fixture data path, where data files should be written.
*/
public $fixtureDataPath = '@tests/unit/fixtures/data';
/**
* @var string Language to use when generating fixtures data.
*/
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.
* @var array
*/
public $providers = [];
/**
* Faker generator instance
* @var \Faker\Generator
* @var \Faker\Generator Faker generator instance
*/
private $_generator;
......@@ -177,7 +177,7 @@ class FixtureController extends \yii\console\controllers\FixtureController
public function globalOptions()
{
return array_merge(parent::globalOptions(), [
'templatePath', 'language'
'templatePath', 'language', 'fixtureDataPath'
]);
}
......@@ -201,7 +201,7 @@ class FixtureController extends \yii\console\controllers\FixtureController
public function actionGenerate(array $file, $times = 2)
{
$templatePath = Yii::getAlias($this->templatePath);
$fixturePath = Yii::getAlias($this->fixturePath);
$fixtureDataPath = Yii::getAlias($this->fixtureDataPath);
if ($this->needToGenerateAll($file[0])) {
$files = FileHelper::findFiles($templatePath, ['only' => ['*.php']]);
......@@ -233,9 +233,10 @@ class FixtureController extends \yii\console\controllers\FixtureController
}
$content = $this->exportFixtures($fixtures);
$filePath = realpath($fixturePath . '/' . $fixtureFileName);
file_put_contents($filePath, $content);
$this->stdout("Fixture file was generated under: $filePath\n", Console::FG_GREEN);
FileHelper::createDirectory($fixtureDataPath);
file_put_contents($fixtureDataPath . '/'. $fixtureFileName, $content);
$this->stdout("Fixture file was generated under: $fixtureDataPath\n", Console::FG_GREEN);
}
}
......@@ -357,9 +358,9 @@ class FixtureController extends \yii\console\controllers\FixtureController
public function confirmGeneration($files)
{
$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(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) {
$this->stdout(" " . ($index + 1) . ". " . basename($fileName) . "\n", Console::FG_GREEN);
......
......@@ -95,13 +95,14 @@ php yii fixture/generate users
//also a short version of this command ("generate" action is default)
php yii fixture users
//to generate fixtures for several tables, use "," as a separator, for example:
php yii fixture users,profile,some_other_table
//to generate several fixtures data files, use "," as a separator, for example:
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
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 yii fixture/generate all
......@@ -124,8 +125,8 @@ php yii fixture/generate users 5 --language='ru_RU'
//read templates from the other path
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.
php yii fixture/generate all --fixturePath='@tests/unit/fixtures/subfolder1/subfolder2/subfolder3'
//generate fixtures into other directory.
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;
......
......@@ -5,6 +5,7 @@ Yii Framework 2 gii extension Change Log
----------------------------
- 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)
- Enh #1624: generate rules for unique indexes (lucianobaraglia)
- Enh #1818: Do not display checkbox column if all rows are empty (johonunu)
......
......@@ -80,8 +80,8 @@ class Generator extends \yii\gii\Generator
return array_merge(parent::rules(), [
[['controller', 'actions', 'baseClass', 'ns'], 'filter', 'filter' => 'trim'],
[['controller', 'baseClass'], 'required'],
[['controller'], 'match', 'pattern' => '/^[a-z\\-\\/]*$/', 'message' => 'Only a-z, dashes (-) and slashes (/) are allowed.'],
[['actions'], 'match', 'pattern' => '/^[a-z\\-,\\s]*$/', 'message' => 'Only a-z, dashes (-), spaces and commas 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][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.'],
[['ns'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'],
]);
......
......@@ -53,12 +53,12 @@ class ActiveFixture extends BaseActiveFixture
/**
* Loads the fixture data.
* The default implementation will first reset the DB table and then populate it with the data
* returned by [[getData()]].
* Data will be batch inserted into the given collection.
*/
public function load()
{
$this->resetCollection();
parent::load();
$data = $this->getData();
$this->getCollection()->batchInsert($data);
foreach ($data as $alias => $row) {
......@@ -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()
{
return $this->db->getCollection($this->getCollectionName());
......
......@@ -81,7 +81,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface
} else {
/** @var ActiveRecord $class */
$class = $this->modelClass;
$model = $class::create($row);
$model = $class::instantiate($row);
$class::populateRecord($model, $row);
}
if (!empty($this->with)) {
$models = [$model];
......
......@@ -81,7 +81,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface
} else {
/** @var ActiveRecord $class */
$class = $this->modelClass;
$model = $class::create($row);
$model = $class::instantiate($row);
$class::populateRecord($model, $row);
}
if (!empty($this->with)) {
$models = [$model];
......
......@@ -112,7 +112,8 @@ class ActiveQuery extends \yii\base\Component implements ActiveQueryInterface
} else {
/** @var ActiveRecord $class */
$class = $this->modelClass;
$model = $class::create($row);
$model = $class::instantiate($row);
$class::populateRecord($model, $row);
}
if (!empty($this->with)) {
$models = [$model];
......
......@@ -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)
- 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
-----------------------------
......
......@@ -139,7 +139,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface
} else {
/** @var $class ActiveRecord */
$class = $this->modelClass;
$model = $class::create($row);
$model = $class::instantiate($row);
$class::populateRecord($model, $row);
}
if (!empty($this->with)) {
$models = [$model];
......
......@@ -619,29 +619,17 @@ abstract class ActiveRecord extends BaseActiveRecord
}
/**
* Creates an active record object using a row of data.
* 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.
* @inheritdoc
*/
public static function create($row)
public static function populateRecord($record, $row)
{
$record = static::instantiate($row);
$columns = static::getIndexSchema()->columns;
foreach ($row as $name => $value) {
if (isset($columns[$name])) {
$column = $columns[$name];
if ($column->isMva) {
$value = explode(',', $value);
}
$record->setAttribute($name, $value);
} else {
$record->$name = $value;
if (isset($columns[$name]) && $columns[$name]->isMva) {
$row[$name] = explode(',', $value);
}
}
$record->setOldAttributes($record->getAttributes());
return $record;
parent::populateRecord($record, $row);
}
/**
......
......@@ -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 #2160: SphinxQL does not support OFFSET (qiangxue, romeo7)
- 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
-----------------------------
......
......@@ -41,6 +41,8 @@ Yii Framework 2 Change Log
- Bug #2091: `QueryBuilder::buildInCondition()` fails to handle array not starting with index 0 (qiangxue)
- 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 #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 incorrect event name for `yii\jui\Spinner` (samdark)
- Bug: Json::encode() did not handle objects that implement JsonSerializable interface correctly (cebe)
......@@ -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 #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: 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 `Widget::autoIdPrefix` to support prefixing automatically generated widget IDs (qiangxue)
- Enh: Support for file aliases in console command 'message' (omnilight)
......@@ -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 #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 #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 `ActiveRecord::getPopulatedRelations()` to `getRelatedRecords()` (qiangxue)
- Chg: Renamed `attributeName` and `className` to `targetAttribute` and `targetClass` for `UniqueValidator` and `ExistValidator` (qiangxue)
......
......@@ -93,6 +93,13 @@ class Theme extends Component
public function 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 ($this->basePath !== null) {
$this->basePath = Yii::getAlias($this->basePath);
......@@ -101,20 +108,6 @@ class Theme extends Component
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
{
$path = FileHelper::normalizePath($path);
foreach ($this->pathMap as $from => $tos) {
$from = FileHelper::normalizePath(Yii::getAlias($from)) . DIRECTORY_SEPARATOR;
if (strpos($path, $from) === 0) {
$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);
if (is_file($file)) {
return $file;
......
......@@ -61,16 +61,21 @@ class Controller extends \yii\base\Controller
* @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.
* @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
*/
public function runAction($id, $params = [])
{
if (!empty($params)) {
// populate global options here so that they are available in beforeAction().
$options = $this->globalOptions();
foreach ($params as $name => $value) {
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]);
} 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
*/
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) {
$method = new \ReflectionMethod($this, $action->actionMethod);
} else {
$method = new \ReflectionMethod($action, 'run');
}
$args = array_values($params);
$missing = [];
foreach ($method->getParameters() as $i => $param) {
if ($param->isArray() && isset($args[$i])) {
......
......@@ -144,7 +144,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface
} else {
/** @var ActiveRecord $class */
$class = $this->modelClass;
$model = $class::create($row);
$model = $class::instantiate($row);
$class::populateRecord($model, $row);
}
if (!empty($this->with)) {
$models = [$model];
......
......@@ -128,11 +128,14 @@ trait ActiveQueryTrait
$class = $this->modelClass;
if ($this->indexBy === null) {
foreach ($rows as $row) {
$models[] = $class::create($row);
$model = $class::instantiate($row);
$class::populateRecord($model, $row);
$models[] = $model;
}
} else {
foreach ($rows as $row) {
$model = $class::create($row);
$model = $class::instantiate($row);
$class::populateRecord($model, $row);
if (is_string($this->indexBy)) {
$key = $model->{$this->indexBy};
} else {
......
......@@ -277,23 +277,15 @@ class ActiveRecord extends BaseActiveRecord
/**
* @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;
foreach ($row as $name => $value) {
if (isset($columns[$name])) {
$value = $columns[$name]->typecast($value);
}
if (isset($attributes[$name])) {
$record->setAttribute($name, $value);
} else {
$record->$name = $value;
$row[$name] = $columns[$name]->typecast($value);
}
}
$record->setOldAttributes($record->getAttributes());
return $record;
parent::populateRecord($record, $row);
}
/**
......
......@@ -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.
*
* It is an internal method meant to be called to create active record objects after
* This 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
* the query results into Active Records.
* the query results into active records.
*
* When calling this method manually you should call [[afterFind()]] on the created
* 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)
* @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());
foreach ($row as $name => $value) {
if (isset($columns[$name])) {
......@@ -1010,12 +1008,13 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
}
}
$record->_oldAttributes = $record->_attributes;
return $record;
}
/**
* 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
* depends on the row data to be populated into the record.
* For example, by creating a record based on the value of a column,
......
......@@ -65,7 +65,6 @@ class ActiveFixture extends BaseActiveFixture
/**
* 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()]].
*
* If you override this method, you should consider calling the parent implementation
......@@ -73,7 +72,7 @@ class ActiveFixture extends BaseActiveFixture
*/
public function load()
{
$this->resetTable();
parent::load();
$table = $this->getTableSchema();
......@@ -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.
*
* 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
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;
use Yii;
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.
......@@ -33,50 +31,6 @@ trait FixtureTrait
* if B depends on A.
*/
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.
......@@ -101,7 +55,7 @@ trait FixtureTrait
*
* @return array the fixtures needed by the current test case
*/
protected function fixtures()
public function fixtures()
{
return [];
}
......@@ -113,100 +67,139 @@ trait FixtureTrait
* @return array the fixtures shared and required by different test cases.
* @see fixtures()
*/
protected function globalFixtures()
public function globalFixtures()
{
return [];
}
/**
* Loads the fixtures.
* This method will load the fixtures specified by `$fixtures` or [[globalFixtures()]] and [[fixtures()]].
* @param array $fixtures the fixtures to loaded. If not set, [[fixtures()]] will be loaded instead.
* @throws InvalidConfigException if fixtures are not properly configured or if a circular dependency among
* the fixtures is detected.
* Loads the specified fixtures.
* This method will call [[Fixture::load()]] 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 loadFixtures($fixtures = null)
public function loadFixtures($fixtures = null)
{
if ($fixtures === null) {
$fixtures = array_merge($this->globalFixtures(), $this->fixtures());
}
// 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'.");
}
}
$fixtures = $this->getFixtures();
}
// load fixtures
/** @var Fixture $fixture */
foreach ($this->_fixtures as $fixture) {
foreach ($fixtures as $fixture) {
$fixture->beforeLoad();
}
foreach ($this->_fixtures as $fixture) {
foreach ($fixtures as $fixture) {
$fixture->load();
}
foreach ($this->_fixtures as $fixture) {
foreach (array_reverse($fixtures) as $fixture) {
$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 */
foreach (array_reverse($this->_fixtures) as $fixture) {
foreach ($fixtures as $fixture) {
$fixture->beforeUnload();
}
$fixtures = array_reverse($fixtures);
foreach ($fixtures as $fixture) {
$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;
}
/**
* 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.
*/
protected function getFixture($name)
public function getFixture($name)
{
$class = isset($this->_fixtureAliases[$name]) ? $this->_fixtureAliases[$name] : $name;
return isset($this->_fixtures[$class]) ? $this->_fixtures[$class] : null;
if ($this->_fixtures === 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
*/
public function beforeLoad()
{
foreach ($this->schemas as $schema) {
$this->checkIntegrity(false, $schema);
}
$this->checkIntegrity(false);
}
/**
......@@ -56,9 +54,7 @@ class InitDbFixture extends DbFixture
*/
public function afterLoad()
{
foreach ($this->schemas as $schema) {
$this->checkIntegrity(true, $schema);
}
$this->checkIntegrity(true);
}
/**
......@@ -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.
* @param boolean $check whether to turn on or off the integrity check.
*/
......
......@@ -25,22 +25,22 @@ class MyDbTestCase
public function setUp()
{
$this->unloadFixtures();
$this->loadFixtures();
}
public function tearDown()
{
$this->unloadFixtures();
}
protected function fixtures()
public function fixtures()
{
return [
'customers' => CustomerFixture::className(),
];
}
protected function globalFixtures()
public function globalFixtures()
{
return [
InitDbFixture::className(),
......@@ -71,24 +71,26 @@ class ActiveFixtureTest extends DatabaseTestCase
{
$test = new MyDbTestCase();
$test->setUp();
$fixture = $test->customers;
$fixture = $test->getFixture('customers');
$this->assertEquals(CustomerFixture::className(), get_class($fixture));
$this->assertEquals(2, count($fixture));
$this->assertEquals(1, $fixture['customer1']['id']);
$this->assertEquals('customer1@example.com', $fixture['customer1']['email']);
$this->assertEquals(2, $fixture['customer2']['id']);
$this->assertEquals('customer2@example.com', $fixture['customer2']['email']);
$test->tearDown();
}
public function testGetModel()
{
$test = new MyDbTestCase();
$test->setUp();
$fixture = $test->customers;
$fixture = $test->getFixture('customers');
$this->assertEquals(Customer::className(), get_class($fixture->getModel('customer1')));
$this->assertEquals(1, $fixture->getModel('customer1')->id);
$this->assertEquals('customer1@example.com', $fixture->getModel('customer1')->email);
$this->assertEquals(2, $fixture->getModel('customer2')->id);
$this->assertEquals('customer2@example.com', $fixture->getModel('customer2')->email);
$test->tearDown();
}
}
......@@ -78,7 +78,7 @@ class MyTestCase
return $this->getFixture($name);
}
protected function fixtures()
public function fixtures()
{
switch ($this->scenario) {
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