123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313 |
- <?php
- declare(strict_types=1);
- /**
- * This file is part of Hyperf.
- *
- * @link https://www.hyperf.io
- * @document https://hyperf.wiki
- * @contact group@hyperf.io
- * @license https://github.com/hyperf/hyperf/blob/master/LICENSE
- */
- namespace Hyperf\Di\Annotation;
- use Hyperf\Config\ProviderConfig;
- use Hyperf\Di\Aop\ProxyManager;
- use Hyperf\Di\Exception\DirectoryNotExistException;
- use Hyperf\Di\MetadataCollector;
- use Hyperf\Di\ReflectionManager;
- use Hyperf\Di\ScanHandler\ScanHandlerInterface;
- use Hyperf\Support\Composer;
- use Hyperf\Support\Filesystem\Filesystem;
- use ReflectionClass;
- class Scanner
- {
- protected Filesystem $filesystem;
- protected string $path = BASE_PATH . '/runtime/container/scan.cache';
- public function __construct(protected ScanConfig $scanConfig, protected ScanHandlerInterface $handler)
- {
- $this->filesystem = new Filesystem();
- }
- public function collect(AnnotationReader $reader, ReflectionClass $reflection): void
- {
- $className = $reflection->getName();
- if ($path = $this->scanConfig->getClassMap()[$className] ?? null) {
- if ($reflection->getFileName() !== $path) {
- // When the original class is dynamically replaced, the original class should not be collected.
- return;
- }
- }
- // Parse class annotations
- foreach ($reader->getAttributes($reflection) as $classAnnotation) {
- if ($classAnnotation instanceof AnnotationInterface) {
- $classAnnotation->collectClass($className);
- }
- }
- // Parse properties annotations
- foreach ($reflection->getProperties() as $property) {
- foreach ($reader->getAttributes($property) as $propertyAnnotation) {
- if ($propertyAnnotation instanceof AnnotationInterface) {
- $propertyAnnotation->collectProperty($className, $property->getName());
- }
- }
- }
- // Parse methods annotations
- foreach ($reflection->getMethods() as $method) {
- foreach ($reader->getAttributes($method) as $methodAnnotation) {
- if ($methodAnnotation instanceof AnnotationInterface) {
- $methodAnnotation->collectMethod($className, $method->getName());
- }
- }
- }
- // Parse class constants annotations
- foreach ($reflection->getReflectionConstants() as $classConstant) {
- foreach ($reader->getAttributes($classConstant) as $constantAnnotation) {
- if ($constantAnnotation instanceof AnnotationInterface) {
- $constantAnnotation->collectClassConstant($className, $classConstant->getName());
- }
- }
- }
- unset($reflection);
- }
- public function scan(array $classMap = [], string $proxyDir = ''): array
- {
- $paths = $this->scanConfig->getPaths();
- $collectors = $this->scanConfig->getCollectors();
- if (! $paths) {
- return [];
- }
- $lastCacheModified = file_exists($this->path) ? $this->filesystem->lastModified($this->path) : 0;
- if ($lastCacheModified > 0 && $this->scanConfig->isCacheable()) {
- return $this->deserializeCachedScanData($collectors);
- }
- $scanned = $this->handler->scan();
- if ($scanned->isScanned()) {
- return $this->deserializeCachedScanData($collectors);
- }
- $this->deserializeCachedScanData($collectors);
- $annotationReader = new AnnotationReader($this->scanConfig->getIgnoreAnnotations());
- $paths = $this->normalizeDir($paths);
- $classes = ReflectionManager::getAllClasses($paths);
- $this->clearRemovedClasses($collectors, $classes);
- $reflectionClassMap = [];
- foreach ($classes as $className => $reflectionClass) {
- $reflectionClassMap[$className] = $reflectionClass->getFileName();
- if ($this->filesystem->lastModified($reflectionClass->getFileName()) >= $lastCacheModified) {
- /** @var MetadataCollector $collector */
- foreach ($collectors as $collector) {
- $collector::clear($className);
- }
- $this->collect($annotationReader, $reflectionClass);
- }
- }
- $this->loadAspects($lastCacheModified);
- $data = [];
- /** @var MetadataCollector|string $collector */
- foreach ($collectors as $collector) {
- $data[$collector] = $collector::serialize();
- }
- // Get the class map of Composer loader
- $classMap = array_merge($reflectionClassMap, $classMap);
- $proxyManager = new ProxyManager($classMap, $proxyDir);
- $proxies = $proxyManager->getProxies();
- $aspectClasses = $proxyManager->getAspectClasses();
- $this->putCache($this->path, serialize([$data, $proxies, $aspectClasses]));
- exit;
- }
- /**
- * Normalizes given directory names by removing directory not exist.
- * @throws DirectoryNotExistException
- */
- public function normalizeDir(array $paths): array
- {
- $result = [];
- foreach ($paths as $path) {
- if (is_dir($path)) {
- $result[] = $path;
- }
- }
- if ($paths && ! $result) {
- throw new DirectoryNotExistException('The scanned directory does not exist');
- }
- return $result;
- }
- protected function deserializeCachedScanData(array $collectors): array
- {
- if (! file_exists($this->path)) {
- return [];
- }
- [$data, $proxies] = unserialize(file_get_contents($this->path));
- foreach ($data as $collector => $deserialized) {
- /** @var MetadataCollector $collector */
- if (in_array($collector, $collectors)) {
- $collector::deserialize($deserialized);
- }
- }
- return $proxies;
- }
- /**
- * @param ReflectionClass[] $reflections
- */
- protected function clearRemovedClasses(array $collectors, array $reflections): void
- {
- $path = BASE_PATH . '/runtime/container/classes.cache';
- $classes = array_keys($reflections);
- $data = [];
- if ($this->filesystem->exists($path)) {
- $data = unserialize($this->filesystem->get($path));
- }
- $this->putCache($path, serialize($classes));
- $removed = array_diff($data, $classes);
- foreach ($removed as $class) {
- /** @var MetadataCollector $collector */
- foreach ($collectors as $collector) {
- $collector::clear($class);
- }
- }
- }
- protected function putCache(string $path, $data)
- {
- if (! $this->filesystem->isDirectory($dir = dirname($path))) {
- $this->filesystem->makeDirectory($dir, 0755, true);
- }
- $this->filesystem->put($path, $data);
- }
- /**
- * Load aspects to AspectCollector by configuration files and ConfigProvider.
- */
- protected function loadAspects(int $lastCacheModified): void
- {
- $configDir = $this->scanConfig->getConfigDir();
- if (! $configDir) {
- return;
- }
- $aspectsPath = $configDir . '/autoload/aspects.php';
- $basePath = $configDir . '/config.php';
- $aspects = file_exists($aspectsPath) ? include $aspectsPath : [];
- $baseConfig = file_exists($basePath) ? include $basePath : [];
- $providerConfig = [];
- if (class_exists(ProviderConfig::class)) {
- $providerConfig = ProviderConfig::load();
- }
- if (! isset($aspects) || ! is_array($aspects)) {
- $aspects = [];
- }
- if (! isset($baseConfig['aspects']) || ! is_array($baseConfig['aspects'])) {
- $baseConfig['aspects'] = [];
- }
- if (! isset($providerConfig['aspects']) || ! is_array($providerConfig['aspects'])) {
- $providerConfig['aspects'] = [];
- }
- $aspects = array_merge($providerConfig['aspects'], $baseConfig['aspects'], $aspects);
- [$removed, $changed] = $this->getChangedAspects($aspects, $lastCacheModified);
- // When the aspect removed from config, it should be removed from AspectCollector.
- foreach ($removed as $aspect) {
- AspectCollector::clear($aspect);
- }
- foreach ($aspects as $key => $value) {
- if (is_numeric($key)) {
- $aspect = $value;
- $priority = null;
- } else {
- $aspect = $key;
- $priority = (int) $value;
- }
- if (! in_array($aspect, $changed)) {
- continue;
- }
- [$instanceClasses, $instanceAnnotations, $instancePriority] = AspectLoader::load($aspect);
- $classes = $instanceClasses ?: [];
- // Annotations
- $annotations = $instanceAnnotations ?: [];
- // Priority
- $priority = $priority ?: ($instancePriority ?? null);
- // Save the metadata to AspectCollector
- AspectCollector::setAround($aspect, $classes, $annotations, $priority);
- }
- }
- protected function getChangedAspects(array $aspects, int $lastCacheModified): array
- {
- $path = BASE_PATH . '/runtime/container/aspects.cache';
- $classes = [];
- foreach ($aspects as $key => $value) {
- if (is_numeric($key)) {
- $classes[] = $value;
- } else {
- $classes[] = $key;
- }
- }
- $data = [];
- if ($this->filesystem->exists($path)) {
- $data = unserialize($this->filesystem->get($path));
- }
- $this->putCache($path, serialize($classes));
- $diff = array_diff($data, $classes);
- $changed = array_diff($classes, $data);
- $removed = [];
- foreach ($diff as $item) {
- $annotation = AnnotationCollector::getClassAnnotation($item, Aspect::class);
- if (is_null($annotation)) {
- $removed[] = $item;
- }
- }
- foreach ($classes as $class) {
- $file = Composer::getLoader()->findFile($class);
- if ($file === false) {
- echo sprintf('Skip class %s, because it does not exist in composer class loader.', $class) . PHP_EOL;
- continue;
- }
- if ($lastCacheModified <= $this->filesystem->lastModified($file)) {
- $changed[] = $class;
- }
- }
- return [
- array_values(array_unique($removed)),
- array_values(array_unique($changed)),
- ];
- }
- }
|