Skip to content
Snippets Groups Projects

Add a Composer plugin to core for writing the locations of Drupal files - 9.5.x

Files
36
<?php
namespace Drupal\Composer\Plugin\Locations;
use Composer\IO\IOInterface;
use Composer\Composer;
use Composer\Factory;
/**
* Generates the DrupalLocation file which defines the Drupal app root constant.
*
* Drupal and the app root may be installed in various locations, and at runtime
* it is not easy to determine these. Part of the difficulty is that Composer
* can symlink packages in, and PHP's file location constants such as __DIR__
* resolve symlinks.
*
* The authority on the location of packages is Composer, since it puts them in
* their locations, and the authority on the location of the app root is this
* scaffolding plugin, since it reads it from the composer.json file.
*
* Reading the locations from composer.json is possible at runtime, but is
* undesirable for performance. Therefore, during the Composer install process
* when we have access this data, this plugin writes a PHP class file which
* defines the locations as class constants.
*
* The class file is written into the Composer project root. To be loadable, it
* must be defined to the autoloader in the project's composer.json:
* @code
* "autoload": {
* "psr-4": {
* "Drupal\\Locations\\": ""
* }
* },
* @endcode
*
* To ensure the project's codebase is portable, the generated class must not
* use absolute paths. It should instead use the __DIR__ constant, which in the
* generated class will give the project root.
*
* @internal
*/
class GenerateLocationsClass {
/**
* The template code for the DrupalLocation class file which is written.
*
* This contains the token '%app_root' which is replaced when the file is
* written.
*
* @var string
*/
private static $generatedFileTemplate = <<<'EOF'
<?php
namespace Drupal\Locations;
/**
* Defines the Drupal app root.
*
* This allows Drupal to find the directory in which Drupal files are
* located, such as Drupal core, the sites directory, and the extension
* directories.
*
* This is defined in a class so it is available as soon as the Composer
* autoloader has been included.
*
* This file was generated by
* \Drupal\Composer\Plugin\Locations\GenerateLocationsClass.
*/
class DrupalLocation {
/**
* Defines the root directory of the Drupal installation.
*
* This is the directory containing the scaffolded index.php file which is
* Drupal's web entry point.
*/
public const APP_ROOT = __DIR__ . '%app_root';
}
EOF;
/**
* This class provides only static methods.
*/
private function __construct() {
}
/**
* Writes the DrupalLocations class into the same directory as this file.
*
* @param \Composer\Composer $composer
* The Composer object.
* @param \Composer\IO\IOInterface $io
* The Composer I/O object.
*/
public static function generate(Composer $composer, IOInterface $io) {
$extra = $composer->getPackage()->getExtra();
// Composer changes the current directory to the project root, even if it
// run in a subdirectory.
$absolute_project_root = getcwd();
if (isset($extra['drupal-scaffold']['locations']['web-root'])) {
// The root composer.json defines a scaffold location for Drupal. We
// therefore know this is the app root.
$web_root = $extra['drupal-scaffold']['locations']['web-root'];
$absolute_app_root = $absolute_project_root . '/' . $web_root;
$absolute_app_root = realpath($absolute_app_root);
$io->write("Drupal app root defined as {$absolute_app_root}.");
$relative_web_root = '/' . trim($web_root, '/');
static::generateLocations($composer, $io, $absolute_project_root, $absolute_app_root, $relative_web_root);
}
else {
// Get the project root's absolute path from the root composer file path.
// This is given as a relative path.
$composer_file_path = Factory::getComposerFile();
$absolute_app_root = realpath(dirname($composer_file_path));
$io->write("Drupal app root assumed to be the Composer project root, {$absolute_app_root}.");
static::generateLocations($composer, $io, $absolute_project_root, $absolute_app_root, '');
}
}
/**
* Writes the DrupalLocations class with the given value of the app root.
*
* @param \Composer\Composer $composer
* The Composer object.
* @param \Composer\IO\IOInterface $io
* The Composer I/O object.
* @param string $absolute_project_root
* The absolute path to the Composer project root, without a trailing slash.
* @param string $absolute_app_root
* The absolute path to the Drupal app root, without a trailing slash.
* @param string $relative_app_root
* The relative path to the Drupal app root, relative to the Composer
* project root, with an initial slash, and without a trailing slash.
*/
protected static function generateLocations(Composer $composer, IOInterface $io, $absolute_project_root, $absolute_app_root, $relative_app_root) {
$class_php = str_replace('%app_root', $relative_app_root, static::$generatedFileTemplate);
$locations_class_directory = static::getLocationsClassDirectory($absolute_project_root, $absolute_app_root);
if (!is_writable($locations_class_directory)) {
throw new \Exception(sprintf("The directory %s is not writable.", $locations_class_directory));
}
$file_location = $locations_class_directory . '/DrupalLocation.php';
$result = file_put_contents($file_location, $class_php);
if ($result !== FALSE) {
$io->write("Writing Drupal locations class to $file_location.");
}
else {
$io->writeError("There was a problem writing the Drupal locations class to $file_location.");
}
}
/**
* Gets the directory to write the locations class to.
*
* @param string $absolute_project_root
* The absolute path to the Composer project root, without a trailing slash.
* @param string $absolute_app_root
* The absolute path to the Drupal app root, without a trailing slash.
*
* @return string
* The absolute path of the directory to write to.
*/
protected static function getLocationsClassDirectory(string $absolute_project_root, string $absolute_app_root): string {
return $absolute_project_root;
}
}
Loading