From 8af6d86371bd184ab6bcd6ad949ed002025cd687 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 17 Dec 2019 18:26:04 +0100 Subject: [PATCH] [Config] improve perf of glob discovery when GLOB_BRACE is not available --- .../Config/Resource/GlobResource.php | 47 ++++++++++++++++++- .../Tests/Resource/GlobResourceTest.php | 29 ++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Config/Resource/GlobResource.php b/src/Symfony/Component/Config/Resource/GlobResource.php index 52c1c93f8ee4..9f6e0d8dcf88 100644 --- a/src/Symfony/Component/Config/Resource/GlobResource.php +++ b/src/Symfony/Component/Config/Resource/GlobResource.php @@ -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 @@ 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)); @@ -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) { @@ -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; + } } diff --git a/src/Symfony/Component/Config/Tests/Resource/GlobResourceTest.php b/src/Symfony/Component/Config/Tests/Resource/GlobResourceTest.php index 629054461aca..2b6609d740c8 100644 --- a/src/Symfony/Component/Config/Tests/Resource/GlobResourceTest.php +++ b/src/Symfony/Component/Config/Tests/Resource/GlobResourceTest.php @@ -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))); + } }