-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Config] Fix resource tracking with new GlobResource #22621
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Config\Resource; | ||
|
||
use Symfony\Component\Finder\Finder; | ||
use Symfony\Component\Finder\Glob; | ||
|
||
/** | ||
* GlobResource represents a set of resources stored on the filesystem. | ||
* | ||
* Only existence/removal is tracked (not mtimes.) | ||
* | ||
* @author Nicolas Grekas <p@tchwork.com> | ||
*/ | ||
class GlobResource implements \IteratorAggregate, SelfCheckingResourceInterface, \Serializable | ||
{ | ||
private $prefix; | ||
private $pattern; | ||
private $recursive; | ||
private $hash; | ||
|
||
/** | ||
* Constructor. | ||
* | ||
* @param string $prefix A directory prefix | ||
* @param string $pattern A glob pattern | ||
* @param bool $recursive Whether directories should be scanned recursively or not | ||
* | ||
* @throws \InvalidArgumentException | ||
*/ | ||
public function __construct($prefix, $pattern, $recursive) | ||
{ | ||
$this->prefix = realpath($prefix) ?: (file_exists($prefix) ? $prefix : false); | ||
$this->pattern = $pattern; | ||
$this->recursive = $recursive; | ||
|
||
if (false === $this->prefix) { | ||
throw new \InvalidArgumentException(sprintf('The path "%s" does not exist.', $prefix)); | ||
} | ||
} | ||
|
||
public function getPrefix() | ||
{ | ||
return $this->prefix; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function __toString() | ||
{ | ||
return 'glob.'.$this->prefix.$this->pattern.(int) $this->recursive; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you should add a delimiter between these, to avoid having similar identifiers for different prefixes and patterns (using a delimiter unlikely to appear in a glob) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In fact, that's on purpose, two resources with the same concat for (prefix, pattern) should be considered the same, because they'll end up listing the same files. |
||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function isFresh($timestamp) | ||
{ | ||
$hash = $this->computeHash(); | ||
|
||
if (null === $this->hash) { | ||
$this->hash = $hash; | ||
} | ||
|
||
return $this->hash === $hash; | ||
} | ||
|
||
public function serialize() | ||
{ | ||
if (null === $this->hash) { | ||
$this->hash = $this->computeHash(); | ||
} | ||
|
||
return serialize(array($this->prefix, $this->pattern, $this->recursive, $this->hash)); | ||
} | ||
|
||
public function unserialize($serialized) | ||
{ | ||
list($this->prefix, $this->pattern, $this->recursive, $this->hash) = unserialize($serialized); | ||
} | ||
|
||
public function getIterator() | ||
{ | ||
if (!file_exists($this->prefix) || (!$this->recursive && '' === $this->pattern)) { | ||
return; | ||
} | ||
|
||
if (false === strpos($this->pattern, '/**/') && (defined('GLOB_BRACE') || false === strpos($this->pattern, '{'))) { | ||
foreach (glob($this->prefix.$this->pattern, defined('GLOB_BRACE') ? GLOB_BRACE : 0) as $path) { | ||
if ($this->recursive && is_dir($path)) { | ||
$files = iterator_to_array(new \RecursiveIteratorIterator( | ||
new \RecursiveCallbackFilterIterator( | ||
new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), | ||
function (\SplFileInfo $file) { return '.' !== $file->getBasename()[0]; } | ||
), | ||
\RecursiveIteratorIterator::LEAVES_ONLY | ||
)); | ||
uasort($files, function (\SplFileInfo $a, \SplFileInfo $b) { | ||
return (string) $a > (string) $b ? 1 : -1; | ||
}); | ||
|
||
foreach ($files as $path => $info) { | ||
if ($info->isFile()) { | ||
yield $path => $info; | ||
} | ||
} | ||
} elseif (is_file($path)) { | ||
yield $path => new \SplFileInfo($path); | ||
} | ||
} | ||
|
||
return; | ||
} | ||
|
||
if (!class_exists(Finder::class)) { | ||
throw new \LogicException(sprintf('Extended glob pattern "%s" cannot be used as the Finder component is not installed.', $this->pattern)); | ||
} | ||
|
||
$finder = new Finder(); | ||
$regex = Glob::toRegex($this->pattern); | ||
if ($this->recursive) { | ||
$regex = substr_replace($regex, '(/|$)', -2, 1); | ||
} | ||
|
||
$prefixLen = strlen($this->prefix); | ||
foreach ($finder->followLinks()->sortByName()->in($this->prefix) as $path => $info) { | ||
if (preg_match($regex, substr($path, $prefixLen)) && $info->isFile()) { | ||
yield $path => $info; | ||
} | ||
} | ||
} | ||
|
||
private function computeHash() | ||
{ | ||
$hash = hash_init('md5'); | ||
|
||
foreach ($this->getIterator() as $path => $info) { | ||
hash_update($hash, $path."\n"); | ||
} | ||
|
||
return hash_final($hash); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the fact that we ignore the resource here means that we may miss some cases for reloading the container when using a glob import.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's how things are now: the Config component doesn't know anything about the container so this cannot trivially be otherwise. I guess $sourceResource is here for that purpose. Not something this PR tries to improve.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But if the import is done by a DI loader extending this abstract class, it should be given a chance to register the resource somewhere (the child class in the component knows what it wants to do with the resource)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and I know this is not what this PR tries to improve. But I detected it while reviewing it. And this looks like one of the cases that we still need to polish before the stable release including glob support.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you be able to give it a try after this PR is merged?