AnnotationDriver::addPaths()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 1
crap 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 array<int, string>
46
     */
47
    protected $paths = [];
48
49
    /**
50
     * The paths excluded from path where to look for mapping files.
51
     *
52
     * @var array<int, 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 array<int, string>|null
67
     */
68
    protected $classNames;
69
70
    /**
71
     * Name of the entity annotations as keys.
72
     *
73
     * @var array<string, int>
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 6
    public function __construct(Reader $reader, $paths = null)
85
    {
86 6
        $this->reader = $reader;
87
88 6
        if ($paths === '' || $paths === [] || $paths === null) {
89
            return;
90
        }
91
92 6
        if (! is_array($paths)) {
93
            $paths = [$paths];
94
        }
95
96 6
        $this->addPaths($paths);
97 6
    }
98
99
    /**
100
     * Appends lookup paths to metadata driver.
101
     *
102
     * @param array<int, string> $paths
103
     *
104
     * @return void
105
     */
106 6
    public function addPaths(array $paths)
107
    {
108 6
        $this->paths = array_unique(array_merge($this->paths, $paths));
109 6
    }
110
111
    /**
112
     * Retrieves the defined metadata lookup paths.
113
     *
114
     * @return array<int, string>
115
     */
116 1
    public function getPaths()
117
    {
118 1
        return $this->paths;
119
    }
120
121
    /**
122
     * Append exclude lookup paths to metadata driver.
123
     *
124
     * @param array<int, string> $paths
125
     *
126
     * @return void
127
     */
128 1
    public function addExcludePaths(array $paths)
129
    {
130 1
        $this->excludePaths = array_unique(array_merge($this->excludePaths, $paths));
131 1
    }
132
133
    /**
134
     * Retrieve the defined metadata lookup exclude paths.
135
     *
136
     * @return array<int, string>
137
     */
138 1
    public function getExcludePaths()
139
    {
140 1
        return $this->excludePaths;
141
    }
142
143
    /**
144
     * Retrieve the current annotation reader
145
     *
146
     * @return Reader
147
     */
148 1
    public function getReader()
149
    {
150 1
        return $this->reader;
151
    }
152
153
    /**
154
     * Gets the file extension used to look for mapping files under.
155
     *
156
     * @return string
157
     */
158 1
    public function getFileExtension()
159
    {
160 1
        return $this->fileExtension;
161
    }
162
163
    /**
164
     * Sets the file extension used to look for mapping files under.
165
     *
166
     * @param string $fileExtension The file extension to set.
167
     *
168
     * @return void
169
     */
170 1
    public function setFileExtension(string $fileExtension)
171
    {
172 1
        $this->fileExtension = $fileExtension;
173 1
    }
174
175
    /**
176
     * Returns whether the class with the specified name is transient. Only non-transient
177
     * classes, that is entities and mapped superclasses, should have their metadata loaded.
178
     *
179
     * A class is non-transient if it is annotated with an annotation
180
     * from the {@see AnnotationDriver::entityAnnotationClasses}.
181
     *
182
     * @return bool
183
     */
184 2
    public function isTransient(string $className)
185
    {
186 2
        $classAnnotations = $this->reader->getClassAnnotations(new ReflectionClass($className));
187
188 2
        foreach ($classAnnotations as $annot) {
189 2
            if (isset($this->entityAnnotationClasses[get_class($annot)])) {
190 2
                return false;
191
            }
192
        }
193
194 2
        return true;
195
    }
196
197
    /**
198
     * {@inheritDoc}
199
     */
200 1
    public function getAllClassNames()
201
    {
202 1
        if ($this->classNames !== null) {
203
            return $this->classNames;
204
        }
205
206 1
        if ($this->paths === []) {
207
            throw MappingException::pathRequired();
208
        }
209
210 1
        $classes       = [];
211 1
        $includedFiles = [];
212
213 1
        foreach ($this->paths as $path) {
214 1
            if (! is_dir($path)) {
215
                throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
216
            }
217
218 1
            $iterator = new RegexIterator(
219 1
                new RecursiveIteratorIterator(
220 1
                    new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
221 1
                    RecursiveIteratorIterator::LEAVES_ONLY
222
                ),
223 1
                '/^.+' . preg_quote($this->fileExtension) . '$/i',
224 1
                RecursiveRegexIterator::GET_MATCH
225
            );
226
227 1
            foreach ($iterator as $file) {
228 1
                $sourceFile = $file[0];
229
230 1
                if (preg_match('(^phar:)i', $sourceFile) === false) {
231
                    $sourceFile = realpath($sourceFile);
232
                }
233
234 1
                foreach ($this->excludePaths as $excludePath) {
235
                    $realpath = realpath($excludePath);
236
                    assert(is_string($realpath));
237
238
                    $exclude = str_replace('\\', '/', $realpath);
239
                    $current = str_replace('\\', '/', $sourceFile);
240
241
                    if (strpos($current, $exclude) !== false) {
242
                        continue 2;
243
                    }
244
                }
245
246 1
                require_once $sourceFile;
247
248 1
                $includedFiles[] = $sourceFile;
249
            }
250
        }
251
252 1
        $declared = get_declared_classes();
253
254 1
        foreach ($declared as $className) {
255 1
            $rc = new ReflectionClass($className);
256
257 1
            $sourceFile = $rc->getFileName();
258
259 1
            if (! in_array($sourceFile, $includedFiles, true) || $this->isTransient($className)) {
260 1
                continue;
261
            }
262
263 1
            $classes[] = $className;
264
        }
265
266 1
        $this->classNames = $classes;
267
268 1
        return $classes;
269
    }
270
}
271