.gitattributes000064400000000054151360554210007440 0ustar00/tests export-ignore /.github export-ignore LICENSE000064400000002054151360554210005554 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.json000064400000002760151360554210007275 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.php000064400000003261151360554210015113 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.php000064400000002701151360554210013756 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.php000064400000003512151360554210015473 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.php000064400000007666151360554210014160 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.php000064400000001664151360554210014122 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.php000064400000003265151360554210011405 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.php000064400000005412151360554210013250 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.php000064400000002542151360554210012531 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.php000064400000000530151360554210011204 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.php000064400000001243151360554210012534 0ustar00process(); } } src/Annotation/MultipleAnnotation.php000064400000004317151360554210014013 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.php000064400000001210151360554210015621 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.php000064400000024744151360554210011564 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.php000064400000001050151360554210011464 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.php000064400000001467151360554210011307 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.php000064400000002407151360554210012430 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.php000064400000002037151360554210010334 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.php000064400000004545151360554210012506 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.php000064400000007471151360554210012724 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.php000064400000011701151360554210013267 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.php000064400000022665151360554210012075 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.php000064400000013761151360554210011211 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.php000064400000012426151360554210010717 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.php000064400000003441151360554210014553 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.php000064400000004373151360554210012231 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.php000064400000001172151360554210011666 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.php000064400000002545151360554210013350 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.php000064400000000767151360554210015175 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.php000064400000010514151360554210007771 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.php000064400000001262151360554210014050 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.php000064400000002132151360554210014735 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.php000064400000003021151360554210013215 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.php000064400000004647151360554210013370 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.php000064400000002217151360554210013772 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.php000064400000003256151360554210014001 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.php000064400000001714151360554210014556 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.php000064400000001166151360554210016556 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.php000064400000000521151360554210015455 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.php000064400000001326151360554210014374 0ustar00originalClassName, '\\')) { $originalClassName = '\\' . $this->originalClassName; } else { $originalClassName = $this->originalClassName; } $this->builder = $this->builder->extend($originalClassName); return $this; } } src/LazyLoader/FallbackLazyProxyBuilder.php000064400000000673151360554210015032 0ustar00originalClassName, '\\')) { $originalClassName = '\\' . $this->originalClassName; } else { $originalClassName = $this->originalClassName; } $this->builder = $this->builder->implement($originalClassName); return $this; } } src/LazyLoader/LazyLoader.php000064400000012462151360554210012167 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.php000064400000002625151360554210013106 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.php000064400000005207151360554210014057 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.php000064400000002565151360554210012371 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.php000064400000003450151360554210011437 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.php000064400000001222151360554210014764 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.php000064400000001251151360554210011001 0ustar00name; } public function allowsNull(): bool { return $this->allowsNull; } public function getMeta(string $key) { return $this->metadata[$key] ?? null; } } src/Resolver/DepthGuard.php000064400000003404151360554210011677 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.php000064400000005047151360554210013006 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.php000064400000007205151360554210012603 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.php000064400000006206151360554210013315 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.php000064400000006053151360554210013463 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.php000064400000002266151360554210013277 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.php000064400000000531151360554210014232 0ustar00scanned; } }