Passed
Pull Request — 1.x (#1)
by Aleksei
13:42
created

PhpFileSchemaProvider::config()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 3
c 1
b 0
f 1
nc 1
nop 2
dl 0
loc 7
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Cycle\Schema\Provider;
6
7
use Closure;
8
use Cycle\Schema\Provider\Exception\ConfigurationException;
9
use Cycle\Schema\Provider\Exception\SchemaProviderException;
10
use Cycle\Schema\Renderer\PhpSchemaRenderer;
11
use Spiral\Files\Files;
12
use Spiral\Files\FilesInterface;
13
14
final class PhpFileSchemaProvider implements SchemaProviderInterface
15
{
16
    public const MODE_READ_AND_WRITE = 0;
17
    public const MODE_WRITE_ONLY = 1;
18
19
    private string $file = '';
20
    private int $mode = self::MODE_READ_AND_WRITE;
21
22
    /** @var Closure(non-empty-string): non-empty-string */
23
    private Closure $pathResolver;
24
    private FilesInterface $files;
25
26
    /**
27
     * @param null|callable(non-empty-string): non-empty-string $pathResolver A function that resolves
28
     *        framework-specific file paths.
29
     */
30
    public function __construct(?callable $pathResolver = null, ?FilesInterface $files = null)
31
    {
32
        $this->files = $files ?? new Files();
33
        $this->pathResolver = $pathResolver === null
34
            ? static fn (string $path): string => $path
35
            : Closure::fromCallable($pathResolver);
36
    }
37
38
    /**
39
     * Create a configuration array for the {@see self::withConfig()} method.
40
     */
41
    public static function config(
42
        string $file,
43
        int $mode = self::MODE_READ_AND_WRITE,
44
    ): array {
45
        return [
46
            'file' => $file,
47
            'mode' => $mode,
48
        ];
49
    }
50
51
    /**
52
     * @param array{
53
     *     file: non-empty-string,
54
     *     mode?: self::MODE_READ_AND_WRITE|self::MODE_WRITE_ONLY,
55
     * } $config
56
     */
57
    public function withConfig(array $config): self
58
    {
59
        $new = clone $this;
60
61
        // required option
62
        if ($this->file === '' && !array_key_exists('file', $config)) {
63
            throw new ConfigurationException('The `file` parameter is required.');
64
        }
65
        $new->file = ($this->pathResolver)($config['file']);
66
67
        $new->mode = $config['mode'] ?? $this->mode;
68
69
        return $new;
70
    }
71
72
    public function read(?SchemaProviderInterface $nextProvider = null): ?array
73
    {
74
        if (!$this->isReadable()) {
75
            if ($nextProvider === null) {
76
                throw new SchemaProviderException(__CLASS__ . ' can not read schema.');
77
            }
78
            $schema = null;
79
        } else {
80
            /** @psalm-suppress UnresolvableInclude */
81
            $schema = !$this->files->isFile($this->file) ? null : (include $this->file);
82
        }
83
84
        if ($schema !== null || $nextProvider === null) {
85
            return $schema;
86
        }
87
88
        $schema = $nextProvider->read();
89
        if ($schema !== null) {
90
            $this->write($schema);
91
        }
92
        return $schema;
93
    }
94
95
    public function clear(): bool
96
    {
97
        try {
98
            return $this->removeFile();
99
        } catch (\Throwable $e) {
100
            return false;
101
        }
102
    }
103
104
    private function write(array $schema): bool
105
    {
106
        if (\basename($this->file) === '') {
107
            throw new SchemaProviderException('The `file` parameter must not be empty.');
108
        }
109
110
        $content = (new PhpSchemaRenderer())->render($schema);
111
        $this->files->write($this->file, $content, 0777, true);
112
113
        return true;
114
    }
115
116
    private function removeFile(): bool
117
    {
118
        if (!$this->files->exists($this->file)) {
119
            return true;
120
        }
121
        if (!$this->files->isFile($this->file)) {
122
            throw new SchemaProviderException(\sprintf('`%s` is not a file.', $this->file));
123
        }
124
        if (!\is_writable($this->file)) {
125
            throw new SchemaProviderException(\sprintf('File `%s` is not writeable.', $this->file));
126
        }
127
128
        return $this->files->delete($this->file);
129
    }
130
131
    private function isReadable(): bool
132
    {
133
        return $this->mode !== self::MODE_WRITE_ONLY;
134
    }
135
}
136