Migrator.php 22 KB

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