class LongLivedSandboxSubscriber

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

Maintains state for the long-lived sandbox directory.

  • Records the timestamp when a sandbox is created (used by cron to determine when a sandbox is stale and can be removed).
  • Deletes the sandbox directory when the `include_unknown_files_in_project_root` setting is changed to FALSE, because the rsync flags used to sync changes won't delete files that are now excluded from the source directory.

This subscriber intentionally injects only the container to avoid resolving the heavy ComposerStager dependency tree (Beginner, Stager, Committer and their precondition chains) at construction time. Those services are resolved lazily inside onConfigSave() only when the setting actually changes.

@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\LongLivedSandboxSubscriber implements \Symfony\Component\EventDispatcher\EventSubscriberInterface

Expanded class hierarchy of LongLivedSandboxSubscriber

2 files declare their use of LongLivedSandboxSubscriber
CronHook.php in core/modules/package_manager/src/Hook/CronHook.php
SandboxManagerBaseTest.php in core/modules/package_manager/tests/src/Kernel/SandboxManagerBaseTest.php

File

core/modules/package_manager/src/EventSubscriber/LongLivedSandboxSubscriber.php, line 46

Namespace

Drupal\package_manager\EventSubscriber
View source
final class LongLivedSandboxSubscriber implements EventSubscriberInterface {
  
  /**
   * The state key which holds the last time a create event ran.
   *
   * @var string
   */
  public const STATE_KEY = 'package_manager.last_create_event';
  public function __construct(private readonly StateInterface $state, private readonly TimeInterface $time, private readonly ContainerInterface $container) {
  }
  
  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() : array {
    return [
      ConfigEvents::SAVE => 'onConfigSave',
      PostCreateEvent::class => 'updateLastCreatedTime',
    ];
  }
  
  /**
   * Updates state with the last time a create event ran.
   *
   * @param \Drupal\package_manager\Event\PostCreateEvent $event
   *   The event being handled.
   */
  public function updateLastCreatedTime(PostCreateEvent $event) : void {
    $this->state
      ->set(static::STATE_KEY, $this->time
      ->getCurrentTime());
  }
  
  /**
   * Reacts when config is saved.
   *
   * @param \Drupal\Core\Config\ConfigCrudEvent $event
   *   The event object.
   */
  public function onConfigSave(ConfigCrudEvent $event) : void {
    $config = $event->getConfig();
    if ($config->getName() !== 'package_manager.settings' || !$event->isChanged('include_unknown_files_in_project_root')) {
      return;
    }
    // Skip initial module install where the original value is NULL, no sandbox
    // directory exists yet to clean up. Removing this guard will cause test
    // failures (e.g., SymlinkValidatorTest) where the subscriber's side effects
    // during module install are unexpected.
    if ($config->getOriginal('include_unknown_files_in_project_root') === NULL) {
      return;
    }
    // Ensure the sandbox directory is deleted when unknown files setting is
    // changed to FALSE, as the rsync flags won't delete unknown files if they
    // still exist in the source directory.
    // @see \PhpTuf\ComposerStager\Internal\FileSyncer\Service\FileSyncer::buildCommand
    if ($config->get('include_unknown_files_in_project_root') === FALSE) {
      $sandbox_manager = new class ($this->container
        ->get(PathLocator::class), $this->container
        ->get(BeginnerInterface::class), $this->container
        ->get(StagerInterface::class), $this->container
        ->get(CommitterInterface::class), $this->container
        ->get(QueueFactory::class), $this->container
        ->get(EventDispatcherInterface::class), $this->container
        ->get(SharedTempStoreFactory::class), $this->container
        ->get(TimeInterface::class), $this->container
        ->get(PathFactoryInterface::class), $this->container
        ->get(FailureMarker::class)) extends SandboxManagerBase {

};
      // In direct-write mode, getSandboxDirectory() returns the project root.
      // There is no separate sandbox directory to delete.
      if ($sandbox_manager->isDirectWrite()) {
        return;
      }
      $is_available = $sandbox_manager->isAvailable();
      $sandbox_dir = $sandbox_manager->getSandboxDirectory();
      if (file_exists($sandbox_dir) && $is_available) {
        /** @var \Drupal\Core\File\FileSystemInterface $file_system */
        $file_system = $this->container
          ->get(FileSystemInterface::class);
        $file_system->deleteRecursive($sandbox_dir, function (string $path) use ($file_system) : void {
          $file_system->chmod($path, is_dir($path) ? 0700 : 0600);
        });
        $this->container
          ->get(StateInterface::class)
          ->delete(static::STATE_KEY);
      }
    }
  }

}

Members

Title Sort descending Modifiers Object type Summary
LongLivedSandboxSubscriber::getSubscribedEvents public static function
LongLivedSandboxSubscriber::onConfigSave public function Reacts when config is saved.
LongLivedSandboxSubscriber::STATE_KEY public constant The state key which holds the last time a create event ran.
LongLivedSandboxSubscriber::updateLastCreatedTime public function Updates state with the last time a create event ran.
LongLivedSandboxSubscriber::__construct public function

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