FixtureController.php 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\console\controllers;
  8. use Yii;
  9. use yii\console\Controller;
  10. use yii\console\Exception;
  11. use yii\helpers\FileHelper;
  12. use yii\helpers\Console;
  13. /**
  14. * This command manages fixtures load to the database tables.
  15. * You can specify different options of this command to point fixture manager
  16. * to the specific tables of the different database connections.
  17. *
  18. * To use this command simply configure your console.php config like this:
  19. *
  20. * ~~~
  21. * 'db' => [
  22. * 'class' => 'yii\db\Connection',
  23. * 'dsn' => 'mysql:host=localhost;dbname={your_database}',
  24. * 'username' => '{your_db_user}',
  25. * 'password' => '',
  26. * 'charset' => 'utf8',
  27. * ],
  28. * 'fixture' => [
  29. * 'class' => 'yii\test\DbFixtureManager',
  30. * ],
  31. * ~~~
  32. *
  33. * ~~~
  34. * #load fixtures under $fixturePath to the "users" table
  35. * yii fixture/apply users
  36. *
  37. * #also a short version of this command (generate action is default)
  38. * yii fixture users
  39. *
  40. * #load fixtures under $fixturePath to the "users" table to the different connection
  41. * yii fixture/apply users --db=someOtherDbConneciton
  42. *
  43. * #load fixtures under different $fixturePath to the "users" table.
  44. * yii fixture/apply users --fixturePath=@app/some/other/path/to/fixtures
  45. * ~~~
  46. *
  47. * @author Mark Jebri <[email protected]>
  48. * @since 2.0
  49. */
  50. class FixtureController extends Controller
  51. {
  52. use DbTestTrait;
  53. /**
  54. * type of fixture apply to database
  55. */
  56. const APPLY_ALL = 'all';
  57. /**
  58. * @var string controller default action ID.
  59. */
  60. public $defaultAction = 'apply';
  61. /**
  62. * Alias to the path, where all fixtures are stored.
  63. * @var string
  64. */
  65. public $fixturePath = '@tests/unit/fixtures';
  66. /**
  67. * Id of the database connection component of the application.
  68. * @var string
  69. */
  70. public $db = 'db';
  71. /**
  72. * Returns the names of the global options for this command.
  73. * @return array the names of the global options for this command.
  74. */
  75. public function globalOptions()
  76. {
  77. return array_merge(parent::globalOptions(), [
  78. 'db', 'fixturePath'
  79. ]);
  80. }
  81. /**
  82. * This method is invoked right before an action is to be executed (after all possible filters.)
  83. * It checks that fixtures path and database connection are available.
  84. * @param \yii\base\Action $action
  85. * @return boolean
  86. */
  87. public function beforeAction($action)
  88. {
  89. if (parent::beforeAction($action)) {
  90. $this->checkRequirements();
  91. return true;
  92. } else {
  93. return false;
  94. }
  95. }
  96. /**
  97. * Apply given fixture to the table. You can load several fixtures specifying
  98. * their names separated with commas, like: tbl_user,tbl_profile. Be sure there is no
  99. * whitespace between tables names.
  100. * @param array $fixtures
  101. * @throws \yii\console\Exception
  102. */
  103. public function actionApply(array $fixtures, array $except = [])
  104. {
  105. if ($this->getFixtureManager() === null) {
  106. throw new Exception('Fixture manager is not configured properly. Please refer to official documentation for this purposes.');
  107. }
  108. $foundFixtures = $this->findFixtures($fixtures);
  109. if (!$this->needToApplyAll($fixtures[0])) {
  110. $notFoundFixtures = array_diff($fixtures, $foundFixtures);
  111. if ($notFoundFixtures) {
  112. $this->notifyNotFound($notFoundFixtures);
  113. }
  114. }
  115. if (!$foundFixtures) {
  116. throw new Exception("No files were found by name: \"" . implode(', ', $fixtures) . "\".\n"
  117. . "Check that fixtures with these name exists, under fixtures path: \n\"" . Yii::getAlias($this->fixturePath) . "\"."
  118. );
  119. }
  120. if (!$this->confirmApply($foundFixtures, $except)) {
  121. return;
  122. }
  123. $fixtures = array_diff($foundFixtures, $except);
  124. $this->getFixtureManager()->basePath = $this->fixturePath;
  125. $this->getFixtureManager()->db = $this->db;
  126. $transaction = Yii::$app->db->beginTransaction();
  127. try {
  128. $this->loadFixtures($foundFixtures);
  129. $transaction->commit();
  130. } catch (\Exception $e) {
  131. $transaction->rollback();
  132. $this->stdout("Exception occured, transaction rollback. Tables will be in same state.\n", Console::BG_RED);
  133. throw $e;
  134. }
  135. $this->notifySuccess($foundFixtures);
  136. }
  137. /**
  138. * Truncate given table and clear all fixtures from it. You can clear several tables specifying
  139. * their names separated with commas, like: tbl_user,tbl_profile. Be sure there is no
  140. * whitespace between tables names.
  141. * @param array|string $tables
  142. */
  143. public function actionClear(array $tables, array $except = ['tbl_migration'])
  144. {
  145. if ($this->needToApplyAll($tables[0])) {
  146. $tables = $this->getDbConnection()->schema->getTableNames();
  147. }
  148. if (!$this->confirmClear($tables, $except)) {
  149. return;
  150. }
  151. $tables = array_diff($tables, $except);
  152. $transaction = Yii::$app->db->beginTransaction();
  153. try {
  154. $this->getDbConnection()->createCommand()->checkIntegrity(false)->execute();
  155. foreach($tables as $table) {
  156. $this->getDbConnection()->createCommand()->delete($table)->execute();
  157. $this->getDbConnection()->createCommand()->resetSequence($table)->execute();
  158. $this->stdout(" Table \"{$table}\" was successfully cleared. \n", Console::FG_GREEN);
  159. }
  160. $this->getDbConnection()->createCommand()->checkIntegrity(true)->execute();
  161. $transaction->commit();
  162. } catch (\Exception $e) {
  163. $transaction->rollback();
  164. $this->stdout("Exception occured, transaction rollback. Tables will be in same state.\n", Console::BG_RED);
  165. throw $e;
  166. }
  167. }
  168. /**
  169. * Checks if the database and fixtures path are available.
  170. * @throws Exception
  171. */
  172. public function checkRequirements()
  173. {
  174. $path = Yii::getAlias($this->fixturePath, false);
  175. if (!is_dir($path) || !is_writable($path)) {
  176. throw new Exception("The fixtures path \"{$this->fixturePath}\" not exist or is not writable.");
  177. }
  178. }
  179. /**
  180. * Returns database connection component
  181. * @return \yii\db\Connection
  182. * @throws Exception if [[db]] is invalid.
  183. */
  184. public function getDbConnection()
  185. {
  186. $db = Yii::$app->getComponent($this->db);
  187. if ($db === null) {
  188. throw new Exception("There is no database connection component with id \"{$this->db}\".");
  189. }
  190. return $db;
  191. }
  192. /**
  193. * Notifies user that fixtures were successfully loaded.
  194. * @param array $fixtures
  195. */
  196. private function notifySuccess($fixtures)
  197. {
  198. $this->stdout("Fixtures were successfully loaded from path:\n", Console::FG_YELLOW);
  199. $this->stdout(Yii::getAlias($this->fixturePath) . "\n\n", Console::FG_GREEN);
  200. $this->outputList($fixtures);
  201. }
  202. /**
  203. * Notifies user that fixtures were not found under fixtures path.
  204. * @param array $fixtures
  205. */
  206. private function notifyNotFound($fixtures)
  207. {
  208. $this->stdout("Some fixtures were not found under path:\n", Console::BG_RED);
  209. $this->stdout(Yii::getAlias($this->fixturePath) . "\n\n", Console::FG_GREEN);
  210. $this->outputList($fixtures);
  211. $this->stdout("\n");
  212. }
  213. /**
  214. * Prompts user with confirmation if fixtures should be loaded.
  215. * @param array $fixtures
  216. * @param array $except
  217. * @return boolean
  218. */
  219. private function confirmApply($fixtures, $except)
  220. {
  221. $this->stdout("Fixtures will be loaded from path: \n", Console::FG_YELLOW);
  222. $this->stdout(Yii::getAlias($this->fixturePath) . "\n\n", Console::FG_GREEN);
  223. $this->outputList($fixtures);
  224. if (count($except)) {
  225. $this->stdout("\nFixtures that will NOT be loaded: \n\n", Console::FG_YELLOW);
  226. $this->outputList($except);
  227. }
  228. return $this->confirm("\nLoad to database above fixtures?");
  229. }
  230. /**
  231. * Prompts user with confirmation for tables that should be cleared.
  232. * @param array $tables
  233. * @param array $except
  234. * @return boolean
  235. */
  236. private function confirmClear($tables, $except)
  237. {
  238. $this->stdout("Tables below will be cleared:\n\n", Console::FG_YELLOW);
  239. $this->outputList($tables);
  240. if (count($except)) {
  241. $this->stdout("\nTables that will NOT be cleared:\n\n", Console::FG_YELLOW);
  242. $this->outputList($except);
  243. }
  244. return $this->confirm("\nClear tables?");
  245. }
  246. /**
  247. * Outputs data to the console as a list.
  248. * @param array $data
  249. */
  250. private function outputList($data)
  251. {
  252. foreach($data as $index => $item) {
  253. $this->stdout(" " . ($index + 1) . ". {$item}\n", Console::FG_GREEN);
  254. }
  255. }
  256. /**
  257. * Checks if needed to apply all fixtures.
  258. * @param string $fixture
  259. * @return bool
  260. */
  261. public function needToApplyAll($fixture)
  262. {
  263. return $fixture == self::APPLY_ALL;
  264. }
  265. /**
  266. * @param array $fixtures
  267. * @return array Array of found fixtures. These may differ from input parameter as not all fixtures may exists.
  268. */
  269. private function findFixtures(array $fixtures)
  270. {
  271. $fixturesPath = Yii::getAlias($this->fixturePath);
  272. $filesToSearch = ['.php'];
  273. if (!$this->needToApplyAll($fixtures[0])) {
  274. $filesToSearch = [];
  275. foreach ($fixtures as $fileName) {
  276. $filesToSearch[] = $fileName . '.php';
  277. }
  278. }
  279. $files = FileHelper::findFiles($fixturesPath, ['only' => $filesToSearch]);
  280. $foundFixtures = [];
  281. foreach ($files as $fixture) {
  282. $foundFixtures[] = basename($fixture , '.php');
  283. }
  284. return $foundFixtures;
  285. }
  286. }