bug #35015 [Config] fix perf of glob discovery when GLOB_BRACE is not available (nicolas-grekas)
This PR was merged into the 4.4 branch.
Discussion
----------
[Config] fix perf of glob discovery when GLOB_BRACE is not available
| Q | A
| ------------- | ---
| Branch? | 4.4
| Bug fix? | no
| New feature? | no
| Deprecations? | no
| Tickets | Fix #35009
| License | MIT
| Doc PR | -
This PR implements a fast fallback implementation of GLOB_BRACE for musl-based libc, as found on Alpine. It is *not* a feature-complete fallback implementation. Implementing one would be [much more involving](https://github.com/zendframework/zend-stdlib/blob/master/src/Glob.php). But the provided implementation is good enough in practice IMHO, and the slow path is still used when not-covered glob patterns are used.
Here is the comparison:
![image](https://user-images.githubusercontent.com/243674/71022909-eb9f7000-2101-11ea-99f5-eab0286c77a3.png)
![image](https://user-images.githubusercontent.com/243674/71022899-e4786200-2101-11ea-8663-80c1674602db.png)
Commits
-------
8af6d86371
[Config] improve perf of glob discovery when GLOB_BRACE is not available
This commit is contained in:
commit
d7a0679011
@ -31,6 +31,7 @@ class GlobResource implements \IteratorAggregate, SelfCheckingResourceInterface
|
||||
private $hash;
|
||||
private $forExclusion;
|
||||
private $excludedPrefixes;
|
||||
private $globBrace;
|
||||
|
||||
/**
|
||||
* @param string $prefix A directory prefix
|
||||
@ -47,6 +48,7 @@ class GlobResource implements \IteratorAggregate, SelfCheckingResourceInterface
|
||||
$this->recursive = $recursive;
|
||||
$this->forExclusion = $forExclusion;
|
||||
$this->excludedPrefixes = $excludedPrefixes;
|
||||
$this->globBrace = \defined('GLOB_BRACE') ? GLOB_BRACE : 0;
|
||||
|
||||
if (false === $this->prefix) {
|
||||
throw new \InvalidArgumentException(sprintf('The path "%s" does not exist.', $prefix));
|
||||
@ -101,9 +103,20 @@ class GlobResource implements \IteratorAggregate, SelfCheckingResourceInterface
|
||||
return;
|
||||
}
|
||||
$prefix = str_replace('\\', '/', $this->prefix);
|
||||
$paths = null;
|
||||
|
||||
if (0 !== strpos($this->prefix, 'phar://') && false === strpos($this->pattern, '/**/') && (\defined('GLOB_BRACE') || false === strpos($this->pattern, '{'))) {
|
||||
$paths = glob($this->prefix.$this->pattern, GLOB_NOSORT | (\defined('GLOB_BRACE') ? GLOB_BRACE : 0));
|
||||
if (0 !== strpos($this->prefix, 'phar://') && false === strpos($this->pattern, '/**/')) {
|
||||
if ($this->globBrace || false === strpos($this->pattern, '{')) {
|
||||
$paths = glob($this->prefix.$this->pattern, GLOB_NOSORT | $this->globBrace);
|
||||
} elseif (false === strpos($this->pattern, '\\') || !preg_match('/\\\\[,{}]/', $this->pattern)) {
|
||||
foreach ($this->expandGlob($this->pattern) as $p) {
|
||||
$paths[] = glob($this->prefix.$p, GLOB_NOSORT);
|
||||
}
|
||||
$paths = array_merge(...$paths);
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $paths) {
|
||||
sort($paths);
|
||||
foreach ($paths as $path) {
|
||||
if ($this->excludedPrefixes) {
|
||||
@ -187,4 +200,34 @@ class GlobResource implements \IteratorAggregate, SelfCheckingResourceInterface
|
||||
|
||||
return hash_final($hash);
|
||||
}
|
||||
|
||||
private function expandGlob(string $pattern): array
|
||||
{
|
||||
$segments = preg_split('/\{([^{}]*+)\}/', $pattern, -1, PREG_SPLIT_DELIM_CAPTURE);
|
||||
$paths = [$segments[0]];
|
||||
$patterns = [];
|
||||
|
||||
for ($i = 1; $i < \count($segments); $i += 2) {
|
||||
$patterns = [];
|
||||
|
||||
foreach (explode(',', $segments[$i]) as $s) {
|
||||
foreach ($paths as $p) {
|
||||
$patterns[] = $p.$s.$segments[1 + $i];
|
||||
}
|
||||
}
|
||||
|
||||
$paths = $patterns;
|
||||
}
|
||||
|
||||
$j = 0;
|
||||
foreach ($patterns as $i => $p) {
|
||||
if (false !== strpos($p, '{')) {
|
||||
$p = $this->expandGlob($p);
|
||||
array_splice($paths, $i + $j, 1, $p);
|
||||
$j += \count($p) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return $paths;
|
||||
}
|
||||
}
|
||||
|
@ -165,4 +165,33 @@ class GlobResourceTest extends TestCase
|
||||
touch($dir.'/Resource/TmpGlob');
|
||||
$this->assertFalse($resource->isFresh(0));
|
||||
}
|
||||
|
||||
public function testBraceFallback()
|
||||
{
|
||||
$dir = \dirname(__DIR__).\DIRECTORY_SEPARATOR.'Fixtures';
|
||||
$resource = new GlobResource($dir, '/*{/*/*.txt,.x{m,n}l}', true);
|
||||
|
||||
$p = new \ReflectionProperty($resource, 'globBrace');
|
||||
$p->setAccessible(true);
|
||||
$p->setValue($resource, 0);
|
||||
|
||||
$expected = [
|
||||
$dir.'/Exclude/ExcludeToo/AnotheExcludedFile.txt',
|
||||
$dir.'/foo.xml',
|
||||
];
|
||||
|
||||
$this->assertSame($expected, array_keys(iterator_to_array($resource)));
|
||||
}
|
||||
|
||||
public function testUnbalancedBraceFallback()
|
||||
{
|
||||
$dir = \dirname(__DIR__).\DIRECTORY_SEPARATOR.'Fixtures';
|
||||
$resource = new GlobResource($dir, '/*{/*/*.txt,.x{m,nl}', true);
|
||||
|
||||
$p = new \ReflectionProperty($resource, 'globBrace');
|
||||
$p->setAccessible(true);
|
||||
$p->setValue($resource, 0);
|
||||
|
||||
$this->assertSame([], array_keys(iterator_to_array($resource)));
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user