Parse and validate AT Protocol Lexicons with DTO generation for Laravel
at main 3.3 kB view raw
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}