diff --git a/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php b/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php index d4a86a7d54..ee4c644f0e 100644 --- a/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php +++ b/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php @@ -138,6 +138,9 @@ class DoctrineDataCollector extends DataCollector if (!\is_array($query['params'])) { $query['params'] = [$query['params']]; } + if (!\is_array($query['types'])) { + $query['types'] = []; + } foreach ($query['params'] as $j => $param) { if (isset($query['types'][$j])) { // Transform the param according to the type diff --git a/src/Symfony/Bridge/Doctrine/Messenger/DoctrineTransactionMiddleware.php b/src/Symfony/Bridge/Doctrine/Messenger/DoctrineTransactionMiddleware.php index 945e1fd245..b0ff88410d 100644 --- a/src/Symfony/Bridge/Doctrine/Messenger/DoctrineTransactionMiddleware.php +++ b/src/Symfony/Bridge/Doctrine/Messenger/DoctrineTransactionMiddleware.php @@ -13,7 +13,9 @@ namespace Symfony\Bridge\Doctrine\Messenger; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\Exception\HandlerFailedException; use Symfony\Component\Messenger\Middleware\StackInterface; +use Symfony\Component\Messenger\Stamp\HandledStamp; /** * Wraps all handlers in a single doctrine transaction. @@ -36,6 +38,12 @@ class DoctrineTransactionMiddleware extends AbstractDoctrineMiddleware } catch (\Throwable $exception) { $entityManager->getConnection()->rollBack(); + if ($exception instanceof HandlerFailedException) { + // Remove all HandledStamp from the envelope so the retry will execute all handlers again. + // When a handler fails, the queries of allegedly successful previous handlers just got rolled back. + throw new HandlerFailedException($exception->getEnvelope()->withoutAll(HandledStamp::class), $exception->getNestedExceptions()); + } + throw $exception; } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTest.php b/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTest.php index 95e0e715ff..6a33f0680a 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTest.php @@ -102,6 +102,18 @@ class DoctrineDataCollectorTest extends TestCase $this->assertTrue($collectedQueries['default'][1]['explainable']); } + public function testCollectQueryWithNoTypes() + { + $queries = [ + ['sql' => 'SET sql_mode=(SELECT REPLACE(@@sql_mode, \'ONLY_FULL_GROUP_BY\', \'\'))', 'params' => [], 'types' => null, 'executionMS' => 1], + ]; + $c = $this->createCollector($queries); + $c->collect(new Request(), new Response()); + + $collectedQueries = $c->getQueries(); + $this->assertSame([], $collectedQueries['default'][0]['types']); + } + public function testReset() { $queries = [ diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php index 55089e0be1..f9457c937d 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php @@ -139,10 +139,13 @@ class DeprecationErrorHandler $group = 'unsilenced'; } elseif ($deprecation->isLegacy(self::$utilPrefix)) { $group = 'legacy'; - } elseif (!$deprecation->isSelf()) { - $group = $deprecation->isIndirect() ? 'remaining indirect' : 'remaining direct'; } else { - $group = 'remaining self'; + $group = [ + Deprecation::TYPE_SELF => 'remaining self', + Deprecation::TYPE_DIRECT => 'remaining direct', + Deprecation::TYPE_INDIRECT => 'remaining indirect', + Deprecation::TYPE_UNDETERMINED => 'other', + ][$deprecation->getType()]; } if ($this->getConfiguration()->shouldDisplayStackTrace($msg)) { @@ -216,7 +219,13 @@ class DeprecationErrorHandler return $this->configuration; } if (false === $mode = $this->mode) { - $mode = getenv('SYMFONY_DEPRECATIONS_HELPER'); + if (isset($_SERVER['SYMFONY_DEPRECATIONS_HELPER'])) { + $mode = $_SERVER['SYMFONY_DEPRECATIONS_HELPER']; + } elseif (isset($_ENV['SYMFONY_DEPRECATIONS_HELPER'])) { + $mode = $_ENV['SYMFONY_DEPRECATIONS_HELPER']; + } else { + $mode = getenv('SYMFONY_DEPRECATIONS_HELPER'); + } } if ('strict' === $mode) { return $this->configuration = Configuration::inStrictMode(); diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php index ba9e753c7b..6d5b39fd54 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php @@ -18,6 +18,15 @@ use Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerFor; */ class Deprecation { + const PATH_TYPE_VENDOR = 'path_type_vendor'; + const PATH_TYPE_SELF = 'path_type_internal'; + const PATH_TYPE_UNDETERMINED = 'path_type_undetermined'; + + const TYPE_SELF = 'type_self'; + const TYPE_DIRECT = 'type_direct'; + const TYPE_INDIRECT = 'type_indirect'; + const TYPE_UNDETERMINED = 'type_undetermined'; + /** * @var array */ @@ -39,13 +48,21 @@ class Deprecation private $originMethod; /** - * @var bool + * @var string one of the PATH_TYPE_* constants */ - private $self; + private $triggeringFilePathType; /** @var string[] absolute paths to vendor directories */ private static $vendors; + /** + * @var string[] absolute paths to source or tests of the project. This + * excludes cache directories, because it is based on + * autoloading rules and cache systems typically do not use + * those. + */ + private static $internalPaths; + /** * @param string $message * @param string $file @@ -59,7 +76,7 @@ class Deprecation // No-op } $line = $trace[$i]; - $this->self = !$this->pathOriginatesFromVendor($file); + $this->trigerringFilePathType = $this->getPathType($file); if (isset($line['object']) || isset($line['class'])) { if (isset($line['class']) && 0 === strpos($line['class'], SymfonyTestsListenerFor::class)) { $parsedMsg = unserialize($this->message); @@ -70,8 +87,9 @@ class Deprecation // \Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait::endTest() // then we need to use the serialized information to determine // if the error has been triggered from vendor code. - $this->self = isset($parsedMsg['triggering_file']) - && $this->pathOriginatesFromVendor($parsedMsg['triggering_file']); + if (isset($parsedMsg['triggering_file'])) { + $this->trigerringFilePathType = $this->getPathType($parsedMsg['triggering_file']); + } return; } @@ -101,14 +119,6 @@ class Deprecation return isset($this->originClass); } - /** - * @return bool - */ - public function isSelf() - { - return $this->self; - } - /** * @return string */ @@ -163,10 +173,16 @@ class Deprecation * Tells whether both the calling package and the called package are vendor * packages. * - * @return bool + * @return string */ - public function isIndirect() + public function getType() { + if (self::PATH_TYPE_SELF === $this->trigerringFilePathType) { + return self::TYPE_SELF; + } + if (self::PATH_TYPE_UNDETERMINED === $this->trigerringFilePathType) { + return self::TYPE_UNDETERMINED; + } $erroringFile = $erroringPackage = null; foreach ($this->trace as $line) { if (\in_array($line['function'], ['require', 'require_once', 'include', 'include_once'], true)) { @@ -179,13 +195,16 @@ class Deprecation if ('-' === $file || 'Standard input code' === $file || !realpath($file)) { continue; } - if (!$this->pathOriginatesFromVendor($file)) { - return false; + if (self::PATH_TYPE_SELF === $this->getPathType($file)) { + return self::TYPE_DIRECT; + } + if (self::PATH_TYPE_UNDETERMINED === $this->getPathType($file)) { + return self::TYPE_UNDETERMINED; } if (null !== $erroringFile && null !== $erroringPackage) { $package = $this->getPackage($file); if ('composer' !== $package && $package !== $erroringPackage) { - return true; + return self::TYPE_INDIRECT; } continue; } @@ -193,11 +212,11 @@ class Deprecation $erroringPackage = $this->getPackage($file); } - return false; + return self::TYPE_DIRECT; } /** - * pathOriginatesFromVendor() should always be called prior to calling this method. + * getPathType() should always be called prior to calling this method. * * @param string $path * @@ -237,6 +256,15 @@ class Deprecation $v = \dirname(\dirname($r->getFileName())); if (file_exists($v.'/composer/installed.json')) { self::$vendors[] = $v; + $loader = require $v.'/autoload.php'; + $paths = self::getSourcePathsFromPrefixes(array_merge($loader->getPrefixes(), $loader->getPrefixesPsr4())); + } + } + } + foreach ($paths as $path) { + foreach (self::$vendors as $vendor) { + if (0 !== strpos($path, $vendor)) { + self::$internalPaths[] = $path; } } } @@ -245,24 +273,41 @@ class Deprecation return self::$vendors; } + private static function getSourcePathsFromPrefixes(array $prefixesByNamespace) + { + foreach ($prefixesByNamespace as $prefixes) { + foreach ($prefixes as $prefix) { + if (false !== realpath($prefix)) { + yield realpath($prefix); + } + } + } + } + /** * @param string $path * - * @return bool + * @return string */ - private function pathOriginatesFromVendor($path) + private function getPathType($path) { $realPath = realpath($path); if (false === $realPath && '-' !== $path && 'Standard input code' !== $path) { - return true; + return self::PATH_TYPE_UNDETERMINED; } foreach (self::getVendors() as $vendor) { if (0 === strpos($realPath, $vendor) && false !== strpbrk(substr($realPath, \strlen($vendor), 1), '/'.\DIRECTORY_SEPARATOR)) { - return true; + return self::PATH_TYPE_VENDOR; } } - return false; + foreach (self::$internalPaths as $internalPath) { + if (0 === strpos($realPath, $internalPath)) { + return self::PATH_TYPE_SELF; + } + } + + return self::PATH_TYPE_UNDETERMINED; } /** @@ -281,19 +326,4 @@ class Deprecation "\n".str_replace(' '.getcwd().\DIRECTORY_SEPARATOR, ' ', $exception->getTraceAsString()). "\n"; } - - private function getPackageFromLine(array $line) - { - if (!isset($line['file'])) { - return 'internal function'; - } - if (!$this->pathOriginatesFromVendor($line['file'])) { - return 'source code'; - } - try { - return $this->getPackage($line['file']); - } catch (\RuntimeException $e) { - return 'unknown'; - } - } } diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php index 92bad71e08..583cca22f4 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php @@ -27,7 +27,7 @@ class DeprecationTest extends TestCase public function testItCanTellWhetherItIsInternal() { $deprecation = new Deprecation('💩', $this->debugBacktrace(), __FILE__); - $this->assertTrue($deprecation->isSelf()); + $this->assertSame(Deprecation::TYPE_SELF, $deprecation->getType()); } public function testLegacyTestMethodIsDetectedAsSuch() @@ -46,7 +46,7 @@ class DeprecationTest extends TestCase public function testItRulesOutFilesOutsideVendorsAsIndirect() { $deprecation = new Deprecation('💩', $this->debugBacktrace(), __FILE__); - $this->assertFalse($deprecation->isIndirect()); + $this->assertNotSame(Deprecation::TYPE_INDIRECT, $deprecation->getType()); } /** diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt index e9f7bec966..126d23389a 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt @@ -73,15 +73,13 @@ Unsilenced deprecation notices (3) 1x: unsilenced bar deprecation 1x in FooTestCase::testNonLegacyBar -Remaining self deprecation notices (1) +Legacy deprecation notices (1) + +Other deprecation notices (2) + + 1x: root deprecation 1x: silenced bar deprecation 1x in FooTestCase::testNonLegacyBar -Legacy deprecation notices (1) - -Other deprecation notices (1) - - 1x: root deprecation - I get precedence over any exit statements inside the deprecation error handler. diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/disabled.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/disabled.phpt index 0115bbd242..a55e88d1a8 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/disabled.phpt +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/disabled.phpt @@ -1,9 +1,9 @@ --TEST-- -Test DeprecationErrorHandler in weak mode +Test DeprecationErrorHandler in disabled mode --FILE-- +--EXPECTF-- +00 diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/disabled_from_putenv.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/disabled_from_putenv.phpt new file mode 100644 index 0000000000..73a67241a4 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/disabled_from_putenv.phpt @@ -0,0 +1,24 @@ +--TEST-- +Test DeprecationErrorHandler in disabled mode (via putenv) +--FILE-- + +--EXPECTF-- +00 diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_vendor/autoload.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_vendor/autoload.php index bf315f2eaa..3c4471bcbe 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_vendor/autoload.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_vendor/autoload.php @@ -1,3 +1,5 @@ {{- form_label(form) }} {# -#} {{ form_widget(form, widget_attr) }} {# -#} + {{- form_help(form) -}} {{ form_errors(form) }} {# -#} {# -#} {%- endblock form_row %} diff --git a/src/Symfony/Bundle/DebugBundle/DependencyInjection/DebugExtension.php b/src/Symfony/Bundle/DebugBundle/DependencyInjection/DebugExtension.php index a183e82cf8..8309db19ec 100644 --- a/src/Symfony/Bundle/DebugBundle/DependencyInjection/DebugExtension.php +++ b/src/Symfony/Bundle/DebugBundle/DependencyInjection/DebugExtension.php @@ -17,6 +17,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\Extension; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\VarDumper\Caster\ReflectionCaster; use Symfony\Component\VarDumper\Dumper\CliDumper; use Symfony\Component\VarDumper\Dumper\HtmlDumper; @@ -43,9 +44,9 @@ class DebugExtension extends Extension ->addMethodCall('setMinDepth', [$config['min_depth']]) ->addMethodCall('setMaxString', [$config['max_string_length']]); - if (method_exists(ReflectionClass::class, 'unsetClosureFileInfo')) { + if (method_exists(ReflectionCaster::class, 'unsetClosureFileInfo')) { $container->getDefinition('var_dumper.cloner') - ->addMethodCall('addCasters', ReflectionClass::UNSET_CLOSURE_FILE_INFO); + ->addMethodCall('addCasters', [ReflectionCaster::UNSET_CLOSURE_FILE_INFO]); } if (method_exists(HtmlDumper::class, 'setTheme') && 'dark' !== $config['theme']) { diff --git a/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/DebugExtensionTest.php b/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/DebugExtensionTest.php index cd6084c5e3..c09ed8bc8c 100644 --- a/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/DebugExtensionTest.php +++ b/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/DebugExtensionTest.php @@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Bundle\DebugBundle\DependencyInjection\DebugExtension; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\VarDumper\Caster\ReflectionCaster; class DebugExtensionTest extends TestCase { @@ -36,6 +37,39 @@ class DebugExtensionTest extends TestCase $this->assertSame($expectedTags, $container->getDefinition('data_collector.dump')->getTag('data_collector')); } + public function testUnsetClosureFileInfoShouldBeRegisteredInVarCloner() + { + if (!method_exists(ReflectionCaster::class, 'unsetClosureFileInfo')) { + $this->markTestSkipped('Method not available'); + } + + $container = $this->createContainer(); + $container->registerExtension(new DebugExtension()); + $container->loadFromExtension('debug', []); + $this->compileContainer($container); + + $definition = $container->getDefinition('var_dumper.cloner'); + + $called = false; + foreach ($definition->getMethodCalls() as $call) { + if ('addCasters' !== $call[0]) { + continue; + } + + $argument = $call[1][0] ?? null; + if (null === $argument) { + continue; + } + + if (['Closure' => ReflectionCaster::class.'::unsetClosureFileInfo'] === $argument) { + $called = true; + break; + } + } + + $this->assertTrue($called); + } + private function createContainer() { $container = new ContainerBuilder(new ParameterBag([ diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index a405c199d6..52489f7ac2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -23,7 +23,7 @@ CHANGELOG * [BC Break] When using Messenger, the default transport changed from using Symfony's serializer service to use `PhpSerializer`, which uses PHP's native `serialize()` and `unserialize()` functions. To use the - original serialization method, set the `framework.messenger.defaut_serializer` + original serialization method, set the `framework.messenger.default_serializer` config option to `messenger.transport.symfony_serializer`. Or set the `serializer` option under one specific `transport`. * [BC Break] The `framework.messenger.serializer` config key changed to diff --git a/src/Symfony/Bundle/FrameworkBundle/Client.php b/src/Symfony/Bundle/FrameworkBundle/Client.php index f33808db04..3e395a4904 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Client.php +++ b/src/Symfony/Bundle/FrameworkBundle/Client.php @@ -66,7 +66,7 @@ class Client extends HttpKernelBrowser */ public function getProfile() { - if (!$this->kernel->getContainer()->has('profiler')) { + if (null === $this->response || !$this->kernel->getContainer()->has('profiler')) { return false; } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index cea61eddd1..db9658088c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -652,6 +652,10 @@ class FrameworkExtension extends Extension $definitionDefinition->addArgument($transitions); $definitionDefinition->addArgument($initialMarking); $definitionDefinition->addArgument($metadataStoreDefinition); + $definitionDefinition->addTag('workflow.definition', [ + 'name' => $name, + 'type' => $type, + ]); // Create MarkingStore if (isset($workflow['marking_store']['type'])) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 59e45d2ae7..ef1932133e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -311,6 +311,8 @@ abstract class FrameworkExtensionTest extends TestCase $workflowDefinition->getArgument(0), 'Places are passed to the workflow definition' ); + + $this->assertSame(['workflow.definition' => [['name' => 'legacy', 'type' => 'state_machine']]], $workflowDefinition->getTags()); } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ProfilerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ProfilerTest.php index c5252c0d58..2768b59a1c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ProfilerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ProfilerTest.php @@ -28,9 +28,9 @@ class ProfilerTest extends WebTestCase // enable the profiler for the next request $client->enableProfiler(); - $crawler = $client->request('GET', '/profiler'); - $profile = $client->getProfile(); - $this->assertInternalType('object', $profile); + $this->assertFalse($client->getProfile()); + $client->request('GET', '/profiler'); + $this->assertInternalType('object', $client->getProfile()); $client->request('GET', '/profiler'); $this->assertFalse($client->getProfile()); diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index de007cd0b5..3e57b8e6c9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -48,7 +48,7 @@ "symfony/security-http": "^3.4|^4.0|^5.0", "symfony/serializer": "^4.3|^5.0", "symfony/stopwatch": "^3.4|^4.0|^5.0", - "symfony/translation": "^4.2|^5.0", + "symfony/translation": "^4.3|^5.0", "symfony/templating": "^3.4|^4.0|^5.0", "symfony/twig-bundle": "^3.4|^4.0|^5.0", "symfony/validator": "^4.1|^5.0", diff --git a/src/Symfony/Component/Cache/Tests/Adapter/SimpleCacheAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/SimpleCacheAdapterTest.php index 788fe59d93..8097e49cfd 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/SimpleCacheAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/SimpleCacheAdapterTest.php @@ -12,8 +12,8 @@ namespace Symfony\Component\Cache\Tests\Adapter; use Symfony\Component\Cache\Adapter\SimpleCacheAdapter; -use Symfony\Component\Cache\Simple\FilesystemCache; use Symfony\Component\Cache\Simple\ArrayCache; +use Symfony\Component\Cache\Simple\FilesystemCache; /** * @group time-sensitive diff --git a/src/Symfony/Component/Dotenv/Tests/DotenvTest.php b/src/Symfony/Component/Dotenv/Tests/DotenvTest.php index 63039d5660..1554293e19 100644 --- a/src/Symfony/Component/Dotenv/Tests/DotenvTest.php +++ b/src/Symfony/Component/Dotenv/Tests/DotenvTest.php @@ -314,7 +314,7 @@ class DotenvTest extends TestCase $dotenv->load(__DIR__); } - public function testServerSuperglobalIsNotOverriden() + public function testServerSuperglobalIsNotOverridden() { $originalValue = $_SERVER['argc']; @@ -324,7 +324,7 @@ class DotenvTest extends TestCase $this->assertSame($originalValue, $_SERVER['argc']); } - public function testEnvVarIsNotOverriden() + public function testEnvVarIsNotOverridden() { putenv('TEST_ENV_VAR=original_value'); $_SERVER['TEST_ENV_VAR'] = 'original_value'; @@ -335,7 +335,7 @@ class DotenvTest extends TestCase $this->assertSame('original_value', getenv('TEST_ENV_VAR')); } - public function testHttpVarIsPartiallyOverriden() + public function testHttpVarIsPartiallyOverridden() { $_SERVER['HTTP_TEST_ENV_VAR'] = 'http_value'; diff --git a/src/Symfony/Component/Filesystem/Filesystem.php b/src/Symfony/Component/Filesystem/Filesystem.php index d83b27330c..dd3d8b471e 100644 --- a/src/Symfony/Component/Filesystem/Filesystem.php +++ b/src/Symfony/Component/Filesystem/Filesystem.php @@ -569,14 +569,15 @@ class Filesystem } $this->mkdir($targetDir); - $targetDirInfo = new \SplFileInfo($targetDir); + $filesCreatedWhileMirroring = []; foreach ($iterator as $file) { - if ($file->getPathname() === $targetDir || $file->getRealPath() === $targetDir || 0 === strpos($file->getRealPath(), $targetDirInfo->getRealPath())) { + if ($file->getPathname() === $targetDir || $file->getRealPath() === $targetDir || isset($filesCreatedWhileMirroring[$file->getRealPath()])) { continue; } $target = $targetDir.substr($file->getPathname(), $originDirLen); + $filesCreatedWhileMirroring[$target] = true; if (!$copyOnWindows && is_link($file)) { $this->symlink($file->getLinkTarget(), $target); diff --git a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php index a1a3ce0e6e..7fa2ecd3de 100644 --- a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php +++ b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php @@ -1362,6 +1362,22 @@ class FilesystemTest extends FilesystemTestCase $this->assertFalse($this->filesystem->exists($targetPath.'target')); } + public function testMirrorFromSubdirectoryInToParentDirectory() + { + $targetPath = $this->workspace.\DIRECTORY_SEPARATOR.'foo'.\DIRECTORY_SEPARATOR; + $sourcePath = $targetPath.'bar'.\DIRECTORY_SEPARATOR; + $file1 = $sourcePath.'file1'; + $file2 = $sourcePath.'file2'; + + $this->filesystem->mkdir($sourcePath); + file_put_contents($file1, 'FILE1'); + file_put_contents($file2, 'FILE2'); + + $this->filesystem->mirror($sourcePath, $targetPath); + + $this->assertFileEquals($file1, $targetPath.'file1'); + } + /** * @dataProvider providePathsForIsAbsolutePath */ diff --git a/src/Symfony/Component/Form/Resources/translations/validators.tr.xlf b/src/Symfony/Component/Form/Resources/translations/validators.tr.xlf new file mode 100644 index 0000000000..70e8541ed9 --- /dev/null +++ b/src/Symfony/Component/Form/Resources/translations/validators.tr.xlf @@ -0,0 +1,19 @@ + + + + + + This form should not contain extra fields. + Form ekstra alanlar içeremez. + + + The uploaded file was too large. Please try to upload a smaller file. + Yüklenen dosya boyutu çok yüksek. Lütfen daha küçük bir dosya yüklemeyi deneyin. + + + The CSRF token is invalid. Please try to resubmit the form. + CSRF fişi geçersiz. Formu tekrar göndermeyi deneyin. + + + + diff --git a/src/Symfony/Component/HttpClient/NativeHttpClient.php b/src/Symfony/Component/HttpClient/NativeHttpClient.php index 068f3eb7ba..58c1d3d65f 100644 --- a/src/Symfony/Component/HttpClient/NativeHttpClient.php +++ b/src/Symfony/Component/HttpClient/NativeHttpClient.php @@ -98,9 +98,9 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac 'http_code' => 0, 'redirect_count' => 0, 'start_time' => 0.0, - 'fopen_time' => 0.0, 'connect_time' => 0.0, 'redirect_time' => 0.0, + 'pretransfer_time' => 0.0, 'starttransfer_time' => 0.0, 'total_time' => 0.0, 'namelookup_time' => 0.0, @@ -118,7 +118,7 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac $onProgress = static function (...$progress) use ($onProgress, &$lastProgress, &$info) { $progressInfo = $info; $progressInfo['url'] = implode('', $info['url']); - unset($progressInfo['fopen_time'], $progressInfo['size_body']); + unset($progressInfo['size_body']); if ($progress && -1 === $progress[0]) { // Response completed @@ -133,14 +133,14 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac // Always register a notification callback to compute live stats about the response $notification = static function (int $code, int $severity, ?string $msg, int $msgCode, int $dlNow, int $dlSize) use ($onProgress, &$info) { - $now = microtime(true); - $info['total_time'] = $now - $info['start_time']; + $info['total_time'] = microtime(true) - $info['start_time']; if (STREAM_NOTIFY_PROGRESS === $code) { + $info['starttransfer_time'] = $info['starttransfer_time'] ?: $info['total_time']; $info['size_upload'] += $dlNow ? 0 : $info['size_body']; $info['size_download'] = $dlNow; } elseif (STREAM_NOTIFY_CONNECT === $code) { - $info['connect_time'] += $now - $info['fopen_time']; + $info['connect_time'] = $info['total_time']; $info['debug'] .= $info['request_header']; unset($info['request_header']); } else { @@ -310,7 +310,7 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac throw new TransportException(sprintf('Could not resolve host "%s".', $host)); } - $info['namelookup_time'] += microtime(true) - $now; + $info['namelookup_time'] = microtime(true) - ($info['start_time'] ?: $now); $multi->dnsCache[$host] = $ip = $ip[0]; $info['debug'] .= "* Added {$host}:0:{$ip} to DNS cache\n"; } else { @@ -368,10 +368,9 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac return null; } - $now = microtime(true); $info['url'] = $url; ++$info['redirect_count']; - $info['redirect_time'] = $now - $info['start_time']; + $info['redirect_time'] = microtime(true) - $info['start_time']; // Do like curl and browsers: turn POST to GET on 301, 302 and 303 if (\in_array($info['http_code'], [301, 302, 303], true)) { diff --git a/src/Symfony/Component/HttpClient/Response/CurlResponse.php b/src/Symfony/Component/HttpClient/Response/CurlResponse.php index 14d3f935e0..30dd31f0ae 100644 --- a/src/Symfony/Component/HttpClient/Response/CurlResponse.php +++ b/src/Symfony/Component/HttpClient/Response/CurlResponse.php @@ -55,6 +55,7 @@ final class CurlResponse implements ResponseInterface $this->info['start_time'] = $this->info['start_time'] ?? microtime(true); $info = &$this->info; $headers = &$this->headers; + $debugBuffer = $this->debugBuffer; if (!$info['response_headers']) { // Used to keep track of what we're waiting for @@ -88,9 +89,11 @@ final class CurlResponse implements ResponseInterface if ($onProgress = $options['on_progress']) { $url = isset($info['url']) ? ['url' => $info['url']] : []; curl_setopt($ch, CURLOPT_NOPROGRESS, false); - curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, static function ($ch, $dlSize, $dlNow) use ($onProgress, &$info, $url, $multi) { + curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, static function ($ch, $dlSize, $dlNow) use ($onProgress, &$info, $url, $multi, $debugBuffer) { try { - $onProgress($dlNow, $dlSize, $url + curl_getinfo($ch) + $info); + rewind($debugBuffer); + $debug = ['debug' => stream_get_contents($debugBuffer)]; + $onProgress($dlNow, $dlSize, $url + curl_getinfo($ch) + $info + $debug); } catch (\Throwable $e) { $multi->handlesActivity[(int) $ch][] = null; $multi->handlesActivity[(int) $ch][] = $e; @@ -148,12 +151,6 @@ final class CurlResponse implements ResponseInterface if (!$info = $this->finalInfo) { self::perform($this->multi); - if ('debug' === $type) { - rewind($this->debugBuffer); - - return stream_get_contents($this->debugBuffer); - } - $info = array_merge($this->info, curl_getinfo($this->handle)); $info['url'] = $this->info['url'] ?? $info['url']; $info['redirect_url'] = $this->info['redirect_url'] ?? null; @@ -164,9 +161,10 @@ final class CurlResponse implements ResponseInterface $info['starttransfer_time'] = 0.0; } + rewind($this->debugBuffer); + $info['debug'] = stream_get_contents($this->debugBuffer); + if (!\in_array(curl_getinfo($this->handle, CURLINFO_PRIVATE), ['headers', 'content'], true)) { - rewind($this->debugBuffer); - $info['debug'] = stream_get_contents($this->debugBuffer); curl_setopt($this->handle, CURLOPT_VERBOSE, false); rewind($this->debugBuffer); ftruncate($this->debugBuffer, 0); @@ -289,7 +287,19 @@ final class CurlResponse implements ResponseInterface // Regular header line: add it to the list self::addResponseHeaders([substr($data, 0, -2)], $info, $headers); - if (0 === strpos($data, 'HTTP') && 300 <= $info['http_code'] && $info['http_code'] < 400) { + if (0 !== strpos($data, 'HTTP/')) { + if (0 === stripos($data, 'Location:')) { + $location = trim(substr($data, 9, -2)); + } + + return \strlen($data); + } + + if (\function_exists('openssl_x509_read') && $certinfo = curl_getinfo($ch, CURLINFO_CERTINFO)) { + $info['peer_certificate_chain'] = array_map('openssl_x509_read', array_column($certinfo, 'Cert')); + } + + if (300 <= $info['http_code'] && $info['http_code'] < 400) { if (curl_getinfo($ch, CURLINFO_REDIRECT_COUNT) === $options['max_redirects']) { curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); } elseif (303 === $info['http_code'] || ('POST' === $info['http_method'] && \in_array($info['http_code'], [301, 302], true))) { @@ -298,15 +308,14 @@ final class CurlResponse implements ResponseInterface } } - if (0 === stripos($data, 'Location:')) { - $location = trim(substr($data, 9, -2)); - } - return \strlen($data); } // End of headers: handle redirects and add to the activity list - $statusCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE); + if (200 > $statusCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE)) { + return \strlen($data); + } + $info['redirect_url'] = null; if (300 <= $statusCode && $statusCode < 400 && null !== $location) { @@ -336,10 +345,6 @@ final class CurlResponse implements ResponseInterface return 0; } - if (\function_exists('openssl_x509_read') && $certinfo = curl_getinfo($ch, CURLINFO_CERTINFO)) { - $info['peer_certificate_chain'] = array_map('openssl_x509_read', array_column($certinfo, 'Cert')); - } - curl_setopt($ch, CURLOPT_PRIVATE, 'content'); } elseif (null !== $info['redirect_url'] && $logger) { $logger->info(sprintf('Redirecting: "%s %s"', $info['http_code'], $info['redirect_url'])); diff --git a/src/Symfony/Component/HttpClient/Response/NativeResponse.php b/src/Symfony/Component/HttpClient/Response/NativeResponse.php index 8be4b04163..766506479f 100644 --- a/src/Symfony/Component/HttpClient/Response/NativeResponse.php +++ b/src/Symfony/Component/HttpClient/Response/NativeResponse.php @@ -78,18 +78,12 @@ final class NativeResponse implements ResponseInterface if (!$info = $this->finalInfo) { self::perform($this->multi); - if ('debug' === $type) { - return $this->info['debug']; - } - $info = $this->info; $info['url'] = implode('', $info['url']); - unset($info['fopen_time'], $info['size_body'], $info['request_header']); + unset($info['size_body'], $info['request_header']); if (null === $this->buffer) { $this->finalInfo = $info; - } else { - unset($info['debug']); } } @@ -134,7 +128,6 @@ final class NativeResponse implements ResponseInterface $this->info['request_header'] .= implode("\r\n", $context['http']['header'])."\r\n\r\n"; // Send request and follow redirects when needed - $this->info['fopen_time'] = microtime(true); $this->handle = $h = fopen($url, 'r', false, $this->context); self::addResponseHeaders($http_response_header, $this->info, $this->headers, $this->info['debug']); $url = ($this->resolveRedirect)($this->multi, $this->headers['location'][0] ?? null, $this->context); @@ -152,7 +145,7 @@ final class NativeResponse implements ResponseInterface return; } finally { - $this->info['starttransfer_time'] = $this->info['total_time'] = microtime(true) - $this->info['start_time']; + $this->info['pretransfer_time'] = $this->info['total_time'] = microtime(true) - $this->info['start_time']; restore_error_handler(); } @@ -273,6 +266,7 @@ final class NativeResponse implements ResponseInterface if (null !== $e || !$remaining || feof($h)) { // Stream completed $info['total_time'] = microtime(true) - $info['start_time']; + $info['starttransfer_time'] = $info['starttransfer_time'] ?: $info['total_time']; if ($onProgress) { try { diff --git a/src/Symfony/Component/HttpFoundation/Session/Session.php b/src/Symfony/Component/HttpFoundation/Session/Session.php index 867ceba97f..db0b9aeb0b 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Session.php +++ b/src/Symfony/Component/HttpFoundation/Session/Session.php @@ -253,7 +253,9 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable */ public function getBag($name) { - return $this->storage->getBag($name)->getBag(); + $bag = $this->storage->getBag($name); + + return method_exists($bag, 'getBag') ? $bag->getBag() : $bag; } /** diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/SessionTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/SessionTest.php index afa00fc7c3..acb129984e 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/SessionTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/SessionTest.php @@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\SessionBagProxy; use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; /** @@ -260,4 +261,28 @@ class SessionTest extends TestCase $flash->get('hello'); $this->assertTrue($this->session->isEmpty()); } + + public function testGetBagWithBagImplementingGetBag() + { + $bag = new AttributeBag(); + $bag->setName('foo'); + + $storage = new MockArraySessionStorage(); + $storage->registerBag($bag); + + $this->assertSame($bag, (new Session($storage))->getBag('foo')); + } + + public function testGetBagWithBagNotImplementingGetBag() + { + $data = []; + + $bag = new AttributeBag(); + $bag->setName('foo'); + + $storage = new MockArraySessionStorage(); + $storage->registerBag(new SessionBagProxy($bag, $data, $usageIndex)); + + $this->assertSame($bag, (new Session($storage))->getBag('foo')); + } } diff --git a/src/Symfony/Component/Lock/Store/RedisStore.php b/src/Symfony/Component/Lock/Store/RedisStore.php index 496ce65778..39360de45c 100644 --- a/src/Symfony/Component/Lock/Store/RedisStore.php +++ b/src/Symfony/Component/Lock/Store/RedisStore.php @@ -71,6 +71,9 @@ class RedisStore implements StoreInterface $this->checkNotExpired($key); } + /** + * {@inheritdoc} + */ public function waitAndSave(Key $key) { throw new InvalidArgumentException(sprintf('The store "%s" does not supports blocking locks.', \get_class($this))); diff --git a/src/Symfony/Component/Mailer/Bridge/Mailgun/Http/Api/MailgunTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailgun/Http/Api/MailgunTransport.php index dac472e2b0..0740050d2d 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailgun/Http/Api/MailgunTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailgun/Http/Api/MailgunTransport.php @@ -68,7 +68,7 @@ class MailgunTransport extends AbstractApiTransport { $headers = $email->getHeaders(); $html = $email->getHtmlBody(); - if (null !== $html) { + if (null !== $html && \is_resource($html)) { if (stream_get_meta_data($html)['seekable'] ?? false) { rewind($html); } diff --git a/src/Symfony/Component/Mailer/Tests/TransportTest.php b/src/Symfony/Component/Mailer/Tests/TransportTest.php index 3c15480f2c..8c7cd99d98 100644 --- a/src/Symfony/Component/Mailer/Tests/TransportTest.php +++ b/src/Symfony/Component/Mailer/Tests/TransportTest.php @@ -71,11 +71,18 @@ class TransportTest extends TestCase Transport::fromDsn('some://'); } + public function testNoScheme() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The "//sendmail" mailer DSN must contain a transport scheme.'); + Transport::fromDsn('//sendmail'); + } + public function testFromInvalidDsnNoHost() { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The "?!" mailer DSN must contain a mailer name.'); - Transport::fromDsn('?!'); + $this->expectExceptionMessage('The "file:///some/path" mailer DSN must contain a mailer name.'); + Transport::fromDsn('file:///some/path'); } public function testFromInvalidTransportName() @@ -168,6 +175,19 @@ class TransportTest extends TestCase $transport = Transport::fromDsn('api://'.urlencode('u$er').':'.urlencode('pa$s').'@mailgun?region=us', $dispatcher, $client, $logger); $transport->send($message); + $message = (new Email())->from('me@me.com')->to('you@you.com')->subject('hello')->html('test'); + $client = $this->createMock(HttpClientInterface::class); + $client->expects($this->once())->method('request')->with('POST', 'https://api.mailgun.net/v3/pa%24s/messages')->willReturn($response); + $transport = Transport::fromDsn('api://'.urlencode('u$er').':'.urlencode('pa$s').'@mailgun?region=us', $dispatcher, $client, $logger); + $transport->send($message); + + $stream = fopen('data://text/plain,'.$message->getTextBody(), 'r'); + $message = (new Email())->from('me@me.com')->to('you@you.com')->subject('hello')->html($stream); + $client = $this->createMock(HttpClientInterface::class); + $client->expects($this->once())->method('request')->with('POST', 'https://api.mailgun.net/v3/pa%24s/messages')->willReturn($response); + $transport = Transport::fromDsn('api://'.urlencode('u$er').':'.urlencode('pa$s').'@mailgun?region=us', $dispatcher, $client, $logger); + $transport->send($message); + $this->expectException(LogicException::class); Transport::fromDsn('foo://mailgun'); } diff --git a/src/Symfony/Component/Mailer/Transport.php b/src/Symfony/Component/Mailer/Transport.php index 690608c9f3..9c703f51df 100644 --- a/src/Symfony/Component/Mailer/Transport.php +++ b/src/Symfony/Component/Mailer/Transport.php @@ -64,6 +64,10 @@ class Transport throw new InvalidArgumentException(sprintf('The "%s" mailer DSN is invalid.', $dsn)); } + if (!isset($parsedDsn['scheme'])) { + throw new InvalidArgumentException(sprintf('The "%s" mailer DSN must contain a transport scheme.', $dsn)); + } + if (!isset($parsedDsn['host'])) { throw new InvalidArgumentException(sprintf('The "%s" mailer DSN must contain a mailer name.', $dsn)); } diff --git a/src/Symfony/Component/Messenger/Exception/DelayedMessageHandlingException.php b/src/Symfony/Component/Messenger/Exception/DelayedMessageHandlingException.php index 313d6f672c..4314a4f2fb 100644 --- a/src/Symfony/Component/Messenger/Exception/DelayedMessageHandlingException.php +++ b/src/Symfony/Component/Messenger/Exception/DelayedMessageHandlingException.php @@ -17,7 +17,7 @@ namespace Symfony\Component\Messenger\Exception; * * @author Tobias Nyholm */ -class DelayedMessageHandlingException extends \RuntimeException implements ExceptionInterface +class DelayedMessageHandlingException extends RuntimeException { private $exceptions; diff --git a/src/Symfony/Component/Messenger/Exception/MessageDecodingFailedException.php b/src/Symfony/Component/Messenger/Exception/MessageDecodingFailedException.php index 9e429ecc9b..f908d42b5e 100644 --- a/src/Symfony/Component/Messenger/Exception/MessageDecodingFailedException.php +++ b/src/Symfony/Component/Messenger/Exception/MessageDecodingFailedException.php @@ -16,6 +16,6 @@ namespace Symfony\Component\Messenger\Exception; * * @experimental in 4.3 */ -class MessageDecodingFailedException extends \InvalidArgumentException implements ExceptionInterface +class MessageDecodingFailedException extends InvalidArgumentException { } diff --git a/src/Symfony/Component/Messenger/Exception/NoHandlerForMessageException.php b/src/Symfony/Component/Messenger/Exception/NoHandlerForMessageException.php index a3fc0fa414..1e8e674d6a 100644 --- a/src/Symfony/Component/Messenger/Exception/NoHandlerForMessageException.php +++ b/src/Symfony/Component/Messenger/Exception/NoHandlerForMessageException.php @@ -16,6 +16,6 @@ namespace Symfony\Component\Messenger\Exception; * * @experimental in 4.3 */ -class NoHandlerForMessageException extends \LogicException implements ExceptionInterface +class NoHandlerForMessageException extends LogicException { } diff --git a/src/Symfony/Component/Messenger/Exception/UnknownSenderException.php b/src/Symfony/Component/Messenger/Exception/UnknownSenderException.php index 72fccfa566..ec138c1e20 100644 --- a/src/Symfony/Component/Messenger/Exception/UnknownSenderException.php +++ b/src/Symfony/Component/Messenger/Exception/UnknownSenderException.php @@ -16,6 +16,6 @@ namespace Symfony\Component\Messenger\Exception; * * @experimental in 4.3 */ -class UnknownSenderException extends \InvalidArgumentException implements ExceptionInterface +class UnknownSenderException extends InvalidArgumentException { } diff --git a/src/Symfony/Component/Messenger/Exception/UnrecoverableExceptionInterface.php b/src/Symfony/Component/Messenger/Exception/UnrecoverableExceptionInterface.php new file mode 100644 index 0000000000..d5dd01dbaa --- /dev/null +++ b/src/Symfony/Component/Messenger/Exception/UnrecoverableExceptionInterface.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Messenger\Exception; + +/** + * Marker interface for exceptions to indicate that handling a message will continue to fail. + * + * If something goes wrong while handling a message that's received from a transport + * and the message should not be retried, a handler can throw such an exception. + * + * @author Tobias Schultze + * + * @experimental in 4.3 + */ +interface UnrecoverableExceptionInterface extends \Throwable +{ +} diff --git a/src/Symfony/Component/Messenger/Exception/UnrecoverableMessageHandlingException.php b/src/Symfony/Component/Messenger/Exception/UnrecoverableMessageHandlingException.php index df08e79d8b..3a5e52be21 100644 --- a/src/Symfony/Component/Messenger/Exception/UnrecoverableMessageHandlingException.php +++ b/src/Symfony/Component/Messenger/Exception/UnrecoverableMessageHandlingException.php @@ -12,15 +12,12 @@ namespace Symfony\Component\Messenger\Exception; /** - * Thrown while handling a message to indicate that handling will continue to fail. - * - * If something goes wrong while handling a message that's received from a transport - * and the message should not be retried, a handler can throw this exception. + * A concrete implementation of UnrecoverableExceptionInterface that can be used directly. * * @author Frederic Bouchery * * @experimental in 4.3 */ -class UnrecoverableMessageHandlingException extends RuntimeException +class UnrecoverableMessageHandlingException extends RuntimeException implements UnrecoverableExceptionInterface { } diff --git a/src/Symfony/Component/Messenger/Exception/ValidationFailedException.php b/src/Symfony/Component/Messenger/Exception/ValidationFailedException.php index a05a213526..da87bcd2a0 100644 --- a/src/Symfony/Component/Messenger/Exception/ValidationFailedException.php +++ b/src/Symfony/Component/Messenger/Exception/ValidationFailedException.php @@ -18,7 +18,7 @@ use Symfony\Component\Validator\ConstraintViolationListInterface; * * @experimental in 4.3 */ -class ValidationFailedException extends \RuntimeException implements ExceptionInterface +class ValidationFailedException extends RuntimeException { private $violations; private $violatingMessage; diff --git a/src/Symfony/Component/Messenger/Middleware/DispatchAfterCurrentBusMiddleware.php b/src/Symfony/Component/Messenger/Middleware/DispatchAfterCurrentBusMiddleware.php index 4c098c79b7..19c714d1b8 100644 --- a/src/Symfony/Component/Messenger/Middleware/DispatchAfterCurrentBusMiddleware.php +++ b/src/Symfony/Component/Messenger/Middleware/DispatchAfterCurrentBusMiddleware.php @@ -112,7 +112,7 @@ final class QueuedEnvelope public function __construct(Envelope $envelope, StackInterface $stack) { - $this->envelope = $envelope; + $this->envelope = $envelope->withoutAll(DispatchAfterCurrentBusStamp::class); $this->stack = $stack; } diff --git a/src/Symfony/Component/Messenger/Tests/Middleware/DispatchAfterCurrentBusMiddlewareTest.php b/src/Symfony/Component/Messenger/Tests/Middleware/DispatchAfterCurrentBusMiddlewareTest.php index f2217affda..fe6d190c10 100644 --- a/src/Symfony/Component/Messenger/Tests/Middleware/DispatchAfterCurrentBusMiddlewareTest.php +++ b/src/Symfony/Component/Messenger/Tests/Middleware/DispatchAfterCurrentBusMiddlewareTest.php @@ -99,6 +99,53 @@ class DispatchAfterCurrentBusMiddlewareTest extends TestCase $messageBus->dispatch($message); } + public function testHandleDelayedEventFromQueue() + { + $message = new DummyMessage('Hello'); + $event = new DummyEvent('Event on queue'); + + $middleware = new DispatchAfterCurrentBusMiddleware(); + $commandHandlingMiddleware = $this->createMock(MiddlewareInterface::class); + $eventHandlingMiddleware = $this->createMock(MiddlewareInterface::class); + + // This bus simulates the bus that are used when messages come back form the queue + $messageBusAfterQueue = new MessageBus([ + // Create a new middleware + new DispatchAfterCurrentBusMiddleware(), + $eventHandlingMiddleware, + ]); + + $fakePutMessageOnQueue = $this->createMock(MiddlewareInterface::class); + $fakePutMessageOnQueue->expects($this->any()) + ->method('handle') + ->with($this->callback(function ($envelope) use ($messageBusAfterQueue) { + // Fake putting the message on the queue + // Fake reading the queue + // Now, we add the message back to a new bus. + $messageBusAfterQueue->dispatch($envelope); + + return true; + })) + ->willReturnArgument(0); + + $eventBus = new MessageBus([ + $middleware, + $fakePutMessageOnQueue, + ]); + + $messageBus = new MessageBus([ + $middleware, + new DispatchingMiddleware($eventBus, [ + new Envelope($event, [new DispatchAfterCurrentBusStamp()]), + ]), + $commandHandlingMiddleware, + ]); + + $this->expectHandledMessage($commandHandlingMiddleware, 0, $message); + $this->expectHandledMessage($eventHandlingMiddleware, 0, $event); + $messageBus->dispatch($message); + } + /** * @param MiddlewareInterface|MockObject $handlingMiddleware */ diff --git a/src/Symfony/Component/Messenger/Tests/RetryIntegrationTest.php b/src/Symfony/Component/Messenger/Tests/RetryIntegrationTest.php index 64ff20bae3..2447a0c82b 100644 --- a/src/Symfony/Component/Messenger/Tests/RetryIntegrationTest.php +++ b/src/Symfony/Component/Messenger/Tests/RetryIntegrationTest.php @@ -34,9 +34,9 @@ class RetryIntegrationTest extends TestCase $senderAndReceiver = new DummySenderAndReceiver(); $senderLocator = $this->createMock(ContainerInterface::class); - $senderLocator->method('has')->with('sender_alias')->willReturn(true); - $senderLocator->method('get')->with('sender_alias')->willReturn($senderAndReceiver); - $senderLocator = new SendersLocator([DummyMessage::class => ['sender_alias']], $senderLocator); + $senderLocator->method('has')->with('transportName')->willReturn(true); + $senderLocator->method('get')->with('transportName')->willReturn($senderAndReceiver); + $senderLocator = new SendersLocator([DummyMessage::class => ['transportName']], $senderLocator); $handler = new DummyMessageHandlerFailingFirstTimes(0); $throwingHandler = new DummyMessageHandlerFailingFirstTimes(1); @@ -52,7 +52,7 @@ class RetryIntegrationTest extends TestCase $envelope = new Envelope(new DummyMessage('API')); $bus->dispatch($envelope); - $worker = new Worker(['receiverName' => $senderAndReceiver], $bus, ['receiverName' => new MultiplierRetryStrategy()]); + $worker = new Worker(['transportName' => $senderAndReceiver], $bus, ['transportName' => new MultiplierRetryStrategy()]); $worker->run([], function (?Envelope $envelope) use ($worker) { if (null === $envelope) { $worker->stop(); diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/ConnectionTest.php b/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/ConnectionTest.php index 9393e210b7..83708c5085 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/ConnectionTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/ConnectionTest.php @@ -230,4 +230,90 @@ class ConnectionTest extends TestCase { Connection::buildConfiguration('doctrine://default?new_option=woops'); } + + public function testFind() + { + $queryBuilder = $this->getQueryBuilderMock(); + $driverConnection = $this->getDBALConnectionMock(); + $schemaSynchronizer = $this->getSchemaSynchronizerMock(); + $id = 1; + $stmt = $this->getStatementMock([ + 'id' => $id, + 'body' => '{"message":"Hi"}', + 'headers' => json_encode(['type' => DummyMessage::class]), + ]); + + $driverConnection + ->method('createQueryBuilder') + ->willReturn($queryBuilder); + $queryBuilder + ->method('where') + ->willReturn($queryBuilder); + $queryBuilder + ->method('getSQL') + ->willReturn(''); + $queryBuilder + ->method('getParameters') + ->willReturn([]); + $driverConnection + ->method('prepare') + ->willReturn($stmt); + + $connection = new Connection([], $driverConnection, $schemaSynchronizer); + $doctrineEnvelope = $connection->find($id); + $this->assertEquals(1, $doctrineEnvelope['id']); + $this->assertEquals('{"message":"Hi"}', $doctrineEnvelope['body']); + $this->assertEquals(['type' => DummyMessage::class], $doctrineEnvelope['headers']); + } + + public function testFindAll() + { + $queryBuilder = $this->getQueryBuilderMock(); + $driverConnection = $this->getDBALConnectionMock(); + $schemaSynchronizer = $this->getSchemaSynchronizerMock(); + $message1 = [ + 'id' => 1, + 'body' => '{"message":"Hi"}', + 'headers' => json_encode(['type' => DummyMessage::class]), + ]; + $message2 = [ + 'id' => 2, + 'body' => '{"message":"Hi again"}', + 'headers' => json_encode(['type' => DummyMessage::class]), + ]; + + $stmt = $this->getMockBuilder(Statement::class) + ->disableOriginalConstructor() + ->getMock(); + $stmt->expects($this->once()) + ->method('fetchAll') + ->willReturn([$message1, $message2]); + + $driverConnection + ->method('createQueryBuilder') + ->willReturn($queryBuilder); + $queryBuilder + ->method('where') + ->willReturn($queryBuilder); + $queryBuilder + ->method('getSQL') + ->willReturn(''); + $queryBuilder + ->method('getParameters') + ->willReturn([]); + $driverConnection + ->method('prepare') + ->willReturn($stmt); + + $connection = new Connection([], $driverConnection, $schemaSynchronizer); + $doctrineEnvelopes = $connection->findAll(); + + $this->assertEquals(1, $doctrineEnvelopes[0]['id']); + $this->assertEquals('{"message":"Hi"}', $doctrineEnvelopes[0]['body']); + $this->assertEquals(['type' => DummyMessage::class], $doctrineEnvelopes[0]['headers']); + + $this->assertEquals(2, $doctrineEnvelopes[1]['id']); + $this->assertEquals('{"message":"Hi again"}', $doctrineEnvelopes[1]['body']); + $this->assertEquals(['type' => DummyMessage::class], $doctrineEnvelopes[1]['headers']); + } } diff --git a/src/Symfony/Component/Messenger/Tests/WorkerTest.php b/src/Symfony/Component/Messenger/Tests/WorkerTest.php index 46e8149f6c..c82652fe1d 100644 --- a/src/Symfony/Component/Messenger/Tests/WorkerTest.php +++ b/src/Symfony/Component/Messenger/Tests/WorkerTest.php @@ -84,7 +84,7 @@ class WorkerTest extends TestCase public function testDispatchCausesRetry() { $receiver = new DummyReceiver([ - [new Envelope(new DummyMessage('Hello'), [new SentStamp('Some\Sender', 'sender_alias')])], + [new Envelope(new DummyMessage('Hello'), [new SentStamp('Some\Sender', 'transport1')])], ]); $bus = $this->getMockBuilder(MessageBusInterface::class)->getMock(); @@ -97,7 +97,7 @@ class WorkerTest extends TestCase $this->assertNotNull($redeliveryStamp); // retry count now at 1 $this->assertSame(1, $redeliveryStamp->getRetryCount()); - $this->assertSame('sender_alias', $redeliveryStamp->getSenderClassOrAlias()); + $this->assertSame('transport1', $redeliveryStamp->getSenderClassOrAlias()); // received stamp is removed $this->assertNull($envelope->last(ReceivedStamp::class)); @@ -108,7 +108,7 @@ class WorkerTest extends TestCase $retryStrategy = $this->getMockBuilder(RetryStrategyInterface::class)->getMock(); $retryStrategy->expects($this->once())->method('isRetryable')->willReturn(true); - $worker = new Worker(['receiver1' => $receiver], $bus, ['receiver1' => $retryStrategy]); + $worker = new Worker(['transport1' => $receiver], $bus, ['transport1' => $retryStrategy]); $worker->run([], function (?Envelope $envelope) use ($worker) { // stop after the messages finish if (null === $envelope) { @@ -123,7 +123,7 @@ class WorkerTest extends TestCase public function testDispatchCausesRejectWhenNoRetry() { $receiver = new DummyReceiver([ - [new Envelope(new DummyMessage('Hello'), [new SentStamp('Some\Sender', 'sender_alias')])], + [new Envelope(new DummyMessage('Hello'), [new SentStamp('Some\Sender', 'transport1')])], ]); $bus = $this->getMockBuilder(MessageBusInterface::class)->getMock(); @@ -132,7 +132,7 @@ class WorkerTest extends TestCase $retryStrategy = $this->getMockBuilder(RetryStrategyInterface::class)->getMock(); $retryStrategy->expects($this->once())->method('isRetryable')->willReturn(false); - $worker = new Worker(['receiver1' => $receiver], $bus, ['receiver1' => $retryStrategy]); + $worker = new Worker(['transport1' => $receiver], $bus, ['transport1' => $retryStrategy]); $worker->run([], function (?Envelope $envelope) use ($worker) { // stop after the messages finish if (null === $envelope) { @@ -155,7 +155,7 @@ class WorkerTest extends TestCase $retryStrategy = $this->getMockBuilder(RetryStrategyInterface::class)->getMock(); $retryStrategy->expects($this->never())->method('isRetryable'); - $worker = new Worker(['receiver1' => $receiver], $bus, ['receiver1' => $retryStrategy]); + $worker = new Worker(['transport1' => $receiver], $bus, ['transport1' => $retryStrategy]); $worker->run([], function (?Envelope $envelope) use ($worker) { // stop after the messages finish if (null === $envelope) { diff --git a/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php b/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php index 5b3a31c9ec..74a85bbfbe 100644 --- a/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php +++ b/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php @@ -155,7 +155,7 @@ class Connection return null; } - $doctrineEnvelope['headers'] = json_decode($doctrineEnvelope['headers'], true); + $doctrineEnvelope = $this->decodeEnvelopeHeaders($doctrineEnvelope); $queryBuilder = $this->driverConnection->createQueryBuilder() ->update($this->configuration['table_name']) @@ -238,7 +238,11 @@ class Connection $queryBuilder->setMaxResults($limit); } - return $this->executeQuery($queryBuilder->getSQL(), $queryBuilder->getParameters())->fetchAll(); + $data = $this->executeQuery($queryBuilder->getSQL(), $queryBuilder->getParameters())->fetchAll(); + + return array_map(function ($doctrineEnvelope) { + return $this->decodeEnvelopeHeaders($doctrineEnvelope); + }, $data); } public function find($id): ?array @@ -254,7 +258,7 @@ class Connection 'id' => $id, ])->fetch(); - return false === $data ? null : $data; + return false === $data ? null : $this->decodeEnvelopeHeaders($data); } private function createAvailableMessagesQueryBuilder(): QueryBuilder @@ -332,4 +336,11 @@ class Connection { return $dateTime->format('Y-m-d\TH:i:s'); } + + private function decodeEnvelopeHeaders(array $doctrineEnvelope): array + { + $doctrineEnvelope['headers'] = json_decode($doctrineEnvelope['headers'], true); + + return $doctrineEnvelope; + } } diff --git a/src/Symfony/Component/Messenger/Worker.php b/src/Symfony/Component/Messenger/Worker.php index b54382bd71..a51cd3506f 100644 --- a/src/Symfony/Component/Messenger/Worker.php +++ b/src/Symfony/Component/Messenger/Worker.php @@ -17,13 +17,11 @@ use Symfony\Component\Messenger\Event\WorkerMessageHandledEvent; use Symfony\Component\Messenger\Event\WorkerMessageReceivedEvent; use Symfony\Component\Messenger\Event\WorkerStoppedEvent; use Symfony\Component\Messenger\Exception\HandlerFailedException; -use Symfony\Component\Messenger\Exception\LogicException; -use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException; +use Symfony\Component\Messenger\Exception\UnrecoverableExceptionInterface; use Symfony\Component\Messenger\Retry\RetryStrategyInterface; use Symfony\Component\Messenger\Stamp\DelayStamp; use Symfony\Component\Messenger\Stamp\ReceivedStamp; use Symfony\Component\Messenger\Stamp\RedeliveryStamp; -use Symfony\Component\Messenger\Stamp\SentStamp; use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; @@ -150,7 +148,7 @@ class Worker implements WorkerInterface // add the delay and retry stamp info + remove ReceivedStamp $retryEnvelope = $envelope->with(new DelayStamp($delay)) - ->with(new RedeliveryStamp($retryCount, $this->getSenderClassOrAlias($envelope))) + ->with(new RedeliveryStamp($retryCount, $transportName)) ->withoutAll(ReceivedStamp::class); // re-send the message @@ -193,32 +191,10 @@ class Worker implements WorkerInterface private function shouldRetry(\Throwable $e, Envelope $envelope, RetryStrategyInterface $retryStrategy): bool { - if ($e instanceof UnrecoverableMessageHandlingException) { - return false; - } - - $sentStamp = $envelope->last(SentStamp::class); - if (null === $sentStamp) { - if (null !== $this->logger) { - $this->logger->warning('Message will not be retried because the SentStamp is missing and so the target sender cannot be determined.'); - } - + if ($e instanceof UnrecoverableExceptionInterface) { return false; } return $retryStrategy->isRetryable($envelope); } - - private function getSenderClassOrAlias(Envelope $envelope): string - { - /** @var SentStamp|null $sentStamp */ - $sentStamp = $envelope->last(SentStamp::class); - - if (null === $sentStamp) { - // should not happen, because of the check in shouldRetry() - throw new LogicException('Could not find SentStamp.'); - } - - return $sentStamp->getSenderAlias() ?: $sentStamp->getSenderClass(); - } } diff --git a/src/Symfony/Component/PropertyInfo/PropertyInfoCacheExtractor.php b/src/Symfony/Component/PropertyInfo/PropertyInfoCacheExtractor.php index 5731cf4458..0eb9f63a54 100644 --- a/src/Symfony/Component/PropertyInfo/PropertyInfoCacheExtractor.php +++ b/src/Symfony/Component/PropertyInfo/PropertyInfoCacheExtractor.php @@ -24,13 +24,6 @@ class PropertyInfoCacheExtractor implements PropertyInfoExtractorInterface, Prop { private $propertyInfoExtractor; private $cacheItemPool; - - /** - * A cache of property information, first keyed by the method called and - * then by the serialized method arguments. - * - * @var array - */ private $arrayCache = []; public function __construct(PropertyInfoExtractorInterface $propertyInfoExtractor, CacheItemPoolInterface $cacheItemPool) @@ -110,34 +103,22 @@ class PropertyInfoCacheExtractor implements PropertyInfoExtractorInterface, Prop } // Calling rawurlencode escapes special characters not allowed in PSR-6's keys - $encodedMethod = rawurlencode($method); - if (\array_key_exists($encodedMethod, $this->arrayCache) && \array_key_exists($serializedArguments, $this->arrayCache[$encodedMethod])) { - return $this->arrayCache[$encodedMethod][$serializedArguments]; + $key = rawurlencode($method.'.'.$serializedArguments); + + if (\array_key_exists($key, $this->arrayCache)) { + return $this->arrayCache[$key]; } - $item = $this->cacheItemPool->getItem($encodedMethod); + $item = $this->cacheItemPool->getItem($key); - $data = $item->get(); if ($item->isHit()) { - $this->arrayCache[$encodedMethod] = $data[$encodedMethod]; - // Only match if the specific arguments have been cached. - if (\array_key_exists($serializedArguments, $data[$encodedMethod])) { - return $this->arrayCache[$encodedMethod][$serializedArguments]; - } - } - - // It's possible that the method has been called, but with different - // arguments, in which case $data will already be initialized. - if (!$data) { - $data = []; + return $this->arrayCache[$key] = $item->get(); } $value = $this->propertyInfoExtractor->{$method}(...$arguments); - $data[$encodedMethod][$serializedArguments] = $value; - $this->arrayCache[$encodedMethod][$serializedArguments] = $value; - $item->set($data); + $item->set($value); $this->cacheItemPool->save($item); - return $this->arrayCache[$encodedMethod][$serializedArguments]; + return $this->arrayCache[$key] = $value; } } diff --git a/src/Symfony/Component/Security/Core/Encoder/Argon2iPasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/Argon2iPasswordEncoder.php index 888458ea76..4c568985e1 100644 --- a/src/Symfony/Component/Security/Core/Encoder/Argon2iPasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/Argon2iPasswordEncoder.php @@ -51,10 +51,6 @@ class Argon2iPasswordEncoder extends BasePasswordEncoder implements SelfSaltingE return true; } - if (class_exists('ParagonIE_Sodium_Compat') && method_exists('ParagonIE_Sodium_Compat', 'crypto_pwhash_is_available')) { - return \ParagonIE_Sodium_Compat::crypto_pwhash_is_available(); - } - return \function_exists('sodium_crypto_pwhash_str') || \extension_loaded('libsodium'); } diff --git a/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php index a05eb288e5..84d87c6e24 100644 --- a/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php @@ -33,8 +33,8 @@ final class NativePasswordEncoder implements PasswordEncoderInterface, SelfSalti $opsLimit = $opsLimit ?? max(6, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_MODERATE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_MODERATE : 6); $memLimit = $memLimit ?? max(64 * 1024 * 1024, \defined('SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE : 64 * 1024 * 1024); - if (2 > $opsLimit) { - throw new \InvalidArgumentException('$opsLimit must be 2 or greater.'); + if (3 > $opsLimit) { + throw new \InvalidArgumentException('$opsLimit must be 3 or greater.'); } if (10 * 1024 > $memLimit) { diff --git a/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php index 0cdfea718d..66a513f3d2 100644 --- a/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php @@ -37,8 +37,8 @@ final class SodiumPasswordEncoder implements PasswordEncoderInterface, SelfSalti $this->opsLimit = $opsLimit ?? max(6, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_MODERATE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_MODERATE : 6); $this->memLimit = $memLimit ?? max(64 * 1024 * 1024, \defined('SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE : 64 * 1024 * 2014); - if (2 > $this->opsLimit) { - throw new \InvalidArgumentException('$opsLimit must be 2 or greater.'); + if (3 > $this->opsLimit) { + throw new \InvalidArgumentException('$opsLimit must be 3 or greater.'); } if (10 * 1024 > $this->memLimit) { @@ -48,15 +48,7 @@ final class SodiumPasswordEncoder implements PasswordEncoderInterface, SelfSalti public static function isSupported(): bool { - if (\extension_loaded('libsodium') || \function_exists('sodium_crypto_pwhash_str')) { - return true; - } - - if (class_exists('ParagonIE_Sodium_Compat') && method_exists('ParagonIE_Sodium_Compat', 'crypto_pwhash_is_available')) { - return \ParagonIE_Sodium_Compat::crypto_pwhash_is_available(); - } - - return false; + return \function_exists('sodium_crypto_pwhash_str_needs_rehash') || \function_exists('Sodium\crypto_pwhash_str_needs_rehash'); } /** diff --git a/src/Symfony/Component/Validator/Constraints/AbstractComparison.php b/src/Symfony/Component/Validator/Constraints/AbstractComparison.php index 89c2690c08..435ced6eb8 100644 --- a/src/Symfony/Component/Validator/Constraints/AbstractComparison.php +++ b/src/Symfony/Component/Validator/Constraints/AbstractComparison.php @@ -14,6 +14,7 @@ namespace Symfony\Component\Validator\Constraints; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Exception\ConstraintDefinitionException; +use Symfony\Component\Validator\Exception\LogicException; /** * Used for the comparison of values. @@ -46,7 +47,7 @@ abstract class AbstractComparison extends Constraint } if (isset($options['propertyPath']) && !class_exists(PropertyAccess::class)) { - throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires the Symfony PropertyAccess component to use the "propertyPath" option.', \get_class($this))); + throw new LogicException(sprintf('The "%s" constraint requires the Symfony PropertyAccess component to use the "propertyPath" option.', \get_class($this))); } } diff --git a/src/Symfony/Component/VarExporter/README.md b/src/Symfony/Component/VarExporter/README.md index 180554ed1a..bb13960e0d 100644 --- a/src/Symfony/Component/VarExporter/README.md +++ b/src/Symfony/Component/VarExporter/README.md @@ -31,7 +31,7 @@ It also provides a few improvements over `var_export()`/`serialize()`: Resources --------- - * [Documentation](https://symfony.com/doc/current/components/var_exporter/introduction.html) + * [Documentation](https://symfony.com/doc/current/components/var_exporter.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) diff --git a/src/Symfony/Contracts/HttpClient/Test/Fixtures/web/index.php b/src/Symfony/Contracts/HttpClient/Test/Fixtures/web/index.php index 8d1762913c..bc613868f2 100644 --- a/src/Symfony/Contracts/HttpClient/Test/Fixtures/web/index.php +++ b/src/Symfony/Contracts/HttpClient/Test/Fixtures/web/index.php @@ -41,6 +41,17 @@ switch ($vars['REQUEST_URI']) { ob_start('ob_gzhandler'); break; + case '/103': + header('HTTP/1.1 103 Early Hints'); + header('Link: ; rel=preload; as=style', false); + header('Link: ; rel=preload; as=script', false); + echo "HTTP/1.1 200 OK\r\n"; + echo "Date: Fri, 26 May 2017 10:02:11 GMT\r\n"; + echo "Content-Length: 13\r\n"; + echo "\r\n"; + echo 'Here the body'; + exit; + case '/404': header('Content-Type: application/json', true, 404); break; diff --git a/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php b/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php index c995bc26fc..c0acd55cea 100644 --- a/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php +++ b/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php @@ -721,6 +721,15 @@ abstract class HttpClientTestCase extends TestCase $this->assertSame('/?a=a&b=b', $body['REQUEST_URI']); } + public function testInformationalResponse() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/103'); + + $this->assertSame('Here the body', $response->getContent()); + $this->assertSame(200, $response->getStatusCode()); + } + /** * @requires extension zlib */