ca46a3ab7b88b1b631e740f9162a830201804494
[yaffs-website] / vendor / drush / drush / src / Application.php
1 <?php
2 namespace Drush;
3
4 use Consolidation\AnnotatedCommand\AnnotatedCommand;
5 use Consolidation\AnnotatedCommand\CommandFileDiscovery;
6 use Drush\Boot\BootstrapManager;
7 use Drush\Runtime\TildeExpansionHook;
8 use Consolidation\SiteAlias\SiteAliasManager;
9 use Drush\Log\LogLevel;
10 use Drush\Command\RemoteCommandProxy;
11 use Drush\Runtime\RedispatchHook;
12 use Robo\Common\ConfigAwareTrait;
13 use Robo\Contract\ConfigAwareInterface;
14 use Symfony\Component\Console\Application as SymfonyApplication;
15 use Symfony\Component\Console\Input\InputOption;
16 use Symfony\Component\Console\Exception\CommandNotFoundException;
17 use Symfony\Component\Console\Input\InputInterface;
18 use Symfony\Component\Console\Output\OutputInterface;
19 use Psr\Log\LoggerAwareInterface;
20 use Psr\Log\LoggerAwareTrait;
21
22 /**
23  * Our application object
24  *
25  * Note: Implementing *AwareInterface here does NOT automatically cause
26  * that corresponding service to be injected into the Application. This
27  * is because the application object is created prior to the DI container.
28  * See DependencyInjection::injectApplicationServices() to add more services.
29  */
30 class Application extends SymfonyApplication implements LoggerAwareInterface, ConfigAwareInterface
31 {
32     use LoggerAwareTrait;
33     use ConfigAwareTrait;
34
35     /** @var BootstrapManager */
36     protected $bootstrapManager;
37
38     /** @var SiteAliasManager */
39     protected $aliasManager;
40
41     /** @var RedispatchHook */
42     protected $redispatchHook;
43
44     /** @var TildeExpansionHook */
45     protected $tildeExpansionHook;
46
47     /**
48      * Add global options to the Application and their default values to Config.
49      */
50     public function configureGlobalOptions()
51     {
52         $this->getDefinition()
53             ->addOption(
54                 new InputOption('--debug', 'd', InputOption::VALUE_NONE, 'Equivalent to -vv')
55             );
56
57         $this->getDefinition()
58             ->addOption(
59                 new InputOption('--yes', 'y', InputOption::VALUE_NONE, 'Equivalent to --no-interaction.')
60             );
61
62         // Note that -n belongs to Symfony Console's --no-interaction.
63         $this->getDefinition()
64             ->addOption(
65                 new InputOption('--no', null, InputOption::VALUE_NONE, 'Cancels at any confirmation prompt.')
66             );
67
68         $this->getDefinition()
69             ->addOption(
70                 new InputOption('--remote-host', null, InputOption::VALUE_REQUIRED, 'Run on a remote server.')
71             );
72
73         $this->getDefinition()
74             ->addOption(
75                 new InputOption('--remote-user', null, InputOption::VALUE_REQUIRED, 'The user to use in remote execution.')
76             );
77
78         $this->getDefinition()
79             ->addOption(
80                 new InputOption('--root', '-r', InputOption::VALUE_REQUIRED, 'The Drupal root for this site.')
81             );
82
83
84         $this->getDefinition()
85             ->addOption(
86                 new InputOption('--uri', '-l', InputOption::VALUE_REQUIRED, 'Which multisite from the selected root to use.')
87             );
88
89         $this->getDefinition()
90             ->addOption(
91                 new InputOption('--simulate', null, InputOption::VALUE_NONE, 'Run in simulated mode (show what would have happened).')
92             );
93
94         // TODO: Implement handling for 'pipe'
95         $this->getDefinition()
96             ->addOption(
97                 new InputOption('--pipe', null, InputOption::VALUE_NONE, 'Select the canonical script-friendly output format.')
98             );
99
100         $this->getDefinition()
101             ->addOption(
102                 new InputOption('--define', '-D', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Define a configuration item value.', [])
103             );
104     }
105
106     public function bootstrapManager()
107     {
108         return $this->bootstrapManager;
109     }
110
111     public function setBootstrapManager(BootstrapManager $bootstrapManager)
112     {
113         $this->bootstrapManager = $bootstrapManager;
114     }
115
116     public function aliasManager()
117     {
118         return $this->aliasManager;
119     }
120
121     public function setAliasManager($aliasManager)
122     {
123         $this->aliasManager = $aliasManager;
124     }
125
126     public function setRedispatchHook(RedispatchHook $redispatchHook)
127     {
128         $this->redispatchHook = $redispatchHook;
129     }
130
131     public function setTildeExpansionHook(TildeExpansionHook $tildeExpansionHook)
132     {
133         $this->tildeExpansionHook = $tildeExpansionHook;
134     }
135
136     /**
137      * Return the framework uri selected by the user.
138      */
139     public function getUri()
140     {
141         if (!$this->bootstrapManager) {
142             return 'default';
143         }
144         return $this->bootstrapManager->getUri();
145     }
146
147     /**
148      * If the user did not explicitly select a site URI,
149      * then pick an appropriate site from the cwd.
150      */
151     public function refineUriSelection($cwd)
152     {
153         if (!$this->bootstrapManager || !$this->aliasManager) {
154             return;
155         }
156         $selfAliasRecord = $this->aliasManager->getSelf();
157         if (!$selfAliasRecord->hasRoot() && !$this->bootstrapManager()->drupalFinder()->getDrupalRoot()) {
158             return;
159         }
160         $uri = $selfAliasRecord->uri();
161
162         if (empty($uri)) {
163             $uri = $this->selectUri($cwd);
164             $selfAliasRecord->setUri($uri);
165             $this->aliasManager->setSelf($selfAliasRecord);
166         }
167         // Update the uri in the bootstrap manager
168         $this->bootstrapManager->setUri($uri);
169     }
170
171     /**
172      * Select a URI to use for the site, based on directory or config.
173      */
174     public function selectUri($cwd)
175     {
176         $uri = $this->config->get('options.uri');
177         if ($uri) {
178             return $uri;
179         }
180         return $this->bootstrapManager()->selectUri($cwd);
181     }
182
183     /**
184      * @inheritdoc
185      */
186     public function find($name)
187     {
188         if (empty($name)) {
189             return;
190         }
191         $command = $this->bootstrapAndFind($name);
192         // Avoid exception when help is being built by https://github.com/bamarni/symfony-console-autocomplete.
193         // @todo Find a cleaner solution.
194         if (Drush::config()->get('runtime.argv')[1] !== 'help') {
195             $this->checkObsolete($command);
196         }
197         return $command;
198     }
199
200     /**
201      * Look up a command. Bootstrap further if necessary.
202      */
203     protected function bootstrapAndFind($name)
204     {
205         try {
206             return parent::find($name);
207         } catch (CommandNotFoundException $e) {
208             // Is the unknown command destined for a remote site?
209             if ($this->aliasManager) {
210                 $selfAlias = $this->aliasManager->getSelf();
211                 if ($selfAlias->isRemote()) {
212                     $command = new RemoteCommandProxy($name, $this->redispatchHook);
213                     $command->setApplication($this);
214                     return $command;
215                 }
216             }
217             // If we have no bootstrap manager, then just re-throw
218             // the exception.
219             if (!$this->bootstrapManager) {
220                 throw $e;
221             }
222
223             $this->logger->log(LogLevel::DEBUG, 'Bootstrap further to find {command}', ['command' => $name]);
224             $this->bootstrapManager->bootstrapMax();
225             $this->logger->log(LogLevel::DEBUG, 'Done with bootstrap max in Application::find(): trying to find {command} again.', ['command' => $name]);
226
227             if (!$this->bootstrapManager()->hasBootstrapped(DRUSH_BOOTSTRAP_DRUPAL_ROOT)) {
228                 // Unable to progress in the bootstrap. Give friendly error message.
229                 throw new CommandNotFoundException(dt('Command !command was not found. Pass --root or a @siteAlias in order to run Drupal-specific commands.', ['!command' => $name]));
230             }
231
232             // Try to find it again, now that we bootstrapped as far as possible.
233             try {
234                 return parent::find($name);
235             } catch (CommandNotFoundException $e) {
236                 if (!$this->bootstrapManager()->hasBootstrapped(DRUSH_BOOTSTRAP_DRUPAL_DATABASE)) {
237                     // Unable to bootstrap to DB. Give targetted error message.
238                     throw new CommandNotFoundException(dt('Command !command was not found. Drush was unable to query the database. As a result, many commands are unavailable. Re-run your command with --debug to see relevant log messages.', ['!command' => $name]));
239                 }
240                 if (!$this->bootstrapManager()->hasBootstrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) {
241                     // Unable to fully bootstrap. Give targetted error message.
242                     throw new CommandNotFoundException(dt('Command !command was not found. Drush successfully connected to the database but was unable to fully bootstrap your site. As a result, many commands are unavailable. Re-run your command with --debug to see relevant log messages.', ['!command' => $name]));
243                 } else {
244                     // We fully bootstrapped but still could not find command. Rethrow.
245                     throw $e;
246                 }
247             }
248         }
249     }
250
251     /**
252      * If a command is annotated @obsolete, then we will throw an exception
253      * immediately; the command will not run, and no hooks will be called.
254      */
255     protected function checkObsolete($command)
256     {
257         if (!$command instanceof AnnotatedCommand) {
258             return;
259         }
260
261         $annotationData = $command->getAnnotationData();
262         if (!$annotationData->has('obsolete')) {
263             return;
264         }
265
266         $obsoleteMessage = $command->getDescription();
267         throw new \Exception($obsoleteMessage);
268     }
269
270     /**
271      * @inheritdoc
272      *
273      * Note: This method is called twice, as we wish to configure the IO
274      * objects earlier than Symfony does. We could define a boolean class
275      * field to record when this method is called, and do nothing on the
276      * second call. At the moment, the work done here is trivial, so we let
277      * it happen twice.
278      */
279     protected function configureIO(InputInterface $input, OutputInterface $output)
280     {
281         // Do default Symfony confguration.
282         parent::configureIO($input, $output);
283
284         // Process legacy Drush global options.
285         // Note that `getParameterOption` returns the VALUE of the option if
286         // it is found, or NULL if it finds an option with no value.
287         if ($input->getParameterOption(['--yes', '-y', '--no', '-n'], false, true) !== false) {
288             $input->setInteractive(false);
289         }
290         // Symfony will set these later, but we want it set upfront
291         if ($input->getParameterOption(['--verbose', '-v'], false, true) !== false) {
292             $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
293         }
294         // We are not using "very verbose", but set this for completeness
295         if ($input->getParameterOption(['-vv'], false, true) !== false) {
296             $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
297         }
298         // Use -vvv of --debug for even more verbose logging.
299         if ($input->getParameterOption(['--debug', '-d', '-vvv'], false, true) !== false) {
300             $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
301         }
302     }
303
304     /**
305      * Configure the application object and register all of the commandfiles
306      * available in the search paths provided via Preflight
307      */
308     public function configureAndRegisterCommands(InputInterface $input, OutputInterface $output, $commandfileSearchpath)
309     {
310         // Symfony will call this method for us in run() (it will be
311         // called again), but we want to call it up-front, here, so that
312         // our $input and $output objects have been appropriately
313         // configured in case we wish to use them (e.g. for logging) in
314         // any of the configuration steps we do here.
315         $this->configureIO($input, $output);
316
317         $discovery = $this->commandDiscovery();
318         $commandClasses = $discovery->discover($commandfileSearchpath, '\Drush');
319
320         $this->loadCommandClasses($commandClasses);
321
322         // Uncomment the lines below to use Console's built in help and list commands.
323         // unset($commandClasses[__DIR__ . '/Commands/help/HelpCommands.php']);
324         // unset($commandClasses[__DIR__ . '/Commands/help/ListCommands.php']);
325
326         // Use the robo runner to register commands with Symfony application.
327         // This method could / should be refactored in Robo so that we can use
328         // it without creating a Runner object that we would not otherwise need.
329         $runner = new \Robo\Runner();
330         $runner->registerCommandClasses($this, $commandClasses);
331     }
332
333     /**
334      * Ensure that any discovered class that is not part of the autoloader
335      * is, in fact, included.
336      */
337     protected function loadCommandClasses($commandClasses)
338     {
339         foreach ($commandClasses as $file => $commandClass) {
340             if (!class_exists($commandClass)) {
341                 include $file;
342             }
343         }
344     }
345
346     /**
347      * Create a command file discovery object
348      */
349     protected function commandDiscovery()
350     {
351         $discovery = new CommandFileDiscovery();
352         $discovery
353             ->setIncludeFilesAtBase(true)
354             ->setSearchDepth(3)
355             ->ignoreNamespacePart('contrib', 'Commands')
356             ->ignoreNamespacePart('custom', 'Commands')
357             ->ignoreNamespacePart('src')
358             ->setSearchLocations(['Commands', 'Hooks', 'Generators'])
359             ->setSearchPattern('#.*(Command|Hook|Generator)s?.php$#');
360         return $discovery;
361     }
362 }