mirror of
				https://github.com/yiisoft/yii2.git
				synced 2025-11-04 06:37:55 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			327 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			327 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?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\Console;
 | 
						|
use yii\helpers\FileHelper;
 | 
						|
use yii\helpers\VarDumper;
 | 
						|
 | 
						|
/**
 | 
						|
 * This command manage fixtures creations based on given template.
 | 
						|
 *
 | 
						|
 * Fixtures are one of the important paths in unit testing. To speed up developers
 | 
						|
 * work these fixtures can be generated automatically, based on prepared template.
 | 
						|
 * 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 "fixture"):
 | 
						|
 *
 | 
						|
 * ~~~
 | 
						|
 * 'controllerMap' => [
 | 
						|
 *     'fixture' => [
 | 
						|
 *         '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/templates/fixtures)
 | 
						|
 * return [
 | 
						|
 *     'name' => $faker->firstName,
 | 
						|
 *     'phone' => $faker->phoneNumber,
 | 
						|
 *     'city' => $faker->city,
 | 
						|
 *     'password' => Yii::$app->getSecurity()->generatePasswordHash('password_' . $index),
 | 
						|
 *     'auth_key' => Yii::$app->getSecurity()->generateRandomString(),
 | 
						|
 *     'intro' => $faker->sentence(7, true),  // generate a sentence with 7 words
 | 
						|
 * ];
 | 
						|
 * ```
 | 
						|
 *
 | 
						|
 * If you use callback as a attribute value, then it will be called as shown with three parameters:
 | 
						|
 *
 | 
						|
 * - `$faker`: the Faker generator instance
 | 
						|
 * - `$index`: the current fixture index. For example if user need to generate 3 fixtures for user table, 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
 | 
						|
 *
 | 
						|
 * ~~~
 | 
						|
 * yii fixture/generate users
 | 
						|
 *
 | 
						|
 * //also a short version of this command (generate action is default)
 | 
						|
 * yii fixture users
 | 
						|
 *
 | 
						|
 * //to generate fixtures for several tables, use "," as a separator, for example:
 | 
						|
 * yii fixture users,profile
 | 
						|
 * ~~~
 | 
						|
 *
 | 
						|
 * In the code above "users" is template name, after this command run, new file named same as template
 | 
						|
 * will be created under the `$fixtureDataPath` folder.
 | 
						|
 * You can generate fixtures for all templates by specifying keyword "all"
 | 
						|
 *
 | 
						|
 * ~~~
 | 
						|
 * yii fixture/generate all
 | 
						|
 * ~~~
 | 
						|
 *
 | 
						|
 * This command will generate fixtures for all template files that are stored under $templatePath and
 | 
						|
 * 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).
 | 
						|
 *
 | 
						|
 * ~~~
 | 
						|
 * yii fixture/generate all 3
 | 
						|
 * ~~~
 | 
						|
 *
 | 
						|
 * You can specify different options of this command:
 | 
						|
 *
 | 
						|
 * ~~~
 | 
						|
 * //generate fixtures in russian language
 | 
						|
 * yii fixture/generate users 5 --language=ru_RU
 | 
						|
 *
 | 
						|
 * //read templates from the other path
 | 
						|
 * yii fixture/generate all --templatePath=@app/path/to/my/custom/templates
 | 
						|
 *
 | 
						|
 * //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);
 | 
						|
 * 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' => [
 | 
						|
 *        'fixture' => [
 | 
						|
 *            'class' => 'yii\faker\FixtureController',
 | 
						|
 *            'providers' => [
 | 
						|
 *                'app\tests\unit\faker\providers\Book',
 | 
						|
 *            ],
 | 
						|
 *        ],
 | 
						|
 *    ],
 | 
						|
 * ~~~
 | 
						|
 *
 | 
						|
 * @author Mark Jebri <mark.github@yandex.ru>
 | 
						|
 * @since 2.0.0
 | 
						|
 */
 | 
						|
class FixtureController extends \yii\console\controllers\FixtureController
 | 
						|
{
 | 
						|
    /**
 | 
						|
     * type of fixture generating
 | 
						|
     */
 | 
						|
    const GENERATE_ALL = 'all';
 | 
						|
 | 
						|
    /**
 | 
						|
     * @var string controller default action ID.
 | 
						|
     */
 | 
						|
    public $defaultAction = 'generate';
 | 
						|
    /**
 | 
						|
     * @var string Alias to the template path, where all tables templates are stored.
 | 
						|
     */
 | 
						|
    public $templatePath = '@tests/unit/templates/fixtures';
 | 
						|
    /**
 | 
						|
     * @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;
 | 
						|
    /**
 | 
						|
     * @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.
 | 
						|
     */
 | 
						|
    public $providers = [];
 | 
						|
 | 
						|
    /**
 | 
						|
     * @var \Faker\Generator Faker generator instance
 | 
						|
     */
 | 
						|
    private $_generator;
 | 
						|
 | 
						|
 | 
						|
    /**
 | 
						|
     * @inheritdoc
 | 
						|
     */
 | 
						|
    public function options($actionId)
 | 
						|
    {
 | 
						|
        return array_merge(parent::options($actionId), [
 | 
						|
            'templatePath', 'language', 'fixtureDataPath'
 | 
						|
        ]);
 | 
						|
    }
 | 
						|
 | 
						|
    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 array|string $file filename for the table template.
 | 
						|
     * You can generate all fixtures for all tables by specifying keyword "all" as filename.
 | 
						|
     * @param integer $times how much fixtures do you want per table
 | 
						|
     * @throws \yii\base\InvalidParamException
 | 
						|
     * @throws \yii\console\Exception
 | 
						|
     */
 | 
						|
    public function actionGenerate(array $file, $times = 2)
 | 
						|
    {
 | 
						|
        $templatePath = Yii::getAlias($this->templatePath);
 | 
						|
        $fixtureDataPath = Yii::getAlias($this->fixtureDataPath);
 | 
						|
 | 
						|
        if ($this->needToGenerateAll($file[0])) {
 | 
						|
            $files = FileHelper::findFiles($templatePath, ['only' => ['*.php']]);
 | 
						|
        } else {
 | 
						|
            $filesToSearch = [];
 | 
						|
            foreach ($file as $fileName) {
 | 
						|
                $filesToSearch[] = $fileName . '.php';
 | 
						|
            }
 | 
						|
            $files = FileHelper::findFiles($templatePath, ['only' => $filesToSearch]);
 | 
						|
        }
 | 
						|
 | 
						|
        if (empty($files)) {
 | 
						|
            throw new Exception("No files were found by name: \"" . implode(', ', $file) . "\".\n"
 | 
						|
                . "Check that template with these name exists, under template path: \n\"{$templatePath}\"."
 | 
						|
            );
 | 
						|
        }
 | 
						|
 | 
						|
        if (!$this->confirmGeneration($files)) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        foreach ($files as $templateFile) {
 | 
						|
            $fixtureFileName = basename($templateFile);
 | 
						|
            $fixtures = [];
 | 
						|
 | 
						|
            for ($i = 0; $i < $times; $i++) {
 | 
						|
                $fixtures[$i] = $this->generateFixture($templateFile, $i);
 | 
						|
            }
 | 
						|
 | 
						|
            $content = $this->exportFixtures($fixtures);
 | 
						|
            FileHelper::createDirectory($fixtureDataPath);
 | 
						|
            file_put_contents($fixtureDataPath . '/'. $fixtureFileName, $content);
 | 
						|
 | 
						|
            $this->stdout("Fixture file was generated under: $fixtureDataPath\n", Console::FG_GREEN);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Returns Faker generator instance. Getter for private property.
 | 
						|
     * @return \Faker\Generator
 | 
						|
     */
 | 
						|
    public function getGenerator()
 | 
						|
    {
 | 
						|
        if ($this->_generator === null) {
 | 
						|
            $language = $this->language === null ? Yii::$app->language : $this->language;
 | 
						|
            $this->_generator = \Faker\Factory::create(str_replace('-', '_', $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 exported to the string representation of given fixtures array.
 | 
						|
     * @param array $fixtures
 | 
						|
     * @return string exported fixtures format
 | 
						|
     */
 | 
						|
    public function exportFixtures($fixtures)
 | 
						|
    {
 | 
						|
        return "<?php\n\nreturn " . VarDumper::export($fixtures) . ";\n";
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Generates fixture from given template
 | 
						|
     * @param string $_template_ the fixture template file
 | 
						|
     * @param integer $index the current fixture index
 | 
						|
     * @return array fixture
 | 
						|
     */
 | 
						|
    public function generateFixture($_template_, $index)
 | 
						|
    {
 | 
						|
        // $faker and $index are exposed to the template file
 | 
						|
        $faker = $this->getGenerator();
 | 
						|
        return require($_template_);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Prompts user with message if he confirm generation with given fixture templates files.
 | 
						|
     * @param array $files
 | 
						|
     * @return boolean
 | 
						|
     */
 | 
						|
    public function confirmGeneration($files)
 | 
						|
    {
 | 
						|
        $this->stdout("Fixtures will be generated under the path: \n", Console::FG_YELLOW);
 | 
						|
        $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("\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);
 | 
						|
        }
 | 
						|
 | 
						|
        return $this->confirm('Generate above fixtures?');
 | 
						|
    }
 | 
						|
}
 |