.gitattributes000064400000000054151360552510007441 0ustar00/tests export-ignore /.github export-ignore LICENSE000064400000002054151360552510005555 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.json000064400000002473151360552510007277 0ustar00{ "name": "hyperf/di", "description": "A DI for Hyperf.", "license": "MIT", "keywords": [ "php", "swoole", "hyperf", "di", "annotation" ], "homepage": "https://hyperf.io", "support": { "docs": "https://hyperf.wiki", "issues": "https://github.com/hyperf/hyperf/issues", "pull-request": "https://github.com/hyperf/hyperf/pulls", "source": "https://github.com/hyperf/hyperf" }, "require": { "php": ">=7.3", "doctrine/annotations": "^1.6", "doctrine/instantiator": "^1.0", "nikic/php-parser": "^4.1", "php-di/phpdoc-reader": "^2.2", "psr/container": "^1.0|^2.0", "symfony/finder": "^5.0", "vlucas/phpdotenv": "^5.0" }, "suggest": { "hyperf/config": "Require this component for annotation scan progress to retrieve the scan path." }, "autoload": { "psr-4": { "Hyperf\\Di\\": "src/" } }, "autoload-dev": { "psr-4": { "HyperfTest\\Di\\": "tests/" } }, "config": { "sort-packages": true }, "extra": { "branch-alias": { "dev-master": "2.1-dev" }, "hyperf": { "config": "Hyperf\\Di\\ConfigProvider" } } } src/AbstractCallableDefinitionCollector.php000064400000002550151360552510015114 0ustar00 $parameters */ protected function getDefinitionsFromParameters(array $parameters): array { $definitions = []; foreach ($parameters as $parameter) { $definitions[] = $this->createType( $parameter->getName(), $parameter->getType(), $parameter->allowsNull(), $parameter->isDefaultValueAvailable(), $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null ); } return $definitions; } /** * @param mixed $defaultValue */ protected function createType(string $name, ?\ReflectionType $type, bool $allowsNull, bool $hasDefault = false, $defaultValue = null): ReflectionType { return new ReflectionType($type ? $type->getName() : 'mixed', $allowsNull, [ 'defaultValueAvailable' => $hasDefault, 'defaultValue' => $defaultValue, 'name' => $name, ]); } } src/Annotation/AbstractAnnotation.php000064400000004007151360552510013760 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/AnnotationCollector.php000064400000006600151360552510014144 0ustar00 $metadata) { if (! isset($metadata['_c'][$annotation])) { continue; } $result[$class] = $metadata['_c'][$annotation]; } return $result; } /** * @deprecated v3.0 */ public static function getMethodByAnnotation(string $annotation): array { return static::getMethodsByAnnotation($annotation); } public static function getMethodsByAnnotation(string $annotation): array { $result = []; foreach (static::$container as $class => $metadata) { foreach ($metadata['_m'] ?? [] as $method => $_metadata) { if ($value = $_metadata[$annotation] ?? null) { $result[] = ['class' => $class, 'method' => $method, 'annotation' => $value]; } } } return $result; } public static function getPropertiesByAnnotation(string $annotation): array { $properties = []; foreach (static::$container as $class => $metadata) { foreach ($metadata['_p'] ?? [] as $property => $_metadata) { if ($value = $_metadata[$annotation] ?? null) { $properties[] = ['class' => $class, 'property' => $property, 'annotation' => $value]; } } } return $properties; } public static function getClassAnnotation(string $class, string $annotation) { return static::get($class . '._c.' . $annotation); } public static function getClassMethodAnnotation(string $class, string $method) { return static::get($class . '._m.' . $method); } public static function getClassPropertyAnnotation(string $class, string $property) { return static::get($class . '._p.' . $property); } public static function getContainer(): array { return static::$container; } } src/Annotation/AnnotationInterface.php000064400000001414151360552510014114 0ustar00 'Doctrine\Common\Annotations\Annotation\IgnoreAnnotation', ]; /** * A list with annotations that are not causing exceptions when not resolved to an annotation class. * * The names are case sensitive. * * @var array */ private static $globalIgnoredNames = [ // Annotation tags 'Annotation' => true, 'Attribute' => true, 'Attributes' => true, /* Can we enable this? 'Enum' => true, */ 'Required' => true, 'Target' => true, // Widely used tags (but not existent in phpdoc) 'fix' => true, 'fixme' => true, 'override' => true, // PHPDocumentor 1 tags 'abstract' => true, 'access' => true, 'code' => true, 'deprec' => true, 'endcode' => true, 'exception' => true, 'final' => true, 'ingroup' => true, 'inheritdoc' => true, 'inheritDoc' => true, 'magic' => true, 'name' => true, 'toc' => true, 'tutorial' => true, 'private' => true, 'static' => true, 'staticvar' => true, 'staticVar' => true, 'throw' => true, // PHPDocumentor 2 tags. 'api' => true, 'author' => true, 'category' => true, 'copyright' => true, 'deprecated' => true, 'example' => true, 'filesource' => true, 'global' => true, 'ignore' => true, /* Can we enable this? 'index' => true, */ 'internal' => true, 'license' => true, 'link' => true, 'method' => true, 'package' => true, 'param' => true, 'property' => true, 'property-read' => true, 'property-write' => true, 'return' => true, 'see' => true, 'since' => true, 'source' => true, 'subpackage' => true, 'throws' => true, 'todo' => true, 'TODO' => true, 'usedby' => true, 'uses' => true, 'var' => true, 'version' => true, // PHPUnit tags 'codeCoverageIgnore' => true, 'codeCoverageIgnoreStart' => true, 'codeCoverageIgnoreEnd' => true, // PHPCheckStyle 'SuppressWarnings' => true, // PHPStorm 'noinspection' => true, // PEAR 'package_version' => true, // PlantUML 'startuml' => true, 'enduml' => true, // Symfony 3.3 Cache Adapter 'experimental' => true, // Slevomat Coding Standard 'phpcsSuppress' => true, // PHP CodeSniffer 'codingStandardsIgnoreStart' => true, 'codingStandardsIgnoreEnd' => true, // PHPStan 'template' => true, 'implements' => true, 'extends' => true, 'use' => true, ]; /** * A list with annotations that are not causing exceptions when not resolved to an annotation class. * * The names are case sensitive. * * @var array */ private static $globalIgnoredNamespaces = []; /** * Annotations parser. * * @var \Doctrine\Common\Annotations\DocParser */ private $parser; /** * Annotations parser used to collect parsing metadata. * * @var \Doctrine\Common\Annotations\DocParser */ private $preParser; /** * PHP parser used to collect imports. * * @var \Doctrine\Common\Annotations\PhpParser */ private $phpParser; /** * In-memory cache mechanism to store imported annotations per class. * * @var array */ private $imports = []; /** * In-memory cache mechanism to store ignored annotations per class. * * @var array */ private $ignoredAnnotationNames = []; /** * Constructor. * * Initializes a new AnnotationReader. * * @param DocParser $parser * * @throws AnnotationException */ public function __construct(DocParser $parser = null) { if (extension_loaded('Zend Optimizer+') && (ini_get('zend_optimizerplus.save_comments') === '0' || ini_get('opcache.save_comments') === '0')) { throw AnnotationException::optimizerPlusSaveComments(); } if (extension_loaded('Zend OPcache') && ini_get('opcache.save_comments') == 0) { throw AnnotationException::optimizerPlusSaveComments(); } // Make sure that the IgnoreAnnotation annotation is loaded class_exists(IgnoreAnnotation::class); $this->parser = $parser ?: new DocParser(); $this->preParser = new DocParser(); $this->preParser->setImports(self::$globalImports); $this->preParser->setIgnoreNotImportedAnnotations(true); $this->preParser->setIgnoredAnnotationNames(self::$globalIgnoredNames); $this->phpParser = new PhpParser(); } public static function addGlobalImports(string $alias, string $annotation) { self::$globalImports[strtolower($alias)] = $annotation; } /** * Add a new annotation to the globally ignored annotation names with regard to exception handling. * * @param string $name */ public static function addGlobalIgnoredName($name) { self::$globalIgnoredNames[$name] = true; } /** * Add a new annotation to the globally ignored annotation namespaces with regard to exception handling. * * @param string $namespace */ public static function addGlobalIgnoredNamespace($namespace) { self::$globalIgnoredNamespaces[$namespace] = true; } public function getClassAnnotations(ReflectionClass $class) { if (\PHP_VERSION_ID >= 80000) { if ($attributes = $this->getAttributes($class)) { return $attributes; } } $this->parser->setTarget(Target::TARGET_CLASS); $this->parser->setImports($this->getClassImports($class)); $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName()); } public function getClassAnnotation(ReflectionClass $class, $annotationName) { $annotations = $this->getClassAnnotations($class); foreach ($annotations as $annotation) { if ($annotation instanceof $annotationName) { return $annotation; } } return null; } public function getAttributes(\Reflector $reflection): array { $result = []; if (! method_exists($reflection, 'getAttributes')) { return $result; } $attributes = $reflection->getAttributes(); foreach ($attributes as $attribute) { $result[] = $attribute->newInstance(); } return $result; } public function getPropertyAnnotations(ReflectionProperty $property) { if (\PHP_VERSION_ID >= 80000) { if ($attributes = $this->getAttributes($property)) { return $attributes; } } $class = $property->getDeclaringClass(); $context = 'property ' . $class->getName() . '::$' . $property->getName(); $this->parser->setTarget(Target::TARGET_PROPERTY); $this->parser->setImports($this->getPropertyImports($property)); $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); return $this->parser->parse($property->getDocComment(), $context); } public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) { $annotations = $this->getPropertyAnnotations($property); foreach ($annotations as $annotation) { if ($annotation instanceof $annotationName) { return $annotation; } } return null; } public function getMethodAnnotations(ReflectionMethod $method) { if (\PHP_VERSION_ID >= 80000) { if ($attributes = $this->getAttributes($method)) { return $attributes; } } $class = $method->getDeclaringClass(); $context = 'method ' . $class->getName() . '::' . $method->getName() . '()'; $this->parser->setTarget(Target::TARGET_METHOD); $this->parser->setImports($this->getMethodImports($method)); $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); return $this->parser->parse($method->getDocComment(), $context); } public function getMethodAnnotation(ReflectionMethod $method, $annotationName) { $annotations = $this->getMethodAnnotations($method); foreach ($annotations as $annotation) { if ($annotation instanceof $annotationName) { return $annotation; } } return null; } /** * Returns the ignored annotations for the given class. * * @return array */ private function getIgnoredAnnotationNames(ReflectionClass $class) { $name = $class->getName(); if (isset($this->ignoredAnnotationNames[$name])) { return $this->ignoredAnnotationNames[$name]; } $this->collectParsingMetadata($class); return $this->ignoredAnnotationNames[$name]; } /** * Retrieves imports. * * @return array */ private function getClassImports(ReflectionClass $class) { $name = $class->getName(); if (isset($this->imports[$name])) { return $this->imports[$name]; } $this->collectParsingMetadata($class); return $this->imports[$name]; } /** * Retrieves imports for methods. * * @return array */ private function getMethodImports(ReflectionMethod $method) { $class = $method->getDeclaringClass(); $classImports = $this->getClassImports($class); $traitImports = []; foreach ($class->getTraits() as $trait) { if ($trait->hasMethod($method->getName()) && $trait->getFileName() === $method->getFileName() ) { $traitImports = array_merge($traitImports, $this->phpParser->parseClass($trait)); } } return array_merge($classImports, $traitImports); } /** * Retrieves imports for properties. * * @return array */ private function getPropertyImports(ReflectionProperty $property) { $class = $property->getDeclaringClass(); $classImports = $this->getClassImports($class); $traitImports = []; foreach ($class->getTraits() as $trait) { if ($trait->hasProperty($property->getName())) { $traitImports = array_merge($traitImports, $this->phpParser->parseClass($trait)); } } return array_merge($classImports, $traitImports); } /** * Collects parsing metadata for a given class. */ private function collectParsingMetadata(ReflectionClass $class) { $ignoredAnnotationNames = self::$globalIgnoredNames; $annotations = $this->preParser->parse($class->getDocComment(), 'class ' . $class->name); foreach ($annotations as $annotation) { if ($annotation instanceof IgnoreAnnotation) { foreach ($annotation->names as $annot) { $ignoredAnnotationNames[$annot] = true; } } } $name = $class->getName(); $this->imports[$name] = array_merge( self::$globalImports, $this->phpParser->parseClass($class), ['__NAMESPACE__' => $class->getNamespaceName()] ); $this->ignoredAnnotationNames[$name] = $ignoredAnnotationNames; } } src/Annotation/Aspect.php000064400000005050151360552510011400 0ustar00collect($className); } protected function collect(string $className) { // Create the aspect instance without invoking their constructor. $reflectionClass = ReflectionManager::reflectClass($className); $properties = $reflectionClass->getProperties(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); } } // Classes $classes = $this->classes; $classes = $instanceClasses ? array_merge($classes, $instanceClasses) : $classes; // Annotations $annotations = $this->annotations; $annotations = $instanceAnnotations ? array_merge($annotations, $instanceAnnotations) : $annotations; // Priority $annotationPriority = $this->priority; $propertyPriority = $instancePriority ? $instancePriority : null; if (! is_null($annotationPriority) && ! is_null($propertyPriority) && $annotationPriority !== $propertyPriority) { throw new \InvalidArgumentException('Cannot define two difference priority of Aspect.'); } $priority = $annotationPriority ?? $propertyPriority; // Save the metadata to AspectCollector AspectCollector::setAround($className, $classes, $annotations, $priority); } } src/Annotation/AspectCollector.php000064400000005737151360552510013263 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/Debug.php000064400000000602151360552510011205 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.php000064400000001227151360552510012537 0ustar00process(); } } src/Annotation/RelationCollector.php000064400000001525151360552510013610 0ustar00cacheable = $cacheable; $this->configDir = $configDir; $this->paths = $paths; $this->dependencies = $dependencies; $this->ignoreAnnotations = $ignoreAnnotations; $this->globalImports = $globalImports; $this->collectors = $collectors; $this->classMap = $classMap; } public function isCacheable(): bool { return $this->cacheable; } public function getConfigDir(): string { return $this->configDir; } public function getPaths(): array { return $this->paths; } public function getCollectors(): array { return $this->collectors; } public function getIgnoreAnnotations(): array { return $this->ignoreAnnotations; } public function getGlobalImports(): array { return $this->globalImports; } public function getDependencies(): array { return $this->dependencies; } public function getClassMap(): array { return $this->classMap; } public static function instance(string $configDir): self { if (self::$instance) { return self::$instance; } $configDir = rtrim($configDir, '/'); [$config, $serverDependencies, $cacheable] = static::initConfigByFile($configDir); return self::$instance = new self( $cacheable, $configDir, $config['paths'] ?? [], $serverDependencies ?? [], $config['ignore_annotations'] ?? [], $config['global_imports'] ?? [], $config['collectors'] ?? [], $config['class_map'] ?? [] ); } private static function initConfigByFile(string $configDir): array { $config = []; $configFromProviders = []; $cacheable = false; if (class_exists(ProviderConfig::class)) { $configFromProviders = ProviderConfig::load(); } $serverDependencies = $configFromProviders['dependencies'] ?? []; if (file_exists($configDir . '/autoload/dependencies.php')) { $definitions = include $configDir . '/autoload/dependencies.php'; $serverDependencies = array_replace($serverDependencies, $definitions ?? []); } $config = static::allocateConfigValue($configFromProviders['annotations'] ?? [], $config); // Load the config/autoload/annotations.php and merge the config if (file_exists($configDir . '/autoload/annotations.php')) { $annotations = include $configDir . '/autoload/annotations.php'; $config = static::allocateConfigValue($annotations, $config); } // Load the config/config.php and merge the config if (file_exists($configDir . '/config.php')) { $configContent = include $configDir . '/config.php'; $appEnv = $configContent['app_env'] ?? 'dev'; $cacheable = value($configContent['scan_cacheable'] ?? $appEnv === 'prod'); if (isset($configContent['annotations'])) { $config = static::allocateConfigValue($configContent['annotations'], $config); } } return [$config, $serverDependencies, $cacheable]; } private static function allocateConfigValue(array $content, array $config): array { if (! isset($content['scan'])) { return []; } foreach ($content['scan'] as $key => $value) { if (! isset($config[$key])) { $config[$key] = []; } if (! is_array($value)) { $value = [$value]; } $config[$key] = array_merge($config[$key], $value); } return $config; } } src/Annotation/Scanner.php000064400000030351151360552510011554 0ustar00classloader = $classloader; $this->scanConfig = $scanConfig; $this->filesystem = new Filesystem(); foreach ($scanConfig->getIgnoreAnnotations() as $annotation) { AnnotationReader::addGlobalIgnoredName($annotation); } foreach ($scanConfig->getGlobalImports() as $alias => $annotation) { AnnotationReader::addGlobalImports($alias, $annotation); } } public function collect(AnnotationReader $reader, ReflectionClass $reflection) { $className = $reflection->getName(); if ($path = $this->scanConfig->getClassMap()[$className] ?? null) { if ($reflection->getFileName() !== $path) { // When the original class is dynamically replaced, the original class should not be collected. return; } } // Parse class annotations $classAnnotations = $reader->getClassAnnotations($reflection); if (! empty($classAnnotations)) { foreach ($classAnnotations as $classAnnotation) { if ($classAnnotation instanceof AnnotationInterface) { $classAnnotation->collectClass($className); } } } // Parse properties annotations $properties = $reflection->getProperties(); foreach ($properties as $property) { $propertyAnnotations = $reader->getPropertyAnnotations($property); if (! empty($propertyAnnotations)) { foreach ($propertyAnnotations as $propertyAnnotation) { if ($propertyAnnotation instanceof AnnotationInterface) { $propertyAnnotation->collectProperty($className, $property->getName()); } } } } // Parse methods annotations $methods = $reflection->getMethods(); foreach ($methods as $method) { $methodAnnotations = $reader->getMethodAnnotations($method); if (! empty($methodAnnotations)) { foreach ($methodAnnotations as $methodAnnotation) { if ($methodAnnotation instanceof AnnotationInterface) { $methodAnnotation->collectMethod($className, $method->getName()); } } } } unset($reflection, $classAnnotations, $properties, $methods); } public function scan(array $classMap = [], string $proxyDir = ''): array { $paths = $this->scanConfig->getPaths(); $collectors = $this->scanConfig->getCollectors(); if (! $paths) { return []; } $lastCacheModified = file_exists($this->path) ? $this->filesystem->lastModified($this->path) : 0; if ($lastCacheModified > 0 && $this->scanConfig->isCacheable()) { return $this->deserializeCachedScanData($collectors); } $pid = pcntl_fork(); if ($pid == -1) { throw new Exception('The process fork failed'); } if ($pid) { pcntl_wait($status); return $this->deserializeCachedScanData($collectors); } $this->deserializeCachedScanData($collectors); $annotationReader = new AnnotationReader(); $paths = $this->normalizeDir($paths); $classes = ReflectionManager::getAllClasses($paths); $this->clearRemovedClasses($collectors, $classes); $reflectionClassMap = []; foreach ($classes as $className => $reflectionClass) { $reflectionClassMap[$className] = $reflectionClass->getFileName(); if ($this->filesystem->lastModified($reflectionClass->getFileName()) >= $lastCacheModified) { /** @var MetadataCollector $collector */ foreach ($collectors as $collector) { $collector::clear($className); } $this->collect($annotationReader, $reflectionClass); } } $this->loadAspects($lastCacheModified); $data = []; /** @var MetadataCollector|string $collector */ foreach ($collectors as $collector) { $data[$collector] = $collector::serialize(); } // Get the class map of Composer loader $classMap = array_merge($reflectionClassMap, $classMap); $proxyManager = new ProxyManager($classMap, $proxyDir); $proxies = $proxyManager->getProxies(); $this->putCache($this->path, serialize([$data, $proxies])); exit; } /** * Normalizes given directory names by removing directory not exist. * @throws DirectoryNotExistException */ public function normalizeDir(array $paths): array { $result = []; foreach ($paths as $path) { if (is_dir($path)) { $result[] = $path; } } if ($paths && ! $result) { throw new DirectoryNotExistException('The scanned directory does not exist'); } return $result; } protected function deserializeCachedScanData(array $collectors): array { if (! file_exists($this->path)) { return []; } [$data, $proxies] = unserialize(file_get_contents($this->path)); foreach ($data as $collector => $deserialized) { /** @var MetadataCollector $collector */ if (in_array($collector, $collectors)) { $collector::deserialize($deserialized); } } return $proxies; } protected function deserializeCachedCollectors(array $collectors): int { if (! file_exists($this->path)) { return 0; } $data = unserialize(file_get_contents($this->path)); foreach ($data as $collector => $deserialized) { /** @var MetadataCollector $collector */ if (in_array($collector, $collectors)) { $collector::deserialize($deserialized); } } return $this->filesystem->lastModified($this->path); } /** * @param ReflectionClass[] $reflections */ protected function clearRemovedClasses(array $collectors, array $reflections): void { $path = BASE_PATH . '/runtime/container/classes.cache'; $classes = array_keys($reflections); $data = []; if ($this->filesystem->exists($path)) { $data = unserialize($this->filesystem->get($path)); } $this->putCache($path, serialize($classes)); $removed = array_diff($data, $classes); foreach ($removed as $class) { /** @var MetadataCollector $collector */ foreach ($collectors as $collector) { $collector::clear($class); } } } protected function putCache(string $path, $data) { if (! $this->filesystem->isDirectory($dir = dirname($path))) { $this->filesystem->makeDirectory($dir, 0755, true); } $this->filesystem->put($path, $data); } /** * Load aspects to AspectCollector by configuration files and ConfigProvider. */ protected function loadAspects(int $lastCacheModified): void { $configDir = $this->scanConfig->getConfigDir(); if (! $configDir) { return; } $aspectsPath = $configDir . '/autoload/aspects.php'; $basePath = $configDir . '/config.php'; $aspects = file_exists($aspectsPath) ? include $aspectsPath : []; $baseConfig = file_exists($basePath) ? include $basePath : []; $providerConfig = []; if (class_exists(ProviderConfig::class)) { $providerConfig = ProviderConfig::load(); } if (! isset($aspects) || ! is_array($aspects)) { $aspects = []; } if (! isset($baseConfig['aspects']) || ! is_array($baseConfig['aspects'])) { $baseConfig['aspects'] = []; } if (! isset($providerConfig['aspects']) || ! is_array($providerConfig['aspects'])) { $providerConfig['aspects'] = []; } $aspects = array_merge($providerConfig['aspects'], $baseConfig['aspects'], $aspects); [$removed, $changed] = $this->getChangedAspects($aspects, $lastCacheModified); // When the aspect removed from config, it should removed from AspectCollector. foreach ($removed as $aspect) { AspectCollector::clear($aspect); } foreach ($aspects ?? [] as $key => $value) { if (is_numeric($key)) { $aspect = $value; $priority = null; } else { $aspect = $key; $priority = (int) $value; } if (! in_array($aspect, $changed)) { continue; } // Create the aspect instance without invoking their constructor. $reflectionClass = ReflectionManager::reflectClass($aspect); $properties = $reflectionClass->getProperties(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); } } $classes = $instanceClasses ?: []; // Annotations $annotations = $instanceAnnotations ?: []; // Priority $priority = $priority ?: ($instancePriority ?? null); // Save the metadata to AspectCollector AspectCollector::setAround($aspect, $classes, $annotations, $priority); } } protected function getChangedAspects(array $aspects, int $lastCacheModified): array { $path = BASE_PATH . '/runtime/container/aspects.cache'; $classes = []; foreach ($aspects as $key => $value) { if (is_numeric($key)) { $classes[] = $value; } else { $classes[] = $key; } } $data = []; if ($this->filesystem->exists($path)) { $data = unserialize($this->filesystem->get($path)); } $this->putCache($path, serialize($classes)); $diff = array_diff($data, $classes); $changed = array_diff($classes, $data); $removed = []; foreach ($diff as $item) { $annotation = AnnotationCollector::getClassAnnotation($item, Aspect::class); if (is_null($annotation)) { $removed[] = $item; } } foreach ($classes as $class) { $file = $this->classloader->getComposerClassLoader()->findFile($class); if ($lastCacheModified <= $this->filesystem->lastModified($file)) { $changed[] = $class; } } return [ array_values(array_unique($removed)), array_values(array_unique($changed)), ]; } } src/Aop/AbstractAspect.php000064400000001152151360552510011470 0ustar00class = $class; $this->method = $method; } } src/Aop/AroundInterface.php000064400000000755151360552510011646 0ustar00 $collection) { if ($type === 'classes') { static::parseClasses($collection, $class, $rewriteCollection); } elseif ($type === 'annotations') { static::parseAnnotations($collection, $class, $rewriteCollection); } } return $rewriteCollection; } /** * @return array [isMatch, $matchedMethods] */ public static function isMatchClassRule(string $target, string $rule): array { /* * e.g. Foo/Bar * e.g. Foo/B* * e.g. F*o/Bar * e.g. Foo/Bar::method * e.g. Foo/Bar::met* */ $ruleMethod = null; $ruleClass = $rule; $method = null; $class = $target; if (strpos($rule, '::') !== false) { [$ruleClass, $ruleMethod] = explode('::', $rule); } if (strpos($target, '::') !== false) { [$class, $method] = explode('::', $target); } if ($method == null) { if (strpos($ruleClass, '*') === false) { /* * Match [rule] Foo/Bar::ruleMethod [target] Foo/Bar [return] true,ruleMethod * Match [rule] Foo/Bar [target] Foo/Bar [return] true,null * Match [rule] FooBar::rule*Method [target] Foo/Bar [return] true,rule*Method */ if ($ruleClass === $class) { return [true, $ruleMethod]; } return [false, null]; } /** * Match [rule] Foo*Bar::ruleMethod [target] Foo/Bar [return] true,ruleMethod * Match [rule] Foo*Bar [target] Foo/Bar [return] true,null. */ $preg = str_replace(['*', '\\'], ['.*', '\\\\'], $ruleClass); $pattern = "#^{$preg}$#"; if (preg_match($pattern, $class)) { return [true, $ruleMethod]; } return [false, null]; } if (strpos($rule, '*') === false) { /* * Match [rule] Foo/Bar::ruleMethod [target] Foo/Bar::ruleMethod [return] true,ruleMethod * Match [rule] Foo/Bar [target] Foo/Bar::ruleMethod [return] false,null */ if ($ruleClass === $class && ($ruleMethod === null || $ruleMethod === $method)) { return [true, $method]; } return [false, null]; } /* * Match [rule] Foo*Bar::ruleMethod [target] Foo/Bar::ruleMethod [return] true,ruleMethod * Match [rule] FooBar::rule*Method [target] Foo/Bar::ruleMethod [return] true,rule*Method */ if ($ruleMethod) { $preg = str_replace(['*', '\\'], ['.*', '\\\\'], $rule); $pattern = "#^{$preg}$#"; if (preg_match($pattern, $target)) { return [true, $method]; } } else { /** * Match [rule] Foo*Bar [target] Foo/Bar::ruleMethod [return] true,null. */ $preg = str_replace(['*', '\\'], ['.*', '\\\\'], $rule); $pattern = "#^{$preg}$#"; if (preg_match($pattern, $class)) { return [true, $method]; } } return [false, null]; } public static function isMatch(string $class, string $method, string $rule): bool { [$isMatch,] = self::isMatchClassRule($class . '::' . $method, $rule); return $isMatch; } private static function parseAnnotations(array $collection, string $class, RewriteCollection $rewriteCollection) { // Get the annotations of class and method. $annotations = AnnotationCollector::get($class); $classMapping = $annotations['_c'] ?? []; $methodMapping = value(function () use ($annotations) { $mapping = []; $methodAnnotations = $annotations['_m'] ?? []; foreach ($methodAnnotations as $method => $targetAnnotations) { $keys = array_keys($targetAnnotations); foreach ($keys as $key) { $mapping[$key][] = $method; } } return $mapping; }); $aspects = array_keys($collection); foreach ($aspects ?? [] as $aspect) { $rules = AspectCollector::getRule($aspect); foreach ($rules['annotations'] ?? [] as $rule) { // If exist class level annotation, then all methods should rewrite, so return an empty array directly. if (isset($classMapping[$rule])) { return $rewriteCollection->setLevel(RewriteCollection::CLASS_LEVEL); } if (isset($methodMapping[$rule])) { $rewriteCollection->add($methodMapping[$rule]); } } } return $rewriteCollection; } private static function parseClasses(array $collection, string $class, RewriteCollection $rewriteCollection) { $aspects = array_keys($collection); foreach ($aspects ?? [] as $aspect) { $rules = AspectCollector::getRule($aspect); foreach ($rules['classes'] ?? [] as $rule) { [$isMatch, $method] = static::isMatchClassRule($class, $rule); if ($isMatch) { if ($method === null) { return $rewriteCollection->setLevel(RewriteCollection::CLASS_LEVEL); } $rewriteCollection->add($method); } } } } } src/Aop/AspectManager.php000064400000001453151360552510011303 0ustar00astParser = $parserFactory->create(ParserFactory::ONLY_PHP7); $this->printer = new Standard(); } public function parse(string $code): ?array { return $this->astParser->parse($code); } public function proxy(string $className) { $code = $this->getCodeByClassName($className); $stmts = $this->astParser->parse($code); $traverser = new NodeTraverser(); $visitorMetadata = new VisitorMetadata(); $visitorMetadata->className = $className; // User could modify or replace the node vistors by Hyperf\Di\Aop\AstVisitorRegistry. $queue = clone AstVisitorRegistry::getQueue(); foreach ($queue as $string) { $visitor = new $string($visitorMetadata); $traverser->addVisitor($visitor); } $modifiedStmts = $traverser->traverse($stmts); return $this->printer->prettyPrintFile($modifiedStmts); } public function parseClassByStmts(array $stmts): string { $namespace = $className = ''; foreach ($stmts as $stmt) { if ($stmt instanceof Namespace_ && $stmt->name) { $namespace = $stmt->name->toString(); foreach ($stmt->stmts as $node) { if ($node instanceof Class_ && $node->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.php000064400000002365151360552510012434 0ustar00{$name}(...$arguments); } throw new \InvalidArgumentException('Invalid method for ' . __CLASS__); } public static function insert($value, $priority) { static::$values[] = $value; return static::getQueue()->insert($value, $priority); } public static function exists($value): bool { return in_array($value, static::$values); } public static function getQueue(): \SplPriorityQueue { if (! static::$queue instanceof \SplPriorityQueue) { static::$queue = new \SplPriorityQueue(); } return static::$queue; } } src/Aop/Pipeline.php000064400000002033151360552510010331 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.php000064400000005212151360552510012477 0ustar00originalMethod = $originalMethod; $this->className = $className; $this->methodName = $methodName; $this->arguments = $arguments; } /** * Delegate to the next aspect. */ public function process() { $closure = $this->pipe; if (! $closure instanceof Closure) { throw new Exception('The pipe is not instanceof \Closure'); } return $closure($this); } /** * Process the original method, this method should trigger by pipeline. */ public function processOriginalMethod() { $this->pipe = null; $closure = $this->originalMethod; if (count($this->arguments['keys']) > 1) { $arguments = $this->getArguments(); } else { $arguments = array_values($this->arguments['keys']); } return $closure(...$arguments); } public function getAnnotationMetadata(): AnnotationMetadata { $metadata = AnnotationCollector::get($this->className); return new AnnotationMetadata($metadata['_c'] ?? [], $metadata['_m'][$this->methodName] ?? []); } public function getArguments() { return value(function () { $result = []; foreach ($this->arguments['order'] ?? [] as $order) { $result[] = $this->arguments['keys'][$order]; } return $result; }); } public function getReflectMethod(): \ReflectionMethod { return ReflectionManager::reflectMethod( $this->className, $this->methodName ); } public function getInstance(): ?object { $ref = new \ReflectionFunction($this->originalMethod); return $ref->getClosureThis(); } } src/Aop/PropertyHandlerTrait.php000064400000007454151360552510012726 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.php000064400000012135151360552510013272 0ustar00visitorMetadata = $visitorMetadata; } public function enterNode(Node $node) { if ($node instanceof Node\Stmt\Class_) { if ($this->visitorMetadata->hasExtends === null) { if ($node->extends) { $this->visitorMetadata->hasExtends = true; } else { $this->visitorMetadata->hasExtends = false; } } } if ($node instanceof Node\Stmt\ClassMethod) { if ($node->name->toString() === '__construct') { $this->visitorMetadata->hasConstructor = true; $this->visitorMetadata->constructorNode = $node; } } return null; } public function leaveNode(Node $node) { if (! $this->visitorMetadata->hasConstructor && $node instanceof Node\Stmt\Class_ && ! $node->isAnonymous()) { $constructor = $this->buildConstructor(); if ($this->visitorMetadata->hasExtends) { $constructor->stmts[] = $this->buildCallParentConstructorStatement(); } $constructor->stmts[] = $this->buildMethodCallStatement(); $node->stmts = array_merge([$this->buildProxyTraitUseStatement()], [$constructor], $node->stmts); $this->visitorMetadata->hasConstructor = true; } else { if ($node instanceof Node\Stmt\ClassMethod && $node->name->toString() === '__construct') { $node->stmts = array_merge([$this->buildMethodCallStatement()], $node->stmts); } if ($node instanceof Node\Stmt\Class_ && ! $node->isAnonymous()) { $node->stmts = array_merge([$this->buildProxyTraitUseStatement()], $node->stmts); } } return null; } protected function buildConstructor(): Node\Stmt\ClassMethod { if ($this->visitorMetadata->constructorNode instanceof Node\Stmt\ClassMethod) { // Returns the parsed constructor class method node. $constructor = $this->visitorMetadata->constructorNode; } else { // Create a new constructor class method node. $constructor = new Node\Stmt\ClassMethod('__construct'); $reflection = ReflectionManager::reflectClass($this->visitorMetadata->className); try { $parameters = $reflection->getMethod('__construct')->getParameters(); foreach ($parameters as $parameter) { $constructor->params[] = PhpParser::getInstance()->getNodeFromReflectionParameter($parameter); } } catch (\ReflectionException $exception) { // Cannot found __construct method in parent class or traits, do noting. } } return $constructor; } protected function buildCallParentConstructorStatement(): Node\Stmt { $hasConstructor = new Node\Expr\FuncCall(new Name('method_exists'), [ new Node\Arg(new Node\Expr\ClassConstFetch(new Name('parent'), 'class')), new Node\Arg(new Node\Scalar\String_('__construct')), ]); return new Node\Stmt\If_($hasConstructor, [ 'stmts' => [ new Node\Stmt\Expression(new Node\Expr\StaticCall(new Name('parent'), '__construct', [ new Node\Arg(new Node\Expr\FuncCall(new Name('func_get_args')), false, true), ])), ], ]); } protected function buildMethodCallStatement(): Node\Stmt\Expression { return new Node\Stmt\Expression(new Node\Expr\MethodCall(new Node\Expr\Variable('this'), '__handlePropertyHandler', [ new Node\Arg(new Node\Scalar\MagicConst\Class_()), ])); } /** * Build `use PropertyHandlerTrait;` statement. */ protected function buildProxyTraitUseStatement(): TraitUse { $traits = []; foreach ($this->proxyTraits as $proxyTrait) { // Should not check the trait whether or not exist to avoid class autoload. if (! is_string($proxyTrait)) { continue; } // Add backslash prefix if the proxy trait does not start with backslash. $proxyTrait[0] !== '\\' && $proxyTrait = '\\' . $proxyTrait; $traits[] = new Name($proxyTrait); } return new TraitUse($traits); } } src/Aop/ProxyCallVisitor.php000064400000017352151360552510012073 0ustar00visitorMetadata = $visitorMetadata; } public function beforeTraverse(array $nodes) { foreach ($nodes as $namespace) { if ($namespace instanceof Node\Stmt\Declare_) { continue; } if (! $namespace instanceof Node\Stmt\Namespace_) { break; } foreach ($namespace->stmts as $class) { switch ($class) { case $class instanceof Node\Stmt\ClassLike: $this->visitorMetadata->classLike = get_class($class); break; } } } return null; } public function enterNode(Node $node) { switch ($node) { case $node instanceof ClassMethod: if ($this->shouldRewrite($node)) { $this->shouldRewrite = true; } else { $this->shouldRewrite = false; } break; } return null; } public function leaveNode(Node $node) { switch ($node) { case $node instanceof ClassMethod: if (! $this->shouldRewrite($node)) { return $node; } // Rewrite the method to proxy call method. return $this->rewriteMethod($node); case $node instanceof Node\Stmt\Trait_: // If the node is trait and php version >= 7.3, it can `use ProxyTrait` like class. case $node instanceof Class_ && ! $node->isAnonymous(): // Add use proxy traits. $stmts = $node->stmts; if ($stmt = $this->buildProxyCallTraitUseStatement()) { array_unshift($stmts, $stmt); } $node->stmts = $stmts; unset($stmts); return $node; case $node instanceof MagicConstFunction: // Rewrite __FUNCTION__ to $__function__ variable. if ($this->shouldRewrite) { return new Variable('__function__'); } break; case $node instanceof MagicConstMethod: // Rewrite __METHOD__ to $__method__ variable. if ($this->shouldRewrite) { return new Variable('__method__'); } break; } return null; } /** * Build `use ProxyTrait;`. */ private function buildProxyCallTraitUseStatement(): ?TraitUse { $traits = []; foreach ($this->proxyTraits as $proxyTrait) { if (! is_string($proxyTrait) || ! trait_exists($proxyTrait)) { continue; } // Add backslash prefix if the proxy trait does not start with backslash. $proxyTrait[0] !== '\\' && $proxyTrait = '\\' . $proxyTrait; $traits[] = new Name($proxyTrait); } if (empty($traits)) { return null; } return new TraitUse($traits); } /** * Rewrite a normal class method to a proxy call method, * include normal class method and static method. */ private function rewriteMethod(ClassMethod $node): ClassMethod { // Build the static proxy call method base on the original method. $shouldReturn = true; $returnType = $node->getReturnType(); if ($returnType instanceof Identifier && $returnType->name === 'void') { $shouldReturn = false; } $staticCall = new StaticCall(new Name('self'), '__proxyCall', [ // __CLASS__ new Arg($this->getMagicConst()), // __FUNCTION__ new Arg(new MagicConstFunction()), // self::getParamMap(OriginalClass::class, __FUNCTION, func_get_args()) new Arg(new StaticCall(new Name('self'), '__getParamsMap', [ new Arg(new MagicConstClass()), new Arg(new MagicConstFunction()), new Arg(new FuncCall(new Name('func_get_args'))), ])), // A closure that wrapped original method code. new Arg(new Closure([ 'params' => value(function () use ($node) { // Transfer the variadic variable to normal variable at closure argument. ...$params => $parms $params = $node->getParams(); foreach ($params as $key => $param) { if ($param instanceof Node\Param && $param->variadic) { $newParam = clone $param; $newParam->variadic = false; $params[$key] = $newParam; } } return $params; }), 'uses' => [ new Variable('__function__'), new Variable('__method__'), ], 'stmts' => $node->stmts, ])), ]); $stmts = $this->unshiftMagicMethods([]); if ($shouldReturn) { $stmts[] = new Return_($staticCall); } else { $stmts[] = new Expression($staticCall); } $node->stmts = $stmts; return $node; } private function unshiftMagicMethods($stmts = []) { $magicConstFunction = new Expression(new Assign(new Variable('__function__'), new MagicConstFunction())); $magicConstMethod = new Expression(new Assign(new Variable('__method__'), new MagicConstMethod())); array_unshift($stmts, $magicConstFunction, $magicConstMethod); return $stmts; } private function getMagicConst(): Node\Scalar\MagicConst { switch ($this->visitorMetadata->classLike) { case Trait_::class: return new MagicConstTrait(); case Class_::class: default: return new MagicConstClass(); } } private function shouldRewrite(ClassMethod $node) { if (in_array($this->visitorMetadata->classLike, [Node\Stmt\Interface_::class])) { return false; } $rewriteCollection = Aspect::parse($this->visitorMetadata->className); return $rewriteCollection->shouldRewrite($node->name->toString()); } } src/Aop/ProxyManager.php000064400000013341151360552510011204 0ustar00classMap = $classMap; $this->proxyDir = $proxyDir; $this->filesystem = new Filesystem(); $this->proxies = $this->generateProxyFiles($this->initProxiesByReflectionClassMap( $this->classMap )); } public function getProxies(): array { return $this->proxies; } public function getProxyDir(): string { return $this->proxyDir; } protected function generateProxyFiles(array $proxies = []): array { $proxyFiles = []; if (! $proxies) { return $proxyFiles; } if (! file_exists($this->getProxyDir())) { mkdir($this->getProxyDir(), 0755, true); } // WARNING: Ast class SHOULD NOT use static instance, because it will read the code from file, then would be caused coroutine switch. $ast = new Ast(); foreach ($proxies as $className => $aspects) { $proxyFiles[$className] = $this->putProxyFile($ast, $className); } return $proxyFiles; } protected function putProxyFile(Ast $ast, $className) { $proxyFilePath = $this->getProxyFilePath($className); $modified = true; if (file_exists($proxyFilePath)) { $modified = $this->isModified($className, $proxyFilePath); } if ($modified) { $code = $ast->proxy($className); file_put_contents($proxyFilePath, $code); } return $proxyFilePath; } protected function isModified(string $className, string $proxyFilePath = null): bool { $proxyFilePath = $proxyFilePath ?? $this->getProxyFilePath($className); $time = $this->filesystem->lastModified($proxyFilePath); $origin = $this->classMap[$className]; if ($time >= $this->filesystem->lastModified($origin)) { return false; } return true; } protected function getProxyFilePath($className) { return $this->getProxyDir() . str_replace('\\', '_', $className) . '.proxy.php'; } protected function isMatch(string $rule, string $target): bool { if (strpos($rule, '::') !== false) { [$rule,] = explode('::', $rule); } if (strpos($rule, '*') === false && $rule === $target) { return true; } $preg = str_replace(['*', '\\'], ['.*', '\\\\'], $rule); $pattern = "/^{$preg}$/"; if (preg_match($pattern, $target)) { return true; } return false; } protected function initProxiesByReflectionClassMap(array $reflectionClassMap = []): array { // According to the data of AspectCollector to parse all the classes that need proxy. $proxies = []; if (! $reflectionClassMap) { return $proxies; } $classesAspects = AspectCollector::get('classes', []); foreach ($classesAspects as $aspect => $rules) { foreach ($rules as $rule) { foreach ($reflectionClassMap as $class => $path) { if (! $this->isMatch($rule, $class)) { continue; } $proxies[$class][] = $aspect; } } } foreach ($reflectionClassMap as $className => $path) { // Aggregate the class annotations $classAnnotations = $this->retrieveAnnotations($className . '._c'); // Aggregate all methods annotations $methodAnnotations = $this->retrieveAnnotations($className . '._m'); // Aggregate all properties annotations $propertyAnnotations = $this->retrieveAnnotations($className . '._p'); $annotations = array_unique(array_merge($classAnnotations, $methodAnnotations, $propertyAnnotations)); if ($annotations) { $annotationsAspects = AspectCollector::get('annotations', []); foreach ($annotationsAspects as $aspect => $rules) { foreach ($rules as $rule) { foreach ($annotations as $annotation) { if ($this->isMatch($rule, $annotation)) { $proxies[$className][] = $aspect; } } } } } } return $proxies; } protected function retrieveAnnotations(string $annotationCollectorKey): array { $defined = []; $annotations = AnnotationCollector::get($annotationCollectorKey, []); foreach ($annotations as $k => $annotation) { if (is_object($annotation)) { $defined[] = $k; } else { $defined = array_merge($defined, array_keys($annotation)); } } return $defined; } } src/Aop/ProxyTrait.php000064400000012471151360552510010720 0ustar00 [], 'order' => [], ]; $reflectMethod = ReflectionManager::reflectMethod($className, $method); $reflectParameters = $reflectMethod->getParameters(); $leftArgCount = count($args); foreach ($reflectParameters as $key => $reflectionParameter) { $arg = $reflectionParameter->isVariadic() ? $args : array_shift($args); if (! isset($arg) && $leftArgCount <= 0) { $arg = $reflectionParameter->getDefaultValue(); } --$leftArgCount; $map['keys'][$reflectionParameter->getName()] = $arg; $map['order'][] = $reflectionParameter->getName(); } return $map; } protected static function handleAround(ProceedingJoinPoint $proceedingJoinPoint) { $className = $proceedingJoinPoint->className; $methodName = $proceedingJoinPoint->methodName; if (! AspectManager::has($className, $methodName)) { AspectManager::set($className, $methodName, []); $aspects = array_unique(array_merge(static::getClassesAspects($className, $methodName), static::getAnnotationAspects($className, $methodName))); $queue = new \SplPriorityQueue(); foreach ($aspects as $aspect) { $queue->insert($aspect, AspectCollector::getPriority($aspect)); } while ($queue->valid()) { AspectManager::insert($className, $methodName, $queue->current()); $queue->next(); } unset($annotationAspects, $aspects, $queue); } if (empty(AspectManager::get($className, $methodName))) { return $proceedingJoinPoint->processOriginalMethod(); } return static::makePipeline()->via('process') ->through(AspectManager::get($className, $methodName)) ->send($proceedingJoinPoint) ->then(function (ProceedingJoinPoint $proceedingJoinPoint) { return $proceedingJoinPoint->processOriginalMethod(); }); } protected static function makePipeline(): Pipeline { $container = ApplicationContext::getContainer(); if (method_exists($container, 'make')) { $pipeline = $container->make(Pipeline::class); } else { $pipeline = new Pipeline($container); } return $pipeline; } protected static function getClassesAspects(string $className, string $method): array { $aspects = AspectCollector::get('classes', []); $matchedAspect = []; foreach ($aspects as $aspect => $rules) { foreach ($rules as $rule) { if (Aspect::isMatch($className, $method, $rule)) { $matchedAspect[] = $aspect; break; } } } // The matched aspects maybe have duplicate aspect, should unique it when use it. return $matchedAspect; } protected static function getAnnotationAspects(string $className, string $method): array { $matchedAspect = $annotations = $rules = []; $classAnnotations = AnnotationCollector::get($className . '._c', []); $methodAnnotations = AnnotationCollector::get($className . '._m.' . $method, []); $annotations = array_unique(array_merge(array_keys($classAnnotations), array_keys($methodAnnotations))); if (! $annotations) { return $matchedAspect; } $aspects = AspectCollector::get('annotations', []); foreach ($aspects as $aspect => $rules) { foreach ($rules as $rule) { foreach ($annotations as $annotation) { if (strpos($rule, '*') !== false) { $preg = str_replace(['*', '\\'], ['.*', '\\\\'], $rule); $pattern = "/^{$preg}$/"; if (! preg_match($pattern, $annotation)) { continue; } } elseif ($rule !== $annotation) { continue; } $matchedAspect[] = $aspect; } } } // The matched aspects maybe have duplicate aspect, should unique it when use it. return $matchedAspect; } } src/Aop/RegisterInjectPropertyHandler.php000064400000003553151360552510014560 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.php000064400000004606151360552510012231 0ustar00class = $class; } /** * @param array|string $methods */ public function add($methods): self { $methods = (array) $methods; foreach ($methods as $method) { if (strpos($method, '*') === false) { $this->methods[] = $method; } else { $preg = str_replace(['*', '\\'], ['.*', '\\\\'], $method); $this->pattern[] = "/^{$preg}$/"; } } return $this; } public function shouldRewrite(string $method): bool { if ($this->level === self::CLASS_LEVEL) { if (in_array($method, $this->shouldNotRewriteMethods)) { return false; } return true; } if (in_array($method, $this->methods)) { return true; } foreach ($this->pattern as $pattern) { if (preg_match($pattern, $method)) { return true; } } return false; } public function setLevel(int $level): self { $this->level = $level; return $this; } public function getLevel(): int { return $this->level; } public function getMethods(): array { return $this->methods; } public function getClass(): string { return $this->class; } /** * Return the methods that should not rewrite. */ public function getShouldNotRewriteMethods(): array { return $this->shouldNotRewriteMethods; } } src/Aop/VisitorMetadata.php000064400000001176151360552510011673 0ustar00 ProxyFileAbsolutePath ]. * * @var array */ protected $proxies = []; public function __construct(ComposerClassLoader $classLoader, string $proxyFileDir, string $configDir) { $this->setComposerClassLoader($classLoader); if (file_exists(BASE_PATH . '/.env')) { $this->loadDotenv(); } // Scan by ScanConfig to generate the reflection class map $config = ScanConfig::instance($configDir); $classLoader->addClassMap($config->getClassMap()); $scanner = new Scanner($this, $config); $this->proxies = $scanner->scan($this->getComposerClassLoader()->getClassMap(), $proxyFileDir); } public function loadClass(string $class): void { $path = $this->locateFile($class); if ($path) { include $path; } } public static function init(?string $proxyFileDirPath = null, ?string $configDir = null): void { if (! $proxyFileDirPath) { // This dir is the default proxy file dir path of Hyperf $proxyFileDirPath = BASE_PATH . '/runtime/container/proxy/'; } if (! $configDir) { // This dir is the default proxy file dir path of Hyperf $configDir = BASE_PATH . '/config/'; } $loaders = spl_autoload_functions(); // Proxy the composer class loader foreach ($loaders as &$loader) { $unregisterLoader = $loader; if (is_array($loader) && $loader[0] instanceof ComposerClassLoader) { /** @var ComposerClassLoader $composerClassLoader */ $composerClassLoader = $loader[0]; AnnotationRegistry::registerLoader(function ($class) use ($composerClassLoader) { return (bool) $composerClassLoader->findFile($class); }); $loader[0] = new static($composerClassLoader, $proxyFileDirPath, $configDir); } spl_autoload_unregister($unregisterLoader); } unset($loader); // Re-register the loaders foreach ($loaders as $loader) { spl_autoload_register($loader); } // Initialize Lazy Loader. This will prepend LazyLoader to the top of autoload queue. LazyLoader::bootstrap($configDir); } public function setComposerClassLoader(ComposerClassLoader $classLoader): self { $this->composerClassLoader = $classLoader; // Set the ClassLoader to Hyperf\Utils\Composer to avoid unnecessary find process. Composer::setLoader($classLoader); return $this; } public function getComposerClassLoader(): ComposerClassLoader { return $this->composerClassLoader; } protected function locateFile(string $className): ?string { if (isset($this->proxies[$className]) && file_exists($this->proxies[$className])) { $file = $this->proxies[$className]; } else { $file = $this->getComposerClassLoader()->findFile($className); } return is_string($file) ? $file : null; } protected function loadDotenv(): void { $repository = RepositoryBuilder::createWithNoAdapters() ->addAdapter(Adapter\PutenvAdapter::class) ->immutable() ->make(); Dotenv::create($repository, [BASE_PATH])->load(); } } src/ClosureDefinitionCollector.php000064400000002425151360552510013346 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.php000064400000000672151360552510015171 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.php000064400000013445151360552510010000 0ustar00definitionSource = $definitionSource; $this->definitionResolver = new ResolverDispatcher($this); // Auto-register the container. $this->resolvedEntries = [ self::class => $this, PsrContainerInterface::class => $this, HyperfContainerInterface::class => $this, ]; } /** * Build an entry of the container by its name. * This method behave like get() except resolves the entry again every time. * For example if the entry is a class then a new instance will be created each time. * This method makes the container behave like a factory. * * @param string $name entry name or a class name * @param array $parameters Optional parameters to use to build the entry. Use this to force specific parameters * to specific values. Parameters not defined in this array will be resolved using * the container. * @throws NotFoundException no entry found for the given name * @throws InvalidArgumentException the name parameter must be of type string */ public function make(string $name, array $parameters = []) { $definition = $this->getDefinition($name); if (! $definition) { throw new NotFoundException("No entry or class found for '{$name}'"); } return $this->resolveDefinition($definition, $parameters); } /** * Bind an arbitrary resolved entry to an identifier. * Useful for testing 'get'. * @param mixed $entry */ public function set(string $name, $entry) { $this->resolvedEntries[$name] = $entry; } /** * Bind an arbitrary definition to an identifier. * Useful for testing 'make'. * * @param array|callable|string $definition */ public function define(string $name, $definition) { $this->setDefinition($name, $definition); } /** * Finds an entry of the container by its identifier and returns it. * * @param string $name identifier of the entry to look for */ public function get($name) { // If the entry is already resolved we return it if (isset($this->resolvedEntries[$name]) || array_key_exists($name, $this->resolvedEntries)) { return $this->resolvedEntries[$name]; } return $this->resolvedEntries[$name] = $this->make($name); } /** * Returns true if the container can return an entry for the given identifier. * Returns false otherwise. * `has($name)` returning true does not mean that `get($name)` will not throw an exception. * It does however mean that `get($name)` will not throw a `NotFoundExceptionInterface`. * * @param mixed|string $name identifier of the entry to look for */ public function has($name): bool { if (! is_string($name)) { throw new InvalidArgumentException(sprintf('The name parameter must be of type string, %s given', is_object($name) ? get_class($name) : gettype($name))); } if (array_key_exists($name, $this->resolvedEntries)) { return true; } $definition = $this->getDefinition($name); if ($definition === null) { return false; } if ($definition instanceof ObjectDefinition) { return $definition->isInstantiable(); } return true; } public function getDefinitionSource(): Definition\DefinitionSourceInterface { return $this->definitionSource; } /** * @param array|callable|string $definition */ private function setDefinition(string $name, $definition): void { // Clear existing entry if it exists if (array_key_exists($name, $this->resolvedEntries)) { unset($this->resolvedEntries[$name]); } $this->fetchedDefinitions = []; // Completely clear this local cache $this->definitionSource->addDefinition($name, $definition); } private function getDefinition(string $name): ?DefinitionInterface { // Local cache that avoids fetching the same definition twice if (! array_key_exists($name, $this->fetchedDefinitions)) { $this->fetchedDefinitions[$name] = $this->definitionSource->getDefinition($name); } return $this->fetchedDefinitions[$name]; } /** * Resolves a definition. */ private function resolveDefinition(DefinitionInterface $definition, array $parameters = []) { return $this->definitionResolver->resolve($definition, $parameters); } } src/Definition/DefinitionInterface.php000064400000001215151360552510014047 0ustar00source = $this->normalizeSource($source); } /** * Returns the DI definition for the entry name. */ public function getDefinition(string $name): ?DefinitionInterface { return $this->source[$name] ?? $this->source[$name] = $this->autowire($name); } /** * @return array definitions indexed by their name */ public function getDefinitions(): array { return $this->source; } /** * @param array|callable|string $definition */ public function addDefinition(string $name, $definition): self { $this->source[$name] = $this->normalizeDefinition($name, $definition); return $this; } public function clearDefinitions(): void { $this->source = []; } /** * Read the type-hinting from the parameters of the function. */ private function getParametersDefinition(ReflectionFunctionAbstract $constructor): array { $parameters = []; foreach ($constructor->getParameters() as $index => $parameter) { // Skip optional parameters. if ($parameter->isOptional()) { continue; } $parameterType = $parameter->getType(); if ($parameterType && $parameterType instanceof \ReflectionNamedType && ! $parameterType->isBuiltin()) { $parameters[$index] = new Reference($parameterType->getName()); } } return $parameters; } /** * Normaliaze the user definition source to a standard definition souce. */ private function normalizeSource(array $source): array { $definitions = []; foreach ($source as $identifier => $definition) { $normalizedDefinition = $this->normalizeDefinition($identifier, $definition); if (! is_null($normalizedDefinition)) { $definitions[$identifier] = $normalizedDefinition; } } return $definitions; } /** * @param array|callable|string $definition */ private function normalizeDefinition(string $identifier, $definition): ?DefinitionInterface { if (is_string($definition) && class_exists($definition)) { if (method_exists($definition, '__invoke')) { return new FactoryDefinition($identifier, $definition, []); } return $this->autowire($identifier, new ObjectDefinition($identifier, $definition)); } if (is_callable($definition)) { return new FactoryDefinition($identifier, $definition, []); } return null; } private function autowire(string $name, ObjectDefinition $definition = null): ?ObjectDefinition { $className = $definition ? $definition->getClassName() : $name; if (! class_exists($className) && ! interface_exists($className)) { return $definition; } $definition = $definition ?: new ObjectDefinition($name); /** * Constructor. */ $class = ReflectionManager::reflectClass($className); $constructor = $class->getConstructor(); if ($constructor && $constructor->isPublic()) { $constructorInjection = new MethodInjection('__construct', $this->getParametersDefinition($constructor)); $definition->completeConstructorInjection($constructorInjection); } return $definition; } } src/Definition/DefinitionSourceFactory.php000064400000002566151360552510014751 0ustar00enableCache = $enableCache; if (! defined('BASE_PATH')) { throw new Exception('BASE_PATH is not defined.'); } $this->baseUri = BASE_PATH; } public function __invoke() { $configDir = $this->baseUri . '/config'; $configFromProviders = []; if (class_exists(ProviderConfig::class)) { $configFromProviders = ProviderConfig::load(); } $serverDependencies = $configFromProviders['dependencies'] ?? []; if (file_exists($configDir . '/autoload/dependencies.php')) { $definitions = include $configDir . '/autoload/dependencies.php'; $serverDependencies = array_replace($serverDependencies, $definitions ?? []); } return new DefinitionSource($serverDependencies); } } src/Definition/DefinitionSourceInterface.php000064400000001603151360552510015231 0ustar00name = $name; $this->factory = $factory; $this->parameters = $parameters; } public function __toString(): string { return 'Factory'; } public function getName(): string { return $this->name; } public function setName(string $name): self { $this->name = $name; return $this; } /** * @return callable|string */ public function getFactory() { return $this->factory; } /** * @return mixed[] */ public function getParameters(): array { return $this->parameters; } /** * Determine if the definition need to transfer to a proxy class. */ public function isNeedProxy(): bool { return $this->needProxy; } public function setNeedProxy($needProxy): self { $this->needProxy = $needProxy; return $this; } } src/Definition/MethodInjection.php000064400000003371151360552510013226 0ustar00parameters = $parameters; $this->methodName = $methodName; } public function __toString(): string { return sprintf('method(%s)', $this->methodName); } public function getName(): string { return ''; } public function setName(string $name) { // The name does not matter for method injections, so do nothing. } /** * @return mixed[] */ public function getParameters(): array { return $this->parameters; } public function merge(self $definition) { // In case of conflicts, the current definition prevails. $this->parameters = $this->parameters + $definition->parameters; } /** * Reset the target should be resolved. * If it is the FactoryDefinition, then the target means $factory property, * If it is the ObjectDefinition, then the target means $className property. */ public function setTarget(string $value) { $this->methodName = $value; } /** * Determine if the definition need to transfer to a proxy class. */ public function isNeedProxy(): bool { // Method injection does not has proxy. return false; } } src/Definition/ObjectDefinition.php000064400000005267151360552510013370 0ustar00name = $name; $this->setClassName($className ?? $name); } public function __toString(): string { return sprintf('Object[%s]', $this->getClassName()); } public function getName(): string { return $this->name; } public function setName(string $name): self { $this->name = $name; return $this; } public function setClassName(string $className = null): void { $this->className = $className; $this->updateStatusCache(); } public function getClassName(): string { if ($this->className !== null) { return $this->className; } return $this->name; } public function isClassExists(): bool { return $this->classExists; } public function isInstantiable(): bool { return $this->instantiable; } /** * @return null|\Hyperf\Di\Definition\MethodInjection */ public function getConstructorInjection() { return $this->constructorInjection; } public function setConstructorInjection(MethodInjection $injection): self { $this->constructorInjection = $injection; return $this; } public function completeConstructorInjection(MethodInjection $injection): void { if ($this->constructorInjection !== null) { // Merge $this->constructorInjection->merge($injection); } else { // Set $this->constructorInjection = $injection; } } private function updateStatusCache(): void { $className = $this->getClassName(); $this->classExists = class_exists($className) || interface_exists($className); if (! $this->classExists) { $this->instantiable = false; return; } $this->instantiable = ReflectionManager::reflectClass($className)->isInstantiable(); } } src/Definition/PropertyDefinition.php000064400000003610151360552510013774 0ustar00propertyName = $propertyName; $this->value = $value; $this->className = $className; } /** * Definitions can be cast to string for debugging information. */ public function __toString(): string { return 'Property'; } /** * Returns the name of the entry in the container. */ public function getName(): string { return $this->propertyName; } /** * Set the name of the entry in the container. */ public function setName(string $name) { $this->propertyName = $name; } public function getValue() { return $this->value; } public function getClassName(): ?string { return $this->className; } /** * Determine if the definition need to transfer to a proxy class. */ public function isNeedProxy(): bool { return false; } } src/Definition/PropertyHandlerManager.php000064400000001747151360552510014565 0ustar00targetEntryName = $targetEntryName; } /** * Definitions can be cast to string for debugging information. */ public function __toString(): string { return sprintf('get(%s)', $this->targetEntryName); } /** * Returns the name of the entry in the container. */ public function getName(): string { return $this->name; } /** * Set the name of the entry in the container. */ public function setName(string $name) { $this->name = $name; } public function getTargetEntryName(): string { return $this->targetEntryName; } public function resolve(ContainerInterface $container) { return $container->get($this->getTargetEntryName()); } public function isResolvable(ContainerInterface $container): bool { return $container->has($this->getTargetEntryName()); } /** * Determine if the definition need to transfer to a proxy class. */ public function isNeedProxy(): bool { return $this->needProxy; } public function setNeedProxy($needProxy): self { $this->needProxy = $needProxy; return $this; } } src/Definition/ScanConfig.php000064400000002572151360552510012157 0ustar00dirs = $dirs; $this->ignoreAnnotations = $ignoreAnnotations; $this->collectors = $collectors; } public function getDirs(): array { return $this->dirs; } public function setDirs(array $dirs): self { $this->dirs = $dirs; return $this; } public function getIgnoreAnnotations(): array { return $this->ignoreAnnotations; } public function setIgnoreAnnotations(array $ignoreAnnotations): self { $this->ignoreAnnotations = $ignoreAnnotations; return $this; } public function getCollectors(): array { return $this->collectors; } public function setCollectors(array $collectors): self { $this->collectors = $collectors; return $this; } } src/Definition/SelfResolvingDefinitionInterface.php000064400000001165151360552510016556 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.php000064400000000520151360552510015455 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.php000064400000001320151360552510014367 0ustar00originalClassName, '\\') !== 0) { $originalClassName = '\\' . $this->originalClassName; } else { $originalClassName = $this->originalClassName; } $this->builder = $this->builder->extend($originalClassName); return $this; } } src/LazyLoader/FallbackLazyProxyBuilder.php000064400000000672151360552510015032 0ustar00originalClassName, '\\') !== 0) { $originalClassName = '\\' . $this->originalClassName; } else { $originalClassName = $this->originalClassName; } $this->builder = $this->builder->implement($originalClassName); return $this; } } src/LazyLoader/LazyLoader.php000064400000013157151360552510012172 0ustar00config = $config; $this->register(); } /** * Get or create the singleton lazy loader instance. */ public static function bootstrap(string $configDir): LazyLoader { if (static::$instance) { return static::$instance; } $path = $configDir . self::CONFIG_FILE_NAME; $config = []; if (file_exists($path)) { $config = include $path; } return static::$instance = new static($config); } /** * Load a class proxy if it is registered. * * @return null|bool */ public function load(string $proxy) { if (array_key_exists($proxy, $this->config) || $this->startsWith($proxy, 'HyperfLazy\\')) { $this->loadProxy($proxy); return true; } return null; } /** * Register the loader on the auto-loader stack. */ protected function register(): void { if (! $this->registered) { $this->prependToLoaderStack(); $this->registered = true; } } /** * Load a real-time facade for the given proxy. */ protected function loadProxy(string $proxy) { require_once $this->ensureProxyExists($proxy); } /** * Ensure that the given proxy has an existing real-time facade class. */ protected function ensureProxyExists(string $proxy): string { $dir = BASE_PATH . '/runtime/container/proxy/'; if (! file_exists($dir)) { mkdir($dir, 0755, true); } $code = $this->generatorLazyProxy( $proxy, $this->config[$proxy] ?? Str::after($proxy, 'HyperfLazy\\') ); $path = str_replace('\\', '_', $dir . $proxy . '_' . crc32($code) . '.php'); $key = md5($path); // If the proxy file does not exist, then try to acquire the coroutine lock. if (! file_exists($path) && CoLocker::lock($key)) { $targetPath = $path . '.' . uniqid(); file_put_contents($targetPath, $code); rename($targetPath, $path); CoLocker::unlock($key); } return $path; } /** * Format the lazy proxy with the proper namespace and class. */ protected function generatorLazyProxy(string $proxy, string $target): string { $targetReflection = new ReflectionClass($target); if ($this->isUnsupportedReflectionType($targetReflection)) { $builder = new FallbackLazyProxyBuilder(); return $this->buildNewCode($builder, $proxy, $targetReflection); } if ($targetReflection->isInterface()) { $builder = new InterfaceLazyProxyBuilder(); return $this->buildNewCode($builder, $proxy, $targetReflection); } $builder = new ClassLazyProxyBuilder(); return $this->buildNewCode($builder, $proxy, $targetReflection); } /** * Prepend the load method to the auto-loader stack. */ protected function prependToLoaderStack(): void { /** @var callable(string): void */ $load = [$this, 'load']; spl_autoload_register($load, true, true); } private function startsWith($haystack, $needle): bool { return substr($haystack, 0, strlen($needle)) === (string) $needle; } /** * These conditions are really hard to proxy via inheritence. * Luckily these conditions are very rarely met. * * TODO: implement some of them. * * @param ReflectionClass $targetReflection [description] * @return bool [description] */ private function isUnsupportedReflectionType(ReflectionClass $targetReflection): bool { // Final class if ($targetReflection->isFinal()) { return true; } return false; } 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.php000064400000002637151360552510013112 0ustar00{$var}); } } public function __call(string $method, array $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.php000064400000005272151360552510014062 0ustar00stmts = $stmts; if (strpos($originalClassName, '\\') !== 0) { $originalClassName = '\\' . $originalClassName; } $this->originalClassName = $originalClassName; } public function enterNode(Node $node) { if ($node instanceof Interface_ || $node instanceof Class_) { $node->stmts = $this->stmts; } if ($node instanceof ClassMethod) { $methodCall = new MethodCall( new Variable('this'), '__call', [ new Node\Arg(new MagicConstFunction()), new Node\Arg(new FuncCall(new Name('func_get_args'))), ] ); $shouldReturn = true; if ($node->getReturnType() && method_exists($node->getReturnType(), 'toString')) { if ($node->getReturnType()->toString() === 'self') { $node->returnType = new Name($this->originalClassName); } if ($node->getReturnType()->toString() === 'void') { $shouldReturn = false; $methodCall = new Expression($methodCall); } } $shouldReturn && $methodCall = new Return_($methodCall); $node->stmts = [ $methodCall, ]; $node->flags &= ~Class_::MODIFIER_ABSTRACT; } return null; } public function leaveNode(Node $node) { if ($node instanceof ClassMethod && $node->isPublic() && ! $node->isMagic()) { $this->nodes[] = $node; } return null; } } src/MetadataCacheCollector.php000064400000002752151360552510012370 0ustar00collectors = $collectors; } public function addCollector(string $collector) { $this->collectors = array_unique(array_merge( $this->collectors, [$collector] )); } public function clear() { $this->collectors = []; } public function serialize(): string { $metadata = []; foreach ($this->collectors as $collector) { if (is_string($collector) && method_exists($collector, 'serialize')) { $metadata[$collector] = call([$collector, 'serialize']); } } return json_encode($metadata); } public function unserialize($serialized): void { $metadatas = json_decode($serialized, true) ?? []; $collectors = []; foreach ($metadatas as $collector => $metadata) { if (method_exists($collector, 'deserialize')) { call([$collector, 'deserialize'], [$metadata]); $collectors[] = $collector; } } $this->collectors = $collectors; } } src/MetadataCollector.php000064400000003514151360552510011441 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' => $parameter->getClass()->getName() ?? 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.php000064400000001221151360552510014764 0ustar00getMethod($method); } return static::$container['method'][$key]; } public static function reflectProperty(string $className, string $property): ReflectionProperty { $key = $className . '::' . $property; if (! isset(static::$container['property'][$key])) { if (! class_exists($className)) { throw new InvalidArgumentException("Class {$className} not exist"); } static::$container['property'][$key] = static::reflectClass($className)->getProperty($property); } return static::$container['property'][$key]; } public static function reflectPropertyNames(string $className) { $key = $className; if (! isset(static::$container['property_names'][$key])) { if (! class_exists($className) && ! interface_exists($className) && ! trait_exists($className)) { throw new InvalidArgumentException("Class {$className} not exist"); } static::$container['property_names'][$key] = value(function () use ($className) { $properties = static::reflectClass($className)->getProperties(); $result = []; foreach ($properties as $property) { $result[] = $property->getName(); } return $result; }); } return static::$container['property_names'][$key]; } public static function clear(?string $key = null): void { if ($key === null) { static::$container = []; } } public static function getPropertyDefaultValue(ReflectionProperty $property) { return method_exists($property, 'getDefaultValue') ? $property->getDefaultValue() : $property->getDeclaringClass()->getDefaultProperties()[$property->getName()] ?? null; } public static function getAllClasses(array $paths): array { $finder = new Finder(); $finder->files()->in($paths)->name('*.php'); $parser = new Ast(); $reflectionClasses = []; foreach ($finder as $file) { try { $stmts = $parser->parse($file->getContents()); if (! $className = $parser->parseClassByStmts($stmts)) { continue; } $reflectionClasses[$className] = static::reflectClass($className); } catch (\Throwable $e) { } } return $reflectionClasses; } } src/ReflectionType.php000064400000001647151360552510011013 0ustar00name = $name; $this->allowsNull = $allowsNull; $this->metadata = $metadata; } public function getName(): string { return $this->name; } public function allowsNull(): bool { return $this->allowsNull; } public function getMeta(string $key) { return $this->metadata[$key] ?? null; } } src/Resolver/DepthGuard.php000064400000003453151360552510011704 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.php000064400000005531151360552510013005 0ustar00container = $container; $this->resolver = $resolver; } /** * Resolve a factory definition to a value. * * @param FactoryDefinition $definition object that defines how the value should be obtained * @param array $parameters optional parameters to use to build the entry * @throws InvalidDefinitionException if the definition cannot be resolved * @return mixed value obtained from the definition */ public function resolve(DefinitionInterface $definition, array $parameters = []) { $callable = null; try { $callable = $definition->getFactory(); if (! method_exists($callable, '__invoke')) { throw new NotCallableException(); } if (is_string($callable)) { $callable = $this->container->get($callable); $object = $callable($this->container); } else { $object = call($callable, [$this->container]); } return $object; } catch (NotCallableException $e) { // Custom error message to help debugging if (is_string($callable) && class_exists($callable) && method_exists($callable, '__invoke')) { throw new InvalidDefinitionException(sprintf('Entry "%s" cannot be resolved: factory %s. Invokable classes cannot be automatically resolved if autowiring is disabled on the container, you need to enable autowiring or define the entry manually.', $definition->getName(), $e->getMessage())); } throw new InvalidDefinitionException(sprintf('Entry "%s" cannot be resolved: factory %s', $definition->getName(), $e->getMessage())); } } /** * Check if a definition can be resolved. * * @param DefinitionInterface $definition object that defines how the value should be obtained * @param array $parameters optional parameters to use to build the entry */ public function isResolvable(DefinitionInterface $definition, array $parameters = []): bool { return true; } } src/Resolver/ObjectResolver.php000064400000007602151360552510012605 0ustar00container = $container; $this->definitionResolver = $definitionResolver; $this->parameterResolver = new ParameterResolver($definitionResolver); } /** * Resolve a definition to a value. * * @param DefinitionInterface $definition object that defines how the value should be obtained * @param array $parameters optional parameters to use to build the entry * @throws InvalidDefinitionException * @throws DependencyException * @return mixed value obtained from the definition */ public function resolve(DefinitionInterface $definition, array $parameters = []) { if (! $definition instanceof ObjectDefinition) { throw InvalidDefinitionException::create( $definition, sprintf('Entry "%s" cannot be resolved: the class is not instanceof ObjectDefinition', $definition->getName()) ); } return $this->createInstance($definition, $parameters); } /** * Check if a definition can be resolved. * * @param ObjectDefinition $definition object that defines how the value should be obtained * @param array $parameters optional parameters to use to build the entry */ public function isResolvable(DefinitionInterface $definition, array $parameters = []): bool { return $definition->isInstantiable(); } private function createInstance(ObjectDefinition $definition, array $parameters) { // Check that the class is instantiable if (! $definition->isInstantiable()) { // Check that the class exists if (! $definition->isClassExists()) { throw InvalidDefinitionException::create($definition, sprintf('Entry "%s" cannot be resolved: the class doesn\'t exist', $definition->getName())); } throw InvalidDefinitionException::create($definition, sprintf('Entry "%s" cannot be resolved: the class is not instantiable', $definition->getName())); } $classReflection = null; try { $className = $definition->getClassName(); $classReflection = ReflectionManager::reflectClass($className); $constructorInjection = $definition->getConstructorInjection(); $args = $this->parameterResolver->resolveParameters($constructorInjection, $classReflection->getConstructor(), $parameters); $object = new $className(...$args); } catch (NotFoundExceptionInterface $e) { throw new DependencyException(sprintf('Error while injecting dependencies into %s: %s', $classReflection ? $classReflection->getName() : '', $e->getMessage()), 0, $e); } catch (InvalidDefinitionException $e) { throw InvalidDefinitionException::create($definition, sprintf('Entry "%s" cannot be resolved: %s', $definition->getName(), $e->getMessage())); } return $object; } } src/Resolver/ParameterResolver.php000064400000006357151360552510013325 0ustar00definitionResolver = $definitionResolver; } public function resolveParameters( MethodInjection $definition = null, ReflectionMethod $method = null, array $parameters = [] ): array { $args = []; if (! $method) { return $args; } $definitionParameters = $definition ? $definition->getParameters() : []; foreach ($method->getParameters() as $index => $parameter) { if (array_key_exists($parameter->getName(), $parameters)) { $value = &$parameters[$parameter->getName()]; } elseif (array_key_exists($index, $parameters)) { $value = &$parameters[$index]; } elseif (array_key_exists($index, $definitionParameters)) { $value = &$definitionParameters[$index]; } else { if ($parameter->isDefaultValueAvailable() || $parameter->isOptional()) { $args[] = $this->getParameterDefaultValue($parameter, $method); continue; } throw new InvalidDefinitionException(sprintf( 'Parameter $%s of %s has no value defined or guessable', $parameter->getName(), $this->getFunctionName($method) )); } // Nested definitions if ($value instanceof DefinitionInterface) { // If the container cannot produce the entry, we can use the default parameter value if ($parameter->isOptional() && ! $this->definitionResolver->isResolvable($value)) { $value = $this->getParameterDefaultValue($parameter, $method); } else { $value = $this->definitionResolver->resolve($value); } } $args[] = &$value; } return $args; } private function getParameterDefaultValue(ReflectionParameter $parameter, ReflectionMethod $function) { try { return $parameter->getDefaultValue(); } catch (\ReflectionException $e) { throw new InvalidDefinitionException(sprintf( 'The parameter "%s" of %s has no type defined or guessable. It has a default value, ' . 'but the default value can\'t be read through Reflection because it is a PHP internal class.', $parameter->getName(), $this->getFunctionName($function) )); } } private function getFunctionName(ReflectionMethod $method): string { return $method->getName() . '()'; } } src/Resolver/ResolverDispatcher.php000064400000007151151360552510013464 0ustar00container = $container; } /** * Resolve a definition to a value. * * @param DefinitionInterface $definition object that defines how the value should be obtained * @param array $parameters optional parameters to use to build the entry * @throws InvalidDefinitionException if the definition cannot be resolved * @return mixed value obtained from the definition */ public function resolve(DefinitionInterface $definition, array $parameters = []) { if ($definition instanceof SelfResolvingDefinitionInterface) { return $definition->resolve($this->container); } $guard = DepthGuard::getInstance(); return $guard->call($definition->getName(), function () use ($definition, $parameters) { $resolver = $this->getDefinitionResolver($definition); return $resolver->resolve($definition, $parameters); }); } /** * Check if a definition can be resolved. * * @param DefinitionInterface $definition object that defines how the value should be obtained * @param array $parameters optional parameters to use to build the entry */ public function isResolvable(DefinitionInterface $definition, array $parameters = []): bool { if ($definition instanceof SelfResolvingDefinitionInterface) { return $definition->isResolvable($this->container); } $guard = DepthGuard::getInstance(); return $guard->call($definition->getName(), function () use ($definition, $parameters) { $resolver = $this->getDefinitionResolver($definition); return $resolver->isResolvable($definition, $parameters); }); } /** * Returns a resolver capable of handling the given definition. * * @throws RuntimeException no definition resolver was found for this type of definition */ private function getDefinitionResolver(DefinitionInterface $definition): ResolverInterface { switch (true) { case $definition instanceof ObjectDefinition: if (! $this->objectResolver) { $this->objectResolver = new ObjectResolver($this->container, $this); } return $this->objectResolver; case $definition instanceof FactoryDefinition: if (! $this->factoryResolver) { $this->factoryResolver = new FactoryResolver($this->container, $this); } return $this->factoryResolver; default: throw new RuntimeException('No definition resolver was configured for definition of type ' . get_class($definition)); } } } src/Resolver/ResolverInterface.php000064400000002265151360552510013277 0ustar00