DrupalApplication.php

Same filename and directory in other branches
  1. main core/lib/Drupal/Core/Command/DrupalApplication.php

Namespace

Drupal\Core\Command

File

core/lib/Drupal/Core/Command/DrupalApplication.php

View source
<?php

declare (strict_types=1);
namespace Drupal\Core\Command;

use Composer\Autoload\ClassLoader;
use Drupal\Core\DrupalKernel;
use Drupal\Core\Site\Settings;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Command\HelpCommand;
use Symfony\Component\Console\Command\ListCommand;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;

/**
 * Customize the Symfony Application for Drupal.
 *
 * @ingroup console_commands
 */
class DrupalApplication extends Application {
  
  /**
   * Flag to remember if Drupal has already been bootstrapped.
   */
  protected bool $booted = FALSE;
  public function __construct(protected ClassLoader $classloader, protected array $context) {
    parent::__construct('Drupal', \Drupal::VERSION);
  }
  
  /**
   * {@inheritdoc}
   */
  protected function getDefaultInputDefinition() : InputDefinition {
    $definition = parent::getDefaultInputDefinition();
    $definition->addOption(new InputOption('url', NULL, InputOption::VALUE_REQUIRED, 'A base URL (e.g. example.com). Used for building links, selecting a multi-site, etc.'));
    return $definition;
  }
  
  /**
   * {@inheritdoc}
   */
  protected function getDefaultCommands() : array {
    $default_commands = parent::getDefaultCommands();
    // Commands registered here are available even if Drupal does not boot.
    // Only register commands here that do not need to bootstrap Drupal.
    $default_commands[] = new QuickStartCommand();
    $default_commands[] = new InstallCommand($this->classloader);
    $default_commands[] = new CacheRebuildCommand($this->classloader);
    $default_commands[] = new ServerCommand($this->classloader);
    $default_commands[] = new GenerateTheme();
    return $default_commands;
  }
  
  /**
   * {@inheritdoc}
   */
  public function find(string $name) : Command {
    try {
      $command = parent::find($name);
      if ($command instanceof ListCommand || $command instanceof HelpCommand) {
        $this->bootstrap();
      }
      return $command;
    } catch (CommandNotFoundException $e) {
      if (!$this->bootstrap()) {
        throw $e;
      }
      return parent::find($name);
    }
  }
  
  /**
   * Bootstraps Drupal in a way that is appropriate for console commands.
   */
  protected function bootstrap() : bool {
    if ($this->booted) {
      return TRUE;
    }
    try {
      $request = Request::create($this->getUri());
      // Discovery can get out of whack if cleared caches and try to run this
      // command without a web request priming discovery.
      chdir(\DRUPAL_ROOT);
      // We need to not load a cached copy of the container from disk. For
      // example, inside Kernel tests, we need to fully build the container so
      // we discover and register commands, instead of reusing the container
      // from the Kernel test itself. Therefore, we pass `FALSE` for the
      // `$allow_dumping` parameter here.
      $kernel = new DrupalKernel('prod', $this->classloader, FALSE);
      // We tried calling `DrupalKernel::bootEnvironment()` right here to setup
      // some common environment and PHP initialization steps. However, that
      // method also calls `set_error_handler('_drupal_error_handler')` which
      // creates problems when running commands inside of tests. Since
      // `bootEnvironment()` has a static flag to only happen once, it's hard to
      // restore the previous error handler after running commands, and we don't
      // want to make tests responsible for restoring the error handler.
      // @todo Refactor `bootEnvironment()' so that we can use the parts we need
      // but leave the error handler alone.
      // @see https://www.drupal.org/node/2690035
      // Define the DRUPAL_TEST_IN_CHILD_SITE based on if we're inside a test.
      DrupalKernel::setupDrupalTestInChildSite($kernel->getAppRoot());
      $sitePath = DrupalKernel::findSitePath($request, TRUE);
      $kernel->setSitePath($sitePath);
      Settings::initialize($kernel->getAppRoot(), $kernel->getSitePath(), $this->classloader);
      $kernel->boot();
      $container = $kernel->getContainer();
      $container->get('request_stack')
        ->push($request);
      // Set base URL - needed when in a subdir e.g. http://example.com/subdir.
      $container->get('router.request_context')
        ->setBaseUrl($request->getPathInfo());
      // This sets things up, esp loadLegacyIncludes().
      $kernel->preHandle($request);
      // Adds commands as part of installed modules.
      $this->addDrupalCommands($container);
      // Dispatch standard console events.
      $this->setDispatcher($container->get('event_dispatcher'));
    } catch (\Throwable) {
      return FALSE;
    }
    $this->booted = TRUE;
    return TRUE;
  }
  
  /**
   * Gets the URI for the request.
   *
   * @return string
   *   The base URL for this request.
   */
  public function getUri() : string {
    $argv = new ArgvInput();
    return $argv->getParameterOption('--url') ?: $this->context['DRUPAL_URI'] ?? 'http://localhost';
  }
  
  /**
   * Discovers commands that are provided by installed modules.
   */
  protected function addDrupalCommands(ContainerInterface $container) : void {
    $this->setCommandLoader($container->get('console.command_loader'));
    // Add commands that don't use an attribute, relying solely on configure().
    if ($container->hasParameter('console.command.ids')) {
      foreach ($container->getParameter('console.command.ids') as $id) {
        $this->addCommand($container->get($id));
      }
    }
  }

}

Classes

Title Deprecated Summary
DrupalApplication Customize the Symfony Application for Drupal.

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