.gitattributes000064400000000054151360550730007443 0ustar00/tests export-ignore /.github export-ignore LICENSE000064400000002054151360550730005557 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.json000064400000002760151360550730007300 0ustar00{ "name": "hyperf/di", "description": "A DI for Hyperf.", "license": "MIT", "keywords": [ "php", "swoole", "hyperf", "di", "annotation" ], "homepage": "https://hyperf.io", "support": { "issues": "https://github.com/hyperf/hyperf/issues", "source": "https://github.com/hyperf/hyperf", "docs": "https://hyperf.wiki", "pull-request": "https://github.com/hyperf/hyperf/pulls" }, "require": { "php": ">=8.1", "doctrine/instantiator": "^1.0", "hyperf/code-parser": "~3.1.0", "hyperf/pipeline": "~3.1.0", "hyperf/stdlib": "~3.1.0", "hyperf/support": "~3.1.0", "nikic/php-parser": "^4.1", "php-di/phpdoc-reader": "^2.2", "psr/container": "^1.0 || ^2.0", "symfony/finder": "^5.0 || ^6.0 || ^7.0", "vlucas/phpdotenv": "^5.0" }, "suggest": { "ext-pcntl": "Required to scan annotations.", "hyperf/config": "Require this component for annotation scan progress to retrieve the scan path." }, "autoload": { "psr-4": { "Hyperf\\Di\\": "src/" } }, "autoload-dev": { "psr-4": { "HyperfTest\\Di\\": "tests/" } }, "config": { "sort-packages": true }, "extra": { "branch-alias": { "dev-master": "3.1-dev" }, "hyperf": { "config": "Hyperf\\Di\\ConfigProvider" } } } src/AbstractCallableDefinitionCollector.php000064400000003261151360550730015116 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 { // TODO: Support ReflectionUnionType. $typeName = match (true) { $type instanceof ReflectionNamedType => $type->getName(), $type instanceof ReflectionUnionType => $type->getTypes()[0]->getName(), default => 'mixed' }; return new ReflectionType($typeName, $allowsNull, [ 'defaultValueAvailable' => $hasDefault, 'defaultValue' => $defaultValue, 'name' => $name, ]); } } src/Annotation/AbstractAnnotation.php000064400000002701151360550730013761 0ustar00getProperties(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 collectClassConstant(string $className, ?string $target): void { AnnotationCollector::collectClassConstant($className, $target, 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); } } src/Annotation/AbstractMultipleAnnotation.php000064400000003512151360550730015476 0ustar00formatAnnotation($annotation)); } public function collectClassConstant(string $className, ?string $target): void { $annotation = AnnotationCollector::getClassConstantAnnotation($className, $target)[static::class] ?? null; AnnotationCollector::collectClassConstant($className, $target, static::class, $this->formatAnnotation($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.php000064400000007666151360550730014163 0ustar00 $metadata) { foreach ($metadata['_cc'] ?? [] as $constant => $_metadata) { if ($value = $_metadata[$annotation] ?? null) { $result[] = ['class' => $class, 'constant' => $constant, 'annotation' => $value]; } } } return $result; } public static function getClassesByAnnotation(string $annotation): array { $result = []; foreach (static::$container as $class => $metadata) { if (! isset($metadata['_c'][$annotation])) { continue; } $result[$class] = $metadata['_c'][$annotation]; } return $result; } 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 getClassAnnotations(string $class) { return static::get($class . '._c'); } public static function getClassConstantAnnotation(string $class, string $constant) { return static::get($class . '._cc.' . $constant); } 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.php000064400000001664151360550730014125 0ustar00getAttributes($class); } public function getClassAnnotation(ReflectionClass $class, $annotationName) { $annotations = $this->getClassAnnotations($class); foreach ($annotations as $annotation) { if ($annotation instanceof $annotationName) { return $annotation; } } return null; } public function getPropertyAnnotations(ReflectionProperty $property) { return $this->getAttributes($property); } 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) { return $this->getAttributes($method); } public function getMethodAnnotation(ReflectionMethod $method, $annotationName) { $annotations = $this->getMethodAnnotations($method); 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 (in_array($attribute->getName(), $this->ignoreAnnotations, true)) { continue; } if (! class_exists($attribute->getName())) { $className = $methodName = $propertyName = $classConstantName = ''; 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(); } elseif ($reflection instanceof ReflectionClassConstant) { $className = $reflection->getDeclaringClass()->getName(); $classConstantName = $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); } if ($classConstantName) { $message .= sprintf('::%s class constant', $classConstantName); } throw new NotFoundException($message); } $result[] = $attribute->newInstance(); } return $result; } } src/Annotation/Aspect.php000064400000003265151360550730011410 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 ?: 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.php000064400000005412151360550730013253 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.php000064400000002542151360550730012534 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.php000064400000000530151360550730011207 0ustar00value)) { $reflectionClass = ReflectionManager::reflectClass($className); $reflectionProperty = $reflectionClass->getProperty($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.php000064400000001243151360550730012537 0ustar00process(); } } src/Annotation/MultipleAnnotation.php000064400000004317151360550730014016 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): static { 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 collectClassConstant(string $className, ?string $target): void { throw new AnnotationException('MultipleAnnotation[' . $this->className() . '] does not support collectClassConstant()'); } 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.php000064400000001210151360550730015624 0ustar00cacheable; } 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 $config; } 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.php000064400000024744151360550730011567 0ustar00filesystem = new Filesystem(); } public function collect(AnnotationReader $reader, ReflectionClass $reflection): void { $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 foreach ($reader->getAttributes($reflection) as $classAnnotation) { if ($classAnnotation instanceof AnnotationInterface) { $classAnnotation->collectClass($className); } } // Parse properties annotations foreach ($reflection->getProperties() as $property) { foreach ($reader->getAttributes($property) as $propertyAnnotation) { if ($propertyAnnotation instanceof AnnotationInterface) { $propertyAnnotation->collectProperty($className, $property->getName()); } } } // Parse methods annotations foreach ($reflection->getMethods() as $method) { foreach ($reader->getAttributes($method) as $methodAnnotation) { if ($methodAnnotation instanceof AnnotationInterface) { $methodAnnotation->collectMethod($className, $method->getName()); } } } // Parse class constants annotations foreach ($reflection->getReflectionConstants() as $classConstant) { foreach ($reader->getAttributes($classConstant) as $constantAnnotation) { if ($constantAnnotation instanceof AnnotationInterface) { $constantAnnotation->collectClassConstant($className, $classConstant->getName()); } } } unset($reflection); } 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($this->scanConfig->getIgnoreAnnotations()); $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(); $aspectClasses = $proxyManager->getAspectClasses(); $this->putCache($this->path, serialize([$data, $proxies, $aspectClasses])); 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; } /** * @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 be 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 = Composer::getLoader()->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.php000064400000001050151360550730011467 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 (str_contains($rule, '::')) { [$ruleClass, $ruleMethod] = explode('::', $rule); } if (str_contains($target, '::')) { [$class, $method] = explode('::', $target); } if ($method == null) { if (! str_contains($ruleClass, '*')) { /* * 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 (! str_contains($rule, '*')) { /* * 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): void { // 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])) { $rewriteCollection->setLevel(RewriteCollection::CLASS_LEVEL); return; } if (isset($methodMapping[$rule])) { $rewriteCollection->add($methodMapping[$rule]); } } } } private static function parseClasses(array $collection, string $class, RewriteCollection $rewriteCollection): void { $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) { $rewriteCollection->setLevel(RewriteCollection::CLASS_LEVEL); return; } $rewriteCollection->add($method); } } } } } src/Aop/AspectManager.php000064400000001467151360550730011312 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($className); // User could modify or replace the node visitors 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 ClassLike) && $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.php000064400000002407151360550730012433 0ustar00{$name}(...$arguments); } throw new InvalidArgumentException('Invalid method for ' . __CLASS__); } public static function insert($value, $priority = 0) { 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.php000064400000002037151360550730010337 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.php000064400000004545151360550730012511 0ustar00pipe; 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; $arguments = $this->getArguments(); return $closure(...$arguments); } public function getAnnotationMetadata(): AnnotationMetadata { $metadata = AnnotationCollector::get($this->className); return new AnnotationMetadata($metadata['_c'] ?? [], $metadata['_m'][$this->methodName] ?? []); } public function getArguments(): array { $result = []; foreach ($this->arguments['order'] ?? [] as $order) { $result[] = $this->arguments['keys'][$order]; } // Variable arguments are always placed at the end. if (isset($this->arguments['variadic'], $order) && $order === $this->arguments['variadic']) { $variadic = array_pop($result); $result = array_merge($result, $variadic); } 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.php000064400000007471151360550730012727 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) { $callback($this, $currentClassName, $targetClassName, $propertyName, $annotation); } $handled[] = $propertyName; } } } return $handled; } } src/Aop/PropertyHandlerVisitor.php000064400000011701151360550730013272 0ustar00visitorMetadata->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) { // Cannot found __construct method in parent class or traits, do nothing. } } 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 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.php000064400000022665151360550730012100 0ustar00stmts as $class) { if ($class instanceof Node\Stmt\ClassLike) { $this->visitorMetadata->classLike = get_class($class); } } } return null; } public function enterNode(Node $node) { switch ($node) { case $node instanceof ClassMethod: $this->shouldRewrite = $this->shouldRewrite($node); 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 Trait_: // If the node is trait and php version >= 7.3, it can `use ProxyTrait` like class. case $node instanceof Enum_: // If the node is enum and php version >= 8.1, 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; case $node instanceof MagicConstDir: // Rewrite __DIR__ as the real directory path if ($file = Composer::getLoader()->findFile($this->visitorMetadata->className)) { return new String_(dirname(realpath($file))); } break; case $node instanceof MagicConstFile: // Rewrite __FILE__ to the real file path if ($file = Composer::getLoader()->findFile($this->visitorMetadata->className)) { return new String_(realpath($file)); } 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()), // ['order' => ['param1', 'param2'], 'keys' => compact('param1', 'param2'), 'variadic' => 'param2'] new Arg($this->getArguments($node->getParams())), // A closure that wrapped original method code. new Arg(new Closure([ 'params' => $this->filterModifier($node->getParams()), '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; } /** * @param Node\Param[] $params * @return Node\Param[] */ private function filterModifier(array $params): array { return array_map(function (Node\Param $param) { $tempParam = clone $param; $tempParam->flags &= ~Class_::VISIBILITY_MODIFIER_MASK & ~Class_::MODIFIER_READONLY; return $tempParam; }, $params); } /** * @param Node\Param[] $params */ private function getArguments(array $params): Array_ { if (empty($params)) { return new Array_([ new ArrayItem( key: new String_('keys'), value: new Array_([], ['kind' => Array_::KIND_SHORT]), ), ], ['kind' => Array_::KIND_SHORT]); } // ['param1', 'param2', ...] $methodParamsList = new Array_( array_map(fn (Node\Param $param) => new ArrayItem(new String_($param->var->name)), $params), ['kind' => Array_::KIND_SHORT] ); return new Array_([ new ArrayItem( key: new String_('order'), value: $methodParamsList, ), new ArrayItem( key: new String_('keys'), value: new FuncCall(new Name('compact'), [new Arg($methodParamsList)]) ), new ArrayItem( key: new String_('variadic'), value: $this->getVariadicParamName($params), )], ['kind' => Array_::KIND_SHORT]); } /** * @param Node\Param[] $params */ private function getVariadicParamName(array $params): String_ { foreach ($params as $param) { if ($param->variadic) { return new String_($param->var->name); } } return new String_(''); } private function unshiftMagicMethods(array $stmts = []): array { $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 { return match ($this->visitorMetadata->classLike) { Trait_::class => new MagicConstTrait(), default => new MagicConstClass(), }; } private function shouldRewrite(ClassMethod $node): bool { if ($this->visitorMetadata->classLike == Node\Stmt\Interface_::class || $node->isAbstract()) { return false; } $rewriteCollection = Aspect::parse($this->visitorMetadata->className); return $rewriteCollection->shouldRewrite($node->name->toString()); } } src/Aop/ProxyManager.php000064400000013761151360550730011214 0ustar00filesystem = new Filesystem(); $this->proxies = $this->generateProxyFiles($this->initProxiesByReflectionClassMap( $this->classMap )); } public function getProxies(): array { return $this->proxies; } public function getProxyDir(): string { return $this->proxyDir; } public function getAspectClasses(): array { $aspectClasses = []; $classesAspects = AspectCollector::get('classes', []); foreach ($classesAspects as $aspect => $rules) { foreach ($rules as $rule) { if (isset($this->proxies[$rule])) { $aspectClasses[$aspect][$rule] = $this->proxies[$rule]; } } } return $aspectClasses; } 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 (str_contains($rule, '::')) { [$rule] = explode('::', $rule); } if (! str_contains($rule, '*') && $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.php000064400000012426151360550730010722 0ustar00 [], 'order' => [], ]; $reflectParameters = ReflectionManager::reflectMethod($className, $method)->getParameters(); $leftArgCount = count($args); foreach ($reflectParameters as $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($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 = []; $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 (str_contains($rule, '*')) { $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.php000064400000003441151360550730014556 0ustar00has($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.php000064400000004373151360550730012234 0ustar00methods[] = $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.php000064400000001172151360550730011671 0ustar00addClassMap($config->getClassMap()); $scanner = new Scanner($config, $handler); $composerLoader->addClassMap( $scanner->scan($composerLoader->getClassMap(), $proxyFileDirPath) ); // Initialize Lazy Loader. This will prepend LazyLoader to the top of autoload queue. LazyLoader::bootstrap($configDir); } /** * @see DotenvManager::load() * @deprecated use DotenvManager instead */ protected static function loadDotenv(): void { $repository = RepositoryBuilder::createWithNoAdapters() ->addAdapter(Adapter\PutenvAdapter::class) ->immutable() ->make(); Dotenv::create($repository, [BASE_PATH])->load(); } } src/ClosureDefinitionCollector.php000064400000002545151360550730013353 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.php000064400000000767151360550730015200 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.php000064400000010514151360550730007774 0ustar00definitionResolver = new ResolverDispatcher($this); // Auto-register the container. $this->resolvedEntries = [ self::class => $this, PsrContainerInterface::class => $this, HyperfContainerInterface::class => $this, ]; } /** * @internal */ 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); } /** * @internal * @param mixed $entry */ public function set(string $name, $entry): void { $this->resolvedEntries[$name] = $entry; } /** * @internal */ public function unbind(string $name): void { if ($this->has($name)) { unset($this->resolvedEntries[$name]); } } /** * @internal * @param mixed $definition */ public function define(string $name, $definition): void { $this->setDefinition($name, $definition); } /** * @internal * @param mixed $id */ public function get($id) { // If the entry is already resolved we return it if (isset($this->resolvedEntries[$id]) || array_key_exists($id, $this->resolvedEntries)) { return $this->resolvedEntries[$id]; } return $this->resolvedEntries[$id] = $this->make($id); } /** * @internal * @param mixed $id */ public function has($id): bool { if (! is_string($id)) { throw new InvalidArgumentException(sprintf('The name parameter must be of type string, %s given', is_object($id) ? get_class($id) : gettype($id))); } if (array_key_exists($id, $this->resolvedEntries)) { return true; } $definition = $this->getDefinition($id); if ($definition === null) { return false; } if ($definition instanceof ObjectDefinition) { return $definition->isInstantiable(); } return true; } /** * @param array|callable|string $definition */ protected 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); } protected 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. */ protected function resolveDefinition(DefinitionInterface $definition, array $parameters = []) { return $this->definitionResolver->resolve($definition, $parameters); } } src/Definition/DefinitionInterface.php000064400000001262151360550730014053 0ustar00source = $this->normalizeSource($source); } /** * Returns the DI definition for the entry name. */ public function getDefinition(string $name): ?DefinitionInterface { return $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): static { $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. */ protected 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; } /** * Normalize the user definition source to a standard definition source. */ protected 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 */ protected function normalizeDefinition(string $identifier, $definition): ?DefinitionInterface { if ($definition instanceof PriorityDefinition) { $definition = $definition->getDefinition(); } 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; } protected 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.php000064400000002132151360550730014740 0ustar00name; } public function setName(string $name): self { $this->name = $name; return $this; } public function getFactory(): callable|string { return $this->factory; } 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.php000064400000003021151360550730013220 0ustar00methodName); } public function getName(): string { return ''; } public function setName(string $name) { // The name does not matter for method injections, so do nothing. } 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.php000064400000004647151360550730013373 0ustar00setClassName($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 { return $this->className ?? $this->name; } public function isClassExists(): bool { return $this->classExists; } public function isInstantiable(): bool { return $this->instantiable; } /** * @return null|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/PriorityDefinition.php000064400000002217151360550730013775 0ustar00 */ protected array $dependencies = []; public function __construct(string $class, int $priority = 0) { $this->dependencies[$class] = $priority; } public function getDependencies(): array { return $this->dependencies; } public function merge(PriorityDefinition $factory): static { foreach ($factory->getDependencies() as $class => $priority) { if (isset($this->dependencies[$class]) && $priority <= $this->dependencies[$class]) { continue; } $this->dependencies[$class] = $priority; } return $this; } public function getDefinition(): string { $dependencies = array_flip($this->dependencies); ksort($dependencies); return array_pop($dependencies); } } src/Definition/PropertyDefinition.php000064400000003256151360550730014004 0ustar00propertyName; } /** * 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.php000064400000001714151360550730014561 0ustar00targetEntryName); } /** * 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/SelfResolvingDefinitionInterface.php000064400000001166151360550730016561 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.php000064400000000521151360550730015460 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.php000064400000001326151360550730014377 0ustar00originalClassName, '\\')) { $originalClassName = '\\' . $this->originalClassName; } else { $originalClassName = $this->originalClassName; } $this->builder = $this->builder->extend($originalClassName); return $this; } } src/LazyLoader/FallbackLazyProxyBuilder.php000064400000000673151360550730015035 0ustar00originalClassName, '\\')) { $originalClassName = '\\' . $this->originalClassName; } else { $originalClassName = $this->originalClassName; } $this->builder = $this->builder->implement($originalClassName); return $this; } } src/LazyLoader/LazyLoader.php000064400000012462151360550730012172 0ustar00register(); } /** * 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) || str_starts_with($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); } /** * These conditions are really hard to proxy via inheritance. * 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.php000064400000002625151360550730013111 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.php000064400000005207151360550730014062 0ustar00originalClassName = $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.php000064400000002565151360550730012374 0ustar00collectors = 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] = $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')) { $collector::deserialize($metadata); $collectors[] = $collector; } } $this->collectors = $collectors; } } src/MetadataCollector.php000064400000003450151360550730011442 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.php000064400000001222151360550730014767 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'); return static::getAllClassesByFinder($finder); } public static function getAllClassesByFinder(Finder $finder): array { $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) { echo sprintf( "\033[31m%s\033[0m", '[ERROR] DI Reflection Manager collecting class reflections failed. ' . PHP_EOL . "File: {$file->getRealPath()}." . PHP_EOL . 'Exception: ' . $e->getMessage() ) . PHP_EOL; } } return $reflectionClasses; } public static function getContainer(): array { return self::$container; } } src/ReflectionType.php000064400000001251151360550730011004 0ustar00name; } public function allowsNull(): bool { return $this->allowsNull; } public function getMeta(string $key) { return $this->metadata[$key] ?? null; } } src/Resolver/DepthGuard.php000064400000003404151360550730011702 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.php000064400000005047151360550730013011 0ustar00getFactory(); if (! method_exists($callable, '__invoke')) { throw new NotCallableException(); } if (is_string($callable)) { $callable = $this->container->get($callable); } return $callable($this->container, $parameters); } 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.php000064400000007205151360550730012606 0ustar00parameterResolver = new ParameterResolver($this->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 * @return mixed value obtained from the definition * @throws InvalidDefinitionException * @throws DependencyException */ 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.php000064400000006206151360550730013320 0ustar00getParameters() : []; 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) { 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.php000064400000006053151360550730013466 0ustar00resolve($this->container); } $guard = DepthGuard::getInstance(); return $guard->call( $definition->getName(), fn () => $this->getDefinitionResolver($definition)->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(), fn () => $this->getDefinitionResolver($definition)->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 { return match (true) { $definition instanceof ObjectDefinition => $this->objectResolver ??= new ObjectResolver($this->container, $this), $definition instanceof FactoryDefinition => $this->factoryResolver ??= new FactoryResolver($this->container, $this), default => throw new RuntimeException('No definition resolver was configured for definition of type ' . get_class($definition)), }; } } src/Resolver/ResolverInterface.php000064400000002266151360550730013302 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])); if (proc_close($proc) !== 0) { echo $output; exit(-1); } return new Scanned(true); } } src/ScanHandler/ScanHandlerInterface.php000064400000000531151360550730014235 0ustar00scanned; } }