class ChangeLogger

Same name and namespace in other branches
  1. main core/modules/package_manager/src/EventSubscriber/ChangeLogger.php \Drupal\package_manager\EventSubscriber\ChangeLogger

Event subscriber to log changes that happen during the stage life cycle.

@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.

Hierarchy

  • class \Drupal\package_manager\EventSubscriber\ChangeLogger implements \Symfony\Component\EventDispatcher\EventSubscriberInterface, \Psr\Log\LoggerAwareInterface uses \Psr\Log\LoggerAwareTrait, \Drupal\Core\StringTranslation\StringTranslationTrait

Expanded class hierarchy of ChangeLogger

1 file declares its use of ChangeLogger
ChangeLoggerTest.php in core/modules/package_manager/tests/src/Kernel/ChangeLoggerTest.php

File

core/modules/package_manager/src/EventSubscriber/ChangeLogger.php, line 25

Namespace

Drupal\package_manager\EventSubscriber
View source
final class ChangeLogger implements EventSubscriberInterface, LoggerAwareInterface {
  use LoggerAwareTrait;
  use StringTranslationTrait;
  
  /**
   * The key to store the list of packages installed when the stage is created.
   *
   * @var string
   *
   * @see ::recordInstalledPackages()
   */
  private const INSTALLED_PACKAGES_KEY = 'package_manager_installed_packages';
  
  /**
   * The metadata key under which to store the requested package versions.
   *
   * @var string
   *
   * @see ::recordRequestedPackageVersions()
   */
  private const REQUESTED_PACKAGES_KEY = 'package_manager_requested_packages';
  public function __construct(private readonly ComposerInspector $composerInspector, private readonly PathLocator $pathLocator) {
  }
  
  /**
   * Records packages installed in the project root.
   *
   * We need to do this before the staging environment has been created, so that
   * we have a complete picture of which requested packages are merely being
   * updated, and which are being newly added. Once the staging environment has
   * been created, the installed packages won't change -- if they do, a
   * validation error will be raised.
   *
   * @param \Drupal\package_manager\Event\PreCreateEvent $event
   *   The event being handled.
   *
   * @see \Drupal\package_manager\Validator\LockFileValidator
   */
  public function recordInstalledPackages(PreCreateEvent $event) : void {
    $packages = $this->composerInspector
      ->getInstalledPackagesList($this->pathLocator
      ->getProjectRoot());
    $event->sandboxManager
      ->setMetadata(static::INSTALLED_PACKAGES_KEY, $packages);
  }
  
  /**
   * Records requested packages.
   *
   * @param \Drupal\package_manager\Event\PostRequireEvent $event
   *   The event object.
   */
  public function recordRequestedPackageVersions(PostRequireEvent $event) : void {
    // There could be multiple 'require' operations, so overlay the requested
    // packages from the current operation onto the requested packages from any
    // previous 'require' operation.
    $requested_packages = array_merge($event->sandboxManager
      ->getMetadata(static::REQUESTED_PACKAGES_KEY) ?? [], $event->getRuntimePackages(), $event->getDevPackages());
    $event->sandboxManager
      ->setMetadata(static::REQUESTED_PACKAGES_KEY, $requested_packages);
    // If we're in direct-write mode, the changes have already been made, so
    // we should log them right away.
    if ($event->sandboxManager
      ->isDirectWrite()) {
      $this->logChanges($event);
    }
  }
  
  /**
   * Logs changes made by Package Manager.
   *
   * @param \Drupal\package_manager\Event\PostApplyEvent|\Drupal\package_manager\Event\PostRequireEvent $event
   *   The event being handled.
   */
  public function logChanges(PostApplyEvent|PostRequireEvent $event) : void {
    $installed_at_start = $event->sandboxManager
      ->getMetadata(static::INSTALLED_PACKAGES_KEY);
    $installed_post_apply = $this->composerInspector
      ->getInstalledPackagesList($this->pathLocator
      ->getProjectRoot());
    // Compare the packages which were installed when the stage was created
    // against the package versions that were requested over all the stage's
    // require operations, and create a log entry listing all of it.
    $requested_log = [];
    $requested_packages = $event->sandboxManager
      ->getMetadata(static::REQUESTED_PACKAGES_KEY) ?? [];
    // Sort the requested packages by name, to make it easier to review a large
    // change list.
    ksort($requested_packages, SORT_NATURAL);
    foreach ($requested_packages as $name => $constraint) {
      $installed_version = $installed_at_start[$name]?->version;
      if ($installed_version === NULL) {
        // For clarity, make the "any version" constraint human-readable.
        if ($constraint === '*') {
          $constraint = $this->t('* (any version)');
        }
        $requested_log[] = $this->t('- Install @name @constraint', [
          '@name' => $name,
          '@constraint' => $constraint,
        ]);
      }
      else {
        $requested_log[] = $this->t('- Update @name from @installed_version to @constraint', [
          '@name' => $name,
          '@installed_version' => $installed_version,
          '@constraint' => $constraint,
        ]);
      }
    }
    // It's possible that $requested_log will be empty: for example, a custom
    // stage that only does removals, or some other operation, and never
    // dispatches PostRequireEvent.
    if ($requested_log) {
      $message = $this->t("Requested changes:\n@change_list", [
        '@change_list' => implode("\n", array_map('strval', $requested_log)),
      ]);
      $this->logger?->info($message);
    }
    // Create a separate log entry listing everything that actually changed.
    $applied_log = [];
    $updated_packages = $installed_post_apply->getPackagesWithDifferentVersionsIn($installed_at_start);
    // Sort the packages by name to make it easier to review large change sets.
    $updated_packages->ksort(SORT_NATURAL);
    foreach ($updated_packages as $name => $package) {
      $applied_log[] = $this->t('- Updated @name from @installed_version to @updated_version', [
        '@name' => $name,
        '@installed_version' => $installed_at_start[$name]->version,
        '@updated_version' => $package->version,
      ]);
    }
    $added_packages = $installed_post_apply->getPackagesNotIn($installed_at_start);
    $added_packages->ksort(SORT_NATURAL);
    foreach ($added_packages as $name => $package) {
      $applied_log[] = $this->t('- Installed @name @version', [
        '@name' => $name,
        '@version' => $package->version,
      ]);
    }
    $removed_packages = $installed_at_start->getPackagesNotIn($installed_post_apply);
    $removed_packages->ksort(SORT_NATURAL);
    foreach ($installed_at_start->getPackagesNotIn($installed_post_apply) as $name => $package) {
      $applied_log[] = $this->t('- Uninstalled @name', [
        '@name' => $name,
      ]);
    }
    $message = $this->t("Applied changes:\n@change_list", [
      '@change_list' => implode("\n", array_map('strval', $applied_log)),
    ]);
    $this->logger?->info($message);
  }
  
  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() : array {
    return [
      PreCreateEvent::class => [
        'recordInstalledPackages',
      ],
      PostRequireEvent::class => [
        'recordRequestedPackageVersions',
      ],
      PostApplyEvent::class => [
        'logChanges',
      ],
    ];
  }

}

Members

Title Sort descending Modifiers Object type Summary Overrides
ChangeLogger::getSubscribedEvents public static function
ChangeLogger::INSTALLED_PACKAGES_KEY private constant The key to store the list of packages installed when the stage is created.
ChangeLogger::logChanges public function Logs changes made by Package Manager.
ChangeLogger::recordInstalledPackages public function Records packages installed in the project root.
ChangeLogger::recordRequestedPackageVersions public function Records requested packages.
ChangeLogger::REQUESTED_PACKAGES_KEY private constant The metadata key under which to store the requested package versions.
ChangeLogger::__construct public function
StringTranslationTrait::$stringTranslation protected property The string translation service. 3
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language. 1

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.