Completed
Pull Request — 2.x (#402)
by
unknown
02:15
created

Enumerator::getExcludePaths()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 6
cts 6
cp 1
rs 9.9
c 0
b 0
f 0
cc 2
nc 2
nop 0
crap 2
1
<?php
2
/*
3
 * Go! AOP framework
4
 *
5
 * @copyright Copyright 2015, Lisachenko Alexander <[email protected]>
6
 *
7
 * This source file is subject to the license that is bundled
8
 * with this source code in the file LICENSE.
9
 */
10
11
namespace Go\Instrument\FileSystem;
12
13
use CallbackFilterIterator;
14
use InvalidArgumentException;
15
use LogicException;
16
use RecursiveIteratorIterator;
17
use SplFileInfo;
18
use Symfony\Component\Finder\Finder;
19
use UnexpectedValueException;
20
21
/**
22
 * Enumerates files in the concrete directory, applying filtration logic
23
 */
24
class Enumerator
25
{
26
27
    /**
28
     * Path to the root directory, where enumeration should start
29
     *
30
     * @var string
31
     */
32
    private $rootDirectory;
33
34
    /**
35
     * List of additional include paths, should be below rootDirectory
36
     *
37
     * @var array
38
     */
39
    private $includePaths;
40
41
    /**
42
     * List of additional exclude paths, should be below rootDirectory
43
     *
44
     * @var array
45
     */
46
    private $excludePaths;
47
48
    /**
49
     * Initializes an enumerator
50
     *
51
     * @param string $rootDirectory Path to the root directory
52
     * @param array  $includePaths  List of additional include paths
53
     * @param array  $excludePaths  List of additional exclude paths
54
     */
55 7
    public function __construct($rootDirectory, array $includePaths = [], array $excludePaths = [])
56
    {
57 7
        $this->rootDirectory = $rootDirectory;
58 7
        $this->includePaths = $includePaths;
59 7
        $this->excludePaths = $excludePaths;
60 7
    }
61
62
    /**
63
     * Returns an enumerator for files
64
     *
65
     * @return CallbackFilterIterator|RecursiveIteratorIterator|\IteratorIterator|SplFileInfo[]
66
     * @throws UnexpectedValueException
67
     * @throws InvalidArgumentException
68
     * @throws LogicException
69
     */
70 7
    public function enumerate()
71
    {
72 7
        $finder = new Finder();
73 7
        $finder->files()
74 7
            ->name('*.php')
75 7
            ->in($this->getInPaths());
76
77 7
        foreach ($this->getExcludePaths() as $path) {
78 4
            $finder->notPath($path);
79
        }
80
81 7
        $arr = [];
82 7
        foreach ($finder->getIterator() as $x) {
83 7
            $arr[] = $x;
84
        }
85
86 7
        return new \ArrayIterator($arr);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new \ArrayIterator($arr); (ArrayIterator) is incompatible with the return type documented by Go\Instrument\FileSystem\Enumerator::enumerate of type RecursiveIteratorIterato...rIterator|SplFileInfo[].

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
87
88
        $this->dumpForAppVeyor($finder->getIterator());
0 ignored issues
show
Unused Code introduced by
$this->dumpForAppVeyor($finder->getIterator()); does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
89
        $iterator = new \RecursiveIteratorIterator(
90
            new \RecursiveDirectoryIterator(
91
                $this->rootDirectory,
92
                \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::UNIX_PATHS
93
            )
94
        );
95
96
        $callback = $this->getFilter();
97
        $iterator = new \CallbackFilterIterator($iterator, $callback);
98
99
        $this->dumpForAppVeyor($iterator);
100
101
        return $finder->getIterator();
102
    }
103
104
    private function dumpForAppVeyor($iterator)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
105
    {
106
        echo 'start' . PHP_EOL . PHP_EOL;
107
108
        foreach ($iterator as $thing) {
109
            var_dump($thing->getRealPath());
0 ignored issues
show
Security Debugging Code introduced by
var_dump($thing->getRealPath()); looks like debug code. Are you sure you do not want to remove it? This might expose sensitive data.
Loading history...
110
        }
111
    }
112
113
    /**
114
     * @return array
115
     * @throws UnexpectedValueException
116
     */
117 7
    private function getInPaths()
118
    {
119 7
        $inPaths = [];
120
121 7
        foreach ($this->includePaths as $path) {
122 1
            if (strpos($path, $this->rootDirectory, 0) === false) {
123
                throw new UnexpectedValueException(sprintf('Path %s is not in %s', $path, $this->rootDirectory));
124
            }
125
126 1
            $path = str_replace('*', '', $path);
127 1
            $inPaths[] = $path;
128
        }
129
130 7
        if (empty($inPaths)) {
131 6
            $inPaths[] = $this->rootDirectory;
132
        }
133
134 7
        return $inPaths;
135
    }
136
137
    /**
138
     * @return array
139
     */
140 7
    private function getExcludePaths()
141
    {
142 7
        $excludePaths = [];
143
144 7
        foreach ($this->excludePaths as $path) {
145 4
            $path = str_replace('*', '.*', $path);
146 4
            $excludePaths[] = '#' . str_replace($this->rootDirectory . '/', '', $path) . '#';
147
        }
148
149 7
        return $excludePaths;
150
    }
151
152
    /**
153
     * Returns a filter callback for enumerating files
154
     *
155
     * @return \Closure
156
     */
157
    public function getFilter()
158
    {
159
        $rootDirectory = $this->rootDirectory;
160
        $includePaths = $this->includePaths;
161
        $excludePaths = $this->excludePaths;
162
163
        return function (SplFileInfo $file) use ($rootDirectory, $includePaths, $excludePaths) {
164
165
            if ($file->getExtension() !== 'php') {
166
                return false;
167
            }
168
169
            $fullPath = $this->getFileFullPath($file);
170
            // Do not touch files that not under rootDirectory
171
            if (strpos($fullPath, $rootDirectory) !== 0) {
172
                return false;
173
            }
174
175
            if (!empty($includePaths)) {
176
                $found = false;
177
                foreach ($includePaths as $includePattern) {
178
                    if (fnmatch("{$includePattern}*", $fullPath, FNM_NOESCAPE)) {
179
                        $found = true;
180
                        break;
181
                    }
182
                }
183
                if (!$found) {
184
                    return false;
185
                }
186
            }
187
188
            foreach ($excludePaths as $excludePattern) {
189
                if (fnmatch("{$excludePattern}*", $fullPath, FNM_NOESCAPE)) {
190
                    return false;
191
                }
192
            }
193
194
            return true;
195
        };
196
    }
197
198
    /**
199
     * Return the real path of the given file
200
     *
201
     * This is used for testing purpose with virtual file system.
202
     * In a vfs the 'realPath' methode will always return false.
203
     * So we have a chance to mock this single function to return different path.
204
     *
205
     * @param SplFileInfo $file
206
     *
207
     * @return string
208
     */
209
    protected function getFileFullPath(SplFileInfo $file)
210
    {
211
        return $file->getRealPath();
212
    }
213
214
}
215