Skip to content
Snippets Groups Projects
ComposerExecutableValidator.php 4.35 KiB
Newer Older
namespace Drupal\package_manager\Validator;
use Composer\Semver\Comparator;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Url;
use Drupal\package_manager\Event\PreCreateEvent;
use Drupal\package_manager\Event\PreOperationStageEvent;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use PhpTuf\ComposerStager\Domain\Exception\ExceptionInterface;
use PhpTuf\ComposerStager\Domain\Service\ProcessOutputCallback\ProcessOutputCallbackInterface;
use PhpTuf\ComposerStager\Domain\Service\ProcessRunner\ComposerRunnerInterface;
 * Validates the Composer executable is the correct version.
 *
 * @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 ComposerExecutableValidator implements PreOperationStageValidatorInterface, ProcessOutputCallbackInterface {
  /**
   * The minimum required version of Composer.
   *
   * @var string
   */
  public const MINIMUM_COMPOSER_VERSION = '2.3.5';
   * @var \PhpTuf\ComposerStager\Domain\Service\ProcessRunner\ComposerRunnerInterface
  /**
   * The module handler service.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

   * The detected version of Composer.

  /**
   * Constructs a ComposerExecutableValidator object.
   *
   * @param \PhpTuf\ComposerStager\Domain\Service\ProcessRunner\ComposerRunnerInterface $composer
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler service.
   * @param \Drupal\Core\StringTranslation\TranslationInterface $translation
   *   The translation service.
  public function __construct(ComposerRunnerInterface $composer, ModuleHandlerInterface $module_handler, TranslationInterface $translation) {
    $this->moduleHandler = $module_handler;
    $this->setStringTranslation($translation);
  public function validateStagePreOperation(PreOperationStageEvent $event): void {
      $this->composer->run(['--version'], $this);
    catch (ExceptionInterface $e) {
      $this->setError($e->getMessage(), $event);
      if (Comparator::lessThan($this->version, static::MINIMUM_COMPOSER_VERSION)) {
        $message = $this->t('Composer @minimum_version or later is required, but version @detected_version was detected.', [
          '@minimum_version' => static::MINIMUM_COMPOSER_VERSION,
          '@detected_version' => $this->version,
        $this->setError($message, $event);
      $this->setError($this->t('The Composer version could not be detected.'), $event);
    }
  }

  /**
   * Flags a validation error.
   *
   * @param string $message
   *   The error message. If the Help module is enabled, a link to Package
   *   Manager's online documentation will be appended.
   * @param \Drupal\package_manager\Event\PreOperationStageEvent $event
   *   The event object.
   *
   * @see package_manager_help()
   */
  protected function setError(string $message, PreOperationStageEvent $event): void {
    if ($this->moduleHandler->moduleExists('help')) {
      $url = Url::fromRoute('help.page', ['name' => 'package_manager'])
        ->setOption('fragment', 'package-manager-requirements')
        ->toString();

      $message = $this->t('@message See <a href=":package-manager-help">the help page</a> for information on how to configure the path to Composer.', [
        '@message' => $message,
        ':package-manager-help' => $url,
    $event->addError([$message]);
  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() {
    return [
      PreCreateEvent::class => 'validateStagePreOperation',
  /**
   * {@inheritdoc}
   */
  public function __invoke(string $type, string $buffer): void {
    $matched = [];
    // Search for a semantic version number and optional stability flag.
    if (preg_match('/([0-9]+\.?){3}-?((alpha|beta|rc)[0-9]*)?/i', $buffer, $matched)) {
      $this->version = $matched[0];
    }
  }