Files
huangjingfen/pro_v3.5.1/vendor/ergebnis/classy/src/Constructs.php

257 lines
7.3 KiB
PHP
Raw Normal View History

2026-03-07 22:29:07 +08:00
<?php
declare(strict_types=1);
/**
* Copyright (c) 2017-2022 Andreas Möller
*
* For the full copyright and license information, please view
* the LICENSE.md file that was distributed with this source code.
*
* @see https://github.com/ergebnis/classy
*/
namespace Ergebnis\Classy;
final class Constructs
{
/**
* Returns an array of names of classy constructs (classes, interfaces, traits) found in source.
*
* @throws Exception\ParseError
*
* @return array<int, Construct>
*/
public static function fromSource(string $source): array
{
$constructs = [];
try {
$sequence = \token_get_all(
$source,
\TOKEN_PARSE,
);
} catch (\ParseError $exception) {
throw Exception\ParseError::fromParseError($exception);
}
$count = \count($sequence);
$namespacePrefix = '';
$namespaceSegmentOrNamespaceTokens = [
\T_STRING,
\T_NAME_QUALIFIED,
];
$classyTokens = [
\T_CLASS,
\T_INTERFACE,
\T_TRAIT,
];
// https://wiki.php.net/rfc/enumerations
if (\PHP_VERSION_ID >= 80100 && \defined('T_ENUM')) {
$classyTokens = [
\T_CLASS,
\T_ENUM,
\T_INTERFACE,
\T_TRAIT,
];
}
for ($index = 0; $index < $count; ++$index) {
$token = $sequence[$index];
// collect namespace name
if (\is_array($token) && \T_NAMESPACE === $token[0]) {
$namespaceSegments = [];
// collect namespace segments
for ($index = self::significantAfter($index, $sequence, $count); $index < $count; ++$index) {
$token = $sequence[$index];
if (\is_array($token) && !\in_array($token[0], $namespaceSegmentOrNamespaceTokens, true)) {
continue;
}
$content = self::content($token);
if (\in_array($content, ['{', ';'], true)) {
break;
}
$namespaceSegments[] = $content;
}
$namespace = \implode('\\', $namespaceSegments);
$namespacePrefix = $namespace . '\\';
}
// skip non-classy tokens
if (!\is_array($token) || !\in_array($token[0], $classyTokens, true)) {
continue;
}
// skip anonymous classes
if (\T_CLASS === $token[0]) {
$current = self::significantBefore($index, $sequence);
$token = $sequence[$current];
// if significant token before T_CLASS is T_NEW, it's an instantiation of an anonymous class
if (\is_array($token) && \T_NEW === $token[0]) {
continue;
}
}
$index = self::significantAfter($index, $sequence, $count);
$token = $sequence[$index];
$constructs[] = Construct::fromName($namespacePrefix . self::content($token));
}
\usort($constructs, static function (Construct $a, Construct $b): int {
return \strcmp(
$a->name(),
$b->name(),
);
});
return $constructs;
}
/**
* Returns an array of constructs defined in a directory.
*
* @throws Exception\DirectoryDoesNotExist
* @throws Exception\MultipleDefinitionsFound
*
* @return array<int, Construct>
*/
public static function fromDirectory(string $directory): array
{
if (!\is_dir($directory)) {
throw Exception\DirectoryDoesNotExist::fromDirectory($directory);
}
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(
$directory,
\RecursiveDirectoryIterator::FOLLOW_SYMLINKS,
));
$constructs = [];
foreach ($iterator as $fileInfo) {
/** @var \SplFileInfo $fileInfo */
if (!$fileInfo->isFile()) {
continue;
}
if ($fileInfo->getBasename('.php') === $fileInfo->getBasename()) {
continue;
}
/** @var string $fileName */
$fileName = $fileInfo->getRealPath();
/** @var string $source */
$source = \file_get_contents($fileName);
try {
$constructsFromFile = self::fromSource($source);
} catch (Exception\ParseError $exception) {
throw Exception\ParseError::fromFileNameAndParseError(
$fileName,
$exception,
);
}
if (0 === \count($constructsFromFile)) {
continue;
}
foreach ($constructsFromFile as $construct) {
$name = $construct->name();
if (\array_key_exists($name, $constructs)) {
$construct = $constructs[$name];
}
$constructs[$name] = $construct->definedIn($fileName);
}
}
\usort($constructs, static function (Construct $a, Construct $b): int {
return \strcmp(
$a->name(),
$b->name(),
);
});
$constructsWithMultipleDefinitions = \array_filter($constructs, static function (Construct $construct): bool {
return 1 < \count($construct->fileNames());
});
if (0 < \count($constructsWithMultipleDefinitions)) {
throw Exception\MultipleDefinitionsFound::fromConstructs($constructsWithMultipleDefinitions);
}
return \array_values($constructs);
}
/**
* Returns the index of the significant token after the index.
*
* @param array<int, array{0: int, 1: string, 2: int}|string> $sequence
*/
private static function significantAfter(int $index, array $sequence, int $count): int
{
for ($current = $index + 1; $current < $count; ++$current) {
$token = $sequence[$current];
if (\is_array($token) && \in_array($token[0], [\T_COMMENT, \T_DOC_COMMENT, \T_WHITESPACE], true)) {
continue;
}
return $current;
}
throw Exception\ShouldNotHappen::create();
}
/**
* Returns the index of the significant token after the index.
*
* @param array<int, array{0: int, 1: string, 2: int}|string> $sequence
*/
private static function significantBefore(
int $index,
array $sequence,
): int {
for ($current = $index - 1; -1 < $current; --$current) {
$token = $sequence[$current];
if (\is_array($token) && \in_array($token[0], [\T_COMMENT, \T_DOC_COMMENT, \T_WHITESPACE], true)) {
continue;
}
return $current;
}
throw Exception\ShouldNotHappen::create();
}
/**
* Returns the string content of a token.
*
* @param array{0: int, 1: string, 2: int}|string $token
*/
private static function content($token): string
{
if (\is_array($token)) {
return $token[1];
}
return $token;
}
}