.gitattributes000064400000000054151360554140007442 0ustar00/tests export-ignore /.github export-ignore LICENSE000064400000002054151360554140005556 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.json000064400000002516151360554140007276 0ustar00{ "name": "hyperf/di", "description": "A DI for Hyperf.", "license": "MIT", "keywords": [ "php", "swoole", "hyperf", "di", "annotation" ], "homepage": "https://hyperf.io", "support": { "docs": "https://hyperf.wiki", "issues": "https://github.com/hyperf/hyperf/issues", "pull-request": "https://github.com/hyperf/hyperf/pulls", "source": "https://github.com/hyperf/hyperf" }, "require": { "php": ">=8.0", "doctrine/instantiator": "^1.0", "nikic/php-parser": "^4.1", "php-di/phpdoc-reader": "^2.2", "psr/container": "^1.0|^2.0", "symfony/finder": "^5.0|^6.0", "vlucas/phpdotenv": "^5.0" }, "suggest": { "hyperf/config": "Require this component for annotation scan progress to retrieve the scan path.", "ext-pcntl": "Required to scan annotations." }, "autoload": { "psr-4": { "Hyperf\\Di\\": "src/" } }, "autoload-dev": { "psr-4": { "HyperfTest\\Di\\": "tests/" } }, "config": { "sort-packages": true }, "extra": { "branch-alias": { "dev-master": "3.0-dev" }, "hyperf": { "config": "Hyperf\\Di\\ConfigProvider" } } } src/AbstractCallableDefinitionCollector.php000064400000003147151360554140015120 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.php000064400000004000151360554140013752 0ustar00formatParams($value); foreach ($formattedValue as $key => $val) { if (property_exists($this, $key)) { $this->{$key} = $val; } } } public function toArray(): array { $properties = ReflectionManager::reflectClass(static::class)->getProperties(ReflectionProperty::IS_PUBLIC); $result = []; foreach ($properties as $property) { $result[$property->getName()] = $property->getValue($this); } return $result; } public function collectClass(string $className): void { AnnotationCollector::collectClass($className, static::class, $this); } public function collectMethod(string $className, ?string $target): void { AnnotationCollector::collectMethod($className, $target, static::class, $this); } public function collectProperty(string $className, ?string $target): void { AnnotationCollector::collectProperty($className, $target, static::class, $this); } protected function formatParams($value): array { if (isset($value[0])) { $value = $value[0]; } if (! is_array($value)) { $value = ['value' => $value]; } return $value; } protected function bindMainProperty(string $key, array $value) { $formattedValue = $this->formatParams($value); if (isset($formattedValue['value'])) { $this->{$key} = $formattedValue['value']; } } } src/Annotation/AbstractMultipleAnnotation.php000064400000002770151360554140015502 0ustar00formatAnnotation($annotation)); } public function collectMethod(string $className, ?string $target): void { $annotation = AnnotationCollector::getClassMethodAnnotation($className, $target)[static::class] ?? null; AnnotationCollector::collectMethod($className, $target, static::class, $this->formatAnnotation($annotation)); } public function collectProperty(string $className, ?string $target): void { $annotation = AnnotationCollector::getClassPropertyAnnotation($className, $target)[static::class] ?? null; AnnotationCollector::collectProperty($className, $target, static::class, $this->formatAnnotation($annotation)); } protected function formatAnnotation(?MultipleAnnotation $annotation): MultipleAnnotation { if ($annotation instanceof MultipleAnnotation) { return $annotation->insert($this); } return new MultipleAnnotation($this); } } src/Annotation/AnnotationCollector.php000064400000005755151360554140014157 0ustar00 $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 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.php000064400000001411151360554140014112 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 = ''; if ($reflection instanceof ReflectionClass) { $className = $reflection->getName(); } elseif ($reflection instanceof ReflectionMethod) { $className = $reflection->getDeclaringClass()->getName(); $methodName = $reflection->getName(); } elseif ($reflection instanceof ReflectionProperty) { $className = $reflection->getDeclaringClass()->getName(); $propertyName = $reflection->getName(); } $message = sprintf( "No attribute class found for '%s' in %s", $attribute->getName(), $className ); if ($methodName) { $message .= sprintf('->%s() method', $methodName); } if ($propertyName) { $message .= sprintf('::$%s property', $propertyName); } throw new NotFoundException($message); } $result[] = $attribute->newInstance(); } return $result; } } src/Annotation/Aspect.php000064400000003227151360554140011405 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.php000064400000005411151360554140013251 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.php000064400000002541151360554140012532 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.php000064400000000527151360554140011214 0ustar00getProperty($target); if (method_exists($reflectionProperty, 'hasType') && $reflectionProperty->hasType()) { /* @phpstan-ignore-next-line */ $this->value = $reflectionProperty->getType()->getName(); } else { $this->value = PhpDocReaderManager::getInstance()->getPropertyClass($reflectionProperty); } if (empty($this->value)) { throw new AnnotationException("The @Inject value is invalid for {$className}->{$target}"); } if ($this->lazy) { $this->value = 'HyperfLazy\\' . $this->value; } AnnotationCollector::collectProperty($className, $target, static::class, $this); } catch (AnnotationException|DocReaderAnnotationException $exception) { if ($this->required) { throw new AnnotationException($exception->getMessage()); } $this->value = ''; } catch (\Throwable $exception) { throw new AnnotationException("The @Inject value is invalid for {$className}->{$target}. Because {$exception->getMessage()}"); } } } src/Annotation/InjectAspect.php000064400000001242151360554140012535 0ustar00process(); } } src/Annotation/MultipleAnnotation.php000064400000003755151360554140014022 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 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.php000064400000001207151360554140015631 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 []; } 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.php000064400000025776151360554140011574 0ustar00filesystem = new Filesystem(); } public function collect(AnnotationReader $reader, ReflectionClass $reflection) { $className = $reflection->getName(); if ($path = $this->scanConfig->getClassMap()[$className] ?? null) { if ($reflection->getFileName() !== $path) { // When the original class is dynamically replaced, the original class should not be collected. return; } } // Parse class annotations $classAnnotations = $reader->getClassAnnotations($reflection); if (! empty($classAnnotations)) { foreach ($classAnnotations as $classAnnotation) { if ($classAnnotation instanceof AnnotationInterface) { $classAnnotation->collectClass($className); } } } // Parse properties annotations $properties = $reflection->getProperties(); foreach ($properties as $property) { $propertyAnnotations = $reader->getPropertyAnnotations($property); if (! empty($propertyAnnotations)) { foreach ($propertyAnnotations as $propertyAnnotation) { if ($propertyAnnotation instanceof AnnotationInterface) { $propertyAnnotation->collectProperty($className, $property->getName()); } } } } // Parse methods annotations $methods = $reflection->getMethods(); foreach ($methods as $method) { $methodAnnotations = $reader->getMethodAnnotations($method); if (! empty($methodAnnotations)) { foreach ($methodAnnotations as $methodAnnotation) { if ($methodAnnotation instanceof AnnotationInterface) { $methodAnnotation->collectMethod($className, $method->getName()); } } } } unset($reflection, $classAnnotations, $properties, $methods); } public function scan(array $classMap = [], string $proxyDir = ''): array { $paths = $this->scanConfig->getPaths(); $collectors = $this->scanConfig->getCollectors(); if (! $paths) { return []; } $lastCacheModified = file_exists($this->path) ? $this->filesystem->lastModified($this->path) : 0; if ($lastCacheModified > 0 && $this->scanConfig->isCacheable()) { return $this->deserializeCachedScanData($collectors); } $scanned = $this->handler->scan(); if ($scanned->isScanned()) { return $this->deserializeCachedScanData($collectors); } $this->deserializeCachedScanData($collectors); $annotationReader = new AnnotationReader($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(); $this->putCache($this->path, serialize([$data, $proxies])); exit; } /** * Normalizes given directory names by removing directory not exist. * @throws DirectoryNotExistException */ public function normalizeDir(array $paths): array { $result = []; foreach ($paths as $path) { if (is_dir($path)) { $result[] = $path; } } if ($paths && ! $result) { throw new DirectoryNotExistException('The scanned directory does not exist'); } return $result; } protected function deserializeCachedScanData(array $collectors): array { if (! file_exists($this->path)) { return []; } [$data, $proxies] = unserialize(file_get_contents($this->path)); foreach ($data as $collector => $deserialized) { /** @var MetadataCollector $collector */ if (in_array($collector, $collectors)) { $collector::deserialize($deserialized); } } return $proxies; } protected function deserializeCachedCollectors(array $collectors): int { if (! file_exists($this->path)) { return 0; } $data = unserialize(file_get_contents($this->path)); foreach ($data as $collector => $deserialized) { /** @var MetadataCollector $collector */ if (in_array($collector, $collectors)) { $collector::deserialize($deserialized); } } return $this->filesystem->lastModified($this->path); } /** * @param ReflectionClass[] $reflections */ protected function clearRemovedClasses(array $collectors, array $reflections): void { $path = BASE_PATH . '/runtime/container/classes.cache'; $classes = array_keys($reflections); $data = []; if ($this->filesystem->exists($path)) { $data = unserialize($this->filesystem->get($path)); } $this->putCache($path, serialize($classes)); $removed = array_diff($data, $classes); foreach ($removed as $class) { /** @var MetadataCollector $collector */ foreach ($collectors as $collector) { $collector::clear($class); } } } protected function putCache(string $path, $data) { if (! $this->filesystem->isDirectory($dir = dirname($path))) { $this->filesystem->makeDirectory($dir, 0755, true); } $this->filesystem->put($path, $data); } /** * Load aspects to AspectCollector by configuration files and ConfigProvider. */ protected function loadAspects(int $lastCacheModified): void { $configDir = $this->scanConfig->getConfigDir(); if (! $configDir) { return; } $aspectsPath = $configDir . '/autoload/aspects.php'; $basePath = $configDir . '/config.php'; $aspects = file_exists($aspectsPath) ? include $aspectsPath : []; $baseConfig = file_exists($basePath) ? include $basePath : []; $providerConfig = []; if (class_exists(ProviderConfig::class)) { $providerConfig = ProviderConfig::load(); } if (! isset($aspects) || ! is_array($aspects)) { $aspects = []; } if (! isset($baseConfig['aspects']) || ! is_array($baseConfig['aspects'])) { $baseConfig['aspects'] = []; } if (! isset($providerConfig['aspects']) || ! is_array($providerConfig['aspects'])) { $providerConfig['aspects'] = []; } $aspects = array_merge($providerConfig['aspects'], $baseConfig['aspects'], $aspects); [$removed, $changed] = $this->getChangedAspects($aspects, $lastCacheModified); // When the aspect removed from config, it should 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.php000064400000001047151360554140011474 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.php000064400000001466151360554140011310 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 Class_ || $node instanceof Interface_) && $node->name) { $className = $node->name->toString(); break; } } } } return ($namespace && $className) ? $namespace . '\\' . $className : ''; } private function getCodeByClassName(string $className): string { $file = Composer::getLoader()->findFile($className); if (! $file) { return ''; } return file_get_contents($file); } } src/Aop/AstVisitorRegistry.php000064400000002333151360554140012430 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.php000064400000002033151360554140010332 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.php000064400000004423151360554140012503 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; if (count($this->arguments['keys']) > 1) { $arguments = $this->getArguments(); } else { $arguments = array_values($this->arguments['keys']); } return $closure(...$arguments); } public function getAnnotationMetadata(): AnnotationMetadata { $metadata = AnnotationCollector::get($this->className); return new AnnotationMetadata($metadata['_c'] ?? [], $metadata['_m'][$this->methodName] ?? []); } public function getArguments() { return value(function () { $result = []; foreach ($this->arguments['order'] ?? [] as $order) { $result[] = $this->arguments['keys'][$order]; } return $result; }); } public function getReflectMethod(): \ReflectionMethod { return ReflectionManager::reflectMethod( $this->className, $this->methodName ); } public function getInstance(): ?object { $ref = new \ReflectionFunction($this->originalMethod); return $ref->getClosureThis(); } } src/Aop/PropertyHandlerTrait.php000064400000007454151360554140012727 0ustar00__handle($className, $className, $properties); // Inject the properties of traits. // Because the property annotations of trait couldn't be collected by class. $handled = $this->__handleTrait($reflectionClass, $handled, $className); // Inject the properties of parent class. // It can be used to deal with parent classes whose subclasses have constructor function, but don't execute `parent::__construct()`. // For example: // class SubClass extend ParentClass // { // public function __construct() { // } // } $parentReflectionClass = $reflectionClass; while ($parentReflectionClass = $parentReflectionClass->getParentClass()) { $parentClassProperties = ReflectionManager::reflectPropertyNames($parentReflectionClass->getName()); $parentClassProperties = array_filter($parentClassProperties, static function ($property) use ($reflectionClass) { return $reflectionClass->hasProperty($property); }); $parentClassProperties = array_diff($parentClassProperties, $handled); $handled = array_merge( $handled, $this->__handle($className, $parentReflectionClass->getName(), $parentClassProperties) ); } } protected function __handleTrait(\ReflectionClass $reflectionClass, array $handled, string $className): array { foreach ($reflectionClass->getTraits() ?? [] as $reflectionTrait) { if (in_array($reflectionTrait->getName(), [ProxyTrait::class, PropertyHandlerTrait::class])) { continue; } $traitProperties = ReflectionManager::reflectPropertyNames($reflectionTrait->getName()); $traitProperties = array_diff($traitProperties, $handled); if (! $traitProperties) { continue; } $handled = array_merge( $handled, $this->__handle($className, $reflectionTrait->getName(), $traitProperties) ); $handled = $this->__handleTrait($reflectionTrait, $handled, $className); } return $handled; } protected function __handle(string $currentClassName, string $targetClassName, array $properties): array { $handled = []; foreach ($properties as $propertyName) { $propertyMetadata = AnnotationCollector::getClassPropertyAnnotation($targetClassName, $propertyName); if (! $propertyMetadata) { continue; } foreach ($propertyMetadata as $annotationName => $annotation) { if ($callbacks = PropertyHandlerManager::get($annotationName)) { foreach ($callbacks as $callback) { call($callback, [$this, $currentClassName, $targetClassName, $propertyName, $annotation]); } $handled[] = $propertyName; } } } return $handled; } } src/Aop/PropertyHandlerVisitor.php000064400000011653151360554140013277 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.php000064400000016623151360554140012074 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 Node\Stmt\Trait_: // If the node is trait and php version >= 7.3, it can `use ProxyTrait` like class. case $node instanceof Class_ && ! $node->isAnonymous(): // Add use proxy traits. $stmts = $node->stmts; if ($stmt = $this->buildProxyCallTraitUseStatement()) { array_unshift($stmts, $stmt); } $node->stmts = $stmts; unset($stmts); return $node; case $node instanceof MagicConstFunction: // Rewrite __FUNCTION__ to $__function__ variable. if ($this->shouldRewrite) { return new Variable('__function__'); } break; case $node instanceof MagicConstMethod: // Rewrite __METHOD__ to $__method__ variable. if ($this->shouldRewrite) { return new Variable('__method__'); } break; } return null; } /** * Build `use ProxyTrait;`. */ private function buildProxyCallTraitUseStatement(): ?TraitUse { $traits = []; foreach ($this->proxyTraits as $proxyTrait) { if (! is_string($proxyTrait) || ! trait_exists($proxyTrait)) { continue; } // Add backslash prefix if the proxy trait does not start with backslash. $proxyTrait[0] !== '\\' && $proxyTrait = '\\' . $proxyTrait; $traits[] = new Name($proxyTrait); } if (empty($traits)) { return null; } return new TraitUse($traits); } /** * Rewrite a normal class method to a proxy call method, * include normal class method and static method. */ private function rewriteMethod(ClassMethod $node): ClassMethod { // Build the static proxy call method base on the original method. $shouldReturn = true; $returnType = $node->getReturnType(); if ($returnType instanceof Identifier && $returnType->name === 'void') { $shouldReturn = false; } $staticCall = new StaticCall(new Name('self'), '__proxyCall', [ // __CLASS__ new Arg($this->getMagicConst()), // __FUNCTION__ new Arg(new MagicConstFunction()), // self::getParamMap(__CLASS__, __FUNCTION__, func_get_args()) // self::getParamMap(__TRAIT__, __FUNCTION__, func_get_args()) new Arg(new StaticCall(new Name('self'), '__getParamsMap', [ new Arg($this->getMagicConst()), new Arg(new MagicConstFunction()), new Arg(new FuncCall(new Name('func_get_args'))), ])), // A closure that wrapped original method code. new Arg(new Closure([ 'params' => value(function () use ($node) { // Transfer the variadic variable to normal variable at closure argument. ...$params => $params $params = $node->getParams(); foreach ($params as $key => $param) { if ($param instanceof Node\Param && $param->variadic) { $newParam = clone $param; $newParam->variadic = false; $newParam->type = null; $params[$key] = $newParam; } } return $params; }), 'uses' => [ new Variable('__function__'), new Variable('__method__'), ], 'stmts' => $node->stmts, ])), ]); $stmts = $this->unshiftMagicMethods([]); if ($shouldReturn) { $stmts[] = new Return_($staticCall); } else { $stmts[] = new Expression($staticCall); } $node->stmts = $stmts; return $node; } private function unshiftMagicMethods(array $stmts = []) { $magicConstFunction = new Expression(new Assign(new Variable('__function__'), new MagicConstFunction())); $magicConstMethod = new Expression(new Assign(new Variable('__method__'), new MagicConstMethod())); array_unshift($stmts, $magicConstFunction, $magicConstMethod); return $stmts; } private function getMagicConst(): Node\Scalar\MagicConst { 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) { return false; } $rewriteCollection = Aspect::parse($this->visitorMetadata->className); return $rewriteCollection->shouldRewrite($node->name->toString()); } } src/Aop/ProxyManager.php000064400000013055151360554140011207 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; } 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.php000064400000012425151360554140010720 0ustar00 [], 'order' => [], ]; $reflectMethod = ReflectionManager::reflectMethod($className, $method); $reflectParameters = $reflectMethod->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($annotationAspects, $aspects, $queue); } if (empty(AspectManager::get($className, $methodName))) { return $proceedingJoinPoint->processOriginalMethod(); } return static::makePipeline()->via('process') ->through(AspectManager::get($className, $methodName)) ->send($proceedingJoinPoint) ->then(function (ProceedingJoinPoint $proceedingJoinPoint) { return $proceedingJoinPoint->processOriginalMethod(); }); } protected static function makePipeline(): Pipeline { $container = ApplicationContext::getContainer(); if (method_exists($container, 'make')) { $pipeline = $container->make(Pipeline::class); } else { $pipeline = new Pipeline($container); } return $pipeline; } protected static function getClassesAspects(string $className, string $method): array { $aspects = AspectCollector::get('classes', []); $matchedAspect = []; foreach ($aspects as $aspect => $rules) { foreach ($rules as $rule) { if (Aspect::isMatch($className, $method, $rule)) { $matchedAspect[] = $aspect; break; } } } // The matched aspects maybe have duplicate aspect, should unique it when use it. return $matchedAspect; } protected static function getAnnotationAspects(string $className, string $method): array { $matchedAspect = []; $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.php000064400000003516151360554140014560 0ustar00setAccessible(true); $container = ApplicationContext::getContainer(); if ($container->has($annotation->value)) { $reflectionProperty->setValue($object, $container->get($annotation->value)); } elseif ($annotation->required) { throw new NotFoundException("No entry or class found for '{$annotation->value}'"); } } catch (\Throwable $throwable) { if ($annotation->required) { throw $throwable; } } } }); static::$registered = true; } } src/Aop/RewriteCollection.php000064400000004372151360554140012232 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.php000064400000001171151360554140011667 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); } protected static function loadDotenv(): void { $repository = RepositoryBuilder::createWithNoAdapters() ->addAdapter(Adapter\PutenvAdapter::class) ->immutable() ->make(); Dotenv::create($repository, [BASE_PATH])->load(); } } src/ClosureDefinitionCollector.php000064400000002502151360554140013343 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.php000064400000000752151360554140015171 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.php000064400000013516151360554140010000 0ustar00definitionResolver = new ResolverDispatcher($this); // Auto-register the container. $this->resolvedEntries = [ self::class => $this, PsrContainerInterface::class => $this, HyperfContainerInterface::class => $this, ]; } /** * Build an entry of the container by its name. * This method behave like get() except resolves the entry again every time. * For example if the entry is a class then a new instance will be created each time. * This method makes the container behave like a factory. * * @param string $name entry name or a class name * @param array $parameters Optional parameters to use to build the entry. Use this to force specific parameters * to specific values. Parameters not defined in this array will be resolved using * the container. * @throws NotFoundException no entry found for the given name * @throws InvalidArgumentException the name parameter must be of type string */ public function make(string $name, array $parameters = []) { $definition = $this->getDefinition($name); if (! $definition) { throw new NotFoundException("No entry or class found for '{$name}'"); } return $this->resolveDefinition($definition, $parameters); } /** * Bind an arbitrary resolved entry to an identifier. * Useful for testing 'get'. * @param mixed $entry */ public function set(string $name, $entry): void { $this->resolvedEntries[$name] = $entry; } /** * Unbind an arbitrary resolved entry. */ public function unbind(string $name): void { if ($this->has($name)) { unset($this->resolvedEntries[$name]); } } /** * Bind an arbitrary definition to an identifier. * Useful for testing 'make'. * * @param array|callable|string $definition */ public function define(string $name, $definition): void { $this->setDefinition($name, $definition); } /** * Finds an entry of the container by its identifier and returns it. * * @param string $id identifier of the entry to look for */ 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); } /** * Returns true if the container can return an entry for the given identifier. * Returns false otherwise. * `has($name)` returning true does not mean that `get($name)` will not throw an exception. * It does however mean that `get($name)` will not throw a `NotFoundExceptionInterface`. * * @param mixed|string $id identifier of the entry to look for */ 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; } /** * @deprecated */ public function getDefinitionSource(): Definition\DefinitionSourceInterface { return $this->definitionSource; } /** * @param array|callable|string $definition */ private function setDefinition(string $name, $definition): void { // Clear existing entry if it exists if (array_key_exists($name, $this->resolvedEntries)) { unset($this->resolvedEntries[$name]); } $this->fetchedDefinitions = []; // Completely clear this local cache $this->definitionSource->addDefinition($name, $definition); } private function getDefinition(string $name): ?DefinitionInterface { // Local cache that avoids fetching the same definition twice if (! array_key_exists($name, $this->fetchedDefinitions)) { $this->fetchedDefinitions[$name] = $this->definitionSource->getDefinition($name); } return $this->fetchedDefinitions[$name]; } /** * Resolves a definition. */ private function resolveDefinition(DefinitionInterface $definition, array $parameters = []) { return $this->definitionResolver->resolve($definition, $parameters); } } src/Definition/DefinitionInterface.php000064400000001215151360554140014050 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. */ private function getParametersDefinition(ReflectionFunctionAbstract $constructor): array { $parameters = []; foreach ($constructor->getParameters() as $index => $parameter) { // Skip optional parameters. if ($parameter->isOptional()) { continue; } $parameterType = $parameter->getType(); if ($parameterType instanceof \ReflectionNamedType && ! $parameterType->isBuiltin()) { $parameters[$index] = new Reference($parameterType->getName()); } } return $parameters; } /** * Normalize the user definition source to a standard definition source. */ private function normalizeSource(array $source): array { $definitions = []; foreach ($source as $identifier => $definition) { $normalizedDefinition = $this->normalizeDefinition($identifier, $definition); if (! is_null($normalizedDefinition)) { $definitions[$identifier] = $normalizedDefinition; } } return $definitions; } /** * @param array|callable|string $definition */ private function normalizeDefinition(string $identifier, $definition): ?DefinitionInterface { if (is_string($definition) && class_exists($definition)) { if (method_exists($definition, '__invoke')) { return new FactoryDefinition($identifier, $definition, []); } return $this->autowire($identifier, new ObjectDefinition($identifier, $definition)); } if (is_callable($definition)) { return new FactoryDefinition($identifier, $definition, []); } return null; } private function autowire(string $name, ObjectDefinition $definition = null): ?ObjectDefinition { $className = $definition ? $definition->getClassName() : $name; if (! class_exists($className) && ! interface_exists($className)) { return $definition; } $definition = $definition ?: new ObjectDefinition($name); /** * Constructor. */ $class = ReflectionManager::reflectClass($className); $constructor = $class->getConstructor(); if ($constructor && $constructor->isPublic()) { $constructorInjection = new MethodInjection('__construct', $this->getParametersDefinition($constructor)); $definition->completeConstructorInjection($constructorInjection); } return $definition; } } src/Definition/DefinitionSourceFactory.php000064400000002131151360554140014736 0ustar00name; } public function setName(string $name): self { $this->name = $name; return $this; } /** * @return callable|string */ public function getFactory() { 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.php000064400000003020151360554140013216 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.php000064400000004672151360554140013370 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|\Hyperf\Di\Definition\MethodInjection */ public function getConstructorInjection() { return $this->constructorInjection; } public function setConstructorInjection(MethodInjection $injection): self { $this->constructorInjection = $injection; return $this; } public function completeConstructorInjection(MethodInjection $injection): void { if ($this->constructorInjection !== null) { // Merge $this->constructorInjection->merge($injection); } else { // Set $this->constructorInjection = $injection; } } private function updateStatusCache(): void { $className = $this->getClassName(); $this->classExists = class_exists($className) || interface_exists($className); if (! $this->classExists) { $this->instantiable = false; return; } $this->instantiable = ReflectionManager::reflectClass($className)->isInstantiable(); } } src/Definition/PropertyDefinition.php000064400000003255151360554140014002 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.php000064400000001713151360554140014557 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.php000064400000001165151360554140016557 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.php000064400000000520151360554140015456 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.php000064400000001325151360554140014375 0ustar00originalClassName, '\\')) { $originalClassName = '\\' . $this->originalClassName; } else { $originalClassName = $this->originalClassName; } $this->builder = $this->builder->extend($originalClassName); return $this; } } src/LazyLoader/FallbackLazyProxyBuilder.php000064400000000672151360554140015033 0ustar00originalClassName, '\\')) { $originalClassName = '\\' . $this->originalClassName; } else { $originalClassName = $this->originalClassName; } $this->builder = $this->builder->implement($originalClassName); return $this; } } src/LazyLoader/LazyLoader.php000064400000012465151360554140012174 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.php000064400000002622151360554140013105 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.php000064400000005206151360554140014060 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.php000064400000002610151360554140012362 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] = call([$collector, 'serialize']); } } return json_encode($metadata); } public function unserialize($serialized): void { $metadatas = json_decode($serialized, true) ?? []; $collectors = []; foreach ($metadatas as $collector => $metadata) { if (method_exists($collector, 'deserialize')) { call([$collector, 'deserialize'], [$metadata]); $collectors[] = $collector; } } $this->collectors = $collectors; } } src/MetadataCollector.php000064400000003442151360554140011442 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.php000064400000001221151360554140014765 0ustar00getMethod($method); } return static::$container['method'][$key]; } public static function reflectProperty(string $className, string $property): ReflectionProperty { $key = $className . '::' . $property; if (! isset(static::$container['property'][$key])) { if (! class_exists($className)) { throw new InvalidArgumentException("Class {$className} not exist"); } static::$container['property'][$key] = static::reflectClass($className)->getProperty($property); } return static::$container['property'][$key]; } public static function reflectPropertyNames(string $className) { $key = $className; if (! isset(static::$container['property_names'][$key])) { if (! class_exists($className) && ! interface_exists($className) && ! trait_exists($className)) { throw new InvalidArgumentException("Class {$className} not exist"); } static::$container['property_names'][$key] = value(function () use ($className) { $properties = static::reflectClass($className)->getProperties(); $result = []; foreach ($properties as $property) { $result[] = $property->getName(); } return $result; }); } return static::$container['property_names'][$key]; } public static function clear(?string $key = null): void { if ($key === null) { static::$container = []; } } public static function getPropertyDefaultValue(ReflectionProperty $property) { return method_exists($property, 'getDefaultValue') ? $property->getDefaultValue() : $property->getDeclaringClass()->getDefaultProperties()[$property->getName()] ?? null; } public static function getAllClasses(array $paths): array { $finder = new Finder(); $finder->files()->in($paths)->name('*.php'); $parser = new Ast(); $reflectionClasses = []; foreach ($finder as $file) { try { $stmts = $parser->parse($file->getContents()); if (! $className = $parser->parseClassByStmts($stmts)) { continue; } $reflectionClasses[$className] = static::reflectClass($className); } catch (\Throwable) { } } return $reflectionClasses; } public static function getContainer(): array { return self::$container; } } src/ReflectionType.php000064400000001250151360554140011002 0ustar00name; } public function allowsNull(): bool { return $this->allowsNull; } public function getMeta(string $key) { return $this->metadata[$key] ?? null; } } src/Resolver/DepthGuard.php000064400000003403151360554140011700 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.php000064400000005221151360554140013002 0ustar00getFactory(); if (! method_exists($callable, '__invoke')) { throw new NotCallableException(); } if (is_string($callable)) { $callable = $this->container->get($callable); $object = $callable($this->container); } else { $object = call($callable, [$this->container]); } return $object; } catch (NotCallableException $e) { // Custom error message to help debugging if (is_string($callable) && class_exists($callable) && method_exists($callable, '__invoke')) { throw new InvalidDefinitionException(sprintf('Entry "%s" cannot be resolved: factory %s. Invokable classes cannot be automatically resolved if autowiring is disabled on the container, you need to enable autowiring or define the entry manually.', $definition->getName(), $e->getMessage())); } throw new InvalidDefinitionException(sprintf('Entry "%s" cannot be resolved: factory %s', $definition->getName(), $e->getMessage())); } } /** * Check if a definition can be resolved. * * @param DefinitionInterface $definition object that defines how the value should be obtained * @param array $parameters optional parameters to use to build the entry */ public function isResolvable(DefinitionInterface $definition, array $parameters = []): bool { return true; } } src/Resolver/ObjectResolver.php000064400000007204151360554140012604 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 * @throws InvalidDefinitionException * @throws DependencyException * @return mixed value obtained from the definition */ public function resolve(DefinitionInterface $definition, array $parameters = []) { if (! $definition instanceof ObjectDefinition) { throw InvalidDefinitionException::create( $definition, sprintf('Entry "%s" cannot be resolved: the class is not instanceof ObjectDefinition', $definition->getName()) ); } return $this->createInstance($definition, $parameters); } /** * Check if a definition can be resolved. * * @param ObjectDefinition $definition object that defines how the value should be obtained * @param array $parameters optional parameters to use to build the entry */ public function isResolvable(DefinitionInterface $definition, array $parameters = []): bool { return $definition->isInstantiable(); } private function createInstance(ObjectDefinition $definition, array $parameters) { // Check that the class is instantiable if (! $definition->isInstantiable()) { // Check that the class exists if (! $definition->isClassExists()) { throw InvalidDefinitionException::create($definition, sprintf('Entry "%s" cannot be resolved: the class doesn\'t exist', $definition->getName())); } throw InvalidDefinitionException::create($definition, sprintf('Entry "%s" cannot be resolved: the class is not instantiable', $definition->getName())); } $classReflection = null; try { $className = $definition->getClassName(); $classReflection = ReflectionManager::reflectClass($className); $constructorInjection = $definition->getConstructorInjection(); $args = $this->parameterResolver->resolveParameters($constructorInjection, $classReflection->getConstructor(), $parameters); $object = new $className(...$args); } catch (NotFoundExceptionInterface $e) { throw new DependencyException(sprintf('Error while injecting dependencies into %s: %s', $classReflection ? $classReflection->getName() : '', $e->getMessage()), 0, $e); } catch (InvalidDefinitionException $e) { throw InvalidDefinitionException::create($definition, sprintf('Entry "%s" cannot be resolved: %s', $definition->getName(), $e->getMessage())); } return $object; } } src/Resolver/ParameterResolver.php000064400000006153151360554140013320 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.php000064400000006052151360554140013464 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.php000064400000002265151360554140013300 0ustar00bin = $bin; $this->stub = $stub; } public function scan(): Scanned { if (env(static::SCAN_PROC_WORKER)) { return new Scanned(false); } $proc = proc_open( [$this->bin, $this->stub], [0 => STDIN, 1 => ['pipe', 'w'], 2 => ['redirect', 1]], $pipes, null, [static::SCAN_PROC_WORKER => '(true)'] ); $output = ''; do { $output .= fread($pipes[1], 8192); } while (! feof($pipes[1])); proc_close($proc); return new Scanned(true); } } src/ScanHandler/ScanHandlerInterface.php000064400000000530151360554140014233 0ustar00scanned; } }