Просмотр файла vendor/robmorgan/phinx/src/Phinx/Console/Command/Create.php

Размер файла: 11.97Kb
  1. <?php
  2.  
  3. /**
  4. * MIT License
  5. * For full license information, please view the LICENSE file that was distributed with this source code.
  6. */
  7.  
  8. namespace Phinx\Console\Command;
  9.  
  10. use Exception;
  11. use InvalidArgumentException;
  12. use Phinx\Config\NamespaceAwareInterface;
  13. use Phinx\Util\Util;
  14. use RuntimeException;
  15. use Symfony\Component\Console\Input\InputArgument;
  16. use Symfony\Component\Console\Input\InputInterface;
  17. use Symfony\Component\Console\Input\InputOption;
  18. use Symfony\Component\Console\Output\OutputInterface;
  19. use Symfony\Component\Console\Question\ChoiceQuestion;
  20. use Symfony\Component\Console\Question\ConfirmationQuestion;
  21.  
  22. class Create extends AbstractCommand
  23. {
  24. /**
  25. * @var string
  26. */
  27. protected static $defaultName = 'create';
  28.  
  29. /**
  30. * The name of the interface that any external template creation class is required to implement.
  31. */
  32. public const CREATION_INTERFACE = 'Phinx\Migration\CreationInterface';
  33.  
  34. /**
  35. * {@inheritDoc}
  36. *
  37. * @return void
  38. */
  39. protected function configure()
  40. {
  41. parent::configure();
  42.  
  43. $this->setDescription('Create a new migration')
  44. ->addArgument('name', InputArgument::OPTIONAL, 'Class name of the migration (in CamelCase)')
  45. ->setHelp(sprintf(
  46. '%sCreates a new database migration%s',
  47. PHP_EOL,
  48. PHP_EOL
  49. ));
  50.  
  51. // An alternative template.
  52. $this->addOption('template', 't', InputOption::VALUE_REQUIRED, 'Use an alternative template');
  53.  
  54. // A classname to be used to gain access to the template content as well as the ability to
  55. // have a callback once the migration file has been created.
  56. $this->addOption('class', 'l', InputOption::VALUE_REQUIRED, 'Use a class implementing "' . self::CREATION_INTERFACE . '" to generate the template');
  57.  
  58. // Allow the migration path to be chosen non-interactively.
  59. $this->addOption('path', null, InputOption::VALUE_REQUIRED, 'Specify the path in which to create this migration');
  60. }
  61.  
  62. /**
  63. * Get the confirmation question asking if the user wants to create the
  64. * migrations directory.
  65. *
  66. * @return \Symfony\Component\Console\Question\ConfirmationQuestion
  67. */
  68. protected function getCreateMigrationDirectoryQuestion()
  69. {
  70. return new ConfirmationQuestion('Create migrations directory? [y]/n ', true);
  71. }
  72.  
  73. /**
  74. * Get the question that allows the user to select which migration path to use.
  75. *
  76. * @param string[] $paths Paths
  77. *
  78. * @return \Symfony\Component\Console\Question\ChoiceQuestion
  79. */
  80. protected function getSelectMigrationPathQuestion(array $paths)
  81. {
  82. return new ChoiceQuestion('Which migrations path would you like to use?', $paths, 0);
  83. }
  84.  
  85. /**
  86. * Returns the migration path to create the migration in.
  87. *
  88. * @param \Symfony\Component\Console\Input\InputInterface $input Input
  89. * @param \Symfony\Component\Console\Output\OutputInterface $output Output
  90. *
  91. * @throws \Exception
  92. *
  93. * @return string
  94. */
  95. protected function getMigrationPath(InputInterface $input, OutputInterface $output)
  96. {
  97. // First, try the non-interactive option:
  98. $path = $input->getOption('path');
  99.  
  100. if (!empty($path)) {
  101. return $path;
  102. }
  103.  
  104. $paths = $this->getConfig()->getMigrationPaths();
  105.  
  106. // No paths? That's a problem.
  107. if (empty($paths)) {
  108. throw new Exception('No migration paths set in your Phinx configuration file.');
  109. }
  110.  
  111. $paths = Util::globAll($paths);
  112.  
  113. if (empty($paths)) {
  114. throw new Exception(
  115. 'You probably used curly braces to define migration path in your Phinx configuration file, ' .
  116. 'but no directories have been matched using this pattern. ' .
  117. 'You need to create a migration directory manually.'
  118. );
  119. }
  120.  
  121. // Only one path set, so select that:
  122. if (count($paths) === 1) {
  123. return array_shift($paths);
  124. }
  125.  
  126. /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */
  127. $helper = $this->getHelper('question');
  128. $question = $this->getSelectMigrationPathQuestion($paths);
  129.  
  130. return $helper->ask($input, $output, $question);
  131. }
  132.  
  133. /**
  134. * Create the new migration.
  135. *
  136. * @param \Symfony\Component\Console\Input\InputInterface $input Input
  137. * @param \Symfony\Component\Console\Output\OutputInterface $output Output
  138. *
  139. * @throws \RuntimeException
  140. * @throws \InvalidArgumentException
  141. *
  142. * @return int 0 on success
  143. */
  144. protected function execute(InputInterface $input, OutputInterface $output)
  145. {
  146. $this->bootstrap($input, $output);
  147.  
  148. // get the migration path from the config
  149. $path = $this->getMigrationPath($input, $output);
  150.  
  151. if (!file_exists($path)) {
  152. /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */
  153. $helper = $this->getHelper('question');
  154. $question = $this->getCreateMigrationDirectoryQuestion();
  155.  
  156. if ($helper->ask($input, $output, $question)) {
  157. mkdir($path, 0755, true);
  158. }
  159. }
  160.  
  161. $this->verifyMigrationDirectory($path);
  162.  
  163. $config = $this->getConfig();
  164. $namespace = $config instanceof NamespaceAwareInterface ? $config->getMigrationNamespaceByPath($path) : null;
  165.  
  166. $path = realpath($path);
  167. $className = $input->getArgument('name');
  168. if ($className === null) {
  169. $currentTimestamp = Util::getCurrentTimestamp();
  170. $className = "V" . $currentTimestamp;
  171. $fileName = $currentTimestamp . '.php';
  172. } else {
  173. if (!Util::isValidPhinxClassName($className)) {
  174. throw new InvalidArgumentException(sprintf(
  175. 'The migration class name "%s" is invalid. Please use CamelCase format.',
  176. $className
  177. ));
  178. }
  179.  
  180. // Compute the file path
  181. $fileName = Util::mapClassNameToFileName($className);
  182. }
  183.  
  184. if (!Util::isUniqueMigrationClassName($className, $path)) {
  185. throw new InvalidArgumentException(sprintf(
  186. 'The migration class name "%s%s" already exists',
  187. $namespace ? ($namespace . '\\') : '',
  188. $className
  189. ));
  190. }
  191.  
  192. $filePath = $path . DIRECTORY_SEPARATOR . $fileName;
  193.  
  194. if (is_file($filePath)) {
  195. throw new InvalidArgumentException(sprintf(
  196. 'The file "%s" already exists',
  197. $filePath
  198. ));
  199. }
  200.  
  201. // Get the alternative template and static class options from the config, but only allow one of them.
  202. $defaultAltTemplate = $this->getConfig()->getTemplateFile();
  203. $defaultCreationClassName = $this->getConfig()->getTemplateClass();
  204. if ($defaultAltTemplate && $defaultCreationClassName) {
  205. throw new InvalidArgumentException('Cannot define template:class and template:file at the same time');
  206. }
  207.  
  208. // Get the alternative template and static class options from the command line, but only allow one of them.
  209. $altTemplate = $input->getOption('template');
  210. $creationClassName = $input->getOption('class');
  211. if ($altTemplate && $creationClassName) {
  212. throw new InvalidArgumentException('Cannot use --template and --class at the same time');
  213. }
  214.  
  215. // If no commandline options then use the defaults.
  216. if (!$altTemplate && !$creationClassName) {
  217. $altTemplate = $defaultAltTemplate;
  218. $creationClassName = $defaultCreationClassName;
  219. }
  220.  
  221. // Verify the alternative template file's existence.
  222. if ($altTemplate && !is_file($altTemplate)) {
  223. throw new InvalidArgumentException(sprintf(
  224. 'The alternative template file "%s" does not exist',
  225. $altTemplate
  226. ));
  227. }
  228.  
  229. // Verify that the template creation class (or the aliased class) exists and that it implements the required interface.
  230. $aliasedClassName = null;
  231. if ($creationClassName) {
  232. // Supplied class does not exist, is it aliased?
  233. if (!class_exists($creationClassName)) {
  234. $aliasedClassName = $this->getConfig()->getAlias($creationClassName);
  235. if ($aliasedClassName && !class_exists($aliasedClassName)) {
  236. throw new InvalidArgumentException(sprintf(
  237. 'The class "%s" via the alias "%s" does not exist',
  238. $aliasedClassName,
  239. $creationClassName
  240. ));
  241. } elseif (!$aliasedClassName) {
  242. throw new InvalidArgumentException(sprintf(
  243. 'The class "%s" does not exist',
  244. $creationClassName
  245. ));
  246. }
  247. }
  248.  
  249. // Does the class implement the required interface?
  250. if (!$aliasedClassName && !is_subclass_of($creationClassName, self::CREATION_INTERFACE)) {
  251. throw new InvalidArgumentException(sprintf(
  252. 'The class "%s" does not implement the required interface "%s"',
  253. $creationClassName,
  254. self::CREATION_INTERFACE
  255. ));
  256. } elseif ($aliasedClassName && !is_subclass_of($aliasedClassName, self::CREATION_INTERFACE)) {
  257. throw new InvalidArgumentException(sprintf(
  258. 'The class "%s" via the alias "%s" does not implement the required interface "%s"',
  259. $aliasedClassName,
  260. $creationClassName,
  261. self::CREATION_INTERFACE
  262. ));
  263. }
  264. }
  265.  
  266. // Use the aliased class.
  267. $creationClassName = $aliasedClassName ?: $creationClassName;
  268.  
  269. // Determine the appropriate mechanism to get the template
  270. if ($creationClassName) {
  271. // Get the template from the creation class
  272. $creationClass = new $creationClassName($input, $output);
  273. $contents = $creationClass->getMigrationTemplate();
  274. } else {
  275. // Load the alternative template if it is defined.
  276. $contents = file_get_contents($altTemplate ?: $this->getMigrationTemplateFilename());
  277. }
  278.  
  279. // inject the class names appropriate to this migration
  280. $classes = [
  281. '$namespaceDefinition' => $namespace !== null ? (PHP_EOL . 'namespace ' . $namespace . ';' . PHP_EOL) : '',
  282. '$namespace' => $namespace,
  283. '$useClassName' => $this->getConfig()->getMigrationBaseClassName(false),
  284. '$className' => $className,
  285. '$version' => Util::getVersionFromFileName($fileName),
  286. '$baseClassName' => $this->getConfig()->getMigrationBaseClassName(true),
  287. ];
  288. $contents = strtr($contents, $classes);
  289.  
  290. if (file_put_contents($filePath, $contents) === false) {
  291. throw new RuntimeException(sprintf(
  292. 'The file "%s" could not be written to',
  293. $path
  294. ));
  295. }
  296.  
  297. // Do we need to do the post creation call to the creation class?
  298. if (isset($creationClass)) {
  299. /** @var \Phinx\Migration\CreationInterface $creationClass */
  300. $creationClass->postMigrationCreation($filePath, $className, $this->getConfig()->getMigrationBaseClassName());
  301. }
  302.  
  303. $output->writeln('<info>using migration base class</info> ' . $classes['$useClassName']);
  304.  
  305. if (!empty($altTemplate)) {
  306. $output->writeln('<info>using alternative template</info> ' . $altTemplate);
  307. } elseif (!empty($creationClassName)) {
  308. $output->writeln('<info>using template creation class</info> ' . $creationClassName);
  309. } else {
  310. $output->writeln('<info>using default template</info>');
  311. }
  312.  
  313. $output->writeln('<info>created</info> ' . Util::relativePath($filePath));
  314.  
  315. return self::CODE_SUCCESS;
  316. }
  317. }