function ExtensionInstallCommand::__invoke

Installs one or more modules or themes.

File

core/modules/system/src/Command/ExtensionInstallCommand.php, line 54

Class

ExtensionInstallCommand
Installs modules or themes, resolving and confirming their dependencies.

Namespace

Drupal\system\Command

Code

public function __invoke(InputInterface $input, OutputInterface $output, #[Argument(description: 'A comma-separated list of module or theme machine names to install.')] string $extensions, #[Option(description: 'Proceed without prompting for confirmation when additional dependencies will be installed.', shortcut: 'y')] bool $yes = FALSE, #[Option(description: 'Show the operations that would be performed without installing anything.')] bool $dryRun = FALSE) : int {
  $io = new SymfonyStyle($input, $output);
  // Parse the comma-delimited list of extension machine names, dropping any
  // empty entries left by stray or trailing commas.
  $requested = array_values(array_filter(explode(',', $extensions)));
  if (empty($requested)) {
    $io->error((string) $this->t('No modules or themes were specified.'));
    return Command::INVALID;
  }
  // Classify each requested extension as a module or a theme. The extension
  // lists are reset so that any extensions added directly to the filesystem
  // are picked up.
  $module_data = $this->moduleExtensionList
    ->reset()
    ->getList();
  $theme_data = $this->themeExtensionList
    ->reset()
    ->getList();
  $modules = [];
  $themes = [];
  $unknown = [];
  foreach ($requested as $name) {
    if (isset($module_data[$name])) {
      $modules[] = $name;
    }
    elseif (isset($theme_data[$name])) {
      $themes[] = $name;
    }
    else {
      $unknown[] = $name;
    }
  }
  if (!empty($unknown)) {
    $io->error((string) $this->formatPlural(count($unknown), 'The following could not be found as a module or theme: @names.', 'The following could not be found as modules or themes: @names.', [
      '@names' => implode(', ', $unknown),
    ]));
    return Command::FAILURE;
  }
  // Installing modules and themes together would require interleaving two
  // different dependency-resolution and install processes (a theme may even
  // depend on a module being installed first). Keep each invocation to a
  // single extension type rather than guessing at the correct ordering.
  if (!empty($modules) && !empty($themes)) {
    $io->error((string) $this->t('Cannot install modules (@modules) and themes (@themes) in the same command. Run "ex:install" separately for each.', [
      '@modules' => implode(', ', $modules),
      '@themes' => implode(', ', $themes),
    ]));
    return Command::FAILURE;
  }
  $is_theme = !empty($themes);
  $type = $is_theme ? 'theme' : 'module';
  $names = $is_theme ? $themes : $modules;
  $data = $is_theme ? $theme_data : $module_data;
  // Evaluate the full set of extensions that will be installed, including
  // dependencies that are pulled in automatically. This validates the
  // request and throws on missing/incompatible/obsolete extensions.
  try {
    $resolved = $is_theme ? $this->themeInstaller
      ->getThemesToInstall($names) : $this->moduleInstaller
      ->getModulesToInstall($names);
  } catch (MissingDependencyException|ObsoleteExtensionException|UnknownExtensionException $e) {
    $io->error($e->getMessage());
    return Command::FAILURE;
  }
  if (empty($resolved)) {
    $io->success((string) $this->formatPlural(count($names), 'Nothing to do: the requested @type is already installed.', 'Nothing to do: the requested @types are already installed.', [
      '@type' => $type,
    ]));
    return Command::SUCCESS;
  }
  // Split the resolved set into the extensions that were explicitly
  // requested and the additional dependencies that will be installed too.
  $additional = array_diff($resolved, $names);
  $verb = $dryRun ? $this->t('would be installed') : $this->t('will be installed');
  $requested_labels = [];
  $additional_labels = [];
  foreach ($resolved as $machine_name) {
    $label = sprintf('%s (%s)', $data[$machine_name]->info['name'], $machine_name);
    if (in_array($machine_name, $names, TRUE)) {
      $requested_labels[] = $label;
    }
    else {
      $additional_labels[] = $label;
    }
  }
  $io->writeln((string) $this->formatPlural(count($requested_labels), 'The following @type @verb:', 'The following @types @verb:', [
    '@type' => $type,
    '@verb' => $verb,
  ]));
  $io->listing($requested_labels);
  if ($additional_labels) {
    $io->writeln((string) $this->formatPlural(count($additional_labels), 'The following additional @type @verb as a dependency:', 'The following additional @types @verb as dependencies:', [
      '@type' => $type,
      '@verb' => $verb,
    ]));
    $io->listing($additional_labels);
  }
  // Perform the same requirements checks as the modules administration form.
  // Themes mirror the current behavior and rely on the dependency checks
  // already performed by the theme installer.
  if (!$is_theme && !$this->checkRequirements($resolved, $io)) {
    return Command::FAILURE;
  }
  if ($dryRun) {
    $io->note((string) $this->t('Dry run: no changes were made.'));
    return Command::SUCCESS;
  }
  // Prompt before pulling in dependencies the user did not request. In
  // non-interactive mode there is nobody to answer the prompt, so require
  // --yes to be explicit rather than silently installing the dependencies.
  if (!empty($additional) && !$yes) {
    if (!$input->isInteractive()) {
      throw new UserAbortException(sprintf('Installing the requested %s(s) requires additional dependencies. Re-run with --yes to install them.', $type));
    }
    $question = (string) $this->formatPlural(count($additional), 'Install @count additional @type as a dependency?', 'Install @count additional @types as dependencies?', [
      '@type' => $type,
    ]);
    if (!$io->confirm($question, TRUE)) {
      throw new UserAbortException();
    }
  }
  try {
    if ($is_theme) {
      $this->themeInstaller
        ->install($names);
    }
    else {
      $this->moduleInstaller
        ->install($names);
    }
  } catch (\Exception $e) {
    $io->error($e->getMessage());
    return Command::FAILURE;
  }
  $items = [];
  foreach ($resolved as $machine_name) {
    $label = sprintf('%s (%s)', $data[$machine_name]->info['name'], $machine_name);
    $links = $this->getExtensionLinks($machine_name, $data[$machine_name]);
    if ($links) {
      $label .= ' — ' . implode(', ', $links);
    }
    $items[] = $label;
  }
  $io->success((string) $this->formatPlural(count($resolved), 'Successfully installed @count @type.', 'Successfully installed @count @types.', [
    '@type' => $type,
  ]));
  $io->listing($items);
  return Command::SUCCESS;
}

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.