3 namespace Drush\Drupal;
5 use Composer\Semver\Semver;
6 use Drupal\Core\DependencyInjection\ServiceModifierInterface;
7 use Drupal\Core\Site\Settings;
9 use Drush\Log\LogLevel;
12 * Common functionality for overridden kernels.
14 trait DrupalKernelTrait
16 /** @var ServiceModifierInterface[] */
17 protected $serviceModifiers = [];
20 * Add a service modifier to the container builder.
22 * The container is not compiled until $kernel->boot(), so there is a chance
23 * for clients to add compiler passes et. al. before then.
25 public function addServiceModifier(ServiceModifierInterface $serviceModifier)
27 drush_log(dt("Add service modifier"), LogLevel::DEBUG);
28 $this->serviceModifiers[] = $serviceModifier;
34 protected function getContainerBuilder()
36 drush_log(dt("Get container builder"), LogLevel::DEBUG);
37 $container = parent::getContainerBuilder();
38 foreach ($this->serviceModifiers as $serviceModifier) {
39 $serviceModifier->alter($container);
45 * Initializes the service container.
47 * @return \Symfony\Component\DependencyInjection\ContainerInterface
49 protected function initializeContainer()
51 $container_definition = $this->getCachedContainerDefinition();
53 if ($this->shouldDrushInvalidateContainer()) {
54 // Normally when the container is being rebuilt, the existing
55 // container is still available for use until the newly built one
56 // replaces it. Certain contrib modules rely on services (like State
57 // or the config factory) being available for things like defining
58 // event subscriptions.
59 // @see https://github.com/drush-ops/drush/issues/3123
60 if (isset($container_definition)) {
61 $class = Settings::get('container_base_class', '\Drupal\Core\DependencyInjection\Container');
62 $container = new $class($container_definition);
63 $this->attachSynthetic($container);
64 \Drupal::setContainer($container);
67 $this->invalidateContainer();
69 return parent::initializeContainer();
72 protected function shouldDrushInvalidateContainer()
74 if (empty($this->moduleList) && !$this->containerNeedsRebuild) {
75 $container_definition = $this->getCachedContainerDefinition();
76 foreach ($this->serviceModifiers as $serviceModifier) {
77 if (!$serviceModifier->check($container_definition)) {
88 public function discoverServiceProviders()
90 // Let Drupal discover all of its service providers
91 parent::discoverServiceProviders();
93 // Add those Drush service providers from Drush core that
94 // need references to the Drupal DI container. This includes
95 // Drush commands, and those services needed by those Drush
99 // - We list all of the individual service files we use here.
100 // - These commands are not available until Drupal is bootstrapped.
101 $this->addDrushServiceProvider("_drush__config", DRUSH_BASE_PATH . '/src/Drupal/Commands/config/drush.services.yml');
102 $this->addDrushServiceProvider("_drush__core", DRUSH_BASE_PATH . '/src/Drupal/Commands/core/drush.services.yml');
103 $this->addDrushServiceProvider("_drush__pm", DRUSH_BASE_PATH . '/src/Drupal/Commands/pm/drush.services.yml');
104 $this->addDrushServiceProvider("_drush__sql", DRUSH_BASE_PATH . '/src/Drupal/Commands/sql/drush.services.yml');
106 // TODO: We could potentially also add service providers from:
107 // - DRUSH_BASE_PATH . '/drush/drush.services.yml');
108 // - DRUSH_BASE_PATH . '/../drush/drush.services.yml');
109 // Or, perhaps better yet, from every Drush command directory
110 // (e.g. DRUSH_BASE_PATH/drush/mycmd/drush.services.yml) in
111 // any of these `drush` folders. In order to do this, it is
112 // necessary that the class files in these commands are available
113 // in the autoloader.
115 // Also add Drush services from all modules
116 $module_filenames = $this->getModuleFileNames();
117 // Load each module's serviceProvider class.
118 foreach ($module_filenames as $module => $filename) {
119 $this->addModuleDrushServiceProvider($module, $filename);
124 * Determine whether or not the Drush services.yml file is applicable
125 * for this version of Drush.
127 protected function addModuleDrushServiceProvider($module, $filename)
129 $serviceYmlPath = $this->findModuleDrushServiceProvider($module, dirname($filename));
130 $this->addDrushServiceProvider("_drush.$module", $serviceYmlPath);
133 protected function findModuleDrushServiceProvider($module, $dir)
135 $services = $this->findModuleDrushServiceProviderFromComposer($dir);
137 return $this->findDefaultServicesFile($module, $dir);
139 return $this->findAppropriateServicesFile($module, $services, $dir);
142 protected function findDefaultServicesFile($module, $dir)
144 $result = $dir . "/drush.services.yml";
145 if (!file_exists($result)) {
148 drush_log(dt("!module should have an extra.drush.services section in its composer.json. See http://docs.drush.org/en/master/commands/#specifying-the-services-file.", ['!module' => $module]), LogLevel::DEBUG);
153 * In composer.json, the Drush version constraints will appear
154 * in the 'extra' section like so:
159 * "drush.services.yml": "^9"
164 * There may be multiple drush service files listed; the first
165 * one that has a version constraint that matches the Drush version
168 protected function findModuleDrushServiceProviderFromComposer($dir)
170 $composerJsonPath = "$dir/composer.json";
171 if (!file_exists($composerJsonPath)) {
174 $composerJsonContents = file_get_contents($composerJsonPath);
175 $info = json_decode($composerJsonContents, true);
177 drush_log(dt('Invalid json in {composer}', ['composer' => $composerJsonPath]), LogLevel::WARNING);
180 if (!isset($info['extra']['drush']['services'])) {
183 return $info['extra']['drush']['services'];
186 protected function findAppropriateServicesFile($module, $services, $dir)
188 $version = Drush::getVersion();
189 foreach ($services as $serviceYmlPath => $versionConstraint) {
190 $version = preg_replace('#-dev.*#', '', $version);
191 if (Semver::satisfies($version, $versionConstraint)) {
192 drush_log(dt('Found {services} for {module} Drush commands', ['module' => $module, 'services' => $serviceYmlPath]), LogLevel::DEBUG);
193 return $dir . '/' . $serviceYmlPath;
196 drush_log(dt('{module} has Drush commands, but none of {constraints} match the current Drush version "{version}"', ['module' => $module, 'constraints' => implode(',', $services), 'version' => $version]), LogLevel::DEBUG);
201 * Add a services.yml file if it exists.
203 protected function addDrushServiceProvider($serviceProviderName, $serviceYmlPath)
205 if (file_exists($serviceYmlPath)) {
206 $this->serviceYamls['app'][$serviceProviderName] = $serviceYmlPath;