Test Failed
Push — test ( 6fb5b7...66cd7f )
by Tom
02:53
created

LibFs::fileLookUp()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 5

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 10
c 1
b 0
f 0
nc 6
nop 2
dl 0
loc 18
ccs 10
cts 10
cp 1
crap 5
rs 9.6111
1
<?php
2
3
/* this file is part of pipelines */
4
5
namespace Ktomk\Pipelines;
6
7
use UnexpectedValueException;
8
9
/**
10
 * Class LibFs - Low level file-system utility functions
11
 *
12
 * @covers \Ktomk\Pipelines\LibFs
13
 */
14
class LibFs
15
{
16
    /**
17
     * @param string $path
18
     * @param string $mode [optional]
19
     *
20
     * @return bool
21
     */
22 2
    public static function canFopen($path, $mode = null)
23
    {
24 2
        if (null === $mode) {
25 1
            $mode = 'rb';
26
        }
27
28 2
        $handle = @fopen($path, $mode);
29 2
        if (false === $handle) {
30 1
            return false;
31
        }
32
33
        /** @scrutinizer ignore-unhandled */
34 1
        @fclose($handle);
35
36 1
        return true;
37
    }
38
39
    /**
40
     * locate (readable) file by basename upward all parent directories
41
     *
42
     * @param string $basename
43
     * @param string $directory [optional] directory to operate from, defaults
44
     *               to "." (relative path of present working directory)
45
     *
46
     * @return null|string
47
     */
48 4
    public static function fileLookUp($basename, $directory = null)
49
    {
50 4
        if ('' === $directory || null === $directory) {
51 1
            $directory = '.';
52
        }
53
54
        for (
55 4
            $dirName = $directory, $old = null;
56 4
            $old !== $dirName;
57 2
            $old = $dirName, $dirName = dirname($dirName)
58
        ) {
59 4
            $test = $dirName . '/' . $basename;
60 4
            if (self::isReadableFile($test)) {
61 3
                return $test;
62
            }
63
        }
64
65 1
        return null;
66
    }
67
68
    /**
69
     * check if path is absolute
70
     *
71
     * @param string $path
72
     *
73
     * @return bool
74
     */
75 8
    public static function isAbsolutePath($path)
76
    {
77
        // TODO: a variant with PHP stream wrapper prefix support
78
        /* @see isStreamUri */
79
80 8
        $count = strspn($path, '/', 0, 3) % 2;
81
82 8
        return (bool)$count;
83
    }
84
85
    /**
86
     * check if path is basename
87
     *
88
     * @param string $path
89
     *
90
     * @return bool
91
     */
92 5
    public static function isBasename($path)
93
    {
94 5
        if (in_array($path, array('', '.', '..'), true)) {
95 1
            return false;
96
        }
97
98 4
        if (false !== strpos($path, '/')) {
99 3
            return false;
100
        }
101
102 1
        return true;
103
    }
104
105
    /**
106
     * see 2.2 Standards permit the exclusion of bad filenames / POSIX.1-2008
107
     *
108
     * @link https://dwheeler.com/essays/fixing-unix-linux-filenames.html
109
     *
110
     * @param string $filename
111
     *
112
     * @return bool
113
     */
114 10
    public static function isPortableFilename($filename)
115
    {
116
        # A-Z, a-z, 0-9, <period>, <underscore>, and <hyphen>)
117 10
        $result = preg_match('(^(?!-)[A-Za-z0-9._-]+$)', $filename);
118 10
        if (false === $result) {
119
            // @codeCoverageIgnoreStart
120
            throw new UnexpectedValueException('preg_match pattern failed');
121
            // @codeCoverageIgnoreEnd
122
        }
123
124 10
        return 1 === $result;
125
    }
126
127
    /**
128
     * @param string $path
129
     *
130
     * @return bool
131
     */
132 11
    public static function isReadableFile($path)
133
    {
134 11
        if (!is_file($path)) {
135 4
            return false;
136
        }
137
138 9
        return is_readable($path) ?: self::canFopen($path, 'rb');
139
    }
140
141
    /**
142
     * similar to a readable file, a readable path allows stream-urls
143
     * as well, e.g. php://stdin or php://fd/0 but only for local
144
     * streams.
145
     *
146 2
     * @param string $path
147
     *
148 2
     * @return bool
149 2
     */
150 1
    public static function isReadableStream($path)
151
    {
152
        if (self::isReadableFile($path)) {
153 1
            return true;
154
        }
155
156
        if (!is_string($path)) {
0 ignored issues
show
introduced by
The condition is_string($path) is always true.
Loading history...
157
            return false;
158
        }
159
160
        if (!stream_is_local($path)) {
161
            return false;
162
        }
163
164 7
        return self::canFopen($path, 'rb');
165
    }
166 7
167
    /**
168 6
     * @param string $path
169
     *
170
     * @return bool
171
     */
172
    public static function isStreamUri($path)
173
    {
174
        $scheme = parse_url($path, PHP_URL_SCHEME);
175
        if (null === $scheme) {
176
            return false;
177 7
        }
178
179
        return in_array($scheme, stream_get_wrappers(), true);
180
    }
181
182
    /**
183
     * create directory if not yet exists
184
     *
185
     * @param string $path
186
     * @param int $mode [optional]
187
     *
188
     * @return string
189
     */
190 5
    public static function mkDir($path, $mode = 0777)
191
    {
192 5
        if (!is_dir($path)) {
193
            /** @noinspection NestedPositiveIfStatementsInspection */
194 5
            if (!mkdir($path, $mode, true) && !is_dir($path)) {
195
                // @codeCoverageIgnoreStart
196
                throw new \RuntimeException(
197
                    sprintf('Directory "%s" was not created', $path)
198 5
                );
199 4
                // @codeCoverageIgnoreEnd
200 4
            }
201
        }
202
203 5
        return $path;
204
    }
205 5
206
    /**
207
     * Normalize a path as/if common in PHP
208
     *
209
     * E.g. w/ phar:// in front which means w/ stream wrappers in
210
     * mind.
211
     *
212
     * @param string $path
213
     *
214
     * @return string
215
     */
216
    public static function normalizePath($path)
217 18
    {
218
        $buffer = $path;
219 18
220 1
        $scheme = '';
221
        // TODO support for all supported stream wrappers (w/ absolute/relative notation?)
222
        /* @see isStreamUri */
223 17
        /* @see isAbsolutePath */
224
        if (0 === strpos($buffer, 'phar://') || 0 === strpos($buffer, 'file://')) {
225 17
            $scheme = substr($buffer, 0, 7);
226 17
            $buffer = substr($buffer, 7);
227 17
        }
228 9
229 9
        $normalized = self::normalizePathSegments($buffer);
230
231
        return $scheme . $normalized;
232 17
    }
233
234 17
    /**
235 4
     * Resolve relative path segments in a path on it's own
236
     *
237
     * This is not realpath, not resolving any links.
238 13
     *
239 13
     * @param string $path
240 2
     *
241
     * @return string
242
     */
243 11
    public static function normalizePathSegments($path)
244
    {
245 11
        if ('' === $path) {
246 11
            return $path;
247 11
        }
248 11
249 11
        $buffer = $path;
250 2
251
        $prefix = '';
252
        $len = strspn($buffer, '/');
253 11
        if (0 < $len) {
254 11
            $prefix = substr($buffer, 0, $len);
255
            $buffer = substr($buffer, $len);
256 11
        }
257
258
        $buffer = rtrim($buffer, '/');
259 9
260 9
        if (in_array($buffer, array('', '.'), true)) {
261
            return $prefix;
262 9
        }
263
264
        $pos = strpos($buffer, '/');
265 1
        if (false === $pos) {
266
            return $prefix . $buffer;
267
        }
268 11
269
        $buffer = preg_replace('~/+~', '/', $buffer);
270
271
        $segments = explode('/', $buffer);
272
        $stack = array();
273
        foreach ($segments as $segment) {
274
            $i = count($stack) - 1;
275
            if ('.' === $segment) {
276
                continue;
277
            }
278
279 1
            if ('..' !== $segment) {
280
                $stack[] = $segment;
281 1
282 1
                continue;
283
            }
284
285 1
            if (($i > -1) && '..' !== $stack[$i]) {
286
                array_pop($stack);
287
288
                continue;
289
            }
290
291
            $stack[] = $segment;
292
        }
293 5
294
        return $prefix . implode('/', $stack);
295 5
    }
296 5
297
    /**
298
     * rename a file
299 5
     *
300
     * @param string $old
301
     * @param string $new
302
     *
303
     * @return string new file-name
304
     */
305
    public static function rename($old, $new)
306
    {
307
        if (!@rename($old, $new)) {
308
            throw new \RuntimeException(sprintf('Failed to rename "%s" to "%s"', $old, $new));
309 3
        }
310
311 3
        return $new;
312 3
    }
313 1
314
    /**
315
     * @param string $file
316 3
     *
317 3
     * @return string
318 3
     */
319 3
    public static function rm($file)
320 3
    {
321 3
        if (self::isReadableFile($file)) {
322 1
            unlink($file);
323
        }
324 2
325 2
        return $file;
326 1
    }
327 1
328 1
    /**
329 1
     * @param string $dir
330 1
     *
331
     * @throws UnexpectedValueException
332
     *
333
     * @return void
334
     */
335 2
    public static function rmDir($dir)
336
    {
337 2
        $result = @lstat($dir);
338
        if (false === $result) {
339 2
            return;
340
        }
341
342
        $dirs = array();
343
        $dirs[] = $dir;
344
        for ($i = 0; isset($dirs[$i]); $i++) {
345
            $current = $dirs[$i];
346
            $result = @scandir($current);
347
            if (false === $result) {
348
                throw new UnexpectedValueException(sprintf('Failed to open directory: %s', $current));
349
            }
350
            $files = array_diff($result, array('.', '..'));
351 1
            foreach ($files as $file) {
352 1
                $path = "${current}/${file}";
353 1
                if (is_dir($path)) {
354
                    $dirs[] = $path;
355
                } elseif (is_file($path)) {
356
                    self::rm($path);
357
                }
358
            }
359
        }
360
361
        while (null !== ($pop = array_pop($dirs))) {
362 1
            /* @scrutinizer ignore-unhandled */
363 1
            @rmdir($pop);
364
        }
365 1
    }
366
367
    /**
368
     * create symbolic link, recreate if it exists
369
     *
370
     * @param string $target
371
     * @param string $link
372
     *
373
     * @return void
374
     */
375
    public static function symlink($target, $link)
376
    {
377
        self::unlink($link);
378
        symlink($target, $link);
379
    }
380
381
    /**
382
     * @param string $link
383
     *
384
     * @return void
385
     */
386
    public static function unlink($link)
387
    {
388
        if (is_link($link)) {
389
            unlink($link);
390
        }
391
    }
392
}
393