Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create a service adapter for drush.services.yml #5553

Merged
merged 31 commits into from
May 11, 2023
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
8b5789f
Add a basic Drush service adapter. Feed it from the existing drush.se…
greg-1-anderson May 4, 2023
fb89bd4
Use our own service discovery class instead of Symfony DI container c…
greg-1-anderson May 5, 2023
6b47912
Code style
greg-1-anderson May 5, 2023
62a9682
Fix module command discovery
greg-1-anderson May 6, 2023
705f2fd
Fix typo in Console command handling
greg-1-anderson May 6, 2023
9e02f9a
Rename DrushServiceFinder to LegacyServiceFinder and add ServiceManag…
greg-1-anderson May 8, 2023
51834e0
Code style
greg-1-anderson May 8, 2023
f993888
Move ModuleGeneratorTest to the functional tests, because integration…
greg-1-anderson May 8, 2023
dc97db7
Merge branch '12.x' into drush-service-adapter
greg-1-anderson May 8, 2023
aa661d3
Skip failing archive:restore test.
greg-1-anderson May 8, 2023
ece54ea
Simplify LegacyServiceInstantiator; remove unnecessary containers.
greg-1-anderson May 8, 2023
674207b
Code style
greg-1-anderson May 8, 2023
2025668
Remove unused class DrushServiceModifier, and also remove some unused…
greg-1-anderson May 9, 2023
36cc20f
Remove container test, because Drush is no longer involved with conta…
greg-1-anderson May 9, 2023
dbcdbbb
Move PSR-4 command discovery out of Generate command and into the Ser…
greg-1-anderson May 9, 2023
da8f93b
Remove unused FindCommandsCompilerPass
greg-1-anderson May 9, 2023
170b5ad
Move command discovery to the service manager.
greg-1-anderson May 9, 2023
c74ec7e
Code style
greg-1-anderson May 9, 2023
077f123
Fix typo in LegacyServiceFinder
greg-1-anderson May 9, 2023
971aff9
Use module handler instead of container.modules in DrupalBoot8.
greg-1-anderson May 9, 2023
e5fad4e
Move module discovery methods to the service manager
greg-1-anderson May 9, 2023
c9428ba
Move discovery code out of DrupalKernelTrait and into LegacyServiceIn…
greg-1-anderson May 10, 2023
c979e3c
Code style
greg-1-anderson May 10, 2023
ebfa31f
Merge branch '12.x' into drush-service-adapter
greg-1-anderson May 11, 2023
503fa6f
Move bootstrap classes from Application to ServiceManager
greg-1-anderson May 11, 2023
d59b343
Declare FilterHooks in ServiceManager
greg-1-anderson May 11, 2023
f003f14
Explicitly instantiate command info alterers
greg-1-anderson May 11, 2023
b092149
Add some docblock comments
greg-1-anderson May 11, 2023
ce2ce5d
Throw Symfony ParameterNotFoundException when service cannot be initi…
greg-1-anderson May 11, 2023
d3998b7
Merge branch '12.x' into drush-service-adapter
greg-1-anderson May 11, 2023
c598710
Typehints and docblock comments for service manager
greg-1-anderson May 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 13 additions & 102 deletions src/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,15 @@

use Composer\Autoload\ClassLoader;
use Consolidation\AnnotatedCommand\AnnotatedCommand;
use Consolidation\AnnotatedCommand\CommandFileDiscovery;
use Consolidation\Filter\Hooks\FilterHooks;
use Consolidation\SiteAlias\SiteAliasManager;
use Drush\Boot\BootstrapManager;
use Drush\Boot\DrupalBootLevels;
use Drush\Command\RemoteCommandProxy;
use Drush\Commands\DrushCommands;
use Drush\Config\ConfigAwareTrait;
use Drush\Runtime\RedispatchHook;
use Drush\Runtime\TildeExpansionHook;
use Grasmash\YamlCli\Command\GetValueCommand;
use Grasmash\YamlCli\Command\LintCommand;
use Grasmash\YamlCli\Command\UnsetKeyCommand;
use Grasmash\YamlCli\Command\UpdateKeyCommand;
use Grasmash\YamlCli\Command\UpdateValueCommand;
use Drush\Runtime\ServiceManager;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Robo\ClassDiscovery\RelativeNamespaceDiscovery;
Expand Down Expand Up @@ -58,6 +52,9 @@ class Application extends SymfonyApplication implements LoggerAwareInterface, Co
/** @var TildeExpansionHook */
protected $tildeExpansionHook;

/** @var ServiceManager */
protected $serviceManager;

/** @var string[] */
protected array $bootstrapCommandClasses = [];

Expand Down Expand Up @@ -137,6 +134,11 @@ public function setTildeExpansionHook(TildeExpansionHook $tildeExpansionHook)
$this->tildeExpansionHook = $tildeExpansionHook;
}

public function setServiceManager(ServiceManager $serviceManager)
{
$this->serviceManager = $serviceManager;
}

/**
* Return the framework uri selected by the user.
*/
Expand Down Expand Up @@ -325,12 +327,12 @@ public function configureAndRegisterCommands(InputInterface $input, OutputInterf
$this->configureIO($input, $output);

// Directly add the yaml-cli commands.
$this->addCommands($this->discoverYamlCliCommands());
$this->addCommands($this->serviceManager->instantiateYamlCliCommands());

$commandClasses = array_unique(array_merge(
$this->discoverCommandsFromConfiguration(),
$this->discoverCommands($commandfileSearchpath, '\Drush'),
$this->discoverPsr4Commands($classLoader),
$this->serviceManager->discoverCommandsFromConfiguration(),
$this->serviceManager->discoverCommands($commandfileSearchpath, '\Drush'),
$this->serviceManager->discoverPsr4Commands(),
[FilterHooks::class]
));

Expand All @@ -356,74 +358,6 @@ public function configureAndRegisterCommands(InputInterface $input, OutputInterf
Robo::register($this, $commandClasses);
}

protected function discoverCommandsFromConfiguration()
{
$commandList = [];
foreach ($this->config->get('drush.commands', []) as $key => $value) {
if (is_numeric($key)) {
$classname = $value;
$commandList[] = $classname;
} else {
$classname = ltrim($key, '\\');
$commandList[$value] = $classname;
}
}
$this->loadCommandClasses($commandList);
return array_values($commandList);
}

/**
* Ensure that any discovered class that is not part of the autoloader
* is, in fact, included.
*/
protected function loadCommandClasses($commandClasses)
{
foreach ($commandClasses as $file => $commandClass) {
if (!class_exists($commandClass)) {
include $file;
}
}
}

/**
* Discovers command classes.
*/
protected function discoverCommands(array $directoryList, string $baseNamespace): array
{
$discovery = new CommandFileDiscovery();
$discovery
->setIncludeFilesAtBase(true)
->setSearchDepth(3)
->ignoreNamespacePart('contrib', 'Commands')
->ignoreNamespacePart('custom', 'Commands')
->ignoreNamespacePart('src')
->setSearchLocations(['Commands', 'Hooks', 'Generators'])
->setSearchPattern('#.*(Command|Hook|Generator)s?.php$#');
$baseNamespace = ltrim($baseNamespace, '\\');
$commandClasses = $discovery->discover($directoryList, $baseNamespace);
$this->loadCommandClasses($commandClasses);
return array_values($commandClasses);
}

/**
* Discovers commands that are PSR4 auto-loaded.
*/
protected function discoverPsr4Commands(ClassLoader $classLoader): array
{
$classes = (new RelativeNamespaceDiscovery($classLoader))
->setRelativeNamespace('Drush\Commands')
->setSearchPattern('/.*DrushCommands\.php$/')
->getClasses();

return array_filter($classes, function (string $class): bool {
$reflectionClass = new \ReflectionClass($class);
return $reflectionClass->isSubclassOf(DrushCommands::class)
&& !$reflectionClass->isAbstract()
&& !$reflectionClass->isInterface()
&& !$reflectionClass->isTrait();
});
}

/**
* Renders a caught exception. Omits the command docs at end.
*/
Expand All @@ -443,27 +377,4 @@ public function renderThrowable(\Throwable $e, OutputInterface $output): void

$this->doRenderThrowable($e, $output);
}

private function discoverYamlCliCommands(): array
{
$classes_yaml = [
GetValueCommand::class,
LintCommand::class,
UpdateKeyCommand::class,
UnsetKeyCommand::class,
UpdateValueCommand::class
];

foreach ($classes_yaml as $class_yaml) {
/** @var Command $instance */
$instance = new $class_yaml();
// Namespace the commands.
$name = $instance->getName();
$instance->setName('yaml:' . $name);
$instance->setAliases(['y:' . $name]);
$instance->setHelp('See https://github.com/grasmash/yaml-cli for a README and bug reports.');
$instances[] = $instance;
}
return $instances;
}
}
117 changes: 52 additions & 65 deletions src/Boot/DrupalBoot8.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@
use Drupal\Core\Session\AnonymousUserSession;
use Drush\Config\ConfigLocator;
use Drush\Drupal\DrushLoggerServiceProvider;
use Drush\Drupal\DrushServiceModifier;
use Drush\Drush;
use Psr\Log\LoggerInterface;
use Symfony\Component\Filesystem\Path;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Consolidation\AnnotatedCommand\CommandFileDiscovery;
use Robo\Robo;
use Drush\Runtime\LegacyServiceInstantiator;
use Drush\Runtime\LegacyServiceFinder;

class DrupalBoot8 extends DrupalBoot implements AutoloaderAwareInterface
{
Expand All @@ -29,6 +30,10 @@ class DrupalBoot8 extends DrupalBoot implements AutoloaderAwareInterface
protected ?DrupalKernelInterface $kernel = null;
protected Request $request;

public function __construct(protected $serviceManager)
{
}

public function getRequest(): Request
{
return $this->request;
Expand Down Expand Up @@ -217,9 +222,6 @@ public function bootstrapDrupalConfiguration(BootstrapManager $manager, Annotati
$allow_dumping = $kernel !== Kernels::UPDATE;
/** @var DrupalKernelInterface kernel */
$this->kernel = $kernel_factory($request, $classloader, 'prod', $allow_dumping, $manager->getRoot());
// Include Drush services in the container.
// @see Drush\Drupal\DrupalKernel::addServiceModifier()
$this->kernel->addServiceModifier(new DrushServiceModifier());

// Unset drupal error handler and restore Drush's one.
restore_error_handler();
Expand Down Expand Up @@ -247,55 +249,70 @@ public function bootstrapDrupalFull(BootstrapManager $manager): void
public function addDrupalModuleDrushCommands($manager): void
{
$application = Drush::getApplication();
$drushContainer = Drush::getContainer();

$this->logger->debug(dt("Loading drupal module drush commands & etc.", []));

// We have to get the service command list from the container, because
// it is constructed in an indirect way during the container initialization.
// The upshot is that the list of console commands is not available
// until after $kernel->boot() is called.
$container = \Drupal::getContainer();
$moduleHandler = \Drupal::moduleHandler();

// Legacy service adapters for drush.services.yml files.
$serviceFinder = new LegacyServiceFinder($moduleHandler, Drush::config());
$drushServiceFiles = $serviceFinder->getDrushServiceFiles();
$legacyServiceInstantiator = new LegacyServiceInstantiator($container);
$legacyServiceInstantiator->loadServiceFiles($drushServiceFiles);

// Find the containerless commands, generators and command info alterers
$bootstrapCommandClasses = $application->bootstrapCommandClasses();
$commandInfoAlterers = [];
foreach ($container->getParameter('container.modules') as $moduleId => $moduleInfo) {
$path = dirname(DRUPAL_ROOT . '/' . $moduleInfo['pathname']) . '/src/Drush/';
$commandsInThisModule = $this->discoverModuleCommands([$path], "\\Drupal\\" . $moduleId . "\\Drush");
foreach ($moduleHandler->getModuleList() as $moduleId => $extension) {
$path = DRUPAL_ROOT . '/' . $extension->getPath() . '/src/Drush/';
$commandsInThisModule = $this->serviceManager->discoverModuleCommands([$path], "\\Drupal\\" . $moduleId . "\\Drush");
// TODO: Maybe $bootstrapCommandClasses could use a better name.
// These are commandhandlers that have static create factory methods.
$bootstrapCommandClasses = array_merge($bootstrapCommandClasses, $commandsInThisModule);
$commandInfoAlterersInThisModule = $this->discoverCommandInfoAlterers([$path], "\\Drupal\\" . $moduleId . "\\Drush");
// TODO: Support PSR-4 command info alterers, like bootstrapCommandClasses?
$commandInfoAlterersInThisModule = $this->serviceManager->discoverModuleCommandInfoAlterers([$path], "\\Drupal\\" . $moduleId . "\\Drush");
$commandInfoAlterers = array_merge($commandInfoAlterers, $commandInfoAlterersInThisModule);
}

// Find the command info alterers in Drush services.
if ($container->has(DrushServiceModifier::DRUSH_COMMAND_INFO_ALTERER_SERVICES)) {
$serviceCommandInfoAltererList = $container->get(DrushServiceModifier::DRUSH_COMMAND_INFO_ALTERER_SERVICES);
$commandFactory = Drush::commandFactory();
$commandInfoAlterers = array_merge($commandInfoAlterers, $serviceCommandInfoAltererList->getCommandList());
}
// Look up the generators from the legacy service instantiator and inject
// them into the service manager. The `generate` command will get the
// generators from the service manager.
$this->serviceManager->injectGenerators($legacyServiceInstantiator->taggedServices('drush.generator.v3'));

// Set the command info alterers.
foreach ($serviceCommandInfoAltererList->getCommandList() as $altererHandler) {
// Find the command info alterers in Drush services.
$commandFactory = Drush::commandFactory();
// TODO: $commandInfoAlterers are class names, taggedServices are
// instantiated classes. Let's not mix these.
$commandInfoAlterers = array_merge($commandInfoAlterers, $legacyServiceInstantiator->taggedServices('drush.command_info_alterer'));

// Set the command info alterers. We must do this prior to calling
// Robo::register to add any commands, as that is the point where the
// alteration will happen.
foreach ($commandInfoAlterers as $altererHandler) {
$commandFactory->addCommandInfoAlterer($altererHandler);
$this->logger->debug(dt('Commands are potentially altered in !class.', ['!class' => get_class($altererHandler)]));
}

// Register the Drush Symfony Console commands found in Drush services
if ($container->has(DrushServiceModifier::DRUSH_CONSOLE_SERVICES)) {
$serviceCommandList = $container->get(DrushServiceModifier::DRUSH_CONSOLE_SERVICES);
foreach ($serviceCommandList->getCommandList() as $command) {
$manager->inflect($command);
$this->logger->debug(dt('Add a command: !name', ['!name' => $command->getName()]));
$application->add($command);
}
$drushServicesConsoleCommands = $legacyServiceInstantiator->taggedServices('console.command');
foreach ($drushServicesConsoleCommands as $command) {
$manager->inflect($command);
$this->logger->debug(dt('Add a command: !name', ['!name' => $command->getName()]));
$application->add($command);
}

// Do the same thing with the annotation commands.
if ($container->has(DrushServiceModifier::DRUSH_COMMAND_SERVICES)) {
$serviceCommandList = $container->get(DrushServiceModifier::DRUSH_COMMAND_SERVICES);
foreach ($serviceCommandList->getCommandList() as $commandHandler) {
$manager->inflect($commandHandler);
$this->logger->debug(dt('Add a commandfile class: !name', ['!name' => get_class($commandHandler)]));
Robo::register($application, $commandHandler);
}
// Add annotation commands from drush.services.yml
$drushServicesCommandHandlers = $legacyServiceInstantiator->taggedServices('drush.command');
foreach ($drushServicesCommandHandlers as $commandHandler) {
$manager->inflect($commandHandler);
$this->logger->debug(dt('Add a commandfile class: !name', ['!name' => get_class($commandHandler)]));
Robo::register($application, $commandHandler);
}

// Finally, instantiate all of the classes we discovered in
Expand All @@ -309,13 +326,15 @@ public function addDrupalModuleDrushCommands($manager): void
// of double-instantiating Drush service commands, if anyone decided
// to put those in the same namespace (\Drupal\modulename\Drush\Commands)
if ($this->hasStaticCreateFactory($class)) {
$commandHandler = $class::create($container);
$commandHandler = $class::create($container, $drushContainer);
}
} catch (\Exception $e) {
}
// Fail silently if the command handler could not be
// instantiated, e.g. if it tries to fetch services from
// a module that has not been enabled.
// a module that has not been enabled. Note that Robo::register
// can accept either Annotated Command command handlers or
// Symfony Console Command objects.
if ($commandHandler) {
$manager->inflect($commandHandler);
Robo::register($application, $commandHandler);
Expand All @@ -333,38 +352,6 @@ protected function hasStaticCreateFactory($class)
return $reflectionMethod->isStatic();
}

/**
* Discover module commands. This is the preferred way to find module
* commands in Drush 12+.
*/
protected function discoverModuleCommands(array $directoryList, string $baseNamespace): array
{
$discovery = new CommandFileDiscovery();
$discovery
->setIncludeFilesAtBase(true)
->setSearchDepth(1)
->ignoreNamespacePart('src')
->setSearchLocations(['Commands', 'Hooks', 'Generators'])
->setSearchPattern('#.*(Command|Hook|Generator)s?.php$#');
$baseNamespace = ltrim($baseNamespace, '\\');
$commandClasses = $discovery->discover($directoryList, $baseNamespace);
return array_values($commandClasses);
}

protected function discoverCommandInfoAlterers(array $directoryList, string $baseNamespace): array
{
$discovery = new CommandFileDiscovery();
$discovery
->setIncludeFilesAtBase(true)
->setSearchDepth(1)
->ignoreNamespacePart('src')
->setSearchLocations(['CommandInfoAlterers'])
->setSearchPattern('#.*CommandInfoAlterer.php$#');
$baseNamespace = ltrim($baseNamespace, '\\');
$commandClasses = $discovery->discover($directoryList, $baseNamespace);
return array_values($commandClasses);
}

/**
* {@inheritdoc}
*/
Expand Down
7 changes: 2 additions & 5 deletions src/Commands/core/MkCommands.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
use Consolidation\AnnotatedCommand\AnnotationData;
use Consolidation\SiteAlias\SiteAliasManagerAwareTrait;
use Drush\Attributes as CLI;
use Drush\Boot\AutoloaderAwareInterface;
use Drush\Boot\AutoloaderAwareTrait;
use Drush\Boot\DrupalBootLevels;
use Drush\Commands\DrushCommands;
use Drush\Commands\help\HelpCLIFormatter;
Expand All @@ -27,10 +25,9 @@
use Symfony\Component\Filesystem\Path;
use Symfony\Component\Yaml\Yaml;

final class MkCommands extends DrushCommands implements SiteAliasManagerAwareInterface, AutoloaderAwareInterface
final class MkCommands extends DrushCommands implements SiteAliasManagerAwareInterface
{
use SiteAliasManagerAwareTrait;
use AutoloaderAwareTrait;

/**
* Build a Markdown document for each Drush command/generator that is available on a site.
Expand All @@ -56,7 +53,7 @@ public function docs(): void
$destination = 'generators';
$destination_path = Path::join($dir_root, 'docs', $destination);
$this->prepare($destination_path);
$application_generate = (new ApplicationFactory(\Drupal::getContainer(), $this->autoloader(), $this->logger()))->create();
$application_generate = (new ApplicationFactory(\Drupal::getContainer(), $this->logger()))->create();
$all = $this->createAnnotatedCommands($application_generate, Drush::getApplication());
$namespaced = ListCommands::categorize($all);
[$nav_generators, $pages_generators, $map_generators] = $this->writeContentFilesAndBuildNavAndBuildRedirectMap($namespaced, $destination, $dir_root, $destination_path);
Expand Down
Loading