Skip to content
Snippets Groups Projects
Commit 119836ad authored by Wim Leers's avatar Wim Leers Committed by Adam G-H
Browse files

Issue #3342817 by Wim Leers, phenaproxima, tedbow: Decide which classes should...

Issue #3342817 by Wim Leers, phenaproxima, tedbow: Decide which classes should be internal and/or final — delete ExcludedPathsTrait, make CollectPathsToExcludeEvent richer
parent 21fb4771
No related branches found
No related tags found
No related merge requests found
Showing
with 154 additions and 244 deletions
......@@ -5,7 +5,9 @@ declare(strict_types = 1);
namespace Drupal\package_manager\Event;
use Drupal\package_manager\StageBase;
use Drupal\package_manager\PathLocator;
use PhpTuf\ComposerStager\Domain\Value\PathList\PathListInterface;
use PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactoryInterface;
use PhpTuf\ComposerStager\Infrastructure\Value\PathList\PathList;
/**
......@@ -14,7 +16,7 @@ use PhpTuf\ComposerStager\Infrastructure\Value\PathList\PathList;
* These paths are excluded by Composer Stager and are never copied into the
* stage directory from the active directory, or vice-versa.
*/
class CollectPathsToExcludeEvent extends StageEvent implements PathListInterface {
final class CollectPathsToExcludeEvent extends StageEvent implements PathListInterface {
/**
* The list of paths to exclude.
......@@ -24,9 +26,20 @@ class CollectPathsToExcludeEvent extends StageEvent implements PathListInterface
protected PathListInterface $pathList;
/**
* {@inheritdoc}
* Constructs a CollectPathsToExcludeEvent object.
*
* @param \Drupal\package_manager\StageBase $stage
* The stage which fired this event.
* @param \Drupal\package_manager\PathLocator $pathLocator
* The path locator service.
* @param \PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactoryInterface $pathFactory
* The path factory service.
*/
public function __construct(StageBase $stage) {
public function __construct(
StageBase $stage,
protected PathLocator $pathLocator,
protected PathFactoryInterface $pathFactory
) {
parent::__construct($stage);
$this->pathList = new PathList([]);
}
......@@ -45,4 +58,71 @@ class CollectPathsToExcludeEvent extends StageEvent implements PathListInterface
return $this->pathList->getAll();
}
/**
* Flags paths to be ignored, relative to the web root.
*
* This should only be used for paths that, if they exist at all, are
* *guaranteed* to exist within the web root.
*
* @param string[] $paths
* The paths to ignore. These should be relative to the web root, and will
* be made relative to the project root.
*/
public function addPathsRelativeToWebRoot(array $paths): void {
$web_root = $this->pathLocator->getWebRoot();
if ($web_root) {
$web_root .= '/';
}
foreach ($paths as $path) {
// Make the path relative to the project root by prefixing the web root.
$this->add([$web_root . $path]);
}
}
/**
* Flags paths to be ignored, relative to the project root.
*
* @param string[] $paths
* The paths to ignore. Absolute paths will be made relative to the project
* root; relative paths will be assumed to already be relative to the
* project root, and ignored as given.
*/
public function addPathsRelativeToProjectRoot(array $paths): void {
$project_root = $this->pathLocator->getProjectRoot();
foreach ($paths as $path) {
if ($this->pathFactory->create($path)->isAbsolute()) {
if (!str_starts_with($path, $project_root)) {
throw new \LogicException("$path is not inside the project root: $project_root.");
}
}
// Make absolute paths relative to the project root.
$path = str_replace($project_root, '', $path);
$path = ltrim($path, '/');
$this->add([$path]);
}
}
/**
* Finds all directories in the project root matching the given name.
*
* @param string $directory_name
* The directory name to scan for.
*
* @return string[]
* All discovered absolute paths matching the given directory name.
*/
public function scanForDirectoriesByName(string $directory_name): array {
$flags = \FilesystemIterator::UNIX_PATHS;
$flags |= \FilesystemIterator::CURRENT_AS_SELF;
$directories_tree = new \RecursiveDirectoryIterator($this->pathLocator->getProjectRoot(), $flags);
$filtered_directories = new \RecursiveIteratorIterator($directories_tree, \RecursiveIteratorIterator::SELF_FIRST);
$matched_directories = new \CallbackFilterIterator($filtered_directories,
fn (\RecursiveDirectoryIterator $current) => $current->isDir() && $current->getFilename() === $directory_name
);
return array_keys(iterator_to_array($matched_directories));
}
}
<?php
declare(strict_types = 1);
namespace Drupal\package_manager\Event;
/**
* Common functionality for events which can collect excluded paths.
*/
trait ExcludedPathsTrait {
/**
* Paths to exclude from the update.
*
* @var string[]
*/
protected $excludedPaths = [];
/**
* Returns the paths to exclude from the current operation.
*
* @return string[]
* The paths to exclude.
*/
public function getExcludedPaths(): array {
return array_unique($this->excludedPaths);
}
}
......@@ -7,5 +7,5 @@ namespace Drupal\package_manager\Event;
/**
* Event fired after staged changes are synced to the active directory.
*/
class PostApplyEvent extends StageEvent {
final class PostApplyEvent extends StageEvent {
}
......@@ -7,5 +7,5 @@ namespace Drupal\package_manager\Event;
/**
* Event fired after a stage directory has been created.
*/
class PostCreateEvent extends StageEvent {
final class PostCreateEvent extends StageEvent {
}
......@@ -12,5 +12,5 @@ namespace Drupal\package_manager\Event;
*
* @see \Drupal\package_manager\StageBase::destroy()
*/
class PostDestroyEvent extends StageEvent {
final class PostDestroyEvent extends StageEvent {
}
......@@ -7,7 +7,7 @@ namespace Drupal\package_manager\Event;
/**
* Event fired after packages are updated to the stage directory.
*/
class PostRequireEvent extends StageEvent {
final class PostRequireEvent extends StageEvent {
use RequireEventTrait;
......
......@@ -9,9 +9,24 @@ use Drupal\package_manager\StageBase;
/**
* Event fired before staged changes are synced to the active directory.
*/
class PreApplyEvent extends PreOperationStageEvent {
final class PreApplyEvent extends PreOperationStageEvent {
use ExcludedPathsTrait;
/**
* Paths to exclude from the update.
*
* @var string[]
*/
protected array $excludedPaths = [];
/**
* Returns the paths to exclude from the current operation.
*
* @return string[]
* The paths to exclude.
*/
public function getExcludedPaths(): array {
return array_unique($this->excludedPaths);
}
/**
* Constructs a PreApplyEvent object.
......
......@@ -9,9 +9,24 @@ use Drupal\package_manager\StageBase;
/**
* Event fired before a stage directory is created.
*/
class PreCreateEvent extends PreOperationStageEvent {
final class PreCreateEvent extends PreOperationStageEvent {
use ExcludedPathsTrait;
/**
* Paths to exclude from the update.
*
* @var string[]
*/
protected array $excludedPaths = [];
/**
* Returns the paths to exclude from the current operation.
*
* @return string[]
* The paths to exclude.
*/
public function getExcludedPaths(): array {
return array_unique($this->excludedPaths);
}
/**
* Constructs a PreCreateEvent object.
......
......@@ -12,5 +12,5 @@ namespace Drupal\package_manager\Event;
*
* @see \Drupal\package_manager\StageBase::destroy()
*/
class PreDestroyEvent extends PreOperationStageEvent {
final class PreDestroyEvent extends PreOperationStageEvent {
}
......@@ -7,7 +7,7 @@ namespace Drupal\package_manager\Event;
/**
* Event fired before packages are updated to the stage directory.
*/
class PreRequireEvent extends PreOperationStageEvent {
final class PreRequireEvent extends PreOperationStageEvent {
use RequireEventTrait;
......
......@@ -15,7 +15,7 @@ use Drupal\system\SystemManager;
* The event's stage will be set with the type of stage that will perform the
* operations. The stage may or may not be currently in use.
*/
class StatusCheckEvent extends PreOperationStageEvent {
final class StatusCheckEvent extends PreOperationStageEvent {
/**
* Returns paths to exclude or NULL if a base requirement is not fulfilled.
......
......@@ -9,5 +9,5 @@ namespace Drupal\package_manager\Exception;
*
* Should not be thrown by external code.
*/
class StageOwnershipException extends StageException {
final class StageOwnershipException extends StageException {
}
......@@ -7,7 +7,6 @@ namespace Drupal\package_manager\PathExcluder;
use Drupal\package_manager\ComposerInspector;
use Drupal\package_manager\Event\CollectPathsToExcludeEvent;
use Drupal\package_manager\PathLocator;
use PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactoryInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
......@@ -20,22 +19,18 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
*/
final class GitExcluder implements EventSubscriberInterface {
use PathExclusionsTrait;
/**
* Constructs a GitExcluder object.
*
* @param \Drupal\package_manager\PathLocator $path_locator
* @param \Drupal\package_manager\PathLocator $pathLocator
* The path locator service.
* @param \Drupal\package_manager\ComposerInspector $composerInspector
* The Composer inspector service.
* @param \PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactoryInterface $path_factory
* The path factory service.
*/
public function __construct(PathLocator $path_locator, private readonly ComposerInspector $composerInspector, PathFactoryInterface $path_factory) {
$this->pathLocator = $path_locator;
$this->pathFactory = $path_factory;
}
public function __construct(
private readonly PathLocator $pathLocator,
private readonly ComposerInspector $composerInspector
) {}
/**
* {@inheritdoc}
......@@ -77,7 +72,7 @@ final class GitExcluder implements EventSubscriberInterface {
$installed_paths[] = $package->path;
}
}
$paths = $this->scanForDirectoriesByName('.git');
$paths = $event->scanForDirectoriesByName('.git');
foreach ($paths as $git_directory) {
// Don't exclude any `.git` directory that is directly under an installed
// package's path, since it means Composer probably installed that package
......@@ -87,7 +82,7 @@ final class GitExcluder implements EventSubscriberInterface {
$paths_to_exclude[] = $git_directory;
}
}
$this->excludeInProjectRoot($event, $paths_to_exclude);
$event->addPathsRelativeToProjectRoot($paths_to_exclude);
}
}
......@@ -5,8 +5,6 @@ declare(strict_types = 1);
namespace Drupal\package_manager\PathExcluder;
use Drupal\package_manager\Event\CollectPathsToExcludeEvent;
use Drupal\package_manager\PathLocator;
use PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactoryInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
......@@ -19,21 +17,6 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
*/
class NodeModulesExcluder implements EventSubscriberInterface {
use PathExclusionsTrait;
/**
* Constructs a NodeModulesExcluder object.
*
* @param \Drupal\package_manager\PathLocator $path_locator
* The path locator service.
* @param \PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactoryInterface $path_factory
* The path factory service.
*/
public function __construct(PathLocator $path_locator, PathFactoryInterface $path_factory) {
$this->pathLocator = $path_locator;
$this->pathFactory = $path_factory;
}
/**
* Excludes node_modules directories from stage operations.
*
......@@ -41,8 +24,7 @@ class NodeModulesExcluder implements EventSubscriberInterface {
* The event object.
*/
public function excludeNodeModulesFiles(CollectPathsToExcludeEvent $event): void {
$paths = $this->scanForDirectoriesByName('node_modules');
$this->excludeInProjectRoot($event, $paths);
$event->addPathsRelativeToProjectRoot($event->scanForDirectoriesByName('node_modules'));
}
/**
......
<?php
declare(strict_types = 1);
namespace Drupal\package_manager\PathExcluder;
use Drupal\package_manager\Event\CollectPathsToExcludeEvent;
/**
* Contains methods for excluding paths from stage operations.
*/
trait PathExclusionsTrait {
/**
* The path locator service.
*
* @var \Drupal\package_manager\PathLocator
*/
protected $pathLocator;
/**
* The path factory service.
*
* @var \PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactoryInterface
*/
protected $pathFactory;
/**
* Flags paths to be excluded, relative to the web root.
*
* This should only be used for paths that, if they exist at all, are
* *guaranteed* to exist within the web root.
*
* @param \Drupal\package_manager\Event\CollectPathsToExcludeEvent|\Drupal\package_manager\Event\StageEvent $event
* The event object.
* @param string[] $paths
* The paths to exclude. These should be relative to the web root, and will
* be made relative to the project root.
*/
protected function excludeInWebRoot(CollectPathsToExcludeEvent $event, array $paths): void {
$web_root = $this->pathLocator->getWebRoot();
if ($web_root) {
$web_root .= '/';
}
foreach ($paths as $path) {
// Make the path relative to the project root by prefixing the web root.
$event->add([$web_root . $path]);
}
}
/**
* Flags paths to be excluded, relative to the project root.
*
* @param \Drupal\package_manager\Event\CollectPathsToExcludeEvent|\Drupal\package_manager\Event\StageEvent $event
* The event object.
* @param string[] $paths
* The paths to exclude. Absolute paths will be made relative to the project
* root; relative paths will be assumed to already be relative to the
* project root, and excluded as given.
*/
protected function excludeInProjectRoot(CollectPathsToExcludeEvent $event, array $paths): void {
$project_root = $this->pathLocator->getProjectRoot();
foreach ($paths as $path) {
if ($this->pathFactory->create($path)->isAbsolute()) {
if (!str_starts_with($path, $project_root)) {
throw new \LogicException("$path is not inside the project root: $project_root.");
}
}
// Make absolute paths relative to the project root.
$path = str_replace($project_root, '', $path);
$path = ltrim($path, '/');
$event->add([$path]);
}
}
/**
* Finds all directories in the project root matching the given name.
*
* @param string $directory_name
* The directory name to scan for.
*
* @return string[]
* All discovered absolute paths matching the given directory name.
*/
protected function scanForDirectoriesByName(string $directory_name): array {
$flags = \FilesystemIterator::UNIX_PATHS;
$flags |= \FilesystemIterator::CURRENT_AS_SELF;
$directories_tree = new \RecursiveDirectoryIterator($this->pathLocator->getProjectRoot(), $flags);
$filtered_directories = new \RecursiveIteratorIterator($directories_tree, \RecursiveIteratorIterator::SELF_FIRST);
$matched_directories = new \CallbackFilterIterator($filtered_directories,
fn (\RecursiveDirectoryIterator $current) => $current->isDir() && $current->getFilename() === $directory_name
);
return array_keys(iterator_to_array($matched_directories));
}
}
......@@ -5,8 +5,6 @@ declare(strict_types = 1);
namespace Drupal\package_manager\PathExcluder;
use Drupal\package_manager\Event\CollectPathsToExcludeEvent;
use Drupal\package_manager\PathLocator;
use PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactoryInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
......@@ -19,22 +17,13 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
*/
class SiteConfigurationExcluder implements EventSubscriberInterface {
use PathExclusionsTrait;
/**
* Constructs an ExcludedPathsSubscriber.
* Constructs an SiteConfigurationExcluder.
*
* @param string $sitePath
* The current site path, relative to the Drupal root.
* @param \Drupal\package_manager\PathLocator $path_locator
* The path locator service.
* @param \PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactoryInterface $path_factory
* The path factory service.
*/
public function __construct(protected string $sitePath, PathLocator $path_locator, PathFactoryInterface $path_factory) {
$this->pathLocator = $path_locator;
$this->pathFactory = $path_factory;
}
public function __construct(protected string $sitePath) {}
/**
* Excludes site configuration files from stage operations.
......@@ -57,7 +46,7 @@ class SiteConfigurationExcluder implements EventSubscriberInterface {
$paths[] = $this->sitePath . '/' . $settings_file;
$paths[] = 'sites/default/' . $settings_file;
}
$this->excludeInWebRoot($event, $paths);
$event->addPathsRelativeToWebRoot($paths);
}
/**
......
......@@ -7,8 +7,6 @@ namespace Drupal\package_manager\PathExcluder;
use Drupal\Core\StreamWrapper\LocalStream;
use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
use Drupal\package_manager\Event\CollectPathsToExcludeEvent;
use Drupal\package_manager\PathLocator;
use PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactoryInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Filesystem\Filesystem;
......@@ -22,29 +20,18 @@ use Symfony\Component\Filesystem\Filesystem;
*/
final class SiteFilesExcluder implements EventSubscriberInterface {
use PathExclusionsTrait;
/**
* Constructs a SiteFilesExcluder object.
*
* @param \Drupal\package_manager\PathLocator $path_locator
* The path locator service.
* @param \PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactoryInterface $path_factory
* The path factory service.
* @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $streamWrapperManager
* The stream wrapper manager service.
* @param \Symfony\Component\Filesystem\Filesystem $fileSystem
* The Symfony file system service.
*/
public function __construct(
PathLocator $path_locator,
PathFactoryInterface $path_factory,
private readonly StreamWrapperManagerInterface $streamWrapperManager,
private readonly Filesystem $fileSystem
) {
$this->pathLocator = $path_locator;
$this->pathFactory = $path_factory;
}
) {}
/**
* {@inheritdoc}
......@@ -72,10 +59,10 @@ final class SiteFilesExcluder implements EventSubscriberInterface {
$path = $wrapper->getDirectoryPath();
if ($this->fileSystem->isAbsolutePath($path)) {
$this->excludeInProjectRoot($event, [realpath($path)]);
$event->addPathsRelativeToProjectRoot([realpath($path)]);
}
else {
$this->excludeInWebRoot($event, [$path]);
$event->addPathsRelativeToWebRoot([$path]);
}
}
}
......
......@@ -7,7 +7,6 @@ namespace Drupal\package_manager\PathExcluder;
use Drupal\Core\Database\Connection;
use Drupal\package_manager\Event\CollectPathsToExcludeEvent;
use Drupal\package_manager\PathLocator;
use PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactoryInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
......@@ -20,22 +19,20 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
*/
class SqliteDatabaseExcluder implements EventSubscriberInterface {
use PathExclusionsTrait;
/**
* Constructs a SqliteDatabaseExcluder object.
*
* @param \Drupal\package_manager\PathLocator $path_locator
* @param \Drupal\package_manager\PathLocator $pathLocator
* The path locator service.
* @param \Drupal\Core\Database\Connection $database
* The database connection.
* @param \PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactoryInterface $path_factory
* The path factory service.
*/
public function __construct(PathLocator $path_locator, protected Connection $database, PathFactoryInterface $path_factory) {
$this->pathLocator = $path_locator;
$this->pathFactory = $path_factory;
}
public function __construct(
private readonly PathLocator $pathLocator,
// TRICKY: this cannot be private nor readonly for testing purposes.
// @see \Drupal\Tests\package_manager\Kernel\PathExcluder\SqliteDatabaseExcluderTest::mockDatabase()
protected Connection $database
) {}
/**
* {@inheritdoc}
......@@ -61,7 +58,7 @@ class SqliteDatabaseExcluder implements EventSubscriberInterface {
if (str_starts_with($options['database'], '/') && !str_starts_with($options['database'], $this->pathLocator->getProjectRoot())) {
return;
}
$this->excludeInProjectRoot($event, [
$event->addPathsRelativeToProjectRoot([
$options['database'],
$options['database'] . '-shm',
$options['database'] . '-wal',
......
......@@ -5,8 +5,6 @@ declare(strict_types = 1);
namespace Drupal\package_manager\PathExcluder;
use Drupal\package_manager\Event\CollectPathsToExcludeEvent;
use Drupal\package_manager\PathLocator;
use PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactoryInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
......@@ -19,21 +17,6 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
*/
final class TestSiteExcluder implements EventSubscriberInterface {
use PathExclusionsTrait;
/**
* Constructs a TestSiteExcluder object.
*
* @param \Drupal\package_manager\PathLocator $path_locator
* The path locator service.
* @param \PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactoryInterface $path_factory
* The path factory service.
*/
public function __construct(PathLocator $path_locator, PathFactoryInterface $path_factory) {
$this->pathLocator = $path_locator;
$this->pathFactory = $path_factory;
}
/**
* {@inheritdoc}
*/
......@@ -52,7 +35,7 @@ final class TestSiteExcluder implements EventSubscriberInterface {
public function excludeTestSites(CollectPathsToExcludeEvent $event): void {
// Always exclude automated test directories. If they exist, they will be in
// the web root.
$this->excludeInWebRoot($event, ['sites/simpletest']);
$event->addPathsRelativeToWebRoot(['sites/simpletest']);
}
}
......@@ -8,7 +8,6 @@ use Drupal\Component\Serialization\Json;
use Drupal\package_manager\ComposerInspector;
use Drupal\package_manager\Event\CollectPathsToExcludeEvent;
use Drupal\package_manager\PathLocator;
use PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactoryInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
......@@ -31,22 +30,18 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
*/
final class UnknownPathExcluder implements EventSubscriberInterface {
use PathExclusionsTrait;
/**
* Constructs a UnknownPathExcluder object.
*
* @param \Drupal\package_manager\ComposerInspector $composerInspector
* The Composer inspector service.
* @param \Drupal\package_manager\PathLocator $path_locator
* @param \Drupal\package_manager\PathLocator $pathLocator
* The path locator service.
* @param \PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactoryInterface $path_factory
* The path factory service.
*/
public function __construct(private readonly ComposerInspector $composerInspector, PathLocator $path_locator, PathFactoryInterface $path_factory) {
$this->pathLocator = $path_locator;
$this->pathFactory = $path_factory;
}
public function __construct(
private readonly ComposerInspector $composerInspector,
private readonly PathLocator $pathLocator,
) {}
/**
* {@inheritdoc}
......@@ -93,7 +88,7 @@ final class UnknownPathExcluder implements EventSubscriberInterface {
$paths[] = $path_in_project_root;
}
}
$this->excludeInProjectRoot($event, $paths);
$event->addPathsRelativeToProjectRoot($paths);
}
/**
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment