diff -u b/core/lib/Drupal/Component/Scaffold/Handler.php b/core/lib/Drupal/Component/Scaffold/Handler.php
--- b/core/lib/Drupal/Component/Scaffold/Handler.php
+++ b/core/lib/Drupal/Component/Scaffold/Handler.php
@@ -9,9 +9,9 @@
use Composer\Package\PackageInterface;
use Composer\Plugin\CommandEvent;
use Composer\Util\Filesystem;
-use Drupal\Component\Scaffold\Operations\OperationCollection;
use Drupal\Component\Scaffold\Operations\OperationData;
use Drupal\Component\Scaffold\Operations\OperationFactory;
+use Drupal\Component\Scaffold\Operations\ScaffoldFileCollection;
/**
* Core class of the plugin.
@@ -155,11 +155,15 @@
$file_mappings = $this->getFileMappingsFromPackages($allowed_packages);
// Analyze the list of file mappings, and determine which take priority.
- $scaffold_collection = new OperationCollection($this->io);
$location_replacements = $this->manageOptions->getLocationReplacements();
-
- // Write the collected scaffold files to the designated location on disk.
- $scaffold_results = $scaffold_collection->process($file_mappings, $location_replacements, $this->manageOptions->getOptions());
+ $scaffold_files = ScaffoldFileCollection::create($file_mappings, $location_replacements);
+ $scaffold_results = [];
+ // Process all of the files that were collated one package at a time.
+ iterator_apply(
+ $scaffold_files,
+ [ScaffoldFileCollection::class, 'process'],
+ [$scaffold_files, $this->io, $this->manageOptions->getOptions(), &$scaffold_results]
+ );
// Generate an autoload file in the document root that includes the
// autoload.php file in the vendor directory, wherever that is. Drupal
reverted:
--- b/core/lib/Drupal/Component/Scaffold/Operations/OperationCollection.php
+++ /dev/null
@@ -1,79 +0,0 @@
-io = $io;
- }
-
- /**
- * Process all of the scaffold files listed in the provided file mappings.
- *
- * @param array $file_mappings
- * An multidimensional array of file mappings, as returned by
- * self::getFileMappingsFromPackages().
- * @param \Drupal\Component\Scaffold\Interpolator $location_replacements
- * An object with the location mappings (e.g. [web-root]).
- * @param \Drupal\Component\Scaffold\ScaffoldOptions $options
- * Configuration options from the top-level composer.json file.
- *
- * @return \Drupal\Component\Scaffold\Operations\ScaffoldResult[]
- * Associative array keyed by destination path and values as the scaffold
- * result for each scaffolded file.
- */
- public function process(array $file_mappings, Interpolator $location_replacements, ScaffoldOptions $options) {
- $collatedScaffoldFiles = new ScaffoldFileCollator($this->io);
- $collatedScaffoldFiles->collateScaffoldFiles($file_mappings, $location_replacements);
- return $this->processScaffoldFiles($collatedScaffoldFiles, $options);
- }
-
- /**
- * Scaffolds the files in our scaffold collection, package-by-package.
- *
- * @param ScaffoldFileCollator $collatedScaffoldFiles
- * Collection of collated scaffold files.
- * @param \Drupal\Component\Scaffold\ScaffoldOptions $options
- * Configuration options from the top-level composer.json file.
- *
- * @return \Drupal\Component\Scaffold\Operations\ScaffoldResult[]
- * Associative array keyed by destination path and values as the scaffold
- * result for each scaffolded file.
- */
- protected function processScaffoldFiles(ScaffoldFileCollator $collatedScaffoldFiles, ScaffoldOptions $options) {
- $result = [];
- // Process all of the files that were collated one package at a time.
- foreach ($collatedScaffoldFiles->resolvedScaffoldFiles() as $package_name => $package_scaffold_files) {
- $this->io->write("Scaffolding files for {$package_name}:");
- foreach ($package_scaffold_files as $dest_rel_path => $scaffold_file) {
- if (!$collatedScaffoldFiles->overridden($scaffold_file)) {
- $result[$dest_rel_path] = $scaffold_file->process($this->io, $options);
- }
- }
- }
- return $result;
- }
-
-}
reverted:
--- b/core/lib/Drupal/Component/Scaffold/Operations/ScaffoldFileCollator.php
+++ /dev/null
@@ -1,139 +0,0 @@
-io = $io;
- }
-
- /**
- * Organizes provided file mappings by destination and package.
- *
- * @param array $file_mappings
- * An multidimensional array of file mappings, as returned by
- * self::getFileMappingsFromPackages().
- * @param \Drupal\Component\Scaffold\Interpolator $location_replacements
- * An object with the location mappings (e.g. [web-root]).
- */
- public function collateScaffoldFiles(array $file_mappings, Interpolator $location_replacements) {
- foreach ($file_mappings as $package_name => $package_file_mappings) {
- foreach ($package_file_mappings as $destination_rel_path => $op) {
- $destination = ScaffoldFilePath::destinationPath($package_name, $destination_rel_path, $location_replacements);
- // If there was already a scaffolding operation happening at this path,
- // and the new operation is Conjoinable, then use a ConjunctionOp to
- // join together both operations. This will cause both operations to
- // run, one after the other. At the moment, only AppendOp is
- // conjoinable; all other operations simply replace anything at the same
- // path.
- if (isset($this->listOfScaffoldFiles[$destination_rel_path]) && $op instanceof ConjoinableInterface) {
- $op = new ConjunctionOp($this->listOfScaffoldFiles[$destination_rel_path]->op(), $op);
- }
-
- $scaffold_file = new ScaffoldFileInfo($destination, $op);
- $this->listOfScaffoldFiles[$destination_rel_path] = $scaffold_file;
- $this->resolvedFileMappings[$package_name][$destination_rel_path] = $scaffold_file;
- }
- }
- }
-
- /**
- * Provides access to the resolved scaffold files.
- *
- * @return \Drupal\Component\Scaffold\ScaffoldFileInfo[][]
- * Collation of all scaffold files.
- */
- public function resolvedScaffoldFiles() {
- return $this->resolvedFileMappings;
- }
-
- /**
- * Determines whether a given location has been overridden.
- *
- * As a side effect, reports a status message noting that the file was
- * skipped.
- *
- * @param \Drupal\Component\Scaffold\ScaffoldFileInfo $scaffold_file
- * Scaffold file location to test overridden status.
- *
- * @return bool
- * TRUE if specified location is overridden by another package.
- */
- public function overridden(ScaffoldFileInfo $scaffold_file) {
- $overriding_package = $this->findProvidingPackage($scaffold_file);
- $overridden = $scaffold_file->overridden($overriding_package);
- if ($overridden) {
- $this->io->write($scaffold_file->interpolate(" - Skip [dest-rel-path]: overridden in {$overriding_package}"));
- }
- return $overridden;
- }
-
- /**
- * Finds the package name that provides the scaffold file.
- *
- * This will usually be $scaffold_file->packageName(), unless there are
- * multiple scaffold files with the same destination path. In the case
- * that there are multiple scaffold files, the name of the last package
- * that provided a scaffold file at that path will be returned.
- *
- * @param \Drupal\Component\Scaffold\ScaffoldFileInfo $scaffold_file
- * The scaffold file to use to find a providing package name.
- *
- * @return string
- * The name of the package that provided the scaffold file information.
- */
- protected function findProvidingPackage(ScaffoldFileInfo $scaffold_file) {
- // The scaffold file should always be in our list, but we will check
- // just to be sure that it really is.
- $dest_rel_path = $scaffold_file->destination()->relativePath();
- if (!array_key_exists($dest_rel_path, $this->listOfScaffoldFiles)) {
- $msg = $scaffold_file->interpolate(" - Scaffold file [dest-rel-path] not found in list of all scaffold files.");
- throw new \RuntimeException($msg);
- }
- return $this->listOfScaffoldFiles[$dest_rel_path]->packageName();
- }
-
-}
diff -u b/core/lib/Drupal/Component/Scaffold/Operations/SkipOp.php b/core/lib/Drupal/Component/Scaffold/Operations/SkipOp.php
--- b/core/lib/Drupal/Component/Scaffold/Operations/SkipOp.php
+++ b/core/lib/Drupal/Component/Scaffold/Operations/SkipOp.php
@@ -17,11 +17,28 @@
const ID = 'skip';
/**
+ * The message to output while processing.
+ *
+ * @var string
+ */
+ protected $message;
+
+ /**
+ * SkipOp constructor.
+ *
+ * @param string $message
+ * (optional) A custom message to output while skipping.
+ */
+ public function __construct($message = " - Skip [dest-rel-path]: disabled") {
+ $this->message = $message;
+ }
+
+ /**
* {@inheritdoc}
*/
public function process(ScaffoldFilePath $destination, IOInterface $io, ScaffoldOptions $options) {
$interpolator = $destination->getInterpolator();
- $io->write($interpolator->interpolate(" - Skip [dest-rel-path]: disabled"));
+ $io->write($interpolator->interpolate($this->message));
return new ScaffoldResult($destination, FALSE);
}
reverted:
--- b/core/tests/Drupal/Tests/Component/Scaffold/Integration/ScaffoldFileCollatorTest.php
+++ /dev/null
@@ -1,131 +0,0 @@
-getLocationReplacements();
- $scaffold_file_fixtures = [
- 'fixtures/drupal-assets-fixture' => [
- '[web-root]/index.php' => $fixtures->replaceOp('drupal-assets-fixture', 'index.php'),
- '[web-root]/.htaccess' => $fixtures->replaceOp('drupal-assets-fixture', '.htaccess'),
- '[web-root]/robots.txt' => $fixtures->replaceOp('drupal-assets-fixture', 'robots.txt'),
- '[web-root]/sites/default/default.services.yml' => $fixtures->replaceOp('drupal-assets-fixture', 'default.services.yml'),
- ],
- 'fixtures/drupal-profile' => [
- '[web-root]/sites/default/default.services.yml' => $fixtures->replaceOp('drupal-profile', 'profile.default.services.yml'),
- ],
- 'fixtures/drupal-drupal' => [
- '[web-root]/.htaccess' => new SkipOp(),
- '[web-root]/robots.txt' => $fixtures->appendOp('drupal-drupal-test-append', 'append-to-robots.txt'),
- ],
- ];
- $sut = new ScaffoldFileCollator($fixtures->io());
- // Test the system under test.
- $sut->collateScaffoldFiles($scaffold_file_fixtures, $locationReplacements);
- $scaffold_list = $this->accessProtected($sut, 'listOfScaffoldFiles');
- $resolved_file_mappings = $sut->resolvedScaffoldFiles();
- // Confirm that the keys of the output are the same as the keys of the
- // input.
- $this->assertEquals(array_keys($scaffold_file_fixtures), array_keys($resolved_file_mappings));
- // Also assert that we have the right ScaffoldFileInfo objects in the
- // destination.
- $this->assertResolvedToSameOp('fixtures/drupal-assets-fixture', '[web-root]/index.php', $scaffold_file_fixtures, $scaffold_list, $resolved_file_mappings);
- $this->assertResolvedToSameOp('fixtures/drupal-profile', '[web-root]/sites/default/default.services.yml', $scaffold_file_fixtures, $scaffold_list, $resolved_file_mappings);
- $this->assertResolvedToSameOp('fixtures/drupal-drupal', '[web-root]/robots.txt', $scaffold_file_fixtures, $scaffold_list, $resolved_file_mappings);
- // Assert that the files below have been overridden.
- $this->assertOverridden('fixtures/drupal-assets-fixture', '[web-root]/.htaccess', $scaffold_list, $resolved_file_mappings);
- $this->assertOverridden('fixtures/drupal-assets-fixture', '[web-root]/robots.txt', $scaffold_list, $resolved_file_mappings);
- }
-
- /**
- * Checks to see if a given file was not overridden.
- *
- * The package name in the scaffold list for the provided destination should
- * match the package name from the specified project.
- *
- * @param string $project
- * The project to check.
- * @param string $dest
- * The destination to check.
- * @param array $scaffold_file_fixtures
- * The test file mappings keyed by project and destination.
- * @param \Drupal\Component\Scaffold\ScaffoldFileInfo[] $scaffold_list
- * The list of scaffolded files keyed by destination.
- * @param array $resolved_file_mappings
- * The list of resolved file mappings keyed by project and destination.
- */
- protected function assertResolvedToSameOp($project, $dest, array $scaffold_file_fixtures, array $scaffold_list, array $resolved_file_mappings) {
- $resolved_file_info = $resolved_file_mappings[$project][$dest];
- $this->assertEquals(get_class($resolved_file_info), ScaffoldFileInfo::class);
- $resolved_scaffold_op = $resolved_file_info->op();
- // If this is an append op then it will be part of a conjunction op.
- $expected = get_class($scaffold_file_fixtures[$project][$dest]);
- if ($expected == AppendOp::class) {
- $this->assertEquals(ConjunctionOp::class, get_class($resolved_scaffold_op));
- }
- else {
- $this->assertEquals($expected, get_class($resolved_scaffold_op));
- $this->assertEquals($scaffold_file_fixtures[$project][$dest], $resolved_scaffold_op);
- }
- $this->assertEquals($project, $scaffold_list[$dest]->packageName());
- }
-
- /**
- * Checks if a given file was overridden.
- *
- * Assert that the file in the scaffold list at the specified destination
- * comes from a different package than the one in the file info.
- *
- * @param string $project
- * The project to check.
- * @param string $dest
- * The destination to check.
- * @param \Drupal\Component\Scaffold\ScaffoldFileInfo[] $scaffold_list
- * The list of scaffolded files keyed by destination.
- * @param array $resolved_file_mappings
- * The list of resolved file mappings keyed by project and destination.
- */
- protected function assertOverridden($project, $dest, array $scaffold_list, array $resolved_file_mappings) {
- $resolved_file_info = $resolved_file_mappings[$project][$dest];
- $this->assertEquals(get_class($resolved_file_info), ScaffoldFileInfo::class);
- $this->assertNotEquals($project, $scaffold_list[$dest]->packageName());
- }
-
- /**
- * Uses reflection to access a protected field of an object.
- *
- * @param mixed $obj
- * The object to inspect.
- * @param string $prop
- * The name of the property to access.
- *
- * @return mixed
- * The value of the requested property.
- */
- protected function accessProtected($obj, $prop) {
- $reflection = new \ReflectionClass($obj);
- $property = $reflection->getProperty($prop);
- $property->setAccessible(TRUE);
- return $property->getValue($obj);
- }
-
-}
only in patch2:
unchanged:
--- /dev/null
+++ b/core/lib/Drupal/Component/Scaffold/Operations/ScaffoldFileCollection.php
@@ -0,0 +1,103 @@
+ $package_file_mappings) {
+ foreach ($package_file_mappings as $destination_rel_path => $op) {
+ $destination = ScaffoldFilePath::destinationPath($package_name, $destination_rel_path, $location_replacements);
+ // If there was already a scaffolding operation happening at this path,
+ // and the new operation is Conjoinable, then use a ConjunctionOp to
+ // join together both operations. This will cause both operations to
+ // run, one after the other. At the moment, only AppendOp is
+ // conjoinable; all other operations simply replace anything at the same
+ // path.
+ if (isset($scaffoldFiles[$destination_rel_path])) {
+ $previous_scaffold_file = $scaffoldFiles[$destination_rel_path];
+ if ($op instanceof ConjoinableInterface) {
+ $op = new ConjunctionOp($previous_scaffold_file->op(), $op);
+ // Remove the previous file so we only do the operation once.
+ unset($scaffoldFilesByProject[$previous_scaffold_file->packageName()][$destination_rel_path]);
+ }
+ else {
+ $message = " - Skip [dest-rel-path]: overridden in {$package_name}";
+ $scaffoldFilesByProject[$previous_scaffold_file->packageName()][$destination_rel_path] = new ScaffoldFileInfo($destination, new SkipOp($message));
+ }
+ }
+ $scaffold_file = new ScaffoldFileInfo($destination, $op);
+ $scaffoldFiles[$destination_rel_path] = $scaffold_file;
+ $scaffoldFilesByProject[$package_name][$destination_rel_path] = $scaffold_file;
+ }
+ }
+ return new \RecursiveArrayIterator($scaffoldFilesByProject, \RecursiveArrayIterator::CHILD_ARRAYS_ONLY);
+ }
+
+ /**
+ * Processes the iterator created by ScaffoldFileCollection::create().
+ *
+ * @param \RecursiveIterator $iterator
+ * The iterator to process.
+ * @param \Composer\IO\IOInterface $io
+ * The Composer IO object.
+ * @param \Drupal\Component\Scaffold\ScaffoldOptions $scaffold_options
+ * The scaffold options.
+ * @param \Drupal\Component\Scaffold\Operations\ScaffoldResult[] $results
+ * The results array, passed by-reference and is populated by this method.
+ */
+ public static function process(RecursiveIterator $iterator, IOInterface $io, ScaffoldOptions $scaffold_options, array &$results) {
+ while ($iterator->valid()) {
+ if ($iterator->hasChildren()) {
+ $io->write("Scaffolding files for {$iterator->key()}:");
+ static::process($iterator->getChildren(), $io, $scaffold_options, $results);
+ }
+ else {
+ $scaffold_file = $iterator->current();
+ $results[$scaffold_file->destination()->relativePath()] = $scaffold_file->process($io, $scaffold_options);
+ }
+ $iterator->next();
+ }
+ }
+
+}
only in patch2:
unchanged:
--- /dev/null
+++ b/core/tests/Drupal/Tests/Component/Scaffold/Integration/ScaffoldFileCollectionTest.php
@@ -0,0 +1,67 @@
+getLocationReplacements();
+ $scaffold_file_fixtures = [
+ 'fixtures/drupal-assets-fixture' => [
+ '[web-root]/index.php' => $fixtures->replaceOp('drupal-assets-fixture', 'index.php'),
+ '[web-root]/.htaccess' => $fixtures->replaceOp('drupal-assets-fixture', '.htaccess'),
+ '[web-root]/robots.txt' => $fixtures->replaceOp('drupal-assets-fixture', 'robots.txt'),
+ '[web-root]/sites/default/default.services.yml' => $fixtures->replaceOp('drupal-assets-fixture', 'default.services.yml'),
+ ],
+ 'fixtures/drupal-profile' => [
+ '[web-root]/sites/default/default.services.yml' => $fixtures->replaceOp('drupal-profile', 'profile.default.services.yml'),
+ ],
+ 'fixtures/drupal-drupal' => [
+ '[web-root]/.htaccess' => new SkipOp(),
+ '[web-root]/robots.txt' => $fixtures->appendOp('drupal-drupal-test-append', 'append-to-robots.txt'),
+ ],
+ ];
+ $sut = ScaffoldFileCollection::create($scaffold_file_fixtures, $locationReplacements);
+ $resolved_file_mappings = iterator_to_array($sut);
+ // Confirm that the keys of the output are the same as the keys of the
+ // input.
+ $this->assertEquals(array_keys($scaffold_file_fixtures), array_keys($resolved_file_mappings));
+ // Ensure that '[web-root]/robots.txt' has been removed as it is now part of
+ // a conjunction operation.
+ $this->assertEquals([
+ '[web-root]/index.php',
+ '[web-root]/.htaccess',
+ '[web-root]/sites/default/default.services.yml',
+ ], array_keys($resolved_file_mappings['fixtures/drupal-assets-fixture']));
+
+ $this->assertEquals([
+ '[web-root]/sites/default/default.services.yml',
+ ], array_keys($resolved_file_mappings['fixtures/drupal-profile']));
+
+ $this->assertEquals([
+ '[web-root]/.htaccess',
+ '[web-root]/robots.txt',
+ ], array_keys($resolved_file_mappings['fixtures/drupal-drupal']));
+
+ // Test that .htaccess is skipped.
+ $this->assertInstanceOf(SkipOp::class, $resolved_file_mappings['fixtures/drupal-assets-fixture']['[web-root]/.htaccess']->op());
+ // Test that the expected conjunction operation exists.
+ $this->assertInstanceOf(ConjunctionOp::class, $resolved_file_mappings['fixtures/drupal-drupal']['[web-root]/robots.txt']->op());
+ }
+
+}