Passed
Push — main ( 3f4cd8...a7294a )
by Sammy
01:18
created

FileSystem.php (1 issue)

Labels
Severity
1
<?php
2
3
namespace HexMakina\LocalFS;
4
5
/**
6
 * FILEPATH: /var/www/dev.engine/krafto-cinergie/lib/LocalFS/FileSystem.php
7
 * 
8
 * The FileSystem class provides a set of methods to interact with a directory and subdirectories.
9
 * The root is the starting point of the represented file system.
10
 * It allows to work with relative paths and provides methods to get absolute paths, list directories and files,
11
 * and ensure that a path is writable.
12
 * 
13
 * @package HexMakina\LocalFS
14
 */
15
16
class FileSystem
17
{
18
19
    private $rootPath = null;
20
21
    /**
22
     * Constructs a new FileSystem object with the given root path.
23
     *
24
     * @param string $rootPath The starting point of the represented file system.
25
     * @throws \InvalidArgumentException If the root path is invalid.
26
     */
27
    function __construct(string $rootPath)
28
    {
29
        $rootPath = realpath($rootPath);
30
        if (!$rootPath)
31
            throw new \InvalidArgumentException('INVALID_ROOT_PATH');
32
33
        $this->rootPath = $rootPath;
34
    }
35
36
    /**
37
     * Returns the starting point of a filesystem's abstraction, not the machine's filesystem root. 
38
     * It can be a project's root, or any directory you may want to work with.
39
     *
40
     * @return string The root path of the file system.
41
     */
42
    public function root(): string
43
    {
44
        return $this->rootPath;
45
    }
46
47
    /**
48
     * Returns the absolute path for a given relative path.
49
     *
50
     * @param string $relativePath The relative path to get the absolute path for.
51
     * @param bool $checkExistence Whether to check if the file exists or not. Default is false.
52
     * @return string The absolute path for the given relative path.
53
     * @throws \InvalidArgumentException If the relative path is empty or invalid.
54
     */
55
    public function absolutePathFor(string $relativePath, bool $checkExistence = false): string
56
    {
57
        $this->validRelativePath($relativePath);
0 ignored issues
show
The method validRelativePath() does not exist on HexMakina\LocalFS\FileSystem. ( Ignorable by Annotation )

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

57
        $this->/** @scrutinizer ignore-call */ 
58
               validRelativePath($relativePath);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
58
59
        $absolute = sprintf('%s/%s', $this->root(), $relativePath);
60
61
        if ($checkExistence) {
62
            $absolute = realpath($absolute);
63
            if (!$absolute) {
64
                throw new \InvalidArgumentException('INVALID_PATH');
65
            }
66
        }
67
68
        return $absolute;
69
    }
70
71
    /**
72
     * Lists the contents of a directory.
73
     *
74
     * @param string $relativePath The relative path of the directory to list.
75
     * @return array An array of filenames in the directory.
76
     * @throws \InvalidArgumentException If the specified path is not a directory.
77
     */
78
    public function list(string $relativePath): array
79
    {
80
        $absolutePath = $this->absolutePathFor($relativePath);
81
82
        if (!is_dir($absolutePath)) {
83
            throw new \InvalidArgumentException('RELATIVE_PATH_NOT_A_DIRECTORY');
84
        }
85
86
        return array_diff(scandir($absolutePath), ['.', '..']);
87
    }
88
89
    /**
90
     * Returns an array of files in the specified directory.
91
     *
92
     * @param string $relativePath The relative path of the directory to search.
93
     * @return array An array of file names.
94
     */
95
    public function files(string $relativePath): array
96
    {
97
        $absolutePath = $this->absolutePathFor($relativePath);
98
99
        // Filter the list of files to include only files (not directories).
100
        $files = array_filter($this->list($relativePath), function ($filename) use ($absolutePath) {
101
            return is_file($absolutePath . DIRECTORY_SEPARATOR . $filename);
102
        });
103
104
        return $files;
105
    }
106
107
    /**
108
     * Returns an array of directories in the specified relative path.
109
     *
110
     * @param string $relativePath The relative path to search for directories.
111
     * @return array An array of directories in the specified relative path.
112
     */
113
    public function directories(string $relativePath): array
114
    {
115
        $absolutePath = $this->absolutePathFor($relativePath);
116
117
        // Filter the list of files to include only files (not directories).
118
        $files = array_filter($this->list($relativePath), function ($filename) use ($absolutePath) {
119
            return is_dir($absolutePath . DIRECTORY_SEPARATOR . $filename);
120
        });
121
122
        return $files;
123
    }
124
125
    /**
126
     * Ensures that the specified relative path is writable.
127
     *
128
     * @param string $relativePath The relative path to ensure is writable.
129
     * @throws \InvalidArgumentException If the path is not inside the root path, the target directory cannot be created, or the target directory is not writable.
130
     * @return bool True if the path is writable, false otherwise.
131
     */
132
133
    public function ensureWritablePath(string $absoluteDirectoryPath, string $filename=null): bool
134
    {
135
        if (strpos($absoluteDirectoryPath, $this->root()) !== 0) {
136
            throw new \InvalidArgumentException('PATH_NOT_INSIDE_ROOT_PATH');
137
        }
138
139
        $relativeDirectoryPath = substr($absoluteDirectoryPath, strlen($this->root()) + 1);
140
        $pathParts = explode('/', $relativeDirectoryPath);
141
        
142
143
        $targetDir = $this->root();
144
        foreach ($pathParts as $i => $part) {
145
            $targetDir .= '/' . $part;
146
147
            // Create the folder if it doesn't exist
148
            if (!file_exists($targetDir) && mkdir($targetDir, 0755, true) === false) {
149
                throw new \InvalidArgumentException('UNABLE_TO_CREATE_MISSING_TARGET_DIRECTORY');
150
            }
151
152
            if (!is_dir($targetDir)) {
153
                throw new \InvalidArgumentException('TARGET_DIRECTORY_NOT_A_DIRECTORY');
154
            }
155
            if (!is_writable($targetDir)) {
156
                throw new \InvalidArgumentException('TARGET_DIRECTORY_NOT_WRITABLE');
157
            }
158
        }
159
        return true;
160
    }
161
162
    public function filenames($regex = null): array
163
    {
164
        if (!file_exists($this->rootPath) && mkdir($this->rootPath) === false) {
165
            return [];
166
        }
167
168
        $filenames = self::preg_scandir($this->rootPath, $regex); // ID_SEQUENCENUMBER.ext
169
        if (!is_null($filenames)) {
170
            sort($filenames);
171
        }
172
173
        return $filenames;
174
    }
175
176
    // previous implementation, to update
177
    public function filepathes($regex = null): array
178
    {
179
        $filenames = $this->filenames($regex);
180
        $filepathes = [];
181
        foreach ($filenames as $filename) {
182
            $filepathes[] = $this->absolutePathFor($filename);
183
        }
184
185
        return $filepathes;
186
    }
187
188
    /**
189
     * Resolves a symbolic link to its target path.
190
     *
191
     * @param string $path The path to resolve.
192
     * @throws \Exception If `readlink` fails to resolve the symbolic link.
193
     * @return string The resolved path.
194
     */
195
    public static function resolve_symlink($path)
196
    {
197
        if (is_link($path)) {
198
            if (($path = readlink($path)) === false) {
199
                throw new \Exception('Failed to resolve symbolic link');
200
            }
201
        }
202
        return $path;
203
    }
204
    /**
205
     * Copies a file from the source path to the destination path.
206
     *
207
     * @param string $sourcePath The path of the source file.
208
     * @param string $destinationPath The path of the destination file.
209
     * @return bool Returns TRUE on success or FALSE on failure.
210
     */
211
    public static function copy($sourcePath, $destinationPath)
212
    {
213
        if (file_exists($sourcePath) && is_file($sourcePath)) {
214
            $destination = new FilePath($destinationPath);
215
            if (file_exists($destination->dir()) && is_dir($destination->dir())) {
216
                return copy($sourcePath, $destinationPath);
217
            }
218
        }
219
        return false;
220
    }
221
222
    /**
223
     * Moves a file from the source path to the destination path.
224
     *
225
     * @param string $sourcePath The path of the source file.
226
     * @param string $destinationPath The path of the destination file.
227
     * @return bool Returns TRUE on success or FALSE on failure.
228
     */
229
    public static function move($sourcePath, $destinationPath)
230
    {
231
        if (file_exists($sourcePath) && is_file($sourcePath)) {
232
            $destination = new FilePath($destinationPath);
233
            if (file_exists($destination->dir()) && is_dir($destination->dir())) {
234
                return rename($sourcePath, $destinationPath);
235
            }
236
        }
237
238
        return false;
239
    }
240
241
    /**
242
     * Creates a directory with the specified path and permissions.
243
     *
244
     * @param string $directoryPath The path of the directory to create.
245
     * @param int $permission The permissions to set for the directory.
246
     * @param bool $recursive Whether to create parent directories if they don't exist.
247
     * @return bool Returns TRUE on success or FALSE on failure.
248
     */
249
    public static function makeDirectory($directoryPath, $permission = 0777, $recursive = true): bool
250
    {
251
        return mkdir($directoryPath, $permission, $recursive);
252
    }
253
254
    /**
255
     * Removes a file or directory with the specified path.
256
     *
257
     * @param string $sourcePath The path of the file or directory to remove.
258
     * @param bool $followLink Whether to follow symbolic links.
259
     * @return bool Returns TRUE on success or FALSE on failure.
260
     */
261
    public static function remove($sourcePath, $followLink = false): bool
262
    {
263
        $success = false;
264
        if (file_exists($sourcePath)) {
265
            if (is_link($sourcePath) && $followLink === true) {
266
                $success = self::remove(readlink($sourcePath));
267
            } elseif (is_file($sourcePath)) {
268
                $success = unlink($sourcePath);
269
            } elseif (is_dir($sourcePath)) {
270
                $success = rmdir($sourcePath);
271
            }
272
        }
273
        return $success;
274
    }
275
276
    /**
277
     * Scans a directory for files and directories that match a regular expression.
278
     *
279
     * @param string $directoryPath The path of the directory to scan.
280
     * @param string|null $regex The regular expression to match against file and directory names.
281
     * @return array|null Returns an array of file and directory names that match the regular expression, or NULL if the directory doesn't exist.
282
     * @throws \Exception If the directory cannot be scanned.
283
     */
284
    public static function pregScandir($directoryPath, $regex = null)
285
    {
286
        if (!file_exists($directoryPath) || !is_dir($directoryPath)) {
287
            return null;
288
        }
289
290
        if (($fileNames = scandir($directoryPath, SCANDIR_SORT_ASCENDING)) !== false) {
291
            return is_null($regex) ? $fileNames : preg_grep($regex, $fileNames);
292
        }
293
294
        throw new \Exception("directory path '$directoryPath' cannot be scanned");
295
    }
296
}
297