FixtureController.php 8.44 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\FileHelper;
Qiang Xue committed
14
use yii\test\DbTestTrait;
Mark committed
15
use yii\helpers\Console;
Mark committed
16 17 18 19 20

/**
 * This command manages fixtures load to the database tables.
 * You can specify different options of this command to point fixture manager
 * to the specific tables of the different database connections.
Qiang Xue committed
21
 *
Mark committed
22
 * To use this command simply configure your console.php config like this:
Qiang Xue committed
23
 *
Mark committed
24
 * ~~~
Qiang Xue committed
25 26 27 28 29 30 31 32 33 34
 * 'db' => [
 *     'class' => 'yii\db\Connection',
 *     'dsn' => 'mysql:host=localhost;dbname={your_database}',
 *     'username' => '{your_db_user}',
 *     'password' => '',
 *     'charset' => 'utf8',
 * ],
 * 'fixture' => [
 *     'class' => 'yii\test\DbFixtureManager',
 * ],
Mark committed
35
 * ~~~
Qiang Xue committed
36
 *
Mark committed
37
 * ~~~
Qiang Xue committed
38 39 40
 * #load fixtures under $fixturePath to the "users" table
 * yii fixture/apply users
 *
Mark committed
41
 * #also a short version of this command (generate action is default)
Qiang Xue committed
42 43 44
 * yii fixture users
 *
 * #load fixtures under $fixturePath to the "users" table to the different connection
Qiang Xue committed
45
 * yii fixture/apply users --db=someOtherDbConneciton
Qiang Xue committed
46 47
 *
 * #load fixtures under different $fixturePath to the "users" table.
Qiang Xue committed
48
 * yii fixture/apply users --fixturePath=@app/some/other/path/to/fixtures
Mark committed
49
 * ~~~
Qiang Xue committed
50
 *
Mark committed
51 52 53 54 55
 * @author Mark Jebri <mark.github@yandex.ru>
 * @since 2.0
 */
class FixtureController extends Controller
{
Qiang Xue committed
56
	use DbTestTrait;
Mark committed
57 58 59 60 61
	
	/**
	 * type of fixture apply to database
	 */
	const APPLY_ALL = 'all';
Mark committed
62 63 64 65 66 67 68 69 70

	/**
	 * @var string controller default action ID.
	 */
	public $defaultAction = 'apply';
	/**
	 * Alias to the path, where all fixtures are stored.
	 * @var string
	 */
Qiang Xue committed
71
	public $fixturePath = '@tests/unit/fixtures';
Mark committed
72 73
	/**
	 * Id of the database connection component of the application.
Qiang Xue committed
74
	 * @var string
Mark committed
75 76 77
	 */
	public $db = 'db';

Carsten Brandt committed
78

Mark committed
79 80 81 82 83 84 85
	/**
	 * 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(), [
Qiang Xue committed
86
			'db', 'fixturePath'
Mark committed
87 88 89 90 91 92
		]);
	}

	/**
	 * This method is invoked right before an action is to be executed (after all possible filters.)
	 * It checks that fixtures path and database connection are available.
Qiang Xue committed
93
	 * @param \yii\base\Action $action
Mark committed
94 95 96 97 98 99 100 101 102 103 104 105 106
	 * @return boolean
	 */
	public function beforeAction($action)
	{
		if (parent::beforeAction($action)) {
			$this->checkRequirements();
			return true;
		} else {
			return false;
		}
	}

	/**
107 108 109
	 * Apply given fixture to the table. You can load several fixtures specifying
	 * their names separated with commas, like: tbl_user,tbl_profile. Be sure there is no
	 * whitespace between tables names.
Carsten Brandt committed
110 111
	 * @param array $fixtures
	 * @throws \yii\console\Exception
Mark committed
112
	 */
Mark committed
113
	public function actionApply(array $fixtures)
Mark committed
114
	{
115 116
		if ($this->getFixtureManager() === null) {
			throw new Exception('Fixture manager is not configured properly. Please refer to official documentation for this purposes.');
117 118
		}

Mark committed
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
		$foundFixtures = $this->findFixtures($fixtures);

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

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

		if (!$foundFixtures) {
			throw new Exception("No files were found by name: \"" . implode(', ', $fixtures) . "\".\n"
				. "Check that fixtures with these name exists, under fixtures path: \n\"" . Yii::getAlias($this->fixturePath) . "\"."
			);			
		}

		if (!$this->confirmApply($foundFixtures)) {
Mark committed
136 137 138
			return;
		}

Qiang Xue committed
139 140
		$this->getFixtureManager()->basePath = $this->fixturePath;
		$this->getFixtureManager()->db = $this->db;
Mark committed
141 142 143

		$transaction = Yii::$app->db->beginTransaction();

Mark committed
144
		try {
Mark committed
145 146 147
			$this->loadFixtures($foundFixtures);
			$transaction->commit();

Mark committed
148
		} catch (\Exception $e) {
Mark committed
149 150 151 152 153
			$transaction->rollback();
			$this->stdout("Exception occured, transaction rollback. Tables will be in same state.\n", Console::BG_RED);
			throw $e;
		}
		$this->notifySuccess($foundFixtures);
Mark committed
154 155 156
	}

	/**
157 158 159
	 * Truncate given table and clear all fixtures from it. You can clear several tables specifying
	 * their names separated with commas, like: tbl_user,tbl_profile. Be sure there is no
	 * whitespace between tables names.
Carsten Brandt committed
160
	 * @param array|string $tables
Mark committed
161
	 */
Mark committed
162
	public function actionClear(array $tables)
Mark committed
163 164 165 166 167
	{		
		if ($this->needToApplyAll($tables[0])) {
			$tables = $this->getDbConnection()->schema->getTableNames();
		}

Mark committed
168 169 170 171
		if (!$this->confirmClear($tables)) {
			return;
		}

Mark committed
172 173
		$transaction = Yii::$app->db->beginTransaction();

Mark committed
174
		try {
Mark committed
175 176 177 178 179 180 181 182 183 184 185
			$this->getDbConnection()->createCommand()->checkIntegrity(false)->execute();

			foreach($tables as $table) {
				$this->getDbConnection()->createCommand()->truncateTable($table)->execute();
				$this->getDbConnection()->createCommand()->resetSequence($table)->execute();
				$this->stdout("    Table \"{$table}\" was successfully cleared. \n", Console::FG_GREEN);
			}

			$this->getDbConnection()->createCommand()->checkIntegrity(true)->execute();
			$transaction->commit();

Mark committed
186
		} catch (\Exception $e) {
Mark committed
187 188 189
			$transaction->rollback();
			$this->stdout("Exception occured, transaction rollback. Tables will be in same state.\n", Console::BG_RED);
			throw $e;
Mark committed
190
		}
Mark committed
191 192 193 194
	}

	/**
	 * Checks if the database and fixtures path are available.
195
	 * @throws Exception
Mark committed
196 197 198
	 */
	public function checkRequirements()
	{
Qiang Xue committed
199
		$path = Yii::getAlias($this->fixturePath, false);
Mark committed
200 201

		if (!is_dir($path) || !is_writable($path)) {
202
			throw new Exception("The fixtures path \"{$this->fixturePath}\" not exist or is not writable.");
Mark committed
203 204 205 206 207 208
		}

	}

	/**
	 * Returns database connection component
Qiang Xue committed
209
	 * @return \yii\db\Connection
210
	 * @throws Exception if [[db]] is invalid.
Mark committed
211 212 213 214 215
	 */
	public function getDbConnection()
	{
		$db = Yii::$app->getComponent($this->db);

Carsten Brandt committed
216
		if ($db === null) {
217
			throw new Exception("There is no database connection component with id \"{$this->db}\".");
Mark committed
218 219 220 221 222
		}

		return $db;
	}

223 224 225 226 227 228
	/**
	 * Notifies user that fixtures were successfully loaded.
	 * @param array $fixtures
	 */
	private function notifySuccess($fixtures)
	{
Carsten Brandt committed
229 230
		$this->stdout("Fixtures were successfully loaded from path:\n", Console::FG_YELLOW);
		$this->stdout(Yii::getAlias($this->fixturePath) . "\n\n", Console::FG_GREEN);
Mark committed
231 232
		$this->outputList($fixtures);
	}
233

Mark committed
234 235 236 237 238 239 240 241 242 243 244 245
	/**
	 * 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(Yii::getAlias($this->fixturePath) . "\n\n", Console::FG_GREEN);
		$this->outputList($fixtures);
		$this->stdout("\n");
	}

Mark committed
246 247 248 249 250 251 252 253
	/**
	 * Prompts user with confirmation if fixtures should be loaded.
	 * @param array $fixtures
	 * @return boolean
	 */
	private function confirmApply($fixtures)
	{
		$this->stdout("Fixtures will be loaded from path: \n", Console::FG_YELLOW);
Carsten Brandt committed
254
		$this->stdout(Yii::getAlias($this->fixturePath) . "\n\n", Console::FG_GREEN);
Mark committed
255 256 257 258 259 260 261 262 263 264 265
		$this->outputList($fixtures);
		return $this->confirm('Load to database above fixtures?');
	}

	/**
	 * Prompts user with confirmation for tables that should be cleared.
	 * @param array $tables
	 * @return boolean
	 */
	private function confirmClear($tables)
	{
Carsten Brandt committed
266
		$this->stdout("Tables below will be cleared:\n\n", Console::FG_YELLOW);
Mark committed
267 268 269 270 271 272 273 274 275 276 277
		$this->outputList($tables);
		return $this->confirm('Clear tables?');
	}

	/**
	 * Outputs data to the console as a list.
	 * @param array $data
	 */
	private function outputList($data)
	{
		foreach($data as $index => $item) {
Mark committed
278
			$this->stdout("    " . ($index + 1) . ". {$item}\n", Console::FG_GREEN);
279 280
		}
	}
Mark committed
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 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

	/**
	 * Checks if needed to apply all fixtures.
	 * @param string $fixture
	 * @return bool
	 */
	public function needToApplyAll($fixture)
	{
		return $fixture == self::APPLY_ALL;
	}

	/**
	 * Returns array of found fixtures. These may differer from input parameter as not all fixtures may exists.
	 * @param array $fixtures
	 */
	private function findFixtures(array $fixtures)
	{
		$fixturesPath = Yii::getAlias($this->fixturePath);

		$files = [];

		if ($this->needToApplyAll($fixtures[0])) {
			$files = FileHelper::findFiles($fixturesPath, ['only' => ['.php']]);
		} else {
			$filesToSearch = [];
			foreach ($fixtures as $fileName) {
				$filesToSearch[] = $fileName . '.php';
			}
			$files = FileHelper::findFiles($fixturesPath, ['only' => $filesToSearch]);
		}
		
		$foundFixtures = [];

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

		return $foundFixtures;
	}

Mark committed
321
}