Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Yii2 faker integration #1743

Merged
merged 13 commits into from Jan 3, 2014
2 changes: 2 additions & 0 deletions composer.json
Expand Up @@ -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",
Expand Down Expand Up @@ -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/",
Expand Down
12 changes: 12 additions & 0 deletions 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.
335 changes: 335 additions & 0 deletions extensions/yii/faker/FixtureController.php
@@ -0,0 +1,335 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/

namespace yii\faker;

use Yii;
use yii\console\Exception;
use yii\helpers\FileHelper;
use yii\helpers\Console;

/**
* This command manage fixtures creations based on given template.
*
* Fixtures are one of the important paths in unit testing. To speed up developers
* work this fixtures can be generated automatically, based on prepared template.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this -> these

* This command is a simple wrapper for the fixtures library [Faker](https://github.com/fzaninotto/Faker).
*
* You should configure this command as follows (you can use any alias, not only "faker:fixture"):
*
* ~~~
* '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:
*
* ~~~
* #users.php file under $templatePath
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use // for inline comments. # is never used in any other core code.

*
* 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to use - as bullets here since * looks weird in phpdoc.

* * $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';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't quite like all_fixtures. Perhaps just use all?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was thinking about the same all, sounds good, but is not it a little bit confusing? what all can user think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the command can be configured using fixture as the key, which will override the default fixture command.
Then all makes sense: yii fixture/generate all

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, will do changes. change docs also about fixture alias and not faker for the command?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. There's no need to have both fixture and faker commands in the same app. This only causes confusion.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we can do it. If you configure it via controllerMap, it will take precedence when resolving route.

Also, I suggest we rename FakerController to be FixtureController.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but if we do so, user will no be able to load his fixtures to db? what are you thinking about fixture:db and fixture:faker aliases? But since faker controller is extended from fixtures he also can load to db, true.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I suggest we rename FakerController to be FixtureController.

its already so, or what do you mean?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... since faker controller is extended from fixtures he also can load to db,

Yes, faker controller has all the features of fixture controller. I don't see problem replacing the default fixture controller with faker.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its already so, or what do you mean?

Forget about it. I didn't read carefully.


/**
* @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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Short array syntax should be used.


/**
* 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about using * to stand for generating fixtures for all tables?
The command should also prompt for confirmation of generating fixtures.

* @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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use { even for one liners now.

$files = FileHelper::findFiles($templatePath, ['only' => ['.php']]);
else
$files = FileHelper::findFiles($templatePath, ['only' => [$file.'.php']]);

foreach ($files as $templateFile)
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be on the same line as foreach.

$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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to use //


$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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be a separate method? If yes, should it be public?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, i think it should, public is enough here, because it could be overridden and almost all Yii2/Yii1 code use private/public.

}

/**
* 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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently it can't be called twice because of require w/o once. Can it be an issue?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

require calling each time, what exactly you are referring to? Tested it with tbl_user, tbl_profile templates, works fine with php yii faker all_fixtures and by single one.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK.


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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about simply export()?
getExportedFormat() doesn't sound right to me. It sounds like returning a format, but then "exported format" doesn't make much sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var_export exports in old 5.3 arrays format. or what export() command do you mean?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm talking about renaming the method. It could be exportFixtures().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok.

{
$content = "<?php\n\nreturn [";

foreach($fixtures as $fixture) {

$content .= "\n\t[";

foreach($fixture as $name=>$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;
}

}
32 changes: 32 additions & 0 deletions 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.