Skip to content

Commit

Permalink
bug #35015 [Config] fix perf of glob discovery when GLOB_BRACE is not…
Browse files Browse the repository at this point in the history
… 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
-------

8af6d86 [Config] improve perf of glob discovery when GLOB_BRACE is not available
  • Loading branch information
nicolas-grekas committed Dec 18, 2019
2 parents b450a2b + 8af6d86 commit d7a0679
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 2 deletions.
47 changes: 45 additions & 2 deletions src/Symfony/Component/Config/Resource/GlobResource.php
Expand Up @@ -31,6 +31,7 @@ class GlobResource implements \IteratorAggregate, SelfCheckingResourceInterface
private $hash;
private $forExclusion;
private $excludedPrefixes;
private $globBrace;

/**
* @param string $prefix A directory prefix
Expand All @@ -47,6 +48,7 @@ public function __construct(string $prefix, string $pattern, bool $recursive, bo
$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));
Expand Down Expand Up @@ -101,9 +103,20 @@ public function getIterator()
return;
}
$prefix = str_replace('\\', '/', $this->prefix);
$paths = null;

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 (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 (null !== $paths) {
sort($paths);
foreach ($paths as $path) {
if ($this->excludedPrefixes) {
Expand Down Expand Up @@ -187,4 +200,34 @@ private function computeHash(): string

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;
}
}
29 changes: 29 additions & 0 deletions src/Symfony/Component/Config/Tests/Resource/GlobResourceTest.php
Expand Up @@ -165,4 +165,33 @@ public function testIsFreshRecursiveDetectsNewFile()
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)));
}
}

0 comments on commit d7a0679

Please sign in to comment.