Passed
Push — master ( 51cba3...dc9091 )
by Peter
03:11
created

Flysystem::fileExists()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
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 $filesystems = [];
22
23
    /** @var callable[] callables that can check if a filesystem is suited to be used for a given path */
24
    protected $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("isFlushable requires an array or \ArrayAccess, received object is not: %s", get_class($obj));
0 ignored issues
show
Bug introduced by
get_class($obj) of type string is incompatible with the type integer expected by parameter $code of InvalidArgumentException::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

33
                throw new InvalidArgumentException("isFlushable requires an array or \ArrayAccess, received object is not: %s", /** @scrutinizer ignore-type */ get_class($obj));
Loading history...
34
            }
35
36
            if ($obj['basename'] === '.gitignore') {
37
                return false;
38
            }
39
40
            if (!empty($obj['extension']) && strtolower($obj['extension']) === 'php') {
41
                return false;
42
            }
43
44
            return true;
45
        };
46
    }
47
48
    /**
49
     * @param callable $isFlushable must expect an array containing file information and return a true if a file is
50
     *                              flushable
51
     *
52
     * @return $this
53
     */
54
    public function setIsFlushable(callable $isFlushable): ICacheManager
55
    {
56
        $this->isFlushable = $isFlushable;
57
58
        return $this;
59
    }
60
61
    /**
62
     * @param string $path
63
     *
64
     * @return FilesystemOperator
65
     */
66
    protected function getFilesystem(string $path): FilesystemOperator
67
    {
68
        foreach ($this->filesystems as $priority => $filesystem) {
69
            if (empty($this->pathCheckers[$priority])) {
70
                return $filesystem;
71
            }
72
73
            if (call_user_func($this->pathCheckers[$priority], $path)) {
74
                return $filesystem;
75
            }
76
        }
77
78
        throw new \InvalidArgumentException(static::ERROR_FILESYSTEM_NOT_FOUND);
79
    }
80
81
    /**
82
     * @param FilesystemOperator $filesystem
83
     * @param callable|null      $checker
84
     * @param int|null           $priority
85
     */
86
    public function registerFilesystem(FilesystemOperator $filesystem, callable $checker = null, ?int $priority = null)
87
    {
88
        $priority = $priority === null ? count($this->filesystems) * -1 : $priority;
89
90
        $this->filesystems[$priority]  = $filesystem;
91
        $this->pathCheckers[$priority] = $checker;
92
93
        krsort($this->filesystems);
94
        krsort($this->pathCheckers);
95
    }
96
97
    /**
98
     * @param string $path
99
     *
100
     * @return bool
101
     * @throws FilesystemException
102
     */
103
    public function fileExists(string $path): bool
104
    {
105
        $fs = $this->getFilesystem($path);
106
107
        return $fs->fileExists($path);
108
    }
109
110
    /**
111
     * @param string $path
112
     *
113
     * @return string|null
114
     * @throws FilesystemException
115
     * @throws UnableToReadFile
116
     */
117
    public function read(string $path): ?string
118
    {
119
        $fs = $this->getFilesystem($path);
120
121
        if (!$fs->fileExists($path)) {
122
            return null;
123
        }
124
125
        try {
126
            return $fs->read($path);
127
        } catch (UnableToReadFile $e) {
128
            return null;
129
        }
130
    }
131
132
    /**
133
     * @param string $path
134
     * @param string $content
135
     * @param bool   $force
136
     *
137
     * @return bool
138
     * @throws FilesystemException
139
     * @throws UnableToWriteFile
140
     */
141
    public function write(string $path, string $content, bool $force = true): bool
142
    {
143
        $fs = $this->getFilesystem($path);
144
145
        if ($fs->fileExists($path)) {
146
            if (!$force) {
147
                return false;
148
            }
149
150
            try {
151
                $fs->delete($path);
152
            } catch (UnableToDeleteFile $e) {
153
                // This is a noop(), production builds will (should) remove it completely
154
                // Note that this can happen if the file was removed between the `$fs->fileExists()` and `$fs->delete()` calls
155
                assert(true);
156
            }
157
        }
158
159
        try {
160
            $fs->write($path, $content);
161
        } catch (UnableToWriteFile $e) {
162
            return false;
163
        }
164
165
        return true;
166
    }
167
168
    /**
169
     * @param string $path
170
     *
171
     * @return string
172
     * @throws UnableToRetrieveMetadata
173
     * @throws FilesystemException
174
     */
175
    public function getWebPath(string $path): string
176
    {
177
        $fs = $this->getFilesystem($path);
178
179
        $timestamp = (string)$fs->lastModified($path);
180
181
        $path = DIRECTORY_SEPARATOR . ltrim($path, DIRECTORY_SEPARATOR);
182
        $rand = substr(md5($timestamp), 0, 5);
183
184
        return sprintf('%s?%s', $path, $rand);
185
    }
186
187
    /**
188
     * @throws FilesystemException
189
     */
190
    public function flush()
191
    {
192
        foreach ($this->filesystems as $filesystem) {
193
            $objects = $filesystem->listContents('/', false);
194
195
            foreach ($objects->getIterator() as $object) {
196
                if (!call_user_func($this->isFlushable, $object)) {
197
                    continue;
198
                }
199
                $filesystem->delete($object['path']);
200
            }
201
        }
202
    }
203
}
204