|
|
|
|
@ -0,0 +1,654 @@
|
|
|
|
|
<?php
|
|
|
|
|
/** @noinspection PhpUnused */
|
|
|
|
|
|
|
|
|
|
namespace jrosset\Reflection;
|
|
|
|
|
|
|
|
|
|
use FilesystemIterator;
|
|
|
|
|
use ReflectionClass;
|
|
|
|
|
use ReflectionEnum;
|
|
|
|
|
use ReflectionException;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A reflection class for namespaces (based on {@link https://www.php-fig.org/psr/psr-4/ PSR-4})
|
|
|
|
|
*
|
|
|
|
|
* Reports information about a namespace
|
|
|
|
|
*/
|
|
|
|
|
class ReflectionNamespace {
|
|
|
|
|
/**
|
|
|
|
|
* Don't return abstract classes
|
|
|
|
|
*
|
|
|
|
|
* @see static::getClasses()
|
|
|
|
|
*/
|
|
|
|
|
public const CLASS_IS_NOT_ABSTRACT = 1;
|
|
|
|
|
/**
|
|
|
|
|
* Don't return final classes
|
|
|
|
|
*
|
|
|
|
|
* @see static::getClasses()
|
|
|
|
|
*/
|
|
|
|
|
public const CLASS_IS_NOT_FINAL = 2;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @var string The namespace name
|
|
|
|
|
*/
|
|
|
|
|
private string $name;
|
|
|
|
|
/**
|
|
|
|
|
* @var string[] The list of directories that maps the namespace
|
|
|
|
|
*/
|
|
|
|
|
private array $directories;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create a reflection on a namespace
|
|
|
|
|
*
|
|
|
|
|
* @param string $name The namespace name
|
|
|
|
|
* @param string[]|string $directories The list of directories that maps the namespace
|
|
|
|
|
*/
|
|
|
|
|
public function __construct (string $name, array|string $directories) {
|
|
|
|
|
$this->name = self::normalizeNamespaceName($name);
|
|
|
|
|
|
|
|
|
|
if (!is_array($directories)) {
|
|
|
|
|
$directories = [$directories];
|
|
|
|
|
}
|
|
|
|
|
$this->setDirectories($directories);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create a reflection on a namespace from a mapping of {namespace} => {directories}
|
|
|
|
|
*
|
|
|
|
|
* @param string $name The namespace name
|
|
|
|
|
* @param array<string,string[]> $mapping The mapping
|
|
|
|
|
*
|
|
|
|
|
* @return static The namespace reflection
|
|
|
|
|
*
|
|
|
|
|
* @throws ReflectionException If the namespace can not be found with the mapping
|
|
|
|
|
*/
|
|
|
|
|
public static function createFromMapping (string $name, array $mapping): static {
|
|
|
|
|
$nsReflection = new static ($name, []);
|
|
|
|
|
|
|
|
|
|
//region Normalize the mapping
|
|
|
|
|
$normalizedMapping = [];
|
|
|
|
|
foreach ($mapping as $mapNamespace => $mapDirectories) {
|
|
|
|
|
$normalizedMapping[self::normalizeNamespaceName($mapNamespace)] = static::normalizeDirectoriesPath($mapDirectories);
|
|
|
|
|
}
|
|
|
|
|
//endregion
|
|
|
|
|
//region Get the map matching this namespace
|
|
|
|
|
$namespaceParts = explode('\\', $nsReflection->getName());
|
|
|
|
|
$namespacePartsDiscarded = [];
|
|
|
|
|
|
|
|
|
|
$map = null;
|
|
|
|
|
while (count($namespaceParts) > 0) {
|
|
|
|
|
$namespaceSearch = implode('\\', $namespaceParts);
|
|
|
|
|
if (!isset($normalizedMapping[$namespaceSearch])) {
|
|
|
|
|
$namespacePartsDiscarded[] = array_pop($namespaceParts);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$map = $normalizedMapping[$namespaceSearch];
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($map === null || count($map) === 0) {
|
|
|
|
|
throw new ReflectionException('Unable to identify namespace directories');
|
|
|
|
|
}
|
|
|
|
|
$mapDirectoriesSuffix = implode(DIRECTORY_SEPARATOR, array_reverse($namespacePartsDiscarded));
|
|
|
|
|
//endregion
|
|
|
|
|
//region Check each directory of the map
|
|
|
|
|
$nsDirectories = [];
|
|
|
|
|
foreach ($map as $mapDirectory) {
|
|
|
|
|
$mapDirectory .= DIRECTORY_SEPARATOR . $mapDirectoriesSuffix;
|
|
|
|
|
if (!file_exists($mapDirectory) || !is_dir($mapDirectory) || !is_readable($mapDirectory)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$nsDirectories[] = $mapDirectory;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (count($nsDirectories) === 0) {
|
|
|
|
|
throw new ReflectionException('Unable to identify namespace directories');
|
|
|
|
|
}
|
|
|
|
|
//endregion
|
|
|
|
|
|
|
|
|
|
return $nsReflection->setDirectories($nsDirectories);
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* Create a reflection on a namespace from the {@link https://getcomposer.org/ composer} mapping (PSR-0 and PSR-4)
|
|
|
|
|
*
|
|
|
|
|
* @param string $name The namespace name
|
|
|
|
|
* @param string $vendorDirectoryPath The path to the “vendor” directory
|
|
|
|
|
*
|
|
|
|
|
* @return static The namespace reflection
|
|
|
|
|
*
|
|
|
|
|
* @throws ReflectionException If the namespace can not be found
|
|
|
|
|
*/
|
|
|
|
|
public static function createFromComposerMapping (string $name, string $vendorDirectoryPath): static {
|
|
|
|
|
$composerDirectory = static::normalizeDirectoryPath($vendorDirectoryPath) . DIRECTORY_SEPARATOR . 'composer';
|
|
|
|
|
if (!file_exists($composerDirectory) || !is_dir($vendorDirectoryPath) || !is_readable($vendorDirectoryPath)) {
|
|
|
|
|
throw new ReflectionException('Unable to find composer directory: ' . $composerDirectory);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$composerDirectory .= DIRECTORY_SEPARATOR;
|
|
|
|
|
$composerFiles = [
|
|
|
|
|
$composerDirectory . 'autoload_psr4.php', // PSR-4
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$mapping = [];
|
|
|
|
|
foreach ($composerFiles as $composerFile) {
|
|
|
|
|
if (!file_exists($composerFile)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
$mapping = array_merge_recursive(
|
|
|
|
|
$mapping,
|
|
|
|
|
require($composerFile)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return static::createFromMapping(
|
|
|
|
|
$name,
|
|
|
|
|
$mapping
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The namespace name
|
|
|
|
|
*
|
|
|
|
|
* @return string The namespace name
|
|
|
|
|
*/
|
|
|
|
|
public function getName (): string {
|
|
|
|
|
return $this->name;
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* The namespace short name, without parent namespaces
|
|
|
|
|
*
|
|
|
|
|
* @return string The namespace short name
|
|
|
|
|
*/
|
|
|
|
|
public function getShortName (): string {
|
|
|
|
|
$name = $this->getName();
|
|
|
|
|
if (($pos = mb_strrpos($name, '\\')) !== false) {
|
|
|
|
|
$name = mb_substr($name, $pos + 1);
|
|
|
|
|
}
|
|
|
|
|
return $name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The parent namespace name or Null if there is no parent
|
|
|
|
|
*
|
|
|
|
|
* @return string|null The parent namespace name or Null if there is no parent
|
|
|
|
|
*/
|
|
|
|
|
public function getParentName (): ?string {
|
|
|
|
|
if (($pos = mb_strrpos($this->getName(), '\\')) === false) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return mb_substr($this->getName(), 0, $pos);
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* The parent namespace or Null if there is no parent
|
|
|
|
|
*
|
|
|
|
|
* <b>WARNING:</b> There is a high probability this method return a {@see ReflectionException} but with invalid directories, breaking his behavior.
|
|
|
|
|
* I recommend using a createFromXXX method with {@see static::getParentName()}
|
|
|
|
|
*
|
|
|
|
|
* @return static|null The parent namespace or Null if there is no parent
|
|
|
|
|
*/
|
|
|
|
|
public function getParent (): ?static {
|
|
|
|
|
if (($parentNamespaceName = $this->getParentName()) === null) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$parentNamespaceDirectories = [];
|
|
|
|
|
foreach ($this->getDirectories() as $directory) {
|
|
|
|
|
if (($parentNamespaceDirectory = basename($directory)) === '') {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
$parentNamespaceDirectories[] = $parentNamespaceDirectory;
|
|
|
|
|
}
|
|
|
|
|
if (count($parentNamespaceDirectories) === 0) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new static ($parentNamespaceName, $parentNamespaceDirectories);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get a sub namespace of the namespace
|
|
|
|
|
*
|
|
|
|
|
* @param string $subNamespaceName The sub namespace name
|
|
|
|
|
*
|
|
|
|
|
* @return static The sub namespace
|
|
|
|
|
*
|
|
|
|
|
* @throws ReflectionException If the sub namespace doesn't exist
|
|
|
|
|
*/
|
|
|
|
|
public function getSubNamespace (string $subNamespaceName): static {
|
|
|
|
|
$this->checkValidDirectories();
|
|
|
|
|
|
|
|
|
|
$subNamespaceDirectories = [];
|
|
|
|
|
foreach ($this->getDirectories() as $directory) {
|
|
|
|
|
$subNamespaceDirectory = $directory . DIRECTORY_SEPARATOR . $subNamespaceName;
|
|
|
|
|
if (!file_exists($subNamespaceDirectory) || !is_dir($subNamespaceDirectory) || !is_readable($subNamespaceDirectory)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$subNamespaceDirectories[] = $subNamespaceDirectory;
|
|
|
|
|
}
|
|
|
|
|
if (count($subNamespaceDirectories) === 0) {
|
|
|
|
|
throw new ReflectionException('Unable to find namespace "' . $this->getName() . '\\' . $subNamespaceName . '"');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new static ($this->getName() . '\\' . $subNamespaceName, $subNamespaceDirectories);
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* Get the sub namespaces of the namespace
|
|
|
|
|
*
|
|
|
|
|
* @param bool $recursive Search also in sub-sub namespaces
|
|
|
|
|
*
|
|
|
|
|
* @return static[] The list of subs namespaces
|
|
|
|
|
*
|
|
|
|
|
* @throws ReflectionException If the namespace has no configured directories
|
|
|
|
|
*/
|
|
|
|
|
public function getSubNamespaces (bool $recursive = false): array {
|
|
|
|
|
$this->checkValidDirectories();
|
|
|
|
|
|
|
|
|
|
/** @var ReflectionNamespace[] $subNamespaces */
|
|
|
|
|
$subNamespaces = [];
|
|
|
|
|
|
|
|
|
|
//region For each directory of the namespace
|
|
|
|
|
foreach ($this->getDirectories() as $directory) {
|
|
|
|
|
$directoryIterator = new FilesystemIterator($directory, FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS);
|
|
|
|
|
|
|
|
|
|
//region For each file of the current directory
|
|
|
|
|
foreach ($directoryIterator as $directoryEntry) {
|
|
|
|
|
//region Ignore hidden files (start with a dot)
|
|
|
|
|
if (mb_substr($directoryEntry->getFilename(), 0, 1) === '.') {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
//endregion
|
|
|
|
|
//region Ignore elements that are not directories
|
|
|
|
|
if (!$directoryEntry->isDir()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
//endregion
|
|
|
|
|
|
|
|
|
|
$subNamespaceReflection = new static ($this->getName() . '\\' . $directoryEntry->getFilename(), [$directoryEntry->getPathname()]);
|
|
|
|
|
|
|
|
|
|
//region Treat recursivity
|
|
|
|
|
if ($recursive) {
|
|
|
|
|
$subNamespaces = array_merge(
|
|
|
|
|
$subNamespaces,
|
|
|
|
|
$subNamespaceReflection->getSubNamespaces(true)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
//endregion }
|
|
|
|
|
|
|
|
|
|
if (isset($subNamespaces[$subNamespaceReflection->getName()])) {
|
|
|
|
|
$existingSubNamespace = $subNamespaces[$subNamespaceReflection->getName()];
|
|
|
|
|
$existingSubNamespace->setDirectories(
|
|
|
|
|
array_unique(
|
|
|
|
|
array_merge(
|
|
|
|
|
$existingSubNamespace->getDirectories(),
|
|
|
|
|
$subNamespaceReflection->getDirectories()
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
$subNamespaces[$subNamespaceReflection->getName()] = $subNamespaceReflection;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
//endregion
|
|
|
|
|
}
|
|
|
|
|
//endregion
|
|
|
|
|
|
|
|
|
|
return $subNamespaces;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get a class of the namespace
|
|
|
|
|
*
|
|
|
|
|
* @param string $className The class name
|
|
|
|
|
*
|
|
|
|
|
* @return ReflectionClass The reflection class
|
|
|
|
|
*
|
|
|
|
|
* @throws ReflectionException If the class doesn't exist or is not a class
|
|
|
|
|
*/
|
|
|
|
|
public function getClass (string $className): ReflectionClass {
|
|
|
|
|
$reflection = new ReflectionClass($this->getName() . '\\' . $className);
|
|
|
|
|
if ($reflection->isInterface() || $reflection->isTrait()) {
|
|
|
|
|
throw new ReflectionException('The "' . $this->getName() . '\\' . $className . '" is not a class');
|
|
|
|
|
}
|
|
|
|
|
return $reflection;
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* Get the classes of the namespace
|
|
|
|
|
*
|
|
|
|
|
* @param int|null $filters The filters, separated with "|".
|
|
|
|
|
* <br>All possibles filters: {@see static::CLASS_IS_NOT_ABSTRACT}, {@see static::CLASS_IS_NOT_FINAL}
|
|
|
|
|
* @param bool $recursive Search also in sub namespaces
|
|
|
|
|
*
|
|
|
|
|
* @return ReflectionClass[] The list of classes
|
|
|
|
|
*
|
|
|
|
|
* @throws ReflectionException If the namespace has no configured directories
|
|
|
|
|
*/
|
|
|
|
|
public function getClasses (?int $filters = null, bool $recursive = false): array {
|
|
|
|
|
$filters ??= 0;
|
|
|
|
|
$classes = [];
|
|
|
|
|
|
|
|
|
|
//region For each valid element of the namespace
|
|
|
|
|
foreach ($this->getValidElements($recursive) as $element) {
|
|
|
|
|
//region Try to get the reflection class
|
|
|
|
|
try {
|
|
|
|
|
$classReflection = new ReflectionClass($this->getName() . '\\' . $element);
|
|
|
|
|
}
|
|
|
|
|
catch (ReflectionException) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
//endregion
|
|
|
|
|
//region Check it is a class (ignore interfaces and traits)
|
|
|
|
|
if ($classReflection->isInterface() || $classReflection->isTrait() || $classReflection->isEnum()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
//endregion
|
|
|
|
|
//region Check filters
|
|
|
|
|
if (($filters & static::CLASS_IS_NOT_ABSTRACT) === static::CLASS_IS_NOT_ABSTRACT && $classReflection->isAbstract()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (($filters & static::CLASS_IS_NOT_FINAL) === static::CLASS_IS_NOT_FINAL && $classReflection->isFinal()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
//endregion
|
|
|
|
|
|
|
|
|
|
$classes[] = $classReflection;
|
|
|
|
|
}
|
|
|
|
|
//endregion
|
|
|
|
|
|
|
|
|
|
return $classes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get an interface of the namespace
|
|
|
|
|
*
|
|
|
|
|
* @param string $interfaceName The interface name
|
|
|
|
|
*
|
|
|
|
|
* @return ReflectionClass The reflection interface
|
|
|
|
|
*
|
|
|
|
|
* @throws ReflectionException If the interface doesn't exist or is not an interface
|
|
|
|
|
*/
|
|
|
|
|
public function getInterface (string $interfaceName): ReflectionClass {
|
|
|
|
|
$reflection = new ReflectionClass($this->getName() . '\\' . $interfaceName);
|
|
|
|
|
if (!$reflection->isInterface()) {
|
|
|
|
|
throw new ReflectionException('The "' . $this->getName() . '\\' . $interfaceName . '" is not an interface');
|
|
|
|
|
}
|
|
|
|
|
return $reflection;
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* Get the interfaces of the namespace
|
|
|
|
|
*
|
|
|
|
|
* @param bool $recursive Search also in sub namespaces
|
|
|
|
|
*
|
|
|
|
|
* @return ReflectionClass[] The list of interfaces
|
|
|
|
|
*
|
|
|
|
|
* @throws ReflectionException If the namespace has no configured directories
|
|
|
|
|
*/
|
|
|
|
|
public function getInterfaces (bool $recursive = false): array {
|
|
|
|
|
$interfaces = [];
|
|
|
|
|
|
|
|
|
|
//region For each valid element of the namespace
|
|
|
|
|
foreach ($this->getValidElements($recursive) as $element) {
|
|
|
|
|
//region Try to get the reflection class
|
|
|
|
|
try {
|
|
|
|
|
$interfaceReflection = new ReflectionClass($this->getName() . '\\' . $element);
|
|
|
|
|
}
|
|
|
|
|
catch (ReflectionException) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
//endregion
|
|
|
|
|
//region Check it is an interface
|
|
|
|
|
if (!$interfaceReflection->isInterface()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
//endregion
|
|
|
|
|
|
|
|
|
|
$interfaces[] = $interfaceReflection;
|
|
|
|
|
}
|
|
|
|
|
//endregion
|
|
|
|
|
|
|
|
|
|
return $interfaces;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get a trait of the namespace
|
|
|
|
|
*
|
|
|
|
|
* @param string $traitName The trait name
|
|
|
|
|
*
|
|
|
|
|
* @return ReflectionClass The reflection trait
|
|
|
|
|
*
|
|
|
|
|
* @throws ReflectionException If the trait doesn't exist or is not a trait
|
|
|
|
|
*/
|
|
|
|
|
public function getTrait (string $traitName): ReflectionClass {
|
|
|
|
|
$reflection = new ReflectionClass($this->getName() . '\\' . $traitName);
|
|
|
|
|
if (!$reflection->isTrait()) {
|
|
|
|
|
throw new ReflectionException('The "' . $this->getName() . '\\' . $traitName . '" is not a trait');
|
|
|
|
|
}
|
|
|
|
|
return $reflection;
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* Get the traits of the namespace
|
|
|
|
|
*
|
|
|
|
|
* @param bool $recursive Search also in sub namespaces
|
|
|
|
|
*
|
|
|
|
|
* @return ReflectionClass[] The list of traits
|
|
|
|
|
*
|
|
|
|
|
* @throws ReflectionException If the namespace has no configured directories
|
|
|
|
|
*/
|
|
|
|
|
public function getTraits (bool $recursive = false): array {
|
|
|
|
|
$traits = [];
|
|
|
|
|
|
|
|
|
|
//region For each valid element of the namespace
|
|
|
|
|
foreach ($this->getValidElements($recursive) as $element) {
|
|
|
|
|
//region Try to get the reflection class
|
|
|
|
|
try {
|
|
|
|
|
$traitReflection = new ReflectionClass($this->getName() . '\\' . $element);
|
|
|
|
|
}
|
|
|
|
|
catch (ReflectionException) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
//endregion
|
|
|
|
|
//region Check it is a trait
|
|
|
|
|
if (!$traitReflection->isTrait()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
//endregion
|
|
|
|
|
|
|
|
|
|
$traits[] = $traitReflection;
|
|
|
|
|
}
|
|
|
|
|
//endregion
|
|
|
|
|
|
|
|
|
|
return $traits;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get an enum of the namespace
|
|
|
|
|
*
|
|
|
|
|
* @param string $enumName The enum name
|
|
|
|
|
*
|
|
|
|
|
* @return ReflectionEnum The reflection enum
|
|
|
|
|
*
|
|
|
|
|
* @throws ReflectionException If the enum doesn't exist
|
|
|
|
|
*/
|
|
|
|
|
public function getEnum (string $enumName): ReflectionEnum {
|
|
|
|
|
return new ReflectionEnum($this->getName() . '\\' . $enumName);
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* Get the enums of the namespace
|
|
|
|
|
*
|
|
|
|
|
* @param bool $recursive Search also in sub namespaces
|
|
|
|
|
*
|
|
|
|
|
* @return ReflectionEnum[] The list of enums
|
|
|
|
|
*
|
|
|
|
|
* @throws ReflectionException If the namespace has no configured directories
|
|
|
|
|
*/
|
|
|
|
|
public function getEnums (bool $recursive = false): array {
|
|
|
|
|
$traits = [];
|
|
|
|
|
|
|
|
|
|
//region For each valid element of the namespace
|
|
|
|
|
foreach ($this->getValidElements($recursive) as $element) {
|
|
|
|
|
//region Try to get the reflection class
|
|
|
|
|
try {
|
|
|
|
|
$enumReflection = new ReflectionEnum($this->getName() . '\\' . $element);
|
|
|
|
|
}
|
|
|
|
|
catch (ReflectionException) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
//endregion
|
|
|
|
|
|
|
|
|
|
$traits[] = $enumReflection;
|
|
|
|
|
}
|
|
|
|
|
//endregion
|
|
|
|
|
|
|
|
|
|
return $traits;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The list of directories that maps the namespace
|
|
|
|
|
*
|
|
|
|
|
* @return string[] The list of directories that maps the namespace
|
|
|
|
|
*/
|
|
|
|
|
public function getDirectories (): array {
|
|
|
|
|
return $this->directories;
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* Set the list of directories that maps the namespace
|
|
|
|
|
*
|
|
|
|
|
* @param string[] $directories The list of directories that maps the namespace
|
|
|
|
|
*/
|
|
|
|
|
public function setDirectories (array $directories): static {
|
|
|
|
|
$this->directories = array_unique(static::normalizeDirectoriesPath($directories));
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if at least one directory is configured
|
|
|
|
|
*
|
|
|
|
|
* @return void
|
|
|
|
|
*
|
|
|
|
|
* @throws ReflectionException If the namespace has no configured directories
|
|
|
|
|
*/
|
|
|
|
|
protected function checkValidDirectories (): void {
|
|
|
|
|
if (count($this->getDirectories()) === 0) {
|
|
|
|
|
throw new ReflectionException('The namespace must have at least one directory configured');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* Get the valid “elements” (class, interface, trait or enum) name of the namespace
|
|
|
|
|
*
|
|
|
|
|
* @param bool $recursive Search also in sub namespaces
|
|
|
|
|
*
|
|
|
|
|
* @return string[] The list of valid elements
|
|
|
|
|
*
|
|
|
|
|
* @throws ReflectionException If the namespace has no configured directories
|
|
|
|
|
*/
|
|
|
|
|
protected function getValidElements (bool $recursive): array {
|
|
|
|
|
$this->checkValidDirectories();
|
|
|
|
|
|
|
|
|
|
$namespaceName = $this->getName();
|
|
|
|
|
$elements = [];
|
|
|
|
|
|
|
|
|
|
//region For each directory of the namespace
|
|
|
|
|
foreach ($this->getDirectories() as $directory) {
|
|
|
|
|
$directoryIterator = new FilesystemIterator($directory, FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS);
|
|
|
|
|
|
|
|
|
|
//region For each file of the current directory
|
|
|
|
|
foreach ($directoryIterator as $directoryEntry) {
|
|
|
|
|
//region Ignore hidden files (start with a dot)
|
|
|
|
|
if (mb_substr($directoryEntry->getFilename(), 0, 1) === '.') {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
//endregion
|
|
|
|
|
//region Treat subdirectories
|
|
|
|
|
if ($directoryEntry->isDir()) {
|
|
|
|
|
//region Recursivity disabled → ignore subdirectories
|
|
|
|
|
if (!$recursive) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
//endregion
|
|
|
|
|
//region PSR-4 → each subdirectory is a sub namespace → created it and get its elements
|
|
|
|
|
$subNamespaceReflection = new static ($this->getName() . '\\' . $directoryEntry->getFilename(), [$directoryEntry->getPathname()]);
|
|
|
|
|
$subNamespaceElementsPrefix = mb_substr($subNamespaceReflection->getName(), mb_strlen($namespaceName . '\\')) . '\\';
|
|
|
|
|
|
|
|
|
|
$elements = array_merge(
|
|
|
|
|
$elements,
|
|
|
|
|
array_map(
|
|
|
|
|
function (string $subNamespaceElement) use ($subNamespaceElementsPrefix): string {
|
|
|
|
|
return $subNamespaceElementsPrefix . $subNamespaceElement;
|
|
|
|
|
},
|
|
|
|
|
$subNamespaceReflection->getValidElements(true)
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
unset($subNamespaceReflection);
|
|
|
|
|
//endregion
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
//endregion
|
|
|
|
|
//region Ignore elements that are not files
|
|
|
|
|
if (!$directoryEntry->isFile()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
//endregion
|
|
|
|
|
|
|
|
|
|
//region Check file name against PSR-4 (one class by file, file name is class name)
|
|
|
|
|
if (preg_match('#^\s*(?<elementName>[A-Z][A-Za-z0-9_]+)\s*\.php\s*$#u', $directoryEntry->getFilename(), $match) !== 1) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
//endregion
|
|
|
|
|
$elements[] = $match['elementName'];
|
|
|
|
|
}
|
|
|
|
|
//endregion
|
|
|
|
|
}
|
|
|
|
|
//endregion
|
|
|
|
|
|
|
|
|
|
return $elements;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Normalize a namespace name : remove initial and final "\" if present
|
|
|
|
|
*
|
|
|
|
|
* @param string $name The name
|
|
|
|
|
*
|
|
|
|
|
* @return string The normalized name
|
|
|
|
|
*/
|
|
|
|
|
public static final function normalizeNamespaceName (string $name): string {
|
|
|
|
|
return preg_replace('#^\\\\|\\\\$#u', '', $name);
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* Normalize a directory path : ensure use of {@see DIRECTORY_SEPARATOR} and remove final separator
|
|
|
|
|
*
|
|
|
|
|
* @param string $path The path
|
|
|
|
|
*
|
|
|
|
|
* @return string The normalized path
|
|
|
|
|
*/
|
|
|
|
|
public static final function normalizeDirectoryPath (string $path): string {
|
|
|
|
|
return realpath(
|
|
|
|
|
preg_replace(
|
|
|
|
|
[
|
|
|
|
|
'#[/\\\\]#u',
|
|
|
|
|
'#[/\\\\]$#u',
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
DIRECTORY_SEPARATOR,
|
|
|
|
|
'',
|
|
|
|
|
],
|
|
|
|
|
$path
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* Normalize a list of directory path : ensure use of {@see DIRECTORY_SEPARATOR} and remove final separator
|
|
|
|
|
*
|
|
|
|
|
* @param string[] $paths The path list
|
|
|
|
|
*
|
|
|
|
|
* @return string[] The normalized path list
|
|
|
|
|
*/
|
|
|
|
|
public static final function normalizeDirectoriesPath (array $paths): array {
|
|
|
|
|
return array_map(
|
|
|
|
|
[static::class, 'normalizeDirectoryPath'],
|
|
|
|
|
$paths
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|