From 244d76240086666ffdcc8ec0145f1e02605a3d85 Mon Sep 17 00:00:00 2001 From: Maksym Slesarenko Date: Mon, 23 Apr 2018 14:10:14 +0300 Subject: [PATCH 001/938] added ability to specify folder for flock --- .../DependencyInjection/FrameworkExtension.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 51aee716f43f5..9aca337c0292c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -58,6 +58,7 @@ use Symfony\Component\Lock\Factory; use Symfony\Component\Lock\Lock; use Symfony\Component\Lock\LockInterface; +use Symfony\Component\Lock\Store\FlockStore; use Symfony\Component\Lock\Store\StoreFactory; use Symfony\Component\Lock\StoreInterface; use Symfony\Component\Messenger\Handler\MessageHandlerInterface; @@ -1377,6 +1378,14 @@ private function registerLockConfiguration(array $config, ContainerBuilder $cont case 'flock' === $storeDsn: $storeDefinition = new Reference('lock.store.flock'); break; + case 0 === strpos($storeDsn, 'flock://'): + $flockPath = substr($storeDsn, 8); + + $storeDefinitionId = '.lock.flock.store.'.$container->hash($storeDsn); + $container->register($storeDefinitionId, FlockStore::class)->addArgument($flockPath); + + $storeDefinition = new Reference($storeDefinitionId); + break; case 'semaphore' === $storeDsn: $storeDefinition = new Reference('lock.store.semaphore'); break; From e697c7d2721d30884193ed86c5f6fd6f044323ab Mon Sep 17 00:00:00 2001 From: Anton Dyshkant Date: Tue, 24 Apr 2018 14:54:22 +0300 Subject: [PATCH 002/938] [Finder] added "use natural sort" option --- src/Symfony/Component/Finder/CHANGELOG.md | 5 + src/Symfony/Component/Finder/Finder.php | 8 +- .../Finder/Iterator/SortableIterator.php | 5 + .../Component/Finder/Tests/FinderTest.php | 495 ++++++++++++++++-- .../Iterator/DateRangeFilterIteratorTest.php | 18 + .../Iterator/DepthRangeFilterIteratorTest.php | 20 + .../ExcludeDirectoryFilterIteratorTest.php | 27 + .../Iterator/FileTypeFilterIteratorTest.php | 9 + .../Tests/Iterator/RealIteratorTestCase.php | 9 + .../Iterator/SizeRangeFilterIteratorTest.php | 1 + .../Tests/Iterator/SortableIteratorTest.php | 109 +++- 11 files changed, 655 insertions(+), 51 deletions(-) diff --git a/src/Symfony/Component/Finder/CHANGELOG.md b/src/Symfony/Component/Finder/CHANGELOG.md index c795e54cf5a28..13bc98c65dd92 100644 --- a/src/Symfony/Component/Finder/CHANGELOG.md +++ b/src/Symfony/Component/Finder/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +4.2.0 +----- + + * added $useNaturalSort option to Finder::sortByName() method + 4.0.0 ----- diff --git a/src/Symfony/Component/Finder/Finder.php b/src/Symfony/Component/Finder/Finder.php index 105acc70ce41a..b7e388d1874ec 100644 --- a/src/Symfony/Component/Finder/Finder.php +++ b/src/Symfony/Component/Finder/Finder.php @@ -397,13 +397,17 @@ public function sort(\Closure $closure) * * This can be slow as all the matching files and directories must be retrieved for comparison. * + * @param bool $useNaturalSort Whether to use natural sort or not, disabled by default + * * @return $this * * @see SortableIterator */ - public function sortByName() + public function sortByName(/* bool $useNaturalSort = false */) { - $this->sort = Iterator\SortableIterator::SORT_BY_NAME; + $useNaturalSort = 0 < func_num_args() && func_get_arg(0); + + $this->sort = $useNaturalSort ? Iterator\SortableIterator::SORT_BY_NAME_NATURAL : Iterator\SortableIterator::SORT_BY_NAME; return $this; } diff --git a/src/Symfony/Component/Finder/Iterator/SortableIterator.php b/src/Symfony/Component/Finder/Iterator/SortableIterator.php index c2f54b937652f..4734a6ebdc74a 100644 --- a/src/Symfony/Component/Finder/Iterator/SortableIterator.php +++ b/src/Symfony/Component/Finder/Iterator/SortableIterator.php @@ -23,6 +23,7 @@ class SortableIterator implements \IteratorAggregate const SORT_BY_ACCESSED_TIME = 3; const SORT_BY_CHANGED_TIME = 4; const SORT_BY_MODIFIED_TIME = 5; + const SORT_BY_NAME_NATURAL = 6; private $iterator; private $sort; @@ -41,6 +42,10 @@ public function __construct(\Traversable $iterator, $sort) $this->sort = function ($a, $b) { return strcmp($a->getRealpath() ?: $a->getPathname(), $b->getRealpath() ?: $b->getPathname()); }; + } elseif (self::SORT_BY_NAME_NATURAL === $sort) { + $this->sort = function ($a, $b) { + return strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + }; } elseif (self::SORT_BY_TYPE === $sort) { $this->sort = function ($a, $b) { if ($a->isDir() && $b->isFile()) { diff --git a/src/Symfony/Component/Finder/Tests/FinderTest.php b/src/Symfony/Component/Finder/Tests/FinderTest.php index c7908fa86743d..134c93641b67f 100644 --- a/src/Symfony/Component/Finder/Tests/FinderTest.php +++ b/src/Symfony/Component/Finder/Tests/FinderTest.php @@ -24,33 +24,70 @@ public function testDirectories() { $finder = $this->buildFinder(); $this->assertSame($finder, $finder->directories()); - $this->assertIterator($this->toAbsolute(array('foo', 'toto')), $finder->in(self::$tmpDir)->getIterator()); + $this->assertIterator($this->toAbsolute(array('foo', 'qux', 'toto')), $finder->in(self::$tmpDir)->getIterator()); $finder = $this->buildFinder(); $finder->directories(); $finder->files(); $finder->directories(); - $this->assertIterator($this->toAbsolute(array('foo', 'toto')), $finder->in(self::$tmpDir)->getIterator()); + $this->assertIterator($this->toAbsolute(array('foo', 'qux', 'toto')), $finder->in(self::$tmpDir)->getIterator()); } public function testFiles() { $finder = $this->buildFinder(); $this->assertSame($finder, $finder->files()); - $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', 'test.php', 'test.py', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', + 'test.php', + 'test.py', + 'foo bar', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', + )), $finder->in(self::$tmpDir)->getIterator()); $finder = $this->buildFinder(); $finder->files(); $finder->directories(); $finder->files(); - $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', 'test.php', 'test.py', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', + 'test.php', + 'test.py', + 'foo bar', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', + )), $finder->in(self::$tmpDir)->getIterator()); } public function testRemoveTrailingSlash() { $finder = $this->buildFinder(); - $expected = $this->toAbsolute(array('foo/bar.tmp', 'test.php', 'test.py', 'foo bar')); + $expected = $this->toAbsolute(array( + 'foo/bar.tmp', + 'test.php', + 'test.py', + 'foo bar', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', + )); $in = self::$tmpDir.'//'; $this->assertIterator($expected, $finder->in($in)->files()->getIterator()); @@ -89,15 +126,43 @@ public function testDepth() { $finder = $this->buildFinder(); $this->assertSame($finder, $finder->depth('< 1')); - $this->assertIterator($this->toAbsolute(array('foo', 'test.php', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + $this->assertIterator($this->toAbsolute(array('foo', + 'test.php', + 'test.py', + 'toto', + 'foo bar', + 'qux', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', + )), $finder->in(self::$tmpDir)->getIterator()); $finder = $this->buildFinder(); $this->assertSame($finder, $finder->depth('<= 0')); - $this->assertIterator($this->toAbsolute(array('foo', 'test.php', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + $this->assertIterator($this->toAbsolute(array('foo', + 'test.php', + 'test.py', + 'toto', + 'foo bar', + 'qux', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', + )), $finder->in(self::$tmpDir)->getIterator()); $finder = $this->buildFinder(); $this->assertSame($finder, $finder->depth('>= 1')); - $this->assertIterator($this->toAbsolute(array('foo/bar.tmp')), $finder->in(self::$tmpDir)->getIterator()); + $this->assertIterator($this->toAbsolute(array( + 'foo/bar.tmp', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + )), $finder->in(self::$tmpDir)->getIterator()); $finder = $this->buildFinder(); $finder->depth('< 1')->depth('>= 1'); @@ -108,7 +173,15 @@ public function testName() { $finder = $this->buildFinder(); $this->assertSame($finder, $finder->name('*.php')); - $this->assertIterator($this->toAbsolute(array('test.php')), $finder->in(self::$tmpDir)->getIterator()); + $this->assertIterator($this->toAbsolute(array( + 'test.php', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', + )), $finder->in(self::$tmpDir)->getIterator()); $finder = $this->buildFinder(); $finder->name('test.ph*'); @@ -121,7 +194,15 @@ public function testName() $finder = $this->buildFinder(); $finder->name('~\\.php$~i'); - $this->assertIterator($this->toAbsolute(array('test.php')), $finder->in(self::$tmpDir)->getIterator()); + $this->assertIterator($this->toAbsolute(array( + 'test.php', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', + )), $finder->in(self::$tmpDir)->getIterator()); $finder = $this->buildFinder(); $finder->name('test.p{hp,y}'); @@ -132,12 +213,27 @@ public function testNotName() { $finder = $this->buildFinder(); $this->assertSame($finder, $finder->notName('*.php')); - $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + $this->assertIterator($this->toAbsolute(array( + 'foo', + 'foo/bar.tmp', + 'test.py', + 'toto', + 'foo bar', + 'qux', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + )), $finder->in(self::$tmpDir)->getIterator()); $finder = $this->buildFinder(); $finder->notName('*.php'); $finder->notName('*.py'); - $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + $this->assertIterator($this->toAbsolute(array( + 'foo', + 'foo/bar.tmp', + 'toto', + 'foo bar', + 'qux', + )), $finder->in(self::$tmpDir)->getIterator()); $finder = $this->buildFinder(); $finder->name('test.ph*'); @@ -160,7 +256,10 @@ public function testRegexName($regex) { $finder = $this->buildFinder(); $finder->name($regex); - $this->assertIterator($this->toAbsolute(array('test.py', 'test.php')), $finder->in(self::$tmpDir)->getIterator()); + $this->assertIterator($this->toAbsolute(array( + 'test.py', + 'test.php', + )), $finder->in(self::$tmpDir)->getIterator()); } public function testSize() @@ -181,79 +280,356 @@ public function testExclude() { $finder = $this->buildFinder(); $this->assertSame($finder, $finder->exclude('foo')); - $this->assertIterator($this->toAbsolute(array('test.php', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + $this->assertIterator($this->toAbsolute(array( + 'test.php', + 'test.py', + 'toto', + 'foo bar', + 'qux', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', + )), $finder->in(self::$tmpDir)->getIterator()); } public function testIgnoreVCS() { $finder = $this->buildFinder(); $this->assertSame($finder, $finder->ignoreVCS(false)->ignoreDotFiles(false)); - $this->assertIterator($this->toAbsolute(array('.git', 'foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', 'toto/.git', '.bar', '.foo', '.foo/.bar', '.foo/bar', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + $this->assertIterator($this->toAbsolute(array( + '.git', + 'foo', + 'foo/bar.tmp', + 'test.php', + 'test.py', + 'toto', + 'toto/.git', + '.bar', + '.foo', + '.foo/.bar', + '.foo/bar', + 'foo bar', + 'qux', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', + )), $finder->in(self::$tmpDir)->getIterator()); $finder = $this->buildFinder(); $finder->ignoreVCS(false)->ignoreVCS(false)->ignoreDotFiles(false); - $this->assertIterator($this->toAbsolute(array('.git', 'foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', 'toto/.git', '.bar', '.foo', '.foo/.bar', '.foo/bar', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + $this->assertIterator($this->toAbsolute(array( + '.git', + 'foo', + 'foo/bar.tmp', + 'test.php', + 'test.py', + 'toto', + 'toto/.git', + '.bar', + '.foo', + '.foo/.bar', + '.foo/bar', + 'foo bar', + 'qux', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', + )), $finder->in(self::$tmpDir)->getIterator()); $finder = $this->buildFinder(); $this->assertSame($finder, $finder->ignoreVCS(true)->ignoreDotFiles(false)); - $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', '.bar', '.foo', '.foo/.bar', '.foo/bar', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + $this->assertIterator($this->toAbsolute(array( + 'foo', + 'foo/bar.tmp', + 'test.php', + 'test.py', + 'toto', + '.bar', + '.foo', + '.foo/.bar', + '.foo/bar', + 'foo bar', + 'qux', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', + )), $finder->in(self::$tmpDir)->getIterator()); } public function testIgnoreDotFiles() { $finder = $this->buildFinder(); $this->assertSame($finder, $finder->ignoreDotFiles(false)->ignoreVCS(false)); - $this->assertIterator($this->toAbsolute(array('.git', '.bar', '.foo', '.foo/.bar', '.foo/bar', 'foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', 'toto/.git', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + $this->assertIterator($this->toAbsolute(array( + '.git', + '.bar', + '.foo', + '.foo/.bar', + '.foo/bar', + 'foo', + 'foo/bar.tmp', + 'test.php', + 'test.py', + 'toto', + 'toto/.git', + 'foo bar', + 'qux', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', + )), $finder->in(self::$tmpDir)->getIterator()); $finder = $this->buildFinder(); $finder->ignoreDotFiles(false)->ignoreDotFiles(false)->ignoreVCS(false); - $this->assertIterator($this->toAbsolute(array('.git', '.bar', '.foo', '.foo/.bar', '.foo/bar', 'foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', 'toto/.git', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + $this->assertIterator($this->toAbsolute(array( + '.git', + '.bar', + '.foo', + '.foo/.bar', + '.foo/bar', + 'foo', + 'foo/bar.tmp', + 'test.php', + 'test.py', + 'toto', + 'toto/.git', + 'foo bar', + 'qux', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', + )), $finder->in(self::$tmpDir)->getIterator()); $finder = $this->buildFinder(); $this->assertSame($finder, $finder->ignoreDotFiles(true)->ignoreVCS(false)); - $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + $this->assertIterator($this->toAbsolute(array( + 'foo', + 'foo/bar.tmp', + 'test.php', + 'test.py', + 'toto', + 'foo bar', + 'qux', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', + )), $finder->in(self::$tmpDir)->getIterator()); } public function testSortByName() { $finder = $this->buildFinder(); $this->assertSame($finder, $finder->sortByName()); - $this->assertIterator($this->toAbsolute(array('foo', 'foo bar', 'foo/bar.tmp', 'test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)->getIterator()); + $this->assertIterator($this->toAbsolute(array( + 'foo', + 'foo bar', + 'foo/bar.tmp', + 'qux', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', + 'test.php', + 'test.py', + 'toto', + )), $finder->in(self::$tmpDir)->getIterator()); } public function testSortByType() { $finder = $this->buildFinder(); $this->assertSame($finder, $finder->sortByType()); - $this->assertIterator($this->toAbsolute(array('foo', 'foo bar', 'toto', 'foo/bar.tmp', 'test.php', 'test.py')), $finder->in(self::$tmpDir)->getIterator()); + $this->assertIterator($this->toAbsolute(array( + 'foo', + 'foo bar', + 'toto', + 'foo/bar.tmp', + 'test.php', + 'test.py', + 'qux', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', + )), $finder->in(self::$tmpDir)->getIterator()); } public function testSortByAccessedTime() { $finder = $this->buildFinder(); $this->assertSame($finder, $finder->sortByAccessedTime()); - $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', 'test.php', 'toto', 'test.py', 'foo', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + $this->assertIterator($this->toAbsolute(array( + 'foo/bar.tmp', + 'test.php', + 'toto', + 'test.py', + 'foo', + 'foo bar', + 'qux', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', + )), $finder->in(self::$tmpDir)->getIterator()); } public function testSortByChangedTime() { $finder = $this->buildFinder(); $this->assertSame($finder, $finder->sortByChangedTime()); - $this->assertIterator($this->toAbsolute(array('toto', 'test.py', 'test.php', 'foo/bar.tmp', 'foo', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + $this->assertIterator($this->toAbsolute(array( + 'toto', + 'test.py', + 'test.php', + 'foo/bar.tmp', + 'foo', + 'foo bar', + 'qux', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', + )), $finder->in(self::$tmpDir)->getIterator()); } public function testSortByModifiedTime() { $finder = $this->buildFinder(); $this->assertSame($finder, $finder->sortByModifiedTime()); - $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', 'test.php', 'toto', 'test.py', 'foo', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + $this->assertIterator($this->toAbsolute(array( + 'foo/bar.tmp', + 'test.php', + 'toto', + 'test.py', + 'foo', + 'foo bar', + 'qux', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', + )), $finder->in(self::$tmpDir)->getIterator()); + } + + public function testSortByNameNatural() + { + $finder = $this->buildFinder(); + $this->assertSame($finder, $finder->sortByName(true)); + $this->assertIterator($this->toAbsolute(array( + 'foo', + 'foo bar', + 'foo/bar.tmp', + 'qux', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', + 'test.php', + 'test.py', + 'toto', + )), $finder->in(self::$tmpDir)->getIterator()); + + $finder = $this->buildFinder(); + $this->assertSame($finder, $finder->sortByName(false)); + $this->assertIterator($this->toAbsolute(array( + 'foo', + 'foo bar', + 'foo/bar.tmp', + 'qux', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', + 'test.php', + 'test.py', + 'toto', + )), $finder->in(self::$tmpDir)->getIterator()); } public function testSort() { $finder = $this->buildFinder(); $this->assertSame($finder, $finder->sort(function (\SplFileInfo $a, \SplFileInfo $b) { return strcmp($a->getRealPath(), $b->getRealPath()); })); - $this->assertIterator($this->toAbsolute(array('foo', 'foo bar', 'foo/bar.tmp', 'test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)->getIterator()); + $this->assertIterator($this->toAbsolute(array( + 'foo', + 'foo bar', + 'foo/bar.tmp', + 'test.php', + 'test.py', + 'toto', + 'qux', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', + )), $finder->in(self::$tmpDir)->getIterator()); } public function testFilter() @@ -271,7 +647,23 @@ public function testFollowLinks() $finder = $this->buildFinder(); $this->assertSame($finder, $finder->followLinks()); - $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + $this->assertIterator($this->toAbsolute(array( + 'foo', + 'foo/bar.tmp', + 'test.php', + 'test.py', + 'toto', + 'foo bar', + 'qux', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', + )), $finder->in(self::$tmpDir)->getIterator()); } public function testIn() @@ -283,6 +675,12 @@ public function testIn() self::$tmpDir.DIRECTORY_SEPARATOR.'test.php', __DIR__.DIRECTORY_SEPARATOR.'FinderTest.php', __DIR__.DIRECTORY_SEPARATOR.'GlobTest.php', + self::$tmpDir.DIRECTORY_SEPARATOR.'qux_0_1.php', + self::$tmpDir.DIRECTORY_SEPARATOR.'qux_1000_1.php', + self::$tmpDir.DIRECTORY_SEPARATOR.'qux_1002_0.php', + self::$tmpDir.DIRECTORY_SEPARATOR.'qux_10_2.php', + self::$tmpDir.DIRECTORY_SEPARATOR.'qux_12_0.php', + self::$tmpDir.DIRECTORY_SEPARATOR.'qux_2_0.php', ); $this->assertIterator($expected, $iterator); @@ -339,7 +737,7 @@ public function testGetIterator() $dirs[] = (string) $dir; } - $expected = $this->toAbsolute(array('foo', 'toto')); + $expected = $this->toAbsolute(array('foo', 'qux', 'toto')); sort($dirs); sort($expected); @@ -347,7 +745,7 @@ public function testGetIterator() $this->assertEquals($expected, $dirs, 'implements the \IteratorAggregate interface'); $finder = $this->buildFinder(); - $this->assertEquals(2, iterator_count($finder->directories()->in(self::$tmpDir)), 'implements the \IteratorAggregate interface'); + $this->assertEquals(3, iterator_count($finder->directories()->in(self::$tmpDir)), 'implements the \IteratorAggregate interface'); $finder = $this->buildFinder(); $a = iterator_to_array($finder->directories()->in(self::$tmpDir)); @@ -366,7 +764,7 @@ public function testRelativePath() $paths[] = $file->getRelativePath(); } - $ref = array('', '', '', '', 'foo', ''); + $ref = array('', '', '', '', '', '', '', '', '', '', '', 'foo', 'qux', 'qux', ''); sort($ref); sort($paths); @@ -384,7 +782,23 @@ public function testRelativePathname() $paths[] = $file->getRelativePathname(); } - $ref = array('test.php', 'toto', 'test.py', 'foo', 'foo'.DIRECTORY_SEPARATOR.'bar.tmp', 'foo bar'); + $ref = array( + 'test.php', + 'toto', + 'test.py', + 'foo', + 'foo'.DIRECTORY_SEPARATOR.'bar.tmp', + 'foo bar', + 'qux', + 'qux'.DIRECTORY_SEPARATOR.'baz_100_1.py', + 'qux'.DIRECTORY_SEPARATOR.'baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', + ); sort($paths); sort($ref); @@ -402,7 +816,7 @@ public function testAppendWithAFinder() $finder = $finder->append($finder1); - $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'toto')), $finder->getIterator()); + $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'qux', 'toto')), $finder->getIterator()); } public function testAppendWithAnArray() @@ -601,7 +1015,7 @@ public function getContainsTestData() public function getRegexNameTestData() { return array( - array('~.+\\.p.+~i'), + array('~.*t\\.p.+~i'), array('~t.*s~i'), ); } @@ -718,7 +1132,20 @@ public function testIgnoredAccessDeniedException() chmod($testDir, 0333); if (false === ($couldRead = is_readable($testDir))) { - $this->assertIterator($this->toAbsolute(array('foo bar', 'test.php', 'test.py')), $finder->getIterator()); + $this->assertIterator($this->toAbsolute(array( + 'foo bar', + 'test.php', + 'test.py', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', + ) + ), $finder->getIterator()); } // restore original permissions diff --git a/src/Symfony/Component/Finder/Tests/Iterator/DateRangeFilterIteratorTest.php b/src/Symfony/Component/Finder/Tests/Iterator/DateRangeFilterIteratorTest.php index 1ec70518278aa..6da91da95c50d 100644 --- a/src/Symfony/Component/Finder/Tests/Iterator/DateRangeFilterIteratorTest.php +++ b/src/Symfony/Component/Finder/Tests/Iterator/DateRangeFilterIteratorTest.php @@ -45,6 +45,15 @@ public function getAcceptData() '.foo/.bar', 'foo bar', '.foo/bar', + 'qux', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', ); $since2MonthsAgo = array( @@ -58,6 +67,15 @@ public function getAcceptData() '.foo/.bar', 'foo bar', '.foo/bar', + 'qux', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', ); $untilLastMonth = array( diff --git a/src/Symfony/Component/Finder/Tests/Iterator/DepthRangeFilterIteratorTest.php b/src/Symfony/Component/Finder/Tests/Iterator/DepthRangeFilterIteratorTest.php index 2e90140530cd3..3a403cb9559e8 100644 --- a/src/Symfony/Component/Finder/Tests/Iterator/DepthRangeFilterIteratorTest.php +++ b/src/Symfony/Component/Finder/Tests/Iterator/DepthRangeFilterIteratorTest.php @@ -41,6 +41,13 @@ public function getAcceptData() '.foo', '.bar', 'foo bar', + 'qux', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', ); $lessThanOrEqualTo1 = array( @@ -56,6 +63,15 @@ public function getAcceptData() '.bar', 'foo bar', '.foo/bar', + 'qux', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', ); $graterThanOrEqualTo1 = array( @@ -63,6 +79,8 @@ public function getAcceptData() 'foo/bar.tmp', '.foo/.bar', '.foo/bar', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', ); $equalTo1 = array( @@ -70,6 +88,8 @@ public function getAcceptData() 'foo/bar.tmp', '.foo/.bar', '.foo/bar', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', ); return array( diff --git a/src/Symfony/Component/Finder/Tests/Iterator/ExcludeDirectoryFilterIteratorTest.php b/src/Symfony/Component/Finder/Tests/Iterator/ExcludeDirectoryFilterIteratorTest.php index fa192c31f4dec..c977b0cfdb383 100644 --- a/src/Symfony/Component/Finder/Tests/Iterator/ExcludeDirectoryFilterIteratorTest.php +++ b/src/Symfony/Component/Finder/Tests/Iterator/ExcludeDirectoryFilterIteratorTest.php @@ -41,6 +41,15 @@ public function getAcceptData() 'toto', 'toto/.git', 'foo bar', + 'qux', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', ); $fo = array( @@ -56,6 +65,15 @@ public function getAcceptData() 'toto', 'toto/.git', 'foo bar', + 'qux', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', ); $toto = array( @@ -69,6 +87,15 @@ public function getAcceptData() 'foo/bar.tmp', 'test.php', 'foo bar', + 'qux', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', ); return array( diff --git a/src/Symfony/Component/Finder/Tests/Iterator/FileTypeFilterIteratorTest.php b/src/Symfony/Component/Finder/Tests/Iterator/FileTypeFilterIteratorTest.php index 4350b00ca940a..0ecd8dfe7346e 100644 --- a/src/Symfony/Component/Finder/Tests/Iterator/FileTypeFilterIteratorTest.php +++ b/src/Symfony/Component/Finder/Tests/Iterator/FileTypeFilterIteratorTest.php @@ -37,11 +37,20 @@ public function getAcceptData() '.foo/.bar', '.foo/bar', 'foo bar', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', ); $onlyDirectories = array( '.git', 'foo', + 'qux', 'toto', 'toto/.git', '.foo', diff --git a/src/Symfony/Component/Finder/Tests/Iterator/RealIteratorTestCase.php b/src/Symfony/Component/Finder/Tests/Iterator/RealIteratorTestCase.php index 94253c7ee7ca2..ea00122f76265 100644 --- a/src/Symfony/Component/Finder/Tests/Iterator/RealIteratorTestCase.php +++ b/src/Symfony/Component/Finder/Tests/Iterator/RealIteratorTestCase.php @@ -33,6 +33,15 @@ public static function setUpBeforeClass() 'toto/', 'toto/.git/', 'foo bar', + 'qux_0_1.php', + 'qux_2_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux/', + 'qux/baz_1_2.py', + 'qux/baz_100_1.py', ); self::$files = self::toAbsolute(self::$files); diff --git a/src/Symfony/Component/Finder/Tests/Iterator/SizeRangeFilterIteratorTest.php b/src/Symfony/Component/Finder/Tests/Iterator/SizeRangeFilterIteratorTest.php index 6d75b0f2f0b18..9bd34855b17c9 100644 --- a/src/Symfony/Component/Finder/Tests/Iterator/SizeRangeFilterIteratorTest.php +++ b/src/Symfony/Component/Finder/Tests/Iterator/SizeRangeFilterIteratorTest.php @@ -34,6 +34,7 @@ public function getAcceptData() '.foo', '.git', 'foo', + 'qux', 'test.php', 'toto', 'toto/.git', diff --git a/src/Symfony/Component/Finder/Tests/Iterator/SortableIteratorTest.php b/src/Symfony/Component/Finder/Tests/Iterator/SortableIteratorTest.php index 444654a28fb61..9428013a4f49c 100644 --- a/src/Symfony/Component/Finder/Tests/Iterator/SortableIteratorTest.php +++ b/src/Symfony/Component/Finder/Tests/Iterator/SortableIteratorTest.php @@ -82,6 +82,15 @@ public function getAcceptData() 'foo', 'foo bar', 'foo/bar.tmp', + 'qux', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', 'test.php', 'test.py', 'toto', @@ -92,6 +101,7 @@ public function getAcceptData() '.foo', '.git', 'foo', + 'qux', 'toto', 'toto/.git', '.bar', @@ -99,25 +109,18 @@ public function getAcceptData() '.foo/bar', 'foo bar', 'foo/bar.tmp', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', 'test.php', 'test.py', ); - $customComparison = array( - '.bar', - '.foo', - '.foo/.bar', - '.foo/bar', - '.git', - 'foo', - 'foo bar', - 'foo/bar.tmp', - 'test.php', - 'test.py', - 'toto', - 'toto/.git', - ); - $sortByAccessedTime = array( // For these two files the access time was set to 2005-10-15 array('foo/bar.tmp', 'test.php'), @@ -132,6 +135,15 @@ public function getAcceptData() 'toto', 'toto/.git', 'foo bar', + 'qux', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', ), // This file was accessed after sleeping for 1 sec array('.bar'), @@ -149,6 +161,15 @@ public function getAcceptData() 'toto', 'toto/.git', 'foo bar', + 'qux', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', ), array('test.php'), array('test.py'), @@ -166,17 +187,75 @@ public function getAcceptData() 'toto', 'toto/.git', 'foo bar', + 'qux', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', ), array('test.php'), array('test.py'), ); + $sortByNameNatural = array( + '.bar', + '.foo', + '.foo/.bar', + '.foo/bar', + '.git', + 'foo', + 'foo/bar.tmp', + 'foo bar', + 'qux', + 'qux/baz_1_2.py', + 'qux/baz_100_1.py', + 'qux_0_1.php', + 'qux_2_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'test.php', + 'test.py', + 'toto', + 'toto/.git', + ); + + $customComparison = array( + '.bar', + '.foo', + '.foo/.bar', + '.foo/bar', + '.git', + 'foo', + 'foo bar', + 'foo/bar.tmp', + 'qux', + 'qux/baz_100_1.py', + 'qux/baz_1_2.py', + 'qux_0_1.php', + 'qux_1000_1.php', + 'qux_1002_0.php', + 'qux_10_2.php', + 'qux_12_0.php', + 'qux_2_0.php', + 'test.php', + 'test.py', + 'toto', + 'toto/.git', + ); + return array( array(SortableIterator::SORT_BY_NAME, $this->toAbsolute($sortByName)), array(SortableIterator::SORT_BY_TYPE, $this->toAbsolute($sortByType)), array(SortableIterator::SORT_BY_ACCESSED_TIME, $this->toAbsolute($sortByAccessedTime)), array(SortableIterator::SORT_BY_CHANGED_TIME, $this->toAbsolute($sortByChangedTime)), array(SortableIterator::SORT_BY_MODIFIED_TIME, $this->toAbsolute($sortByModifiedTime)), + array(SortableIterator::SORT_BY_NAME_NATURAL, $this->toAbsolute($sortByNameNatural)), array(function (\SplFileInfo $a, \SplFileInfo $b) { return strcmp($a->getRealPath(), $b->getRealPath()); }, $this->toAbsolute($customComparison)), ); } From 32fc58df8b7cc4079ccb6d1571d0b3ca091b4cf4 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 4 May 2018 20:14:52 -0700 Subject: [PATCH 003/938] [DI] Allow binding by type+name --- .../Compiler/AutowirePass.php | 2 +- .../Compiler/ResolveBindingsPass.php | 14 +++++++---- .../DependencyInjection/Definition.php | 4 ++++ .../Loader/Configurator/Traits/BindTrait.php | 2 +- .../Compiler/ResolveBindingsPassTest.php | 24 +++++++++++++++++++ .../Tests/Fixtures/NamedArgumentsDummy.php | 4 ++++ ...RegisterControllerArgumentLocatorsPass.php | 7 +++--- ...sterControllerArgumentLocatorsPassTest.php | 21 ++++++++++++---- 8 files changed, 65 insertions(+), 13 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index f94284d2e0722..419d955d3c333 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -208,7 +208,7 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a continue; } $type = ProxyHelper::getTypeHint($reflectionMethod, $parameter, false); - $type = $type ? sprintf('is type-hinted "%s"', $type) : 'has no type-hint'; + $type = $type ? sprintf('is type-hinted "%s"', ltrim($type, '\\')) : 'has no type-hint'; throw new AutowiringFailedException($this->currentId, sprintf('Cannot autowire service "%s": argument "$%s" of method "%s()" %s, you should configure its value explicitly.', $this->currentId, $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method, $type)); } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php index c82a974360674..5ab1a0138dc10 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php @@ -74,7 +74,7 @@ protected function processValue($value, $isRoot = false) $this->unusedBindings[$bindingId] = array($key, $this->currentId); } - if (isset($key[0]) && '$' === $key[0]) { + if (preg_match('/^(?:(?:array|bool|float|int|string) )?\$/', $key)) { continue; } @@ -113,15 +113,21 @@ protected function processValue($value, $isRoot = false) continue; } + $typeHint = ProxyHelper::getTypeHint($reflectionMethod, $parameter); + + if (array_key_exists($k = ltrim($typeHint, '\\').' $'.$parameter->name, $bindings)) { + $arguments[$key] = $this->getBindingValue($bindings[$k]); + + continue; + } + if (array_key_exists('$'.$parameter->name, $bindings)) { $arguments[$key] = $this->getBindingValue($bindings['$'.$parameter->name]); continue; } - $typeHint = ProxyHelper::getTypeHint($reflectionMethod, $parameter, true); - - if (!isset($bindings[$typeHint])) { + if (!$typeHint || '\\' !== $typeHint[0] || !isset($bindings[$typeHint = substr($typeHint, 1)])) { continue; } diff --git a/src/Symfony/Component/DependencyInjection/Definition.php b/src/Symfony/Component/DependencyInjection/Definition.php index b6ddff186aedf..807ce13a07edd 100644 --- a/src/Symfony/Component/DependencyInjection/Definition.php +++ b/src/Symfony/Component/DependencyInjection/Definition.php @@ -859,6 +859,10 @@ public function getBindings() public function setBindings(array $bindings) { foreach ($bindings as $key => $binding) { + if (0 < strpos($key, '$') && $key !== $k = preg_replace('/[ \t]*\$/', ' $', $key)) { + unset($bindings[$key]); + $bindings[$key = $k] = $binding; + } if (!$binding instanceof BoundArgument) { $bindings[$key] = new BoundArgument($binding); } diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/BindTrait.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/BindTrait.php index 4511ed659d4e5..5d6c57cb3b9b2 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/BindTrait.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/BindTrait.php @@ -31,7 +31,7 @@ trait BindTrait final public function bind($nameOrFqcn, $valueOrRef) { $valueOrRef = static::processValue($valueOrRef, true); - if (isset($nameOrFqcn[0]) && '$' !== $nameOrFqcn[0] && !$valueOrRef instanceof Reference) { + if (!preg_match('/^(?:(?:array|bool|float|int|string)[ \t]*+)?\$/', $nameOrFqcn) && !$valueOrRef instanceof Reference) { throw new InvalidArgumentException(sprintf('Invalid binding for service "%s": named arguments must start with a "$", and FQCN must map to references. Neither applies to binding "%s".', $this->id, $nameOrFqcn)); } $bindings = $this->definition->getBindings(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php index 16e486afafe2e..cf68841ca8974 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php @@ -95,4 +95,28 @@ public function testScalarSetter() $this->assertEquals(array(array('setDefaultLocale', array('fr'))), $definition->getMethodCalls()); } + + public function testTupleBinding() + { + $container = new ContainerBuilder(); + + $bindings = array( + '$c' => new BoundArgument(new Reference('bar')), + CaseSensitiveClass::class.'$c' => new BoundArgument(new Reference('foo')), + ); + + $definition = $container->register(NamedArgumentsDummy::class, NamedArgumentsDummy::class); + $definition->addMethodCall('setSensitiveClass'); + $definition->addMethodCall('setAnotherC'); + $definition->setBindings($bindings); + + $pass = new ResolveBindingsPass(); + $pass->process($container); + + $expected = array( + array('setSensitiveClass', array(new Reference('foo'))), + array('setAnotherC', array(new Reference('bar'))), + ); + $this->assertEquals($expected, $definition->getMethodCalls()); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/NamedArgumentsDummy.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/NamedArgumentsDummy.php index 09d907dfae769..802ba842715bf 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/NamedArgumentsDummy.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/NamedArgumentsDummy.php @@ -18,4 +18,8 @@ public function setApiKey($apiKey) public function setSensitiveClass(CaseSensitiveClass $c) { } + + public function setAnotherC($c) + { + } } diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php index cd76e56caab83..f8f924ab01698 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php @@ -120,7 +120,7 @@ public function process(ContainerBuilder $container) $args = array(); foreach ($parameters as $p) { /** @var \ReflectionParameter $p */ - $type = $target = ProxyHelper::getTypeHint($r, $p, true); + $type = ltrim($target = ProxyHelper::getTypeHint($r, $p), '\\'); $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; if (isset($arguments[$r->name][$p->name])) { @@ -132,7 +132,7 @@ public function process(ContainerBuilder $container) } elseif ($p->allowsNull() && !$p->isOptional()) { $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE; } - } elseif (isset($bindings[$bindingName = '$'.$p->name]) || isset($bindings[$bindingName = $type])) { + } elseif (isset($bindings[$bindingName = $type.' $'.$p->name]) || isset($bindings[$bindingName = '$'.$p->name]) || isset($bindings[$bindingName = $type])) { $binding = $bindings[$bindingName]; list($bindingValue, $bindingId) = $binding->getValues(); @@ -148,7 +148,7 @@ public function process(ContainerBuilder $container) } continue; - } elseif (!$type || !$autowire) { + } elseif (!$type || !$autowire || '\\' !== $target[0]) { continue; } elseif (!$p->allowsNull()) { $invalidBehavior = ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE; @@ -169,6 +169,7 @@ public function process(ContainerBuilder $container) throw new InvalidArgumentException($message); } + $target = ltrim($target, '\\'); $args[$p->name] = $type ? new TypedReference($target, $type, $invalidBehavior) : new Reference($target, $invalidBehavior); } // register the maps as a per-method service-locators diff --git a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php index 98c77f4aa32d1..189515496ca81 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php @@ -308,16 +308,23 @@ public function testBindings($bindingName) public function provideBindings() { - return array(array(ControllerDummy::class), array('$bar')); + return array( + array(ControllerDummy::class.'$bar'), + array(ControllerDummy::class), + array('$bar'), + ); } - public function testBindScalarValueToControllerArgument() + /** + * @dataProvider provideBindScalarValueToControllerArgument + */ + public function testBindScalarValueToControllerArgument($bindingKey) { $container = new ContainerBuilder(); $resolver = $container->register('argument_resolver.service')->addArgument(array()); $container->register('foo', ArgumentWithoutTypeController::class) - ->setBindings(array('$someArg' => '%foo%')) + ->setBindings(array($bindingKey => '%foo%')) ->addTag('controller.service_arguments'); $container->setParameter('foo', 'foo_val'); @@ -339,6 +346,12 @@ public function testBindScalarValueToControllerArgument() $this->assertTrue($container->has((string) $reference)); $this->assertSame('foo_val', $container->get((string) $reference)); } + + public function provideBindScalarValueToControllerArgument() + { + yield array('$someArg'); + yield array('string $someArg'); + } } class RegisterTestController @@ -396,7 +409,7 @@ public function barAction(NonExistentClass $nonExistent = null, $bar) class ArgumentWithoutTypeController { - public function fooAction($someArg) + public function fooAction(string $someArg) { } } From f1e9aa4ed84e424248b1dca52572e1b90b187dc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Thu, 3 May 2018 15:47:55 +0200 Subject: [PATCH 004/938] [HttpKernel] Better exception page when the controller returns nothing --- ...ntrollerDoesNotReturnResponseException.php | 78 +++++++++++++++++++ .../Component/HttpKernel/HttpKernel.php | 4 +- .../HttpKernel/Tests/HttpKernelTest.php | 17 +++- 3 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 src/Symfony/Component/HttpKernel/Exception/ControllerDoesNotReturnResponseException.php diff --git a/src/Symfony/Component/HttpKernel/Exception/ControllerDoesNotReturnResponseException.php b/src/Symfony/Component/HttpKernel/Exception/ControllerDoesNotReturnResponseException.php new file mode 100644 index 0000000000000..2abc4069cf1be --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Exception/ControllerDoesNotReturnResponseException.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Grégoire Pineau + */ +class ControllerDoesNotReturnResponseException extends \LogicException +{ + public function __construct(string $message, callable $controller, string $file, int $line) + { + parent::__construct($message); + + if (!$controllerDefinition = $this->parseControllerDefinition($controller)) { + return; + } + + $this->file = $controllerDefinition['file']; + $this->line = $controllerDefinition['line']; + $r = new \ReflectionProperty(\Exception::class, 'trace'); + $r->setAccessible(true); + $r->setValue($this, array_merge(array( + array( + 'line' => $line, + 'file' => $file, + ), + ), $this->getTrace())); + } + + private function parseControllerDefinition(callable $controller): ?array + { + if (is_string($controller) && false !== strpos($controller, '::')) { + $controller = explode('::', $controller); + } + + if (is_array($controller)) { + try { + $r = new \ReflectionMethod($controller[0], $controller[1]); + + return array( + 'file' => $r->getFileName(), + 'line' => $r->getEndLine(), + ); + } catch (\ReflectionException $e) { + return null; + } + } + + if ($controller instanceof \Closure) { + $r = new \ReflectionFunction($controller); + + return array( + 'file' => $r->getFileName(), + 'line' => $r->getEndLine(), + ); + } + + if (is_object($controller)) { + $r = new \ReflectionClass($controller); + + return array( + 'file' => $r->getFileName(), + 'line' => $r->getEndLine(), + ); + } + + return null; + } +} diff --git a/src/Symfony/Component/HttpKernel/HttpKernel.php b/src/Symfony/Component/HttpKernel/HttpKernel.php index d4ef09e8478ff..9ebf5847b437e 100644 --- a/src/Symfony/Component/HttpKernel/HttpKernel.php +++ b/src/Symfony/Component/HttpKernel/HttpKernel.php @@ -16,6 +16,7 @@ use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; use Symfony\Component\HttpKernel\Event\FilterControllerArgumentsEvent; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\Exception\ControllerDoesNotReturnResponseException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; use Symfony\Component\HttpKernel\Event\FilterControllerEvent; @@ -162,7 +163,8 @@ private function handleRaw(Request $request, int $type = self::MASTER_REQUEST) if (null === $response) { $msg .= ' Did you forget to add a return statement somewhere in your controller?'; } - throw new \LogicException($msg); + + throw new ControllerDoesNotReturnResponseException($msg, $controller, __FILE__, __LINE__ - 17); } } diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php index 0adef984c6f5c..bb19c496cce89 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php @@ -23,6 +23,7 @@ use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpKernel\Exception\ControllerDoesNotReturnResponseException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -211,15 +212,23 @@ public function testHandleWhenTheControllerIsAStaticArray() $this->assertResponseEquals(new Response('foo'), $kernel->handle(new Request())); } - /** - * @expectedException \LogicException - */ public function testHandleWhenTheControllerDoesNotReturnAResponse() { $dispatcher = new EventDispatcher(); $kernel = $this->getHttpKernel($dispatcher, function () { return 'foo'; }); - $kernel->handle(new Request()); + try { + $kernel->handle(new Request()); + + $this->fail('The kernel should throw an exception.'); + } catch (ControllerDoesNotReturnResponseException $e) { + } + + $first = $e->getTrace()[0]; + + // `file` index the array starting at 0, and __FILE__ starts at 1 + $line = file($first['file'])[$first['line'] - 1]; + $this->assertContains('call_user_func_array', $line); } public function testHandleWhenTheControllerDoesNotReturnAResponseButAViewIsRegistered() From 016d5562628af6935bbf76e2eb3fb99683f7cf7b Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 7 May 2018 16:51:25 +0200 Subject: [PATCH 005/938] updated version to 4.2 --- composer.json | 2 +- src/Symfony/Bridge/Doctrine/composer.json | 2 +- src/Symfony/Bridge/Monolog/composer.json | 2 +- src/Symfony/Bridge/PhpUnit/composer.json | 2 +- src/Symfony/Bridge/ProxyManager/composer.json | 2 +- src/Symfony/Bridge/Twig/composer.json | 2 +- src/Symfony/Bundle/DebugBundle/composer.json | 2 +- src/Symfony/Bundle/FrameworkBundle/composer.json | 2 +- src/Symfony/Bundle/SecurityBundle/composer.json | 2 +- src/Symfony/Bundle/TwigBundle/composer.json | 2 +- src/Symfony/Bundle/WebProfilerBundle/composer.json | 2 +- src/Symfony/Bundle/WebServerBundle/composer.json | 2 +- src/Symfony/Component/Asset/composer.json | 2 +- src/Symfony/Component/BrowserKit/composer.json | 2 +- src/Symfony/Component/Cache/composer.json | 2 +- src/Symfony/Component/Config/composer.json | 2 +- src/Symfony/Component/Console/composer.json | 2 +- src/Symfony/Component/CssSelector/composer.json | 2 +- src/Symfony/Component/Debug/composer.json | 2 +- .../Component/DependencyInjection/composer.json | 2 +- src/Symfony/Component/DomCrawler/composer.json | 2 +- src/Symfony/Component/Dotenv/composer.json | 2 +- src/Symfony/Component/EventDispatcher/composer.json | 2 +- src/Symfony/Component/ExpressionLanguage/composer.json | 2 +- src/Symfony/Component/Filesystem/composer.json | 2 +- src/Symfony/Component/Finder/composer.json | 2 +- src/Symfony/Component/Form/composer.json | 2 +- src/Symfony/Component/HttpFoundation/composer.json | 2 +- src/Symfony/Component/HttpKernel/Kernel.php | 10 +++++----- src/Symfony/Component/HttpKernel/composer.json | 2 +- src/Symfony/Component/Inflector/composer.json | 2 +- src/Symfony/Component/Intl/composer.json | 2 +- src/Symfony/Component/Ldap/composer.json | 2 +- src/Symfony/Component/Lock/composer.json | 2 +- src/Symfony/Component/Messenger/composer.json | 2 +- src/Symfony/Component/OptionsResolver/composer.json | 2 +- src/Symfony/Component/Process/composer.json | 2 +- src/Symfony/Component/PropertyAccess/composer.json | 2 +- src/Symfony/Component/PropertyInfo/composer.json | 2 +- src/Symfony/Component/Routing/composer.json | 2 +- src/Symfony/Component/Security/Core/composer.json | 2 +- src/Symfony/Component/Security/Csrf/composer.json | 2 +- src/Symfony/Component/Security/Guard/composer.json | 2 +- src/Symfony/Component/Security/Http/composer.json | 2 +- src/Symfony/Component/Security/composer.json | 2 +- src/Symfony/Component/Serializer/composer.json | 2 +- src/Symfony/Component/Stopwatch/composer.json | 2 +- src/Symfony/Component/Templating/composer.json | 2 +- src/Symfony/Component/Translation/composer.json | 2 +- src/Symfony/Component/Validator/composer.json | 2 +- src/Symfony/Component/VarDumper/composer.json | 2 +- src/Symfony/Component/WebLink/composer.json | 2 +- src/Symfony/Component/Workflow/composer.json | 2 +- src/Symfony/Component/Yaml/composer.json | 2 +- 54 files changed, 58 insertions(+), 58 deletions(-) diff --git a/composer.json b/composer.json index cd86573f80797..3a861887ce60d 100644 --- a/composer.json +++ b/composer.json @@ -133,7 +133,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index f36c2d647c563..4f6e21f74b784 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -58,7 +58,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Bridge/Monolog/composer.json b/src/Symfony/Bridge/Monolog/composer.json index a4d3e984100c9..9d0d6acf85f7d 100644 --- a/src/Symfony/Bridge/Monolog/composer.json +++ b/src/Symfony/Bridge/Monolog/composer.json @@ -44,7 +44,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Bridge/PhpUnit/composer.json b/src/Symfony/Bridge/PhpUnit/composer.json index 7338fca00db74..4d72603eb452f 100644 --- a/src/Symfony/Bridge/PhpUnit/composer.json +++ b/src/Symfony/Bridge/PhpUnit/composer.json @@ -40,7 +40,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" }, "thanks": { "name": "phpunit/phpunit", diff --git a/src/Symfony/Bridge/ProxyManager/composer.json b/src/Symfony/Bridge/ProxyManager/composer.json index 90f000c828618..a224d5a8a90ad 100644 --- a/src/Symfony/Bridge/ProxyManager/composer.json +++ b/src/Symfony/Bridge/ProxyManager/composer.json @@ -32,7 +32,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index 14c80b21a33e2..81bab7092dc45 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -68,7 +68,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Bundle/DebugBundle/composer.json b/src/Symfony/Bundle/DebugBundle/composer.json index 7897621b54e10..ec781784640d7 100644 --- a/src/Symfony/Bundle/DebugBundle/composer.json +++ b/src/Symfony/Bundle/DebugBundle/composer.json @@ -43,7 +43,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 8c1c32194fdea..0e3f16fb8effc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -93,7 +93,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index 58e34ac55ae6e..e9dc497220fea 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -58,7 +58,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Bundle/TwigBundle/composer.json b/src/Symfony/Bundle/TwigBundle/composer.json index ab5c539735e25..bd5fdc6ef8292 100644 --- a/src/Symfony/Bundle/TwigBundle/composer.json +++ b/src/Symfony/Bundle/TwigBundle/composer.json @@ -52,7 +52,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Bundle/WebProfilerBundle/composer.json b/src/Symfony/Bundle/WebProfilerBundle/composer.json index df801f033d592..4e3783d695dcb 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/composer.json +++ b/src/Symfony/Bundle/WebProfilerBundle/composer.json @@ -44,7 +44,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Bundle/WebServerBundle/composer.json b/src/Symfony/Bundle/WebServerBundle/composer.json index 81eaaad4488fe..4a981ea93e419 100644 --- a/src/Symfony/Bundle/WebServerBundle/composer.json +++ b/src/Symfony/Bundle/WebServerBundle/composer.json @@ -37,7 +37,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/Asset/composer.json b/src/Symfony/Component/Asset/composer.json index bbd74fe239346..bcf70fc18318f 100644 --- a/src/Symfony/Component/Asset/composer.json +++ b/src/Symfony/Component/Asset/composer.json @@ -34,7 +34,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/BrowserKit/composer.json b/src/Symfony/Component/BrowserKit/composer.json index 6c226bed79c55..0607378c4d367 100644 --- a/src/Symfony/Component/BrowserKit/composer.json +++ b/src/Symfony/Component/BrowserKit/composer.json @@ -35,7 +35,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/Cache/composer.json b/src/Symfony/Component/Cache/composer.json index 9005eeb1462e2..869a9bbd5ab06 100644 --- a/src/Symfony/Component/Cache/composer.json +++ b/src/Symfony/Component/Cache/composer.json @@ -43,7 +43,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/Config/composer.json b/src/Symfony/Component/Config/composer.json index eb5bdbb51054d..35fbffdb69a41 100644 --- a/src/Symfony/Component/Config/composer.json +++ b/src/Symfony/Component/Config/composer.json @@ -41,7 +41,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/Console/composer.json b/src/Symfony/Component/Console/composer.json index d3aadfbc1d2f6..48da2819ff232 100644 --- a/src/Symfony/Component/Console/composer.json +++ b/src/Symfony/Component/Console/composer.json @@ -46,7 +46,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/CssSelector/composer.json b/src/Symfony/Component/CssSelector/composer.json index e2ed078e364af..ebe7d0d5c1eea 100644 --- a/src/Symfony/Component/CssSelector/composer.json +++ b/src/Symfony/Component/CssSelector/composer.json @@ -31,7 +31,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/Debug/composer.json b/src/Symfony/Component/Debug/composer.json index 9b2deebe6864a..45799e2e60f67 100644 --- a/src/Symfony/Component/Debug/composer.json +++ b/src/Symfony/Component/Debug/composer.json @@ -34,7 +34,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/DependencyInjection/composer.json b/src/Symfony/Component/DependencyInjection/composer.json index 8230395645c7c..4578af6ffd3dd 100644 --- a/src/Symfony/Component/DependencyInjection/composer.json +++ b/src/Symfony/Component/DependencyInjection/composer.json @@ -49,7 +49,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/DomCrawler/composer.json b/src/Symfony/Component/DomCrawler/composer.json index afb44bd7843c0..167d0fdfe50c9 100644 --- a/src/Symfony/Component/DomCrawler/composer.json +++ b/src/Symfony/Component/DomCrawler/composer.json @@ -35,7 +35,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/Dotenv/composer.json b/src/Symfony/Component/Dotenv/composer.json index 90ffb8f5f5ca7..6590da03e5169 100644 --- a/src/Symfony/Component/Dotenv/composer.json +++ b/src/Symfony/Component/Dotenv/composer.json @@ -30,7 +30,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/EventDispatcher/composer.json b/src/Symfony/Component/EventDispatcher/composer.json index 12ee53270edb9..faad1204a87cb 100644 --- a/src/Symfony/Component/EventDispatcher/composer.json +++ b/src/Symfony/Component/EventDispatcher/composer.json @@ -41,7 +41,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/ExpressionLanguage/composer.json b/src/Symfony/Component/ExpressionLanguage/composer.json index 3c453b8f49f12..489612428c59b 100644 --- a/src/Symfony/Component/ExpressionLanguage/composer.json +++ b/src/Symfony/Component/ExpressionLanguage/composer.json @@ -28,7 +28,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/Filesystem/composer.json b/src/Symfony/Component/Filesystem/composer.json index 77f62b75b04c6..ee8a319a7d1c5 100644 --- a/src/Symfony/Component/Filesystem/composer.json +++ b/src/Symfony/Component/Filesystem/composer.json @@ -28,7 +28,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/Finder/composer.json b/src/Symfony/Component/Finder/composer.json index dd37f2e0ee1f5..37d34a5e51d76 100644 --- a/src/Symfony/Component/Finder/composer.json +++ b/src/Symfony/Component/Finder/composer.json @@ -27,7 +27,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/Form/composer.json b/src/Symfony/Component/Form/composer.json index 7700d75b54874..4e4f65ebf3d3c 100644 --- a/src/Symfony/Component/Form/composer.json +++ b/src/Symfony/Component/Form/composer.json @@ -59,7 +59,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/HttpFoundation/composer.json b/src/Symfony/Component/HttpFoundation/composer.json index ef4bf826e5454..76381a7c46a05 100644 --- a/src/Symfony/Component/HttpFoundation/composer.json +++ b/src/Symfony/Component/HttpFoundation/composer.json @@ -32,7 +32,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index cfaef24ac3a85..cd90b037d8224 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -63,15 +63,15 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private $requestStackSize = 0; private $resetServices = false; - const VERSION = '4.1.0-DEV'; - const VERSION_ID = 40100; + const VERSION = '4.2.0-DEV'; + const VERSION_ID = 40200; const MAJOR_VERSION = 4; - const MINOR_VERSION = 1; + const MINOR_VERSION = 2; const RELEASE_VERSION = 0; const EXTRA_VERSION = 'DEV'; - const END_OF_MAINTENANCE = '01/2019'; - const END_OF_LIFE = '07/2019'; + const END_OF_MAINTENANCE = '07/2019'; + const END_OF_LIFE = '01/2020'; public function __construct(string $environment, bool $debug) { diff --git a/src/Symfony/Component/HttpKernel/composer.json b/src/Symfony/Component/HttpKernel/composer.json index e610f27418777..816af1222faf5 100644 --- a/src/Symfony/Component/HttpKernel/composer.json +++ b/src/Symfony/Component/HttpKernel/composer.json @@ -65,7 +65,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/Inflector/composer.json b/src/Symfony/Component/Inflector/composer.json index 36b9b77d81a9e..b4312cd037dfe 100644 --- a/src/Symfony/Component/Inflector/composer.json +++ b/src/Symfony/Component/Inflector/composer.json @@ -35,7 +35,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/Intl/composer.json b/src/Symfony/Component/Intl/composer.json index 874be36cb3bb6..b0f3e3b6d53c7 100644 --- a/src/Symfony/Component/Intl/composer.json +++ b/src/Symfony/Component/Intl/composer.json @@ -43,7 +43,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/Ldap/composer.json b/src/Symfony/Component/Ldap/composer.json index 10bb67cf3fa89..2c69b7dfe0ce6 100644 --- a/src/Symfony/Component/Ldap/composer.json +++ b/src/Symfony/Component/Ldap/composer.json @@ -29,7 +29,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/Lock/composer.json b/src/Symfony/Component/Lock/composer.json index 1ca9a4da40220..8aaf0eab94d25 100644 --- a/src/Symfony/Component/Lock/composer.json +++ b/src/Symfony/Component/Lock/composer.json @@ -31,7 +31,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/Messenger/composer.json b/src/Symfony/Component/Messenger/composer.json index 1767f4dab967e..5bb25399574dd 100644 --- a/src/Symfony/Component/Messenger/composer.json +++ b/src/Symfony/Component/Messenger/composer.json @@ -40,7 +40,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/OptionsResolver/composer.json b/src/Symfony/Component/OptionsResolver/composer.json index e78b66f25c105..1c819495bf89d 100644 --- a/src/Symfony/Component/OptionsResolver/composer.json +++ b/src/Symfony/Component/OptionsResolver/composer.json @@ -27,7 +27,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/Process/composer.json b/src/Symfony/Component/Process/composer.json index 58f4005b594b7..44bad06b597b9 100644 --- a/src/Symfony/Component/Process/composer.json +++ b/src/Symfony/Component/Process/composer.json @@ -27,7 +27,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/PropertyAccess/composer.json b/src/Symfony/Component/PropertyAccess/composer.json index bacba378a4434..33f9b188d0d1c 100644 --- a/src/Symfony/Component/PropertyAccess/composer.json +++ b/src/Symfony/Component/PropertyAccess/composer.json @@ -34,7 +34,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/PropertyInfo/composer.json b/src/Symfony/Component/PropertyInfo/composer.json index 0350fb411f315..a7531a3cc4c76 100644 --- a/src/Symfony/Component/PropertyInfo/composer.json +++ b/src/Symfony/Component/PropertyInfo/composer.json @@ -53,7 +53,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/Routing/composer.json b/src/Symfony/Component/Routing/composer.json index a558a1d749f3f..df999a11f7f0b 100644 --- a/src/Symfony/Component/Routing/composer.json +++ b/src/Symfony/Component/Routing/composer.json @@ -50,7 +50,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/Security/Core/composer.json b/src/Symfony/Component/Security/Core/composer.json index 30c42bb80f6b0..60603bff8b4ca 100644 --- a/src/Symfony/Component/Security/Core/composer.json +++ b/src/Symfony/Component/Security/Core/composer.json @@ -44,7 +44,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/Security/Csrf/composer.json b/src/Symfony/Component/Security/Csrf/composer.json index f13de8fefb2d1..cdc19ade22f15 100644 --- a/src/Symfony/Component/Security/Csrf/composer.json +++ b/src/Symfony/Component/Security/Csrf/composer.json @@ -37,7 +37,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/Security/Guard/composer.json b/src/Symfony/Component/Security/Guard/composer.json index e1faa18d81f47..982b16f012a32 100644 --- a/src/Symfony/Component/Security/Guard/composer.json +++ b/src/Symfony/Component/Security/Guard/composer.json @@ -32,7 +32,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/Security/Http/composer.json b/src/Symfony/Component/Security/Http/composer.json index 7bf03b6e874d4..3eb368c078fef 100644 --- a/src/Symfony/Component/Security/Http/composer.json +++ b/src/Symfony/Component/Security/Http/composer.json @@ -41,7 +41,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/Security/composer.json b/src/Symfony/Component/Security/composer.json index 965f881bdfdf4..bf04e86730388 100644 --- a/src/Symfony/Component/Security/composer.json +++ b/src/Symfony/Component/Security/composer.json @@ -55,7 +55,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/Serializer/composer.json b/src/Symfony/Component/Serializer/composer.json index a11c952f6a578..52961bb7c0528 100644 --- a/src/Symfony/Component/Serializer/composer.json +++ b/src/Symfony/Component/Serializer/composer.json @@ -58,7 +58,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/Stopwatch/composer.json b/src/Symfony/Component/Stopwatch/composer.json index cb1f823cc49f7..26c6c3e922bd9 100644 --- a/src/Symfony/Component/Stopwatch/composer.json +++ b/src/Symfony/Component/Stopwatch/composer.json @@ -27,7 +27,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/Templating/composer.json b/src/Symfony/Component/Templating/composer.json index 9e7f3e1bb815e..9e56b0247cc88 100644 --- a/src/Symfony/Component/Templating/composer.json +++ b/src/Symfony/Component/Templating/composer.json @@ -34,7 +34,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/Translation/composer.json b/src/Symfony/Component/Translation/composer.json index 64ab46b31b732..2991949987471 100644 --- a/src/Symfony/Component/Translation/composer.json +++ b/src/Symfony/Component/Translation/composer.json @@ -47,7 +47,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/Validator/composer.json b/src/Symfony/Component/Validator/composer.json index c9b201d81c5f6..531ffd9d57101 100644 --- a/src/Symfony/Component/Validator/composer.json +++ b/src/Symfony/Component/Validator/composer.json @@ -64,7 +64,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/VarDumper/composer.json b/src/Symfony/Component/VarDumper/composer.json index 59e9201720589..5cf48475a78e1 100644 --- a/src/Symfony/Component/VarDumper/composer.json +++ b/src/Symfony/Component/VarDumper/composer.json @@ -47,7 +47,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/WebLink/composer.json b/src/Symfony/Component/WebLink/composer.json index 1211d16ea3d05..2c09787390037 100644 --- a/src/Symfony/Component/WebLink/composer.json +++ b/src/Symfony/Component/WebLink/composer.json @@ -37,7 +37,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/Workflow/composer.json b/src/Symfony/Component/Workflow/composer.json index 643f762e4a27c..17500cbb2576f 100644 --- a/src/Symfony/Component/Workflow/composer.json +++ b/src/Symfony/Component/Workflow/composer.json @@ -37,7 +37,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Component/Yaml/composer.json b/src/Symfony/Component/Yaml/composer.json index 6537e42b2b073..c8b7123f239d4 100644 --- a/src/Symfony/Component/Yaml/composer.json +++ b/src/Symfony/Component/Yaml/composer.json @@ -37,7 +37,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } From a7c96963829667928cd84931ceaa935f5e9a1f26 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Fri, 4 May 2018 15:23:26 +0200 Subject: [PATCH 006/938] Added new templates for GitHub issues --- .github/ISSUE_TEMPLATE.md | 13 ---------- .github/ISSUE_TEMPLATE/Bug_report.md | 26 +++++++++++++++++++ .github/ISSUE_TEMPLATE/Documentation_issue.md | 15 +++++++++++ .github/ISSUE_TEMPLATE/Feature_request.md | 17 ++++++++++++ .github/ISSUE_TEMPLATE/Security_issue.md | 16 ++++++++++++ .github/ISSUE_TEMPLATE/Support_question.md | 16 ++++++++++++ 6 files changed, 90 insertions(+), 13 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/ISSUE_TEMPLATE/Bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/Documentation_issue.md create mode 100644 .github/ISSUE_TEMPLATE/Feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/Security_issue.md create mode 100644 .github/ISSUE_TEMPLATE/Support_question.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 6eaec7c81da9a..0000000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,13 +0,0 @@ -| Q | A -| ---------------- | ----- -| Bug report? | yes/no -| Feature request? | yes/no -| BC Break report? | yes/no -| RFC? | yes/no -| Symfony version | x.y.z - - diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md new file mode 100644 index 0000000000000..42173b6d49faa --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -0,0 +1,26 @@ +--- +name: Bug Report +about: Report errors and problems + +--- + + + +**Symfony version(s) affected**: x.y.z + +**Description** + + +**How to reproduce** + + +**Possible Solution** + + +**Additional context** + diff --git a/.github/ISSUE_TEMPLATE/Documentation_issue.md b/.github/ISSUE_TEMPLATE/Documentation_issue.md new file mode 100644 index 0000000000000..eed9470b6878f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Documentation_issue.md @@ -0,0 +1,15 @@ +--- +name: Documentation Issue +about: Anything related to Symfony Documentation + +--- + + + +Symfony Documentation has its own dedicated repository. Please open your +documentation-related issue at https://github.com/symfony/symfony-docs/issues + +Thanks! diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md new file mode 100644 index 0000000000000..3422cc1353edc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature Request +about: RFC and ideas for new features and improvements + +--- + + + +**Description** + + +**Example** + diff --git a/.github/ISSUE_TEMPLATE/Security_issue.md b/.github/ISSUE_TEMPLATE/Security_issue.md new file mode 100644 index 0000000000000..29981939d043b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Security_issue.md @@ -0,0 +1,16 @@ +--- +name: Security Issue +about: Report security-related errors + +--- + + + +If you have found a security issue in Symfony, please send the details to +security [at] symfony.com and don't disclose it publicly until we can provide a +fix for it. + +More information: https://symfony.com/security diff --git a/.github/ISSUE_TEMPLATE/Support_question.md b/.github/ISSUE_TEMPLATE/Support_question.md new file mode 100644 index 0000000000000..40a9277035404 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Support_question.md @@ -0,0 +1,16 @@ +--- +name: Support Question +about: Questions about using Symfony and its components + +--- + + + +We use GitHub issues only to discuss about Symfony bugs and new features. For +this kind of questions about using Symfony or third-party bundles, please use +any of the support alternatives shown in https://symfony.com/support + +Thanks! From 3ae3a03f41c34d6224cd9787c8ca111f7a8a71a4 Mon Sep 17 00:00:00 2001 From: Baptiste Lafontaine Date: Fri, 27 Apr 2018 15:39:41 +0200 Subject: [PATCH 007/938] [DI][DX] Allow exclude to be an array of patterns (taking #24428 over) --- .../Configurator/PrototypeConfigurator.php | 12 +++++---- .../DependencyInjection/Loader/FileLoader.php | 16 ++++++------ .../Loader/XmlFileLoader.php | 9 ++++++- .../schema/dic/services/services-1.0.xsd | 1 + .../config/prototype_array.expected.yml | 25 +++++++++++++++++++ .../Tests/Fixtures/config/prototype_array.php | 22 ++++++++++++++++ .../Fixtures/xml/services_prototype_array.xml | 9 +++++++ .../Tests/Loader/FileLoaderTest.php | 19 ++++++++++++++ .../Tests/Loader/XmlFileLoaderTest.php | 20 +++++++++++++++ 9 files changed, 119 insertions(+), 14 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype_array.expected.yml create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype_array.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_prototype_array.xml diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/PrototypeConfigurator.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/PrototypeConfigurator.php index 573dcc51e8204..45e88a0bfaef0 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/PrototypeConfigurator.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/PrototypeConfigurator.php @@ -39,7 +39,7 @@ class PrototypeConfigurator extends AbstractServiceConfigurator private $loader; private $resource; - private $exclude; + private $excludes; private $allowParent; public function __construct(ServicesConfigurator $parent, PhpFileLoader $loader, Definition $defaults, string $namespace, string $resource, bool $allowParent) @@ -63,19 +63,21 @@ public function __destruct() parent::__destruct(); if ($this->loader) { - $this->loader->registerClasses($this->definition, $this->id, $this->resource, $this->exclude); + $this->loader->registerClasses($this->definition, $this->id, $this->resource, $this->excludes); } $this->loader = null; } /** - * Excludes files from registration using a glob pattern. + * Excludes files from registration using glob patterns. + * + * @param string[]|string $excludes * * @return $this */ - final public function exclude(string $exclude) + final public function exclude($excludes) { - $this->exclude = $exclude; + $this->excludes = (array) $excludes; return $this; } diff --git a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php index 83a3f4f87ca45..b193354a6eae0 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php @@ -40,10 +40,10 @@ public function __construct(ContainerBuilder $container, FileLocatorInterface $l /** * Registers a set of classes as services using PSR-4 for discovery. * - * @param Definition $prototype A definition to use as template - * @param string $namespace The namespace prefix of classes in the scanned directory - * @param string $resource The directory to look for classes, glob-patterns allowed - * @param string $exclude A globed path of files to exclude + * @param Definition $prototype A definition to use as template + * @param string $namespace The namespace prefix of classes in the scanned directory + * @param string $resource The directory to look for classes, glob-patterns allowed + * @param string|string[]|null $exclude A globbed path of files to exclude or an array of globbed paths of files to exclude */ public function registerClasses(Definition $prototype, $namespace, $resource, $exclude = null) { @@ -54,7 +54,7 @@ public function registerClasses(Definition $prototype, $namespace, $resource, $e throw new InvalidArgumentException(sprintf('Namespace is not a valid PSR-4 prefix: %s.', $namespace)); } - $classes = $this->findClasses($namespace, $resource, $exclude); + $classes = $this->findClasses($namespace, $resource, (array) $exclude); // prepare for deep cloning $serializedPrototype = serialize($prototype); $interfaces = array(); @@ -101,14 +101,14 @@ protected function setDefinition($id, Definition $definition) } } - private function findClasses($namespace, $pattern, $excludePattern) + private function findClasses($namespace, $pattern, array $excludePatterns) { $parameterBag = $this->container->getParameterBag(); $excludePaths = array(); $excludePrefix = null; - if ($excludePattern) { - $excludePattern = $parameterBag->unescapeValue($parameterBag->resolveValue($excludePattern)); + $excludePatterns = $parameterBag->unescapeValue($parameterBag->resolveValue($excludePatterns)); + foreach ($excludePatterns as $excludePattern) { foreach ($this->glob($excludePattern, true, $resource) as $path => $info) { if (null === $excludePrefix) { $excludePrefix = $resource->getPrefix(); diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index 3b1c3548f6d55..9fbff021d680c 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -146,7 +146,14 @@ private function parseDefinitions(\DOMDocument $xml, $file, $defaults) foreach ($services as $service) { if (null !== $definition = $this->parseDefinition($service, $file, $defaults)) { if ('prototype' === $service->tagName) { - $this->registerClasses($definition, (string) $service->getAttribute('namespace'), (string) $service->getAttribute('resource'), (string) $service->getAttribute('exclude')); + $excludes = array_column($this->getChildren($service, 'exclude'), 'nodeValue'); + if ($service->hasAttribute('exclude')) { + if (count($excludes) > 0) { + throw new InvalidArgumentException('You cannot use both the attribute "exclude" and tags at the same time.'); + } + $excludes = array($service->getAttribute('exclude')); + } + $this->registerClasses($definition, (string) $service->getAttribute('namespace'), (string) $service->getAttribute('resource'), $excludes); } else { $this->setDefinition((string) $service->getAttribute('id'), $definition); } diff --git a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd index 60a01bd666aed..015f81f9536e8 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd +++ b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd @@ -160,6 +160,7 @@ + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype_array.expected.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype_array.expected.yml new file mode 100644 index 0000000000000..e8a03691c95c6 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype_array.expected.yml @@ -0,0 +1,25 @@ + +services: + service_container: + class: Symfony\Component\DependencyInjection\ContainerInterface + public: true + synthetic: true + Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Foo: + class: Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Foo + public: true + tags: + - { name: foo } + - { name: baz } + deprecated: "%service_id%" + arguments: [1] + factory: f + Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Sub\Bar: + class: Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Sub\Bar + public: true + tags: + - { name: foo } + - { name: baz } + deprecated: "%service_id%" + lazy: true + arguments: [1] + factory: f diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype_array.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype_array.php new file mode 100644 index 0000000000000..4d5625c245aa7 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype_array.php @@ -0,0 +1,22 @@ +services()->defaults() + ->tag('baz'); + $di->load(Prototype::class.'\\', '../Prototype') + ->autoconfigure() + ->exclude(array('../Prototype/OtherDir', '../Prototype/BadClasses')) + ->factory('f') + ->deprecate('%service_id%') + ->args(array(0)) + ->args(array(1)) + ->autoconfigure(false) + ->tag('foo') + ->parent('foo'); + $di->set('foo')->lazy()->abstract(); + $di->get(Prototype\Foo::class)->lazy(false); +}; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_prototype_array.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_prototype_array.xml new file mode 100644 index 0000000000000..892e0a7e8c95d --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_prototype_array.xml @@ -0,0 +1,9 @@ + + + + + ../Prototype/OtherDir + ../Prototype/BadClasses + + + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php index 8a271a818a475..a15b1b0315108 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php @@ -136,6 +136,25 @@ public function testRegisterClassesWithExclude() ); } + public function testRegisterClassesWithExcludeAsArray() + { + $container = new ContainerBuilder(); + $container->setParameter('sub_dir', 'Sub'); + $loader = new TestFileLoader($container, new FileLocator(self::$fixturesPath.'/Fixtures')); + $loader->registerClasses( + new Definition(), + 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\\', + 'Prototype/*', array( + 'Prototype/%sub_dir%', + 'Prototype/OtherDir/AnotherSub/DeeperBaz.php', + ) + ); + $this->assertTrue($container->has(Foo::class)); + $this->assertTrue($container->has(Baz::class)); + $this->assertFalse($container->has(Bar::class)); + $this->assertFalse($container->has(DeeperBaz::class)); + } + public function testNestedRegisterClasses() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php index 7e0ce38719097..2e87291bf2c15 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php @@ -617,6 +617,26 @@ public function testPrototype() $this->assertContains('reflection.Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Sub\Bar', $resources); } + public function testPrototypeExcludeWithArray() + { + $container = new ContainerBuilder(); + $loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml')); + $loader->load('services_prototype_array.xml'); + + $ids = array_keys($container->getDefinitions()); + sort($ids); + $this->assertSame(array(Prototype\Foo::class, Prototype\Sub\Bar::class, 'service_container'), $ids); + + $resources = $container->getResources(); + + $fixturesDir = dirname(__DIR__).DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR; + $this->assertTrue(false !== array_search(new FileResource($fixturesDir.'xml'.DIRECTORY_SEPARATOR.'services_prototype_array.xml'), $resources)); + $this->assertTrue(false !== array_search(new GlobResource($fixturesDir.'Prototype', '/*', true), $resources)); + $resources = array_map('strval', $resources); + $this->assertContains('reflection.Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Foo', $resources); + $this->assertContains('reflection.Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Sub\Bar', $resources); + } + /** * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException * @expectedExceptionMessage Invalid attribute "class" defined for alias "bar" in From a05ae9b9cdd3977a1fde8dd146317f5de41c4fc1 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 7 May 2018 16:41:05 -0700 Subject: [PATCH 008/938] Improve github issue templates --- .github/ISSUE_TEMPLATE/{Bug_report.md => 1_Bug_report.md} | 2 +- .../{Feature_request.md => 2_Feature_request.md} | 2 +- .../{Support_question.md => 3_Support_question.md} | 4 ++-- .../{Documentation_issue.md => 4_Documentation_issue.md} | 4 ++-- .../{Security_issue.md => 5_Security_issue.md} | 6 ++++-- 5 files changed, 10 insertions(+), 8 deletions(-) rename .github/ISSUE_TEMPLATE/{Bug_report.md => 1_Bug_report.md} (97%) rename .github/ISSUE_TEMPLATE/{Feature_request.md => 2_Feature_request.md} (95%) rename .github/ISSUE_TEMPLATE/{Support_question.md => 3_Support_question.md} (77%) rename .github/ISSUE_TEMPLATE/{Documentation_issue.md => 4_Documentation_issue.md} (75%) rename .github/ISSUE_TEMPLATE/{Security_issue.md => 5_Security_issue.md} (69%) diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/1_Bug_report.md similarity index 97% rename from .github/ISSUE_TEMPLATE/Bug_report.md rename to .github/ISSUE_TEMPLATE/1_Bug_report.md index 42173b6d49faa..77210d5df7e20 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report.md +++ b/.github/ISSUE_TEMPLATE/1_Bug_report.md @@ -1,5 +1,5 @@ --- -name: Bug Report +name: 🐛 Bug Report about: Report errors and problems --- diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/2_Feature_request.md similarity index 95% rename from .github/ISSUE_TEMPLATE/Feature_request.md rename to .github/ISSUE_TEMPLATE/2_Feature_request.md index 3422cc1353edc..5e791f9f08b07 100644 --- a/.github/ISSUE_TEMPLATE/Feature_request.md +++ b/.github/ISSUE_TEMPLATE/2_Feature_request.md @@ -1,5 +1,5 @@ --- -name: Feature Request +name: 🚀 Feature Request about: RFC and ideas for new features and improvements --- diff --git a/.github/ISSUE_TEMPLATE/Support_question.md b/.github/ISSUE_TEMPLATE/3_Support_question.md similarity index 77% rename from .github/ISSUE_TEMPLATE/Support_question.md rename to .github/ISSUE_TEMPLATE/3_Support_question.md index 40a9277035404..4975ea242c8f1 100644 --- a/.github/ISSUE_TEMPLATE/Support_question.md +++ b/.github/ISSUE_TEMPLATE/3_Support_question.md @@ -1,6 +1,6 @@ --- -name: Support Question -about: Questions about using Symfony and its components +name: ⛔ Support Question +about: See https://symfony.com/support for questions about using Symfony and its components --- diff --git a/.github/ISSUE_TEMPLATE/Documentation_issue.md b/.github/ISSUE_TEMPLATE/4_Documentation_issue.md similarity index 75% rename from .github/ISSUE_TEMPLATE/Documentation_issue.md rename to .github/ISSUE_TEMPLATE/4_Documentation_issue.md index eed9470b6878f..8570d0c6e44a0 100644 --- a/.github/ISSUE_TEMPLATE/Documentation_issue.md +++ b/.github/ISSUE_TEMPLATE/4_Documentation_issue.md @@ -1,6 +1,6 @@ --- -name: Documentation Issue -about: Anything related to Symfony Documentation +name: ⛔ Documentation Issue +about: See https://github.com/symfony/symfony-docs/issues for documentation issues --- diff --git a/.github/ISSUE_TEMPLATE/Security_issue.md b/.github/ISSUE_TEMPLATE/5_Security_issue.md similarity index 69% rename from .github/ISSUE_TEMPLATE/Security_issue.md rename to .github/ISSUE_TEMPLATE/5_Security_issue.md index 29981939d043b..e3283c73969b8 100644 --- a/.github/ISSUE_TEMPLATE/Security_issue.md +++ b/.github/ISSUE_TEMPLATE/5_Security_issue.md @@ -1,9 +1,11 @@ --- -name: Security Issue -about: Report security-related errors +name: ⛔ Security Issue +about: See https://symfony.com/security to report security-related issues --- +⚠ PLEASE DON'T DISCLOSE SECURITY-RELATED ISSUES PUBLICLY, SEE BELOW. + - **Symfony version(s) affected**: x.y.z **Description** diff --git a/.github/ISSUE_TEMPLATE/2_Feature_request.md b/.github/ISSUE_TEMPLATE/2_Feature_request.md index 5e791f9f08b07..335321e413607 100644 --- a/.github/ISSUE_TEMPLATE/2_Feature_request.md +++ b/.github/ISSUE_TEMPLATE/2_Feature_request.md @@ -4,11 +4,6 @@ about: RFC and ideas for new features and improvements --- - - **Description** diff --git a/.github/ISSUE_TEMPLATE/3_Support_question.md b/.github/ISSUE_TEMPLATE/3_Support_question.md index 4975ea242c8f1..9480710c15655 100644 --- a/.github/ISSUE_TEMPLATE/3_Support_question.md +++ b/.github/ISSUE_TEMPLATE/3_Support_question.md @@ -4,11 +4,6 @@ about: See https://symfony.com/support for questions about using Symfony and its --- - - We use GitHub issues only to discuss about Symfony bugs and new features. For this kind of questions about using Symfony or third-party bundles, please use any of the support alternatives shown in https://symfony.com/support diff --git a/.github/ISSUE_TEMPLATE/4_Documentation_issue.md b/.github/ISSUE_TEMPLATE/4_Documentation_issue.md index 8570d0c6e44a0..0855c3c5f1e12 100644 --- a/.github/ISSUE_TEMPLATE/4_Documentation_issue.md +++ b/.github/ISSUE_TEMPLATE/4_Documentation_issue.md @@ -4,11 +4,6 @@ about: See https://github.com/symfony/symfony-docs/issues for documentation issu --- - - Symfony Documentation has its own dedicated repository. Please open your documentation-related issue at https://github.com/symfony/symfony-docs/issues diff --git a/.github/ISSUE_TEMPLATE/5_Security_issue.md b/.github/ISSUE_TEMPLATE/5_Security_issue.md index e3283c73969b8..9b3165eb1db47 100644 --- a/.github/ISSUE_TEMPLATE/5_Security_issue.md +++ b/.github/ISSUE_TEMPLATE/5_Security_issue.md @@ -6,11 +6,6 @@ about: See https://symfony.com/security to report security-related issues ⚠ PLEASE DON'T DISCLOSE SECURITY-RELATED ISSUES PUBLICLY, SEE BELOW. - - If you have found a security issue in Symfony, please send the details to security [at] symfony.com and don't disclose it publicly until we can provide a fix for it. From 88ecd0dc9ac2a369a477dfb5558bc5229dc204f5 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sun, 13 May 2018 23:24:43 +0200 Subject: [PATCH 011/938] [DI] fine tune dumped factories --- .../DependencyInjection/Dumper/PhpDumper.php | 30 ++++++++++++++++--- .../Tests/Fixtures/php/services9_as_files.txt | 16 ++-------- .../Tests/Fixtures/php/services9_compiled.php | 6 ++-- .../php/services_errored_definition.php | 6 ++-- .../php/services_private_in_expression.php | 2 +- 5 files changed, 36 insertions(+), 24 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 8491f806b4b17..b12d885325723 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -17,6 +17,7 @@ use Symfony\Component\DependencyInjection\Variable; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Compiler\AnalyzeServiceReferencesPass; +use Symfony\Component\DependencyInjection\Compiler\ServiceReferenceGraphNode; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Container; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -68,6 +69,7 @@ class PhpDumper extends Dumper private $inlineRequires; private $inlinedRequires = array(); private $circularReferences = array(); + private $singleUsePrivateIds = array(); /** * @var ProxyDumper @@ -141,10 +143,14 @@ public function dump(array $options = array()) (new AnalyzeServiceReferencesPass())->process($this->container); $this->circularReferences = array(); + $this->singleUsePrivateIds = array(); $checkedNodes = array(); foreach ($this->container->getCompiler()->getServiceReferenceGraph()->getNodes() as $id => $node) { $currentPath = array($id => $id); $this->analyzeCircularReferences($node->getOutEdges(), $checkedNodes, $currentPath); + if ($this->isSingleUsePrivateNode($node)) { + $this->singleUsePrivateIds[$id] = $id; + } } $this->container->getCompiler()->getServiceReferenceGraph()->clear(); @@ -526,7 +532,7 @@ private function addServiceInstance(string $id, Definition $definition, string $ $isProxyCandidate = $this->getProxyDumper()->isProxyCandidate($definition); $instantiation = ''; - if (!$isProxyCandidate && $definition->isShared()) { + if (!$isProxyCandidate && $definition->isShared() && !isset($this->singleUsePrivateIds[$id])) { $instantiation = sprintf('$this->%s[\'%s\'] = %s', $this->container->getDefinition($id)->isPublic() ? 'services' : 'privates', $id, $isSimpleInstance ? '' : '$instance'); } elseif (!$isSimpleInstance) { $instantiation = '$instance'; @@ -819,7 +825,7 @@ private function generateServiceFiles() $definitions = $this->container->getDefinitions(); ksort($definitions); foreach ($definitions as $id => $definition) { - if (!$definition->isSynthetic() && !$this->isHotPath($definition)) { + if (!$definition->isSynthetic() && !$this->isHotPath($definition) && ($definition->isPublic() || !$this->isTrivialInstance($definition))) { $code = $this->addService($id, $definition, $file); if (!$definition->isShared()) { @@ -1662,7 +1668,7 @@ private function getServiceCall(string $id, Reference $reference = null): string $code = 'null'; } elseif ($this->isTrivialInstance($definition)) { $code = substr($this->addNewInstance($definition, '', '', $id), 8, -2); - if ($definition->isShared()) { + if ($definition->isShared() && !isset($this->singleUsePrivateIds[$id])) { $code = sprintf('$this->%s[\'%s\'] = %s', $definition->isPublic() ? 'services' : 'privates', $id, $code); } } elseif ($this->asFiles && !$this->isHotPath($definition)) { @@ -1674,7 +1680,7 @@ private function getServiceCall(string $id, Reference $reference = null): string } else { $code = sprintf('$this->%s()', $this->generateMethodName($id)); } - if ($definition->isShared()) { + if ($definition->isShared() && !isset($this->singleUsePrivateIds[$id])) { $code = sprintf('($this->%s[\'%s\'] ?? %s)', $definition->isPublic() ? 'services' : 'privates', $id, $code); } @@ -1798,6 +1804,22 @@ private function isHotPath(Definition $definition) return $this->hotPathTag && $definition->hasTag($this->hotPathTag) && !$definition->isDeprecated(); } + private function isSingleUsePrivateNode(ServiceReferenceGraphNode $node): bool + { + if ($node->getValue()->isPublic()) { + return false; + } + $ids = array(); + foreach ($node->getInEdges() as $edge) { + if ($edge->isLazy() || !$edge->getSourceNode()->getValue()->isShared()) { + return false; + } + $ids[$edge->getSourceNode()->getId()] = true; + } + + return 1 === \count($ids); + } + private function export($value) { if (null !== $this->targetDirRegex && is_string($value) && preg_match($this->targetDirRegex, $value, $matches, PREG_OFFSET_CAPTURE)) { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt index 2b92c5838ba15..3a092f7306d70 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt @@ -155,7 +155,7 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException; // This file has been auto-generated by the Symfony Dependency Injection Component for internal use. // Returns the public 'factory_service_simple' shared service. -return $this->services['factory_service_simple'] = ($this->privates['factory_simple'] ?? $this->load('getFactorySimpleService.php'))->getInstance(); +return $this->services['factory_service_simple'] = $this->load('getFactorySimpleService.php')->getInstance(); [Container%s/getFactorySimpleService.php] => privates['factory_simple'] = new \SimpleFactoryClass('foo'); +return new \SimpleFactoryClass('foo'); [Container%s/getFooService.php] => services['runtime_error'] = new \stdClass(($this->privates['errored_definition'] ?? $this->load('getErroredDefinitionService.php'))); +return $this->services['runtime_error'] = new \stdClass($this->load('getErroredDefinitionService.php')); [Container%s/getServiceFromStaticMethodService.php] => services['tagged_iterator'] = new \Bar(new RewindableGenerator(fun yield 1 => ($this->privates['tagged_iterator_foo'] ?? $this->privates['tagged_iterator_foo'] = new \Bar()); }, 2)); - [Container%s/getTaggedIteratorFooService.php] => privates['tagged_iterator_foo'] = new \Bar(); - [Container%s/ProjectServiceContainer.php] => services['factory_service_simple'] = ($this->privates['factory_simple'] ?? $this->getFactorySimpleService())->getInstance(); + return $this->services['factory_service_simple'] = $this->getFactorySimpleService()->getInstance(); } /** @@ -381,7 +381,7 @@ protected function getNewFactoryServiceService() */ protected function getRuntimeErrorService() { - return $this->services['runtime_error'] = new \stdClass(($this->privates['errored_definition'] ?? $this->getErroredDefinitionService())); + return $this->services['runtime_error'] = new \stdClass($this->getErroredDefinitionService()); } /** @@ -428,7 +428,7 @@ protected function getFactorySimpleService() { @trigger_error('The "factory_simple" service is deprecated. You should stop using it, as it will soon be removed.', E_USER_DEPRECATED); - return $this->privates['factory_simple'] = new \SimpleFactoryClass('foo'); + return new \SimpleFactoryClass('foo'); } public function getParameter($name) diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_errored_definition.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_errored_definition.php index 34a38dfc40274..9ff22bf4d43d8 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_errored_definition.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_errored_definition.php @@ -243,7 +243,7 @@ protected function getFactoryServiceService() */ protected function getFactoryServiceSimpleService() { - return $this->services['factory_service_simple'] = ($this->privates['factory_simple'] ?? $this->getFactorySimpleService())->getInstance(); + return $this->services['factory_service_simple'] = $this->getFactorySimpleService()->getInstance(); } /** @@ -381,7 +381,7 @@ protected function getNewFactoryServiceService() */ protected function getRuntimeErrorService() { - return $this->services['runtime_error'] = new \stdClass(($this->privates['errored_definition'] ?? $this->getErroredDefinitionService())); + return $this->services['runtime_error'] = new \stdClass($this->getErroredDefinitionService()); } /** @@ -428,7 +428,7 @@ protected function getFactorySimpleService() { @trigger_error('The "factory_simple" service is deprecated. You should stop using it, as it will soon be removed.', E_USER_DEPRECATED); - return $this->privates['factory_simple'] = new \SimpleFactoryClass('foo'); + return new \SimpleFactoryClass('foo'); } public function getParameter($name) diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_in_expression.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_in_expression.php index 5caf9104dd34d..2ae36458036d9 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_in_expression.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_in_expression.php @@ -67,6 +67,6 @@ public function getRemovedIds() */ protected function getPublicFooService() { - return $this->services['public_foo'] = new \stdClass(($this->privates['private_foo'] ?? $this->privates['private_foo'] = new \stdClass())); + return $this->services['public_foo'] = new \stdClass(new \stdClass()); } } From 1c64c8267ddd9332d6434f87badfecc96146fd7d Mon Sep 17 00:00:00 2001 From: Jonathan Hedstrom Date: Tue, 1 May 2018 16:16:06 -0700 Subject: [PATCH 012/938] [BrowserKit] Adds support for meta refresh --- src/Symfony/Component/BrowserKit/Client.php | 35 ++++++++++++++++++- .../Component/BrowserKit/Tests/ClientTest.php | 33 +++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/BrowserKit/Client.php b/src/Symfony/Component/BrowserKit/Client.php index 799b3579f0f69..ddd99a1ce2622 100644 --- a/src/Symfony/Component/BrowserKit/Client.php +++ b/src/Symfony/Component/BrowserKit/Client.php @@ -40,6 +40,7 @@ abstract class Client protected $insulated = false; protected $redirect; protected $followRedirects = true; + protected $followMetaRefresh = false; private $maxRedirects = -1; private $redirectCount = 0; @@ -68,6 +69,14 @@ public function followRedirects($followRedirect = true) $this->followRedirects = (bool) $followRedirect; } + /** + * Sets whether to automatically follow meta refresh redirects or not. + */ + public function followMetaRefresh(bool $followMetaRefresh = true) + { + $this->followMetaRefresh = $followMetaRefresh; + } + /** * Returns whether client automatically follows redirects or not. * @@ -367,7 +376,16 @@ public function request(string $method, string $uri, array $parameters = array() return $this->crawler = $this->followRedirect(); } - return $this->crawler = $this->createCrawlerFromContent($this->internalRequest->getUri(), $this->internalResponse->getContent(), $this->internalResponse->getHeader('Content-Type')); + $this->crawler = $this->createCrawlerFromContent($this->internalRequest->getUri(), $this->internalResponse->getContent(), $this->internalResponse->getHeader('Content-Type')); + + // Check for meta refresh redirect + if ($this->followMetaRefresh && null !== $redirect = $this->getMetaRefreshUrl()) { + $this->redirect = $redirect; + $this->redirects[serialize($this->history->current())] = true; + $this->crawler = $this->followRedirect(); + } + + return $this->crawler; } /** @@ -563,6 +581,21 @@ public function followRedirect() return $response; } + /** + * @see https://dev.w3.org/html5/spec-preview/the-meta-element.html#attr-meta-http-equiv-refresh + */ + private function getMetaRefreshUrl(): ?string + { + $metaRefresh = $this->getCrawler()->filter('head meta[http-equiv="refresh"]'); + foreach ($metaRefresh->extract(array('content')) as $content) { + if (preg_match('/^\s*0\s*;\s*URL\s*=\s*(?|\'([^\']++)|"([^"]++)|([^\'"].*))/i', $content, $m)) { + return str_replace("\t\r\n", '', rtrim($m[1])); + } + } + + return null; + } + /** * Restarts the client. * diff --git a/src/Symfony/Component/BrowserKit/Tests/ClientTest.php b/src/Symfony/Component/BrowserKit/Tests/ClientTest.php index 909bf3c67c245..020fa07526e03 100644 --- a/src/Symfony/Component/BrowserKit/Tests/ClientTest.php +++ b/src/Symfony/Component/BrowserKit/Tests/ClientTest.php @@ -594,6 +594,39 @@ public function testFollowRedirectDropPostMethod() } } + /** + * @dataProvider getTestsForMetaRefresh + */ + public function testFollowMetaRefresh(string $content, string $expectedEndingUrl, bool $followMetaRefresh = true) + { + $client = new TestClient(); + $client->followMetaRefresh($followMetaRefresh); + $client->setNextResponse(new Response($content)); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $this->assertEquals($expectedEndingUrl, $client->getRequest()->getUri()); + } + + public function getTestsForMetaRefresh() + { + return array( + array('', 'http://www.example.com/redirected'), + array('', 'http://www.example.com/redirected'), + array('', 'http://www.example.com/redirected'), + array('', 'http://www.example.com/redirected'), + array('', 'http://www.example.com/redirected'), + array('', 'http://www.example.com/redirected'), + array('', 'http://www.example.com/redirected'), + array('', 'http://www.example.com/redirected'), + // Non-zero timeout should not result in a redirect. + array('', 'http://www.example.com/foo/foobar'), + array('', 'http://www.example.com/foo/foobar'), + // Invalid meta tag placement should not result in a redirect. + array('', 'http://www.example.com/foo/foobar'), + // Valid meta refresh should not be followed if disabled. + array('', 'http://www.example.com/foo/foobar', false), + ); + } + public function testBack() { $client = new TestClient(); From 07cfa93fc11f155b5961c97bddd7b1cbe71bb97f Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 18 May 2018 08:08:04 +0200 Subject: [PATCH 013/938] removed unneeded private methods --- .../Ldap/Adapter/ExtLdap/UpdateOperation.php | 31 ++++--------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/src/Symfony/Component/Ldap/Adapter/ExtLdap/UpdateOperation.php b/src/Symfony/Component/Ldap/Adapter/ExtLdap/UpdateOperation.php index 87bd451eb7229..bb5d146b9e05d 100644 --- a/src/Symfony/Component/Ldap/Adapter/ExtLdap/UpdateOperation.php +++ b/src/Symfony/Component/Ldap/Adapter/ExtLdap/UpdateOperation.php @@ -34,8 +34,12 @@ class UpdateOperation */ public function __construct(int $operationType, string $attribute, ?array $values) { - $this->assertValidOperationType($operationType); - $this->assertNullValuesOnRemoveAll($operationType, $values); + if (!in_array($operationType, $this->validOperationTypes, true)) { + throw new UpdateOperationException(sprintf('"%s" is not a valid modification type.', $operationType)); + } + if (LDAP_MODIFY_BATCH_REMOVE_ALL === $operationType && null !== $values) { + throw new UpdateOperationException(sprintf('$values must be null for LDAP_MODIFY_BATCH_REMOVE_ALL operation, "%s" given.', gettype($values))); + } $this->operationType = $operationType; $this->attribute = $attribute; @@ -50,27 +54,4 @@ public function toArray(): array 'values' => $this->values, ); } - - /** - * @param int $operationType - */ - private function assertValidOperationType(int $operationType): void - { - if (!in_array($operationType, $this->validOperationTypes, true)) { - throw new UpdateOperationException(sprintf('"%s" is not a valid modification type.', $operationType)); - } - } - - /** - * @param int $operationType - * @param array|null $values - * - * @throws \Symfony\Component\Ldap\Exception\UpdateOperationException - */ - private function assertNullValuesOnRemoveAll(int $operationType, ?array $values): void - { - if (LDAP_MODIFY_BATCH_REMOVE_ALL === $operationType && null !== $values) { - throw new UpdateOperationException(sprintf('$values must be null for LDAP_MODIFY_BATCH_REMOVE_ALL operation, "%s" given.', gettype($values))); - } - } } From 2ae7ad9fe195f7a9ec2b3c37c19bd6da50dd98a1 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 18 May 2018 08:10:24 +0200 Subject: [PATCH 014/938] updated CHANGELOG --- src/Symfony/Component/Ldap/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Symfony/Component/Ldap/CHANGELOG.md b/src/Symfony/Component/Ldap/CHANGELOG.md index 014c487eed2cf..b3f367d9ef445 100644 --- a/src/Symfony/Component/Ldap/CHANGELOG.md +++ b/src/Symfony/Component/Ldap/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +4.2.0 +----- + + * added `EntryManager::applyOperations` + 4.1.0 ----- From 589ff697f4a09b6aa391389cc64d5504b4ba1eb1 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 14 Apr 2018 19:21:41 -0500 Subject: [PATCH 015/938] [Cache] Add [Taggable]CacheInterface, the easiest way to use a cache --- .../Resources/config/cache.xml | 6 +++ .../Cache/Adapter/AbstractAdapter.php | 5 ++- .../Component/Cache/Adapter/ArrayAdapter.php | 5 ++- .../Component/Cache/Adapter/ChainAdapter.php | 35 ++++++++++++++- .../Component/Cache/Adapter/NullAdapter.php | 6 ++- .../Cache/Adapter/PhpArrayAdapter.php | 30 ++++++++++++- .../Component/Cache/Adapter/ProxyAdapter.php | 19 +++++++- .../Cache/Adapter/TagAwareAdapter.php | 6 ++- .../Cache/Adapter/TraceableAdapter.php | 36 +++++++++++++++- .../Adapter/TraceableTagAwareAdapter.php | 4 +- src/Symfony/Component/Cache/CHANGELOG.md | 6 +++ .../Component/Cache/CacheInterface.php | 37 ++++++++++++++++ src/Symfony/Component/Cache/CacheItem.php | 7 ++- .../DataCollector/CacheDataCollector.php | 10 ++++- .../Cache/Exception/LogicException.php | 19 ++++++++ .../Cache/TaggableCacheInterface.php | 35 +++++++++++++++ .../Cache/Tests/Adapter/AdapterTestCase.php | 21 +++++++++ .../Tests/Adapter/PhpArrayAdapterTest.php | 1 + .../Component/Cache/Tests/CacheItemTest.php | 21 +++++++++ .../Component/Cache/Traits/GetTrait.php | 43 +++++++++++++++++++ 20 files changed, 341 insertions(+), 11 deletions(-) create mode 100644 src/Symfony/Component/Cache/CacheInterface.php create mode 100644 src/Symfony/Component/Cache/Exception/LogicException.php create mode 100644 src/Symfony/Component/Cache/TaggableCacheInterface.php create mode 100644 src/Symfony/Component/Cache/Traits/GetTrait.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml index f7162adb1c701..d0e596ab83338 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml @@ -15,6 +15,10 @@ + + + + @@ -122,7 +126,9 @@ + + diff --git a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php index 3afc982089018..1e246b8790cb4 100644 --- a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php @@ -15,17 +15,20 @@ use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; +use Symfony\Component\Cache\CacheInterface; use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\Exception\InvalidArgumentException; use Symfony\Component\Cache\ResettableInterface; use Symfony\Component\Cache\Traits\AbstractTrait; +use Symfony\Component\Cache\Traits\GetTrait; /** * @author Nicolas Grekas */ -abstract class AbstractAdapter implements AdapterInterface, LoggerAwareInterface, ResettableInterface +abstract class AbstractAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface { use AbstractTrait; + use GetTrait; private static $apcuSupported; private static $phpFilesSupported; diff --git a/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php b/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php index fee7ed6d906d5..17f2beaf0fe75 100644 --- a/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php @@ -13,16 +13,19 @@ use Psr\Cache\CacheItemInterface; use Psr\Log\LoggerAwareInterface; +use Symfony\Component\Cache\CacheInterface; use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\ResettableInterface; use Symfony\Component\Cache\Traits\ArrayTrait; +use Symfony\Component\Cache\Traits\GetTrait; /** * @author Nicolas Grekas */ -class ArrayAdapter implements AdapterInterface, LoggerAwareInterface, ResettableInterface +class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface { use ArrayTrait; + use GetTrait; private $createCacheItem; diff --git a/src/Symfony/Component/Cache/Adapter/ChainAdapter.php b/src/Symfony/Component/Cache/Adapter/ChainAdapter.php index 98b0cc24693b4..ea0af87d9f238 100644 --- a/src/Symfony/Component/Cache/Adapter/ChainAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ChainAdapter.php @@ -13,10 +13,12 @@ use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\CacheInterface; use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\Exception\InvalidArgumentException; use Symfony\Component\Cache\PruneableInterface; use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\GetTrait; /** * Chains several adapters together. @@ -26,8 +28,10 @@ * * @author Kévin Dunglas */ -class ChainAdapter implements AdapterInterface, PruneableInterface, ResettableInterface +class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface { + use GetTrait; + private $adapters = array(); private $adapterCount; private $syncItem; @@ -61,6 +65,8 @@ function ($sourceItem, $item) use ($defaultLifetime) { $item->expiry = $sourceItem->expiry; $item->isHit = $sourceItem->isHit; + $sourceItem->isTaggable = false; + if (0 < $sourceItem->defaultLifetime && $sourceItem->defaultLifetime < $defaultLifetime) { $defaultLifetime = $sourceItem->defaultLifetime; } @@ -75,6 +81,33 @@ function ($sourceItem, $item) use ($defaultLifetime) { ); } + /** + * {@inheritdoc} + */ + public function get(string $key, callable $callback) + { + $lastItem = null; + $i = 0; + $wrap = function (CacheItem $item = null) use ($key, $callback, &$wrap, &$i, &$lastItem) { + $adapter = $this->adapters[$i]; + if (isset($this->adapters[++$i])) { + $callback = $wrap; + } + if ($adapter instanceof CacheInterface) { + $value = $adapter->get($key, $callback); + } else { + $value = $this->doGet($adapter, $key, $callback); + } + if (null !== $item) { + ($this->syncItem)($lastItem = $lastItem ?? $item, $item); + } + + return $value; + }; + + return $wrap(); + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Cache/Adapter/NullAdapter.php b/src/Symfony/Component/Cache/Adapter/NullAdapter.php index f58f81e5b8960..44929f9e76aeb 100644 --- a/src/Symfony/Component/Cache/Adapter/NullAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/NullAdapter.php @@ -12,13 +12,17 @@ namespace Symfony\Component\Cache\Adapter; use Psr\Cache\CacheItemInterface; +use Symfony\Component\Cache\CacheInterface; use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Traits\GetTrait; /** * @author Titouan Galopin */ -class NullAdapter implements AdapterInterface +class NullAdapter implements AdapterInterface, CacheInterface { + use GetTrait; + private $createCacheItem; public function __construct() diff --git a/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php b/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php index ca5ef743d2285..bcd322fede1ba 100644 --- a/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php @@ -13,10 +13,12 @@ use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\CacheInterface; use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\Exception\InvalidArgumentException; use Symfony\Component\Cache\PruneableInterface; use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\GetTrait; use Symfony\Component\Cache\Traits\PhpArrayTrait; /** @@ -26,9 +28,10 @@ * @author Titouan Galopin * @author Nicolas Grekas */ -class PhpArrayAdapter implements AdapterInterface, PruneableInterface, ResettableInterface +class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface { use PhpArrayTrait; + use GetTrait; private $createCacheItem; @@ -77,6 +80,31 @@ public static function create($file, CacheItemPoolInterface $fallbackPool) return $fallbackPool; } + /** + * {@inheritdoc} + */ + public function get(string $key, callable $callback) + { + if (null === $this->values) { + $this->initialize(); + } + if (null === $value = $this->values[$key] ?? null) { + if ($this->pool instanceof CacheInterface) { + return $this->pool->get($key, $callback); + } + + return $this->doGet($this->pool, $key, $callback); + } + if ('N;' === $value) { + return null; + } + if (\is_string($value) && isset($value[2]) && ':' === $value[1]) { + return unserialize($value); + } + + return $value; + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php b/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php index da286dbf173f7..b9981f5e64c0c 100644 --- a/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php @@ -13,17 +13,20 @@ use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\CacheInterface; use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\PruneableInterface; use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\GetTrait; use Symfony\Component\Cache\Traits\ProxyTrait; /** * @author Nicolas Grekas */ -class ProxyAdapter implements AdapterInterface, PruneableInterface, ResettableInterface +class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface { use ProxyTrait; + use GetTrait; private $namespace; private $namespaceLen; @@ -54,6 +57,20 @@ function ($key, $innerItem) use ($defaultLifetime, $poolHash) { ); } + /** + * {@inheritdoc} + */ + public function get(string $key, callable $callback) + { + if (!$this->pool instanceof CacheInterface) { + return $this->doGet($this->pool, $key, $callback); + } + + return $this->pool->get($this->getId($key), function ($innerItem) use ($key, $callback) { + return $callback(($this->createCacheItem)($key, $innerItem)); + }); + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php index 62f815e0171ad..257e404e1c43e 100644 --- a/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php @@ -16,16 +16,19 @@ use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\PruneableInterface; use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\TaggableCacheInterface; +use Symfony\Component\Cache\Traits\GetTrait; use Symfony\Component\Cache\Traits\ProxyTrait; /** * @author Nicolas Grekas */ -class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, ResettableInterface +class TagAwareAdapter implements TagAwareAdapterInterface, TaggableCacheInterface, PruneableInterface, ResettableInterface { const TAGS_PREFIX = "\0tags\0"; use ProxyTrait; + use GetTrait; private $deferred = array(); private $createCacheItem; @@ -58,6 +61,7 @@ function ($key, $value, CacheItem $protoItem) { ); $this->setCacheItemTags = \Closure::bind( function (CacheItem $item, $key, array &$itemTags) { + $item->isTaggable = true; if (!$item->isHit) { return $item; } diff --git a/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php b/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php index 98d0e526933b9..a0df682d92b69 100644 --- a/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php @@ -12,6 +12,8 @@ namespace Symfony\Component\Cache\Adapter; use Psr\Cache\CacheItemInterface; +use Symfony\Component\Cache\CacheInterface; +use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\PruneableInterface; use Symfony\Component\Cache\ResettableInterface; @@ -22,7 +24,7 @@ * @author Tobias Nyholm * @author Nicolas Grekas */ -class TraceableAdapter implements AdapterInterface, PruneableInterface, ResettableInterface +class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface { protected $pool; private $calls = array(); @@ -32,6 +34,38 @@ public function __construct(AdapterInterface $pool) $this->pool = $pool; } + /** + * {@inheritdoc} + */ + public function get(string $key, callable $callback) + { + if (!$this->pool instanceof CacheInterface) { + throw new \BadMethodCallException(sprintf('Cannot call "%s::get()": this class doesn\'t implement "%s".', get_class($this->pool), CacheInterface::class)); + } + + $isHit = true; + $callback = function (CacheItem $item) use ($callback, &$isHit) { + $isHit = $item->isHit(); + + return $callback($item); + }; + + $event = $this->start(__FUNCTION__); + try { + $value = $this->pool->get($key, $callback); + $event->result[$key] = \is_object($value) ? \get_class($value) : gettype($value); + } finally { + $event->end = microtime(true); + } + if ($isHit) { + ++$event->hits; + } else { + ++$event->misses; + } + + return $value; + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Cache/Adapter/TraceableTagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/TraceableTagAwareAdapter.php index de68955d8e56d..2fda8b360240e 100644 --- a/src/Symfony/Component/Cache/Adapter/TraceableTagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/TraceableTagAwareAdapter.php @@ -11,10 +11,12 @@ namespace Symfony\Component\Cache\Adapter; +use Symfony\Component\Cache\TaggableCacheInterface; + /** * @author Robin Chalas */ -class TraceableTagAwareAdapter extends TraceableAdapter implements TagAwareAdapterInterface +class TraceableTagAwareAdapter extends TraceableAdapter implements TaggableCacheInterface, TagAwareAdapterInterface { public function __construct(TagAwareAdapterInterface $pool) { diff --git a/src/Symfony/Component/Cache/CHANGELOG.md b/src/Symfony/Component/Cache/CHANGELOG.md index 11c1b9364ebd5..d21b2cbda4df1 100644 --- a/src/Symfony/Component/Cache/CHANGELOG.md +++ b/src/Symfony/Component/Cache/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +4.2.0 +----- + + * added `CacheInterface` and `TaggableCacheInterface` + * throw `LogicException` when `CacheItem::tag()` is called on an item coming from a non tag-aware pool + 3.4.0 ----- diff --git a/src/Symfony/Component/Cache/CacheInterface.php b/src/Symfony/Component/Cache/CacheInterface.php new file mode 100644 index 0000000000000..c5c877ddbd746 --- /dev/null +++ b/src/Symfony/Component/Cache/CacheInterface.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache; + +use Psr\Cache\CacheItemInterface; + +/** + * Gets and stores items from a cache. + * + * On cache misses, a callback is called that should return the missing value. + * It is given two arguments: + * - the missing cache key + * - the corresponding PSR-6 CacheItemInterface object, + * allowing time-based expiration control. + * + * If you need tag-based invalidation, use TaggableCacheInterface instead. + * + * @author Nicolas Grekas + */ +interface CacheInterface +{ + /** + * @param callable(CacheItemInterface):mixed $callback Should return the computed value for the given key/item + * + * @return mixed The value corresponding to the provided key + */ + public function get(string $key, callable $callback); +} diff --git a/src/Symfony/Component/Cache/CacheItem.php b/src/Symfony/Component/Cache/CacheItem.php index cecaa126d9129..82ad9df68262c 100644 --- a/src/Symfony/Component/Cache/CacheItem.php +++ b/src/Symfony/Component/Cache/CacheItem.php @@ -14,6 +14,7 @@ use Psr\Cache\CacheItemInterface; use Psr\Log\LoggerInterface; use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Exception\LogicException; /** * @author Nicolas Grekas @@ -29,6 +30,7 @@ final class CacheItem implements CacheItemInterface protected $prevTags = array(); protected $innerItem; protected $poolHash; + protected $isTaggable = false; /** * {@inheritdoc} @@ -109,7 +111,10 @@ public function expiresAfter($time) */ public function tag($tags) { - if (!\is_array($tags)) { + if (!$this->isTaggable) { + throw new LogicException(sprintf('Cache item "%s" comes from a non tag-aware pool: you cannot tag it.', $this->key)); + } + if (!\is_iterable($tags)) { $tags = array($tags); } foreach ($tags as $tag) { diff --git a/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php b/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php index 91763e5a9f33b..5f29bfe5f2769 100644 --- a/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php +++ b/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php @@ -121,7 +121,15 @@ private function calculateStatistics(): array foreach ($calls as $call) { ++$statistics[$name]['calls']; $statistics[$name]['time'] += $call->end - $call->start; - if ('getItem' === $call->name) { + if ('get' === $call->name) { + ++$statistics[$name]['reads']; + if ($call->hits) { + ++$statistics[$name]['hits']; + } else { + ++$statistics[$name]['misses']; + ++$statistics[$name]['writes']; + } + } elseif ('getItem' === $call->name) { ++$statistics[$name]['reads']; if ($call->hits) { ++$statistics[$name]['hits']; diff --git a/src/Symfony/Component/Cache/Exception/LogicException.php b/src/Symfony/Component/Cache/Exception/LogicException.php new file mode 100644 index 0000000000000..042f73e6a5040 --- /dev/null +++ b/src/Symfony/Component/Cache/Exception/LogicException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Exception; + +use Psr\Cache\InvalidArgumentException as Psr6CacheInterface; +use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInterface; + +class LogicException extends \LogicException implements Psr6CacheInterface, SimpleCacheInterface +{ +} diff --git a/src/Symfony/Component/Cache/TaggableCacheInterface.php b/src/Symfony/Component/Cache/TaggableCacheInterface.php new file mode 100644 index 0000000000000..c112e72586d74 --- /dev/null +++ b/src/Symfony/Component/Cache/TaggableCacheInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache; + +/** + * Gets and stores items from a tag-aware cache. + * + * On cache misses, a callback is called that should return the missing value. + * It is given two arguments: + * - the missing cache key + * - the corresponding Symfony CacheItem object, + * allowing time-based *and* tags-based expiration control + * + * If you don't need tags-based invalidation, use CacheInterface instead. + * + * @author Nicolas Grekas + */ +interface TaggableCacheInterface extends CacheInterface +{ + /** + * @param callable(CacheItem):mixed $callback Should return the computed value for the given key/item + * + * @return mixed The value corresponding to the provided key + */ + public function get(string $key, callable $callback); +} diff --git a/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php b/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php index 018d149467482..3c96b731cc565 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php @@ -13,6 +13,7 @@ use Cache\IntegrationTests\CachePoolTest; use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\PruneableInterface; abstract class AdapterTestCase extends CachePoolTest @@ -26,6 +27,26 @@ protected function setUp() } } + public function testGet() + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $cache = $this->createCachePool(); + + $value = mt_rand(); + + $this->assertSame($value, $cache->get('foo', function (CacheItem $item) use ($value) { + $this->assertSame('foo', $item->getKey()); + + return $value; + })); + + $item = $cache->getItem('foo'); + $this->assertSame($value, $item->get()); + } + public function testDefaultLifeTime() { if (isset($this->skippedTests[__FUNCTION__])) { diff --git a/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterTest.php index 14b61263c5892..8630b52cf30c9 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterTest.php @@ -21,6 +21,7 @@ class PhpArrayAdapterTest extends AdapterTestCase { protected $skippedTests = array( + 'testGet' => 'PhpArrayAdapter is read-only.', 'testBasicUsage' => 'PhpArrayAdapter is read-only.', 'testBasicUsageWithLongKey' => 'PhpArrayAdapter is read-only.', 'testClear' => 'PhpArrayAdapter is read-only.', diff --git a/src/Symfony/Component/Cache/Tests/CacheItemTest.php b/src/Symfony/Component/Cache/Tests/CacheItemTest.php index daca925fd5b78..3a0ea098ad7c1 100644 --- a/src/Symfony/Component/Cache/Tests/CacheItemTest.php +++ b/src/Symfony/Component/Cache/Tests/CacheItemTest.php @@ -55,6 +55,9 @@ public function provideInvalidKey() public function testTag() { $item = new CacheItem(); + $r = new \ReflectionProperty($item, 'isTaggable'); + $r->setAccessible(true); + $r->setValue($item, true); $this->assertSame($item, $item->tag('foo')); $this->assertSame($item, $item->tag(array('bar', 'baz'))); @@ -72,6 +75,24 @@ public function testTag() public function testInvalidTag($tag) { $item = new CacheItem(); + $r = new \ReflectionProperty($item, 'isTaggable'); + $r->setAccessible(true); + $r->setValue($item, true); + $item->tag($tag); } + + /** + * @expectedException \Symfony\Component\Cache\Exception\LogicException + * @expectedExceptionMessage Cache item "foo" comes from a non tag-aware pool: you cannot tag it. + */ + public function testNonTaggableItem() + { + $item = new CacheItem(); + $r = new \ReflectionProperty($item, 'key'); + $r->setAccessible(true); + $r->setValue($item, 'foo'); + + $item->tag(array()); + } } diff --git a/src/Symfony/Component/Cache/Traits/GetTrait.php b/src/Symfony/Component/Cache/Traits/GetTrait.php new file mode 100644 index 0000000000000..d2a5f92da2e53 --- /dev/null +++ b/src/Symfony/Component/Cache/Traits/GetTrait.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Psr\Cache\CacheItemPoolInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +trait GetTrait +{ + /** + * {@inheritdoc} + */ + public function get(string $key, callable $callback) + { + return $this->doGet($this, $key, $callback); + } + + private function doGet(CacheItemPoolInterface $pool, string $key, callable $callback) + { + $item = $pool->getItem($key); + + if ($item->isHit()) { + return $item->get(); + } + + $pool->save($item->set($value = $callback($item))); + + return $value; + } +} From 9dbf39924781092ad6e6809ff4b4d70323a02cdc Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 18 May 2018 12:12:47 +0200 Subject: [PATCH 016/938] [Security/Core] Add "is_granted()" to security expressions, deprecate "has_role()" --- UPGRADE-4.2.md | 7 +++ .../DependencyInjection/Configuration.php | 2 +- .../Resources/config/security.xml | 1 + .../app/StandardFormLogin/config.yml | 2 +- .../Bundle/SecurityBundle/composer.json | 2 +- src/Symfony/Component/Security/CHANGELOG.md | 6 +++ .../ExpressionLanguageProvider.php | 10 ++++ .../Authorization/Voter/ExpressionVoter.php | 19 ++++++- .../Authorization/ExpressionLanguageTest.php | 50 ++++++++++++++++--- .../Voter/ExpressionVoterTest.php | 7 +-- 10 files changed, 91 insertions(+), 15 deletions(-) create mode 100644 UPGRADE-4.2.md diff --git a/UPGRADE-4.2.md b/UPGRADE-4.2.md new file mode 100644 index 0000000000000..23982ef159b36 --- /dev/null +++ b/UPGRADE-4.2.md @@ -0,0 +1,7 @@ +UPGRADE FROM 4.1 to 4.2 +======================= + +Security +-------- + + * Using the `has_role()` function in security expressions is deprecated, use the `is_granted()` function instead. diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index ce65f52bef128..9f0e07fd149d6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -370,7 +370,7 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode) ->scalarNode('guard') ->cannotBeEmpty() ->info('An expression to block the transition') - ->example('is_fully_authenticated() and has_role(\'ROLE_JOURNALIST\') and subject.getTitle() == \'My first article\'') + ->example('is_fully_authenticated() and is_granted(\'ROLE_JOURNALIST\') and subject.getTitle() == \'My first article\'') ->end() ->arrayNode('from') ->beforeNormalization() diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml index 9cbdd2061e119..65c48e0855cb7 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml @@ -119,6 +119,7 @@ + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/config.yml index 19b9d8952ec5e..d7c73aa0b6dc0 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/config.yml @@ -40,5 +40,5 @@ security: - { path: ^/secured-by-one-ip$, ip: 10.10.10.10, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/secured-by-two-ips$, ips: [1.1.1.1, 2.2.2.2], roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/highly_protected_resource$, roles: IS_ADMIN } - - { path: ^/protected-via-expression$, allow_if: "(is_anonymous() and request.headers.get('user-agent') matches '/Firefox/i') or has_role('ROLE_USER')" } + - { path: ^/protected-via-expression$, allow_if: "(is_anonymous() and request.headers.get('user-agent') matches '/Firefox/i') or is_granted('ROLE_USER')" } - { path: .*, roles: IS_AUTHENTICATED_FULLY } diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index 5dd550ec3b84d..e95f3430a94b7 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -18,7 +18,7 @@ "require": { "php": "^7.1.3", "ext-xml": "*", - "symfony/security": "~4.1", + "symfony/security": "~4.2", "symfony/dependency-injection": "^3.4.3|^4.0.3", "symfony/http-kernel": "^4.1" }, diff --git a/src/Symfony/Component/Security/CHANGELOG.md b/src/Symfony/Component/Security/CHANGELOG.md index ec33a7af97a4b..87939e3a26388 100644 --- a/src/Symfony/Component/Security/CHANGELOG.md +++ b/src/Symfony/Component/Security/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +4.2.0 +----- + +* added the `is_granted()` function in security expressions +* deprecated the `has_role()` function in security expressions, use `is_granted()` instead + 4.1.0 ----- diff --git a/src/Symfony/Component/Security/Core/Authorization/ExpressionLanguageProvider.php b/src/Symfony/Component/Security/Core/Authorization/ExpressionLanguageProvider.php index 9293ba7e39d4e..9e551115837cb 100644 --- a/src/Symfony/Component/Security/Core/Authorization/ExpressionLanguageProvider.php +++ b/src/Symfony/Component/Security/Core/Authorization/ExpressionLanguageProvider.php @@ -42,6 +42,12 @@ public function getFunctions() return $variables['trust_resolver']->isFullFledged($variables['token']); }), + new ExpressionFunction('is_granted', function ($attributes, $object = 'null') { + return sprintf('$auth_checker->isGranted(%s, %s)', $attributes, $object); + }, function (array $variables, $attributes, $object = null) { + return $variables['auth_checker']->isGranted($attributes, $object); + }), + new ExpressionFunction('is_remember_me', function () { return '$trust_resolver->isRememberMe($token)'; }, function (array $variables) { @@ -49,8 +55,12 @@ public function getFunctions() }), new ExpressionFunction('has_role', function ($role) { + @trigger_error('Using the "has_role()" function in security expressions is deprecated since Symfony 4.2, use "is_granted()" instead.', E_USER_DEPRECATED); + return sprintf('in_array(%s, $roles)', $role); }, function (array $variables, $role) { + @trigger_error('Using the "has_role()" function in security expressions is deprecated since Symfony 4.2, use "is_granted()" instead.', E_USER_DEPRECATED); + return in_array($role, $variables['roles']); }), ); diff --git a/src/Symfony/Component/Security/Core/Authorization/Voter/ExpressionVoter.php b/src/Symfony/Component/Security/Core/Authorization/Voter/ExpressionVoter.php index cbee938667789..726313c69d910 100644 --- a/src/Symfony/Component/Security/Core/Authorization/Voter/ExpressionVoter.php +++ b/src/Symfony/Component/Security/Core/Authorization/Voter/ExpressionVoter.php @@ -13,6 +13,7 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\Authorization\ExpressionLanguage; use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; @@ -28,12 +29,27 @@ class ExpressionVoter implements VoterInterface { private $expressionLanguage; private $trustResolver; + private $authChecker; private $roleHierarchy; - public function __construct(ExpressionLanguage $expressionLanguage, AuthenticationTrustResolverInterface $trustResolver, RoleHierarchyInterface $roleHierarchy = null) + /** + * @param AuthorizationCheckerInterface $authChecker + */ + public function __construct(ExpressionLanguage $expressionLanguage, AuthenticationTrustResolverInterface $trustResolver, $authChecker = null, RoleHierarchyInterface $roleHierarchy = null) { + if ($authChecker instanceof RoleHierarchyInterface) { + @trigger_error(sprintf('Passing a RoleHierarchyInterface to "%s()" is deprecated since Symfony 4.2. Pass an AuthorizationCheckerInterface instead.', __METHOD__), E_USER_DEPRECATED); + $roleHierarchy = $authChecker; + $authChecker = null; + } elseif (null === $authChecker) { + @trigger_error(sprintf('Argument 3 passed to "%s()" should be an instanceof AuthorizationCheckerInterface, not passing it is deprecated since Symfony 4.2.', __METHOD__), E_USER_DEPRECATED); + } elseif (!$authChecker instanceof AuthorizationCheckerInterface) { + throw new \InvalidArgumentException(sprintf('Argument 3 passed to %s() must be an instance of %s or null, %s given.', __METHOD__, AuthorizationCheckerInterface::class, is_object($authChecker) ? get_class($authChecker) : gettype($authChecker))); + } + $this->expressionLanguage = $expressionLanguage; $this->trustResolver = $trustResolver; + $this->authChecker = $authChecker; $this->roleHierarchy = $roleHierarchy; } @@ -87,6 +103,7 @@ private function getVariables(TokenInterface $token, $subject) 'subject' => $subject, 'roles' => array_map(function ($role) { return $role->getRole(); }, $roles), 'trust_resolver' => $this->trustResolver, + 'auth_checker' => $this->authChecker, ); // this is mainly to propose a better experience when the expression is used diff --git a/src/Symfony/Component/Security/Core/Tests/Authorization/ExpressionLanguageTest.php b/src/Symfony/Component/Security/Core/Tests/Authorization/ExpressionLanguageTest.php index 1565d1c865256..6c05ecfab506a 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authorization/ExpressionLanguageTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authorization/ExpressionLanguageTest.php @@ -12,10 +12,15 @@ namespace Symfony\Component\Security\Core\Tests\Authorization; use PHPUnit\Framework\TestCase; +use Symfony\Component\Security\Core\Authorization\AccessDecisionManager; +use Symfony\Component\Security\Core\Authorization\AuthorizationChecker; use Symfony\Component\Security\Core\Authorization\ExpressionLanguage; +use Symfony\Component\Security\Core\Authorization\Voter\RoleVoter; +use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver; use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\User\User; @@ -24,17 +29,21 @@ class ExpressionLanguageTest extends TestCase /** * @dataProvider provider */ - public function testIsAuthenticated($token, $expression, $result, array $roles = array()) + public function testIsAuthenticated($token, $expression, $result) { $anonymousTokenClass = 'Symfony\\Component\\Security\\Core\\Authentication\\Token\\AnonymousToken'; $rememberMeTokenClass = 'Symfony\\Component\\Security\\Core\\Authentication\\Token\\RememberMeToken'; $expressionLanguage = new ExpressionLanguage(); $trustResolver = new AuthenticationTrustResolver($anonymousTokenClass, $rememberMeTokenClass); + $tokenStorage = new TokenStorage(); + $tokenStorage->setToken($token); + $accessDecisionManager = new AccessDecisionManager(array(new RoleVoter())); + $authChecker = new AuthorizationChecker($tokenStorage, $this->getMockBuilder(AuthenticationManagerInterface::class)->getMock(), $accessDecisionManager); $context = array(); $context['trust_resolver'] = $trustResolver; + $context['auth_checker'] = $authChecker; $context['token'] = $token; - $context['roles'] = $roles; $this->assertEquals($result, $expressionLanguage->evaluate($expression, $context)); } @@ -54,27 +63,52 @@ public function provider() array($noToken, 'is_authenticated()', false), array($noToken, 'is_fully_authenticated()', false), array($noToken, 'is_remember_me()', false), - array($noToken, "has_role('ROLE_USER')", false), array($anonymousToken, 'is_anonymous()', true), array($anonymousToken, 'is_authenticated()', false), array($anonymousToken, 'is_fully_authenticated()', false), array($anonymousToken, 'is_remember_me()', false), - array($anonymousToken, "has_role('ROLE_USER')", false), + array($anonymousToken, "is_granted('ROLE_USER')", false), array($rememberMeToken, 'is_anonymous()', false), array($rememberMeToken, 'is_authenticated()', true), array($rememberMeToken, 'is_fully_authenticated()', false), array($rememberMeToken, 'is_remember_me()', true), - array($rememberMeToken, "has_role('ROLE_FOO')", false, $roles), - array($rememberMeToken, "has_role('ROLE_USER')", true, $roles), + array($rememberMeToken, "is_granted('ROLE_FOO')", false), + array($rememberMeToken, "is_granted('ROLE_USER')", true), array($usernamePasswordToken, 'is_anonymous()', false), array($usernamePasswordToken, 'is_authenticated()', true), array($usernamePasswordToken, 'is_fully_authenticated()', true), array($usernamePasswordToken, 'is_remember_me()', false), - array($usernamePasswordToken, "has_role('ROLE_FOO')", false, $roles), - array($usernamePasswordToken, "has_role('ROLE_USER')", true, $roles), + array($usernamePasswordToken, "is_granted('ROLE_FOO')", false), + array($usernamePasswordToken, "is_granted('ROLE_USER')", true), + ); + } + + /** + * @dataProvider provideLegacyHasRole + * @group legacy + */ + public function testLegacyHasRole($expression, $result, $roles = array()) + { + $expressionLanguage = new ExpressionLanguage(); + $context = array('roles' => $roles); + + $this->assertEquals($result, $expressionLanguage->evaluate($expression, $context)); + } + + public function provideLegacyHasRole() + { + $roles = array('ROLE_USER', 'ROLE_ADMIN'); + + return array( + array("has_role('ROLE_FOO')", false), + array("has_role('ROLE_USER')", false), + array("has_role('ROLE_ADMIN')", false), + array("has_role('ROLE_FOO')", false, $roles), + array("has_role('ROLE_USER')", true, $roles), + array("has_role('ROLE_ADMIN')", true, $roles), ); } } diff --git a/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/ExpressionVoterTest.php b/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/ExpressionVoterTest.php index 79626835264ab..9b7bf67709ce0 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/ExpressionVoterTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/ExpressionVoterTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Security\Core\Tests\Authorization\Voter; use PHPUnit\Framework\TestCase; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\Authorization\Voter\ExpressionVoter; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; use Symfony\Component\Security\Core\Role\Role; @@ -23,7 +24,7 @@ class ExpressionVoterTest extends TestCase */ public function testVote($roles, $attributes, $expected, $tokenExpectsGetRoles = true, $expressionLanguageExpectsEvaluate = true) { - $voter = new ExpressionVoter($this->createExpressionLanguage($expressionLanguageExpectsEvaluate), $this->createTrustResolver()); + $voter = new ExpressionVoter($this->createExpressionLanguage($expressionLanguageExpectsEvaluate), $this->createTrustResolver(), $this->createAuthorizationChecker()); $this->assertSame($expected, $voter->vote($this->getToken($roles, $tokenExpectsGetRoles), null, $attributes)); } @@ -75,9 +76,9 @@ protected function createTrustResolver() return $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface')->getMock(); } - protected function createRoleHierarchy() + protected function createAuthorizationChecker() { - return $this->getMockBuilder('Symfony\Component\Security\Core\Role\RoleHierarchyInterface')->getMock(); + return $this->getMockBuilder(AuthorizationCheckerInterface::class)->getMock(); } protected function createExpression() From 5551e0c09105a303d7e2822e3f206c2214589ab9 Mon Sep 17 00:00:00 2001 From: kiler129 Date: Sat, 19 May 2018 16:51:12 -0500 Subject: [PATCH 017/938] feature #26824 add exception chain breadcrumbs navigation --- .../TwigBundle/Resources/views/Exception/exception.html.twig | 4 ++-- .../TwigBundle/Resources/views/Exception/traces.html.twig | 2 +- .../Bundle/TwigBundle/Resources/views/exception.css.twig | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.html.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.html.twig index 2f29571ae3278..00b1988ec3e67 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.html.twig +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.html.twig @@ -3,10 +3,10 @@

{% for previousException in exception.allPrevious|reverse %} - {{ previousException.class|abbr_class }} + {{ previousException.class|abbr_class }} {{ include('@Twig/images/chevron-right.svg') }} {% endfor %} - {{ exception.class|abbr_class }} + {{ exception.class|abbr_class }}

HTTP {{ status_code }} {{ status_text }} diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/traces.html.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/traces.html.twig index 29952e2cb75b5..01f3796079c0d 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/traces.html.twig +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/traces.html.twig @@ -1,4 +1,4 @@ -
+
diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/exception.css.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/exception.css.twig index 8cd6e6d07c0c6..e252281975e31 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/exception.css.twig +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/exception.css.twig @@ -73,7 +73,7 @@ header .container { display: flex; justify-content: space-between; } .exception-summary { background: #B0413E; border-bottom: 2px solid rgba(0, 0, 0, 0.1); border-top: 1px solid rgba(0, 0, 0, .3); flex: 0 0 auto; margin-bottom: 15px; } .exception-metadata { background: rgba(0, 0, 0, 0.1); padding: 7px 0; } .exception-metadata .container { display: flex; flex-direction: row; justify-content: space-between; } -.exception-metadata h2 { color: rgba(255, 255, 255, 0.8); font-size: 13px; font-weight: 400; margin: 0; } +.exception-metadata h2, .exception-metadata h2 > a { color: rgba(255, 255, 255, 0.8); font-size: 13px; font-weight: 400; margin: 0; } .exception-http small { font-size: 13px; opacity: .7; } .exception-hierarchy { flex: 1; } .exception-hierarchy .icon { margin: 0 3px; opacity: .7; } From 6e43838c5ddca05755f77869c46b86c95d65c27e Mon Sep 17 00:00:00 2001 From: Maxime Steinhausser Date: Wed, 16 May 2018 10:25:36 +0200 Subject: [PATCH 018/938] [Messenger] Activation middleware decorator --- src/Symfony/Component/Messenger/Envelope.php | 11 +++ .../ActivationMiddlewareDecorator.php | 48 ++++++++++ .../ActivationMiddlewareDecoratorTest.php | 92 +++++++++++++++++++ 3 files changed, 151 insertions(+) create mode 100644 src/Symfony/Component/Messenger/Middleware/Enhancers/ActivationMiddlewareDecorator.php create mode 100644 src/Symfony/Component/Messenger/Tests/Middleware/Enhancers/ActivationMiddlewareDecoratorTest.php diff --git a/src/Symfony/Component/Messenger/Envelope.php b/src/Symfony/Component/Messenger/Envelope.php index 4d5f7a02a9d5d..6ac9bc7f3c6d1 100644 --- a/src/Symfony/Component/Messenger/Envelope.php +++ b/src/Symfony/Component/Messenger/Envelope.php @@ -86,4 +86,15 @@ public function getMessage() { return $this->message; } + + /** + * @param object $target + * + * @return Envelope|object The original message or the envelope if the target supports it + * (i.e implements {@link EnvelopeAwareInterface}). + */ + public function getMessageFor($target) + { + return $target instanceof EnvelopeAwareInterface ? $this : $this->message; + } } diff --git a/src/Symfony/Component/Messenger/Middleware/Enhancers/ActivationMiddlewareDecorator.php b/src/Symfony/Component/Messenger/Middleware/Enhancers/ActivationMiddlewareDecorator.php new file mode 100644 index 0000000000000..1c812ccc12153 --- /dev/null +++ b/src/Symfony/Component/Messenger/Middleware/Enhancers/ActivationMiddlewareDecorator.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Messenger\Middleware\Enhancers; + +use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\EnvelopeAwareInterface; +use Symfony\Component\Messenger\Middleware\MiddlewareInterface; + +/** + * Execute the inner middleware according to an activation strategy. + * + * @author Maxime Steinhausser + */ +class ActivationMiddlewareDecorator implements MiddlewareInterface, EnvelopeAwareInterface +{ + private $inner; + private $activated; + + /** + * @param bool|callable $activated + */ + public function __construct(MiddlewareInterface $inner, $activated) + { + $this->inner = $inner; + $this->activated = $activated; + } + + /** + * @param Envelope $message + */ + public function handle($message, callable $next) + { + if (\is_callable($this->activated) ? ($this->activated)($message) : $this->activated) { + return $this->inner->handle($message->getMessageFor($this->inner), $next); + } + + return $next($message); + } +} diff --git a/src/Symfony/Component/Messenger/Tests/Middleware/Enhancers/ActivationMiddlewareDecoratorTest.php b/src/Symfony/Component/Messenger/Tests/Middleware/Enhancers/ActivationMiddlewareDecoratorTest.php new file mode 100644 index 0000000000000..32b4a7d0f2fdf --- /dev/null +++ b/src/Symfony/Component/Messenger/Tests/Middleware/Enhancers/ActivationMiddlewareDecoratorTest.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Messenger\Tests\Middleware\Enhancers; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\EnvelopeAwareInterface; +use Symfony\Component\Messenger\Middleware\Enhancers\ActivationMiddlewareDecorator; +use Symfony\Component\Messenger\Middleware\MiddlewareInterface; +use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; + +/** + * @author Maxime Steinhausser + */ +class ActivationMiddlewareDecoratorTest extends TestCase +{ + public function testExecuteMiddlewareOnActivated() + { + $message = new DummyMessage('Hello'); + $envelope = Envelope::wrap($message); + + $next = $this->createPartialMock(\stdClass::class, array('__invoke')); + $next->expects($this->never())->method('__invoke'); + + $middleware = $this->createMock(MiddlewareInterface::class); + $middleware->expects($this->once())->method('handle')->with($message, $next)->willReturn('Hello from middleware'); + + $decorator = new ActivationMiddlewareDecorator($middleware, true); + + $this->assertSame('Hello from middleware', $decorator->handle($envelope, $next)); + } + + public function testExecuteMiddlewareOnActivatedWithCallable() + { + $message = new DummyMessage('Hello'); + $envelope = Envelope::wrap($message); + + $activated = $this->createPartialMock(\stdClass::class, array('__invoke')); + $activated->expects($this->once())->method('__invoke')->with($envelope)->willReturn(true); + + $next = $this->createPartialMock(\stdClass::class, array('__invoke')); + $next->expects($this->never())->method('__invoke'); + + $middleware = $this->createMock(MiddlewareInterface::class); + $middleware->expects($this->once())->method('handle')->with($message, $next)->willReturn('Hello from middleware'); + + $decorator = new ActivationMiddlewareDecorator($middleware, $activated); + + $this->assertSame('Hello from middleware', $decorator->handle($envelope, $next)); + } + + public function testExecuteEnvelopeAwareMiddlewareWithEnvelope() + { + $message = new DummyMessage('Hello'); + $envelope = Envelope::wrap($message); + + $next = $this->createPartialMock(\stdClass::class, array('__invoke')); + $next->expects($this->never())->method('__invoke'); + + $middleware = $this->createMock(array(MiddlewareInterface::class, EnvelopeAwareInterface::class)); + $middleware->expects($this->once())->method('handle')->with($envelope, $next)->willReturn('Hello from middleware'); + + $decorator = new ActivationMiddlewareDecorator($middleware, true); + + $this->assertSame('Hello from middleware', $decorator->handle($envelope, $next)); + } + + public function testExecuteMiddlewareOnDeactivated() + { + $message = new DummyMessage('Hello'); + $envelope = Envelope::wrap($message); + + $next = $this->createPartialMock(\stdClass::class, array('__invoke')); + $next->expects($this->once())->method('__invoke')->with($envelope)->willReturn('Hello from $next'); + + $middleware = $this->createMock(MiddlewareInterface::class); + $middleware->expects($this->never())->method('handle'); + + $decorator = new ActivationMiddlewareDecorator($middleware, false); + + $this->assertSame('Hello from $next', $decorator->handle($envelope, $next)); + } +} From 42186a2bac29f073e2dd70abddd565a59794f020 Mon Sep 17 00:00:00 2001 From: Bob van de Vijver Date: Fri, 4 May 2018 18:10:27 +0200 Subject: [PATCH 019/938] [DI] Select specific key from an array resolved env var --- .../DependencyInjection/EnvVarProcessor.php | 20 ++++ .../RegisterEnvVarProcessorsPassTest.php | 1 + .../Tests/EnvVarProcessorTest.php | 106 ++++++++++++++++++ 3 files changed, 127 insertions(+) diff --git a/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php b/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php index 43022ddb7b9d3..e91df9670b5a1 100644 --- a/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php +++ b/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php @@ -41,6 +41,7 @@ public static function getProvidedTypes() 'float' => 'float', 'int' => 'int', 'json' => 'array', + 'key' => 'bool|int|float|string|array', 'resolve' => 'string', 'string' => 'string', ); @@ -53,6 +54,25 @@ public function getEnv($prefix, $name, \Closure $getEnv) { $i = strpos($name, ':'); + if ('key' === $prefix) { + if (false === $i) { + throw new RuntimeException(sprintf('Invalid configuration: env var "key:%s" does not contain a key specifier.', $name)); + } + + $next = substr($name, $i + 1); + $key = substr($name, 0, $i); + $array = $getEnv($next); + + if (!is_array($array)) { + throw new RuntimeException(sprintf('Resolved value of "%s" did not result in an array value.', $next)); + } + if (!array_key_exists($key, $array)) { + throw new RuntimeException(sprintf('Key "%s" not found in "%s" (resolved from "%s")', $key, json_encode($array), $next)); + } + + return $array[$key]; + } + if ('file' === $prefix) { if (!is_scalar($file = $getEnv($name))) { throw new RuntimeException(sprintf('Invalid file name: env var "%s" is non-scalar.', $name)); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterEnvVarProcessorsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterEnvVarProcessorsPassTest.php index e330017bcd8e8..4681092ca7849 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterEnvVarProcessorsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterEnvVarProcessorsPassTest.php @@ -38,6 +38,7 @@ public function testSimpleProcessor() 'float' => array('float'), 'int' => array('int'), 'json' => array('array'), + 'key' => array('bool', 'int', 'float', 'string', 'array'), 'resolve' => array('string'), 'string' => array('string'), ); diff --git a/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php b/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php index 79b3e47c79de9..5cd3a68b21baa 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php @@ -314,4 +314,110 @@ public function testGetEnvUnknown() return 'foo'; }); } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException + * @expectedExceptionMessage Invalid configuration: env var "key:foo" does not contain a key specifier. + */ + public function testGetEnvKeyInvalidKey() + { + $processor = new EnvVarProcessor(new Container()); + + $processor->getEnv('key', 'foo', function ($name) { + $this->fail('Should not get here'); + }); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException + * @expectedExceptionMessage Resolved value of "foo" did not result in an array value. + * @dataProvider noArrayValues + */ + public function testGetEnvKeyNoArrayResult($value) + { + $processor = new EnvVarProcessor(new Container()); + + $processor->getEnv('key', 'index:foo', function ($name) use ($value) { + $this->assertSame('foo', $name); + + return $value; + }); + } + + public function noArrayValues() + { + return array( + array(null), + array('string'), + array(1), + array(true), + ); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException + * @expectedExceptionMessage Key "index" not found in + * @dataProvider invalidArrayValues + */ + public function testGetEnvKeyArrayKeyNotFound($value) + { + $processor = new EnvVarProcessor(new Container()); + + $processor->getEnv('key', 'index:foo', function ($name) use ($value) { + $this->assertSame('foo', $name); + + return $value; + }); + } + + public function invalidArrayValues() + { + return array( + array(array()), + array(array('index2' => 'value')), + array(array('index', 'index2')), + ); + } + + /** + * @dataProvider arrayValues + */ + public function testGetEnvKey($value) + { + $processor = new EnvVarProcessor(new Container()); + + $this->assertSame($value['index'], $processor->getEnv('key', 'index:foo', function ($name) use ($value) { + $this->assertSame('foo', $name); + + return $value; + })); + } + + public function arrayValues() + { + return array( + array(array('index' => 'password')), + array(array('index' => 'true')), + array(array('index' => false)), + array(array('index' => '1')), + array(array('index' => 1)), + array(array('index' => '1.1')), + array(array('index' => 1.1)), + array(array('index' => array())), + array(array('index' => array('val1', 'val2'))), + ); + } + + public function testGetEnvKeyChained() + { + $processor = new EnvVarProcessor(new Container()); + + $this->assertSame('password', $processor->getEnv('key', 'index:file:foo', function ($name) { + $this->assertSame('file:foo', $name); + + return array( + 'index' => 'password', + ); + })); + } } From 57a1dd1c57170f610c8396001c24f18c03f45f81 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 21 May 2018 20:43:31 +0200 Subject: [PATCH 020/938] fix deps=low --- src/Symfony/Component/HttpKernel/composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/composer.json b/src/Symfony/Component/HttpKernel/composer.json index 816af1222faf5..a66f70c80f2bf 100644 --- a/src/Symfony/Component/HttpKernel/composer.json +++ b/src/Symfony/Component/HttpKernel/composer.json @@ -28,7 +28,7 @@ "symfony/config": "~3.4|~4.0", "symfony/console": "~3.4|~4.0", "symfony/css-selector": "~3.4|~4.0", - "symfony/dependency-injection": "^4.1", + "symfony/dependency-injection": "^4.2", "symfony/dom-crawler": "~3.4|~4.0", "symfony/expression-language": "~3.4|~4.0", "symfony/finder": "~3.4|~4.0", @@ -45,7 +45,7 @@ }, "conflict": { "symfony/config": "<3.4", - "symfony/dependency-injection": "<4.1", + "symfony/dependency-injection": "<4.2", "symfony/var-dumper": "<4.1", "twig/twig": "<1.34|<2.4,>=2" }, From a71ba784782b87317694add148f8e7434767c6f7 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Thu, 17 May 2018 18:15:49 +0200 Subject: [PATCH 021/938] [Security][SecurityBundle] FirewallMap/FirewallContext deprecations --- UPGRADE-4.2.md | 8 ++++ UPGRADE-5.0.md | 4 ++ .../Security/FirewallContext.php | 1 + .../SecurityDataCollectorTest.php | 2 +- .../Debug/TraceableFirewallListenerTest.php | 2 +- .../Tests/Security/FirewallContextTest.php | 9 +++++ .../Component/Security/Http/Firewall.php | 8 +++- .../Security/Http/FirewallMapInterface.php | 5 ++- .../Security/Http/Tests/FirewallTest.php | 37 ++++++++++++++++++- 9 files changed, 70 insertions(+), 6 deletions(-) diff --git a/UPGRADE-4.2.md b/UPGRADE-4.2.md index 23982ef159b36..2bac6f514b7ab 100644 --- a/UPGRADE-4.2.md +++ b/UPGRADE-4.2.md @@ -5,3 +5,11 @@ Security -------- * Using the `has_role()` function in security expressions is deprecated, use the `is_granted()` function instead. + * Not returning an array of 3 elements from `FirewallMapInterface::getListeners()` is deprecated, the 3rd element + must be an instance of `LogoutListener` or `null`. + +SecurityBundle +-------------- + + * Passing a `FirewallConfig` instance as 3rd argument to the `FirewallContext` constructor is deprecated, + pass a `LogoutListener` instance instead. diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md index 61b7237b44923..48be0e094a65b 100644 --- a/UPGRADE-5.0.md +++ b/UPGRADE-5.0.md @@ -78,6 +78,8 @@ Security * The `ContextListener::setLogoutOnUserChange()` method has been removed. * The `Symfony\Component\Security\Core\User\AdvancedUserInterface` has been removed. * The `ExpressionVoter::addExpressionLanguageProvider()` method has been removed. + * The `FirewallMapInterface::getListeners()` method must return an array of 3 elements, + the 3rd one must be either a `LogoutListener` instance or `null`. SecurityBundle -------------- @@ -85,6 +87,8 @@ SecurityBundle * The `logout_on_user_change` firewall option has been removed. * The `switch_user.stateless` firewall option has been removed. * The `SecurityUserValueResolver` class has been removed. + * Passing a `FirewallConfig` instance as 3rd argument to the `FirewallContext` constructor + now throws a `\TypeError`, pass a `LogoutListener` instance instead. Translation ----------- diff --git a/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php b/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php index a3b7f15406919..ac0d1f2406841 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php +++ b/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php @@ -36,6 +36,7 @@ public function __construct(iterable $listeners, ExceptionListener $exceptionLis $this->exceptionListener = $exceptionListener; if ($logoutListener instanceof FirewallConfig) { $this->config = $logoutListener; + @trigger_error(sprintf('Passing an instance of %s as 3rd argument to %s() is deprecated since Symfony 4.2. Pass a %s instance instead.', FirewallConfig::class, __METHOD__, LogoutListener::class), E_USER_DEPRECATED); } elseif (null === $logoutListener || $logoutListener instanceof LogoutListener) { $this->logoutListener = $logoutListener; $this->config = $config; diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php index 548b1939fc251..0ceacd22468b3 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php @@ -208,7 +208,7 @@ public function testGetListeners() ->expects($this->once()) ->method('getListeners') ->with($request) - ->willReturn(array(array($listener), null)); + ->willReturn(array(array($listener), null, null)); $firewall = new TraceableFirewallListener($firewallMap, new EventDispatcher(), new LogoutUrlGenerator()); $firewall->onKernelRequest($event); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Debug/TraceableFirewallListenerTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Debug/TraceableFirewallListenerTest.php index 287ba531f4031..829d1f11e67d2 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Debug/TraceableFirewallListenerTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Debug/TraceableFirewallListenerTest.php @@ -51,7 +51,7 @@ public function testOnKernelRequestRecordsListeners() ->expects($this->once()) ->method('getListeners') ->with($request) - ->willReturn(array(array($listener), null)); + ->willReturn(array(array($listener), null, null)); $firewall = new TraceableFirewallListener($firewallMap, new EventDispatcher(), new LogoutUrlGenerator()); $firewall->onKernelRequest($event); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallContextTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallContextTest.php index 520a129716f4f..129c72fab9107 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallContextTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallContextTest.php @@ -40,6 +40,15 @@ public function testGetters() $this->assertEquals($config, $context->getConfig()); } + /** + * @group legacy + * @expectedDeprecation Passing an instance of Symfony\Bundle\SecurityBundle\Security\FirewallConfig as 3rd argument to Symfony\Bundle\SecurityBundle\Security\FirewallContext::__construct() is deprecated since Symfony 4.2. Pass a Symfony\Component\Security\Http\Firewall\LogoutListener instance instead. + */ + public function testFirewallConfigAs3rdConstructorArgument() + { + new FirewallContext(array(), $this->getExceptionListenerMock(), new FirewallConfig('main', 'user_checker', 'request_matcher')); + } + private function getExceptionListenerMock() { return $this diff --git a/src/Symfony/Component/Security/Http/Firewall.php b/src/Symfony/Component/Security/Http/Firewall.php index 9bee596759bd8..b533f547b643b 100644 --- a/src/Symfony/Component/Security/Http/Firewall.php +++ b/src/Symfony/Component/Security/Http/Firewall.php @@ -16,6 +16,7 @@ use Symfony\Component\HttpKernel\Event\FinishRequestEvent; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Security\Http\Firewall\LogoutListener; /** * Firewall uses a FirewallMap to register security listeners for the given @@ -49,9 +50,14 @@ public function onKernelRequest(GetResponseEvent $event) // register listeners for this firewall $listeners = $this->map->getListeners($event->getRequest()); + if (3 !== \count($listeners)) { + @trigger_error(sprintf('Not returning an array of 3 elements from %s::getListeners() is deprecated since Symfony 4.2, the 3rd element must be an instance of %s or null.', FirewallMapInterface::class, LogoutListener::class), E_USER_DEPRECATED); + $listeners[2] = null; + } + $authenticationListeners = $listeners[0]; $exceptionListener = $listeners[1]; - $logoutListener = isset($listeners[2]) ? $listeners[2] : null; + $logoutListener = $listeners[2]; if (null !== $exceptionListener) { $this->exceptionListeners[$event->getRequest()] = $exceptionListener; diff --git a/src/Symfony/Component/Security/Http/FirewallMapInterface.php b/src/Symfony/Component/Security/Http/FirewallMapInterface.php index ebdd498123a1c..be7a78ccdb883 100644 --- a/src/Symfony/Component/Security/Http/FirewallMapInterface.php +++ b/src/Symfony/Component/Security/Http/FirewallMapInterface.php @@ -30,7 +30,10 @@ interface FirewallMapInterface * If there is no exception listener, the second element of the outer array * must be null. * - * @return array of the format array(array(AuthenticationListener), ExceptionListener) + * If there is no logout listener, the third element of the outer array + * must be null. + * + * @return array of the format array(array(AuthenticationListener), ExceptionListener, LogoutListener) */ public function getListeners(Request $request); } diff --git a/src/Symfony/Component/Security/Http/Tests/FirewallTest.php b/src/Symfony/Component/Security/Http/Tests/FirewallTest.php index bd475bb4e5b1f..acc231dc93581 100644 --- a/src/Symfony/Component/Security/Http/Tests/FirewallTest.php +++ b/src/Symfony/Component/Security/Http/Tests/FirewallTest.php @@ -12,10 +12,14 @@ namespace Symfony\Component\Security\Http\Tests; use PHPUnit\Framework\TestCase; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Security\Http\Firewall; +use Symfony\Component\Security\Http\Firewall\ExceptionListener; +use Symfony\Component\Security\Http\FirewallMapInterface; class FirewallTest extends TestCase { @@ -37,7 +41,7 @@ public function testOnKernelRequestRegistersExceptionListener() ->expects($this->once()) ->method('getListeners') ->with($this->equalTo($request)) - ->will($this->returnValue(array(array(), $listener))) + ->will($this->returnValue(array(array(), $listener, null))) ; $event = new GetResponseEvent($this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(), $request, HttpKernelInterface::MASTER_REQUEST); @@ -66,7 +70,7 @@ public function testOnKernelRequestStopsWhenThereIsAResponse() $map ->expects($this->once()) ->method('getListeners') - ->will($this->returnValue(array(array($first, $second), null))) + ->will($this->returnValue(array(array($first, $second), null, null))) ; $event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent') @@ -107,4 +111,33 @@ public function testOnKernelRequestWithSubRequest() $this->assertFalse($event->hasResponse()); } + + /** + * @group legacy + * @expectedDeprecation Not returning an array of 3 elements from Symfony\Component\Security\Http\FirewallMapInterface::getListeners() is deprecated since Symfony 4.2, the 3rd element must be an instance of Symfony\Component\Security\Http\Firewall\LogoutListener or null. + */ + public function testMissingLogoutListener() + { + $dispatcher = $this->getMockBuilder(EventDispatcherInterface::class)->getMock(); + + $listener = $this->getMockBuilder(ExceptionListener::class)->disableOriginalConstructor()->getMock(); + $listener + ->expects($this->once()) + ->method('register') + ->with($this->equalTo($dispatcher)) + ; + + $request = new Request(); + + $map = $this->getMockBuilder(FirewallMapInterface::class)->getMock(); + $map + ->expects($this->once()) + ->method('getListeners') + ->with($this->equalTo($request)) + ->willReturn(array(array(), $listener)) + ; + + $firewall = new Firewall($map, $dispatcher); + $firewall->onKernelRequest(new GetResponseEvent($this->getMockBuilder(HttpKernelInterface::class)->getMock(), $request, HttpKernelInterface::MASTER_REQUEST)); + } } From e36099503fa9e3fb31506e0e2c618b057621f75a Mon Sep 17 00:00:00 2001 From: Sergey Rabochiy Date: Tue, 15 May 2018 18:05:47 +0700 Subject: [PATCH 022/938] [FrameworkBundle] Change priority of AddConsoleCommandPass to TYPE_BEFORE_REMOVING --- .../FrameworkBundle/FrameworkBundle.php | 2 +- .../AddConsoleCommandPassTest.php | 83 ++++++++++++++++++- 2 files changed, 80 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index 17c12686dad23..6a05b436df143 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -107,7 +107,7 @@ public function build(ContainerBuilder $container) $this->addCompilerPassIfExists($container, AddConstraintValidatorsPass::class, PassConfig::TYPE_BEFORE_REMOVING); $container->addCompilerPass(new AddAnnotationsCachedReaderPass(), PassConfig::TYPE_AFTER_REMOVING, -255); $this->addCompilerPassIfExists($container, AddValidatorInitializersPass::class); - $this->addCompilerPassIfExists($container, AddConsoleCommandPass::class); + $this->addCompilerPassIfExists($container, AddConsoleCommandPass::class, PassConfig::TYPE_BEFORE_REMOVING); if (class_exists(TranslatorPass::class)) { // Arguments to be removed in 4.0, relying on the default values $container->addCompilerPass(new TranslatorPass('translator.default', 'translation.loader')); diff --git a/src/Symfony/Component/Console/Tests/DependencyInjection/AddConsoleCommandPassTest.php b/src/Symfony/Component/Console/Tests/DependencyInjection/AddConsoleCommandPassTest.php index 34f648610836a..67fbb98643f1e 100644 --- a/src/Symfony/Component/Console/Tests/DependencyInjection/AddConsoleCommandPassTest.php +++ b/src/Symfony/Component/Console/Tests/DependencyInjection/AddConsoleCommandPassTest.php @@ -12,10 +12,12 @@ namespace Symfony\Component\Console\Tests\DependencyInjection; use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\CommandLoader\ContainerCommandLoader; use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass; -use Symfony\Component\Console\Command\Command; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\TypedReference; @@ -28,7 +30,7 @@ class AddConsoleCommandPassTest extends TestCase public function testProcess($public) { $container = new ContainerBuilder(); - $container->addCompilerPass(new AddConsoleCommandPass()); + $container->addCompilerPass(new AddConsoleCommandPass(), PassConfig::TYPE_BEFORE_REMOVING); $container->setParameter('my-command.class', 'Symfony\Component\Console\Tests\DependencyInjection\MyCommand'); $definition = new Definition('%my-command.class%'); @@ -127,7 +129,7 @@ public function testProcessThrowAnExceptionIfTheServiceIsAbstract() { $container = new ContainerBuilder(); $container->setResourceTracking(false); - $container->addCompilerPass(new AddConsoleCommandPass()); + $container->addCompilerPass(new AddConsoleCommandPass(), PassConfig::TYPE_BEFORE_REMOVING); $definition = new Definition('Symfony\Component\Console\Tests\DependencyInjection\MyCommand'); $definition->addTag('console.command'); @@ -145,7 +147,7 @@ public function testProcessThrowAnExceptionIfTheServiceIsNotASubclassOfCommand() { $container = new ContainerBuilder(); $container->setResourceTracking(false); - $container->addCompilerPass(new AddConsoleCommandPass()); + $container->addCompilerPass(new AddConsoleCommandPass(), PassConfig::TYPE_BEFORE_REMOVING); $definition = new Definition('SplObjectStorage'); $definition->addTag('console.command'); @@ -175,6 +177,79 @@ public function testProcessPrivateServicesWithSameCommand() $this->assertTrue($container->hasAlias($alias1)); $this->assertTrue($container->hasAlias($alias2)); } + + public function testProcessOnChildDefinitionWithClass() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new AddConsoleCommandPass(), PassConfig::TYPE_BEFORE_REMOVING); + $className = 'Symfony\Component\Console\Tests\DependencyInjection\MyCommand'; + + $parentId = 'my-parent-command'; + $childId = 'my-child-command'; + + $parentDefinition = new Definition(/* no class */); + $parentDefinition->setAbstract(true)->setPublic(false); + + $childDefinition = new ChildDefinition($parentId); + $childDefinition->addTag('console.command')->setPublic(true); + $childDefinition->setClass($className); + + $container->setDefinition($parentId, $parentDefinition); + $container->setDefinition($childId, $childDefinition); + + $container->compile(); + $command = $container->get($childId); + + $this->assertInstanceOf($className, $command); + } + + public function testProcessOnChildDefinitionWithParentClass() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new AddConsoleCommandPass(), PassConfig::TYPE_BEFORE_REMOVING); + $className = 'Symfony\Component\Console\Tests\DependencyInjection\MyCommand'; + + $parentId = 'my-parent-command'; + $childId = 'my-child-command'; + + $parentDefinition = new Definition($className); + $parentDefinition->setAbstract(true)->setPublic(false); + + $childDefinition = new ChildDefinition($parentId); + $childDefinition->addTag('console.command')->setPublic(true); + + $container->setDefinition($parentId, $parentDefinition); + $container->setDefinition($childId, $childDefinition); + + $container->compile(); + $command = $container->get($childId); + + $this->assertInstanceOf($className, $command); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage The definition for "my-child-command" has no class. + */ + public function testProcessOnChildDefinitionWithoutClass() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new AddConsoleCommandPass(), PassConfig::TYPE_BEFORE_REMOVING); + + $parentId = 'my-parent-command'; + $childId = 'my-child-command'; + + $parentDefinition = new Definition(); + $parentDefinition->setAbstract(true)->setPublic(false); + + $childDefinition = new ChildDefinition($parentId); + $childDefinition->addTag('console.command')->setPublic(true); + + $container->setDefinition($parentId, $parentDefinition); + $container->setDefinition($childId, $childDefinition); + + $container->compile(); + } } class MyCommand extends Command From ca314889e7fb7a8b5c1f58ca489b9a22b7e71293 Mon Sep 17 00:00:00 2001 From: Maxime Steinhausser Date: Sat, 26 May 2018 11:34:32 +0200 Subject: [PATCH 023/938] [Serializer] Fix serializer tries to denormalize null values on nullable properties --- .../Normalizer/AbstractNormalizer.php | 6 ++++ .../NullableConstructorArgumentDummy.php | 32 +++++++++++++++++++ .../Normalizer/AbstractNormalizerTest.php | 12 +++++++ 3 files changed, 50 insertions(+) create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/NullableConstructorArgumentDummy.php diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php index 74a133214f55b..cad6205dfb826 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php @@ -349,6 +349,12 @@ protected function instantiateObject(array &$data, $class, array &$context, \Ref } } elseif ($allowed && !$ignored && (isset($data[$key]) || array_key_exists($key, $data))) { $parameterData = $data[$key]; + if (null === $parameterData && $constructorParameter->allowsNull()) { + $params[] = null; + // Don't run set for a parameter passed to the constructor + unset($data[$key]); + continue; + } try { if (null !== $constructorParameter->getClass()) { if (!$this->serializer instanceof DenormalizerInterface) { diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/NullableConstructorArgumentDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/NullableConstructorArgumentDummy.php new file mode 100644 index 0000000000000..616fab4ae8df0 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/NullableConstructorArgumentDummy.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Fixtures; + +class NullableConstructorArgumentDummy +{ + private $foo; + + public function __construct(?\stdClass $foo) + { + $this->foo = $foo; + } + + public function setFoo($foo) + { + $this->foo = 'this setter should not be called when using the constructor argument'; + } + + public function getFoo() + { + return $this->foo; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractNormalizerTest.php index e07fb56bf5b7f..28edc05872710 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractNormalizerTest.php @@ -9,6 +9,7 @@ use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Tests\Fixtures\AbstractNormalizerDummy; +use Symfony\Component\Serializer\Tests\Fixtures\NullableConstructorArgumentDummy; use Symfony\Component\Serializer\Tests\Fixtures\ProxyDummy; use Symfony\Component\Serializer\Tests\Fixtures\StaticConstructorDummy; use Symfony\Component\Serializer\Tests\Fixtures\StaticConstructorNormalizer; @@ -116,4 +117,15 @@ public function testObjectWithStaticConstructor() $this->assertEquals('baz', $dummy->quz); $this->assertNull($dummy->foo); } + + /** + * @requires PHP 7.1 + */ + public function testObjectWithNullableConstructorArgument() + { + $normalizer = new ObjectNormalizer(); + $dummy = $normalizer->denormalize(array('foo' => null), NullableConstructorArgumentDummy::class); + + $this->assertNull($dummy->getFoo()); + } } From e3412e6a6732093920b08e8b2b612d8c21ea979c Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Thu, 17 May 2018 09:59:56 -0400 Subject: [PATCH 024/938] Triggering RememberMe's loginFail() when token cannot be created --- .../Http/Firewall/RememberMeListener.php | 20 +++++++++- .../Tests/Firewall/RememberMeListenerTest.php | 37 +++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Security/Http/Firewall/RememberMeListener.php b/src/Symfony/Component/Security/Http/Firewall/RememberMeListener.php index fe670f3de1f38..d139e7a5facff 100644 --- a/src/Symfony/Component/Security/Http/Firewall/RememberMeListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/RememberMeListener.php @@ -68,7 +68,25 @@ public function handle(GetResponseEvent $event) } $request = $event->getRequest(); - if (null === $token = $this->rememberMeServices->autoLogin($request)) { + try { + if (null === $token = $this->rememberMeServices->autoLogin($request)) { + return; + } + } catch (AuthenticationException $e) { + if (null !== $this->logger) { + $this->logger->warning( + 'The token storage was not populated with remember-me token as the' + .' RememberMeServices was not able to create a token from the remember' + .' me information.', array('exception' => $e) + ); + } + + $this->rememberMeServices->loginFail($request); + + if (!$this->catchExceptions) { + throw $e; + } + return; } diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/RememberMeListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/RememberMeListenerTest.php index 2249dcbd2059d..6fb7e3c5924fa 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/RememberMeListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/RememberMeListenerTest.php @@ -143,6 +143,43 @@ public function testOnCoreSecurityIgnoresAuthenticationOptionallyRethrowsExcepti $listener->handle($event); } + public function testOnCoreSecurityAuthenticationExceptionDuringAutoLoginTriggersLoginFail() + { + list($listener, $tokenStorage, $service, $manager) = $this->getListener(); + + $tokenStorage + ->expects($this->once()) + ->method('getToken') + ->will($this->returnValue(null)) + ; + + $exception = new AuthenticationException('Authentication failed.'); + $service + ->expects($this->once()) + ->method('autoLogin') + ->will($this->throwException($exception)) + ; + + $service + ->expects($this->once()) + ->method('loginFail') + ; + + $manager + ->expects($this->never()) + ->method('authenticate') + ; + + $event = $this->getGetResponseEvent(); + $event + ->expects($this->once()) + ->method('getRequest') + ->will($this->returnValue(new Request())) + ; + + $listener->handle($event); + } + public function testOnCoreSecurity() { list($listener, $tokenStorage, $service, $manager) = $this->getListener(); From 860d4549c242c66348f2eddcf6d6b6186e334e11 Mon Sep 17 00:00:00 2001 From: Iltar van der Berg Date: Thu, 19 Apr 2018 12:35:58 +0200 Subject: [PATCH 025/938] No more support for custom anon/remember tokens based on FQCN --- UPGRADE-4.2.md | 13 ++ UPGRADE-5.0.md | 3 + .../Bundle/SecurityBundle/CHANGELOG.md | 9 + .../Resources/config/security.xml | 4 +- src/Symfony/Component/Security/CHANGELOG.md | 5 + .../AuthenticationTrustResolver.php | 24 ++- .../AuthenticationTrustResolverTest.php | 161 +++++++++++++++++- .../Authorization/ExpressionLanguageTest.php | 4 +- .../Voter/AuthenticatedVoterTest.php | 10 +- 9 files changed, 214 insertions(+), 19 deletions(-) diff --git a/UPGRADE-4.2.md b/UPGRADE-4.2.md index 23982ef159b36..b5f7f022aa8f0 100644 --- a/UPGRADE-4.2.md +++ b/UPGRADE-4.2.md @@ -5,3 +5,16 @@ Security -------- * Using the `has_role()` function in security expressions is deprecated, use the `is_granted()` function instead. + * Passing custom class names to the + `Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver` to define + custom anonymous and remember me token classes is deprecated. To + use custom tokens, extend the existing `Symfony\Component\Security\Core\Authentication\Token\AnonymousToken` + or `Symfony\Component\Security\Core\Authentication\Token\RememberMeToken`. + +SecurityBundle +-------------- + + * Using the `security.authentication.trust_resolver.anonymous_class` and + `security.authentication.trust_resolver.rememberme_class` parameters to define + the token classes is deprecated. To use + custom tokens extend the existing AnonymousToken and RememberMeToken. diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md index 61b7237b44923..66ae66c172340 100644 --- a/UPGRADE-5.0.md +++ b/UPGRADE-5.0.md @@ -78,6 +78,7 @@ Security * The `ContextListener::setLogoutOnUserChange()` method has been removed. * The `Symfony\Component\Security\Core\User\AdvancedUserInterface` has been removed. * The `ExpressionVoter::addExpressionLanguageProvider()` method has been removed. + * The `AuthenticationTrustResolver` constructor arguments have been removed. SecurityBundle -------------- @@ -85,6 +86,8 @@ SecurityBundle * The `logout_on_user_change` firewall option has been removed. * The `switch_user.stateless` firewall option has been removed. * The `SecurityUserValueResolver` class has been removed. + * The `security.authentication.trust_resolver.anonymous_class` parameter has been removed. + * The `security.authentication.trust_resolver.rememberme_class` parameter has been removed. Translation ----------- diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index 648189bb15a68..32b9f618adafd 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -1,6 +1,15 @@ CHANGELOG ========= +4.2.0 +----- + + * Using the `security.authentication.trust_resolver.anonymous_class` and + `security.authentication.trust_resolver.rememberme_class` parameters to define + the token classes is deprecated. To use + custom tokens extend the existing `Symfony\Component\Security\Core\Authentication\Token\AnonymousToken` + or `Symfony\Component\Security\Core\Authentication\Token\RememberMeToken`. + 4.1.0 ----- diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml index 65c48e0855cb7..a9d81bc4174a9 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml @@ -5,8 +5,8 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - Symfony\Component\Security\Core\Authentication\Token\AnonymousToken - Symfony\Component\Security\Core\Authentication\Token\RememberMeToken + null + null diff --git a/src/Symfony/Component/Security/CHANGELOG.md b/src/Symfony/Component/Security/CHANGELOG.md index 87939e3a26388..9348d1f38f08e 100644 --- a/src/Symfony/Component/Security/CHANGELOG.md +++ b/src/Symfony/Component/Security/CHANGELOG.md @@ -6,6 +6,11 @@ CHANGELOG * added the `is_granted()` function in security expressions * deprecated the `has_role()` function in security expressions, use `is_granted()` instead +* Passing custom class names to the + `Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver` to define + custom anonymous and remember me token classes is deprecated. To + use custom tokens, extend the existing `Symfony\Component\Security\Core\Authentication\Token\AnonymousToken` + or `Symfony\Component\Security\Core\Authentication\Token\RememberMeToken`. 4.1.0 ----- diff --git a/src/Symfony/Component/Security/Core/Authentication/AuthenticationTrustResolver.php b/src/Symfony/Component/Security/Core/Authentication/AuthenticationTrustResolver.php index da7c34e58625b..cba6a8708243e 100644 --- a/src/Symfony/Component/Security/Core/Authentication/AuthenticationTrustResolver.php +++ b/src/Symfony/Component/Security/Core/Authentication/AuthenticationTrustResolver.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Security\Core\Authentication; +use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; +use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; /** @@ -23,10 +25,18 @@ class AuthenticationTrustResolver implements AuthenticationTrustResolverInterfac private $anonymousClass; private $rememberMeClass; - public function __construct(string $anonymousClass, string $rememberMeClass) + public function __construct(?string $anonymousClass = null, ?string $rememberMeClass = null) { $this->anonymousClass = $anonymousClass; $this->rememberMeClass = $rememberMeClass; + + if (null !== $anonymousClass && !is_a($anonymousClass, AnonymousToken::class, true)) { + @trigger_error(sprintf('Configuring a custom anonymous token class is deprecated since Symfony 4.2; have the "%s" class extend the "%s" class instead, and remove the "%s" constructor argument.', $anonymousClass, AnonymousToken::class, self::class), E_USER_DEPRECATED); + } + + if (null !== $rememberMeClass && !is_a($rememberMeClass, RememberMeToken::class, true)) { + @trigger_error(sprintf('Configuring a custom remember me token class is deprecated since Symfony 4.2; have the "%s" class extend the "%s" class instead, and remove the "%s" constructor argument.', $rememberMeClass, RememberMeToken::class, self::class), E_USER_DEPRECATED); + } } /** @@ -38,7 +48,11 @@ public function isAnonymous(TokenInterface $token = null) return false; } - return $token instanceof $this->anonymousClass; + if (null !== $this->anonymousClass) { + return $token instanceof $this->anonymousClass; + } + + return $token instanceof AnonymousToken; } /** @@ -50,7 +64,11 @@ public function isRememberMe(TokenInterface $token = null) return false; } - return $token instanceof $this->rememberMeClass; + if (null !== $this->rememberMeClass) { + return $token instanceof $this->rememberMeClass; + } + + return $token instanceof RememberMeToken; } /** diff --git a/src/Symfony/Component/Security/Core/Tests/Authentication/AuthenticationTrustResolverTest.php b/src/Symfony/Component/Security/Core/Tests/Authentication/AuthenticationTrustResolverTest.php index 55ca05b43b5fb..fb5a885161bf1 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authentication/AuthenticationTrustResolverTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/AuthenticationTrustResolverTest.php @@ -13,10 +13,82 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver; +use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; +use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; class AuthenticationTrustResolverTest extends TestCase { public function testIsAnonymous() + { + $resolver = new AuthenticationTrustResolver(); + $this->assertFalse($resolver->isAnonymous(null)); + $this->assertFalse($resolver->isAnonymous($this->getToken())); + $this->assertFalse($resolver->isAnonymous($this->getRememberMeToken())); + $this->assertFalse($resolver->isAnonymous(new FakeCustomToken())); + $this->assertTrue($resolver->isAnonymous(new RealCustomAnonymousToken())); + $this->assertTrue($resolver->isAnonymous($this->getAnonymousToken())); + } + + public function testIsRememberMe() + { + $resolver = new AuthenticationTrustResolver(); + + $this->assertFalse($resolver->isRememberMe(null)); + $this->assertFalse($resolver->isRememberMe($this->getToken())); + $this->assertFalse($resolver->isRememberMe($this->getAnonymousToken())); + $this->assertFalse($resolver->isRememberMe(new FakeCustomToken())); + $this->assertTrue($resolver->isRememberMe(new RealCustomRememberMeToken())); + $this->assertTrue($resolver->isRememberMe($this->getRememberMeToken())); + } + + public function testisFullFledged() + { + $resolver = new AuthenticationTrustResolver(); + + $this->assertFalse($resolver->isFullFledged(null)); + $this->assertFalse($resolver->isFullFledged($this->getAnonymousToken())); + $this->assertFalse($resolver->isFullFledged($this->getRememberMeToken())); + $this->assertFalse($resolver->isFullFledged(new RealCustomAnonymousToken())); + $this->assertFalse($resolver->isFullFledged(new RealCustomRememberMeToken())); + $this->assertTrue($resolver->isFullFledged($this->getToken())); + $this->assertTrue($resolver->isFullFledged(new FakeCustomToken())); + } + + /** + * @group legacy + * @expectedDeprecation Configuring a custom anonymous token class is deprecated since Symfony 4.2; have the "Symfony\Component\Security\Core\Tests\Authentication\FakeCustomToken" class extend the "Symfony\Component\Security\Core\Authentication\Token\AnonymousToken" class instead, and remove the "Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver" constructor argument. + */ + public function testsAnonymousDeprecationWithCustomClasses() + { + $resolver = new AuthenticationTrustResolver(FakeCustomToken::class); + + $this->assertTrue($resolver->isAnonymous(new FakeCustomToken())); + } + + /** + * @group legacy + * @expectedDeprecation Configuring a custom remember me token class is deprecated since Symfony 4.2; have the "Symfony\Component\Security\Core\Tests\Authentication\FakeCustomToken" class extend the "Symfony\Component\Security\Core\Authentication\Token\RememberMeToken" class instead, and remove the "Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver" constructor argument. + */ + public function testIsRememberMeDeprecationWithCustomClasses() + { + $resolver = new AuthenticationTrustResolver(null, FakeCustomToken::class); + + $this->assertTrue($resolver->isRememberMe(new FakeCustomToken())); + } + + /** + * @group legacy + * @expectedDeprecation Configuring a custom remember me token class is deprecated since Symfony 4.2; have the "Symfony\Component\Security\Core\Tests\Authentication\FakeCustomToken" class extend the "Symfony\Component\Security\Core\Authentication\Token\RememberMeToken" class instead, and remove the "Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver" constructor argument. + */ + public function testIsFullFledgedDeprecationWithCustomClasses() + { + $resolver = new AuthenticationTrustResolver(FakeCustomToken::class, FakeCustomToken::class); + + $this->assertFalse($resolver->isFullFledged(new FakeCustomToken())); + } + + public function testIsAnonymousWithClassAsConstructorButStillExtending() { $resolver = $this->getResolver(); @@ -24,9 +96,10 @@ public function testIsAnonymous() $this->assertFalse($resolver->isAnonymous($this->getToken())); $this->assertFalse($resolver->isAnonymous($this->getRememberMeToken())); $this->assertTrue($resolver->isAnonymous($this->getAnonymousToken())); + $this->assertTrue($resolver->isAnonymous(new RealCustomAnonymousToken())); } - public function testIsRememberMe() + public function testIsRememberMeWithClassAsConstructorButStillExtending() { $resolver = $this->getResolver(); @@ -34,15 +107,18 @@ public function testIsRememberMe() $this->assertFalse($resolver->isRememberMe($this->getToken())); $this->assertFalse($resolver->isRememberMe($this->getAnonymousToken())); $this->assertTrue($resolver->isRememberMe($this->getRememberMeToken())); + $this->assertTrue($resolver->isRememberMe(new RealCustomRememberMeToken())); } - public function testisFullFledged() + public function testisFullFledgedWithClassAsConstructorButStillExtending() { $resolver = $this->getResolver(); $this->assertFalse($resolver->isFullFledged(null)); $this->assertFalse($resolver->isFullFledged($this->getAnonymousToken())); $this->assertFalse($resolver->isFullFledged($this->getRememberMeToken())); + $this->assertFalse($resolver->isFullFledged(new RealCustomAnonymousToken())); + $this->assertFalse($resolver->isFullFledged(new RealCustomRememberMeToken())); $this->assertTrue($resolver->isFullFledged($this->getToken())); } @@ -69,3 +145,84 @@ protected function getResolver() ); } } + +class FakeCustomToken implements TokenInterface +{ + public function serialize() + { + } + + public function unserialize($serialized) + { + } + + public function __toString() + { + } + + public function getRoles() + { + } + + public function getCredentials() + { + } + + public function getUser() + { + } + + public function setUser($user) + { + } + + public function getUsername() + { + } + + public function isAuthenticated() + { + } + + public function setAuthenticated($isAuthenticated) + { + } + + public function eraseCredentials() + { + } + + public function getAttributes() + { + } + + public function setAttributes(array $attributes) + { + } + + public function hasAttribute($name) + { + } + + public function getAttribute($name) + { + } + + public function setAttribute($name, $value) + { + } +} + +class RealCustomAnonymousToken extends AnonymousToken +{ + public function __construct() + { + } +} + +class RealCustomRememberMeToken extends RememberMeToken +{ + public function __construct() + { + } +} diff --git a/src/Symfony/Component/Security/Core/Tests/Authorization/ExpressionLanguageTest.php b/src/Symfony/Component/Security/Core/Tests/Authorization/ExpressionLanguageTest.php index 6c05ecfab506a..7991715b7c18e 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authorization/ExpressionLanguageTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authorization/ExpressionLanguageTest.php @@ -31,10 +31,8 @@ class ExpressionLanguageTest extends TestCase */ public function testIsAuthenticated($token, $expression, $result) { - $anonymousTokenClass = 'Symfony\\Component\\Security\\Core\\Authentication\\Token\\AnonymousToken'; - $rememberMeTokenClass = 'Symfony\\Component\\Security\\Core\\Authentication\\Token\\RememberMeToken'; $expressionLanguage = new ExpressionLanguage(); - $trustResolver = new AuthenticationTrustResolver($anonymousTokenClass, $rememberMeTokenClass); + $trustResolver = new AuthenticationTrustResolver(); $tokenStorage = new TokenStorage(); $tokenStorage->setToken($token); $accessDecisionManager = new AccessDecisionManager(array(new RoleVoter())); diff --git a/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/AuthenticatedVoterTest.php b/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/AuthenticatedVoterTest.php index 1ba7e39163ad1..0651526f494a9 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/AuthenticatedVoterTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/AuthenticatedVoterTest.php @@ -23,7 +23,7 @@ class AuthenticatedVoterTest extends TestCase */ public function testVote($authenticated, $attributes, $expected) { - $voter = new AuthenticatedVoter($this->getResolver()); + $voter = new AuthenticatedVoter(new AuthenticationTrustResolver()); $this->assertSame($expected, $voter->vote($this->getToken($authenticated), null, $attributes)); } @@ -52,14 +52,6 @@ public function getVoteTests() ); } - protected function getResolver() - { - return new AuthenticationTrustResolver( - 'Symfony\\Component\\Security\\Core\\Authentication\\Token\\AnonymousToken', - 'Symfony\\Component\\Security\\Core\\Authentication\\Token\\RememberMeToken' - ); - } - protected function getToken($authenticated) { if ('fully' === $authenticated) { From 9372e7a8136ab677b74c7a31a7a13b8ee1db10c5 Mon Sep 17 00:00:00 2001 From: Alexey Kopytko Date: Fri, 18 May 2018 18:18:34 +0900 Subject: [PATCH 026/938] [Process] Consider \"executable\" suffixes first on Windows --- .../Component/Process/ExecutableFinder.php | 2 +- .../Process/Tests/ExecutableFinderTest.php | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Process/ExecutableFinder.php b/src/Symfony/Component/Process/ExecutableFinder.php index 1ec6526d45efd..defa66de6b359 100644 --- a/src/Symfony/Component/Process/ExecutableFinder.php +++ b/src/Symfony/Component/Process/ExecutableFinder.php @@ -73,7 +73,7 @@ public function find($name, $default = null, array $extraDirs = array()) $suffixes = array(''); if ('\\' === DIRECTORY_SEPARATOR) { $pathExt = getenv('PATHEXT'); - $suffixes = array_merge($suffixes, $pathExt ? explode(PATH_SEPARATOR, $pathExt) : $this->suffixes); + $suffixes = array_merge($pathExt ? explode(PATH_SEPARATOR, $pathExt) : $this->suffixes, $suffixes); } foreach ($suffixes as $suffix) { foreach ($dirs as $dir) { diff --git a/src/Symfony/Component/Process/Tests/ExecutableFinderTest.php b/src/Symfony/Component/Process/Tests/ExecutableFinderTest.php index 3aaeb0fb88513..669e1b3eb6835 100644 --- a/src/Symfony/Component/Process/Tests/ExecutableFinderTest.php +++ b/src/Symfony/Component/Process/Tests/ExecutableFinderTest.php @@ -129,6 +129,36 @@ public function testFindProcessInOpenBasedir() $this->assertSamePath(PHP_BINARY, $result); } + /** + * @requires PHP 5.4 + */ + public function testFindBatchExecutableOnWindows() + { + if (ini_get('open_basedir')) { + $this->markTestSkipped('Cannot test when open_basedir is set'); + } + if ('\\' !== DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Can be only tested on windows'); + } + + $target = tempnam(sys_get_temp_dir(), 'example-windows-executable'); + + touch($target); + touch($target.'.BAT'); + + $this->assertFalse(is_executable($target)); + + $this->setPath(sys_get_temp_dir()); + + $finder = new ExecutableFinder(); + $result = $finder->find(basename($target), false); + + unlink($target); + unlink($target.'.BAT'); + + $this->assertSamePath($target.'.BAT', $result); + } + private function assertSamePath($expected, $tested) { if ('\\' === DIRECTORY_SEPARATOR) { From 18f55feef85c51368549833a89bcb223cd433a37 Mon Sep 17 00:00:00 2001 From: Kamil Madejski Date: Wed, 18 Apr 2018 13:57:06 +0200 Subject: [PATCH 027/938] [HttpKernel] Set first trusted proxy as REMOTE_ADDR in InlineFragmentRenderer. --- .../HttpKernel/Fragment/InlineFragmentRenderer.php | 4 +++- .../Tests/Fragment/InlineFragmentRendererTest.php | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php b/src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php index 17ed967fb51c3..76c8e95d79434 100644 --- a/src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php +++ b/src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php @@ -122,7 +122,9 @@ protected function createSubRequest($uri, Request $request) // Do nothing } - $server['REMOTE_ADDR'] = '127.0.0.1'; + $trustedProxies = Request::getTrustedProxies(); + $server['REMOTE_ADDR'] = $trustedProxies ? reset($trustedProxies) : '127.0.0.1'; + unset($server['HTTP_IF_MODIFIED_SINCE']); unset($server['HTTP_IF_NONE_MATCH']); diff --git a/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php b/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php index 4c1d6a00c44c9..6ed3c86537602 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php @@ -56,6 +56,7 @@ public function testRenderWithObjectsAsAttributes() $subRequest->attributes->replace(array('object' => $object, '_format' => 'html', '_controller' => 'main_controller', '_locale' => 'en')); $subRequest->headers->set('x-forwarded-for', array('127.0.0.1')); $subRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1'); + $subRequest->server->set('REMOTE_ADDR', '1.1.1.1'); $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($subRequest)); @@ -84,7 +85,7 @@ public function testRenderWithTrustedHeaderDisabled() { Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, ''); - $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest(Request::create('/'))); + $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest(Request::create('/', 'GET', array(), array(), array(), array('REMOTE_ADDR' => '1.1.1.1')))); $this->assertSame('foo', $strategy->render('/', Request::create('/'))->getContent()); } @@ -168,6 +169,7 @@ public function testESIHeaderIsKeptInSubrequest() { $expectedSubRequest = Request::create('/'); $expectedSubRequest->headers->set('Surrogate-Capability', 'abc="ESI/1.0"'); + $expectedSubRequest->server->set('REMOTE_ADDR', '1.1.1.1'); if (Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP)) { $expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1')); @@ -193,7 +195,7 @@ public function testESIHeaderIsKeptInSubrequestWithTrustedHeaderDisabled() public function testHeadersPossiblyResultingIn304AreNotAssignedToSubrequest() { - $expectedSubRequest = Request::create('/'); + $expectedSubRequest = Request::create('/', 'GET', array(), array(), array(), array('REMOTE_ADDR' => '1.1.1.1')); if (Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP)) { $expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1')); $expectedSubRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1'); From 92e3023195e4afeed0568a4e94adf2dc2453aa1a Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 28 May 2018 17:16:05 +0200 Subject: [PATCH 028/938] [HttpKernel] fix registering IDE links --- src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php | 3 --- src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml | 1 + .../WebProfilerBundle/Controller/ExceptionController.php | 1 + .../HttpKernel/EventListener/ExceptionListener.php | 6 ++++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index 17c12686dad23..2a62d391a7b22 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -61,9 +61,6 @@ class FrameworkBundle extends Bundle { public function boot() { - if (!ini_get('xdebug.file_link_format') && !get_cfg_var('xdebug.file_link_format')) { - ini_set('xdebug.file_link_format', $this->container->getParameter('debug.file_link_format')); - } ErrorHandler::register(null, false)->throwAt($this->container->getParameter('debug.error_handler.throw_at'), true); if ($this->container->hasParameter('kernel.trusted_proxies')) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml index 565aef68fd45c..f6dd2bb9df630 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml @@ -74,6 +74,7 @@ %kernel.debug% %kernel.charset% + %debug.file_link_format% diff --git a/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionController.php index f008b0b5284fc..2137477a5cf35 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionController.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionController.php @@ -30,6 +30,7 @@ class ExceptionController protected $twig; protected $debug; protected $profiler; + private $fileLinkFormat; public function __construct(Profiler $profiler = null, Environment $twig, $debug, FileLinkFormatter $fileLinkFormat = null) { diff --git a/src/Symfony/Component/HttpKernel/EventListener/ExceptionListener.php b/src/Symfony/Component/HttpKernel/EventListener/ExceptionListener.php index 3dfa4cd8ea79a..4d8ad1e7e5971 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/ExceptionListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/ExceptionListener.php @@ -36,13 +36,15 @@ class ExceptionListener implements EventSubscriberInterface protected $logger; protected $debug; private $charset; + private $fileLinkFormat; - public function __construct($controller, LoggerInterface $logger = null, $debug = false, $charset = null) + public function __construct($controller, LoggerInterface $logger = null, $debug = false, $charset = null, $fileLinkFormat = null) { $this->controller = $controller; $this->logger = $logger; $this->debug = $debug; $this->charset = $charset; + $this->fileLinkFormat = $fileLinkFormat; } public function onKernelException(GetResponseForExceptionEvent $event) @@ -123,7 +125,7 @@ protected function duplicateRequest(\Exception $exception, Request $request) $attributes = array( 'exception' => $exception = FlattenException::create($exception), '_controller' => $this->controller ?: function () use ($exception) { - $handler = new ExceptionHandler($this->debug, $this->charset); + $handler = new ExceptionHandler($this->debug, $this->charset, $this->fileLinkFormat); return new Response($handler->getHtml($exception), $exception->getStatusCode(), $exception->getHeaders()); }, From c250fbdda08a7279155bb18af33129bda11d3e41 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 28 May 2018 21:30:19 +0200 Subject: [PATCH 029/938] [Cache] Remove TaggableCacheInterface, alias cache.app.taggable to CacheInterface --- .../Resources/config/cache.xml | 3 +- .../Cache/Adapter/TagAwareAdapter.php | 4 +-- .../Adapter/TraceableTagAwareAdapter.php | 4 +-- src/Symfony/Component/Cache/CHANGELOG.md | 2 +- .../Component/Cache/CacheInterface.php | 6 +--- .../Cache/TaggableCacheInterface.php | 35 ------------------- 6 files changed, 7 insertions(+), 47 deletions(-) delete mode 100644 src/Symfony/Component/Cache/TaggableCacheInterface.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml index d0e596ab83338..cd4d51e2c3936 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml @@ -126,9 +126,8 @@ - - + diff --git a/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php index 257e404e1c43e..e810f5d00fb0d 100644 --- a/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php @@ -13,17 +13,17 @@ use Psr\Cache\CacheItemInterface; use Psr\Cache\InvalidArgumentException; +use Symfony\Component\Cache\CacheInterface; use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\PruneableInterface; use Symfony\Component\Cache\ResettableInterface; -use Symfony\Component\Cache\TaggableCacheInterface; use Symfony\Component\Cache\Traits\GetTrait; use Symfony\Component\Cache\Traits\ProxyTrait; /** * @author Nicolas Grekas */ -class TagAwareAdapter implements TagAwareAdapterInterface, TaggableCacheInterface, PruneableInterface, ResettableInterface +class TagAwareAdapter implements CacheInterface, TagAwareAdapterInterface, PruneableInterface, ResettableInterface { const TAGS_PREFIX = "\0tags\0"; diff --git a/src/Symfony/Component/Cache/Adapter/TraceableTagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/TraceableTagAwareAdapter.php index 2fda8b360240e..c597c81c386ea 100644 --- a/src/Symfony/Component/Cache/Adapter/TraceableTagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/TraceableTagAwareAdapter.php @@ -11,12 +11,12 @@ namespace Symfony\Component\Cache\Adapter; -use Symfony\Component\Cache\TaggableCacheInterface; +use Symfony\Component\Cache\CacheInterface; /** * @author Robin Chalas */ -class TraceableTagAwareAdapter extends TraceableAdapter implements TaggableCacheInterface, TagAwareAdapterInterface +class TraceableTagAwareAdapter extends TraceableAdapter implements CacheInterface, TagAwareAdapterInterface { public function __construct(TagAwareAdapterInterface $pool) { diff --git a/src/Symfony/Component/Cache/CHANGELOG.md b/src/Symfony/Component/Cache/CHANGELOG.md index d21b2cbda4df1..f6fb43ebe7874 100644 --- a/src/Symfony/Component/Cache/CHANGELOG.md +++ b/src/Symfony/Component/Cache/CHANGELOG.md @@ -4,7 +4,7 @@ CHANGELOG 4.2.0 ----- - * added `CacheInterface` and `TaggableCacheInterface` + * added `CacheInterface`, which should become the preferred way to use a cache * throw `LogicException` when `CacheItem::tag()` is called on an item coming from a non tag-aware pool 3.4.0 diff --git a/src/Symfony/Component/Cache/CacheInterface.php b/src/Symfony/Component/Cache/CacheInterface.php index c5c877ddbd746..49194d135e9bd 100644 --- a/src/Symfony/Component/Cache/CacheInterface.php +++ b/src/Symfony/Component/Cache/CacheInterface.php @@ -11,8 +11,6 @@ namespace Symfony\Component\Cache; -use Psr\Cache\CacheItemInterface; - /** * Gets and stores items from a cache. * @@ -22,14 +20,12 @@ * - the corresponding PSR-6 CacheItemInterface object, * allowing time-based expiration control. * - * If you need tag-based invalidation, use TaggableCacheInterface instead. - * * @author Nicolas Grekas */ interface CacheInterface { /** - * @param callable(CacheItemInterface):mixed $callback Should return the computed value for the given key/item + * @param callable(CacheItem):mixed $callback Should return the computed value for the given key/item * * @return mixed The value corresponding to the provided key */ diff --git a/src/Symfony/Component/Cache/TaggableCacheInterface.php b/src/Symfony/Component/Cache/TaggableCacheInterface.php deleted file mode 100644 index c112e72586d74..0000000000000 --- a/src/Symfony/Component/Cache/TaggableCacheInterface.php +++ /dev/null @@ -1,35 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Cache; - -/** - * Gets and stores items from a tag-aware cache. - * - * On cache misses, a callback is called that should return the missing value. - * It is given two arguments: - * - the missing cache key - * - the corresponding Symfony CacheItem object, - * allowing time-based *and* tags-based expiration control - * - * If you don't need tags-based invalidation, use CacheInterface instead. - * - * @author Nicolas Grekas - */ -interface TaggableCacheInterface extends CacheInterface -{ - /** - * @param callable(CacheItem):mixed $callback Should return the computed value for the given key/item - * - * @return mixed The value corresponding to the provided key - */ - public function get(string $key, callable $callback); -} From 479aa9074b40ef9cc01c5369681c30a81b832ceb Mon Sep 17 00:00:00 2001 From: Davide Borsatto Date: Tue, 29 May 2018 10:57:40 +0200 Subject: [PATCH 030/938] Change PHPDoc in ResponseHeaderBag::getCookies() to help IDEs --- src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php b/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php index a042328ca5229..c299c369288d6 100644 --- a/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php +++ b/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php @@ -160,7 +160,7 @@ public function removeCookie($name, $path = '/', $domain = null) * * @param string $format * - * @return array + * @return Cookie[] * * @throws \InvalidArgumentException When the $format is invalid */ From 896be4cc2b7235745911876bbcc74b9c577bf39b Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 14 Apr 2018 23:12:15 -0500 Subject: [PATCH 031/938] [FrameworkBundle] Allow configuring taggable cache pools --- .../Bundle/FrameworkBundle/CHANGELOG.md | 5 +++++ .../Compiler/CachePoolClearerPass.php | 6 ++--- .../Compiler/CachePoolPass.php | 12 +++++----- .../DependencyInjection/Configuration.php | 1 + .../FrameworkExtension.php | 22 ++++++++++++++++++- .../Compiler/CachePoolClearerPassTest.php | 13 +++++++++-- .../Compiler/CachePoolPassTest.php | 22 +++++++++++++++++++ .../Tests/Functional/CachePoolsTest.php | 20 +++++++++++++++++ .../Functional/app/CachePools/config.yml | 12 ++++++++++ .../app/CachePools/redis_config.yml | 14 ++++++++++++ .../app/CachePools/redis_custom_config.yml | 14 ++++++++++++ 11 files changed, 130 insertions(+), 11 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index b03567d68e35a..139041aba81a8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +4.2.0 +----- + + * Allowed configuring taggable cache pools via a new `framework.cache.pools.tags` option (bool|service-id) + 4.1.0 ----- diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolClearerPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolClearerPass.php index 094712ded69d3..c285e935cbad8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolClearerPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolClearerPass.php @@ -31,9 +31,9 @@ public function process(ContainerBuilder $container) foreach ($container->findTaggedServiceIds('cache.pool.clearer') as $id => $attr) { $clearer = $container->getDefinition($id); $pools = array(); - foreach ($clearer->getArgument(0) as $id => $ref) { - if ($container->hasDefinition($id)) { - $pools[$id] = new Reference($id); + foreach ($clearer->getArgument(0) as $name => $ref) { + if ($container->hasDefinition($ref)) { + $pools[$name] = new Reference($ref); } } $clearer->replaceArgument(0, $pools); diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php index 2530d9e75e024..670c5edd36440 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php @@ -41,6 +41,7 @@ public function process(ContainerBuilder $container) $clearers = array(); $attributes = array( 'provider', + 'name', 'namespace', 'default_lifetime', 'reset', @@ -56,8 +57,9 @@ public function process(ContainerBuilder $container) $tags[0] += $t[0]; } } + $name = $tags[0]['name'] ?? $id; if (!isset($tags[0]['namespace'])) { - $tags[0]['namespace'] = $this->getNamespace($seed, $id); + $tags[0]['namespace'] = $this->getNamespace($seed, $name); } if (isset($tags[0]['clearer'])) { $clearer = $tags[0]['clearer']; @@ -67,7 +69,7 @@ public function process(ContainerBuilder $container) } else { $clearer = null; } - unset($tags[0]['clearer']); + unset($tags[0]['clearer'], $tags[0]['name']); if (isset($tags[0]['provider'])) { $tags[0]['provider'] = new Reference(static::getServiceProvider($container, $tags[0]['provider'])); @@ -86,14 +88,14 @@ public function process(ContainerBuilder $container) unset($tags[0][$attr]); } if (!empty($tags[0])) { - throw new InvalidArgumentException(sprintf('Invalid "cache.pool" tag for service "%s": accepted attributes are "clearer", "provider", "namespace", "default_lifetime" and "reset", found "%s".', $id, implode('", "', array_keys($tags[0])))); + throw new InvalidArgumentException(sprintf('Invalid "cache.pool" tag for service "%s": accepted attributes are "clearer", "provider", "name", "namespace", "default_lifetime" and "reset", found "%s".', $id, implode('", "', array_keys($tags[0])))); } if (null !== $clearer) { - $clearers[$clearer][$id] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE); + $clearers[$clearer][$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE); } - $pools[$id] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE); + $pools[$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE); } $clearer = 'cache.global_clearer'; diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 31e8e6a6ba912..e9a380dd78e63 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -870,6 +870,7 @@ private function addCacheSection(ArrayNodeDefinition $rootNode) ->prototype('array') ->children() ->scalarNode('adapter')->defaultValue('cache.app')->end() + ->scalarNode('tags')->defaultNull()->end() ->booleanNode('public')->defaultFalse()->end() ->integerNode('default_lifetime')->end() ->scalarNode('provider') diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 318af6c8b9a2f..f15e9ac12c435 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -22,6 +22,7 @@ use Symfony\Component\Cache\Adapter\AbstractAdapter; use Symfony\Component\Cache\Adapter\AdapterInterface; use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\Cache\Adapter\TagAwareAdapter; use Symfony\Component\Cache\ResettableInterface; use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\Loader\LoaderInterface; @@ -1556,12 +1557,31 @@ private function registerCacheConfiguration(array $config, ContainerBuilder $con $config['pools']['cache.'.$name] = array( 'adapter' => $config[$name], 'public' => true, + 'tags' => false, ); } foreach ($config['pools'] as $name => $pool) { + if ($config['pools'][$pool['adapter']]['tags'] ?? false) { + $pool['adapter'] = '.'.$pool['adapter'].'.inner'; + } $definition = new ChildDefinition($pool['adapter']); + + if ($pool['tags']) { + if ($config['pools'][$pool['tags']]['tags'] ?? false) { + $pool['tags'] = '.'.$pool['tags'].'.inner'; + } + $container->register($name, TagAwareAdapter::class) + ->addArgument(new Reference('.'.$name.'.inner')) + ->addArgument(true !== $pool['tags'] ? new Reference($pool['tags']) : null) + ->setPublic($pool['public']) + ; + + $pool['name'] = $name; + $pool['public'] = false; + $name = '.'.$name.'.inner'; + } $definition->setPublic($pool['public']); - unset($pool['adapter'], $pool['public']); + unset($pool['adapter'], $pool['public'], $pool['tags']); $definition->addTag('cache.pool', $pool); $container->setDefinition($name, $definition); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolClearerPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolClearerPassTest.php index 9230405d7560e..243061712a8c4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolClearerPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolClearerPassTest.php @@ -39,6 +39,11 @@ public function testPoolRefsAreWeak() $publicPool->addTag('cache.pool', array('clearer' => 'clearer_alias')); $container->setDefinition('public.pool', $publicPool); + $publicPool = new Definition(); + $publicPool->addArgument('namespace'); + $publicPool->addTag('cache.pool', array('clearer' => 'clearer_alias', 'name' => 'pool2')); + $container->setDefinition('public.pool2', $publicPool); + $privatePool = new Definition(); $privatePool->setPublic(false); $privatePool->addArgument('namespace'); @@ -55,7 +60,11 @@ public function testPoolRefsAreWeak() $pass->process($container); } - $this->assertEquals(array(array('public.pool' => new Reference('public.pool'))), $clearer->getArguments()); - $this->assertEquals(array(array('public.pool' => new Reference('public.pool'))), $globalClearer->getArguments()); + $expected = array(array( + 'public.pool' => new Reference('public.pool'), + 'pool2' => new Reference('public.pool2'), + )); + $this->assertEquals($expected, $clearer->getArguments()); + $this->assertEquals($expected, $globalClearer->getArguments()); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolPassTest.php index 4619301b6e997..44443a52a50e9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolPassTest.php @@ -93,6 +93,28 @@ public function testArgsAreReplaced() $this->assertSame(3, $cachePool->getArgument(2)); } + public function testWithNameAttribute() + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->setParameter('kernel.name', 'app'); + $container->setParameter('kernel.environment', 'prod'); + $container->setParameter('cache.prefix.seed', 'foo'); + $cachePool = new Definition(); + $cachePool->addTag('cache.pool', array( + 'name' => 'foobar', + 'provider' => 'foobar', + )); + $cachePool->addArgument(null); + $cachePool->addArgument(null); + $cachePool->addArgument(null); + $container->setDefinition('app.cache_pool', $cachePool); + + $this->cachePoolPass->process($container); + + $this->assertSame('9HvPgAayyh', $cachePool->getArgument(1)); + } + /** * @expectedException \InvalidArgumentException * @expectedExceptionMessage Invalid "cache.pool" tag for service "app.cache_pool": accepted attributes are diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolsTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolsTest.php index 9cdb93a493f20..d152f5cd873af 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolsTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolsTest.php @@ -13,6 +13,7 @@ use Symfony\Component\Cache\Adapter\AdapterInterface; use Symfony\Component\Cache\Adapter\RedisAdapter; +use Symfony\Component\Cache\Adapter\TagAwareAdapter; use Symfony\Component\Cache\Exception\InvalidArgumentException; class CachePoolsTest extends WebTestCase @@ -94,6 +95,25 @@ private function doTestCachePools($options, $adapterClass) $item = $pool2->getItem($key); $this->assertTrue($item->isHit()); + + $prefix = "\0".TagAwareAdapter::class."\0"; + $pool4 = $container->get('cache.pool4'); + $this->assertInstanceof(TagAwareAdapter::class, $pool4); + $pool4 = (array) $pool4; + $this->assertSame($pool4[$prefix.'pool'], $pool4[$prefix.'tags'] ?? $pool4['tags']); + + $pool5 = $container->get('cache.pool5'); + $this->assertInstanceof(TagAwareAdapter::class, $pool5); + $pool5 = (array) $pool5; + $this->assertSame($pool2, $pool5[$prefix.'tags'] ?? $pool5['tags']); + + $pool6 = $container->get('cache.pool6'); + $this->assertInstanceof(TagAwareAdapter::class, $pool6); + $pool6 = (array) $pool6; + $this->assertSame($pool4[$prefix.'pool'], $pool6[$prefix.'tags'] ?? $pool6['tags']); + + $pool7 = $container->get('cache.pool7'); + $this->assertNotInstanceof(TagAwareAdapter::class, $pool7); } protected static function createKernel(array $options = array()) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/config.yml index de1e144dad062..8c7bcb4eb1fac 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/config.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/config.yml @@ -12,3 +12,15 @@ framework: adapter: cache.pool3 cache.pool3: clearer: ~ + cache.pool4: + tags: true + public: true + cache.pool5: + tags: cache.pool2 + public: true + cache.pool6: + tags: cache.pool4 + public: true + cache.pool7: + adapter: cache.pool4 + public: true diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/redis_config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/redis_config.yml index 3bf10f448f9c2..30c69163d4f2f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/redis_config.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/redis_config.yml @@ -15,3 +15,17 @@ framework: cache.pool2: public: true clearer: ~ + cache.pool3: + clearer: ~ + cache.pool4: + tags: true + public: true + cache.pool5: + tags: cache.pool2 + public: true + cache.pool6: + tags: cache.pool4 + public: true + cache.pool7: + adapter: cache.pool4 + public: true diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/redis_custom_config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/redis_custom_config.yml index d0a219753eb8e..df20c5357f7a4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/redis_custom_config.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/redis_custom_config.yml @@ -26,3 +26,17 @@ framework: cache.pool2: public: true clearer: ~ + cache.pool3: + clearer: ~ + cache.pool4: + tags: true + public: true + cache.pool5: + tags: cache.pool2 + public: true + cache.pool6: + tags: cache.pool4 + public: true + cache.pool7: + adapter: cache.pool4 + public: true From 3a2eb0d9514f0b322ab31a128aa48b9f89e28d50 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 29 May 2018 15:22:55 +0200 Subject: [PATCH 032/938] Minor clean up in UPGRADE files --- UPGRADE-4.2.md | 6 +++--- UPGRADE-5.0.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/UPGRADE-4.2.md b/UPGRADE-4.2.md index c714e7f9190ec..7386221a5df9d 100644 --- a/UPGRADE-4.2.md +++ b/UPGRADE-4.2.md @@ -5,7 +5,7 @@ Security -------- * Using the `has_role()` function in security expressions is deprecated, use the `is_granted()` function instead. - * Not returning an array of 3 elements from `FirewallMapInterface::getListeners()` is deprecated, the 3rd element + * Not returning an array of 3 elements from `FirewallMapInterface::getListeners()` is deprecated, the 3rd element must be an instance of `LogoutListener` or `null`. * Passing custom class names to the `Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver` to define @@ -16,9 +16,9 @@ Security SecurityBundle -------------- - * Passing a `FirewallConfig` instance as 3rd argument to the `FirewallContext` constructor is deprecated, + * Passing a `FirewallConfig` instance as 3rd argument to the `FirewallContext` constructor is deprecated, pass a `LogoutListener` instance instead. - * Using the `security.authentication.trust_resolver.anonymous_class` and + * Using the `security.authentication.trust_resolver.anonymous_class` and `security.authentication.trust_resolver.rememberme_class` parameters to define the token classes is deprecated. To use custom tokens extend the existing AnonymousToken and RememberMeToken. diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md index 75d8febcadebe..7c4ddf1d8eeb3 100644 --- a/UPGRADE-5.0.md +++ b/UPGRADE-5.0.md @@ -88,10 +88,10 @@ SecurityBundle * The `logout_on_user_change` firewall option has been removed. * The `switch_user.stateless` firewall option has been removed. * The `SecurityUserValueResolver` class has been removed. - * Passing a `FirewallConfig` instance as 3rd argument to the `FirewallContext` constructor + * Passing a `FirewallConfig` instance as 3rd argument to the `FirewallContext` constructor now throws a `\TypeError`, pass a `LogoutListener` instance instead. - * The `security.authentication.trust_resolver.anonymous_class` parameter has been removed. - * The `security.authentication.trust_resolver.rememberme_class` parameter has been removed. + * The `security.authentication.trust_resolver.anonymous_class` parameter has been removed. + * The `security.authentication.trust_resolver.rememberme_class` parameter has been removed. Translation ----------- From 09c660d45442c09bdcbe790652783f861bd3922f Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 30 May 2018 06:18:11 +0200 Subject: [PATCH 033/938] removed unneeded comments in tests --- .../Tests/OptionsResolver2Dot6Test.php | 64 ------------------- 1 file changed, 64 deletions(-) diff --git a/src/Symfony/Component/OptionsResolver/Tests/OptionsResolver2Dot6Test.php b/src/Symfony/Component/OptionsResolver/Tests/OptionsResolver2Dot6Test.php index ffa2872243981..aeaf019969840 100644 --- a/src/Symfony/Component/OptionsResolver/Tests/OptionsResolver2Dot6Test.php +++ b/src/Symfony/Component/OptionsResolver/Tests/OptionsResolver2Dot6Test.php @@ -29,10 +29,6 @@ protected function setUp() $this->resolver = new OptionsResolver(); } - //////////////////////////////////////////////////////////////////////////// - // resolve() - //////////////////////////////////////////////////////////////////////////// - /** * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException * @expectedExceptionMessage The option "foo" does not exist. Defined options are: "a", "z". @@ -69,10 +65,6 @@ public function testResolveFailsFromLazyOption() $this->resolver->resolve(); } - //////////////////////////////////////////////////////////////////////////// - // setDefault()/hasDefault() - //////////////////////////////////////////////////////////////////////////// - public function testSetDefaultReturnsThis() { $this->assertSame($this->resolver, $this->resolver->setDefault('foo', 'bar')); @@ -115,10 +107,6 @@ public function testHasDefaultWithNullValue() $this->assertTrue($this->resolver->hasDefault('foo')); } - //////////////////////////////////////////////////////////////////////////// - // lazy setDefault() - //////////////////////////////////////////////////////////////////////////// - public function testSetLazyReturnsThis() { $this->assertSame($this->resolver, $this->resolver->setDefault('foo', function (Options $options) {})); @@ -232,10 +220,6 @@ public function testInvokeEachLazyOptionOnlyOnce() $this->assertSame(2, $calls); } - //////////////////////////////////////////////////////////////////////////// - // setRequired()/isRequired()/getRequiredOptions() - //////////////////////////////////////////////////////////////////////////// - public function testSetRequiredReturnsThis() { $this->assertSame($this->resolver, $this->resolver->setRequired('foo')); @@ -330,10 +314,6 @@ public function testGetRequiredOptions() $this->assertSame(array('foo', 'bar'), $this->resolver->getRequiredOptions()); } - //////////////////////////////////////////////////////////////////////////// - // isMissing()/getMissingOptions() - //////////////////////////////////////////////////////////////////////////// - public function testIsMissingIfNotSet() { $this->assertFalse($this->resolver->isMissing('foo')); @@ -373,10 +353,6 @@ public function testGetMissingOptions() $this->assertSame(array('bar'), $this->resolver->getMissingOptions()); } - //////////////////////////////////////////////////////////////////////////// - // setDefined()/isDefined()/getDefinedOptions() - //////////////////////////////////////////////////////////////////////////// - /** * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException */ @@ -474,10 +450,6 @@ public function testClearedOptionsAreNotDefined() $this->assertFalse($this->resolver->isDefined('foo')); } - //////////////////////////////////////////////////////////////////////////// - // setAllowedTypes() - //////////////////////////////////////////////////////////////////////////// - /** * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException */ @@ -568,10 +540,6 @@ public function testResolveSucceedsIfInstanceOfClass() $this->assertNotEmpty($this->resolver->resolve()); } - //////////////////////////////////////////////////////////////////////////// - // addAllowedTypes() - //////////////////////////////////////////////////////////////////////////// - /** * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException */ @@ -654,10 +622,6 @@ public function testAddAllowedTypesDoesNotOverwrite2() $this->assertNotEmpty($this->resolver->resolve()); } - //////////////////////////////////////////////////////////////////////////// - // setAllowedValues() - //////////////////////////////////////////////////////////////////////////// - /** * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException */ @@ -809,10 +773,6 @@ function () { return false; }, $this->assertEquals(array('foo' => 'bar'), $this->resolver->resolve()); } - //////////////////////////////////////////////////////////////////////////// - // addAllowedValues() - //////////////////////////////////////////////////////////////////////////// - /** * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException */ @@ -929,10 +889,6 @@ public function testResolveSucceedsIfAnyAddedClosureReturnsTrue2() $this->assertEquals(array('foo' => 'bar'), $this->resolver->resolve()); } - //////////////////////////////////////////////////////////////////////////// - // setNormalizer() - //////////////////////////////////////////////////////////////////////////// - public function testSetNormalizerReturnsThis() { $this->resolver->setDefault('foo', 'bar'); @@ -1184,10 +1140,6 @@ public function testNormalizerNotCalledForUnsetOptions() $this->assertEmpty($this->resolver->resolve()); } - //////////////////////////////////////////////////////////////////////////// - // setDefaults() - //////////////////////////////////////////////////////////////////////////// - public function testSetDefaultsReturnsThis() { $this->assertSame($this->resolver, $this->resolver->setDefaults(array('foo', 'bar'))); @@ -1222,10 +1174,6 @@ public function testFailIfSetDefaultsFromLazyOption() $this->resolver->resolve(); } - //////////////////////////////////////////////////////////////////////////// - // remove() - //////////////////////////////////////////////////////////////////////////// - public function testRemoveReturnsThis() { $this->resolver->setDefault('foo', 'bar'); @@ -1314,10 +1262,6 @@ public function testRemoveUnknownOptionIgnored() $this->assertNotNull($this->resolver->remove('foo')); } - //////////////////////////////////////////////////////////////////////////// - // clear() - //////////////////////////////////////////////////////////////////////////// - public function testClearReturnsThis() { $this->assertSame($this->resolver, $this->resolver->clear()); @@ -1404,10 +1348,6 @@ public function testClearOptionAndNormalizer() $this->assertEmpty($this->resolver->resolve()); } - //////////////////////////////////////////////////////////////////////////// - // ArrayAccess - //////////////////////////////////////////////////////////////////////////// - public function testArrayAccess() { $this->resolver->setDefault('default1', 0); @@ -1522,10 +1462,6 @@ public function testFailIfCyclicDependency() $this->resolver->resolve(); } - //////////////////////////////////////////////////////////////////////////// - // Countable - //////////////////////////////////////////////////////////////////////////// - public function testCount() { $this->resolver->setDefault('default', 0); From 28c8c85da1d5f93b141ab477c4f2a9761548f363 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 30 May 2018 06:26:49 +0200 Subject: [PATCH 034/938] removed unneeded comments in tests --- .../Component/OptionsResolver/Tests/OptionsResolverTest.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php b/src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php index f50911d374553..12dc77b3c6b1c 100644 --- a/src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php +++ b/src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php @@ -634,10 +634,6 @@ public function testResolveFailsIfNotInstanceOfClass() $this->resolver->resolve(); } - //////////////////////////////////////////////////////////////////////////// - // addAllowedTypes() - //////////////////////////////////////////////////////////////////////////// - /** * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException */ From 8c62ecfad2001fe062559fd4a2f8893c31500411 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Wed, 30 May 2018 07:26:26 +0200 Subject: [PATCH 035/938] CODEOWNERS: some more rules --- .github/CODEOWNERS | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index db1c2a8a6ff44..415464c40b9a8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,9 @@ +# Console +/src/Symfony/Component/Console/Logger/ConsoleLogger.php @dunglas +# DependencyInjection +/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @dunglas +# HttpKernel +/src/Symfony/Component/HttpKernel/Log/Logger.php @dunglas # LDAP /src/Symfony/Component/Ldap/* @csarrazi # Lock @@ -5,6 +11,13 @@ # Messenger /src/Symfony/Bridge/Doctrine/Messenger/* @sroze /src/Symfony/Component/Messenger/* @sroze +# PropertyInfo +/src/Symfony/Component/PropertyInfo/* @dunglas +/src/Symfony/Bridge/Doctrine/PropertyInfo/* @dunglas +# Serializer +/src/Symfony/Component/Serializer/* @dunglas +# WebLink +/src/Symfony/Component/WebLink/* @dunglas # Workflow /src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php @lyrixx /src/Symfony/Bridge/Twig/Tests/Extension/WorkflowExtensionTest.php @lyrixx From cac37caa7de85cdd4f16072e4f1f86e2c0afed7c Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 30 May 2018 05:47:13 +0200 Subject: [PATCH 036/938] [WebProfilerBundle] made Twig bundle an explicit dependency --- src/Symfony/Bundle/WebProfilerBundle/composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/WebProfilerBundle/composer.json b/src/Symfony/Bundle/WebProfilerBundle/composer.json index 4e3783d695dcb..712ec9cdffda7 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/composer.json +++ b/src/Symfony/Bundle/WebProfilerBundle/composer.json @@ -19,7 +19,7 @@ "php": "^7.1.3", "symfony/http-kernel": "~4.1", "symfony/routing": "~3.4|~4.0", - "symfony/twig-bridge": "~3.4|~4.0", + "symfony/twig-bundle": "^3.4.3|^4.0.3", "symfony/var-dumper": "~3.4|~4.0", "twig/twig": "~1.34|~2.4" }, From 1c2f43f17cea2a9470a6d1d70ed96882c1d194dc Mon Sep 17 00:00:00 2001 From: Maxime Steinhausser Date: Tue, 22 May 2018 19:48:15 +0200 Subject: [PATCH 037/938] [Messenger][Profiler] Show dispatch caller --- .../views/Collector/messenger.html.twig | 33 ++++++++++++++-- .../views/Profiler/profiler.css.twig | 21 ++++++++++ .../DataCollector/MessengerDataCollector.php | 1 + .../MessengerDataCollectorTest.php | 21 ++++++++-- .../Tests/TraceableMessageBusTest.php | 18 +++++++++ .../Messenger/TraceableMessageBus.php | 39 +++++++++++++++++++ 6 files changed, 125 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messenger.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messenger.html.twig index d8befbbf8dca6..50dfbb9d3a719 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messenger.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messenger.html.twig @@ -55,7 +55,8 @@ .message-bus .badge.status-some-errors { line-height: 16px; border-bottom: 2px solid #B0413E; } - .message-item .sf-toggle-content.sf-toggle-visible { display: table-row-group; } + .message-item tbody.sf-toggle-content.sf-toggle-visible { display: table-row-group; } + td.message-bus-dispatch-caller { background: #f1f2f3; } {% endblock %} @@ -100,12 +101,12 @@ {% macro render_bus_messages(messages, showBus = false) %} {% set discr = random() %} - {% for i, dispatchCall in messages %} + {% for dispatchCall in messages %} - + + + + {% if showBus %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig index f9bc41d6a1b54..eb66f63681f8c 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig @@ -913,6 +913,27 @@ table.logs .metadata { background: rgba(255, 255, 153, 0.5); } +{# Messenger panel + ========================================================================= #} + +#collector-content .message-bus .trace { + border: 1px solid #DDD; + background: #FFF; + padding: 10px; + margin: 0.5em 0; + overflow: auto; +} +#collector-content .message-bus .trace { + font-size: 12px; +} +#collector-content .message-bus .trace li { + margin-bottom: 0; + padding: 0; +} +#collector-content .message-bus .trace li.selected { + background: rgba(255, 255, 153, 0.5); +} + {# Dump panel ========================================================================= #} #collector-content .sf-dump { diff --git a/src/Symfony/Component/Messenger/DataCollector/MessengerDataCollector.php b/src/Symfony/Component/Messenger/DataCollector/MessengerDataCollector.php index ed98f4cfa43f5..d71f16d544952 100644 --- a/src/Symfony/Component/Messenger/DataCollector/MessengerDataCollector.php +++ b/src/Symfony/Component/Messenger/DataCollector/MessengerDataCollector.php @@ -97,6 +97,7 @@ private function collectMessage(string $busName, array $tracedMessage) 'type' => new ClassStub(\get_class($message)), 'value' => $message, ), + 'caller' => $tracedMessage['caller'], ); if (array_key_exists('result', $tracedMessage)) { diff --git a/src/Symfony/Component/Messenger/Tests/DataCollector/MessengerDataCollectorTest.php b/src/Symfony/Component/Messenger/Tests/DataCollector/MessengerDataCollectorTest.php index d88593e3e747d..7c4fafa34090b 100644 --- a/src/Symfony/Component/Messenger/Tests/DataCollector/MessengerDataCollectorTest.php +++ b/src/Symfony/Component/Messenger/Tests/DataCollector/MessengerDataCollectorTest.php @@ -59,6 +59,7 @@ public function testHandle($returnedValue, $expected) public function getHandleTestData() { + $file = __FILE__; $messageDump = << "default" "envelopeItems" => null @@ -68,12 +69,17 @@ public function getHandleTestData() -message: "dummy message" } ] + "caller" => array:3 [ + "name" => "MessengerDataCollectorTest.php" + "file" => "$file" + "line" => %d + ] DUMP; yield 'no returned value' => array( null, << array:2 [ "type" => "NULL" @@ -86,7 +92,7 @@ public function getHandleTestData() yield 'scalar returned value' => array( 'returned value', << array:2 [ "type" => "string" @@ -99,7 +105,7 @@ public function getHandleTestData() yield 'array returned value' => array( array('returned value'), << array:2 [ "type" => "array" @@ -124,6 +130,7 @@ public function testHandleWithException() $collector->registerBus('default', $bus); try { + $line = __LINE__ + 1; $bus->dispatch($message); } catch (\Throwable $e) { // Ignore. @@ -134,8 +141,9 @@ public function testHandleWithException() $messages = $collector->getMessages(); $this->assertCount(1, $messages); + $file = __FILE__; $this->assertStringMatchesFormat(<< "default" "envelopeItems" => null "message" => array:2 [ @@ -144,6 +152,11 @@ public function testHandleWithException() -message: "dummy message" } ] + "caller" => array:3 [ + "name" => "MessengerDataCollectorTest.php" + "file" => "$file" + "line" => $line + ] "exception" => array:2 [ "type" => "RuntimeException" "value" => RuntimeException %A diff --git a/src/Symfony/Component/Messenger/Tests/TraceableMessageBusTest.php b/src/Symfony/Component/Messenger/Tests/TraceableMessageBusTest.php index 8a2946ee42778..339dc9ae5264d 100644 --- a/src/Symfony/Component/Messenger/Tests/TraceableMessageBusTest.php +++ b/src/Symfony/Component/Messenger/Tests/TraceableMessageBusTest.php @@ -28,12 +28,18 @@ public function testItTracesResult() $bus->expects($this->once())->method('dispatch')->with($message)->willReturn($result = array('foo' => 'bar')); $traceableBus = new TraceableMessageBus($bus); + $line = __LINE__ + 1; $this->assertSame($result, $traceableBus->dispatch($message)); $this->assertCount(1, $tracedMessages = $traceableBus->getDispatchedMessages()); $this->assertArraySubset(array( 'message' => $message, 'result' => $result, 'envelopeItems' => null, + 'caller' => array( + 'name' => 'TraceableMessageBusTest.php', + 'file' => __FILE__, + 'line' => $line, + ), ), $tracedMessages[0], true); } @@ -45,12 +51,18 @@ public function testItTracesResultWithEnvelope() $bus->expects($this->once())->method('dispatch')->with($envelope)->willReturn($result = array('foo' => 'bar')); $traceableBus = new TraceableMessageBus($bus); + $line = __LINE__ + 1; $this->assertSame($result, $traceableBus->dispatch($envelope)); $this->assertCount(1, $tracedMessages = $traceableBus->getDispatchedMessages()); $this->assertArraySubset(array( 'message' => $message, 'result' => $result, 'envelopeItems' => array($envelopeItem), + 'caller' => array( + 'name' => 'TraceableMessageBusTest.php', + 'file' => __FILE__, + 'line' => $line, + ), ), $tracedMessages[0], true); } @@ -64,6 +76,7 @@ public function testItTracesExceptions() $traceableBus = new TraceableMessageBus($bus); try { + $line = __LINE__ + 1; $traceableBus->dispatch($message); } catch (\RuntimeException $e) { $this->assertSame($exception, $e); @@ -74,6 +87,11 @@ public function testItTracesExceptions() 'message' => $message, 'exception' => $exception, 'envelopeItems' => null, + 'caller' => array( + 'name' => 'TraceableMessageBusTest.php', + 'file' => __FILE__, + 'line' => $line, + ), ), $tracedMessages[0], true); } } diff --git a/src/Symfony/Component/Messenger/TraceableMessageBus.php b/src/Symfony/Component/Messenger/TraceableMessageBus.php index b60d220b15ce1..2204f4e41ae2b 100644 --- a/src/Symfony/Component/Messenger/TraceableMessageBus.php +++ b/src/Symfony/Component/Messenger/TraceableMessageBus.php @@ -29,6 +29,7 @@ public function __construct(MessageBusInterface $decoratedBus) */ public function dispatch($message) { + $caller = $this->getCaller(); $callTime = microtime(true); $messageToTrace = $message instanceof Envelope ? $message->getMessage() : $message; $envelopeItems = $message instanceof Envelope ? array_values($message->all()) : null; @@ -41,6 +42,7 @@ public function dispatch($message) 'message' => $messageToTrace, 'result' => $result, 'callTime' => $callTime, + 'caller' => $caller, ); return $result; @@ -50,6 +52,7 @@ public function dispatch($message) 'message' => $messageToTrace, 'exception' => $e, 'callTime' => $callTime, + 'caller' => $caller, ); throw $e; @@ -65,4 +68,40 @@ public function reset() { $this->dispatchedMessages = array(); } + + private function getCaller(): array + { + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 8); + + $file = $trace[1]['file']; + $line = $trace[1]['line']; + + for ($i = 2; $i < 8; ++$i) { + if (isset($trace[$i]['class'], $trace[$i]['function']) + && 'dispatch' === $trace[$i]['function'] + && is_a($trace[$i]['class'], MessageBusInterface::class, true) + ) { + $file = $trace[$i]['file']; + $line = $trace[$i]['line']; + + while (++$i < 8) { + if (isset($trace[$i]['function'], $trace[$i]['file']) && empty($trace[$i]['class']) && 0 !== strpos( + $trace[$i]['function'], + 'call_user_func' + )) { + $file = $trace[$i]['file']; + $line = $trace[$i]['line']; + + break; + } + } + break; + } + } + + $name = str_replace('\\', '/', $file); + $name = substr($name, strrpos($name, '/') + 1); + + return compact('name', 'file', 'line'); + } } From 52647b86bfc15d579f9a894a49eb73140f45a19b Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 30 May 2018 15:07:50 +0200 Subject: [PATCH 038/938] bumped Symfony version to 4.1.1 --- src/Symfony/Component/HttpKernel/Kernel.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 3894f0a7c9c87..567268f2e1a8e 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -63,12 +63,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private $requestStackSize = 0; private $resetServices = false; - const VERSION = '4.1.0'; - const VERSION_ID = 40100; + const VERSION = '4.1.1-DEV'; + const VERSION_ID = 40101; const MAJOR_VERSION = 4; const MINOR_VERSION = 1; - const RELEASE_VERSION = 0; - const EXTRA_VERSION = ''; + const RELEASE_VERSION = 1; + const EXTRA_VERSION = 'DEV'; const END_OF_MAINTENANCE = '01/2019'; const END_OF_LIFE = '07/2019'; From bbbcd46005557ef27b42876c6b6bddbf2d48a9ff Mon Sep 17 00:00:00 2001 From: Samuel ROZE Date: Wed, 30 May 2018 17:45:47 +0200 Subject: [PATCH 039/938] Add an alias to the property info type extractor --- .../Bundle/FrameworkBundle/Resources/config/property_info.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.xml index a893127276564..bcf2f33b10a3a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.xml @@ -13,7 +13,11 @@ + + + + From f5703dc6b3b01c549f9dc292887508b21c1238cd Mon Sep 17 00:00:00 2001 From: Maxime Steinhausser Date: Wed, 30 May 2018 19:32:47 +0200 Subject: [PATCH 040/938] [Messenger] Fix suggested enqueue adapter package --- src/Symfony/Component/Messenger/composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Messenger/composer.json b/src/Symfony/Component/Messenger/composer.json index adbd0e2a9f671..8a34c564e6f24 100644 --- a/src/Symfony/Component/Messenger/composer.json +++ b/src/Symfony/Component/Messenger/composer.json @@ -30,7 +30,7 @@ "symfony/var-dumper": "~3.4|~4.0" }, "suggest": { - "sroze/enqueue-bridge": "For using the php-enqueue library as a transport." + "enqueue/messenger-adapter": "For using the php-enqueue library as a transport." }, "autoload": { "psr-4": { "Symfony\\Component\\Messenger\\": "" }, From 06ea72e3b298eb8b8d63900d395c9acc1c06541d Mon Sep 17 00:00:00 2001 From: Samuel ROZE Date: Wed, 30 May 2018 17:38:36 +0200 Subject: [PATCH 041/938] [PropertyInfo] Auto-enable PropertyInfo component --- .../FrameworkBundle/DependencyInjection/Configuration.php | 3 ++- .../Tests/DependencyInjection/ConfigurationTest.php | 2 +- .../Tests/DependencyInjection/FrameworkExtensionTest.php | 6 ------ 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index e9a380dd78e63..59289c38b3029 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -21,6 +21,7 @@ use Symfony\Component\Form\Form; use Symfony\Component\Lock\Lock; use Symfony\Component\Lock\Store\SemaphoreStore; +use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Serializer\Serializer; @@ -833,7 +834,7 @@ private function addPropertyInfoSection(ArrayNodeDefinition $rootNode) ->children() ->arrayNode('property_info') ->info('Property info configuration') - ->canBeEnabled() + ->{!class_exists(FullStack::class) && interface_exists(PropertyInfoExtractorInterface::class) ? 'canBeDisabled' : 'canBeEnabled'}() ->end() ->end() ; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index edac04c4c4f7c..58f7e564c97f3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -219,7 +219,7 @@ protected static function getBundleDefaultConfig() 'throw_exception_on_invalid_index' => false, ), 'property_info' => array( - 'enabled' => false, + 'enabled' => !class_exists(FullStack::class), ), 'router' => array( 'enabled' => false, diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 2ae8411c22909..85908734396ca 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -1153,12 +1153,6 @@ public function testSerializerServiceIsNotRegisteredWhenDisabled() $this->assertFalse($container->hasDefinition('serializer')); } - public function testPropertyInfoDisabled() - { - $container = $this->createContainerFromFile('default_config'); - $this->assertFalse($container->has('property_info')); - } - public function testPropertyInfoEnabled() { $container = $this->createContainerFromFile('property_info'); From c09ca94a284817b2876f2e5b2d7ead002dd925a1 Mon Sep 17 00:00:00 2001 From: DonCallisto Date: Wed, 30 May 2018 11:57:41 +0200 Subject: [PATCH 042/938] Update UPGRADE-4.0.md This https://github.com/symfony/symfony/blob/3.4/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php#L127 was not reported. --- UPGRADE-3.4.md | 3 +++ UPGRADE-4.0.md | 3 +++ src/Symfony/Component/Security/CHANGELOG.md | 2 ++ 3 files changed, 8 insertions(+) diff --git a/UPGRADE-3.4.md b/UPGRADE-3.4.md index 765e5cf467637..69dc6b000a945 100644 --- a/UPGRADE-3.4.md +++ b/UPGRADE-3.4.md @@ -344,6 +344,9 @@ Security * The `GuardAuthenticatorInterface` has been deprecated and will be removed in 4.0. Use `AuthenticatorInterface` instead. + + * When extending `AbstractGuardAuthenticator` it's deprecated to return `null` from `getCredentials()`. + Return `false` from `supports()` if no credentials available. SecurityBundle -------------- diff --git a/UPGRADE-4.0.md b/UPGRADE-4.0.md index 14dc6f07a6f8d..0e9e69ff80dcd 100644 --- a/UPGRADE-4.0.md +++ b/UPGRADE-4.0.md @@ -758,6 +758,9 @@ Security * The `GuardAuthenticatorInterface` interface has been removed. Use `AuthenticatorInterface` instead. + + * When extending `AbstractGuardAuthenticator` getCredentials() cannot return + `null` anymore, return false from `supports()` if no credentials available instead. SecurityBundle -------------- diff --git a/src/Symfony/Component/Security/CHANGELOG.md b/src/Symfony/Component/Security/CHANGELOG.md index bd1b7b59eb793..7ef21c03311a8 100644 --- a/src/Symfony/Component/Security/CHANGELOG.md +++ b/src/Symfony/Component/Security/CHANGELOG.md @@ -16,6 +16,8 @@ CHANGELOG * deprecated HTTP digest authentication * Added a new password encoder for the Argon2i hashing algorithm * deprecated `GuardAuthenticatorInterface` in favor of `AuthenticatorInterface` + * deprecated to return `null` from `getCredentials()` in classes that extend + `AbstractGuardAuthenticator`. Return `false` from `supports()` instead. 3.3.0 ----- From efe9beb1863f6a80fb975200c8dd6227d8f2befc Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 31 May 2018 12:02:37 +0200 Subject: [PATCH 043/938] [HttpKernel] Fix restoring trusted proxies in tests --- .../Tests/Processor/WebProcessorTest.php | 2 ++ .../HttpFoundation/Tests/RequestTest.php | 11 +------- .../ValidateRequestListenerTest.php | 5 ++++ .../Fragment/InlineFragmentRendererTest.php | 28 ++++++++++++++++--- .../Tests/HttpCache/HttpCacheTest.php | 2 ++ .../HttpKernel/Tests/HttpKernelTest.php | 2 ++ 6 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/Symfony/Bridge/Monolog/Tests/Processor/WebProcessorTest.php b/src/Symfony/Bridge/Monolog/Tests/Processor/WebProcessorTest.php index 51bddd1d9828c..6ce418d317319 100644 --- a/src/Symfony/Bridge/Monolog/Tests/Processor/WebProcessorTest.php +++ b/src/Symfony/Bridge/Monolog/Tests/Processor/WebProcessorTest.php @@ -49,6 +49,8 @@ public function testUseRequestClientIp() $this->assertEquals($server['REQUEST_METHOD'], $record['extra']['http_method']); $this->assertEquals($server['SERVER_NAME'], $record['extra']['server']); $this->assertEquals($server['HTTP_REFERER'], $record['extra']['referrer']); + + Request::setTrustedProxies(array()); } public function testCanBeConstructedWithExtraFields() diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php index 0080cf8ac530a..3b65bcc99c25d 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php @@ -20,6 +20,7 @@ class RequestTest extends TestCase { protected function tearDown() { + Request::setTrustedProxies(array()); Request::setTrustedHosts(array()); } @@ -750,8 +751,6 @@ public function testGetPort() )); $port = $request->getPort(); $this->assertEquals(80, $port, 'With only PROTO set and value is not recognized, getPort() defaults to 80.'); - - Request::setTrustedProxies(array()); } /** @@ -827,8 +826,6 @@ public function testGetClientIp($expected, $remoteAddr, $httpForwardedFor, $trus $request = $this->getRequestInstanceForClientIpTests($remoteAddr, $httpForwardedFor, $trustedProxies); $this->assertEquals($expected[0], $request->getClientIp()); - - Request::setTrustedProxies(array()); } /** @@ -839,8 +836,6 @@ public function testGetClientIps($expected, $remoteAddr, $httpForwardedFor, $tru $request = $this->getRequestInstanceForClientIpTests($remoteAddr, $httpForwardedFor, $trustedProxies); $this->assertEquals($expected, $request->getClientIps()); - - Request::setTrustedProxies(array()); } /** @@ -851,8 +846,6 @@ public function testGetClientIpsForwarded($expected, $remoteAddr, $httpForwarded $request = $this->getRequestInstanceForClientIpsForwardedTests($remoteAddr, $httpForwarded, $trustedProxies); $this->assertEquals($expected, $request->getClientIps()); - - Request::setTrustedProxies(array()); } public function getClientIpsForwardedProvider() @@ -975,8 +968,6 @@ public function testGetClientIpsWithAgreeingHeaders($httpForwarded, $httpXForwar $clientIps = $request->getClientIps(); - Request::setTrustedProxies(array()); - $this->assertSame($expectedIps, $clientIps); } diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/ValidateRequestListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/ValidateRequestListenerTest.php index 55dc59e13f716..388f1b814bdf6 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/ValidateRequestListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/ValidateRequestListenerTest.php @@ -21,6 +21,11 @@ class ValidateRequestListenerTest extends TestCase { + protected function tearDown() + { + Request::setTrustedProxies(array()); + } + /** * @expectedException \Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException */ diff --git a/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php b/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php index 6ed3c86537602..998b372181bb1 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php @@ -56,7 +56,6 @@ public function testRenderWithObjectsAsAttributes() $subRequest->attributes->replace(array('object' => $object, '_format' => 'html', '_controller' => 'main_controller', '_locale' => 'en')); $subRequest->headers->set('x-forwarded-for', array('127.0.0.1')); $subRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1'); - $subRequest->server->set('REMOTE_ADDR', '1.1.1.1'); $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($subRequest)); @@ -85,7 +84,7 @@ public function testRenderWithTrustedHeaderDisabled() { Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, ''); - $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest(Request::create('/', 'GET', array(), array(), array(), array('REMOTE_ADDR' => '1.1.1.1')))); + $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest(Request::create('/'))); $this->assertSame('foo', $strategy->render('/', Request::create('/'))->getContent()); } @@ -169,7 +168,6 @@ public function testESIHeaderIsKeptInSubrequest() { $expectedSubRequest = Request::create('/'); $expectedSubRequest->headers->set('Surrogate-Capability', 'abc="ESI/1.0"'); - $expectedSubRequest->server->set('REMOTE_ADDR', '1.1.1.1'); if (Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP)) { $expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1')); @@ -195,7 +193,7 @@ public function testESIHeaderIsKeptInSubrequestWithTrustedHeaderDisabled() public function testHeadersPossiblyResultingIn304AreNotAssignedToSubrequest() { - $expectedSubRequest = Request::create('/', 'GET', array(), array(), array(), array('REMOTE_ADDR' => '1.1.1.1')); + $expectedSubRequest = Request::create('/'); if (Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP)) { $expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1')); $expectedSubRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1'); @@ -206,6 +204,28 @@ public function testHeadersPossiblyResultingIn304AreNotAssignedToSubrequest() $strategy->render('/', $request); } + public function testFirstTrustedProxyIsSetAsRemote() + { + $expectedSubRequest = Request::create('/'); + $expectedSubRequest->headers->set('Surrogate-Capability', 'abc="ESI/1.0"'); + $expectedSubRequest->server->set('REMOTE_ADDR', '1.1.1.1'); + + if (Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP)) { + $expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1')); + $expectedSubRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1'); + } + + Request::setTrustedProxies(array('1.1.1.1')); + + $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($expectedSubRequest)); + + $request = Request::create('/'); + $request->headers->set('Surrogate-Capability', 'abc="ESI/1.0"'); + $strategy->render('/', $request); + + Request::setTrustedProxies(array()); + } + /** * Creates a Kernel expecting a request equals to $request * Allows delta in comparison in case REQUEST_TIME changed by 1 second. diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php index d6902f4880abf..41c2d57833769 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php @@ -1315,6 +1315,8 @@ public function testHttpCacheIsSetAsATrustedProxy(array $existing, array $expect $this->request('GET', '/', array('REMOTE_ADDR' => '10.0.0.1')); $this->assertEquals($expected, Request::getTrustedProxies()); + + Request::setTrustedProxies(array()); } public function getTrustedProxyData() diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php index 448dc10cf1185..22d9907e5cd9e 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php @@ -291,6 +291,8 @@ public function testInconsistentClientIpsOnMasterRequests() $request->headers->set('X_FORWARDED_FOR', '3.3.3.3'); $kernel->handle($request, $kernel::MASTER_REQUEST, false); + + Request::setTrustedProxies(array()); } protected function getResolver($controller = null) From f8746ce8bd7d3707c291fb7bf175c5c880ce3cdb Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Wed, 9 May 2018 12:49:31 -0400 Subject: [PATCH 044/938] Add ability to deprecate options --- .../Component/OptionsResolver/CHANGELOG.md | 5 + .../OptionsResolver/OptionsResolver.php | 71 +++++++ .../Tests/OptionsResolverTest.php | 181 ++++++++++++++++++ 3 files changed, 257 insertions(+) diff --git a/src/Symfony/Component/OptionsResolver/CHANGELOG.md b/src/Symfony/Component/OptionsResolver/CHANGELOG.md index 6e9d49fb61d75..ec0084acd15b0 100644 --- a/src/Symfony/Component/OptionsResolver/CHANGELOG.md +++ b/src/Symfony/Component/OptionsResolver/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +4.2.0 +----- + + * added `setDeprecated` and `isDeprecated` methods + 3.4.0 ----- diff --git a/src/Symfony/Component/OptionsResolver/OptionsResolver.php b/src/Symfony/Component/OptionsResolver/OptionsResolver.php index 68b4154b10da2..d42c567ab74e9 100644 --- a/src/Symfony/Component/OptionsResolver/OptionsResolver.php +++ b/src/Symfony/Component/OptionsResolver/OptionsResolver.php @@ -12,6 +12,7 @@ namespace Symfony\Component\OptionsResolver; use Symfony\Component\OptionsResolver\Exception\AccessException; +use Symfony\Component\OptionsResolver\Exception\InvalidArgumentException; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; use Symfony\Component\OptionsResolver\Exception\MissingOptionsException; use Symfony\Component\OptionsResolver\Exception\NoSuchOptionException; @@ -75,6 +76,11 @@ class OptionsResolver implements Options */ private $calling = array(); + /** + * A list of deprecated options. + */ + private $deprecated = array(); + /** * Whether the instance is locked for reading. * @@ -348,6 +354,57 @@ public function getDefinedOptions() return array_keys($this->defined); } + /** + * Deprecates an option, allowed types or values. + * + * Instead of passing the message, you may also pass a closure with the + * following signature: + * + * function ($value) { + * // ... + * } + * + * The closure receives the value as argument and should return a string. + * Returns an empty string to ignore the option deprecation. + * + * The closure is invoked when {@link resolve()} is called. The parameter + * passed to the closure is the value of the option after validating it + * and before normalizing it. + * + * @param string|\Closure $deprecationMessage + */ + public function setDeprecated(string $option, $deprecationMessage = 'The option "%name%" is deprecated.'): self + { + if ($this->locked) { + throw new AccessException('Options cannot be deprecated from a lazy option or normalizer.'); + } + + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist, defined options are: "%s".', $option, implode('", "', array_keys($this->defined)))); + } + + if (!\is_string($deprecationMessage) && !$deprecationMessage instanceof \Closure) { + throw new InvalidArgumentException(sprintf('Invalid type for deprecation message argument, expected string or \Closure, but got "%s".', \gettype($deprecationMessage))); + } + + // ignore if empty string + if ('' === $deprecationMessage) { + return $this; + } + + $this->deprecated[$option] = $deprecationMessage; + + // Make sure the option is processed + unset($this->resolved[$option]); + + return $this; + } + + public function isDeprecated(string $option): bool + { + return isset($this->deprecated[$option]); + } + /** * Sets the normalizer for an option. * @@ -620,6 +677,7 @@ public function clear() $this->normalizers = array(); $this->allowedTypes = array(); $this->allowedValues = array(); + $this->deprecated = array(); return $this; } @@ -836,6 +894,19 @@ public function offsetGet($option) } } + // Check whether the option is deprecated + if (isset($this->deprecated[$option])) { + $deprecationMessage = $this->deprecated[$option]; + + if ($deprecationMessage instanceof \Closure && !\is_string($deprecationMessage = $deprecationMessage($value))) { + throw new InvalidOptionsException(sprintf('Invalid type for deprecation message, expected string but got "%s", returns an empty string to ignore.', \gettype($deprecationMessage))); + } + + if ('' !== $deprecationMessage) { + @trigger_error(strtr($deprecationMessage, array('%name%' => $option)), E_USER_DEPRECATED); + } + } + // Normalize the validated option if (isset($this->normalizers[$option])) { // If the closure is already being called, we have a cyclic diff --git a/src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php b/src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php index 12dc77b3c6b1c..8f3ab6370dc4c 100644 --- a/src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php +++ b/src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php @@ -450,6 +450,187 @@ public function testClearedOptionsAreNotDefined() $this->assertFalse($this->resolver->isDefined('foo')); } + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testFailIfSetDeprecatedFromLazyOption() + { + $this->resolver + ->setDefault('bar', 'baz') + ->setDefault('foo', function (Options $options) { + $options->setDeprecated('bar'); + }) + ->resolve() + ; + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException + */ + public function testSetDeprecatedFailsIfUnknownOption() + { + $this->resolver->setDeprecated('foo'); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidArgumentException + * @expectedExceptionMessage Invalid type for deprecation message argument, expected string or \Closure, but got "boolean". + */ + public function testSetDeprecatedFailsIfInvalidDeprecationMessageType() + { + $this->resolver + ->setDefined('foo') + ->setDeprecated('foo', true) + ; + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidArgumentException + * @expectedExceptionMessage Invalid type for deprecation message, expected string but got "boolean", returns an empty string to ignore. + */ + public function testLazyDeprecationFailsIfInvalidDeprecationMessageType() + { + $this->resolver + ->setDefault('foo', true) + ->setDeprecated('foo', function ($value) { + return false; + }) + ; + $this->resolver->resolve(); + } + + public function testIsDeprecated() + { + $this->resolver + ->setDefined('foo') + ->setDeprecated('foo') + ; + $this->assertTrue($this->resolver->isDeprecated('foo')); + } + + public function testIsNotDeprecatedIfEmptyString() + { + $this->resolver + ->setDefined('foo') + ->setDeprecated('foo', '') + ; + $this->assertFalse($this->resolver->isDeprecated('foo')); + } + + /** + * @dataProvider provideDeprecationData + */ + public function testDeprecationMessages(\Closure $configureOptions, array $options, ?array $expectedError) + { + error_clear_last(); + set_error_handler(function () { return false; }); + $e = error_reporting(0); + + $configureOptions($this->resolver); + $this->resolver->resolve($options); + + error_reporting($e); + restore_error_handler(); + + $lastError = error_get_last(); + unset($lastError['file'], $lastError['line']); + + $this->assertSame($expectedError, $lastError); + } + + public function provideDeprecationData() + { + yield 'It deprecates an option with default message' => array( + function (OptionsResolver $resolver) { + $resolver + ->setDefined(array('foo', 'bar')) + ->setDeprecated('foo') + ; + }, + array('foo' => 'baz'), + array( + 'type' => E_USER_DEPRECATED, + 'message' => 'The option "foo" is deprecated.', + ), + ); + + yield 'It deprecates an option with custom message' => array( + function (OptionsResolver $resolver) { + $resolver + ->setDefined('foo') + ->setDefault('bar', function (Options $options) { + return $options['foo']; + }) + ->setDeprecated('foo', 'The option "foo" is deprecated, use "bar" option instead.') + ; + }, + array('foo' => 'baz'), + array( + 'type' => E_USER_DEPRECATED, + 'message' => 'The option "foo" is deprecated, use "bar" option instead.', + ), + ); + + yield 'It deprecates a missing option with default value' => array( + function (OptionsResolver $resolver) { + $resolver + ->setDefaults(array('foo' => null, 'bar' => null)) + ->setDeprecated('foo') + ; + }, + array('bar' => 'baz'), + array( + 'type' => E_USER_DEPRECATED, + 'message' => 'The option "foo" is deprecated.', + ), + ); + + yield 'It deprecates allowed type and value' => array( + function (OptionsResolver $resolver) { + $resolver + ->setDefault('foo', null) + ->setAllowedTypes('foo', array('null', 'string', \stdClass::class)) + ->setDeprecated('foo', function ($value) { + if ($value instanceof \stdClass) { + return sprintf('Passing an instance of "%s" to option "foo" is deprecated, pass its FQCN instead.', \stdClass::class); + } + + return ''; + }) + ; + }, + array('foo' => new \stdClass()), + array( + 'type' => E_USER_DEPRECATED, + 'message' => 'Passing an instance of "stdClass" to option "foo" is deprecated, pass its FQCN instead.', + ), + ); + + yield 'It ignores deprecation for missing option without default value' => array( + function (OptionsResolver $resolver) { + $resolver + ->setDefined(array('foo', 'bar')) + ->setDeprecated('foo') + ; + }, + array('bar' => 'baz'), + null, + ); + + yield 'It ignores deprecation if closure returns an empty string' => array( + function (OptionsResolver $resolver) { + $resolver + ->setDefault('foo', null) + ->setDeprecated('foo', function ($value) { + return ''; + }) + ; + }, + array('foo' => Bar::class), + null, + ); + } + /** * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException */ From 68994a662e72fe22542dfdea7789ea04910cd4d1 Mon Sep 17 00:00:00 2001 From: Titouan Galopin Date: Thu, 31 May 2018 18:52:49 +0200 Subject: [PATCH 045/938] [FrameworkBundle][TwigBridge] Fix BC break from strong dependency on CSRF token storage --- .../Bridge/Twig/Extension/CsrfExtension.php | 16 ++------- .../Bridge/Twig/Extension/CsrfRuntime.php | 33 +++++++++++++++++++ .../Resources/config/security_csrf.xml | 6 +++- 3 files changed, 40 insertions(+), 15 deletions(-) create mode 100644 src/Symfony/Bridge/Twig/Extension/CsrfRuntime.php diff --git a/src/Symfony/Bridge/Twig/Extension/CsrfExtension.php b/src/Symfony/Bridge/Twig/Extension/CsrfExtension.php index 97f3484a29776..2f061acfd3ae1 100644 --- a/src/Symfony/Bridge/Twig/Extension/CsrfExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/CsrfExtension.php @@ -11,34 +11,22 @@ namespace Symfony\Bridge\Twig\Extension; -use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Twig\Extension\AbstractExtension; use Twig\TwigFunction; /** * @author Christian Flothmann + * @author Titouan Galopin */ class CsrfExtension extends AbstractExtension { - private $csrfTokenManager; - - public function __construct(CsrfTokenManagerInterface $csrfTokenManager) - { - $this->csrfTokenManager = $csrfTokenManager; - } - /** * {@inheritdoc} */ public function getFunctions(): array { return array( - new TwigFunction('csrf_token', array($this, 'getCsrfToken')), + new TwigFunction('csrf_token', array(CsrfRuntime::class, 'getCsrfToken')), ); } - - public function getCsrfToken(string $tokenId): string - { - return $this->csrfTokenManager->getToken($tokenId)->getValue(); - } } diff --git a/src/Symfony/Bridge/Twig/Extension/CsrfRuntime.php b/src/Symfony/Bridge/Twig/Extension/CsrfRuntime.php new file mode 100644 index 0000000000000..1b2910c830cba --- /dev/null +++ b/src/Symfony/Bridge/Twig/Extension/CsrfRuntime.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; + +/** + * @author Christian Flothmann + * @author Titouan Galopin + */ +class CsrfRuntime +{ + private $csrfTokenManager; + + public function __construct(CsrfTokenManagerInterface $csrfTokenManager) + { + $this->csrfTokenManager = $csrfTokenManager; + } + + public function getCsrfToken(string $tokenId): string + { + return $this->csrfTokenManager->getToken($tokenId)->getValue(); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/security_csrf.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/security_csrf.xml index e7bcec1c5fa33..d13e3c7fab738 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/security_csrf.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/security_csrf.xml @@ -22,9 +22,13 @@ + + + + + - From 146e01cb4442fbcb3b05dd8ae2569d9894374044 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 1 Jun 2018 14:37:16 +0200 Subject: [PATCH 046/938] [HttpKernel] fix session tracking in surrogate master requests --- .../HttpFoundation/Session/Session.php | 19 +++++++--- .../Session/SessionBagProxy.php | 14 +++++-- .../EventListener/AbstractSessionListener.php | 17 ++++++++- .../EventListener/SessionListenerTest.php | 38 ++++++++++++++++++- .../Component/HttpKernel/composer.json | 2 +- 5 files changed, 77 insertions(+), 13 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/Session/Session.php b/src/Symfony/Component/HttpFoundation/Session/Session.php index a46cffbb8dbd6..f0379c1697b82 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Session.php +++ b/src/Symfony/Component/HttpFoundation/Session/Session.php @@ -29,7 +29,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable private $flashName; private $attributeName; private $data = array(); - private $hasBeenStarted; + private $usageIndex = 0; /** * @param SessionStorageInterface $storage A SessionStorageInterface instance @@ -54,6 +54,8 @@ public function __construct(SessionStorageInterface $storage = null, AttributeBa */ public function start() { + ++$this->usageIndex; + return $this->storage->start(); } @@ -142,13 +144,13 @@ public function count() } /** - * @return bool + * @return int * * @internal */ - public function hasBeenStarted() + public function getUsageIndex() { - return $this->hasBeenStarted; + return $this->usageIndex; } /** @@ -158,6 +160,7 @@ public function hasBeenStarted() */ public function isEmpty() { + ++$this->usageIndex; foreach ($this->data as &$data) { if (!empty($data)) { return false; @@ -182,6 +185,8 @@ public function invalidate($lifetime = null) */ public function migrate($destroy = false, $lifetime = null) { + ++$this->usageIndex; + return $this->storage->regenerate($destroy, $lifetime); } @@ -190,6 +195,8 @@ public function migrate($destroy = false, $lifetime = null) */ public function save() { + ++$this->usageIndex; + $this->storage->save(); } @@ -230,6 +237,8 @@ public function setName($name) */ public function getMetadataBag() { + ++$this->usageIndex; + return $this->storage->getMetadataBag(); } @@ -238,7 +247,7 @@ public function getMetadataBag() */ public function registerBag(SessionBagInterface $bag) { - $this->storage->registerBag(new SessionBagProxy($bag, $this->data, $this->hasBeenStarted)); + $this->storage->registerBag(new SessionBagProxy($bag, $this->data, $this->usageIndex)); } /** diff --git a/src/Symfony/Component/HttpFoundation/Session/SessionBagProxy.php b/src/Symfony/Component/HttpFoundation/Session/SessionBagProxy.php index 307836d5f9461..88005ee092e2a 100644 --- a/src/Symfony/Component/HttpFoundation/Session/SessionBagProxy.php +++ b/src/Symfony/Component/HttpFoundation/Session/SessionBagProxy.php @@ -20,13 +20,13 @@ final class SessionBagProxy implements SessionBagInterface { private $bag; private $data; - private $hasBeenStarted; + private $usageIndex; - public function __construct(SessionBagInterface $bag, array &$data, &$hasBeenStarted) + public function __construct(SessionBagInterface $bag, array &$data, &$usageIndex) { $this->bag = $bag; $this->data = &$data; - $this->hasBeenStarted = &$hasBeenStarted; + $this->usageIndex = &$usageIndex; } /** @@ -34,6 +34,8 @@ public function __construct(SessionBagInterface $bag, array &$data, &$hasBeenSta */ public function getBag() { + ++$this->usageIndex; + return $this->bag; } @@ -42,6 +44,8 @@ public function getBag() */ public function isEmpty() { + ++$this->usageIndex; + return empty($this->data[$this->bag->getStorageKey()]); } @@ -58,7 +62,7 @@ public function getName() */ public function initialize(array &$array) { - $this->hasBeenStarted = true; + ++$this->usageIndex; $this->data[$this->bag->getStorageKey()] = &$array; $this->bag->initialize($array); @@ -77,6 +81,8 @@ public function getStorageKey() */ public function clear() { + ++$this->usageIndex; + return $this->bag->clear(); } } diff --git a/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php b/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php index dff29ee80b418..a54ca62d87b6c 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php @@ -14,6 +14,7 @@ use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\EventDispatcher\EventSubscriberInterface; @@ -25,6 +26,8 @@ */ abstract class AbstractSessionListener implements EventSubscriberInterface { + private $sessionUsageStack = array(); + public function onKernelRequest(GetResponseEvent $event) { if (!$event->isMasterRequest()) { @@ -33,6 +36,7 @@ public function onKernelRequest(GetResponseEvent $event) $request = $event->getRequest(); $session = $this->getSession(); + $this->sessionUsageStack[] = $session instanceof Session ? $session->getUsageIndex() : null; if (null === $session || $request->hasSession()) { return; } @@ -50,7 +54,7 @@ public function onKernelResponse(FilterResponseEvent $event) return; } - if ($session->isStarted() || ($session instanceof Session && $session->hasBeenStarted())) { + if ($session instanceof Session ? $session->getUsageIndex() !== end($this->sessionUsageStack) : $session->isStarted()) { $event->getResponse() ->setPrivate() ->setMaxAge(0) @@ -58,12 +62,23 @@ public function onKernelResponse(FilterResponseEvent $event) } } + /** + * @internal + */ + public function onFinishRequest(FinishRequestEvent $event) + { + if ($event->isMasterRequest()) { + array_pop($this->sessionUsageStack); + } + } + public static function getSubscribedEvents() { return array( KernelEvents::REQUEST => array('onKernelRequest', 128), // low priority to come after regular response listeners, same as SaveSessionListener KernelEvents::RESPONSE => array('onKernelResponse', -1000), + KernelEvents::FINISH_REQUEST => array('onFinishRequest'), ); } diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/SessionListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/SessionListenerTest.php index 34598363c8914..a6416e7f693df 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/SessionListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/SessionListenerTest.php @@ -18,6 +18,7 @@ use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; use Symfony\Component\HttpKernel\EventListener\AbstractSessionListener; use Symfony\Component\HttpKernel\EventListener\SessionListener; use Symfony\Component\HttpKernel\HttpKernelInterface; @@ -58,8 +59,7 @@ public function testSessionIsSet() public function testResponseIsPrivate() { $session = $this->getMockBuilder(Session::class)->disableOriginalConstructor()->getMock(); - $session->expects($this->once())->method('isStarted')->willReturn(false); - $session->expects($this->once())->method('hasBeenStarted')->willReturn(true); + $session->expects($this->exactly(2))->method('getUsageIndex')->will($this->onConsecutiveCalls(0, 1)); $container = new Container(); $container->set('session', $session); @@ -76,4 +76,38 @@ public function testResponseIsPrivate() $this->assertTrue($response->headers->hasCacheControlDirective('must-revalidate')); $this->assertSame('0', $response->headers->getCacheControlDirective('max-age')); } + + public function testSurrogateMasterRequestIsPublic() + { + $session = $this->getMockBuilder(Session::class)->disableOriginalConstructor()->getMock(); + $session->expects($this->exactly(4))->method('getUsageIndex')->will($this->onConsecutiveCalls(0, 1, 1, 1)); + + $container = new Container(); + $container->set('session', $session); + + $listener = new SessionListener($container); + $kernel = $this->getMockBuilder(HttpKernelInterface::class)->disableOriginalConstructor()->getMock(); + + $request = new Request(); + $response = new Response(); + $response->setCache(array('public' => true, 'max_age' => '30')); + $listener->onKernelRequest(new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST)); + $this->assertTrue($request->hasSession()); + + $subRequest = clone $request; + $this->assertSame($request->getSession(), $subRequest->getSession()); + $listener->onKernelRequest(new GetResponseEvent($kernel, $subRequest, HttpKernelInterface::MASTER_REQUEST)); + $listener->onKernelResponse(new FilterResponseEvent($kernel, $subRequest, HttpKernelInterface::MASTER_REQUEST, $response)); + $listener->onFinishRequest(new FinishRequestEvent($kernel, $subRequest, HttpKernelInterface::MASTER_REQUEST)); + + $this->assertFalse($response->headers->hasCacheControlDirective('private')); + $this->assertFalse($response->headers->hasCacheControlDirective('must-revalidate')); + $this->assertSame('30', $response->headers->getCacheControlDirective('max-age')); + + $listener->onKernelResponse(new FilterResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST, $response)); + + $this->assertTrue($response->headers->hasCacheControlDirective('private')); + $this->assertTrue($response->headers->hasCacheControlDirective('must-revalidate')); + $this->assertSame('0', $response->headers->getCacheControlDirective('max-age')); + } } diff --git a/src/Symfony/Component/HttpKernel/composer.json b/src/Symfony/Component/HttpKernel/composer.json index 17c0544c62560..585ab5b37dbaa 100644 --- a/src/Symfony/Component/HttpKernel/composer.json +++ b/src/Symfony/Component/HttpKernel/composer.json @@ -18,7 +18,7 @@ "require": { "php": "^5.5.9|>=7.0.8", "symfony/event-dispatcher": "~2.8|~3.0|~4.0", - "symfony/http-foundation": "^3.4.4|^4.0.4", + "symfony/http-foundation": "~3.4.12|~4.0.12|^4.1.1", "symfony/debug": "~2.8|~3.0|~4.0", "symfony/polyfill-ctype": "~1.8", "psr/log": "~1.0" From 11a14e001cde9c57a0cbd7df61ab6f7ba51ab596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Ostroluck=C3=BD?= Date: Sat, 2 Jun 2018 19:48:17 +0200 Subject: [PATCH 047/938] simple-phpunit: remove outdated appveryor workaround --- src/Symfony/Bridge/PhpUnit/bin/simple-phpunit | 24 +------------------ 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit index 59c7a1fe3565e..552d5abb36b5e 100755 --- a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit +++ b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit @@ -188,15 +188,6 @@ if ($components) { } } - // Fixes for colors support on appveyor - // See https://github.com/appveyor/ci/issues/373 - $colorFixes = array( - array("S\033[0m\033[0m\033[36m\033[1mS", "E\033[0m\033[0m\033[31m\033[1mE", "I\033[0m\033[0m\033[33m\033[1mI", "F\033[0m\033[0m\033[41m\033[37mF"), - array("SS", "EE", "II", "FF"), - ); - $colorFixes[0] = array_merge($colorFixes[0], $colorFixes[0]); - $colorFixes[1] = array_merge($colorFixes[1], $colorFixes[1]); - while ($runningProcs) { usleep(300000); $terminatedProcs = array(); @@ -212,20 +203,7 @@ if ($components) { foreach ($terminatedProcs as $component => $procStatus) { foreach (array('out', 'err') as $file) { $file = "$component/phpunit.std$file"; - - if ('\\' === DIRECTORY_SEPARATOR) { - $h = fopen($file, 'rb'); - while (false !== $line = fgets($h)) { - echo str_replace($colorFixes[0], $colorFixes[1], preg_replace( - '/(\033\[[0-9]++);([0-9]++m)(?:(.)(\033\[0m))?/', - "$1m\033[$2$3$4$4", - $line - )); - } - fclose($h); - } else { - readfile($file); - } + readfile($file); unlink($file); } From a0015a18ecee87ed30dea6e34358e9ce9792e2e0 Mon Sep 17 00:00:00 2001 From: Roland Franssen Date: Fri, 1 Jun 2018 19:28:37 +0200 Subject: [PATCH 048/938] [DI] Remove default env type check on validate --- .../Component/Config/Definition/BaseNode.php | 28 +++++-------------- .../Compiler/ValidateEnvPlaceholdersPass.php | 22 +++++++-------- .../ValidateEnvPlaceholdersPassTest.php | 28 +++++++++---------- .../DependencyInjection/composer.json | 2 +- 4 files changed, 32 insertions(+), 48 deletions(-) diff --git a/src/Symfony/Component/Config/Definition/BaseNode.php b/src/Symfony/Component/Config/Definition/BaseNode.php index a49e0b0bdc86e..00aa212bdc404 100644 --- a/src/Symfony/Component/Config/Definition/BaseNode.php +++ b/src/Symfony/Component/Config/Definition/BaseNode.php @@ -507,35 +507,21 @@ private static function resolvePlaceholderValue($value) return $value; } - private static function getType($value): string - { - switch ($type = \gettype($value)) { - case 'boolean': - return 'bool'; - case 'double': - return 'float'; - case 'integer': - return 'int'; - } - - return $type; - } - private function doValidateType($value): void { - if (null === $this->handlingPlaceholder || null === $value) { - $this->validateType($value); - - return; - } - - if (!$this->allowPlaceholders()) { + if (null !== $this->handlingPlaceholder && !$this->allowPlaceholders()) { $e = new InvalidTypeException(sprintf('A dynamic value is not compatible with a "%s" node type at path "%s".', get_class($this), $this->getPath())); $e->setPath($this->getPath()); throw $e; } + if (null === $this->handlingPlaceholder || null === $value) { + $this->validateType($value); + + return; + } + $knownTypes = array_keys(self::$placeholders[$this->handlingPlaceholder]); $validTypes = $this->getValidPlaceholderTypes(); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ValidateEnvPlaceholdersPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ValidateEnvPlaceholdersPass.php index 1fe57f9db6cfb..09c83ce3bf2ee 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ValidateEnvPlaceholdersPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ValidateEnvPlaceholdersPass.php @@ -14,7 +14,6 @@ use Symfony\Component\Config\Definition\BaseNode; use Symfony\Component\Config\Definition\Processor; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface; use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; @@ -42,21 +41,20 @@ public function process(ContainerBuilder $container) return; } - $defaultBag = new ParameterBag($container->getParameterBag()->all()); + $defaultBag = new ParameterBag($resolvingBag->all()); $envTypes = $resolvingBag->getProvidedTypes(); try { foreach ($resolvingBag->getEnvPlaceholders() + $resolvingBag->getUnusedEnvPlaceholders() as $env => $placeholders) { - $prefix = (false === $i = strpos($env, ':')) ? 'string' : substr($env, 0, $i); - $types = $envTypes[$prefix] ?? array('string'); - $default = ($hasEnv = (false === $i && $defaultBag->has("env($env)"))) ? $defaultBag->get("env($env)") : null; - - if (null !== $default && !in_array($type = self::getType($default), $types, true)) { - throw new LogicException(sprintf('Invalid type for env parameter "env(%s)". Expected "%s", but got "%s".', $env, implode('", "', $types), $type)); - } - $values = array(); - foreach ($types as $type) { - $values[$type] = $hasEnv ? $default : self::$typeFixtures[$type] ?? null; + if (false === $i = strpos($env, ':')) { + $default = $defaultBag->has("env($env)") ? $defaultBag->get("env($env)") : null; + $defaultType = null !== $default ? self::getType($default) : 'string'; + $values[$defaultType] = $default; + } else { + $prefix = substr($env, 0, $i); + foreach ($envTypes[$prefix] ?? array('string') as $type) { + $values[$type] = self::$typeFixtures[$type] ?? null; + } } foreach ($placeholders as $placeholder) { BaseNode::setPlaceholder($placeholder, $values); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ValidateEnvPlaceholdersPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ValidateEnvPlaceholdersPassTest.php index bd554cd285901..9534b11a90bb2 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ValidateEnvPlaceholdersPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ValidateEnvPlaceholdersPassTest.php @@ -22,31 +22,31 @@ class ValidateEnvPlaceholdersPassTest extends TestCase { - /** - * @expectedException \Symfony\Component\DependencyInjection\Exception\LogicException - * @expectedExceptionMessage Invalid type for env parameter "env(FOO)". Expected "string", but got "bool". - */ - public function testDefaultEnvIsValidatedByType() + public function testEnvsAreValidatedInConfig() { $container = new ContainerBuilder(); - $container->setParameter('env(FOO)', true); - $container->registerExtension(new EnvExtension()); - $container->prependExtensionConfig('env_extension', array( - 'scalar_node' => '%env(FOO)%', + $container->setParameter('env(NULLED)', null); + $container->setParameter('env(FLOATISH)', 3.2); + $container->registerExtension($ext = new EnvExtension()); + $container->prependExtensionConfig('env_extension', $expected = array( + 'scalar_node' => '%env(NULLED)%', + 'scalar_node_not_empty' => '%env(FLOATISH)%', + 'int_node' => '%env(int:FOO)%', + 'float_node' => '%env(float:BAR)%', )); $this->doProcess($container); + + $this->assertSame($expected, $container->resolveEnvPlaceholders($ext->getConfig())); } - public function testEnvsAreValidatedInConfig() + public function testDefaultEnvWithoutPrefixIsValidatedInConfig() { $container = new ContainerBuilder(); - $container->setParameter('env(NULLED)', null); + $container->setParameter('env(FLOATISH)', 3.2); $container->registerExtension($ext = new EnvExtension()); $container->prependExtensionConfig('env_extension', $expected = array( - 'scalar_node' => '%env(NULLED)%', - 'int_node' => '%env(int:FOO)%', - 'float_node' => '%env(float:BAR)%', + 'float_node' => '%env(FLOATISH)%', )); $this->doProcess($container); diff --git a/src/Symfony/Component/DependencyInjection/composer.json b/src/Symfony/Component/DependencyInjection/composer.json index 8230395645c7c..12758018f22da 100644 --- a/src/Symfony/Component/DependencyInjection/composer.json +++ b/src/Symfony/Component/DependencyInjection/composer.json @@ -32,7 +32,7 @@ "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them" }, "conflict": { - "symfony/config": "<4.1", + "symfony/config": "<4.1.1", "symfony/finder": "<3.4", "symfony/proxy-manager-bridge": "<3.4", "symfony/yaml": "<3.4" From a8f41289c4b8b189a162d3c87987440b66beacf6 Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Thu, 31 May 2018 12:02:59 +0200 Subject: [PATCH 049/938] [DX] Improve exception message when AbstractController::getParameter fails --- .../Controller/AbstractController.php | 3 ++- .../Controller/AbstractControllerTest.php | 26 ++++++++++++++----- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php index b317786c68583..7286162c2e5ac 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php @@ -13,6 +13,7 @@ use Psr\Container\ContainerInterface; use Doctrine\Common\Persistence\ManagerRegistry; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface; use Symfony\Component\DependencyInjection\ServiceSubscriberInterface; use Symfony\Component\Form\FormFactoryInterface; @@ -64,7 +65,7 @@ public function setContainer(ContainerInterface $container) protected function getParameter(string $name) { if (!$this->container->has('parameter_bag')) { - throw new \LogicException('The "parameter_bag" service is not available. Try running "composer require dependency-injection:^4.1"'); + throw new ServiceNotFoundException('parameter_bag', null, null, array(), sprintf('The "%s::getParameter()" method is missing a parameter bag to work properly. Did you forget to register your controller as a service subscriber? This can be fixed either by using autoconfiguration or by manually wiring a "parameter_bag" in the service locator passed to the controller.', get_class($this))); } return $this->container->get('parameter_bag')->get($name); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php index 0ae42c14ce317..03b451528d48f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php @@ -52,20 +52,32 @@ public function testSubscribedServices() public function testGetParameter() { + if (!class_exists(ContainerBag::class)) { + $this->markTestSkipped('ContainerBag class does not exist'); + } + $container = new Container(new FrozenParameterBag(array('foo' => 'bar'))); + $container->set('parameter_bag', new ContainerBag($container)); $controller = $this->createController(); $controller->setContainer($container); - if (!class_exists(ContainerBag::class)) { - $this->expectException(\LogicException::class); - $this->expectExceptionMessage('The "parameter_bag" service is not available. Try running "composer require dependency-injection:^4.1"'); - } else { - $container->set('parameter_bag', new ContainerBag($container)); - } - $this->assertSame('bar', $controller->getParameter('foo')); } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException + * @expectedExceptionMessage TestAbstractController::getParameter()" method is missing a parameter bag + */ + public function testMissingParameterBag() + { + $container = new Container(); + + $controller = $this->createController(); + $controller->setContainer($container); + + $controller->getParameter('foo'); + } } class TestAbstractController extends AbstractController From e2f344fa322a0b3c3bb7d8cf67ab9ba7a615b750 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 1 Jun 2018 09:08:10 +0200 Subject: [PATCH 050/938] [FrameworkBundle] Deprecate auto-injection of the container in AbstractController instances --- src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md | 1 + .../Controller/ControllerResolver.php | 14 ++++++++++---- .../Tests/Controller/ControllerResolverTest.php | 8 ++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 139041aba81a8..fde20aaaba6e0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG ----- * Allowed configuring taggable cache pools via a new `framework.cache.pools.tags` option (bool|service-id) + * Deprecated auto-injection of the container in AbstractController instances, register them as service subscribers instead 4.1.0 ----- diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerResolver.php b/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerResolver.php index 27e714acc86e8..d8f2cd8e1af85 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerResolver.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerResolver.php @@ -51,16 +51,22 @@ protected function createController($controller) */ protected function instantiateController($class) { - return $this->configureController(parent::instantiateController($class)); + return $this->configureController(parent::instantiateController($class), $class); } - private function configureController($controller) + private function configureController($controller, string $class) { if ($controller instanceof ContainerAwareInterface) { $controller->setContainer($this->container); } - if ($controller instanceof AbstractController && null !== $previousContainer = $controller->setContainer($this->container)) { - $controller->setContainer($previousContainer); + if ($controller instanceof AbstractController) { + if (null === $previousContainer = $controller->setContainer($this->container)) { + @trigger_error(sprintf('Auto-injection of the container for "%s" is deprecated since Symfony 4.2. Configure it as a service instead.', $class), E_USER_DEPRECATED); + // To be uncommented on Symfony 5: + //throw new \LogicException(sprintf('"%s" has no container set, did you forget to define it as a service subscriber?', $class)); + } else { + $controller->setContainer($previousContainer); + } } return $controller; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerResolverTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerResolverTest.php index a4529a657c7f2..d5d35cf4e2855 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerResolverTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerResolverTest.php @@ -92,6 +92,10 @@ class_exists(AbstractControllerTest::class); $this->assertSame($container, $controller->getContainer()); } + /** + * @group legacy + * @expectedDeprecation Auto-injection of the container for "Symfony\Bundle\FrameworkBundle\Tests\Controller\TestAbstractController" is deprecated since Symfony 4.2. Configure it as a service instead. + */ public function testAbstractControllerGetsContainerWhenNotSet() { class_exists(AbstractControllerTest::class); @@ -110,6 +114,10 @@ class_exists(AbstractControllerTest::class); $this->assertSame($container, $controller->setContainer($container)); } + /** + * @group legacy + * @expectedDeprecation Auto-injection of the container for "Symfony\Bundle\FrameworkBundle\Tests\Controller\DummyController" is deprecated since Symfony 4.2. Configure it as a service instead. + */ public function testAbstractControllerServiceWithFcqnIdGetsContainerWhenNotSet() { class_exists(AbstractControllerTest::class); From e3aa90f852f69040be19da3d8729cdf02d238ec7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 4 Jun 2018 10:07:03 +0200 Subject: [PATCH 051/938] =?UTF-8?q?[BrowserKit]=20Fix=20a=20BC=20break=20i?= =?UTF-8?q?n=20Client=20affecting=20Panth=C3=A8re?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Symfony/Component/BrowserKit/Client.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/BrowserKit/Client.php b/src/Symfony/Component/BrowserKit/Client.php index 799b3579f0f69..d5909b7c80191 100644 --- a/src/Symfony/Component/BrowserKit/Client.php +++ b/src/Symfony/Component/BrowserKit/Client.php @@ -284,14 +284,16 @@ public function click(Link $link) /** * Submits a form. * - * @param Form $form A Form instance - * @param array $values An array of form field values + * @param Form $form A Form instance + * @param array $values An array of form field values + * @param array $serverParameters An array of server parameters * * @return Crawler */ - public function submit(Form $form, array $values = array(), $serverParameters = array()) + public function submit(Form $form, array $values = array()/*, array $serverParameters = array()*/) { $form->setValues($values); + $serverParameters = 2 < \func_num_args() ? func_get_arg(2) : array(); return $this->request($form->getMethod(), $form->getUri(), $form->getPhpValues(), $form->getPhpFiles(), $serverParameters); } From dd4b0edb22bedc666237a6320cb861a7a1afa9ec Mon Sep 17 00:00:00 2001 From: Maxime Steinhausser Date: Mon, 4 Jun 2018 19:45:48 +0200 Subject: [PATCH 052/938] [DebugBundle] DebugBundle::registerCommands should be noop --- src/Symfony/Bundle/DebugBundle/DebugBundle.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Symfony/Bundle/DebugBundle/DebugBundle.php b/src/Symfony/Bundle/DebugBundle/DebugBundle.php index b00c06af78289..04fd507612747 100644 --- a/src/Symfony/Bundle/DebugBundle/DebugBundle.php +++ b/src/Symfony/Bundle/DebugBundle/DebugBundle.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\DebugBundle; use Symfony\Bundle\DebugBundle\DependencyInjection\Compiler\DumpDataCollectorPass; +use Symfony\Component\Console\Application; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\VarDumper\VarDumper; @@ -52,4 +53,9 @@ public function build(ContainerBuilder $container) $container->addCompilerPass(new DumpDataCollectorPass()); } + + public function registerCommands(Application $application) + { + // noop + } } From 7a750d4508b6edbcf93f4149b79a96ef4a161485 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 4 Jun 2018 19:54:36 +0200 Subject: [PATCH 053/938] [Routing] Don't reorder past variable-length placeholders --- .../Matcher/Dumper/StaticPrefixCollection.php | 2 +- .../Tests/Fixtures/dumper/url_matcher12.php | 36 ++++++++----------- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/StaticPrefixCollection.php b/src/Symfony/Component/Routing/Matcher/Dumper/StaticPrefixCollection.php index cd9e6b4c3e7f3..030aeaed4139b 100644 --- a/src/Symfony/Component/Routing/Matcher/Dumper/StaticPrefixCollection.php +++ b/src/Symfony/Component/Routing/Matcher/Dumper/StaticPrefixCollection.php @@ -176,7 +176,7 @@ private function getCommonPrefix(string $prefix, string $anotherPrefix): array break; } $subPattern = substr($prefix, $i, $j - $i); - if ($prefix !== $anotherPrefix && !preg_match('/^\(\[[^\]]++\]\+\+\)$/', $subPattern) && !preg_match('{(? '{^(?' - .'|/abc([^/]++)/(?' - .'|1(?' - .'|(*:27)' - .'|0(?' - .'|(*:38)' - .'|0(*:46)' - .')' - .')' - .'|2(?' - .'|(*:59)' - .'|0(?' - .'|(*:70)' - .'|0(*:78)' - .')' - .')' + .'|/abc(?' + .'|([^/]++)/1(*:24)' + .'|([^/]++)/2(*:41)' + .'|([^/]++)/10(*:59)' + .'|([^/]++)/20(*:77)' + .'|([^/]++)/100(*:96)' + .'|([^/]++)/200(*:115)' .')' .')$}sD', ); @@ -53,12 +45,12 @@ public function match($rawPathinfo) switch ($m = (int) $matches['MARK']) { default: $routes = array( - 27 => array(array('_route' => 'r1'), array('foo'), null, null), - 38 => array(array('_route' => 'r10'), array('foo'), null, null), - 46 => array(array('_route' => 'r100'), array('foo'), null, null), - 59 => array(array('_route' => 'r2'), array('foo'), null, null), - 70 => array(array('_route' => 'r20'), array('foo'), null, null), - 78 => array(array('_route' => 'r200'), array('foo'), null, null), + 24 => array(array('_route' => 'r1'), array('foo'), null, null), + 41 => array(array('_route' => 'r2'), array('foo'), null, null), + 59 => array(array('_route' => 'r10'), array('foo'), null, null), + 77 => array(array('_route' => 'r20'), array('foo'), null, null), + 96 => array(array('_route' => 'r100'), array('foo'), null, null), + 115 => array(array('_route' => 'r200'), array('foo'), null, null), ); list($ret, $vars, $requiredMethods, $requiredSchemes) = $routes[$m]; @@ -84,7 +76,7 @@ public function match($rawPathinfo) return $ret; } - if (78 === $m) { + if (115 === $m) { break; } $regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m)); From 7c97846811420719bd225756a4d3dd94ea861797 Mon Sep 17 00:00:00 2001 From: Maxime Steinhausser Date: Mon, 4 Jun 2018 19:53:17 +0200 Subject: [PATCH 054/938] [FrameworkBundle][SecurityBundle] Remove no-longer necessary Bundle::registerCommands override --- src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php | 6 ------ src/Symfony/Bundle/SecurityBundle/SecurityBundle.php | 6 ------ 2 files changed, 12 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index 70a83f2052d9f..e9143ba56d0e5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -27,7 +27,6 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TestServiceContainerRealRefPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\UnusedTagsPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\WorkflowGuardListenerPass; -use Symfony\Component\Console\Application; use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass; use Symfony\Component\HttpKernel\DependencyInjection\ControllerArgumentValueResolverPass; use Symfony\Component\HttpKernel\DependencyInjection\LoggerPass; @@ -137,9 +136,4 @@ private function addCompilerPassIfExists(ContainerBuilder $container, $class, $t $container->addCompilerPass(new $class(), $type, $priority); } } - - public function registerCommands(Application $application) - { - // noop - } } diff --git a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php index dd6dd829d7494..89d7a7f98e99b 100644 --- a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php +++ b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php @@ -13,7 +13,6 @@ use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterCsrfTokenClearingLogoutHandlerPass; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\JsonLoginFactory; -use Symfony\Component\Console\Application; use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -62,9 +61,4 @@ public function build(ContainerBuilder $container) $container->addCompilerPass(new AddSessionDomainConstraintPass(), PassConfig::TYPE_BEFORE_REMOVING); $container->addCompilerPass(new RegisterCsrfTokenClearingLogoutHandlerPass()); } - - public function registerCommands(Application $application) - { - // noop - } } From 44616d9bcca8afc932a65da96dfeba87fa0796f9 Mon Sep 17 00:00:00 2001 From: Arnaud Kleinpeter Date: Mon, 4 Jun 2018 15:03:54 +0200 Subject: [PATCH 055/938] [Router] regression when matching a route --- .../Routing/Tests/Matcher/UrlMatcherTest.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php index b3bfd78271bf3..1e13f0883e9db 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php @@ -245,6 +245,18 @@ public function testMatchRegression() } } + public function testMultipleParams() + { + $coll = new RouteCollection(); + $coll->add('foo1', new Route('/foo/{a}/{b}')); + $coll->add('foo2', new Route('/foo/{a}/test/test/{b}')); + $coll->add('foo3', new Route('/foo/{a}/{b}/{c}/{d}')); + + $route = $this->getUrlMatcher($coll)->match('/foo/test/test/test/bar')['_route']; + + $this->assertEquals('foo2', $route); + } + public function testDefaultRequirementForOptionalVariables() { $coll = new RouteCollection(); From a6b6206a62f0846005111aafb2f3b843d89e465c Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sun, 3 Jun 2018 22:42:09 +0200 Subject: [PATCH 056/938] [DI] Don't generate factories for errored services --- .../DependencyInjection/Dumper/PhpDumper.php | 29 ++++++++++++++++++- .../Tests/Fixtures/php/services9_as_files.txt | 12 +------- .../Tests/Fixtures/php/services9_compiled.php | 17 ++++------- .../php/services_errored_definition.php | 17 ++++------- 4 files changed, 41 insertions(+), 34 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index ce96fdd607bef..7f591f7f49975 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -70,6 +70,7 @@ class PhpDumper extends Dumper private $inlinedRequires = array(); private $circularReferences = array(); private $singleUsePrivateIds = array(); + private $addThrow = false; /** * @var ProxyDumper @@ -125,6 +126,7 @@ public function dump(array $options = array()) 'build_time' => time(), ), $options); + $this->addThrow = false; $this->namespace = $options['namespace']; $this->asFiles = $options['as_files']; $this->hotPathTag = $options['hot_path_tag']; @@ -556,10 +558,13 @@ private function addServiceInstance(string $id, Definition $definition, string $ private function isTrivialInstance(Definition $definition): bool { + if ($definition->getErrors()) { + return true; + } if ($definition->isSynthetic() || $definition->getFile() || $definition->getMethodCalls() || $definition->getProperties() || $definition->getConfigurator()) { return false; } - if ($definition->isDeprecated() || $definition->isLazy() || $definition->getFactory() || 3 < count($definition->getArguments()) || $definition->getErrors()) { + if ($definition->isDeprecated() || $definition->isLazy() || $definition->getFactory() || 3 < count($definition->getArguments())) { return false; } @@ -1318,6 +1323,18 @@ private function exportParameters(array $parameters, string $path = '', int $ind private function endClass(): string { + if ($this->addThrow) { + return <<<'EOF' + + protected function throw($message) + { + throw new RuntimeException($message); + } +} + +EOF; + } + return <<<'EOF' } @@ -1525,6 +1542,11 @@ private function dumpValue($value, bool $interpolate = true): string list($this->definitionVariables, $this->referenceVariables, $this->variableCount) = $scope; } } elseif ($value instanceof Definition) { + if ($e = $value->getErrors()) { + $this->addThrow = true; + + return sprintf('$this->throw(%s)', $this->export(reset($e))); + } if (null !== $this->definitionVariables && $this->definitionVariables->contains($value)) { return $this->dumpValue($this->definitionVariables[$value], $interpolate); } @@ -1670,6 +1692,11 @@ private function getServiceCall(string $id, Reference $reference = null): string return $code; } } elseif ($this->isTrivialInstance($definition)) { + if ($e = $definition->getErrors()) { + $this->addThrow = true; + + return sprintf('$this->throw(%s)', $this->export(reset($e))); + } $code = substr($this->addNewInstance($definition, '', '', $id), 8, -2); if ($definition->isShared() && !isset($this->singleUsePrivateIds[$id])) { $code = sprintf('$this->%s[\'%s\'] = %s', $definition->isPublic() ? 'services' : 'privates', $id, $code); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt index 3a092f7306d70..90b90f64b3bb8 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt @@ -127,16 +127,6 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException; return $this->services['deprecated_service'] = new \stdClass(); - [Container%s/getErroredDefinitionService.php] => services['runtime_error'] = new \stdClass($this->load('getErroredDefinitionService.php')); +return $this->services['runtime_error'] = new \stdClass($this->throw('Service "errored_definition" is broken.')); [Container%s/getServiceFromStaticMethodService.php] => services['runtime_error'] = new \stdClass($this->getErroredDefinitionService()); + return $this->services['runtime_error'] = new \stdClass($this->throw('Service "errored_definition" is broken.')); } /** @@ -407,16 +407,6 @@ protected function getTaggedIteratorService() }, 2)); } - /** - * Gets the private 'errored_definition' shared service. - * - * @return \stdClass - */ - protected function getErroredDefinitionService() - { - throw new RuntimeException('Service "errored_definition" is broken.'); - } - /** * Gets the private 'factory_simple' shared service. * @@ -500,4 +490,9 @@ protected function getDefaultParameters() 'foo' => 'bar', ); } + + protected function throw($message) + { + throw new RuntimeException($message); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_errored_definition.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_errored_definition.php index 9ff22bf4d43d8..ddc5dc59da4f3 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_errored_definition.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_errored_definition.php @@ -381,7 +381,7 @@ protected function getNewFactoryServiceService() */ protected function getRuntimeErrorService() { - return $this->services['runtime_error'] = new \stdClass($this->getErroredDefinitionService()); + return $this->services['runtime_error'] = new \stdClass($this->throw('Service "errored_definition" is broken.')); } /** @@ -407,16 +407,6 @@ protected function getTaggedIteratorService() }, 2)); } - /** - * Gets the private 'errored_definition' shared service. - * - * @return \stdClass - */ - protected function getErroredDefinitionService() - { - throw new RuntimeException('Service "errored_definition" is broken.'); - } - /** * Gets the private 'factory_simple' shared service. * @@ -501,4 +491,9 @@ protected function getDefaultParameters() 'foo_bar' => 'foo_bar', ); } + + protected function throw($message) + { + throw new RuntimeException($message); + } } From 238e793431e2d71ff89dff0fc41e974fbe50d630 Mon Sep 17 00:00:00 2001 From: Kevin Bond Date: Fri, 27 Apr 2018 10:57:54 -0400 Subject: [PATCH 057/938] [DependencyInjection] add ServiceSubscriberTrait --- .../DependencyInjection/CHANGELOG.md | 5 ++ .../ServiceSubscriberTrait.php | 61 ++++++++++++++++++ .../RegisterServiceSubscribersPassTest.php | 30 +++++++++ .../Tests/Fixtures/TestDefinition1.php | 9 +++ .../Tests/Fixtures/TestDefinition2.php | 9 +++ .../Tests/Fixtures/TestDefinition3.php | 9 +++ .../Fixtures/TestServiceSubscriberChild.php | 28 +++++++++ .../Fixtures/TestServiceSubscriberParent.php | 16 +++++ .../Fixtures/TestServiceSubscriberTrait.php | 11 ++++ .../Tests/ServiceSubscriberTraitTest.php | 63 +++++++++++++++++++ 10 files changed, 241 insertions(+) create mode 100644 src/Symfony/Component/DependencyInjection/ServiceSubscriberTrait.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestDefinition1.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestDefinition2.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestDefinition3.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestServiceSubscriberChild.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestServiceSubscriberParent.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestServiceSubscriberTrait.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/ServiceSubscriberTraitTest.php diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index fb9d0ef90c82a..4393b8c44c40d 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +4.2.0 +----- + + * added `ServiceSubscriberTrait` + 4.1.0 ----- diff --git a/src/Symfony/Component/DependencyInjection/ServiceSubscriberTrait.php b/src/Symfony/Component/DependencyInjection/ServiceSubscriberTrait.php new file mode 100644 index 0000000000000..0f55c742fc9a8 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/ServiceSubscriberTrait.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +use Psr\Container\ContainerInterface; + +/** + * Implementation of ServiceSubscriberInterface that determines subscribed services from + * private method return types. Service ids are available as "ClassName::methodName". + * + * @author Kevin Bond + */ +trait ServiceSubscriberTrait +{ + /** @var ContainerInterface */ + private $container; + + public static function getSubscribedServices(): array + { + static $services; + + if (null !== $services) { + return $services; + } + + $services = \is_callable(array('parent', __FUNCTION__)) ? parent::getSubscribedServices() : array(); + + foreach ((new \ReflectionClass(self::class))->getMethods() as $method) { + if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { + continue; + } + + if (self::class === $method->getDeclaringClass()->name && ($returnType = $method->getReturnType()) && !$returnType->isBuiltin()) { + $services[self::class.'::'.$method->name] = '?'.$returnType->getName(); + } + } + + return $services; + } + + /** + * @required + */ + public function setContainer(ContainerInterface $container) + { + $this->container = $container; + + if (\is_callable(array('parent', __FUNCTION__))) { + return parent::setContainer($container); + } + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php index f7314948aeef6..3a0f2b93353cf 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php @@ -21,7 +21,12 @@ use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition; +use Symfony\Component\DependencyInjection\Tests\Fixtures\TestDefinition1; +use Symfony\Component\DependencyInjection\Tests\Fixtures\TestDefinition2; +use Symfony\Component\DependencyInjection\Tests\Fixtures\TestDefinition3; use Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber; +use Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriberChild; +use Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriberParent; use Symfony\Component\DependencyInjection\TypedReference; require_once __DIR__.'/../Fixtures/includes/classes.php'; @@ -136,4 +141,29 @@ public function testExtraServiceSubscriber() $container->register(TestServiceSubscriber::class, TestServiceSubscriber::class); $container->compile(); } + + public function testServiceSubscriberTrait() + { + $container = new ContainerBuilder(); + + $container->register('foo', TestServiceSubscriberChild::class) + ->addMethodCall('setContainer', array(new Reference(PsrContainerInterface::class))) + ->addTag('container.service_subscriber') + ; + + (new RegisterServiceSubscribersPass())->process($container); + (new ResolveServiceSubscribersPass())->process($container); + + $foo = $container->getDefinition('foo'); + $locator = $container->getDefinition((string) $foo->getMethodCalls()[0][1][0]); + + $expected = array( + TestServiceSubscriberChild::class.'::invalidDefinition' => new ServiceClosureArgument(new TypedReference('Symfony\Component\DependencyInjection\Tests\Fixtures\InvalidDefinition', 'Symfony\Component\DependencyInjection\Tests\Fixtures\InvalidDefinition', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)), + TestServiceSubscriberChild::class.'::testDefinition2' => new ServiceClosureArgument(new TypedReference(TestDefinition2::class, TestDefinition2::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)), + TestServiceSubscriberChild::class.'::testDefinition3' => new ServiceClosureArgument(new TypedReference(TestDefinition3::class, TestDefinition3::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)), + TestServiceSubscriberParent::class.'::testDefinition1' => new ServiceClosureArgument(new TypedReference(TestDefinition1::class, TestDefinition1::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)), + ); + + $this->assertEquals($expected, $container->getDefinition((string) $locator->getFactory()[0])->getArgument(0)); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestDefinition1.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestDefinition1.php new file mode 100644 index 0000000000000..0b12c21e09125 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestDefinition1.php @@ -0,0 +1,9 @@ +container->get(__METHOD__); + } + + private function invalidDefinition(): InvalidDefinition + { + return $this->container->get(__METHOD__); + } + + private function privateFunction1(): string + { + } + + private function privateFunction2(): string + { + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestServiceSubscriberParent.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestServiceSubscriberParent.php new file mode 100644 index 0000000000000..d2abb7a859cdf --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestServiceSubscriberParent.php @@ -0,0 +1,16 @@ +container->get(__METHOD__); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestServiceSubscriberTrait.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestServiceSubscriberTrait.php new file mode 100644 index 0000000000000..ea98a0f2f13e1 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestServiceSubscriberTrait.php @@ -0,0 +1,11 @@ +container->get(__CLASS__.'::'.__FUNCTION__); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/ServiceSubscriberTraitTest.php b/src/Symfony/Component/DependencyInjection/Tests/ServiceSubscriberTraitTest.php new file mode 100644 index 0000000000000..e230b2069ef9a --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/ServiceSubscriberTraitTest.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests; + +use PHPUnit\Framework\TestCase; +use Psr\Container\ContainerInterface; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\ServiceSubscriberInterface; +use Symfony\Component\DependencyInjection\ServiceSubscriberTrait; + +class ServiceSubscriberTraitTest extends TestCase +{ + public function testMethodsOnParentsAndChildrenAreIgnoredInGetSubscribedServices() + { + $expected = array(TestService::class.'::aService' => '?Symfony\Component\DependencyInjection\Tests\Service2'); + + $this->assertEquals($expected, ChildTestService::getSubscribedServices()); + } + + public function testSetContainerIsCalledOnParent() + { + $container = new Container(); + + $this->assertSame($container, (new TestService())->setContainer($container)); + } +} + +class ParentTestService +{ + public function aParentService(): Service1 + { + } + + public function setContainer(ContainerInterface $container) + { + return $container; + } +} + +class TestService extends ParentTestService implements ServiceSubscriberInterface +{ + use ServiceSubscriberTrait; + + public function aService(): Service2 + { + } +} + +class ChildTestService extends TestService +{ + public function aChildService(): Service3 + { + } +} From af0699012ae02accb11b4b3a8d5b3fee477ceb46 Mon Sep 17 00:00:00 2001 From: Aleksey Prilipko Date: Sun, 20 May 2018 12:18:30 +1000 Subject: [PATCH 058/938] bug #27299 [Cache] memcache connect should not add duplicate entries on sequential calls --- .../Component/Cache/Tests/Simple/MemcachedCacheTest.php | 9 +++++++++ src/Symfony/Component/Cache/Traits/MemcachedTrait.php | 6 +++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/Cache/Tests/Simple/MemcachedCacheTest.php b/src/Symfony/Component/Cache/Tests/Simple/MemcachedCacheTest.php index c4af891af7ba7..b46d7e443dd20 100644 --- a/src/Symfony/Component/Cache/Tests/Simple/MemcachedCacheTest.php +++ b/src/Symfony/Component/Cache/Tests/Simple/MemcachedCacheTest.php @@ -45,6 +45,15 @@ public function createSimpleCache($defaultLifetime = 0) return new MemcachedCache($client, str_replace('\\', '.', __CLASS__), $defaultLifetime); } + public function testCreatePersistentConnectionShouldNotDupServerList() + { + $instance = MemcachedCache::createConnection('memcached://'.getenv('MEMCACHED_HOST'), array('persistent_id' => 'persistent')); + $this->assertCount(1, $instance->getServerList()); + + $instance = MemcachedCache::createConnection('memcached://'.getenv('MEMCACHED_HOST'), array('persistent_id' => 'persistent')); + $this->assertCount(1, $instance->getServerList()); + } + public function testOptions() { $client = MemcachedCache::createConnection(array(), array( diff --git a/src/Symfony/Component/Cache/Traits/MemcachedTrait.php b/src/Symfony/Component/Cache/Traits/MemcachedTrait.php index 9b877efb080a9..9148573694cf6 100644 --- a/src/Symfony/Component/Cache/Traits/MemcachedTrait.php +++ b/src/Symfony/Component/Cache/Traits/MemcachedTrait.php @@ -169,12 +169,12 @@ public static function createConnection($servers, array $options = array()) } if ($oldServers !== $newServers) { - // before resetting, ensure $servers is valid - $client->addServers($servers); $client->resetServerList(); + $client->addServers($servers); } + } else { + $client->addServers($servers); } - $client->addServers($servers); if (null !== $username || null !== $password) { if (!method_exists($client, 'setSaslAuthData')) { From 0f3ba5a57d557ed6345cadc89f5f1b27e98fcc04 Mon Sep 17 00:00:00 2001 From: Greg ORIOL Date: Fri, 1 Jun 2018 01:32:42 +0200 Subject: [PATCH 059/938] [WebProfilerBundle] fixed getSession when no session has been set deprecation warnings --- .../WebProfilerBundle/Controller/ProfilerController.php | 8 ++++++-- .../Resources/views/Profiler/results.html.twig | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php index e132d30dd602d..a8263855cd37b 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php @@ -180,7 +180,7 @@ public function searchBarAction(Request $request) $this->cspHandler->disableCsp(); } - if (null === $session = $request->getSession()) { + if (!$request->hasSession()) { $ip = $method = $statusCode = @@ -190,6 +190,8 @@ public function searchBarAction(Request $request) $limit = $token = null; } else { + $session = $request->getSession(); + $ip = $request->query->get('ip', $session->get('_profiler_search_ip')); $method = $request->query->get('method', $session->get('_profiler_search_method')); $statusCode = $request->query->get('status_code', $session->get('_profiler_search_status_code')); @@ -289,7 +291,9 @@ public function searchAction(Request $request) $limit = $request->query->get('limit'); $token = $request->query->get('token'); - if (null !== $session = $request->getSession()) { + if ($request->hasSession()) { + $session = $request->getSession(); + $session->set('_profiler_search_ip', $ip); $session->set('_profiler_search_method', $method); $session->set('_profiler_search_status_code', $statusCode); diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/results.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/results.html.twig index fb5c3aa6d10ca..7ddbf9c4f028e 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/results.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/results.html.twig @@ -1,7 +1,7 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} {% macro profile_search_filter(request, result, property) %} - {%- if request.session is not null -%} + {%- if request.hasSession -%} {{ include('@WebProfiler/Icon/search.svg') }} {%- endif -%} {% endmacro %} From b3cdfc64b561ec3321cbc0a3b1321ca1e387be2f Mon Sep 17 00:00:00 2001 From: Roland Franssen Date: Fri, 1 Jun 2018 20:08:31 +0200 Subject: [PATCH 060/938] [DI] Ignore missing tree root nodes on validate --- .../Config/Definition/Builder/TreeBuilder.php | 3 +- .../TreeWithoutRootNodeException.php | 19 +++++++++++ .../Compiler/ValidateEnvPlaceholdersPass.php | 6 +++- .../ValidateEnvPlaceholdersPassTest.php | 34 +++++++++++++++++-- .../DependencyInjection/composer.json | 2 +- 5 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 src/Symfony/Component/Config/Definition/Exception/TreeWithoutRootNodeException.php diff --git a/src/Symfony/Component/Config/Definition/Builder/TreeBuilder.php b/src/Symfony/Component/Config/Definition/Builder/TreeBuilder.php index 6da510ec1c2dd..6b4323d42f519 100644 --- a/src/Symfony/Component/Config/Definition/Builder/TreeBuilder.php +++ b/src/Symfony/Component/Config/Definition/Builder/TreeBuilder.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Config\Definition\Builder; +use Symfony\Component\Config\Definition\Exception\TreeWithoutRootNodeException; use Symfony\Component\Config\Definition\NodeInterface; /** @@ -74,7 +75,7 @@ public function setPathSeparator(string $separator) private function assertTreeHasRootNode() { if (null === $this->root) { - throw new \RuntimeException('The configuration tree has no root node.'); + throw new TreeWithoutRootNodeException('The configuration tree has no root node.'); } } } diff --git a/src/Symfony/Component/Config/Definition/Exception/TreeWithoutRootNodeException.php b/src/Symfony/Component/Config/Definition/Exception/TreeWithoutRootNodeException.php new file mode 100644 index 0000000000000..67b0c4bb2e084 --- /dev/null +++ b/src/Symfony/Component/Config/Definition/Exception/TreeWithoutRootNodeException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Exception; + +/** + * @author Roland Franssen + */ +class TreeWithoutRootNodeException extends \RuntimeException +{ +} diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ValidateEnvPlaceholdersPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ValidateEnvPlaceholdersPass.php index 1fe57f9db6cfb..4b13cf2b9420a 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ValidateEnvPlaceholdersPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ValidateEnvPlaceholdersPass.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Symfony\Component\Config\Definition\BaseNode; +use Symfony\Component\Config\Definition\Exception\TreeWithoutRootNodeException; use Symfony\Component\Config\Definition\Processor; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\LogicException; @@ -77,7 +78,10 @@ public function process(ContainerBuilder $container) continue; } - $processor->processConfiguration($configuration, $config); + try { + $processor->processConfiguration($configuration, $config); + } catch (TreeWithoutRootNodeException $e) { + } } } finally { BaseNode::resetPlaceholders(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ValidateEnvPlaceholdersPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ValidateEnvPlaceholdersPassTest.php index bd554cd285901..571c070c60179 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ValidateEnvPlaceholdersPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ValidateEnvPlaceholdersPassTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\Config\Definition\Exception\TreeWithoutRootNodeException; use Symfony\Component\DependencyInjection\Compiler\MergeExtensionConfigurationPass; use Symfony\Component\DependencyInjection\Compiler\RegisterEnvVarProcessorsPass; use Symfony\Component\DependencyInjection\Compiler\ValidateEnvPlaceholdersPass; @@ -222,6 +223,17 @@ public function testEnvWithVariableNode(): void $this->assertSame($expected, $container->resolveEnvPlaceholders($ext->getConfig())); } + public function testConfigurationWithoutRootNode(): void + { + $container = new ContainerBuilder(); + $container->registerExtension($ext = new EnvExtension(new EnvConfigurationWithoutRootNode())); + $container->loadFromExtension('env_extension'); + + $this->doProcess($container); + + $this->addToAssertionCount(1); + } + private function doProcess(ContainerBuilder $container): void { (new MergeExtensionConfigurationPass())->process($container); @@ -267,10 +279,24 @@ public function getConfigTreeBuilder() } } +class EnvConfigurationWithoutRootNode implements ConfigurationInterface +{ + public function getConfigTreeBuilder() + { + return new TreeBuilder(); + } +} + class EnvExtension extends Extension { + private $configuration; private $config; + public function __construct(ConfigurationInterface $configuration = null) + { + $this->configuration = $configuration ?? new EnvConfiguration(); + } + public function getAlias() { return 'env_extension'; @@ -278,12 +304,16 @@ public function getAlias() public function getConfiguration(array $config, ContainerBuilder $container) { - return new EnvConfiguration(); + return $this->configuration; } public function load(array $configs, ContainerBuilder $container) { - $this->config = $this->processConfiguration($this->getConfiguration($configs, $container), $configs); + try { + $this->config = $this->processConfiguration($this->getConfiguration($configs, $container), $configs); + } catch (TreeWithoutRootNodeException $e) { + $this->config = null; + } } public function getConfig() diff --git a/src/Symfony/Component/DependencyInjection/composer.json b/src/Symfony/Component/DependencyInjection/composer.json index 8230395645c7c..12758018f22da 100644 --- a/src/Symfony/Component/DependencyInjection/composer.json +++ b/src/Symfony/Component/DependencyInjection/composer.json @@ -32,7 +32,7 @@ "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them" }, "conflict": { - "symfony/config": "<4.1", + "symfony/config": "<4.1.1", "symfony/finder": "<3.4", "symfony/proxy-manager-bridge": "<3.4", "symfony/yaml": "<3.4" From 77b9f90a321a308d88148a3cc0ab06aa41d379d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Wed, 23 May 2018 19:28:00 +0200 Subject: [PATCH 061/938] Remove released semaphore --- .../Component/Lock/Store/SemaphoreStore.php | 22 +++++++++------ .../Lock/Tests/Store/SemaphoreStoreTest.php | 28 +++++++++++++++++++ 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/src/Symfony/Component/Lock/Store/SemaphoreStore.php b/src/Symfony/Component/Lock/Store/SemaphoreStore.php index a6cc9ea8b9f1d..4e149e8deb81c 100644 --- a/src/Symfony/Component/Lock/Store/SemaphoreStore.php +++ b/src/Symfony/Component/Lock/Store/SemaphoreStore.php @@ -75,16 +75,20 @@ private function lock(Key $key, $blocking) return; } - $resource = sem_get(crc32($key)); + $keyId = crc32($key); + $resource = sem_get($keyId); - if (\PHP_VERSION_ID < 50601) { - if (!$blocking) { - throw new NotSupportedException(sprintf('The store "%s" does not supports non blocking locks.', get_class($this))); - } - - $acquired = sem_acquire($resource); + if (\PHP_VERSION_ID >= 50601) { + $acquired = @sem_acquire($resource, !$blocking); + } elseif (!$blocking) { + throw new NotSupportedException(sprintf('The store "%s" does not supports non blocking locks.', get_class($this))); } else { - $acquired = sem_acquire($resource, !$blocking); + $acquired = @sem_acquire($resource); + } + + while ($blocking && !$acquired) { + $resource = sem_get($keyId); + $acquired = @sem_acquire($resource); } if (!$acquired) { @@ -106,7 +110,7 @@ public function delete(Key $key) $resource = $key->getState(__CLASS__); - sem_release($resource); + sem_remove($resource); $key->removeState(__CLASS__); } diff --git a/src/Symfony/Component/Lock/Tests/Store/SemaphoreStoreTest.php b/src/Symfony/Component/Lock/Tests/Store/SemaphoreStoreTest.php index bb37ec1fe1a1b..23b90360bea77 100644 --- a/src/Symfony/Component/Lock/Tests/Store/SemaphoreStoreTest.php +++ b/src/Symfony/Component/Lock/Tests/Store/SemaphoreStoreTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Lock\Tests\Store; +use Symfony\Component\Lock\Key; use Symfony\Component\Lock\Store\SemaphoreStore; /** @@ -33,4 +34,31 @@ protected function getStore() return new SemaphoreStore(); } + + public function testResourceRemoval() + { + $initialCount = $this->getOpenedSemaphores(); + $store = new SemaphoreStore(); + $key = new Key(uniqid(__METHOD__, true)); + $store->waitAndSave($key); + + $this->assertGreaterThan($initialCount, $this->getOpenedSemaphores(), 'Semaphores should have been created'); + + $store->delete($key); + $this->assertEquals($initialCount, $this->getOpenedSemaphores(), 'All semaphores should be removed'); + } + + private function getOpenedSemaphores() + { + $lines = explode(PHP_EOL, trim(`ipcs -su`)); + if ('------ Semaphore Status --------' !== $lines[0]) { + throw new \Exception('Failed to extract list of opend semaphores. Expect a Semaphore status, got '.implode(PHP_EOL, $lines)); + } + list($key, $value) = explode(' = ', $lines[1]); + if ('used arrays' !== $key) { + throw new \Exception('Failed to extract list of opend semaphores. Expect a used arrays key, got '.implode(PHP_EOL, $lines)); + } + + return (int) $value; + } } From 67d4e6dd29d24093c9c81da104297c25f997cf08 Mon Sep 17 00:00:00 2001 From: Aleksey Prilipko Date: Wed, 30 May 2018 11:21:31 +1000 Subject: [PATCH 062/938] bug #27405 [Cache] TagAwareAdapter should not corrupt memcached connection in ascii mode --- .../Simple/MemcachedCacheTextModeTest.php | 25 +++++++++++++++++++ .../Component/Cache/Traits/MemcachedTrait.php | 23 ++++++++++++++--- 2 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 src/Symfony/Component/Cache/Tests/Simple/MemcachedCacheTextModeTest.php diff --git a/src/Symfony/Component/Cache/Tests/Simple/MemcachedCacheTextModeTest.php b/src/Symfony/Component/Cache/Tests/Simple/MemcachedCacheTextModeTest.php new file mode 100644 index 0000000000000..43cadf9034846 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Simple/MemcachedCacheTextModeTest.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Tests\Simple; + +use Symfony\Component\Cache\Adapter\AbstractAdapter; +use Symfony\Component\Cache\Simple\MemcachedCache; + +class MemcachedCacheTextModeTest extends MemcachedCacheTest +{ + public function createSimpleCache($defaultLifetime = 0) + { + $client = AbstractAdapter::createConnection('memcached://'.getenv('MEMCACHED_HOST'), array('binary_protocol' => false)); + + return new MemcachedCache($client, str_replace('\\', '.', __CLASS__), $defaultLifetime); + } +} diff --git a/src/Symfony/Component/Cache/Traits/MemcachedTrait.php b/src/Symfony/Component/Cache/Traits/MemcachedTrait.php index 9b877efb080a9..9f861cf5d29b6 100644 --- a/src/Symfony/Component/Cache/Traits/MemcachedTrait.php +++ b/src/Symfony/Component/Cache/Traits/MemcachedTrait.php @@ -198,7 +198,12 @@ protected function doSave(array $values, $lifetime) $lifetime += time(); } - return $this->checkResultCode($this->getClient()->setMulti($values, $lifetime)); + $encodedValues = array(); + foreach ($values as $key => $value) { + $encodedValues[rawurlencode($key)] = $value; + } + + return $this->checkResultCode($this->getClient()->setMulti($encodedValues, $lifetime)); } /** @@ -208,7 +213,16 @@ protected function doFetch(array $ids) { $unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback'); try { - return $this->checkResultCode($this->getClient()->getMulti($ids)); + $encodedIds = array_map('rawurlencode', $ids); + + $encodedResult = $this->checkResultCode($this->getClient()->getMulti($encodedIds)); + + $result = array(); + foreach ($encodedResult as $key => $value) { + $result[rawurldecode($key)] = $value; + } + + return $result; } catch (\Error $e) { throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine()); } finally { @@ -221,7 +235,7 @@ protected function doFetch(array $ids) */ protected function doHave($id) { - return false !== $this->getClient()->get($id) || $this->checkResultCode(\Memcached::RES_SUCCESS === $this->client->getResultCode()); + return false !== $this->getClient()->get(rawurlencode($id)) || $this->checkResultCode(\Memcached::RES_SUCCESS === $this->client->getResultCode()); } /** @@ -230,7 +244,8 @@ protected function doHave($id) protected function doDelete(array $ids) { $ok = true; - foreach ($this->checkResultCode($this->getClient()->deleteMulti($ids)) as $result) { + $encodedIds = array_map('rawurlencode', $ids); + foreach ($this->checkResultCode($this->getClient()->deleteMulti($encodedIds)) as $result) { if (\Memcached::RES_SUCCESS !== $result && \Memcached::RES_NOTFOUND !== $result) { $ok = false; } From 6a0b75fb9b0e84fe217a9b0d8f35c1aea46007da Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 5 Jun 2018 10:24:18 +0200 Subject: [PATCH 063/938] Remove mentions of "beta" in composer.json files --- src/Symfony/Component/Security/Csrf/composer.json | 2 +- src/Symfony/Component/Security/composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Security/Csrf/composer.json b/src/Symfony/Component/Security/Csrf/composer.json index 86bc9015c8fd9..7a42b37d35e78 100644 --- a/src/Symfony/Component/Security/Csrf/composer.json +++ b/src/Symfony/Component/Security/Csrf/composer.json @@ -25,7 +25,7 @@ "symfony/http-foundation": "^2.7.38|~3.3.13" }, "conflict": { - "symfony/http-foundation": "<2.7.38|~2.8,<2.8.31|~3.3,<3.3.13|~3.4,<3.4-beta5" + "symfony/http-foundation": "<2.7.38|~2.8,<2.8.31|~3.3,<3.3.13" }, "suggest": { "symfony/http-foundation": "For using the class SessionTokenStorage." diff --git a/src/Symfony/Component/Security/composer.json b/src/Symfony/Component/Security/composer.json index 1fb2494cb3619..3e3468feb8597 100644 --- a/src/Symfony/Component/Security/composer.json +++ b/src/Symfony/Component/Security/composer.json @@ -43,7 +43,7 @@ "symfony/ldap": "~2.8|~3.0.0" }, "conflict": { - "symfony/http-foundation": "~2.8,<2.8.31|~3.4,<3.4-beta5" + "symfony/http-foundation": "~2.8,<2.8.31" }, "suggest": { "symfony/form": "", From cf375e528635dcd38b0eb68e3eb33235bc8ed969 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 1 Jun 2018 18:19:33 +0200 Subject: [PATCH 064/938] [DI] Improve performance of removing/inlining passes --- .../Compiler/CachePoolClearerPassTest.php | 7 +- .../Compiler/AbstractRecursivePass.php | 38 +++++++ .../Compiler/AnalyzeServiceReferencesPass.php | 86 ++++++---------- .../Compiler/InlineServiceDefinitionsPass.php | 98 ++++++++++++++++--- .../Compiler/PassConfig.php | 8 +- .../Compiler/RemoveUnusedDefinitionsPass.php | 87 +++++++++------- .../Compiler/RepeatablePassInterface.php | 2 + .../Compiler/RepeatedPass.php | 4 + .../DependencyInjection/Dumper/PhpDumper.php | 7 +- .../AnalyzeServiceReferencesPassTest.php | 3 +- .../InlineServiceDefinitionsPassTest.php | 16 +-- .../RemoveUnusedDefinitionsPassTest.php | 5 +- .../Fixtures/xml/services_instanceof.xml | 2 +- .../Fixtures/yaml/services_instanceof.yml | 4 +- 14 files changed, 239 insertions(+), 128 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolClearerPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolClearerPassTest.php index 243061712a8c4..3de867203f1e1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolClearerPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolClearerPassTest.php @@ -55,7 +55,12 @@ public function testPoolRefsAreWeak() $container->setAlias('clearer_alias', 'clearer'); $pass = new RemoveUnusedDefinitionsPass(); - $pass->setRepeatedPass(new RepeatedPass(array($pass))); + foreach ($container->getCompiler()->getPassConfig()->getRemovingPasses() as $removingPass) { + if ($removingPass instanceof RepeatedPass) { + $pass->setRepeatedPass(new RepeatedPass(array($pass))); + break; + } + } foreach (array(new CachePoolPass(), $pass, new CachePoolClearerPass()) as $pass) { $pass->process($container); } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php index 901dc06ffaee5..ef50f8234e628 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php @@ -15,7 +15,9 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\ExpressionLanguage; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\ExpressionLanguage\Expression; /** * @author Nicolas Grekas @@ -28,6 +30,9 @@ abstract class AbstractRecursivePass implements CompilerPassInterface protected $container; protected $currentId; + private $processExpressions = false; + private $expressionLanguage; + /** * {@inheritdoc} */ @@ -42,11 +47,17 @@ public function process(ContainerBuilder $container) } } + protected function enableExpressionProcessing() + { + $this->processExpressions = true; + } + /** * Processes a value found in a definition tree. * * @param mixed $value * @param bool $isRoot + * @param bool $inExpression * * @return mixed The processed value */ @@ -63,6 +74,8 @@ protected function processValue($value, $isRoot = false) } } elseif ($value instanceof ArgumentInterface) { $value->setValues($this->processValue($value->getValues())); + } elseif ($value instanceof Expression && $this->processExpressions) { + $this->getExpressionLanguage()->compile((string) $value, array('this' => 'container')); } elseif ($value instanceof Definition) { $value->setArguments($this->processValue($value->getArguments())); $value->setProperties($this->processValue($value->getProperties())); @@ -169,4 +182,29 @@ protected function getReflectionMethod(Definition $definition, $method) return $r; } + + private function getExpressionLanguage() + { + if (null === $this->expressionLanguage) { + if (!class_exists(ExpressionLanguage::class)) { + throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); + } + + $providers = $this->container->getExpressionLanguageProviders(); + $this->expressionLanguage = new ExpressionLanguage(null, $providers, function ($arg) { + if ('""' === substr_replace($arg, '', 1, -1)) { + $id = stripcslashes(substr($arg, 1, -1)); + $arg = $this->processValue(new Reference($id), false, true); + if (!$arg instanceof Reference) { + throw new RuntimeException(sprintf('"%s::processValue()" must return a Reference when processing an expression, %s returned for service("%s").', get_class($this), is_object($arg) ? get_class($arg) : gettype($arg))); + } + $arg = sprintf('"%s"', $arg); + } + + return sprintf('$this->get(%s)', $arg); + }); + } + + return $this->expressionLanguage; + } } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php index a67d9b044f4f2..3740c740b4442 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php @@ -14,11 +14,8 @@ use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; -use Symfony\Component\DependencyInjection\Exception\RuntimeException; -use Symfony\Component\DependencyInjection\ExpressionLanguage; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\ExpressionLanguage\Expression; /** * Run this pass before passes that need to know more about the relation of @@ -28,6 +25,7 @@ * retrieve the graph in other passes from the compiler. * * @author Johannes M. Schmitt + * @author Nicolas Grekas */ class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements RepeatablePassInterface { @@ -35,7 +33,8 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements Repe private $currentDefinition; private $onlyConstructorArguments; private $lazy; - private $expressionLanguage; + private $definitions; + private $aliases; /** * @param bool $onlyConstructorArguments Sets this Service Reference pass to ignore method calls @@ -43,6 +42,7 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements Repe public function __construct(bool $onlyConstructorArguments = false) { $this->onlyConstructorArguments = $onlyConstructorArguments; + $this->enableExpressionProcessing(); } /** @@ -50,7 +50,7 @@ public function __construct(bool $onlyConstructorArguments = false) */ public function setRepeatedPass(RepeatedPass $repeatedPass) { - // no-op for BC + @trigger_error(sprintf('The "%s" method is deprecated since Symfony 4.2.', __METHOD__), E_USER_DEPRECATED); } /** @@ -62,16 +62,22 @@ public function process(ContainerBuilder $container) $this->graph = $container->getCompiler()->getServiceReferenceGraph(); $this->graph->clear(); $this->lazy = false; + $this->definitions = $container->getDefinitions(); + $this->aliases = $container->getAliases(); - foreach ($container->getAliases() as $id => $alias) { + foreach ($this->aliases as $id => $alias) { $targetId = $this->getDefinitionId((string) $alias); - $this->graph->connect($id, $alias, $targetId, $this->getDefinition($targetId), null); + $this->graph->connect($id, $alias, $targetId, null !== $targetId ? $this->container->getDefinition($targetId) : null, null); } - parent::process($container); + try { + parent::process($container); + } finally { + $this->aliases = $this->definitions = array(); + } } - protected function processValue($value, $isRoot = false) + protected function processValue($value, $isRoot = false, bool $inExpression = false) { $lazy = $this->lazy; @@ -82,14 +88,9 @@ protected function processValue($value, $isRoot = false) return $value; } - if ($value instanceof Expression) { - $this->getExpressionLanguage()->compile((string) $value, array('this' => 'container')); - - return $value; - } if ($value instanceof Reference) { $targetId = $this->getDefinitionId((string) $value); - $targetDefinition = $this->getDefinition($targetId); + $targetDefinition = null !== $targetId ? $this->container->getDefinition($targetId) : null; $this->graph->connect( $this->currentId, @@ -101,6 +102,18 @@ protected function processValue($value, $isRoot = false) ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $value->getInvalidBehavior() ); + if ($inExpression) { + $this->graph->connect( + '.internal.reference_in_expression', + null, + $targetId, + $targetDefinition, + $value, + $this->lazy || ($targetDefinition && $targetDefinition->isLazy()), + true + ); + } + return $value; } if (!$value instanceof Definition) { @@ -127,49 +140,12 @@ protected function processValue($value, $isRoot = false) return $value; } - private function getDefinition(?string $id): ?Definition - { - return null === $id ? null : $this->container->getDefinition($id); - } - private function getDefinitionId(string $id): ?string { - while ($this->container->hasAlias($id)) { - $id = (string) $this->container->getAlias($id); - } - - if (!$this->container->hasDefinition($id)) { - return null; - } - - return $id; - } - - private function getExpressionLanguage() - { - if (null === $this->expressionLanguage) { - if (!class_exists(ExpressionLanguage::class)) { - throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); - } - - $providers = $this->container->getExpressionLanguageProviders(); - $this->expressionLanguage = new ExpressionLanguage(null, $providers, function ($arg) { - if ('""' === substr_replace($arg, '', 1, -1)) { - $id = stripcslashes(substr($arg, 1, -1)); - $id = $this->getDefinitionId($id); - - $this->graph->connect( - $this->currentId, - $this->currentDefinition, - $id, - $this->getDefinition($id) - ); - } - - return sprintf('$this->get(%s)', $arg); - }); + while (isset($this->aliases[$id])) { + $id = (string) $this->aliases[$id]; } - return $this->expressionLanguage; + return isset($this->definitions[$id]) ? $id : null; } } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php index 9aee66c8e0d5b..d255293d02cf9 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use Symfony\Component\DependencyInjection\Reference; @@ -23,14 +24,78 @@ */ class InlineServiceDefinitionsPass extends AbstractRecursivePass implements RepeatablePassInterface { + private $analyzingPass; + private $repeatedPass; private $cloningIds = array(); + private $connectedIds = array(); + private $notInlinedIds = array(); + private $inlinedIds = array(); + private $graph; + + public function __construct(AnalyzeServiceReferencesPass $analyzingPass = null) + { + $this->analyzingPass = $analyzingPass; + } /** * {@inheritdoc} */ public function setRepeatedPass(RepeatedPass $repeatedPass) { - // no-op for BC + @trigger_error(sprintf('The "%s" method is deprecated since Symfony 4.2.', __METHOD__), E_USER_DEPRECATED); + $this->repeatedPass = $repeatedPass; + } + + public function process(ContainerBuilder $container) + { + $this->container = $container; + if ($this->analyzingPass) { + $analyzedContainer = new ContainerBuilder(); + $analyzedContainer->setAliases($container->getAliases()); + $analyzedContainer->setDefinitions($container->getDefinitions()); + } else { + $analyzedContainer = $container; + } + try { + $this->connectedIds = $this->notInlinedIds = $container->getDefinitions(); + do { + if ($this->analyzingPass) { + $analyzedContainer->setDefinitions(array_intersect_key($analyzedContainer->getDefinitions(), $this->connectedIds)); + $this->analyzingPass->process($analyzedContainer); + } + $this->graph = $analyzedContainer->getCompiler()->getServiceReferenceGraph(); + $notInlinedIds = $this->notInlinedIds; + $this->connectedIds = $this->notInlinedIds = $this->inlinedIds = array(); + + foreach ($analyzedContainer->getDefinitions() as $id => $definition) { + if (!$this->graph->hasNode($id)) { + continue; + } + foreach ($this->graph->getNode($id)->getOutEdges() as $edge) { + if (isset($notInlinedIds[$edge->getSourceNode()->getId()])) { + $this->currentId = $id; + $this->processValue($definition, true); + break; + } + } + } + + foreach ($this->inlinedIds as $id => $isPublic) { + if (!$isPublic) { + $container->removeDefinition($id); + $analyzedContainer->removeDefinition($id); + } + } + } while ($this->inlinedIds && $this->analyzingPass); + + if ($this->inlinedIds && $this->repeatedPass) { + $this->repeatedPass->setRepeat(); + } + } finally { + $this->container = null; + $this->connectedIds = $this->notInlinedIds = $this->inlinedIds = array(); + $this->graph = null; + } } /** @@ -50,17 +115,21 @@ protected function processValue($value, $isRoot = false) $value = clone $value; } - if (!$value instanceof Reference || !$this->container->hasDefinition($id = (string) $value)) { + if (!$value instanceof Reference) { return parent::processValue($value, $isRoot); + } elseif (!$this->container->hasDefinition($id = (string) $value)) { + return $value; } $definition = $this->container->getDefinition($id); - if (!$this->isInlineableDefinition($id, $definition, $this->container->getCompiler()->getServiceReferenceGraph())) { + if (!$this->isInlineableDefinition($id, $definition)) { return $value; } $this->container->log($this, sprintf('Inlined service "%s" to "%s".', $id, $this->currentId)); + $this->inlinedIds[$id] = $definition->isPublic(); + $this->notInlinedIds[$this->currentId] = true; if ($definition->isShared()) { return $definition; @@ -86,7 +155,7 @@ protected function processValue($value, $isRoot = false) * * @return bool If the definition is inlineable */ - private function isInlineableDefinition($id, Definition $definition, ServiceReferenceGraph $graph) + private function isInlineableDefinition($id, Definition $definition) { if ($definition->getErrors() || $definition->isDeprecated() || $definition->isLazy() || $definition->isSynthetic()) { return false; @@ -100,30 +169,37 @@ private function isInlineableDefinition($id, Definition $definition, ServiceRefe return false; } - if (!$graph->hasNode($id)) { + if (!$this->graph->hasNode($id)) { return true; } if ($this->currentId == $id) { return false; } + $this->connectedIds[$id] = true; - $ids = array(); - foreach ($graph->getNode($id)->getInEdges() as $edge) { + $srcIds = array(); + $srcCount = 0; + foreach ($this->graph->getNode($id)->getInEdges() as $edge) { + $srcId = $edge->getSourceNode()->getId(); + $this->connectedIds[$srcId] = true; if ($edge->isWeak()) { return false; } - $ids[] = $edge->getSourceNode()->getId(); + $srcIds[$srcId] = true; + ++$srcCount; } - if (count(array_unique($ids)) > 1) { + if (1 !== \count($srcIds)) { + $this->notInlinedIds[$id] = true; + return false; } - if (count($ids) > 1 && is_array($factory = $definition->getFactory()) && ($factory[0] instanceof Reference || $factory[0] instanceof Definition)) { + if ($srcCount > 1 && is_array($factory = $definition->getFactory()) && ($factory[0] instanceof Reference || $factory[0] instanceof Definition)) { return false; } - return !$ids || $this->container->getDefinition($ids[0])->isShared(); + return $this->container->getDefinition($srcId)->isShared(); } } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php index 170a0edc8aabb..0d8ba9c047df8 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php @@ -81,12 +81,8 @@ public function __construct() new RemovePrivateAliasesPass(), new ReplaceAliasByActualDefinitionPass(), new RemoveAbstractDefinitionsPass(), - new RepeatedPass(array( - new AnalyzeServiceReferencesPass(), - new InlineServiceDefinitionsPass(), - new AnalyzeServiceReferencesPass(), - new RemoveUnusedDefinitionsPass(), - )), + new RemoveUnusedDefinitionsPass(), + new InlineServiceDefinitionsPass(new AnalyzeServiceReferencesPass()), new DefinitionErrorExceptionPass(), new CheckExceptionOnInvalidReferenceBehaviorPass(), new ResolveHotPathPass(), diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RemoveUnusedDefinitionsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/RemoveUnusedDefinitionsPass.php index ec2eed27edbc8..1bebd7ad5c1c4 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/RemoveUnusedDefinitionsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/RemoveUnusedDefinitionsPass.php @@ -12,22 +12,24 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; /** * Removes unused service definitions from the container. * * @author Johannes M. Schmitt + * @author Nicolas Grekas */ -class RemoveUnusedDefinitionsPass implements RepeatablePassInterface +class RemoveUnusedDefinitionsPass extends AbstractRecursivePass implements RepeatablePassInterface { - private $repeatedPass; + private $connectedIds = array(); /** * {@inheritdoc} */ public function setRepeatedPass(RepeatedPass $repeatedPass) { - $this->repeatedPass = $repeatedPass; + @trigger_error(sprintf('The "%s" method is deprecated since Symfony 4.2.', __METHOD__), E_USER_DEPRECATED); } /** @@ -35,51 +37,62 @@ public function setRepeatedPass(RepeatedPass $repeatedPass) */ public function process(ContainerBuilder $container) { - $graph = $container->getCompiler()->getServiceReferenceGraph(); + try { + $this->enableExpressionProcessing(); + $this->container = $container; + $connectedIds = array(); + $aliases = $container->getAliases(); - $hasChanged = false; - foreach ($container->getDefinitions() as $id => $definition) { - if ($definition->isPublic()) { - continue; + foreach ($aliases as $id => $alias) { + if ($alias->isPublic()) { + $this->connectedIds[] = (string) $aliases[$id]; + } } - if ($graph->hasNode($id)) { - $edges = $graph->getNode($id)->getInEdges(); - $referencingAliases = array(); - $sourceIds = array(); - foreach ($edges as $edge) { - if ($edge->isWeak()) { - continue; - } - $node = $edge->getSourceNode(); - $sourceIds[] = $node->getId(); + foreach ($container->getDefinitions() as $id => $definition) { + if ($definition->isPublic()) { + $connectedIds[$id] = true; + $this->processValue($definition); + } + } - if ($node->isAlias()) { - $referencingAliases[] = $node->getValue(); + while ($this->connectedIds) { + $ids = $this->connectedIds; + $this->connectedIds = array(); + foreach ($ids as $id) { + if (!isset($connectedIds[$id]) && $container->has($id)) { + $connectedIds[$id] = true; + $this->processValue($container->getDefinition($id)); } } - $isReferenced = (count(array_unique($sourceIds)) - count($referencingAliases)) > 0; - } else { - $referencingAliases = array(); - $isReferenced = false; } - if (1 === count($referencingAliases) && false === $isReferenced) { - $container->setDefinition((string) reset($referencingAliases), $definition); - $definition->setPublic(!$definition->isPrivate()); - $definition->setPrivate(reset($referencingAliases)->isPrivate()); - $container->removeDefinition($id); - $container->log($this, sprintf('Removed service "%s"; reason: replaces alias %s.', $id, reset($referencingAliases))); - } elseif (0 === count($referencingAliases) && false === $isReferenced) { - $container->removeDefinition($id); - $container->resolveEnvPlaceholders(serialize($definition)); - $container->log($this, sprintf('Removed service "%s"; reason: unused.', $id)); - $hasChanged = true; + foreach ($container->getDefinitions() as $id => $definition) { + if (!isset($connectedIds[$id])) { + $container->removeDefinition($id); + $container->resolveEnvPlaceholders(serialize($definition)); + $container->log($this, sprintf('Removed service "%s"; reason: unused.', $id)); + } } + } finally { + $this->container = null; + $this->connectedIds = array(); } + } - if ($hasChanged) { - $this->repeatedPass->setRepeat(); + /** + * {@inheritdoc} + */ + protected function processValue($value, $isRoot = false) + { + if (!$value instanceof Reference) { + return parent::processValue($value); } + + if (ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE !== $value->getInvalidBehavior()) { + $this->connectedIds[] = (string) $value; + } + + return $value; } } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RepeatablePassInterface.php b/src/Symfony/Component/DependencyInjection/Compiler/RepeatablePassInterface.php index 2b88bfb917a0f..11a5b0d54ffa9 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/RepeatablePassInterface.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/RepeatablePassInterface.php @@ -16,6 +16,8 @@ * RepeatedPass. * * @author Johannes M. Schmitt + * + * @deprecated since Symfony 4.2. */ interface RepeatablePassInterface extends CompilerPassInterface { diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RepeatedPass.php b/src/Symfony/Component/DependencyInjection/Compiler/RepeatedPass.php index 3da1a0d5be8e3..b8add1b83d263 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/RepeatedPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/RepeatedPass.php @@ -11,6 +11,8 @@ namespace Symfony\Component\DependencyInjection\Compiler; +@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.2.', RepeatedPass::class), E_USER_DEPRECATED); + use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; @@ -18,6 +20,8 @@ * A pass that might be run repeatedly. * * @author Johannes M. Schmitt + * + * @deprecated since Symfony 4.2. */ class RepeatedPass implements CompilerPassInterface { diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index ce96fdd607bef..ed3ea9c32ec8e 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -1809,12 +1809,15 @@ private function isHotPath(Definition $definition) private function isSingleUsePrivateNode(ServiceReferenceGraphNode $node): bool { - if ($node->getValue()->isPublic()) { + if (!$node->getValue() || $node->getValue()->isPublic()) { return false; } $ids = array(); foreach ($node->getInEdges() as $edge) { - if ($edge->isLazy() || !$edge->getSourceNode()->getValue()->isShared()) { + if (!$value = $edge->getSourceNode()->getValue()) { + continue; + } + if ($edge->isLazy() || !$value->isShared()) { return false; } $ids[$edge->getSourceNode()->getId()] = true; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AnalyzeServiceReferencesPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AnalyzeServiceReferencesPassTest.php index 7a06e10d9ffad..2f0b56ae7c924 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AnalyzeServiceReferencesPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AnalyzeServiceReferencesPassTest.php @@ -15,7 +15,6 @@ use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Compiler\AnalyzeServiceReferencesPass; -use Symfony\Component\DependencyInjection\Compiler\RepeatedPass; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -207,7 +206,7 @@ public function testProcessDetectsFactoryReferences() protected function process(ContainerBuilder $container) { - $pass = new RepeatedPass(array(new AnalyzeServiceReferencesPass())); + $pass = new AnalyzeServiceReferencesPass(); $pass->process($container); return $container->getCompiler()->getServiceReferenceGraph(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/InlineServiceDefinitionsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/InlineServiceDefinitionsPassTest.php index 78556b2ed21cd..91e7566f1db04 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/InlineServiceDefinitionsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/InlineServiceDefinitionsPassTest.php @@ -14,7 +14,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Compiler\AnalyzeServiceReferencesPass; -use Symfony\Component\DependencyInjection\Compiler\RepeatedPass; use Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -26,7 +25,7 @@ class InlineServiceDefinitionsPassTest extends TestCase public function testProcess() { $container = new ContainerBuilder(); - $container + $inlineable = $container ->register('inlinable.service') ->setPublic(false) ; @@ -40,7 +39,8 @@ public function testProcess() $arguments = $container->getDefinition('service')->getArguments(); $this->assertInstanceOf('Symfony\Component\DependencyInjection\Definition', $arguments[0]); - $this->assertSame($container->getDefinition('inlinable.service'), $arguments[0]); + $this->assertSame($inlineable, $arguments[0]); + $this->assertFalse($container->has('inlinable.service')); } public function testProcessDoesNotInlinesWhenAliasedServiceIsShared() @@ -70,7 +70,7 @@ public function testProcessDoesInlineNonSharedService() ->register('foo') ->setShared(false) ; - $container + $bar = $container ->register('bar') ->setPublic(false) ->setShared(false) @@ -88,8 +88,9 @@ public function testProcessDoesInlineNonSharedService() $this->assertEquals($container->getDefinition('foo'), $arguments[0]); $this->assertNotSame($container->getDefinition('foo'), $arguments[0]); $this->assertSame($ref, $arguments[1]); - $this->assertEquals($container->getDefinition('bar'), $arguments[2]); - $this->assertNotSame($container->getDefinition('bar'), $arguments[2]); + $this->assertEquals($bar, $arguments[2]); + $this->assertNotSame($bar, $arguments[2]); + $this->assertFalse($container->has('bar')); } public function testProcessDoesNotInlineMixedServicesLoop() @@ -327,7 +328,6 @@ public function testProcessDoesNotSetLazyArgumentValuesAfterInlining() protected function process(ContainerBuilder $container) { - $repeatedPass = new RepeatedPass(array(new AnalyzeServiceReferencesPass(), new InlineServiceDefinitionsPass())); - $repeatedPass->process($container); + (new InlineServiceDefinitionsPass(new AnalyzeServiceReferencesPass()))->process($container); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RemoveUnusedDefinitionsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RemoveUnusedDefinitionsPassTest.php index c6f4e5e79d64d..c90a8d6728fe2 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RemoveUnusedDefinitionsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RemoveUnusedDefinitionsPassTest.php @@ -12,8 +12,6 @@ namespace Symfony\Component\DependencyInjection\Tests\Compiler; use PHPUnit\Framework\TestCase; -use Symfony\Component\DependencyInjection\Compiler\AnalyzeServiceReferencesPass; -use Symfony\Component\DependencyInjection\Compiler\RepeatedPass; use Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass; use Symfony\Component\DependencyInjection\Compiler\ResolveParameterPlaceHoldersPass; use Symfony\Component\DependencyInjection\Definition; @@ -131,7 +129,6 @@ public function testProcessConsiderEnvVariablesAsUsedEvenInPrivateServices() protected function process(ContainerBuilder $container) { - $repeatedPass = new RepeatedPass(array(new AnalyzeServiceReferencesPass(), new RemoveUnusedDefinitionsPass())); - $repeatedPass->process($container); + (new RemoveUnusedDefinitionsPass())->process($container); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_instanceof.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_instanceof.xml index 839776a3fed97..8e26c56b75bdf 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_instanceof.xml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_instanceof.xml @@ -6,7 +6,7 @@ - + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_instanceof.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_instanceof.yml index a58cc079e455f..dd93ab8de4664 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_instanceof.yml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_instanceof.yml @@ -7,5 +7,7 @@ services: - { name: foo } - { name: bar } - Symfony\Component\DependencyInjection\Tests\Fixtures\Bar: ~ + Symfony\Component\DependencyInjection\Tests\Fixtures\Bar: + public: true + Symfony\Component\DependencyInjection\Tests\Fixtures\BarInterface: '@Symfony\Component\DependencyInjection\Tests\Fixtures\Bar' From 6764d4e01272b922f6f3c9ab2e3dee2fd19acd43 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 5 Jun 2018 10:01:54 +0200 Subject: [PATCH 065/938] [FrameworkBundle] Fix test-container on kernel reboot, revert to returning the real container from Client::getContainer() --- src/Symfony/Bundle/FrameworkBundle/Client.php | 8 +- .../TestServiceContainerRealRefPass.php | 8 +- .../TestServiceContainerWeakRefPass.php | 4 +- .../FrameworkBundle/Resources/config/test.xml | 14 ++-- .../FrameworkBundle/Test/TestContainer.php | 81 ++++++++++++++----- .../Tests/Functional/ContainerDumpTest.php | 4 +- .../Tests/Functional/LogoutTest.php | 8 +- .../Bundle/SecurityBundle/composer.json | 2 +- 8 files changed, 82 insertions(+), 47 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Client.php b/src/Symfony/Bundle/FrameworkBundle/Client.php index a5f6a1500bfbf..bc76ce28f0cd1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Client.php +++ b/src/Symfony/Bundle/FrameworkBundle/Client.php @@ -30,15 +30,13 @@ class Client extends BaseClient private $hasPerformedRequest = false; private $profiler = false; private $reboot = true; - private $testContainerId; /** * {@inheritdoc} */ - public function __construct(KernelInterface $kernel, array $server = array(), History $history = null, CookieJar $cookieJar = null, string $testContainerId = null) + public function __construct(KernelInterface $kernel, array $server = array(), History $history = null, CookieJar $cookieJar = null) { parent::__construct($kernel, $server, $history, $cookieJar); - $this->testContainerId = $testContainerId; } /** @@ -48,9 +46,7 @@ public function __construct(KernelInterface $kernel, array $server = array(), Hi */ public function getContainer() { - $container = $this->kernel->getContainer(); - - return null !== $this->testContainerId ? $container->get($this->testContainerId) : $container; + return $this->kernel->getContainer(); } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php index 19b36e3d2c843..a4eaa9fbe36e8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php @@ -22,15 +22,11 @@ class TestServiceContainerRealRefPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { - if (!$container->hasDefinition('test.service_container')) { + if (!$container->hasDefinition('test.private_services_locator')) { return; } - $testContainer = $container->getDefinition('test.service_container'); - $privateContainer = $testContainer->getArgument(2); - if ($privateContainer instanceof Reference) { - $privateContainer = $container->getDefinition((string) $privateContainer); - } + $privateContainer = $container->getDefinition('test.private_services_locator'); $definitions = $container->getDefinitions(); $privateServices = $privateContainer->getArgument(0); diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php index 51fe553e19b2b..060d234d38772 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php @@ -23,7 +23,7 @@ class TestServiceContainerWeakRefPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { - if (!$container->hasDefinition('test.service_container')) { + if (!$container->hasDefinition('test.private_services_locator')) { return; } @@ -50,7 +50,7 @@ public function process(ContainerBuilder $container) } if ($privateServices) { - $definitions[(string) $definitions['test.service_container']->getArgument(2)]->replaceArgument(0, $privateServices); + $definitions['test.private_services_locator']->replaceArgument(0, $privateServices); } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/test.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/test.xml index f159208a41a28..09df99cfc086f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/test.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/test.xml @@ -16,7 +16,6 @@ %test.client.parameters% - test.service_container @@ -36,13 +35,12 @@ - - - - - - - + + test.private_services_locator + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/TestContainer.php b/src/Symfony/Bundle/FrameworkBundle/Test/TestContainer.php index 451faa89bd9e3..5b9c1eb51c23f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/TestContainer.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/TestContainer.php @@ -11,24 +11,23 @@ namespace Symfony\Bundle\FrameworkBundle\Test; -use Psr\Container\ContainerInterface as PsrContainerInterface; use Symfony\Component\DependencyInjection\Container; -use Symfony\Component\DependencyInjection\ContainerInterface as SymfonyContainerInterface; -use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; +use Symfony\Component\HttpKernel\KernelInterface; /** * @author Nicolas Grekas + * + * @internal */ class TestContainer extends Container { - private $publicContainer; - private $privateContainer; + private $kernel; + private $privateServicesLocatorId; - public function __construct(?ParameterBagInterface $parameterBag, SymfonyContainerInterface $publicContainer, PsrContainerInterface $privateContainer) + public function __construct(KernelInterface $kernel, string $privateServicesLocatorId) { - $this->parameterBag = $parameterBag ?? $publicContainer->getParameterBag(); - $this->publicContainer = $publicContainer; - $this->privateContainer = $privateContainer; + $this->kernel = $kernel; + $this->privateServicesLocatorId = $privateServicesLocatorId; } /** @@ -36,7 +35,7 @@ public function __construct(?ParameterBagInterface $parameterBag, SymfonyContain */ public function compile() { - $this->publicContainer->compile(); + $this->getPublicContainer()->compile(); } /** @@ -44,7 +43,39 @@ public function compile() */ public function isCompiled() { - return $this->publicContainer->isCompiled(); + return $this->getPublicContainer()->isCompiled(); + } + + /** + * {@inheritdoc} + */ + public function getParameterBag() + { + return $this->getPublicContainer()->getParameterBag(); + } + + /** + * {@inheritdoc} + */ + public function getParameter($name) + { + return $this->getPublicContainer()->getParameter($name); + } + + /** + * {@inheritdoc} + */ + public function hasParameter($name) + { + return $this->getPublicContainer()->hasParameter($name); + } + + /** + * {@inheritdoc} + */ + public function setParameter($name, $value) + { + $this->getPublicContainer()->setParameter($name, $value); } /** @@ -52,7 +83,7 @@ public function isCompiled() */ public function set($id, $service) { - $this->publicContainer->set($id, $service); + $this->getPublicContainer()->set($id, $service); } /** @@ -60,7 +91,7 @@ public function set($id, $service) */ public function has($id) { - return $this->publicContainer->has($id) || $this->privateContainer->has($id); + return $this->getPublicContainer()->has($id) || $this->getPrivateContainer()->has($id); } /** @@ -68,7 +99,7 @@ public function has($id) */ public function get($id, $invalidBehavior = /* self::EXCEPTION_ON_INVALID_REFERENCE */ 1) { - return $this->privateContainer->has($id) ? $this->privateContainer->get($id) : $this->publicContainer->get($id, $invalidBehavior); + return $this->getPrivateContainer()->has($id) ? $this->getPrivateContainer()->get($id) : $this->getPublicContainer()->get($id, $invalidBehavior); } /** @@ -76,7 +107,7 @@ public function get($id, $invalidBehavior = /* self::EXCEPTION_ON_INVALID_REFERE */ public function initialized($id) { - return $this->publicContainer->initialized($id); + return $this->getPublicContainer()->initialized($id); } /** @@ -84,7 +115,7 @@ public function initialized($id) */ public function reset() { - $this->publicContainer->reset(); + $this->getPublicContainer()->reset(); } /** @@ -92,7 +123,7 @@ public function reset() */ public function getServiceIds() { - return $this->publicContainer->getServiceIds(); + return $this->getPublicContainer()->getServiceIds(); } /** @@ -100,6 +131,20 @@ public function getServiceIds() */ public function getRemovedIds() { - return $this->publicContainer->getRemovedIds(); + return $this->getPublicContainer()->getRemovedIds(); + } + + private function getPublicContainer() + { + if (null === $container = $this->kernel->getContainer()) { + throw new \LogicException('Cannot access the container on a non-booted kernel. Did you forget to boot it?'); + } + + return $container; + } + + private function getPrivateContainer() + { + return $this->getPublicContainer()->get($this->privateServicesLocatorId); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDumpTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDumpTest.php index 423d673be2f96..1eff55d4801ad 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDumpTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDumpTest.php @@ -20,13 +20,13 @@ public function testContainerCompilationInDebug() { $client = $this->createClient(array('test_case' => 'ContainerDump', 'root_config' => 'config.yml')); - $this->assertTrue($client->getContainer()->has('serializer')); + $this->assertTrue(static::$container->has('serializer')); } public function testContainerCompilation() { $client = $this->createClient(array('test_case' => 'ContainerDump', 'root_config' => 'config.yml', 'debug' => false)); - $this->assertTrue($client->getContainer()->has('serializer')); + $this->assertTrue(static::$container->has('serializer')); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LogoutTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LogoutTest.php index d3c3b77fd5d61..1c4d1a7e7f512 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LogoutTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LogoutTest.php @@ -35,18 +35,18 @@ public function testSessionLessRememberMeLogout() public function testCsrfTokensAreClearedOnLogout() { $client = $this->createClient(array('test_case' => 'LogoutWithoutSessionInvalidation', 'root_config' => 'config.yml')); - $client->getContainer()->get('security.csrf.token_storage')->setToken('foo', 'bar'); + static::$container->get('security.csrf.token_storage')->setToken('foo', 'bar'); $client->request('POST', '/login', array( '_username' => 'johannes', '_password' => 'test', )); - $this->assertTrue($client->getContainer()->get('security.csrf.token_storage')->hasToken('foo')); - $this->assertSame('bar', $client->getContainer()->get('security.csrf.token_storage')->getToken('foo')); + $this->assertTrue(static::$container->get('security.csrf.token_storage')->hasToken('foo')); + $this->assertSame('bar', static::$container->get('security.csrf.token_storage')->getToken('foo')); $client->request('GET', '/logout'); - $this->assertFalse($client->getContainer()->get('security.csrf.token_storage')->hasToken('foo')); + $this->assertFalse(static::$container->get('security.csrf.token_storage')->hasToken('foo')); } } diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index 31ecaeebeb4a3..e4d0e5ea2eedf 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -47,7 +47,7 @@ "symfony/security": "4.1.0-beta1|4.1.0-beta2", "symfony/var-dumper": "<3.4", "symfony/event-dispatcher": "<3.4", - "symfony/framework-bundle": "<=4.1-beta2", + "symfony/framework-bundle": "<4.1.1", "symfony/console": "<3.4" }, "autoload": { From f03b8bba9d8b268f3455b6bacc73f53186dd12bd Mon Sep 17 00:00:00 2001 From: Thomas Perez Date: Wed, 30 May 2018 12:23:13 +0200 Subject: [PATCH 066/938] CacheWarmerAggregate handle deprecations logs --- .../Resources/config/services.xml | 2 + .../CacheWarmer/CacheWarmerAggregate.php | 67 ++++++++++++++++--- 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml index 2c0072d85d9a1..ec6128553ff73 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml @@ -32,6 +32,8 @@ + %kernel.debug% + %kernel.cache_dir%/%kernel.container_class%Deprecations.log diff --git a/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerAggregate.php b/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerAggregate.php index 8a57732bf3848..4d63804be7d67 100644 --- a/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerAggregate.php +++ b/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerAggregate.php @@ -21,12 +21,16 @@ class CacheWarmerAggregate implements CacheWarmerInterface { private $warmers; + private $debug; + private $deprecationLogsFilepath; private $optionalsEnabled = false; private $onlyOptionalsEnabled = false; - public function __construct(iterable $warmers = array()) + public function __construct(iterable $warmers = array(), bool $debug = false, string $deprecationLogsFilepath = null) { $this->warmers = $warmers; + $this->debug = $debug; + $this->deprecationLogsFilepath = $deprecationLogsFilepath; } public function enableOptionalWarmers() @@ -46,15 +50,62 @@ public function enableOnlyOptionalWarmers() */ public function warmUp($cacheDir) { - foreach ($this->warmers as $warmer) { - if (!$this->optionalsEnabled && $warmer->isOptional()) { - continue; - } - if ($this->onlyOptionalsEnabled && !$warmer->isOptional()) { - continue; + if ($this->debug) { + $collectedLogs = array(); + $previousHandler = defined('PHPUNIT_COMPOSER_INSTALL'); + $previousHandler = $previousHandler ?: set_error_handler(function ($type, $message, $file, $line) use (&$collectedLogs, &$previousHandler) { + if (E_USER_DEPRECATED !== $type && E_DEPRECATED !== $type) { + return $previousHandler ? $previousHandler($type, $message, $file, $line) : false; + } + + if (isset($collectedLogs[$message])) { + ++$collectedLogs[$message]['count']; + + return; + } + + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3); + // Clean the trace by removing first frames added by the error handler itself. + for ($i = 0; isset($backtrace[$i]); ++$i) { + if (isset($backtrace[$i]['file'], $backtrace[$i]['line']) && $backtrace[$i]['line'] === $line && $backtrace[$i]['file'] === $file) { + $backtrace = array_slice($backtrace, 1 + $i); + break; + } + } + + $collectedLogs[$message] = array( + 'type' => $type, + 'message' => $message, + 'file' => $file, + 'line' => $line, + 'trace' => $backtrace, + 'count' => 1, + ); + }); + } + + try { + foreach ($this->warmers as $warmer) { + if (!$this->optionalsEnabled && $warmer->isOptional()) { + continue; + } + if ($this->onlyOptionalsEnabled && !$warmer->isOptional()) { + continue; + } + + $warmer->warmUp($cacheDir); } + } finally { + if ($this->debug && true !== $previousHandler) { + restore_error_handler(); - $warmer->warmUp($cacheDir); + if (file_exists($this->deprecationLogsFilepath)) { + $previousLogs = unserialize(file_get_contents($this->deprecationLogsFilepath)); + $collectedLogs = array_merge($previousLogs, $collectedLogs); + } + + file_put_contents($this->deprecationLogsFilepath, serialize(array_values($collectedLogs))); + } } } From 7f9780b5dfaf2df83990a4e95af9f4191f8ff50a Mon Sep 17 00:00:00 2001 From: Pascal Montoya Date: Wed, 6 Jun 2018 10:34:52 +0200 Subject: [PATCH 067/938] Pass previous exception to FatalErrorException --- .../Component/Debug/Exception/ClassNotFoundException.php | 3 +++ src/Symfony/Component/Debug/Exception/FatalErrorException.php | 4 ++-- .../Component/Debug/Exception/UndefinedFunctionException.php | 3 +++ .../Component/Debug/Exception/UndefinedMethodException.php | 3 +++ 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Debug/Exception/ClassNotFoundException.php b/src/Symfony/Component/Debug/Exception/ClassNotFoundException.php index b91bf46631bbb..de5c45644363b 100644 --- a/src/Symfony/Component/Debug/Exception/ClassNotFoundException.php +++ b/src/Symfony/Component/Debug/Exception/ClassNotFoundException.php @@ -26,6 +26,9 @@ public function __construct($message, \ErrorException $previous) $previous->getSeverity(), $previous->getFile(), $previous->getLine(), + null, + true, + null, $previous->getPrevious() ); $this->setTrace($previous->getTrace()); diff --git a/src/Symfony/Component/Debug/Exception/FatalErrorException.php b/src/Symfony/Component/Debug/Exception/FatalErrorException.php index db2fb43bbceb5..3fd7c45fdbf95 100644 --- a/src/Symfony/Component/Debug/Exception/FatalErrorException.php +++ b/src/Symfony/Component/Debug/Exception/FatalErrorException.php @@ -35,9 +35,9 @@ class FatalErrorException extends \ErrorException */ class FatalErrorException extends LegacyFatalErrorException { - public function __construct($message, $code, $severity, $filename, $lineno, $traceOffset = null, $traceArgs = true, array $trace = null) + public function __construct($message, $code, $severity, $filename, $lineno, $traceOffset = null, $traceArgs = true, array $trace = null, $previous = null) { - parent::__construct($message, $code, $severity, $filename, $lineno); + parent::__construct($message, $code, $severity, $filename, $lineno, $previous); if (null !== $trace) { if (!$traceArgs) { diff --git a/src/Symfony/Component/Debug/Exception/UndefinedFunctionException.php b/src/Symfony/Component/Debug/Exception/UndefinedFunctionException.php index a66ae2a3879c9..8f5f454e55d99 100644 --- a/src/Symfony/Component/Debug/Exception/UndefinedFunctionException.php +++ b/src/Symfony/Component/Debug/Exception/UndefinedFunctionException.php @@ -26,6 +26,9 @@ public function __construct($message, \ErrorException $previous) $previous->getSeverity(), $previous->getFile(), $previous->getLine(), + null, + true, + null, $previous->getPrevious() ); $this->setTrace($previous->getTrace()); diff --git a/src/Symfony/Component/Debug/Exception/UndefinedMethodException.php b/src/Symfony/Component/Debug/Exception/UndefinedMethodException.php index 350dc3187f475..f7e340baf4dc6 100644 --- a/src/Symfony/Component/Debug/Exception/UndefinedMethodException.php +++ b/src/Symfony/Component/Debug/Exception/UndefinedMethodException.php @@ -26,6 +26,9 @@ public function __construct($message, \ErrorException $previous) $previous->getSeverity(), $previous->getFile(), $previous->getLine(), + null, + true, + null, $previous->getPrevious() ); $this->setTrace($previous->getTrace()); From c6acad719dfb93fe7785838ada7640cc51765979 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 6 Jun 2018 11:42:07 +0200 Subject: [PATCH 068/938] Revert "bug #26138 [HttpKernel] Catch HttpExceptions when templating is not installed (cilefen)" This reverts commit b213c5a758bc8b02375bd388b52b351c7e862f6a, reversing changes made to 61af0e3a25fbb2d169999a5b550c1f1801f1c0de. --- .../FrameworkBundle/Resources/config/web.xml | 10 ---------- .../EventListener/ExceptionListener.php | 16 +++------------- .../EventListener/ExceptionListenerTest.php | 17 ----------------- 3 files changed, 3 insertions(+), 40 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml index f6dd2bb9df630..0622c4196c104 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml @@ -67,16 +67,6 @@ - - - - null - - %kernel.debug% - %kernel.charset% - %debug.file_link_format% - - diff --git a/src/Symfony/Component/HttpKernel/EventListener/ExceptionListener.php b/src/Symfony/Component/HttpKernel/EventListener/ExceptionListener.php index 4d8ad1e7e5971..f18e42c7d3693 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/ExceptionListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/ExceptionListener.php @@ -12,11 +12,9 @@ namespace Symfony\Component\HttpKernel\EventListener; use Psr\Log\LoggerInterface; -use Symfony\Component\Debug\ExceptionHandler; use Symfony\Component\Debug\Exception\FlattenException; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\FilterResponseEvent; use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; @@ -35,16 +33,12 @@ class ExceptionListener implements EventSubscriberInterface protected $controller; protected $logger; protected $debug; - private $charset; - private $fileLinkFormat; - public function __construct($controller, LoggerInterface $logger = null, $debug = false, $charset = null, $fileLinkFormat = null) + public function __construct($controller, LoggerInterface $logger = null, $debug = false) { $this->controller = $controller; $this->logger = $logger; $this->debug = $debug; - $this->charset = $charset; - $this->fileLinkFormat = $fileLinkFormat; } public function onKernelException(GetResponseForExceptionEvent $event) @@ -123,12 +117,8 @@ protected function logException(\Exception $exception, $message) protected function duplicateRequest(\Exception $exception, Request $request) { $attributes = array( - 'exception' => $exception = FlattenException::create($exception), - '_controller' => $this->controller ?: function () use ($exception) { - $handler = new ExceptionHandler($this->debug, $this->charset, $this->fileLinkFormat); - - return new Response($handler->getHtml($exception), $exception->getStatusCode(), $exception->getHeaders()); - }, + '_controller' => $this->controller, + 'exception' => FlattenException::create($exception), 'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null, ); $request = $request->duplicate(null, null, $attributes); diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/ExceptionListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/ExceptionListenerTest.php index b607bf900ae91..3cb0b298bb07a 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/ExceptionListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/ExceptionListenerTest.php @@ -151,23 +151,6 @@ public function testCSPHeaderIsRemoved() $this->assertFalse($response->headers->has('content-security-policy'), 'CSP header has been removed'); $this->assertFalse($dispatcher->hasListeners(KernelEvents::RESPONSE), 'CSP removal listener has been removed'); } - - public function testNullController() - { - $listener = new ExceptionListener(null); - $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); - $kernel->expects($this->once())->method('handle')->will($this->returnCallback(function (Request $request) { - $controller = $request->attributes->get('_controller'); - - return $controller(); - })); - $request = Request::create('/'); - $event = new GetResponseForExceptionEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST, new \Exception('foo')); - - $listener->onKernelException($event); - - $this->assertContains('Whoops, looks like something went wrong.', $event->getResponse()->getContent()); - } } class TestLogger extends Logger implements DebugLoggerInterface From 725d774a165dcb71be5535d014097bae1a295e75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phan=20Kochen?= Date: Thu, 7 Jun 2018 10:48:34 +0200 Subject: [PATCH 069/938] Fix security-core cross-dependencies, fixes #27507 --- src/Symfony/Bundle/FrameworkBundle/composer.json | 2 +- src/Symfony/Component/Security/Csrf/composer.json | 2 +- src/Symfony/Component/Security/Guard/composer.json | 2 +- src/Symfony/Component/Security/Http/composer.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 06bd65eae4746..7742d5ca58efb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -29,7 +29,7 @@ "symfony/polyfill-mbstring": "~1.0", "symfony/filesystem": "~2.3|~3.0.0", "symfony/routing": "^2.8.17", - "symfony/security-core": "~2.6.13|~2.7.9|~2.8|~3.0.0", + "symfony/security-core": "^2.8.41|^3.3.17", "symfony/security-csrf": "^2.8.31|^3.3.13", "symfony/stopwatch": "~2.3|~3.0.0", "symfony/templating": "~2.7|~3.0.0", diff --git a/src/Symfony/Component/Security/Csrf/composer.json b/src/Symfony/Component/Security/Csrf/composer.json index 7a42b37d35e78..a015c5299118a 100644 --- a/src/Symfony/Component/Security/Csrf/composer.json +++ b/src/Symfony/Component/Security/Csrf/composer.json @@ -19,7 +19,7 @@ "php": ">=5.3.9", "symfony/polyfill-php56": "~1.0", "symfony/polyfill-php70": "~1.0", - "symfony/security-core": "~2.4|~3.0.0" + "symfony/security-core": "^2.8.41|^3.3.17" }, "require-dev": { "symfony/http-foundation": "^2.7.38|~3.3.13" diff --git a/src/Symfony/Component/Security/Guard/composer.json b/src/Symfony/Component/Security/Guard/composer.json index 35c7456638ea8..5f627c1ae07b8 100644 --- a/src/Symfony/Component/Security/Guard/composer.json +++ b/src/Symfony/Component/Security/Guard/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": ">=5.3.9", - "symfony/security-core": "~2.8|~3.0.0", + "symfony/security-core": "^2.8.41|^3.3.17", "symfony/security-http": "^2.8.31|^3.3.13" }, "require-dev": { diff --git a/src/Symfony/Component/Security/Http/composer.json b/src/Symfony/Component/Security/Http/composer.json index c3e4da18b5b83..925c3da0eab7a 100644 --- a/src/Symfony/Component/Security/Http/composer.json +++ b/src/Symfony/Component/Security/Http/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": ">=5.3.9", - "symfony/security-core": "^2.8.6", + "symfony/security-core": "^2.8.41", "symfony/event-dispatcher": "~2.1|~3.0.0", "symfony/http-foundation": "~2.4|~3.0.0", "symfony/http-kernel": "~2.4|~3.0.0", From 974991f8c69e54cf46a43f4d49deb2cf6d69f3fe Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 7 Jun 2018 22:45:56 +0200 Subject: [PATCH 070/938] [FrameworkBundle] remove dead code in CachePoolClearerPass --- .../Compiler/CachePoolClearerPass.php | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolClearerPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolClearerPass.php index 094712ded69d3..bd6908b9c4507 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolClearerPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolClearerPass.php @@ -11,7 +11,6 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; -use Symfony\Component\Cache\Adapter\AbstractAdapter; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; @@ -38,22 +37,5 @@ public function process(ContainerBuilder $container) } $clearer->replaceArgument(0, $pools); } - - if (!$container->has('cache.annotations')) { - return; - } - $factory = array(AbstractAdapter::class, 'createSystemCache'); - $annotationsPool = $container->findDefinition('cache.annotations'); - if ($factory !== $annotationsPool->getFactory() || 4 !== count($annotationsPool->getArguments())) { - return; - } - if ($container->has('monolog.logger.cache')) { - $annotationsPool->addArgument(new Reference('monolog.logger.cache')); - } elseif ($container->has('cache.system')) { - $systemPool = $container->findDefinition('cache.system'); - if ($factory === $systemPool->getFactory() && 5 <= count($systemArgs = $systemPool->getArguments())) { - $annotationsPool->addArgument($systemArgs[4]); - } - } } } From a74ee8d594eca15d5c1ebe9f7822b98410e50086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarmo=20Lepp=C3=A4nen?= Date: Thu, 7 Jun 2018 20:53:17 +0300 Subject: [PATCH 071/938] Update Finder.php Corrected return type which causes following error with (psalm)[https://getpsalm.org/] ``` ERROR: PossiblyInvalidArgument - src/Command/Utils/CheckVendorDependencies.php:170:62 - Argument 1 of iterator_to_array expects Traversable, possibly different type array|Iterator provided $directories = array_map($closure, iterator_to_array($finder->getIterator())); ``` --- src/Symfony/Component/Finder/Finder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Finder/Finder.php b/src/Symfony/Component/Finder/Finder.php index 0c039ae6666d0..f20a621ee9bef 100644 --- a/src/Symfony/Component/Finder/Finder.php +++ b/src/Symfony/Component/Finder/Finder.php @@ -668,7 +668,7 @@ public function in($dirs) * * This method implements the IteratorAggregate interface. * - * @return \Iterator|SplFileInfo[] An iterator + * @return \Iterator|\SplFileInfo[] An iterator * * @throws \LogicException if the in() method has not been called */ From b79f38c364f923bd8fb7cfc6fafe3ae635f0d7a1 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Thu, 7 Jun 2018 15:51:52 +0200 Subject: [PATCH 072/938] [WebServerBundle] Improve the error message when web server is already running --- .../Bundle/WebServerBundle/Command/ServerStartCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/WebServerBundle/Command/ServerStartCommand.php b/src/Symfony/Bundle/WebServerBundle/Command/ServerStartCommand.php index 664bce114b643..49e38701192e1 100644 --- a/src/Symfony/Bundle/WebServerBundle/Command/ServerStartCommand.php +++ b/src/Symfony/Bundle/WebServerBundle/Command/ServerStartCommand.php @@ -135,7 +135,7 @@ protected function execute(InputInterface $input, OutputInterface $output) try { $server = new WebServer(); if ($server->isRunning($input->getOption('pidfile'))) { - $io->error(sprintf('The web server is already running (listening on http://%s).', $server->getAddress($input->getOption('pidfile')))); + $io->error(sprintf('The web server has already been started. It is currently listening on http://%s. Please stop the web server before you try to start it again.', $server->getAddress($input->getOption('pidfile')))); return 1; } From 8fd4b441c4f5f0e4309d68d2a5e673bd68560764 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 8 Jun 2018 09:55:24 +0200 Subject: [PATCH 073/938] revert #27545 The SplFileInfo class indeed does exist in the Symfony\Component\Finder namespace. --- src/Symfony/Component/Finder/Finder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Finder/Finder.php b/src/Symfony/Component/Finder/Finder.php index f20a621ee9bef..0c039ae6666d0 100644 --- a/src/Symfony/Component/Finder/Finder.php +++ b/src/Symfony/Component/Finder/Finder.php @@ -668,7 +668,7 @@ public function in($dirs) * * This method implements the IteratorAggregate interface. * - * @return \Iterator|\SplFileInfo[] An iterator + * @return \Iterator|SplFileInfo[] An iterator * * @throws \LogicException if the in() method has not been called */ From 465b15caa8e7023c4d18f3869c63c8e9d5d30796 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 5 Jun 2018 21:18:46 +0200 Subject: [PATCH 074/938] [Routing] fix matching host patterns, utf8 prefixes and non-capturing groups --- .../Matcher/Dumper/PhpMatcherDumper.php | 7 +- .../Matcher/Dumper/StaticPrefixCollection.php | 16 +- .../Component/Routing/RouteCompiler.php | 2 +- .../Tests/Fixtures/dumper/url_matcher1.php | 96 +- .../Tests/Fixtures/dumper/url_matcher10.php | 2006 ++++++++--------- .../Tests/Fixtures/dumper/url_matcher12.php | 36 +- .../Tests/Fixtures/dumper/url_matcher13.php | 10 +- .../Tests/Fixtures/dumper/url_matcher2.php | 96 +- .../Tests/Fixtures/dumper/url_matcher8.php | 12 +- .../Routing/Tests/Matcher/UrlMatcherTest.php | 37 + .../Routing/Tests/RouteCompilerTest.php | 1 + 11 files changed, 1185 insertions(+), 1134 deletions(-) diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php index 0a79379160256..adb2b0a1c0148 100644 --- a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php +++ b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php @@ -376,10 +376,10 @@ private function compileDynamicRoutes(RouteCollection $collection, bool $matchHo if ($hostRegex) { preg_match('#^.\^(.*)\$.[a-zA-Z]*$#', $hostRegex, $rx); $state->vars = array(); - $hostRegex = '(?i:'.preg_replace_callback('#\?P<([^>]++)>#', $state->getVars, $rx[1]).')'; + $hostRegex = '(?i:'.preg_replace_callback('#\?P<([^>]++)>#', $state->getVars, $rx[1]).')\.'; $state->hostVars = $state->vars; } else { - $hostRegex = '[^/]*+'; + $hostRegex = '(?:(?:[^.]*+\.)++)'; $state->hostVars = array(); } $state->mark += strlen($rx = ($prev ? ')' : '')."|{$hostRegex}(?"); @@ -406,6 +406,7 @@ private function compileDynamicRoutes(RouteCollection $collection, bool $matchHo $rx = ")$}{$modifiers}"; $code .= "\n .'{$rx}',"; $state->regex .= $rx; + $state->markTail = 0; // if the regex is too large, throw a signaling exception to recompute with smaller chunk size set_error_handler(function ($type, $message) { throw 0 === strpos($message, $this->signalingException->getMessage()) ? $this->signalingException : new \ErrorException($message); }); @@ -427,7 +428,7 @@ private function compileDynamicRoutes(RouteCollection $collection, bool $matchHo EOF; } - $matchedPathinfo = $matchHost ? '$host.$pathinfo' : '$pathinfo'; + $matchedPathinfo = $matchHost ? '$host.\'.\'.$pathinfo' : '$pathinfo'; unset($state->getVars); return <<getCommonPrefix($prefix, $prefix); - } + list($prefix, $staticPrefix) = $this->getCommonPrefix($prefix, $prefix); for ($i = \count($this->items) - 1; 0 <= $i; --$i) { $item = $this->items[$i]; @@ -102,7 +100,7 @@ public function addRoute(string $prefix, $route, string $staticPrefix = null) if ($item instanceof self && $this->prefixes[$i] === $commonPrefix) { // the new route is a child of a previous one, let's nest it - $item->addRoute($prefix, $route, $staticPrefix); + $item->addRoute($prefix, $route); } else { // the new route and a previous one have a common prefix, let's merge them $child = new self($commonPrefix); @@ -176,7 +174,7 @@ private function getCommonPrefix(string $prefix, string $anotherPrefix): array break; } $subPattern = substr($prefix, $i, $j - $i); - if ($prefix !== $anotherPrefix && !preg_match('{(?> 6) && preg_match('//u', $prefix.' '.$anotherPrefix)) { + do { + // Prevent cutting in the middle of an UTF-8 characters + --$i; + } while (0b10 === (\ord($prefix[$i]) >> 6)); + } return array(substr($prefix, 0, $i), substr($prefix, 0, $staticLength ?? $i)); } diff --git a/src/Symfony/Component/Routing/RouteCompiler.php b/src/Symfony/Component/Routing/RouteCompiler.php index 7a89edd4897df..91b4a297cc179 100644 --- a/src/Symfony/Component/Routing/RouteCompiler.php +++ b/src/Symfony/Component/Routing/RouteCompiler.php @@ -321,7 +321,7 @@ private static function transformCapturingGroupsToNonCapturings(string $regexp): continue; } $regexp = substr_replace($regexp, '?:', $i, 0); - $i += 2; + ++$i; } return $regexp; diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php index 615a3cba3090a..b39bc7f2bae0a 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php @@ -79,50 +79,50 @@ public function match($rawPathinfo) return $ret; } - $matchedPathinfo = $host.$pathinfo; + $matchedPathinfo = $host.'.'.$pathinfo; $regexList = array( 0 => '{^(?' - .'|[^/]*+(?' - .'|/foo/(baz|symfony)(*:34)' + .'|(?:(?:[^.]*+\\.)++)(?' + .'|/foo/(baz|symfony)(*:46)' .'|/bar(?' - .'|/([^/]++)(*:57)' - .'|head/([^/]++)(*:77)' + .'|/([^/]++)(*:69)' + .'|head/([^/]++)(*:89)' .')' .'|/test/([^/]++)/(?' - .'|(*:103)' + .'|(*:115)' .')' - .'|/([\']+)(*:119)' + .'|/([\']+)(*:131)' .'|/a/(?' .'|b\'b/([^/]++)(?' - .'|(*:148)' - .'|(*:156)' + .'|(*:160)' + .'|(*:168)' .')' - .'|(.*)(*:169)' + .'|(.*)(*:181)' .'|b\'b/([^/]++)(?' - .'|(*:192)' - .'|(*:200)' + .'|(*:204)' + .'|(*:212)' .')' .')' - .'|/multi/hello(?:/([^/]++))?(*:236)' + .'|/multi/hello(?:/([^/]++))?(*:248)' .'|/([^/]++)/b/([^/]++)(?' - .'|(*:267)' - .'|(*:275)' + .'|(*:279)' + .'|(*:287)' .')' - .'|/aba/([^/]++)(*:297)' - .')|(?i:([^\\.]++)\\.example\\.com)(?' + .'|/aba/([^/]++)(*:309)' + .')|(?i:([^\\.]++)\\.example\\.com)\\.(?' .'|/route1(?' - .'|3/([^/]++)(*:357)' - .'|4/([^/]++)(*:375)' + .'|3/([^/]++)(*:371)' + .'|4/([^/]++)(*:389)' .')' - .')|(?i:c\\.example\\.com)(?' - .'|/route15/([^/]++)(*:425)' - .')|[^/]*+(?' - .'|/route16/([^/]++)(*:460)' + .')|(?i:c\\.example\\.com)\\.(?' + .'|/route15/([^/]++)(*:441)' + .')|(?:(?:[^.]*+\\.)++)(?' + .'|/route16/([^/]++)(*:488)' .'|/a/(?' - .'|a\\.\\.\\.(*:481)' + .'|a\\.\\.\\.(*:509)' .'|b/(?' - .'|([^/]++)(*:502)' - .'|c/([^/]++)(*:520)' + .'|([^/]++)(*:530)' + .'|c/([^/]++)(*:548)' .')' .')' .')' @@ -132,7 +132,7 @@ public function match($rawPathinfo) foreach ($regexList as $offset => $regex) { while (preg_match($regex, $matchedPathinfo, $matches)) { switch ($m = (int) $matches['MARK']) { - case 103: + case 115: $matches = array('foo' => $matches[1] ?? null); // baz4 @@ -159,7 +159,7 @@ public function match($rawPathinfo) not_bazbaz6: break; - case 148: + case 160: $matches = array('foo' => $matches[1] ?? null); // foo1 @@ -173,14 +173,14 @@ public function match($rawPathinfo) not_foo1: break; - case 192: + case 204: $matches = array('foo1' => $matches[1] ?? null); // foo2 return $this->mergeDefaults(array('_route' => 'foo2') + $matches, array()); break; - case 267: + case 279: $matches = array('_locale' => $matches[1] ?? null, 'foo' => $matches[2] ?? null); // foo3 @@ -189,23 +189,23 @@ public function match($rawPathinfo) break; default: $routes = array( - 34 => array(array('_route' => 'foo', 'def' => 'test'), array('bar'), null, null), - 57 => array(array('_route' => 'bar'), array('foo'), array('GET' => 0, 'HEAD' => 1), null), - 77 => array(array('_route' => 'barhead'), array('foo'), array('GET' => 0), null), - 119 => array(array('_route' => 'quoter'), array('quoter'), null, null), - 156 => array(array('_route' => 'bar1'), array('bar'), null, null), - 169 => array(array('_route' => 'overridden'), array('var'), null, null), - 200 => array(array('_route' => 'bar2'), array('bar1'), null, null), - 236 => array(array('_route' => 'helloWorld', 'who' => 'World!'), array('who'), null, null), - 275 => array(array('_route' => 'bar3'), array('_locale', 'bar'), null, null), - 297 => array(array('_route' => 'foo4'), array('foo'), null, null), - 357 => array(array('_route' => 'route13'), array('var1', 'name'), null, null), - 375 => array(array('_route' => 'route14', 'var1' => 'val'), array('var1', 'name'), null, null), - 425 => array(array('_route' => 'route15'), array('name'), null, null), - 460 => array(array('_route' => 'route16', 'var1' => 'val'), array('name'), null, null), - 481 => array(array('_route' => 'a'), array(), null, null), - 502 => array(array('_route' => 'b'), array('var'), null, null), - 520 => array(array('_route' => 'c'), array('var'), null, null), + 46 => array(array('_route' => 'foo', 'def' => 'test'), array('bar'), null, null), + 69 => array(array('_route' => 'bar'), array('foo'), array('GET' => 0, 'HEAD' => 1), null), + 89 => array(array('_route' => 'barhead'), array('foo'), array('GET' => 0), null), + 131 => array(array('_route' => 'quoter'), array('quoter'), null, null), + 168 => array(array('_route' => 'bar1'), array('bar'), null, null), + 181 => array(array('_route' => 'overridden'), array('var'), null, null), + 212 => array(array('_route' => 'bar2'), array('bar1'), null, null), + 248 => array(array('_route' => 'helloWorld', 'who' => 'World!'), array('who'), null, null), + 287 => array(array('_route' => 'bar3'), array('_locale', 'bar'), null, null), + 309 => array(array('_route' => 'foo4'), array('foo'), null, null), + 371 => array(array('_route' => 'route13'), array('var1', 'name'), null, null), + 389 => array(array('_route' => 'route14', 'var1' => 'val'), array('var1', 'name'), null, null), + 441 => array(array('_route' => 'route15'), array('name'), null, null), + 488 => array(array('_route' => 'route16', 'var1' => 'val'), array('name'), null, null), + 509 => array(array('_route' => 'a'), array(), null, null), + 530 => array(array('_route' => 'b'), array('var'), null, null), + 548 => array(array('_route' => 'c'), array('var'), null, null), ); list($ret, $vars, $requiredMethods, $requiredSchemes) = $routes[$m]; @@ -231,7 +231,7 @@ public function match($rawPathinfo) return $ret; } - if (520 === $m) { + if (548 === $m) { break; } $regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m)); diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher10.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher10.php index e976cd73f027c..81ded3417cdd3 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher10.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher10.php @@ -909,877 +909,877 @@ public function match($rawPathinfo) .')$}sD', 24786 => '{^(?' .'|/5(?' - .'|b69b9/([^/]++)/([^/]++)/([^/]++)/5b69b9(*:24845)' + .'|b69b9/([^/]++)/([^/]++)/([^/]++)/5b69b9(*:24837)' .'|9(?' - .'|b90e/([^/]++)/([^/]++)/([^/]++)/59b90e(*:24897)' - .'|c330/([^/]++)/([^/]++)/([^/]++)/59c330(*:24945)' + .'|b90e/([^/]++)/([^/]++)/([^/]++)/59b90e(*:24889)' + .'|c330/([^/]++)/([^/]++)/([^/]++)/59c330(*:24937)' .')' .'|3(?' - .'|fde9/([^/]++)/([^/]++)/([^/]++)/53fde9(*:24998)' - .'|e3a7/([^/]++)/([^/]++)/([^/]++)/53e3a7(*:25046)' + .'|fde9/([^/]++)/([^/]++)/([^/]++)/53fde9(*:24990)' + .'|e3a7/([^/]++)/([^/]++)/([^/]++)/53e3a7(*:25038)' .')' .'|e(?' - .'|a164/([^/]++)/([^/]++)/([^/]++)/5ea164(*:25099)' - .'|3881/([^/]++)/([^/]++)/([^/]++)/5e3881(*:25147)' - .'|9f92/([^/]++)/([^/]++)/([^/]++)/5e9f92(*:25195)' - .'|c91a/([^/]++)/([^/]++)/([^/]++)/5ec91a(*:25243)' + .'|a164/([^/]++)/([^/]++)/([^/]++)/5ea164(*:25091)' + .'|3881/([^/]++)/([^/]++)/([^/]++)/5e3881(*:25139)' + .'|9f92/([^/]++)/([^/]++)/([^/]++)/5e9f92(*:25187)' + .'|c91a/([^/]++)/([^/]++)/([^/]++)/5ec91a(*:25235)' .')' .'|7(?' - .'|3703/([^/]++)/([^/]++)/([^/]++)/573703(*:25296)' - .'|51ec/([^/]++)/([^/]++)/([^/]++)/5751ec(*:25344)' - .'|05e1/([^/]++)/([^/]++)/([^/]++)/5705e1(*:25392)' + .'|3703/([^/]++)/([^/]++)/([^/]++)/573703(*:25288)' + .'|51ec/([^/]++)/([^/]++)/([^/]++)/5751ec(*:25336)' + .'|05e1/([^/]++)/([^/]++)/([^/]++)/5705e1(*:25384)' .')' .'|8(?' - .'|ae74/([^/]++)/([^/]++)/([^/]++)/58ae74(*:25445)' - .'|d4d1/([^/]++)/([^/]++)/([^/]++)/58d4d1(*:25493)' - .'|07a6/([^/]++)/([^/]++)/([^/]++)/5807a6(*:25541)' - .'|e4d4/([^/]++)/([^/]++)/([^/]++)/58e4d4(*:25589)' + .'|ae74/([^/]++)/([^/]++)/([^/]++)/58ae74(*:25437)' + .'|d4d1/([^/]++)/([^/]++)/([^/]++)/58d4d1(*:25485)' + .'|07a6/([^/]++)/([^/]++)/([^/]++)/5807a6(*:25533)' + .'|e4d4/([^/]++)/([^/]++)/([^/]++)/58e4d4(*:25581)' .')' .'|d(?' - .'|44ee/([^/]++)/([^/]++)/([^/]++)/5d44ee(*:25642)' - .'|d9db/([^/]++)/([^/]++)/([^/]++)/5dd9db(*:25690)' + .'|44ee/([^/]++)/([^/]++)/([^/]++)/5d44ee(*:25634)' + .'|d9db/([^/]++)/([^/]++)/([^/]++)/5dd9db(*:25682)' .')' .'|5(?' - .'|b37c/([^/]++)/([^/]++)/([^/]++)/55b37c(*:25743)' - .'|743c/([^/]++)/([^/]++)/([^/]++)/55743c(*:25791)' - .'|6f39/([^/]++)/([^/]++)/([^/]++)/556f39(*:25839)' + .'|b37c/([^/]++)/([^/]++)/([^/]++)/55b37c(*:25735)' + .'|743c/([^/]++)/([^/]++)/([^/]++)/55743c(*:25783)' + .'|6f39/([^/]++)/([^/]++)/([^/]++)/556f39(*:25831)' .')' .'|c(?' - .'|0492/([^/]++)/([^/]++)/([^/]++)/5c0492(*:25892)' - .'|572e/([^/]++)/([^/]++)/([^/]++)/5c572e(*:25940)' - .'|9362/([^/]++)/([^/]++)/([^/]++)/5c9362(*:25988)' + .'|0492/([^/]++)/([^/]++)/([^/]++)/5c0492(*:25884)' + .'|572e/([^/]++)/([^/]++)/([^/]++)/5c572e(*:25932)' + .'|9362/([^/]++)/([^/]++)/([^/]++)/5c9362(*:25980)' .')' .'|4(?' - .'|8731/([^/]++)/([^/]++)/([^/]++)/548731(*:26041)' - .'|a367/([^/]++)/([^/]++)/([^/]++)/54a367(*:26089)' + .'|8731/([^/]++)/([^/]++)/([^/]++)/548731(*:26033)' + .'|a367/([^/]++)/([^/]++)/([^/]++)/54a367(*:26081)' .')' .'|0(?' - .'|0e75/([^/]++)/([^/]++)/([^/]++)/500e75(*:26142)' - .'|c3d7/([^/]++)/([^/]++)/([^/]++)/50c3d7(*:26190)' + .'|0e75/([^/]++)/([^/]++)/([^/]++)/500e75(*:26134)' + .'|c3d7/([^/]++)/([^/]++)/([^/]++)/50c3d7(*:26182)' .')' .'|f(?' - .'|2c22/([^/]++)/([^/]++)/([^/]++)/5f2c22(*:26243)' - .'|0f5e/([^/]++)/([^/]++)/([^/]++)/5f0f5e(*:26291)' + .'|2c22/([^/]++)/([^/]++)/([^/]++)/5f2c22(*:26235)' + .'|0f5e/([^/]++)/([^/]++)/([^/]++)/5f0f5e(*:26283)' .')' - .'|1ef18/([^/]++)/([^/]++)/([^/]++)/51ef18(*:26341)' + .'|1ef18/([^/]++)/([^/]++)/([^/]++)/51ef18(*:26333)' .')' .'|/b(?' .'|5(?' - .'|b41f/([^/]++)/([^/]++)/([^/]++)/b5b41f(*:26399)' - .'|dc4e/([^/]++)/([^/]++)/([^/]++)/b5dc4e(*:26447)' - .'|6a18/([^/]++)/([^/]++)/([^/]++)/b56a18(*:26495)' - .'|5ec2/([^/]++)/([^/]++)/([^/]++)/b55ec2(*:26543)' + .'|b41f/([^/]++)/([^/]++)/([^/]++)/b5b41f(*:26391)' + .'|dc4e/([^/]++)/([^/]++)/([^/]++)/b5dc4e(*:26439)' + .'|6a18/([^/]++)/([^/]++)/([^/]++)/b56a18(*:26487)' + .'|5ec2/([^/]++)/([^/]++)/([^/]++)/b55ec2(*:26535)' .')' - .'|337e8/([^/]++)/([^/]++)/([^/]++)/b337e8(*:26593)' + .'|337e8/([^/]++)/([^/]++)/([^/]++)/b337e8(*:26585)' .'|a(?' - .'|2fd3/([^/]++)/([^/]++)/([^/]++)/ba2fd3(*:26645)' - .'|3866/([^/]++)/([^/]++)/([^/]++)/ba3866(*:26693)' + .'|2fd3/([^/]++)/([^/]++)/([^/]++)/ba2fd3(*:26637)' + .'|3866/([^/]++)/([^/]++)/([^/]++)/ba3866(*:26685)' .')' .'|2(?' - .'|eeb7/([^/]++)/([^/]++)/([^/]++)/b2eeb7(*:26746)' - .'|f627/([^/]++)/([^/]++)/([^/]++)/b2f627(*:26794)' + .'|eeb7/([^/]++)/([^/]++)/([^/]++)/b2eeb7(*:26738)' + .'|f627/([^/]++)/([^/]++)/([^/]++)/b2f627(*:26786)' .')' .'|7(?' - .'|3dfe/([^/]++)/([^/]++)/([^/]++)/b73dfe(*:26847)' - .'|bb35/([^/]++)/([^/]++)/([^/]++)/b7bb35(*:26895)' - .'|ee6f/([^/]++)/([^/]++)/([^/]++)/b7ee6f(*:26943)' - .'|892f/([^/]++)/([^/]++)/([^/]++)/b7892f(*:26991)' - .'|0683/([^/]++)/([^/]++)/([^/]++)/b70683(*:27039)' + .'|3dfe/([^/]++)/([^/]++)/([^/]++)/b73dfe(*:26839)' + .'|bb35/([^/]++)/([^/]++)/([^/]++)/b7bb35(*:26887)' + .'|ee6f/([^/]++)/([^/]++)/([^/]++)/b7ee6f(*:26935)' + .'|892f/([^/]++)/([^/]++)/([^/]++)/b7892f(*:26983)' + .'|0683/([^/]++)/([^/]++)/([^/]++)/b70683(*:27031)' .')' .'|4(?' - .'|288d/([^/]++)/([^/]++)/([^/]++)/b4288d(*:27092)' - .'|a528/([^/]++)/([^/]++)/([^/]++)/b4a528(*:27140)' + .'|288d/([^/]++)/([^/]++)/([^/]++)/b4288d(*:27084)' + .'|a528/([^/]++)/([^/]++)/([^/]++)/b4a528(*:27132)' .')' .'|e(?' - .'|3159/([^/]++)/([^/]++)/([^/]++)/be3159(*:27193)' - .'|b22f/([^/]++)/([^/]++)/([^/]++)/beb22f(*:27241)' - .'|a595/([^/]++)/([^/]++)/([^/]++)/bea595(*:27289)' + .'|3159/([^/]++)/([^/]++)/([^/]++)/be3159(*:27185)' + .'|b22f/([^/]++)/([^/]++)/([^/]++)/beb22f(*:27233)' + .'|a595/([^/]++)/([^/]++)/([^/]++)/bea595(*:27281)' .')' .'|1(?' - .'|eec3/([^/]++)/([^/]++)/([^/]++)/b1eec3(*:27342)' - .'|37fd/([^/]++)/([^/]++)/([^/]++)/b137fd(*:27390)' + .'|eec3/([^/]++)/([^/]++)/([^/]++)/b1eec3(*:27334)' + .'|37fd/([^/]++)/([^/]++)/([^/]++)/b137fd(*:27382)' .')' .'|0(?' - .'|56eb/([^/]++)/([^/]++)/([^/]++)/b056eb(*:27443)' - .'|b183/([^/]++)/([^/]++)/([^/]++)/b0b183(*:27491)' + .'|56eb/([^/]++)/([^/]++)/([^/]++)/b056eb(*:27435)' + .'|b183/([^/]++)/([^/]++)/([^/]++)/b0b183(*:27483)' .')' - .'|f6276/([^/]++)/([^/]++)/([^/]++)/bf6276(*:27541)' + .'|f6276/([^/]++)/([^/]++)/([^/]++)/bf6276(*:27533)' .'|6(?' - .'|edc1/([^/]++)/([^/]++)/([^/]++)/b6edc1(*:27593)' - .'|a108/([^/]++)/([^/]++)/([^/]++)/b6a108(*:27641)' + .'|edc1/([^/]++)/([^/]++)/([^/]++)/b6edc1(*:27585)' + .'|a108/([^/]++)/([^/]++)/([^/]++)/b6a108(*:27633)' .')' - .'|86e8d/([^/]++)/([^/]++)/([^/]++)/b86e8d(*:27691)' + .'|86e8d/([^/]++)/([^/]++)/([^/]++)/b86e8d(*:27683)' .')' .'|/2(?' .'|8(?' - .'|5e19/([^/]++)/([^/]++)/([^/]++)/285e19(*:27749)' + .'|5e19/([^/]++)/([^/]++)/([^/]++)/285e19(*:27741)' .'|2(?' - .'|3f4/([^/]++)/([^/]++)/([^/]++)/2823f4(*:27800)' - .'|67a/([^/]++)/([^/]++)/([^/]++)/28267a(*:27847)' + .'|3f4/([^/]++)/([^/]++)/([^/]++)/2823f4(*:27792)' + .'|67a/([^/]++)/([^/]++)/([^/]++)/28267a(*:27839)' .')' - .'|8cc0/([^/]++)/([^/]++)/([^/]++)/288cc0(*:27896)' - .'|7e03/([^/]++)/([^/]++)/([^/]++)/287e03(*:27944)' + .'|8cc0/([^/]++)/([^/]++)/([^/]++)/288cc0(*:27888)' + .'|7e03/([^/]++)/([^/]++)/([^/]++)/287e03(*:27936)' .')' .'|d(?' - .'|6cc4/([^/]++)/([^/]++)/([^/]++)/2d6cc4(*:27997)' - .'|ea61/([^/]++)/([^/]++)/([^/]++)/2dea61(*:28045)' - .'|ace7/([^/]++)/([^/]++)/([^/]++)/2dace7(*:28093)' + .'|6cc4/([^/]++)/([^/]++)/([^/]++)/2d6cc4(*:27989)' + .'|ea61/([^/]++)/([^/]++)/([^/]++)/2dea61(*:28037)' + .'|ace7/([^/]++)/([^/]++)/([^/]++)/2dace7(*:28085)' .')' .'|b(?' - .'|8a61/([^/]++)/([^/]++)/([^/]++)/2b8a61(*:28146)' - .'|b232/([^/]++)/([^/]++)/([^/]++)/2bb232(*:28194)' - .'|a596/([^/]++)/([^/]++)/([^/]++)/2ba596(*:28242)' - .'|cab9/([^/]++)/([^/]++)/([^/]++)/2bcab9(*:28290)' + .'|8a61/([^/]++)/([^/]++)/([^/]++)/2b8a61(*:28138)' + .'|b232/([^/]++)/([^/]++)/([^/]++)/2bb232(*:28186)' + .'|a596/([^/]++)/([^/]++)/([^/]++)/2ba596(*:28234)' + .'|cab9/([^/]++)/([^/]++)/([^/]++)/2bcab9(*:28282)' .')' .'|9(?' - .'|8f95/([^/]++)/([^/]++)/([^/]++)/298f95(*:28343)' - .'|1597/([^/]++)/([^/]++)/([^/]++)/291597(*:28391)' + .'|8f95/([^/]++)/([^/]++)/([^/]++)/298f95(*:28335)' + .'|1597/([^/]++)/([^/]++)/([^/]++)/291597(*:28383)' .')' - .'|58be1/([^/]++)/([^/]++)/([^/]++)/258be1(*:28441)' + .'|58be1/([^/]++)/([^/]++)/([^/]++)/258be1(*:28433)' .'|3(?' - .'|3509/([^/]++)/([^/]++)/([^/]++)/233509(*:28493)' - .'|ce18/([^/]++)/([^/]++)/([^/]++)/23ce18(*:28541)' + .'|3509/([^/]++)/([^/]++)/([^/]++)/233509(*:28485)' + .'|ce18/([^/]++)/([^/]++)/([^/]++)/23ce18(*:28533)' .')' .'|6(?' - .'|dd0d/([^/]++)/([^/]++)/([^/]++)/26dd0d(*:28594)' - .'|408f/([^/]++)/([^/]++)/([^/]++)/26408f(*:28642)' + .'|dd0d/([^/]++)/([^/]++)/([^/]++)/26dd0d(*:28586)' + .'|408f/([^/]++)/([^/]++)/([^/]++)/26408f(*:28634)' .')' .'|f(?' - .'|37d1/([^/]++)/([^/]++)/([^/]++)/2f37d1(*:28695)' - .'|885d/([^/]++)/([^/]++)/([^/]++)/2f885d(*:28743)' + .'|37d1/([^/]++)/([^/]++)/([^/]++)/2f37d1(*:28687)' + .'|885d/([^/]++)/([^/]++)/([^/]++)/2f885d(*:28735)' .')' .'|2(?' - .'|91d2/([^/]++)/([^/]++)/([^/]++)/2291d2(*:28796)' - .'|ac3c/([^/]++)/([^/]++)/([^/]++)/22ac3c(*:28844)' - .'|fb0c/([^/]++)/([^/]++)/([^/]++)/22fb0c(*:28892)' + .'|91d2/([^/]++)/([^/]++)/([^/]++)/2291d2(*:28788)' + .'|ac3c/([^/]++)/([^/]++)/([^/]++)/22ac3c(*:28836)' + .'|fb0c/([^/]++)/([^/]++)/([^/]++)/22fb0c(*:28884)' .')' .'|4(?' - .'|6819/([^/]++)/([^/]++)/([^/]++)/246819(*:28945)' - .'|896e/([^/]++)/([^/]++)/([^/]++)/24896e(*:28993)' + .'|6819/([^/]++)/([^/]++)/([^/]++)/246819(*:28937)' + .'|896e/([^/]++)/([^/]++)/([^/]++)/24896e(*:28985)' .')' .'|a(?' - .'|fe45/([^/]++)/([^/]++)/([^/]++)/2afe45(*:29046)' - .'|084e/([^/]++)/([^/]++)/([^/]++)/2a084e(*:29094)' - .'|9d12/([^/]++)/([^/]++)/([^/]++)/2a9d12(*:29142)' - .'|b564/([^/]++)/([^/]++)/([^/]++)/2ab564(*:29190)' + .'|fe45/([^/]++)/([^/]++)/([^/]++)/2afe45(*:29038)' + .'|084e/([^/]++)/([^/]++)/([^/]++)/2a084e(*:29086)' + .'|9d12/([^/]++)/([^/]++)/([^/]++)/2a9d12(*:29134)' + .'|b564/([^/]++)/([^/]++)/([^/]++)/2ab564(*:29182)' .')' .'|1(?' - .'|7eed/([^/]++)/([^/]++)/([^/]++)/217eed(*:29243)' - .'|0f76/([^/]++)/([^/]++)/([^/]++)/210f76(*:29291)' + .'|7eed/([^/]++)/([^/]++)/([^/]++)/217eed(*:29235)' + .'|0f76/([^/]++)/([^/]++)/([^/]++)/210f76(*:29283)' .')' - .'|e65f2/([^/]++)/([^/]++)/([^/]++)/2e65f2(*:29341)' - .'|ca65f/([^/]++)/([^/]++)/([^/]++)/2ca65f(*:29390)' - .'|0aee3/([^/]++)/([^/]++)/([^/]++)/20aee3(*:29439)' + .'|e65f2/([^/]++)/([^/]++)/([^/]++)/2e65f2(*:29333)' + .'|ca65f/([^/]++)/([^/]++)/([^/]++)/2ca65f(*:29382)' + .'|0aee3/([^/]++)/([^/]++)/([^/]++)/20aee3(*:29431)' .')' .'|/e(?' .'|8(?' - .'|c065/([^/]++)/([^/]++)/([^/]++)/e8c065(*:29497)' - .'|20a4/([^/]++)/([^/]++)/([^/]++)/e820a4(*:29545)' + .'|c065/([^/]++)/([^/]++)/([^/]++)/e8c065(*:29489)' + .'|20a4/([^/]++)/([^/]++)/([^/]++)/e820a4(*:29537)' .')' .'|2(?' - .'|230b/([^/]++)/([^/]++)/([^/]++)/e2230b(*:29598)' - .'|a2dc/([^/]++)/([^/]++)/([^/]++)/e2a2dc(*:29646)' - .'|05ee/([^/]++)/([^/]++)/([^/]++)/e205ee(*:29694)' + .'|230b/([^/]++)/([^/]++)/([^/]++)/e2230b(*:29590)' + .'|a2dc/([^/]++)/([^/]++)/([^/]++)/e2a2dc(*:29638)' + .'|05ee/([^/]++)/([^/]++)/([^/]++)/e205ee(*:29686)' .')' .'|b(?' - .'|d962/([^/]++)/([^/]++)/([^/]++)/ebd962(*:29747)' - .'|6fdc/([^/]++)/([^/]++)/([^/]++)/eb6fdc(*:29795)' + .'|d962/([^/]++)/([^/]++)/([^/]++)/ebd962(*:29739)' + .'|6fdc/([^/]++)/([^/]++)/([^/]++)/eb6fdc(*:29787)' .')' .'|d(?' - .'|265b/([^/]++)/([^/]++)/([^/]++)/ed265b(*:29848)' - .'|fbe1/([^/]++)/([^/]++)/([^/]++)/edfbe1(*:29896)' - .'|e7e2/([^/]++)/([^/]++)/([^/]++)/ede7e2(*:29944)' + .'|265b/([^/]++)/([^/]++)/([^/]++)/ed265b(*:29840)' + .'|fbe1/([^/]++)/([^/]++)/([^/]++)/edfbe1(*:29888)' + .'|e7e2/([^/]++)/([^/]++)/([^/]++)/ede7e2(*:29936)' .')' .'|6(?' - .'|b4b2/([^/]++)/([^/]++)/([^/]++)/e6b4b2(*:29997)' - .'|cb2a/([^/]++)/([^/]++)/([^/]++)/e6cb2a(*:30045)' + .'|b4b2/([^/]++)/([^/]++)/([^/]++)/e6b4b2(*:29989)' + .'|cb2a/([^/]++)/([^/]++)/([^/]++)/e6cb2a(*:30037)' .')' .'|5(?' - .'|f6ad/([^/]++)/([^/]++)/([^/]++)/e5f6ad(*:30098)' - .'|55eb/([^/]++)/([^/]++)/([^/]++)/e555eb(*:30146)' - .'|841d/([^/]++)/([^/]++)/([^/]++)/e5841d(*:30194)' - .'|7c6b/([^/]++)/([^/]++)/([^/]++)/e57c6b(*:30242)' + .'|f6ad/([^/]++)/([^/]++)/([^/]++)/e5f6ad(*:30090)' + .'|55eb/([^/]++)/([^/]++)/([^/]++)/e555eb(*:30138)' + .'|841d/([^/]++)/([^/]++)/([^/]++)/e5841d(*:30186)' + .'|7c6b/([^/]++)/([^/]++)/([^/]++)/e57c6b(*:30234)' .')' - .'|aae33/([^/]++)/([^/]++)/([^/]++)/eaae33(*:30292)' + .'|aae33/([^/]++)/([^/]++)/([^/]++)/eaae33(*:30284)' .'|4(?' - .'|bb4c/([^/]++)/([^/]++)/([^/]++)/e4bb4c(*:30344)' - .'|9b8b/([^/]++)/([^/]++)/([^/]++)/e49b8b(*:30392)' + .'|bb4c/([^/]++)/([^/]++)/([^/]++)/e4bb4c(*:30336)' + .'|9b8b/([^/]++)/([^/]++)/([^/]++)/e49b8b(*:30384)' .')' .'|7(?' - .'|0611/([^/]++)/([^/]++)/([^/]++)/e70611(*:30445)' - .'|f8a7/([^/]++)/([^/]++)/([^/]++)/e7f8a7(*:30493)' - .'|44f9/([^/]++)/([^/]++)/([^/]++)/e744f9(*:30541)' + .'|0611/([^/]++)/([^/]++)/([^/]++)/e70611(*:30437)' + .'|f8a7/([^/]++)/([^/]++)/([^/]++)/e7f8a7(*:30485)' + .'|44f9/([^/]++)/([^/]++)/([^/]++)/e744f9(*:30533)' .')' .'|9(?' - .'|95f9/([^/]++)/([^/]++)/([^/]++)/e995f9(*:30594)' - .'|4550/([^/]++)/([^/]++)/([^/]++)/e94550(*:30642)' - .'|7ee2/([^/]++)/([^/]++)/([^/]++)/e97ee2(*:30690)' + .'|95f9/([^/]++)/([^/]++)/([^/]++)/e995f9(*:30586)' + .'|4550/([^/]++)/([^/]++)/([^/]++)/e94550(*:30634)' + .'|7ee2/([^/]++)/([^/]++)/([^/]++)/e97ee2(*:30682)' .')' .'|e(?' - .'|fc9e/([^/]++)/([^/]++)/([^/]++)/eefc9e(*:30743)' - .'|b69a/([^/]++)/([^/]++)/([^/]++)/eeb69a(*:30791)' + .'|fc9e/([^/]++)/([^/]++)/([^/]++)/eefc9e(*:30735)' + .'|b69a/([^/]++)/([^/]++)/([^/]++)/eeb69a(*:30783)' .')' .'|0(?' - .'|7413/([^/]++)/([^/]++)/([^/]++)/e07413(*:30844)' - .'|cf1f/([^/]++)/([^/]++)/([^/]++)/e0cf1f(*:30892)' - .'|ec45/([^/]++)/([^/]++)/([^/]++)/e0ec45(*:30940)' + .'|7413/([^/]++)/([^/]++)/([^/]++)/e07413(*:30836)' + .'|cf1f/([^/]++)/([^/]++)/([^/]++)/e0cf1f(*:30884)' + .'|ec45/([^/]++)/([^/]++)/([^/]++)/e0ec45(*:30932)' .')' - .'|f4e3b/([^/]++)/([^/]++)/([^/]++)/ef4e3b(*:30990)' - .'|c5aa0/([^/]++)/([^/]++)/([^/]++)/ec5aa0(*:31039)' + .'|f4e3b/([^/]++)/([^/]++)/([^/]++)/ef4e3b(*:30982)' + .'|c5aa0/([^/]++)/([^/]++)/([^/]++)/ec5aa0(*:31031)' .')' .'|/f(?' .'|f(?' - .'|4d5f/([^/]++)/([^/]++)/([^/]++)/ff4d5f(*:31097)' - .'|eabd/([^/]++)/([^/]++)/([^/]++)/ffeabd(*:31145)' + .'|4d5f/([^/]++)/([^/]++)/([^/]++)/ff4d5f(*:31089)' + .'|eabd/([^/]++)/([^/]++)/([^/]++)/ffeabd(*:31137)' .')' .'|3(?' - .'|f27a/([^/]++)/([^/]++)/([^/]++)/f3f27a(*:31198)' - .'|8762/([^/]++)/([^/]++)/([^/]++)/f38762(*:31246)' + .'|f27a/([^/]++)/([^/]++)/([^/]++)/f3f27a(*:31190)' + .'|8762/([^/]++)/([^/]++)/([^/]++)/f38762(*:31238)' .')' .'|4(?' - .'|be00/([^/]++)/([^/]++)/([^/]++)/f4be00(*:31299)' - .'|5526/([^/]++)/([^/]++)/([^/]++)/f45526(*:31347)' - .'|7d0a/([^/]++)/([^/]++)/([^/]++)/f47d0a(*:31395)' + .'|be00/([^/]++)/([^/]++)/([^/]++)/f4be00(*:31291)' + .'|5526/([^/]++)/([^/]++)/([^/]++)/f45526(*:31339)' + .'|7d0a/([^/]++)/([^/]++)/([^/]++)/f47d0a(*:31387)' .')' .'|0(?' - .'|e52b/([^/]++)/([^/]++)/([^/]++)/f0e52b(*:31448)' - .'|adc8/([^/]++)/([^/]++)/([^/]++)/f0adc8(*:31496)' + .'|e52b/([^/]++)/([^/]++)/([^/]++)/f0e52b(*:31440)' + .'|adc8/([^/]++)/([^/]++)/([^/]++)/f0adc8(*:31488)' .')' - .'|de926/([^/]++)/([^/]++)/([^/]++)/fde926(*:31546)' + .'|de926/([^/]++)/([^/]++)/([^/]++)/fde926(*:31538)' .'|5(?' - .'|deae/([^/]++)/([^/]++)/([^/]++)/f5deae(*:31598)' - .'|7a2f/([^/]++)/([^/]++)/([^/]++)/f57a2f(*:31646)' + .'|deae/([^/]++)/([^/]++)/([^/]++)/f5deae(*:31590)' + .'|7a2f/([^/]++)/([^/]++)/([^/]++)/f57a2f(*:31638)' .')' .'|7(?' - .'|6a89/([^/]++)/([^/]++)/([^/]++)/f76a89(*:31699)' - .'|9921/([^/]++)/([^/]++)/([^/]++)/f79921(*:31747)' - .'|e905/([^/]++)/([^/]++)/([^/]++)/f7e905(*:31795)' + .'|6a89/([^/]++)/([^/]++)/([^/]++)/f76a89(*:31691)' + .'|9921/([^/]++)/([^/]++)/([^/]++)/f79921(*:31739)' + .'|e905/([^/]++)/([^/]++)/([^/]++)/f7e905(*:31787)' .')' .'|2(?' - .'|9c21/([^/]++)/([^/]++)/([^/]++)/f29c21(*:31848)' - .'|201f/([^/]++)/([^/]++)/([^/]++)/f2201f(*:31896)' + .'|9c21/([^/]++)/([^/]++)/([^/]++)/f29c21(*:31840)' + .'|201f/([^/]++)/([^/]++)/([^/]++)/f2201f(*:31888)' .')' .'|a(?' - .'|e0b2/([^/]++)/([^/]++)/([^/]++)/fae0b2(*:31949)' - .'|14d4/([^/]++)/([^/]++)/([^/]++)/fa14d4(*:31997)' - .'|3a3c/([^/]++)/([^/]++)/([^/]++)/fa3a3c(*:32045)' - .'|83a1/([^/]++)/([^/]++)/([^/]++)/fa83a1(*:32093)' + .'|e0b2/([^/]++)/([^/]++)/([^/]++)/fae0b2(*:31941)' + .'|14d4/([^/]++)/([^/]++)/([^/]++)/fa14d4(*:31989)' + .'|3a3c/([^/]++)/([^/]++)/([^/]++)/fa3a3c(*:32037)' + .'|83a1/([^/]++)/([^/]++)/([^/]++)/fa83a1(*:32085)' .')' .'|c(?' - .'|cb3c/([^/]++)/([^/]++)/([^/]++)/fccb3c(*:32146)' - .'|8001/([^/]++)/([^/]++)/([^/]++)/fc8001(*:32194)' - .'|3cf4/([^/]++)/([^/]++)/([^/]++)/fc3cf4(*:32242)' - .'|4930/([^/]++)/([^/]++)/([^/]++)/fc4930(*:32290)' - .')' - .'|64eac/([^/]++)/([^/]++)/([^/]++)/f64eac(*:32340)' - .'|b8970/([^/]++)/([^/]++)/([^/]++)/fb8970(*:32389)' - .'|1c159/([^/]++)/([^/]++)/([^/]++)/f1c159(*:32438)' + .'|cb3c/([^/]++)/([^/]++)/([^/]++)/fccb3c(*:32138)' + .'|8001/([^/]++)/([^/]++)/([^/]++)/fc8001(*:32186)' + .'|3cf4/([^/]++)/([^/]++)/([^/]++)/fc3cf4(*:32234)' + .'|4930/([^/]++)/([^/]++)/([^/]++)/fc4930(*:32282)' + .')' + .'|64eac/([^/]++)/([^/]++)/([^/]++)/f64eac(*:32332)' + .'|b8970/([^/]++)/([^/]++)/([^/]++)/fb8970(*:32381)' + .'|1c159/([^/]++)/([^/]++)/([^/]++)/f1c159(*:32430)' .'|9(?' - .'|028f/([^/]++)/([^/]++)/([^/]++)/f9028f(*:32490)' - .'|a40a/([^/]++)/([^/]++)/([^/]++)/f9a40a(*:32538)' + .'|028f/([^/]++)/([^/]++)/([^/]++)/f9028f(*:32482)' + .'|a40a/([^/]++)/([^/]++)/([^/]++)/f9a40a(*:32530)' .')' .'|e(?' - .'|8c15/([^/]++)/([^/]++)/([^/]++)/fe8c15(*:32591)' - .'|c8d4/([^/]++)/([^/]++)/([^/]++)/fec8d4(*:32639)' - .'|7ee8/([^/]++)/([^/]++)/([^/]++)/fe7ee8(*:32687)' + .'|8c15/([^/]++)/([^/]++)/([^/]++)/fe8c15(*:32583)' + .'|c8d4/([^/]++)/([^/]++)/([^/]++)/fec8d4(*:32631)' + .'|7ee8/([^/]++)/([^/]++)/([^/]++)/fe7ee8(*:32679)' .')' .')' .'|/3(?' .'|8(?' .'|9(?' - .'|bc7/([^/]++)/([^/]++)/([^/]++)/389bc7(*:32749)' - .'|13e/([^/]++)/([^/]++)/([^/]++)/38913e(*:32796)' + .'|bc7/([^/]++)/([^/]++)/([^/]++)/389bc7(*:32741)' + .'|13e/([^/]++)/([^/]++)/([^/]++)/38913e(*:32788)' .')' - .'|71bd/([^/]++)/([^/]++)/([^/]++)/3871bd(*:32845)' + .'|71bd/([^/]++)/([^/]++)/([^/]++)/3871bd(*:32837)' .')' .'|d(?' - .'|c487/([^/]++)/([^/]++)/([^/]++)/3dc487(*:32898)' - .'|2d8c/([^/]++)/([^/]++)/([^/]++)/3d2d8c(*:32946)' - .'|8e28/([^/]++)/([^/]++)/([^/]++)/3d8e28(*:32994)' - .'|f1d4/([^/]++)/([^/]++)/([^/]++)/3df1d4(*:33042)' + .'|c487/([^/]++)/([^/]++)/([^/]++)/3dc487(*:32890)' + .'|2d8c/([^/]++)/([^/]++)/([^/]++)/3d2d8c(*:32938)' + .'|8e28/([^/]++)/([^/]++)/([^/]++)/3d8e28(*:32986)' + .'|f1d4/([^/]++)/([^/]++)/([^/]++)/3df1d4(*:33034)' .')' - .'|7f0e8/([^/]++)/([^/]++)/([^/]++)/37f0e8(*:33092)' + .'|7f0e8/([^/]++)/([^/]++)/([^/]++)/37f0e8(*:33084)' .'|3(?' - .'|e807/([^/]++)/([^/]++)/([^/]++)/33e807(*:33144)' - .'|28bd/([^/]++)/([^/]++)/([^/]++)/3328bd(*:33192)' + .'|e807/([^/]++)/([^/]++)/([^/]++)/33e807(*:33136)' + .'|28bd/([^/]++)/([^/]++)/([^/]++)/3328bd(*:33184)' .')' .'|a(?' .'|0(?' - .'|772/([^/]++)/([^/]++)/([^/]++)/3a0772(*:33248)' - .'|66b/([^/]++)/([^/]++)/([^/]++)/3a066b(*:33295)' + .'|772/([^/]++)/([^/]++)/([^/]++)/3a0772(*:33240)' + .'|66b/([^/]++)/([^/]++)/([^/]++)/3a066b(*:33287)' .')' - .'|835d/([^/]++)/([^/]++)/([^/]++)/3a835d(*:33344)' + .'|835d/([^/]++)/([^/]++)/([^/]++)/3a835d(*:33336)' .')' .'|0(?' - .'|bb38/([^/]++)/([^/]++)/([^/]++)/30bb38(*:33397)' - .'|3ed4/([^/]++)/([^/]++)/([^/]++)/303ed4(*:33445)' - .'|ef30/([^/]++)/([^/]++)/([^/]++)/30ef30(*:33493)' - .'|1ad0/([^/]++)/([^/]++)/([^/]++)/301ad0(*:33541)' + .'|bb38/([^/]++)/([^/]++)/([^/]++)/30bb38(*:33389)' + .'|3ed4/([^/]++)/([^/]++)/([^/]++)/303ed4(*:33437)' + .'|ef30/([^/]++)/([^/]++)/([^/]++)/30ef30(*:33485)' + .'|1ad0/([^/]++)/([^/]++)/([^/]++)/301ad0(*:33533)' .')' .'|4(?' - .'|9389/([^/]++)/([^/]++)/([^/]++)/349389(*:33594)' - .'|35c3/([^/]++)/([^/]++)/([^/]++)/3435c3(*:33642)' + .'|9389/([^/]++)/([^/]++)/([^/]++)/349389(*:33586)' + .'|35c3/([^/]++)/([^/]++)/([^/]++)/3435c3(*:33634)' .')' .'|62(?' - .'|1f1/([^/]++)/([^/]++)/([^/]++)/3621f1(*:33695)' - .'|e80/([^/]++)/([^/]++)/([^/]++)/362e80(*:33742)' + .'|1f1/([^/]++)/([^/]++)/([^/]++)/3621f1(*:33687)' + .'|e80/([^/]++)/([^/]++)/([^/]++)/362e80(*:33734)' .')' .'|5(?' - .'|cf86/([^/]++)/([^/]++)/([^/]++)/35cf86(*:33795)' - .'|2407/([^/]++)/([^/]++)/([^/]++)/352407(*:33843)' + .'|cf86/([^/]++)/([^/]++)/([^/]++)/35cf86(*:33787)' + .'|2407/([^/]++)/([^/]++)/([^/]++)/352407(*:33835)' .')' - .'|2b30a/([^/]++)/([^/]++)/([^/]++)/32b30a(*:33893)' - .'|1839b/([^/]++)/([^/]++)/([^/]++)/31839b(*:33942)' + .'|2b30a/([^/]++)/([^/]++)/([^/]++)/32b30a(*:33885)' + .'|1839b/([^/]++)/([^/]++)/([^/]++)/31839b(*:33934)' .'|b(?' - .'|5dca/([^/]++)/([^/]++)/([^/]++)/3b5dca(*:33994)' - .'|3dba/([^/]++)/([^/]++)/([^/]++)/3b3dba(*:34042)' + .'|5dca/([^/]++)/([^/]++)/([^/]++)/3b5dca(*:33986)' + .'|3dba/([^/]++)/([^/]++)/([^/]++)/3b3dba(*:34034)' .')' - .'|e89eb/([^/]++)/([^/]++)/([^/]++)/3e89eb(*:34092)' - .'|cef96/([^/]++)/([^/]++)/([^/]++)/3cef96(*:34141)' + .'|e89eb/([^/]++)/([^/]++)/([^/]++)/3e89eb(*:34084)' + .'|cef96/([^/]++)/([^/]++)/([^/]++)/3cef96(*:34133)' .')' .'|/0(?' .'|8(?' - .'|7408/([^/]++)/([^/]++)/([^/]++)/087408(*:34199)' - .'|b255/([^/]++)/([^/]++)/([^/]++)/08b255(*:34247)' - .'|c543/([^/]++)/([^/]++)/([^/]++)/08c543(*:34295)' - .'|d986/([^/]++)/([^/]++)/([^/]++)/08d986(*:34343)' - .'|419b/([^/]++)/([^/]++)/([^/]++)/08419b(*:34391)' + .'|7408/([^/]++)/([^/]++)/([^/]++)/087408(*:34191)' + .'|b255/([^/]++)/([^/]++)/([^/]++)/08b255(*:34239)' + .'|c543/([^/]++)/([^/]++)/([^/]++)/08c543(*:34287)' + .'|d986/([^/]++)/([^/]++)/([^/]++)/08d986(*:34335)' + .'|419b/([^/]++)/([^/]++)/([^/]++)/08419b(*:34383)' .')' .'|7(?' - .'|563a/([^/]++)/([^/]++)/([^/]++)/07563a(*:34444)' - .'|6a0c/([^/]++)/([^/]++)/([^/]++)/076a0c(*:34492)' - .'|a96b/([^/]++)/([^/]++)/([^/]++)/07a96b(*:34540)' - .'|c580/([^/]++)/([^/]++)/([^/]++)/07c580(*:34588)' - .'|8719/([^/]++)/([^/]++)/([^/]++)/078719(*:34636)' + .'|563a/([^/]++)/([^/]++)/([^/]++)/07563a(*:34436)' + .'|6a0c/([^/]++)/([^/]++)/([^/]++)/076a0c(*:34484)' + .'|a96b/([^/]++)/([^/]++)/([^/]++)/07a96b(*:34532)' + .'|c580/([^/]++)/([^/]++)/([^/]++)/07c580(*:34580)' + .'|8719/([^/]++)/([^/]++)/([^/]++)/078719(*:34628)' .')' .'|f(?' - .'|cbc6/([^/]++)/([^/]++)/([^/]++)/0fcbc6(*:34689)' - .'|9661/([^/]++)/([^/]++)/([^/]++)/0f9661(*:34737)' + .'|cbc6/([^/]++)/([^/]++)/([^/]++)/0fcbc6(*:34681)' + .'|9661/([^/]++)/([^/]++)/([^/]++)/0f9661(*:34729)' .'|f(?' - .'|39b/([^/]++)/([^/]++)/([^/]++)/0ff39b(*:34788)' - .'|803/([^/]++)/([^/]++)/([^/]++)/0ff803(*:34835)' + .'|39b/([^/]++)/([^/]++)/([^/]++)/0ff39b(*:34780)' + .'|803/([^/]++)/([^/]++)/([^/]++)/0ff803(*:34827)' .')' - .'|840b/([^/]++)/([^/]++)/([^/]++)/0f840b(*:34884)' + .'|840b/([^/]++)/([^/]++)/([^/]++)/0f840b(*:34876)' .')' .'|1(?' - .'|f78b/([^/]++)/([^/]++)/([^/]++)/01f78b(*:34937)' - .'|3a00/([^/]++)/([^/]++)/([^/]++)/013a00(*:34985)' - .'|8825/([^/]++)/([^/]++)/([^/]++)/018825(*:35033)' + .'|f78b/([^/]++)/([^/]++)/([^/]++)/01f78b(*:34929)' + .'|3a00/([^/]++)/([^/]++)/([^/]++)/013a00(*:34977)' + .'|8825/([^/]++)/([^/]++)/([^/]++)/018825(*:35025)' .')' .'|6(?' .'|9(?' - .'|d3b/([^/]++)/([^/]++)/([^/]++)/069d3b(*:35089)' - .'|97f/([^/]++)/([^/]++)/([^/]++)/06997f(*:35136)' + .'|d3b/([^/]++)/([^/]++)/([^/]++)/069d3b(*:35081)' + .'|97f/([^/]++)/([^/]++)/([^/]++)/06997f(*:35128)' .')' - .'|1412/([^/]++)/([^/]++)/([^/]++)/061412(*:35185)' + .'|1412/([^/]++)/([^/]++)/([^/]++)/061412(*:35177)' .')' .'|4(?' - .'|ecb1/([^/]++)/([^/]++)/([^/]++)/04ecb1(*:35238)' - .'|3c3d/([^/]++)/([^/]++)/([^/]++)/043c3d(*:35286)' + .'|ecb1/([^/]++)/([^/]++)/([^/]++)/04ecb1(*:35230)' + .'|3c3d/([^/]++)/([^/]++)/([^/]++)/043c3d(*:35278)' .')' - .'|0ac8e/([^/]++)/([^/]++)/([^/]++)/00ac8e(*:35336)' + .'|0ac8e/([^/]++)/([^/]++)/([^/]++)/00ac8e(*:35328)' .'|5(?' - .'|1e4e/([^/]++)/([^/]++)/([^/]++)/051e4e(*:35388)' - .'|37fb/([^/]++)/([^/]++)/([^/]++)/0537fb(*:35436)' + .'|1e4e/([^/]++)/([^/]++)/([^/]++)/051e4e(*:35380)' + .'|37fb/([^/]++)/([^/]++)/([^/]++)/0537fb(*:35428)' .')' .'|d(?' - .'|7de1/([^/]++)/([^/]++)/([^/]++)/0d7de1(*:35489)' - .'|3180/([^/]++)/([^/]++)/([^/]++)/0d3180(*:35537)' - .'|0871/([^/]++)/([^/]++)/([^/]++)/0d0871(*:35585)' + .'|7de1/([^/]++)/([^/]++)/([^/]++)/0d7de1(*:35481)' + .'|3180/([^/]++)/([^/]++)/([^/]++)/0d3180(*:35529)' + .'|0871/([^/]++)/([^/]++)/([^/]++)/0d0871(*:35577)' .')' - .'|cb929/([^/]++)/([^/]++)/([^/]++)/0cb929(*:35635)' + .'|cb929/([^/]++)/([^/]++)/([^/]++)/0cb929(*:35627)' .'|2(?' - .'|a32a/([^/]++)/([^/]++)/([^/]++)/02a32a(*:35687)' - .'|4d7f/([^/]++)/([^/]++)/([^/]++)/024d7f(*:35735)' + .'|a32a/([^/]++)/([^/]++)/([^/]++)/02a32a(*:35679)' + .'|4d7f/([^/]++)/([^/]++)/([^/]++)/024d7f(*:35727)' .')' - .'|efe32/([^/]++)/([^/]++)/([^/]++)/0efe32(*:35785)' - .'|a113e/([^/]++)/([^/]++)/([^/]++)/0a113e(*:35834)' - .'|b8aff/([^/]++)/([^/]++)/([^/]++)/0b8aff(*:35883)' + .'|efe32/([^/]++)/([^/]++)/([^/]++)/0efe32(*:35777)' + .'|a113e/([^/]++)/([^/]++)/([^/]++)/0a113e(*:35826)' + .'|b8aff/([^/]++)/([^/]++)/([^/]++)/0b8aff(*:35875)' .')' .'|/a(?' .'|7(?' - .'|6088/([^/]++)/([^/]++)/([^/]++)/a76088(*:35941)' - .'|aeed/([^/]++)/([^/]++)/([^/]++)/a7aeed(*:35989)' - .'|33fa/([^/]++)/([^/]++)/([^/]++)/a733fa(*:36037)' + .'|6088/([^/]++)/([^/]++)/([^/]++)/a76088(*:35933)' + .'|aeed/([^/]++)/([^/]++)/([^/]++)/a7aeed(*:35981)' + .'|33fa/([^/]++)/([^/]++)/([^/]++)/a733fa(*:36029)' .')' .'|9a(?' - .'|665/([^/]++)/([^/]++)/([^/]++)/a9a665(*:36090)' - .'|1d5/([^/]++)/([^/]++)/([^/]++)/a9a1d5(*:36137)' + .'|665/([^/]++)/([^/]++)/([^/]++)/a9a665(*:36082)' + .'|1d5/([^/]++)/([^/]++)/([^/]++)/a9a1d5(*:36129)' .')' .'|8(?' - .'|6c45/([^/]++)/([^/]++)/([^/]++)/a86c45(*:36190)' - .'|849b/([^/]++)/([^/]++)/([^/]++)/a8849b(*:36238)' + .'|6c45/([^/]++)/([^/]++)/([^/]++)/a86c45(*:36182)' + .'|849b/([^/]++)/([^/]++)/([^/]++)/a8849b(*:36230)' .'|e(?' - .'|864/([^/]++)/([^/]++)/([^/]++)/a8e864(*:36289)' - .'|cba/([^/]++)/([^/]++)/([^/]++)/a8ecba(*:36336)' + .'|864/([^/]++)/([^/]++)/([^/]++)/a8e864(*:36281)' + .'|cba/([^/]++)/([^/]++)/([^/]++)/a8ecba(*:36328)' .')' .')' .'|c(?' - .'|c3e0/([^/]++)/([^/]++)/([^/]++)/acc3e0(*:36390)' - .'|f4b8/([^/]++)/([^/]++)/([^/]++)/acf4b8(*:36438)' + .'|c3e0/([^/]++)/([^/]++)/([^/]++)/acc3e0(*:36382)' + .'|f4b8/([^/]++)/([^/]++)/([^/]++)/acf4b8(*:36430)' .')' .'|b(?' - .'|d815/([^/]++)/([^/]++)/([^/]++)/abd815(*:36491)' - .'|233b/([^/]++)/([^/]++)/([^/]++)/ab233b(*:36539)' - .'|a3b6/([^/]++)/([^/]++)/([^/]++)/aba3b6(*:36587)' - .'|88b1/([^/]++)/([^/]++)/([^/]++)/ab88b1(*:36635)' + .'|d815/([^/]++)/([^/]++)/([^/]++)/abd815(*:36483)' + .'|233b/([^/]++)/([^/]++)/([^/]++)/ab233b(*:36531)' + .'|a3b6/([^/]++)/([^/]++)/([^/]++)/aba3b6(*:36579)' + .'|88b1/([^/]++)/([^/]++)/([^/]++)/ab88b1(*:36627)' .')' .'|5(?' - .'|3240/([^/]++)/([^/]++)/([^/]++)/a53240(*:36688)' - .'|cdd4/([^/]++)/([^/]++)/([^/]++)/a5cdd4(*:36736)' + .'|3240/([^/]++)/([^/]++)/([^/]++)/a53240(*:36680)' + .'|cdd4/([^/]++)/([^/]++)/([^/]++)/a5cdd4(*:36728)' .')' .'|f(?' .'|d(?' - .'|483/([^/]++)/([^/]++)/([^/]++)/afd483(*:36792)' - .'|a33/([^/]++)/([^/]++)/([^/]++)/afda33(*:36839)' + .'|483/([^/]++)/([^/]++)/([^/]++)/afd483(*:36784)' + .'|a33/([^/]++)/([^/]++)/([^/]++)/afda33(*:36831)' .')' - .'|f162/([^/]++)/([^/]++)/([^/]++)/aff162(*:36888)' + .'|f162/([^/]++)/([^/]++)/([^/]++)/aff162(*:36880)' .')' .'|e(?' - .'|0eb3/([^/]++)/([^/]++)/([^/]++)/ae0eb3(*:36941)' - .'|b313/([^/]++)/([^/]++)/([^/]++)/aeb313(*:36989)' + .'|0eb3/([^/]++)/([^/]++)/([^/]++)/ae0eb3(*:36933)' + .'|b313/([^/]++)/([^/]++)/([^/]++)/aeb313(*:36981)' .')' .'|1(?' - .'|d33d/([^/]++)/([^/]++)/([^/]++)/a1d33d(*:37042)' - .'|140a/([^/]++)/([^/]++)/([^/]++)/a1140a(*:37090)' + .'|d33d/([^/]++)/([^/]++)/([^/]++)/a1d33d(*:37034)' + .'|140a/([^/]++)/([^/]++)/([^/]++)/a1140a(*:37082)' .')' - .'|ddfa9/([^/]++)/([^/]++)/([^/]++)/addfa9(*:37140)' + .'|ddfa9/([^/]++)/([^/]++)/([^/]++)/addfa9(*:37132)' .'|6(?' - .'|7f09/([^/]++)/([^/]++)/([^/]++)/a67f09(*:37192)' - .'|4c94/([^/]++)/([^/]++)/([^/]++)/a64c94(*:37240)' + .'|7f09/([^/]++)/([^/]++)/([^/]++)/a67f09(*:37184)' + .'|4c94/([^/]++)/([^/]++)/([^/]++)/a64c94(*:37232)' .')' - .'|a169b/([^/]++)/([^/]++)/([^/]++)/aa169b(*:37290)' - .'|4300b/([^/]++)/([^/]++)/([^/]++)/a4300b(*:37339)' - .'|3d68b/([^/]++)/([^/]++)/([^/]++)/a3d68b(*:37388)' + .'|a169b/([^/]++)/([^/]++)/([^/]++)/aa169b(*:37282)' + .'|4300b/([^/]++)/([^/]++)/([^/]++)/a4300b(*:37331)' + .'|3d68b/([^/]++)/([^/]++)/([^/]++)/a3d68b(*:37380)' .')' .'|/1(?' .'|0(?' .'|a(?' - .'|7cd/([^/]++)/([^/]++)/([^/]++)/10a7cd(*:37449)' - .'|5ab/([^/]++)/([^/]++)/([^/]++)/10a5ab(*:37496)' + .'|7cd/([^/]++)/([^/]++)/([^/]++)/10a7cd(*:37441)' + .'|5ab/([^/]++)/([^/]++)/([^/]++)/10a5ab(*:37488)' .')' - .'|9a0c/([^/]++)/([^/]++)/([^/]++)/109a0c(*:37545)' + .'|9a0c/([^/]++)/([^/]++)/([^/]++)/109a0c(*:37537)' .')' - .'|3f320/([^/]++)/([^/]++)/([^/]++)/13f320(*:37595)' + .'|3f320/([^/]++)/([^/]++)/([^/]++)/13f320(*:37587)' .'|6(?' - .'|c222/([^/]++)/([^/]++)/([^/]++)/16c222(*:37647)' - .'|8908/([^/]++)/([^/]++)/([^/]++)/168908(*:37695)' + .'|c222/([^/]++)/([^/]++)/([^/]++)/16c222(*:37639)' + .'|8908/([^/]++)/([^/]++)/([^/]++)/168908(*:37687)' .')' .'|5(?' - .'|de21/([^/]++)/([^/]++)/([^/]++)/15de21(*:37748)' - .'|95af/([^/]++)/([^/]++)/([^/]++)/1595af(*:37796)' + .'|de21/([^/]++)/([^/]++)/([^/]++)/15de21(*:37740)' + .'|95af/([^/]++)/([^/]++)/([^/]++)/1595af(*:37788)' .')' .'|1(?' - .'|b921/([^/]++)/([^/]++)/([^/]++)/11b921(*:37849)' - .'|4193/([^/]++)/([^/]++)/([^/]++)/114193(*:37897)' + .'|b921/([^/]++)/([^/]++)/([^/]++)/11b921(*:37841)' + .'|4193/([^/]++)/([^/]++)/([^/]++)/114193(*:37889)' .')' - .'|bb91f/([^/]++)/([^/]++)/([^/]++)/1bb91f(*:37947)' + .'|bb91f/([^/]++)/([^/]++)/([^/]++)/1bb91f(*:37939)' .'|7(?' - .'|28ef/([^/]++)/([^/]++)/([^/]++)/1728ef(*:37999)' - .'|c276/([^/]++)/([^/]++)/([^/]++)/17c276(*:38047)' - .'|0c94/([^/]++)/([^/]++)/([^/]++)/170c94(*:38095)' + .'|28ef/([^/]++)/([^/]++)/([^/]++)/1728ef(*:37991)' + .'|c276/([^/]++)/([^/]++)/([^/]++)/17c276(*:38039)' + .'|0c94/([^/]++)/([^/]++)/([^/]++)/170c94(*:38087)' .')' .'|85(?' - .'|c29/([^/]++)/([^/]++)/([^/]++)/185c29(*:38148)' - .'|e65/([^/]++)/([^/]++)/([^/]++)/185e65(*:38195)' + .'|c29/([^/]++)/([^/]++)/([^/]++)/185c29(*:38140)' + .'|e65/([^/]++)/([^/]++)/([^/]++)/185e65(*:38187)' .')' .'|9(?' - .'|2fc0/([^/]++)/([^/]++)/([^/]++)/192fc0(*:38248)' + .'|2fc0/([^/]++)/([^/]++)/([^/]++)/192fc0(*:38240)' .'|b(?' - .'|c91/([^/]++)/([^/]++)/([^/]++)/19bc91(*:38299)' - .'|650/([^/]++)/([^/]++)/([^/]++)/19b650(*:38346)' + .'|c91/([^/]++)/([^/]++)/([^/]++)/19bc91(*:38291)' + .'|650/([^/]++)/([^/]++)/([^/]++)/19b650(*:38338)' .')' - .'|05ae/([^/]++)/([^/]++)/([^/]++)/1905ae(*:38395)' + .'|05ae/([^/]++)/([^/]++)/([^/]++)/1905ae(*:38387)' .')' .'|e(?' - .'|cfb4/([^/]++)/([^/]++)/([^/]++)/1ecfb4(*:38448)' - .'|fa39/([^/]++)/([^/]++)/([^/]++)/1efa39(*:38496)' - .'|056d/([^/]++)/([^/]++)/([^/]++)/1e056d(*:38544)' + .'|cfb4/([^/]++)/([^/]++)/([^/]++)/1ecfb4(*:38440)' + .'|fa39/([^/]++)/([^/]++)/([^/]++)/1efa39(*:38488)' + .'|056d/([^/]++)/([^/]++)/([^/]++)/1e056d(*:38536)' .')' - .'|aa48f/([^/]++)/([^/]++)/([^/]++)/1aa48f(*:38594)' + .'|aa48f/([^/]++)/([^/]++)/([^/]++)/1aa48f(*:38586)' .'|f(?' - .'|c214/([^/]++)/([^/]++)/([^/]++)/1fc214(*:38646)' - .'|5089/([^/]++)/([^/]++)/([^/]++)/1f5089(*:38694)' - .'|4477/([^/]++)/([^/]++)/([^/]++)/1f4477(*:38742)' + .'|c214/([^/]++)/([^/]++)/([^/]++)/1fc214(*:38638)' + .'|5089/([^/]++)/([^/]++)/([^/]++)/1f5089(*:38686)' + .'|4477/([^/]++)/([^/]++)/([^/]++)/1f4477(*:38734)' .')' .'|c(?' - .'|c363/([^/]++)/([^/]++)/([^/]++)/1cc363(*:38795)' - .'|1d4d/([^/]++)/([^/]++)/([^/]++)/1c1d4d(*:38843)' - .'|e927/([^/]++)/([^/]++)/([^/]++)/1ce927(*:38891)' + .'|c363/([^/]++)/([^/]++)/([^/]++)/1cc363(*:38787)' + .'|1d4d/([^/]++)/([^/]++)/([^/]++)/1c1d4d(*:38835)' + .'|e927/([^/]++)/([^/]++)/([^/]++)/1ce927(*:38883)' .')' .')' .'|/6(?' .'|3(?' - .'|538f/([^/]++)/([^/]++)/([^/]++)/63538f(*:38950)' - .'|2cee/([^/]++)/([^/]++)/([^/]++)/632cee(*:38998)' - .'|95eb/([^/]++)/([^/]++)/([^/]++)/6395eb(*:39046)' + .'|538f/([^/]++)/([^/]++)/([^/]++)/63538f(*:38942)' + .'|2cee/([^/]++)/([^/]++)/([^/]++)/632cee(*:38990)' + .'|95eb/([^/]++)/([^/]++)/([^/]++)/6395eb(*:39038)' .')' .'|9(?' - .'|421f/([^/]++)/([^/]++)/([^/]++)/69421f(*:39099)' - .'|2f93/([^/]++)/([^/]++)/([^/]++)/692f93(*:39147)' + .'|421f/([^/]++)/([^/]++)/([^/]++)/69421f(*:39091)' + .'|2f93/([^/]++)/([^/]++)/([^/]++)/692f93(*:39139)' .')' - .'|5658f/([^/]++)/([^/]++)/([^/]++)/65658f(*:39197)' + .'|5658f/([^/]++)/([^/]++)/([^/]++)/65658f(*:39189)' .'|4(?' - .'|7bba/([^/]++)/([^/]++)/([^/]++)/647bba(*:39249)' - .'|223c/([^/]++)/([^/]++)/([^/]++)/64223c(*:39297)' + .'|7bba/([^/]++)/([^/]++)/([^/]++)/647bba(*:39241)' + .'|223c/([^/]++)/([^/]++)/([^/]++)/64223c(*:39289)' .')' .'|e(?' - .'|2713/([^/]++)/([^/]++)/([^/]++)/6e2713(*:39350)' - .'|0721/([^/]++)/([^/]++)/([^/]++)/6e0721(*:39398)' - .'|7b33/([^/]++)/([^/]++)/([^/]++)/6e7b33(*:39446)' + .'|2713/([^/]++)/([^/]++)/([^/]++)/6e2713(*:39342)' + .'|0721/([^/]++)/([^/]++)/([^/]++)/6e0721(*:39390)' + .'|7b33/([^/]++)/([^/]++)/([^/]++)/6e7b33(*:39438)' .')' .'|0(?' - .'|5ff7/([^/]++)/([^/]++)/([^/]++)/605ff7(*:39499)' - .'|8159/([^/]++)/([^/]++)/([^/]++)/608159(*:39547)' + .'|5ff7/([^/]++)/([^/]++)/([^/]++)/605ff7(*:39491)' + .'|8159/([^/]++)/([^/]++)/([^/]++)/608159(*:39539)' .')' .'|a(?' - .'|ca97/([^/]++)/([^/]++)/([^/]++)/6aca97(*:39600)' - .'|10bb/([^/]++)/([^/]++)/([^/]++)/6a10bb(*:39648)' - .'|ab12/([^/]++)/([^/]++)/([^/]++)/6aab12(*:39696)' + .'|ca97/([^/]++)/([^/]++)/([^/]++)/6aca97(*:39592)' + .'|10bb/([^/]++)/([^/]++)/([^/]++)/6a10bb(*:39640)' + .'|ab12/([^/]++)/([^/]++)/([^/]++)/6aab12(*:39688)' .')' .'|7(?' - .'|66aa/([^/]++)/([^/]++)/([^/]++)/6766aa(*:39749)' - .'|e103/([^/]++)/([^/]++)/([^/]++)/67e103(*:39797)' + .'|66aa/([^/]++)/([^/]++)/([^/]++)/6766aa(*:39741)' + .'|e103/([^/]++)/([^/]++)/([^/]++)/67e103(*:39789)' .'|d(?' - .'|96d/([^/]++)/([^/]++)/([^/]++)/67d96d(*:39848)' - .'|16d/([^/]++)/([^/]++)/([^/]++)/67d16d(*:39895)' + .'|96d/([^/]++)/([^/]++)/([^/]++)/67d96d(*:39840)' + .'|16d/([^/]++)/([^/]++)/([^/]++)/67d16d(*:39887)' .')' - .'|0e8a/([^/]++)/([^/]++)/([^/]++)/670e8a(*:39944)' - .'|7e09/([^/]++)/([^/]++)/([^/]++)/677e09(*:39992)' + .'|0e8a/([^/]++)/([^/]++)/([^/]++)/670e8a(*:39936)' + .'|7e09/([^/]++)/([^/]++)/([^/]++)/677e09(*:39984)' .')' .'|8(?' - .'|264b/([^/]++)/([^/]++)/([^/]++)/68264b(*:40045)' - .'|053a/([^/]++)/([^/]++)/([^/]++)/68053a(*:40093)' + .'|264b/([^/]++)/([^/]++)/([^/]++)/68264b(*:40037)' + .'|053a/([^/]++)/([^/]++)/([^/]++)/68053a(*:40085)' .')' .'|c(?' - .'|2979/([^/]++)/([^/]++)/([^/]++)/6c2979(*:40146)' - .'|d67d/([^/]++)/([^/]++)/([^/]++)/6cd67d(*:40194)' - .'|3cf7/([^/]++)/([^/]++)/([^/]++)/6c3cf7(*:40242)' - .'|fe0e/([^/]++)/([^/]++)/([^/]++)/6cfe0e(*:40290)' - .')' - .'|bc24f/([^/]++)/([^/]++)/([^/]++)/6bc24f(*:40340)' - .'|f2268/([^/]++)/([^/]++)/([^/]++)/6f2268(*:40389)' - .'|1b4a6/([^/]++)/([^/]++)/([^/]++)/61b4a6(*:40438)' - .'|21461/([^/]++)/([^/]++)/([^/]++)/621461(*:40487)' - .'|d0f84/([^/]++)/([^/]++)/([^/]++)/6d0f84(*:40536)' - .'|60229/([^/]++)/([^/]++)/([^/]++)/660229(*:40585)' + .'|2979/([^/]++)/([^/]++)/([^/]++)/6c2979(*:40138)' + .'|d67d/([^/]++)/([^/]++)/([^/]++)/6cd67d(*:40186)' + .'|3cf7/([^/]++)/([^/]++)/([^/]++)/6c3cf7(*:40234)' + .'|fe0e/([^/]++)/([^/]++)/([^/]++)/6cfe0e(*:40282)' + .')' + .'|bc24f/([^/]++)/([^/]++)/([^/]++)/6bc24f(*:40332)' + .'|f2268/([^/]++)/([^/]++)/([^/]++)/6f2268(*:40381)' + .'|1b4a6/([^/]++)/([^/]++)/([^/]++)/61b4a6(*:40430)' + .'|21461/([^/]++)/([^/]++)/([^/]++)/621461(*:40479)' + .'|d0f84/([^/]++)/([^/]++)/([^/]++)/6d0f84(*:40528)' + .'|60229/([^/]++)/([^/]++)/([^/]++)/660229(*:40577)' .')' .'|/c(?' .'|f(?' - .'|6735/([^/]++)/([^/]++)/([^/]++)/cf6735(*:40643)' - .'|bce4/([^/]++)/([^/]++)/([^/]++)/cfbce4(*:40691)' + .'|6735/([^/]++)/([^/]++)/([^/]++)/cf6735(*:40635)' + .'|bce4/([^/]++)/([^/]++)/([^/]++)/cfbce4(*:40683)' .')' .'|3(?' .'|99(?' - .'|86/([^/]++)/([^/]++)/([^/]++)/c39986(*:40747)' - .'|2e/([^/]++)/([^/]++)/([^/]++)/c3992e(*:40793)' + .'|86/([^/]++)/([^/]++)/([^/]++)/c39986(*:40739)' + .'|2e/([^/]++)/([^/]++)/([^/]++)/c3992e(*:40785)' .')' - .'|61bc/([^/]++)/([^/]++)/([^/]++)/c361bc(*:40842)' - .'|2d9b/([^/]++)/([^/]++)/([^/]++)/c32d9b(*:40890)' + .'|61bc/([^/]++)/([^/]++)/([^/]++)/c361bc(*:40834)' + .'|2d9b/([^/]++)/([^/]++)/([^/]++)/c32d9b(*:40882)' .')' - .'|75b6f/([^/]++)/([^/]++)/([^/]++)/c75b6f(*:40940)' + .'|75b6f/([^/]++)/([^/]++)/([^/]++)/c75b6f(*:40932)' .'|c(?' .'|b(?' - .'|1d4/([^/]++)/([^/]++)/([^/]++)/ccb1d4(*:40995)' - .'|098/([^/]++)/([^/]++)/([^/]++)/ccb098(*:41042)' + .'|1d4/([^/]++)/([^/]++)/([^/]++)/ccb1d4(*:40987)' + .'|098/([^/]++)/([^/]++)/([^/]++)/ccb098(*:41034)' .')' - .'|c0aa/([^/]++)/([^/]++)/([^/]++)/ccc0aa(*:41091)' - .'|1aa4/([^/]++)/([^/]++)/([^/]++)/cc1aa4(*:41139)' + .'|c0aa/([^/]++)/([^/]++)/([^/]++)/ccc0aa(*:41083)' + .'|1aa4/([^/]++)/([^/]++)/([^/]++)/cc1aa4(*:41131)' .')' .'|b(?' - .'|cb58/([^/]++)/([^/]++)/([^/]++)/cbcb58(*:41192)' - .'|b6a3/([^/]++)/([^/]++)/([^/]++)/cbb6a3(*:41240)' + .'|cb58/([^/]++)/([^/]++)/([^/]++)/cbcb58(*:41184)' + .'|b6a3/([^/]++)/([^/]++)/([^/]++)/cbb6a3(*:41232)' .')' - .'|9892a/([^/]++)/([^/]++)/([^/]++)/c9892a(*:41290)' - .'|6e19e/([^/]++)/([^/]++)/([^/]++)/c6e19e(*:41339)' - .'|dc0d6/([^/]++)/([^/]++)/([^/]++)/cdc0d6(*:41388)' - .'|5ab0b/([^/]++)/([^/]++)/([^/]++)/c5ab0b(*:41437)' + .'|9892a/([^/]++)/([^/]++)/([^/]++)/c9892a(*:41282)' + .'|6e19e/([^/]++)/([^/]++)/([^/]++)/c6e19e(*:41331)' + .'|dc0d6/([^/]++)/([^/]++)/([^/]++)/cdc0d6(*:41380)' + .'|5ab0b/([^/]++)/([^/]++)/([^/]++)/c5ab0b(*:41429)' .'|a(?' - .'|9c26/([^/]++)/([^/]++)/([^/]++)/ca9c26(*:41489)' - .'|8155/([^/]++)/([^/]++)/([^/]++)/ca8155(*:41537)' - .'|7591/([^/]++)/([^/]++)/([^/]++)/ca7591(*:41585)' + .'|9c26/([^/]++)/([^/]++)/([^/]++)/ca9c26(*:41481)' + .'|8155/([^/]++)/([^/]++)/([^/]++)/ca8155(*:41529)' + .'|7591/([^/]++)/([^/]++)/([^/]++)/ca7591(*:41577)' .')' .'|0(?' - .'|6d06/([^/]++)/([^/]++)/([^/]++)/c06d06(*:41638)' - .'|f168/([^/]++)/([^/]++)/([^/]++)/c0f168(*:41686)' + .'|6d06/([^/]++)/([^/]++)/([^/]++)/c06d06(*:41630)' + .'|f168/([^/]++)/([^/]++)/([^/]++)/c0f168(*:41678)' .')' .'|8(?' - .'|ed21/([^/]++)/([^/]++)/([^/]++)/c8ed21(*:41739)' - .'|fbbc/([^/]++)/([^/]++)/([^/]++)/c8fbbc(*:41787)' - .'|c41c/([^/]++)/([^/]++)/([^/]++)/c8c41c(*:41835)' + .'|ed21/([^/]++)/([^/]++)/([^/]++)/c8ed21(*:41731)' + .'|fbbc/([^/]++)/([^/]++)/([^/]++)/c8fbbc(*:41779)' + .'|c41c/([^/]++)/([^/]++)/([^/]++)/c8c41c(*:41827)' .')' - .'|15da1/([^/]++)/([^/]++)/([^/]++)/c15da1(*:41885)' + .'|15da1/([^/]++)/([^/]++)/([^/]++)/c15da1(*:41877)' .'|2(?' - .'|626d/([^/]++)/([^/]++)/([^/]++)/c2626d(*:41937)' - .'|aee8/([^/]++)/([^/]++)/([^/]++)/c2aee8(*:41985)' - .'|2abf/([^/]++)/([^/]++)/([^/]++)/c22abf(*:42033)' + .'|626d/([^/]++)/([^/]++)/([^/]++)/c2626d(*:41929)' + .'|aee8/([^/]++)/([^/]++)/([^/]++)/c2aee8(*:41977)' + .'|2abf/([^/]++)/([^/]++)/([^/]++)/c22abf(*:42025)' .')' - .'|e78d1/([^/]++)/([^/]++)/([^/]++)/ce78d1(*:42083)' + .'|e78d1/([^/]++)/([^/]++)/([^/]++)/ce78d1(*:42075)' .'|4(?' - .'|015b/([^/]++)/([^/]++)/([^/]++)/c4015b(*:42135)' - .'|b31c/([^/]++)/([^/]++)/([^/]++)/c4b31c(*:42183)' + .'|015b/([^/]++)/([^/]++)/([^/]++)/c4015b(*:42127)' + .'|b31c/([^/]++)/([^/]++)/([^/]++)/c4b31c(*:42175)' .')' .')' .'|/8(?' .'|5(?' - .'|422a/([^/]++)/([^/]++)/([^/]++)/85422a(*:42242)' - .'|1ddf/([^/]++)/([^/]++)/([^/]++)/851ddf(*:42290)' - .'|fc37/([^/]++)/([^/]++)/([^/]++)/85fc37(*:42338)' + .'|422a/([^/]++)/([^/]++)/([^/]++)/85422a(*:42234)' + .'|1ddf/([^/]++)/([^/]++)/([^/]++)/851ddf(*:42282)' + .'|fc37/([^/]++)/([^/]++)/([^/]++)/85fc37(*:42330)' .')' .'|1(?' - .'|4481/([^/]++)/([^/]++)/([^/]++)/814481(*:42391)' - .'|e74d/([^/]++)/([^/]++)/([^/]++)/81e74d(*:42439)' + .'|4481/([^/]++)/([^/]++)/([^/]++)/814481(*:42383)' + .'|e74d/([^/]++)/([^/]++)/([^/]++)/81e74d(*:42431)' .')' .'|d(?' .'|3(?' - .'|420/([^/]++)/([^/]++)/([^/]++)/8d3420(*:42495)' - .'|17b/([^/]++)/([^/]++)/([^/]++)/8d317b(*:42542)' + .'|420/([^/]++)/([^/]++)/([^/]++)/8d3420(*:42487)' + .'|17b/([^/]++)/([^/]++)/([^/]++)/8d317b(*:42534)' .')' - .'|f707/([^/]++)/([^/]++)/([^/]++)/8df707(*:42591)' - .'|6dc3/([^/]++)/([^/]++)/([^/]++)/8d6dc3(*:42639)' + .'|f707/([^/]++)/([^/]++)/([^/]++)/8df707(*:42583)' + .'|6dc3/([^/]++)/([^/]++)/([^/]++)/8d6dc3(*:42631)' .')' .'|e(?' - .'|efcf/([^/]++)/([^/]++)/([^/]++)/8eefcf(*:42692)' - .'|bda5/([^/]++)/([^/]++)/([^/]++)/8ebda5(*:42740)' - .'|82ab/([^/]++)/([^/]++)/([^/]++)/8e82ab(*:42788)' + .'|efcf/([^/]++)/([^/]++)/([^/]++)/8eefcf(*:42684)' + .'|bda5/([^/]++)/([^/]++)/([^/]++)/8ebda5(*:42732)' + .'|82ab/([^/]++)/([^/]++)/([^/]++)/8e82ab(*:42780)' .')' .'|b(?' - .'|16eb/([^/]++)/([^/]++)/([^/]++)/8b16eb(*:42841)' - .'|6dd7/([^/]++)/([^/]++)/([^/]++)/8b6dd7(*:42889)' - .'|5040/([^/]++)/([^/]++)/([^/]++)/8b5040(*:42937)' + .'|16eb/([^/]++)/([^/]++)/([^/]++)/8b16eb(*:42833)' + .'|6dd7/([^/]++)/([^/]++)/([^/]++)/8b6dd7(*:42881)' + .'|5040/([^/]++)/([^/]++)/([^/]++)/8b5040(*:42929)' .')' .'|c(?' - .'|7bbb/([^/]++)/([^/]++)/([^/]++)/8c7bbb(*:42990)' - .'|6744/([^/]++)/([^/]++)/([^/]++)/8c6744(*:43038)' - .'|235f/([^/]++)/([^/]++)/([^/]++)/8c235f(*:43086)' + .'|7bbb/([^/]++)/([^/]++)/([^/]++)/8c7bbb(*:42982)' + .'|6744/([^/]++)/([^/]++)/([^/]++)/8c6744(*:43030)' + .'|235f/([^/]++)/([^/]++)/([^/]++)/8c235f(*:43078)' .')' .'|8(?' - .'|4d24/([^/]++)/([^/]++)/([^/]++)/884d24(*:43139)' - .'|ae63/([^/]++)/([^/]++)/([^/]++)/88ae63(*:43187)' + .'|4d24/([^/]++)/([^/]++)/([^/]++)/884d24(*:43131)' + .'|ae63/([^/]++)/([^/]++)/([^/]++)/88ae63(*:43179)' .')' .'|7(?' - .'|5715/([^/]++)/([^/]++)/([^/]++)/875715(*:43240)' - .'|2488/([^/]++)/([^/]++)/([^/]++)/872488(*:43288)' + .'|5715/([^/]++)/([^/]++)/([^/]++)/875715(*:43232)' + .'|2488/([^/]++)/([^/]++)/([^/]++)/872488(*:43280)' .')' .'|4(?' - .'|1172/([^/]++)/([^/]++)/([^/]++)/841172(*:43341)' - .'|6c26/([^/]++)/([^/]++)/([^/]++)/846c26(*:43389)' - .'|f7e6/([^/]++)/([^/]++)/([^/]++)/84f7e6(*:43437)' - .'|7cc5/([^/]++)/([^/]++)/([^/]++)/847cc5(*:43485)' + .'|1172/([^/]++)/([^/]++)/([^/]++)/841172(*:43333)' + .'|6c26/([^/]++)/([^/]++)/([^/]++)/846c26(*:43381)' + .'|f7e6/([^/]++)/([^/]++)/([^/]++)/84f7e6(*:43429)' + .'|7cc5/([^/]++)/([^/]++)/([^/]++)/847cc5(*:43477)' .')' .'|f(?' - .'|ecb2/([^/]++)/([^/]++)/([^/]++)/8fecb2(*:43538)' - .'|7d80/([^/]++)/([^/]++)/([^/]++)/8f7d80(*:43586)' - .'|468c/([^/]++)/([^/]++)/([^/]++)/8f468c(*:43634)' + .'|ecb2/([^/]++)/([^/]++)/([^/]++)/8fecb2(*:43530)' + .'|7d80/([^/]++)/([^/]++)/([^/]++)/8f7d80(*:43578)' + .'|468c/([^/]++)/([^/]++)/([^/]++)/8f468c(*:43626)' .')' - .'|a0e11/([^/]++)/([^/]++)/([^/]++)/8a0e11(*:43684)' + .'|a0e11/([^/]++)/([^/]++)/([^/]++)/8a0e11(*:43676)' .'|2(?' - .'|f2b3/([^/]++)/([^/]++)/([^/]++)/82f2b3(*:43736)' - .'|489c/([^/]++)/([^/]++)/([^/]++)/82489c(*:43784)' + .'|f2b3/([^/]++)/([^/]++)/([^/]++)/82f2b3(*:43728)' + .'|489c/([^/]++)/([^/]++)/([^/]++)/82489c(*:43776)' .')' .'|6(?' - .'|b122/([^/]++)/([^/]++)/([^/]++)/86b122(*:43837)' - .'|0320/([^/]++)/([^/]++)/([^/]++)/860320(*:43885)' + .'|b122/([^/]++)/([^/]++)/([^/]++)/86b122(*:43829)' + .'|0320/([^/]++)/([^/]++)/([^/]++)/860320(*:43877)' .')' .'|9(?' - .'|2c91/([^/]++)/([^/]++)/([^/]++)/892c91(*:43938)' - .'|fcd0/([^/]++)/([^/]++)/([^/]++)/89fcd0(*:43986)' + .'|2c91/([^/]++)/([^/]++)/([^/]++)/892c91(*:43930)' + .'|fcd0/([^/]++)/([^/]++)/([^/]++)/89fcd0(*:43978)' .')' - .'|065d0/([^/]++)/([^/]++)/([^/]++)/8065d0(*:44036)' + .'|065d0/([^/]++)/([^/]++)/([^/]++)/8065d0(*:44028)' .')' .'|/d(?' .'|6(?' - .'|4a34/([^/]++)/([^/]++)/([^/]++)/d64a34(*:44094)' - .'|c651/([^/]++)/([^/]++)/([^/]++)/d6c651(*:44142)' + .'|4a34/([^/]++)/([^/]++)/([^/]++)/d64a34(*:44086)' + .'|c651/([^/]++)/([^/]++)/([^/]++)/d6c651(*:44134)' .')' .'|f(?' - .'|877f/([^/]++)/([^/]++)/([^/]++)/df877f(*:44195)' - .'|263d/([^/]++)/([^/]++)/([^/]++)/df263d(*:44243)' - .'|7f28/([^/]++)/([^/]++)/([^/]++)/df7f28(*:44291)' - .'|6d23/([^/]++)/([^/]++)/([^/]++)/df6d23(*:44339)' + .'|877f/([^/]++)/([^/]++)/([^/]++)/df877f(*:44187)' + .'|263d/([^/]++)/([^/]++)/([^/]++)/df263d(*:44235)' + .'|7f28/([^/]++)/([^/]++)/([^/]++)/df7f28(*:44283)' + .'|6d23/([^/]++)/([^/]++)/([^/]++)/df6d23(*:44331)' .')' .'|b(?' - .'|85e2/([^/]++)/([^/]++)/([^/]++)/db85e2(*:44392)' - .'|e272/([^/]++)/([^/]++)/([^/]++)/dbe272(*:44440)' + .'|85e2/([^/]++)/([^/]++)/([^/]++)/db85e2(*:44384)' + .'|e272/([^/]++)/([^/]++)/([^/]++)/dbe272(*:44432)' .')' .'|d(?' .'|45(?' - .'|85/([^/]++)/([^/]++)/([^/]++)/dd4585(*:44496)' - .'|04/([^/]++)/([^/]++)/([^/]++)/dd4504(*:44542)' + .'|85/([^/]++)/([^/]++)/([^/]++)/dd4585(*:44488)' + .'|04/([^/]++)/([^/]++)/([^/]++)/dd4504(*:44534)' .')' - .'|8eb9/([^/]++)/([^/]++)/([^/]++)/dd8eb9(*:44591)' + .'|8eb9/([^/]++)/([^/]++)/([^/]++)/dd8eb9(*:44583)' .')' .'|a(?' - .'|ca41/([^/]++)/([^/]++)/([^/]++)/daca41(*:44644)' - .'|8ce5/([^/]++)/([^/]++)/([^/]++)/da8ce5(*:44692)' - .'|0d11/([^/]++)/([^/]++)/([^/]++)/da0d11(*:44740)' + .'|ca41/([^/]++)/([^/]++)/([^/]++)/daca41(*:44636)' + .'|8ce5/([^/]++)/([^/]++)/([^/]++)/da8ce5(*:44684)' + .'|0d11/([^/]++)/([^/]++)/([^/]++)/da0d11(*:44732)' .')' .'|4(?' - .'|90d7/([^/]++)/([^/]++)/([^/]++)/d490d7(*:44793)' - .'|c2e4/([^/]++)/([^/]++)/([^/]++)/d4c2e4(*:44841)' + .'|90d7/([^/]++)/([^/]++)/([^/]++)/d490d7(*:44785)' + .'|c2e4/([^/]++)/([^/]++)/([^/]++)/d4c2e4(*:44833)' .')' .'|8(?' - .'|6ea6/([^/]++)/([^/]++)/([^/]++)/d86ea6(*:44894)' - .'|40cc/([^/]++)/([^/]++)/([^/]++)/d840cc(*:44942)' + .'|6ea6/([^/]++)/([^/]++)/([^/]++)/d86ea6(*:44886)' + .'|40cc/([^/]++)/([^/]++)/([^/]++)/d840cc(*:44934)' .')' .'|c(?' - .'|82d6/([^/]++)/([^/]++)/([^/]++)/dc82d6(*:44995)' - .'|6a70/([^/]++)/([^/]++)/([^/]++)/dc6a70(*:45043)' - .'|5689/([^/]++)/([^/]++)/([^/]++)/dc5689(*:45091)' + .'|82d6/([^/]++)/([^/]++)/([^/]++)/dc82d6(*:44987)' + .'|6a70/([^/]++)/([^/]++)/([^/]++)/dc6a70(*:45035)' + .'|5689/([^/]++)/([^/]++)/([^/]++)/dc5689(*:45083)' .')' .'|7(?' - .'|a728/([^/]++)/([^/]++)/([^/]++)/d7a728(*:45144)' - .'|0732/([^/]++)/([^/]++)/([^/]++)/d70732(*:45192)' - .'|9aac/([^/]++)/([^/]++)/([^/]++)/d79aac(*:45240)' + .'|a728/([^/]++)/([^/]++)/([^/]++)/d7a728(*:45136)' + .'|0732/([^/]++)/([^/]++)/([^/]++)/d70732(*:45184)' + .'|9aac/([^/]++)/([^/]++)/([^/]++)/d79aac(*:45232)' .')' - .'|14220/([^/]++)/([^/]++)/([^/]++)/d14220(*:45290)' + .'|14220/([^/]++)/([^/]++)/([^/]++)/d14220(*:45282)' .'|5(?' - .'|cfea/([^/]++)/([^/]++)/([^/]++)/d5cfea(*:45342)' - .'|8072/([^/]++)/([^/]++)/([^/]++)/d58072(*:45390)' - .'|54f7/([^/]++)/([^/]++)/([^/]++)/d554f7(*:45438)' - .'|16b1/([^/]++)/([^/]++)/([^/]++)/d516b1(*:45486)' - .'|6b9f/([^/]++)/([^/]++)/([^/]++)/d56b9f(*:45534)' + .'|cfea/([^/]++)/([^/]++)/([^/]++)/d5cfea(*:45334)' + .'|8072/([^/]++)/([^/]++)/([^/]++)/d58072(*:45382)' + .'|54f7/([^/]++)/([^/]++)/([^/]++)/d554f7(*:45430)' + .'|16b1/([^/]++)/([^/]++)/([^/]++)/d516b1(*:45478)' + .'|6b9f/([^/]++)/([^/]++)/([^/]++)/d56b9f(*:45526)' .')' - .'|045c5/([^/]++)/([^/]++)/([^/]++)/d045c5(*:45584)' + .'|045c5/([^/]++)/([^/]++)/([^/]++)/d045c5(*:45576)' .'|2(?' - .'|ed45/([^/]++)/([^/]++)/([^/]++)/d2ed45(*:45636)' - .'|40e3/([^/]++)/([^/]++)/([^/]++)/d240e3(*:45684)' + .'|ed45/([^/]++)/([^/]++)/([^/]++)/d2ed45(*:45628)' + .'|40e3/([^/]++)/([^/]++)/([^/]++)/d240e3(*:45676)' .')' - .'|93ed5/([^/]++)/([^/]++)/([^/]++)/d93ed5(*:45734)' + .'|93ed5/([^/]++)/([^/]++)/([^/]++)/d93ed5(*:45726)' .')' .'|/7(?' .'|b(?' - .'|cdf7/([^/]++)/([^/]++)/([^/]++)/7bcdf7(*:45792)' - .'|13b2/([^/]++)/([^/]++)/([^/]++)/7b13b2(*:45840)' + .'|cdf7/([^/]++)/([^/]++)/([^/]++)/7bcdf7(*:45784)' + .'|13b2/([^/]++)/([^/]++)/([^/]++)/7b13b2(*:45832)' .')' - .'|dcd34/([^/]++)/([^/]++)/([^/]++)/7dcd34(*:45890)' + .'|dcd34/([^/]++)/([^/]++)/([^/]++)/7dcd34(*:45882)' .'|f(?' - .'|24d2/([^/]++)/([^/]++)/([^/]++)/7f24d2(*:45942)' - .'|5d04/([^/]++)/([^/]++)/([^/]++)/7f5d04(*:45990)' - .'|1171/([^/]++)/([^/]++)/([^/]++)/7f1171(*:46038)' - .'|a732/([^/]++)/([^/]++)/([^/]++)/7fa732(*:46086)' + .'|24d2/([^/]++)/([^/]++)/([^/]++)/7f24d2(*:45934)' + .'|5d04/([^/]++)/([^/]++)/([^/]++)/7f5d04(*:45982)' + .'|1171/([^/]++)/([^/]++)/([^/]++)/7f1171(*:46030)' + .'|a732/([^/]++)/([^/]++)/([^/]++)/7fa732(*:46078)' .')' .'|6(?' - .'|6ebc/([^/]++)/([^/]++)/([^/]++)/766ebc(*:46139)' - .'|34ea/([^/]++)/([^/]++)/([^/]++)/7634ea(*:46187)' + .'|6ebc/([^/]++)/([^/]++)/([^/]++)/766ebc(*:46131)' + .'|34ea/([^/]++)/([^/]++)/([^/]++)/7634ea(*:46179)' .')' - .'|750ca/([^/]++)/([^/]++)/([^/]++)/7750ca(*:46237)' + .'|750ca/([^/]++)/([^/]++)/([^/]++)/7750ca(*:46229)' .'|1(?' .'|a(?' - .'|3cb/([^/]++)/([^/]++)/([^/]++)/71a3cb(*:46292)' - .'|d16/([^/]++)/([^/]++)/([^/]++)/71ad16(*:46339)' + .'|3cb/([^/]++)/([^/]++)/([^/]++)/71a3cb(*:46284)' + .'|d16/([^/]++)/([^/]++)/([^/]++)/71ad16(*:46331)' .')' - .'|43d7/([^/]++)/([^/]++)/([^/]++)/7143d7(*:46388)' + .'|43d7/([^/]++)/([^/]++)/([^/]++)/7143d7(*:46380)' .')' - .'|88d98/([^/]++)/([^/]++)/([^/]++)/788d98(*:46438)' + .'|88d98/([^/]++)/([^/]++)/([^/]++)/788d98(*:46430)' .'|2(?' - .'|da7f/([^/]++)/([^/]++)/([^/]++)/72da7f(*:46490)' - .'|50eb/([^/]++)/([^/]++)/([^/]++)/7250eb(*:46538)' + .'|da7f/([^/]++)/([^/]++)/([^/]++)/72da7f(*:46482)' + .'|50eb/([^/]++)/([^/]++)/([^/]++)/7250eb(*:46530)' .')' .'|c(?' - .'|590f/([^/]++)/([^/]++)/([^/]++)/7c590f(*:46591)' - .'|e328/([^/]++)/([^/]++)/([^/]++)/7ce328(*:46639)' - .')' - .'|a5392/([^/]++)/([^/]++)/([^/]++)/7a5392(*:46689)' - .'|95c7a/([^/]++)/([^/]++)/([^/]++)/795c7a(*:46738)' - .'|504ad/([^/]++)/([^/]++)/([^/]++)/7504ad(*:46787)' - .'|04afe/([^/]++)/([^/]++)/([^/]++)/704afe(*:46836)' - .'|4bba2/([^/]++)/([^/]++)/([^/]++)/74bba2(*:46885)' + .'|590f/([^/]++)/([^/]++)/([^/]++)/7c590f(*:46583)' + .'|e328/([^/]++)/([^/]++)/([^/]++)/7ce328(*:46631)' + .')' + .'|a5392/([^/]++)/([^/]++)/([^/]++)/7a5392(*:46681)' + .'|95c7a/([^/]++)/([^/]++)/([^/]++)/795c7a(*:46730)' + .'|504ad/([^/]++)/([^/]++)/([^/]++)/7504ad(*:46779)' + .'|04afe/([^/]++)/([^/]++)/([^/]++)/704afe(*:46828)' + .'|4bba2/([^/]++)/([^/]++)/([^/]++)/74bba2(*:46877)' .')' .'|/9(?' .'|b(?' - .'|72e3/([^/]++)/([^/]++)/([^/]++)/9b72e3(*:46943)' - .'|698e/([^/]++)/([^/]++)/([^/]++)/9b698e(*:46991)' + .'|72e3/([^/]++)/([^/]++)/([^/]++)/9b72e3(*:46935)' + .'|698e/([^/]++)/([^/]++)/([^/]++)/9b698e(*:46983)' .')' - .'|7e852/([^/]++)/([^/]++)/([^/]++)/97e852(*:47041)' - .'|4c7bb/([^/]++)/([^/]++)/([^/]++)/94c7bb(*:47090)' + .'|7e852/([^/]++)/([^/]++)/([^/]++)/97e852(*:47033)' + .'|4c7bb/([^/]++)/([^/]++)/([^/]++)/94c7bb(*:47082)' .'|9(?' - .'|c5e0/([^/]++)/([^/]++)/([^/]++)/99c5e0(*:47142)' - .'|6a7f/([^/]++)/([^/]++)/([^/]++)/996a7f(*:47190)' - .'|bcfc/([^/]++)/([^/]++)/([^/]++)/99bcfc(*:47238)' - .'|0827/([^/]++)/([^/]++)/([^/]++)/990827(*:47286)' + .'|c5e0/([^/]++)/([^/]++)/([^/]++)/99c5e0(*:47134)' + .'|6a7f/([^/]++)/([^/]++)/([^/]++)/996a7f(*:47182)' + .'|bcfc/([^/]++)/([^/]++)/([^/]++)/99bcfc(*:47230)' + .'|0827/([^/]++)/([^/]++)/([^/]++)/990827(*:47278)' .')' .'|a(?' - .'|d6aa/([^/]++)/([^/]++)/([^/]++)/9ad6aa(*:47339)' - .'|b0d8/([^/]++)/([^/]++)/([^/]++)/9ab0d8(*:47387)' + .'|d6aa/([^/]++)/([^/]++)/([^/]++)/9ad6aa(*:47331)' + .'|b0d8/([^/]++)/([^/]++)/([^/]++)/9ab0d8(*:47379)' .')' .'|c(?' - .'|f81d/([^/]++)/([^/]++)/([^/]++)/9cf81d(*:47440)' - .'|c138/([^/]++)/([^/]++)/([^/]++)/9cc138(*:47488)' - .'|82c7/([^/]++)/([^/]++)/([^/]++)/9c82c7(*:47536)' - .'|0180/([^/]++)/([^/]++)/([^/]++)/9c0180(*:47584)' + .'|f81d/([^/]++)/([^/]++)/([^/]++)/9cf81d(*:47432)' + .'|c138/([^/]++)/([^/]++)/([^/]++)/9cc138(*:47480)' + .'|82c7/([^/]++)/([^/]++)/([^/]++)/9c82c7(*:47528)' + .'|0180/([^/]++)/([^/]++)/([^/]++)/9c0180(*:47576)' .')' .'|f(?' - .'|396f/([^/]++)/([^/]++)/([^/]++)/9f396f(*:47637)' - .'|e859/([^/]++)/([^/]++)/([^/]++)/9fe859(*:47685)' - .'|53d8/([^/]++)/([^/]++)/([^/]++)/9f53d8(*:47733)' + .'|396f/([^/]++)/([^/]++)/([^/]++)/9f396f(*:47629)' + .'|e859/([^/]++)/([^/]++)/([^/]++)/9fe859(*:47677)' + .'|53d8/([^/]++)/([^/]++)/([^/]++)/9f53d8(*:47725)' .')' - .'|12d2b/([^/]++)/([^/]++)/([^/]++)/912d2b(*:47783)' - .'|59a55/([^/]++)/([^/]++)/([^/]++)/959a55(*:47832)' + .'|12d2b/([^/]++)/([^/]++)/([^/]++)/912d2b(*:47775)' + .'|59a55/([^/]++)/([^/]++)/([^/]++)/959a55(*:47824)' .'|6(?' - .'|ea64/([^/]++)/([^/]++)/([^/]++)/96ea64(*:47884)' - .'|b9bf/([^/]++)/([^/]++)/([^/]++)/96b9bf(*:47932)' + .'|ea64/([^/]++)/([^/]++)/([^/]++)/96ea64(*:47876)' + .'|b9bf/([^/]++)/([^/]++)/([^/]++)/96b9bf(*:47924)' .')' - .'|e3cfc/([^/]++)/([^/]++)/([^/]++)/9e3cfc(*:47982)' + .'|e3cfc/([^/]++)/([^/]++)/([^/]++)/9e3cfc(*:47974)' .'|2(?' - .'|fb0c/([^/]++)/([^/]++)/([^/]++)/92fb0c(*:48034)' - .'|262b/([^/]++)/([^/]++)/([^/]++)/92262b(*:48082)' - .'|32fe/([^/]++)/([^/]++)/([^/]++)/9232fe(*:48130)' - .'|977a/([^/]++)/([^/]++)/([^/]++)/92977a(*:48178)' - .')' - .'|8d6f5/([^/]++)/([^/]++)/([^/]++)/98d6f5(*:48228)' - .'|0794e/([^/]++)/([^/]++)/([^/]++)/90794e(*:48277)' - .'|34815/([^/]++)/([^/]++)/([^/]++)/934815(*:48326)' + .'|fb0c/([^/]++)/([^/]++)/([^/]++)/92fb0c(*:48026)' + .'|262b/([^/]++)/([^/]++)/([^/]++)/92262b(*:48074)' + .'|32fe/([^/]++)/([^/]++)/([^/]++)/9232fe(*:48122)' + .'|977a/([^/]++)/([^/]++)/([^/]++)/92977a(*:48170)' + .')' + .'|8d6f5/([^/]++)/([^/]++)/([^/]++)/98d6f5(*:48220)' + .'|0794e/([^/]++)/([^/]++)/([^/]++)/90794e(*:48269)' + .'|34815/([^/]++)/([^/]++)/([^/]++)/934815(*:48318)' .')' .'|/4(?' .'|e(?' - .'|4b5f/([^/]++)/([^/]++)/([^/]++)/4e4b5f(*:48384)' - .'|a06f/([^/]++)/([^/]++)/([^/]++)/4ea06f(*:48432)' + .'|4b5f/([^/]++)/([^/]++)/([^/]++)/4e4b5f(*:48376)' + .'|a06f/([^/]++)/([^/]++)/([^/]++)/4ea06f(*:48424)' .'|0(?' - .'|928/([^/]++)/([^/]++)/([^/]++)/4e0928(*:48483)' - .'|cb6/([^/]++)/([^/]++)/([^/]++)/4e0cb6(*:48530)' + .'|928/([^/]++)/([^/]++)/([^/]++)/4e0928(*:48475)' + .'|cb6/([^/]++)/([^/]++)/([^/]++)/4e0cb6(*:48522)' .')' .')' - .'|6922a/([^/]++)/([^/]++)/([^/]++)/46922a(*:48581)' + .'|6922a/([^/]++)/([^/]++)/([^/]++)/46922a(*:48573)' .'|4(?' - .'|c4c1/([^/]++)/([^/]++)/([^/]++)/44c4c1(*:48633)' - .'|3cb0/([^/]++)/([^/]++)/([^/]++)/443cb0(*:48681)' + .'|c4c1/([^/]++)/([^/]++)/([^/]++)/44c4c1(*:48625)' + .'|3cb0/([^/]++)/([^/]++)/([^/]++)/443cb0(*:48673)' .')' - .'|8ab2f/([^/]++)/([^/]++)/([^/]++)/48ab2f(*:48731)' + .'|8ab2f/([^/]++)/([^/]++)/([^/]++)/48ab2f(*:48723)' .'|5(?' - .'|645a/([^/]++)/([^/]++)/([^/]++)/45645a(*:48783)' - .'|58db/([^/]++)/([^/]++)/([^/]++)/4558db(*:48831)' + .'|645a/([^/]++)/([^/]++)/([^/]++)/45645a(*:48775)' + .'|58db/([^/]++)/([^/]++)/([^/]++)/4558db(*:48823)' .')' - .'|2e77b/([^/]++)/([^/]++)/([^/]++)/42e77b(*:48881)' - .'|c27ce/([^/]++)/([^/]++)/([^/]++)/4c27ce(*:48930)' + .'|2e77b/([^/]++)/([^/]++)/([^/]++)/42e77b(*:48873)' + .'|c27ce/([^/]++)/([^/]++)/([^/]++)/4c27ce(*:48922)' .'|f(?' - .'|fce0/([^/]++)/([^/]++)/([^/]++)/4ffce0(*:48982)' - .'|ac9b/([^/]++)/([^/]++)/([^/]++)/4fac9b(*:49030)' + .'|fce0/([^/]++)/([^/]++)/([^/]++)/4ffce0(*:48974)' + .'|ac9b/([^/]++)/([^/]++)/([^/]++)/4fac9b(*:49022)' .')' - .'|a47d2/([^/]++)/([^/]++)/([^/]++)/4a47d2(*:49080)' - .'|70e7a/([^/]++)/([^/]++)/([^/]++)/470e7a(*:49129)' + .'|a47d2/([^/]++)/([^/]++)/([^/]++)/4a47d2(*:49072)' + .'|70e7a/([^/]++)/([^/]++)/([^/]++)/470e7a(*:49121)' .'|b(?' .'|0(?' - .'|4a6/([^/]++)/([^/]++)/([^/]++)/4b04a6(*:49184)' - .'|a59/([^/]++)/([^/]++)/([^/]++)/4b0a59(*:49231)' - .'|250/([^/]++)/([^/]++)/([^/]++)/4b0250(*:49278)' + .'|4a6/([^/]++)/([^/]++)/([^/]++)/4b04a6(*:49176)' + .'|a59/([^/]++)/([^/]++)/([^/]++)/4b0a59(*:49223)' + .'|250/([^/]++)/([^/]++)/([^/]++)/4b0250(*:49270)' .')' - .'|6538/([^/]++)/([^/]++)/([^/]++)/4b6538(*:49327)' + .'|6538/([^/]++)/([^/]++)/([^/]++)/4b6538(*:49319)' .')' .'|3(?' .'|f(?' - .'|a7f/([^/]++)/([^/]++)/([^/]++)/43fa7f(*:49383)' - .'|eae/([^/]++)/([^/]++)/([^/]++)/43feae(*:49430)' + .'|a7f/([^/]++)/([^/]++)/([^/]++)/43fa7f(*:49375)' + .'|eae/([^/]++)/([^/]++)/([^/]++)/43feae(*:49422)' .')' - .'|0c36/([^/]++)/([^/]++)/([^/]++)/430c36(*:49479)' - .'|7d7d/([^/]++)/([^/]++)/([^/]++)/437d7d(*:49527)' - .'|1135/([^/]++)/([^/]++)/([^/]++)/431135(*:49575)' + .'|0c36/([^/]++)/([^/]++)/([^/]++)/430c36(*:49471)' + .'|7d7d/([^/]++)/([^/]++)/([^/]++)/437d7d(*:49519)' + .'|1135/([^/]++)/([^/]++)/([^/]++)/431135(*:49567)' .')' .'|d(?' - .'|5b99/([^/]++)/([^/]++)/([^/]++)/4d5b99(*:49628)' - .'|aa3d/([^/]++)/([^/]++)/([^/]++)/4daa3d(*:49676)' + .'|5b99/([^/]++)/([^/]++)/([^/]++)/4d5b99(*:49620)' + .'|aa3d/([^/]++)/([^/]++)/([^/]++)/4daa3d(*:49668)' .')' - .'|9c9ad/([^/]++)/([^/]++)/([^/]++)/49c9ad(*:49726)' + .'|9c9ad/([^/]++)/([^/]++)/([^/]++)/49c9ad(*:49718)' .')' .')$}sD', ); @@ -2290,505 +2290,505 @@ public function match($rawPathinfo) 24688 => array(array('_route' => '_391'), array('a', 'b', 'c'), null, null), 24737 => array(array('_route' => '_462'), array('a', 'b', 'c'), null, null), 24786 => array(array('_route' => '_476'), array('a', 'b', 'c'), null, null), - 24845 => array(array('_route' => '_501'), array('a', 'b', 'c'), null, null), - 24897 => array(array('_route' => '_514'), array('a', 'b', 'c'), null, null), - 24945 => array(array('_route' => '_731'), array('a', 'b', 'c'), null, null), - 24998 => array(array('_route' => '_522'), array('a', 'b', 'c'), null, null), - 25046 => array(array('_route' => '_693'), array('a', 'b', 'c'), null, null), - 25099 => array(array('_route' => '_537'), array('a', 'b', 'c'), null, null), - 25147 => array(array('_route' => '_554'), array('a', 'b', 'c'), null, null), - 25195 => array(array('_route' => '_645'), array('a', 'b', 'c'), null, null), - 25243 => array(array('_route' => '_862'), array('a', 'b', 'c'), null, null), - 25296 => array(array('_route' => '_539'), array('a', 'b', 'c'), null, null), - 25344 => array(array('_route' => '_729'), array('a', 'b', 'c'), null, null), - 25392 => array(array('_route' => '_897'), array('a', 'b', 'c'), null, null), - 25445 => array(array('_route' => '_561'), array('a', 'b', 'c'), null, null), - 25493 => array(array('_route' => '_615'), array('a', 'b', 'c'), null, null), - 25541 => array(array('_route' => '_764'), array('a', 'b', 'c'), null, null), - 25589 => array(array('_route' => '_948'), array('a', 'b', 'c'), null, null), - 25642 => array(array('_route' => '_617'), array('a', 'b', 'c'), null, null), - 25690 => array(array('_route' => '_671'), array('a', 'b', 'c'), null, null), - 25743 => array(array('_route' => '_649'), array('a', 'b', 'c'), null, null), - 25791 => array(array('_route' => '_651'), array('a', 'b', 'c'), null, null), - 25839 => array(array('_route' => '_684'), array('a', 'b', 'c'), null, null), - 25892 => array(array('_route' => '_669'), array('a', 'b', 'c'), null, null), - 25940 => array(array('_route' => '_743'), array('a', 'b', 'c'), null, null), - 25988 => array(array('_route' => '_962'), array('a', 'b', 'c'), null, null), - 26041 => array(array('_route' => '_694'), array('a', 'b', 'c'), null, null), - 26089 => array(array('_route' => '_985'), array('a', 'b', 'c'), null, null), - 26142 => array(array('_route' => '_707'), array('a', 'b', 'c'), null, null), - 26190 => array(array('_route' => '_718'), array('a', 'b', 'c'), null, null), - 26243 => array(array('_route' => '_720'), array('a', 'b', 'c'), null, null), - 26291 => array(array('_route' => '_745'), array('a', 'b', 'c'), null, null), - 26341 => array(array('_route' => '_874'), array('a', 'b', 'c'), null, null), - 26399 => array(array('_route' => '_502'), array('a', 'b', 'c'), null, null), - 26447 => array(array('_route' => '_667'), array('a', 'b', 'c'), null, null), - 26495 => array(array('_route' => '_911'), array('a', 'b', 'c'), null, null), - 26543 => array(array('_route' => '_942'), array('a', 'b', 'c'), null, null), - 26593 => array(array('_route' => '_504'), array('a', 'b', 'c'), null, null), - 26645 => array(array('_route' => '_524'), array('a', 'b', 'c'), null, null), - 26693 => array(array('_route' => '_732'), array('a', 'b', 'c'), null, null), - 26746 => array(array('_route' => '_596'), array('a', 'b', 'c'), null, null), - 26794 => array(array('_route' => '_601'), array('a', 'b', 'c'), null, null), - 26847 => array(array('_route' => '_620'), array('a', 'b', 'c'), null, null), - 26895 => array(array('_route' => '_631'), array('a', 'b', 'c'), null, null), - 26943 => array(array('_route' => '_771'), array('a', 'b', 'c'), null, null), - 26991 => array(array('_route' => '_937'), array('a', 'b', 'c'), null, null), - 27039 => array(array('_route' => '_999'), array('a', 'b', 'c'), null, null), - 27092 => array(array('_route' => '_657'), array('a', 'b', 'c'), null, null), - 27140 => array(array('_route' => '_701'), array('a', 'b', 'c'), null, null), - 27193 => array(array('_route' => '_662'), array('a', 'b', 'c'), null, null), - 27241 => array(array('_route' => '_797'), array('a', 'b', 'c'), null, null), - 27289 => array(array('_route' => '_924'), array('a', 'b', 'c'), null, null), - 27342 => array(array('_route' => '_702'), array('a', 'b', 'c'), null, null), - 27390 => array(array('_route' => '_750'), array('a', 'b', 'c'), null, null), - 27443 => array(array('_route' => '_749'), array('a', 'b', 'c'), null, null), - 27491 => array(array('_route' => '_837'), array('a', 'b', 'c'), null, null), - 27541 => array(array('_route' => '_758'), array('a', 'b', 'c'), null, null), - 27593 => array(array('_route' => '_810'), array('a', 'b', 'c'), null, null), - 27641 => array(array('_route' => '_902'), array('a', 'b', 'c'), null, null), - 27691 => array(array('_route' => '_845'), array('a', 'b', 'c'), null, null), - 27749 => array(array('_route' => '_503'), array('a', 'b', 'c'), null, null), - 27800 => array(array('_route' => '_756'), array('a', 'b', 'c'), null, null), - 27847 => array(array('_route' => '_799'), array('a', 'b', 'c'), null, null), - 27896 => array(array('_route' => '_769'), array('a', 'b', 'c'), null, null), - 27944 => array(array('_route' => '_981'), array('a', 'b', 'c'), null, null), - 27997 => array(array('_route' => '_507'), array('a', 'b', 'c'), null, null), - 28045 => array(array('_route' => '_672'), array('a', 'b', 'c'), null, null), - 28093 => array(array('_route' => '_790'), array('a', 'b', 'c'), null, null), - 28146 => array(array('_route' => '_515'), array('a', 'b', 'c'), null, null), - 28194 => array(array('_route' => '_523'), array('a', 'b', 'c'), null, null), - 28242 => array(array('_route' => '_957'), array('a', 'b', 'c'), null, null), - 28290 => array(array('_route' => '_995'), array('a', 'b', 'c'), null, null), - 28343 => array(array('_route' => '_532'), array('a', 'b', 'c'), null, null), - 28391 => array(array('_route' => '_642'), array('a', 'b', 'c'), null, null), - 28441 => array(array('_route' => '_579'), array('a', 'b', 'c'), null, null), - 28493 => array(array('_route' => '_625'), array('a', 'b', 'c'), null, null), - 28541 => array(array('_route' => '_916'), array('a', 'b', 'c'), null, null), - 28594 => array(array('_route' => '_633'), array('a', 'b', 'c'), null, null), - 28642 => array(array('_route' => '_656'), array('a', 'b', 'c'), null, null), - 28695 => array(array('_route' => '_658'), array('a', 'b', 'c'), null, null), - 28743 => array(array('_route' => '_943'), array('a', 'b', 'c'), null, null), - 28796 => array(array('_route' => '_664'), array('a', 'b', 'c'), null, null), - 28844 => array(array('_route' => '_852'), array('a', 'b', 'c'), null, null), - 28892 => array(array('_route' => '_870'), array('a', 'b', 'c'), null, null), - 28945 => array(array('_route' => '_683'), array('a', 'b', 'c'), null, null), - 28993 => array(array('_route' => '_915'), array('a', 'b', 'c'), null, null), - 29046 => array(array('_route' => '_719'), array('a', 'b', 'c'), null, null), - 29094 => array(array('_route' => '_859'), array('a', 'b', 'c'), null, null), - 29142 => array(array('_route' => '_912'), array('a', 'b', 'c'), null, null), - 29190 => array(array('_route' => '_978'), array('a', 'b', 'c'), null, null), - 29243 => array(array('_route' => '_738'), array('a', 'b', 'c'), null, null), - 29291 => array(array('_route' => '_883'), array('a', 'b', 'c'), null, null), - 29341 => array(array('_route' => '_741'), array('a', 'b', 'c'), null, null), - 29390 => array(array('_route' => '_760'), array('a', 'b', 'c'), null, null), - 29439 => array(array('_route' => '_895'), array('a', 'b', 'c'), null, null), - 29497 => array(array('_route' => '_505'), array('a', 'b', 'c'), null, null), - 29545 => array(array('_route' => '_935'), array('a', 'b', 'c'), null, null), - 29598 => array(array('_route' => '_509'), array('a', 'b', 'c'), null, null), - 29646 => array(array('_route' => '_820'), array('a', 'b', 'c'), null, null), - 29694 => array(array('_route' => '_910'), array('a', 'b', 'c'), null, null), - 29747 => array(array('_route' => '_518'), array('a', 'b', 'c'), null, null), - 29795 => array(array('_route' => '_618'), array('a', 'b', 'c'), null, null), - 29848 => array(array('_route' => '_546'), array('a', 'b', 'c'), null, null), - 29896 => array(array('_route' => '_740'), array('a', 'b', 'c'), null, null), - 29944 => array(array('_route' => '_867'), array('a', 'b', 'c'), null, null), - 29997 => array(array('_route' => '_572'), array('a', 'b', 'c'), null, null), - 30045 => array(array('_route' => '_952'), array('a', 'b', 'c'), null, null), - 30098 => array(array('_route' => '_573'), array('a', 'b', 'c'), null, null), - 30146 => array(array('_route' => '_692'), array('a', 'b', 'c'), null, null), - 30194 => array(array('_route' => '_700'), array('a', 'b', 'c'), null, null), - 30242 => array(array('_route' => '_772'), array('a', 'b', 'c'), null, null), - 30292 => array(array('_route' => '_653'), array('a', 'b', 'c'), null, null), - 30344 => array(array('_route' => '_695'), array('a', 'b', 'c'), null, null), - 30392 => array(array('_route' => '_748'), array('a', 'b', 'c'), null, null), - 30445 => array(array('_route' => '_710'), array('a', 'b', 'c'), null, null), - 30493 => array(array('_route' => '_716'), array('a', 'b', 'c'), null, null), - 30541 => array(array('_route' => '_969'), array('a', 'b', 'c'), null, null), - 30594 => array(array('_route' => '_734'), array('a', 'b', 'c'), null, null), - 30642 => array(array('_route' => '_742'), array('a', 'b', 'c'), null, null), - 30690 => array(array('_route' => '_844'), array('a', 'b', 'c'), null, null), - 30743 => array(array('_route' => '_763'), array('a', 'b', 'c'), null, null), - 30791 => array(array('_route' => '_965'), array('a', 'b', 'c'), null, null), - 30844 => array(array('_route' => '_778'), array('a', 'b', 'c'), null, null), - 30892 => array(array('_route' => '_813'), array('a', 'b', 'c'), null, null), - 30940 => array(array('_route' => '_831'), array('a', 'b', 'c'), null, null), - 30990 => array(array('_route' => '_955'), array('a', 'b', 'c'), null, null), - 31039 => array(array('_route' => '_997'), array('a', 'b', 'c'), null, null), - 31097 => array(array('_route' => '_506'), array('a', 'b', 'c'), null, null), - 31145 => array(array('_route' => '_575'), array('a', 'b', 'c'), null, null), - 31198 => array(array('_route' => '_516'), array('a', 'b', 'c'), null, null), - 31246 => array(array('_route' => '_553'), array('a', 'b', 'c'), null, null), - 31299 => array(array('_route' => '_528'), array('a', 'b', 'c'), null, null), - 31347 => array(array('_route' => '_847'), array('a', 'b', 'c'), null, null), - 31395 => array(array('_route' => '_904'), array('a', 'b', 'c'), null, null), - 31448 => array(array('_route' => '_574'), array('a', 'b', 'c'), null, null), - 31496 => array(array('_route' => '_818'), array('a', 'b', 'c'), null, null), - 31546 => array(array('_route' => '_577'), array('a', 'b', 'c'), null, null), - 31598 => array(array('_route' => '_584'), array('a', 'b', 'c'), null, null), - 31646 => array(array('_route' => '_905'), array('a', 'b', 'c'), null, null), - 31699 => array(array('_route' => '_612'), array('a', 'b', 'c'), null, null), - 31747 => array(array('_route' => '_688'), array('a', 'b', 'c'), null, null), - 31795 => array(array('_route' => '_854'), array('a', 'b', 'c'), null, null), - 31848 => array(array('_route' => '_613'), array('a', 'b', 'c'), null, null), - 31896 => array(array('_route' => '_767'), array('a', 'b', 'c'), null, null), - 31949 => array(array('_route' => '_666'), array('a', 'b', 'c'), null, null), - 31997 => array(array('_route' => '_759'), array('a', 'b', 'c'), null, null), - 32045 => array(array('_route' => '_827'), array('a', 'b', 'c'), null, null), - 32093 => array(array('_route' => '_840'), array('a', 'b', 'c'), null, null), - 32146 => array(array('_route' => '_680'), array('a', 'b', 'c'), null, null), - 32194 => array(array('_route' => '_784'), array('a', 'b', 'c'), null, null), - 32242 => array(array('_route' => '_842'), array('a', 'b', 'c'), null, null), - 32290 => array(array('_route' => '_860'), array('a', 'b', 'c'), null, null), - 32340 => array(array('_route' => '_704'), array('a', 'b', 'c'), null, null), - 32389 => array(array('_route' => '_727'), array('a', 'b', 'c'), null, null), - 32438 => array(array('_route' => '_777'), array('a', 'b', 'c'), null, null), - 32490 => array(array('_route' => '_838'), array('a', 'b', 'c'), null, null), - 32538 => array(array('_route' => '_861'), array('a', 'b', 'c'), null, null), - 32591 => array(array('_route' => '_849'), array('a', 'b', 'c'), null, null), - 32639 => array(array('_route' => '_982'), array('a', 'b', 'c'), null, null), - 32687 => array(array('_route' => '_986'), array('a', 'b', 'c'), null, null), - 32749 => array(array('_route' => '_508'), array('a', 'b', 'c'), null, null), - 32796 => array(array('_route' => '_517'), array('a', 'b', 'c'), null, null), - 32845 => array(array('_route' => '_622'), array('a', 'b', 'c'), null, null), - 32898 => array(array('_route' => '_513'), array('a', 'b', 'c'), null, null), - 32946 => array(array('_route' => '_655'), array('a', 'b', 'c'), null, null), - 32994 => array(array('_route' => '_843'), array('a', 'b', 'c'), null, null), - 33042 => array(array('_route' => '_939'), array('a', 'b', 'c'), null, null), - 33092 => array(array('_route' => '_529'), array('a', 'b', 'c'), null, null), - 33144 => array(array('_route' => '_535'), array('a', 'b', 'c'), null, null), - 33192 => array(array('_route' => '_685'), array('a', 'b', 'c'), null, null), - 33248 => array(array('_route' => '_559'), array('a', 'b', 'c'), null, null), - 33295 => array(array('_route' => '_661'), array('a', 'b', 'c'), null, null), - 33344 => array(array('_route' => '_768'), array('a', 'b', 'c'), null, null), - 33397 => array(array('_route' => '_589'), array('a', 'b', 'c'), null, null), - 33445 => array(array('_route' => '_647'), array('a', 'b', 'c'), null, null), - 33493 => array(array('_route' => '_652'), array('a', 'b', 'c'), null, null), - 33541 => array(array('_route' => '_834'), array('a', 'b', 'c'), null, null), - 33594 => array(array('_route' => '_591'), array('a', 'b', 'c'), null, null), - 33642 => array(array('_route' => '_599'), array('a', 'b', 'c'), null, null), - 33695 => array(array('_route' => '_787'), array('a', 'b', 'c'), null, null), - 33742 => array(array('_route' => '_848'), array('a', 'b', 'c'), null, null), - 33795 => array(array('_route' => '_796'), array('a', 'b', 'c'), null, null), - 33843 => array(array('_route' => '_877'), array('a', 'b', 'c'), null, null), - 33893 => array(array('_route' => '_809'), array('a', 'b', 'c'), null, null), - 33942 => array(array('_route' => '_817'), array('a', 'b', 'c'), null, null), - 33994 => array(array('_route' => '_819'), array('a', 'b', 'c'), null, null), - 34042 => array(array('_route' => '_865'), array('a', 'b', 'c'), null, null), - 34092 => array(array('_route' => '_919'), array('a', 'b', 'c'), null, null), - 34141 => array(array('_route' => '_949'), array('a', 'b', 'c'), null, null), - 34199 => array(array('_route' => '_510'), array('a', 'b', 'c'), null, null), - 34247 => array(array('_route' => '_590'), array('a', 'b', 'c'), null, null), - 34295 => array(array('_route' => '_597'), array('a', 'b', 'c'), null, null), - 34343 => array(array('_route' => '_682'), array('a', 'b', 'c'), null, null), - 34391 => array(array('_route' => '_723'), array('a', 'b', 'c'), null, null), - 34444 => array(array('_route' => '_521'), array('a', 'b', 'c'), null, null), - 34492 => array(array('_route' => '_594'), array('a', 'b', 'c'), null, null), - 34540 => array(array('_route' => '_689'), array('a', 'b', 'c'), null, null), - 34588 => array(array('_route' => '_713'), array('a', 'b', 'c'), null, null), - 34636 => array(array('_route' => '_889'), array('a', 'b', 'c'), null, null), - 34689 => array(array('_route' => '_531'), array('a', 'b', 'c'), null, null), - 34737 => array(array('_route' => '_639'), array('a', 'b', 'c'), null, null), - 34788 => array(array('_route' => '_646'), array('a', 'b', 'c'), null, null), - 34835 => array(array('_route' => '_659'), array('a', 'b', 'c'), null, null), - 34884 => array(array('_route' => '_959'), array('a', 'b', 'c'), null, null), - 34937 => array(array('_route' => '_550'), array('a', 'b', 'c'), null, null), - 34985 => array(array('_route' => '_833'), array('a', 'b', 'c'), null, null), - 35033 => array(array('_route' => '_899'), array('a', 'b', 'c'), null, null), - 35089 => array(array('_route' => '_580'), array('a', 'b', 'c'), null, null), - 35136 => array(array('_route' => '_762'), array('a', 'b', 'c'), null, null), - 35185 => array(array('_route' => '_896'), array('a', 'b', 'c'), null, null), - 35238 => array(array('_route' => '_595'), array('a', 'b', 'c'), null, null), - 35286 => array(array('_route' => '_933'), array('a', 'b', 'c'), null, null), - 35336 => array(array('_route' => '_610'), array('a', 'b', 'c'), null, null), - 35388 => array(array('_route' => '_629'), array('a', 'b', 'c'), null, null), - 35436 => array(array('_route' => '_744'), array('a', 'b', 'c'), null, null), - 35489 => array(array('_route' => '_674'), array('a', 'b', 'c'), null, null), - 35537 => array(array('_route' => '_726'), array('a', 'b', 'c'), null, null), - 35585 => array(array('_route' => '_929'), array('a', 'b', 'c'), null, null), - 35635 => array(array('_route' => '_696'), array('a', 'b', 'c'), null, null), - 35687 => array(array('_route' => '_841'), array('a', 'b', 'c'), null, null), - 35735 => array(array('_route' => '_890'), array('a', 'b', 'c'), null, null), - 35785 => array(array('_route' => '_885'), array('a', 'b', 'c'), null, null), - 35834 => array(array('_route' => '_888'), array('a', 'b', 'c'), null, null), - 35883 => array(array('_route' => '_996'), array('a', 'b', 'c'), null, null), - 35941 => array(array('_route' => '_511'), array('a', 'b', 'c'), null, null), - 35989 => array(array('_route' => '_576'), array('a', 'b', 'c'), null, null), - 36037 => array(array('_route' => '_623'), array('a', 'b', 'c'), null, null), - 36090 => array(array('_route' => '_560'), array('a', 'b', 'c'), null, null), - 36137 => array(array('_route' => '_585'), array('a', 'b', 'c'), null, null), - 36190 => array(array('_route' => '_570'), array('a', 'b', 'c'), null, null), - 36238 => array(array('_route' => '_578'), array('a', 'b', 'c'), null, null), - 36289 => array(array('_route' => '_780'), array('a', 'b', 'c'), null, null), - 36336 => array(array('_route' => '_808'), array('a', 'b', 'c'), null, null), - 36390 => array(array('_route' => '_593'), array('a', 'b', 'c'), null, null), - 36438 => array(array('_route' => '_900'), array('a', 'b', 'c'), null, null), - 36491 => array(array('_route' => '_632'), array('a', 'b', 'c'), null, null), - 36539 => array(array('_route' => '_654'), array('a', 'b', 'c'), null, null), - 36587 => array(array('_route' => '_721'), array('a', 'b', 'c'), null, null), - 36635 => array(array('_route' => '_836'), array('a', 'b', 'c'), null, null), - 36688 => array(array('_route' => '_637'), array('a', 'b', 'c'), null, null), - 36736 => array(array('_route' => '_737'), array('a', 'b', 'c'), null, null), - 36792 => array(array('_route' => '_699'), array('a', 'b', 'c'), null, null), - 36839 => array(array('_route' => '_822'), array('a', 'b', 'c'), null, null), - 36888 => array(array('_route' => '_853'), array('a', 'b', 'c'), null, null), - 36941 => array(array('_route' => '_708'), array('a', 'b', 'c'), null, null), - 36989 => array(array('_route' => '_871'), array('a', 'b', 'c'), null, null), - 37042 => array(array('_route' => '_752'), array('a', 'b', 'c'), null, null), - 37090 => array(array('_route' => '_989'), array('a', 'b', 'c'), null, null), - 37140 => array(array('_route' => '_855'), array('a', 'b', 'c'), null, null), - 37192 => array(array('_route' => '_858'), array('a', 'b', 'c'), null, null), - 37240 => array(array('_route' => '_898'), array('a', 'b', 'c'), null, null), - 37290 => array(array('_route' => '_903'), array('a', 'b', 'c'), null, null), - 37339 => array(array('_route' => '_909'), array('a', 'b', 'c'), null, null), - 37388 => array(array('_route' => '_950'), array('a', 'b', 'c'), null, null), - 37449 => array(array('_route' => '_512'), array('a', 'b', 'c'), null, null), - 37496 => array(array('_route' => '_691'), array('a', 'b', 'c'), null, null), - 37545 => array(array('_route' => '_686'), array('a', 'b', 'c'), null, null), - 37595 => array(array('_route' => '_527'), array('a', 'b', 'c'), null, null), - 37647 => array(array('_route' => '_541'), array('a', 'b', 'c'), null, null), - 37695 => array(array('_route' => '_956'), array('a', 'b', 'c'), null, null), - 37748 => array(array('_route' => '_555'), array('a', 'b', 'c'), null, null), - 37796 => array(array('_route' => '_681'), array('a', 'b', 'c'), null, null), - 37849 => array(array('_route' => '_556'), array('a', 'b', 'c'), null, null), - 37897 => array(array('_route' => '_802'), array('a', 'b', 'c'), null, null), - 37947 => array(array('_route' => '_558'), array('a', 'b', 'c'), null, null), - 37999 => array(array('_route' => '_564'), array('a', 'b', 'c'), null, null), - 38047 => array(array('_route' => '_670'), array('a', 'b', 'c'), null, null), - 38095 => array(array('_route' => '_884'), array('a', 'b', 'c'), null, null), - 38148 => array(array('_route' => '_627'), array('a', 'b', 'c'), null, null), - 38195 => array(array('_route' => '_746'), array('a', 'b', 'c'), null, null), - 38248 => array(array('_route' => '_668'), array('a', 'b', 'c'), null, null), - 38299 => array(array('_route' => '_712'), array('a', 'b', 'c'), null, null), - 38346 => array(array('_route' => '_863'), array('a', 'b', 'c'), null, null), - 38395 => array(array('_route' => '_801'), array('a', 'b', 'c'), null, null), - 38448 => array(array('_route' => '_709'), array('a', 'b', 'c'), null, null), - 38496 => array(array('_route' => '_850'), array('a', 'b', 'c'), null, null), - 38544 => array(array('_route' => '_918'), array('a', 'b', 'c'), null, null), - 38594 => array(array('_route' => '_803'), array('a', 'b', 'c'), null, null), - 38646 => array(array('_route' => '_864'), array('a', 'b', 'c'), null, null), - 38694 => array(array('_route' => '_880'), array('a', 'b', 'c'), null, null), - 38742 => array(array('_route' => '_927'), array('a', 'b', 'c'), null, null), - 38795 => array(array('_route' => '_930'), array('a', 'b', 'c'), null, null), - 38843 => array(array('_route' => '_951'), array('a', 'b', 'c'), null, null), - 38891 => array(array('_route' => '_963'), array('a', 'b', 'c'), null, null), - 38950 => array(array('_route' => '_519'), array('a', 'b', 'c'), null, null), - 38998 => array(array('_route' => '_823'), array('a', 'b', 'c'), null, null), - 39046 => array(array('_route' => '_954'), array('a', 'b', 'c'), null, null), - 39099 => array(array('_route' => '_525'), array('a', 'b', 'c'), null, null), - 39147 => array(array('_route' => '_991'), array('a', 'b', 'c'), null, null), - 39197 => array(array('_route' => '_536'), array('a', 'b', 'c'), null, null), - 39249 => array(array('_route' => '_545'), array('a', 'b', 'c'), null, null), - 39297 => array(array('_route' => '_944'), array('a', 'b', 'c'), null, null), - 39350 => array(array('_route' => '_557'), array('a', 'b', 'c'), null, null), - 39398 => array(array('_route' => '_783'), array('a', 'b', 'c'), null, null), - 39446 => array(array('_route' => '_807'), array('a', 'b', 'c'), null, null), - 39499 => array(array('_route' => '_586'), array('a', 'b', 'c'), null, null), - 39547 => array(array('_route' => '_711'), array('a', 'b', 'c'), null, null), - 39600 => array(array('_route' => '_598'), array('a', 'b', 'c'), null, null), - 39648 => array(array('_route' => '_635'), array('a', 'b', 'c'), null, null), - 39696 => array(array('_route' => '_983'), array('a', 'b', 'c'), null, null), - 39749 => array(array('_route' => '_634'), array('a', 'b', 'c'), null, null), - 39797 => array(array('_route' => '_641'), array('a', 'b', 'c'), null, null), - 39848 => array(array('_route' => '_779'), array('a', 'b', 'c'), null, null), - 39895 => array(array('_route' => '_876'), array('a', 'b', 'c'), null, null), - 39944 => array(array('_route' => '_811'), array('a', 'b', 'c'), null, null), - 39992 => array(array('_route' => '_824'), array('a', 'b', 'c'), null, null), - 40045 => array(array('_route' => '_660'), array('a', 'b', 'c'), null, null), - 40093 => array(array('_route' => '_789'), array('a', 'b', 'c'), null, null), - 40146 => array(array('_route' => '_733'), array('a', 'b', 'c'), null, null), - 40194 => array(array('_route' => '_735'), array('a', 'b', 'c'), null, null), - 40242 => array(array('_route' => '_882'), array('a', 'b', 'c'), null, null), - 40290 => array(array('_route' => '_967'), array('a', 'b', 'c'), null, null), - 40340 => array(array('_route' => '_736'), array('a', 'b', 'c'), null, null), - 40389 => array(array('_route' => '_753'), array('a', 'b', 'c'), null, null), - 40438 => array(array('_route' => '_786'), array('a', 'b', 'c'), null, null), - 40487 => array(array('_route' => '_907'), array('a', 'b', 'c'), null, null), - 40536 => array(array('_route' => '_920'), array('a', 'b', 'c'), null, null), - 40585 => array(array('_route' => '_971'), array('a', 'b', 'c'), null, null), - 40643 => array(array('_route' => '_520'), array('a', 'b', 'c'), null, null), - 40691 => array(array('_route' => '_891'), array('a', 'b', 'c'), null, null), - 40747 => array(array('_route' => '_534'), array('a', 'b', 'c'), null, null), - 40793 => array(array('_route' => '_602'), array('a', 'b', 'c'), null, null), - 40842 => array(array('_route' => '_605'), array('a', 'b', 'c'), null, null), - 40890 => array(array('_route' => '_979'), array('a', 'b', 'c'), null, null), - 40940 => array(array('_route' => '_547'), array('a', 'b', 'c'), null, null), - 40995 => array(array('_route' => '_549'), array('a', 'b', 'c'), null, null), - 41042 => array(array('_route' => '_755'), array('a', 'b', 'c'), null, null), - 41091 => array(array('_route' => '_922'), array('a', 'b', 'c'), null, null), - 41139 => array(array('_route' => '_977'), array('a', 'b', 'c'), null, null), - 41192 => array(array('_route' => '_565'), array('a', 'b', 'c'), null, null), - 41240 => array(array('_route' => '_926'), array('a', 'b', 'c'), null, null), - 41290 => array(array('_route' => '_571'), array('a', 'b', 'c'), null, null), - 41339 => array(array('_route' => '_581'), array('a', 'b', 'c'), null, null), - 41388 => array(array('_route' => '_619'), array('a', 'b', 'c'), null, null), - 41437 => array(array('_route' => '_636'), array('a', 'b', 'c'), null, null), - 41489 => array(array('_route' => '_679'), array('a', 'b', 'c'), null, null), - 41537 => array(array('_route' => '_866'), array('a', 'b', 'c'), null, null), - 41585 => array(array('_route' => '_973'), array('a', 'b', 'c'), null, null), - 41638 => array(array('_route' => '_690'), array('a', 'b', 'c'), null, null), - 41686 => array(array('_route' => '_775'), array('a', 'b', 'c'), null, null), - 41739 => array(array('_route' => '_722'), array('a', 'b', 'c'), null, null), - 41787 => array(array('_route' => '_906'), array('a', 'b', 'c'), null, null), - 41835 => array(array('_route' => '_946'), array('a', 'b', 'c'), null, null), - 41885 => array(array('_route' => '_788'), array('a', 'b', 'c'), null, null), - 41937 => array(array('_route' => '_828'), array('a', 'b', 'c'), null, null), - 41985 => array(array('_route' => '_892'), array('a', 'b', 'c'), null, null), - 42033 => array(array('_route' => '_972'), array('a', 'b', 'c'), null, null), - 42083 => array(array('_route' => '_829'), array('a', 'b', 'c'), null, null), - 42135 => array(array('_route' => '_923'), array('a', 'b', 'c'), null, null), - 42183 => array(array('_route' => '_947'), array('a', 'b', 'c'), null, null), - 42242 => array(array('_route' => '_526'), array('a', 'b', 'c'), null, null), - 42290 => array(array('_route' => '_614'), array('a', 'b', 'c'), null, null), - 42338 => array(array('_route' => '_621'), array('a', 'b', 'c'), null, null), - 42391 => array(array('_route' => '_543'), array('a', 'b', 'c'), null, null), - 42439 => array(array('_route' => '_812'), array('a', 'b', 'c'), null, null), - 42495 => array(array('_route' => '_548'), array('a', 'b', 'c'), null, null), - 42542 => array(array('_route' => '_747'), array('a', 'b', 'c'), null, null), - 42591 => array(array('_route' => '_715'), array('a', 'b', 'c'), null, null), - 42639 => array(array('_route' => '_940'), array('a', 'b', 'c'), null, null), - 42692 => array(array('_route' => '_563'), array('a', 'b', 'c'), null, null), - 42740 => array(array('_route' => '_611'), array('a', 'b', 'c'), null, null), - 42788 => array(array('_route' => '_830'), array('a', 'b', 'c'), null, null), - 42841 => array(array('_route' => '_569'), array('a', 'b', 'c'), null, null), - 42889 => array(array('_route' => '_908'), array('a', 'b', 'c'), null, null), - 42937 => array(array('_route' => '_913'), array('a', 'b', 'c'), null, null), - 42990 => array(array('_route' => '_644'), array('a', 'b', 'c'), null, null), - 43038 => array(array('_route' => '_776'), array('a', 'b', 'c'), null, null), - 43086 => array(array('_route' => '_856'), array('a', 'b', 'c'), null, null), - 43139 => array(array('_route' => '_650'), array('a', 'b', 'c'), null, null), - 43187 => array(array('_route' => '_761'), array('a', 'b', 'c'), null, null), - 43240 => array(array('_route' => '_663'), array('a', 'b', 'c'), null, null), - 43288 => array(array('_route' => '_754'), array('a', 'b', 'c'), null, null), - 43341 => array(array('_route' => '_665'), array('a', 'b', 'c'), null, null), - 43389 => array(array('_route' => '_805'), array('a', 'b', 'c'), null, null), - 43437 => array(array('_route' => '_846'), array('a', 'b', 'c'), null, null), - 43485 => array(array('_route' => '_857'), array('a', 'b', 'c'), null, null), - 43538 => array(array('_route' => '_675'), array('a', 'b', 'c'), null, null), - 43586 => array(array('_route' => '_839'), array('a', 'b', 'c'), null, null), - 43634 => array(array('_route' => '_968'), array('a', 'b', 'c'), null, null), - 43684 => array(array('_route' => '_697'), array('a', 'b', 'c'), null, null), - 43736 => array(array('_route' => '_725'), array('a', 'b', 'c'), null, null), - 43784 => array(array('_route' => '_794'), array('a', 'b', 'c'), null, null), - 43837 => array(array('_route' => '_773'), array('a', 'b', 'c'), null, null), - 43885 => array(array('_route' => '_992'), array('a', 'b', 'c'), null, null), - 43938 => array(array('_route' => '_901'), array('a', 'b', 'c'), null, null), - 43986 => array(array('_route' => '_970'), array('a', 'b', 'c'), null, null), - 44036 => array(array('_route' => '_964'), array('a', 'b', 'c'), null, null), - 44094 => array(array('_route' => '_530'), array('a', 'b', 'c'), null, null), - 44142 => array(array('_route' => '_703'), array('a', 'b', 'c'), null, null), - 44195 => array(array('_route' => '_533'), array('a', 'b', 'c'), null, null), - 44243 => array(array('_route' => '_739'), array('a', 'b', 'c'), null, null), - 44291 => array(array('_route' => '_791'), array('a', 'b', 'c'), null, null), - 44339 => array(array('_route' => '_987'), array('a', 'b', 'c'), null, null), - 44392 => array(array('_route' => '_566'), array('a', 'b', 'c'), null, null), - 44440 => array(array('_route' => '_592'), array('a', 'b', 'c'), null, null), - 44496 => array(array('_route' => '_568'), array('a', 'b', 'c'), null, null), - 44542 => array(array('_route' => '_868'), array('a', 'b', 'c'), null, null), - 44591 => array(array('_route' => '_878'), array('a', 'b', 'c'), null, null), - 44644 => array(array('_route' => '_588'), array('a', 'b', 'c'), null, null), - 44692 => array(array('_route' => '_793'), array('a', 'b', 'c'), null, null), - 44740 => array(array('_route' => '_917'), array('a', 'b', 'c'), null, null), - 44793 => array(array('_route' => '_600'), array('a', 'b', 'c'), null, null), - 44841 => array(array('_route' => '_728'), array('a', 'b', 'c'), null, null), - 44894 => array(array('_route' => '_603'), array('a', 'b', 'c'), null, null), - 44942 => array(array('_route' => '_765'), array('a', 'b', 'c'), null, null), - 44995 => array(array('_route' => '_607'), array('a', 'b', 'c'), null, null), - 45043 => array(array('_route' => '_676'), array('a', 'b', 'c'), null, null), - 45091 => array(array('_route' => '_804'), array('a', 'b', 'c'), null, null), - 45144 => array(array('_route' => '_609'), array('a', 'b', 'c'), null, null), - 45192 => array(array('_route' => '_961'), array('a', 'b', 'c'), null, null), - 45240 => array(array('_route' => '_980'), array('a', 'b', 'c'), null, null), - 45290 => array(array('_route' => '_714'), array('a', 'b', 'c'), null, null), - 45342 => array(array('_route' => '_730'), array('a', 'b', 'c'), null, null), - 45390 => array(array('_route' => '_806'), array('a', 'b', 'c'), null, null), - 45438 => array(array('_route' => '_825'), array('a', 'b', 'c'), null, null), - 45486 => array(array('_route' => '_879'), array('a', 'b', 'c'), null, null), - 45534 => array(array('_route' => '_893'), array('a', 'b', 'c'), null, null), - 45584 => array(array('_route' => '_928'), array('a', 'b', 'c'), null, null), - 45636 => array(array('_route' => '_932'), array('a', 'b', 'c'), null, null), - 45684 => array(array('_route' => '_958'), array('a', 'b', 'c'), null, null), - 45734 => array(array('_route' => '_984'), array('a', 'b', 'c'), null, null), - 45792 => array(array('_route' => '_538'), array('a', 'b', 'c'), null, null), - 45840 => array(array('_route' => '_993'), array('a', 'b', 'c'), null, null), - 45890 => array(array('_route' => '_542'), array('a', 'b', 'c'), null, null), - 45942 => array(array('_route' => '_551'), array('a', 'b', 'c'), null, null), - 45990 => array(array('_route' => '_687'), array('a', 'b', 'c'), null, null), - 46038 => array(array('_route' => '_724'), array('a', 'b', 'c'), null, null), - 46086 => array(array('_route' => '_925'), array('a', 'b', 'c'), null, null), - 46139 => array(array('_route' => '_587'), array('a', 'b', 'c'), null, null), - 46187 => array(array('_route' => '_914'), array('a', 'b', 'c'), null, null), - 46237 => array(array('_route' => '_616'), array('a', 'b', 'c'), null, null), - 46292 => array(array('_route' => '_677'), array('a', 'b', 'c'), null, null), - 46339 => array(array('_route' => '_815'), array('a', 'b', 'c'), null, null), - 46388 => array(array('_route' => '_781'), array('a', 'b', 'c'), null, null), - 46438 => array(array('_route' => '_717'), array('a', 'b', 'c'), null, null), - 46490 => array(array('_route' => '_782'), array('a', 'b', 'c'), null, null), - 46538 => array(array('_route' => '_832'), array('a', 'b', 'c'), null, null), - 46591 => array(array('_route' => '_795'), array('a', 'b', 'c'), null, null), - 46639 => array(array('_route' => '_887'), array('a', 'b', 'c'), null, null), - 46689 => array(array('_route' => '_800'), array('a', 'b', 'c'), null, null), - 46738 => array(array('_route' => '_826'), array('a', 'b', 'c'), null, null), - 46787 => array(array('_route' => '_881'), array('a', 'b', 'c'), null, null), - 46836 => array(array('_route' => '_886'), array('a', 'b', 'c'), null, null), - 46885 => array(array('_route' => '_938'), array('a', 'b', 'c'), null, null), - 46943 => array(array('_route' => '_540'), array('a', 'b', 'c'), null, null), - 46991 => array(array('_route' => '_643'), array('a', 'b', 'c'), null, null), - 47041 => array(array('_route' => '_544'), array('a', 'b', 'c'), null, null), - 47090 => array(array('_route' => '_552'), array('a', 'b', 'c'), null, null), - 47142 => array(array('_route' => '_567'), array('a', 'b', 'c'), null, null), - 47190 => array(array('_route' => '_608'), array('a', 'b', 'c'), null, null), - 47238 => array(array('_route' => '_698'), array('a', 'b', 'c'), null, null), - 47286 => array(array('_route' => '_988'), array('a', 'b', 'c'), null, null), - 47339 => array(array('_route' => '_583'), array('a', 'b', 'c'), null, null), - 47387 => array(array('_route' => '_998'), array('a', 'b', 'c'), null, null), - 47440 => array(array('_route' => '_604'), array('a', 'b', 'c'), null, null), - 47488 => array(array('_route' => '_630'), array('a', 'b', 'c'), null, null), - 47536 => array(array('_route' => '_706'), array('a', 'b', 'c'), null, null), - 47584 => array(array('_route' => '_976'), array('a', 'b', 'c'), null, null), - 47637 => array(array('_route' => '_673'), array('a', 'b', 'c'), null, null), - 47685 => array(array('_route' => '_678'), array('a', 'b', 'c'), null, null), - 47733 => array(array('_route' => '_931'), array('a', 'b', 'c'), null, null), - 47783 => array(array('_route' => '_751'), array('a', 'b', 'c'), null, null), - 47832 => array(array('_route' => '_766'), array('a', 'b', 'c'), null, null), - 47884 => array(array('_route' => '_792'), array('a', 'b', 'c'), null, null), - 47932 => array(array('_route' => '_814'), array('a', 'b', 'c'), null, null), - 47982 => array(array('_route' => '_798'), array('a', 'b', 'c'), null, null), - 48034 => array(array('_route' => '_851'), array('a', 'b', 'c'), null, null), - 48082 => array(array('_route' => '_941'), array('a', 'b', 'c'), null, null), - 48130 => array(array('_route' => '_953'), array('a', 'b', 'c'), null, null), - 48178 => array(array('_route' => '_975'), array('a', 'b', 'c'), null, null), - 48228 => array(array('_route' => '_873'), array('a', 'b', 'c'), null, null), - 48277 => array(array('_route' => '_936'), array('a', 'b', 'c'), null, null), - 48326 => array(array('_route' => '_994'), array('a', 'b', 'c'), null, null), - 48384 => array(array('_route' => '_562'), array('a', 'b', 'c'), null, null), - 48432 => array(array('_route' => '_770'), array('a', 'b', 'c'), null, null), - 48483 => array(array('_route' => '_774'), array('a', 'b', 'c'), null, null), - 48530 => array(array('_route' => '_966'), array('a', 'b', 'c'), null, null), - 48581 => array(array('_route' => '_582'), array('a', 'b', 'c'), null, null), - 48633 => array(array('_route' => '_606'), array('a', 'b', 'c'), null, null), - 48681 => array(array('_route' => '_648'), array('a', 'b', 'c'), null, null), - 48731 => array(array('_route' => '_624'), array('a', 'b', 'c'), null, null), - 48783 => array(array('_route' => '_626'), array('a', 'b', 'c'), null, null), - 48831 => array(array('_route' => '_821'), array('a', 'b', 'c'), null, null), - 48881 => array(array('_route' => '_628'), array('a', 'b', 'c'), null, null), - 48930 => array(array('_route' => '_638'), array('a', 'b', 'c'), null, null), - 48982 => array(array('_route' => '_640'), array('a', 'b', 'c'), null, null), - 49030 => array(array('_route' => '_990'), array('a', 'b', 'c'), null, null), - 49080 => array(array('_route' => '_705'), array('a', 'b', 'c'), null, null), - 49129 => array(array('_route' => '_757'), array('a', 'b', 'c'), null, null), - 49184 => array(array('_route' => '_785'), array('a', 'b', 'c'), null, null), - 49231 => array(array('_route' => '_875'), array('a', 'b', 'c'), null, null), - 49278 => array(array('_route' => '_894'), array('a', 'b', 'c'), null, null), - 49327 => array(array('_route' => '_945'), array('a', 'b', 'c'), null, null), - 49383 => array(array('_route' => '_816'), array('a', 'b', 'c'), null, null), - 49430 => array(array('_route' => '_872'), array('a', 'b', 'c'), null, null), - 49479 => array(array('_route' => '_921'), array('a', 'b', 'c'), null, null), - 49527 => array(array('_route' => '_960'), array('a', 'b', 'c'), null, null), - 49575 => array(array('_route' => '_974'), array('a', 'b', 'c'), null, null), - 49628 => array(array('_route' => '_835'), array('a', 'b', 'c'), null, null), - 49676 => array(array('_route' => '_934'), array('a', 'b', 'c'), null, null), - 49726 => array(array('_route' => '_869'), array('a', 'b', 'c'), null, null), + 24837 => array(array('_route' => '_501'), array('a', 'b', 'c'), null, null), + 24889 => array(array('_route' => '_514'), array('a', 'b', 'c'), null, null), + 24937 => array(array('_route' => '_731'), array('a', 'b', 'c'), null, null), + 24990 => array(array('_route' => '_522'), array('a', 'b', 'c'), null, null), + 25038 => array(array('_route' => '_693'), array('a', 'b', 'c'), null, null), + 25091 => array(array('_route' => '_537'), array('a', 'b', 'c'), null, null), + 25139 => array(array('_route' => '_554'), array('a', 'b', 'c'), null, null), + 25187 => array(array('_route' => '_645'), array('a', 'b', 'c'), null, null), + 25235 => array(array('_route' => '_862'), array('a', 'b', 'c'), null, null), + 25288 => array(array('_route' => '_539'), array('a', 'b', 'c'), null, null), + 25336 => array(array('_route' => '_729'), array('a', 'b', 'c'), null, null), + 25384 => array(array('_route' => '_897'), array('a', 'b', 'c'), null, null), + 25437 => array(array('_route' => '_561'), array('a', 'b', 'c'), null, null), + 25485 => array(array('_route' => '_615'), array('a', 'b', 'c'), null, null), + 25533 => array(array('_route' => '_764'), array('a', 'b', 'c'), null, null), + 25581 => array(array('_route' => '_948'), array('a', 'b', 'c'), null, null), + 25634 => array(array('_route' => '_617'), array('a', 'b', 'c'), null, null), + 25682 => array(array('_route' => '_671'), array('a', 'b', 'c'), null, null), + 25735 => array(array('_route' => '_649'), array('a', 'b', 'c'), null, null), + 25783 => array(array('_route' => '_651'), array('a', 'b', 'c'), null, null), + 25831 => array(array('_route' => '_684'), array('a', 'b', 'c'), null, null), + 25884 => array(array('_route' => '_669'), array('a', 'b', 'c'), null, null), + 25932 => array(array('_route' => '_743'), array('a', 'b', 'c'), null, null), + 25980 => array(array('_route' => '_962'), array('a', 'b', 'c'), null, null), + 26033 => array(array('_route' => '_694'), array('a', 'b', 'c'), null, null), + 26081 => array(array('_route' => '_985'), array('a', 'b', 'c'), null, null), + 26134 => array(array('_route' => '_707'), array('a', 'b', 'c'), null, null), + 26182 => array(array('_route' => '_718'), array('a', 'b', 'c'), null, null), + 26235 => array(array('_route' => '_720'), array('a', 'b', 'c'), null, null), + 26283 => array(array('_route' => '_745'), array('a', 'b', 'c'), null, null), + 26333 => array(array('_route' => '_874'), array('a', 'b', 'c'), null, null), + 26391 => array(array('_route' => '_502'), array('a', 'b', 'c'), null, null), + 26439 => array(array('_route' => '_667'), array('a', 'b', 'c'), null, null), + 26487 => array(array('_route' => '_911'), array('a', 'b', 'c'), null, null), + 26535 => array(array('_route' => '_942'), array('a', 'b', 'c'), null, null), + 26585 => array(array('_route' => '_504'), array('a', 'b', 'c'), null, null), + 26637 => array(array('_route' => '_524'), array('a', 'b', 'c'), null, null), + 26685 => array(array('_route' => '_732'), array('a', 'b', 'c'), null, null), + 26738 => array(array('_route' => '_596'), array('a', 'b', 'c'), null, null), + 26786 => array(array('_route' => '_601'), array('a', 'b', 'c'), null, null), + 26839 => array(array('_route' => '_620'), array('a', 'b', 'c'), null, null), + 26887 => array(array('_route' => '_631'), array('a', 'b', 'c'), null, null), + 26935 => array(array('_route' => '_771'), array('a', 'b', 'c'), null, null), + 26983 => array(array('_route' => '_937'), array('a', 'b', 'c'), null, null), + 27031 => array(array('_route' => '_999'), array('a', 'b', 'c'), null, null), + 27084 => array(array('_route' => '_657'), array('a', 'b', 'c'), null, null), + 27132 => array(array('_route' => '_701'), array('a', 'b', 'c'), null, null), + 27185 => array(array('_route' => '_662'), array('a', 'b', 'c'), null, null), + 27233 => array(array('_route' => '_797'), array('a', 'b', 'c'), null, null), + 27281 => array(array('_route' => '_924'), array('a', 'b', 'c'), null, null), + 27334 => array(array('_route' => '_702'), array('a', 'b', 'c'), null, null), + 27382 => array(array('_route' => '_750'), array('a', 'b', 'c'), null, null), + 27435 => array(array('_route' => '_749'), array('a', 'b', 'c'), null, null), + 27483 => array(array('_route' => '_837'), array('a', 'b', 'c'), null, null), + 27533 => array(array('_route' => '_758'), array('a', 'b', 'c'), null, null), + 27585 => array(array('_route' => '_810'), array('a', 'b', 'c'), null, null), + 27633 => array(array('_route' => '_902'), array('a', 'b', 'c'), null, null), + 27683 => array(array('_route' => '_845'), array('a', 'b', 'c'), null, null), + 27741 => array(array('_route' => '_503'), array('a', 'b', 'c'), null, null), + 27792 => array(array('_route' => '_756'), array('a', 'b', 'c'), null, null), + 27839 => array(array('_route' => '_799'), array('a', 'b', 'c'), null, null), + 27888 => array(array('_route' => '_769'), array('a', 'b', 'c'), null, null), + 27936 => array(array('_route' => '_981'), array('a', 'b', 'c'), null, null), + 27989 => array(array('_route' => '_507'), array('a', 'b', 'c'), null, null), + 28037 => array(array('_route' => '_672'), array('a', 'b', 'c'), null, null), + 28085 => array(array('_route' => '_790'), array('a', 'b', 'c'), null, null), + 28138 => array(array('_route' => '_515'), array('a', 'b', 'c'), null, null), + 28186 => array(array('_route' => '_523'), array('a', 'b', 'c'), null, null), + 28234 => array(array('_route' => '_957'), array('a', 'b', 'c'), null, null), + 28282 => array(array('_route' => '_995'), array('a', 'b', 'c'), null, null), + 28335 => array(array('_route' => '_532'), array('a', 'b', 'c'), null, null), + 28383 => array(array('_route' => '_642'), array('a', 'b', 'c'), null, null), + 28433 => array(array('_route' => '_579'), array('a', 'b', 'c'), null, null), + 28485 => array(array('_route' => '_625'), array('a', 'b', 'c'), null, null), + 28533 => array(array('_route' => '_916'), array('a', 'b', 'c'), null, null), + 28586 => array(array('_route' => '_633'), array('a', 'b', 'c'), null, null), + 28634 => array(array('_route' => '_656'), array('a', 'b', 'c'), null, null), + 28687 => array(array('_route' => '_658'), array('a', 'b', 'c'), null, null), + 28735 => array(array('_route' => '_943'), array('a', 'b', 'c'), null, null), + 28788 => array(array('_route' => '_664'), array('a', 'b', 'c'), null, null), + 28836 => array(array('_route' => '_852'), array('a', 'b', 'c'), null, null), + 28884 => array(array('_route' => '_870'), array('a', 'b', 'c'), null, null), + 28937 => array(array('_route' => '_683'), array('a', 'b', 'c'), null, null), + 28985 => array(array('_route' => '_915'), array('a', 'b', 'c'), null, null), + 29038 => array(array('_route' => '_719'), array('a', 'b', 'c'), null, null), + 29086 => array(array('_route' => '_859'), array('a', 'b', 'c'), null, null), + 29134 => array(array('_route' => '_912'), array('a', 'b', 'c'), null, null), + 29182 => array(array('_route' => '_978'), array('a', 'b', 'c'), null, null), + 29235 => array(array('_route' => '_738'), array('a', 'b', 'c'), null, null), + 29283 => array(array('_route' => '_883'), array('a', 'b', 'c'), null, null), + 29333 => array(array('_route' => '_741'), array('a', 'b', 'c'), null, null), + 29382 => array(array('_route' => '_760'), array('a', 'b', 'c'), null, null), + 29431 => array(array('_route' => '_895'), array('a', 'b', 'c'), null, null), + 29489 => array(array('_route' => '_505'), array('a', 'b', 'c'), null, null), + 29537 => array(array('_route' => '_935'), array('a', 'b', 'c'), null, null), + 29590 => array(array('_route' => '_509'), array('a', 'b', 'c'), null, null), + 29638 => array(array('_route' => '_820'), array('a', 'b', 'c'), null, null), + 29686 => array(array('_route' => '_910'), array('a', 'b', 'c'), null, null), + 29739 => array(array('_route' => '_518'), array('a', 'b', 'c'), null, null), + 29787 => array(array('_route' => '_618'), array('a', 'b', 'c'), null, null), + 29840 => array(array('_route' => '_546'), array('a', 'b', 'c'), null, null), + 29888 => array(array('_route' => '_740'), array('a', 'b', 'c'), null, null), + 29936 => array(array('_route' => '_867'), array('a', 'b', 'c'), null, null), + 29989 => array(array('_route' => '_572'), array('a', 'b', 'c'), null, null), + 30037 => array(array('_route' => '_952'), array('a', 'b', 'c'), null, null), + 30090 => array(array('_route' => '_573'), array('a', 'b', 'c'), null, null), + 30138 => array(array('_route' => '_692'), array('a', 'b', 'c'), null, null), + 30186 => array(array('_route' => '_700'), array('a', 'b', 'c'), null, null), + 30234 => array(array('_route' => '_772'), array('a', 'b', 'c'), null, null), + 30284 => array(array('_route' => '_653'), array('a', 'b', 'c'), null, null), + 30336 => array(array('_route' => '_695'), array('a', 'b', 'c'), null, null), + 30384 => array(array('_route' => '_748'), array('a', 'b', 'c'), null, null), + 30437 => array(array('_route' => '_710'), array('a', 'b', 'c'), null, null), + 30485 => array(array('_route' => '_716'), array('a', 'b', 'c'), null, null), + 30533 => array(array('_route' => '_969'), array('a', 'b', 'c'), null, null), + 30586 => array(array('_route' => '_734'), array('a', 'b', 'c'), null, null), + 30634 => array(array('_route' => '_742'), array('a', 'b', 'c'), null, null), + 30682 => array(array('_route' => '_844'), array('a', 'b', 'c'), null, null), + 30735 => array(array('_route' => '_763'), array('a', 'b', 'c'), null, null), + 30783 => array(array('_route' => '_965'), array('a', 'b', 'c'), null, null), + 30836 => array(array('_route' => '_778'), array('a', 'b', 'c'), null, null), + 30884 => array(array('_route' => '_813'), array('a', 'b', 'c'), null, null), + 30932 => array(array('_route' => '_831'), array('a', 'b', 'c'), null, null), + 30982 => array(array('_route' => '_955'), array('a', 'b', 'c'), null, null), + 31031 => array(array('_route' => '_997'), array('a', 'b', 'c'), null, null), + 31089 => array(array('_route' => '_506'), array('a', 'b', 'c'), null, null), + 31137 => array(array('_route' => '_575'), array('a', 'b', 'c'), null, null), + 31190 => array(array('_route' => '_516'), array('a', 'b', 'c'), null, null), + 31238 => array(array('_route' => '_553'), array('a', 'b', 'c'), null, null), + 31291 => array(array('_route' => '_528'), array('a', 'b', 'c'), null, null), + 31339 => array(array('_route' => '_847'), array('a', 'b', 'c'), null, null), + 31387 => array(array('_route' => '_904'), array('a', 'b', 'c'), null, null), + 31440 => array(array('_route' => '_574'), array('a', 'b', 'c'), null, null), + 31488 => array(array('_route' => '_818'), array('a', 'b', 'c'), null, null), + 31538 => array(array('_route' => '_577'), array('a', 'b', 'c'), null, null), + 31590 => array(array('_route' => '_584'), array('a', 'b', 'c'), null, null), + 31638 => array(array('_route' => '_905'), array('a', 'b', 'c'), null, null), + 31691 => array(array('_route' => '_612'), array('a', 'b', 'c'), null, null), + 31739 => array(array('_route' => '_688'), array('a', 'b', 'c'), null, null), + 31787 => array(array('_route' => '_854'), array('a', 'b', 'c'), null, null), + 31840 => array(array('_route' => '_613'), array('a', 'b', 'c'), null, null), + 31888 => array(array('_route' => '_767'), array('a', 'b', 'c'), null, null), + 31941 => array(array('_route' => '_666'), array('a', 'b', 'c'), null, null), + 31989 => array(array('_route' => '_759'), array('a', 'b', 'c'), null, null), + 32037 => array(array('_route' => '_827'), array('a', 'b', 'c'), null, null), + 32085 => array(array('_route' => '_840'), array('a', 'b', 'c'), null, null), + 32138 => array(array('_route' => '_680'), array('a', 'b', 'c'), null, null), + 32186 => array(array('_route' => '_784'), array('a', 'b', 'c'), null, null), + 32234 => array(array('_route' => '_842'), array('a', 'b', 'c'), null, null), + 32282 => array(array('_route' => '_860'), array('a', 'b', 'c'), null, null), + 32332 => array(array('_route' => '_704'), array('a', 'b', 'c'), null, null), + 32381 => array(array('_route' => '_727'), array('a', 'b', 'c'), null, null), + 32430 => array(array('_route' => '_777'), array('a', 'b', 'c'), null, null), + 32482 => array(array('_route' => '_838'), array('a', 'b', 'c'), null, null), + 32530 => array(array('_route' => '_861'), array('a', 'b', 'c'), null, null), + 32583 => array(array('_route' => '_849'), array('a', 'b', 'c'), null, null), + 32631 => array(array('_route' => '_982'), array('a', 'b', 'c'), null, null), + 32679 => array(array('_route' => '_986'), array('a', 'b', 'c'), null, null), + 32741 => array(array('_route' => '_508'), array('a', 'b', 'c'), null, null), + 32788 => array(array('_route' => '_517'), array('a', 'b', 'c'), null, null), + 32837 => array(array('_route' => '_622'), array('a', 'b', 'c'), null, null), + 32890 => array(array('_route' => '_513'), array('a', 'b', 'c'), null, null), + 32938 => array(array('_route' => '_655'), array('a', 'b', 'c'), null, null), + 32986 => array(array('_route' => '_843'), array('a', 'b', 'c'), null, null), + 33034 => array(array('_route' => '_939'), array('a', 'b', 'c'), null, null), + 33084 => array(array('_route' => '_529'), array('a', 'b', 'c'), null, null), + 33136 => array(array('_route' => '_535'), array('a', 'b', 'c'), null, null), + 33184 => array(array('_route' => '_685'), array('a', 'b', 'c'), null, null), + 33240 => array(array('_route' => '_559'), array('a', 'b', 'c'), null, null), + 33287 => array(array('_route' => '_661'), array('a', 'b', 'c'), null, null), + 33336 => array(array('_route' => '_768'), array('a', 'b', 'c'), null, null), + 33389 => array(array('_route' => '_589'), array('a', 'b', 'c'), null, null), + 33437 => array(array('_route' => '_647'), array('a', 'b', 'c'), null, null), + 33485 => array(array('_route' => '_652'), array('a', 'b', 'c'), null, null), + 33533 => array(array('_route' => '_834'), array('a', 'b', 'c'), null, null), + 33586 => array(array('_route' => '_591'), array('a', 'b', 'c'), null, null), + 33634 => array(array('_route' => '_599'), array('a', 'b', 'c'), null, null), + 33687 => array(array('_route' => '_787'), array('a', 'b', 'c'), null, null), + 33734 => array(array('_route' => '_848'), array('a', 'b', 'c'), null, null), + 33787 => array(array('_route' => '_796'), array('a', 'b', 'c'), null, null), + 33835 => array(array('_route' => '_877'), array('a', 'b', 'c'), null, null), + 33885 => array(array('_route' => '_809'), array('a', 'b', 'c'), null, null), + 33934 => array(array('_route' => '_817'), array('a', 'b', 'c'), null, null), + 33986 => array(array('_route' => '_819'), array('a', 'b', 'c'), null, null), + 34034 => array(array('_route' => '_865'), array('a', 'b', 'c'), null, null), + 34084 => array(array('_route' => '_919'), array('a', 'b', 'c'), null, null), + 34133 => array(array('_route' => '_949'), array('a', 'b', 'c'), null, null), + 34191 => array(array('_route' => '_510'), array('a', 'b', 'c'), null, null), + 34239 => array(array('_route' => '_590'), array('a', 'b', 'c'), null, null), + 34287 => array(array('_route' => '_597'), array('a', 'b', 'c'), null, null), + 34335 => array(array('_route' => '_682'), array('a', 'b', 'c'), null, null), + 34383 => array(array('_route' => '_723'), array('a', 'b', 'c'), null, null), + 34436 => array(array('_route' => '_521'), array('a', 'b', 'c'), null, null), + 34484 => array(array('_route' => '_594'), array('a', 'b', 'c'), null, null), + 34532 => array(array('_route' => '_689'), array('a', 'b', 'c'), null, null), + 34580 => array(array('_route' => '_713'), array('a', 'b', 'c'), null, null), + 34628 => array(array('_route' => '_889'), array('a', 'b', 'c'), null, null), + 34681 => array(array('_route' => '_531'), array('a', 'b', 'c'), null, null), + 34729 => array(array('_route' => '_639'), array('a', 'b', 'c'), null, null), + 34780 => array(array('_route' => '_646'), array('a', 'b', 'c'), null, null), + 34827 => array(array('_route' => '_659'), array('a', 'b', 'c'), null, null), + 34876 => array(array('_route' => '_959'), array('a', 'b', 'c'), null, null), + 34929 => array(array('_route' => '_550'), array('a', 'b', 'c'), null, null), + 34977 => array(array('_route' => '_833'), array('a', 'b', 'c'), null, null), + 35025 => array(array('_route' => '_899'), array('a', 'b', 'c'), null, null), + 35081 => array(array('_route' => '_580'), array('a', 'b', 'c'), null, null), + 35128 => array(array('_route' => '_762'), array('a', 'b', 'c'), null, null), + 35177 => array(array('_route' => '_896'), array('a', 'b', 'c'), null, null), + 35230 => array(array('_route' => '_595'), array('a', 'b', 'c'), null, null), + 35278 => array(array('_route' => '_933'), array('a', 'b', 'c'), null, null), + 35328 => array(array('_route' => '_610'), array('a', 'b', 'c'), null, null), + 35380 => array(array('_route' => '_629'), array('a', 'b', 'c'), null, null), + 35428 => array(array('_route' => '_744'), array('a', 'b', 'c'), null, null), + 35481 => array(array('_route' => '_674'), array('a', 'b', 'c'), null, null), + 35529 => array(array('_route' => '_726'), array('a', 'b', 'c'), null, null), + 35577 => array(array('_route' => '_929'), array('a', 'b', 'c'), null, null), + 35627 => array(array('_route' => '_696'), array('a', 'b', 'c'), null, null), + 35679 => array(array('_route' => '_841'), array('a', 'b', 'c'), null, null), + 35727 => array(array('_route' => '_890'), array('a', 'b', 'c'), null, null), + 35777 => array(array('_route' => '_885'), array('a', 'b', 'c'), null, null), + 35826 => array(array('_route' => '_888'), array('a', 'b', 'c'), null, null), + 35875 => array(array('_route' => '_996'), array('a', 'b', 'c'), null, null), + 35933 => array(array('_route' => '_511'), array('a', 'b', 'c'), null, null), + 35981 => array(array('_route' => '_576'), array('a', 'b', 'c'), null, null), + 36029 => array(array('_route' => '_623'), array('a', 'b', 'c'), null, null), + 36082 => array(array('_route' => '_560'), array('a', 'b', 'c'), null, null), + 36129 => array(array('_route' => '_585'), array('a', 'b', 'c'), null, null), + 36182 => array(array('_route' => '_570'), array('a', 'b', 'c'), null, null), + 36230 => array(array('_route' => '_578'), array('a', 'b', 'c'), null, null), + 36281 => array(array('_route' => '_780'), array('a', 'b', 'c'), null, null), + 36328 => array(array('_route' => '_808'), array('a', 'b', 'c'), null, null), + 36382 => array(array('_route' => '_593'), array('a', 'b', 'c'), null, null), + 36430 => array(array('_route' => '_900'), array('a', 'b', 'c'), null, null), + 36483 => array(array('_route' => '_632'), array('a', 'b', 'c'), null, null), + 36531 => array(array('_route' => '_654'), array('a', 'b', 'c'), null, null), + 36579 => array(array('_route' => '_721'), array('a', 'b', 'c'), null, null), + 36627 => array(array('_route' => '_836'), array('a', 'b', 'c'), null, null), + 36680 => array(array('_route' => '_637'), array('a', 'b', 'c'), null, null), + 36728 => array(array('_route' => '_737'), array('a', 'b', 'c'), null, null), + 36784 => array(array('_route' => '_699'), array('a', 'b', 'c'), null, null), + 36831 => array(array('_route' => '_822'), array('a', 'b', 'c'), null, null), + 36880 => array(array('_route' => '_853'), array('a', 'b', 'c'), null, null), + 36933 => array(array('_route' => '_708'), array('a', 'b', 'c'), null, null), + 36981 => array(array('_route' => '_871'), array('a', 'b', 'c'), null, null), + 37034 => array(array('_route' => '_752'), array('a', 'b', 'c'), null, null), + 37082 => array(array('_route' => '_989'), array('a', 'b', 'c'), null, null), + 37132 => array(array('_route' => '_855'), array('a', 'b', 'c'), null, null), + 37184 => array(array('_route' => '_858'), array('a', 'b', 'c'), null, null), + 37232 => array(array('_route' => '_898'), array('a', 'b', 'c'), null, null), + 37282 => array(array('_route' => '_903'), array('a', 'b', 'c'), null, null), + 37331 => array(array('_route' => '_909'), array('a', 'b', 'c'), null, null), + 37380 => array(array('_route' => '_950'), array('a', 'b', 'c'), null, null), + 37441 => array(array('_route' => '_512'), array('a', 'b', 'c'), null, null), + 37488 => array(array('_route' => '_691'), array('a', 'b', 'c'), null, null), + 37537 => array(array('_route' => '_686'), array('a', 'b', 'c'), null, null), + 37587 => array(array('_route' => '_527'), array('a', 'b', 'c'), null, null), + 37639 => array(array('_route' => '_541'), array('a', 'b', 'c'), null, null), + 37687 => array(array('_route' => '_956'), array('a', 'b', 'c'), null, null), + 37740 => array(array('_route' => '_555'), array('a', 'b', 'c'), null, null), + 37788 => array(array('_route' => '_681'), array('a', 'b', 'c'), null, null), + 37841 => array(array('_route' => '_556'), array('a', 'b', 'c'), null, null), + 37889 => array(array('_route' => '_802'), array('a', 'b', 'c'), null, null), + 37939 => array(array('_route' => '_558'), array('a', 'b', 'c'), null, null), + 37991 => array(array('_route' => '_564'), array('a', 'b', 'c'), null, null), + 38039 => array(array('_route' => '_670'), array('a', 'b', 'c'), null, null), + 38087 => array(array('_route' => '_884'), array('a', 'b', 'c'), null, null), + 38140 => array(array('_route' => '_627'), array('a', 'b', 'c'), null, null), + 38187 => array(array('_route' => '_746'), array('a', 'b', 'c'), null, null), + 38240 => array(array('_route' => '_668'), array('a', 'b', 'c'), null, null), + 38291 => array(array('_route' => '_712'), array('a', 'b', 'c'), null, null), + 38338 => array(array('_route' => '_863'), array('a', 'b', 'c'), null, null), + 38387 => array(array('_route' => '_801'), array('a', 'b', 'c'), null, null), + 38440 => array(array('_route' => '_709'), array('a', 'b', 'c'), null, null), + 38488 => array(array('_route' => '_850'), array('a', 'b', 'c'), null, null), + 38536 => array(array('_route' => '_918'), array('a', 'b', 'c'), null, null), + 38586 => array(array('_route' => '_803'), array('a', 'b', 'c'), null, null), + 38638 => array(array('_route' => '_864'), array('a', 'b', 'c'), null, null), + 38686 => array(array('_route' => '_880'), array('a', 'b', 'c'), null, null), + 38734 => array(array('_route' => '_927'), array('a', 'b', 'c'), null, null), + 38787 => array(array('_route' => '_930'), array('a', 'b', 'c'), null, null), + 38835 => array(array('_route' => '_951'), array('a', 'b', 'c'), null, null), + 38883 => array(array('_route' => '_963'), array('a', 'b', 'c'), null, null), + 38942 => array(array('_route' => '_519'), array('a', 'b', 'c'), null, null), + 38990 => array(array('_route' => '_823'), array('a', 'b', 'c'), null, null), + 39038 => array(array('_route' => '_954'), array('a', 'b', 'c'), null, null), + 39091 => array(array('_route' => '_525'), array('a', 'b', 'c'), null, null), + 39139 => array(array('_route' => '_991'), array('a', 'b', 'c'), null, null), + 39189 => array(array('_route' => '_536'), array('a', 'b', 'c'), null, null), + 39241 => array(array('_route' => '_545'), array('a', 'b', 'c'), null, null), + 39289 => array(array('_route' => '_944'), array('a', 'b', 'c'), null, null), + 39342 => array(array('_route' => '_557'), array('a', 'b', 'c'), null, null), + 39390 => array(array('_route' => '_783'), array('a', 'b', 'c'), null, null), + 39438 => array(array('_route' => '_807'), array('a', 'b', 'c'), null, null), + 39491 => array(array('_route' => '_586'), array('a', 'b', 'c'), null, null), + 39539 => array(array('_route' => '_711'), array('a', 'b', 'c'), null, null), + 39592 => array(array('_route' => '_598'), array('a', 'b', 'c'), null, null), + 39640 => array(array('_route' => '_635'), array('a', 'b', 'c'), null, null), + 39688 => array(array('_route' => '_983'), array('a', 'b', 'c'), null, null), + 39741 => array(array('_route' => '_634'), array('a', 'b', 'c'), null, null), + 39789 => array(array('_route' => '_641'), array('a', 'b', 'c'), null, null), + 39840 => array(array('_route' => '_779'), array('a', 'b', 'c'), null, null), + 39887 => array(array('_route' => '_876'), array('a', 'b', 'c'), null, null), + 39936 => array(array('_route' => '_811'), array('a', 'b', 'c'), null, null), + 39984 => array(array('_route' => '_824'), array('a', 'b', 'c'), null, null), + 40037 => array(array('_route' => '_660'), array('a', 'b', 'c'), null, null), + 40085 => array(array('_route' => '_789'), array('a', 'b', 'c'), null, null), + 40138 => array(array('_route' => '_733'), array('a', 'b', 'c'), null, null), + 40186 => array(array('_route' => '_735'), array('a', 'b', 'c'), null, null), + 40234 => array(array('_route' => '_882'), array('a', 'b', 'c'), null, null), + 40282 => array(array('_route' => '_967'), array('a', 'b', 'c'), null, null), + 40332 => array(array('_route' => '_736'), array('a', 'b', 'c'), null, null), + 40381 => array(array('_route' => '_753'), array('a', 'b', 'c'), null, null), + 40430 => array(array('_route' => '_786'), array('a', 'b', 'c'), null, null), + 40479 => array(array('_route' => '_907'), array('a', 'b', 'c'), null, null), + 40528 => array(array('_route' => '_920'), array('a', 'b', 'c'), null, null), + 40577 => array(array('_route' => '_971'), array('a', 'b', 'c'), null, null), + 40635 => array(array('_route' => '_520'), array('a', 'b', 'c'), null, null), + 40683 => array(array('_route' => '_891'), array('a', 'b', 'c'), null, null), + 40739 => array(array('_route' => '_534'), array('a', 'b', 'c'), null, null), + 40785 => array(array('_route' => '_602'), array('a', 'b', 'c'), null, null), + 40834 => array(array('_route' => '_605'), array('a', 'b', 'c'), null, null), + 40882 => array(array('_route' => '_979'), array('a', 'b', 'c'), null, null), + 40932 => array(array('_route' => '_547'), array('a', 'b', 'c'), null, null), + 40987 => array(array('_route' => '_549'), array('a', 'b', 'c'), null, null), + 41034 => array(array('_route' => '_755'), array('a', 'b', 'c'), null, null), + 41083 => array(array('_route' => '_922'), array('a', 'b', 'c'), null, null), + 41131 => array(array('_route' => '_977'), array('a', 'b', 'c'), null, null), + 41184 => array(array('_route' => '_565'), array('a', 'b', 'c'), null, null), + 41232 => array(array('_route' => '_926'), array('a', 'b', 'c'), null, null), + 41282 => array(array('_route' => '_571'), array('a', 'b', 'c'), null, null), + 41331 => array(array('_route' => '_581'), array('a', 'b', 'c'), null, null), + 41380 => array(array('_route' => '_619'), array('a', 'b', 'c'), null, null), + 41429 => array(array('_route' => '_636'), array('a', 'b', 'c'), null, null), + 41481 => array(array('_route' => '_679'), array('a', 'b', 'c'), null, null), + 41529 => array(array('_route' => '_866'), array('a', 'b', 'c'), null, null), + 41577 => array(array('_route' => '_973'), array('a', 'b', 'c'), null, null), + 41630 => array(array('_route' => '_690'), array('a', 'b', 'c'), null, null), + 41678 => array(array('_route' => '_775'), array('a', 'b', 'c'), null, null), + 41731 => array(array('_route' => '_722'), array('a', 'b', 'c'), null, null), + 41779 => array(array('_route' => '_906'), array('a', 'b', 'c'), null, null), + 41827 => array(array('_route' => '_946'), array('a', 'b', 'c'), null, null), + 41877 => array(array('_route' => '_788'), array('a', 'b', 'c'), null, null), + 41929 => array(array('_route' => '_828'), array('a', 'b', 'c'), null, null), + 41977 => array(array('_route' => '_892'), array('a', 'b', 'c'), null, null), + 42025 => array(array('_route' => '_972'), array('a', 'b', 'c'), null, null), + 42075 => array(array('_route' => '_829'), array('a', 'b', 'c'), null, null), + 42127 => array(array('_route' => '_923'), array('a', 'b', 'c'), null, null), + 42175 => array(array('_route' => '_947'), array('a', 'b', 'c'), null, null), + 42234 => array(array('_route' => '_526'), array('a', 'b', 'c'), null, null), + 42282 => array(array('_route' => '_614'), array('a', 'b', 'c'), null, null), + 42330 => array(array('_route' => '_621'), array('a', 'b', 'c'), null, null), + 42383 => array(array('_route' => '_543'), array('a', 'b', 'c'), null, null), + 42431 => array(array('_route' => '_812'), array('a', 'b', 'c'), null, null), + 42487 => array(array('_route' => '_548'), array('a', 'b', 'c'), null, null), + 42534 => array(array('_route' => '_747'), array('a', 'b', 'c'), null, null), + 42583 => array(array('_route' => '_715'), array('a', 'b', 'c'), null, null), + 42631 => array(array('_route' => '_940'), array('a', 'b', 'c'), null, null), + 42684 => array(array('_route' => '_563'), array('a', 'b', 'c'), null, null), + 42732 => array(array('_route' => '_611'), array('a', 'b', 'c'), null, null), + 42780 => array(array('_route' => '_830'), array('a', 'b', 'c'), null, null), + 42833 => array(array('_route' => '_569'), array('a', 'b', 'c'), null, null), + 42881 => array(array('_route' => '_908'), array('a', 'b', 'c'), null, null), + 42929 => array(array('_route' => '_913'), array('a', 'b', 'c'), null, null), + 42982 => array(array('_route' => '_644'), array('a', 'b', 'c'), null, null), + 43030 => array(array('_route' => '_776'), array('a', 'b', 'c'), null, null), + 43078 => array(array('_route' => '_856'), array('a', 'b', 'c'), null, null), + 43131 => array(array('_route' => '_650'), array('a', 'b', 'c'), null, null), + 43179 => array(array('_route' => '_761'), array('a', 'b', 'c'), null, null), + 43232 => array(array('_route' => '_663'), array('a', 'b', 'c'), null, null), + 43280 => array(array('_route' => '_754'), array('a', 'b', 'c'), null, null), + 43333 => array(array('_route' => '_665'), array('a', 'b', 'c'), null, null), + 43381 => array(array('_route' => '_805'), array('a', 'b', 'c'), null, null), + 43429 => array(array('_route' => '_846'), array('a', 'b', 'c'), null, null), + 43477 => array(array('_route' => '_857'), array('a', 'b', 'c'), null, null), + 43530 => array(array('_route' => '_675'), array('a', 'b', 'c'), null, null), + 43578 => array(array('_route' => '_839'), array('a', 'b', 'c'), null, null), + 43626 => array(array('_route' => '_968'), array('a', 'b', 'c'), null, null), + 43676 => array(array('_route' => '_697'), array('a', 'b', 'c'), null, null), + 43728 => array(array('_route' => '_725'), array('a', 'b', 'c'), null, null), + 43776 => array(array('_route' => '_794'), array('a', 'b', 'c'), null, null), + 43829 => array(array('_route' => '_773'), array('a', 'b', 'c'), null, null), + 43877 => array(array('_route' => '_992'), array('a', 'b', 'c'), null, null), + 43930 => array(array('_route' => '_901'), array('a', 'b', 'c'), null, null), + 43978 => array(array('_route' => '_970'), array('a', 'b', 'c'), null, null), + 44028 => array(array('_route' => '_964'), array('a', 'b', 'c'), null, null), + 44086 => array(array('_route' => '_530'), array('a', 'b', 'c'), null, null), + 44134 => array(array('_route' => '_703'), array('a', 'b', 'c'), null, null), + 44187 => array(array('_route' => '_533'), array('a', 'b', 'c'), null, null), + 44235 => array(array('_route' => '_739'), array('a', 'b', 'c'), null, null), + 44283 => array(array('_route' => '_791'), array('a', 'b', 'c'), null, null), + 44331 => array(array('_route' => '_987'), array('a', 'b', 'c'), null, null), + 44384 => array(array('_route' => '_566'), array('a', 'b', 'c'), null, null), + 44432 => array(array('_route' => '_592'), array('a', 'b', 'c'), null, null), + 44488 => array(array('_route' => '_568'), array('a', 'b', 'c'), null, null), + 44534 => array(array('_route' => '_868'), array('a', 'b', 'c'), null, null), + 44583 => array(array('_route' => '_878'), array('a', 'b', 'c'), null, null), + 44636 => array(array('_route' => '_588'), array('a', 'b', 'c'), null, null), + 44684 => array(array('_route' => '_793'), array('a', 'b', 'c'), null, null), + 44732 => array(array('_route' => '_917'), array('a', 'b', 'c'), null, null), + 44785 => array(array('_route' => '_600'), array('a', 'b', 'c'), null, null), + 44833 => array(array('_route' => '_728'), array('a', 'b', 'c'), null, null), + 44886 => array(array('_route' => '_603'), array('a', 'b', 'c'), null, null), + 44934 => array(array('_route' => '_765'), array('a', 'b', 'c'), null, null), + 44987 => array(array('_route' => '_607'), array('a', 'b', 'c'), null, null), + 45035 => array(array('_route' => '_676'), array('a', 'b', 'c'), null, null), + 45083 => array(array('_route' => '_804'), array('a', 'b', 'c'), null, null), + 45136 => array(array('_route' => '_609'), array('a', 'b', 'c'), null, null), + 45184 => array(array('_route' => '_961'), array('a', 'b', 'c'), null, null), + 45232 => array(array('_route' => '_980'), array('a', 'b', 'c'), null, null), + 45282 => array(array('_route' => '_714'), array('a', 'b', 'c'), null, null), + 45334 => array(array('_route' => '_730'), array('a', 'b', 'c'), null, null), + 45382 => array(array('_route' => '_806'), array('a', 'b', 'c'), null, null), + 45430 => array(array('_route' => '_825'), array('a', 'b', 'c'), null, null), + 45478 => array(array('_route' => '_879'), array('a', 'b', 'c'), null, null), + 45526 => array(array('_route' => '_893'), array('a', 'b', 'c'), null, null), + 45576 => array(array('_route' => '_928'), array('a', 'b', 'c'), null, null), + 45628 => array(array('_route' => '_932'), array('a', 'b', 'c'), null, null), + 45676 => array(array('_route' => '_958'), array('a', 'b', 'c'), null, null), + 45726 => array(array('_route' => '_984'), array('a', 'b', 'c'), null, null), + 45784 => array(array('_route' => '_538'), array('a', 'b', 'c'), null, null), + 45832 => array(array('_route' => '_993'), array('a', 'b', 'c'), null, null), + 45882 => array(array('_route' => '_542'), array('a', 'b', 'c'), null, null), + 45934 => array(array('_route' => '_551'), array('a', 'b', 'c'), null, null), + 45982 => array(array('_route' => '_687'), array('a', 'b', 'c'), null, null), + 46030 => array(array('_route' => '_724'), array('a', 'b', 'c'), null, null), + 46078 => array(array('_route' => '_925'), array('a', 'b', 'c'), null, null), + 46131 => array(array('_route' => '_587'), array('a', 'b', 'c'), null, null), + 46179 => array(array('_route' => '_914'), array('a', 'b', 'c'), null, null), + 46229 => array(array('_route' => '_616'), array('a', 'b', 'c'), null, null), + 46284 => array(array('_route' => '_677'), array('a', 'b', 'c'), null, null), + 46331 => array(array('_route' => '_815'), array('a', 'b', 'c'), null, null), + 46380 => array(array('_route' => '_781'), array('a', 'b', 'c'), null, null), + 46430 => array(array('_route' => '_717'), array('a', 'b', 'c'), null, null), + 46482 => array(array('_route' => '_782'), array('a', 'b', 'c'), null, null), + 46530 => array(array('_route' => '_832'), array('a', 'b', 'c'), null, null), + 46583 => array(array('_route' => '_795'), array('a', 'b', 'c'), null, null), + 46631 => array(array('_route' => '_887'), array('a', 'b', 'c'), null, null), + 46681 => array(array('_route' => '_800'), array('a', 'b', 'c'), null, null), + 46730 => array(array('_route' => '_826'), array('a', 'b', 'c'), null, null), + 46779 => array(array('_route' => '_881'), array('a', 'b', 'c'), null, null), + 46828 => array(array('_route' => '_886'), array('a', 'b', 'c'), null, null), + 46877 => array(array('_route' => '_938'), array('a', 'b', 'c'), null, null), + 46935 => array(array('_route' => '_540'), array('a', 'b', 'c'), null, null), + 46983 => array(array('_route' => '_643'), array('a', 'b', 'c'), null, null), + 47033 => array(array('_route' => '_544'), array('a', 'b', 'c'), null, null), + 47082 => array(array('_route' => '_552'), array('a', 'b', 'c'), null, null), + 47134 => array(array('_route' => '_567'), array('a', 'b', 'c'), null, null), + 47182 => array(array('_route' => '_608'), array('a', 'b', 'c'), null, null), + 47230 => array(array('_route' => '_698'), array('a', 'b', 'c'), null, null), + 47278 => array(array('_route' => '_988'), array('a', 'b', 'c'), null, null), + 47331 => array(array('_route' => '_583'), array('a', 'b', 'c'), null, null), + 47379 => array(array('_route' => '_998'), array('a', 'b', 'c'), null, null), + 47432 => array(array('_route' => '_604'), array('a', 'b', 'c'), null, null), + 47480 => array(array('_route' => '_630'), array('a', 'b', 'c'), null, null), + 47528 => array(array('_route' => '_706'), array('a', 'b', 'c'), null, null), + 47576 => array(array('_route' => '_976'), array('a', 'b', 'c'), null, null), + 47629 => array(array('_route' => '_673'), array('a', 'b', 'c'), null, null), + 47677 => array(array('_route' => '_678'), array('a', 'b', 'c'), null, null), + 47725 => array(array('_route' => '_931'), array('a', 'b', 'c'), null, null), + 47775 => array(array('_route' => '_751'), array('a', 'b', 'c'), null, null), + 47824 => array(array('_route' => '_766'), array('a', 'b', 'c'), null, null), + 47876 => array(array('_route' => '_792'), array('a', 'b', 'c'), null, null), + 47924 => array(array('_route' => '_814'), array('a', 'b', 'c'), null, null), + 47974 => array(array('_route' => '_798'), array('a', 'b', 'c'), null, null), + 48026 => array(array('_route' => '_851'), array('a', 'b', 'c'), null, null), + 48074 => array(array('_route' => '_941'), array('a', 'b', 'c'), null, null), + 48122 => array(array('_route' => '_953'), array('a', 'b', 'c'), null, null), + 48170 => array(array('_route' => '_975'), array('a', 'b', 'c'), null, null), + 48220 => array(array('_route' => '_873'), array('a', 'b', 'c'), null, null), + 48269 => array(array('_route' => '_936'), array('a', 'b', 'c'), null, null), + 48318 => array(array('_route' => '_994'), array('a', 'b', 'c'), null, null), + 48376 => array(array('_route' => '_562'), array('a', 'b', 'c'), null, null), + 48424 => array(array('_route' => '_770'), array('a', 'b', 'c'), null, null), + 48475 => array(array('_route' => '_774'), array('a', 'b', 'c'), null, null), + 48522 => array(array('_route' => '_966'), array('a', 'b', 'c'), null, null), + 48573 => array(array('_route' => '_582'), array('a', 'b', 'c'), null, null), + 48625 => array(array('_route' => '_606'), array('a', 'b', 'c'), null, null), + 48673 => array(array('_route' => '_648'), array('a', 'b', 'c'), null, null), + 48723 => array(array('_route' => '_624'), array('a', 'b', 'c'), null, null), + 48775 => array(array('_route' => '_626'), array('a', 'b', 'c'), null, null), + 48823 => array(array('_route' => '_821'), array('a', 'b', 'c'), null, null), + 48873 => array(array('_route' => '_628'), array('a', 'b', 'c'), null, null), + 48922 => array(array('_route' => '_638'), array('a', 'b', 'c'), null, null), + 48974 => array(array('_route' => '_640'), array('a', 'b', 'c'), null, null), + 49022 => array(array('_route' => '_990'), array('a', 'b', 'c'), null, null), + 49072 => array(array('_route' => '_705'), array('a', 'b', 'c'), null, null), + 49121 => array(array('_route' => '_757'), array('a', 'b', 'c'), null, null), + 49176 => array(array('_route' => '_785'), array('a', 'b', 'c'), null, null), + 49223 => array(array('_route' => '_875'), array('a', 'b', 'c'), null, null), + 49270 => array(array('_route' => '_894'), array('a', 'b', 'c'), null, null), + 49319 => array(array('_route' => '_945'), array('a', 'b', 'c'), null, null), + 49375 => array(array('_route' => '_816'), array('a', 'b', 'c'), null, null), + 49422 => array(array('_route' => '_872'), array('a', 'b', 'c'), null, null), + 49471 => array(array('_route' => '_921'), array('a', 'b', 'c'), null, null), + 49519 => array(array('_route' => '_960'), array('a', 'b', 'c'), null, null), + 49567 => array(array('_route' => '_974'), array('a', 'b', 'c'), null, null), + 49620 => array(array('_route' => '_835'), array('a', 'b', 'c'), null, null), + 49668 => array(array('_route' => '_934'), array('a', 'b', 'c'), null, null), + 49718 => array(array('_route' => '_869'), array('a', 'b', 'c'), null, null), ); list($ret, $vars, $requiredMethods, $requiredSchemes) = $routes[$m]; @@ -2814,7 +2814,7 @@ public function match($rawPathinfo) return $ret; } - if (49726 === $m) { + if (49718 === $m) { break; } $regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m)); diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher12.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher12.php index 7571aa75d1d3f..02f6ac949eb20 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher12.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher12.php @@ -29,13 +29,21 @@ public function match($rawPathinfo) $matchedPathinfo = $pathinfo; $regexList = array( 0 => '{^(?' - .'|/abc(?' - .'|([^/]++)/1(*:24)' - .'|([^/]++)/2(*:41)' - .'|([^/]++)/10(*:59)' - .'|([^/]++)/20(*:77)' - .'|([^/]++)/100(*:96)' - .'|([^/]++)/200(*:115)' + .'|/abc([^/]++)/(?' + .'|1(?' + .'|(*:27)' + .'|0(?' + .'|(*:38)' + .'|0(*:46)' + .')' + .')' + .'|2(?' + .'|(*:59)' + .'|0(?' + .'|(*:70)' + .'|0(*:78)' + .')' + .')' .')' .')$}sD', ); @@ -45,12 +53,12 @@ public function match($rawPathinfo) switch ($m = (int) $matches['MARK']) { default: $routes = array( - 24 => array(array('_route' => 'r1'), array('foo'), null, null), - 41 => array(array('_route' => 'r2'), array('foo'), null, null), - 59 => array(array('_route' => 'r10'), array('foo'), null, null), - 77 => array(array('_route' => 'r20'), array('foo'), null, null), - 96 => array(array('_route' => 'r100'), array('foo'), null, null), - 115 => array(array('_route' => 'r200'), array('foo'), null, null), + 27 => array(array('_route' => 'r1'), array('foo'), null, null), + 38 => array(array('_route' => 'r10'), array('foo'), null, null), + 46 => array(array('_route' => 'r100'), array('foo'), null, null), + 59 => array(array('_route' => 'r2'), array('foo'), null, null), + 70 => array(array('_route' => 'r20'), array('foo'), null, null), + 78 => array(array('_route' => 'r200'), array('foo'), null, null), ); list($ret, $vars, $requiredMethods, $requiredSchemes) = $routes[$m]; @@ -76,7 +84,7 @@ public function match($rawPathinfo) return $ret; } - if (115 === $m) { + if (78 === $m) { break; } $regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m)); diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher13.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher13.php index a7ff452cc6ef1..b4457d7cdc7e7 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher13.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher13.php @@ -27,12 +27,12 @@ public function match($rawPathinfo) $canonicalMethod = 'GET'; } - $matchedPathinfo = $host.$pathinfo; + $matchedPathinfo = $host.'.'.$pathinfo; $regexList = array( 0 => '{^(?' - .'|(?i:([^\\.]++)\\.exampple\\.com)(?' + .'|(?i:([^\\.]++)\\.exampple\\.com)\\.(?' .'|/abc([^/]++)(?' - .'|(*:54)' + .'|(*:56)' .')' .')' .')$}sD', @@ -41,7 +41,7 @@ public function match($rawPathinfo) foreach ($regexList as $offset => $regex) { while (preg_match($regex, $matchedPathinfo, $matches)) { switch ($m = (int) $matches['MARK']) { - case 54: + case 56: $matches = array('foo' => $matches[1] ?? null, 'foo' => $matches[2] ?? null); // r1 @@ -53,7 +53,7 @@ public function match($rawPathinfo) break; } - if (54 === $m) { + if (56 === $m) { break; } $regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m)); diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php index c33e1846a8cfb..79b650eb5b26d 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php @@ -116,50 +116,50 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a return $ret; } - $matchedPathinfo = $host.$pathinfo; + $matchedPathinfo = $host.'.'.$pathinfo; $regexList = array( 0 => '{^(?' - .'|[^/]*+(?' - .'|/foo/(baz|symfony)(*:34)' + .'|(?:(?:[^.]*+\\.)++)(?' + .'|/foo/(baz|symfony)(*:46)' .'|/bar(?' - .'|/([^/]++)(*:57)' - .'|head/([^/]++)(*:77)' + .'|/([^/]++)(*:69)' + .'|head/([^/]++)(*:89)' .')' .'|/test/([^/]++)/(?' - .'|(*:103)' + .'|(*:115)' .')' - .'|/([\']+)(*:119)' + .'|/([\']+)(*:131)' .'|/a/(?' .'|b\'b/([^/]++)(?' - .'|(*:148)' - .'|(*:156)' + .'|(*:160)' + .'|(*:168)' .')' - .'|(.*)(*:169)' + .'|(.*)(*:181)' .'|b\'b/([^/]++)(?' - .'|(*:192)' - .'|(*:200)' + .'|(*:204)' + .'|(*:212)' .')' .')' - .'|/multi/hello(?:/([^/]++))?(*:236)' + .'|/multi/hello(?:/([^/]++))?(*:248)' .'|/([^/]++)/b/([^/]++)(?' - .'|(*:267)' - .'|(*:275)' + .'|(*:279)' + .'|(*:287)' .')' - .'|/aba/([^/]++)(*:297)' - .')|(?i:([^\\.]++)\\.example\\.com)(?' + .'|/aba/([^/]++)(*:309)' + .')|(?i:([^\\.]++)\\.example\\.com)\\.(?' .'|/route1(?' - .'|3/([^/]++)(*:357)' - .'|4/([^/]++)(*:375)' + .'|3/([^/]++)(*:371)' + .'|4/([^/]++)(*:389)' .')' - .')|(?i:c\\.example\\.com)(?' - .'|/route15/([^/]++)(*:425)' - .')|[^/]*+(?' - .'|/route16/([^/]++)(*:460)' + .')|(?i:c\\.example\\.com)\\.(?' + .'|/route15/([^/]++)(*:441)' + .')|(?:(?:[^.]*+\\.)++)(?' + .'|/route16/([^/]++)(*:488)' .'|/a/(?' - .'|a\\.\\.\\.(*:481)' + .'|a\\.\\.\\.(*:509)' .'|b/(?' - .'|([^/]++)(*:502)' - .'|c/([^/]++)(*:520)' + .'|([^/]++)(*:530)' + .'|c/([^/]++)(*:548)' .')' .')' .')' @@ -169,7 +169,7 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a foreach ($regexList as $offset => $regex) { while (preg_match($regex, $matchedPathinfo, $matches)) { switch ($m = (int) $matches['MARK']) { - case 103: + case 115: $matches = array('foo' => $matches[1] ?? null); // baz4 @@ -196,7 +196,7 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a not_bazbaz6: break; - case 148: + case 160: $matches = array('foo' => $matches[1] ?? null); // foo1 @@ -210,14 +210,14 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a not_foo1: break; - case 192: + case 204: $matches = array('foo1' => $matches[1] ?? null); // foo2 return $this->mergeDefaults(array('_route' => 'foo2') + $matches, array()); break; - case 267: + case 279: $matches = array('_locale' => $matches[1] ?? null, 'foo' => $matches[2] ?? null); // foo3 @@ -226,23 +226,23 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a break; default: $routes = array( - 34 => array(array('_route' => 'foo', 'def' => 'test'), array('bar'), null, null), - 57 => array(array('_route' => 'bar'), array('foo'), array('GET' => 0, 'HEAD' => 1), null), - 77 => array(array('_route' => 'barhead'), array('foo'), array('GET' => 0), null), - 119 => array(array('_route' => 'quoter'), array('quoter'), null, null), - 156 => array(array('_route' => 'bar1'), array('bar'), null, null), - 169 => array(array('_route' => 'overridden'), array('var'), null, null), - 200 => array(array('_route' => 'bar2'), array('bar1'), null, null), - 236 => array(array('_route' => 'helloWorld', 'who' => 'World!'), array('who'), null, null), - 275 => array(array('_route' => 'bar3'), array('_locale', 'bar'), null, null), - 297 => array(array('_route' => 'foo4'), array('foo'), null, null), - 357 => array(array('_route' => 'route13'), array('var1', 'name'), null, null), - 375 => array(array('_route' => 'route14', 'var1' => 'val'), array('var1', 'name'), null, null), - 425 => array(array('_route' => 'route15'), array('name'), null, null), - 460 => array(array('_route' => 'route16', 'var1' => 'val'), array('name'), null, null), - 481 => array(array('_route' => 'a'), array(), null, null), - 502 => array(array('_route' => 'b'), array('var'), null, null), - 520 => array(array('_route' => 'c'), array('var'), null, null), + 46 => array(array('_route' => 'foo', 'def' => 'test'), array('bar'), null, null), + 69 => array(array('_route' => 'bar'), array('foo'), array('GET' => 0, 'HEAD' => 1), null), + 89 => array(array('_route' => 'barhead'), array('foo'), array('GET' => 0), null), + 131 => array(array('_route' => 'quoter'), array('quoter'), null, null), + 168 => array(array('_route' => 'bar1'), array('bar'), null, null), + 181 => array(array('_route' => 'overridden'), array('var'), null, null), + 212 => array(array('_route' => 'bar2'), array('bar1'), null, null), + 248 => array(array('_route' => 'helloWorld', 'who' => 'World!'), array('who'), null, null), + 287 => array(array('_route' => 'bar3'), array('_locale', 'bar'), null, null), + 309 => array(array('_route' => 'foo4'), array('foo'), null, null), + 371 => array(array('_route' => 'route13'), array('var1', 'name'), null, null), + 389 => array(array('_route' => 'route14', 'var1' => 'val'), array('var1', 'name'), null, null), + 441 => array(array('_route' => 'route15'), array('name'), null, null), + 488 => array(array('_route' => 'route16', 'var1' => 'val'), array('name'), null, null), + 509 => array(array('_route' => 'a'), array(), null, null), + 530 => array(array('_route' => 'b'), array('var'), null, null), + 548 => array(array('_route' => 'c'), array('var'), null, null), ); list($ret, $vars, $requiredMethods, $requiredSchemes) = $routes[$m]; @@ -268,7 +268,7 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a return $ret; } - if (520 === $m) { + if (548 === $m) { break; } $regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m)); diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher8.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher8.php index f3163063c4329..0f2ce53fed0b9 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher8.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher8.php @@ -32,10 +32,10 @@ public function match($rawPathinfo) .'|/(a)(*:11)' .')$}sD', 11 => '{^(?' - .'|/(.)(*:26)' + .'|/(.)(*:22)' .')$}sDu', - 26 => '{^(?' - .'|/(.)(*:41)' + 22 => '{^(?' + .'|/(.)(*:33)' .')$}sD', ); @@ -45,8 +45,8 @@ public function match($rawPathinfo) default: $routes = array( 11 => array(array('_route' => 'a'), array('a'), null, null), - 26 => array(array('_route' => 'b'), array('a'), null, null), - 41 => array(array('_route' => 'c'), array('a'), null, null), + 22 => array(array('_route' => 'b'), array('a'), null, null), + 33 => array(array('_route' => 'c'), array('a'), null, null), ); list($ret, $vars, $requiredMethods, $requiredSchemes) = $routes[$m]; @@ -72,7 +72,7 @@ public function match($rawPathinfo) return $ret; } - if (41 === $m) { + if (33 === $m) { break; } $regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m)); diff --git a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php index 1e13f0883e9db..f0013d7bc3d10 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php @@ -633,6 +633,43 @@ public function testDotAllWithCatchAll() $this->assertEquals(array('_route' => 'a', 'id' => 'foo/bar'), $matcher->match('/foo/bar.html')); } + public function testHostPattern() + { + $coll = new RouteCollection(); + $coll->add('a', new Route('/{app}/{action}/{unused}', array(), array(), array(), '{host}')); + + $expected = array( + '_route' => 'a', + 'app' => 'an_app', + 'action' => 'an_action', + 'unused' => 'unused', + 'host' => 'foo', + ); + $matcher = $this->getUrlMatcher($coll, new RequestContext('', 'GET', 'foo')); + $this->assertEquals($expected, $matcher->match('/an_app/an_action/unused')); + } + + public function testUtf8Prefix() + { + $coll = new RouteCollection(); + $coll->add('a', new Route('/é{foo}', array(), array(), array('utf8' => true))); + $coll->add('b', new Route('/è{bar}', array(), array(), array('utf8' => true))); + + $matcher = $this->getUrlMatcher($coll); + $this->assertEquals('a', $matcher->match('/éo')['_route']); + } + + public function testUtf8AndMethodMatching() + { + $coll = new RouteCollection(); + $coll->add('a', new Route('/admin/api/list/{shortClassName}/{id}.{_format}', array(), array(), array('utf8' => true), '', array(), array('PUT'))); + $coll->add('b', new Route('/admin/api/package.{_format}', array(), array(), array(), '', array(), array('POST'))); + $coll->add('c', new Route('/admin/api/package.{_format}', array('_format' => 'json'), array(), array(), '', array(), array('GET'))); + + $matcher = $this->getUrlMatcher($coll); + $this->assertEquals('c', $matcher->match('/admin/api/package.json')['_route']); + } + protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null) { return new UrlMatcher($routes, $context ?: new RequestContext()); diff --git a/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php b/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php index 1ce132b4d21a8..04bcdac918ac2 100644 --- a/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php +++ b/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php @@ -398,6 +398,7 @@ public function provideRemoveCapturingGroup() yield array('#^/(?P(?:b))$#sD', '(?:b)'); yield array('#^/(?P(?(b)b))$#sD', '(?(b)b)'); yield array('#^/(?P(*F))$#sD', '(*F)'); + yield array('#^/(?P(?:(?:foo)))$#sD', '((foo))'); } } From 9786ec8e193942287b94b0888e669a2ffa4c58f1 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 8 Jun 2018 11:29:49 +0200 Subject: [PATCH 075/938] [Cache][Security] Use Throwable where possible --- .../Component/Cache/Adapter/PhpArrayAdapter.php | 10 ++-------- .../Component/Cache/Simple/PhpArrayCache.php | 17 ++++++----------- .../Component/Cache/Traits/ApcuTrait.php | 14 ++++++-------- .../Config/ResourceCheckerConfigCache.php | 14 +++++++------- .../Security/Http/Firewall/ContextListener.php | 3 +-- 5 files changed, 22 insertions(+), 36 deletions(-) diff --git a/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php b/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php index ca5ef743d2285..02b12b237114d 100644 --- a/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php @@ -99,12 +99,8 @@ public function getItem($key) $value = null; } elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) { try { - $e = null; $value = unserialize($value); - } catch (\Error $e) { - } catch (\Exception $e) { - } - if (null !== $e) { + } catch (\Throwable $e) { $value = null; $isHit = false; } @@ -238,9 +234,7 @@ private function generateItems(array $keys): \Generator } elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) { try { yield $key => $f($key, unserialize($value), true); - } catch (\Error $e) { - yield $key => $f($key, null, false); - } catch (\Exception $e) { + } catch (\Throwable $e) { yield $key => $f($key, null, false); } } else { diff --git a/src/Symfony/Component/Cache/Simple/PhpArrayCache.php b/src/Symfony/Component/Cache/Simple/PhpArrayCache.php index 3feb9f0b1a004..64dc776f74a31 100644 --- a/src/Symfony/Component/Cache/Simple/PhpArrayCache.php +++ b/src/Symfony/Component/Cache/Simple/PhpArrayCache.php @@ -74,15 +74,12 @@ public function get($key, $default = null) $value = $this->values[$key]; if ('N;' === $value) { - $value = null; - } elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) { + return null; + } + if (\is_string($value) && isset($value[2]) && ':' === $value[1]) { try { - $e = null; - $value = unserialize($value); - } catch (\Error $e) { - } catch (\Exception $e) { - } - if (null !== $e) { + return unserialize($value); + } catch (\Throwable $e) { return $default; } } @@ -235,9 +232,7 @@ private function generateItems(array $keys, $default) } elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) { try { yield $key => unserialize($value); - } catch (\Error $e) { - yield $key => $default; - } catch (\Exception $e) { + } catch (\Throwable $e) { yield $key => $default; } } else { diff --git a/src/Symfony/Component/Cache/Traits/ApcuTrait.php b/src/Symfony/Component/Cache/Traits/ApcuTrait.php index fe7dfbab7d8c0..cec621b868f49 100644 --- a/src/Symfony/Component/Cache/Traits/ApcuTrait.php +++ b/src/Symfony/Component/Cache/Traits/ApcuTrait.php @@ -103,15 +103,13 @@ protected function doSave(array $values, $lifetime) } return array_keys($failures); - } catch (\Error $e) { - } catch (\Exception $e) { - } + } catch (\Throwable $e) { + if (1 === count($values)) { + // Workaround https://github.com/krakjoe/apcu/issues/170 + apcu_delete(key($values)); + } - if (1 === count($values)) { - // Workaround https://github.com/krakjoe/apcu/issues/170 - apcu_delete(key($values)); + throw $e; } - - throw $e; } } diff --git a/src/Symfony/Component/Config/ResourceCheckerConfigCache.php b/src/Symfony/Component/Config/ResourceCheckerConfigCache.php index ebacb7a4c0be9..6f64c4abcd66f 100644 --- a/src/Symfony/Component/Config/ResourceCheckerConfigCache.php +++ b/src/Symfony/Component/Config/ResourceCheckerConfigCache.php @@ -168,13 +168,13 @@ private function safelyUnserialize($file) try { $meta = unserialize(file_get_contents($file)); - } catch (\Error $e) { - } catch (\Exception $e) { - } - restore_error_handler(); - ini_set('unserialize_callback_func', $prevUnserializeHandler); - if (null !== $e && $e !== $signalingException) { - throw $e; + } catch (\Throwable $e) { + if ($e !== $signalingException) { + throw $e; + } + } finally { + restore_error_handler(); + ini_set('unserialize_callback_func', $prevUnserializeHandler); } return $meta; diff --git a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php index 6486f0eaf6085..7d3b960a1402a 100644 --- a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php @@ -224,8 +224,7 @@ private function safelyUnserialize($serializedToken) try { $token = unserialize($serializedToken); - } catch (\Error $e) { - } catch (\Exception $e) { + } catch (\Throwable $e) { } restore_error_handler(); ini_set('unserialize_callback_func', $prevUnserializeHandler); From 847abd3ec98d82b2c0496e5c7657662ecd95c9ea Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 9 Jun 2018 00:30:30 +0200 Subject: [PATCH 076/938] [FrameworkBundle] decouple some cache-warmer's test from internal details --- .../CacheWarmer/SerializerCacheWarmerTest.php | 15 ++++------- .../CacheWarmer/ValidatorCacheWarmerTest.php | 26 +++++++------------ 2 files changed, 15 insertions(+), 26 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/SerializerCacheWarmerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/SerializerCacheWarmerTest.php index e5df7b8c8a7cc..86aee6e0cc354 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/SerializerCacheWarmerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/SerializerCacheWarmerTest.php @@ -14,6 +14,8 @@ use Symfony\Bundle\FrameworkBundle\CacheWarmer\SerializerCacheWarmer; use Symfony\Bundle\FrameworkBundle\Tests\TestCase; use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\Cache\Adapter\NullAdapter; +use Symfony\Component\Cache\Adapter\PhpArrayAdapter; use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader; use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader; @@ -41,12 +43,10 @@ public function testWarmUp() $this->assertFileExists($file); - $values = require $file; + $arrayPool = new PhpArrayAdapter($file, new NullAdapter()); - $this->assertInternalType('array', $values); - $this->assertCount(2, $values); - $this->assertArrayHasKey('Symfony_Bundle_FrameworkBundle_Tests_Fixtures_Serialization_Person', $values); - $this->assertArrayHasKey('Symfony_Bundle_FrameworkBundle_Tests_Fixtures_Serialization_Author', $values); + $this->assertTrue($arrayPool->getItem('Symfony_Bundle_FrameworkBundle_Tests_Fixtures_Serialization_Person')->isHit()); + $this->assertTrue($arrayPool->getItem('Symfony_Bundle_FrameworkBundle_Tests_Fixtures_Serialization_Author')->isHit()); $values = $fallbackPool->getValues(); @@ -72,11 +72,6 @@ public function testWarmUpWithoutLoader() $this->assertFileExists($file); - $values = require $file; - - $this->assertInternalType('array', $values); - $this->assertCount(0, $values); - $values = $fallbackPool->getValues(); $this->assertInternalType('array', $values); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/ValidatorCacheWarmerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/ValidatorCacheWarmerTest.php index 23b4732afcb3a..f8d3710c6d1c5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/ValidatorCacheWarmerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/ValidatorCacheWarmerTest.php @@ -14,6 +14,9 @@ use Symfony\Bundle\FrameworkBundle\CacheWarmer\ValidatorCacheWarmer; use Symfony\Bundle\FrameworkBundle\Tests\TestCase; use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\Cache\Adapter\NullAdapter; +use Symfony\Component\Cache\Adapter\PhpArrayAdapter; +use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\ValidatorBuilder; class ValidatorCacheWarmerTest extends TestCase @@ -36,12 +39,10 @@ public function testWarmUp() $this->assertFileExists($file); - $values = require $file; + $arrayPool = new PhpArrayAdapter($file, new NullAdapter()); - $this->assertInternalType('array', $values); - $this->assertCount(2, $values); - $this->assertArrayHasKey('Symfony.Bundle.FrameworkBundle.Tests.Fixtures.Validation.Person', $values); - $this->assertArrayHasKey('Symfony.Bundle.FrameworkBundle.Tests.Fixtures.Validation.Author', $values); + $this->assertTrue($arrayPool->getItem('Symfony.Bundle.FrameworkBundle.Tests.Fixtures.Validation.Person')->isHit()); + $this->assertTrue($arrayPool->getItem('Symfony.Bundle.FrameworkBundle.Tests.Fixtures.Validation.Author')->isHit()); $values = $fallbackPool->getValues(); @@ -67,14 +68,12 @@ public function testWarmUpWithAnnotations() $this->assertFileExists($file); - $values = require $file; + $arrayPool = new PhpArrayAdapter($file, new NullAdapter()); - $this->assertInternalType('array', $values); - $this->assertCount(1, $values); - $this->assertArrayHasKey('Symfony.Bundle.FrameworkBundle.Tests.Fixtures.Validation.Category', $values); + $item = $arrayPool->getItem('Symfony.Bundle.FrameworkBundle.Tests.Fixtures.Validation.Category'); + $this->assertTrue($item->isHit()); - // Simple check to make sure that at least one constraint is actually cached, in this case the "id" property Type. - $this->assertContains('"int"', $values['Symfony.Bundle.FrameworkBundle.Tests.Fixtures.Validation.Category']); + $this->assertInstanceOf(ClassMetadata::class, $item->get()); $values = $fallbackPool->getValues(); @@ -98,11 +97,6 @@ public function testWarmUpWithoutLoader() $this->assertFileExists($file); - $values = require $file; - - $this->assertInternalType('array', $values); - $this->assertCount(0, $values); - $values = $fallbackPool->getValues(); $this->assertInternalType('array', $values); From 16806741744d8f14ca6a7397772c754abd74a483 Mon Sep 17 00:00:00 2001 From: David Maicher Date: Sat, 9 Jun 2018 14:38:46 +0200 Subject: [PATCH 077/938] [FrameworkBundle] fix for allowing single colon controller notation --- .../Routing/DelegatingLoader.php | 4 +- .../Tests/Routing/DelegatingLoaderTest.php | 48 +++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.php b/src/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.php index 6df729b02afe1..049887ba35e8b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.php @@ -94,8 +94,8 @@ public function load($resource, $type = null) } if (1 === substr_count($controller, ':')) { - $controller = str_replace(':', '::', $controller); - @trigger_error(sprintf('Referencing controllers with a single colon is deprecated since Symfony 4.1. Use %s instead.', $controller), E_USER_DEPRECATED); + $nonDeprecatedNotation = str_replace(':', '::', $controller); + @trigger_error(sprintf('Referencing controllers with a single colon is deprecated since Symfony 4.1. Use %s instead.', $nonDeprecatedNotation), E_USER_DEPRECATED); } $route->setDefault('_controller', $controller); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/DelegatingLoaderTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/DelegatingLoaderTest.php index 407158333d89e..5f08cce0ab860 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/DelegatingLoaderTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/DelegatingLoaderTest.php @@ -5,7 +5,11 @@ use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser; use Symfony\Bundle\FrameworkBundle\Routing\DelegatingLoader; +use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\Config\Loader\LoaderResolver; +use Symfony\Component\Config\Loader\LoaderResolverInterface; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; class DelegatingLoaderTest extends TestCase { @@ -17,4 +21,48 @@ public function testConstructorApi() new DelegatingLoader($controllerNameParser, new LoaderResolver()); $this->assertTrue(true, '__construct() takes a ControllerNameParser and LoaderResolverInterface respectively as its first and second argument.'); } + + /** + * @group legacy + * @expectedDeprecation Referencing controllers with foo:bar:baz is deprecated since Symfony 4.1. Use some_parsed::controller instead. + * @expectedDeprecation Referencing controllers with a single colon is deprecated since Symfony 4.1. Use foo::baz instead. + */ + public function testLoad() + { + $controllerNameParser = $this->getMockBuilder(ControllerNameParser::class) + ->disableOriginalConstructor() + ->getMock(); + + $controllerNameParser->expects($this->once()) + ->method('parse') + ->with('foo:bar:baz') + ->willReturn('some_parsed::controller'); + + $loaderResolver = $this->getMockBuilder(LoaderResolverInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $loader = $this->getMockBuilder(LoaderInterface::class)->getMock(); + + $loaderResolver->expects($this->once()) + ->method('resolve') + ->willReturn($loader); + + $routeCollection = new RouteCollection(); + $routeCollection->add('foo', new Route('/', array('_controller' => 'foo:bar:baz'))); + $routeCollection->add('bar', new Route('/', array('_controller' => 'foo::baz'))); + $routeCollection->add('baz', new Route('/', array('_controller' => 'foo:baz'))); + + $loader->expects($this->once()) + ->method('load') + ->willReturn($routeCollection); + + $delegatingLoader = new DelegatingLoader($controllerNameParser, $loaderResolver); + + $loadedRouteCollection = $delegatingLoader->load('foo'); + $this->assertCount(3, $loadedRouteCollection); + $this->assertSame('some_parsed::controller', $routeCollection->get('foo')->getDefault('_controller')); + $this->assertSame('foo::baz', $routeCollection->get('bar')->getDefault('_controller')); + $this->assertSame('foo:baz', $routeCollection->get('baz')->getDefault('_controller')); + } } From 322f58b334105e09330809e91e9b09e27b7c1e99 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 9 Jun 2018 21:00:10 +0200 Subject: [PATCH 078/938] [DI] Deduplicate generated proxy classes --- .../DependencyInjection/Dumper/PhpDumper.php | 7 +- .../Tests/Dumper/PhpDumperTest.php | 13 +++ .../Tests/Fixtures/includes/classes.php | 4 +- .../php/services_dedup_lazy_proxy.php | 88 +++++++++++++++++++ .../Fixtures/php/services_non_shared_lazy.php | 4 +- 5 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_dedup_lazy_proxy.php diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index d640b9a2351d1..560b2516bb3c4 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -396,6 +396,7 @@ private function collectLineage($class, array &$lineage) private function generateProxyClasses() { + $alreadyGenerated = array(); $definitions = $this->container->getDefinitions(); $strip = '' === $this->docStar && method_exists('Symfony\Component\HttpKernel\Kernel', 'stripComments'); $proxyDumper = $this->getProxyDumper(); @@ -404,8 +405,12 @@ private function generateProxyClasses() if (!$proxyDumper->isProxyCandidate($definition)) { continue; } + if (isset($alreadyGenerated[$class = $definition->getClass()])) { + continue; + } + $alreadyGenerated[$class] = true; // register class' reflector for resource tracking - $this->container->getReflectionClass($definition->getClass()); + $this->container->getReflectionClass($class); $proxyCode = "\n".$proxyDumper->getProxyCode($definition); if ($strip) { $proxyCode = "addToAssertionCount(1); } + public function testDedupLazyProxy() + { + $container = new ContainerBuilder(); + $container->register('foo', 'stdClass')->setLazy(true)->setPublic(true); + $container->register('bar', 'stdClass')->setLazy(true)->setPublic(true); + $container->compile(); + + $dumper = new PhpDumper($container); + $dumper->setProxyDumper(new \DummyProxyDumper()); + + $this->assertStringEqualsFile(self::$fixturesPath.'/php/services_dedup_lazy_proxy.php', $dumper->dump()); + } + public function testLazyArgumentProvideGenerator() { require_once self::$fixturesPath.'/includes/classes.php'; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php index bced911043c55..33b043fa3f384 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php @@ -90,12 +90,12 @@ public function isProxyCandidate(Definition $definition) public function getProxyFactoryCode(Definition $definition, $id, $factoryCall = null) { - return " // lazy factory\n\n"; + return " // lazy factory for {$definition->getClass()}\n\n"; } public function getProxyCode(Definition $definition) { - return "// proxy code\n"; + return "// proxy code for {$definition->getClass()}\n"; } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_dedup_lazy_proxy.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_dedup_lazy_proxy.php new file mode 100644 index 0000000000000..73a7f259f86b5 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_dedup_lazy_proxy.php @@ -0,0 +1,88 @@ +services = array(); + $this->methodMap = array( + 'bar' => 'getBarService', + 'foo' => 'getFooService', + ); + + $this->aliases = array(); + } + + public function getRemovedIds() + { + return array( + 'Psr\\Container\\ContainerInterface' => true, + 'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true, + ); + } + + public function compile() + { + throw new LogicException('You cannot compile a dumped container that was already compiled.'); + } + + public function isCompiled() + { + return true; + } + + public function isFrozen() + { + @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the isCompiled() method instead.', __METHOD__), E_USER_DEPRECATED); + + return true; + } + + protected function createProxy($class, \Closure $factory) + { + return $factory(); + } + + /** + * Gets the public 'bar' shared service. + * + * @return \stdClass + */ + protected function getBarService($lazyLoad = true) + { + // lazy factory for stdClass + + return new \stdClass(); + } + + /** + * Gets the public 'foo' shared service. + * + * @return \stdClass + */ + protected function getFooService($lazyLoad = true) + { + // lazy factory for stdClass + + return new \stdClass(); + } +} + +// proxy code for stdClass diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy.php index 9e2f1ab915f8e..6c3b1405069f1 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy.php @@ -83,10 +83,10 @@ protected function getBarService() */ protected function getFooService($lazyLoad = true) { - // lazy factory + // lazy factory for stdClass return new \stdClass(); } } -// proxy code +// proxy code for stdClass From 50979b5ea47336e5a052bba88df3746e4e7fb775 Mon Sep 17 00:00:00 2001 From: nsbx Date: Sat, 9 Jun 2018 15:16:17 +0200 Subject: [PATCH 079/938] [PhpUnitBridge] Fix error on some Windows OS --- src/Symfony/Bridge/PhpUnit/bin/simple-phpunit | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit index 552d5abb36b5e..197650915fe91 100755 --- a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit +++ b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit @@ -70,6 +70,8 @@ if (!file_exists("$PHPUNIT_DIR/phpunit-$PHPUNIT_VERSION/phpunit") || md5_file(__ throw new \RuntimeException("Could not find $remoteZip"); } stream_copy_to_stream($remoteZipStream, fopen("$PHPUNIT_VERSION.zip", 'wb')); + } elseif ('\\' === DIRECTORY_SEPARATOR) { + passthru("certutil -urlcache -split -f \"https://github.com/sebastianbergmann/phpunit/archive/$PHPUNIT_VERSION.zip\" $PHPUNIT_VERSION.zip"); } else { @unlink("$PHPUNIT_VERSION.zip"); passthru("wget -q https://github.com/sebastianbergmann/phpunit/archive/$PHPUNIT_VERSION.zip"); From 85b832bcc9d862d6e372acd19f5d5ef23e46d7fb Mon Sep 17 00:00:00 2001 From: Roland Franssen Date: Sat, 9 Jun 2018 09:49:02 +0200 Subject: [PATCH 080/938] [HttpKernel] Log/Collect exceptions at prio 0 --- src/Symfony/Component/HttpKernel/CHANGELOG.md | 2 +- .../Component/HttpKernel/EventListener/ExceptionListener.php | 3 +-- .../Component/HttpKernel/EventListener/ProfilerListener.php | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/CHANGELOG.md b/src/Symfony/Component/HttpKernel/CHANGELOG.md index f64905cbe086e..c98d2235e333d 100644 --- a/src/Symfony/Component/HttpKernel/CHANGELOG.md +++ b/src/Symfony/Component/HttpKernel/CHANGELOG.md @@ -5,7 +5,7 @@ CHANGELOG ----- * added orphaned events support to `EventDataCollector` - * `ExceptionListener` now logs and collects exceptions at priority `2048` (previously logged at `-128` and collected at `0`) + * `ExceptionListener` now logs exceptions at priority `0` (previously logged at `-128`) * Deprecated `service:action` syntax with a single colon to reference controllers. Use `service::method` instead. * Added the ability to profile individual argument value resolvers via the `Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver` diff --git a/src/Symfony/Component/HttpKernel/EventListener/ExceptionListener.php b/src/Symfony/Component/HttpKernel/EventListener/ExceptionListener.php index fc749de599a48..4812f426951c8 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/ExceptionListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/ExceptionListener.php @@ -44,7 +44,6 @@ public function __construct($controller, LoggerInterface $logger = null, $debug public function logKernelException(GetResponseForExceptionEvent $event) { $exception = $event->getException(); - $request = $event->getRequest(); $this->logException($exception, sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', get_class($exception), $exception->getMessage(), $exception->getFile(), $exception->getLine())); } @@ -90,7 +89,7 @@ public static function getSubscribedEvents() { return array( KernelEvents::EXCEPTION => array( - array('logKernelException', 2048), + array('logKernelException', 0), array('onKernelException', -128), ), ); diff --git a/src/Symfony/Component/HttpKernel/EventListener/ProfilerListener.php b/src/Symfony/Component/HttpKernel/EventListener/ProfilerListener.php index 9cc554db72ab0..045e08347ff81 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/ProfilerListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/ProfilerListener.php @@ -121,7 +121,7 @@ public static function getSubscribedEvents() { return array( KernelEvents::RESPONSE => array('onKernelResponse', -100), - KernelEvents::EXCEPTION => array('onKernelException', 2048), + KernelEvents::EXCEPTION => array('onKernelException', 0), KernelEvents::TERMINATE => array('onKernelTerminate', -1024), ); } From 1f346f446d462e5d0b79bf0642ed4b89621a1684 Mon Sep 17 00:00:00 2001 From: Webnet team Date: Mon, 21 May 2018 12:07:14 +0200 Subject: [PATCH 081/938] [Serializer] deserialize from xml: Fix a collection that contains the only one element --- .../Normalizer/AbstractObjectNormalizer.php | 6 + .../AbstractObjectNormalizerTest.php | 196 ++++++++++++++++++ 2 files changed, 202 insertions(+) diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index 251ec5fac05e4..020b3d5317a74 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -255,6 +255,12 @@ private function validateAndDenormalize($currentClass, $attribute, $data, $forma $builtinType = Type::BUILTIN_TYPE_OBJECT; $class = $collectionValueType->getClassName().'[]'; + // Fix a collection that contains the only one element + // This is special to xml format only + if ('xml' === $format && !is_int(key($data))) { + $data = array($data); + } + if (null !== $collectionKeyType = $type->getCollectionKeyType()) { $context['key_type'] = $collectionKeyType; } diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php index 3ca418d55ed6b..b1d40dcee3e1d 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -13,9 +13,13 @@ use Doctrine\Common\Annotations\AnnotationReader; use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Exception\NotNormalizableValueException; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use Symfony\Component\Serializer\SerializerAwareInterface; +use Symfony\Component\Serializer\SerializerInterface; class AbstractObjectNormalizerTest extends TestCase { @@ -69,6 +73,75 @@ public function testDenormalizeWithExtraAttributesAndNoGroupsWithMetadataFactory array('allow_extra_attributes' => false) ); } + + public function testDenormalizeCollectionDecodedFromXmlWithOneChild() + { + $denormalizer = $this->getDenormalizerForDummyCollection(); + + $dummyCollection = $denormalizer->denormalize( + array( + 'children' => array( + 'bar' => 'first', + ), + ), + DummyCollection::class, + 'xml' + ); + + $this->assertInstanceOf(DummyCollection::class, $dummyCollection); + $this->assertInternalType('array', $dummyCollection->children); + $this->assertCount(1, $dummyCollection->children); + $this->assertInstanceOf(DummyChild::class, $dummyCollection->children[0]); + } + + public function testDenormalizeCollectionDecodedFromXmlWithTwoChildren() + { + $denormalizer = $this->getDenormalizerForDummyCollection(); + + $dummyCollection = $denormalizer->denormalize( + array( + 'children' => array( + array('bar' => 'first'), + array('bar' => 'second'), + ), + ), + DummyCollection::class, + 'xml' + ); + + $this->assertInstanceOf(DummyCollection::class, $dummyCollection); + $this->assertInternalType('array', $dummyCollection->children); + $this->assertCount(2, $dummyCollection->children); + $this->assertInstanceOf(DummyChild::class, $dummyCollection->children[0]); + $this->assertInstanceOf(DummyChild::class, $dummyCollection->children[1]); + } + + private function getDenormalizerForDummyCollection() + { + $extractor = $this->getMockBuilder('Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor')->getMock(); + $extractor->method('getTypes') + ->will($this->onConsecutiveCalls( + array( + new \Symfony\Component\PropertyInfo\Type( + 'array', + false, + null, + true, + new \Symfony\Component\PropertyInfo\Type('int'), + new \Symfony\Component\PropertyInfo\Type('object', false, DummyChild::class) + ), + ), + null + )); + + $denormalizer = new AbstractObjectNormalizerCollectionDummy(null, null, $extractor); + $arrayDenormalizer = new ArrayDenormalizerDummy(); + $serializer = new SerializerCollectionDummy(array($arrayDenormalizer, $denormalizer)); + $arrayDenormalizer->setSerializer($serializer); + $denormalizer->setSerializer($serializer); + + return $denormalizer; + } } class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer @@ -124,3 +197,126 @@ protected function setAttributeValue($object, $attribute, $value, $format = null $object->$attribute = $value; } } + +class DummyCollection +{ + /** @var DummyChild[] */ + public $children; +} + +class DummyChild +{ + public $bar; +} + +class SerializerCollectionDummy implements \Symfony\Component\Serializer\SerializerInterface, \Symfony\Component\Serializer\Normalizer\DenormalizerInterface +{ + /** @var \Symfony\Component\Serializer\Normalizer\DenormalizerInterface */ + private $normalizers; + + /** + * @param $normalizers + */ + public function __construct($normalizers) + { + $this->normalizers = $normalizers; + } + + public function serialize($data, $format, array $context = array()) + { + } + + public function deserialize($data, $type, $format, array $context = array()) + { + } + + public function denormalize($data, $type, $format = null, array $context = array()) + { + foreach ($this->normalizers as $normalizer) { + if ($normalizer instanceof \Symfony\Component\Serializer\Normalizer\DenormalizerInterface && $normalizer->supportsDenormalization($data, $type, $format, $context)) { + return $normalizer->denormalize($data, $type, $format, $context); + } + } + } + + public function supportsDenormalization($data, $type, $format = null) + { + return true; + } +} + +class AbstractObjectNormalizerCollectionDummy extends \Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer +{ + protected function extractAttributes($object, $format = null, array $context = array()) + { + } + + protected function getAttributeValue($object, $attribute, $format = null, array $context = array()) + { + } + + protected function setAttributeValue($object, $attribute, $value, $format = null, array $context = array()) + { + $object->$attribute = $value; + } + + protected function isAllowedAttribute($classOrObject, $attribute, $format = null, array $context = array()) + { + return true; + } + + public function instantiateObject(array &$data, $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes, $format = null) + { + return parent::instantiateObject($data, $class, $context, $reflectionClass, $allowedAttributes, $format); + } + + public function serialize($data, $format, array $context = array()) + { + } + + public function deserialize($data, $type, $format, array $context = array()) + { + } +} + +class ArrayDenormalizerDummy implements DenormalizerInterface, SerializerAwareInterface +{ + /** + * @var SerializerInterface|DenormalizerInterface + */ + private $serializer; + + /** + * {@inheritdoc} + * + * @throws NotNormalizableValueException + */ + public function denormalize($data, $class, $format = null, array $context = array()) + { + $serializer = $this->serializer; + $class = substr($class, 0, -2); + + foreach ($data as $key => $value) { + $data[$key] = $serializer->denormalize($value, $class, $format, $context); + } + + return $data; + } + + /** + * {@inheritdoc} + */ + public function supportsDenormalization($data, $type, $format = null, array $context = array()) + { + return '[]' === substr($type, -2) + && $this->serializer->supportsDenormalization($data, substr($type, 0, -2), $format, $context); + } + + /** + * {@inheritdoc} + */ + public function setSerializer(SerializerInterface $serializer) + { + $this->serializer = $serializer; + } +} From cca73bb564adae22d0e9dd0c6dafbf1466a555c1 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Wed, 30 May 2018 10:06:54 -0400 Subject: [PATCH 082/938] Avoid migration on stateless firewalls --- .../Factory/GuardAuthenticationFactory.php | 1 + .../Security/Factory/HttpBasicFactory.php | 1 + .../Security/Factory/HttpDigestFactory.php | 1 + .../Security/Factory/RemoteUserFactory.php | 1 + .../SimplePreAuthenticationFactory.php | 1 + .../Security/Factory/X509Factory.php | 1 + .../DependencyInjection/SecurityExtension.php | 4 ++ .../Resources/config/security.xml | 4 ++ .../Bundle/SecurityBundle/composer.json | 2 +- .../Guard/GuardAuthenticatorHandler.php | 24 ++++++++---- .../Tests/GuardAuthenticatorHandlerTest.php | 37 +++++++++++++++++++ .../AbstractPreAuthenticatedListener.php | 24 ++++++++---- .../Firewall/BasicAuthenticationListener.php | 24 ++++++++---- .../Firewall/DigestAuthenticationListener.php | 27 ++++++++++---- .../SimplePreAuthenticationListener.php | 24 ++++++++---- 15 files changed, 138 insertions(+), 38 deletions(-) diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php index 533560d6d986d..bd49cbc932083 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php @@ -77,6 +77,7 @@ public function create(ContainerBuilder $container, $id, $config, $userProvider, $listener = $container->setDefinition($listenerId, new DefinitionDecorator('security.authentication.listener.guard')); $listener->replaceArgument(2, $id); $listener->replaceArgument(3, $authenticatorReferences); + $listener->addMethodCall('setSessionAuthenticationStrategy', array(new Reference('security.authentication.session_strategy.'.$id))); // determine the entryPointId to use $entryPointId = $this->determineEntryPoint($defaultEntryPoint, $config); diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicFactory.php index 162ea05157984..f09636ec71c0d 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicFactory.php @@ -41,6 +41,7 @@ public function create(ContainerBuilder $container, $id, $config, $userProvider, $listener = $container->setDefinition($listenerId, new DefinitionDecorator('security.authentication.listener.basic')); $listener->replaceArgument(2, $id); $listener->replaceArgument(3, new Reference($entryPointId)); + $listener->addMethodCall('setSessionAuthenticationStrategy', array(new Reference('security.authentication.session_strategy.'.$id))); return array($provider, $listenerId, $entryPointId); } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpDigestFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpDigestFactory.php index 4cfb79653c054..944a9100f389d 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpDigestFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpDigestFactory.php @@ -42,6 +42,7 @@ public function create(ContainerBuilder $container, $id, $config, $userProvider, $listener->replaceArgument(1, new Reference($userProvider)); $listener->replaceArgument(2, $id); $listener->replaceArgument(3, new Reference($entryPointId)); + $listener->addMethodCall('setSessionAuthenticationStrategy', array(new Reference('security.authentication.session_strategy.'.$id))); return array($provider, $listenerId, $entryPointId); } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RemoteUserFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RemoteUserFactory.php index cf2e2ed71b16c..5be068e6c4870 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RemoteUserFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RemoteUserFactory.php @@ -38,6 +38,7 @@ public function create(ContainerBuilder $container, $id, $config, $userProvider, $listener = $container->setDefinition($listenerId, new DefinitionDecorator('security.authentication.listener.remote_user')); $listener->replaceArgument(2, $id); $listener->replaceArgument(3, $config['user']); + $listener->addMethodCall('setSessionAuthenticationStrategy', array(new Reference('security.authentication.session_strategy.'.$id))); return array($providerId, $listenerId, $defaultEntryPoint); } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SimplePreAuthenticationFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SimplePreAuthenticationFactory.php index c1c6e48083856..03fca8d6a25df 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SimplePreAuthenticationFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SimplePreAuthenticationFactory.php @@ -57,6 +57,7 @@ public function create(ContainerBuilder $container, $id, $config, $userProvider, $listener = $container->setDefinition($listenerId, new DefinitionDecorator('security.authentication.listener.simple_preauth')); $listener->replaceArgument(2, $id); $listener->replaceArgument(3, new Reference($config['authenticator'])); + $listener->addMethodCall('setSessionAuthenticationStrategy', array(new Reference('security.authentication.session_strategy.'.$id))); return array($provider, $listenerId, null); } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/X509Factory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/X509Factory.php index 0467ef2ba2c75..a745de9b2d78c 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/X509Factory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/X509Factory.php @@ -39,6 +39,7 @@ public function create(ContainerBuilder $container, $id, $config, $userProvider, $listener->replaceArgument(2, $id); $listener->replaceArgument(3, $config['user']); $listener->replaceArgument(4, $config['credentials']); + $listener->addMethodCall('setSessionAuthenticationStrategy', array(new Reference('security.authentication.session_strategy.'.$id))); return array($providerId, $listenerId, $defaultEntryPoint); } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 34276e95e79f2..5138eff36719e 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -285,7 +285,11 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a } $listeners[] = new Reference($this->createContextListener($container, $contextKey)); + $sessionStrategyId = 'security.authentication.session_strategy'; + } else { + $sessionStrategyId = 'security.authentication.session_strategy_noop'; } + $container->setAlias(new Alias('security.authentication.session_strategy.'.$id, false), $sessionStrategyId); // Logout listener $logoutListenerId = null; diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml index 029395de9dea0..74b097aa4c2b7 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml @@ -84,6 +84,10 @@ %security.authentication.session_strategy.strategy% + + none + + diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index f588b04888161..c0508ea29b02b 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=5.3.9", "ext-xml": "*", - "symfony/security": "^2.8.41|^3.4.11", + "symfony/security": "^2.8.42|^3.4.12", "symfony/security-acl": "~2.7|~3.0.0", "symfony/http-kernel": "~2.7|~3.0.0", "symfony/polyfill-php70": "~1.0" diff --git a/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php b/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php index 5e6eba339bf64..0164ba9235262 100644 --- a/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php +++ b/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php @@ -20,6 +20,7 @@ use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; use Symfony\Component\Security\Http\SecurityEvents; +use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface; /** * A utility class that does much of the *work* during the guard authentication process. @@ -32,8 +33,8 @@ class GuardAuthenticatorHandler { private $tokenStorage; - private $dispatcher; + private $sessionStrategy; public function __construct(TokenStorageInterface $tokenStorage, EventDispatcherInterface $eventDispatcher = null) { @@ -46,7 +47,7 @@ public function __construct(TokenStorageInterface $tokenStorage, EventDispatcher */ public function authenticateWithToken(TokenInterface $token, Request $request) { - $this->migrateSession($request); + $this->migrateSession($request, $token); $this->tokenStorage->setToken($token); if (null !== $this->dispatcher) { @@ -129,15 +130,22 @@ public function handleAuthenticationFailure(AuthenticationException $authenticat )); } - private function migrateSession(Request $request) + /** + * Call this method if your authentication token is stored to a session. + * + * @final since version 2.8 + */ + public function setSessionAuthenticationStrategy(SessionAuthenticationStrategyInterface $sessionStrategy) + { + $this->sessionStrategy = $sessionStrategy; + } + + private function migrateSession(Request $request, TokenInterface $token) { - if (!$request->hasSession() || !$request->hasPreviousSession()) { + if (!$this->sessionStrategy || !$request->hasSession() || !$request->hasPreviousSession()) { return; } - // Destroying the old session is broken in php 5.4.0 - 5.4.10 - // See https://bugs.php.net/63379 - $destroy = \PHP_VERSION_ID < 50400 || \PHP_VERSION_ID >= 50411; - $request->getSession()->migrate($destroy); + $this->sessionStrategy->onAuthentication($request, $token); } } diff --git a/src/Symfony/Component/Security/Guard/Tests/GuardAuthenticatorHandlerTest.php b/src/Symfony/Component/Security/Guard/Tests/GuardAuthenticatorHandlerTest.php index 662bace30877c..49ce6548acab5 100644 --- a/src/Symfony/Component/Security/Guard/Tests/GuardAuthenticatorHandlerTest.php +++ b/src/Symfony/Component/Security/Guard/Tests/GuardAuthenticatorHandlerTest.php @@ -25,6 +25,7 @@ class GuardAuthenticatorHandlerTest extends TestCase private $dispatcher; private $token; private $request; + private $sessionStrategy; private $guardAuthenticator; public function testAuthenticateWithToken() @@ -117,12 +118,38 @@ public function getTokenClearingTests() return $tests; } + public function testNoFailureIfSessionStrategyNotPassed() + { + $this->configurePreviousSession(); + + $this->tokenStorage->expects($this->once()) + ->method('setToken') + ->with($this->token); + + $handler = new GuardAuthenticatorHandler($this->tokenStorage, $this->dispatcher); + $handler->authenticateWithToken($this->token, $this->request); + } + + public function testSessionStrategyIsCalled() + { + $this->configurePreviousSession(); + + $this->sessionStrategy->expects($this->once()) + ->method('onAuthentication') + ->with($this->request, $this->token); + + $handler = new GuardAuthenticatorHandler($this->tokenStorage, $this->dispatcher); + $handler->setSessionAuthenticationStrategy($this->sessionStrategy); + $handler->authenticateWithToken($this->token, $this->request); + } + protected function setUp() { $this->tokenStorage = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface')->getMock(); $this->dispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock(); $this->token = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock(); $this->request = new Request(array(), array(), array(), array(), array(), array()); + $this->sessionStrategy = $this->getMockBuilder('Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface')->getMock(); $this->guardAuthenticator = $this->getMockBuilder('Symfony\Component\Security\Guard\GuardAuthenticatorInterface')->getMock(); } @@ -134,4 +161,14 @@ protected function tearDown() $this->request = null; $this->guardAuthenticator = null; } + + private function configurePreviousSession() + { + $session = $this->getMockBuilder('Symfony\Component\HttpFoundation\Session\SessionInterface')->getMock(); + $session->expects($this->any()) + ->method('getName') + ->willReturn('test_session_name'); + $this->request->setSession($session); + $this->request->cookies->set('test_session_name', 'session_cookie_val'); + } } diff --git a/src/Symfony/Component/Security/Http/Firewall/AbstractPreAuthenticatedListener.php b/src/Symfony/Component/Security/Http/Firewall/AbstractPreAuthenticatedListener.php index 2054c4aa0774e..6451d882e8b94 100644 --- a/src/Symfony/Component/Security/Http/Firewall/AbstractPreAuthenticatedListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/AbstractPreAuthenticatedListener.php @@ -14,6 +14,7 @@ use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; use Symfony\Component\Security\Http\SecurityEvents; @@ -22,6 +23,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Security\Core\Exception\BadCredentialsException; +use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface; /** * AbstractPreAuthenticatedListener is the base class for all listener that @@ -37,6 +39,7 @@ abstract class AbstractPreAuthenticatedListener implements ListenerInterface private $authenticationManager; private $providerKey; private $dispatcher; + private $sessionStrategy; public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, $providerKey, LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null) { @@ -83,7 +86,7 @@ final public function handle(GetResponseEvent $event) $this->logger->info('Pre-authentication successful.', array('token' => (string) $token)); } - $this->migrateSession($request); + $this->migrateSession($request, $token); $this->tokenStorage->setToken($token); @@ -96,6 +99,16 @@ final public function handle(GetResponseEvent $event) } } + /** + * Call this method if your authentication token is stored to a session. + * + * @final since version 2.8 + */ + public function setSessionAuthenticationStrategy(SessionAuthenticationStrategyInterface $sessionStrategy) + { + $this->sessionStrategy = $sessionStrategy; + } + /** * Clears a PreAuthenticatedToken for this provider (if present). */ @@ -118,15 +131,12 @@ private function clearToken(AuthenticationException $exception) */ abstract protected function getPreAuthenticatedData(Request $request); - private function migrateSession(Request $request) + private function migrateSession(Request $request, TokenInterface $token) { - if (!$request->hasSession() || !$request->hasPreviousSession()) { + if (!$this->sessionStrategy || !$request->hasSession() || !$request->hasPreviousSession()) { return; } - // Destroying the old session is broken in php 5.4.0 - 5.4.10 - // See https://bugs.php.net/63379 - $destroy = \PHP_VERSION_ID < 50400 || \PHP_VERSION_ID >= 50411; - $request->getSession()->migrate($destroy); + $this->sessionStrategy->onAuthentication($request, $token); } } diff --git a/src/Symfony/Component/Security/Http/Firewall/BasicAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/BasicAuthenticationListener.php index 63bd013c64e31..4b14a842dc134 100644 --- a/src/Symfony/Component/Security/Http/Firewall/BasicAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/BasicAuthenticationListener.php @@ -14,11 +14,13 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; use Psr\Log\LoggerInterface; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface; /** * BasicAuthenticationListener implements Basic HTTP authentication. @@ -33,6 +35,7 @@ class BasicAuthenticationListener implements ListenerInterface private $authenticationEntryPoint; private $logger; private $ignoreFailure; + private $sessionStrategy; public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, $providerKey, AuthenticationEntryPointInterface $authenticationEntryPoint, LoggerInterface $logger = null) { @@ -72,7 +75,7 @@ public function handle(GetResponseEvent $event) try { $token = $this->authenticationManager->authenticate(new UsernamePasswordToken($username, $request->headers->get('PHP_AUTH_PW'), $this->providerKey)); - $this->migrateSession($request); + $this->migrateSession($request, $token); $this->tokenStorage->setToken($token); } catch (AuthenticationException $e) { @@ -93,15 +96,22 @@ public function handle(GetResponseEvent $event) } } - private function migrateSession(Request $request) + /** + * Call this method if your authentication token is stored to a session. + * + * @final since version 2.8 + */ + public function setSessionAuthenticationStrategy(SessionAuthenticationStrategyInterface $sessionStrategy) + { + $this->sessionStrategy = $sessionStrategy; + } + + private function migrateSession(Request $request, TokenInterface $token) { - if (!$request->hasSession() || !$request->hasPreviousSession()) { + if (!$this->sessionStrategy || !$request->hasSession() || !$request->hasPreviousSession()) { return; } - // Destroying the old session is broken in php 5.4.0 - 5.4.10 - // See https://bugs.php.net/63379 - $destroy = \PHP_VERSION_ID < 50400 || \PHP_VERSION_ID >= 50411; - $request->getSession()->migrate($destroy); + $this->sessionStrategy->onAuthentication($request, $token); } } diff --git a/src/Symfony/Component/Security/Http/Firewall/DigestAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/DigestAuthenticationListener.php index 5655315a8b0c6..b4853931ca4b0 100644 --- a/src/Symfony/Component/Security/Http/Firewall/DigestAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/DigestAuthenticationListener.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Security\Http\Firewall; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Http\EntryPoint\DigestAuthenticationEntryPoint; use Psr\Log\LoggerInterface; @@ -23,6 +24,7 @@ use Symfony\Component\Security\Core\Exception\NonceExpiredException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface; /** * DigestAuthenticationListener implements Digest HTTP authentication. @@ -36,6 +38,7 @@ class DigestAuthenticationListener implements ListenerInterface private $providerKey; private $authenticationEntryPoint; private $logger; + private $sessionStrategy; public function __construct(TokenStorageInterface $tokenStorage, UserProviderInterface $provider, $providerKey, DigestAuthenticationEntryPoint $authenticationEntryPoint, LoggerInterface $logger = null) { @@ -117,9 +120,20 @@ public function handle(GetResponseEvent $event) $this->logger->info('Digest authentication successful.', array('username' => $digestAuth->getUsername(), 'received' => $digestAuth->getResponse())); } - $this->migrateSession($request); + $token = new UsernamePasswordToken($user, $user->getPassword(), $this->providerKey); + $this->migrateSession($request, $token); - $this->tokenStorage->setToken(new UsernamePasswordToken($user, $user->getPassword(), $this->providerKey)); + $this->tokenStorage->setToken($token); + } + + /** + * Call this method if your authentication token is stored to a session. + * + * @final since version 2.8 + */ + public function setSessionAuthenticationStrategy(SessionAuthenticationStrategyInterface $sessionStrategy) + { + $this->sessionStrategy = $sessionStrategy; } private function fail(GetResponseEvent $event, Request $request, AuthenticationException $authException) @@ -136,16 +150,13 @@ private function fail(GetResponseEvent $event, Request $request, AuthenticationE $event->setResponse($this->authenticationEntryPoint->start($request, $authException)); } - private function migrateSession(Request $request) + private function migrateSession(Request $request, TokenInterface $token) { - if (!$request->hasSession() || !$request->hasPreviousSession()) { + if (!$this->sessionStrategy || !$request->hasSession() || !$request->hasPreviousSession()) { return; } - // Destroying the old session is broken in php 5.4.0 - 5.4.10 - // See https://bugs.php.net/63379 - $destroy = \PHP_VERSION_ID < 50400 || \PHP_VERSION_ID >= 50411; - $request->getSession()->migrate($destroy); + $this->sessionStrategy->onAuthentication($request, $token); } } diff --git a/src/Symfony/Component/Security/Http/Firewall/SimplePreAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/SimplePreAuthenticationListener.php index 23e517969f4e5..cdfb06d4fa2e6 100644 --- a/src/Symfony/Component/Security/Http/Firewall/SimplePreAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/SimplePreAuthenticationListener.php @@ -19,12 +19,14 @@ use Symfony\Component\Security\Core\Authentication\SimplePreAuthenticatorInterface; use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; use Symfony\Component\Security\Http\SecurityEvents; use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface; /** * SimplePreAuthenticationListener implements simple proxying to an authenticator. @@ -39,6 +41,7 @@ class SimplePreAuthenticationListener implements ListenerInterface private $simpleAuthenticator; private $logger; private $dispatcher; + private $sessionStrategy; /** * @param TokenStorageInterface $tokenStorage A TokenStorageInterface instance @@ -62,6 +65,16 @@ public function __construct(TokenStorageInterface $tokenStorage, AuthenticationM $this->dispatcher = $dispatcher; } + /** + * Call this method if your authentication token is stored to a session. + * + * @final since version 2.8 + */ + public function setSessionAuthenticationStrategy(SessionAuthenticationStrategyInterface $sessionStrategy) + { + $this->sessionStrategy = $sessionStrategy; + } + /** * Handles basic authentication. */ @@ -87,7 +100,7 @@ public function handle(GetResponseEvent $event) $token = $this->authenticationManager->authenticate($token); - $this->migrateSession($request); + $this->migrateSession($request, $token); $this->tokenStorage->setToken($token); @@ -124,15 +137,12 @@ public function handle(GetResponseEvent $event) } } - private function migrateSession(Request $request) + private function migrateSession(Request $request, TokenInterface $token) { - if (!$request->hasSession() || !$request->hasPreviousSession()) { + if (!$this->sessionStrategy || !$request->hasSession() || !$request->hasPreviousSession()) { return; } - // Destroying the old session is broken in php 5.4.0 - 5.4.10 - // See https://bugs.php.net/63379 - $destroy = \PHP_VERSION_ID < 50400 || \PHP_VERSION_ID >= 50411; - $request->getSession()->migrate($destroy); + $this->sessionStrategy->onAuthentication($request, $token); } } From 5c2b2bb2ce38b4055f35ffc08984a49694a8f79f Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 10 Jun 2018 12:30:11 +0200 Subject: [PATCH 083/938] fixed CS --- .../Component/Security/Guard/GuardAuthenticatorHandler.php | 2 +- .../Security/Http/Firewall/AbstractPreAuthenticatedListener.php | 2 +- .../Security/Http/Firewall/BasicAuthenticationListener.php | 2 +- .../Security/Http/Firewall/DigestAuthenticationListener.php | 2 +- .../Security/Http/Firewall/SimplePreAuthenticationListener.php | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php b/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php index 0164ba9235262..7b340a2601cee 100644 --- a/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php +++ b/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php @@ -133,7 +133,7 @@ public function handleAuthenticationFailure(AuthenticationException $authenticat /** * Call this method if your authentication token is stored to a session. * - * @final since version 2.8 + * @final */ public function setSessionAuthenticationStrategy(SessionAuthenticationStrategyInterface $sessionStrategy) { diff --git a/src/Symfony/Component/Security/Http/Firewall/AbstractPreAuthenticatedListener.php b/src/Symfony/Component/Security/Http/Firewall/AbstractPreAuthenticatedListener.php index 6451d882e8b94..062f4521cfbac 100644 --- a/src/Symfony/Component/Security/Http/Firewall/AbstractPreAuthenticatedListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/AbstractPreAuthenticatedListener.php @@ -102,7 +102,7 @@ final public function handle(GetResponseEvent $event) /** * Call this method if your authentication token is stored to a session. * - * @final since version 2.8 + * @final */ public function setSessionAuthenticationStrategy(SessionAuthenticationStrategyInterface $sessionStrategy) { diff --git a/src/Symfony/Component/Security/Http/Firewall/BasicAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/BasicAuthenticationListener.php index 4b14a842dc134..bfc61f3faacf7 100644 --- a/src/Symfony/Component/Security/Http/Firewall/BasicAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/BasicAuthenticationListener.php @@ -99,7 +99,7 @@ public function handle(GetResponseEvent $event) /** * Call this method if your authentication token is stored to a session. * - * @final since version 2.8 + * @final */ public function setSessionAuthenticationStrategy(SessionAuthenticationStrategyInterface $sessionStrategy) { diff --git a/src/Symfony/Component/Security/Http/Firewall/DigestAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/DigestAuthenticationListener.php index b4853931ca4b0..5bc3c5ea94df9 100644 --- a/src/Symfony/Component/Security/Http/Firewall/DigestAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/DigestAuthenticationListener.php @@ -129,7 +129,7 @@ public function handle(GetResponseEvent $event) /** * Call this method if your authentication token is stored to a session. * - * @final since version 2.8 + * @final */ public function setSessionAuthenticationStrategy(SessionAuthenticationStrategyInterface $sessionStrategy) { diff --git a/src/Symfony/Component/Security/Http/Firewall/SimplePreAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/SimplePreAuthenticationListener.php index cdfb06d4fa2e6..9cb90edbe65f7 100644 --- a/src/Symfony/Component/Security/Http/Firewall/SimplePreAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/SimplePreAuthenticationListener.php @@ -68,7 +68,7 @@ public function __construct(TokenStorageInterface $tokenStorage, AuthenticationM /** * Call this method if your authentication token is stored to a session. * - * @final since version 2.8 + * @final */ public function setSessionAuthenticationStrategy(SessionAuthenticationStrategyInterface $sessionStrategy) { From c06f3229decae3950d52b76d5e262d8a6b54a23e Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Fri, 8 Jun 2018 09:36:24 -0400 Subject: [PATCH 084/938] Avoiding session migration for stateless firewall UsernamePasswordJsonAuthenticationListener --- .../Security/Factory/JsonLoginFactory.php | 1 + .../DependencyInjection/SecurityExtension.php | 4 ++ .../Resources/config/security.xml | 3 ++ .../Bundle/SecurityBundle/composer.json | 2 +- ...namePasswordJsonAuthenticationListener.php | 20 ++++++++-- ...PasswordJsonAuthenticationListenerTest.php | 38 +++++++++++++++++++ 6 files changed, 63 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginFactory.php index 5a391ffacaeab..6c7adb032395b 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginFactory.php @@ -89,6 +89,7 @@ protected function createListener($container, $id, $config, $userProvider) $listener->replaceArgument(4, isset($config['success_handler']) ? new Reference($this->createAuthenticationSuccessHandler($container, $id, $config)) : null); $listener->replaceArgument(5, isset($config['failure_handler']) ? new Reference($this->createAuthenticationFailureHandler($container, $id, $config)) : null); $listener->replaceArgument(6, array_intersect_key($config, $this->options)); + $listener->addMethodCall('setSessionAuthenticationStrategy', array(new Reference('security.authentication.session_strategy.'.$id))); $listenerId .= '.'.$id; $container->setDefinition($listenerId, $listener); diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index d8f1db8dd96f4..b17c93f3e90c2 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -377,7 +377,11 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a $this->logoutOnUserChangeByContextKey[$contextKey] = array($id, $logoutOnUserChange); $listeners[] = new Reference($this->createContextListener($container, $contextKey, $logoutOnUserChange)); + $sessionStrategyId = 'security.authentication.session_strategy'; + } else { + $sessionStrategyId = 'security.authentication.session_strategy_noop'; } + $container->setAlias(new Alias('security.authentication.session_strategy.'.$id, false), $sessionStrategyId); $config->replaceArgument(6, $contextKey); diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml index 515f56db6e8ca..dea2481df457a 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml @@ -62,6 +62,9 @@ %security.authentication.session_strategy.strategy% + + none + diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index cc46a01f0f678..ebf39d1beea59 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -18,7 +18,7 @@ "require": { "php": "^5.5.9|>=7.0.8", "ext-xml": "*", - "symfony/security": "~3.4.11|^4.0.11", + "symfony/security": "~3.4.12|^4.0.12|^4.1.1", "symfony/dependency-injection": "^3.4.3|^4.0.3", "symfony/http-kernel": "~3.4|~4.0", "symfony/polyfill-php70": "~1.0" diff --git a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php index 8bde1e00151e8..d83e94d4b2efb 100644 --- a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php @@ -33,6 +33,7 @@ use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; use Symfony\Component\Security\Http\HttpUtils; use Symfony\Component\Security\Http\SecurityEvents; +use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface; /** * UsernamePasswordJsonAuthenticationListener is a stateless implementation of @@ -52,6 +53,7 @@ class UsernamePasswordJsonAuthenticationListener implements ListenerInterface private $logger; private $eventDispatcher; private $propertyAccessor; + private $sessionStrategy; public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, HttpUtils $httpUtils, $providerKey, AuthenticationSuccessHandlerInterface $successHandler = null, AuthenticationFailureHandlerInterface $failureHandler = null, array $options = array(), LoggerInterface $logger = null, EventDispatcherInterface $eventDispatcher = null, PropertyAccessorInterface $propertyAccessor = null) { @@ -139,7 +141,7 @@ private function onSuccess(Request $request, TokenInterface $token) $this->logger->info('User has been authenticated successfully.', array('username' => $token->getUsername())); } - $this->migrateSession($request); + $this->migrateSession($request, $token); $this->tokenStorage->setToken($token); @@ -185,11 +187,21 @@ private function onFailure(Request $request, AuthenticationException $failed) return $response; } - private function migrateSession(Request $request) + /** + * Call this method if your authentication token is stored to a session. + * + * @final since version 3.4 + */ + public function setSessionAuthenticationStrategy(SessionAuthenticationStrategyInterface $sessionStrategy) + { + $this->sessionStrategy = $sessionStrategy; + } + + private function migrateSession(Request $request, TokenInterface $token) { - if (!$request->hasSession() || !$request->hasPreviousSession()) { + if (!$this->sessionStrategy || !$request->hasSession() || !$request->hasPreviousSession()) { return; } - $request->getSession()->migrate(true); + $this->sessionStrategy->onAuthentication($request, $token); } } diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/UsernamePasswordJsonAuthenticationListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/UsernamePasswordJsonAuthenticationListenerTest.php index 74526fcd543e0..d8bb77bb9e818 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/UsernamePasswordJsonAuthenticationListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/UsernamePasswordJsonAuthenticationListenerTest.php @@ -212,4 +212,42 @@ public function testAttemptAuthenticationIfRequestPathMatchesCheckPath() $this->listener->handle($event); $this->assertSame('ok', $event->getResponse()->getContent()); } + + public function testNoErrorOnMissingSessionStrategy() + { + $this->createListener(); + $request = new Request(array(), array(), array(), array(), array(), array('HTTP_CONTENT_TYPE' => 'application/json'), '{"username": "dunglas", "password": "foo"}'); + $this->configurePreviousSession($request); + $event = new GetResponseEvent($this->getMockBuilder(KernelInterface::class)->getMock(), $request, KernelInterface::MASTER_REQUEST); + + $this->listener->handle($event); + $this->assertEquals('ok', $event->getResponse()->getContent()); + } + + public function testMigratesViaSessionStrategy() + { + $this->createListener(); + $request = new Request(array(), array(), array(), array(), array(), array('HTTP_CONTENT_TYPE' => 'application/json'), '{"username": "dunglas", "password": "foo"}'); + $this->configurePreviousSession($request); + $event = new GetResponseEvent($this->getMockBuilder(KernelInterface::class)->getMock(), $request, KernelInterface::MASTER_REQUEST); + + $sessionStrategy = $this->getMockBuilder('Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface')->getMock(); + $sessionStrategy->expects($this->once()) + ->method('onAuthentication') + ->with($request, $this->isInstanceOf(TokenInterface::class)); + $this->listener->setSessionAuthenticationStrategy($sessionStrategy); + + $this->listener->handle($event); + $this->assertEquals('ok', $event->getResponse()->getContent()); + } + + private function configurePreviousSession(Request $request) + { + $session = $this->getMockBuilder('Symfony\Component\HttpFoundation\Session\SessionInterface')->getMock(); + $session->expects($this->any()) + ->method('getName') + ->willReturn('test_session_name'); + $request->setSession($session); + $request->cookies->set('test_session_name', 'session_cookie_val'); + } } From 697a6a0ae4db1c613b47af2f465746b051351e3e Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 10 Jun 2018 12:33:24 +0200 Subject: [PATCH 085/938] fixed CS --- .../Firewall/UsernamePasswordJsonAuthenticationListener.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php index d83e94d4b2efb..61a14823d999a 100644 --- a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php @@ -190,7 +190,7 @@ private function onFailure(Request $request, AuthenticationException $failed) /** * Call this method if your authentication token is stored to a session. * - * @final since version 3.4 + * @final */ public function setSessionAuthenticationStrategy(SessionAuthenticationStrategyInterface $sessionStrategy) { From 13523ad9854cd733c8db55640b7059e7789e2e2b Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 23 Apr 2018 18:02:04 +0200 Subject: [PATCH 086/938] [Cache] Add stampede protection via probabilistic early expiration --- UPGRADE-4.2.md | 5 ++ UPGRADE-5.0.md | 5 ++ .../Cache/Adapter/AbstractAdapter.php | 21 ++++++- .../Component/Cache/Adapter/ChainAdapter.php | 11 ++-- .../Cache/Adapter/PhpArrayAdapter.php | 6 +- .../Component/Cache/Adapter/ProxyAdapter.php | 53 ++++++++++++++---- .../Cache/Adapter/TagAwareAdapter.php | 4 +- .../Cache/Adapter/TraceableAdapter.php | 4 +- src/Symfony/Component/Cache/CHANGELOG.md | 5 +- .../Component/Cache/CacheInterface.php | 6 +- src/Symfony/Component/Cache/CacheItem.php | 41 ++++++++++++-- .../Cache/Tests/Adapter/AdapterTestCase.php | 36 ++++++++++++ .../Cache/Tests/Adapter/ArrayAdapterTest.php | 1 + .../Cache/Tests/Adapter/ChainAdapterTest.php | 6 +- .../Adapter/NamespacedProxyAdapterTest.php | 7 ++- .../Tests/Adapter/PhpArrayAdapterTest.php | 7 ++- .../Cache/Tests/Adapter/ProxyAdapterTest.php | 7 ++- .../Tests/Adapter/TagAwareAdapterTest.php | 15 +++++ .../Component/Cache/Tests/CacheItemTest.php | 2 +- .../Component/Cache/Traits/GetTrait.php | 55 +++++++++++++++++-- 20 files changed, 254 insertions(+), 43 deletions(-) diff --git a/UPGRADE-4.2.md b/UPGRADE-4.2.md index 7386221a5df9d..743543a1c871a 100644 --- a/UPGRADE-4.2.md +++ b/UPGRADE-4.2.md @@ -1,6 +1,11 @@ UPGRADE FROM 4.1 to 4.2 ======================= +Cache +----- + + * Deprecated `CacheItem::getPreviousTags()`, use `CacheItem::getMetadata()` instead. + Security -------- diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md index 7c4ddf1d8eeb3..4d8447368e9cd 100644 --- a/UPGRADE-5.0.md +++ b/UPGRADE-5.0.md @@ -1,6 +1,11 @@ UPGRADE FROM 4.x to 5.0 ======================= +Cache +----- + + * Removed `CacheItem::getPreviousTags()`, use `CacheItem::getMetadata()` instead. + Config ------ diff --git a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php index 1e246b8790cb4..c6caee6ced4e3 100644 --- a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php @@ -46,9 +46,18 @@ protected function __construct(string $namespace = '', int $defaultLifetime = 0) function ($key, $value, $isHit) use ($defaultLifetime) { $item = new CacheItem(); $item->key = $key; - $item->value = $value; + $item->value = $v = $value; $item->isHit = $isHit; $item->defaultLifetime = $defaultLifetime; + // Detect wrapped values that encode for their expiry and creation duration + // For compactness, these values are packed in the key of an array using + // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F + if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = \key($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) { + $item->value = $v[$k]; + $v = \unpack('Ve/Nc', \substr($k, 1, -1)); + $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET; + $item->metadata[CacheItem::METADATA_CTIME] = $v['c']; + } return $item; }, @@ -64,12 +73,18 @@ function ($deferred, $namespace, &$expiredIds) use ($getId) { foreach ($deferred as $key => $item) { if (null === $item->expiry) { - $byLifetime[0 < $item->defaultLifetime ? $item->defaultLifetime : 0][$getId($key)] = $item->value; + $ttl = 0 < $item->defaultLifetime ? $item->defaultLifetime : 0; } elseif ($item->expiry > $now) { - $byLifetime[$item->expiry - $now][$getId($key)] = $item->value; + $ttl = $item->expiry - $now; } else { $expiredIds[] = $getId($key); + continue; + } + if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) { + unset($metadata[CacheItem::METADATA_TAGS]); } + // For compactness, expiry and creation duration are packed in the key of a array, using magic numbers as separators + $byLifetime[$ttl][$getId($key)] = $metadata ? array("\x9D".pack('VN', (int) $metadata[CacheItem::METADATA_EXPIRY] - CacheItem::METADATA_EXPIRY_OFFSET, $metadata[CacheItem::METADATA_CTIME])."\x5F" => $item->value) : $item->value; } return $byLifetime; diff --git a/src/Symfony/Component/Cache/Adapter/ChainAdapter.php b/src/Symfony/Component/Cache/Adapter/ChainAdapter.php index ea0af87d9f238..57b6cafd0970c 100644 --- a/src/Symfony/Component/Cache/Adapter/ChainAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ChainAdapter.php @@ -64,8 +64,10 @@ function ($sourceItem, $item) use ($defaultLifetime) { $item->value = $sourceItem->value; $item->expiry = $sourceItem->expiry; $item->isHit = $sourceItem->isHit; + $item->metadata = $sourceItem->metadata; $sourceItem->isTaggable = false; + unset($sourceItem->metadata[CacheItem::METADATA_TAGS]); if (0 < $sourceItem->defaultLifetime && $sourceItem->defaultLifetime < $defaultLifetime) { $defaultLifetime = $sourceItem->defaultLifetime; @@ -84,19 +86,20 @@ function ($sourceItem, $item) use ($defaultLifetime) { /** * {@inheritdoc} */ - public function get(string $key, callable $callback) + public function get(string $key, callable $callback, float $beta = null) { $lastItem = null; $i = 0; - $wrap = function (CacheItem $item = null) use ($key, $callback, &$wrap, &$i, &$lastItem) { + $wrap = function (CacheItem $item = null) use ($key, $callback, $beta, &$wrap, &$i, &$lastItem) { $adapter = $this->adapters[$i]; if (isset($this->adapters[++$i])) { $callback = $wrap; + $beta = INF === $beta ? INF : 0; } if ($adapter instanceof CacheInterface) { - $value = $adapter->get($key, $callback); + $value = $adapter->get($key, $callback, $beta); } else { - $value = $this->doGet($adapter, $key, $callback); + $value = $this->doGet($adapter, $key, $callback, $beta ?? 1.0); } if (null !== $item) { ($this->syncItem)($lastItem = $lastItem ?? $item, $item); diff --git a/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php b/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php index bcd322fede1ba..daba071bb7eb6 100644 --- a/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php @@ -83,17 +83,17 @@ public static function create($file, CacheItemPoolInterface $fallbackPool) /** * {@inheritdoc} */ - public function get(string $key, callable $callback) + public function get(string $key, callable $callback, float $beta = null) { if (null === $this->values) { $this->initialize(); } if (null === $value = $this->values[$key] ?? null) { if ($this->pool instanceof CacheInterface) { - return $this->pool->get($key, $callback); + return $this->pool->get($key, $callback, $beta); } - return $this->doGet($this->pool, $key, $callback); + return $this->doGet($this->pool, $key, $callback, $beta ?? 1.0); } if ('N;' === $value) { return null; diff --git a/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php b/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php index b9981f5e64c0c..796dd6c6063fc 100644 --- a/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php @@ -31,6 +31,7 @@ class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterfa private $namespace; private $namespaceLen; private $createCacheItem; + private $setInnerItem; private $poolHash; public function __construct(CacheItemPoolInterface $pool, string $namespace = '', int $defaultLifetime = 0) @@ -43,11 +44,22 @@ public function __construct(CacheItemPoolInterface $pool, string $namespace = '' function ($key, $innerItem) use ($defaultLifetime, $poolHash) { $item = new CacheItem(); $item->key = $key; - $item->value = $innerItem->get(); + $item->value = $v = $innerItem->get(); $item->isHit = $innerItem->isHit(); $item->defaultLifetime = $defaultLifetime; $item->innerItem = $innerItem; $item->poolHash = $poolHash; + // Detect wrapped values that encode for their expiry and creation duration + // For compactness, these values are packed in the key of an array using + // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F + if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = \key($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) { + $item->value = $v[$k]; + $v = \unpack('Ve/Nc', \substr($k, 1, -1)); + $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET; + $item->metadata[CacheItem::METADATA_CTIME] = $v['c']; + } elseif ($innerItem instanceof CacheItem) { + $item->metadata = $innerItem->metadata; + } $innerItem->set(null); return $item; @@ -55,20 +67,43 @@ function ($key, $innerItem) use ($defaultLifetime, $poolHash) { null, CacheItem::class ); + $this->setInnerItem = \Closure::bind( + /** + * @param array $item A CacheItem cast to (array); accessing protected properties requires adding the \0*\0" PHP prefix + */ + function (CacheItemInterface $innerItem, array $item) { + // Tags are stored separately, no need to account for them when considering this item's newly set metadata + if (isset(($metadata = $item["\0*\0newMetadata"])[CacheItem::METADATA_TAGS])) { + unset($metadata[CacheItem::METADATA_TAGS]); + } + if ($metadata) { + // For compactness, expiry and creation duration are packed in the key of a array, using magic numbers as separators + $item["\0*\0value"] = array("\x9D".pack('VN', (int) $metadata[CacheItem::METADATA_EXPIRY] - CacheItem::METADATA_EXPIRY_OFFSET, $metadata[CacheItem::METADATA_CTIME])."\x5F" => $item["\0*\0value"]); + } + $innerItem->set($item["\0*\0value"]); + $innerItem->expiresAt(null !== $item["\0*\0expiry"] ? \DateTime::createFromFormat('U', $item["\0*\0expiry"]) : null); + }, + null, + CacheItem::class + ); } /** * {@inheritdoc} */ - public function get(string $key, callable $callback) + public function get(string $key, callable $callback, float $beta = null) { if (!$this->pool instanceof CacheInterface) { - return $this->doGet($this->pool, $key, $callback); + return $this->doGet($this, $key, $callback, $beta ?? 1.0); } return $this->pool->get($this->getId($key), function ($innerItem) use ($key, $callback) { - return $callback(($this->createCacheItem)($key, $innerItem)); - }); + $item = ($this->createCacheItem)($key, $innerItem); + $item->set($value = $callback($item)); + ($this->setInnerItem)($innerItem, (array) $item); + + return $value; + }, $beta); } /** @@ -164,13 +199,11 @@ private function doSave(CacheItemInterface $item, $method) return false; } $item = (array) $item; - $expiry = $item["\0*\0expiry"]; - if (null === $expiry && 0 < $item["\0*\0defaultLifetime"]) { - $expiry = time() + $item["\0*\0defaultLifetime"]; + if (null === $item["\0*\0expiry"] && 0 < $item["\0*\0defaultLifetime"]) { + $item["\0*\0expiry"] = time() + $item["\0*\0defaultLifetime"]; } $innerItem = $item["\0*\0poolHash"] === $this->poolHash ? $item["\0*\0innerItem"] : $this->pool->getItem($this->namespace.$item["\0*\0key"]); - $innerItem->set($item["\0*\0value"]); - $innerItem->expiresAt(null !== $expiry ? \DateTime::createFromFormat('U', $expiry) : null); + ($this->setInnerItem)($innerItem, $item); return $this->pool->$method($innerItem); } diff --git a/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php index e810f5d00fb0d..f49e97119ad53 100644 --- a/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php @@ -67,7 +67,7 @@ function (CacheItem $item, $key, array &$itemTags) { } if (isset($itemTags[$key])) { foreach ($itemTags[$key] as $tag => $version) { - $item->prevTags[$tag] = $tag; + $item->metadata[CacheItem::METADATA_TAGS][$tag] = $tag; } unset($itemTags[$key]); } else { @@ -84,7 +84,7 @@ function (CacheItem $item, $key, array &$itemTags) { function ($deferred) { $tagsByKey = array(); foreach ($deferred as $key => $item) { - $tagsByKey[$key] = $item->tags; + $tagsByKey[$key] = $item->newMetadata[CacheItem::METADATA_TAGS] ?? array(); } return $tagsByKey; diff --git a/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php b/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php index a0df682d92b69..76db2f66d8386 100644 --- a/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php @@ -37,7 +37,7 @@ public function __construct(AdapterInterface $pool) /** * {@inheritdoc} */ - public function get(string $key, callable $callback) + public function get(string $key, callable $callback, float $beta = null) { if (!$this->pool instanceof CacheInterface) { throw new \BadMethodCallException(sprintf('Cannot call "%s::get()": this class doesn\'t implement "%s".', get_class($this->pool), CacheInterface::class)); @@ -52,7 +52,7 @@ public function get(string $key, callable $callback) $event = $this->start(__FUNCTION__); try { - $value = $this->pool->get($key, $callback); + $value = $this->pool->get($key, $callback, $beta); $event->result[$key] = \is_object($value) ? \get_class($value) : gettype($value); } finally { $event->end = microtime(true); diff --git a/src/Symfony/Component/Cache/CHANGELOG.md b/src/Symfony/Component/Cache/CHANGELOG.md index f6fb43ebe7874..b0f7793a25386 100644 --- a/src/Symfony/Component/Cache/CHANGELOG.md +++ b/src/Symfony/Component/Cache/CHANGELOG.md @@ -4,8 +4,9 @@ CHANGELOG 4.2.0 ----- - * added `CacheInterface`, which should become the preferred way to use a cache + * added `CacheInterface`, which provides stampede protection via probabilistic early expiration and should become the preferred way to use a cache * throw `LogicException` when `CacheItem::tag()` is called on an item coming from a non tag-aware pool + * deprecated `CacheItem::getPreviousTags()`, use `CacheItem::getMetadata()` instead 3.4.0 ----- @@ -19,7 +20,7 @@ CHANGELOG 3.3.0 ----- - * [EXPERIMENTAL] added CacheItem::getPreviousTags() to get bound tags coming from the pool storage if any + * added CacheItem::getPreviousTags() to get bound tags coming from the pool storage if any * added PSR-16 "Simple Cache" implementations for all existing PSR-6 adapters * added Psr6Cache and SimpleCacheAdapter for bidirectional interoperability between PSR-6 and PSR-16 * added MemcachedAdapter (PSR-6) and MemcachedCache (PSR-16) diff --git a/src/Symfony/Component/Cache/CacheInterface.php b/src/Symfony/Component/Cache/CacheInterface.php index 49194d135e9bd..7a149d71a9268 100644 --- a/src/Symfony/Component/Cache/CacheInterface.php +++ b/src/Symfony/Component/Cache/CacheInterface.php @@ -26,8 +26,12 @@ interface CacheInterface { /** * @param callable(CacheItem):mixed $callback Should return the computed value for the given key/item + * @param float|null $beta A float that controls the likeliness of triggering early expiration. + * 0 disables it, INF forces immediate expiration. + * The default (or providing null) is implementation dependent but should + * typically be 1.0, which should provide optimal stampede protection. * * @return mixed The value corresponding to the provided key */ - public function get(string $key, callable $callback); + public function get(string $key, callable $callback, float $beta = null); } diff --git a/src/Symfony/Component/Cache/CacheItem.php b/src/Symfony/Component/Cache/CacheItem.php index 82ad9df68262c..91fdc53164040 100644 --- a/src/Symfony/Component/Cache/CacheItem.php +++ b/src/Symfony/Component/Cache/CacheItem.php @@ -21,13 +21,30 @@ */ final class CacheItem implements CacheItemInterface { + /** + * References the Unix timestamp stating when the item will expire. + */ + const METADATA_EXPIRY = 'expiry'; + + /** + * References the time the item took to be created, in milliseconds. + */ + const METADATA_CTIME = 'ctime'; + + /** + * References the list of tags that were assigned to the item, as string[]. + */ + const METADATA_TAGS = 'tags'; + + private const METADATA_EXPIRY_OFFSET = 1527506807; + protected $key; protected $value; protected $isHit = false; protected $expiry; protected $defaultLifetime; - protected $tags = array(); - protected $prevTags = array(); + protected $metadata = array(); + protected $newMetadata = array(); protected $innerItem; protected $poolHash; protected $isTaggable = false; @@ -121,7 +138,7 @@ public function tag($tags) if (!\is_string($tag)) { throw new InvalidArgumentException(sprintf('Cache tag must be string, "%s" given', is_object($tag) ? get_class($tag) : gettype($tag))); } - if (isset($this->tags[$tag])) { + if (isset($this->newMetadata[self::METADATA_TAGS][$tag])) { continue; } if ('' === $tag) { @@ -130,7 +147,7 @@ public function tag($tags) if (false !== strpbrk($tag, '{}()/\@:')) { throw new InvalidArgumentException(sprintf('Cache tag "%s" contains reserved characters {}()/\@:', $tag)); } - $this->tags[$tag] = $tag; + $this->newMetadata[self::METADATA_TAGS][$tag] = $tag; } return $this; @@ -140,10 +157,24 @@ public function tag($tags) * Returns the list of tags bound to the value coming from the pool storage if any. * * @return array + * + * @deprecated since Symfony 4.2, use the "getMetadata()" method instead. */ public function getPreviousTags() { - return $this->prevTags; + @trigger_error(sprintf('The "%s" method is deprecated since Symfony 4.2, use the "getMetadata()" method instead.', __METHOD__), E_USER_DEPRECATED); + + return $this->metadata[self::METADATA_TAGS] ?? array(); + } + + /** + * Returns a list of metadata info that were saved alongside with the cached value. + * + * See public CacheItem::METADATA_* consts for keys potentially found in the returned array. + */ + public function getMetadata(): array + { + return $this->metadata; } /** diff --git a/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php b/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php index 3c96b731cc565..385c70720901a 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php @@ -45,6 +45,42 @@ public function testGet() $item = $cache->getItem('foo'); $this->assertSame($value, $item->get()); + + $isHit = true; + $this->assertSame($value, $cache->get('foo', function (CacheItem $item) use (&$isHit) { $isHit = false; }, 0)); + $this->assertTrue($isHit); + + $this->assertNull($cache->get('foo', function (CacheItem $item) use (&$isHit, $value) { + $isHit = false; + $this->assertTrue($item->isHit()); + $this->assertSame($value, $item->get()); + }, INF)); + $this->assertFalse($isHit); + } + + public function testGetMetadata() + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $cache = $this->createCachePool(0, __FUNCTION__); + + $cache->deleteItem('foo'); + $cache->get('foo', function ($item) { + $item->expiresAfter(10); + sleep(1); + + return 'bar'; + }); + + $item = $cache->getItem('foo'); + + $expected = array( + CacheItem::METADATA_EXPIRY => 9 + time(), + CacheItem::METADATA_CTIME => 1000, + ); + $this->assertSame($expected, $item->getMetadata()); } public function testDefaultLifeTime() diff --git a/src/Symfony/Component/Cache/Tests/Adapter/ArrayAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/ArrayAdapterTest.php index 725d79015082e..9503501899b33 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/ArrayAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/ArrayAdapterTest.php @@ -19,6 +19,7 @@ class ArrayAdapterTest extends AdapterTestCase { protected $skippedTests = array( + 'testGetMetadata' => 'ArrayAdapter does not keep metadata.', 'testDeferredSaveWithoutCommit' => 'Assumes a shared cache which ArrayAdapter is not.', 'testSaveWithoutExpire' => 'Assumes a shared cache which ArrayAdapter is not.', ); diff --git a/src/Symfony/Component/Cache/Tests/Adapter/ChainAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/ChainAdapterTest.php index 293a90cc86783..0c9969e9275c8 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/ChainAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/ChainAdapterTest.php @@ -24,8 +24,12 @@ */ class ChainAdapterTest extends AdapterTestCase { - public function createCachePool($defaultLifetime = 0) + public function createCachePool($defaultLifetime = 0, $testMethod = null) { + if ('testGetMetadata' === $testMethod) { + return new ChainAdapter(array(new FilesystemAdapter('', $defaultLifetime)), $defaultLifetime); + } + return new ChainAdapter(array(new ArrayAdapter($defaultLifetime), new ExternalAdapter(), new FilesystemAdapter('', $defaultLifetime)), $defaultLifetime); } diff --git a/src/Symfony/Component/Cache/Tests/Adapter/NamespacedProxyAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/NamespacedProxyAdapterTest.php index c2714033385f4..f1ffcbb823fb7 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/NamespacedProxyAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/NamespacedProxyAdapterTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Cache\Tests\Adapter; use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\Cache\Adapter\FilesystemAdapter; use Symfony\Component\Cache\Adapter\ProxyAdapter; /** @@ -19,8 +20,12 @@ */ class NamespacedProxyAdapterTest extends ProxyAdapterTest { - public function createCachePool($defaultLifetime = 0) + public function createCachePool($defaultLifetime = 0, $testMethod = null) { + if ('testGetMetadata' === $testMethod) { + return new ProxyAdapter(new FilesystemAdapter(), 'foo', $defaultLifetime); + } + return new ProxyAdapter(new ArrayAdapter($defaultLifetime), 'foo', $defaultLifetime); } } diff --git a/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterTest.php index 8630b52cf30c9..19c1285af40d5 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Cache\Tests\Adapter; use Psr\Cache\CacheItemInterface; +use Symfony\Component\Cache\Adapter\FilesystemAdapter; use Symfony\Component\Cache\Adapter\NullAdapter; use Symfony\Component\Cache\Adapter\PhpArrayAdapter; @@ -68,8 +69,12 @@ protected function tearDown() } } - public function createCachePool() + public function createCachePool($defaultLifetime = 0, $testMethod = null) { + if ('testGetMetadata' === $testMethod) { + return new PhpArrayAdapter(self::$file, new FilesystemAdapter()); + } + return new PhpArrayAdapterWrapper(self::$file, new NullAdapter()); } diff --git a/src/Symfony/Component/Cache/Tests/Adapter/ProxyAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/ProxyAdapterTest.php index ff4b9d34bcbaf..fbbdac22a8874 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/ProxyAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/ProxyAdapterTest.php @@ -13,6 +13,7 @@ use Psr\Cache\CacheItemInterface; use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\Cache\Adapter\FilesystemAdapter; use Symfony\Component\Cache\Adapter\ProxyAdapter; use Symfony\Component\Cache\CacheItem; @@ -27,8 +28,12 @@ class ProxyAdapterTest extends AdapterTestCase 'testPrune' => 'ProxyAdapter just proxies', ); - public function createCachePool($defaultLifetime = 0) + public function createCachePool($defaultLifetime = 0, $testMethod = null) { + if ('testGetMetadata' === $testMethod) { + return new ProxyAdapter(new FilesystemAdapter(), '', $defaultLifetime); + } + return new ProxyAdapter(new ArrayAdapter(), '', $defaultLifetime); } diff --git a/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAdapterTest.php index 7074299e7ac34..ad37fbef7d25d 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAdapterTest.php @@ -14,6 +14,7 @@ use Symfony\Component\Cache\Adapter\AdapterInterface; use Symfony\Component\Cache\Adapter\FilesystemAdapter; use Symfony\Component\Cache\Adapter\TagAwareAdapter; +use Symfony\Component\Cache\CacheItem; /** * @group time-sensitive @@ -138,6 +139,9 @@ public function testTagItemExpiry() $this->assertFalse($pool->getItem('foo')->isHit()); } + /** + * @group legacy + */ public function testGetPreviousTags() { $pool = $this->createCachePool(); @@ -149,6 +153,17 @@ public function testGetPreviousTags() $this->assertSame(array('foo' => 'foo'), $i->getPreviousTags()); } + public function testGetMetadata() + { + $pool = $this->createCachePool(); + + $i = $pool->getItem('k'); + $pool->save($i->tag('foo')); + + $i = $pool->getItem('k'); + $this->assertSame(array('foo' => 'foo'), $i->getMetadata()[CacheItem::METADATA_TAGS]); + } + public function testPrune() { $cache = new TagAwareAdapter($this->getPruneableMock()); diff --git a/src/Symfony/Component/Cache/Tests/CacheItemTest.php b/src/Symfony/Component/Cache/Tests/CacheItemTest.php index 3a0ea098ad7c1..2572651290cfc 100644 --- a/src/Symfony/Component/Cache/Tests/CacheItemTest.php +++ b/src/Symfony/Component/Cache/Tests/CacheItemTest.php @@ -63,7 +63,7 @@ public function testTag() $this->assertSame($item, $item->tag(array('bar', 'baz'))); call_user_func(\Closure::bind(function () use ($item) { - $this->assertSame(array('foo' => 'foo', 'bar' => 'bar', 'baz' => 'baz'), $item->tags); + $this->assertSame(array('foo' => 'foo', 'bar' => 'bar', 'baz' => 'baz'), $item->newMetadata[CacheItem::METADATA_TAGS]); }, $this, CacheItem::class)); } diff --git a/src/Symfony/Component/Cache/Traits/GetTrait.php b/src/Symfony/Component/Cache/Traits/GetTrait.php index d2a5f92da2e53..c2aef90c389dc 100644 --- a/src/Symfony/Component/Cache/Traits/GetTrait.php +++ b/src/Symfony/Component/Cache/Traits/GetTrait.php @@ -11,9 +11,15 @@ namespace Symfony\Component\Cache\Traits; +use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\CacheItem; /** + * An implementation for CacheInterface that provides stampede protection via probabilistic early expiration. + * + * @see https://en.wikipedia.org/wiki/Cache_stampede + * * @author Nicolas Grekas * * @internal @@ -23,21 +29,58 @@ trait GetTrait /** * {@inheritdoc} */ - public function get(string $key, callable $callback) + public function get(string $key, callable $callback, float $beta = null) { - return $this->doGet($this, $key, $callback); + return $this->doGet($this, $key, $callback, $beta ?? 1.0); } - private function doGet(CacheItemPoolInterface $pool, string $key, callable $callback) + private function doGet(CacheItemPoolInterface $pool, string $key, callable $callback, float $beta) { + $t = 0; $item = $pool->getItem($key); + $recompute = !$item->isHit() || INF === $beta; + + if ($item instanceof CacheItem && 0 < $beta) { + if ($recompute) { + $t = microtime(true); + } else { + $metadata = $item->getMetadata(); + $expiry = $metadata[CacheItem::METADATA_EXPIRY] ?? false; + $ctime = $metadata[CacheItem::METADATA_CTIME] ?? false; + + if ($ctime && $expiry) { + $t = microtime(true); + $recompute = $expiry <= $t - $ctime / 1000 * $beta * log(random_int(1, PHP_INT_MAX) / PHP_INT_MAX); + } + } + if ($recompute) { + // force applying defaultLifetime to expiry + $item->expiresAt(null); + } + } - if ($item->isHit()) { + if (!$recompute) { return $item->get(); } - $pool->save($item->set($value = $callback($item))); + static $save = null; + + if (null === $save) { + $save = \Closure::bind( + function (CacheItemPoolInterface $pool, CacheItemInterface $item, $value, float $startTime) { + if ($item instanceof CacheItem && $startTime && $item->expiry > $endTime = microtime(true)) { + $item->newMetadata[CacheItem::METADATA_EXPIRY] = $item->expiry; + $item->newMetadata[CacheItem::METADATA_CTIME] = 1000 * (int) ($endTime - $startTime); + } + $pool->save($item->set($value)); + + return $value; + }, + null, + CacheItem::class + ); + } - return $value; + return $save($pool, $item, $callback($item), $t); } } From 51381e530a948af3162b4a29fede0843795c35c7 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 7 Jun 2018 22:31:06 +0200 Subject: [PATCH 087/938] [Cache] Unconditionally use PhpFilesAdapter for system pools --- .../FrameworkExtension.php | 1 - .../Resources/config/cache.xml | 8 ++--- .../Cache/Adapter/AbstractAdapter.php | 4 +++ .../Cache/Adapter/PhpArrayAdapter.php | 1 - .../Cache/Adapter/PhpFilesAdapter.php | 5 +-- src/Symfony/Component/Cache/CHANGELOG.md | 1 + .../Component/Cache/Simple/PhpArrayCache.php | 1 - .../Component/Cache/Simple/PhpFilesCache.php | 5 +-- .../Tests/Adapter/MaxIdLengthAdapterTest.php | 2 +- .../Tests/Adapter/PhpFilesAdapterTest.php | 4 --- .../Cache/Tests/Simple/PhpFilesCacheTest.php | 4 --- .../Component/Cache/Traits/AbstractTrait.php | 14 ++++++-- .../Cache/Traits/FilesystemCommonTrait.php | 12 +++++-- .../Component/Cache/Traits/PhpArrayTrait.php | 13 +------ .../Component/Cache/Traits/PhpFilesTrait.php | 34 ++++++++++--------- 15 files changed, 51 insertions(+), 58 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index f15e9ac12c435..b5f4b4d9c366c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -1538,7 +1538,6 @@ private function registerCacheConfiguration(array $config, ContainerBuilder $con { $version = new Parameter('container.build_id'); $container->getDefinition('cache.adapter.apcu')->replaceArgument(2, $version); - $container->getDefinition('cache.adapter.system')->replaceArgument(2, $version); $container->getDefinition('cache.adapter.filesystem')->replaceArgument(2, $config['directory']); if (isset($config['prefix_seed'])) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml index cd4d51e2c3936..4040709c788f8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml @@ -35,15 +35,15 @@ - - + 0 - %kernel.cache_dir%/pools - + + + diff --git a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php index c6caee6ced4e3..6b9f7ed50ddf7 100644 --- a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php @@ -102,9 +102,13 @@ function ($deferred, $namespace, &$expiredIds) use ($getId) { * @param LoggerInterface|null $logger * * @return AdapterInterface + * + * @deprecated since Symfony 4.2 */ public static function createSystemCache($namespace, $defaultLifetime, $version, $directory, LoggerInterface $logger = null) { + @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2.', __CLASS__), E_USER_DEPRECATED); + if (null === self::$apcuSupported) { self::$apcuSupported = ApcuAdapter::isSupported(); } diff --git a/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php b/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php index 0cc791cd5bb88..f72fb8a6f8be0 100644 --- a/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php @@ -43,7 +43,6 @@ public function __construct(string $file, AdapterInterface $fallbackPool) { $this->file = $file; $this->pool = $fallbackPool; - $this->zendDetectUnicode = ini_get('zend.detect_unicode'); $this->createCacheItem = \Closure::bind( function ($key, $value, $isHit) { $item = new CacheItem(); diff --git a/src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php b/src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php index 41879df266571..7c1850662d86d 100644 --- a/src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php @@ -24,14 +24,11 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface */ public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null) { - if (!static::isSupported()) { - throw new CacheException('OPcache is not enabled'); - } + self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time(); parent::__construct('', $defaultLifetime); $this->init($namespace, $directory); $e = new \Exception(); $this->includeHandler = function () use ($e) { throw $e; }; - $this->zendDetectUnicode = ini_get('zend.detect_unicode'); } } diff --git a/src/Symfony/Component/Cache/CHANGELOG.md b/src/Symfony/Component/Cache/CHANGELOG.md index b0f7793a25386..9026c1c95adf3 100644 --- a/src/Symfony/Component/Cache/CHANGELOG.md +++ b/src/Symfony/Component/Cache/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * added `CacheInterface`, which provides stampede protection via probabilistic early expiration and should become the preferred way to use a cache * throw `LogicException` when `CacheItem::tag()` is called on an item coming from a non tag-aware pool * deprecated `CacheItem::getPreviousTags()`, use `CacheItem::getMetadata()` instead + * deprecated the `AbstractAdapter::createSystemCache()` method 3.4.0 ----- diff --git a/src/Symfony/Component/Cache/Simple/PhpArrayCache.php b/src/Symfony/Component/Cache/Simple/PhpArrayCache.php index 64dc776f74a31..5d401be767d78 100644 --- a/src/Symfony/Component/Cache/Simple/PhpArrayCache.php +++ b/src/Symfony/Component/Cache/Simple/PhpArrayCache.php @@ -36,7 +36,6 @@ public function __construct(string $file, CacheInterface $fallbackPool) { $this->file = $file; $this->pool = $fallbackPool; - $this->zendDetectUnicode = ini_get('zend.detect_unicode'); } /** diff --git a/src/Symfony/Component/Cache/Simple/PhpFilesCache.php b/src/Symfony/Component/Cache/Simple/PhpFilesCache.php index 77239c32eda69..3347038bb33aa 100644 --- a/src/Symfony/Component/Cache/Simple/PhpFilesCache.php +++ b/src/Symfony/Component/Cache/Simple/PhpFilesCache.php @@ -24,14 +24,11 @@ class PhpFilesCache extends AbstractCache implements PruneableInterface */ public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null) { - if (!static::isSupported()) { - throw new CacheException('OPcache is not enabled'); - } + self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time(); parent::__construct('', $defaultLifetime); $this->init($namespace, $directory); $e = new \Exception(); $this->includeHandler = function () use ($e) { throw $e; }; - $this->zendDetectUnicode = ini_get('zend.detect_unicode'); } } diff --git a/src/Symfony/Component/Cache/Tests/Adapter/MaxIdLengthAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/MaxIdLengthAdapterTest.php index cf2384c5f37e6..660f5c2b2c1be 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/MaxIdLengthAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/MaxIdLengthAdapterTest.php @@ -26,7 +26,7 @@ public function testLongKey() $cache->expects($this->exactly(2)) ->method('doHave') ->withConsecutive( - array($this->equalTo('----------:0GTYWa9n4ed8vqNlOT2iEr:')), + array($this->equalTo('----------:nWfzGiCgLczv3SSUzXL3kg:')), array($this->equalTo('----------:---------------------------------------')) ); diff --git a/src/Symfony/Component/Cache/Tests/Adapter/PhpFilesAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/PhpFilesAdapterTest.php index 8e93c937f6a65..9fecd9724b029 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/PhpFilesAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/PhpFilesAdapterTest.php @@ -25,10 +25,6 @@ class PhpFilesAdapterTest extends AdapterTestCase public function createCachePool() { - if (!PhpFilesAdapter::isSupported()) { - $this->markTestSkipped('OPcache extension is not enabled.'); - } - return new PhpFilesAdapter('sf-cache'); } diff --git a/src/Symfony/Component/Cache/Tests/Simple/PhpFilesCacheTest.php b/src/Symfony/Component/Cache/Tests/Simple/PhpFilesCacheTest.php index 7a402682ae247..38e5ee90b1221 100644 --- a/src/Symfony/Component/Cache/Tests/Simple/PhpFilesCacheTest.php +++ b/src/Symfony/Component/Cache/Tests/Simple/PhpFilesCacheTest.php @@ -25,10 +25,6 @@ class PhpFilesCacheTest extends CacheTestCase public function createSimpleCache() { - if (!PhpFilesCache::isSupported()) { - $this->markTestSkipped('OPcache extension is not enabled.'); - } - return new PhpFilesCache('sf-cache'); } diff --git a/src/Symfony/Component/Cache/Traits/AbstractTrait.php b/src/Symfony/Component/Cache/Traits/AbstractTrait.php index 92999a2f3c34d..60a9e77abac48 100644 --- a/src/Symfony/Component/Cache/Traits/AbstractTrait.php +++ b/src/Symfony/Component/Cache/Traits/AbstractTrait.php @@ -27,6 +27,7 @@ trait AbstractTrait private $namespaceVersion = ''; private $versioningIsEnabled = false; private $deferred = array(); + private $ids = array(); /** * @var int|null The maximum length to enforce for identifiers or null when no limit applies @@ -198,6 +199,7 @@ public function reset() $this->commit(); } $this->namespaceVersion = ''; + $this->ids = array(); } /** @@ -229,8 +231,6 @@ protected static function unserialize($value) private function getId($key) { - CacheItem::validateKey($key); - if ($this->versioningIsEnabled && '' === $this->namespaceVersion) { $this->namespaceVersion = '1:'; foreach ($this->doFetch(array('@'.$this->namespace)) as $v) { @@ -238,11 +238,19 @@ private function getId($key) } } + if (\is_string($key) && isset($this->ids[$key])) { + return $this->namespace.$this->namespaceVersion.$this->ids[$key]; + } + CacheItem::validateKey($key); + $this->ids[$key] = $key; + if (null === $this->maxIdLength) { return $this->namespace.$this->namespaceVersion.$key; } if (\strlen($id = $this->namespace.$this->namespaceVersion.$key) > $this->maxIdLength) { - $id = $this->namespace.$this->namespaceVersion.substr_replace(base64_encode(hash('sha256', $key, true)), ':', -22); + // Use MD5 to favor speed over security, which is not an issue here + $this->ids[$key] = $id = substr_replace(base64_encode(hash('md5', $key, true)), ':', -2); + $id = $this->namespace.$this->namespaceVersion.$id; } return $id; diff --git a/src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php b/src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php index b0f495e4d4c51..2f1764c425df2 100644 --- a/src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php +++ b/src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php @@ -56,7 +56,7 @@ protected function doClear($namespace) $ok = true; foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS)) as $file) { - $ok = ($file->isDir() || @unlink($file) || !file_exists($file)) && $ok; + $ok = ($file->isDir() || $this->doUnlink($file) || !file_exists($file)) && $ok; } return $ok; @@ -71,12 +71,17 @@ protected function doDelete(array $ids) foreach ($ids as $id) { $file = $this->getFile($id); - $ok = (!file_exists($file) || @unlink($file) || !file_exists($file)) && $ok; + $ok = (!file_exists($file) || $this->doUnlink($file) || !file_exists($file)) && $ok; } return $ok; } + protected function doUnlink($file) + { + return @unlink($file); + } + private function write($file, $data, $expiresAt = null) { set_error_handler(__CLASS__.'::throwError'); @@ -98,7 +103,8 @@ private function write($file, $data, $expiresAt = null) private function getFile($id, $mkdir = false) { - $hash = str_replace('/', '-', base64_encode(hash('sha256', static::class.$id, true))); + // Use MD5 to favor speed over security, which is not an issue here + $hash = str_replace('/', '-', base64_encode(hash('md5', static::class.$id, true))); $dir = $this->directory.strtoupper($hash[0].DIRECTORY_SEPARATOR.$hash[1].DIRECTORY_SEPARATOR); if ($mkdir && !file_exists($dir)) { diff --git a/src/Symfony/Component/Cache/Traits/PhpArrayTrait.php b/src/Symfony/Component/Cache/Traits/PhpArrayTrait.php index e90492b3a14d3..837d429854fc5 100644 --- a/src/Symfony/Component/Cache/Traits/PhpArrayTrait.php +++ b/src/Symfony/Component/Cache/Traits/PhpArrayTrait.php @@ -26,7 +26,6 @@ trait PhpArrayTrait private $file; private $values; - private $zendDetectUnicode; /** * Store an array of cached values. @@ -98,7 +97,6 @@ public function warmUp(array $values) } $dump .= "\n);\n"; - $dump = str_replace("' . \"\\0\" . '", "\0", $dump); $tmpFile = uniqid($this->file, true); @@ -128,15 +126,6 @@ public function clear() */ private function initialize() { - if ($this->zendDetectUnicode) { - $zmb = ini_set('zend.detect_unicode', 0); - } - try { - $this->values = file_exists($this->file) ? (include $this->file ?: array()) : array(); - } finally { - if ($this->zendDetectUnicode) { - ini_set('zend.detect_unicode', $zmb); - } - } + $this->values = file_exists($this->file) ? (include $this->file ?: array()) : array(); } } diff --git a/src/Symfony/Component/Cache/Traits/PhpFilesTrait.php b/src/Symfony/Component/Cache/Traits/PhpFilesTrait.php index 32bbeb71237e2..2c0ff3aef1577 100644 --- a/src/Symfony/Component/Cache/Traits/PhpFilesTrait.php +++ b/src/Symfony/Component/Cache/Traits/PhpFilesTrait.php @@ -26,11 +26,14 @@ trait PhpFilesTrait use FilesystemCommonTrait; private $includeHandler; - private $zendDetectUnicode; + + private static $startTime; public static function isSupported() { - return function_exists('opcache_invalidate') && ini_get('opcache.enable'); + self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time(); + + return \function_exists('opcache_invalidate') && ini_get('opcache.enable') && ('cli' !== \PHP_SAPI || ini_get('opcache.enable_cli')); } /** @@ -40,7 +43,6 @@ public function prune() { $time = time(); $pruned = true; - $allowCompile = 'cli' !== PHP_SAPI || ini_get('opcache.enable_cli'); set_error_handler($this->includeHandler); try { @@ -48,11 +50,7 @@ public function prune() list($expiresAt) = include $file; if ($time >= $expiresAt) { - $pruned = @unlink($file) && !file_exists($file) && $pruned; - - if ($allowCompile) { - @opcache_invalidate($file, true); - } + $pruned = $this->doUnlink($file) && !file_exists($file) && $pruned; } } } finally { @@ -70,9 +68,6 @@ protected function doFetch(array $ids) $values = array(); $now = time(); - if ($this->zendDetectUnicode) { - $zmb = ini_set('zend.detect_unicode', 0); - } set_error_handler($this->includeHandler); try { foreach ($ids as $id) { @@ -88,9 +83,6 @@ protected function doFetch(array $ids) } } finally { restore_error_handler(); - if ($this->zendDetectUnicode) { - ini_set('zend.detect_unicode', $zmb); - } } foreach ($values as $id => $value) { @@ -119,7 +111,7 @@ protected function doSave(array $values, $lifetime) { $ok = true; $data = array($lifetime ? time() + $lifetime : PHP_INT_MAX, ''); - $allowCompile = 'cli' !== PHP_SAPI || ini_get('opcache.enable_cli'); + $allowCompile = self::isSupported(); foreach ($values as $key => $value) { if (null === $value || \is_object($value)) { @@ -142,7 +134,8 @@ protected function doSave(array $values, $lifetime) $data[1] = $value; $file = $this->getFile($key, true); - $ok = $this->write($file, 'write($file, ' Date: Tue, 24 Apr 2018 14:56:46 +0200 Subject: [PATCH 088/938] [Cache] Use sub-second accuracy for internal expiry calculations --- .../Component/Cache/Adapter/AbstractAdapter.php | 8 +++----- src/Symfony/Component/Cache/Adapter/ArrayAdapter.php | 6 +++--- src/Symfony/Component/Cache/Adapter/ProxyAdapter.php | 4 ++-- src/Symfony/Component/Cache/CHANGELOG.md | 1 + src/Symfony/Component/Cache/CacheItem.php | 10 +++++----- src/Symfony/Component/Cache/Simple/ArrayCache.php | 4 ++-- .../Component/Cache/Tests/Adapter/AdapterTestCase.php | 4 ++-- .../Component/Cache/Tests/Fixtures/ArrayCache.php | 4 ++-- src/Symfony/Component/Cache/Traits/ArrayTrait.php | 4 ++-- 9 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php index 6b9f7ed50ddf7..b6e493916b860 100644 --- a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php @@ -68,15 +68,13 @@ function ($key, $value, $isHit) use ($defaultLifetime) { $this->mergeByLifetime = \Closure::bind( function ($deferred, $namespace, &$expiredIds) use ($getId) { $byLifetime = array(); - $now = time(); + $now = microtime(true); $expiredIds = array(); foreach ($deferred as $key => $item) { if (null === $item->expiry) { $ttl = 0 < $item->defaultLifetime ? $item->defaultLifetime : 0; - } elseif ($item->expiry > $now) { - $ttl = $item->expiry - $now; - } else { + } elseif (0 >= $ttl = (int) ($item->expiry - $now)) { $expiredIds[] = $getId($key); continue; } @@ -107,7 +105,7 @@ function ($deferred, $namespace, &$expiredIds) use ($getId) { */ public static function createSystemCache($namespace, $defaultLifetime, $version, $directory, LoggerInterface $logger = null) { - @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2.', __CLASS__), E_USER_DEPRECATED); + @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2.', __METHOD__), E_USER_DEPRECATED); if (null === self::$apcuSupported) { self::$apcuSupported = ApcuAdapter::isSupported(); diff --git a/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php b/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php index 17f2beaf0fe75..07fc198115a50 100644 --- a/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php @@ -87,7 +87,7 @@ public function getItems(array $keys = array()) CacheItem::validateKey($key); } - return $this->generateItems($keys, time(), $this->createCacheItem); + return $this->generateItems($keys, microtime(true), $this->createCacheItem); } /** @@ -115,7 +115,7 @@ public function save(CacheItemInterface $item) $value = $item["\0*\0value"]; $expiry = $item["\0*\0expiry"]; - if (null !== $expiry && $expiry <= time()) { + if (null !== $expiry && $expiry <= microtime(true)) { $this->deleteItem($key); return true; @@ -131,7 +131,7 @@ public function save(CacheItemInterface $item) } } if (null === $expiry && 0 < $item["\0*\0defaultLifetime"]) { - $expiry = time() + $item["\0*\0defaultLifetime"]; + $expiry = microtime(true) + $item["\0*\0defaultLifetime"]; } $this->values[$key] = $value; diff --git a/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php b/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php index 796dd6c6063fc..ddb533e0a95d7 100644 --- a/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php @@ -81,7 +81,7 @@ function (CacheItemInterface $innerItem, array $item) { $item["\0*\0value"] = array("\x9D".pack('VN', (int) $metadata[CacheItem::METADATA_EXPIRY] - CacheItem::METADATA_EXPIRY_OFFSET, $metadata[CacheItem::METADATA_CTIME])."\x5F" => $item["\0*\0value"]); } $innerItem->set($item["\0*\0value"]); - $innerItem->expiresAt(null !== $item["\0*\0expiry"] ? \DateTime::createFromFormat('U', $item["\0*\0expiry"]) : null); + $innerItem->expiresAt(null !== $item["\0*\0expiry"] ? \DateTime::createFromFormat('U.u', sprintf('%.6f', $item["\0*\0expiry"])) : null); }, null, CacheItem::class @@ -200,7 +200,7 @@ private function doSave(CacheItemInterface $item, $method) } $item = (array) $item; if (null === $item["\0*\0expiry"] && 0 < $item["\0*\0defaultLifetime"]) { - $item["\0*\0expiry"] = time() + $item["\0*\0defaultLifetime"]; + $item["\0*\0expiry"] = microtime(true) + $item["\0*\0defaultLifetime"]; } $innerItem = $item["\0*\0poolHash"] === $this->poolHash ? $item["\0*\0innerItem"] : $this->pool->getItem($this->namespace.$item["\0*\0key"]); ($this->setInnerItem)($innerItem, $item); diff --git a/src/Symfony/Component/Cache/CHANGELOG.md b/src/Symfony/Component/Cache/CHANGELOG.md index 9026c1c95adf3..2288db8cfebdb 100644 --- a/src/Symfony/Component/Cache/CHANGELOG.md +++ b/src/Symfony/Component/Cache/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG ----- * added `CacheInterface`, which provides stampede protection via probabilistic early expiration and should become the preferred way to use a cache + * added sub-second expiry accuracy for backends that support it * throw `LogicException` when `CacheItem::tag()` is called on an item coming from a non tag-aware pool * deprecated `CacheItem::getPreviousTags()`, use `CacheItem::getMetadata()` instead * deprecated the `AbstractAdapter::createSystemCache()` method diff --git a/src/Symfony/Component/Cache/CacheItem.php b/src/Symfony/Component/Cache/CacheItem.php index 91fdc53164040..d762539203df6 100644 --- a/src/Symfony/Component/Cache/CacheItem.php +++ b/src/Symfony/Component/Cache/CacheItem.php @@ -89,9 +89,9 @@ public function set($value) public function expiresAt($expiration) { if (null === $expiration) { - $this->expiry = $this->defaultLifetime > 0 ? time() + $this->defaultLifetime : null; + $this->expiry = $this->defaultLifetime > 0 ? microtime(true) + $this->defaultLifetime : null; } elseif ($expiration instanceof \DateTimeInterface) { - $this->expiry = (int) $expiration->format('U'); + $this->expiry = (float) $expiration->format('U.u'); } else { throw new InvalidArgumentException(sprintf('Expiration date must implement DateTimeInterface or be null, "%s" given', is_object($expiration) ? get_class($expiration) : gettype($expiration))); } @@ -105,11 +105,11 @@ public function expiresAt($expiration) public function expiresAfter($time) { if (null === $time) { - $this->expiry = $this->defaultLifetime > 0 ? time() + $this->defaultLifetime : null; + $this->expiry = $this->defaultLifetime > 0 ? microtime(true) + $this->defaultLifetime : null; } elseif ($time instanceof \DateInterval) { - $this->expiry = (int) \DateTime::createFromFormat('U', time())->add($time)->format('U'); + $this->expiry = microtime(true) + \DateTime::createFromFormat('U', 0)->add($time)->format('U.u'); } elseif (\is_int($time)) { - $this->expiry = $time + time(); + $this->expiry = $time + microtime(true); } else { throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given', is_object($time) ? get_class($time) : gettype($time))); } diff --git a/src/Symfony/Component/Cache/Simple/ArrayCache.php b/src/Symfony/Component/Cache/Simple/ArrayCache.php index d1ef583125b8b..6cef8b6e745c4 100644 --- a/src/Symfony/Component/Cache/Simple/ArrayCache.php +++ b/src/Symfony/Component/Cache/Simple/ArrayCache.php @@ -64,7 +64,7 @@ public function getMultiple($keys, $default = null) CacheItem::validateKey($key); } - return $this->generateItems($keys, time(), function ($k, $v, $hit) use ($default) { return $hit ? $v : $default; }); + return $this->generateItems($keys, microtime(true), function ($k, $v, $hit) use ($default) { return $hit ? $v : $default; }); } /** @@ -121,7 +121,7 @@ public function setMultiple($values, $ttl = null) } } } - $expiry = 0 < $ttl ? time() + $ttl : PHP_INT_MAX; + $expiry = 0 < $ttl ? microtime(true) + $ttl : PHP_INT_MAX; foreach ($valuesArray as $key => $value) { $this->values[$key] = $value; diff --git a/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php b/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php index 385c70720901a..72d143e3c039c 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php @@ -77,10 +77,10 @@ public function testGetMetadata() $item = $cache->getItem('foo'); $expected = array( - CacheItem::METADATA_EXPIRY => 9 + time(), + CacheItem::METADATA_EXPIRY => 9.5 + time(), CacheItem::METADATA_CTIME => 1000, ); - $this->assertSame($expected, $item->getMetadata()); + $this->assertEquals($expected, $item->getMetadata(), 'Item metadata should embed expiry and ctime.', .6); } public function testDefaultLifeTime() diff --git a/src/Symfony/Component/Cache/Tests/Fixtures/ArrayCache.php b/src/Symfony/Component/Cache/Tests/Fixtures/ArrayCache.php index 1a6157e822117..27fb82de0186e 100644 --- a/src/Symfony/Component/Cache/Tests/Fixtures/ArrayCache.php +++ b/src/Symfony/Component/Cache/Tests/Fixtures/ArrayCache.php @@ -21,12 +21,12 @@ protected function doContains($id) $expiry = $this->data[$id][1]; - return !$expiry || time() <= $expiry || !$this->doDelete($id); + return !$expiry || microtime(true) < $expiry || !$this->doDelete($id); } protected function doSave($id, $data, $lifeTime = 0) { - $this->data[$id] = array($data, $lifeTime ? time() + $lifeTime : false); + $this->data[$id] = array($data, $lifeTime ? microtime(true) + $lifeTime : false); return true; } diff --git a/src/Symfony/Component/Cache/Traits/ArrayTrait.php b/src/Symfony/Component/Cache/Traits/ArrayTrait.php index b7d2ad6d6299c..86ad17e9c52e6 100644 --- a/src/Symfony/Component/Cache/Traits/ArrayTrait.php +++ b/src/Symfony/Component/Cache/Traits/ArrayTrait.php @@ -44,7 +44,7 @@ public function hasItem($key) { CacheItem::validateKey($key); - return isset($this->expiries[$key]) && ($this->expiries[$key] >= time() || !$this->deleteItem($key)); + return isset($this->expiries[$key]) && ($this->expiries[$key] > microtime(true) || !$this->deleteItem($key)); } /** @@ -81,7 +81,7 @@ private function generateItems(array $keys, $now, $f) { foreach ($keys as $i => $key) { try { - if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] >= $now || !$this->deleteItem($key))) { + if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] > $now || !$this->deleteItem($key))) { $this->values[$key] = $value = null; } elseif (!$this->storeSerialized) { $value = $this->values[$key]; From 68729cc68abc0c8088868b6426cd8334af7a4cc5 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 11 Jun 2018 14:15:46 +0200 Subject: [PATCH 089/938] [Cache] Fix expiry comparisons in array-based pools --- src/Symfony/Component/Cache/Tests/Fixtures/ArrayCache.php | 2 +- src/Symfony/Component/Cache/Traits/ArrayTrait.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/Cache/Tests/Fixtures/ArrayCache.php b/src/Symfony/Component/Cache/Tests/Fixtures/ArrayCache.php index 1a6157e822117..7cdcafd8e5453 100644 --- a/src/Symfony/Component/Cache/Tests/Fixtures/ArrayCache.php +++ b/src/Symfony/Component/Cache/Tests/Fixtures/ArrayCache.php @@ -21,7 +21,7 @@ protected function doContains($id) $expiry = $this->data[$id][1]; - return !$expiry || time() <= $expiry || !$this->doDelete($id); + return !$expiry || time() < $expiry || !$this->doDelete($id); } protected function doSave($id, $data, $lifeTime = 0) diff --git a/src/Symfony/Component/Cache/Traits/ArrayTrait.php b/src/Symfony/Component/Cache/Traits/ArrayTrait.php index b7d2ad6d6299c..88385ed480f34 100644 --- a/src/Symfony/Component/Cache/Traits/ArrayTrait.php +++ b/src/Symfony/Component/Cache/Traits/ArrayTrait.php @@ -44,7 +44,7 @@ public function hasItem($key) { CacheItem::validateKey($key); - return isset($this->expiries[$key]) && ($this->expiries[$key] >= time() || !$this->deleteItem($key)); + return isset($this->expiries[$key]) && ($this->expiries[$key] > time() || !$this->deleteItem($key)); } /** @@ -81,7 +81,7 @@ private function generateItems(array $keys, $now, $f) { foreach ($keys as $i => $key) { try { - if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] >= $now || !$this->deleteItem($key))) { + if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] > $now || !$this->deleteItem($key))) { $this->values[$key] = $value = null; } elseif (!$this->storeSerialized) { $value = $this->values[$key]; From 8b72a6cb2073c0e239ed0cd87f70e8cf2d3c1720 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 11 Jun 2018 15:11:40 +0200 Subject: [PATCH 090/938] fixed CS --- .../Bundle/FrameworkBundle/Routing/DelegatingLoader.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.php b/src/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.php index 049887ba35e8b..0755cc161a3f0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.php @@ -87,7 +87,7 @@ public function load($resource, $type = null) try { $controller = $this->parser->parse($controller, false); - @trigger_error(sprintf('Referencing controllers with %s is deprecated since Symfony 4.1. Use %s instead.', $deprecatedNotation, $controller), E_USER_DEPRECATED); + @trigger_error(sprintf('Referencing controllers with %s is deprecated since Symfony 4.1, use "%s" instead.', $deprecatedNotation, $controller), E_USER_DEPRECATED); } catch (\InvalidArgumentException $e) { // unable to optimize unknown notation } @@ -95,7 +95,7 @@ public function load($resource, $type = null) if (1 === substr_count($controller, ':')) { $nonDeprecatedNotation = str_replace(':', '::', $controller); - @trigger_error(sprintf('Referencing controllers with a single colon is deprecated since Symfony 4.1. Use %s instead.', $nonDeprecatedNotation), E_USER_DEPRECATED); + @trigger_error(sprintf('Referencing controllers with a single colon is deprecated since Symfony 4.1, use "%s" instead.', $nonDeprecatedNotation), E_USER_DEPRECATED); } $route->setDefault('_controller', $controller); From 14bbcdb4965b4cc0d0d1e78b363e82e2a25514e6 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 11 Jun 2018 15:18:57 +0200 Subject: [PATCH 091/938] fix deps --- src/Symfony/Bundle/SecurityBundle/composer.json | 2 +- src/Symfony/Component/Security/Http/composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index ebf39d1beea59..fa304be517a22 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -18,7 +18,7 @@ "require": { "php": "^5.5.9|>=7.0.8", "ext-xml": "*", - "symfony/security": "~3.4.12|^4.0.12|^4.1.1", + "symfony/security": "~3.4.12|~4.0.12|^4.1.1", "symfony/dependency-injection": "^3.4.3|^4.0.3", "symfony/http-kernel": "~3.4|~4.0", "symfony/polyfill-php70": "~1.0" diff --git a/src/Symfony/Component/Security/Http/composer.json b/src/Symfony/Component/Security/Http/composer.json index 35695079b95bf..9ede332facfa0 100644 --- a/src/Symfony/Component/Security/Http/composer.json +++ b/src/Symfony/Component/Security/Http/composer.json @@ -27,7 +27,7 @@ }, "require-dev": { "symfony/routing": "~2.8|~3.0|~4.0", - "symfony/security-csrf": "^2.8.41|^3.3.17|^3.4.11|^4.0.11", + "symfony/security-csrf": "^2.8.41|~3.3.17|~3.4.11|^4.0.11", "psr/log": "~1.0" }, "conflict": { From 24b6848ea7e3f207fea6750303039684dc0828a5 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 11 Jun 2018 15:57:31 +0200 Subject: [PATCH 092/938] [Serializer] fix CS --- .../AbstractObjectNormalizerTest.php | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php index b1d40dcee3e1d..25f4c007d13f0 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -13,6 +13,8 @@ use Doctrine\Common\Annotations\AnnotationReader; use PHPUnit\Framework\TestCase; +use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; +use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; @@ -118,17 +120,17 @@ public function testDenormalizeCollectionDecodedFromXmlWithTwoChildren() private function getDenormalizerForDummyCollection() { - $extractor = $this->getMockBuilder('Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor')->getMock(); + $extractor = $this->getMockBuilder(PhpDocExtractor::class)->getMock(); $extractor->method('getTypes') ->will($this->onConsecutiveCalls( array( - new \Symfony\Component\PropertyInfo\Type( + new Type( 'array', false, null, true, - new \Symfony\Component\PropertyInfo\Type('int'), - new \Symfony\Component\PropertyInfo\Type('object', false, DummyChild::class) + new Type('int'), + new Type('object', false, DummyChild::class) ), ), null @@ -209,13 +211,12 @@ class DummyChild public $bar; } -class SerializerCollectionDummy implements \Symfony\Component\Serializer\SerializerInterface, \Symfony\Component\Serializer\Normalizer\DenormalizerInterface +class SerializerCollectionDummy implements SerializerInterface, DenormalizerInterface { - /** @var \Symfony\Component\Serializer\Normalizer\DenormalizerInterface */ private $normalizers; /** - * @param $normalizers + * @param DenormalizerInterface[] $normalizers */ public function __construct($normalizers) { @@ -233,7 +234,7 @@ public function deserialize($data, $type, $format, array $context = array()) public function denormalize($data, $type, $format = null, array $context = array()) { foreach ($this->normalizers as $normalizer) { - if ($normalizer instanceof \Symfony\Component\Serializer\Normalizer\DenormalizerInterface && $normalizer->supportsDenormalization($data, $type, $format, $context)) { + if ($normalizer instanceof DenormalizerInterface && $normalizer->supportsDenormalization($data, $type, $format, $context)) { return $normalizer->denormalize($data, $type, $format, $context); } } @@ -245,7 +246,7 @@ public function supportsDenormalization($data, $type, $format = null) } } -class AbstractObjectNormalizerCollectionDummy extends \Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer +class AbstractObjectNormalizerCollectionDummy extends AbstractObjectNormalizer { protected function extractAttributes($object, $format = null, array $context = array()) { From fc666f0d80bcb575b7de2a0db151976d9a8f1e68 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 11 Jun 2018 16:29:01 +0200 Subject: [PATCH 093/938] fix typo --- .../FrameworkBundle/Tests/Routing/DelegatingLoaderTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/DelegatingLoaderTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/DelegatingLoaderTest.php index 5f08cce0ab860..3a465275175a2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/DelegatingLoaderTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/DelegatingLoaderTest.php @@ -24,8 +24,8 @@ public function testConstructorApi() /** * @group legacy - * @expectedDeprecation Referencing controllers with foo:bar:baz is deprecated since Symfony 4.1. Use some_parsed::controller instead. - * @expectedDeprecation Referencing controllers with a single colon is deprecated since Symfony 4.1. Use foo::baz instead. + * @expectedDeprecation Referencing controllers with foo:bar:baz is deprecated since Symfony 4.1, use some_parsed::controller instead. + * @expectedDeprecation Referencing controllers with a single colon is deprecated since Symfony 4.1, use foo::baz instead. */ public function testLoad() { From 413af69e1d93d551618114731e9ec1ee49d7f3bd Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 11 Jun 2018 16:41:42 +0200 Subject: [PATCH 094/938] fix typo --- .../FrameworkBundle/Tests/Routing/DelegatingLoaderTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/DelegatingLoaderTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/DelegatingLoaderTest.php index 3a465275175a2..a0ad94b33e02e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/DelegatingLoaderTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/DelegatingLoaderTest.php @@ -24,8 +24,8 @@ public function testConstructorApi() /** * @group legacy - * @expectedDeprecation Referencing controllers with foo:bar:baz is deprecated since Symfony 4.1, use some_parsed::controller instead. - * @expectedDeprecation Referencing controllers with a single colon is deprecated since Symfony 4.1, use foo::baz instead. + * @expectedDeprecation Referencing controllers with foo:bar:baz is deprecated since Symfony 4.1, use "some_parsed::controller" instead. + * @expectedDeprecation Referencing controllers with a single colon is deprecated since Symfony 4.1, use "foo::baz" instead. */ public function testLoad() { From a0f78a5e0b64205c7b527afff218a8b21216794d Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Tue, 12 Jun 2018 14:15:08 +0200 Subject: [PATCH 095/938] Avoid calling eval when there is no script embedded in the toolbar --- .../Resources/views/Profiler/base_js.html.twig | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig index 0c5967b4e75ce..fb266c554bf9e 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig @@ -419,9 +419,10 @@ function(xhr, el) { /* Evaluate in global scope scripts embedded inside the toolbar */ - eval.call({}, ([].slice.call(el.querySelectorAll('script')).map(function (script) { - return script.firstChild.nodeValue; - }).join(';\n'))); + var i, scripts = [].slice.call(el.querySelectorAll('script')); + for (i = 0; i < scripts.length; ++i) { + eval.call({}, scripts[i].firstChild.nodeValue); + } el.style.display = -1 !== xhr.responseText.indexOf('sf-toolbarreset') ? 'block' : 'none'; @@ -440,7 +441,7 @@ } /* Handle toolbar-info position */ - var i, toolbarBlocks = [].slice.call(el.querySelectorAll('.sf-toolbar-block')); + var toolbarBlocks = [].slice.call(el.querySelectorAll('.sf-toolbar-block')); for (i = 0; i < toolbarBlocks.length; ++i) { toolbarBlocks[i].onmouseover = function () { var toolbarInfo = this.querySelectorAll('.sf-toolbar-info')[0]; From 2c0ac93e259c061a9e0b59003424ae28f0ec33e9 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Mon, 11 Jun 2018 18:23:43 -0400 Subject: [PATCH 096/938] Fix bad method call with guard authentication + session migration --- .../Factory/GuardAuthenticationFactory.php | 1 - .../DependencyInjection/SecurityExtension.php | 5 ++++ .../SecurityBundle/Resources/config/guard.xml | 4 +++ .../SecurityExtensionTest.php | 27 +++++++++++++++++++ .../Firewall/GuardAuthenticationListener.php | 2 +- .../Guard/GuardAuthenticatorHandler.php | 21 ++++++++++----- .../Tests/GuardAuthenticatorHandlerTest.php | 12 +++++++++ 7 files changed, 64 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php index bd49cbc932083..533560d6d986d 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php @@ -77,7 +77,6 @@ public function create(ContainerBuilder $container, $id, $config, $userProvider, $listener = $container->setDefinition($listenerId, new DefinitionDecorator('security.authentication.listener.guard')); $listener->replaceArgument(2, $id); $listener->replaceArgument(3, $authenticatorReferences); - $listener->addMethodCall('setSessionAuthenticationStrategy', array(new Reference('security.authentication.session_strategy.'.$id))); // determine the entryPointId to use $entryPointId = $this->determineEntryPoint($defaultEntryPoint, $config); diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 5138eff36719e..0b671d96914d3 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -39,6 +39,7 @@ class SecurityExtension extends Extension private $factories = array(); private $userProviderFactories = array(); private $expressionLanguage; + private $statelessFirewallKeys = array(); public function __construct() { @@ -89,6 +90,9 @@ public function load(array $configs, ContainerBuilder $container) $this->createAuthorization($config, $container); $this->createRoleHierarchy($config, $container); + $container->getDefinition('security.authentication.guard_handler') + ->replaceArgument(2, $this->statelessFirewallKeys); + if ($config['encoders']) { $this->createEncoders($config['encoders'], $container); } @@ -287,6 +291,7 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a $listeners[] = new Reference($this->createContextListener($container, $contextKey)); $sessionStrategyId = 'security.authentication.session_strategy'; } else { + $this->statelessFirewallKeys[] = $id; $sessionStrategyId = 'security.authentication.session_strategy_noop'; } $container->setAlias(new Alias('security.authentication.session_strategy.'.$id, false), $sessionStrategyId); diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/guard.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/guard.xml index 80318c243a970..a4e57be0b0048 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/guard.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/guard.xml @@ -10,6 +10,10 @@ > + + + + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php index 72ef2e0c3ed56..6c6b26c48f94d 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php @@ -119,6 +119,33 @@ public function testDisableRoleHierarchyVoter() $this->assertFalse($container->hasDefinition('security.access.role_hierarchy_voter')); } + public function testGuardHandlerIsPassedStatelessFirewalls() + { + $container = $this->getRawContainer(); + + $container->loadFromExtension('security', array( + 'providers' => array( + 'default' => array('id' => 'foo'), + ), + + 'firewalls' => array( + 'some_firewall' => array( + 'pattern' => '^/admin', + 'http_basic' => null, + ), + 'stateless_firewall' => array( + 'pattern' => '/.*', + 'stateless' => true, + 'http_basic' => null, + ), + ), + )); + + $container->compile(); + $definition = $container->getDefinition('security.authentication.guard_handler'); + $this->assertSame(array('stateless_firewall'), $definition->getArgument(2)); + } + protected function getRawContainer() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php index ed0a36e9dca5d..8184406fc696d 100644 --- a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php +++ b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php @@ -117,7 +117,7 @@ private function executeGuardAuthenticator($uniqueGuardKey, GuardAuthenticatorIn } // sets the token on the token storage, etc - $this->guardHandler->authenticateWithToken($token, $request); + $this->guardHandler->authenticateWithToken($token, $request, $this->providerKey); } catch (AuthenticationException $e) { // oh no! Authentication failed! diff --git a/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php b/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php index 7b340a2601cee..d715c805824e9 100644 --- a/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php +++ b/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php @@ -35,19 +35,28 @@ class GuardAuthenticatorHandler private $tokenStorage; private $dispatcher; private $sessionStrategy; + private $statelessProviderKeys; - public function __construct(TokenStorageInterface $tokenStorage, EventDispatcherInterface $eventDispatcher = null) + /** + * @param array $statelessProviderKeys An array of provider/firewall keys that are "stateless" and so do not need the session migrated on success + */ + public function __construct(TokenStorageInterface $tokenStorage, EventDispatcherInterface $eventDispatcher = null, array $statelessProviderKeys = array()) { $this->tokenStorage = $tokenStorage; $this->dispatcher = $eventDispatcher; + $this->statelessProviderKeys = $statelessProviderKeys; } /** * Authenticates the given token in the system. + * + * @param string $providerKey The name of the provider/firewall being used for authentication */ - public function authenticateWithToken(TokenInterface $token, Request $request) + public function authenticateWithToken(TokenInterface $token, Request $request/*, string $providerKey */) { - $this->migrateSession($request, $token); + $providerKey = \func_num_args() > 2 ? func_get_arg(2) : null; + + $this->migrateSession($request, $token, $providerKey); $this->tokenStorage->setToken($token); if (null !== $this->dispatcher) { @@ -98,7 +107,7 @@ public function authenticateUserAndHandleSuccess(UserInterface $user, Request $r // create an authenticated token for the User $token = $authenticator->createAuthenticatedToken($user, $providerKey); // authenticate this in the system - $this->authenticateWithToken($token, $request); + $this->authenticateWithToken($token, $request, $providerKey); // return the success metric return $this->handleAuthenticationSuccess($token, $request, $authenticator, $providerKey); @@ -140,9 +149,9 @@ public function setSessionAuthenticationStrategy(SessionAuthenticationStrategyIn $this->sessionStrategy = $sessionStrategy; } - private function migrateSession(Request $request, TokenInterface $token) + private function migrateSession(Request $request, TokenInterface $token, $providerKey) { - if (!$this->sessionStrategy || !$request->hasSession() || !$request->hasPreviousSession()) { + if (!$this->sessionStrategy || !$request->hasSession() || !$request->hasPreviousSession() || \in_array($providerKey, $this->statelessProviderKeys, true)) { return; } diff --git a/src/Symfony/Component/Security/Guard/Tests/GuardAuthenticatorHandlerTest.php b/src/Symfony/Component/Security/Guard/Tests/GuardAuthenticatorHandlerTest.php index 49ce6548acab5..f7a8de7affd01 100644 --- a/src/Symfony/Component/Security/Guard/Tests/GuardAuthenticatorHandlerTest.php +++ b/src/Symfony/Component/Security/Guard/Tests/GuardAuthenticatorHandlerTest.php @@ -143,6 +143,18 @@ public function testSessionStrategyIsCalled() $handler->authenticateWithToken($this->token, $this->request); } + public function testSessionStrategyIsNotCalledWhenStateless() + { + $this->configurePreviousSession(); + + $this->sessionStrategy->expects($this->never()) + ->method('onAuthentication'); + + $handler = new GuardAuthenticatorHandler($this->tokenStorage, $this->dispatcher, array('some_provider_key')); + $handler->setSessionAuthenticationStrategy($this->sessionStrategy); + $handler->authenticateWithToken($this->token, $this->request, 'some_provider_key'); + } + protected function setUp() { $this->tokenStorage = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface')->getMock(); From 473a025643cc24b6ea9e74bb1cf25201a2fdb21e Mon Sep 17 00:00:00 2001 From: Rhodri Pugh Date: Tue, 12 Jun 2018 16:19:40 +0100 Subject: [PATCH 097/938] add property path to exception message when error writing property --- src/Symfony/Component/PropertyAccess/PropertyAccessor.php | 6 +++--- .../Component/PropertyAccess/Tests/PropertyAccessorTest.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php index 37637e67833a4..bb959223c4f73 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php +++ b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php @@ -151,14 +151,14 @@ public function setValue(&$objectOrArray, $propertyPath, $value) $value = $zval[self::VALUE]; } } catch (\TypeError $e) { - self::throwInvalidArgumentException($e->getMessage(), $e->getTrace(), 0); + self::throwInvalidArgumentException($e->getMessage(), $e->getTrace(), 0, $propertyPath); // It wasn't thrown in this class so rethrow it throw $e; } } - private static function throwInvalidArgumentException($message, $trace, $i) + private static function throwInvalidArgumentException($message, $trace, $i, $propertyPath) { if (isset($trace[$i]['file']) && __FILE__ === $trace[$i]['file'] && isset($trace[$i]['args'][0])) { $pos = strpos($message, $delim = 'must be of the type ') ?: (strpos($message, $delim = 'must be an instance of ') ?: strpos($message, $delim = 'must implement interface ')); @@ -166,7 +166,7 @@ private static function throwInvalidArgumentException($message, $trace, $i) $type = $trace[$i]['args'][0]; $type = is_object($type) ? get_class($type) : gettype($type); - throw new InvalidArgumentException(sprintf('Expected argument of type "%s", "%s" given.', substr($message, $pos, strpos($message, ',', $pos) - $pos), $type)); + throw new InvalidArgumentException(sprintf('Expected argument of type "%s", "%s" given at property path "%s".', substr($message, $pos, strpos($message, ',', $pos) - $pos), $type, $propertyPath)); } } diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php index f160be61f325f..edbf9840f62e0 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php +++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php @@ -529,7 +529,7 @@ public function testIsWritableForReferenceChainIssue($object, $path, $value) /** * @expectedException \Symfony\Component\PropertyAccess\Exception\InvalidArgumentException - * @expectedExceptionMessage Expected argument of type "DateTime", "string" given + * @expectedExceptionMessage Expected argument of type "DateTime", "string" given at property path "date" */ public function testThrowTypeError() { From 516ff5a9853eea3f63c92221d35fee3d947748d8 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 6 Jun 2018 22:11:23 +0200 Subject: [PATCH 098/938] [FrameworkBundle] give access to non-shared services when using test.service_container --- .../TestServiceContainerRefPassesTest.php | 58 +++++++++++++++++++ .../Bundle/FrameworkBundle/composer.json | 2 +- .../Compiler/InlineServiceDefinitionsPass.php | 6 ++ 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/TestServiceContainerRefPassesTest.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/TestServiceContainerRefPassesTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/TestServiceContainerRefPassesTest.php new file mode 100644 index 0000000000000..22053a9b2c739 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/TestServiceContainerRefPassesTest.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler; + +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TestServiceContainerWeakRefPass; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TestServiceContainerRealRefPass; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\PassConfig; +use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\DependencyInjection\Reference; + +class TestServiceContainerRefPassesTest extends TestCase +{ + public function testProcess() + { + $container = new ContainerBuilder(); + $container->register('test.private_services_locator', ServiceLocator::class) + ->setPublic(true) + ->addArgument(0, array()); + + $container->addCompilerPass(new TestServiceContainerWeakRefPass(), PassConfig::TYPE_BEFORE_REMOVING, -32); + $container->addCompilerPass(new TestServiceContainerRealRefPass(), PassConfig::TYPE_AFTER_REMOVING); + + $container->register('Test\public_service') + ->setPublic(true) + ->addArgument(new Reference('Test\private_used_shared_service')) + ->addArgument(new Reference('Test\private_used_non_shared_service')) + ; + + $container->register('Test\private_used_shared_service'); + $container->register('Test\private_unused_shared_service'); + $container->register('Test\private_used_non_shared_service')->setShared(false); + $container->register('Test\private_unused_non_shared_service')->setShared(false); + + $container->compile(); + + $expected = array( + 'Test\private_used_shared_service' => new ServiceClosureArgument(new Reference('Test\private_used_shared_service')), + 'Test\private_used_non_shared_service' => new ServiceClosureArgument(new Reference('Test\private_used_non_shared_service')), + 'Psr\Container\ContainerInterface' => new ServiceClosureArgument(new Reference('service_container')), + 'Symfony\Component\DependencyInjection\ContainerInterface' => new ServiceClosureArgument(new Reference('service_container')), + ); + $this->assertEquals($expected, $container->getDefinition('test.private_services_locator')->getArgument(0)); + $this->assertSame($container, $container->get('test.private_services_locator')->get('Psr\Container\ContainerInterface')); + $this->assertFalse($container->getDefinition('Test\private_used_non_shared_service')->isShared()); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 08e2f4ead0619..0c2a3dec226d8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -19,7 +19,7 @@ "php": "^7.1.3", "ext-xml": "*", "symfony/cache": "~3.4|~4.0", - "symfony/dependency-injection": "^4.1", + "symfony/dependency-injection": "^4.1.1", "symfony/config": "~3.4|~4.0", "symfony/event-dispatcher": "^4.1", "symfony/http-foundation": "^4.1", diff --git a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php index 9aee66c8e0d5b..12b6798804e34 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php @@ -93,6 +93,12 @@ private function isInlineableDefinition($id, Definition $definition, ServiceRefe } if (!$definition->isShared()) { + foreach ($graph->getNode($id)->getInEdges() as $edge) { + if ($edge->isWeak()) { + return false; + } + } + return true; } From 39dd9b2f9793571df6d4e46fbdc5ceef482ffbe0 Mon Sep 17 00:00:00 2001 From: Sepehr Lajevardi Date: Thu, 14 Jun 2018 11:11:11 +0430 Subject: [PATCH 099/938] [Cache] Fix typo in comment. --- src/Symfony/Component/Cache/Traits/RedisTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Cache/Traits/RedisTrait.php b/src/Symfony/Component/Cache/Traits/RedisTrait.php index 9fc6177c6bd78..b247b115fa2c0 100644 --- a/src/Symfony/Component/Cache/Traits/RedisTrait.php +++ b/src/Symfony/Component/Cache/Traits/RedisTrait.php @@ -197,7 +197,7 @@ protected function doHave($id) protected function doClear($namespace) { // When using a native Redis cluster, clearing the cache is done by versioning in AbstractTrait::clear(). - // This means old keys are not really removed until they expire and may need gargage collection. + // This means old keys are not really removed until they expire and may need garbage collection. $cleared = true; $hosts = array($this->redis); From 198bee0916fb62ca209c3623709c9066b4559a71 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 11 Jun 2018 16:08:45 +0200 Subject: [PATCH 100/938] [ProxyManagerBridge] Fixed support of private services --- .../LazyProxy/PhpDumper/ProxyDumper.php | 2 +- .../LazyProxy/PhpDumper/ProxyDumperTest.php | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php index 316cc8250173f..4a54cc7658e6b 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php @@ -57,7 +57,7 @@ public function getProxyFactoryCode(Definition $definition, $id, $factoryCode = $instantiation = 'return'; if ($definition->isShared()) { - $instantiation .= " \$this->services['$id'] ="; + $instantiation .= sprintf(' $this->%s[\'%s\'] =', $definition->isPublic() && !$definition->isPrivate() ? 'services' : 'privates', $id); } if (null === $factoryCode) { diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php index b4ce2106d8f2b..357b26844c364 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php @@ -83,6 +83,34 @@ public function testGetProxyFactoryCode() ); } + /** + * @dataProvider getPrivatePublicDefinitions + */ + public function testCorrectAssigning(Definition $definition, $access) + { + $definition->setLazy(true); + + $code = $this->dumper->getProxyFactoryCode($definition, 'foo', '$this->getFoo2Service(false)'); + + $this->assertStringMatchesFormat('%A$this->'.$access.'[\'foo\'] = %A', $code); + } + + public function getPrivatePublicDefinitions() + { + return array( + array( + (new Definition(__CLASS__)) + ->setPublic(false), + 'privates', + ), + array( + (new Definition(__CLASS__)) + ->setPublic(true), + 'services', + ), + ); + } + /** * @group legacy */ From 32988b42941b657dd48d633bd57e5305d363b081 Mon Sep 17 00:00:00 2001 From: benoushnorouzi Date: Thu, 7 Jun 2018 11:24:03 +0200 Subject: [PATCH 101/938] Enhance the twig not found exception Enhance the twig not found exception --- .../DependencyInjection/TwigExtension.php | 5 ++ .../Loader/NativeFilesystemLoader.php | 50 +++++++++++++++++++ .../Loader/NativeFilesystemLoaderTest.php | 41 +++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 src/Symfony/Bundle/TwigBundle/Loader/NativeFilesystemLoader.php create mode 100644 src/Symfony/Bundle/TwigBundle/Tests/Loader/NativeFilesystemLoaderTest.php diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php index b44dd2481c631..f117dd47bc4fe 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\TwigBundle\DependencyInjection; use Symfony\Bridge\Twig\Extension\WebLinkExtension; +use Symfony\Bundle\TwigBundle\Loader\NativeFilesystemLoader; use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\Resource\FileExistenceResource; use Symfony\Component\Console\Application; @@ -92,6 +93,10 @@ public function load(array $configs, ContainerBuilder $container) $twigFilesystemLoaderDefinition = $container->getDefinition('twig.loader.native_filesystem'); + if ($container->getParameter('kernel.debug')) { + $twigFilesystemLoaderDefinition->setClass(NativeFilesystemLoader::class); + } + // register user-configured paths foreach ($config['paths'] as $path => $namespace) { if (!$namespace) { diff --git a/src/Symfony/Bundle/TwigBundle/Loader/NativeFilesystemLoader.php b/src/Symfony/Bundle/TwigBundle/Loader/NativeFilesystemLoader.php new file mode 100644 index 0000000000000..9ef58d7bdbbe6 --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/Loader/NativeFilesystemLoader.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\TwigBundle\Loader; + +use Twig\Error\LoaderError; +use Twig\Loader\FilesystemLoader; + +/** + * @author Behnoush Norouzali + * + * @internal + */ +class NativeFilesystemLoader extends FilesystemLoader +{ + /** + * {@inheritdoc} + */ + protected function findTemplate($template, $throw = true) + { + try { + return parent::findTemplate($template, $throw); + } catch (LoaderError $e) { + if ('' === $template || '@' === $template[0] || !preg_match('/^(?P[^:]*?)(?:Bundle)?:(?P[^:]*+):(?P
{{ profiler_dump(dispatchCall.message.type) }} @@ -122,7 +123,31 @@
+ + + +
Bus