Completed
Pull Request — master (#50)
by Jonathan
04:19 queued 02:16
created

SymfonyFileLocator   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 225
Duplicated Lines 0 %

Test Coverage

Coverage 83.54%

Importance

Changes 0
Metric Value
wmc 29
eloc 71
dl 0
loc 225
ccs 66
cts 79
cp 0.8354
rs 10
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A setFileExtension() 0 3 1
B getAllClassNames() 0 45 8
A addNamespacePrefixes() 0 4 1
A fileExists() 0 28 6
A getNamespacePrefixes() 0 3 1
A getFileExtension() 0 3 1
A findMappingFile() 0 30 6
A realpath() 0 9 2
A __construct() 0 13 2
A getPaths() 0 3 1
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 14
    public function addNamespacePrefixes(array $prefixes) : void
87
    {
88 14
        $this->prefixes = array_merge($this->prefixes, $prefixes);
89 14
        $this->paths    = array_merge($this->paths, array_keys($prefixes));
90 14
    }
91
92
    /**
93
     * Gets Namespace Prefixes.
94
     *
95
     * @return string[]
96
     */
97 1
    public function getNamespacePrefixes() : array
98
    {
99 1
        return $this->prefixes;
100
    }
101
102
    /**
103
     * {@inheritDoc}
104
     */
105 1
    public function getPaths() : array
106
    {
107 1
        return $this->paths;
108
    }
109
110
    /**
111
     * {@inheritDoc}
112
     */
113 1
    public function getFileExtension() : ?string
114
    {
115 1
        return $this->fileExtension;
116
    }
117
118
    /**
119
     * Sets the file extension used to look for mapping files under.
120
     *
121
     * @param string $fileExtension The file extension to set.
122
     */
123 1
    public function setFileExtension(string $fileExtension) : void
124
    {
125 1
        $this->fileExtension = $fileExtension;
126 1
    }
127
128
    /**
129
     * {@inheritDoc}
130
     */
131 1
    public function fileExists(string $className) : bool
132
    {
133 1
        $defaultFileName = str_replace('\\', $this->nsSeparator, $className) . $this->fileExtension;
134
135 1
        foreach ($this->paths as $path) {
136 1
            if (! isset($this->prefixes[$path])) {
137
                // global namespace class
138
                if (is_file($path . DIRECTORY_SEPARATOR . $defaultFileName)) {
139
                    return true;
140
                }
141
142
                continue;
143
            }
144
145 1
            $prefix = $this->prefixes[$path];
146
147 1
            if (strpos($className, $prefix . '\\') !== 0) {
148
                continue;
149
            }
150
151 1
            $filename = $path . '/' . strtr(substr($className, strlen($prefix) + 1), '\\', $this->nsSeparator) . $this->fileExtension;
152
153 1
            if (is_file($filename)) {
154 1
                return true;
155
            }
156
        }
157
158 1
        return false;
159
    }
160
161
    /**
162
     * {@inheritDoc}
163
     */
164 3
    public function getAllClassNames(string $globalBasename) : array
165
    {
166 3
        if ($this->paths === []) {
167
            return [];
168
        }
169
170 3
        $classes = [];
171
172 3
        foreach ($this->paths as $path) {
173 3
            if (! is_dir($path)) {
174
                throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
175
            }
176
177 3
            $iterator = new RecursiveIteratorIterator(
178 3
                new RecursiveDirectoryIterator($path),
179 3
                RecursiveIteratorIterator::LEAVES_ONLY
180
            );
181
182 3
            foreach ($iterator as $file) {
183 3
                $fileName = $file->getBasename($this->fileExtension);
184
185 3
                if ($fileName === $file->getBasename() || $fileName === $globalBasename) {
186 3
                    continue;
187
                }
188
189
                // NOTE: All files found here means classes are not transient!
190 3
                if (isset($this->prefixes[$path])) {
191
                    // Calculate namespace suffix for given prefix as a relative path from basepath to file path
192 3
                    $nsSuffix = strtr(
193 3
                        substr($this->realpath($file->getPath()), strlen($this->realpath($path))),
194 3
                        $this->nsSeparator,
195 3
                        '\\'
196
                    );
197
198 3
                    $classes[] = $this->prefixes[$path] . str_replace(DIRECTORY_SEPARATOR, '\\', $nsSuffix) . '\\' . str_replace($this->nsSeparator, '\\', $fileName);
199
                } else {
200
                    /** @var string $className */
201
                    $className = str_replace($this->nsSeparator, '\\', $fileName);
202
203
                    $classes[] = $className;
204
                }
205
            }
206
        }
207
208 3
        return $classes;
209
    }
210
211
    /**
212
     * {@inheritDoc}
213
     */
214 6
    public function findMappingFile(string $className) : string
215
    {
216 6
        $defaultFileName = str_replace('\\', $this->nsSeparator, $className) . $this->fileExtension;
217 6
        foreach ($this->paths as $path) {
218 6
            if (! isset($this->prefixes[$path])) {
219
                if (is_file($path . DIRECTORY_SEPARATOR . $defaultFileName)) {
220
                    return $path . DIRECTORY_SEPARATOR . $defaultFileName;
221
                }
222
223
                continue;
224
            }
225
226 6
            $prefix = $this->prefixes[$path];
227
228 6
            if (strpos($className, $prefix . '\\') !== 0) {
229
                continue;
230
            }
231
232 6
            $filename = $path . '/' . strtr(substr($className, strlen($prefix) + 1), '\\', $this->nsSeparator) . $this->fileExtension;
233 6
            if (is_file($filename)) {
234 5
                return $filename;
235
            }
236
        }
237
238
        /** @var int $pos */
239 1
        $pos = strrpos($className, '\\');
240
241 1
        throw MappingException::mappingFileNotFound(
242 1
            $className,
243 1
            substr($className, $pos + 1) . $this->fileExtension
244
        );
245
    }
246
247 3
    private function realpath(string $path) : string
248
    {
249 3
        $realpath = realpath($path);
250
251 3
        if ($realpath === false) {
252
            throw new RuntimeException(sprintf('Could not get realpath for %s', $path));
253
        }
254
255 3
        return $realpath;
256
    }
257
}
258