| 1 | <?php |
||
| 2 | |||
| 3 | declare(strict_types=1); |
||
| 4 | |||
| 5 | namespace Cycle\Schema\Provider; |
||
| 6 | |||
| 7 | use Cycle\Schema\Provider\Exception\ConfigurationException; |
||
| 8 | use Cycle\Schema\Provider\Exception\SchemaFileNotFoundException; |
||
| 9 | use Webmozart\Glob\Glob; |
||
| 10 | use Webmozart\Glob\Iterator\GlobIterator; |
||
| 11 | use Cycle\Schema\Provider\Support\SchemaMerger; |
||
| 12 | |||
| 13 | /** |
||
| 14 | * Be careful, using this class may be insecure. |
||
| 15 | */ |
||
| 16 | final class FromFilesSchemaProvider implements SchemaProviderInterface |
||
| 17 | { |
||
| 18 | /** |
||
| 19 | * @var array<non-empty-string> Schema files |
||
|
0 ignored issues
–
show
Documentation
Bug
introduced
by
Loading history...
|
|||
| 20 | */ |
||
| 21 | private array $files = []; |
||
| 22 | |||
| 23 | /** |
||
| 24 | * @var bool Throw exception if file not found |
||
| 25 | */ |
||
| 26 | private bool $strict = false; |
||
| 27 | |||
| 28 | /** |
||
| 29 | * @var \Closure(non-empty-string): non-empty-string |
||
| 30 | */ |
||
| 31 | private \Closure $pathResolver; |
||
| 32 | |||
| 33 | /** |
||
| 34 | * @param null|callable(non-empty-string): non-empty-string $pathResolver A function that resolves |
||
| 35 | * framework-specific file paths. |
||
| 36 | */ |
||
| 37 | 21 | public function __construct(?callable $pathResolver = null) |
|
| 38 | { |
||
| 39 | /** @psalm-suppress PropertyTypeCoercion */ |
||
| 40 | 21 | $this->pathResolver = $pathResolver === null |
|
| 41 | 21 | ? static fn (string $path): string => $path |
|
| 42 | 21 | : \Closure::fromCallable($pathResolver); |
|
| 43 | } |
||
| 44 | |||
| 45 | /** |
||
| 46 | * Create a configuration array for the {@see self::withConfig()} method. |
||
| 47 | * @param array<non-empty-string> $files |
||
|
0 ignored issues
–
show
|
|||
| 48 | */ |
||
| 49 | 1 | public static function config(array $files, bool $strict = false): array |
|
| 50 | { |
||
| 51 | 1 | return [ |
|
| 52 | 1 | 'files' => $files, |
|
| 53 | 1 | 'strict' => $strict, |
|
| 54 | 1 | ]; |
|
| 55 | } |
||
| 56 | |||
| 57 | 18 | public function withConfig(array $config): self |
|
| 58 | { |
||
| 59 | 18 | $files = $config['files'] ?? []; |
|
| 60 | 18 | if (!\is_array($files)) { |
|
| 61 | 1 | throw new ConfigurationException('The `files` parameter must be an array.'); |
|
| 62 | } |
||
| 63 | 17 | if (\count($files) === 0) { |
|
| 64 | 2 | throw new ConfigurationException('Schema file list is not set.'); |
|
| 65 | } |
||
| 66 | |||
| 67 | 15 | $strict = $config['strict'] ?? $this->strict; |
|
| 68 | 15 | if (!\is_bool($strict)) { |
|
| 69 | 1 | throw new ConfigurationException('The `strict` parameter must be a boolean.'); |
|
| 70 | } |
||
| 71 | |||
| 72 | 14 | $files = \array_map( |
|
| 73 | 14 | function (mixed $file) { |
|
| 74 | 14 | if (!\is_string($file) || $file === '') { |
|
| 75 | 6 | throw new ConfigurationException('The `files` parameter must contain non-empty string values.'); |
|
| 76 | } |
||
| 77 | 8 | return ($this->pathResolver)($file); |
|
| 78 | 14 | }, |
|
| 79 | 14 | $files |
|
| 80 | 14 | ); |
|
| 81 | |||
| 82 | 8 | $new = clone $this; |
|
| 83 | 8 | $new->files = $files; |
|
| 84 | 8 | $new->strict = $strict; |
|
| 85 | 8 | return $new; |
|
| 86 | } |
||
| 87 | |||
| 88 | 10 | public function read(?SchemaProviderInterface $nextProvider = null): ?array |
|
| 89 | { |
||
| 90 | 10 | $schema = (new SchemaMerger())->merge(...$this->readFiles()); |
|
| 91 | |||
| 92 | 8 | return $schema !== null || $nextProvider === null ? $schema : $nextProvider->read(); |
|
| 93 | } |
||
| 94 | |||
| 95 | 1 | public function clear(): bool |
|
| 96 | { |
||
| 97 | 1 | return false; |
|
| 98 | } |
||
| 99 | |||
| 100 | /** |
||
| 101 | * Read schema from each file |
||
| 102 | * |
||
| 103 | * @return \Generator<int, array|null> |
||
| 104 | */ |
||
| 105 | 10 | private function readFiles(): \Generator |
|
| 106 | { |
||
| 107 | 10 | foreach ($this->files as $path) { |
|
| 108 | 8 | $path = \str_replace('\\', '/', $path); |
|
| 109 | 8 | if (!Glob::isDynamic($path)) { |
|
| 110 | 7 | yield $this->loadFile($path); |
|
| 111 | 7 | continue; |
|
| 112 | } |
||
| 113 | 1 | foreach (new GlobIterator($path) as $file) { |
|
| 114 | 1 | yield $this->loadFile($file); |
|
| 115 | } |
||
| 116 | } |
||
| 117 | } |
||
| 118 | |||
| 119 | 8 | private function loadFile(string $path): ?array |
|
| 120 | { |
||
| 121 | 8 | $isFile = \is_file($path); |
|
| 122 | |||
| 123 | 8 | if (!$isFile && $this->strict) { |
|
| 124 | 1 | throw new SchemaFileNotFoundException($path); |
|
| 125 | } |
||
| 126 | |||
| 127 | 8 | return $isFile ? require $path : null; |
|
| 128 | } |
||
| 129 | } |
||
| 130 |