FixtureController.php 10.3 KB
Newer Older
Mark committed
1 2 3 4 5 6 7 8 9 10 11 12
<?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;
Qiang Xue committed
13
use yii\helpers\FileHelper;
Qiang Xue committed
14
use yii\helpers\VarDumper;
Mark committed
15 16 17 18 19

/**
 * This command manage fixtures creations based on given template.
 *
 * Fixtures are one of the important paths in unit testing. To speed up developers
Mark committed
20
 * work these fixtures can be generated automatically, based on prepared template.
Mark committed
21
 * This command is a simple wrapper for the fixtures library [Faker](https://github.com/fzaninotto/Faker).
Qiang Xue committed
22 23 24
 *
 * You should configure this command as follows (you can use any alias, not only "fixture"):
 *
Mark committed
25
 * ~~~
Qiang Xue committed
26 27 28 29 30
 * 'controllerMap' => [
 *     'fixture' => [
 *         'class' => 'yii\faker\FixtureController',
 *     ],
 * ],
Mark committed
31
 * ~~~
Qiang Xue committed
32
 *
Mark committed
33 34
 * 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:
Qiang Xue committed
35
 *
Qiang Xue committed
36 37
 * ```php
 * // users.php file under template path (by default @tests/unit/templates/fixtures)
Mark committed
38
 * return [
Qiang Xue committed
39 40 41 42 43 44
 *     '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
Mark committed
45
 * ];
Qiang Xue committed
46
 * ```
Qiang Xue committed
47
 *
Mark committed
48
 * If you use callback as a attribute value, then it will be called as shown with three parameters:
Qiang Xue committed
49
 *
Qiang Xue committed
50 51
 * - `$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.
Qiang Xue committed
52
 *
Mark committed
53
 * After you set all needed fields in callback, you need to return $fixture array back from the callback.
Qiang Xue committed
54
 *
Mark committed
55
 * After you prepared needed templates for tables you can simply generate your fixtures via command
Qiang Xue committed
56
 *
Mark committed
57
 * ~~~
Qiang Xue committed
58 59
 * yii fixture/generate users
 *
60
 * //also a short version of this command (generate action is default)
Qiang Xue committed
61
 * yii fixture users
Mark committed
62 63
 *
 * //to generate fixtures for several tables, use "," as a separator, for example:
Qiang Xue committed
64
 * yii fixture users,profile
Mark committed
65
 * ~~~
Qiang Xue committed
66
 *
Mark committed
67
 * In the code above "users" is template name, after this command run, new file named same as template
Mark committed
68
 * will be created under the `$fixtureDataPath` folder.
Qiang Xue committed
69 70
 * You can generate fixtures for all templates by specifying keyword "all"
 *
Mark committed
71
 * ~~~
Qiang Xue committed
72
 * yii fixture/generate all
Mark committed
73
 * ~~~
Qiang Xue committed
74 75
 *
 * This command will generate fixtures for all template files that are stored under $templatePath and
Mark committed
76
 * store fixtures under `$fixtureDataPath` with file names same as templates names.
Qiang Xue committed
77
 *
Mark committed
78 79
 * 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).
Qiang Xue committed
80
 *
Mark committed
81
 * ~~~
Qiang Xue committed
82
 * yii fixture/generate all 3
Mark committed
83
 * ~~~
Qiang Xue committed
84
 *
Mark committed
85
 * You can specify different options of this command:
Qiang Xue committed
86
 *
Mark committed
87
 * ~~~
Qiang Xue committed
88
 * //generate fixtures in russian language
Qiang Xue committed
89
 * yii fixture/generate users 5 --language=ru_RU
Qiang Xue committed
90
 *
91
 * //read templates from the other path
Qiang Xue committed
92
 * yii fixture/generate all --templatePath=@app/path/to/my/custom/templates
Qiang Xue committed
93
 *
Mark committed
94 95
 * //generate fixtures into other folders
 * yii fixture/generate all --fixtureDataPath=@tests/unit/fixtures/subfolder1/subfolder2/subfolder3
Mark committed
96
 * ~~~
Qiang Xue committed
97
 *
Mark committed
98 99
 * 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:
Qiang Xue committed
100
 *
Mark committed
101
 * ~~~
Qiang Xue committed
102 103 104 105 106 107 108
 * 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);
 *     }
Mark committed
109
 *
Qiang Xue committed
110 111 112 113 114
 *     public function ISBN()
 *     {
 *         return $this->generator->randomNumber(13);
 *     }
 * }
Mark committed
115
 * ~~~
Qiang Xue committed
116
 *
Mark committed
117
 * you can use it by adding it to the $providers property of the current command. In your console.php config:
Qiang Xue committed
118
 *
Mark committed
119
 * ~~~
Qiang Xue committed
120 121 122 123 124 125 126 127
 *    'controllerMap' => [
 *        'fixture' => [
 *            'class' => 'yii\faker\FixtureController',
 *            'providers' => [
 *                'app\tests\unit\faker\providers\Book',
 *            ],
 *        ],
 *    ],
Mark committed
128
 * ~~~
Qiang Xue committed
129 130
 *
 * @author Mark Jebri <mark.github@yandex.ru>
Mark committed
131 132 133 134
 * @since 2.0.0
 */
class FixtureController extends \yii\console\controllers\FixtureController
{
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
    /**
     * 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 = [];
161

162 163 164 165 166 167 168
    /**
     * @var \Faker\Generator Faker generator instance
     */
    private $_generator;


    /**
169
     * @inheritdoc
170
     */
171
    public function options($actionId)
172
    {
173
        return array_merge(parent::options($actionId), [
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
            '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.
192 193 194
     *
     * @param array|string $file filename for the table template.
     * You can generate all fixtures for all tables by specifying keyword "all" as filename.
195
     * @param integer $times how much fixtures do you want per table
196 197
     * @throws \yii\base\InvalidParamException
     * @throws \yii\console\Exception
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
     */
    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++) {
229
                $fixtures[$i] = $this->generateFixture($templateFile, $i);
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
            }

            $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()
    {
Qiang Xue committed
246 247 248
        if ($this->_generator === null) {
            $language = $this->language === null ? Yii::$app->language : $this->language;
            $this->_generator = \Faker\Factory::create(str_replace('-', '_', $language));
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
        }
        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.
277
     * @param string $file
278 279 280 281 282 283 284 285 286
     * @return bool
     */
    public function needToGenerateAll($file)
    {
        return $file == self::GENERATE_ALL;
    }

    /**
     * Returns exported to the string representation of given fixtures array.
287
     * @param array $fixtures
288 289 290 291
     * @return string exported fixtures format
     */
    public function exportFixtures($fixtures)
    {
Qiang Xue committed
292
        return "<?php\n\nreturn " . VarDumper::export($fixtures) . ";\n";
293 294 295 296
    }

    /**
     * Generates fixture from given template
297 298
     * @param string $_template_ the fixture template file
     * @param integer $index the current fixture index
299
     * @return array fixture
300
     */
301
    public function generateFixture($_template_, $index)
302
    {
303
        // $faker and $index are exposed to the template file
Qiang Xue committed
304
        $faker = $this->getGenerator();
305
        return require($_template_);
306 307 308 309
    }

    /**
     * Prompts user with message if he confirm generation with given fixture templates files.
310
     * @param array $files
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
     * @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?');
    }
Mark committed
326
}