sunrise-php /
http-router
| 1 | <?php |
||
| 2 | |||
| 3 | /** |
||
| 4 | * It's free open-source software released under the MIT License. |
||
| 5 | * |
||
| 6 | * @author Anatoly Nekhay <[email protected]> |
||
| 7 | * @copyright Copyright (c) 2018, Anatoly Nekhay |
||
| 8 | * @license https://github.com/sunrise-php/http-router/blob/master/LICENSE |
||
| 9 | * @link https://github.com/sunrise-php/http-router |
||
| 10 | */ |
||
| 11 | |||
| 12 | declare(strict_types=1); |
||
| 13 | |||
| 14 | namespace Sunrise\Http\Router\Loader; |
||
| 15 | |||
| 16 | use Generator; |
||
| 17 | use InvalidArgumentException; |
||
| 18 | use Psr\Http\Server\RequestHandlerInterface; |
||
| 19 | use Psr\SimpleCache\CacheException; |
||
| 20 | use Psr\SimpleCache\CacheInterface; |
||
| 21 | use ReflectionAttribute; |
||
| 22 | use ReflectionClass; |
||
| 23 | use ReflectionException; |
||
| 24 | use ReflectionMethod; |
||
| 25 | use Sunrise\Http\Router\Annotation\Consumes; |
||
| 26 | use Sunrise\Http\Router\Annotation\DefaultAttribute; |
||
| 27 | use Sunrise\Http\Router\Annotation\Deprecated; |
||
| 28 | use Sunrise\Http\Router\Annotation\Description; |
||
| 29 | use Sunrise\Http\Router\Annotation\Method; |
||
| 30 | use Sunrise\Http\Router\Annotation\Middleware; |
||
| 31 | use Sunrise\Http\Router\Annotation\NamePrefix; |
||
| 32 | use Sunrise\Http\Router\Annotation\PathPostfix; |
||
| 33 | use Sunrise\Http\Router\Annotation\PathPrefix; |
||
| 34 | use Sunrise\Http\Router\Annotation\Pattern; |
||
| 35 | use Sunrise\Http\Router\Annotation\Priority; |
||
| 36 | use Sunrise\Http\Router\Annotation\Produces; |
||
| 37 | use Sunrise\Http\Router\Annotation\Route as Descriptor; |
||
| 38 | use Sunrise\Http\Router\Annotation\Summary; |
||
| 39 | use Sunrise\Http\Router\Annotation\Tag; |
||
| 40 | use Sunrise\Http\Router\Helper\ClassFinder; |
||
| 41 | use Sunrise\Http\Router\Helper\ReflectorHelper; |
||
| 42 | use Sunrise\Http\Router\Helper\RouteCompiler; |
||
| 43 | use Sunrise\Http\Router\Route; |
||
| 44 | |||
| 45 | use function array_map; |
||
| 46 | use function class_exists; |
||
| 47 | use function implode; |
||
| 48 | use function is_dir; |
||
| 49 | use function is_file; |
||
| 50 | use function sprintf; |
||
| 51 | use function strtoupper; |
||
| 52 | use function usort; |
||
| 53 | |||
| 54 | /** |
||
| 55 | * @since 2.10.0 |
||
| 56 | */ |
||
| 57 | final class DescriptorLoader implements DescriptorLoaderInterface |
||
| 58 | { |
||
| 59 | /** |
||
| 60 | * @since 3.0.0 |
||
| 61 | */ |
||
| 62 | public const DESCRIPTORS_CACHE_KEY = 'sunrise_http_router_descriptors'; |
||
| 63 | |||
| 64 | 55 | public function __construct( |
|
| 65 | /** @var array<array-key, string> */ |
||
| 66 | private readonly array $resources, |
||
| 67 | private readonly ?CacheInterface $cache = null, |
||
| 68 | ) { |
||
| 69 | 55 | } |
|
| 70 | |||
| 71 | /** |
||
| 72 | * @inheritDoc |
||
| 73 | * |
||
| 74 | * @throws CacheException |
||
| 75 | * @throws InvalidArgumentException |
||
| 76 | * @throws ReflectionException |
||
| 77 | */ |
||
| 78 | 54 | public function load(): Generator |
|
| 79 | { |
||
| 80 | 54 | foreach ($this->getDescriptors() as $descriptor) { |
|
| 81 | 50 | yield $descriptor->name => new Route( |
|
| 82 | 50 | name: $descriptor->name, |
|
| 83 | 50 | path: $descriptor->path, |
|
| 84 | 50 | requestHandler: $descriptor->holder, |
|
| 85 | 50 | patterns: $descriptor->patterns, |
|
| 86 | 50 | methods: $descriptor->methods, |
|
| 87 | 50 | attributes: $descriptor->attributes, |
|
| 88 | 50 | middlewares: $descriptor->middlewares, |
|
| 89 | 50 | consumes: $descriptor->consumes, |
|
| 90 | 50 | produces: $descriptor->produces, |
|
| 91 | 50 | tags: $descriptor->tags, |
|
| 92 | 50 | summary: $descriptor->summary, |
|
| 93 | 50 | description: $descriptor->description, |
|
| 94 | 50 | isDeprecated: $descriptor->isDeprecated, |
|
| 95 | 50 | isApiRoute: $descriptor->isApiRoute, |
|
| 96 | 50 | pattern: $descriptor->pattern, |
|
| 97 | 50 | ); |
|
| 98 | } |
||
| 99 | } |
||
| 100 | |||
| 101 | /** |
||
| 102 | * @throws CacheException |
||
| 103 | */ |
||
| 104 | 1 | public function clearCache(): void |
|
| 105 | { |
||
| 106 | 1 | $this->cache?->delete(self::DESCRIPTORS_CACHE_KEY); |
|
| 107 | } |
||
| 108 | |||
| 109 | /** |
||
| 110 | * @return list<Descriptor> |
||
|
0 ignored issues
–
show
|
|||
| 111 | * |
||
| 112 | * @throws CacheException |
||
| 113 | * @throws InvalidArgumentException |
||
| 114 | * @throws ReflectionException |
||
| 115 | */ |
||
| 116 | 54 | private function getDescriptors(): array |
|
| 117 | { |
||
| 118 | /** @var list<Descriptor>|null $descriptors */ |
||
| 119 | 54 | $descriptors = $this->cache?->get(self::DESCRIPTORS_CACHE_KEY); |
|
| 120 | 54 | if ($descriptors !== null) { |
|
| 121 | 1 | return $descriptors; |
|
|
0 ignored issues
–
show
|
|||
| 122 | } |
||
| 123 | |||
| 124 | 53 | $descriptors = []; |
|
| 125 | 53 | foreach ($this->resources as $resource) { |
|
| 126 | 53 | foreach (self::getResourceDescriptors($resource) as $descriptor) { |
|
| 127 | 49 | $descriptors[] = $descriptor; |
|
| 128 | } |
||
| 129 | } |
||
| 130 | |||
| 131 | 52 | usort($descriptors, static fn(Descriptor $a, Descriptor $b): int => $b->priority <=> $a->priority); |
|
| 132 | |||
| 133 | 52 | $this->cache?->set(self::DESCRIPTORS_CACHE_KEY, $descriptors); |
|
| 134 | |||
| 135 | 52 | return $descriptors; |
|
| 136 | } |
||
| 137 | |||
| 138 | /** |
||
| 139 | * @return Generator<int, Descriptor> |
||
| 140 | * |
||
| 141 | * @throws InvalidArgumentException |
||
| 142 | * @throws ReflectionException |
||
| 143 | */ |
||
| 144 | 53 | private static function getResourceDescriptors(string $resource): Generator |
|
| 145 | { |
||
| 146 | 53 | if (is_dir($resource)) { |
|
| 147 | 4 | foreach (ClassFinder::getDirClasses($resource) as $class) { |
|
| 148 | 4 | yield from self::getClassDescriptors($class); |
|
| 149 | } |
||
| 150 | |||
| 151 | 4 | return; |
|
| 152 | } |
||
| 153 | |||
| 154 | 49 | if (is_file($resource)) { |
|
| 155 | 1 | foreach (ClassFinder::getFileClasses($resource) as $class) { |
|
| 156 | 1 | yield from self::getClassDescriptors($class); |
|
| 157 | } |
||
| 158 | |||
| 159 | 1 | return; |
|
| 160 | } |
||
| 161 | |||
| 162 | 48 | if (class_exists($resource)) { |
|
| 163 | 47 | $class = new ReflectionClass($resource); |
|
| 164 | 47 | yield from self::getClassDescriptors($class); |
|
| 165 | 47 | return; |
|
| 166 | } |
||
| 167 | |||
| 168 | 1 | throw new InvalidArgumentException(sprintf( |
|
| 169 | 1 | 'The loader "%s" only accepts directory, file or class names; ' . |
|
| 170 | 1 | 'however, the resource "%s" is not one of them.', |
|
| 171 | 1 | self::class, |
|
| 172 | 1 | $resource, |
|
| 173 | 1 | )); |
|
| 174 | } |
||
| 175 | |||
| 176 | /** |
||
| 177 | * @param ReflectionClass<object> $class |
||
| 178 | * |
||
| 179 | * @return Generator<int, Descriptor> |
||
| 180 | * |
||
| 181 | * @throws InvalidArgumentException |
||
| 182 | */ |
||
| 183 | 52 | private static function getClassDescriptors(ReflectionClass $class): Generator |
|
| 184 | { |
||
| 185 | 52 | if (!$class->isInstantiable()) { |
|
| 186 | 4 | return; |
|
| 187 | } |
||
| 188 | |||
| 189 | 52 | if ($class->isSubclassOf(RequestHandlerInterface::class)) { |
|
| 190 | 6 | $descriptor = self::getClassOrMethodDescriptor($class); |
|
| 191 | 6 | if ($descriptor !== null) { |
|
| 192 | 6 | yield $descriptor; |
|
| 193 | } |
||
| 194 | } |
||
| 195 | |||
| 196 | 52 | foreach ($class->getMethods() as $method) { |
|
| 197 | 52 | if (!$method->isPublic() || $method->isStatic()) { |
|
| 198 | 4 | continue; |
|
| 199 | } |
||
| 200 | |||
| 201 | 49 | $descriptor = self::getClassOrMethodDescriptor($method); |
|
| 202 | 49 | if ($descriptor !== null) { |
|
| 203 | 47 | yield $descriptor; |
|
| 204 | } |
||
| 205 | } |
||
| 206 | } |
||
| 207 | |||
| 208 | /** |
||
| 209 | * @param ReflectionClass<object>|ReflectionMethod $classOrMethod |
||
| 210 | * |
||
| 211 | * @throws InvalidArgumentException |
||
| 212 | */ |
||
| 213 | 49 | private static function getClassOrMethodDescriptor(ReflectionClass|ReflectionMethod $classOrMethod): ?Descriptor |
|
| 214 | { |
||
| 215 | /** @var list<ReflectionAttribute<Descriptor>> $annotations */ |
||
| 216 | 49 | $annotations = $classOrMethod->getAttributes(Descriptor::class, ReflectionAttribute::IS_INSTANCEOF); |
|
| 217 | 49 | if ($annotations === []) { |
|
|
0 ignored issues
–
show
|
|||
| 218 | 6 | return null; |
|
| 219 | } |
||
| 220 | |||
| 221 | 49 | $descriptor = $annotations[0]->newInstance(); |
|
| 222 | |||
| 223 | 49 | foreach (ReflectorHelper::getAncestry($classOrMethod) as $member) { |
|
| 224 | 49 | self::enrichDescriptorFromClassOrMethod($descriptor, $member); |
|
| 225 | } |
||
| 226 | |||
| 227 | 49 | self::completeDescriptor($descriptor, $classOrMethod); |
|
| 228 | |||
| 229 | 49 | return $descriptor; |
|
| 230 | } |
||
| 231 | |||
| 232 | /** |
||
| 233 | * @param ReflectionClass<object>|ReflectionMethod $classOrMethod |
||
| 234 | * |
||
| 235 | * @throws InvalidArgumentException |
||
| 236 | */ |
||
| 237 | 49 | private static function enrichDescriptorFromClassOrMethod( |
|
| 238 | Descriptor $descriptor, |
||
| 239 | ReflectionClass|ReflectionMethod $classOrMethod, |
||
| 240 | ): void { |
||
| 241 | /** @var list<ReflectionAttribute<NamePrefix>> $annotations */ |
||
| 242 | 49 | $annotations = $classOrMethod->getAttributes(NamePrefix::class); |
|
| 243 | 49 | if (isset($annotations[0])) { |
|
| 244 | 7 | $annotation = $annotations[0]->newInstance(); |
|
| 245 | 7 | $descriptor->namePrefixes[] = $annotation->value; |
|
| 246 | } |
||
| 247 | |||
| 248 | /** @var list<ReflectionAttribute<PathPrefix>> $annotations */ |
||
| 249 | 49 | $annotations = $classOrMethod->getAttributes(PathPrefix::class); |
|
| 250 | 49 | if (isset($annotations[0])) { |
|
| 251 | 7 | $annotation = $annotations[0]->newInstance(); |
|
| 252 | 7 | $descriptor->pathPrefixes[] = $annotation->value; |
|
| 253 | } |
||
| 254 | |||
| 255 | /** @var list<ReflectionAttribute<PathPostfix>> $annotations */ |
||
| 256 | 49 | $annotations = $classOrMethod->getAttributes(PathPostfix::class); |
|
| 257 | 49 | if (isset($annotations[0])) { |
|
| 258 | 1 | $annotation = $annotations[0]->newInstance(); |
|
| 259 | 1 | $descriptor->path .= $annotation->value; |
|
| 260 | } |
||
| 261 | |||
| 262 | /** @var list<ReflectionAttribute<Pattern>> $annotations */ |
||
| 263 | 49 | $annotations = $classOrMethod->getAttributes(Pattern::class, ReflectionAttribute::IS_INSTANCEOF); |
|
| 264 | 49 | foreach ($annotations as $annotation) { |
|
| 265 | 1 | $annotation = $annotation->newInstance(); |
|
| 266 | 1 | $descriptor->patterns[$annotation->variableName] = $annotation->value; |
|
| 267 | } |
||
| 268 | |||
| 269 | /** @var list<ReflectionAttribute<Method>> $annotations */ |
||
| 270 | 49 | $annotations = $classOrMethod->getAttributes(Method::class, ReflectionAttribute::IS_INSTANCEOF); |
|
| 271 | 49 | foreach ($annotations as $annotation) { |
|
| 272 | 9 | $annotation = $annotation->newInstance(); |
|
| 273 | 9 | foreach ($annotation->values as $value) { |
|
| 274 | 9 | $descriptor->methods[] = $value; |
|
| 275 | } |
||
| 276 | } |
||
| 277 | |||
| 278 | /** @var list<ReflectionAttribute<DefaultAttribute>> $annotations */ |
||
| 279 | 49 | $annotations = $classOrMethod->getAttributes(DefaultAttribute::class); |
|
| 280 | 49 | foreach ($annotations as $annotation) { |
|
| 281 | 1 | $annotation = $annotation->newInstance(); |
|
| 282 | 1 | $descriptor->attributes[$annotation->name] = $annotation->value; |
|
| 283 | } |
||
| 284 | |||
| 285 | /** @var list<ReflectionAttribute<Middleware>> $annotations */ |
||
| 286 | 49 | $annotations = $classOrMethod->getAttributes(Middleware::class); |
|
| 287 | 49 | foreach ($annotations as $annotation) { |
|
| 288 | 1 | $annotation = $annotation->newInstance(); |
|
| 289 | 1 | foreach ($annotation->values as $value) { |
|
| 290 | 1 | $descriptor->middlewares[] = $value; |
|
| 291 | } |
||
| 292 | } |
||
| 293 | |||
| 294 | /** @var list<ReflectionAttribute<Consumes>> $annotations */ |
||
| 295 | 49 | $annotations = $classOrMethod->getAttributes(Consumes::class, ReflectionAttribute::IS_INSTANCEOF); |
|
| 296 | 49 | foreach ($annotations as $annotation) { |
|
| 297 | 7 | $annotation = $annotation->newInstance(); |
|
| 298 | 7 | foreach ($annotation->values as $value) { |
|
| 299 | 7 | $descriptor->consumes[] = $value; |
|
| 300 | } |
||
| 301 | } |
||
| 302 | |||
| 303 | /** @var list<ReflectionAttribute<Produces>> $annotations */ |
||
| 304 | 49 | $annotations = $classOrMethod->getAttributes(Produces::class, ReflectionAttribute::IS_INSTANCEOF); |
|
| 305 | 49 | foreach ($annotations as $annotation) { |
|
| 306 | 7 | $annotation = $annotation->newInstance(); |
|
| 307 | 7 | foreach ($annotation->values as $value) { |
|
| 308 | 7 | $descriptor->produces[] = $value; |
|
| 309 | } |
||
| 310 | } |
||
| 311 | |||
| 312 | /** @var list<ReflectionAttribute<Tag>> $annotations */ |
||
| 313 | 49 | $annotations = $classOrMethod->getAttributes(Tag::class, ReflectionAttribute::IS_INSTANCEOF); |
|
| 314 | 49 | foreach ($annotations as $annotation) { |
|
| 315 | 7 | $annotation = $annotation->newInstance(); |
|
| 316 | 7 | foreach ($annotation->values as $value) { |
|
| 317 | 7 | $descriptor->tags[] = $value; |
|
| 318 | } |
||
| 319 | } |
||
| 320 | |||
| 321 | /** @var list<ReflectionAttribute<Summary>> $annotations */ |
||
| 322 | 49 | $annotations = $classOrMethod->getAttributes(Summary::class); |
|
| 323 | 49 | foreach ($annotations as $annotation) { |
|
| 324 | 7 | $annotation = $annotation->newInstance(); |
|
| 325 | 7 | $descriptor->summary .= $annotation->value; |
|
| 326 | } |
||
| 327 | |||
| 328 | /** @var list<ReflectionAttribute<Description>> $annotations */ |
||
| 329 | 49 | $annotations = $classOrMethod->getAttributes(Description::class); |
|
| 330 | 49 | foreach ($annotations as $annotation) { |
|
| 331 | 1 | $annotation = $annotation->newInstance(); |
|
| 332 | 1 | $descriptor->description .= $annotation->value; |
|
| 333 | } |
||
| 334 | |||
| 335 | /** @var list<ReflectionAttribute<Deprecated>> $annotations */ |
||
| 336 | 49 | $annotations = $classOrMethod->getAttributes(Deprecated::class); |
|
| 337 | 49 | if (isset($annotations[0])) { |
|
| 338 | 5 | $descriptor->isDeprecated = true; |
|
| 339 | } |
||
| 340 | |||
| 341 | /** @var list<ReflectionAttribute<Priority>> $annotations */ |
||
| 342 | 49 | $annotations = $classOrMethod->getAttributes(Priority::class); |
|
| 343 | 49 | if (isset($annotations[0])) { |
|
| 344 | 1 | $annotation = $annotations[0]->newInstance(); |
|
| 345 | 1 | $descriptor->priority = $annotation->value; |
|
| 346 | } |
||
| 347 | } |
||
| 348 | |||
| 349 | /** |
||
| 350 | * @param ReflectionClass<object>|ReflectionMethod $holder |
||
| 351 | * |
||
| 352 | * @throws InvalidArgumentException |
||
| 353 | */ |
||
| 354 | 49 | private static function completeDescriptor(Descriptor $descriptor, ReflectionClass|ReflectionMethod $holder): void |
|
| 355 | { |
||
| 356 | 49 | $descriptor->holder = $holder instanceof ReflectionClass ? $holder->name : [$holder->class, $holder->name]; |
|
| 357 | 49 | $descriptor->name = implode($descriptor->namePrefixes) . $descriptor->name; |
|
| 358 | 49 | $descriptor->path = implode($descriptor->pathPrefixes) . $descriptor->path; |
|
| 359 | 49 | $descriptor->methods = array_map(strtoupper(...), $descriptor->methods); |
|
|
0 ignored issues
–
show
The type
strtoupper was not found. Maybe you did not declare it correctly or list all dependencies?
The issue could also be caused by a filter entry in the build configuration.
If the path has been excluded in your configuration, e.g. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths Loading history...
|
|||
| 360 | 49 | $descriptor->pattern = RouteCompiler::compileRoute($descriptor->path, $descriptor->patterns); |
|
| 361 | } |
||
| 362 | } |
||
| 363 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths