Migrator.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. <?php
  2. namespace Illuminate\Database\Migrations;
  3. use Illuminate\Support\Arr;
  4. use Illuminate\Support\Str;
  5. use Illuminate\Support\Collection;
  6. use Illuminate\Console\OutputStyle;
  7. use Illuminate\Filesystem\Filesystem;
  8. use Illuminate\Contracts\Events\Dispatcher;
  9. use Illuminate\Database\Events\MigrationEnded;
  10. use Illuminate\Database\Events\MigrationsEnded;
  11. use Illuminate\Database\Events\MigrationStarted;
  12. use Illuminate\Database\Events\MigrationsStarted;
  13. use Illuminate\Database\ConnectionResolverInterface as Resolver;
  14. class Migrator
  15. {
  16. /**
  17. * The event dispatcher instance.
  18. *
  19. * @var \Illuminate\Contracts\Events\Dispatcher
  20. */
  21. protected $events;
  22. /**
  23. * The migration repository implementation.
  24. *
  25. * @var \Illuminate\Database\Migrations\MigrationRepositoryInterface
  26. */
  27. protected $repository;
  28. /**
  29. * The filesystem instance.
  30. *
  31. * @var \Illuminate\Filesystem\Filesystem
  32. */
  33. protected $files;
  34. /**
  35. * The connection resolver instance.
  36. *
  37. * @var \Illuminate\Database\ConnectionResolverInterface
  38. */
  39. protected $resolver;
  40. /**
  41. * The name of the default connection.
  42. *
  43. * @var string
  44. */
  45. protected $connection;
  46. /**
  47. * The paths to all of the migration files.
  48. *
  49. * @var array
  50. */
  51. protected $paths = [];
  52. /**
  53. * The output interface implementation.
  54. *
  55. * @var \Illuminate\Console\OutputStyle
  56. */
  57. protected $output;
  58. /**
  59. * Create a new migrator instance.
  60. *
  61. * @param \Illuminate\Database\Migrations\MigrationRepositoryInterface $repository
  62. * @param \Illuminate\Database\ConnectionResolverInterface $resolver
  63. * @param \Illuminate\Filesystem\Filesystem $files
  64. * @param \Illuminate\Contracts\Events\Dispatcher|null $dispatcher
  65. * @return void
  66. */
  67. public function __construct(MigrationRepositoryInterface $repository,
  68. Resolver $resolver,
  69. Filesystem $files,
  70. Dispatcher $dispatcher = null)
  71. {
  72. $this->files = $files;
  73. $this->events = $dispatcher;
  74. $this->resolver = $resolver;
  75. $this->repository = $repository;
  76. }
  77. /**
  78. * Run the pending migrations at a given path.
  79. *
  80. * @param array|string $paths
  81. * @param array $options
  82. * @return array
  83. */
  84. public function run($paths = [], array $options = [])
  85. {
  86. // Once we grab all of the migration files for the path, we will compare them
  87. // against the migrations that have already been run for this package then
  88. // run each of the outstanding migrations against a database connection.
  89. $files = $this->getMigrationFiles($paths);
  90. $this->requireFiles($migrations = $this->pendingMigrations(
  91. $files, $this->repository->getRan()
  92. ));
  93. // Once we have all these migrations that are outstanding we are ready to run
  94. // we will go ahead and run them "up". This will execute each migration as
  95. // an operation against a database. Then we'll return this list of them.
  96. $this->runPending($migrations, $options);
  97. return $migrations;
  98. }
  99. /**
  100. * Get the migration files that have not yet run.
  101. *
  102. * @param array $files
  103. * @param array $ran
  104. * @return array
  105. */
  106. protected function pendingMigrations($files, $ran)
  107. {
  108. return Collection::make($files)
  109. ->reject(function ($file) use ($ran) {
  110. return in_array($this->getMigrationName($file), $ran);
  111. })->values()->all();
  112. }
  113. /**
  114. * Run an array of migrations.
  115. *
  116. * @param array $migrations
  117. * @param array $options
  118. * @return void
  119. */
  120. public function runPending(array $migrations, array $options = [])
  121. {
  122. // First we will just make sure that there are any migrations to run. If there
  123. // aren't, we will just make a note of it to the developer so they're aware
  124. // that all of the migrations have been run against this database system.
  125. if (count($migrations) === 0) {
  126. $this->note('<info>Nothing to migrate.</info>');
  127. return;
  128. }
  129. // Next, we will get the next batch number for the migrations so we can insert
  130. // correct batch number in the database migrations repository when we store
  131. // each migration's execution. We will also extract a few of the options.
  132. $batch = $this->repository->getNextBatchNumber();
  133. $pretend = $options['pretend'] ?? false;
  134. $step = $options['step'] ?? false;
  135. $this->fireMigrationEvent(new MigrationsStarted);
  136. // Once we have the array of migrations, we will spin through them and run the
  137. // migrations "up" so the changes are made to the databases. We'll then log
  138. // that the migration was run so we don't repeat it next time we execute.
  139. foreach ($migrations as $file) {
  140. $this->runUp($file, $batch, $pretend);
  141. if ($step) {
  142. $batch++;
  143. }
  144. }
  145. $this->fireMigrationEvent(new MigrationsEnded);
  146. }
  147. /**
  148. * Run "up" a migration instance.
  149. *
  150. * @param string $file
  151. * @param int $batch
  152. * @param bool $pretend
  153. * @return void
  154. */
  155. protected function runUp($file, $batch, $pretend)
  156. {
  157. // First we will resolve a "real" instance of the migration class from this
  158. // migration file name. Once we have the instances we can run the actual
  159. // command such as "up" or "down", or we can just simulate the action.
  160. $migration = $this->resolve(
  161. $name = $this->getMigrationName($file)
  162. );
  163. if ($pretend) {
  164. return $this->pretendToRun($migration, 'up');
  165. }
  166. $this->note("<comment>Migrating:</comment> {$name}");
  167. $startTime = microtime(true);
  168. $this->runMigration($migration, 'up');
  169. $runTime = round(microtime(true) - $startTime, 2);
  170. // Once we have run a migrations class, we will log that it was run in this
  171. // repository so that we don't try to run it next time we do a migration
  172. // in the application. A migration repository keeps the migrate order.
  173. $this->repository->log($name, $batch);
  174. $this->note("<info>Migrated:</info> {$name} ({$runTime} seconds)");
  175. }
  176. /**
  177. * Rollback the last migration operation.
  178. *
  179. * @param array|string $paths
  180. * @param array $options
  181. * @return array
  182. */
  183. public function rollback($paths = [], array $options = [])
  184. {
  185. // We want to pull in the last batch of migrations that ran on the previous
  186. // migration operation. We'll then reverse those migrations and run each
  187. // of them "down" to reverse the last migration "operation" which ran.
  188. $migrations = $this->getMigrationsForRollback($options);
  189. if (count($migrations) === 0) {
  190. $this->note('<info>Nothing to rollback.</info>');
  191. return [];
  192. }
  193. return $this->rollbackMigrations($migrations, $paths, $options);
  194. }
  195. /**
  196. * Get the migrations for a rollback operation.
  197. *
  198. * @param array $options
  199. * @return array
  200. */
  201. protected function getMigrationsForRollback(array $options)
  202. {
  203. if (($steps = $options['step'] ?? 0) > 0) {
  204. return $this->repository->getMigrations($steps);
  205. }
  206. return $this->repository->getLast();
  207. }
  208. /**
  209. * Rollback the given migrations.
  210. *
  211. * @param array $migrations
  212. * @param array|string $paths
  213. * @param array $options
  214. * @return array
  215. */
  216. protected function rollbackMigrations(array $migrations, $paths, array $options)
  217. {
  218. $rolledBack = [];
  219. $this->requireFiles($files = $this->getMigrationFiles($paths));
  220. $this->fireMigrationEvent(new MigrationsStarted);
  221. // Next we will run through all of the migrations and call the "down" method
  222. // which will reverse each migration in order. This getLast method on the
  223. // repository already returns these migration's names in reverse order.
  224. foreach ($migrations as $migration) {
  225. $migration = (object) $migration;
  226. if (! $file = Arr::get($files, $migration->migration)) {
  227. $this->note("<fg=red>Migration not found:</> {$migration->migration}");
  228. continue;
  229. }
  230. $rolledBack[] = $file;
  231. $this->runDown(
  232. $file, $migration,
  233. $options['pretend'] ?? false
  234. );
  235. }
  236. $this->fireMigrationEvent(new MigrationsEnded);
  237. return $rolledBack;
  238. }
  239. /**
  240. * Rolls all of the currently applied migrations back.
  241. *
  242. * @param array|string $paths
  243. * @param bool $pretend
  244. * @return array
  245. */
  246. public function reset($paths = [], $pretend = false)
  247. {
  248. // Next, we will reverse the migration list so we can run them back in the
  249. // correct order for resetting this database. This will allow us to get
  250. // the database back into its "empty" state ready for the migrations.
  251. $migrations = array_reverse($this->repository->getRan());
  252. if (count($migrations) === 0) {
  253. $this->note('<info>Nothing to rollback.</info>');
  254. return [];
  255. }
  256. return $this->resetMigrations($migrations, $paths, $pretend);
  257. }
  258. /**
  259. * Reset the given migrations.
  260. *
  261. * @param array $migrations
  262. * @param array $paths
  263. * @param bool $pretend
  264. * @return array
  265. */
  266. protected function resetMigrations(array $migrations, array $paths, $pretend = false)
  267. {
  268. // Since the getRan method that retrieves the migration name just gives us the
  269. // migration name, we will format the names into objects with the name as a
  270. // property on the objects so that we can pass it to the rollback method.
  271. $migrations = collect($migrations)->map(function ($m) {
  272. return (object) ['migration' => $m];
  273. })->all();
  274. return $this->rollbackMigrations(
  275. $migrations, $paths, compact('pretend')
  276. );
  277. }
  278. /**
  279. * Run "down" a migration instance.
  280. *
  281. * @param string $file
  282. * @param object $migration
  283. * @param bool $pretend
  284. * @return void
  285. */
  286. protected function runDown($file, $migration, $pretend)
  287. {
  288. // First we will get the file name of the migration so we can resolve out an
  289. // instance of the migration. Once we get an instance we can either run a
  290. // pretend execution of the migration or we can run the real migration.
  291. $instance = $this->resolve(
  292. $name = $this->getMigrationName($file)
  293. );
  294. $this->note("<comment>Rolling back:</comment> {$name}");
  295. if ($pretend) {
  296. return $this->pretendToRun($instance, 'down');
  297. }
  298. $startTime = microtime(true);
  299. $this->runMigration($instance, 'down');
  300. $runTime = round(microtime(true) - $startTime, 2);
  301. // Once we have successfully run the migration "down" we will remove it from
  302. // the migration repository so it will be considered to have not been run
  303. // by the application then will be able to fire by any later operation.
  304. $this->repository->delete($migration);
  305. $this->note("<info>Rolled back:</info> {$name} ({$runTime} seconds)");
  306. }
  307. /**
  308. * Run a migration inside a transaction if the database supports it.
  309. *
  310. * @param object $migration
  311. * @param string $method
  312. * @return void
  313. */
  314. protected function runMigration($migration, $method)
  315. {
  316. $connection = $this->resolveConnection(
  317. $migration->getConnection()
  318. );
  319. $callback = function () use ($migration, $method) {
  320. if (method_exists($migration, $method)) {
  321. $this->fireMigrationEvent(new MigrationStarted($migration, $method));
  322. $migration->{$method}();
  323. $this->fireMigrationEvent(new MigrationEnded($migration, $method));
  324. }
  325. };
  326. $this->getSchemaGrammar($connection)->supportsSchemaTransactions()
  327. && $migration->withinTransaction
  328. ? $connection->transaction($callback)
  329. : $callback();
  330. }
  331. /**
  332. * Pretend to run the migrations.
  333. *
  334. * @param object $migration
  335. * @param string $method
  336. * @return void
  337. */
  338. protected function pretendToRun($migration, $method)
  339. {
  340. foreach ($this->getQueries($migration, $method) as $query) {
  341. $name = get_class($migration);
  342. $this->note("<info>{$name}:</info> {$query['query']}");
  343. }
  344. }
  345. /**
  346. * Get all of the queries that would be run for a migration.
  347. *
  348. * @param object $migration
  349. * @param string $method
  350. * @return array
  351. */
  352. protected function getQueries($migration, $method)
  353. {
  354. // Now that we have the connections we can resolve it and pretend to run the
  355. // queries against the database returning the array of raw SQL statements
  356. // that would get fired against the database system for this migration.
  357. $db = $this->resolveConnection(
  358. $migration->getConnection()
  359. );
  360. return $db->pretend(function () use ($migration, $method) {
  361. if (method_exists($migration, $method)) {
  362. $migration->{$method}();
  363. }
  364. });
  365. }
  366. /**
  367. * Resolve a migration instance from a file.
  368. *
  369. * @param string $file
  370. * @return object
  371. */
  372. public function resolve($file)
  373. {
  374. $class = Str::studly(implode('_', array_slice(explode('_', $file), 4)));
  375. return new $class;
  376. }
  377. /**
  378. * Get all of the migration files in a given path.
  379. *
  380. * @param string|array $paths
  381. * @return array
  382. */
  383. public function getMigrationFiles($paths)
  384. {
  385. return Collection::make($paths)->flatMap(function ($path) {
  386. return Str::endsWith($path, '.php') ? [$path] : $this->files->glob($path.'/*_*.php');
  387. })->filter()->sortBy(function ($file) {
  388. return $this->getMigrationName($file);
  389. })->values()->keyBy(function ($file) {
  390. return $this->getMigrationName($file);
  391. })->all();
  392. }
  393. /**
  394. * Require in all the migration files in a given path.
  395. *
  396. * @param array $files
  397. * @return void
  398. */
  399. public function requireFiles(array $files)
  400. {
  401. foreach ($files as $file) {
  402. $this->files->requireOnce($file);
  403. }
  404. }
  405. /**
  406. * Get the name of the migration.
  407. *
  408. * @param string $path
  409. * @return string
  410. */
  411. public function getMigrationName($path)
  412. {
  413. return str_replace('.php', '', basename($path));
  414. }
  415. /**
  416. * Register a custom migration path.
  417. *
  418. * @param string $path
  419. * @return void
  420. */
  421. public function path($path)
  422. {
  423. $this->paths = array_unique(array_merge($this->paths, [$path]));
  424. }
  425. /**
  426. * Get all of the custom migration paths.
  427. *
  428. * @return array
  429. */
  430. public function paths()
  431. {
  432. return $this->paths;
  433. }
  434. /**
  435. * Get the default connection name.
  436. *
  437. * @return string
  438. */
  439. public function getConnection()
  440. {
  441. return $this->connection;
  442. }
  443. /**
  444. * Set the default connection name.
  445. *
  446. * @param string $name
  447. * @return void
  448. */
  449. public function setConnection($name)
  450. {
  451. if (! is_null($name)) {
  452. $this->resolver->setDefaultConnection($name);
  453. }
  454. $this->repository->setSource($name);
  455. $this->connection = $name;
  456. }
  457. /**
  458. * Resolve the database connection instance.
  459. *
  460. * @param string $connection
  461. * @return \Illuminate\Database\Connection
  462. */
  463. public function resolveConnection($connection)
  464. {
  465. return $this->resolver->connection($connection ?: $this->connection);
  466. }
  467. /**
  468. * Get the schema grammar out of a migration connection.
  469. *
  470. * @param \Illuminate\Database\Connection $connection
  471. * @return \Illuminate\Database\Schema\Grammars\Grammar
  472. */
  473. protected function getSchemaGrammar($connection)
  474. {
  475. if (is_null($grammar = $connection->getSchemaGrammar())) {
  476. $connection->useDefaultSchemaGrammar();
  477. $grammar = $connection->getSchemaGrammar();
  478. }
  479. return $grammar;
  480. }
  481. /**
  482. * Get the migration repository instance.
  483. *
  484. * @return \Illuminate\Database\Migrations\MigrationRepositoryInterface
  485. */
  486. public function getRepository()
  487. {
  488. return $this->repository;
  489. }
  490. /**
  491. * Determine if the migration repository exists.
  492. *
  493. * @return bool
  494. */
  495. public function repositoryExists()
  496. {
  497. return $this->repository->repositoryExists();
  498. }
  499. /**
  500. * Get the file system instance.
  501. *
  502. * @return \Illuminate\Filesystem\Filesystem
  503. */
  504. public function getFilesystem()
  505. {
  506. return $this->files;
  507. }
  508. /**
  509. * Set the output implementation that should be used by the console.
  510. *
  511. * @param \Illuminate\Console\OutputStyle $output
  512. * @return $this
  513. */
  514. public function setOutput(OutputStyle $output)
  515. {
  516. $this->output = $output;
  517. return $this;
  518. }
  519. /**
  520. * Write a note to the console's output.
  521. *
  522. * @param string $message
  523. * @return void
  524. */
  525. protected function note($message)
  526. {
  527. if ($this->output) {
  528. $this->output->writeln($message);
  529. }
  530. }
  531. /**
  532. * Fire the given event for the migration.
  533. *
  534. * @param \Illuminate\Contracts\Database\Events\MigrationEvent $event
  535. * @return void
  536. */
  537. public function fireMigrationEvent($event)
  538. {
  539. if ($this->events) {
  540. $this->events->dispatch($event);
  541. }
  542. }
  543. }