1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace LaravelUi5\OData\Service\Discovery;
6:
7: use LaravelUi5\OData\Edm\Contracts\Annotation\AnnotationInterface;
8: use LaravelUi5\OData\Edm\Contracts\Annotation\TypedAnnotationInterface;
9: use ReflectionAttribute;
10: use ReflectionClass;
11: use ReflectionParameter;
12: use ReflectionProperty;
13:
14: /**
15: * Reads PHP #[Attribute] annotations that implement TypedAnnotationInterface
16: * from Reflection objects and returns them as AnnotationInterface instances.
17: *
18: * This is the bridge between PHP attribute syntax and the Edm annotation model.
19: * It has no framework dependencies and produces no side effects — callers are
20: * responsible for attaching the returned annotations to their EDM model objects.
21: *
22: * Usage:
23: * $annotations = (new AttributeReader)->readClass(new ReflectionClass($class));
24: * // pass $annotations into the EDM model object constructor via HasAnnotations
25: *
26: * Attributes that do not implement TypedAnnotationInterface are silently skipped;
27: * they belong to other framework layers (routing, ORM, validation, etc.).
28: *
29: * @see TypedAnnotationInterface
30: * @see AnnotationInterface
31: * @see OData CSDL XML v4.01 §14.2 (Annotation)
32: */
33: final readonly class AttributeReader
34: {
35: /**
36: * Reads class-level vocabulary annotations.
37: * Covers: EntityType, ComplexType, EnumType, EntitySet, Singleton, EntityContainer.
38: *
39: * @return list<AnnotationInterface>
40: */
41: public function readClass(ReflectionClass $reflection): array
42: {
43: return $this->extract(
44: $reflection->getAttributes(TypedAnnotationInterface::class, ReflectionAttribute::IS_INSTANCEOF),
45: );
46: }
47:
48: /**
49: * Reads property-level vocabulary annotations.
50: * Covers: Property, NavigationProperty.
51: *
52: * @return list<AnnotationInterface>
53: */
54: public function readProperty(ReflectionProperty $reflection): array
55: {
56: return $this->extract(
57: $reflection->getAttributes(TypedAnnotationInterface::class, ReflectionAttribute::IS_INSTANCEOF),
58: );
59: }
60:
61: /**
62: * Reads parameter-level vocabulary annotations.
63: * Covers: FunctionParameter.
64: *
65: * @return list<AnnotationInterface>
66: */
67: public function readParameter(ReflectionParameter $reflection): array
68: {
69: return $this->extract(
70: $reflection->getAttributes(TypedAnnotationInterface::class, ReflectionAttribute::IS_INSTANCEOF),
71: );
72: }
73:
74: /**
75: * @param ReflectionAttribute[] $attributes pre-filtered to TypedAnnotationInterface
76: * @return list<AnnotationInterface>
77: */
78: private function extract(array $attributes): array
79: {
80: return array_values(array_map(
81: static fn(ReflectionAttribute $a): AnnotationInterface => $a->newInstance()->toAnnotation(),
82: $attributes,
83: ));
84: }
85: }
86: