SymfonyFileLocator::getAllClassNames()   B
last analyzed

Complexity

Conditions 8
Paths 7

Size

Total Lines 45
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 8.3364

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 23
c 1
b 0
f 0
dl 0
loc 45
ccs 19
cts 23
cp 0.8261
rs 8.4444
cc 8
nc 7
nop 1
crap 8.3364
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\Persistence\Mapping\Driver;
6
7
use Doctrine\Persistence\Mapping\MappingException;
8
use InvalidArgumentException;
9
use RecursiveDirectoryIterator;
10
use RecursiveIteratorIterator;
11
use RuntimeException;
12
use const DIRECTORY_SEPARATOR;
13
use function array_keys;
14
use function array_merge;
15
use function is_dir;
16
use function is_file;
17
use function realpath;
18
use function sprintf;
19
use function str_replace;
20
use function strlen;
21
use function strpos;
22
use function strrpos;
23
use function strtr;
24
use function substr;
25
26
/**
27
 * The Symfony File Locator makes a simplifying assumptions compared
28
 * to the DefaultFileLocator. By assuming paths only contain entities of a certain
29
 * namespace the mapping files consists of the short classname only.
30
 */
31
class SymfonyFileLocator implements FileLocator
32
{
33
    /**
34
     * The paths where to look for mapping files.
35
     *
36
     * @var array<int, string>
37
     */
38
    protected $paths = [];
39
40
    /**
41
     * A map of mapping directory path to namespace prefix used to expand class shortnames.
42
     *
43
     * @var array<string, string>
44
     */
45
    protected $prefixes = [];
46
47
    /**
48
     * File extension that is searched for.
49
     *
50
     * @var string|null
51
     */
52
    protected $fileExtension;
53
54
    /**
55
     * Represents PHP namespace delimiters when looking for files
56
     *
57
     * @var string
58
     */
59
    private $nsSeparator;
60
61
    /**
62
     * @param array<string, string> $prefixes
63
     * @param string                $nsSeparator String which would be used when converting FQCN
64
     *                                           to filename and vice versa. Should not be empty
65
     */
66 14
    public function __construct(
67
        array $prefixes,
68
        string $fileExtension = '',
69
        string $nsSeparator = '.'
70
    ) {
71 14
        $this->addNamespacePrefixes($prefixes);
72 14
        $this->fileExtension = $fileExtension;
73
74 14
        if ($nsSeparator === '') {
75 1
            throw new InvalidArgumentException('Namespace separator should not be empty');
76
        }
77
78 13
        $this->nsSeparator = $nsSeparator;
79 13
    }
80
81
    /**
82
     * Adds Namespace Prefixes.
83
     *
84
     * @param array<string, string> $prefixes
85
     *
86
     * @return void
87
     */
88 14
    public function addNamespacePrefixes(array $prefixes)
89
    {
90 14
        $this->prefixes = array_merge($this->prefixes, $prefixes);
91 14
        $this->paths    = array_merge($this->paths, array_keys($prefixes));
92 14
    }
93
94
    /**
95
     * Gets Namespace Prefixes.
96
     *
97
     * @return string[]
98
     */
99 1
    public function getNamespacePrefixes()
100
    {
101 1
        return $this->prefixes;
102
    }
103
104
    /**
105
     * {@inheritDoc}
106
     */
107 1
    public function getPaths()
108
    {
109 1
        return $this->paths;
110
    }
111
112
    /**
113
     * {@inheritDoc}
114
     */
115 1
    public function getFileExtension()
116
    {
117 1
        return $this->fileExtension;
118
    }
119
120
    /**
121
     * Sets the file extension used to look for mapping files under.
122
     *
123
     * @param string $fileExtension The file extension to set.
124
     *
125
     * @return void
126
     */
127 1
    public function setFileExtension(string $fileExtension)
128
    {
129 1
        $this->fileExtension = $fileExtension;
130 1
    }
131
132
    /**
133
     * {@inheritDoc}
134
     */
135 1
    public function fileExists(string $className)
136
    {
137 1
        $defaultFileName = str_replace('\\', $this->nsSeparator, $className) . $this->fileExtension;
138 1
        foreach ($this->paths as $path) {
139 1
            if (! isset($this->prefixes[$path])) {
140
                // global namespace class
141
                if (is_file($path . DIRECTORY_SEPARATOR . $defaultFileName)) {
142
                    return true;
143
                }
144
145
                continue;
146
            }
147
148 1
            $prefix = $this->prefixes[$path];
149
150 1
            if (strpos($className, $prefix . '\\') !== 0) {
151
                continue;
152
            }
153
154 1
            $filename = $path . '/' . strtr(substr($className, strlen($prefix) + 1), '\\', $this->nsSeparator) . $this->fileExtension;
155
156 1
            if (is_file($filename)) {
157 1
                return true;
158
            }
159
        }
160
161 1
        return false;
162
    }
163
164
    /**
165
     * {@inheritDoc}
166
     */
167 3
    public function getAllClassNames(?string $globalBasename = null)
168
    {
169 3
        if ($this->paths === []) {
170
            return [];
171
        }
172
173 3
        $classes = [];
174
175 3
        foreach ($this->paths as $path) {
176 3
            if (! is_dir($path)) {
177
                throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
178
            }
179
180 3
            $iterator = new RecursiveIteratorIterator(
181 3
                new RecursiveDirectoryIterator($path),
182 3
                RecursiveIteratorIterator::LEAVES_ONLY
183
            );
184
185 3
            foreach ($iterator as $file) {
186 3
                $fileName = $file->getBasename($this->fileExtension);
187
188 3
                if ($fileName === $file->getBasename() || $fileName === $globalBasename) {
189 3
                    continue;
190
                }
191
192
                // NOTE: All files found here means classes are not transient!
193 3
                if (isset($this->prefixes[$path])) {
194
                    // Calculate namespace suffix for given prefix as a relative path from basepath to file path
195 3
                    $nsSuffix = strtr(
196 3
                        substr($this->realpath($file->getPath()), strlen($this->realpath($path))),
197 3
                        $this->nsSeparator,
198 3
                        '\\'
199
                    );
200
201 3
                    $classes[] = $this->prefixes[$path] . str_replace(DIRECTORY_SEPARATOR, '\\', $nsSuffix) . '\\' . str_replace($this->nsSeparator, '\\', $fileName);
202
                } else {
203
                    /** @var string $className */
204
                    $className = str_replace($this->nsSeparator, '\\', $fileName);
205
206
                    $classes[] = $className;
207
                }
208
            }
209
        }
210
211 3
        return $classes;
212
    }
213
214
    /**
215
     * {@inheritDoc}
216
     */
217 6
    public function findMappingFile(string $className)
218
    {
219 6
        $defaultFileName = str_replace('\\', $this->nsSeparator, $className) . $this->fileExtension;
220 6
        foreach ($this->paths as $path) {
221 6
            if (! isset($this->prefixes[$path])) {
222
                if (is_file($path . DIRECTORY_SEPARATOR . $defaultFileName)) {
223
                    return $path . DIRECTORY_SEPARATOR . $defaultFileName;
224
                }
225
226
                continue;
227
            }
228
229 6
            $prefix = $this->prefixes[$path];
230
231 6
            if (strpos($className, $prefix . '\\') !== 0) {
232
                continue;
233
            }
234
235 6
            $filename = $path . '/' . strtr(substr($className, strlen($prefix) + 1), '\\', $this->nsSeparator) . $this->fileExtension;
236 6
            if (is_file($filename)) {
237 5
                return $filename;
238
            }
239
        }
240
241
        /** @var int $pos */
242 1
        $pos = strrpos($className, '\\');
243
244 1
        throw MappingException::mappingFileNotFound(
245 1
            $className,
246 1
            substr($className, $pos + 1) . $this->fileExtension
247
        );
248
    }
249
250 3
    private function realpath(string $path) : string
251
    {
252 3
        $realpath = realpath($path);
253
254 3
        if ($realpath === false) {
255
            throw new RuntimeException(sprintf('Could not get realpath for %s', $path));
256
        }
257
258 3
        return $realpath;
259
    }
260
}
261