FixtureController.php 10.1 KB
Newer Older
Mark committed
1 2 3 4 5 6 7 8 9 10 11
<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace yii\console\controllers;

use Yii;
use yii\console\Controller;
12
use yii\console\Exception;
Mark committed
13
use yii\helpers\Console;
Qiang Xue committed
14 15
use yii\helpers\FileHelper;
use yii\test\FixtureTrait;
Mark committed
16 17

/**
18
 * Manages loading and unloading fixtures.
Qiang Xue committed
19
 *
Mark committed
20
 * ~~~
Mark committed
21
 * #load fixtures from UsersFixture class with default namespace "tests\unit\fixtures"
Mark committed
22
 * yii fixture/load User
Qiang Xue committed
23
 *
Mark committed
24
 * #also a short version of this command (generate action is default)
Mark committed
25
 * yii fixture User
Qiang Xue committed
26
 *
Mark committed
27
 * #load fixtures with different namespace.
Mark committed
28
 * yii fixture/load User --namespace=alias\my\custom\namespace\goes\here
Mark committed
29
 * ~~~
Qiang Xue committed
30
 *
Mark committed
31 32 33 34 35
 * @author Mark Jebri <mark.github@yandex.ru>
 * @since 2.0
 */
class FixtureController extends Controller
{
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
    use FixtureTrait;

    /**
     * type of fixture apply to database
     */
    const APPLY_ALL = 'all';

    /**
     * @var string controller default action ID.
     */
    public $defaultAction = 'load';
    /**
     * @var string default namespace to search fixtures in
     */
    public $namespace = 'tests\unit\fixtures';
    /**
     * @var array global fixtures that should be applied when loading and unloading. By default it is set to `InitDbFixture`
     * that disables and enables integrity check, so your data can be safely loaded.
     */
    public $globalFixtures = [
        'yii\test\InitDb',
    ];

59

60
    /**
61
     * @inheritdoc
62
     */
63
    public function options($actionId)
64
    {
65
        return array_merge(parent::options($actionId), [
66 67 68 69 70 71 72 73 74
            'namespace', 'globalFixtures'
        ]);
    }

    /**
     * Loads given fixture. You can load several fixtures specifying
     * their names separated with commas, like: User,UserProfile,MyCustom. Be sure there is no
     * whitespace between names. Note that if you are loading fixtures to storage, for example: database or nosql,
     * storage will not be cleared, data will be appended to already existed.
75 76
     * @param array $fixtures
     * @param array $except
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
     * @throws \yii\console\Exception
     */
    public function actionLoad(array $fixtures, array $except = [])
    {
        $foundFixtures = $this->findFixtures($fixtures);

        if (!$this->needToApplyAll($fixtures[0])) {
            $notFoundFixtures = array_diff($fixtures, $foundFixtures);

            if ($notFoundFixtures) {
                $this->notifyNotFound($notFoundFixtures);
            }
        }

        if (!$foundFixtures) {
92 93 94
            throw new Exception(
                "No files were found by name: \"" . implode(', ', $fixtures) . "\".\n" .
                "Check that files with these name exists, under fixtures path: \n\"" . $this->getFixturePath() . "\"."
95 96 97 98
            );
        }

        if (!$this->confirmLoad($foundFixtures, $except)) {
99
            return self::EXIT_CODE_NORMAL;
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
        }

        $filtered = array_diff($foundFixtures, $except);
        $fixtures = $this->getFixturesConfig(array_merge($this->globalFixtures, $filtered));

        if (!$fixtures) {
            throw new Exception('No fixtures were found in namespace: "' . $this->namespace . '"' . '');
        }

        $fixturesObjects = $this->createFixtures($fixtures);
        $this->unloadFixtures($fixturesObjects);
        $this->loadFixtures($fixturesObjects);
        $this->notifyLoaded($fixtures);
    }

    /**
     * Unloads given fixtures. You can clear environment and unload multiple fixtures by specifying
     * their names separated with commas, like: User,UserProfile,MyCustom. Be sure there is no
     * whitespace between names.
     * @param array|string $fixtures
     * @param array|string $except
Carsten Brandt committed
121
     * @throws \yii\console\Exception in case no fixtures are found.
122 123 124 125 126 127 128 129 130 131 132 133 134 135
     */
    public function actionUnload(array $fixtures, array $except = [])
    {
        $foundFixtures = $this->findFixtures($fixtures);

        if (!$this->needToApplyAll($fixtures[0])) {
            $notFoundFixtures = array_diff($fixtures, $foundFixtures);

            if ($notFoundFixtures) {
                $this->notifyNotFound($notFoundFixtures);
            }
        }

        if (!$foundFixtures) {
136 137 138
            throw new Exception(
                "No files were found by name: \"" . implode(', ', $fixtures) . "\".\n" .
                "Check that fixtures with these name exists, under fixtures path: \n\"" . $this->getFixturePath() . "\"."
139 140 141 142
            );
        }

        if (!$this->confirmUnload($foundFixtures, $except)) {
143
            return self::EXIT_CODE_NORMAL;
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
        }

        $filtered = array_diff($foundFixtures, $except);
        $fixtures = $this->getFixturesConfig(array_merge($this->globalFixtures, $filtered));

        if (!$fixtures) {
            throw new Exception('No fixtures were found in namespace: ' . $this->namespace . '".');
        }

        $this->unloadFixtures($this->createFixtures($fixtures));
        $this->notifyUnloaded($fixtures);
    }

    /**
     * Notifies user that fixtures were successfully loaded.
     * @param array $fixtures
     */
    private function notifyLoaded($fixtures)
    {
        $this->stdout("Fixtures were successfully loaded from namespace:\n", Console::FG_YELLOW);
        $this->stdout("\t\"" . Yii::getAlias($this->namespace) . "\"\n\n", Console::FG_GREEN);
        $this->outputList($fixtures);
    }

    /**
     * Notifies user that fixtures were successfully unloaded.
     * @param array $fixtures
     */
    private function notifyUnloaded($fixtures)
    {
        $this->stdout("Fixtures were successfully unloaded from namespace:\n", Console::FG_YELLOW);
        $this->stdout("\t\"" . Yii::getAlias($this->namespace) . "\"\n\n", Console::FG_GREEN);
        $this->outputList($fixtures);
    }

    /**
     * Notifies user that fixtures were not found under fixtures path.
     * @param array $fixtures
     */
    private function notifyNotFound($fixtures)
    {
        $this->stdout("Some fixtures were not found under path:\n", Console::BG_RED);
        $this->stdout("\t" . $this->getFixturePath() . "\n\n", Console::FG_GREEN);
        $this->stdout("Check that they have correct namespace \"{$this->namespace}\" \n", Console::BG_RED);
        $this->outputList($fixtures);
        $this->stdout("\n");
    }

    /**
     * Prompts user with confirmation if fixtures should be loaded.
194 195
     * @param array $fixtures
     * @param array $except
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
     * @return boolean
     */
    private function confirmLoad($fixtures, $except)
    {
        $this->stdout("Fixtures namespace is: \n", Console::FG_YELLOW);
        $this->stdout("\t" . $this->namespace . "\n\n", Console::FG_GREEN);

        if (count($this->globalFixtures)) {
            $this->stdout("Global fixtures will be loaded:\n\n", Console::FG_YELLOW);
            $this->outputList($this->globalFixtures);
        }

        $this->stdout("\nFixtures below will be loaded:\n\n", Console::FG_YELLOW);
        $this->outputList($fixtures);

        if (count($except)) {
            $this->stdout("\nFixtures that will NOT be loaded: \n\n", Console::FG_YELLOW);
            $this->outputList($except);
        }

        return $this->confirm("\nLoad above fixtures?");
    }

    /**
     * Prompts user with confirmation for fixtures that should be unloaded.
221 222
     * @param array $fixtures
     * @param array $except
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
     * @return boolean
     */
    private function confirmUnload($fixtures, $except)
    {
        $this->stdout("Fixtures namespace is: \n", Console::FG_YELLOW);
        $this->stdout("\t" . $this->namespace . "\n\n", Console::FG_GREEN);

        if (count($this->globalFixtures)) {
            $this->stdout("Global fixtures will be unloaded:\n\n", Console::FG_YELLOW);
            $this->outputList($this->globalFixtures);
        }

        $this->stdout("\nFixtures below will be unloaded:\n\n", Console::FG_YELLOW);
        $this->outputList($fixtures);

        if (count($except)) {
            $this->stdout("\nFixtures that will NOT be unloaded:\n\n", Console::FG_YELLOW);
            $this->outputList($except);
        }

        return $this->confirm("\nUnload fixtures?");
    }

    /**
     * Outputs data to the console as a list.
     * @param array $data
     */
    private function outputList($data)
    {
        foreach ($data as $index => $item) {
            $this->stdout("\t" . ($index + 1) . ". {$item}\n", Console::FG_GREEN);
        }
    }

    /**
     * Checks if needed to apply all fixtures.
259
     * @param string $fixture
260 261 262 263 264 265 266 267
     * @return bool
     */
    public function needToApplyAll($fixture)
    {
        return $fixture == self::APPLY_ALL;
    }

    /**
268
     * @param array $fixtures
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
     * @return array Array of found fixtures. These may differ from input parameter as not all fixtures may exists.
     */
    private function findFixtures(array $fixtures)
    {
        $fixturesPath = $this->getFixturePath();

        $filesToSearch = ['*Fixture.php'];
        if (!$this->needToApplyAll($fixtures[0])) {
            $filesToSearch = [];
            foreach ($fixtures as $fileName) {
                $filesToSearch[] = $fileName . 'Fixture.php';
            }
        }

        $files = FileHelper::findFiles($fixturesPath, ['only' => $filesToSearch]);
        $foundFixtures = [];

        foreach ($files as $fixture) {
            $foundFixtures[] = basename($fixture, 'Fixture.php');
        }

        return $foundFixtures;
    }

    /**
     * Returns valid fixtures config that can be used to load them.
295
     * @param array $fixtures fixtures to configure
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
     * @return array
     */
    private function getFixturesConfig($fixtures)
    {
        $config = [];

        foreach ($fixtures as $fixture) {

            $isNamespaced = (strpos($fixture, '\\') !== false);
            $fullClassName = $isNamespaced ? $fixture . 'Fixture' : $this->namespace . '\\' . $fixture . 'Fixture';

            if (class_exists($fullClassName)) {
                $config[] = $fullClassName;
            }
        }

        return $config;
    }

    /**
     * Returns fixture path that determined on fixtures namespace.
     * @return string fixture path
     */
    private function getFixturePath()
    {
        return Yii::getAlias('@' . str_replace('\\', '/', $this->namespace));
    }
Mark committed
323
}