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 @@ -154,16 +154,15 @@ // them. $file_mappings = $this->getFileMappingsFromPackages($allowed_packages); - // Analyze the list of file mappings, and determine which take priority. $location_replacements = $this->manageOptions->getLocationReplacements(); - $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] - ); + $scaffold_options = $this->manageOptions->getOptions(); + + // Create a collection of scaffolded files to process. This determines which + // take priority and which are conjoined. + $scaffold_files = new ScaffoldFileCollection($file_mappings, $location_replacements); + + // Process the list of scaffolded files. + $scaffold_results = ScaffoldFileCollection::process($scaffold_files, $this->io, $scaffold_options); // Generate an autoload file in the document root that includes the // autoload.php file in the vendor directory, wherever that is. Drupal @@ -175,7 +174,7 @@ // Add the managed scaffold files to .gitignore if applicable. $gitIgnoreManager = new ManageGitIgnore($this->io, getcwd()); - $gitIgnoreManager->manageIgnored($scaffold_results, $this->manageOptions->getOptions()); + $gitIgnoreManager->manageIgnored($scaffold_results, $scaffold_options); // Call post-scaffold scripts. $dispatcher->dispatch(self::POST_COMPOSER_SCAFFOLD_CMD); diff -u b/core/lib/Drupal/Component/Scaffold/Operations/ScaffoldFileCollection.php b/core/lib/Drupal/Component/Scaffold/Operations/ScaffoldFileCollection.php --- b/core/lib/Drupal/Component/Scaffold/Operations/ScaffoldFileCollection.php +++ b/core/lib/Drupal/Component/Scaffold/Operations/ScaffoldFileCollection.php @@ -7,44 +7,38 @@ use Drupal\Component\Scaffold\ScaffoldFileInfo; use Drupal\Component\Scaffold\ScaffoldFilePath; use Drupal\Component\Scaffold\ScaffoldOptions; -use RecursiveIterator; /** * Collection of scaffold files. */ -final class ScaffoldFileCollection { +class ScaffoldFileCollection implements \IteratorAggregate { /** - * ScaffoldFileCollection constructor. + * Nested list of all scaffold files. + * + * The top level array maps from the package name to the collection of + * scaffold files provided by that package. Each collection of scaffold files + * is keyed by destination path. + * + * @var \Drupal\Component\Scaffold\ScaffoldFileInfo[][] */ - private function __construct() { - // This class only provides static methods. - } + protected $scaffoldFilesByProject = []; /** * ScaffoldFileCollection constructor. * * @param array $file_mappings - * An multidimensional array of file mappings, as returned by - * self::getFileMappingsFromPackages(). + * A multidimensional array of file mappings. * @param \Drupal\Component\Scaffold\Interpolator $location_replacements * An object with the location mappings (e.g. [web-root]). - * - * @return \RecursiveArrayIterator - * An iterator on a multi-dimensional array of ScaffoldFileInfo objects - * keyed by the project and the destination path. - * - * @see \Drupal\Component\Scaffold\ScaffoldFileInfo */ - public static function create(array $file_mappings, Interpolator $location_replacements) { + public function __construct(array $file_mappings, Interpolator $location_replacements) { // Collection of all destination paths to be scaffolded. Used to determine // when two project scaffold the same file and we have to skip or use a // ConjunctionOp. $scaffoldFiles = []; - // Collection of all destination paths to be scaffolded by project. - $scaffoldFilesByProject = []; - + // Build the list of ScaffoldFileInfo objects by project. 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); @@ -59,45 +53,49 @@ 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]); + unset($this->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)); + $this->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; + $this->scaffoldFilesByProject[$package_name][$destination_rel_path] = $scaffold_file; } } - return new \RecursiveArrayIterator($scaffoldFilesByProject, \RecursiveArrayIterator::CHILD_ARRAYS_ONLY); + } + + /** + * {@inheritdoc} + */ + public function getIterator() { + return new \RecursiveArrayIterator($this->scaffoldFilesByProject, \RecursiveArrayIterator::CHILD_ARRAYS_ONLY); } /** * Processes the iterator created by ScaffoldFileCollection::create(). * - * @param \RecursiveIterator $iterator + * @param \Drupal\Component\Scaffold\Operations\ScaffoldFileCollection $collection * 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. + * + * @return \Drupal\Component\Scaffold\Operations\ScaffoldResult[] + * The results array. */ - 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(); + public static function process(ScaffoldFileCollection $collection, IOInterface $io, ScaffoldOptions $scaffold_options) { + $results = []; + foreach ($collection as $project_name => $scaffold_files) { + $io->write("Scaffolding files for {$project_name}:"); + foreach ($scaffold_files as $scaffold_file) { $results[$scaffold_file->destination()->relativePath()] = $scaffold_file->process($io, $scaffold_options); } - $iterator->next(); } + return $results; } } diff -u b/core/tests/Drupal/Tests/Component/Scaffold/Integration/ScaffoldFileCollectionTest.php b/core/tests/Drupal/Tests/Component/Scaffold/Integration/ScaffoldFileCollectionTest.php --- b/core/tests/Drupal/Tests/Component/Scaffold/Integration/ScaffoldFileCollectionTest.php +++ b/core/tests/Drupal/Tests/Component/Scaffold/Integration/ScaffoldFileCollectionTest.php @@ -16,7 +16,7 @@ class ScaffoldFileCollectionTest extends TestCase { /** - * @covers ::create + * @covers ::__construct */ public function testCreate() { $fixtures = new Fixtures(); @@ -36,7 +36,7 @@ '[web-root]/robots.txt' => $fixtures->appendOp('drupal-drupal-test-append', 'append-to-robots.txt'), ], ]; - $sut = ScaffoldFileCollection::create($scaffold_file_fixtures, $locationReplacements); + $sut = new ScaffoldFileCollection($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.