代码初始化

This commit is contained in:
2025-08-07 20:21:47 +08:00
commit 50f3a2dbb0
2191 changed files with 374790 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\CodeGen;
use Jean85\PrettyVersions;
class Package
{
public static function getPrettyVersion(string $package): string
{
try {
return (string) PrettyVersions::getVersion($package);
} catch (\Throwable $exception) {
return 'unknown';
}
}
}

View File

@@ -0,0 +1,247 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\CodeGen;
use PhpDocReader\AnnotationException;
use PhpDocReader\PhpParser\UseStatementParser;
use ReflectionClass;
use ReflectionMethod;
use ReflectionParameter;
use ReflectionProperty;
use Reflector;
/**
* @see https://github.com/PHP-DI/PhpDocReader
*/
class PhpDocReader
{
private const PRIMITIVE_TYPES = [
'bool' => 'bool',
'boolean' => 'bool',
'string' => 'string',
'int' => 'int',
'integer' => 'int',
'float' => 'float',
'double' => 'float',
'array' => 'array',
'object' => 'object',
'callable' => 'callable',
'resource' => 'resource',
'mixed' => 'mixed',
'iterable' => 'iterable',
];
/**
* @var null|PhpDocReader
*/
protected static $instance;
/** @var UseStatementParser */
private $parser;
/** @var bool */
private $ignorePhpDocErrors;
/**
* @param bool $ignorePhpDocErrors enable or disable throwing errors when PhpDoc errors occur (when parsing annotations)
*/
public function __construct(bool $ignorePhpDocErrors = false)
{
$this->parser = new UseStatementParser();
$this->ignorePhpDocErrors = $ignorePhpDocErrors;
}
public static function getInstance(): PhpDocReader
{
if (static::$instance) {
return static::$instance;
}
return static::$instance = new static();
}
/**
* Parse the docblock of the property to get the type (class or primitive type) of the param annotation.
*
* @throws AnnotationException
*/
public function getReturnType(ReflectionMethod $method, bool $withoutNamespace = false): array
{
return $this->readReturnClass($method, true, $withoutNamespace);
}
/**
* Parse the docblock of the property to get the class of the param annotation.
*
* @throws AnnotationException
*/
public function getReturnClass(ReflectionMethod $method, bool $withoutNamespace = false): array
{
return $this->readReturnClass($method, false, $withoutNamespace);
}
protected function readReturnClass(ReflectionMethod $method, bool $allowPrimitiveTypes, bool $withoutNamespace = false): array
{
// Use reflection
$returnType = $method->getReturnType();
if ($returnType instanceof \ReflectionNamedType) {
if (! $returnType->isBuiltin() || $allowPrimitiveTypes) {
return [($returnType->allowsNull() ? '?' : '') . $returnType->getName()];
}
}
$docComment = $method->getDocComment();
if (! $docComment) {
return ['mixed'];
}
if (preg_match('/@return\s+([^\s]+)\s+/', $docComment, $matches)) {
[, $type] = $matches;
} else {
return ['mixed'];
}
$result = [];
$class = $method->getDeclaringClass();
$types = explode('|', $type);
foreach ($types as $type) {
// Ignore primitive types
if (isset(self::PRIMITIVE_TYPES[$type])) {
if ($allowPrimitiveTypes) {
$result[] = self::PRIMITIVE_TYPES[$type];
}
continue;
}
// Ignore types containing special characters ([], <> ...)
if (! preg_match('/^[a-zA-Z0-9\\\\_]+$/', $type)) {
continue;
}
// If the class name is not fully qualified (i.e. doesn't start with a \)
if ($type[0] !== '\\' && ! $withoutNamespace) {
// Try to resolve the FQN using the class context
$resolvedType = $this->tryResolveFqn($type, $class, $method);
if (! $resolvedType && ! $this->ignorePhpDocErrors) {
throw new AnnotationException(sprintf(
'The @return annotation for parameter "%s" of %s::%s contains a non existent class "%s". '
. 'Did you maybe forget to add a "use" statement for this annotation?',
$method,
$class->name,
$method->name,
$type
));
}
$type = $resolvedType;
}
if (! $this->ignorePhpDocErrors && ! $withoutNamespace && ! $this->classExists($type)) {
throw new AnnotationException(sprintf(
'The @return annotation for parameter "%s" of %s::%s contains a non existent class "%s"',
$method,
$class->name,
$method->name,
$type
));
}
// Remove the leading \ (FQN shouldn't contain it)
$result[] = is_string($type) ? ltrim($type, '\\') : null;
}
return $result;
}
/**
* Attempts to resolve the FQN of the provided $type based on the $class and $member context.
*
* @return null|string Fully qualified name of the type, or null if it could not be resolved
*/
protected function tryResolveFqn(string $type, ReflectionClass $class, Reflector $member): ?string
{
$alias = ($pos = strpos($type, '\\')) === false ? $type : substr($type, 0, $pos);
$loweredAlias = strtolower($alias);
// Retrieve "use" statements
$uses = $this->parser->parseUseStatements($class);
if (isset($uses[$loweredAlias])) {
// Imported classes
if ($pos !== false) {
return $uses[$loweredAlias] . substr($type, $pos);
}
return $uses[$loweredAlias];
}
if ($this->classExists($class->getNamespaceName() . '\\' . $type)) {
return $class->getNamespaceName() . '\\' . $type;
}
if (isset($uses['__NAMESPACE__']) && $this->classExists($uses['__NAMESPACE__'] . '\\' . $type)) {
// Class namespace
return $uses['__NAMESPACE__'] . '\\' . $type;
}
if ($this->classExists($type)) {
// No namespace
return $type;
}
// If all fail, try resolving through related traits
return $this->tryResolveFqnInTraits($type, $class, $member);
}
/**
* Attempts to resolve the FQN of the provided $type based on the $class and $member context, specifically searching
* through the traits that are used by the provided $class.
*
* @return null|string Fully qualified name of the type, or null if it could not be resolved
*/
protected function tryResolveFqnInTraits(string $type, ReflectionClass $class, Reflector $member): ?string
{
/** @var ReflectionClass[] $traits */
$traits = [];
// Get traits for the class and its parents
while ($class) {
$traits = array_merge($traits, $class->getTraits());
$class = $class->getParentClass();
}
foreach ($traits as $trait) {
// Eliminate traits that don't have the property/method/parameter
if ($member instanceof ReflectionProperty && ! $trait->hasProperty($member->name)) {
continue;
}
if ($member instanceof ReflectionMethod && ! $trait->hasMethod($member->name)) {
continue;
}
if ($member instanceof ReflectionParameter && ! $trait->hasMethod($member->getDeclaringFunction()->name)) {
continue;
}
// Run the resolver again with the ReflectionClass instance for the trait
$resolvedType = $this->tryResolveFqn($type, $trait, $member);
if ($resolvedType) {
return $resolvedType;
}
}
return null;
}
protected function classExists(string $class): bool
{
return class_exists($class) || interface_exists($class);
}
}

View File

@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\CodeGen;
use PhpDocReader\PhpDocReader;
class PhpDocReaderManager
{
/**
* @var null|PhpDocReader
*/
protected static $instance;
public static function getInstance(): PhpDocReader
{
if (static::$instance) {
return static::$instance;
}
return static::$instance = new PhpDocReader();
}
}

View File

@@ -0,0 +1,156 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\CodeGen;
use Hyperf\Utils\Exception\InvalidArgumentException;
use PhpParser\Node;
use PhpParser\Parser;
use PhpParser\ParserFactory;
use ReflectionClass;
use ReflectionParameter;
class PhpParser
{
public const TYPES = [
'int',
'float',
'string',
'bool',
'array',
'object',
'resource',
'mixed',
];
/**
* @var null|PhpParser
*/
protected static $instance;
/**
* @var Parser
*/
protected $parser;
public function __construct()
{
$parserFactory = new ParserFactory();
$this->parser = $parserFactory->create(ParserFactory::ONLY_PHP7);
}
public static function getInstance(): PhpParser
{
if (static::$instance) {
return static::$instance;
}
return static::$instance = new static();
}
/**
* @return null|Node\Stmt[]
*/
public function getNodesFromReflectionClass(ReflectionClass $reflectionClass): ?array
{
$code = file_get_contents($reflectionClass->getFileName());
return $this->parser->parse($code);
}
public function getNodeFromReflectionParameter(ReflectionParameter $parameter): Node\Param
{
$result = new Node\Param(
new Node\Expr\Variable($parameter->getName())
);
if ($parameter->isDefaultValueAvailable()) {
$result->default = $this->getExprFromValue($parameter->getDefaultValue());
}
if ($parameter->hasType()) {
/** @var \ReflectionNamedType|\ReflectionUnionType $reflection */
$reflection = $parameter->getType();
if ($reflection instanceof \ReflectionUnionType) {
$unionType = [];
foreach ($reflection->getTypes() as $objType) {
$type = $objType->getName();
if (! in_array($type, static::TYPES)) {
$unionType[] = new Node\Name('\\' . $type);
} else {
$unionType[] = new Node\Identifier($type);
}
}
$result->type = new Node\UnionType($unionType);
} else {
$type = $reflection->getName();
if (! in_array($type, static::TYPES)) {
$result->type = new Node\Name('\\' . $type);
} else {
$result->type = new Node\Identifier($type);
}
}
}
if ($parameter->isPassedByReference()) {
$result->byRef = true;
}
if ($parameter->isVariadic()) {
$result->variadic = true;
}
return $result;
}
public function getExprFromValue($value): Node\Expr
{
switch (gettype($value)) {
case 'array':
return new Node\Expr\Array_($value);
case 'string':
return new Node\Scalar\String_($value);
case 'integer':
return new Node\Scalar\LNumber($value);
case 'double':
return new Node\Scalar\DNumber($value);
case 'NULL':
return new Node\Expr\ConstFetch(new Node\Name('null'));
case 'boolean':
return new Node\Expr\ConstFetch(new Node\Name($value ? 'true' : 'false'));
default:
throw new InvalidArgumentException($value . ' is invalid');
}
}
/**
* @return Node\Stmt\ClassMethod[]
*/
public function getAllMethodsFromStmts(array $stmts): array
{
$methods = [];
foreach ($stmts as $namespace) {
if (! $namespace instanceof Node\Stmt\Namespace_) {
continue;
}
foreach ($namespace->stmts as $class) {
if (! $class instanceof Node\Stmt\Class_ && ! $class instanceof Node\Stmt\Interface_) {
continue;
}
foreach ($class->getMethods() as $method) {
$methods[] = $method;
}
}
}
return $methods;
}
}

View File

@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Utils\CodeGen;
use Hyperf\Utils\Composer;
use Hyperf\Utils\Str;
/**
* Read composer.json autoload psr-4 rules to figure out the namespace or path.
*/
class Project
{
public function namespace(string $path): string
{
$ext = pathinfo($path, PATHINFO_EXTENSION);
if ($ext !== '') {
$path = substr($path, 0, -(strlen($ext) + 1));
} else {
$path = trim($path, '/') . '/';
}
foreach ($this->getAutoloadRules() as $prefix => $prefixPath) {
if ($this->isRootNamespace($prefix) || strpos($path, $prefixPath) === 0) {
return $prefix . str_replace('/', '\\', substr($path, strlen($prefixPath)));
}
}
throw new \RuntimeException("Invalid project path: {$path}");
}
public function className(string $path): string
{
return $this->namespace($path);
}
public function path(string $name, $extension = '.php'): string
{
if (Str::endsWith($name, '\\')) {
$extension = '';
}
foreach ($this->getAutoloadRules() as $prefix => $prefixPath) {
if ($this->isRootNamespace($prefix) || strpos($name, $prefix) === 0) {
return $prefixPath . str_replace('\\', '/', substr($name, strlen($prefix))) . $extension;
}
}
throw new \RuntimeException("Invalid class name: {$name}");
}
protected function isRootNamespace(string $namespace): bool
{
return $namespace === '';
}
protected function getAutoloadRules(): array
{
return data_get(Composer::getJsonContent(), 'autoload.psr-4', []);
}
}