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

SymfonyFileLocator::realpath()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.032

Importance

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