.gitattributes000064400000000054151360553600007442 0ustar00/tests export-ignore /.github export-ignore LICENSE000064400000002054151360553600005556 0ustar00The MIT License (MIT) Copyright (c) Hyperf Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. composer.json000064400000002561151360553600007276 0ustar00{ "name": "hyperf/di", "description": "A DI for Hyperf.", "license": "MIT", "keywords": [ "php", "swoole", "hyperf", "di", "annotation" ], "homepage": "https://hyperf.io", "support": { "docs": "https://hyperf.wiki", "issues": "https://github.com/hyperf/hyperf/issues", "pull-request": "https://github.com/hyperf/hyperf/pulls", "source": "https://github.com/hyperf/hyperf" }, "require": { "php": ">=7.3", "doctrine/annotations": "^1.6", "doctrine/instantiator": "^1.0", "nikic/php-parser": "^4.1", "php-di/phpdoc-reader": "^2.2", "psr/container": "^1.0|^2.0", "symfony/finder": "^5.0", "vlucas/phpdotenv": "^5.0" }, "suggest": { "hyperf/config": "Require this component for annotation scan progress to retrieve the scan path.", "ext-pcntl": "Required to scan annotations." }, "autoload": { "psr-4": { "Hyperf\\Di\\": "src/" } }, "autoload-dev": { "psr-4": { "HyperfTest\\Di\\": "tests/" } }, "config": { "sort-packages": true }, "extra": { "branch-alias": { "dev-master": "2.2-dev" }, "hyperf": { "config": "Hyperf\\Di\\ConfigProvider" } } } src/AbstractCallableDefinitionCollector.php000064400000002550151360553600015115 0ustar00 $parameters */ protected function getDefinitionsFromParameters(array $parameters): array { $definitions = []; foreach ($parameters as $parameter) { $definitions[] = $this->createType( $parameter->getName(), $parameter->getType(), $parameter->allowsNull(), $parameter->isDefaultValueAvailable(), $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null ); } return $definitions; } /** * @param mixed $defaultValue */ protected function createType(string $name, ?\ReflectionType $type, bool $allowsNull, bool $hasDefault = false, $defaultValue = null): ReflectionType { return new ReflectionType($type ? $type->getName() : 'mixed', $allowsNull, [ 'defaultValueAvailable' => $hasDefault, 'defaultValue' => $defaultValue, 'name' => $name, ]); } } src/Annotation/AbstractAnnotation.php000064400000004007151360553600013761 0ustar00formatParams($value); foreach ($formattedValue as $key => $val) { if (property_exists($this, $key)) { $this->{$key} = $val; } } } public function toArray(): array { $properties = ReflectionManager::reflectClass(static::class)->getProperties(ReflectionProperty::IS_PUBLIC); $result = []; foreach ($properties as $property) { $result[$property->getName()] = $property->getValue($this); } return $result; } public function collectClass(string $className): void { AnnotationCollector::collectClass($className, static::class, $this); } public function collectMethod(string $className, ?string $target): void { AnnotationCollector::collectMethod($className, $target, static::class, $this); } public function collectProperty(string $className, ?string $target): void { AnnotationCollector::collectProperty($className, $target, static::class, $this); } protected function formatParams($value): array { if (isset($value[0])) { $value = $value[0]; } if (! is_array($value)) { $value = ['value' => $value]; } return $value; } protected function bindMainProperty(string $key, array $value) { $formattedValue = $this->formatParams($value); if (isset($formattedValue['value'])) { $this->{$key} = $formattedValue['value']; } } } src/Annotation/AbstractMultipleAnnotation.php000064400000002770151360553600015502 0ustar00formatAnnotation($annotation)); } public function collectMethod(string $className, ?string $target): void { $annotation = AnnotationCollector::getClassMethodAnnotation($className, $target)[static::class] ?? null; AnnotationCollector::collectMethod($className, $target, static::class, $this->formatAnnotation($annotation)); } public function collectProperty(string $className, ?string $target): void { $annotation = AnnotationCollector::getClassPropertyAnnotation($className, $target)[static::class] ?? null; AnnotationCollector::collectProperty($className, $target, static::class, $this->formatAnnotation($annotation)); } protected function formatAnnotation(?MultipleAnnotation $annotation): MultipleAnnotation { if ($annotation instanceof MultipleAnnotation) { return $annotation->insert($this); } return new MultipleAnnotation($this); } } src/Annotation/AnnotationCollector.php000064400000006600151360553600014145 0ustar00 $metadata) { if (! isset($metadata['_c'][$annotation])) { continue; } $result[$class] = $metadata['_c'][$annotation]; } return $result; } /** * @deprecated v3.0 */ public static function getMethodByAnnotation(string $annotation): array { return static::getMethodsByAnnotation($annotation); } public static function getMethodsByAnnotation(string $annotation): array { $result = []; foreach (static::$container as $class => $metadata) { foreach ($metadata['_m'] ?? [] as $method => $_metadata) { if ($value = $_metadata[$annotation] ?? null) { $result[] = ['class' => $class, 'method' => $method, 'annotation' => $value]; } } } return $result; } public static function getPropertiesByAnnotation(string $annotation): array { $properties = []; foreach (static::$container as $class => $metadata) { foreach ($metadata['_p'] ?? [] as $property => $_metadata) { if ($value = $_metadata[$annotation] ?? null) { $properties[] = ['class' => $class, 'property' => $property, 'annotation' => $value]; } } } return $properties; } public static function getClassAnnotation(string $class, string $annotation) { return static::get($class . '._c.' . $annotation); } public static function getClassMethodAnnotation(string $class, string $method) { return static::get($class . '._m.' . $method); } public static function getClassPropertyAnnotation(string $class, string $property) { return static::get($class . '._p.' . $property); } public static function getContainer(): array { return static::$container; } } src/Annotation/AnnotationInterface.php000064400000001414151360553600014115 0ustar00 'Doctrine\Common\Annotations\Annotation\IgnoreAnnotation', ]; /** * A list with annotations that are not causing exceptions when not resolved to an annotation class. * * The names are case sensitive. * * @var array */ private static $globalIgnoredNames = [ // Annotation tags 'Annotation' => true, 'Attribute' => true, 'Attributes' => true, /* Can we enable this? 'Enum' => true, */ 'Required' => true, 'Target' => true, // Widely used tags (but not existent in phpdoc) 'fix' => true, 'fixme' => true, 'override' => true, // PHPDocumentor 1 tags 'abstract' => true, 'access' => true, 'code' => true, 'deprec' => true, 'endcode' => true, 'exception' => true, 'final' => true, 'ingroup' => true, 'inheritdoc' => true, 'inheritDoc' => true, 'magic' => true, 'name' => true, 'toc' => true, 'tutorial' => true, 'private' => true, 'static' => true, 'staticvar' => true, 'staticVar' => true, 'throw' => true, // PHPDocumentor 2 tags. 'api' => true, 'author' => true, 'category' => true, 'copyright' => true, 'deprecated' => true, 'example' => true, 'filesource' => true, 'global' => true, 'ignore' => true, /* Can we enable this? 'index' => true, */ 'internal' => true, 'license' => true, 'link' => true, 'method' => true, 'package' => true, 'param' => true, 'property' => true, 'property-read' => true, 'property-write' => true, 'return' => true, 'see' => true, 'since' => true, 'source' => true, 'subpackage' => true, 'throws' => true, 'todo' => true, 'TODO' => true, 'usedby' => true, 'uses' => true, 'var' => true, 'version' => true, // PHPUnit tags 'codeCoverageIgnore' => true, 'codeCoverageIgnoreStart' => true, 'codeCoverageIgnoreEnd' => true, // PHPCheckStyle 'SuppressWarnings' => true, // PHPStorm 'noinspection' => true, // PEAR 'package_version' => true, // PlantUML 'startuml' => true, 'enduml' => true, // Symfony 3.3 Cache Adapter 'experimental' => true, // Slevomat Coding Standard 'phpcsSuppress' => true, // PHP CodeSniffer 'codingStandardsIgnoreStart' => true, 'codingStandardsIgnoreEnd' => true, // PHPStan 'template' => true, 'implements' => true, 'extends' => true, 'use' => true, ]; /** * A list with annotations that are not causing exceptions when not resolved to an annotation class. * * The names are case sensitive. * * @var array */ private static $globalIgnoredNamespaces = []; /** * Annotations parser. * * @var \Doctrine\Common\Annotations\DocParser */ private $parser; /** * Annotations parser used to collect parsing metadata. * * @var \Doctrine\Common\Annotations\DocParser */ private $preParser; /** * PHP parser used to collect imports. * * @var \Doctrine\Common\Annotations\PhpParser */ private $phpParser; /** * In-memory cache mechanism to store imported annotations per class. * * @var array */ private $imports = []; /** * In-memory cache mechanism to store ignored annotations per class. * * @var array */ private $ignoredAnnotationNames = []; /** * Constructor. * * Initializes a new AnnotationReader. * * @param DocParser $parser * * @throws AnnotationException */ public function __construct(DocParser $parser = null) { if (extension_loaded('Zend Optimizer+') && (ini_get('zend_optimizerplus.save_comments') === '0' || ini_get('opcache.save_comments') === '0')) { throw AnnotationException::optimizerPlusSaveComments(); } if (extension_loaded('Zend OPcache') && ini_get('opcache.save_comments') == 0) { throw AnnotationException::optimizerPlusSaveComments(); } // Make sure that the IgnoreAnnotation annotation is loaded class_exists(IgnoreAnnotation::class); $this->parser = $parser ?: new DocParser(); $this->preParser = new DocParser(); $this->preParser->setImports(self::$globalImports); $this->preParser->setIgnoreNotImportedAnnotations(true); $this->preParser->setIgnoredAnnotationNames(self::$globalIgnoredNames); $this->phpParser = new PhpParser(); } public static function addGlobalImports(string $alias, string $annotation) { self::$globalImports[strtolower($alias)] = $annotation; } /** * Add a new annotation to the globally ignored annotation names with regard to exception handling. * * @param string $name */ public static function addGlobalIgnoredName($name) { self::$globalIgnoredNames[$name] = true; } /** * Add a new annotation to the globally ignored annotation namespaces with regard to exception handling. * * @param string $namespace */ public static function addGlobalIgnoredNamespace($namespace) { self::$globalIgnoredNamespaces[$namespace] = true; } public function getClassAnnotations(ReflectionClass $class) { if (\PHP_VERSION_ID >= 80000) { if ($attributes = $this->getAttributes($class)) { return $attributes; } } $this->parser->setTarget(Target::TARGET_CLASS); $this->parser->setImports($this->getClassImports($class)); $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName()); } public function getClassAnnotation(ReflectionClass $class, $annotationName) { $annotations = $this->getClassAnnotations($class); foreach ($annotations as $annotation) { if ($annotation instanceof $annotationName) { return $annotation; } } return null; } public function getAttributes(\Reflector $reflection): array { $result = []; if (! method_exists($reflection, 'getAttributes')) { return $result; } $attributes = $reflection->getAttributes(); foreach ($attributes as $attribute) { if (! class_exists($attribute->getName())) { $className = $methodName = $propertyName = ''; if ($reflection instanceof ReflectionClass) { $className = $reflection->getName(); } elseif ($reflection instanceof ReflectionMethod) { $className = $reflection->getDeclaringClass()->getName(); $methodName = $reflection->getName(); } elseif ($reflection instanceof ReflectionProperty) { $className = $reflection->getDeclaringClass()->getName(); $propertyName = $reflection->getName(); } $message = sprintf( "No attribute class found for '%s' in %s", $attribute->getName(), $className ); if ($methodName) { $message .= sprintf('->%s() method', $methodName); } if ($propertyName) { $message .= sprintf('::$%s property', $propertyName); } throw new NotFoundException($message); } $result[] = $attribute->newInstance(); } return $result; } public function getPropertyAnnotations(ReflectionProperty $property) { if (\PHP_VERSION_ID >= 80000) { if ($attributes = $this->getAttributes($property)) { return $attributes; } } $class = $property->getDeclaringClass(); $context = 'property ' . $class->getName() . '::$' . $property->getName(); $this->parser->setTarget(Target::TARGET_PROPERTY); $this->parser->setImports($this->getPropertyImports($property)); $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); return $this->parser->parse($property->getDocComment(), $context); } public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) { $annotations = $this->getPropertyAnnotations($property); foreach ($annotations as $annotation) { if ($annotation instanceof $annotationName) { return $annotation; } } return null; } public function getMethodAnnotations(ReflectionMethod $method) { if (\PHP_VERSION_ID >= 80000) { if ($attributes = $this->getAttributes($method)) { return $attributes; } } $class = $method->getDeclaringClass(); $context = 'method ' . $class->getName() . '::' . $method->getName() . '()'; $this->parser->setTarget(Target::TARGET_METHOD); $this->parser->setImports($this->getMethodImports($method)); $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); return $this->parser->parse($method->getDocComment(), $context); } public function getMethodAnnotation(ReflectionMethod $method, $annotationName) { $annotations = $this->getMethodAnnotations($method); foreach ($annotations as $annotation) { if ($annotation instanceof $annotationName) { return $annotation; } } return null; } /** * Returns the ignored annotations for the given class. * * @return array */ private function getIgnoredAnnotationNames(ReflectionClass $class) { $name = $class->getName(); if (isset($this->ignoredAnnotationNames[$name])) { return $this->ignoredAnnotationNames[$name]; } $this->collectParsingMetadata($class); return $this->ignoredAnnotationNames[$name]; } /** * Retrieves imports. * * @return array */ private function getClassImports(ReflectionClass $class) { $name = $class->getName(); if (isset($this->imports[$name])) { return $this->imports[$name]; } $this->collectParsingMetadata($class); return $this->imports[$name]; } /** * Retrieves imports for methods. * * @return array */ private function getMethodImports(ReflectionMethod $method) { $class = $method->getDeclaringClass(); $classImports = $this->getClassImports($class); $traitImports = []; foreach ($class->getTraits() as $trait) { if ($trait->hasMethod($method->getName()) && $trait->getFileName() === $method->getFileName() ) { $traitImports = array_merge($traitImports, $this->phpParser->parseClass($trait)); } } return array_merge($classImports, $traitImports); } /** * Retrieves imports for properties. * * @return array */ private function getPropertyImports(ReflectionProperty $property) { $class = $property->getDeclaringClass(); $classImports = $this->getClassImports($class); $traitImports = []; foreach ($class->getTraits() as $trait) { if ($trait->hasProperty($property->getName())) { $traitImports = array_merge($traitImports, $this->phpParser->parseClass($trait)); } } return array_merge($classImports, $traitImports); } /** * Collects parsing metadata for a given class. */ private function collectParsingMetadata(ReflectionClass $class) { $ignoredAnnotationNames = self::$globalIgnoredNames; $annotations = $this->preParser->parse($class->getDocComment(), 'class ' . $class->name); foreach ($annotations as $annotation) { if ($annotation instanceof IgnoreAnnotation) { foreach ($annotation->names as $annot) { $ignoredAnnotationNames[$annot] = true; } } } $name = $class->getName(); $this->imports[$name] = array_merge( self::$globalImports, $this->phpParser->parseClass($class), ['__NAMESPACE__' => $class->getNamespaceName()] ); $this->ignoredAnnotationNames[$name] = $ignoredAnnotationNames; } } src/Annotation/Aspect.php000064400000003412151360553600011401 0ustar00collect($className); } protected function collect(string $className) { [$instanceClasses, $instanceAnnotations, $instancePriority] = AspectLoader::load($className); // Classes $classes = $this->classes; $classes = $instanceClasses ? array_merge($classes, $instanceClasses) : $classes; // Annotations $annotations = $this->annotations; $annotations = $instanceAnnotations ? array_merge($annotations, $instanceAnnotations) : $annotations; // Priority $annotationPriority = $this->priority; $propertyPriority = $instancePriority ? $instancePriority : null; if (! is_null($annotationPriority) && ! is_null($propertyPriority) && $annotationPriority !== $propertyPriority) { throw new \InvalidArgumentException('Cannot define two difference priority of Aspect.'); } $priority = $annotationPriority ?? $propertyPriority; // Save the metadata to AspectCollector AspectCollector::setAround($className, $classes, $annotations, $priority); } } src/Annotation/AspectCollector.php000064400000005737151360553600013264 0ustar00 $priority, 'classes' => array_merge(static::$aspectRules[$aspect]['classes'] ?? [], $classes), 'annotations' => array_merge(static::$aspectRules[$aspect]['annotations'] ?? [], $annotations), ]; } else { static::$aspectRules[$aspect] = [ 'priority' => $priority, 'classes' => $classes, 'annotations' => $annotations, ]; } } public static function clear(?string $key = null): void { if ($key) { unset(static::$container['classes'][$key], static::$container['annotations'][$key], static::$aspectRules[$key]); } else { static::$container = []; static::$aspectRules = []; } } public static function getRule(string $aspect): array { return static::$aspectRules[$aspect] ?? []; } public static function getPriority(string $aspect): int { return static::$aspectRules[$aspect]['priority'] ?? static::getDefaultPriority(); } public static function getRules(): array { return static::$aspectRules; } public static function getContainer(): array { return static::$container; } public static function serialize(): string { return serialize([static::$aspectRules, static::$container]); } public static function deserialize(string $metadata): bool { [$rules, $container] = unserialize($metadata); static::$aspectRules = $rules; static::$container = $container; return true; } private static function getDefaultPriority(): int { return 0; } } src/Annotation/AspectLoader.php000064400000002541151360553600012532 0ustar00getProperties(ReflectionProperty::IS_PUBLIC); $instanceClasses = $instanceAnnotations = []; $instancePriority = null; foreach ($properties as $property) { if ($property->getName() === 'classes') { $instanceClasses = ReflectionManager::getPropertyDefaultValue($property); } elseif ($property->getName() === 'annotations') { $instanceAnnotations = ReflectionManager::getPropertyDefaultValue($property); } elseif ($property->getName() === 'priority') { $instancePriority = ReflectionManager::getPropertyDefaultValue($property); } } return [$instanceClasses, $instanceAnnotations, $instancePriority]; } } src/Annotation/Debug.php000064400000000602151360553600011206 0ustar00getProperty($target); if (method_exists($reflectionProperty, 'hasType') && $reflectionProperty->hasType()) { /* @phpstan-ignore-next-line */ $this->value = $reflectionProperty->getType()->getName(); } else { $this->value = PhpDocReaderManager::getInstance()->getPropertyClass($reflectionProperty); } if (empty($this->value)) { throw new AnnotationException("The @Inject value is invalid for {$className}->{$target}"); } if ($this->lazy) { $this->value = 'HyperfLazy\\' . $this->value; } AnnotationCollector::collectProperty($className, $target, static::class, $this); } catch (AnnotationException|DocReaderAnnotationException $exception) { if ($this->required) { throw new AnnotationException($exception->getMessage()); } $this->value = ''; } catch (\Throwable $exception) { throw new AnnotationException("The @Inject value is invalid for {$className}->{$target}. Because {$exception->getMessage()}"); } } } src/Annotation/InjectAspect.php000064400000001227151360553600012540 0ustar00process(); } } src/Annotation/MultipleAnnotation.php000064400000004055151360553600014014 0ustar00annotations = [$annotation]; $this->className = get_class($annotation); } public function __get(string $name) { if (count($this->annotations) > 1) { throw new AnnotationException('MultipleAnnotation[' . $this->className() . '] has more than one annotations.'); } return $this->annotations[0]->{$name}; } public function className(): string { return $this->className; } public function insert(AnnotationInterface $annotation) { if (! $annotation instanceof $this->className) { throw new AnnotationException(get_class($annotation) . ' must instanceof ' . $this->className); } $this->annotations[] = $annotation; return $this; } public function toAnnotations(): array { return $this->annotations; } public function collectClass(string $className): void { throw new AnnotationException('MultipleAnnotation[' . $this->className() . '] does not support collectClass()'); } public function collectMethod(string $className, ?string $target): void { throw new AnnotationException('MultipleAnnotation[' . $this->className() . '] does not support collectMethod()'); } public function collectProperty(string $className, ?string $target): void { throw new AnnotationException('MultipleAnnotation[' . $this->className() . '] does not support collectProperty()'); } } src/Annotation/MultipleAnnotationInterface.php000064400000001177151360553600015637 0ustar00cacheable = $cacheable; $this->configDir = $configDir; $this->paths = $paths; $this->dependencies = $dependencies; $this->ignoreAnnotations = $ignoreAnnotations; $this->globalImports = $globalImports; $this->collectors = $collectors; $this->classMap = $classMap; } public function isCacheable(): bool { return $this->cacheable; } public function getConfigDir(): string { return $this->configDir; } public function getPaths(): array { return $this->paths; } public function getCollectors(): array { return $this->collectors; } public function getIgnoreAnnotations(): array { return $this->ignoreAnnotations; } public function getGlobalImports(): array { return $this->globalImports; } public function getDependencies(): array { return $this->dependencies; } public function getClassMap(): array { return $this->classMap; } public static function instance(string $configDir): self { if (self::$instance) { return self::$instance; } $configDir = rtrim($configDir, '/'); [$config, $serverDependencies, $cacheable] = static::initConfigByFile($configDir); return self::$instance = new self( $cacheable, $configDir, $config['paths'] ?? [], $serverDependencies ?? [], $config['ignore_annotations'] ?? [], $config['global_imports'] ?? [], $config['collectors'] ?? [], $config['class_map'] ?? [] ); } private static function initConfigByFile(string $configDir): array { $config = []; $configFromProviders = []; $cacheable = false; if (class_exists(ProviderConfig::class)) { $configFromProviders = ProviderConfig::load(); } $serverDependencies = $configFromProviders['dependencies'] ?? []; if (file_exists($configDir . '/autoload/dependencies.php')) { $definitions = include $configDir . '/autoload/dependencies.php'; $serverDependencies = array_replace($serverDependencies, $definitions ?? []); } $config = static::allocateConfigValue($configFromProviders['annotations'] ?? [], $config); // Load the config/autoload/annotations.php and merge the config if (file_exists($configDir . '/autoload/annotations.php')) { $annotations = include $configDir . '/autoload/annotations.php'; $config = static::allocateConfigValue($annotations, $config); } // Load the config/config.php and merge the config if (file_exists($configDir . '/config.php')) { $configContent = include $configDir . '/config.php'; $appEnv = $configContent['app_env'] ?? 'dev'; $cacheable = value($configContent['scan_cacheable'] ?? $appEnv === 'prod'); if (isset($configContent['annotations'])) { $config = static::allocateConfigValue($configContent['annotations'], $config); } } return [$config, $serverDependencies, $cacheable]; } private static function allocateConfigValue(array $content, array $config): array { if (! isset($content['scan'])) { return []; } foreach ($content['scan'] as $key => $value) { if (! isset($config[$key])) { $config[$key] = []; } if (! is_array($value)) { $value = [$value]; } $config[$key] = array_merge($config[$key], $value); } return $config; } } src/Annotation/Scanner.php000064400000027260151360553600011562 0ustar00classloader = $classloader; $this->scanConfig = $scanConfig; $this->handler = $handler; $this->filesystem = new Filesystem(); foreach ($scanConfig->getIgnoreAnnotations() as $annotation) { AnnotationReader::addGlobalIgnoredName($annotation); } foreach ($scanConfig->getGlobalImports() as $alias => $annotation) { AnnotationReader::addGlobalImports($alias, $annotation); } } public function collect(AnnotationReader $reader, ReflectionClass $reflection) { $className = $reflection->getName(); if ($path = $this->scanConfig->getClassMap()[$className] ?? null) { if ($reflection->getFileName() !== $path) { // When the original class is dynamically replaced, the original class should not be collected. return; } } // Parse class annotations $classAnnotations = $reader->getClassAnnotations($reflection); if (! empty($classAnnotations)) { foreach ($classAnnotations as $classAnnotation) { if ($classAnnotation instanceof AnnotationInterface) { $classAnnotation->collectClass($className); } } } // Parse properties annotations $properties = $reflection->getProperties(); foreach ($properties as $property) { $propertyAnnotations = $reader->getPropertyAnnotations($property); if (! empty($propertyAnnotations)) { foreach ($propertyAnnotations as $propertyAnnotation) { if ($propertyAnnotation instanceof AnnotationInterface) { $propertyAnnotation->collectProperty($className, $property->getName()); } } } } // Parse methods annotations $methods = $reflection->getMethods(); foreach ($methods as $method) { $methodAnnotations = $reader->getMethodAnnotations($method); if (! empty($methodAnnotations)) { foreach ($methodAnnotations as $methodAnnotation) { if ($methodAnnotation instanceof AnnotationInterface) { $methodAnnotation->collectMethod($className, $method->getName()); } } } } unset($reflection, $classAnnotations, $properties, $methods); } public function scan(array $classMap = [], string $proxyDir = ''): array { $paths = $this->scanConfig->getPaths(); $collectors = $this->scanConfig->getCollectors(); if (! $paths) { return []; } $lastCacheModified = file_exists($this->path) ? $this->filesystem->lastModified($this->path) : 0; if ($lastCacheModified > 0 && $this->scanConfig->isCacheable()) { return $this->deserializeCachedScanData($collectors); } $scanned = $this->handler->scan(); if ($scanned->isScanned()) { return $this->deserializeCachedScanData($collectors); } $this->deserializeCachedScanData($collectors); $annotationReader = new AnnotationReader(); $paths = $this->normalizeDir($paths); $classes = ReflectionManager::getAllClasses($paths); $this->clearRemovedClasses($collectors, $classes); $reflectionClassMap = []; foreach ($classes as $className => $reflectionClass) { $reflectionClassMap[$className] = $reflectionClass->getFileName(); if ($this->filesystem->lastModified($reflectionClass->getFileName()) >= $lastCacheModified) { /** @var MetadataCollector $collector */ foreach ($collectors as $collector) { $collector::clear($className); } $this->collect($annotationReader, $reflectionClass); } } $this->loadAspects($lastCacheModified); $data = []; /** @var MetadataCollector|string $collector */ foreach ($collectors as $collector) { $data[$collector] = $collector::serialize(); } // Get the class map of Composer loader $classMap = array_merge($reflectionClassMap, $classMap); $proxyManager = new ProxyManager($classMap, $proxyDir); $proxies = $proxyManager->getProxies(); $this->putCache($this->path, serialize([$data, $proxies])); exit; } /** * Normalizes given directory names by removing directory not exist. * @throws DirectoryNotExistException */ public function normalizeDir(array $paths): array { $result = []; foreach ($paths as $path) { if (is_dir($path)) { $result[] = $path; } } if ($paths && ! $result) { throw new DirectoryNotExistException('The scanned directory does not exist'); } return $result; } protected function deserializeCachedScanData(array $collectors): array { if (! file_exists($this->path)) { return []; } [$data, $proxies] = unserialize(file_get_contents($this->path)); foreach ($data as $collector => $deserialized) { /** @var MetadataCollector $collector */ if (in_array($collector, $collectors)) { $collector::deserialize($deserialized); } } return $proxies; } protected function deserializeCachedCollectors(array $collectors): int { if (! file_exists($this->path)) { return 0; } $data = unserialize(file_get_contents($this->path)); foreach ($data as $collector => $deserialized) { /** @var MetadataCollector $collector */ if (in_array($collector, $collectors)) { $collector::deserialize($deserialized); } } return $this->filesystem->lastModified($this->path); } /** * @param ReflectionClass[] $reflections */ protected function clearRemovedClasses(array $collectors, array $reflections): void { $path = BASE_PATH . '/runtime/container/classes.cache'; $classes = array_keys($reflections); $data = []; if ($this->filesystem->exists($path)) { $data = unserialize($this->filesystem->get($path)); } $this->putCache($path, serialize($classes)); $removed = array_diff($data, $classes); foreach ($removed as $class) { /** @var MetadataCollector $collector */ foreach ($collectors as $collector) { $collector::clear($class); } } } protected function putCache(string $path, $data) { if (! $this->filesystem->isDirectory($dir = dirname($path))) { $this->filesystem->makeDirectory($dir, 0755, true); } $this->filesystem->put($path, $data); } /** * Load aspects to AspectCollector by configuration files and ConfigProvider. */ protected function loadAspects(int $lastCacheModified): void { $configDir = $this->scanConfig->getConfigDir(); if (! $configDir) { return; } $aspectsPath = $configDir . '/autoload/aspects.php'; $basePath = $configDir . '/config.php'; $aspects = file_exists($aspectsPath) ? include $aspectsPath : []; $baseConfig = file_exists($basePath) ? include $basePath : []; $providerConfig = []; if (class_exists(ProviderConfig::class)) { $providerConfig = ProviderConfig::load(); } if (! isset($aspects) || ! is_array($aspects)) { $aspects = []; } if (! isset($baseConfig['aspects']) || ! is_array($baseConfig['aspects'])) { $baseConfig['aspects'] = []; } if (! isset($providerConfig['aspects']) || ! is_array($providerConfig['aspects'])) { $providerConfig['aspects'] = []; } $aspects = array_merge($providerConfig['aspects'], $baseConfig['aspects'], $aspects); [$removed, $changed] = $this->getChangedAspects($aspects, $lastCacheModified); // When the aspect removed from config, it should removed from AspectCollector. foreach ($removed as $aspect) { AspectCollector::clear($aspect); } foreach ($aspects ?? [] as $key => $value) { if (is_numeric($key)) { $aspect = $value; $priority = null; } else { $aspect = $key; $priority = (int) $value; } if (! in_array($aspect, $changed)) { continue; } [$instanceClasses, $instanceAnnotations, $instancePriority] = AspectLoader::load($aspect); $classes = $instanceClasses ?: []; // Annotations $annotations = $instanceAnnotations ?: []; // Priority $priority = $priority ?: ($instancePriority ?? null); // Save the metadata to AspectCollector AspectCollector::setAround($aspect, $classes, $annotations, $priority); } } protected function getChangedAspects(array $aspects, int $lastCacheModified): array { $path = BASE_PATH . '/runtime/container/aspects.cache'; $classes = []; foreach ($aspects as $key => $value) { if (is_numeric($key)) { $classes[] = $value; } else { $classes[] = $key; } } $data = []; if ($this->filesystem->exists($path)) { $data = unserialize($this->filesystem->get($path)); } $this->putCache($path, serialize($classes)); $diff = array_diff($data, $classes); $changed = array_diff($classes, $data); $removed = []; foreach ($diff as $item) { $annotation = AnnotationCollector::getClassAnnotation($item, Aspect::class); if (is_null($annotation)) { $removed[] = $item; } } foreach ($classes as $class) { $file = $this->classloader->getComposerClassLoader()->findFile($class); if ($file === false) { echo sprintf('Skip class %s, because it does not exist in composer class loader.', $class) . PHP_EOL; continue; } if ($lastCacheModified <= $this->filesystem->lastModified($file)) { $changed[] = $class; } } return [ array_values(array_unique($removed)), array_values(array_unique($changed)), ]; } } src/Aop/AbstractAspect.php000064400000001152151360553600011471 0ustar00class = $class; $this->method = $method; } } src/Aop/AroundInterface.php000064400000000755151360553600011647 0ustar00 $collection) { if ($type === 'classes') { static::parseClasses($collection, $class, $rewriteCollection); } elseif ($type === 'annotations') { static::parseAnnotations($collection, $class, $rewriteCollection); } } return $rewriteCollection; } /** * @return array [isMatch, $matchedMethods] */ public static function isMatchClassRule(string $target, string $rule): array { /* * e.g. Foo/Bar * e.g. Foo/B* * e.g. F*o/Bar * e.g. Foo/Bar::method * e.g. Foo/Bar::met* */ $ruleMethod = null; $ruleClass = $rule; $method = null; $class = $target; if (strpos($rule, '::') !== false) { [$ruleClass, $ruleMethod] = explode('::', $rule); } if (strpos($target, '::') !== false) { [$class, $method] = explode('::', $target); } if ($method == null) { if (strpos($ruleClass, '*') === false) { /* * Match [rule] Foo/Bar::ruleMethod [target] Foo/Bar [return] true,ruleMethod * Match [rule] Foo/Bar [target] Foo/Bar [return] true,null * Match [rule] FooBar::rule*Method [target] Foo/Bar [return] true,rule*Method */ if ($ruleClass === $class) { return [true, $ruleMethod]; } return [false, null]; } /** * Match [rule] Foo*Bar::ruleMethod [target] Foo/Bar [return] true,ruleMethod * Match [rule] Foo*Bar [target] Foo/Bar [return] true,null. */ $preg = str_replace(['*', '\\'], ['.*', '\\\\'], $ruleClass); $pattern = "#^{$preg}$#"; if (preg_match($pattern, $class)) { return [true, $ruleMethod]; } return [false, null]; } if (strpos($rule, '*') === false) { /* * Match [rule] Foo/Bar::ruleMethod [target] Foo/Bar::ruleMethod [return] true,ruleMethod * Match [rule] Foo/Bar [target] Foo/Bar::ruleMethod [return] false,null */ if ($ruleClass === $class && ($ruleMethod === null || $ruleMethod === $method)) { return [true, $method]; } return [false, null]; } /* * Match [rule] Foo*Bar::ruleMethod [target] Foo/Bar::ruleMethod [return] true,ruleMethod * Match [rule] FooBar::rule*Method [target] Foo/Bar::ruleMethod [return] true,rule*Method */ if ($ruleMethod) { $preg = str_replace(['*', '\\'], ['.*', '\\\\'], $rule); $pattern = "#^{$preg}$#"; if (preg_match($pattern, $target)) { return [true, $method]; } } else { /** * Match [rule] Foo*Bar [target] Foo/Bar::ruleMethod [return] true,null. */ $preg = str_replace(['*', '\\'], ['.*', '\\\\'], $rule); $pattern = "#^{$preg}$#"; if (preg_match($pattern, $class)) { return [true, $method]; } } return [false, null]; } public static function isMatch(string $class, string $method, string $rule): bool { [$isMatch,] = self::isMatchClassRule($class . '::' . $method, $rule); return $isMatch; } private static function parseAnnotations(array $collection, string $class, RewriteCollection $rewriteCollection) { // Get the annotations of class and method. $annotations = AnnotationCollector::get($class); $classMapping = $annotations['_c'] ?? []; $methodMapping = value(function () use ($annotations) { $mapping = []; $methodAnnotations = $annotations['_m'] ?? []; foreach ($methodAnnotations as $method => $targetAnnotations) { $keys = array_keys($targetAnnotations); foreach ($keys as $key) { $mapping[$key][] = $method; } } return $mapping; }); $aspects = array_keys($collection); foreach ($aspects ?? [] as $aspect) { $rules = AspectCollector::getRule($aspect); foreach ($rules['annotations'] ?? [] as $rule) { // If exist class level annotation, then all methods should rewrite, so return an empty array directly. if (isset($classMapping[$rule])) { return $rewriteCollection->setLevel(RewriteCollection::CLASS_LEVEL); } if (isset($methodMapping[$rule])) { $rewriteCollection->add($methodMapping[$rule]); } } } return $rewriteCollection; } private static function parseClasses(array $collection, string $class, RewriteCollection $rewriteCollection) { $aspects = array_keys($collection); foreach ($aspects ?? [] as $aspect) { $rules = AspectCollector::getRule($aspect); foreach ($rules['classes'] ?? [] as $rule) { [$isMatch, $method] = static::isMatchClassRule($class, $rule); if ($isMatch) { if ($method === null) { return $rewriteCollection->setLevel(RewriteCollection::CLASS_LEVEL); } $rewriteCollection->add($method); } } } } } src/Aop/AspectManager.php000064400000001453151360553600011304 0ustar00astParser = $parserFactory->create(ParserFactory::ONLY_PHP7); $this->printer = new Standard(); } public function parse(string $code): ?array { return $this->astParser->parse($code); } public function proxy(string $className) { $code = $this->getCodeByClassName($className); $stmts = $this->astParser->parse($code); $traverser = new NodeTraverser(); $visitorMetadata = new VisitorMetadata(); $visitorMetadata->className = $className; // User could modify or replace the node vistors by Hyperf\Di\Aop\AstVisitorRegistry. $queue = clone AstVisitorRegistry::getQueue(); foreach ($queue as $string) { $visitor = new $string($visitorMetadata); $traverser->addVisitor($visitor); } $modifiedStmts = $traverser->traverse($stmts); return $this->printer->prettyPrintFile($modifiedStmts); } public function parseClassByStmts(array $stmts): string { $namespace = $className = ''; foreach ($stmts as $stmt) { if ($stmt instanceof Namespace_ && $stmt->name) { $namespace = $stmt->name->toString(); foreach ($stmt->stmts as $node) { if (($node instanceof Class_ || $node instanceof Interface_) && $node->name) { $className = $node->name->toString(); break; } } } } return ($namespace && $className) ? $namespace . '\\' . $className : ''; } private function getCodeByClassName(string $className): string { $file = Composer::getLoader()->findFile($className); if (! $file) { return ''; } return file_get_contents($file); } } src/Aop/AstVisitorRegistry.php000064400000002365151360553600012435 0ustar00{$name}(...$arguments); } throw new \InvalidArgumentException('Invalid method for ' . __CLASS__); } public static function insert($value, $priority) { static::$values[] = $value; return static::getQueue()->insert($value, $priority); } public static function exists($value): bool { return in_array($value, static::$values); } public static function getQueue(): \SplPriorityQueue { if (! static::$queue instanceof \SplPriorityQueue) { static::$queue = new \SplPriorityQueue(); } return static::$queue; } } src/Aop/Pipeline.php000064400000002033151360553600010332 0ustar00container->get($pipe); } if (! $passable instanceof ProceedingJoinPoint) { throw new InvalidDefinitionException('$passable must is a ProceedingJoinPoint object.'); } $passable->pipe = $stack; return method_exists($pipe, $this->method) ? $pipe->{$this->method}($passable) : $pipe($passable); }; }; } } src/Aop/ProceedingJoinPoint.php000064400000005212151360553600012500 0ustar00originalMethod = $originalMethod; $this->className = $className; $this->methodName = $methodName; $this->arguments = $arguments; } /** * Delegate to the next aspect. */ public function process() { $closure = $this->pipe; if (! $closure instanceof Closure) { throw new Exception('The pipe is not instanceof \Closure'); } return $closure($this); } /** * Process the original method, this method should trigger by pipeline. */ public function processOriginalMethod() { $this->pipe = null; $closure = $this->originalMethod; if (count($this->arguments['keys']) > 1) { $arguments = $this->getArguments(); } else { $arguments = array_values($this->arguments['keys']); } return $closure(...$arguments); } public function getAnnotationMetadata(): AnnotationMetadata { $metadata = AnnotationCollector::get($this->className); return new AnnotationMetadata($metadata['_c'] ?? [], $metadata['_m'][$this->methodName] ?? []); } public function getArguments() { return value(function () { $result = []; foreach ($this->arguments['order'] ?? [] as $order) { $result[] = $this->arguments['keys'][$order]; } return $result; }); } public function getReflectMethod(): \ReflectionMethod { return ReflectionManager::reflectMethod( $this->className, $this->methodName ); } public function getInstance(): ?object { $ref = new \ReflectionFunction($this->originalMethod); return $ref->getClosureThis(); } } src/Aop/PropertyHandlerTrait.php000064400000007454151360553600012727 0ustar00__handle($className, $className, $properties); // Inject the properties of traits. // Because the property annotations of trait couldn't be collected by class. $handled = $this->__handleTrait($reflectionClass, $handled, $className); // Inject the properties of parent class. // It can be used to deal with parent classes whose subclasses have constructor function, but don't execute `parent::__construct()`. // For example: // class SubClass extend ParentClass // { // public function __construct() { // } // } $parentReflectionClass = $reflectionClass; while ($parentReflectionClass = $parentReflectionClass->getParentClass()) { $parentClassProperties = ReflectionManager::reflectPropertyNames($parentReflectionClass->getName()); $parentClassProperties = array_filter($parentClassProperties, static function ($property) use ($reflectionClass) { return $reflectionClass->hasProperty($property); }); $parentClassProperties = array_diff($parentClassProperties, $handled); $handled = array_merge( $handled, $this->__handle($className, $parentReflectionClass->getName(), $parentClassProperties) ); } } protected function __handleTrait(\ReflectionClass $reflectionClass, array $handled, string $className): array { foreach ($reflectionClass->getTraits() ?? [] as $reflectionTrait) { if (in_array($reflectionTrait->getName(), [ProxyTrait::class, PropertyHandlerTrait::class])) { continue; } $traitProperties = ReflectionManager::reflectPropertyNames($reflectionTrait->getName()); $traitProperties = array_diff($traitProperties, $handled); if (! $traitProperties) { continue; } $handled = array_merge( $handled, $this->__handle($className, $reflectionTrait->getName(), $traitProperties) ); $handled = $this->__handleTrait($reflectionTrait, $handled, $className); } return $handled; } protected function __handle(string $currentClassName, string $targetClassName, array $properties): array { $handled = []; foreach ($properties as $propertyName) { $propertyMetadata = AnnotationCollector::getClassPropertyAnnotation($targetClassName, $propertyName); if (! $propertyMetadata) { continue; } foreach ($propertyMetadata as $annotationName => $annotation) { if ($callbacks = PropertyHandlerManager::get($annotationName)) { foreach ($callbacks as $callback) { call($callback, [$this, $currentClassName, $targetClassName, $propertyName, $annotation]); } $handled[] = $propertyName; } } } return $handled; } } src/Aop/PropertyHandlerVisitor.php000064400000012135151360553600013273 0ustar00visitorMetadata = $visitorMetadata; } public function enterNode(Node $node) { if ($node instanceof Node\Stmt\Class_) { if ($this->visitorMetadata->hasExtends === null) { if ($node->extends) { $this->visitorMetadata->hasExtends = true; } else { $this->visitorMetadata->hasExtends = false; } } } if ($node instanceof Node\Stmt\ClassMethod) { if ($node->name->toString() === '__construct') { $this->visitorMetadata->hasConstructor = true; $this->visitorMetadata->constructorNode = $node; } } return null; } public function leaveNode(Node $node) { if (! $this->visitorMetadata->hasConstructor && $node instanceof Node\Stmt\Class_ && ! $node->isAnonymous()) { $constructor = $this->buildConstructor(); if ($this->visitorMetadata->hasExtends) { $constructor->stmts[] = $this->buildCallParentConstructorStatement(); } $constructor->stmts[] = $this->buildMethodCallStatement(); $node->stmts = array_merge([$this->buildProxyTraitUseStatement()], [$constructor], $node->stmts); $this->visitorMetadata->hasConstructor = true; } else { if ($node instanceof Node\Stmt\ClassMethod && $node->name->toString() === '__construct') { $node->stmts = array_merge([$this->buildMethodCallStatement()], $node->stmts); } if ($node instanceof Node\Stmt\Class_ && ! $node->isAnonymous()) { $node->stmts = array_merge([$this->buildProxyTraitUseStatement()], $node->stmts); } } return null; } protected function buildConstructor(): Node\Stmt\ClassMethod { if ($this->visitorMetadata->constructorNode instanceof Node\Stmt\ClassMethod) { // Returns the parsed constructor class method node. $constructor = $this->visitorMetadata->constructorNode; } else { // Create a new constructor class method node. $constructor = new Node\Stmt\ClassMethod('__construct'); $reflection = ReflectionManager::reflectClass($this->visitorMetadata->className); try { $parameters = $reflection->getMethod('__construct')->getParameters(); foreach ($parameters as $parameter) { $constructor->params[] = PhpParser::getInstance()->getNodeFromReflectionParameter($parameter); } } catch (\ReflectionException $exception) { // Cannot found __construct method in parent class or traits, do noting. } } return $constructor; } protected function buildCallParentConstructorStatement(): Node\Stmt { $hasConstructor = new Node\Expr\FuncCall(new Name('method_exists'), [ new Node\Arg(new Node\Expr\ClassConstFetch(new Name('parent'), 'class')), new Node\Arg(new Node\Scalar\String_('__construct')), ]); return new Node\Stmt\If_($hasConstructor, [ 'stmts' => [ new Node\Stmt\Expression(new Node\Expr\StaticCall(new Name('parent'), '__construct', [ new Node\Arg(new Node\Expr\FuncCall(new Name('func_get_args')), false, true), ])), ], ]); } protected function buildMethodCallStatement(): Node\Stmt\Expression { return new Node\Stmt\Expression(new Node\Expr\MethodCall(new Node\Expr\Variable('this'), '__handlePropertyHandler', [ new Node\Arg(new Node\Scalar\MagicConst\Class_()), ])); } /** * Build `use PropertyHandlerTrait;` statement. */ protected function buildProxyTraitUseStatement(): TraitUse { $traits = []; foreach ($this->proxyTraits as $proxyTrait) { // Should not check the trait whether or not exist to avoid class autoload. if (! is_string($proxyTrait)) { continue; } // Add backslash prefix if the proxy trait does not start with backslash. $proxyTrait[0] !== '\\' && $proxyTrait = '\\' . $proxyTrait; $traits[] = new Name($proxyTrait); } return new TraitUse($traits); } } src/Aop/ProxyCallVisitor.php000064400000017436151360553600012077 0ustar00visitorMetadata = $visitorMetadata; } public function beforeTraverse(array $nodes) { foreach ($nodes as $namespace) { if ($namespace instanceof Node\Stmt\Declare_) { continue; } if (! $namespace instanceof Node\Stmt\Namespace_) { break; } foreach ($namespace->stmts as $class) { switch ($class) { case $class instanceof Node\Stmt\ClassLike: $this->visitorMetadata->classLike = get_class($class); break; } } } return null; } public function enterNode(Node $node) { switch ($node) { case $node instanceof ClassMethod: if ($this->shouldRewrite($node)) { $this->shouldRewrite = true; } else { $this->shouldRewrite = false; } break; } return null; } public function leaveNode(Node $node) { switch ($node) { case $node instanceof ClassMethod: if (! $this->shouldRewrite($node)) { return $node; } // Rewrite the method to proxy call method. return $this->rewriteMethod($node); case $node instanceof Node\Stmt\Trait_: // If the node is trait and php version >= 7.3, it can `use ProxyTrait` like class. case $node instanceof Class_ && ! $node->isAnonymous(): // Add use proxy traits. $stmts = $node->stmts; if ($stmt = $this->buildProxyCallTraitUseStatement()) { array_unshift($stmts, $stmt); } $node->stmts = $stmts; unset($stmts); return $node; case $node instanceof MagicConstFunction: // Rewrite __FUNCTION__ to $__function__ variable. if ($this->shouldRewrite) { return new Variable('__function__'); } break; case $node instanceof MagicConstMethod: // Rewrite __METHOD__ to $__method__ variable. if ($this->shouldRewrite) { return new Variable('__method__'); } break; } return null; } /** * Build `use ProxyTrait;`. */ private function buildProxyCallTraitUseStatement(): ?TraitUse { $traits = []; foreach ($this->proxyTraits as $proxyTrait) { if (! is_string($proxyTrait) || ! trait_exists($proxyTrait)) { continue; } // Add backslash prefix if the proxy trait does not start with backslash. $proxyTrait[0] !== '\\' && $proxyTrait = '\\' . $proxyTrait; $traits[] = new Name($proxyTrait); } if (empty($traits)) { return null; } return new TraitUse($traits); } /** * Rewrite a normal class method to a proxy call method, * include normal class method and static method. */ private function rewriteMethod(ClassMethod $node): ClassMethod { // Build the static proxy call method base on the original method. $shouldReturn = true; $returnType = $node->getReturnType(); if ($returnType instanceof Identifier && $returnType->name === 'void') { $shouldReturn = false; } $staticCall = new StaticCall(new Name('self'), '__proxyCall', [ // __CLASS__ new Arg($this->getMagicConst()), // __FUNCTION__ new Arg(new MagicConstFunction()), // self::getParamMap(OriginalClass::class, __FUNCTION, func_get_args()) new Arg(new StaticCall(new Name('self'), '__getParamsMap', [ new Arg(new MagicConstClass()), new Arg(new MagicConstFunction()), new Arg(new FuncCall(new Name('func_get_args'))), ])), // A closure that wrapped original method code. new Arg(new Closure([ 'params' => value(function () use ($node) { // Transfer the variadic variable to normal variable at closure argument. ...$params => $parms $params = $node->getParams(); foreach ($params as $key => $param) { if ($param instanceof Node\Param && $param->variadic) { $newParam = clone $param; $newParam->variadic = false; $newParam->type = null; $params[$key] = $newParam; } } return $params; }), 'uses' => [ new Variable('__function__'), new Variable('__method__'), ], 'stmts' => $node->stmts, ])), ]); $stmts = $this->unshiftMagicMethods([]); if ($shouldReturn) { $stmts[] = new Return_($staticCall); } else { $stmts[] = new Expression($staticCall); } $node->stmts = $stmts; return $node; } private function unshiftMagicMethods($stmts = []) { $magicConstFunction = new Expression(new Assign(new Variable('__function__'), new MagicConstFunction())); $magicConstMethod = new Expression(new Assign(new Variable('__method__'), new MagicConstMethod())); array_unshift($stmts, $magicConstFunction, $magicConstMethod); return $stmts; } private function getMagicConst(): Node\Scalar\MagicConst { switch ($this->visitorMetadata->classLike) { case Trait_::class: return new MagicConstTrait(); case Class_::class: default: return new MagicConstClass(); } } private function shouldRewrite(ClassMethod $node) { if (in_array($this->visitorMetadata->classLike, [Node\Stmt\Interface_::class])) { return false; } $rewriteCollection = Aspect::parse($this->visitorMetadata->className); return $rewriteCollection->shouldRewrite($node->name->toString()); } } src/Aop/ProxyManager.php000064400000013347151360553600011213 0ustar00classMap = $classMap; $this->proxyDir = $proxyDir; $this->filesystem = new Filesystem(); $this->proxies = $this->generateProxyFiles($this->initProxiesByReflectionClassMap( $this->classMap )); } public function getProxies(): array { return $this->proxies; } public function getProxyDir(): string { return $this->proxyDir; } protected function generateProxyFiles(array $proxies = []): array { $proxyFiles = []; if (! $proxies) { return $proxyFiles; } if (! file_exists($this->getProxyDir())) { mkdir($this->getProxyDir(), 0755, true); } // WARNING: Ast class SHOULD NOT use static instance, because it will read the code from file, then would be caused coroutine switch. $ast = new Ast(); foreach ($proxies as $className => $aspects) { $proxyFiles[$className] = $this->putProxyFile($ast, $className); } return $proxyFiles; } protected function putProxyFile(Ast $ast, $className) { $proxyFilePath = $this->getProxyFilePath($className); $modified = true; if (file_exists($proxyFilePath)) { $modified = $this->isModified($className, $proxyFilePath); } if ($modified) { $code = $ast->proxy($className); file_put_contents($proxyFilePath, $code); } return $proxyFilePath; } protected function isModified(string $className, string $proxyFilePath = null): bool { $proxyFilePath = $proxyFilePath ?? $this->getProxyFilePath($className); $time = $this->filesystem->lastModified($proxyFilePath); $origin = $this->classMap[$className]; if ($time >= $this->filesystem->lastModified($origin)) { return false; } return true; } protected function getProxyFilePath($className) { return $this->getProxyDir() . str_replace('\\', '_', $className) . '.proxy.php'; } protected function isMatch(string $rule, string $target): bool { if (strpos($rule, '::') !== false) { [$rule,] = explode('::', $rule); } if (strpos($rule, '*') === false && $rule === $target) { return true; } $preg = str_replace(['*', '\\'], ['.*', '\\\\'], $rule); $pattern = "/^{$preg}$/"; if (preg_match($pattern, $target)) { return true; } return false; } protected function initProxiesByReflectionClassMap(array $reflectionClassMap = []): array { // According to the data of AspectCollector to parse all the classes that need proxy. $proxies = []; if (! $reflectionClassMap) { return $proxies; } $classesAspects = AspectCollector::get('classes', []); foreach ($classesAspects as $aspect => $rules) { foreach ($rules as $rule) { foreach ($reflectionClassMap as $class => $path) { if (! $this->isMatch($rule, $class)) { continue; } $proxies[$class][] = $aspect; } } } foreach ($reflectionClassMap as $className => $path) { // Aggregate the class annotations $classAnnotations = $this->retrieveAnnotations($className . '._c'); // Aggregate all methods annotations $methodAnnotations = $this->retrieveAnnotations($className . '._m'); // Aggregate all properties annotations $propertyAnnotations = $this->retrieveAnnotations($className . '._p'); $annotations = array_unique(array_merge($classAnnotations, $methodAnnotations, $propertyAnnotations)); if ($annotations) { $annotationsAspects = AspectCollector::get('annotations', []); foreach ($annotationsAspects as $aspect => $rules) { foreach ($rules as $rule) { foreach ($annotations as $annotation) { if ($this->isMatch($rule, $annotation)) { $proxies[$className][] = $aspect; } } } } } } return $proxies; } protected function retrieveAnnotations(string $annotationCollectorKey): array { $defined = []; $annotations = AnnotationCollector::get($annotationCollectorKey, []); foreach ($annotations as $name => $annotation) { if (is_object($annotation)) { $defined[] = $name; } else { $defined = array_merge($defined, array_keys($annotation)); } } return $defined; } } src/Aop/ProxyTrait.php000064400000012471151360553600010721 0ustar00 [], 'order' => [], ]; $reflectMethod = ReflectionManager::reflectMethod($className, $method); $reflectParameters = $reflectMethod->getParameters(); $leftArgCount = count($args); foreach ($reflectParameters as $key => $reflectionParameter) { $arg = $reflectionParameter->isVariadic() ? $args : array_shift($args); if (! isset($arg) && $leftArgCount <= 0) { $arg = $reflectionParameter->getDefaultValue(); } --$leftArgCount; $map['keys'][$reflectionParameter->getName()] = $arg; $map['order'][] = $reflectionParameter->getName(); } return $map; } protected static function handleAround(ProceedingJoinPoint $proceedingJoinPoint) { $className = $proceedingJoinPoint->className; $methodName = $proceedingJoinPoint->methodName; if (! AspectManager::has($className, $methodName)) { AspectManager::set($className, $methodName, []); $aspects = array_unique(array_merge(static::getClassesAspects($className, $methodName), static::getAnnotationAspects($className, $methodName))); $queue = new \SplPriorityQueue(); foreach ($aspects as $aspect) { $queue->insert($aspect, AspectCollector::getPriority($aspect)); } while ($queue->valid()) { AspectManager::insert($className, $methodName, $queue->current()); $queue->next(); } unset($annotationAspects, $aspects, $queue); } if (empty(AspectManager::get($className, $methodName))) { return $proceedingJoinPoint->processOriginalMethod(); } return static::makePipeline()->via('process') ->through(AspectManager::get($className, $methodName)) ->send($proceedingJoinPoint) ->then(function (ProceedingJoinPoint $proceedingJoinPoint) { return $proceedingJoinPoint->processOriginalMethod(); }); } protected static function makePipeline(): Pipeline { $container = ApplicationContext::getContainer(); if (method_exists($container, 'make')) { $pipeline = $container->make(Pipeline::class); } else { $pipeline = new Pipeline($container); } return $pipeline; } protected static function getClassesAspects(string $className, string $method): array { $aspects = AspectCollector::get('classes', []); $matchedAspect = []; foreach ($aspects as $aspect => $rules) { foreach ($rules as $rule) { if (Aspect::isMatch($className, $method, $rule)) { $matchedAspect[] = $aspect; break; } } } // The matched aspects maybe have duplicate aspect, should unique it when use it. return $matchedAspect; } protected static function getAnnotationAspects(string $className, string $method): array { $matchedAspect = $annotations = $rules = []; $classAnnotations = AnnotationCollector::get($className . '._c', []); $methodAnnotations = AnnotationCollector::get($className . '._m.' . $method, []); $annotations = array_unique(array_merge(array_keys($classAnnotations), array_keys($methodAnnotations))); if (! $annotations) { return $matchedAspect; } $aspects = AspectCollector::get('annotations', []); foreach ($aspects as $aspect => $rules) { foreach ($rules as $rule) { foreach ($annotations as $annotation) { if (strpos($rule, '*') !== false) { $preg = str_replace(['*', '\\'], ['.*', '\\\\'], $rule); $pattern = "/^{$preg}$/"; if (! preg_match($pattern, $annotation)) { continue; } } elseif ($rule !== $annotation) { continue; } $matchedAspect[] = $aspect; } } } // The matched aspects maybe have duplicate aspect, should unique it when use it. return $matchedAspect; } } src/Aop/RegisterInjectPropertyHandler.php000064400000003553151360553600014561 0ustar00setAccessible(true); $container = ApplicationContext::getContainer(); if ($container->has($annotation->value)) { $reflectionProperty->setValue($object, $container->get($annotation->value)); } elseif ($annotation->required) { throw new NotFoundException("No entry or class found for '{$annotation->value}'"); } } catch (\Throwable $throwable) { if ($annotation->required) { throw $throwable; } } } }); static::$registered = true; } } src/Aop/RewriteCollection.php000064400000004606151360553600012232 0ustar00class = $class; } /** * @param array|string $methods */ public function add($methods): self { $methods = (array) $methods; foreach ($methods as $method) { if (strpos($method, '*') === false) { $this->methods[] = $method; } else { $preg = str_replace(['*', '\\'], ['.*', '\\\\'], $method); $this->pattern[] = "/^{$preg}$/"; } } return $this; } public function shouldRewrite(string $method): bool { if ($this->level === self::CLASS_LEVEL) { if (in_array($method, $this->shouldNotRewriteMethods)) { return false; } return true; } if (in_array($method, $this->methods)) { return true; } foreach ($this->pattern as $pattern) { if (preg_match($pattern, $method)) { return true; } } return false; } public function setLevel(int $level): self { $this->level = $level; return $this; } public function getLevel(): int { return $this->level; } public function getMethods(): array { return $this->methods; } public function getClass(): string { return $this->class; } /** * Return the methods that should not rewrite. */ public function getShouldNotRewriteMethods(): array { return $this->shouldNotRewriteMethods; } } src/Aop/VisitorMetadata.php000064400000001176151360553600011674 0ustar00 ProxyFileAbsolutePath ]. * * @var array */ protected $proxies = []; public function __construct(ComposerClassLoader $classLoader, string $proxyFileDir, string $configDir, ScanHandlerInterface $handler) { $this->setComposerClassLoader($classLoader); if (file_exists(BASE_PATH . '/.env')) { $this->loadDotenv(); } // Scan by ScanConfig to generate the reflection class map $config = ScanConfig::instance($configDir); $classLoader->addClassMap($config->getClassMap()); $scanner = new Scanner($this, $config, $handler); $this->proxies = $scanner->scan($this->getComposerClassLoader()->getClassMap(), $proxyFileDir); } public function loadClass(string $class): void { $path = $this->locateFile($class); if ($path) { include $path; } } public static function init(?string $proxyFileDirPath = null, ?string $configDir = null, ?ScanHandlerInterface $handler = null): void { if (! $proxyFileDirPath) { // This dir is the default proxy file dir path of Hyperf $proxyFileDirPath = BASE_PATH . '/runtime/container/proxy/'; } if (! $configDir) { // This dir is the default proxy file dir path of Hyperf $configDir = BASE_PATH . '/config/'; } if (! $handler) { $handler = new PcntlScanHandler(); } $loaders = spl_autoload_functions(); // Proxy the composer class loader foreach ($loaders as &$loader) { $unregisterLoader = $loader; if (is_array($loader) && $loader[0] instanceof ComposerClassLoader) { /** @var ComposerClassLoader $composerClassLoader */ $composerClassLoader = $loader[0]; AnnotationRegistry::registerLoader(function ($class) use ($composerClassLoader) { return (bool) $composerClassLoader->findFile($class); }); $loader[0] = new static($composerClassLoader, $proxyFileDirPath, $configDir, $handler); } spl_autoload_unregister($unregisterLoader); } unset($loader); // Re-register the loaders foreach ($loaders as $loader) { spl_autoload_register($loader); } // Initialize Lazy Loader. This will prepend LazyLoader to the top of autoload queue. LazyLoader::bootstrap($configDir); } public function setComposerClassLoader(ComposerClassLoader $classLoader): self { $this->composerClassLoader = $classLoader; // Set the ClassLoader to Hyperf\Utils\Composer to avoid unnecessary find process. Composer::setLoader($classLoader); return $this; } public function getComposerClassLoader(): ComposerClassLoader { return $this->composerClassLoader; } protected function locateFile(string $className): ?string { if (isset($this->proxies[$className]) && file_exists($this->proxies[$className])) { $file = $this->proxies[$className]; } else { $file = $this->getComposerClassLoader()->findFile($className); } return is_string($file) ? $file : null; } protected function loadDotenv(): void { $repository = RepositoryBuilder::createWithNoAdapters() ->addAdapter(Adapter\PutenvAdapter::class) ->immutable() ->make(); Dotenv::create($repository, [BASE_PATH])->load(); } } src/ClosureDefinitionCollector.php000064400000002425151360553600013347 0ustar00getParameters(); $definitions = $this->getDefinitionsFromParameters($parameters); static::set($key, $definitions); return $definitions; } public function getReturnType(\Closure $closure): ReflectionType { $key = spl_object_hash($closure) . '@return'; if (static::has($key)) { return static::get($key); } $returnType = (new \ReflectionFunction($closure))->getReturnType(); $type = $this->createType('', $returnType, $returnType ? $returnType->allowsNull() : true); static::set($key, $type); return $type; } } src/ClosureDefinitionCollectorInterface.php000064400000000672151360553600015172 0ustar00 [ MethodDefinitionCollectorInterface::class => MethodDefinitionCollector::class, ClosureDefinitionCollectorInterface::class => ClosureDefinitionCollector::class, ], 'aspects' => [ InjectAspect::class, ], 'annotations' => [ 'scan' => [ 'paths' => [ __DIR__, ], 'collectors' => [ AnnotationCollector::class, AspectCollector::class, ], ], 'ignore_annotations' => [ 'mixin', ], ], ]; } } src/Container.php000064400000013445151360553600010001 0ustar00definitionSource = $definitionSource; $this->definitionResolver = new ResolverDispatcher($this); // Auto-register the container. $this->resolvedEntries = [ self::class => $this, PsrContainerInterface::class => $this, HyperfContainerInterface::class => $this, ]; } /** * Build an entry of the container by its name. * This method behave like get() except resolves the entry again every time. * For example if the entry is a class then a new instance will be created each time. * This method makes the container behave like a factory. * * @param string $name entry name or a class name * @param array $parameters Optional parameters to use to build the entry. Use this to force specific parameters * to specific values. Parameters not defined in this array will be resolved using * the container. * @throws NotFoundException no entry found for the given name * @throws InvalidArgumentException the name parameter must be of type string */ public function make(string $name, array $parameters = []) { $definition = $this->getDefinition($name); if (! $definition) { throw new NotFoundException("No entry or class found for '{$name}'"); } return $this->resolveDefinition($definition, $parameters); } /** * Bind an arbitrary resolved entry to an identifier. * Useful for testing 'get'. * @param mixed $entry */ public function set(string $name, $entry) { $this->resolvedEntries[$name] = $entry; } /** * Bind an arbitrary definition to an identifier. * Useful for testing 'make'. * * @param array|callable|string $definition */ public function define(string $name, $definition) { $this->setDefinition($name, $definition); } /** * Finds an entry of the container by its identifier and returns it. * * @param string $name identifier of the entry to look for */ public function get($name) { // If the entry is already resolved we return it if (isset($this->resolvedEntries[$name]) || array_key_exists($name, $this->resolvedEntries)) { return $this->resolvedEntries[$name]; } return $this->resolvedEntries[$name] = $this->make($name); } /** * Returns true if the container can return an entry for the given identifier. * Returns false otherwise. * `has($name)` returning true does not mean that `get($name)` will not throw an exception. * It does however mean that `get($name)` will not throw a `NotFoundExceptionInterface`. * * @param mixed|string $name identifier of the entry to look for */ public function has($name): bool { if (! is_string($name)) { throw new InvalidArgumentException(sprintf('The name parameter must be of type string, %s given', is_object($name) ? get_class($name) : gettype($name))); } if (array_key_exists($name, $this->resolvedEntries)) { return true; } $definition = $this->getDefinition($name); if ($definition === null) { return false; } if ($definition instanceof ObjectDefinition) { return $definition->isInstantiable(); } return true; } public function getDefinitionSource(): Definition\DefinitionSourceInterface { return $this->definitionSource; } /** * @param array|callable|string $definition */ private function setDefinition(string $name, $definition): void { // Clear existing entry if it exists if (array_key_exists($name, $this->resolvedEntries)) { unset($this->resolvedEntries[$name]); } $this->fetchedDefinitions = []; // Completely clear this local cache $this->definitionSource->addDefinition($name, $definition); } private function getDefinition(string $name): ?DefinitionInterface { // Local cache that avoids fetching the same definition twice if (! array_key_exists($name, $this->fetchedDefinitions)) { $this->fetchedDefinitions[$name] = $this->definitionSource->getDefinition($name); } return $this->fetchedDefinitions[$name]; } /** * Resolves a definition. */ private function resolveDefinition(DefinitionInterface $definition, array $parameters = []) { return $this->definitionResolver->resolve($definition, $parameters); } } src/Definition/DefinitionInterface.php000064400000001215151360553600014050 0ustar00source = $this->normalizeSource($source); } /** * Returns the DI definition for the entry name. */ public function getDefinition(string $name): ?DefinitionInterface { return $this->source[$name] ?? $this->source[$name] = $this->autowire($name); } /** * @return array definitions indexed by their name */ public function getDefinitions(): array { return $this->source; } /** * @param array|callable|string $definition */ public function addDefinition(string $name, $definition): self { $this->source[$name] = $this->normalizeDefinition($name, $definition); return $this; } public function clearDefinitions(): void { $this->source = []; } /** * Read the type-hinting from the parameters of the function. */ private function getParametersDefinition(ReflectionFunctionAbstract $constructor): array { $parameters = []; foreach ($constructor->getParameters() as $index => $parameter) { // Skip optional parameters. if ($parameter->isOptional()) { continue; } $parameterType = $parameter->getType(); if ($parameterType instanceof \ReflectionNamedType && ! $parameterType->isBuiltin()) { $parameters[$index] = new Reference($parameterType->getName()); } } return $parameters; } /** * Normaliaze the user definition source to a standard definition souce. */ private function normalizeSource(array $source): array { $definitions = []; foreach ($source as $identifier => $definition) { $normalizedDefinition = $this->normalizeDefinition($identifier, $definition); if (! is_null($normalizedDefinition)) { $definitions[$identifier] = $normalizedDefinition; } } return $definitions; } /** * @param array|callable|string $definition */ private function normalizeDefinition(string $identifier, $definition): ?DefinitionInterface { if (is_string($definition) && class_exists($definition)) { if (method_exists($definition, '__invoke')) { return new FactoryDefinition($identifier, $definition, []); } return $this->autowire($identifier, new ObjectDefinition($identifier, $definition)); } if (is_callable($definition)) { return new FactoryDefinition($identifier, $definition, []); } return null; } private function autowire(string $name, ObjectDefinition $definition = null): ?ObjectDefinition { $className = $definition ? $definition->getClassName() : $name; if (! class_exists($className) && ! interface_exists($className)) { return $definition; } $definition = $definition ?: new ObjectDefinition($name); /** * Constructor. */ $class = ReflectionManager::reflectClass($className); $constructor = $class->getConstructor(); if ($constructor && $constructor->isPublic()) { $constructorInjection = new MethodInjection('__construct', $this->getParametersDefinition($constructor)); $definition->completeConstructorInjection($constructorInjection); } return $definition; } } src/Definition/DefinitionSourceFactory.php000064400000002566151360553600014752 0ustar00enableCache = $enableCache; if (! defined('BASE_PATH')) { throw new Exception('BASE_PATH is not defined.'); } $this->baseUri = BASE_PATH; } public function __invoke() { $configDir = $this->baseUri . '/config'; $configFromProviders = []; if (class_exists(ProviderConfig::class)) { $configFromProviders = ProviderConfig::load(); } $serverDependencies = $configFromProviders['dependencies'] ?? []; if (file_exists($configDir . '/autoload/dependencies.php')) { $definitions = include $configDir . '/autoload/dependencies.php'; $serverDependencies = array_replace($serverDependencies, $definitions ?? []); } return new DefinitionSource($serverDependencies); } } src/Definition/DefinitionSourceInterface.php000064400000001603151360553600015232 0ustar00name = $name; $this->factory = $factory; $this->parameters = $parameters; } public function __toString(): string { return 'Factory'; } public function getName(): string { return $this->name; } public function setName(string $name): self { $this->name = $name; return $this; } /** * @return callable|string */ public function getFactory() { return $this->factory; } /** * @return mixed[] */ public function getParameters(): array { return $this->parameters; } /** * Determine if the definition need to transfer to a proxy class. */ public function isNeedProxy(): bool { return $this->needProxy; } public function setNeedProxy($needProxy): self { $this->needProxy = $needProxy; return $this; } } src/Definition/MethodInjection.php000064400000003371151360553600013227 0ustar00parameters = $parameters; $this->methodName = $methodName; } public function __toString(): string { return sprintf('method(%s)', $this->methodName); } public function getName(): string { return ''; } public function setName(string $name) { // The name does not matter for method injections, so do nothing. } /** * @return mixed[] */ public function getParameters(): array { return $this->parameters; } public function merge(self $definition) { // In case of conflicts, the current definition prevails. $this->parameters = $this->parameters + $definition->parameters; } /** * Reset the target should be resolved. * If it is the FactoryDefinition, then the target means $factory property, * If it is the ObjectDefinition, then the target means $className property. */ public function setTarget(string $value) { $this->methodName = $value; } /** * Determine if the definition need to transfer to a proxy class. */ public function isNeedProxy(): bool { // Method injection does not has proxy. return false; } } src/Definition/ObjectDefinition.php000064400000005267151360553600013371 0ustar00name = $name; $this->setClassName($className ?? $name); } public function __toString(): string { return sprintf('Object[%s]', $this->getClassName()); } public function getName(): string { return $this->name; } public function setName(string $name): self { $this->name = $name; return $this; } public function setClassName(string $className = null): void { $this->className = $className; $this->updateStatusCache(); } public function getClassName(): string { if ($this->className !== null) { return $this->className; } return $this->name; } public function isClassExists(): bool { return $this->classExists; } public function isInstantiable(): bool { return $this->instantiable; } /** * @return null|\Hyperf\Di\Definition\MethodInjection */ public function getConstructorInjection() { return $this->constructorInjection; } public function setConstructorInjection(MethodInjection $injection): self { $this->constructorInjection = $injection; return $this; } public function completeConstructorInjection(MethodInjection $injection): void { if ($this->constructorInjection !== null) { // Merge $this->constructorInjection->merge($injection); } else { // Set $this->constructorInjection = $injection; } } private function updateStatusCache(): void { $className = $this->getClassName(); $this->classExists = class_exists($className) || interface_exists($className); if (! $this->classExists) { $this->instantiable = false; return; } $this->instantiable = ReflectionManager::reflectClass($className)->isInstantiable(); } } src/Definition/PropertyDefinition.php000064400000003610151360553600013775 0ustar00propertyName = $propertyName; $this->value = $value; $this->className = $className; } /** * Definitions can be cast to string for debugging information. */ public function __toString(): string { return 'Property'; } /** * Returns the name of the entry in the container. */ public function getName(): string { return $this->propertyName; } /** * Set the name of the entry in the container. */ public function setName(string $name) { $this->propertyName = $name; } public function getValue() { return $this->value; } public function getClassName(): ?string { return $this->className; } /** * Determine if the definition need to transfer to a proxy class. */ public function isNeedProxy(): bool { return false; } } src/Definition/PropertyHandlerManager.php000064400000001747151360553600014566 0ustar00targetEntryName = $targetEntryName; } /** * Definitions can be cast to string for debugging information. */ public function __toString(): string { return sprintf('get(%s)', $this->targetEntryName); } /** * Returns the name of the entry in the container. */ public function getName(): string { return $this->name; } /** * Set the name of the entry in the container. */ public function setName(string $name) { $this->name = $name; } public function getTargetEntryName(): string { return $this->targetEntryName; } public function resolve(ContainerInterface $container) { return $container->get($this->getTargetEntryName()); } public function isResolvable(ContainerInterface $container): bool { return $container->has($this->getTargetEntryName()); } /** * Determine if the definition need to transfer to a proxy class. */ public function isNeedProxy(): bool { return $this->needProxy; } public function setNeedProxy($needProxy): self { $this->needProxy = $needProxy; return $this; } } src/Definition/ScanConfig.php000064400000002572151360553600012160 0ustar00dirs = $dirs; $this->ignoreAnnotations = $ignoreAnnotations; $this->collectors = $collectors; } public function getDirs(): array { return $this->dirs; } public function setDirs(array $dirs): self { $this->dirs = $dirs; return $this; } public function getIgnoreAnnotations(): array { return $this->ignoreAnnotations; } public function setIgnoreAnnotations(array $ignoreAnnotations): self { $this->ignoreAnnotations = $ignoreAnnotations; return $this; } public function getCollectors(): array { return $this->collectors; } public function setCollectors(array $collectors): self { $this->collectors = $collectors; return $this; } } src/Definition/SelfResolvingDefinitionInterface.php000064400000001165151360553600016557 0ustar00sealed) { return; } if (in_array($name, $this->list)) { $this->sealed = true; } $this->updateListAndMessage($name); } private function updateListAndMessage(string $name) { array_unshift($this->list, $name); $listAsString = implode(' -> ', $this->list); $this->message = "depth limit reached due to the following dependencies: {$listAsString}"; } } src/Exception/ConflictAnnotationException.php000064400000000520151360553600015456 0ustar00factory = new BuilderFactory(); $this->builder = $this->factory; } abstract public function addClassRelationship(): AbstractLazyProxyBuilder; public function addClassBoilerplate(string $proxyClassName, string $originalClassName): AbstractLazyProxyBuilder { $namespace = join('\\', array_slice(explode('\\', $proxyClassName), 0, -1)); $this->namespace = $namespace; $this->proxyClassName = $proxyClassName; $this->originalClassName = $originalClassName; $this->builder = $this->factory->class(class_basename($proxyClassName)) ->addStmt(new ClassConst([new Const_('PROXY_TARGET', new String_($originalClassName))])) ->addStmt($this->factory->useTrait('\\Hyperf\\Di\\LazyLoader\\LazyProxyTrait')) ->setDocComment("/** * Be careful: This is a lazy proxy, not the real {$originalClassName} from container. * * {@inheritdoc} */"); return $this; } public function addNodes(array $nodes): AbstractLazyProxyBuilder { foreach ($nodes as $stmt) { $this->builder = $this->builder->addStmt($stmt); } return $this; } public function getNode(): Node { if ($this->namespace) { return $this->factory ->namespace($this->namespace) ->addStmt($this->builder) ->getNode(); } return $this->builder->getNode(); } public function getNamespace(): string { return $this->namespace; } public function getProxyClassName(): string { return $this->proxyClassName; } public function getOriginalClassName(): string { return $this->originalClassName; } } src/LazyLoader/ClassLazyProxyBuilder.php000064400000001320151360553600014370 0ustar00originalClassName, '\\') !== 0) { $originalClassName = '\\' . $this->originalClassName; } else { $originalClassName = $this->originalClassName; } $this->builder = $this->builder->extend($originalClassName); return $this; } } src/LazyLoader/FallbackLazyProxyBuilder.php000064400000000672151360553600015033 0ustar00originalClassName, '\\') !== 0) { $originalClassName = '\\' . $this->originalClassName; } else { $originalClassName = $this->originalClassName; } $this->builder = $this->builder->implement($originalClassName); return $this; } } src/LazyLoader/LazyLoader.php000064400000013040151360553600012162 0ustar00config = $config; $this->register(); } /** * Get or create the singleton lazy loader instance. */ public static function bootstrap(string $configDir): LazyLoader { if (static::$instance) { return static::$instance; } $path = $configDir . self::CONFIG_FILE_NAME; $config = []; if (file_exists($path)) { $config = include $path; } return static::$instance = new static($config); } /** * Load a class proxy if it is registered. * * @return null|bool */ public function load(string $proxy) { if (array_key_exists($proxy, $this->config) || $this->startsWith($proxy, 'HyperfLazy\\')) { $this->loadProxy($proxy); return true; } return null; } /** * Register the loader on the auto-loader stack. */ protected function register(): void { if (! $this->registered) { $this->prependToLoaderStack(); $this->registered = true; } } /** * Load a real-time facade for the given proxy. */ protected function loadProxy(string $proxy) { require_once $this->ensureProxyExists($proxy); } /** * Ensure that the given proxy has an existing real-time facade class. */ protected function ensureProxyExists(string $proxy): string { $dir = BASE_PATH . '/runtime/container/proxy/'; if (! file_exists($dir)) { mkdir($dir, 0755, true); } $code = $this->generatorLazyProxy( $proxy, $this->config[$proxy] ?? Str::after($proxy, 'HyperfLazy\\') ); $path = str_replace('\\', '_', $dir . $proxy . '_' . crc32($code) . '.php'); $key = md5($path); // If the proxy file does not exist, then try to acquire the coroutine lock. if (! file_exists($path) && CoLocker::lock($key)) { $targetPath = $path . '.' . uniqid(); file_put_contents($targetPath, $code); rename($targetPath, $path); CoLocker::unlock($key); } return $path; } /** * Format the lazy proxy with the proper namespace and class. */ protected function generatorLazyProxy(string $proxy, string $target): string { $targetReflection = new ReflectionClass($target); if ($this->isUnsupportedReflectionType($targetReflection)) { $builder = new FallbackLazyProxyBuilder(); return $this->buildNewCode($builder, $proxy, $targetReflection); } if ($targetReflection->isInterface()) { $builder = new InterfaceLazyProxyBuilder(); return $this->buildNewCode($builder, $proxy, $targetReflection); } $builder = new ClassLazyProxyBuilder(); return $this->buildNewCode($builder, $proxy, $targetReflection); } /** * Prepend the load method to the auto-loader stack. */ protected function prependToLoaderStack(): void { /** @var callable(string): void */ $load = [$this, 'load']; spl_autoload_register($load, true, true); } private function startsWith($haystack, $needle): bool { return substr($haystack, 0, strlen($needle)) === (string) $needle; } /** * These conditions are really hard to proxy via inheritence. * Luckily these conditions are very rarely met. * * TODO: implement some of them. * * @param ReflectionClass $targetReflection [description] * @return bool [description] */ private function isUnsupportedReflectionType(ReflectionClass $targetReflection): bool { return $targetReflection->isFinal(); } private function buildNewCode(AbstractLazyProxyBuilder $builder, string $proxy, ReflectionClass $reflectionClass): string { $target = $reflectionClass->getName(); $nodes = PhpParser::getInstance()->getNodesFromReflectionClass($reflectionClass); $builder->addClassBoilerplate($proxy, $target); $builder->addClassRelationship(); $traverser = new NodeTraverser(); $methods = PhpParser::getInstance()->getAllMethodsFromStmts($nodes); $visitor = new PublicMethodVisitor($methods, $builder->getOriginalClassName()); $traverser->addVisitor(new NameResolver()); $traverser->addVisitor($visitor); $traverser->traverse($nodes); $builder->addNodes($visitor->nodes); $prettyPrinter = new Standard(); return $prettyPrinter->prettyPrintFile([$builder->getNode()]); } } src/LazyLoader/LazyProxyTrait.php000064400000002622151360553600013105 0ustar00{$var}); } } public function __call($method, $arguments) { $obj = $this->getInstance(); return call_user_func([$obj, $method], ...$arguments); } public function __get($name) { return $this->getInstance()->{$name}; } public function __set($name, $value) { $this->getInstance()->{$name} = $value; } public function __isset($name) { return isset($this->getInstance()->{$name}); } public function __unset($name) { unset($this->getInstance()->{$name}); } public function __wakeup() { $vars = get_object_vars($this); foreach (array_keys($vars) as $var) { unset($this->{$var}); } } /** * Return The Proxy Target. * @return mixed */ public function getInstance() { return ApplicationContext::getContainer()->get(self::PROXY_TARGET); } } src/LazyLoader/PublicMethodVisitor.php000064400000005272151360553600014063 0ustar00stmts = $stmts; if (strpos($originalClassName, '\\') !== 0) { $originalClassName = '\\' . $originalClassName; } $this->originalClassName = $originalClassName; } public function enterNode(Node $node) { if ($node instanceof Interface_ || $node instanceof Class_) { $node->stmts = $this->stmts; } if ($node instanceof ClassMethod) { $methodCall = new MethodCall( new Variable('this'), '__call', [ new Node\Arg(new MagicConstFunction()), new Node\Arg(new FuncCall(new Name('func_get_args'))), ] ); $shouldReturn = true; if ($node->getReturnType() && method_exists($node->getReturnType(), 'toString')) { if ($node->getReturnType()->toString() === 'self') { $node->returnType = new Name($this->originalClassName); } if ($node->getReturnType()->toString() === 'void') { $shouldReturn = false; $methodCall = new Expression($methodCall); } } $shouldReturn && $methodCall = new Return_($methodCall); $node->stmts = [ $methodCall, ]; $node->flags &= ~Class_::MODIFIER_ABSTRACT; } return null; } public function leaveNode(Node $node) { if ($node instanceof ClassMethod && $node->isPublic() && ! $node->isMagic()) { $this->nodes[] = $node; } return null; } } src/MetadataCacheCollector.php000064400000002752151360553600012371 0ustar00collectors = $collectors; } public function addCollector(string $collector) { $this->collectors = array_unique(array_merge( $this->collectors, [$collector] )); } public function clear() { $this->collectors = []; } public function serialize(): string { $metadata = []; foreach ($this->collectors as $collector) { if (is_string($collector) && method_exists($collector, 'serialize')) { $metadata[$collector] = call([$collector, 'serialize']); } } return json_encode($metadata); } public function unserialize($serialized): void { $metadatas = json_decode($serialized, true) ?? []; $collectors = []; foreach ($metadatas as $collector => $metadata) { if (method_exists($collector, 'deserialize')) { call([$collector, 'deserialize'], [$metadata]); $collectors[] = $collector; } } $this->collectors = $collectors; } } src/MetadataCollector.php000064400000003465151360553600011447 0ustar00getParameters(); $definitions = []; foreach ($parameters as $parameter) { $type = $parameter->getType()->getName(); switch ($type) { case 'int': case 'float': case 'string': case 'array': case 'bool': $definition = [ 'type' => $type, 'name' => $parameter->getName(), 'ref' => '', 'allowsNull' => $parameter->allowsNull(), ]; if ($parameter->isDefaultValueAvailable()) { $definition['defaultValue'] = $parameter->getDefaultValue(); } $definitions[] = $definition; break; default: // Object $definitions[] = [ 'type' => 'object', 'name' => $parameter->getName(), 'ref' => $type ?? null, 'allowsNull' => $parameter->allowsNull(), ]; break; } } static::set($key, $definitions); return $definitions; } public function getParameters(string $class, string $method): array { $key = $class . '::' . $method . '@params'; if (static::has($key)) { return static::get($key); } $parameters = ReflectionManager::reflectClass($class)->getMethod($method)->getParameters(); $definitions = $this->getDefinitionsFromParameters($parameters); static::set($key, $definitions); return $definitions; } public function getReturnType(string $class, string $method): ReflectionType { $key = $class . '::' . $method . '@return'; if (static::has($key)) { return static::get($key); } $returnType = ReflectionManager::reflectClass($class)->getMethod($method)->getReturnType(); $type = $this->createType('', $returnType, $returnType ? $returnType->allowsNull() : true); static::set($key, $type); return $type; } } src/MethodDefinitionCollectorInterface.php000064400000001221151360553600014765 0ustar00getMethod($method); } return static::$container['method'][$key]; } public static function reflectProperty(string $className, string $property): ReflectionProperty { $key = $className . '::' . $property; if (! isset(static::$container['property'][$key])) { if (! class_exists($className)) { throw new InvalidArgumentException("Class {$className} not exist"); } static::$container['property'][$key] = static::reflectClass($className)->getProperty($property); } return static::$container['property'][$key]; } public static function reflectPropertyNames(string $className) { $key = $className; if (! isset(static::$container['property_names'][$key])) { if (! class_exists($className) && ! interface_exists($className) && ! trait_exists($className)) { throw new InvalidArgumentException("Class {$className} not exist"); } static::$container['property_names'][$key] = value(function () use ($className) { $properties = static::reflectClass($className)->getProperties(); $result = []; foreach ($properties as $property) { $result[] = $property->getName(); } return $result; }); } return static::$container['property_names'][$key]; } public static function clear(?string $key = null): void { if ($key === null) { static::$container = []; } } public static function getPropertyDefaultValue(ReflectionProperty $property) { return method_exists($property, 'getDefaultValue') ? $property->getDefaultValue() : $property->getDeclaringClass()->getDefaultProperties()[$property->getName()] ?? null; } public static function getAllClasses(array $paths): array { $finder = new Finder(); $finder->files()->in($paths)->name('*.php'); $parser = new Ast(); $reflectionClasses = []; foreach ($finder as $file) { try { $stmts = $parser->parse($file->getContents()); if (! $className = $parser->parseClassByStmts($stmts)) { continue; } $reflectionClasses[$className] = static::reflectClass($className); } catch (\Throwable $e) { } } return $reflectionClasses; } public static function getContainer(): array { return self::$container; } } src/ReflectionType.php000064400000001647151360553600011014 0ustar00name = $name; $this->allowsNull = $allowsNull; $this->metadata = $metadata; } public function getName(): string { return $this->name; } public function allowsNull(): bool { return $this->allowsNull; } public function getMeta(string $key) { return $this->metadata[$key] ?? null; } } src/Resolver/DepthGuard.php000064400000003453151360553600011705 0ustar00depthLimit = $depthLimit; } public function increment() { Context::override('di.depth', function ($depth) { $depth = $depth ?? 0; if (++$depth > $this->depthLimit) { throw new CircularDependencyException(); } return $depth; }); } public function decrement() { Context::override('di.depth', function ($depth) { return --$depth; }); } public function call(string $name, callable $callable) { try { $this->increment(); return $callable(); } catch (CircularDependencyException $exception) { $exception->addDefinitionName($name); throw $exception; } finally { $this->decrement(); } } } src/Resolver/FactoryResolver.php000064400000005531151360553600013006 0ustar00container = $container; $this->resolver = $resolver; } /** * Resolve a factory definition to a value. * * @param FactoryDefinition $definition object that defines how the value should be obtained * @param array $parameters optional parameters to use to build the entry * @throws InvalidDefinitionException if the definition cannot be resolved * @return mixed value obtained from the definition */ public function resolve(DefinitionInterface $definition, array $parameters = []) { $callable = null; try { $callable = $definition->getFactory(); if (! method_exists($callable, '__invoke')) { throw new NotCallableException(); } if (is_string($callable)) { $callable = $this->container->get($callable); $object = $callable($this->container); } else { $object = call($callable, [$this->container]); } return $object; } catch (NotCallableException $e) { // Custom error message to help debugging if (is_string($callable) && class_exists($callable) && method_exists($callable, '__invoke')) { throw new InvalidDefinitionException(sprintf('Entry "%s" cannot be resolved: factory %s. Invokable classes cannot be automatically resolved if autowiring is disabled on the container, you need to enable autowiring or define the entry manually.', $definition->getName(), $e->getMessage())); } throw new InvalidDefinitionException(sprintf('Entry "%s" cannot be resolved: factory %s', $definition->getName(), $e->getMessage())); } } /** * Check if a definition can be resolved. * * @param DefinitionInterface $definition object that defines how the value should be obtained * @param array $parameters optional parameters to use to build the entry */ public function isResolvable(DefinitionInterface $definition, array $parameters = []): bool { return true; } } src/Resolver/ObjectResolver.php000064400000007602151360553600012606 0ustar00container = $container; $this->definitionResolver = $definitionResolver; $this->parameterResolver = new ParameterResolver($definitionResolver); } /** * Resolve a definition to a value. * * @param DefinitionInterface $definition object that defines how the value should be obtained * @param array $parameters optional parameters to use to build the entry * @throws InvalidDefinitionException * @throws DependencyException * @return mixed value obtained from the definition */ public function resolve(DefinitionInterface $definition, array $parameters = []) { if (! $definition instanceof ObjectDefinition) { throw InvalidDefinitionException::create( $definition, sprintf('Entry "%s" cannot be resolved: the class is not instanceof ObjectDefinition', $definition->getName()) ); } return $this->createInstance($definition, $parameters); } /** * Check if a definition can be resolved. * * @param ObjectDefinition $definition object that defines how the value should be obtained * @param array $parameters optional parameters to use to build the entry */ public function isResolvable(DefinitionInterface $definition, array $parameters = []): bool { return $definition->isInstantiable(); } private function createInstance(ObjectDefinition $definition, array $parameters) { // Check that the class is instantiable if (! $definition->isInstantiable()) { // Check that the class exists if (! $definition->isClassExists()) { throw InvalidDefinitionException::create($definition, sprintf('Entry "%s" cannot be resolved: the class doesn\'t exist', $definition->getName())); } throw InvalidDefinitionException::create($definition, sprintf('Entry "%s" cannot be resolved: the class is not instantiable', $definition->getName())); } $classReflection = null; try { $className = $definition->getClassName(); $classReflection = ReflectionManager::reflectClass($className); $constructorInjection = $definition->getConstructorInjection(); $args = $this->parameterResolver->resolveParameters($constructorInjection, $classReflection->getConstructor(), $parameters); $object = new $className(...$args); } catch (NotFoundExceptionInterface $e) { throw new DependencyException(sprintf('Error while injecting dependencies into %s: %s', $classReflection ? $classReflection->getName() : '', $e->getMessage()), 0, $e); } catch (InvalidDefinitionException $e) { throw InvalidDefinitionException::create($definition, sprintf('Entry "%s" cannot be resolved: %s', $definition->getName(), $e->getMessage())); } return $object; } } src/Resolver/ParameterResolver.php000064400000006357151360553600013326 0ustar00definitionResolver = $definitionResolver; } public function resolveParameters( MethodInjection $definition = null, ReflectionMethod $method = null, array $parameters = [] ): array { $args = []; if (! $method) { return $args; } $definitionParameters = $definition ? $definition->getParameters() : []; foreach ($method->getParameters() as $index => $parameter) { if (array_key_exists($parameter->getName(), $parameters)) { $value = &$parameters[$parameter->getName()]; } elseif (array_key_exists($index, $parameters)) { $value = &$parameters[$index]; } elseif (array_key_exists($index, $definitionParameters)) { $value = &$definitionParameters[$index]; } else { if ($parameter->isDefaultValueAvailable() || $parameter->isOptional()) { $args[] = $this->getParameterDefaultValue($parameter, $method); continue; } throw new InvalidDefinitionException(sprintf( 'Parameter $%s of %s has no value defined or guessable', $parameter->getName(), $this->getFunctionName($method) )); } // Nested definitions if ($value instanceof DefinitionInterface) { // If the container cannot produce the entry, we can use the default parameter value if ($parameter->isOptional() && ! $this->definitionResolver->isResolvable($value)) { $value = $this->getParameterDefaultValue($parameter, $method); } else { $value = $this->definitionResolver->resolve($value); } } $args[] = &$value; } return $args; } private function getParameterDefaultValue(ReflectionParameter $parameter, ReflectionMethod $function) { try { return $parameter->getDefaultValue(); } catch (\ReflectionException $e) { throw new InvalidDefinitionException(sprintf( 'The parameter "%s" of %s has no type defined or guessable. It has a default value, ' . 'but the default value can\'t be read through Reflection because it is a PHP internal class.', $parameter->getName(), $this->getFunctionName($function) )); } } private function getFunctionName(ReflectionMethod $method): string { return $method->getName() . '()'; } } src/Resolver/ResolverDispatcher.php000064400000007151151360553600013465 0ustar00container = $container; } /** * Resolve a definition to a value. * * @param DefinitionInterface $definition object that defines how the value should be obtained * @param array $parameters optional parameters to use to build the entry * @throws InvalidDefinitionException if the definition cannot be resolved * @return mixed value obtained from the definition */ public function resolve(DefinitionInterface $definition, array $parameters = []) { if ($definition instanceof SelfResolvingDefinitionInterface) { return $definition->resolve($this->container); } $guard = DepthGuard::getInstance(); return $guard->call($definition->getName(), function () use ($definition, $parameters) { $resolver = $this->getDefinitionResolver($definition); return $resolver->resolve($definition, $parameters); }); } /** * Check if a definition can be resolved. * * @param DefinitionInterface $definition object that defines how the value should be obtained * @param array $parameters optional parameters to use to build the entry */ public function isResolvable(DefinitionInterface $definition, array $parameters = []): bool { if ($definition instanceof SelfResolvingDefinitionInterface) { return $definition->isResolvable($this->container); } $guard = DepthGuard::getInstance(); return $guard->call($definition->getName(), function () use ($definition, $parameters) { $resolver = $this->getDefinitionResolver($definition); return $resolver->isResolvable($definition, $parameters); }); } /** * Returns a resolver capable of handling the given definition. * * @throws RuntimeException no definition resolver was found for this type of definition */ private function getDefinitionResolver(DefinitionInterface $definition): ResolverInterface { switch (true) { case $definition instanceof ObjectDefinition: if (! $this->objectResolver) { $this->objectResolver = new ObjectResolver($this->container, $this); } return $this->objectResolver; case $definition instanceof FactoryDefinition: if (! $this->factoryResolver) { $this->factoryResolver = new FactoryResolver($this->container, $this); } return $this->factoryResolver; default: throw new RuntimeException('No definition resolver was configured for definition of type ' . get_class($definition)); } } } src/Resolver/ResolverInterface.php000064400000002265151360553600013300 0ustar00bin = $bin; $this->stub = $stub; } public function scan(): Scanned { if (env(static::SCAN_PROC_WORKER)) { return new Scanned(false); } $proc = proc_open( [$this->bin, $this->stub], [0 => STDIN, 1 => ['pipe', 'w'], 2 => ['redirect', 1]], $pipes, null, [static::SCAN_PROC_WORKER => '(true)'] ); $output = ''; do { $output .= fread($pipes[1], 8192); } while (! feof($pipes[1])); proc_close($proc); return new Scanned(true); } } src/ScanHandler/ScanHandlerInterface.php000064400000000530151360553600014233 0ustar00scanned = $scanned; } public function isScanned(): bool { return $this->scanned; } }