* Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Composer\Factory; use Composer\Installer; use Composer\Json\JsonFile; use Composer\Json\JsonManipulator; use Composer\Package\Version\VersionParser; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; /** * @author Jérémy Romey * @author Jordi Boggiano */ class RequireCommand extends InitCommand { protected function configure() { $this ->setName('require') ->setDescription('Adds required packages to your composer.json and installs them') ->setDefinition(array( 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"'), new InputOption('dev', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'), new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies.'), new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'), new InputOption('update-with-dependencies', null, InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated with explicit dependencies.'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'), )) ->setHelp(<<writeln(''.$file.' could not be created.'); return 1; } if (!is_readable($file)) { $output->writeln(''.$file.' is not readable.'); return 1; } if (!is_writable($file)) { $output->writeln(''.$file.' is not writable.'); return 1; } $json = new JsonFile($file); $composerDefinition = $json->read(); $composerBackup = file_get_contents($json->getPath()); $composer = $this->getComposer(); $repos = $composer->getRepositoryManager()->getRepositories(); $this->repos = new CompositeRepository(array_merge( array(new PlatformRepository), $repos )); $requirements = $this->determineRequirements($input, $output, $input->getArgument('packages')); $requireKey = $input->getOption('dev') ? 'require-dev' : 'require'; $removeKey = $input->getOption('dev') ? 'require' : 'require-dev'; $baseRequirements = array_key_exists($requireKey, $composerDefinition) ? $composerDefinition[$requireKey] : array(); $requirements = $this->formatRequirements($requirements); // validate requirements format $versionParser = new VersionParser(); foreach ($requirements as $constraint) { $versionParser->parseConstraints($constraint); } if (!$this->updateFileCleanly($json, $baseRequirements, $requirements, $requireKey, $removeKey)) { foreach ($requirements as $package => $version) { $baseRequirements[$package] = $version; if (isset($composerDefinition[$removeKey][$package])) { unset($composerDefinition[$removeKey][$package]); } } $composerDefinition[$requireKey] = $baseRequirements; $json->write($composerDefinition); } $output->writeln(''.$file.' has been '.($newlyCreated ? 'created' : 'updated').''); if ($input->getOption('no-update')) { return 0; } $updateDevMode = !$input->getOption('update-no-dev'); // Update packages $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress')); $io = $this->getIO(); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'require', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $install = Installer::create($io, $composer); $install ->setVerbose($input->getOption('verbose')) ->setPreferSource($input->getOption('prefer-source')) ->setPreferDist($input->getOption('prefer-dist')) ->setDevMode($updateDevMode) ->setUpdate(true) ->setUpdateWhitelist(array_keys($requirements)) ->setWhitelistDependencies($input->getOption('update-with-dependencies')) ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs')) ; $status = $install->run(); if ($status !== 0) { if ($newlyCreated) { $output->writeln("\n".'Installation failed, deleting '.$file.'.'); unlink($json->getPath()); } else { $output->writeln("\n".'Installation failed, reverting '.$file.' to its original content.'); file_put_contents($json->getPath(), $composerBackup); } } return $status; } private function updateFileCleanly($json, array $base, array $new, $requireKey, $removeKey) { $contents = file_get_contents($json->getPath()); $manipulator = new JsonManipulator($contents); foreach ($new as $package => $constraint) { if (!$manipulator->addLink($requireKey, $package, $constraint)) { return false; } if (!$manipulator->removeSubNode($removeKey, $package)) { return false; } } file_put_contents($json->getPath(), $manipulator->getContents()); return true; } protected function interact(InputInterface $input, OutputInterface $output) { return; } }