Handler.php in Drupal 10
Same filename and directory in other branches
Namespace
Drupal\Composer\Plugin\ScaffoldFile
composer/Plugin/Scaffold/Handler.phpView source
<?php
namespace Drupal\Composer\Plugin\Scaffold;
use Composer\Composer;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Installer\PackageEvent;
use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Util\Filesystem;
use Drupal\Composer\Plugin\Scaffold\Operations\OperationData;
use Drupal\Composer\Plugin\Scaffold\Operations\OperationFactory;
use Drupal\Composer\Plugin\Scaffold\Operations\ScaffoldFileCollection;
/**
* Core class of the plugin.
*
* Contains the primary logic which determines the files to be fetched and
* processed.
*
* @internal
*/
class Handler {
/**
* Composer hook called before scaffolding begins.
*/
const PRE_DRUPAL_SCAFFOLD_CMD = 'pre-drupal-scaffold-cmd';
/**
* Composer hook called after scaffolding completes.
*/
const POST_DRUPAL_SCAFFOLD_CMD = 'post-drupal-scaffold-cmd';
/**
* The Composer service.
*
* @var \Composer\Composer
*/
protected $composer;
/**
* Composer's I/O service.
*
* @var \Composer\IO\IOInterface
*/
protected $io;
/**
* The scaffold options in the top-level composer.json's 'extra' section.
*
* @var \Drupal\Composer\Plugin\Scaffold\ManageOptions
*/
protected $manageOptions;
/**
* The manager that keeps track of which packages are allowed to scaffold.
*
* @var \Drupal\Composer\Plugin\Scaffold\AllowedPackages
*/
protected $manageAllowedPackages;
/**
* The list of listeners that are notified after a package event.
*
* @var \Drupal\Composer\Plugin\Scaffold\PostPackageEventListenerInterface[]
*/
protected $postPackageListeners = [];
/**
* Handler constructor.
*
* @param \Composer\Composer $composer
* The Composer service.
* @param \Composer\IO\IOInterface $io
* The Composer I/O service.
*/
public function __construct(Composer $composer, IOInterface $io) {
$this->composer = $composer;
$this->io = $io;
$this->manageOptions = new ManageOptions($composer);
$this->manageAllowedPackages = new AllowedPackages($composer, $io, $this->manageOptions);
}
/**
* Registers post-package events if the 'require' command was called.
*/
public function requireWasCalled() {
// In order to differentiate between post-package events called after
// 'composer require' vs. the same events called at other times, we will
// only install our handler when a 'require' event is detected.
$this->postPackageListeners[] = $this->manageAllowedPackages;
}
/**
* Posts package command event.
*
* We want to detect packages 'require'd that have scaffold files, but are not
* yet allowed in the top-level composer.json file.
*
* @param \Composer\Installer\PackageEvent $event
* Composer package event sent on install/update/remove.
*/
public function onPostPackageEvent(PackageEvent $event) {
foreach ($this->postPackageListeners as $listener) {
$listener
->event($event);
}
}
/**
* Creates scaffold operation objects for all items in the file mappings.
*
* @param \Composer\Package\PackageInterface $package
* The package that relative paths will be relative from.
* @param array $package_file_mappings
* The package file mappings array keyed by destination path and the values
* are operation metadata arrays.
*
* @return \Drupal\Composer\Plugin\Scaffold\Operations\OperationInterface[]
* A list of scaffolding operation objects
*/
protected function createScaffoldOperations(PackageInterface $package, array $package_file_mappings) {
$scaffold_op_factory = new OperationFactory($this->composer);
$scaffold_ops = [];
foreach ($package_file_mappings as $dest_rel_path => $data) {
$operation_data = new OperationData($dest_rel_path, $data);
$scaffold_ops[$dest_rel_path] = $scaffold_op_factory
->create($package, $operation_data);
}
return $scaffold_ops;
}
/**
* Copies all scaffold files from source to destination.
*/
public function scaffold() {
// Recursively get the list of allowed packages. Only allowed packages
// may declare scaffold files. Note that the top-level composer.json file
// is implicitly allowed.
$allowed_packages = $this->manageAllowedPackages
->getAllowedPackages();
if (empty($allowed_packages)) {
$this->io
->write("Nothing scaffolded because no packages are allowed in the top-level composer.json file.");
return;
}
// Call any pre-scaffold scripts that may be defined.
$dispatcher = new EventDispatcher($this->composer, $this->io);
$dispatcher
->dispatch(self::PRE_DRUPAL_SCAFFOLD_CMD);
// Fetch the list of file mappings from each allowed package and normalize
// them.
$file_mappings = $this
->getFileMappingsFromPackages($allowed_packages);
$location_replacements = $this->manageOptions
->getLocationReplacements();
$scaffold_options = $this->manageOptions
->getOptions();
// Create a collection of scaffolded files to process. This determines which
// take priority and which are combined.
$scaffold_files = new ScaffoldFileCollection($file_mappings, $location_replacements);
// Get the scaffold files whose contents on disk match what we are about to
// write. We can remove these from consideration, as rewriting would be a
// no-op.
$unchanged = $scaffold_files
->checkUnchanged();
$scaffold_files
->filterFiles($unchanged);
// Process the list of scaffolded files.
$scaffold_results = $scaffold_files
->processScaffoldFiles($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
// requires this in order to easily locate relocated vendor dirs.
$web_root = $this->manageOptions
->getOptions()
->getLocation('web-root');
if (!GenerateAutoloadReferenceFile::autoloadFileCommitted($this->io, $this
->rootPackageName(), $web_root)) {
$scaffold_results[] = GenerateAutoloadReferenceFile::generateAutoload($this->io, $this
->rootPackageName(), $web_root, $this
->getVendorPath());
}
// Add the managed scaffold files to .gitignore if applicable.
$gitIgnoreManager = new ManageGitIgnore($this->io, getcwd());
$gitIgnoreManager
->manageIgnored($scaffold_results, $scaffold_options);
// Call post-scaffold scripts.
$dispatcher
->dispatch(self::POST_DRUPAL_SCAFFOLD_CMD);
}
/**
* Gets the path to the 'vendor' directory.
*
* @return string
* The file path of the vendor directory.
*/
protected function getVendorPath() {
$vendor_dir = $this->composer
->getConfig()
->get('vendor-dir');
$filesystem = new Filesystem();
return $filesystem
->normalizePath(realpath($vendor_dir));
}
/**
* Gets a consolidated list of file mappings from all allowed packages.
*
* @param \Composer\Package\Package[] $allowed_packages
* A multidimensional array of file mappings, as returned by
* self::getAllowedPackages().
*
* @return \Drupal\Composer\Plugin\Scaffold\Operations\OperationInterface[][]
* An array of destination paths => scaffold operation objects.
*/
protected function getFileMappingsFromPackages(array $allowed_packages) {
$file_mappings = [];
foreach ($allowed_packages as $package_name => $package) {
$file_mappings[$package_name] = $this
->getPackageFileMappings($package);
}
return $file_mappings;
}
/**
* Gets the array of file mappings provided by a given package.
*
* @param \Composer\Package\PackageInterface $package
* The Composer package from which to get the file mappings.
*
* @return \Drupal\Composer\Plugin\Scaffold\Operations\OperationInterface[]
* An array of destination paths => scaffold operation objects.
*/
protected function getPackageFileMappings(PackageInterface $package) {
$options = $this->manageOptions
->packageOptions($package);
if ($options
->hasFileMapping()) {
return $this
->createScaffoldOperations($package, $options
->fileMapping());
}
// Warn the user if they allow a package that does not have any scaffold
// files. We will ignore drupal/core, though, as it is implicitly allowed,
// but might not have scaffold files (version 8.7.x and earlier).
if (!$options
->hasAllowedPackages() && $package
->getName() != 'drupal/core') {
$this->io
->writeError("The allowed package {$package->getName()} does not provide a file mapping for Composer Scaffold.");
}
return [];
}
/**
* Gets the root package name.
*
* @return string
* The package name of the root project
*/
protected function rootPackageName() {
$root_package = $this->composer
->getPackage();
return $root_package
->getName();
}
}