Completed
Push — master ( 6b472e...3cbf63 )
by Bernhard
02:44
created

Glob   D

Complexity

Total Complexity 87

Size/Duplication

Total Lines 462
Duplicated Lines 5.63 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 91.05%

Importance

Changes 20
Bugs 4 Features 8
Metric Value
wmc 87
c 20
b 4
f 8
lcom 1
cbo 2
dl 26
loc 462
ccs 173
cts 190
cp 0.9105
rs 4.8718

8 Methods

Rating   Name   Duplication   Size   Complexity  
A glob() 0 8 1
A match() 0 16 4
B getBasePath() 0 24 4
D toRegEx() 14 128 42
C getStaticPrefix() 12 57 18
A isDynamic() 0 4 4
A __construct() 0 3 1
C filter() 0 43 13

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Glob 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 Glob, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of the webmozart/glob package.
5
 *
6
 * (c) Bernhard Schussek <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Webmozart\Glob;
13
14
use InvalidArgumentException;
15
use Webmozart\Glob\Iterator\GlobIterator;
16
use Webmozart\PathUtil\Path;
17
18
/**
19
 * Searches and matches file paths using Ant-like globs.
20
 *
21
 * This class implements an Ant-like version of PHP's `glob()` function. The
22
 * wildcard "*" matches any number of characters except directory separators.
23
 * The double wildcard "**" matches any number of characters, including
24
 * directory separators.
25
 *
26
 * Use {@link glob()} to glob the filesystem for paths:
27
 *
28
 * ```php
29
 * foreach (Glob::glob('/project/**.twig') as $path) {
30
 *     // do something...
31
 * }
32
 * ```
33
 *
34
 * Use {@link match()} to match a file path against a glob:
35
 *
36
 * ```php
37
 * if (Glob::match('/project/views/index.html.twig', '/project/**.twig')) {
38
 *     // path matches
39
 * }
40
 * ```
41
 *
42
 * You can also filter an array of paths for all paths that match your glob with
43
 * {@link filter()}:
44
 *
45
 * ```php
46
 * $filteredPaths = Glob::filter($paths, '/project/**.twig');
47
 * ```
48
 *
49
 * Internally, the methods described above convert the glob into a regular
50
 * expression that is then matched against the matched paths. If you need to
51
 * match many paths against the same glob, you should convert the glob manually
52
 * and use {@link preg_match()} to test the paths:
53
 *
54
 * ```php
55
 * $staticPrefix = Glob::getStaticPrefix('/project/**.twig');
56
 * $regEx = Glob::toRegEx('/project/**.twig');
57
 *
58
 * if (0 !== strpos($path, $staticPrefix)) {
59
 *     // no match
60
 * }
61
 *
62
 * if (!preg_match($regEx, $path)) {
63
 *     // no match
64
 * }
65
 * ```
66
 *
67
 * The method {@link getStaticPrefix()} returns the part of the glob up to the
68
 * first wildcard "*". You should always test whether a path has this prefix
69
 * before calling the much more expensive {@link preg_match()}.
70
 *
71
 * @since  1.0
72
 *
73
 * @author Bernhard Schussek <[email protected]>
74
 */
75
final class Glob
76
{
77
    /**
78
     * Flag: Filter the values in {@link Glob::filter()}.
79
     */
80
    const FILTER_VALUE = 1;
81
82
    /**
83
     * Flag: Filter the keys in {@link Glob::filter()}.
84
     */
85
    const FILTER_KEY = 2;
86
87
    /**
88
     * Globs the file system paths matching the glob.
89
     *
90
     * The glob may contain the wildcard "*". This wildcard matches any number
91
     * of characters, *including* directory separators.
92
     *
93
     * ```php
94
     * foreach (Glob::glob('/project/**.twig') as $path) {
95
     *     // do something...
96
     * }
97
     * ```
98
     *
99
     * @param string $glob  The canonical glob. The glob should contain forward
100
     *                      slashes as directory separators only. It must not
101
     *                      contain any "." or ".." segments. Use the
102
     *                      "webmozart/path-util" utility to canonicalize globs
103
     *                      prior to calling this method.
104
     * @param int    $flags A bitwise combination of the flag constants in this
105
     *                      class.
106
     *
107
     * @return string[] The matching paths. The keys of the array are
108
     *                  incrementing integers.
109
     */
110 8
    public static function glob($glob, $flags = 0)
111
    {
112 8
        $results = iterator_to_array(new GlobIterator($glob, $flags));
113
114 3
        sort($results);
115
116 3
        return $results;
117
    }
118
119
    /**
120
     * Matches a path against a glob.
121
     *
122
     * ```php
123
     * if (Glob::match('/project/views/index.html.twig', '/project/**.twig')) {
124
     *     // path matches
125
     * }
126
     * ```
127
     *
128
     * @param string $path  The path to match.
129
     * @param string $glob  The canonical glob. The glob should contain forward
130
     *                      slashes as directory separators only. It must not
131
     *                      contain any "." or ".." segments. Use the
132
     *                      "webmozart/path-util" utility to canonicalize globs
133
     *                      prior to calling this method.
134
     * @param int    $flags A bitwise combination of the flag constants in
135
     *                      this class.
136
     *
137
     * @return bool Returns `true` if the path is matched by the glob.
138
     */
139 12
    public static function match($path, $glob, $flags = 0)
140
    {
141 12
        if (!self::isDynamic($glob)) {
142 1
            return $glob === $path;
143
        }
144
145 11
        if (0 !== strpos($path, self::getStaticPrefix($glob, $flags))) {
146 6
            return false;
147
        }
148
149 5
        if (!preg_match(self::toRegEx($glob, $flags), $path)) {
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return (bool) preg_match...$glob, $flags), $path);.
Loading history...
150
            return false;
151
        }
152
153 5
        return true;
154
    }
155
156
    /**
157
     * Filters an array for paths matching a glob.
158
     *
159
     * The filtered array is returned. This array preserves the keys of the
160
     * passed array.
161
     *
162
     * ```php
163
     * $filteredPaths = Glob::filter($paths, '/project/**.twig');
164
     * ```
165
     *
166
     * @param string[] $paths A list of paths.
167
     * @param string   $glob  The canonical glob. The glob should contain
168
     *                        forward slashes as directory separators only. It
169
     *                        must not contain any "." or ".." segments. Use the
170
     *                        "webmozart/path-util" utility to canonicalize
171
     *                        globs prior to calling this method.
172
     * @param int      $flags A bitwise combination of the flag constants in
173
     *                        this class.
174
     *
175
     * @return string[] The paths matching the glob indexed by their original
176
     *                  keys.
177
     */
178 9
    public static function filter(array $paths, $glob, $flags = self::FILTER_VALUE)
179
    {
180 9
        if (($flags & self::FILTER_VALUE) && ($flags & self::FILTER_KEY)) {
181 1
            throw new InvalidArgumentException('The flags Glob::FILTER_VALUE and Glob::FILTER_KEY cannot be passed at the same time.');
182
        }
183
184 8
        if (!self::isDynamic($glob)) {
185 2
            if ($flags & self::FILTER_KEY) {
186 1
                return isset($paths[$glob]) ? array($glob => $paths[$glob]) : array();
187
            }
188
189 1
            $key = array_search($glob, $paths);
190
191 1
            return false !== $key ? array($key => $glob) : array();
192
        }
193
194 6
        $staticPrefix = self::getStaticPrefix($glob, $flags);
195 4
        $regExp = self::toRegEx($glob, $flags);
196 4
        $filter = function ($path) use ($staticPrefix, $regExp) {
197 4
            return 0 === strpos($path, $staticPrefix) && preg_match($regExp, $path);
198 4
        };
199
200 4
        if (PHP_VERSION_ID >= 50600) {
201 4
            $filterFlags = ($flags & self::FILTER_KEY) ? ARRAY_FILTER_USE_KEY : 0;
202
203 4
            return array_filter($paths, $filter, $filterFlags);
204
        }
205
206
        // No support yet for the third argument of array_filter()
207
        if ($flags & self::FILTER_KEY) {
208
            $result = array();
209
210
            foreach ($paths as $path => $value) {
211
                if ($filter($path)) {
212
                    $result[$path] = $value;
213
                }
214
            }
215
216
            return $result;
217
        }
218
219
        return array_filter($paths, $filter);
220
    }
221
222
    /**
223
     * Returns the base path of a glob.
224
     *
225
     * This method returns the most specific directory that contains all files
226
     * matched by the glob. If this directory does not exist on the file system,
227
     * it's not necessary to execute the glob algorithm.
228
     *
229
     * More specifically, the "base path" is the longest path trailed by a "/"
230
     * on the left of the first wildcard "*". If the glob does not contain
231
     * wildcards, the directory name of the glob is returned.
232
     *
233
     * ```php
234
     * Glob::getBasePath('/css/*.css');
235
     * // => /css
236
     *
237
     * Glob::getBasePath('/css/style.css');
238
     * // => /css
239
     *
240
     * Glob::getBasePath('/css/st*.css');
241
     * // => /css
242
     *
243
     * Glob::getBasePath('/*.css');
244
     * // => /
245
     * ```
246
     *
247
     * @param string $glob  The canonical glob. The glob should contain forward
248
     *                      slashes as directory separators only. It must not
249
     *                      contain any "." or ".." segments. Use the
250
     *                      "webmozart/path-util" utility to canonicalize globs
251
     *                      prior to calling this method.
252
     * @param int    $flags A bitwise combination of the flag constants in this
253
     *                      class.
254
     *
255
     * @return string The base path of the glob.
256
     */
257 42
    public static function getBasePath($glob, $flags = 0)
258
    {
259
        // Search the static prefix for the last "/"
260 42
        $staticPrefix = self::getStaticPrefix($glob, $flags);
261
262 40
        if (false !== ($pos = strrpos($staticPrefix, '/'))) {
263
            // Special case: Return "/" if the only slash is at the beginning
264
            // of the glob
265 40
            if (0 === $pos) {
266 2
                return '/';
267
            }
268
269
            // Special case: Include trailing slash of "scheme:///foo"
270 38
            if ($pos - 3 === strpos($glob, '://')) {
271 3
                return substr($staticPrefix, 0, $pos + 1);
272
            }
273
274 35
            return substr($staticPrefix, 0, $pos);
275
        }
276
277
        // Glob contains no slashes on the left of the wildcard
278
        // Return an empty string
279
        return '';
280
    }
281
282
    /**
283
     * Converts a glob to a regular expression.
284
     *
285
     * Use this method if you need to match many paths against a glob:
286
     *
287
     * ```php
288
     * $staticPrefix = Glob::getStaticPrefix('/project/**.twig');
289
     * $regEx = Glob::toRegEx('/project/**.twig');
290
     *
291
     * if (0 !== strpos($path, $staticPrefix)) {
292
     *     // no match
293
     * }
294
     *
295
     * if (!preg_match($regEx, $path)) {
296
     *     // no match
297
     * }
298
     * ```
299
     *
300
     * You should always test whether a path contains the static prefix of the
301
     * glob returned by {@link getStaticPrefix()} to reduce the number of calls
302
     * to the expensive {@link preg_match()}.
303
     *
304
     * @param string $glob  The canonical glob. The glob should contain forward
305
     *                      slashes as directory separators only. It must not
306
     *                      contain any "." or ".." segments. Use the
307
     *                      "webmozart/path-util" utility to canonicalize globs
308
     *                      prior to calling this method.
309
     * @param int    $flags A bitwise combination of the flag constants in this
310
     *                      class.
311
     *
312
     * @return string The regular expression for matching the glob.
313
     */
314 75
    public static function toRegEx($glob, $flags = 0, $delimiter = '~')
0 ignored issues
show
Unused Code introduced by
The parameter $flags is not used and could be removed.

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

Loading history...
315
    {
316 75 View Code Duplication
        if (!Path::isAbsolute($glob) && false === strpos($glob, '://')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
317 1
            throw new InvalidArgumentException(sprintf(
318 1
                'The glob "%s" is not absolute and not a URI.',
319
                $glob
320 1
            ));
321
        }
322
323 74
        $inSquare = false;
324 74
        $curlyLevels = 0;
325 74
        $regex = '';
326 74
        $length = strlen($glob);
327
328 74
        for ($i = 0; $i < $length; ++$i) {
329 74
            $c = $glob[$i];
330
331
            switch ($c) {
332 74
                case '.':
333 74
                case '(':
334 74
                case ')':
335 74
                case '|':
336 74
                case '+':
337 74
                case '^':
338 74
                case '$':
339 74
                case $delimiter:
340 71
                    $regex .= "\\$c";
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $c instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
341 71
                    break;
342
343 74 View Code Duplication
                case '/':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
344 74
                    if (isset($glob[$i + 3]) && '**/' === $glob[$i + 1].$glob[$i + 2].$glob[$i + 3]) {
345 24
                        $regex .= '/([^/]+/)*';
346 24
                        $i += 3;
347 24
                    } else {
348 72
                        $regex .= '/';
349
                    }
350 74
                    break;
351
352 74
                case '*':
353 40
                    $regex .= '[^/]*';
354 40
                    break;
355
356 74
                case '?':
357 3
                    $regex .= '.';
358 3
                    break;
359
360 74
                case '{':
361 8
                    $regex .= '(';
362 8
                    ++$curlyLevels;
363 8
                    break;
364
365 74
                case '}':
366 8
                    if ($curlyLevels > 0) {
367 6
                        $regex .= ')';
368 6
                        --$curlyLevels;
369 6
                    } else {
370 3
                        $regex .= '}';
371
                    }
372 8
                    break;
373
374 74
                case ',':
375 9
                    $regex .= $curlyLevels > 0 ? '|' : ',';
376 9
                    break;
377
378 74
                case '[':
379 12
                    $regex .= '[';
380 12
                    $inSquare = true;
381 12
                    if (isset($glob[$i + 1]) && '^' === $glob[$i + 1]) {
382 1
                        $regex .= '^';
383 1
                        ++$i;
384 1
                    }
385 12
                    break;
386
387 74
                case ']':
388 12
                    $regex .= $inSquare ? ']' : '\\]';
389 12
                    $inSquare = false;
390 12
                    break;
391
392 74
                case '-':
393 13
                    $regex .= $inSquare ? '-' : '\\-';
394 13
                    break;
395
396 74
                case '\\':
397 33
                    if (isset($glob[$i + 1])) {
398 33
                        switch ($glob[$i + 1]) {
399 33
                            case '*':
400 33
                            case '?':
401 33
                            case '{':
402 33
                            case '}':
403 33
                            case '[':
404 33
                            case ']':
405 33
                            case '-':
406 33
                            case '^':
407 33
                            case '\\':
408 33
                                $regex .= '\\'.$glob[$i + 1];
409 33
                                ++$i;
410 33
                                break;
411
412
                            default:
413
                                $regex .= '\\\\';
414 33
                        }
415 33
                    } else {
416
                        $regex .= '\\\\';
417
                    }
418 33
                    break;
419
420 74
                default:
421 74
                    $regex .= $c;
422 74
                    break;
423 74
            }
424 74
        }
425
426 74
        if ($inSquare) {
427 2
            throw new InvalidArgumentException(sprintf(
428 2
                'Invalid glob: missing ] in %s',
429
                $glob
430 2
            ));
431
        }
432
433 72
        if ($curlyLevels > 0) {
434 2
            throw new InvalidArgumentException(sprintf(
435 2
                'Invalid glob: missing } in %s',
436
                $glob
437 2
            ));
438
        }
439
440 70
        return $delimiter.'^'.$regex.'$'.$delimiter;
441
    }
442
443
    /**
444
     * Returns the static prefix of a glob.
445
     *
446
     * The "static prefix" is the part of the glob up to the first wildcard "*".
447
     * If the glob does not contain wildcards, the full glob is returned.
448
     *
449
     * @param string $glob  The canonical glob. The glob should contain forward
450
     *                      slashes as directory separators only. It must not
451
     *                      contain any "." or ".." segments. Use the
452
     *                      "webmozart/path-util" utility to canonicalize globs
453
     *                      prior to calling this method.
454
     * @param int    $flags A bitwise combination of the flag constants in this
455
     *                      class.
456
     *
457
     * @return string The static prefix of the glob.
458
     */
459 81
    public static function getStaticPrefix($glob, $flags = 0)
0 ignored issues
show
Unused Code introduced by
The parameter $flags is not used and could be removed.

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

Loading history...
460
    {
461 81 View Code Duplication
        if (!Path::isAbsolute($glob) && false === strpos($glob, '://')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
462 6
            throw new InvalidArgumentException(sprintf(
463 6
                'The glob "%s" is not absolute and not a URI.',
464
                $glob
465 6
            ));
466
        }
467
468 75
        $prefix = '';
469 75
        $length = strlen($glob);
470
471 75
        for ($i = 0; $i < $length; ++$i) {
472 75
            $c = $glob[$i];
473
474
            switch ($c) {
475 75 View Code Duplication
                case '/':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
476 75
                    $prefix .= '/';
477 75
                    if (isset($glob[$i + 3]) && '**/' === $glob[$i + 1].$glob[$i + 2].$glob[$i + 3]) {
478 20
                        break 2;
479
                    }
480 73
                    break;
481
482 73
                case '*':
483 73
                case '?':
484 73
                case '{':
485 73
                case '[':
486 35
                    break 2;
487
488 72
                case '\\':
489 29
                    if (isset($glob[$i + 1])) {
490 29
                        switch ($glob[$i + 1]) {
491 29
                            case '*':
492 29
                            case '?':
493 29
                            case '{':
494 29
                            case '[':
495 29
                            case '\\':
496 29
                                $prefix .= $glob[$i + 1];
497 29
                                ++$i;
498 29
                                break;
499
500 1
                            default:
501 1
                                $prefix .= '\\';
502 29
                        }
503 29
                    } else {
504
                        $prefix .= '\\';
505
                    }
506 29
                    break;
507
508 72
                default:
509 72
                    $prefix .= $c;
510 72
                    break;
511 72
            }
512 73
        }
513
514 75
        return $prefix;
515
    }
516
517
    /**
518
     * Returns whether the glob contains a dynamic part.
519
     *
520
     * The glob contains a dynamic part if it contains an unescaped "*" or
521
     * "{" character.
522
     *
523
     * @param string $glob The glob to test.
524
     *
525
     * @return bool Returns `true` if the glob contains a dynamic part and
526
     *              `false` otherwise.
527
     */
528 40
    public static function isDynamic($glob)
529
    {
530 40
        return false !== strpos($glob, '*') || false !== strpos($glob, '{') || false !== strpos($glob, '?') || false !== strpos($glob, '[');
531
    }
532
533
    private function __construct()
534
    {
535
    }
536
}
537