Parse and validate AT Protocol Lexicons with DTO generation for Laravel
1<?php
2
3namespace SocialDept\AtpSchema\Data\Types;
4
5use SocialDept\AtpSchema\Data\TypeDefinition;
6use SocialDept\AtpSchema\Exceptions\RecordValidationException;
7
8class ArrayType extends TypeDefinition
9{
10 /**
11 * Type of array items.
12 */
13 public readonly ?TypeDefinition $items;
14
15 /**
16 * Minimum array length.
17 */
18 public readonly ?int $minLength;
19
20 /**
21 * Maximum array length.
22 */
23 public readonly ?int $maxLength;
24
25 /**
26 * Create a new ArrayType.
27 */
28 public function __construct(
29 ?TypeDefinition $items = null,
30 ?int $minLength = null,
31 ?int $maxLength = null,
32 ?string $description = null
33 ) {
34 parent::__construct('array', $description);
35
36 $this->items = $items;
37 $this->minLength = $minLength;
38 $this->maxLength = $maxLength;
39 }
40
41 /**
42 * Create from array data.
43 */
44 public static function fromArray(array $data): self
45 {
46 // Items will be parsed by TypeParser, this is just a placeholder
47 return new self(
48 items: null,
49 minLength: $data['minLength'] ?? null,
50 maxLength: $data['maxLength'] ?? null,
51 description: $data['description'] ?? null
52 );
53 }
54
55 /**
56 * Convert to array.
57 */
58 public function toArray(): array
59 {
60 $array = ['type' => $this->type];
61
62 if ($this->description !== null) {
63 $array['description'] = $this->description;
64 }
65
66 if ($this->items !== null) {
67 $array['items'] = $this->items->toArray();
68 }
69
70 if ($this->minLength !== null) {
71 $array['minLength'] = $this->minLength;
72 }
73
74 if ($this->maxLength !== null) {
75 $array['maxLength'] = $this->maxLength;
76 }
77
78 return $array;
79 }
80
81 /**
82 * Validate a value against this type definition.
83 */
84 public function validate(mixed $value, string $path = ''): void
85 {
86 if (! is_array($value)) {
87 throw RecordValidationException::invalidType($path, 'array', gettype($value));
88 }
89
90 // Check if it's a sequential array
91 if (! array_is_list($value)) {
92 throw RecordValidationException::invalidValue($path, 'must be a sequential array');
93 }
94
95 $length = count($value);
96
97 // Validate length
98 if ($this->minLength !== null && $length < $this->minLength) {
99 throw RecordValidationException::invalidValue($path, "must have at least {$this->minLength} items");
100 }
101
102 if ($this->maxLength !== null && $length > $this->maxLength) {
103 throw RecordValidationException::invalidValue($path, "must have at most {$this->maxLength} items");
104 }
105
106 // Validate items
107 if ($this->items !== null) {
108 foreach ($value as $index => $item) {
109 $itemPath = "{$path}[{$index}]";
110 $this->items->validate($item, $itemPath);
111 }
112 }
113 }
114
115 /**
116 * Set items type after construction.
117 */
118 public function withItems(TypeDefinition $items): self
119 {
120 return new self(
121 items: $items,
122 minLength: $this->minLength,
123 maxLength: $this->maxLength,
124 description: $this->description
125 );
126 }
127}