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

169 170 171 172 173 174 175
    /**
     * @var \Faker\Generator Faker generator instance
     */
    private $_generator;


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

            for ($i = 0; $i < $times; $i++) {
                $fixtures[$i] = $this->generateFixture($template, $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 (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.
288
     * @param string $file
289 290 291 292 293 294 295 296 297
     * @return bool
     */
    public function needToGenerateAll($file)
    {
        return $file == self::GENERATE_ALL;
    }

    /**
     * Returns generator template for the given fixture name
298 299
     * @param string $file template file
     * @return array generator template
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
     * @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.
315
     * @param array $fixtures
316 317 318 319
     * @return string exported fixtures format
     */
    public function exportFixtures($fixtures)
    {
Qiang Xue committed
320
        return "<?php\n\nreturn " . VarDumper::export($fixtures) . ";\n";
321 322 323 324
    }

    /**
     * Generates fixture from given template
325 326 327
     * @param array $template fixture template
     * @param integer $index current fixture index
     * @return array fixture
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
     */
    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;
    }

    /**
     * Prompts user with message if he confirm generation with given fixture templates files.
346
     * @param array $files
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361
     * @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
362
}