Flysystem::flush()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 6
c 0
b 0
f 0
nc 4
nop 0
dl 0
loc 10
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace AbterPhp\Framework\Assets\CacheManager;
6
7
use ArrayAccess;
8
use InvalidArgumentException;
9
use League\Flysystem\FilesystemException;
10
use League\Flysystem\FilesystemOperator;
11
use League\Flysystem\UnableToDeleteFile;
12
use League\Flysystem\UnableToReadFile;
13
use League\Flysystem\UnableToRetrieveMetadata;
14
use League\Flysystem\UnableToWriteFile;
15
16
class Flysystem implements ICacheManager
17
{
18
    protected const ERROR_FILESYSTEM_NOT_FOUND = 'filesystem not found';
19
20
    /** @var FilesystemOperator[] */
21
    protected array $filesystems = [];
22
23
    /** @var callable[] callables that can check if a filesystem is suited to be used for a given path */
24
    protected array $pathCheckers = [];
25
26
    /** @var callable used to decide if a file can be deleted or not */
27
    protected $isFlushable;
28
29
    public function __construct()
30
    {
31
        $this->isFlushable = function ($obj) {
32
            if (!is_array($obj) && !($obj instanceof ArrayAccess)) {
33
                throw new InvalidArgumentException(
34
                    sprintf("isFlushable requires an array or \ArrayAccess, received object is not: %s", get_class($obj))
35
                );
36
            }
37
38
            if ($obj['basename'] === '.gitignore') {
39
                return false;
40
            }
41
42
            if (!empty($obj['extension']) && strtolower($obj['extension']) === 'php') {
43
                return false;
44
            }
45
46
            return true;
47
        };
48
    }
49
50
    /**
51
     * @param callable $isFlushable must expect an array containing file information and return a true if a file is
52
     *                              flushable
53
     *
54
     * @return $this
55
     */
56
    public function setIsFlushable(callable $isFlushable): ICacheManager
57
    {
58
        $this->isFlushable = $isFlushable;
59
60
        return $this;
61
    }
62
63
    /**
64
     * @param string $path
65
     *
66
     * @return FilesystemOperator
67
     */
68
    protected function getFilesystem(string $path): FilesystemOperator
69
    {
70
        foreach ($this->filesystems as $priority => $filesystem) {
71
            if (empty($this->pathCheckers[$priority])) {
72
                return $filesystem;
73
            }
74
75
            if (call_user_func($this->pathCheckers[$priority], $path)) {
76
                return $filesystem;
77
            }
78
        }
79
80
        throw new InvalidArgumentException(static::ERROR_FILESYSTEM_NOT_FOUND);
81
    }
82
83
    /**
84
     * @param FilesystemOperator $filesystem
85
     * @param callable|null      $checker
86
     * @param int|null           $priority
87
     */
88
    public function registerFilesystem(FilesystemOperator $filesystem, callable $checker = null, ?int $priority = null): void
89
    {
90
        $priority = $priority === null ? count($this->filesystems) * -1 : $priority;
91
92
        $this->filesystems[$priority]  = $filesystem;
93
        $this->pathCheckers[$priority] = $checker;
94
95
        krsort($this->filesystems);
96
        krsort($this->pathCheckers);
97
    }
98
99
    /**
100
     * @param string $path
101
     *
102
     * @return bool
103
     * @throws FilesystemException
104
     */
105
    public function fileExists(string $path): bool
106
    {
107
        $fs = $this->getFilesystem($path);
108
109
        return $fs->fileExists($path);
110
    }
111
112
    /**
113
     * @param string $path
114
     *
115
     * @return string|null
116
     * @throws FilesystemException
117
     * @throws UnableToReadFile
118
     */
119
    public function read(string $path): ?string
120
    {
121
        $fs = $this->getFilesystem($path);
122
123
        if (!$fs->fileExists($path)) {
124
            return null;
125
        }
126
127
        try {
128
            return $fs->read($path);
129
        } catch (UnableToReadFile $e) {
130
            return null;
131
        }
132
    }
133
134
    /**
135
     * @param string $path
136
     * @param string $content
137
     * @param bool   $force
138
     *
139
     * @return bool
140
     * @throws FilesystemException
141
     * @throws UnableToWriteFile
142
     */
143
    public function write(string $path, string $content, bool $force = true): bool
144
    {
145
        $fs = $this->getFilesystem($path);
146
147
        if ($fs->fileExists($path)) {
148
            if (!$force) {
149
                return false;
150
            }
151
152
            try {
153
                $fs->delete($path);
154
            } catch (UnableToDeleteFile $e) {
155
                // This is a noop(), production builds will (should) remove it completely
156
                // Note that this can happen if the file was removed between the `$fs->fileExists()` and `$fs->delete()` calls
157
                assert(true);
158
            }
159
        }
160
161
        try {
162
            $fs->write($path, $content);
163
        } catch (UnableToWriteFile $e) {
164
            return false;
165
        }
166
167
        return true;
168
    }
169
170
    /**
171
     * @param string $path
172
     *
173
     * @return string
174
     * @throws UnableToRetrieveMetadata
175
     * @throws FilesystemException
176
     */
177
    public function getWebPath(string $path): string
178
    {
179
        $fs = $this->getFilesystem($path);
180
181
        $timestamp = (string)$fs->lastModified($path);
182
183
        $path = DIRECTORY_SEPARATOR . ltrim($path, DIRECTORY_SEPARATOR);
184
        $rand = substr(md5($timestamp), 0, 5);
185
186
        return sprintf('%s?%s', $path, $rand);
187
    }
188
189
    /**
190
     * @throws FilesystemException
191
     */
192
    public function flush(): void
193
    {
194
        foreach ($this->filesystems as $filesystem) {
195
            $objects = $filesystem->listContents('/', false);
196
197
            foreach ($objects->getIterator() as $object) {
198
                if (!call_user_func($this->isFlushable, $object)) {
199
                    continue;
200
                }
201
                $filesystem->delete($object['path']);
202
            }
203
        }
204
    }
205
}
206