1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace LaravelUi5\OData\Edm\Container;
6:
7: use LaravelUi5\OData\Edm\Contracts\Annotation\AnnotationInterface;
8: use LaravelUi5\OData\Edm\Contracts\Type\EnumMemberInterface;
9: use LaravelUi5\OData\Edm\Contracts\Type\EnumTypeInterface;
10: use LaravelUi5\OData\Edm\EdmPrimitiveType;
11: use LaravelUi5\OData\Edm\HasAnnotations;
12:
13: final readonly class EnumType implements EnumTypeInterface
14: {
15: use HasAnnotations;
16:
17: /**
18: * @param list<EnumMemberInterface> $members
19: * @param list<AnnotationInterface> $annotations
20: */
21: public function __construct(
22: private string $namespace,
23: private string $name,
24: private EdmPrimitiveType $underlyingType = EdmPrimitiveType::Int32,
25: private bool $isFlags = false,
26: private array $members = [],
27: array $annotations = [],
28: ) {
29: $this->annotations = $annotations;
30: }
31:
32: /**
33: * Build an EnumType from a PHP int-backed enum class-string.
34: *
35: * The EDM short name is derived from the PHP short class name. The
36: * underlying type is fixed at Edm.Int32 — matches OData v4 default
37: * and covers all consumer values today. Members are emitted in
38: * declaration order using PHP case names (display labels are an
39: * i18n concern, not the engine's).
40: *
41: * String-backed and pure enums are rejected — OData v4 EnumTypes
42: * are integer-keyed.
43: *
44: * @param class-string<\BackedEnum> $enumClass
45: */
46: public static function fromBackedEnum(string $namespace, string $enumClass): self
47: {
48: if (!enum_exists($enumClass)) {
49: throw new \InvalidArgumentException(sprintf(
50: 'EnumType::fromBackedEnum expected a PHP enum class-string, got "%s".',
51: $enumClass,
52: ));
53: }
54:
55: $reflection = new \ReflectionEnum($enumClass);
56: $backingType = $reflection->getBackingType();
57:
58: if ($backingType === null) {
59: throw new \InvalidArgumentException(sprintf(
60: 'EnumType::fromBackedEnum expected a backed enum, got pure enum "%s".',
61: $enumClass,
62: ));
63: }
64:
65: if ((string) $backingType !== 'int') {
66: throw new \InvalidArgumentException(sprintf(
67: 'EnumType::fromBackedEnum expected an int-backed enum, got %s-backed "%s". '
68: . 'OData v4 EnumTypes are integer-keyed.',
69: (string) $backingType,
70: $enumClass,
71: ));
72: }
73:
74: $members = [];
75: foreach ($reflection->getCases() as $case) {
76: /** @var \ReflectionEnumBackedCase $case */
77: $members[] = new EnumMember($case->getName(), (int) $case->getBackingValue());
78: }
79:
80: return new self(
81: namespace: $namespace,
82: name: $reflection->getShortName(),
83: underlyingType: EdmPrimitiveType::Int32,
84: members: $members,
85: );
86: }
87:
88: public function getName(): string
89: {
90: return $this->name;
91: }
92:
93: public function getQualifiedName(): string
94: {
95: return $this->namespace . '.' . $this->name;
96: }
97:
98: public function getUnderlyingType(): EdmPrimitiveType
99: {
100: return $this->underlyingType;
101: }
102:
103: public function isFlags(): bool
104: {
105: return $this->isFlags;
106: }
107:
108: public function getMembers(): array
109: {
110: return $this->members;
111: }
112:
113: public function getMember(string $name): ?EnumMemberInterface
114: {
115: foreach ($this->members as $member) {
116: if ($member->getName() === $name) {
117: return $member;
118: }
119: }
120: return null;
121: }
122: }
123: