Completed
Push — master ( 91676c...354384 )
by Alexander
02:23
created

Enumerator::enumerate()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3.0067

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 10
cts 11
cp 0.9091
rs 9.6
c 0
b 0
f 0
cc 3
nc 4
nop 0
crap 3.0067
1
<?php
2
declare(strict_types = 1);
3
/*
4
 * Go! AOP framework
5
 *
6
 * @copyright Copyright 2015, Lisachenko Alexander <[email protected]>
7
 *
8
 * This source file is subject to the license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace Go\Instrument\FileSystem;
13
14
use ArrayIterator;
15
use Closure;
16
use InvalidArgumentException;
17
use Iterator;
18
use LogicException;
19
use SplFileInfo;
20
use Symfony\Component\Finder\Finder;
21
use UnexpectedValueException;
22
23
/**
24
 * Enumerates files in the concrete directory, applying filtration logic
25
 */
26
class Enumerator
27
{
28
    /**
29
     * Path to the root directory, where enumeration should start
30
     */
31
    private $rootDirectory;
32
33
    /**
34
     * List of additional include paths, should be below rootDirectory
35
     */
36
    private $includePaths;
37
38
    /**
39
     * List of additional exclude paths, should be below rootDirectory
40
     */
41
    private $excludePaths;
42
43
    /**
44
     * Initializes an enumerator
45
     *
46
     * @param string $rootDirectory Path to the root directory
47
     * @param array  $includePaths  List of additional include paths
48
     * @param array  $excludePaths  List of additional exclude paths
49
     */
50 8
    public function __construct(string $rootDirectory, array $includePaths = [], array $excludePaths = [])
51
    {
52 8
        $this->rootDirectory = $rootDirectory;
53 8
        $this->includePaths = $includePaths;
54 8
        $this->excludePaths = $excludePaths;
55 8
    }
56
57
    /**
58
     * Returns an enumerator for files
59
     *
60
     * @return Iterator|SplFileInfo[]
61
     * @throws UnexpectedValueException
62
     * @throws InvalidArgumentException
63
     * @throws LogicException
64
     */
65 7
    public function enumerate(): Iterator
66
    {
67 7
        $finder = new Finder();
68 7
        $finder->files()
69 7
            ->name('*.php')
70 7
            ->in($this->getInPaths());
71
72 7
        foreach ($this->getExcludePaths() as $path) {
73 4
            $finder->notPath($path);
74
        }
75
76 7
        $iterator = $finder->getIterator();
77
78
        // on Windows platform the default iterator is unable to rewind, not sure why
79 7
        if (strpos(PHP_OS, 'WIN') === 0) {
80
            $iterator = new ArrayIterator(iterator_to_array($iterator));
81
        }
82
83 7
        return $iterator;
84
    }
85
86
    /**
87
     * Returns a filter callback for enumerating files
88
     */
89 18
    public function getFilter(): Closure
90
    {
91 1
        $rootDirectory = $this->rootDirectory;
92 1
        $includePaths = $this->includePaths;
93 1
        $excludePaths = $this->excludePaths;
94
95
        return function (SplFileInfo $file) use ($rootDirectory, $includePaths, $excludePaths) {
96
97 18
            if ($file->getExtension() !== 'php') {
98
                return false;
99
            }
100
101 18
            $fullPath = $this->getFileFullPath($file);
102
            // Do not touch files that not under rootDirectory
103 18
            if (strpos($fullPath, $rootDirectory) !== 0) {
104 18
                return false;
105
            }
106
107 1
            if (!empty($includePaths)) {
108 1
                $found = false;
109 1
                foreach ($includePaths as $includePattern) {
110 1
                    if (fnmatch("{$includePattern}*", $fullPath, FNM_NOESCAPE)) {
111 1
                        $found = true;
112 1
                        break;
113
                    }
114
                }
115 1
                if (!$found) {
116
                    return false;
117
                }
118
            }
119
120 1
            foreach ($excludePaths as $excludePattern) {
121 1
                if (fnmatch("{$excludePattern}*", $fullPath, FNM_NOESCAPE)) {
122
                    return false;
123
                }
124
            }
125
126 1
            return true;
127 1
        };
128
    }
129
130
    /**
131
     * Return the real path of the given file
132
     *
133
     * This is used for testing purpose with virtual file system.
134
     * In a vfs the 'realPath' methode will always return false.
135
     * So we have a chance to mock this single function to return different path.
136
     */
137 18
    protected function getFileFullPath(SplFileInfo $file): string
138
    {
139 18
        return $file->getRealPath();
140
    }
141
142
    /**
143
     * Returns collection of directories to look at
144
     *
145
     * @throws UnexpectedValueException if directory not under the root
146
     */
147 7
    private function getInPaths(): array
148
    {
149 7
        $inPaths = [];
150
151 7
        foreach ($this->includePaths as $path) {
152 1
            if (strpos($path, $this->rootDirectory, 0) === false) {
153
                throw new UnexpectedValueException(sprintf('Path %s is not in %s', $path, $this->rootDirectory));
154
            }
155
156 1
            $path = str_replace('*', '', $path);
157 1
            $inPaths[] = $path;
158
        }
159
160 7
        if (empty($inPaths)) {
161 6
            $inPaths[] = $this->rootDirectory;
162
        }
163
164 7
        return $inPaths;
165
    }
166
167
    /**
168
     * Returns the list of excluded paths
169
     */
170 7
    private function getExcludePaths(): array
171
    {
172 7
        $excludePaths = [];
173
174 7
        foreach ($this->excludePaths as $path) {
175 4
            $path = str_replace('*', '.*', $path);
176 4
            $excludePaths[] = '#' . str_replace($this->rootDirectory . '/', '', $path) . '#';
177
        }
178
179 7
        return $excludePaths;
180
    }
181
}
182