Test Failed
Push — test ( 2be48d...ad29d6 )
by Tom
02:28
created

LibFs   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 358
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 115
c 1
b 0
f 0
dl 0
loc 358
ccs 116
cts 116
cp 1
rs 6.96
wmc 53

17 Methods

Rating   Name   Duplication   Size   Complexity  
A isBasename() 0 11 3
A canFopen() 0 15 3
A tmpDir() 0 7 1
A unlink() 0 4 2
A rm() 0 7 2
A isReadableFile() 0 7 3
A mkDir() 0 14 4
A fileLookUp() 0 18 5
A tmpFile() 0 11 2
A isPortableFilename() 0 11 2
A isAbsolutePath() 0 8 1
A symlink() 0 4 1
A tmpFilePut() 0 6 1
A isStreamUri() 0 8 2
B normalizePathSegments() 0 61 11
B rmDir() 0 29 8
A rename() 0 7 2

How to fix   Complexity   

Complex Class

Complex classes like LibFs often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use LibFs, and based on these observations, apply Extract Interface, too.

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