From b4e4e43f61bf2195dc4b9f23a200b6b223c79b06 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 16 May 2020 14:36:23 +0200 Subject: [PATCH 01/10] bumped Symfony version to 5.1.0 --- Kernel.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Kernel.php b/Kernel.php index 163e112077..b66dba0d3b 100644 --- a/Kernel.php +++ b/Kernel.php @@ -73,12 +73,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private static $freshCache = []; - const VERSION = '5.1.0-RC1'; + const VERSION = '5.1.0-DEV'; const VERSION_ID = 50100; const MAJOR_VERSION = 5; const MINOR_VERSION = 1; const RELEASE_VERSION = 0; - const EXTRA_VERSION = 'RC1'; + const EXTRA_VERSION = 'DEV'; const END_OF_MAINTENANCE = '01/2021'; const END_OF_LIFE = '01/2021'; From 7d9b08c59456c61cbe0e768bddbefa55841c9812 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Sat, 16 May 2020 15:57:47 +0200 Subject: [PATCH 02/10] Revert "Change priority of KernelEvents::RESPONSE subscriber" This reverts commit 6ed624ad16d21a5daf8f0e132cbef53ff1e0de59. --- EventListener/ProfilerListener.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EventListener/ProfilerListener.php b/EventListener/ProfilerListener.php index fec89c3ed3..b8464f1627 100644 --- a/EventListener/ProfilerListener.php +++ b/EventListener/ProfilerListener.php @@ -119,7 +119,7 @@ public function onKernelTerminate(PostResponseEvent $event) public static function getSubscribedEvents() { return [ - KernelEvents::RESPONSE => ['onKernelResponse', -1012], + KernelEvents::RESPONSE => ['onKernelResponse', -100], KernelEvents::EXCEPTION => ['onKernelException', 0], KernelEvents::TERMINATE => ['onKernelTerminate', -1024], ]; From 238f49161af85e68700eb6379826ec2dc03cd9ab Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 18 May 2020 16:22:26 +0200 Subject: [PATCH 03/10] [HttpKernel] Fix error logger when stderr is redirected to /dev/null (FPM) --- Log/Logger.php | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/Log/Logger.php b/Log/Logger.php index e05d8c32ec..bbdd101d7d 100644 --- a/Log/Logger.php +++ b/Log/Logger.php @@ -37,10 +37,10 @@ class Logger extends AbstractLogger private $formatter; private $handle; - public function __construct($minLevel = null, $output = 'php://stderr', callable $formatter = null) + public function __construct($minLevel = null, $output = null, callable $formatter = null) { if (null === $minLevel) { - $minLevel = 'php://stdout' === $output || 'php://stderr' === $output ? LogLevel::CRITICAL : LogLevel::WARNING; + $minLevel = null === $output || 'php://stdout' === $output || 'php://stderr' === $output ? LogLevel::ERROR : LogLevel::WARNING; if (isset($_ENV['SHELL_VERBOSITY']) || isset($_SERVER['SHELL_VERBOSITY'])) { switch ((int) (isset($_ENV['SHELL_VERBOSITY']) ? $_ENV['SHELL_VERBOSITY'] : $_SERVER['SHELL_VERBOSITY'])) { @@ -58,7 +58,7 @@ public function __construct($minLevel = null, $output = 'php://stderr', callable $this->minLevelIndex = self::$levels[$minLevel]; $this->formatter = $formatter ?: [$this, 'format']; - if (false === $this->handle = \is_resource($output) ? $output : @fopen($output, 'a')) { + if ($output && false === $this->handle = \is_resource($output) ? $output : @fopen($output, 'a')) { throw new InvalidArgumentException(sprintf('Unable to open "%s".', $output)); } } @@ -77,7 +77,11 @@ public function log($level, $message, array $context = []) } $formatter = $this->formatter; - @fwrite($this->handle, $formatter($level, $message, $context)); + if ($this->handle) { + @fwrite($this->handle, $formatter($level, $message, $context)); + } else { + error_log($formatter($level, $message, $context, false)); + } } /** @@ -86,7 +90,7 @@ public function log($level, $message, array $context = []) * * @return string */ - private function format($level, $message, array $context) + private function format($level, $message, array $context, $prefixDate = true) { if (false !== strpos($message, '{')) { $replacements = []; @@ -105,6 +109,11 @@ private function format($level, $message, array $context) $message = strtr($message, $replacements); } - return sprintf('%s [%s] %s', date(\DateTime::RFC3339), $level, $message).PHP_EOL; + $log = sprintf('[%s] %s', $level, $message).PHP_EOL; + if ($prefixDate) { + $log = date(\DateTime::RFC3339).' '.$log; + } + + return $log; } } From 00b5b7c0644d59354ce646179b5c1a45fa7f74db Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Sat, 16 May 2020 11:23:27 +0000 Subject: [PATCH 04/10] [HttpKernel] Fix that the `Store` would not save responses with the X-Content-Digest header present --- HttpCache/Store.php | 29 +++++++++++++++-------------- Tests/HttpCache/StoreTest.php | 21 +++++++++++++++++++++ 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/HttpCache/Store.php b/HttpCache/Store.php index c831ba2ac3..fd04a7b23d 100644 --- a/HttpCache/Store.php +++ b/HttpCache/Store.php @@ -177,19 +177,15 @@ public function write(Request $request, Response $response) $key = $this->getCacheKey($request); $storedEnv = $this->persistRequest($request); - // write the response body to the entity store if this is the original response - if (!$response->headers->has('X-Content-Digest')) { - $digest = $this->generateContentDigest($response); + $digest = $this->generateContentDigest($response); + $response->headers->set('X-Content-Digest', $digest); - if (!$this->save($digest, $response->getContent())) { - throw new \RuntimeException('Unable to store the entity.'); - } - - $response->headers->set('X-Content-Digest', $digest); + if (!$this->save($digest, $response->getContent(), false)) { + throw new \RuntimeException('Unable to store the entity.'); + } - if (!$response->headers->has('Transfer-Encoding')) { - $response->headers->set('Content-Length', \strlen($response->getContent())); - } + if (!$response->headers->has('Transfer-Encoding')) { + $response->headers->set('Content-Length', \strlen($response->getContent())); } // read existing cache entries, remove non-varying, and add this one to the list @@ -362,15 +358,20 @@ private function load($key) /** * Save data for the given key. * - * @param string $key The store key - * @param string $data The data to store + * @param string $key The store key + * @param string $data The data to store + * @param bool $overwrite Whether existing data should be overwritten * * @return bool */ - private function save($key, $data) + private function save($key, $data, $overwrite = true) { $path = $this->getPath($key); + if (!$overwrite && file_exists($path)) { + return true; + } + if (isset($this->locks[$key])) { $fp = $this->locks[$key]; @ftruncate($fp, 0); diff --git a/Tests/HttpCache/StoreTest.php b/Tests/HttpCache/StoreTest.php index 77cb34cfa5..07c4154269 100644 --- a/Tests/HttpCache/StoreTest.php +++ b/Tests/HttpCache/StoreTest.php @@ -97,6 +97,27 @@ public function testSetsTheXContentDigestResponseHeaderBeforeStoring() $this->assertEquals('en9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', $res['x-content-digest'][0]); } + public function testDoesNotTrustXContentDigestFromUpstream() + { + $response = new Response('test', 200, ['X-Content-Digest' => 'untrusted-from-elsewhere']); + + $cacheKey = $this->store->write($this->request, $response); + $entries = $this->getStoreMetadata($cacheKey); + list(, $res) = $entries[0]; + + $this->assertEquals('en9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', $res['x-content-digest'][0]); + $this->assertEquals('en9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', $response->headers->get('X-Content-Digest')); + } + + public function testWritesResponseEvenIfXContentDigestIsPresent() + { + // Prime the store + $this->store->write($this->request, new Response('test', 200, ['X-Content-Digest' => 'untrusted-from-elsewhere'])); + + $response = $this->store->lookup($this->request); + $this->assertNotNull($response); + } + public function testFindsAStoredEntryWithLookup() { $this->storeSimpleEntry(); From 2b5cb0043c3250e078fcd23679c6fca0e6aca11e Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 20 May 2020 10:37:50 +0200 Subject: [PATCH 05/10] Use ">=" for the "php" requirement --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 14e1c64cc0..eca3b54b36 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ } ], "require": { - "php": "^7.1.3", + "php": ">=7.1.3", "symfony/error-handler": "^4.4", "symfony/event-dispatcher": "^4.4", "symfony/http-foundation": "^4.4|^5.0", From 34db0698fe3fbcf53ebe1df127e820a028268c5c Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 21 May 2020 13:30:55 +0200 Subject: [PATCH 06/10] Address deprecation of ReflectionType::getClass(). --- Controller/ControllerResolver.php | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Controller/ControllerResolver.php b/Controller/ControllerResolver.php index e3a7211c1b..688d574ceb 100644 --- a/Controller/ControllerResolver.php +++ b/Controller/ControllerResolver.php @@ -136,7 +136,7 @@ protected function doGetArguments(Request $request, $controller, array $paramete } else { $arguments[] = $attributes[$param->name]; } - } elseif ($param->getClass() && $param->getClass()->isInstance($request)) { + } elseif ($this->typeMatchesRequestClass($param, $request)) { $arguments[] = $request; } elseif ($param->isDefaultValueAvailable()) { $arguments[] = $param->getDefaultValue(); @@ -260,4 +260,22 @@ private function getControllerError($callable) return $message; } + + /** + * @return bool + */ + private function typeMatchesRequestClass(\ReflectionParameter $param, Request $request) + { + if (!method_exists($param, 'getType')) { + return $param->getClass() && $param->getClass()->isInstance($request); + } + + if (!($type = $param->getType()) || $type->isBuiltin()) { + return false; + } + + $class = new \ReflectionClass(method_exists($type, 'getName') ? $type->getName() : (string) $type); + + return $class && $class->isInstance($request); + } } From 5b389af7f40a3ce704145b7c719d66bc3a401d48 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Fri, 22 May 2020 15:23:31 +0200 Subject: [PATCH 07/10] [HttpKernel] Prevent calling method_exists() with non-string values. --- Controller/ControllerResolver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Controller/ControllerResolver.php b/Controller/ControllerResolver.php index e3a7211c1b..e01c725454 100644 --- a/Controller/ControllerResolver.php +++ b/Controller/ControllerResolver.php @@ -77,7 +77,7 @@ public function getController(Request $request) throw new \InvalidArgumentException(sprintf('Controller "%s" for URI "%s" is not callable.', \get_class($controller), $request->getPathInfo())); } - if (false === strpos($controller, ':')) { + if (\is_string($controller) && false === strpos($controller, ':')) { if (method_exists($controller, '__invoke')) { return $this->instantiateController($controller); } elseif (\function_exists($controller)) { From 62e2503b8815a3d5d1e40c0e4a514a6ddddb6869 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 23 May 2020 18:55:06 +0200 Subject: [PATCH 08/10] Make PHP 8 green on Travis --- Tests/Controller/ArgumentResolverTest.php | 8 ++++---- Tests/Controller/ControllerResolverTest.php | 8 +++++--- .../ArgumentMetadataFactoryTest.php | 6 +++--- .../Controller/LegacyNullableController.php | 19 +++++++++++++++++++ .../Controller/NullableController.php | 2 +- 5 files changed, 32 insertions(+), 11 deletions(-) create mode 100644 Tests/Fixtures/Controller/LegacyNullableController.php diff --git a/Tests/Controller/ArgumentResolverTest.php b/Tests/Controller/ArgumentResolverTest.php index a964aaeb58..bf94728a43 100644 --- a/Tests/Controller/ArgumentResolverTest.php +++ b/Tests/Controller/ArgumentResolverTest.php @@ -216,10 +216,10 @@ public function testGetNullableArguments() $request = Request::create('/'); $request->attributes->set('foo', 'foo'); $request->attributes->set('bar', new \stdClass()); - $request->attributes->set('mandatory', 'mandatory'); + $request->attributes->set('last', 'last'); $controller = [new NullableController(), 'action']; - $this->assertEquals(['foo', new \stdClass(), 'value', 'mandatory'], self::$resolver->getArguments($request, $controller)); + $this->assertEquals(['foo', new \stdClass(), 'value', 'last'], self::$resolver->getArguments($request, $controller)); } /** @@ -228,10 +228,10 @@ public function testGetNullableArguments() public function testGetNullableArgumentsWithDefaults() { $request = Request::create('/'); - $request->attributes->set('mandatory', 'mandatory'); + $request->attributes->set('last', 'last'); $controller = [new NullableController(), 'action']; - $this->assertEquals([null, null, 'value', 'mandatory'], self::$resolver->getArguments($request, $controller)); + $this->assertEquals([null, null, 'value', 'last'], self::$resolver->getArguments($request, $controller)); } public function testGetSessionArguments() diff --git a/Tests/Controller/ControllerResolverTest.php b/Tests/Controller/ControllerResolverTest.php index d2684791fe..b0a6b20058 100644 --- a/Tests/Controller/ControllerResolverTest.php +++ b/Tests/Controller/ControllerResolverTest.php @@ -15,7 +15,7 @@ use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ControllerResolver; -use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\NullableController; +use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\LegacyNullableController; use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\VariadicController; class ControllerResolverTest extends TestCase @@ -243,6 +243,7 @@ public function testIfExceptionIsThrownWhenMissingAnArgument() /** * @requires PHP 7.1 + * @requires PHP < 8 * @group legacy */ public function testGetNullableArguments() @@ -253,12 +254,13 @@ public function testGetNullableArguments() $request->attributes->set('foo', 'foo'); $request->attributes->set('bar', new \stdClass()); $request->attributes->set('mandatory', 'mandatory'); - $controller = [new NullableController(), 'action']; + $controller = [new LegacyNullableController(), 'action']; $this->assertEquals(['foo', new \stdClass(), 'value', 'mandatory'], $resolver->getArguments($request, $controller)); } /** * @requires PHP 7.1 + * @requires PHP < 8 * @group legacy */ public function testGetNullableArgumentsWithDefaults() @@ -267,7 +269,7 @@ public function testGetNullableArgumentsWithDefaults() $request = Request::create('/'); $request->attributes->set('mandatory', 'mandatory'); - $controller = [new NullableController(), 'action']; + $controller = [new LegacyNullableController(), 'action']; $this->assertEquals([null, null, 'value', 'mandatory'], $resolver->getArguments($request, $controller)); } diff --git a/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php b/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php index e5c62a87ca..00715462a2 100644 --- a/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php +++ b/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php @@ -80,7 +80,7 @@ public function testSignature5() $this->assertEquals([ new ArgumentMetadata('foo', 'array', false, true, null, true), - new ArgumentMetadata('bar', null, false, false, null), + new ArgumentMetadata('bar', null, false, true, null, true), ], $arguments); } @@ -122,7 +122,7 @@ public function testNullableTypesSignature() new ArgumentMetadata('foo', 'string', false, false, null, true), new ArgumentMetadata('bar', \stdClass::class, false, false, null, true), new ArgumentMetadata('baz', 'string', false, true, 'value', true), - new ArgumentMetadata('mandatory', null, false, false, null, true), + new ArgumentMetadata('last', 'string', false, true, '', false), ], $arguments); } @@ -142,7 +142,7 @@ private function signature4($foo = 'default', $bar = 500, $baz = []) { } - private function signature5(array $foo = null, $bar) + private function signature5(array $foo = null, $bar = null) { } } diff --git a/Tests/Fixtures/Controller/LegacyNullableController.php b/Tests/Fixtures/Controller/LegacyNullableController.php new file mode 100644 index 0000000000..23dde69d48 --- /dev/null +++ b/Tests/Fixtures/Controller/LegacyNullableController.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\HttpKernel\Tests\Fixtures\Controller; + +class LegacyNullableController +{ + public function action(?string $foo, ?\stdClass $bar, ?string $baz = 'value', $mandatory) + { + } +} diff --git a/Tests/Fixtures/Controller/NullableController.php b/Tests/Fixtures/Controller/NullableController.php index 9db4df7b4c..aacae0e3e3 100644 --- a/Tests/Fixtures/Controller/NullableController.php +++ b/Tests/Fixtures/Controller/NullableController.php @@ -13,7 +13,7 @@ class NullableController { - public function action(?string $foo, ?\stdClass $bar, ?string $baz = 'value', $mandatory) + public function action(?string $foo, ?\stdClass $bar, ?string $baz = 'value', string $last = '') { } } From 195731b7fa6f7226f7af3964e17eb5c85377e020 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Sat, 23 May 2020 00:49:08 +0200 Subject: [PATCH 09/10] Parse and render anonymous classes correctly on php 8 --- Kernel.php | 7 ++----- Tests/KernelTest.php | 21 +++++++++++++++++++++ composer.json | 1 + 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/Kernel.php b/Kernel.php index 8349de676a..438ef37f34 100644 --- a/Kernel.php +++ b/Kernel.php @@ -228,10 +228,7 @@ public function getBundles() public function getBundle($name) { if (!isset($this->bundles[$name])) { - $class = static::class; - $class = 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class; - - throw new \InvalidArgumentException(sprintf('Bundle "%s" does not exist or it is not enabled. Maybe you forgot to add it in the "registerBundles()" method of your "%s.php" file?', $name, $class)); + throw new \InvalidArgumentException(sprintf('Bundle "%s" does not exist or it is not enabled. Maybe you forgot to add it in the "registerBundles()" method of your "%s.php" file?', $name, get_debug_type($this))); } return $this->bundles[$name]; @@ -474,7 +471,7 @@ protected function build(ContainerBuilder $container) protected function getContainerClass() { $class = static::class; - $class = 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).str_replace('.', '_', ContainerBuilder::hash($class)) : $class; + $class = false !== strpos($class, "@anonymous\0") ? get_parent_class($class).str_replace('.', '_', ContainerBuilder::hash($class)) : $class; $class = $this->name.str_replace('\\', '_', $class).ucfirst($this->environment).($this->debug ? 'Debug' : '').'Container'; if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $class)) { diff --git a/Tests/KernelTest.php b/Tests/KernelTest.php index deac82b72d..36f8ea6e66 100644 --- a/Tests/KernelTest.php +++ b/Tests/KernelTest.php @@ -640,6 +640,27 @@ public function testKernelStartTimeIsResetWhileBootingAlreadyBootedKernel() $this->assertGreaterThan($preReBoot, $kernel->getStartTime()); } + public function testAnonymousKernelGeneratesValidContainerClass(): void + { + $kernel = new class('test', true) extends Kernel { + public function registerBundles(): iterable + { + return []; + } + + public function registerContainerConfiguration(LoaderInterface $loader): void + { + } + + public function getContainerClass(): string + { + return parent::getContainerClass(); + } + }; + + $this->assertRegExp('/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*TestDebugContainer$/', $kernel->getContainerClass()); + } + /** * Returns a mock for the BundleInterface. */ diff --git a/composer.json b/composer.json index eca3b54b36..2ae07df884 100644 --- a/composer.json +++ b/composer.json @@ -22,6 +22,7 @@ "symfony/http-foundation": "^4.4|^5.0", "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.15", "psr/log": "~1.0" }, "require-dev": { From c1594299cd48a7e55171f6b10ce402ae98cd2001 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 26 May 2020 09:22:08 +0200 Subject: [PATCH 10/10] updated VERSION for 5.1.0-RC2 --- Kernel.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Kernel.php b/Kernel.php index c5313ee24f..b06cb82389 100644 --- a/Kernel.php +++ b/Kernel.php @@ -73,12 +73,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private static $freshCache = []; - const VERSION = '5.1.0-DEV'; + const VERSION = '5.1.0-RC2'; const VERSION_ID = 50100; const MAJOR_VERSION = 5; const MINOR_VERSION = 1; const RELEASE_VERSION = 0; - const EXTRA_VERSION = 'DEV'; + const EXTRA_VERSION = 'RC2'; const END_OF_MAINTENANCE = '01/2021'; const END_OF_LIFE = '01/2021';