RequireCommand.php 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. <?php
  2. /*
  3. * This file is part of Composer.
  4. *
  5. * (c) Nils Adermann <naderman@naderman.de>
  6. * Jordi Boggiano <j.boggiano@seld.be>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. namespace Composer\Command;
  12. use Symfony\Component\Console\Input\InputInterface;
  13. use Symfony\Component\Console\Input\InputArgument;
  14. use Symfony\Component\Console\Input\InputOption;
  15. use Symfony\Component\Console\Output\OutputInterface;
  16. use Composer\Factory;
  17. use Composer\Installer;
  18. use Composer\Json\JsonFile;
  19. use Composer\Json\JsonManipulator;
  20. use Composer\Package\Version\VersionParser;
  21. use Composer\Plugin\CommandEvent;
  22. use Composer\Plugin\PluginEvents;
  23. /**
  24. * @author Jérémy Romey <jeremy@free-agent.fr>
  25. * @author Jordi Boggiano <j.boggiano@seld.be>
  26. */
  27. class RequireCommand extends InitCommand
  28. {
  29. protected function configure()
  30. {
  31. $this
  32. ->setName('require')
  33. ->setDescription('Adds required packages to your composer.json and installs them')
  34. ->setDefinition(array(
  35. new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Required package with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'),
  36. new InputOption('dev', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'),
  37. new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
  38. new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'),
  39. new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
  40. new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies.'),
  41. ))
  42. ->setHelp(<<<EOT
  43. The require command adds required packages to your composer.json and installs them
  44. If you do not want to install the new dependencies immediately you can call it with --no-update
  45. EOT
  46. )
  47. ;
  48. }
  49. protected function execute(InputInterface $input, OutputInterface $output)
  50. {
  51. $file = Factory::getComposerFile();
  52. if (!file_exists($file) && !file_put_contents($file, "{\n}\n")) {
  53. $output->writeln('<error>'.$file.' could not be created.</error>');
  54. return 1;
  55. }
  56. if (!is_readable($file)) {
  57. $output->writeln('<error>'.$file.' is not readable.</error>');
  58. return 1;
  59. }
  60. if (!is_writable($file)) {
  61. $output->writeln('<error>'.$file.' is not writable.</error>');
  62. return 1;
  63. }
  64. $json = new JsonFile($file);
  65. $composer = $json->read();
  66. $composerBackup = file_get_contents($json->getPath());
  67. $requirements = $this->determineRequirements($input, $output, $input->getArgument('packages'));
  68. $requireKey = $input->getOption('dev') ? 'require-dev' : 'require';
  69. $baseRequirements = array_key_exists($requireKey, $composer) ? $composer[$requireKey] : array();
  70. $requirements = $this->formatRequirements($requirements);
  71. // validate requirements format
  72. $versionParser = new VersionParser();
  73. foreach ($requirements as $constraint) {
  74. $versionParser->parseConstraints($constraint);
  75. }
  76. if (!$this->updateFileCleanly($json, $baseRequirements, $requirements, $requireKey)) {
  77. foreach ($requirements as $package => $version) {
  78. $baseRequirements[$package] = $version;
  79. }
  80. $composer[$requireKey] = $baseRequirements;
  81. $json->write($composer);
  82. }
  83. $output->writeln('<info>'.$file.' has been updated</info>');
  84. if ($input->getOption('no-update')) {
  85. return 0;
  86. }
  87. // Update packages
  88. $composer = $this->getComposer();
  89. $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
  90. $io = $this->getIO();
  91. $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'require', $input, $output);
  92. $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
  93. $install = Installer::create($io, $composer);
  94. $install
  95. ->setVerbose($input->getOption('verbose'))
  96. ->setPreferSource($input->getOption('prefer-source'))
  97. ->setPreferDist($input->getOption('prefer-dist'))
  98. ->setDevMode(true)
  99. ->setUpdate(true)
  100. ->setUpdateWhitelist(array_keys($requirements));
  101. ;
  102. if (!$install->run()) {
  103. $output->writeln("\n".'<error>Installation failed, reverting '.$file.' to its original content.</error>');
  104. file_put_contents($json->getPath(), $composerBackup);
  105. return 1;
  106. }
  107. return 0;
  108. }
  109. private function updateFileCleanly($json, array $base, array $new, $requireKey)
  110. {
  111. $contents = file_get_contents($json->getPath());
  112. $manipulator = new JsonManipulator($contents);
  113. foreach ($new as $package => $constraint) {
  114. if (!$manipulator->addLink($requireKey, $package, $constraint)) {
  115. return false;
  116. }
  117. }
  118. file_put_contents($json->getPath(), $manipulator->getContents());
  119. return true;
  120. }
  121. protected function interact(InputInterface $input, OutputInterface $output)
  122. {
  123. return;
  124. }
  125. }