Skip to content

Commit

Permalink
Drastically simplify using Composer Runtime API
Browse files Browse the repository at this point in the history
  • Loading branch information
webflo committed Mar 15, 2024
1 parent 8998aec commit 0a14569
Show file tree
Hide file tree
Showing 9 changed files with 1,029 additions and 19 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
}
},
"require-dev": {
"phpunit/phpunit": "^10.4"
"phpunit/phpunit": "^10.4",
"mikey179/vfsstream": "^1.6"
},
"config": {
"platform": {
Expand Down
289 changes: 276 additions & 13 deletions src/DrupalFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,298 @@

namespace DrupalFinder;

use Composer\InstalledVersions;

/**
* @deprecated in drupal-finder:1.3.0 and is removed from drupal-finder:2.0.0.
* Use \DrupalFinder\DrupalFinderComposerRuntime instead.
*/
class DrupalFinder
{
/**
* Get the Drupal root path.
* Drupal root environment variable.
*/
const ENV_DRUPAL_ROOT = 'DRUPAL_FINDER_DRUPAL_ROOT';

/**
* Composer root environment variable.
*/
const ENV_COMPOSER_ROOT = 'DRUPAL_FINDER_COMPOSER_ROOT';

/**
* Vendor directory environment variable.
*/
const ENV_VENDOR_DIR = 'DRUPAL_FINDER_VENDOR_DIR';

/**
* Drupal web public directory.
*
* @var string
*/
private $drupalRoot;

/**
* Drupal package composer directory.
*
* @var bool
*/
private $composerRoot;

/**
* Composer vendor directory.
*
* @var string
*
* @see https://getcomposer.org/doc/06-config.md#vendor-dir
*/
public function getDrupalRoot(): ?string
private $vendorDir;

/**
* Initialize finder.
*
* Optionally pass the starting path.
*
* @param string|null $start_path
* The path to begin the search from.
*
* @throws \Exception
* @todo Make $start_path mandatory in v2.
*/
public function __construct($start_path = null) {
// Initialize path variables to false, indicating their locations are
// not yet known.
$this->drupalRoot = false;
$this->composerRoot = false;
$this->vendorDir = false;

// If a starting path was provided, attempt to locate and set path
// variables.
if (!empty($start_path)) {
$this->discoverRoots($start_path);
}
}

/**
* Locate Drupal, Composer, and vendor directory paths.
*
* @param string $start_path
* The path to begin the search from.
*
* @return bool
* True if the Drupal root was identified, false otherwise.
*
* @throws \Exception
*
* @deprecated Will be removed in v2. Future usage should instantiate
* a new DrupalFinder object by passing the starting path to its
* constructor.
*/
public function locateRoot($start_path)
{
$core = InstalledVersions::getInstallPath('drupal/core');
return $core ? realpath(dirname($core)) : null;
$this->discoverRoots($start_path);
return !empty($this->getDrupalRoot());
}

/**
* Get the path to the Composer root directory.
* Get the Drupal root.
*
* @return string|bool
* The path to the Drupal root, if it was discovered. False otherwise.
*/
public function getComposerRoot(): ?string
public function getDrupalRoot()
{
$root = InstalledVersions::getRootPackage();
return realpath($root['install_path']);
$environment_path = $this->getValidEnvironmentVariablePath(self::ENV_DRUPAL_ROOT);

return !empty($environment_path) ? $environment_path : $this->drupalRoot;
}

/**
* Get the Composer root.
*
* @return string|bool
* The path to the Composer root, if it was discovered. False otherwise.
*/
public function getComposerRoot()
{
$environment_path = $this->getValidEnvironmentVariablePath(self::ENV_COMPOSER_ROOT);
return !empty($environment_path) ? $environment_path : $this->composerRoot;
}

/**
* Get the vendor path.
*
* @return string|bool
* The path to the vendor directory, if it was found. False otherwise.
*/
public function getVendorDir()
{
$environment_path = $this->getValidEnvironmentVariablePath(self::ENV_VENDOR_DIR);
return !empty($environment_path) ? $environment_path : $this->vendorDir;
}

/**
* Discover all valid paths.
*
* @param $start_path
* The path to start the search from.
*
* @throws \Exception
*/
protected function discoverRoots($start_path) {
// Since we are discovering, reset all path variables.
$this->drupalRoot = false;
$this->composerRoot = false;
$this->vendorDir = false;

foreach (array(true, false) as $follow_symlinks) {
$path = $start_path;
if ($follow_symlinks && is_link($path)) {
$path = realpath($path);
}

// Check the start path.
if ($this->findAndValidateRoots($path)) {
return;
} else {
// Move up dir by dir and check each.
while ($path = $this->shiftPathUp($path)) {
if ($follow_symlinks && is_link($path)) {
$path = realpath($path);
}
if ($this->findAndValidateRoots($path)) {
return;
}
}
}
}
}

/**
* Determine if a valid Drupal root exists.
*
* In addition, set any valid path properties if they are found.
*
* @param $path
* The starting path to search from.
*
* @return bool
* True if all roots were discovered and validated. False otherwise.
*/
protected function findAndValidateRoots($path)
{

if (!empty($path) && is_dir($path) && file_exists($path . '/autoload.php') && file_exists($path . '/' . $this->getComposerFileName())) {
// Additional check for the presence of core/composer.json to
// grant it is not a Drupal 7 site with a base folder named "core".
$candidate = 'core/includes/common.inc';
if (file_exists($path . '/' . $candidate) && file_exists($path . '/core/core.services.yml')) {
if (file_exists($path . '/core/misc/drupal.js') || file_exists($path . '/core/assets/js/drupal.js')) {
$this->composerRoot = $path;
$this->drupalRoot = $path;
$this->vendorDir = $this->composerRoot . '/vendor';
}
}
}
if (!empty($path) && is_dir($path) && file_exists($path . '/' . $this->getComposerFileName())) {
$json = json_decode(
file_get_contents($path . '/' . $this->getComposerFileName()),
true
);

if (is_null($json)) {
throw new \Exception('Unable to decode ' . $path . '/' . $this->getComposerFileName());
}

if (is_array($json)) {
if (isset($json['extra']['installer-paths']) && is_array($json['extra']['installer-paths'])) {
foreach ($json['extra']['installer-paths'] as $install_path => $items) {
if (in_array('type:drupal-core', $items) ||
in_array('drupal/core', $items) ||
in_array('drupal/drupal', $items)) {
$this->composerRoot = $path;
// @todo: Remove this magic and detect the major version instead.
if (($install_path == 'core') || ((isset($json['name'])) && ($json['name'] == 'drupal/drupal'))) {
$install_path = '';
} elseif (substr($install_path, -5) == '/core') {
$install_path = substr($install_path, 0, -5);
}
$this->drupalRoot = rtrim($path . '/' . $install_path, '/');
$this->vendorDir = $this->composerRoot . '/vendor';
}
}
}
}
}
if ($this->composerRoot && file_exists($this->composerRoot . '/' . $this->getComposerFileName())) {
$json = json_decode(
file_get_contents($path . '/' . $this->getComposerFileName()),
true
);
if (is_array($json) && isset($json['config']['vendor-dir'])) {
$this->vendorDir = $this->composerRoot . '/' . $json['config']['vendor-dir'];
}
}

return $this->allPathsDiscovered();
}

/**
* @return string
*/
public function getVendorDir(): ?string
protected function getComposerFileName()
{
$reflection = new \ReflectionClass(InstalledVersions::class);
return realpath(dirname(dirname($reflection->getFileName())));
return trim(getenv('COMPOSER')) ?: 'composer.json';
}

/**
* Helper function to quickly determine whether or not all paths were discovered.
*
* @return bool
* True if all paths have been discovered, false if one or more haven't been found.
*/
protected function allPathsDiscovered() {
return !empty($this->drupalRoot) && !empty($this->composerRoot) && !empty($this->vendorDir);
}

/**
* Helper function to quickly determine whether or not all paths are known.
*
* @return bool
* True if all paths are known, false if one or more paths are unknown.
*/
protected function allPathsKnown() {
return !empty($this->getDrupalRoot()) && !empty($this->getComposerRoot()) && !empty($this->getVendorDir());
}

/**
* Get path stored in environment variable.
*
* @param string $variable
* The name of the environment variable to retrieve the path from.
*
* @return false|string
* A path if it is valid. False otherwise.
*/
protected function getValidEnvironmentVariablePath($variable) {
$path = getenv($variable);
if (is_string($path) && is_dir($path)) {
return $path;
}
return false;
}

/**
* Returns parent directory.
*
* @param string
* Path to start from
*
* @return string|false
* Parent path of given path or false when $path is filesystem root
*/
private function shiftPathUp($path)
{
$parent = dirname($path);

return in_array($parent, ['.', $path]) ? false : $parent;
}

}
41 changes: 41 additions & 0 deletions src/DrupalFinderComposerRuntime.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

/**
* @file
* Contains \DrupalFinder\DrupalFinderComposerRuntime.
*/

namespace DrupalFinder;

use Composer\InstalledVersions;

class DrupalFinderComposerRuntime
{
/**
* Get the Drupal root path.
*/
public function getDrupalRoot(): ?string
{
$core = InstalledVersions::getInstallPath('drupal/core');
return $core ? realpath(dirname($core)) : null;
}

/**
* Get the path to the Composer root directory.
*/
public function getComposerRoot(): ?string
{
$root = InstalledVersions::getRootPackage();
return realpath($root['install_path']);
}

/**
* Get the vendor path.
*/
public function getVendorDir(): ?string
{
$reflection = new \ReflectionClass(InstalledVersions::class);
return realpath(dirname(dirname($reflection->getFileName())));
}

}
Loading

0 comments on commit 0a14569

Please sign in to comment.