Parse and validate AT Protocol Lexicons with DTO generation for Laravel

Implement in-memory object caching for SchemaLoader

Changed files
+80 -10
src
+80 -10
src/Parser/SchemaLoader.php
··· 4 4 5 5 use Illuminate\Support\Facades\Cache; 6 6 use Illuminate\Support\Facades\Http; 7 + use SocialDept\Schema\Contracts\SchemaRepository; 8 + use SocialDept\Schema\Data\LexiconDocument; 7 9 use SocialDept\Schema\Exceptions\SchemaNotFoundException; 8 10 use SocialDept\Schema\Exceptions\SchemaParseException; 9 11 10 - class SchemaLoader 12 + class SchemaLoader implements SchemaRepository 11 13 { 12 14 /** 13 15 * In-memory cache of loaded schemas for current request. ··· 81 83 } 82 84 83 85 /** 86 + * Find schema by NSID (nullable version). 87 + */ 88 + public function find(string $nsid): ?LexiconDocument 89 + { 90 + try { 91 + return $this->load($nsid); 92 + } catch (SchemaNotFoundException) { 93 + return null; 94 + } 95 + } 96 + 97 + /** 84 98 * Load schema by NSID. 85 99 */ 86 - public function load(string $nsid): array 100 + public function load(string $nsid): LexiconDocument 87 101 { 88 102 // Check memory cache first 89 103 if (isset($this->memoryCache[$nsid])) { ··· 96 110 $cached = Cache::get($cacheKey); 97 111 98 112 if ($cached !== null) { 99 - $this->memoryCache[$nsid] = $cached; 113 + // Cache stores raw arrays, convert to LexiconDocument 114 + $document = LexiconDocument::fromArray($cached); 115 + $this->memoryCache[$nsid] = $document; 100 116 101 - return $cached; 117 + return $document; 102 118 } 103 119 } 104 120 105 - // Load from sources 106 - $schema = $this->loadFromSources($nsid); 121 + // Load raw array data from sources 122 + $data = $this->loadFromSources($nsid); 107 123 108 - // Cache the result 109 - $this->memoryCache[$nsid] = $schema; 124 + // Parse into LexiconDocument 125 + $document = LexiconDocument::fromArray($data); 126 + 127 + // Cache both in memory (as object) and Laravel cache (as array) 128 + $this->memoryCache[$nsid] = $document; 110 129 111 130 if ($this->useCache) { 112 - Cache::put($this->getCacheKey($nsid), $schema, $this->cacheTtl); 131 + Cache::put($this->getCacheKey($nsid), $data, $this->cacheTtl); 113 132 } 114 133 115 - return $schema; 134 + return $document; 135 + } 136 + 137 + /** 138 + * Load raw schema array by NSID. 139 + */ 140 + protected function loadRaw(string $nsid): array 141 + { 142 + $document = $this->load($nsid); 143 + 144 + return $document->toArray(); 145 + } 146 + 147 + /** 148 + * Get all available schema NSIDs. 149 + * 150 + * @return array<string> 151 + */ 152 + public function all(): array 153 + { 154 + $nsids = []; 155 + 156 + // Scan all source directories for lexicon files 157 + foreach ($this->sources as $source) { 158 + if (! is_dir($source)) { 159 + continue; 160 + } 161 + 162 + // Recursively scan for .json files 163 + $files = new \RecursiveIteratorIterator( 164 + new \RecursiveDirectoryIterator($source, \RecursiveDirectoryIterator::SKIP_DOTS) 165 + ); 166 + 167 + foreach ($files as $file) { 168 + if ($file->isFile() && $file->getExtension() === 'json') { 169 + // Try to parse the NSID from the file 170 + try { 171 + $contents = file_get_contents($file->getPathname()); 172 + $data = json_decode($contents, true); 173 + 174 + if (isset($data['id'])) { 175 + $nsids[] = $data['id']; 176 + } 177 + } catch (\Exception $e) { 178 + // Skip invalid files 179 + continue; 180 + } 181 + } 182 + } 183 + } 184 + 185 + return array_unique($nsids); 116 186 } 117 187 118 188 /**