From a3071fd90dd392de5da4e424e50e72b85ddfe3be Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 2 Jan 2014 23:46:08 +0400 Subject: [PATCH] added faker integration --- composer.json | 2 + extensions/yii/faker/CHANGELOG.md | 12 + extensions/yii/faker/FixtureController.php | 335 +++++++++++++++++++++ extensions/yii/faker/LICENSE.md | 32 ++ extensions/yii/faker/README.md | 134 +++++++++ extensions/yii/faker/composer.json | 27 ++ .../yii/console/controllers/FixtureController.php | 150 +++++++++ 7 files changed, 692 insertions(+) create mode 100644 extensions/yii/faker/CHANGELOG.md create mode 100644 extensions/yii/faker/FixtureController.php create mode 100644 extensions/yii/faker/LICENSE.md create mode 100644 extensions/yii/faker/README.md create mode 100644 extensions/yii/faker/composer.json create mode 100644 framework/yii/console/controllers/FixtureController.php diff --git a/composer.json b/composer.json index 2c807aa..83ee504 100644 --- a/composer.json +++ b/composer.json @@ -55,6 +55,7 @@ "yiisoft/yii2-codeception": "self.version", "yiisoft/yii2-debug": "self.version", "yiisoft/yii2-elasticsearch": "self.version", + "yiisoft/yii2-faker": "self.version", "yiisoft/yii2-imagine": "self.version", "yiisoft/yii2-gii": "self.version", "yiisoft/yii2-jui": "self.version", @@ -97,6 +98,7 @@ "yii\\codeception\\": "extensions/", "yii\\debug\\": "extensions/", "yii\\elasticsearch\\": "extensions/", + "yii\\faker\\": "extensions/", "yii\\gii\\": "extensions/", "yii\\imagine\\" : "extensions/", "yii\\jui\\": "extensions/", diff --git a/extensions/yii/faker/CHANGELOG.md b/extensions/yii/faker/CHANGELOG.md new file mode 100644 index 0000000..9643b4b --- /dev/null +++ b/extensions/yii/faker/CHANGELOG.md @@ -0,0 +1,12 @@ +Yii Framework 2 faker extension Change Log +============================================== + +2.0.0 beta under development +---------------------------- + +- no changes in this release. + +2.0.0 alpha, December 1, 2013 +----------------------------- + +- Initial release. \ No newline at end of file diff --git a/extensions/yii/faker/FixtureController.php b/extensions/yii/faker/FixtureController.php new file mode 100644 index 0000000..ddbc514 --- /dev/null +++ b/extensions/yii/faker/FixtureController.php @@ -0,0 +1,335 @@ + [ + * 'faker' => [ + * 'class' => 'yii\faker\FixtureController', + * ], + * ], + * ~~~ + * + * To start using this command you need to be familiar (read guide) for the Faker library and + * generate fixtures template files, according to the given format: + * + * ~~~ + * #users.php file under $templatePath + * + * return [ + * [ + * 'table_column0' => 'faker_formatter', + * ... + * 'table_columnN' => 'other_faker_formatter + * 'table_columnN+1' => function ($fixture, $faker, $index) { + * //set needed fixture fields based on different conditions + * return $fixture; + * } + * ], + * ]; + * ~~~ + * + * If you use callback as a attribute value, then it will be called as shown with three parameters: + * * $fixture - current fixture array. + * * $faker - faker generator instance + * * $index - current fixture index. For example if user need to generate 3 fixtures for tbl_user, it will be 0..2 + * After you set all needed fields in callback, you need to return $fixture array back from the callback. + * + * After you prepared needed templates for tables you can simply generate your fixtures via command + * + * ~~~ + * php yii faker/generate users + * + * #also a short version of this command (generate action is default) + * php yii faker users + * ~~~ + * + * In the code above "users" is template name, after this command run, new file named same as template + * will be created under the $fixturesPath folder. + * You can generate fixtures for all templates by specifying keyword "all_fixtures" + * + * ~~~ + * php yii faker/generate all_fixtures + * ~~~ + * + * This command will generate fixtures for all template files that are stored under $templatePath and + * store fixtures under $fixturesPath 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). + * + * ~~~ + * php yii faker/generate all_fixtures 3 + * ~~~ + * + * You can specify different options of this command: + * + * ~~~ + * #generate fixtures in russian languge + * php yii faker/generate users 5 --language='ru_RU' + * + * #read templates from the other path + * php yii faker/generate all_fixtures --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 faker/generate all_fixtures --fixturesPath='@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); + * After you created custom provider, for example: + * + * ~~~ + * + * class Book extends \Faker\Provider\Base + * { + * public function title($nbWords = 5) + * { + * $sentence = $this->generator->sentence($nbWords); + * return mb_substr($sentence, 0, mb_strlen($sentence) - 1); + * } + * + * public function ISBN() + * { + * return $this->generator->randomNumber(13); + * } + * } + * ~~~ + * + * you can use it by adding it to the $providers property of the current command. In your console.php config: + * + * ~~~ + * 'controllerMap' => [ + * 'faker' => [ + * 'class' => 'yii\faker\FixtureController', + * 'providers' => [ + * 'app\tests\unit\faker\providers\Book', + * ], + * ], + * ], + * ~~~ + * + * @property \Faker\Generator $generator + * + * @since 2.0.0 + */ +class FixtureController extends \yii\console\controllers\FixtureController +{ + + /** + * type of fixture generating + */ + const GENERATE_ALL = 'all_fixtures'; + + /** + * @var string controller default action ID. + */ + public $defaultAction = 'generate'; + + /** + * Alias to the template path, where all tables templates are stored. + * @var string + */ + public $templatePath = '@tests/unit/templates/fixtures'; + + /** + * Language to use when generating fixtures data. + * @var string + */ + public $language; + + /** + * 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 = array(); + + /** + * Faker generator instance + * @var \Faker\Generator + */ + private $_generator; + + /** + * Returns the names of the global options for this command. + * @return array the names of the global options for this command. + */ + public function globalOptions() + { + return array_merge(parent::globalOptions(), [ + 'templatePath','language' + ]); + } + + public function beforeAction($action) + { + if (parent::beforeAction($action)) { + $this->checkPaths(); + $this->addProviders(); + return true; + } else { + return false; + } + } + + /** + * Generates fixtures and fill them with Faker data. + * @param string $file filename for the table template. You can generate all fixtures for all tables + * by specifiyng keyword "all_fixtures" as filename. + * @param integer $times how much fixtures do you want per table + */ + public function actionGenerate($file, $times = 2) + { + $templatePath = Yii::getAlias($this->templatePath); + $fixturesPath = Yii::getAlias($this->fixturesPath); + + if ($this->needToGenerateAll($file)) + $files = FileHelper::findFiles($templatePath, ['only' => ['.php']]); + else + $files = FileHelper::findFiles($templatePath, ['only' => [$file.'.php']]); + + foreach ($files as $templateFile) + { + $fixtureFileName = basename($templateFile); + $template = $this->getTemplate($templateFile); + $fixtures = []; + + for ($i = 0; $i < $times; $i++) { + $fixtures[$i] = $this->generateFixture($template, $i); + } + + $content = $this->getExportedFormat($fixtures); + file_put_contents($fixturesPath.'/'.$fixtureFileName, $content); + $this->stdout("Fixture file was generated under: " . realpath($fixturesPath . "/" . $fixtureFileName) . "\n", Console::FG_GREEN); + } + } + + /** + * Returns Faker generator instance. Getter for private property. + * @return \Faker\Generator + */ + public function getGenerator() + { + if (is_null($this->_generator)) + { + #replacing - on _ because Faker support only en_US format and not intl + + $language = is_null($this->language) ? str_replace('-','_', Yii::$app->language) : $this->language; + $this->_generator = \Faker\Factory::create($language); + } + + return $this->_generator; + } + + /** + * Check if the template path and migrations path exists and writable. + */ + public function checkPaths() + { + $path = Yii::getAlias($this->templatePath); + + if (!is_dir($path)) + throw new Exception("The template path \"{$this->templatePath}\" not exist"); + } + + /** + * Adds users providers to the faker generator. + */ + public function addProviders() + { + foreach($this->providers as $provider) + $this->generator->addProvider(new $provider($this->generator)); + } + + /** + * Checks if needed to generate all fixtures. + * @param string $file + * @return bool + */ + public function needToGenerateAll($file) + { + return $file == self::GENERATE_ALL; + } + + /** + * Returns generator template for the given fixture name + * @param string $file template file + * @return array generator template + * @throws \yii\console\Exception if wrong file format + */ + public function getTemplate($file) + { + $template = require($file); + + if (!is_array($template)) { + throw new Exception("The template file \"$file\" has wrong format. It should return valid template array"); + } + + return $template; + } + + /** + * Returns exported to the string representation of given fixtures array. + * @param type $fixtures + * @return string exported fixtures format + */ + public function getExportedFormat($fixtures) + { + $content = "$value) { + $content .= "\n\t\t'{$name}' => '{$value}',"; + } + + $content .= "\n\t],"; + + } + $content .= "\n];\n"; + return $content; + } + + /** + * Generates fixture from given template + * @param array $template fixture template + * @param integer $index current fixture index + * @return array fixture + */ + public function generateFixture($template, $index) + { + $fixture = []; + + foreach($template as $attribute => $fakerProperty) { + if (!is_string($fakerProperty)) { + $fixture = call_user_func_array($fakerProperty,[$fixture,$this->generator, $index]); + } else { + $fixture[$attribute] = $this->generator->$fakerProperty; + } + } + + return $fixture; + } + +} diff --git a/extensions/yii/faker/LICENSE.md b/extensions/yii/faker/LICENSE.md new file mode 100644 index 0000000..6edcc4f --- /dev/null +++ b/extensions/yii/faker/LICENSE.md @@ -0,0 +1,32 @@ +The Yii framework is free software. It is released under the terms of +the following BSD License. + +Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of Yii Software LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/extensions/yii/faker/README.md b/extensions/yii/faker/README.md new file mode 100644 index 0000000..27529e8 --- /dev/null +++ b/extensions/yii/faker/README.md @@ -0,0 +1,134 @@ +Faker Extension for Yii 2 +=============================== + +This extension provides a [`Faker`](https://github.com/fzaninotto/Faker) fixture command for Yii 2. + +Installation +------------ + +The preferred way to install this extension is through [composer](http://getcomposer.org/download/). + +Either run + +``` +php composer.phar require yiisoft/yii2-faker "*" +``` + +or add + +```json +"yiisoft/yii2-faker": "*" +``` + +to the require section of your composer.json. + +Usage +----- + +To use this extension, simply add the following code in your application configuration (console.php): + +```php +'controllerMap' => [ + 'faker' => [ + 'class' => 'yii\faker\FixtureController', + ], +], +``` +To start using this command you need to be familiar (read guide) for the Faker library and +generate fixtures template files, according to the given format: + +```php +#users.php file under template path (by default @tests/unit/fixtures/templates) +return [ + [ + 'table_column0' => 'faker_formatter', + ... + 'table_columnN' => 'other_faker_formatter + 'table_columnN+1' => function ($fixture, $faker, $index) { + //set needed fixture fields based on different conditions + return $fixture; + } + ], +]; +~~~ +``` + +If you use callback as a attribute value, then it will be called as shown with three parameters: + +* $fixture - current fixture array. +* $faker - faker generator instance +* $index - current fixture index. For example if user need to generate 3 fixtures for tbl_user, it will be 0..2. + +After you set all needed fields in callback, you need to return $fixture array back from the callback. + +After you prepared needed templates for tables you can simply generate your fixtures via command + +```php +#generate fixtures for the users table based on users fixture template +php yii faker/generate users + +#also a short version of this command ("generate" action is default) +php yii faker users +``` + +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_fixtures" + +```php +php yii faker/generate all_fixtures +``` + +This command will generate fixtures for all template files that are stored under template path and +store fixtures under fixtures path 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). + +```php +php yii faker/generate all_fixtures 3 +``` +You can specify different options of this command: + +```php +#generate fixtures in russian language +php yii faker/generate users 5 --language='ru_RU' + +#read templates from the other path +php yii faker/generate all_fixtures --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 faker/generate all_fixtures --fixturesPath='@tests/unit/fixtures/subfolder1/subfolder2/subfolder3' +``` + +You also can create your own data providers for custom tables fields, see [Faker]((https://github.com/fzaninotto/Faker)) library guide for more info; +After you created custom provider, for example: + +```php +class Book extends \Faker\Provider\Base +{ + public function title($nbWords = 5) + { + $sentence = $this->generator->sentence($nbWords); + return mb_substr($sentence, 0, mb_strlen($sentence) - 1); + } + + public function ISBN() + { + return $this->generator->randomNumber(13); + } + + } +``` + +You can use it by adding it to the $providers property of the current command. In your console.php config: + +```php +'controllerMap' => [ + 'faker:fixture' => [ + 'class' => 'yii\faker\FixtureController', + 'providers' => [ + 'app\tests\unit\faker\providers\Book', + ], + ], +] +``` \ No newline at end of file diff --git a/extensions/yii/faker/composer.json b/extensions/yii/faker/composer.json new file mode 100644 index 0000000..526aa1d --- /dev/null +++ b/extensions/yii/faker/composer.json @@ -0,0 +1,27 @@ +{ + "name": "yiisoft/yii2-faker", + "description": "Fixture generator. The Faker integration for the Yii framework.", + "keywords": ["yii", "faker", "fixture"], + "type": "yii2-extension", + "license": "BSD-3-Clause", + "support": { + "forum": "http://www.yiiframework.com/forum/", + "wiki": "http://www.yiiframework.com/wiki/", + "irc": "irc://irc.freenode.net/yii", + "source": "https://github.com/yiisoft/yii2" + }, + "authors": [ + { + "name": "Mark Jebri", + "email": "mark.github@yandex.ru" + } + ], + "require": { + "yiisoft/yii2": "*", + "fzaninotto/faker": "*" + }, + "autoload": { + "psr-0": { "yii\\faker\\": "" } + }, + "target-dir": "yii/faker" +} \ No newline at end of file diff --git a/framework/yii/console/controllers/FixtureController.php b/framework/yii/console/controllers/FixtureController.php new file mode 100644 index 0000000..9ef8aa8 --- /dev/null +++ b/framework/yii/console/controllers/FixtureController.php @@ -0,0 +1,150 @@ + [ + * 'class' => 'yii\db\Connection', + * 'dsn' => 'mysql:host=localhost;dbname={your_database}', + * 'username' => '{your_db_user}', + * 'password' => '', + * 'charset' => 'utf8', + * ], + * 'fixture' => [ + * 'class' => 'yii\test\DbFixtureManager', + * ], + * ~~~ + * + * ~~~ + * #load fixtures under $fixturesPath to the "users" table + * php yii fixture/apply users + * + * #also a short version of this command (generate action is default) + * php yii fixture users + * + * #load fixtures under $fixturesPath to the "users" table to the different connection + * php yii fixture/apply users --db='someOtherDbConneciton' + * + * #load fixtures under different $fixturesPath to the "users" table. + * php yii fixture/apply users --fixturesPath='@app/some/other/path/to/fixtures' + * ~~~ + * + * @author Mark Jebri + * @since 2.0 + */ +class FixtureController extends Controller +{ + + use \yii\test\DbTestTrait; + + /** + * @var string controller default action ID. + */ + public $defaultAction = 'apply'; + + /** + * Alias to the path, where all fixtures are stored. + * @var string + */ + public $fixturesPath = '@tests/unit/fixtures'; + + /** + * Id of the database connection component of the application. + * @var string + */ + public $db = 'db'; + + /** + * Returns the names of the global options for this command. + * @return array the names of the global options for this command. + */ + public function globalOptions() + { + return array_merge(parent::globalOptions(), [ + 'db','fixturesPath' + ]); + } + + /** + * This method is invoked right before an action is to be executed (after all possible filters.) + * It checks that fixtures path and database connection are available. + * @param type $action + * @return boolean + */ + public function beforeAction($action) + { + if (parent::beforeAction($action)) { + $this->checkRequirements(); + return true; + } else { + return false; + } + } + + /** + * Apply given fixture to the table. Fixture name can be the same as the table name or + * you can specify table name as a second parameter. + * @param string $fixture + */ + public function actionApply($fixture) + { + $this->fixtureManager->basePath = $this->fixturesPath; + $this->fixtureManager->db = $this->db; + $this->loadFixtures([$fixture]); + } + + /** + * Truncate given table and clear all fixtures from it. + * @param string $table + */ + public function actionClear($table) + { + $this->dbConnection->createCommand()->truncateTable($table)->execute(); + echo "Table \"{$table}\" was successfully cleared. \n"; + } + + /** + * Checks if the database and fixtures path are available. + * @throws Exception + */ + public function checkRequirements() + { + $path = Yii::getAlias($this->fixturesPath, false); + + if (!is_dir($path) || !is_writable($path)) { + throw new Exception("The fixtures path \"{$this->fixturesPath}\" not exist or is not writable"); + } + + } + + /** + * Returns database connection component + * @return yii\db\Connection|null + */ + public function getDbConnection() + { + $db = Yii::$app->getComponent($this->db); + + if ($db == null) { + throw new Exception("There is no database connection component with id \"{$this->db}\"."); + } + + return $db; + } + +}