Completed
Pull Request — master (#50)
by Jonathan
05:13 queued 03:02
created

AnnotationDriver   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 224
Duplicated Lines 0 %

Test Coverage

Coverage 62.32%

Importance

Changes 0
Metric Value
wmc 27
eloc 61
dl 0
loc 224
ccs 43
cts 69
cp 0.6232
rs 10
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A getExcludePaths() 0 3 1
A getPaths() 0 3 1
A addExcludePaths() 0 3 1
A __construct() 0 13 5
A getFileExtension() 0 3 1
A getReader() 0 3 1
A addPaths() 0 3 1
A isTransient() 0 11 3
C getAllClassNames() 0 69 12
A setFileExtension() 0 3 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\Persistence\Mapping\Driver;
6
7
use Doctrine\Common\Annotations\Reader;
8
use Doctrine\Persistence\Mapping\MappingException;
9
use FilesystemIterator;
10
use RecursiveDirectoryIterator;
11
use RecursiveIteratorIterator;
12
use RecursiveRegexIterator;
13
use ReflectionClass;
14
use RegexIterator;
15
use function array_merge;
16
use function array_unique;
17
use function assert;
18
use function get_class;
19
use function get_declared_classes;
20
use function in_array;
21
use function is_array;
22
use function is_dir;
23
use function is_string;
24
use function preg_match;
25
use function preg_quote;
26
use function realpath;
27
use function str_replace;
28
use function strpos;
29
30
/**
31
 * The AnnotationDriver reads the mapping metadata from docblock annotations.
32
 */
33
abstract class AnnotationDriver implements MappingDriver
34
{
35
    /**
36
     * The annotation reader.
37
     *
38
     * @var Reader
39
     */
40
    protected $reader;
41
42
    /**
43
     * The paths where to look for mapping files.
44
     *
45
     * @var string[]
46
     */
47
    protected $paths = [];
48
49
    /**
50
     * The paths excluded from path where to look for mapping files.
51
     *
52
     * @var string[]
53
     */
54
    protected $excludePaths = [];
55
56
    /**
57
     * The file extension of mapping documents.
58
     *
59
     * @var string
60
     */
61
    protected $fileExtension = '.php';
62
63
    /**
64
     * Cache for AnnotationDriver#getAllClassNames().
65
     *
66
     * @var string[]|null
67
     */
68
    protected $classNames;
69
70
    /**
71
     * Name of the entity annotations as keys.
72
     *
73
     * @var string[]
74
     */
75
    protected $entityAnnotationClasses = [];
76
77
    /**
78
     * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
79
     * docblock annotations.
80
     *
81
     * @param Reader               $reader The AnnotationReader to use, duck-typed.
82
     * @param string|string[]|null $paths  One or multiple paths where mapping classes can be found.
83
     */
84 1
    public function __construct(Reader $reader, $paths = null)
85
    {
86 1
        $this->reader = $reader;
87
88 1
        if ($paths === '' || $paths === [] || $paths === null) {
89
            return;
90
        }
91
92 1
        if (! is_array($paths)) {
93
            $paths = [$paths];
94
        }
95
96 1
        $this->addPaths($paths);
97 1
    }
98
99
    /**
100
     * Appends lookup paths to metadata driver.
101
     *
102
     * @param string[] $paths
103
     */
104 1
    public function addPaths(array $paths) : void
105
    {
106 1
        $this->paths = array_unique(array_merge($this->paths, $paths));
107 1
    }
108
109
    /**
110
     * Retrieves the defined metadata lookup paths.
111
     *
112
     * @return string[]
113
     */
114
    public function getPaths() : array
115
    {
116
        return $this->paths;
117
    }
118
119
    /**
120
     * Append exclude lookup paths to metadata driver.
121
     *
122
     * @param string[] $paths
123
     */
124
    public function addExcludePaths(array $paths) : void
125
    {
126
        $this->excludePaths = array_unique(array_merge($this->excludePaths, $paths));
127
    }
128
129
    /**
130
     * Retrieve the defined metadata lookup exclude paths.
131
     *
132
     * @return string[]
133
     */
134
    public function getExcludePaths() : array
135
    {
136
        return $this->excludePaths;
137
    }
138
139
    /**
140
     * Retrieve the current annotation reader
141
     */
142
    public function getReader() : Reader
143
    {
144
        return $this->reader;
145
    }
146
147
    /**
148
     * Gets the file extension used to look for mapping files under.
149
     */
150
    public function getFileExtension() : string
151
    {
152
        return $this->fileExtension;
153
    }
154
155
    /**
156
     * Sets the file extension used to look for mapping files under.
157
     *
158
     * @param string $fileExtension The file extension to set.
159
     */
160
    public function setFileExtension(string $fileExtension) : void
161
    {
162
        $this->fileExtension = $fileExtension;
163
    }
164
165
    /**
166
     * Returns whether the class with the specified name is transient. Only non-transient
167
     * classes, that is entities and mapped superclasses, should have their metadata loaded.
168
     *
169
     * A class is non-transient if it is annotated with an annotation
170
     * from the {@see AnnotationDriver::entityAnnotationClasses}.
171
     */
172 1
    public function isTransient(string $className) : bool
173
    {
174 1
        $classAnnotations = $this->reader->getClassAnnotations(new ReflectionClass($className));
175
176 1
        foreach ($classAnnotations as $annot) {
177 1
            if (isset($this->entityAnnotationClasses[get_class($annot)])) {
178 1
                return false;
179
            }
180
        }
181
182 1
        return true;
183
    }
184
185
    /**
186
     * {@inheritDoc}
187
     */
188 1
    public function getAllClassNames() : array
189
    {
190 1
        if ($this->classNames !== null) {
191
            return $this->classNames;
192
        }
193
194 1
        if ($this->paths === []) {
195
            throw MappingException::pathRequired();
196
        }
197
198 1
        $classes       = [];
199 1
        $includedFiles = [];
200
201 1
        foreach ($this->paths as $path) {
202 1
            if (! is_dir($path)) {
203
                throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
204
            }
205
206 1
            $iterator = new RegexIterator(
207 1
                new RecursiveIteratorIterator(
208 1
                    new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
209 1
                    RecursiveIteratorIterator::LEAVES_ONLY
210
                ),
211 1
                '/^.+' . preg_quote($this->fileExtension) . '$/i',
212 1
                RecursiveRegexIterator::GET_MATCH
213
            );
214
215 1
            foreach ($iterator as $file) {
216 1
                $sourceFile = $file[0];
217
218 1
                if (preg_match('(^phar:)i', $sourceFile) === false) {
219
                    $sourceFile = realpath($sourceFile);
220
                }
221
222 1
                foreach ($this->excludePaths as $excludePath) {
223
                    $realpath = realpath($excludePath);
224
                    assert(is_string($realpath));
225
226
                    $exclude = str_replace('\\', '/', $realpath);
227
                    $current = str_replace('\\', '/', $sourceFile);
228
229
                    if (strpos($current, $exclude) !== false) {
230
                        continue 2;
231
                    }
232
                }
233
234 1
                require_once $sourceFile;
235
236 1
                $includedFiles[] = $sourceFile;
237
            }
238
        }
239
240 1
        $declared = get_declared_classes();
241
242 1
        foreach ($declared as $className) {
243 1
            $rc = new ReflectionClass($className);
244
245 1
            $sourceFile = $rc->getFileName();
246
247 1
            if (! in_array($sourceFile, $includedFiles, true) || $this->isTransient($className)) {
248 1
                continue;
249
            }
250
251 1
            $classes[] = $className;
252
        }
253
254 1
        $this->classNames = $classes;
255
256 1
        return $classes;
257
    }
258
}
259