2021-01-08 11:55:00 +01:00

243 lines
7.2 KiB
PHP

<?php
namespace Drupal\xautoload\ClassFinder;
use Drupal\xautoload\DirectoryBehavior\DirectoryBehaviorInterface;
use Drupal\xautoload\ClassFinder\InjectedApi\LoadClassInjectedAPI;
use Drupal\xautoload\ClassFinder\InjectedApi\InjectedApiInterface;
use Drupal\xautoload\DirectoryBehavior\DefaultDirectoryBehavior;
use xautoload_FinderPlugin_Interface;
use Drupal\xautoload\DirectoryBehavior\Psr0DirectoryBehavior;
/**
* Helper class for the class finder.
* This is not part of ClassFinder, because we want to use the same logic for
* namespaces (PSR-0) and prefixes (PEAR).
*
* This thing does not actually deal with class names, but with transformed
* paths.
*
* Example A:
* When looking for a class \Aaa\Bbb\Ccc_Ddd, the class finder will
* 1. Determine that this class is within a namespace.
* 2. Transform that into "Aaa/Bbb/Ccc/Ddd.php".
* 3. Check if the namespace map evaluator has anything registered for
* 3.1. "Aaa/Bbb/"
* 3.2. "Aaa/"
* 3.3. ""
*
* Example A:
* When looking for a class Aaa_Bbb_Ccc, the class finder will
* 1. Determine that this class is NOT within a namespace.
* 2. Check if a file is explicitly registered for the class itself.
* 3. Transform the class name into "Aaa/Bbb/Ccc.php".
* 4. Check if the prefix map evaluator has anything registered for
* 4.1. "Aaa/Bbb/"
* 4.2. "Aaa/"
* 4.3. ""
*/
class GenericPrefixMap {
/**
* @var DirectoryBehaviorInterface[][]
* Format: $[$logical_base_path][$deep_path] = $behavior
*/
protected $paths = array();
/**
* @var string
* Either '\\' or '_'.
*/
protected $separator;
/**
* @param string $separator
*/
function __construct($separator) {
$this->separator = $separator;
}
/**
* If a class file would be in
* $psr0_root . '/' . $path_fragment . $path_suffix
* then instead, we look in
* $deep_path . $path_suffix
*
* @param string $logical_base_path
* The would-be namespace path relative to PSR-0 root.
* That is, the namespace with '\\' replaced by '/'.
* @param string $deep_path
* The filesystem location of the (PSR-0) subfolder for the given namespace.
* @param DirectoryBehaviorInterface $behavior
* Behavior in this directory.
*/
function registerDeepPath($logical_base_path, $deep_path, $behavior) {
$this->paths[$logical_base_path][$deep_path] = $behavior;
}
/**
* @param string $logical_base_path
* The would-be namespace path relative to PSR-0 root.
* That is, the namespace with '\\' replaced by '/'.
* @param string $deep_path
* The filesystem location of the (PSR-0) subfolder for the given namespace.
* @param DirectoryBehaviorInterface $behavior
* Behavior in this directory.
*/
function prependDeepPath($logical_base_path, $deep_path, $behavior) {
$this->paths[$logical_base_path]
= isset($this->paths[$logical_base_path])
? array($deep_path => $behavior) + $this->paths[$logical_base_path]
: array($deep_path => $behavior);
}
/**
* Register a bunch of those paths ..
*
* @param array[] $map
*
* @throws \Exception
*/
function registerDeepPaths(array $map) {
foreach ($map as $key => $paths) {
if (isset($this->paths[$key])) {
$paths += $this->paths[$key];
}
$this->paths[$key] = $paths;
}
}
/**
* Delete a registered path mapping.
*
* @param string $logical_base_path
* @param string $deep_path
*/
function unregisterDeepPath($logical_base_path, $deep_path) {
unset($this->paths[$logical_base_path][$deep_path]);
}
/**
* @param string $class
* @param string $logical_path
* Class name translated into a logical path, either with PSR-4 or with PEAR
* translation rules.
* @param int|bool $lastpos
* Position of the last directory separator in $logical_path.
* FALSE, if there is no directory separator in $logical_path.
*
* @return bool|NULL
* TRUE, if the class was found.
*/
function loadClass($class, $logical_path, $lastpos) {
$pos = $lastpos;
while (TRUE) {
$logical_base_path = (FALSE === $pos)
? ''
: substr($logical_path, 0, $pos + 1);
if (isset($this->paths[$logical_base_path])) {
foreach ($this->paths[$logical_base_path] as $dir => $behavior) {
if ($behavior instanceof DefaultDirectoryBehavior) {
// PSR-4 and PEAR
if (file_exists($file = $dir . substr($logical_path, $pos + 1))) {
require $file;
return TRUE;
}
}
elseif ($behavior instanceof Psr0DirectoryBehavior) {
// PSR-0
if (file_exists(
$file = $dir
. substr($logical_path, $pos + 1, $lastpos - $pos)
. str_replace('_', '/', substr($logical_path, $lastpos + 1))
)) {
require $file;
return TRUE;
}
}
elseif ($behavior instanceof xautoload_FinderPlugin_Interface) {
// Legacy "FinderPlugin".
$api = new LoadClassInjectedAPI($class);
if ($behavior->findFile($api, $logical_base_path, substr($logical_path, $pos + 1), $dir)) {
return TRUE;
}
}
}
}
// Continue with parent fragment.
if (FALSE === $pos) {
return NULL;
}
$pos = strrpos($logical_base_path, '/', -2);
}
return NULL;
}
/**
* Find the file for a class that in PSR-0 or PEAR would be in
* $psr_0_root . '/' . $path_fragment . $path_suffix
*
* @param InjectedApiInterface $api
* @param string $logical_path
* Class name translated into a logical path, either with PSR-4 or with PEAR
* translation rules.
* @param int|bool $lastpos
* Position of the last directory separator in $logical_path.
* FALSE, if there is no directory separator in $logical_path.
*
* @return bool|NULL
* TRUE, if the class was found.
*/
function apiFindFile($api, $logical_path, $lastpos) {
$pos = $lastpos;
while (TRUE) {
$logical_base_path = (FALSE === $pos)
? ''
: substr($logical_path, 0, $pos + 1);
if (isset($this->paths[$logical_base_path])) {
foreach ($this->paths[$logical_base_path] as $dir => $behavior) {
if ($behavior instanceof DefaultDirectoryBehavior) {
// PSR-4 and PEAR
if ($api->suggestFile($dir . substr($logical_path, $pos + 1))) {
return TRUE;
}
}
elseif ($behavior instanceof Psr0DirectoryBehavior) {
// PSR-0
if ($api->suggestFile(
$dir
. substr($logical_path, $pos + 1, $lastpos - $pos)
. str_replace('_', '/', substr($logical_path, $lastpos + 1))
)) {
return TRUE;
}
}
elseif ($behavior instanceof xautoload_FinderPlugin_Interface) {
// Legacy "FinderPlugin".
if ($behavior->findFile($api, $logical_base_path, substr($logical_path, $pos + 1), $dir)) {
return TRUE;
}
}
}
}
// Continue with parent fragment.
if (FALSE === $pos) {
return NULL;
}
$pos = strrpos($logical_base_path, '/', -2);
}
return NULL;
}
}