ComposerPatchesValidator.php
Namespace
Drupal\package_manager\ValidatorFile
-
core/
modules/ package_manager/ src/ Validator/ ComposerPatchesValidator.php
View source
<?php
declare (strict_types=1);
namespace Drupal\package_manager\Validator;
use Composer\Semver\Semver;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Url;
use Drupal\package_manager\ComposerInspector;
use Drupal\package_manager\Event\PreApplyEvent;
use Drupal\package_manager\Event\PreCreateEvent;
use Drupal\package_manager\Event\PreOperationStageEvent;
use Drupal\package_manager\Event\StatusCheckEvent;
use Drupal\package_manager\PathLocator;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Validates the configuration of the cweagans/composer-patches plugin.
*
* To ensure that applied patches remain consistent between the active and
* stage directories, the following rules are enforced if the patcher is
* installed:
* - It must be installed in both places, or in neither of them. It can't, for
* example, be installed in the active directory but not the stage directory
* (or vice versa).
* - It must be one of the project's direct runtime or dev dependencies.
* - It cannot be installed or removed by Package Manager. In other words, it
* must be added to the project at the command line by someone technical
* enough to install and configure it properly.
*
* @internal
* This is an internal part of Package Manager and may be changed or removed
* at any time without warning. External code should not interact with this
* class.
*/
final class ComposerPatchesValidator implements EventSubscriberInterface {
use StringTranslationTrait;
/**
* The name of the plugin being analyzed.
*
* @var string
*/
private const PLUGIN_NAME = 'cweagans/composer-patches';
public function __construct(ModuleHandlerInterface $moduleHandler, ComposerInspector $composerInspector, PathLocator $pathLocator) {
}
/**
* Validates the status of the patcher plugin.
*
* @param \Drupal\package_manager\Event\PreOperationStageEvent $event
* The event object.
*/
public function validate(PreOperationStageEvent $event) : void {
$messages = [];
[
$plugin_installed_in_active,
$is_active_root_requirement,
$active_configuration_ok,
] = $this->computePatcherStatus($this->pathLocator
->getProjectRoot());
if ($event instanceof PreApplyEvent) {
[
$plugin_installed_in_stage,
$is_stage_root_requirement,
$stage_configuration_ok,
] = $this->computePatcherStatus($event->stage
->getStageDirectory());
$has_staged_update = TRUE;
}
else {
// No staged update exists.
$has_staged_update = FALSE;
}
// If there's a staged update and the patcher has been installed or removed
// in the stage directory, that's a problem.
if ($has_staged_update && $plugin_installed_in_active !== $plugin_installed_in_stage) {
if ($plugin_installed_in_stage) {
$message = $this->t('It cannot be installed by Package Manager.');
}
else {
$message = $this->t('It cannot be removed by Package Manager.');
}
$messages[] = $this->createErrorMessage($message, 'package-manager-faq-composer-patches-installed-or-removed');
}
// If the patcher is not listed in the runtime or dev dependencies, that's
// an error as well.
if ($plugin_installed_in_active && !$is_active_root_requirement || $has_staged_update && $plugin_installed_in_stage && !$is_stage_root_requirement) {
$messages[] = $this->createErrorMessage($this->t('It must be a root dependency.'), 'package-manager-faq-composer-patches-not-a-root-dependency');
}
// If the plugin is misconfigured in either the active or stage directories,
// flag an error.
if ($plugin_installed_in_active && !$active_configuration_ok || $has_staged_update && $plugin_installed_in_stage && !$stage_configuration_ok) {
$messages[] = $this->t('The <code>composer-exit-on-patch-failure</code> key is not set to <code>true</code> in the <code>extra</code> section of <code>composer.json</code>.');
}
if ($messages) {
$summary = $this->t("Problems detected related to the Composer plugin <code>@plugin</code>.", [
'@plugin' => static::PLUGIN_NAME,
]);
$event->addError($messages, $summary);
}
}
/**
* Appends a link to online help to an error message.
*
* @param \Drupal\Core\StringTranslation\TranslatableMarkup $message
* The error message.
* @param string $fragment
* The fragment of the online help to link to.
*
* @return \Drupal\Core\StringTranslation\TranslatableMarkup
* The final, translated error message.
*/
private function createErrorMessage(TranslatableMarkup $message, string $fragment) : TranslatableMarkup {
if ($this->moduleHandler
->moduleExists('help')) {
$url = Url::fromRoute('help.page', [
'name' => 'package_manager',
])->setOption('fragment', $fragment)
->toString();
return $this->t('@message See <a href=":url">the help page</a> for information on how to resolve the problem.', [
'@message' => $message,
':url' => $url,
]);
}
return $message;
}
/**
* Computes the status of the patcher plugin in a particular directory.
*
* @param string $working_dir
* The directory in which to run Composer.
*
* @return bool[]
* An indexed array containing three booleans, in order:
* - Whether the patcher plugin is installed.
* - Whether the patcher plugin is a root requirement in composer.json (in
* either the runtime or dev dependencies).
* - Whether the `composer-exit-on-patch-failure` flag is set in the `extra`
* section of composer.json.
*/
private function computePatcherStatus(string $working_dir) : array {
$list = $this->composerInspector
->getInstalledPackagesList($working_dir);
$installed_version = $list[static::PLUGIN_NAME]?->version;
$info = $this->composerInspector
->getRootPackageInfo($working_dir);
$is_root_requirement = array_key_exists(static::PLUGIN_NAME, $info['requires'] ?? []) || array_key_exists(static::PLUGIN_NAME, $info['devRequires'] ?? []);
// The 2.x version of the plugin always exits with an error if a patch can't
// be applied.
if ($installed_version && Semver::satisfies($installed_version, '^2')) {
$exit_on_failure = TRUE;
}
else {
$extra = Json::decode($this->composerInspector
->getConfig('extra', $working_dir));
$exit_on_failure = $extra['composer-exit-on-patch-failure'] ?? FALSE;
}
return [
is_string($installed_version),
$is_root_requirement,
$exit_on_failure,
];
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() : array {
return [
PreCreateEvent::class => 'validate',
PreApplyEvent::class => 'validate',
StatusCheckEvent::class => 'validate',
];
}
}
Classes
Title | Deprecated | Summary |
---|---|---|
ComposerPatchesValidator | Validates the configuration of the cweagans/composer-patches plugin. |
Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.