2 namespace Drush\Preflight;
4 use Drush\Config\Environment;
5 use Drush\Config\ConfigLocator;
6 use Drush\Config\EnvironmentConfigLoader;
7 use Consolidation\SiteAlias\SiteAliasManager;
8 use DrupalFinder\DrupalFinder;
11 * The Drush preflight determines what needs to be done for this request.
12 * The preflight happens after Drush has loaded its autoload file, but
13 * prior to loading Drupal's autoload file and setting up the DI container.
15 * - Pre-parse commandline arguments
16 * - Read configuration .yml files
17 * - Determine the site to use
22 * @var Environment $environment
24 protected $environment;
27 * @var PreflightVerify
34 protected $configLocator;
39 protected $drupalFinder;
44 protected $preflightArgs;
47 * @var SiteAliasManager
49 protected $aliasManager;
52 * @var PreflightLog $logger An early logger, just for Preflight.
57 * Preflight constructor
59 public function __construct(Environment $environment, $verify = null, $configLocator = null)
61 $this->environment = $environment;
62 $this->verify = $verify ?: new PreflightVerify();
63 $this->configLocator = $configLocator ?: new ConfigLocator('DRUSH_', $environment->getConfigFileVariant());
64 $this->drupalFinder = new DrupalFinder();
65 $this->logger = new PreflightLog();
69 * @return PreflightLog
71 public function logger()
77 * @param PreflightLog $logger
79 public function setLogger(PreflightLog $logger)
81 $this->logger = $logger;
85 * Perform preliminary initialization. This mostly involves setting up
88 public function init()
90 // Define legacy constants, and include legacy files that Drush still needs
91 LegacyPreflight::includeCode($this->environment->drushBasePath());
92 LegacyPreflight::defineConstants($this->environment, $this->preflightArgs->applicationPath());
93 LegacyPreflight::setContexts($this->environment);
97 * Remapping table for arguments. Anything found in a key
98 * here will be converted to the corresponding value entry.
101 * --ssh-options='-i mysite_dsa'
103 * -Dssh.options='-i mysite_dsa'
105 * TODO: We could consider loading this from a file or some other
106 * source. However, this table is needed very early -- even earlier
107 * than config is loaded (since this is needed for preflighting the
108 * arguments, which can select config files to load). Hardcoding
109 * is probably best; we might want to move to another class, perhaps.
110 * We also need this prior to Dependency Injection, though.
112 * Eventually, we might want to expose this table to some form of
113 * 'help' output, so folks can see the available conversions.
115 protected function remapOptions()
118 '--ssh-options' => '-Dssh.options',
119 '--php' => '-Druntime.php.path',
120 '--php-options' => '-Druntime.php.options',
121 '--php-notices' => '-Druntime.php.notices',
122 '--halt-on-error' => '-Druntime.php.halt-on-error',
123 '--output_charset' => '-Dio.output.charset',
124 '--output-charset' => '-Dio.output.charset',
125 '--db-su' => '-Dsql.db-su',
126 '--notify' => '-Dnotify.duration',
127 '--xh-link' => '-Dxh.link',
132 * Symfony Console dislikes certain command aliases, because
133 * they are too similar to other Drush commands that contain
134 * the same characters. To avoid the "I don't know which
135 * command you mean"-type errors, we will replace problematic
136 * aliases with their longhand equivalents.
138 * This should be fixed in Symfony Console.
140 protected function remapCommandAliases()
143 'si' => 'site:install',
145 // php was an alias for core-cli which got renamed to php-cli. See https://github.com/drush-ops/drush/issues/3091.
151 * Preprocess the args, removing any @sitealias that may be present.
152 * Arguments and options not used during preflight will be processed
155 public function preflightArgs($argv)
157 $argProcessor = new ArgsPreprocessor();
158 $remapper = new ArgsRemapper($this->remapOptions(), $this->remapCommandAliases());
159 $preflightArgs = new PreflightArgs();
160 $preflightArgs->setHomeDir($this->environment()->homeDir());
161 $argProcessor->setArgsRemapper($remapper);
163 $argProcessor->parse($argv, $preflightArgs);
165 return $preflightArgs;
169 * Create the initial config locator object, and inject any needed
170 * settings, paths and so on into it.
172 public function prepareConfig(Environment $environment)
174 // Make our environment settings available as configuration items
175 $this->configLocator->addEnvironment($environment);
176 $this->configLocator->setLocal($this->preflightArgs->isLocal());
177 $this->configLocator->addUserConfig($this->preflightArgs->configPaths(), $environment->systemConfigPath(), $environment->userConfigPath());
178 $this->configLocator->addDrushConfig($environment->drushBasePath());
182 * Start code coverage collection
184 public function startCoverage()
186 if ($coverage_file = $this->preflightArgs->coverageFile()) {
187 // TODO: modernize code coverage handling
188 drush_set_context('DRUSH_CODE_COVERAGE', $coverage_file);
189 xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE);
190 register_shutdown_function('drush_coverage_shutdown');
194 public function createInput()
196 return $this->preflightArgs->createInput();
199 public function getCommandFilePaths()
201 // Find all of the available commandfiles, save for those that are
202 // provided by modules in the selected site; those will be added
204 return $this->configLocator->getCommandFilePaths($this->preflightArgs->commandPaths(), $this->drupalFinder()->getDrupalRoot());
207 public function loadSiteAutoloader()
209 return $this->environment()->loadSiteAutoloader($this->drupalFinder()->getDrupalRoot());
212 public function config()
214 return $this->configLocator->config();
220 * True if the request was successfully redispatched remotely. False if the request should proceed.
222 public function preflight($argv)
224 // Fail fast if there is anything in our environment that does not check out
225 $this->verify->verify($this->environment);
227 // Get the preflight args and begin collecting configuration files.
228 $this->preflightArgs = $this->preflightArgs($argv);
229 $this->prepareConfig($this->environment);
231 // Now that we know the value, set debug flag.
232 $this->logger()->setDebug($this->preflightArgs->get(PreflightArgs::DEBUG));
234 // Do legacy initialization (load static includes, define old constants, etc.)
237 // Start code coverage
238 $this->startCoverage();
240 // Get the config files provided by prepareConfig()
241 $config = $this->config();
243 // Copy items from the preflight args into configuration.
244 // This will also load certain config values into the preflight args.
245 $this->preflightArgs->applyToConfig($config);
247 // Determine the local site targeted, if any.
248 // Extend configuration and alias files to include files in
250 $root = $this->findSelectedSite();
251 $this->configLocator->addSitewideConfig($root);
252 $this->configLocator->setComposerRoot($this->drupalFinder()->getComposerRoot());
254 // Look up the locations where alias files may be found.
255 $paths = $this->configLocator->getSiteAliasPaths($this->preflightArgs->aliasPaths(), $this->environment);
257 // Configure alias manager.
258 $aliasFileLoader = new \Drush\SiteAlias\SiteAliasFileLoader();
259 $this->aliasManager = (new SiteAliasManager($aliasFileLoader))->addSearchLocations($paths);
260 $this->aliasManager->setReferenceData($config->export());
262 // Find the local site
263 $siteLocator = new PreflightSiteLocator($this->aliasManager);
264 $selfAliasRecord = $siteLocator->findSite($this->preflightArgs, $this->environment, $root);
266 // If we did not find a local site, then we are destined to fail
267 // UNLESS RedispatchToSiteLocal::redispatchIfSiteLocalDrush takes over.
268 // Before we try to redispatch to the site-local Drush, though, we must
269 // initiaze the alias manager & c. based on any alias record we did find.
270 if ($selfAliasRecord) {
271 $this->aliasManager->setSelf($selfAliasRecord);
272 $this->configLocator->addAliasConfig($selfAliasRecord->exportConfig());
274 // Process the selected alias. This might change the selected site,
275 // so we will add new site-wide config location for the new root.
276 $root = $this->setSelectedSite($selfAliasRecord->localRoot());
279 // Now that we have our final Drupal root, check to see if there is
280 // a site-local Drush. If there is, we will redispatch to it.
281 // NOTE: termination handlers have not been set yet, so it is okay
282 // to exit early without taking special action.
283 $status = RedispatchToSiteLocal::redispatchIfSiteLocalDrush($argv, $root, $this->environment->vendorPath(), $this->logger()) ;
284 if ($status !== false) {
288 // If the site locator couldn't find a local site, and we did not
289 // redispatch to a site-local Drush, then we cannot continue.
290 // This can happen when using Drush 9 to call a site-local Drush 8
291 // using an alias record that is only defined in a Drush 8 format.
292 if (!$selfAliasRecord) {
293 // Note that PreflightSiteLocator::findSite only returns 'false'
294 // when preflightArgs->alias() returns an alias name. In all other
295 // instances we will get an alias record, even if it is only a
296 // placeholder 'self' with the root holding the cwd.
297 $aliasName = $this->preflightArgs->alias();
298 throw new \Exception("The alias $aliasName could not be found.");
301 // If we did not redispatch, then add the site-wide config for the
302 // new root (if the root did in fact change) and continue.
303 $this->configLocator->addSitewideConfig($root);
305 // Remember the paths to all the files we loaded, so that we can
306 // report on it from Drush status or wherever else it may be needed.
307 $configFilePaths = $this->configLocator->configFilePaths();
308 $config->set('runtime.config.paths', $configFilePaths);
309 $this->logger()->log(dt('Config paths: ' . implode(',', $configFilePaths)));
310 $this->logger()->log(dt('Alias paths: ' . implode(',', $paths)));
312 // We need to check the php minimum version again, in case anyone
313 // has set it to something higher in one of the config files we loaded.
314 $this->verify->confirmPhpVersion($config->get('drush.php.minimum-version'));
320 * Find the site the user selected based on --root or cwd. If neither of
321 * those result in a site, then we will fall back to the vendor path.
323 protected function findSelectedSite()
325 // TODO: If we want to support ONLY site-local Drush (which is
326 // DIFFERENT than --local), then skip the call to `$preflightArgs->selectedSite`
327 // and just assign `false` to $selectedRoot.
329 // Try two approaches.
330 $selectedRoot = $this->preflightArgs->selectedSite($this->environment->cwd());
331 $fallBackPath = $this->preflightArgs->selectedSite(DRUSH_COMMAND);
332 return $this->setSelectedSite($selectedRoot, $fallBackPath);
336 * Use the DrupalFinder to locate the Drupal Root + Composer Root at
337 * the selected root, or, if nothing is found there, at a fallback path.
339 * @param string $selectedRoot The location to being searching for a site
340 * @param string|bool $fallbackPath The secondary location to search (usualy the vendor director)
342 protected function setSelectedSite($selectedRoot, $fallbackPath = false)
344 if ($selectedRoot || $fallbackPath) {
345 $foundRoot = $this->drupalFinder->locateRoot($selectedRoot);
346 if (!$foundRoot && $fallbackPath) {
347 $this->drupalFinder->locateRoot($fallbackPath);
349 return $this->drupalFinder()->getDrupalRoot();
354 * Return the Drupal Finder
356 * @return DrupalFinder
358 public function drupalFinder()
360 return $this->drupalFinder;
364 * Return the alias manager
366 * @return SiteAliasManager
368 public function aliasManager()
370 return $this->aliasManager;
374 * Return the environment
376 * @return Environment
378 public function environment()
380 return $this->environment;