Parse and validate AT Protocol Lexicons with DTO generation for Laravel
at dev 7.0 kB view raw
1<?php 2 3namespace SocialDept\AtpSchema\Generator; 4 5class ModelMapper 6{ 7 /** 8 * Naming converter instance. 9 */ 10 protected NamingConverter $naming; 11 12 /** 13 * Type mapper instance. 14 */ 15 protected TypeMapper $typeMapper; 16 17 /** 18 * Create a new ModelMapper. 19 */ 20 public function __construct(?NamingConverter $naming = null, ?TypeMapper $typeMapper = null) 21 { 22 $this->naming = $naming ?? new NamingConverter(); 23 $this->typeMapper = $typeMapper ?? new TypeMapper($this->naming); 24 } 25 26 /** 27 * Generate toModel method body. 28 * 29 * @param array<string, array<string, mixed>> $properties 30 */ 31 public function generateToModelBody(array $properties, string $modelClass = 'Model'): string 32 { 33 if (empty($properties)) { 34 return " return new {$modelClass}();"; 35 } 36 37 $lines = []; 38 $lines[] = " return new {$modelClass}(["; 39 40 foreach ($properties as $name => $definition) { 41 $mapping = $this->generatePropertyToModel($name, $definition); 42 $lines[] = " '{$name}' => {$mapping},"; 43 } 44 45 $lines[] = ' ]);'; 46 47 return implode("\n", $lines); 48 } 49 50 /** 51 * Generate fromModel method body. 52 * 53 * @param array<string, array<string, mixed>> $properties 54 */ 55 public function generateFromModelBody(array $properties): string 56 { 57 if (empty($properties)) { 58 return ' return new static();'; 59 } 60 61 $lines = []; 62 $lines[] = ' return new static('; 63 64 foreach ($properties as $name => $definition) { 65 $mapping = $this->generatePropertyFromModel($name, $definition); 66 $lines[] = " {$name}: {$mapping},"; 67 } 68 69 // Remove trailing comma from last line 70 $lastIndex = count($lines) - 1; 71 $lines[$lastIndex] = rtrim($lines[$lastIndex], ','); 72 73 $lines[] = ' );'; 74 75 return implode("\n", $lines); 76 } 77 78 /** 79 * Generate property mapping to model. 80 * 81 * @param array<string, mixed> $definition 82 */ 83 protected function generatePropertyToModel(string $name, array $definition): string 84 { 85 $type = $definition['type'] ?? 'unknown'; 86 87 // Handle DateTime types 88 if ($type === 'string' && isset($definition['format']) && $definition['format'] === 'datetime') { 89 return "\$this->{$name}?->format('Y-m-d H:i:s')"; 90 } 91 92 // Handle blob types 93 if ($type === 'blob') { 94 return "\$this->{$name}?->toArray()"; 95 } 96 97 // Handle nested refs 98 if ($type === 'ref') { 99 return "\$this->{$name}?->toArray()"; 100 } 101 102 // Handle arrays of refs 103 if ($type === 'array' && isset($definition['items']['type']) && $definition['items']['type'] === 'ref') { 104 return "array_map(fn (\$item) => \$item->toArray(), \$this->{$name} ?? [])"; 105 } 106 107 // Handle arrays of objects 108 if ($type === 'array' && isset($definition['items']['type']) && $definition['items']['type'] === 'object') { 109 return "\$this->{$name} ?? []"; 110 } 111 112 // Simple property 113 return "\$this->{$name}"; 114 } 115 116 /** 117 * Generate property mapping from model. 118 * 119 * @param array<string, mixed> $definition 120 */ 121 protected function generatePropertyFromModel(string $name, array $definition): string 122 { 123 $type = $definition['type'] ?? 'unknown'; 124 125 // Handle DateTime types 126 if ($type === 'string' && isset($definition['format']) && $definition['format'] === 'datetime') { 127 return "\$model->{$name} ? new \\DateTime(\$model->{$name}) : null"; 128 } 129 130 // Handle blob types 131 if ($type === 'blob') { 132 return "\$model->{$name} ? \\SocialDept\\AtpSchema\\Data\\BlobReference::fromArray(\$model->{$name}) : null"; 133 } 134 135 // Handle nested refs 136 if ($type === 'ref' && isset($definition['ref'])) { 137 $refClass = $this->naming->nsidToClassName($definition['ref']); 138 $className = basename(str_replace('\\', '/', $refClass)); 139 140 return "\$model->{$name} ? {$className}::fromArray(\$model->{$name}) : null"; 141 } 142 143 // Handle arrays of refs 144 if ($type === 'array' && isset($definition['items']['type']) && $definition['items']['type'] === 'ref') { 145 $refClass = $this->naming->nsidToClassName($definition['items']['ref']); 146 $className = basename(str_replace('\\', '/', $refClass)); 147 148 return "\$model->{$name} ? array_map(fn (\$item) => {$className}::fromArray(\$item), \$model->{$name}) : []"; 149 } 150 151 // Simple property with null coalescing 152 return "\$model->{$name} ?? null"; 153 } 154 155 /** 156 * Get field mapping configuration. 157 * 158 * @param array<string, array<string, mixed>> $properties 159 * @return array<string, string> 160 */ 161 public function getFieldMapping(array $properties): array 162 { 163 $mapping = []; 164 165 foreach ($properties as $name => $definition) { 166 // Convert camelCase to snake_case for database columns 167 $mapping[$name] = $this->naming->toSnakeCase($name); 168 } 169 170 return $mapping; 171 } 172 173 /** 174 * Check if property needs special handling. 175 * 176 * @param array<string, mixed> $definition 177 */ 178 public function needsTransformer(array $definition): bool 179 { 180 $type = $definition['type'] ?? 'unknown'; 181 182 if ($type === 'blob') { 183 return true; 184 } 185 186 if ($type === 'ref') { 187 return true; 188 } 189 190 if ($type === 'string' && isset($definition['format']) && $definition['format'] === 'datetime') { 191 return true; 192 } 193 194 if ($type === 'array' && isset($definition['items']['type'])) { 195 $itemType = $definition['items']['type']; 196 if (in_array($itemType, ['ref', 'object'])) { 197 return true; 198 } 199 } 200 201 return false; 202 } 203 204 /** 205 * Get transformer type for property. 206 * 207 * @param array<string, mixed> $definition 208 */ 209 public function getTransformerType(array $definition): ?string 210 { 211 $type = $definition['type'] ?? 'unknown'; 212 213 if ($type === 'string' && isset($definition['format']) && $definition['format'] === 'datetime') { 214 return 'datetime'; 215 } 216 217 if ($type === 'blob') { 218 return 'blob'; 219 } 220 221 if ($type === 'ref') { 222 return 'ref'; 223 } 224 225 if ($type === 'array' && isset($definition['items']['type'])) { 226 $itemType = $definition['items']['type']; 227 if ($itemType === 'ref') { 228 return 'array_ref'; 229 } 230 if ($itemType === 'object') { 231 return 'array_object'; 232 } 233 } 234 235 return null; 236 } 237}