diff --git a/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php b/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php index e26c2d5986..47901d3108 100644 --- a/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php +++ b/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php @@ -49,7 +49,7 @@ final class BodyRenderer implements BodyRendererInterface $previousRenderingKey = $messageContext[__CLASS__] ?? null; unset($messageContext[__CLASS__]); - $currentRenderingKey = md5(serialize([$messageContext, $message->getTextTemplate(), $message->getHtmlTemplate()])); + $currentRenderingKey = $this->getFingerPrint($message); if ($previousRenderingKey === $currentRenderingKey) { return; } @@ -77,6 +77,23 @@ final class BodyRenderer implements BodyRendererInterface $message->context($message->getContext() + [__CLASS__ => $currentRenderingKey]); } + private function getFingerPrint(TemplatedEmail $message): string + { + $messageContext = $message->getContext(); + unset($messageContext[__CLASS__]); + + $payload = [$messageContext, $message->getTextTemplate(), $message->getHtmlTemplate()]; + try { + $serialized = serialize($payload); + } catch (\Exception $e) { + // Serialization of 'Closure' is not allowed + // Happens when context contain a closure, in that case, we assume that context always change. + $serialized = random_bytes(8); + } + + return md5($serialized); + } + private function convertHtmlToText(string $html): string { if (null !== $this->converter) { diff --git a/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php b/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php index f13ab213c3..8ff343b684 100644 --- a/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php @@ -100,6 +100,27 @@ HTML; $this->assertEquals('reset', $email->getTextBody()); } + public function testRenderedOnceUnserializableContext() + { + $twig = new Environment(new ArrayLoader([ + 'text' => 'Text', + ])); + $renderer = new BodyRenderer($twig); + $email = (new TemplatedEmail()) + ->to('fabien@symfony.com') + ->from('helene@symfony.com') + ; + $email->textTemplate('text'); + $email->context([ + 'foo' => static function () { + return 'bar'; + }, + ]); + + $renderer->render($email); + $this->assertEquals('Text', $email->getTextBody()); + } + private function prepareEmail(?string $text, ?string $html, array $context = []): TemplatedEmail { $twig = new Environment(new ArrayLoader([ diff --git a/src/Symfony/Component/Console/Command/Command.php b/src/Symfony/Component/Console/Command/Command.php index 30f3796e6d..8e3afcf75f 100644 --- a/src/Symfony/Component/Console/Command/Command.php +++ b/src/Symfony/Component/Console/Command/Command.php @@ -429,9 +429,9 @@ class Command /** * Adds an option. * - * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts - * @param int|null $mode The option mode: One of the InputOption::VALUE_* constants - * @param string|string[]|int|bool|null $default The default value (must be null for InputOption::VALUE_NONE) + * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts + * @param int|null $mode The option mode: One of the InputOption::VALUE_* constants + * @param string|string[]|bool|null $default The default value (must be null for InputOption::VALUE_NONE) * * @throws InvalidArgumentException If option mode is invalid or incompatible * diff --git a/src/Symfony/Component/Console/Input/InputOption.php b/src/Symfony/Component/Console/Input/InputOption.php index 08be6eac94..a141f8106f 100644 --- a/src/Symfony/Component/Console/Input/InputOption.php +++ b/src/Symfony/Component/Console/Input/InputOption.php @@ -34,11 +34,11 @@ class InputOption private $description; /** - * @param string $name The option name - * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts - * @param int|null $mode The option mode: One of the VALUE_* constants - * @param string $description A description text - * @param string|string[]|int|bool|null $default The default value (must be null for self::VALUE_NONE) + * @param string $name The option name + * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts + * @param int|null $mode The option mode: One of the VALUE_* constants + * @param string $description A description text + * @param string|string[]|bool|null $default The default value (must be null for self::VALUE_NONE) * * @throws InvalidArgumentException If option mode is invalid or incompatible */ @@ -158,7 +158,7 @@ class InputOption /** * Sets the default value. * - * @param string|string[]|int|bool|null $default The default value + * @param string|string[]|bool|null $default The default value * * @throws LogicException When incorrect default value is given */ @@ -185,7 +185,7 @@ class InputOption /** * Returns the default value. * - * @return string|string[]|int|bool|null The default value + * @return string|string[]|bool|null The default value */ public function getDefault() { diff --git a/src/Symfony/Component/Validator/ConstraintViolation.php b/src/Symfony/Component/Validator/ConstraintViolation.php index 2004745fe2..e9aa1bd9f4 100644 --- a/src/Symfony/Component/Validator/ConstraintViolation.php +++ b/src/Symfony/Component/Validator/ConstraintViolation.php @@ -100,7 +100,7 @@ class ConstraintViolation implements ConstraintViolationInterface */ public function getMessageTemplate() { - return $this->messageTemplate; + return (string) $this->messageTemplate; } /** diff --git a/src/Symfony/Component/Validator/Tests/ConstraintViolationTest.php b/src/Symfony/Component/Validator/Tests/ConstraintViolationTest.php index a7734635aa..dbac96a8af 100644 --- a/src/Symfony/Component/Validator/Tests/ConstraintViolationTest.php +++ b/src/Symfony/Component/Validator/Tests/ConstraintViolationTest.php @@ -171,4 +171,19 @@ EOF; ))->getPropertyPath() ); } + + public function testRetrievedMessageTemplateIsAStringEvenIfNotSet() + { + self::assertSame( + '', + (new ConstraintViolation( + 'irrelevant', + null, + [], + 'irrelevant', + 'irrelevant', + null + ))->getMessageTemplate() + ); + } }