Migrator.php 16KB

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