,exceededTransferLimit:mixed} */ public function manifest(): array { $features = $this->features(); $count = 0; $first = []; foreach ($features as $feature) { if ($count === 0) { $first = $feature['properties'] ?? []; } $count++; } $metadata = $features->getReturn(); return ['type' => $metadata['type'] ?? null, 'feature_count' => $count, 'first_properties' => $first, 'exceededTransferLimit' => $metadata['exceededTransferLimit'] ?? false]; } /** @return Generator,mixed,array> */ public function features(): Generator { $stream = fopen($this->path, 'rb'); abort_unless(is_resource($stream), 422, 'Exemplar privado indisponível.'); $buffer = ''; $position = 0; $peek = static function () use ($stream, &$buffer, &$position): ?string { if ($position === strlen($buffer)) { $buffer = fread($stream, 65536); $position = 0; } return $buffer !== '' ? $buffer[$position] : null; }; $take = static function () use ($peek, &$position): ?string { $char = $peek(); if ($char !== null) { $position++; } return $char; }; $white = static function () use ($peek, $take): void { while (($c = $peek()) !== null && str_contains(" \t\r\n", $c)) { $take(); } }; $expect = static function (string $expected) use ($white, $take): void { $white(); abort_unless($take() === $expected, 422, 'Estrutura JSON do exemplar inválida.'); }; $value = static function () use ($white, $peek, $take, &$buffer, &$position): string { $white(); $first = $peek(); abort_unless($first !== null, 422, 'Exemplar JSON truncado.'); $structured = in_array($first, ['{', '[', '"'], true); $result = $structured ? $take() : ''; $depth = $first === '"' ? 0 : 1; $quoted = $first === '"'; $delimiters = $first === '{' ? '{}"' : ($first === '[' ? '[]"' : '"'); while (true) { abort_unless($peek() !== null, 422, 'Exemplar JSON truncado.'); // Search whole numeric/coordinate spans in C; never loop through every byte in PHP. $span = strcspn($buffer, $structured ? ($quoted ? '"\\' : $delimiters) : ",]} \t\r\n", $position); $result .= substr($buffer, $position, $span); $position += $span; abort_if(strlen($result) > 4 * 1024 * 1024, 422, 'Elemento JSON excede o limite seguro de leitura.'); if ($position === strlen($buffer)) { continue; } if (! $structured) { break; } $c = $take(); $result .= $c; if ($quoted) { if ($c === '\\') { $escaped = $take(); abort_unless($escaped !== null, 422, 'Exemplar JSON truncado.'); $result .= $escaped; } elseif ($c === '"') { $quoted = false; } } elseif ($c === '"') { $quoted = true; } elseif ($c === $first) { $depth++; } else { $depth--; } if (! $quoted && $depth === 0) { break; } } abort_unless($result !== '', 422, 'Valor JSON ausente.'); return $result; }; $decode = static function (string $bytes): mixed { try { return json_decode($bytes, true, 512, JSON_THROW_ON_ERROR); } catch (\JsonException) { abort(422, 'JSON do exemplar inválido.'); } }; try { $expect('{'); $metadata = []; $keys = []; $index = 0; $hadFeatures = false; $white(); abort_if($peek() === '}', 422, 'Exemplar sem coleção de elementos.'); while (true) { $key = $decode($value()); abort_unless(is_string($key) && ! isset($keys[$key]), 422, 'Chave JSON inválida ou repetida.'); $keys[$key] = true; $expect(':'); if ($key === 'features') { $hadFeatures = true; $expect('['); $white(); if ($peek() !== ']') { while (true) { $feature = $decode($value()); abort_unless(is_array($feature) && ($feature['type'] ?? null) === 'Feature', 422, 'Elemento geográfico inválido.'); yield $index++ => $feature; unset($feature); $white(); if ($peek() === ']') { break; }$expect(','); } } $expect(']'); } else { $metadata[$key] = $decode($value()); } $white(); if ($peek() === '}') { break; }$expect(','); } $expect('}'); $white(); abort_unless($peek() === null && $hadFeatures,422,'Exemplar incompleto ou com conteúdo adicional.'); return $metadata; } finally { fclose($stream); } } }