This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | /* |
||
4 | * This file is part of the puli/repository package. |
||
5 | * |
||
6 | * (c) Bernhard Schussek <[email protected]> |
||
7 | * |
||
8 | * For the full copyright and license information, please view the LICENSE |
||
9 | * file that was distributed with this source code. |
||
10 | */ |
||
11 | |||
12 | namespace Puli\Repository; |
||
13 | |||
14 | use Iterator; |
||
15 | use Puli\Repository\Api\ChangeStream\ChangeStream; |
||
16 | use Puli\Repository\Api\Resource\BodyResource; |
||
17 | use Puli\Repository\Api\Resource\FilesystemResource; |
||
18 | use Puli\Repository\Api\Resource\PuliResource; |
||
19 | use Puli\Repository\Api\ResourceCollection; |
||
20 | use Puli\Repository\Api\ResourceNotFoundException; |
||
21 | use Puli\Repository\Api\UnsupportedOperationException; |
||
22 | use Puli\Repository\Api\UnsupportedResourceException; |
||
23 | use Puli\Repository\Resource\Collection\FilesystemResourceCollection; |
||
24 | use Puli\Repository\Resource\DirectoryResource; |
||
25 | use Puli\Repository\Resource\FileResource; |
||
26 | use Puli\Repository\Resource\LinkResource; |
||
27 | use Symfony\Component\Filesystem\Exception\IOException; |
||
28 | use Symfony\Component\Filesystem\Filesystem; |
||
29 | use Webmozart\Assert\Assert; |
||
30 | use Webmozart\Glob\Iterator\GlobIterator; |
||
31 | use Webmozart\Glob\Iterator\RecursiveDirectoryIterator; |
||
32 | use Webmozart\PathUtil\Path; |
||
33 | |||
34 | /** |
||
35 | * A repository reading from the file system. |
||
36 | * |
||
37 | * Resources can be read using their absolute file system paths: |
||
38 | * |
||
39 | * ```php |
||
40 | * use Puli\Repository\FilesystemRepository; |
||
41 | * |
||
42 | * $repo = new FilesystemRepository(); |
||
43 | * $resource = $repo->get('/home/puli/.gitconfig'); |
||
44 | * ``` |
||
45 | * |
||
46 | * The returned resources implement {@link FilesystemResource}. |
||
47 | * |
||
48 | * Optionally, a root directory can be passed to the constructor. Then all paths |
||
49 | * will be read relative to that directory: |
||
50 | * |
||
51 | * ```php |
||
52 | * $repo = new FilesystemRepository('/home/puli'); |
||
53 | * $resource = $repo->get('/.gitconfig'); |
||
54 | * ``` |
||
55 | * |
||
56 | * While "." and ".." segments are supported, files outside the root directory |
||
57 | * cannot be read. Any leading ".." segments will simply be stripped off. |
||
58 | * |
||
59 | * @since 1.0 |
||
60 | * |
||
61 | * @author Bernhard Schussek <[email protected]> |
||
62 | */ |
||
63 | class FilesystemRepository extends AbstractEditableRepository |
||
64 | { |
||
65 | /** |
||
66 | * @var bool|null |
||
67 | */ |
||
68 | private static $symlinkSupported; |
||
69 | |||
70 | /** |
||
71 | * @var string |
||
72 | */ |
||
73 | private $baseDir; |
||
74 | |||
75 | /** |
||
76 | * @var int |
||
77 | */ |
||
78 | private $baseDirLength; |
||
79 | |||
80 | /** |
||
81 | * @var bool |
||
82 | */ |
||
83 | private $symlink; |
||
84 | |||
85 | /** |
||
86 | * @var bool |
||
87 | */ |
||
88 | private $relative; |
||
89 | |||
90 | /** |
||
91 | * @var Filesystem |
||
92 | */ |
||
93 | private $filesystem; |
||
94 | |||
95 | /** |
||
96 | * Returns whether symlinks are supported in the local environment. |
||
97 | * |
||
98 | * @return bool Returns `true` if symlinks are supported. |
||
99 | */ |
||
100 | 305 | public static function isSymlinkSupported() |
|
101 | { |
||
102 | 305 | if (null === self::$symlinkSupported) { |
|
103 | // http://php.net/manual/en/function.symlink.php |
||
104 | // Symlinks are only supported on Windows Vista, Server 2008 or |
||
105 | // greater on PHP 5.3+ |
||
106 | 1 | if (defined('PHP_WINDOWS_VERSION_MAJOR')) { |
|
107 | self::$symlinkSupported = PHP_WINDOWS_VERSION_MAJOR >= 6; |
||
108 | } else { |
||
109 | 1 | self::$symlinkSupported = true; |
|
110 | } |
||
111 | } |
||
112 | |||
113 | 305 | return self::$symlinkSupported; |
|
114 | } |
||
115 | |||
116 | /** |
||
117 | * Creates a new repository. |
||
118 | * |
||
119 | * @param string $baseDir The base directory of the repository on the file |
||
120 | * system. |
||
121 | * @param bool $symlink Whether to use symbolic links for added files. If |
||
122 | * symbolic links are not supported on the current |
||
123 | * system, the repository will create hard copies |
||
124 | * instead. |
||
125 | * @param bool $relative Whether to create relative symbolic links. If |
||
126 | * relative links are not supported on the current |
||
127 | * system, the repository will create absolute links |
||
128 | * instead. |
||
129 | * @param ChangeStream|null $changeStream If provided, the repository will log |
||
130 | * resources changes in this change stream. |
||
131 | */ |
||
132 | 394 | public function __construct($baseDir = '/', $symlink = true, $relative = true, ChangeStream $changeStream = null) |
|
133 | { |
||
134 | 394 | parent::__construct($changeStream); |
|
135 | |||
136 | 394 | Assert::directory($baseDir); |
|
137 | 394 | Assert::boolean($symlink); |
|
138 | |||
139 | 394 | $this->baseDir = rtrim(Path::canonicalize($baseDir), '/'); |
|
140 | 394 | $this->baseDirLength = strlen($baseDir); |
|
141 | 394 | $this->symlink = $symlink && self::isSymlinkSupported(); |
|
142 | 394 | $this->relative = $this->symlink && $relative; |
|
143 | 394 | $this->filesystem = new Filesystem(); |
|
144 | 394 | } |
|
145 | |||
146 | /** |
||
147 | * {@inheritdoc} |
||
148 | */ |
||
149 | 169 | View Code Duplication | public function get($path) |
150 | { |
||
151 | 169 | $path = $this->sanitizePath($path); |
|
152 | 157 | $filesystemPath = $this->baseDir.$path; |
|
153 | |||
154 | 157 | if (!file_exists($filesystemPath)) { |
|
155 | 8 | throw ResourceNotFoundException::forPath($path); |
|
156 | } |
||
157 | |||
158 | 149 | return $this->createResource($filesystemPath, $path); |
|
159 | } |
||
160 | |||
161 | /** |
||
162 | * {@inheritdoc} |
||
163 | */ |
||
164 | 41 | public function find($query, $language = 'glob') |
|
165 | { |
||
166 | 41 | return $this->iteratorToCollection($this->getGlobIterator($query, $language)); |
|
167 | } |
||
168 | |||
169 | /** |
||
170 | * {@inheritdoc} |
||
171 | */ |
||
172 | 63 | public function contains($query, $language = 'glob') |
|
173 | { |
||
174 | 63 | $iterator = $this->getGlobIterator($query, $language); |
|
175 | 50 | $iterator->rewind(); |
|
176 | |||
177 | 50 | return $iterator->valid(); |
|
178 | } |
||
179 | |||
180 | /** |
||
181 | * {@inheritdoc} |
||
182 | */ |
||
183 | 28 | public function hasChildren($path) |
|
184 | { |
||
185 | 28 | $filesystemPath = $this->getFilesystemPath($path); |
|
186 | |||
187 | 12 | if (!is_dir($filesystemPath)) { |
|
188 | 4 | return false; |
|
189 | } |
||
190 | |||
191 | 12 | $iterator = $this->getDirectoryIterator($filesystemPath); |
|
192 | 12 | $iterator->rewind(); |
|
193 | |||
194 | 12 | return $iterator->valid(); |
|
195 | } |
||
196 | |||
197 | /** |
||
198 | * {@inheritdoc} |
||
199 | */ |
||
200 | 42 | public function listChildren($path) |
|
201 | { |
||
202 | 42 | $filesystemPath = $this->getFilesystemPath($path); |
|
203 | |||
204 | 26 | if (!is_dir($filesystemPath)) { |
|
205 | 4 | return new FilesystemResourceCollection(); |
|
206 | } |
||
207 | |||
208 | 22 | return $this->iteratorToCollection($this->getDirectoryIterator($filesystemPath)); |
|
209 | } |
||
210 | |||
211 | /** |
||
212 | * {@inheritdoc} |
||
213 | */ |
||
214 | 359 | public function add($path, $resource) |
|
215 | { |
||
216 | 359 | $path = $this->sanitizePath($path); |
|
217 | |||
218 | 347 | View Code Duplication | if ($resource instanceof ResourceCollection) { |
219 | 4 | $this->ensureDirectoryExists($path); |
|
220 | 4 | foreach ($resource as $child) { |
|
221 | 4 | $this->addResource($path.'/'.$child->getName(), $child); |
|
222 | } |
||
223 | |||
224 | 4 | return; |
|
225 | } |
||
226 | |||
227 | 343 | if ($resource instanceof PuliResource) { |
|
228 | 339 | $this->ensureDirectoryExists(Path::getDirectory($path)); |
|
229 | 339 | $this->addResource($path, $resource); |
|
230 | |||
231 | 334 | return; |
|
232 | } |
||
233 | |||
234 | 4 | throw new UnsupportedResourceException(sprintf( |
|
235 | 4 | 'The passed resource must be a PuliResource or ResourceCollection. Got: %s', |
|
236 | 4 | is_object($resource) ? get_class($resource) : gettype($resource) |
|
237 | )); |
||
238 | } |
||
239 | |||
240 | /** |
||
241 | * {@inheritdoc} |
||
242 | */ |
||
243 | 61 | public function remove($query, $language = 'glob') |
|
244 | { |
||
245 | 61 | $iterator = $this->getGlobIterator($query, $language); |
|
246 | 48 | $removed = 0; |
|
247 | |||
248 | 48 | Assert::notEmpty(trim($query, '/'), 'The root directory cannot be removed.'); |
|
249 | |||
250 | // There's some problem with concurrent deletions at the moment |
||
251 | 40 | foreach (iterator_to_array($iterator) as $filesystemPath) { |
|
252 | 40 | $this->removeResource($filesystemPath, $removed); |
|
253 | } |
||
254 | |||
255 | 40 | return $removed; |
|
256 | } |
||
257 | |||
258 | /** |
||
259 | * {@inheritdoc} |
||
260 | */ |
||
261 | 14 | public function clear() |
|
262 | { |
||
263 | 14 | $iterator = $this->getDirectoryIterator($this->baseDir); |
|
264 | 14 | $removed = 0; |
|
265 | |||
266 | // Batch-delete all versions |
||
267 | 14 | $this->clearVersions(); |
|
268 | |||
269 | 14 | foreach ($iterator as $filesystemPath) { |
|
270 | 10 | $this->removeResource($filesystemPath, $removed); |
|
271 | } |
||
272 | |||
273 | 14 | $this->storeVersion($this->get('/')); |
|
274 | |||
275 | 14 | return $removed; |
|
276 | } |
||
277 | |||
278 | 343 | private function ensureDirectoryExists($path) |
|
279 | { |
||
280 | 343 | $filesystemPath = $this->baseDir.$path; |
|
281 | |||
282 | 343 | if (is_file($filesystemPath)) { |
|
283 | 1 | throw new UnsupportedOperationException(sprintf( |
|
284 | 'Instances of BodyResource do not support child resources in '. |
||
285 | 1 | 'FilesystemRepository. Tried to add a child to %s.', |
|
286 | $filesystemPath |
||
287 | )); |
||
288 | } |
||
289 | |||
290 | 343 | if (!is_dir($filesystemPath)) { |
|
291 | 112 | mkdir($filesystemPath, 0777, true); |
|
292 | } |
||
293 | 343 | } |
|
294 | |||
295 | 343 | private function addResource($path, PuliResource $resource, $checkParentsForSymlinks = true) |
|
296 | { |
||
297 | 343 | $pathInBaseDir = $this->baseDir.$path; |
|
298 | 343 | $hasChildren = $resource->hasChildren(); |
|
299 | 343 | $hasBody = $resource instanceof BodyResource; |
|
300 | |||
301 | 343 | if ($hasChildren && $hasBody) { |
|
302 | 1 | throw new UnsupportedResourceException(sprintf( |
|
303 | 'Instances of BodyResource do not support child resources in '. |
||
304 | 'FilesystemRepository. Tried to add a BodyResource with '. |
||
305 | 1 | 'children at %s.', |
|
306 | $path |
||
307 | )); |
||
308 | } |
||
309 | |||
310 | 342 | $resource = clone $resource; |
|
311 | 342 | $resource->attachTo($this, $path); |
|
312 | |||
313 | 342 | if ($this->symlink && $checkParentsForSymlinks) { |
|
314 | 184 | $this->replaceParentSymlinksByCopies($path); |
|
315 | } |
||
316 | |||
317 | 342 | if ($resource instanceof FilesystemResource) { |
|
318 | 35 | if ($this->symlink) { |
|
319 | 32 | $this->symlinkMirror($resource->getFilesystemPath(), $pathInBaseDir); |
|
320 | 3 | } elseif ($hasBody) { |
|
321 | 2 | $this->filesystem->copy($resource->getFilesystemPath(), $pathInBaseDir); |
|
322 | } else { |
||
323 | 1 | $this->filesystem->mirror($resource->getFilesystemPath(), $pathInBaseDir); |
|
324 | } |
||
325 | |||
326 | 35 | $this->storeVersion($resource); |
|
327 | |||
328 | 35 | return; |
|
329 | } |
||
330 | |||
331 | 315 | if ($resource instanceof LinkResource) { |
|
332 | 8 | if (!$this->symlink) { |
|
333 | 4 | throw new UnsupportedResourceException(sprintf( |
|
334 | 'LinkResource requires support of symbolic links in FilesystemRepository. '. |
||
335 | 4 | 'Tried to add a LinkResource at %s.', |
|
336 | $path |
||
337 | )); |
||
338 | } |
||
339 | |||
340 | 4 | $this->filesystem->symlink($this->baseDir.$resource->getTargetPath(), $pathInBaseDir); |
|
341 | |||
342 | 4 | $this->storeVersion($resource); |
|
343 | |||
344 | 4 | return; |
|
345 | } |
||
346 | |||
347 | 307 | if ($hasBody) { |
|
348 | 175 | file_put_contents($pathInBaseDir, $resource->getBody()); |
|
0 ignored issues
–
show
|
|||
349 | |||
350 | 175 | $this->storeVersion($resource); |
|
351 | |||
352 | 175 | return; |
|
353 | } |
||
354 | |||
355 | 239 | if (is_file($pathInBaseDir)) { |
|
356 | $this->filesystem->remove($pathInBaseDir); |
||
357 | } |
||
358 | |||
359 | 239 | if (!file_exists($pathInBaseDir)) { |
|
360 | 147 | mkdir($pathInBaseDir, 0777, true); |
|
361 | } |
||
362 | |||
363 | 239 | foreach ($resource->listChildren() as $child) { |
|
364 | 148 | $this->addResource($path.'/'.$child->getName(), $child, false); |
|
365 | } |
||
366 | |||
367 | 239 | $this->storeVersion($resource); |
|
368 | 239 | } |
|
369 | |||
370 | 50 | private function removeResource($filesystemPath, &$removed) |
|
371 | { |
||
372 | // Skip paths that have already been removed |
||
373 | 50 | if (!file_exists($filesystemPath)) { |
|
374 | return; |
||
375 | } |
||
376 | |||
377 | 50 | $this->removeVersions($this->getPath($filesystemPath)); |
|
378 | |||
379 | 50 | ++$removed; |
|
380 | |||
381 | 50 | if (is_dir($filesystemPath) && !is_link($filesystemPath)) { |
|
382 | 32 | $iterator = $this->getDirectoryIterator($filesystemPath); |
|
383 | |||
384 | 32 | foreach ($iterator as $childFilesystemPath) { |
|
385 | // Remove children and child versions |
||
386 | 32 | $this->removeResource($childFilesystemPath, $removed); |
|
387 | } |
||
388 | } |
||
389 | |||
390 | 50 | $this->filesystem->remove($filesystemPath); |
|
391 | 50 | } |
|
392 | |||
393 | 149 | private function createResource($filesystemPath, $path) |
|
394 | { |
||
395 | 149 | $resource = null; |
|
396 | |||
397 | 149 | if (is_link($filesystemPath)) { |
|
398 | 6 | $baseDir = rtrim($this->baseDir, '/'); |
|
399 | 6 | $targetFilesystemPath = $this->readLink($filesystemPath); |
|
400 | |||
401 | 6 | if (Path::isBasePath($baseDir, $targetFilesystemPath)) { |
|
402 | 6 | $targetPath = '/'.Path::makeRelative($targetFilesystemPath, $baseDir); |
|
403 | 6 | $resource = new LinkResource($targetPath); |
|
404 | } |
||
405 | } |
||
406 | |||
407 | 149 | if (!$resource && is_dir($filesystemPath)) { |
|
408 | 100 | $resource = new DirectoryResource($filesystemPath); |
|
409 | } |
||
410 | |||
411 | 149 | if (!$resource) { |
|
412 | 79 | $resource = new FileResource($filesystemPath); |
|
413 | } |
||
414 | |||
415 | 149 | $resource->attachTo($this, $path); |
|
416 | |||
417 | 149 | return $resource; |
|
418 | } |
||
419 | |||
420 | 50 | private function iteratorToCollection(Iterator $iterator) |
|
421 | { |
||
422 | 50 | $filesystemPaths = iterator_to_array($iterator); |
|
423 | 50 | $resources = array(); |
|
424 | |||
425 | // RecursiveDirectoryIterator is not guaranteed to return sorted results |
||
426 | 50 | sort($filesystemPaths); |
|
427 | |||
428 | 50 | foreach ($filesystemPaths as $filesystemPath) { |
|
429 | 42 | $resource = is_dir($filesystemPath) |
|
430 | 24 | ? new DirectoryResource($filesystemPath, $this->getPath($filesystemPath)) |
|
431 | 42 | : new FileResource($filesystemPath, $this->getPath($filesystemPath)); |
|
432 | |||
433 | 42 | $resource->attachTo($this); |
|
434 | |||
435 | 42 | $resources[] = $resource; |
|
436 | } |
||
437 | |||
438 | 50 | return new FilesystemResourceCollection($resources); |
|
439 | } |
||
440 | |||
441 | 66 | View Code Duplication | private function getFilesystemPath($path) |
442 | { |
||
443 | 66 | $path = $this->sanitizePath($path); |
|
444 | 42 | $filesystemPath = $this->baseDir.$path; |
|
445 | |||
446 | 42 | if (!file_exists($filesystemPath)) { |
|
447 | 8 | throw ResourceNotFoundException::forPath($path); |
|
448 | } |
||
449 | |||
450 | 34 | return $filesystemPath; |
|
451 | } |
||
452 | |||
453 | 137 | private function getGlobIterator($query, $language) |
|
454 | { |
||
455 | 137 | $this->failUnlessGlob($language); |
|
456 | |||
457 | 134 | Assert::stringNotEmpty($query, 'The glob must be a non-empty string. Got: %s'); |
|
458 | 110 | Assert::startsWith($query, '/', 'The glob %s is not absolute.'); |
|
459 | |||
460 | 98 | $query = Path::canonicalize($query); |
|
461 | |||
462 | 98 | return new GlobIterator($this->baseDir.$query); |
|
463 | } |
||
464 | |||
465 | 84 | private function getDirectoryIterator($filesystemPath) |
|
466 | { |
||
467 | 84 | return new RecursiveDirectoryIterator( |
|
468 | $filesystemPath, |
||
469 | 84 | RecursiveDirectoryIterator::CURRENT_AS_PATHNAME | RecursiveDirectoryIterator::SKIP_DOTS |
|
470 | ); |
||
471 | } |
||
472 | |||
473 | 32 | private function symlinkMirror($origin, $target, array $dirsToKeep = array()) |
|
474 | { |
||
475 | 32 | $targetIsDir = is_dir($target); |
|
476 | 32 | $forceDir = in_array($target, $dirsToKeep, true); |
|
477 | |||
478 | // Merge directories |
||
479 | 32 | if (is_dir($origin) && ($targetIsDir || $forceDir)) { |
|
480 | 16 | if (is_link($target)) { |
|
481 | 4 | $this->replaceLinkByCopy($target, $dirsToKeep); |
|
482 | } |
||
483 | |||
484 | 16 | $iterator = $this->getDirectoryIterator($origin); |
|
485 | |||
486 | 16 | foreach ($iterator as $path) { |
|
487 | 16 | $this->symlinkMirror($path, $target.'/'.basename($path), $dirsToKeep); |
|
488 | } |
||
489 | |||
490 | 16 | return; |
|
491 | } |
||
492 | |||
493 | // Replace target |
||
494 | 32 | if (file_exists($target)) { |
|
495 | 10 | $this->filesystem->remove($target); |
|
496 | } |
||
497 | |||
498 | // Try creating a relative link |
||
499 | 32 | if ($this->relative && $this->trySymlink(Path::makeRelative($origin, Path::getDirectory($target)), $target)) { |
|
500 | 16 | return; |
|
501 | } |
||
502 | |||
503 | // Try creating a absolute link |
||
504 | 16 | if ($this->trySymlink($origin, $target)) { |
|
505 | 16 | return; |
|
506 | } |
||
507 | |||
508 | // Fall back to copy |
||
509 | if (is_dir($origin)) { |
||
510 | $this->filesystem->mirror($origin, $target); |
||
511 | |||
512 | return; |
||
513 | } |
||
514 | |||
515 | $this->filesystem->copy($origin, $target); |
||
516 | } |
||
517 | |||
518 | 184 | private function replaceParentSymlinksByCopies($path) |
|
519 | { |
||
520 | 184 | $previousPath = null; |
|
521 | |||
522 | // Collect all paths that MUST NOT be symlinks after doing the |
||
523 | // replace operation. |
||
524 | // |
||
525 | // Example: |
||
526 | // |
||
527 | // $dirsToKeep = ['/path/to/webmozart', '/path/to/webmozart/views'] |
||
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
55% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. ![]() |
|||
528 | // |
||
529 | // Before: |
||
530 | // /webmozart -> target |
||
531 | // |
||
532 | // After: |
||
533 | // /webmozart |
||
534 | // /config -> target/config |
||
535 | // /views |
||
536 | // /index.html.twig -> target/views/index.html.twig |
||
537 | |||
538 | 184 | $dirsToKeep = array(); |
|
539 | |||
540 | 184 | while ($previousPath !== ($path = Path::getDirectory($path))) { |
|
541 | 184 | $filesystemPath = $this->baseDir.$path; |
|
542 | 184 | $dirsToKeep[] = $filesystemPath; |
|
543 | |||
544 | 184 | if (is_link($filesystemPath)) { |
|
545 | 12 | $this->replaceLinkByCopy($filesystemPath, $dirsToKeep); |
|
546 | |||
547 | 12 | return; |
|
548 | } |
||
549 | |||
550 | 184 | $previousPath = $path; |
|
551 | } |
||
552 | 184 | } |
|
553 | |||
554 | 16 | private function replaceLinkByCopy($path, array $dirsToKeep = array()) |
|
555 | { |
||
556 | 16 | $target = Path::makeAbsolute($this->readLink($path), Path::getDirectory($path)); |
|
557 | 16 | $this->filesystem->remove($path); |
|
558 | 16 | $this->filesystem->mkdir($path); |
|
559 | 16 | $this->symlinkMirror($target, $path, $dirsToKeep); |
|
560 | 16 | } |
|
561 | |||
562 | 32 | private function trySymlink($origin, $target) |
|
563 | { |
||
564 | try { |
||
565 | 32 | $this->filesystem->symlink($origin, $target); |
|
566 | |||
567 | 32 | if (file_exists($target)) { |
|
568 | 32 | return true; |
|
569 | } |
||
570 | } catch (IOException $e) { |
||
571 | } |
||
572 | |||
573 | return false; |
||
574 | } |
||
575 | |||
576 | 22 | private function readLink($filesystemPath) |
|
577 | { |
||
578 | // On Windows, transitive links are resolved to the final target by |
||
579 | // readlink(). realpath(), however, returns the target link on Windows, |
||
580 | // but not on Unix. |
||
581 | |||
582 | // /link1 -> /link2 -> /file |
||
583 | |||
584 | // Windows: readlink(/link1) => /file |
||
585 | // realpath(/link1) => /link2 |
||
586 | |||
587 | // Unix: readlink(/link1) => /link2 |
||
588 | // realpath(/link1) => /file |
||
589 | |||
590 | // Consistency FTW! |
||
591 | |||
592 | 22 | return '\\' === DIRECTORY_SEPARATOR ? realpath($filesystemPath) : readlink($filesystemPath); |
|
593 | } |
||
594 | |||
595 | 92 | private function getPath($filesystemPath) |
|
596 | { |
||
597 | 92 | return substr($filesystemPath, $this->baseDirLength); |
|
598 | } |
||
599 | } |
||
600 |
Let’s take a look at an example:
In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.
Available Fixes
Change the type-hint for the parameter:
Add an additional type-check:
Add the method to the interface: