Issues (4)

src/FileSystem.php (1 issue)

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
    public function __toString()
37
    {
38
        return $this->root();
39
    }
40
41
    /**
42
     * Returns the starting point of a filesystem's abstraction, not the machine's filesystem root. 
43
     * It can be a project's root, or any directory you may want to work with.
44
     *
45
     * @return string The root path of the file system.
46
     */
47
    public function root(): string
48
    {
49
        return $this->rootPath;
50
    }
51
52
    /**
53
     * Returns the absolute path for a given relative path.
54
     *
55
     * @param string $relativePath The relative path to get the absolute path for.
56
     * @param bool $checkExistence Whether to check if the file exists or not. Default is false.
57
     * @return string The absolute path for the given relative path.
58
     * @throws \InvalidArgumentException If the relative path is empty or invalid.
59
     */
60
    public function absolutePathFor(string $relativePath, bool $checkExistence = false): string
61
    {
62
        $absolute = sprintf('%s/%s', $this->root(), $relativePath);
63
64
        if ($checkExistence) {
65
            $absolute = realpath($absolute);
66
            if (!$absolute) {
67
                throw new \InvalidArgumentException('INVALID_PATH');
68
            }
69
        }
70
71
        return $absolute;
72
    }
73
74
    /**
75
     * Lists the contents of a directory.
76
     *
77
     * @param string $relativePath The relative path of the directory to list.
78
     * @return array An array of filenames in the directory.
79
     * @throws \InvalidArgumentException If the specified path is not a directory.
80
     */
81
    public function list(string $relativePath): array
82
    {
83
        $absolutePath = $this->absolutePathFor($relativePath);
84
85
        if (!is_dir($absolutePath)) {
86
            throw new \InvalidArgumentException('RELATIVE_PATH_NOT_A_DIRECTORY');
87
        }
88
89
        return array_diff(scandir($absolutePath), ['.', '..']);
90
    }
91
92
    /**
93
     * Returns an array of files in the specified directory.
94
     *
95
     * @param string $relativePath The relative path of the directory to search.
96
     * @return array An array of file names.
97
     */
98
    public function files(string $relativePath): array
99
    {
100
        $absolutePath = $this->absolutePathFor($relativePath);
101
        // Filter the list of files to include only files (not directories).
102
        $files = array_filter($this->list($relativePath), function ($filename) use ($absolutePath) {
103
            return is_file($absolutePath . DIRECTORY_SEPARATOR . $filename);
104
        });
105
106
        return $files;
107
    }
108
109
    /**
110
     * Returns an array of directories in the specified relative path.
111
     *
112
     * @param string $relativePath The relative path to search for directories.
113
     * @return array An array of directories in the specified relative path.
114
     */
115
    public function directories(string $relativePath): array
116
    {
117
        $absolutePath = $this->absolutePathFor($relativePath);
118
119
        // Filter the list of files to include only files (not directories).
120
        $files = array_filter($this->list($relativePath), function ($filename) use ($absolutePath) {
121
            return is_dir($absolutePath . DIRECTORY_SEPARATOR . $filename);
122
        });
123
124
        return $files;
125
    }
126
127
    /**
128
     * Ensures that the specified relative path is writable.
129
     *
130
     * @param string $relativePath The relative path to ensure is writable.
131
     * @throws \InvalidArgumentException If the path is not inside the root path, the target directory cannot be created, or the target directory is not writable.
132
     * @return bool True if the path is writable, false otherwise.
133
     */
134
135
    public function ensureWritablePath(string $absoluteDirectoryPath, string $filename=null): bool
0 ignored issues
show
The parameter $filename is not used and could be removed. ( Ignorable by Annotation )

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

135
    public function ensureWritablePath(string $absoluteDirectoryPath, /** @scrutinizer ignore-unused */ string $filename=null): bool

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
136
    {
137
        if (strpos($absoluteDirectoryPath, $this->root()) !== 0) {
138
            throw new \InvalidArgumentException('PATH_NOT_INSIDE_ROOT_PATH');
139
        }
140
141
        $relativeDirectoryPath = substr($absoluteDirectoryPath, strlen($this->root()) + 1);
142
        $pathParts = explode('/', $relativeDirectoryPath);
143
        
144
145
        $targetDir = $this->root();
146
        foreach ($pathParts as $i => $part) {
147
            $targetDir .= '/' . $part;
148
149
            // Create the folder if it doesn't exist
150
            if (!file_exists($targetDir) && mkdir($targetDir, 0755, true) === false) {
151
                throw new \InvalidArgumentException('UNABLE_TO_CREATE_MISSING_TARGET_DIRECTORY');
152
            }
153
154
            if (!is_dir($targetDir)) {
155
                throw new \InvalidArgumentException('TARGET_DIRECTORY_NOT_A_DIRECTORY');
156
            }
157
            if (!is_writable($targetDir)) {
158
                throw new \InvalidArgumentException('TARGET_DIRECTORY_NOT_WRITABLE');
159
            }
160
        }
161
        return true;
162
    }
163
164
    public function filenames($regex = null): array
165
    {
166
        $filenames = self::preg_scandir($this->rootPath, $regex); // ID_SEQUENCENUMBER.ext
167
        if (!is_null($filenames)) {
168
            sort($filenames);
169
        }
170
171
        return $filenames;
172
    }
173
174
    // previous implementation, to update
175
    public function filepathes($regex = null): array
176
    {
177
        $filenames = $this->filenames($regex);
178
        $filepathes = [];
179
        foreach ($filenames as $filename) {
180
            $filepathes[] = $this->absolutePathFor($filename);
181
        }
182
183
        return $filepathes;
184
    }
185
186
    /**
187
     * Resolves a symbolic link to its target path.
188
     *
189
     * @param string $path The path to resolve.
190
     * @throws \Exception If `readlink` fails to resolve the symbolic link.
191
     * @return string The resolved path.
192
     */
193
    public static function resolve_symlink($path)
194
    {
195
        if (is_link($path)) {
196
            if (($path = readlink($path)) === false) {
197
                throw new \Exception('Failed to resolve symbolic link');
198
            }
199
        }
200
201
        return $path;
202
    }
203
    /**
204
     * Copies a file from the source path to the destination path.
205
     *
206
     * @param string $sourcePath The path of the source file.
207
     * @param string $destinationPath The path of the destination file.
208
     * @return bool Returns TRUE on success or FALSE on failure.
209
     */
210
    public static function copy($sourcePath, $destinationPath)
211
    {
212
        if (file_exists($sourcePath) && is_file($sourcePath)) {
213
            $destination = new FilePath($destinationPath);
214
            if (file_exists($destination->dir()) && is_dir($destination->dir())) {
215
                return copy($sourcePath, $destinationPath);
216
            }
217
        }
218
        return false;
219
    }
220
221
    /**
222
     * Moves a file from the source path to the destination path.
223
     *
224
     * @param string $sourcePath The path of the source file.
225
     * @param string $destinationPath The path of the destination file.
226
     * @return bool Returns TRUE on success or FALSE on failure.
227
     */
228
    public static function move($sourcePath, $destinationPath)
229
    {
230
        if (file_exists($sourcePath) && is_file($sourcePath)) {
231
            $destination = new FilePath($destinationPath);
232
            if (file_exists($destination->dir()) && is_dir($destination->dir())) {
233
                return rename($sourcePath, $destinationPath);
234
            }
235
        }
236
237
        return false;
238
    }
239
240
    /**
241
     * Creates a directory with the specified path and permissions.
242
     *
243
     * @param string $directoryPath The path of the directory to create.
244
     * @param int $permission The permissions to set for the directory.
245
     * @param bool $recursive Whether to create parent directories if they don't exist.
246
     * @return bool Returns TRUE on success or FALSE on failure.
247
     */
248
    public static function makeDirectory($directoryPath, $permission = 0777, $recursive = true): bool
249
    {
250
        return mkdir($directoryPath, $permission, $recursive);
251
    }
252
253
    /**
254
     * Removes a file or directory with the specified path.
255
     *
256
     * @param string $sourcePath The path of the file or directory to remove.
257
     * @param bool $followLink Whether to follow symbolic links.
258
     * @return bool Returns TRUE on success or FALSE on failure.
259
     */
260
    public static function remove($sourcePath, $followLink = false): bool
261
    {
262
        $success = false;
263
        if (file_exists($sourcePath)) {
264
            if (is_link($sourcePath) && $followLink === true) {
265
                $success = self::remove(readlink($sourcePath));
266
            } elseif (is_file($sourcePath)) {
267
                $success = unlink($sourcePath);
268
            } elseif (is_dir($sourcePath)) {
269
                $success = rmdir($sourcePath);
270
            }
271
        }
272
        return $success;
273
    }
274
275
    /**
276
     * Scans a directory for files and directories that match a regular expression.
277
     *
278
     * @param string $directoryPath The path of the directory to scan.
279
     * @param string|null $regex The regular expression to match against file and directory names.
280
     * @return array|null Returns an array of file and directory names that match the regular expression, or NULL if the directory doesn't exist.
281
     * @throws \Exception If the directory cannot be scanned.
282
     */
283
    public static function preg_scandir($directoryPath, $regex = null)
284
    {
285
        if (!file_exists($directoryPath) || !is_dir($directoryPath)) {
286
            return null;
287
        }
288
        
289
        if (($fileNames = scandir($directoryPath, SCANDIR_SORT_ASCENDING)) !== false) {
290
            return is_null($regex) ? $fileNames : preg_grep($regex, $fileNames);
291
        }
292
293
        throw new \Exception("directory path '$directoryPath' cannot be scanned");
294
    }
295
}
296