From 8fc3dcc45d37ba5daaea5549d228cfd10adfb506 Mon Sep 17 00:00:00 2001 From: Alexander Grimalovsky Date: Sun, 22 Jan 2023 15:38:39 +0300 Subject: [PATCH 01/56] [Messenger] Respect `isRetryable` decision of the retry strategy when deciding if failed message should be re-delivered --- .../SendFailedMessageForRetryListener.php | 7 +++--- .../SendFailedMessageForRetryListenerTest.php | 23 ++++++++++++++++++- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Messenger/EventListener/SendFailedMessageForRetryListener.php b/src/Symfony/Component/Messenger/EventListener/SendFailedMessageForRetryListener.php index dab74b203f795..ddd7f1b1f61b0 100644 --- a/src/Symfony/Component/Messenger/EventListener/SendFailedMessageForRetryListener.php +++ b/src/Symfony/Component/Messenger/EventListener/SendFailedMessageForRetryListener.php @@ -123,7 +123,8 @@ public static function getSubscribedEvents() private function shouldRetry(\Throwable $e, Envelope $envelope, RetryStrategyInterface $retryStrategy): bool { - if ($e instanceof RecoverableExceptionInterface) { + $isRetryable = $retryStrategy->isRetryable($envelope, $e); + if ($isRetryable && $e instanceof RecoverableExceptionInterface) { return true; } @@ -132,7 +133,7 @@ private function shouldRetry(\Throwable $e, Envelope $envelope, RetryStrategyInt if ($e instanceof HandlerFailedException) { $shouldNotRetry = true; foreach ($e->getNestedExceptions() as $nestedException) { - if ($nestedException instanceof RecoverableExceptionInterface) { + if ($isRetryable && $nestedException instanceof RecoverableExceptionInterface) { return true; } @@ -150,7 +151,7 @@ private function shouldRetry(\Throwable $e, Envelope $envelope, RetryStrategyInt return false; } - return $retryStrategy->isRetryable($envelope, $e); + return $isRetryable; } private function getRetryStrategyForTransport(string $alias): ?RetryStrategyInterface diff --git a/src/Symfony/Component/Messenger/Tests/EventListener/SendFailedMessageForRetryListenerTest.php b/src/Symfony/Component/Messenger/Tests/EventListener/SendFailedMessageForRetryListenerTest.php index a5fe10e85578b..8d795d7b86c23 100644 --- a/src/Symfony/Component/Messenger/Tests/EventListener/SendFailedMessageForRetryListenerTest.php +++ b/src/Symfony/Component/Messenger/Tests/EventListener/SendFailedMessageForRetryListenerTest.php @@ -63,7 +63,7 @@ public function testRecoverableStrategyCausesRetry() $senderLocator->expects($this->once())->method('has')->willReturn(true); $senderLocator->expects($this->once())->method('get')->willReturn($sender); $retryStategy = $this->createMock(RetryStrategyInterface::class); - $retryStategy->expects($this->never())->method('isRetryable'); + $retryStategy->expects($this->once())->method('isRetryable')->willReturn(true); $retryStategy->expects($this->once())->method('getWaitingTime')->willReturn(1000); $retryStrategyLocator = $this->createMock(ContainerInterface::class); $retryStrategyLocator->expects($this->once())->method('has')->willReturn(true); @@ -78,6 +78,27 @@ public function testRecoverableStrategyCausesRetry() $listener->onMessageFailed($event); } + public function testRetryIsOnlyAllowedWhenPermittedByRetryStrategy() + { + $senderLocator = $this->createMock(ContainerInterface::class); + $senderLocator->expects($this->never())->method('has'); + $senderLocator->expects($this->never())->method('get'); + $retryStrategy = $this->createMock(RetryStrategyInterface::class); + $retryStrategy->expects($this->once())->method('isRetryable')->willReturn(false); + $retryStrategy->expects($this->never())->method('getWaitingTime'); + $retryStrategyLocator = $this->createMock(ContainerInterface::class); + $retryStrategyLocator->expects($this->once())->method('has')->willReturn(true); + $retryStrategyLocator->expects($this->once())->method('get')->willReturn($retryStrategy); + + $listener = new SendFailedMessageForRetryListener($senderLocator, $retryStrategyLocator); + + $exception = new RecoverableMessageHandlingException('retry'); + $envelope = new Envelope(new \stdClass()); + $event = new WorkerMessageFailedEvent($envelope, 'my_receiver', $exception); + + $listener->onMessageFailed($event); + } + public function testEnvelopeIsSentToTransportOnRetry() { $exception = new \Exception('no!'); From 6d5400a5d9ee0c20f4439f7161a81eed58add375 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 28 Feb 2023 14:26:05 +0100 Subject: [PATCH 02/56] [PropertyInfo] Fix phpDocExtractor nullable array value type --- .../Tests/Extractor/PhpDocExtractorTest.php | 4 ++- .../Extractor/ReflectionExtractorTest.php | 6 ++++ .../PropertyInfo/Tests/Fixtures/Dummy.php | 10 +++++++ .../PropertyInfo/Util/PhpDocTypeHelper.php | 30 ++++++------------- 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php index c6a02b5f2f3e4..b3489d9fb0c10 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php @@ -169,7 +169,7 @@ public function testExtractCollection($property, array $type = null, $shortDescr public static function provideCollectionTypes() { return [ - ['iteratorCollection', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Iterator', true, null, new Type(Type::BUILTIN_TYPE_STRING))], null, null], + ['iteratorCollection', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Iterator', true, [new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_INT)], new Type(Type::BUILTIN_TYPE_STRING))], null, null], ['iteratorCollectionWithKey', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Iterator', true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))], null, null], [ 'nestedIterators', @@ -265,6 +265,8 @@ public static function typesWithCustomPrefixesProvider() ['i', [new Type(Type::BUILTIN_TYPE_STRING, true), new Type(Type::BUILTIN_TYPE_INT, true)], null, null], ['j', [new Type(Type::BUILTIN_TYPE_OBJECT, true, 'DateTime')], null, null], ['nullableCollectionOfNonNullableElements', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_INT, false))], null, null], + ['nonNullableCollectionOfNullableElements', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_INT, true))], null, null], + ['nullableCollectionOfMultipleNonNullableElementTypes', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true, new Type(Type::BUILTIN_TYPE_INT), [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING)])], null, null], ['donotexist', null, null, null], ['staticGetter', null, null, null], ['staticSetter', null, null, null], diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php index b7955584d8c36..5f81dd5a65186 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php @@ -62,6 +62,8 @@ public function testGetProperties() 'i', 'j', 'nullableCollectionOfNonNullableElements', + 'nonNullableCollectionOfNullableElements', + 'nullableCollectionOfMultipleNonNullableElementTypes', 'emptyVar', 'iteratorCollection', 'iteratorCollectionWithKey', @@ -124,6 +126,8 @@ public function testGetPropertiesWithCustomPrefixes() 'i', 'j', 'nullableCollectionOfNonNullableElements', + 'nonNullableCollectionOfNullableElements', + 'nullableCollectionOfMultipleNonNullableElementTypes', 'emptyVar', 'iteratorCollection', 'iteratorCollectionWithKey', @@ -175,6 +179,8 @@ public function testGetPropertiesWithNoPrefixes() 'i', 'j', 'nullableCollectionOfNonNullableElements', + 'nonNullableCollectionOfNullableElements', + 'nullableCollectionOfMultipleNonNullableElementTypes', 'emptyVar', 'iteratorCollection', 'iteratorCollectionWithKey', diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php index 8d956a1103fc0..2fb3d2e0f807c 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php @@ -98,6 +98,16 @@ class Dummy extends ParentDummy */ public $nullableCollectionOfNonNullableElements; + /** + * @var array + */ + public $nonNullableCollectionOfNullableElements; + + /** + * @var null|array + */ + public $nullableCollectionOfMultipleNonNullableElementTypes; + /** * @var array */ diff --git a/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php b/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php index 2c858c3bf9f8b..44a4614985563 100644 --- a/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php +++ b/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php @@ -115,15 +115,10 @@ private function createType(DocType $type, bool $nullable, string $docType = nul [$phpType, $class] = $this->getPhpTypeAndClass((string) $fqsen); - $key = $this->getTypes($type->getKeyType()); - $value = $this->getTypes($type->getValueType()); + $keys = $this->getTypes($type->getKeyType()); + $values = $this->getTypes($type->getValueType()); - // More than 1 type returned means it is a Compound type, which is - // not handled by Type, so better use a null value. - $key = 1 === \count($key) ? $key[0] : null; - $value = 1 === \count($value) ? $value[0] : null; - - return new Type($phpType, $nullable, $class, true, $key, $value); + return new Type($phpType, $nullable, $class, true, $keys, $values); } // Cannot guess @@ -131,27 +126,20 @@ private function createType(DocType $type, bool $nullable, string $docType = nul return null; } - if (str_ends_with($docType, '[]')) { - $collectionKeyType = new Type(Type::BUILTIN_TYPE_INT); - $collectionValueType = $this->createType($type, false, substr($docType, 0, -2)); + if (str_ends_with($docType, '[]') && $type instanceof Array_) { + $collectionKeyTypes = new Type(Type::BUILTIN_TYPE_INT); + $collectionValueTypes = $this->getTypes($type->getValueType()); - return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, $collectionKeyType, $collectionValueType); + return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, $collectionKeyTypes, $collectionValueTypes); } if ((str_starts_with($docType, 'list<') || str_starts_with($docType, 'array<')) && $type instanceof Array_) { // array is converted to x[] which is handled above // so it's only necessary to handle array here - $collectionKeyType = $this->getTypes($type->getKeyType())[0]; - + $collectionKeyTypes = $this->getTypes($type->getKeyType()); $collectionValueTypes = $this->getTypes($type->getValueType()); - if (1 != \count($collectionValueTypes)) { - // the Type class does not support union types yet, so assume that no type was defined - $collectionValueType = null; - } else { - $collectionValueType = $collectionValueTypes[0]; - } - return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, $collectionKeyType, $collectionValueType); + return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, $collectionKeyTypes, $collectionValueTypes); } $docType = $this->normalizeType($docType); From 3547dabea9cf4e10164c215d88d4813a180a6e38 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 28 Apr 2023 15:29:42 +0200 Subject: [PATCH 03/56] Update CHANGELOG for 5.4.23 --- CHANGELOG-5.4.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/CHANGELOG-5.4.md b/CHANGELOG-5.4.md index 0d197bce06163..8241220390c11 100644 --- a/CHANGELOG-5.4.md +++ b/CHANGELOG-5.4.md @@ -7,6 +7,40 @@ in 5.4 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v5.4.0...v5.4.1 +* 5.4.23 (2023-04-28) + + * bug #50143 [Console] trim(): Argument #1 () must be of type string, bool given (danepowell) + * bug #50066 [Dumper] Trim leading newlines when checking if value begins with a space (bradtreloar) + * bug #50111 Fix the list of supported shells for completions in a phar (stof) + * bug #50074 [Cache] Send Predis SSL options in the $hosts parameter (magnusnordlander) + * bug #50099 [Cache] Fix success interpretation when pruning cache (staabm) + * bug #50092 [Security] Fix return type of AuthenticationSuccessHandlerInterface::onAuthenticationSuccess() (nicolas-grekas) + * bug #50072 [HttpClient] Fix global state preventing two CurlHttpClient instances from working together (nicolas-grekas) + * bug #50017 [Validator] Fix support of Enum to `ConstraintValidator::formatValue` (PhoneixS) + * bug #49356 [Process] Path resolution changes for PHP in the cgi-fcgi mode (isdn) + * bug #48886 [Console] Fix computing column width containing multibyte chars (cay89) + * bug #50049 [Messenger] Fix deprecation layer of RedeliveryStamp (nicolas-grekas) + * bug #47505 [Mime] Form field values with integer keys not resolved correctly (claudiu-cristea) + * bug #50048 [PhpUnitBridge] Fix PHPUnit 10.1 compatibility (enumag) + * bug #50047 [VarDumper] Make the server TCP connection sync (ogizanagi) + * bug #48837 [Messenger] [Redis] Fixed problem where worker stops handling messages on first empty message (jvmanji) + * bug #49317 [Messenger] Fix warning message on failed messenger show command (gstapinato) + * bug #49992 [Mailer] [Mailjet] Use body MessageID instead of X-MJ-Request-GUID (Starfox64) + * bug #48972 [HttpFoundation] Fix memory limit problems in BinaryFileResponse (glady) + * bug #48108 [PropertyAccess] Readonly properties must have no PropertyWriteInfo (CasvanDongen) + * bug #49009 [Form] Cast choices value callback result to string (Matth--) + * bug #49537 [Serializer] Unexpected value should throw UnexpectedValueException (ThomasTr) + * bug #49581 Avoid leading .. for temporary files from Filesystem recursive remove (giosh94mhz) + * bug #50036 [ErrorHandler] Don't throw deprecations for HttplugClient (nicolas-grekas) + * bug #50024 [Serializer] Fix denormalization of object with typed constructor arg (not castable) and with COLLECT_DENORMALIZATION_ERRORS (lyrixx) + * bug #50004 [HttpClient] fix proxied redirects in curl client (matthi4s) + * bug #50008 [Intl] Update the ICU data to 73.1 (jderusse) + * bug #49987 [Console] Restoring the ability to output unicode text to the Win10 console (aleksandr-shevchenko) + * bug #49957 [ErrorHandler] Fix sending `Vary` header with `SerializerErrorRenderer` (nicolas-grekas) + * bug #49983 [DomCrawler] Avoid passing null to substr/strrpos methods (VincentLanglet) + * bug #49079 [DoctrineBridge] fix issue with missing stopwatch events (dmaicher) + * bug #49926 [HttpClient] Fix canceling MockResponse (fancyweb) + * 5.4.22 (2023-03-31) * bug #49618 [Serializer] Preserve array keys while denormalize variadic parameters (melya) From 06f5291de558b750579a66a1f4f77afc5406c57d Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 28 Apr 2023 15:29:47 +0200 Subject: [PATCH 04/56] Update CONTRIBUTORS for 5.4.23 --- CONTRIBUTORS.md | 106 +++++++++++++++++++++++++++++++----------------- 1 file changed, 68 insertions(+), 38 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 4f274c985c33f..24afd64907140 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -16,8 +16,8 @@ The Symfony Connect username in parenthesis allows to get more information - Grégoire Pineau (lyrixx) - Wouter de Jong (wouterj) - Maxime Steinhausser (ogizanagi) - - Kévin Dunglas (dunglas) - Christophe Coevoet (stof) + - Kévin Dunglas (dunglas) - Jordi Boggiano (seldaek) - Roland Franssen (ro0) - Victor Berchet (victor) @@ -41,8 +41,8 @@ The Symfony Connect username in parenthesis allows to get more information - Jan Schädlich (jschaedl) - Lukas Kahwe Smith (lsmith) - Jérôme Tamarelle (gromnan) - - Martin Hasoň (hason) - Kevin Bond (kbond) + - Martin Hasoň (hason) - Jeremy Mikola (jmikola) - Jean-François Simon (jfsimon) - Benjamin Eberlei (beberlei) @@ -56,21 +56,21 @@ The Symfony Connect username in parenthesis allows to get more information - Antoine Makdessi (amakdessi) - Laurent VOULLEMIER (lvo) - Pierre du Plessis (pierredup) + - Antoine Lamirault (alamirault) - Grégoire Paris (greg0ire) - Jonathan Wage (jwage) - - Antoine Lamirault (alamirault) - Titouan Galopin (tgalopin) - David Maicher (dmaicher) - - Alexander Schranz (alexander-schranz) - Gábor Egyed (1ed) - - Alexandre Salomé (alexandresalome) - Mathieu Santostefano (welcomattic) + - Alexander Schranz (alexander-schranz) + - Alexandre Salomé (alexandresalome) - William DURAND + - Mathieu Lechat (mat_the_cat) - ornicar - Dany Maillard (maidmaid) - Eriksen Costa - Diego Saint Esteben (dosten) - - Mathieu Lechat (mat_the_cat) - stealth35 ‏ (stealth35) - Alexander Mols (asm89) - Francis Besset (francisbesset) @@ -79,8 +79,8 @@ The Symfony Connect username in parenthesis allows to get more information - Iltar van der Berg - Miha Vrhovnik (mvrhov) - Mathieu Piot (mpiot) - - Saša Stamenković (umpirsky) - Vincent Langlet (deviling) + - Saša Stamenković (umpirsky) - Alex Pott - Guilhem N (guilhemn) - Vladimir Reznichenko (kalessil) @@ -90,26 +90,26 @@ The Symfony Connect username in parenthesis allows to get more information - Bilal Amarni (bamarni) - Eriksen Costa - Florin Patan (florinpatan) + - Konstantin Myakshin (koc) - Peter Rehm (rpet) + - Ruud Kamphuis (ruudk) - Henrik Bjørnskov (henrikbjorn) - David Buchmann (dbu) - - Ruud Kamphuis (ruudk) - - Konstantin Myakshin (koc) - Andrej Hudec (pulzarraider) - Julien Falque (julienfalque) - Massimiliano Arione (garak) + - Jáchym Toušek (enumag) - Douglas Greenshields (shieldo) - Christian Raue - Fran Moreno (franmomu) - - Jáchym Toušek (enumag) - Mathias Arlaud (mtarld) - Graham Campbell (graham) - Michel Weimerskirch (mweimerskirch) - Eric Clemmons (ericclemmons) - Issei Murasawa (issei_m) - Malte Schlüter (maltemaltesich) - - Vasilij Dusko - Denis (yethee) + - Vasilij Dusko - Arnout Boks (aboks) - Charles Sarrazin (csarrazi) - Przemysław Bogusz (przemyslaw-bogusz) @@ -118,8 +118,10 @@ The Symfony Connect username in parenthesis allows to get more information - Maxime Helias (maxhelias) - Ener-Getick - Sebastiaan Stok (sstok) + - Tugdual Saunier (tucksaun) - Jérôme Vasseur (jvasseur) - Ion Bazan (ionbazan) + - Rokas Mikalkėnas (rokasm) - Lee McDermott - Brandon Turner - Luis Cordova (cordoval) @@ -131,7 +133,6 @@ The Symfony Connect username in parenthesis allows to get more information - Smaine Milianni (ismail1432) - John Wards (johnwards) - Dariusz Ruminski - - Rokas Mikalkėnas (rokasm) - Lars Strojny (lstrojny) - Antoine Hérault (herzult) - Konstantin.Myakshin @@ -147,7 +148,6 @@ The Symfony Connect username in parenthesis allows to get more information - Andreas Braun - Teoh Han Hui (teohhanhui) - YaFou - - Tugdual Saunier (tucksaun) - Gary PEGEOT (gary-p) - Chris Wilkinson (thewilkybarkid) - Brice BERNARD (brikou) @@ -163,6 +163,7 @@ The Symfony Connect username in parenthesis allows to get more information - Jeroen Spee (jeroens) - Michael Babker (mbabker) - Włodzimierz Gajda (gajdaw) + - Hugo Alliaume (kocal) - Christian Scheb - Guillaume (guill) - Christopher Hertel (chertel) @@ -171,7 +172,6 @@ The Symfony Connect username in parenthesis allows to get more information - Olivier Dolbeau (odolbeau) - Florian Voutzinos (florianv) - zairig imad (zairigimad) - - Hugo Alliaume (kocal) - Colin Frei - Javier Spagnoletti (phansys) - excelwebzone @@ -260,6 +260,7 @@ The Symfony Connect username in parenthesis allows to get more information - Sokolov Evgeniy (ewgraf) - Stadly - Justin Hileman (bobthecow) + - Bastien Jaillot (bastnic) - Tom Van Looy (tvlooy) - Niels Keurentjes (curry684) - Vyacheslav Pavlov @@ -281,7 +282,6 @@ The Symfony Connect username in parenthesis allows to get more information - Filippo Tessarotto (slamdunk) - 77web - Bohan Yang (brentybh) - - Bastien Jaillot (bastnic) - W0rma - Matthieu Ouellette-Vachon (maoueh) - Lynn van der Berg (kjarli) @@ -293,6 +293,7 @@ The Symfony Connect username in parenthesis allows to get more information - Tyson Andre - GDIBass - Samuel NELA (snela) + - Romain Monteil (ker0x) - dFayet - gnito-org - Karoly Gossler (connorhu) @@ -327,7 +328,6 @@ The Symfony Connect username in parenthesis allows to get more information - D (denderello) - Jonathan Scheiber (jmsche) - DQNEO - - Romain Monteil (ker0x) - Andrii Bodnar - Artem (artemgenvald) - ivan @@ -377,6 +377,7 @@ The Symfony Connect username in parenthesis allows to get more information - Hidde Wieringa (hiddewie) - Christopher Davis (chrisguitarguy) - Lukáš Holeczy (holicz) + - Michael Lee (zerustech) - Florian Lonqueu-Brochard (florianlb) - Leszek Prabucki (l3l0) - Emanuele Panzeri (thepanz) @@ -418,15 +419,18 @@ The Symfony Connect username in parenthesis allows to get more information - Antonio Jose Cerezo (ajcerezo) - Marcin Szepczynski (czepol) - Lescot Edouard (idetox) + - Loïc Frémont (loic425) - Rob Frawley 2nd (robfrawley) - Mohammad Emran Hasan (phpfour) + - Allison Guilhem (a_guilhem) - Dmitriy Mamontov (mamontovdmitriy) + - Kévin THERAGE (kevin_therage) - Nikita Konstantinov (unkind) - - Michael Lee (zerustech) - Dariusz - Francois Zaninotto - Laurent Masforné (heisenberg) - Claude Khedhiri (ck-developer) + - Giorgio Premi - Daniel Tschinder - Christian Schmidt - Alexander Kotynia (olden) @@ -507,20 +511,18 @@ The Symfony Connect username in parenthesis allows to get more information - Frank de Jonge - Chris Tanaskoski - julien57 - - Loïc Frémont (loic425) + - Renan (renanbr) - Ippei Sumida (ippey_s) - Ben Ramsey (ramsey) - - Allison Guilhem (a_guilhem) - Matthieu Auger (matthieuauger) - - Kévin THERAGE (kevin_therage) - Josip Kruslin (jkruslin) - - Giorgio Premi - renanbr - Maxim Dovydenok (shiftby) - Sébastien Lavoie (lavoiesl) - Alex Rock (pierstoval) - Wodor Wodorski - Beau Simensen (simensen) + - Magnus Nordlander (magnusnordlander) - Robert Kiss (kepten) - Zan Baldwin (zanbaldwin) - Antonio J. García Lagar (ajgarlag) @@ -535,10 +537,12 @@ The Symfony Connect username in parenthesis allows to get more information - Pascal Luna (skalpa) - Wouter Van Hecke - Michael Holm (hollo) + - Yassine Guedidi (yguedidi) - Giso Stallenberg (gisostallenberg) - Blanchon Vincent (blanchonvincent) - William Arslett (warslett) - Jérémy REYNAUD (babeuloula) + - Daniel Burger - Christian Schmidt - Gonzalo Vilaseca (gonzalovilaseca) - Vadim Borodavko (javer) @@ -596,6 +600,7 @@ The Symfony Connect username in parenthesis allows to get more information - Emanuele Gaspari (inmarelibero) - Dariusz Rumiński - Terje Bråten + - Florent Morselli (spomky_) - Gennadi Janzen - James Hemery - Egor Taranov @@ -609,6 +614,7 @@ The Symfony Connect username in parenthesis allows to get more information - Khoo Yong Jun - Christin Gruber (christingruber) - Jeremy Livingston (jeremylivingston) + - Tobias Bönner - Julien Turby - scyzoryck - Greg Anderson @@ -631,7 +637,6 @@ The Symfony Connect username in parenthesis allows to get more information - Angelov Dejan (angelov) - DT Inier (gam6itko) - Matthew Lewinski (lewinski) - - Magnus Nordlander (magnusnordlander) - Ricard Clau (ricardclau) - Dmitrii Tarasov (dtarasov) - Philipp Kolesnikov @@ -703,6 +708,7 @@ The Symfony Connect username in parenthesis allows to get more information - Jelle Raaijmakers (gmta) - Roberto Nygaard - Joshua Nye + - Jordane VASPARD (elementaire) - Dalibor Karlović - Randy Geraads - Sanpi (sanpi) @@ -746,6 +752,7 @@ The Symfony Connect username in parenthesis allows to get more information - Hassan Amouhzi - Antonin CLAUZIER (0x346e3730) - Andrei C. (moldman) + - Samaël Villette (samadu61) - Tamas Szijarto - stlrnz - Adrien Wilmet (adrienfr) @@ -830,7 +837,6 @@ The Symfony Connect username in parenthesis allows to get more information - Sebastian Paczkowski (sebpacz) - Dragos Protung (dragosprotung) - Thiago Cordeiro (thiagocordeiro) - - Florent Morselli (spomky_) - Julien Maulny - Brian King - Paul Oms @@ -845,9 +851,7 @@ The Symfony Connect username in parenthesis allows to get more information - Jon Gotlin (jongotlin) - Jeanmonod David (jeanmonod) - Daniel González (daniel.gonzalez) - - Renan (renanbr) - Webnet team (webnet) - - Tobias Bönner - Berny Cantos (xphere81) - Mátyás Somfai (smatyas) - Simon Leblanc (leblanc_simon) @@ -856,6 +860,7 @@ The Symfony Connect username in parenthesis allows to get more information - Niklas Fiekas - Mark Challoner (markchalloner) - Markus Bachmann (baachi) + - Matthieu Lempereur (mryamous) - Roger Guasch (rogerguasch) - Luis Tacón (lutacon) - Alex Hofbauer (alexhofbauer) @@ -863,7 +868,9 @@ The Symfony Connect username in parenthesis allows to get more information - lancergr - Ivan Nikolaev (destillat) - Xavier Leune (xleune) + - Matthieu Calie (matth--) - Ben Roberts (benr77) + - Benjamin Georgeault (wedgesama) - Joost van Driel (j92) - ampaze - Arturs Vonda @@ -900,7 +907,6 @@ The Symfony Connect username in parenthesis allows to get more information - Adam Harvey - ilyes kooli (skafandri) - Anton Bakai - - Daniel Burger - Sam Fleming (sam_fleming) - Alex Bakhturin - Brayden Williams (redstar504) @@ -944,6 +950,7 @@ The Symfony Connect username in parenthesis allows to get more information - mcben - Jérôme Vieilledent (lolautruche) - Filip Procházka (fprochazka) + - Alex Kalineskou - stoccc - Markus Lanthaler (lanthaler) - Gigino Chianese (sajito) @@ -1015,11 +1022,9 @@ The Symfony Connect username in parenthesis allows to get more information - Matthias Schmidt - Lenar Lõhmus - Ilija Tovilo (ilijatovilo) - - Samaël Villette (samadu61) - Zach Badgett (zachbadgett) - Loïc Faugeron - Aurélien Fredouelle - - Jordane VASPARD (elementaire) - Pavel Campr (pcampr) - Forfarle (forfarle) - Johnny Robeson (johnny) @@ -1101,7 +1106,6 @@ The Symfony Connect username in parenthesis allows to get more information - Giuseppe Campanelli - Valentin - pizzaminded - - Matthieu Calie (matth--) - Stéphane Escandell (sescandell) - ivan - linh @@ -1140,7 +1144,6 @@ The Symfony Connect username in parenthesis allows to get more information - Jacek Wilczyński (jacekwilczynski) - Hany el-Kerdany - Wang Jingyu - - Benjamin Georgeault (wedgesama) - Åsmund Garfors - Maxime Douailin - Jean Pasdeloup @@ -1163,6 +1166,7 @@ The Symfony Connect username in parenthesis allows to get more information - Łukasz Chruściel (lchrusciel) - Jan Vernieuwe (vernija) - zenmate + - Cédric Anne - j.schmitt - Georgi Georgiev - David Fuhr @@ -1171,6 +1175,7 @@ The Symfony Connect username in parenthesis allows to get more information - mwos - Aurimas Niekis (gcds) - Volker Killesreiter (ol0lll) + - Benjamin Zaslavsky (tiriel) - Vedran Mihočinec (v-m-i) - creiner - RevZer0 (rav) @@ -1212,6 +1217,7 @@ The Symfony Connect username in parenthesis allows to get more information - Atthaphon Urairat - Jon Green (jontjs) - Mickaël Isaert (misaert) + - alexandre.lassauge - Israel J. Carberry - Julius Kiekbusch - Miquel Rodríguez Telep (mrtorrent) @@ -1261,6 +1267,7 @@ The Symfony Connect username in parenthesis allows to get more information - Szijarto Tamas - Arend Hummeling - Makdessi Alex + - Phil E. Taylor (philetaylor) - Juan Miguel Besada Vidal (soutlink) - dlorek - Stuart Fyfe @@ -1283,6 +1290,7 @@ The Symfony Connect username in parenthesis allows to get more information - dbrekelmans - Piet Steinhart - mousezheng + - Nicolas Dousson - Rémy LESCALLIER - Simon Schick (simonsimcity) - Victor Macko (victor_m) @@ -1466,6 +1474,7 @@ The Symfony Connect username in parenthesis allows to get more information - Barney Hanlon - Bart Wach - Jos Elstgeest + - Thorry84 - Kirill Lazarev - Serhii Smirnov - Martins Eglitis @@ -1671,7 +1680,6 @@ The Symfony Connect username in parenthesis allows to get more information - andrey1s - Abhoryo - Fabian Vogler (fabian) - - Yassine Guedidi (yguedidi) - Korvin Szanto - Simon Ackermann - Stéphan Kochen @@ -1734,6 +1742,7 @@ The Symfony Connect username in parenthesis allows to get more information - Jörn Lang - David Marín Carreño (davefx) - Fabien LUCAS (flucas2) + - Alex (garrett) - Hidde Boomsma (hboomsma) - Johan Wilfer (johanwilfer) - Toby Griffiths (tog) @@ -1774,7 +1783,6 @@ The Symfony Connect username in parenthesis allows to get more information - florian-michael-mast - Henry Snoek - Vlad Dumitrache - - Alex Kalineskou - Derek ROTH - Jeremy Benoist - Ben Johnson @@ -1833,6 +1841,7 @@ The Symfony Connect username in parenthesis allows to get more information - vladyslavstartsev - Kévin - Marc Abramowitz + - Markus Staab - michal - Martijn Evers - Sjoerd Adema @@ -1991,6 +2000,7 @@ The Symfony Connect username in parenthesis allows to get more information - Daniel Alejandro Castro Arellano (lexcast) - Aleksandar Dimitrov (netbull) - Gary Houbre (thegarious) + - Vincent Chalamon - Thomas Jarrand - Baptiste Leduc (bleduc) - Antoine Bluchet (soyuka) @@ -2032,6 +2042,7 @@ The Symfony Connect username in parenthesis allows to get more information - Sander Marechal - Franz Wilding (killerpoke) - Ferenczi Krisztian (fchris82) + - Artyum Petrov - Oleg Golovakhin (doc_tr) - Icode4Food (icode4food) - Radosław Benkel @@ -2050,6 +2061,7 @@ The Symfony Connect username in parenthesis allows to get more information - Sander Coolen (scoolen) - Nicolas Le Goff (nlegoff) - Anne-Sophie Bachelard + - Gordienko Vladislav - Marvin Butkereit - Ben Oman - Chris de Kok @@ -2065,6 +2077,7 @@ The Symfony Connect username in parenthesis allows to get more information - Zachary Tong (polyfractal) - Ashura - Hryhorii Hrebiniuk + - Nsbx - Alex Plekhanov - johnstevenson - hamza @@ -2076,6 +2089,7 @@ The Symfony Connect username in parenthesis allows to get more information - Artem (digi) - boite - Silvio Ginter + - Peter Culka - MGDSoft - joris - Vadim Tyukov (vatson) @@ -2117,6 +2131,7 @@ The Symfony Connect username in parenthesis allows to get more information - Jordi Rejas - Troy McCabe - Ville Mattila + - gstapinato - gr1ev0us - Léo VINCENT - mlazovla @@ -2141,7 +2156,6 @@ The Symfony Connect username in parenthesis allows to get more information - MARYNICH Mikhail (mmarynich-ext) - Viktor Novikov (nowiko) - Paul Mitchum (paul-m) - - Phil E. Taylor (philetaylor) - Angel Koilov (po_taka) - Dan Finnie - Ken Marfilla (marfillaster) @@ -2211,6 +2225,7 @@ The Symfony Connect username in parenthesis allows to get more information - Abderrahman DAIF (death_maker) - Yann Rabiller (einenlum) - Jochen Bayer (jocl) + - VAN DER PUTTE Guillaume (guillaume_vdp) - Patrick Carlo-Hickman - Bruno MATEU - Jeremy Bush @@ -2235,6 +2250,7 @@ The Symfony Connect username in parenthesis allows to get more information - BRAMILLE Sébastien (oktapodia) - Artem Kolesnikov (tyomo4ka) - Gustavo Adrian + - Matthias Neid - Yannick - Kuzia - Vladimir Luchaninov (luchaninov) @@ -2242,6 +2258,7 @@ The Symfony Connect username in parenthesis allows to get more information - rchoquet - v.shevelev - gitlost + - radar3301 - Taras Girnyk - Sergio - Mehrdad @@ -2311,6 +2328,7 @@ The Symfony Connect username in parenthesis allows to get more information - Dmitri Petmanson - heccjj - Alexandre Melard + - Rafał Toboła - AlbinoDrought - Jay Klehr - Sergey Yuferev @@ -2330,11 +2348,11 @@ The Symfony Connect username in parenthesis allows to get more information - Jelte Steijaert (jelte) - David Négrier (moufmouf) - Quique Porta (quiqueporta) - - Benjamin Zaslavsky (tiriel) - Tobias Feijten (tobias93) - Andrea Quintino (dirk39) - Andreas Heigl (heiglandreas) - Tomasz Szymczyk (karion) + - Nadim AL ABDOU (nadim) - Peter Dietrich (xosofox) - Alex Vasilchenko - sez-open @@ -2365,6 +2383,7 @@ The Symfony Connect username in parenthesis allows to get more information - Andrei Igna - azine - Wojciech Zimoń + - Vladimir Melnik - Pierre Tachoire - Dawid Sajdak - Ludek Stepan @@ -2378,6 +2397,7 @@ The Symfony Connect username in parenthesis allows to get more information - karolsojko - Marco Jantke - Saem Ghani + - Claudiu Cristea - Zacharias Luiten - Sebastian Utz - Adrien Gallou (agallou) @@ -2462,6 +2482,8 @@ The Symfony Connect username in parenthesis allows to get more information - Max Summe - Ema Panz - Chihiro Adachi (chihiro-adachi) + - Thomas Trautner (thomastr) + - mfettig - Raphaëll Roussel - Tadcka - Abudarham Yuval @@ -2490,6 +2512,7 @@ The Symfony Connect username in parenthesis allows to get more information - Nicolas Eeckeloo (neeckeloo) - Andriy Prokopenko (sleepyboy) - Dariusz Ruminski + - Starfox64 - Thomas Hanke - Daniel Tschinder - Arnaud CHASSEUX @@ -2547,6 +2570,7 @@ The Symfony Connect username in parenthesis allows to get more information - Simon Neidhold - Valentin VALCIU - Jeremiah VALERIE + - Cas van Dongen - Patrik Patie Gmitter - Yannick Snobbert - Kevin Dew @@ -2574,8 +2598,8 @@ The Symfony Connect username in parenthesis allows to get more information - Kirk Madera - Keith Maika - Mephistofeles + - Oleh Korneliuk - Hoffmann András - - Cédric Anne - LubenZA - Flavian Sierk - Rik van der Heijden @@ -2595,6 +2619,7 @@ The Symfony Connect username in parenthesis allows to get more information - Olivier Scherler (oscherler) - Shane Preece (shane) - Johannes Goslar + - Mike Gladysch - Geoff - georaldc - wusuopu @@ -2667,6 +2692,7 @@ The Symfony Connect username in parenthesis allows to get more information - Dan Blows - Matt Wells - Nicolas Appriou + - Javier Alfonso Bellota de Frutos - stloyd - Andreas - Chris Tickner @@ -2826,11 +2852,13 @@ The Symfony Connect username in parenthesis allows to get more information - Michael van Tricht - ReScO - Tim Strehle + - cay89 - Sam Ward - Hans N. Hjort - Walther Lalk - Adam - Ivo + - Markus Staab - Sören Bernstein - michael.kubovic - devel @@ -2918,6 +2946,7 @@ The Symfony Connect username in parenthesis allows to get more information - Götz Gottwald - Adrien Peyre - Christoph Krapp + - andreyserdjuk - Nick Chiu - Robert Campbell - Matt Lehner @@ -2939,7 +2968,6 @@ The Symfony Connect username in parenthesis allows to get more information - Alessandro Loffredo - Ian Phillips - Remi Collet - - Nicolas Dousson - Haritz - Matthieu Prat - Brieuc Thomas @@ -2964,6 +2992,7 @@ The Symfony Connect username in parenthesis allows to get more information - tourze - Erik van Wingerden - Valouleloup + - Roland Franssen :) - Alexis MARQUIS - Matheus Gontijo - Gerrit Drost @@ -2998,7 +3027,6 @@ The Symfony Connect username in parenthesis allows to get more information - Tomáš Polívka (draczris) - Dennis Smink (dsmink) - Franz Liedke (franzliedke) - - Alex (garrett) - Gaylord Poillon (gaylord_p) - gondo (gondo) - Joris Garonian (grifx) @@ -3034,6 +3062,7 @@ The Symfony Connect username in parenthesis allows to get more information - Yorkie Chadwick (yorkie76) - Pavel Barton - GuillaumeVerdon + - Marien Fressinaud - ureimers - akimsko - Youpie @@ -3175,8 +3204,8 @@ The Symfony Connect username in parenthesis allows to get more information - Arrilot - andrey-tech - Shaun Simmons - - Markus Staab - Pierre-Louis LAUNAY + - A. Pauly - djama - Michael Gwynne - Eduardo Conceição @@ -3242,6 +3271,7 @@ The Symfony Connect username in parenthesis allows to get more information - Alfonso Fernández García - phc - Дмитрий Пацура + - db306 - Michaël VEROUX - Julia - Lin Lu From 5c68164c6fcb76c372c82b7a03a590d990a007f6 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 28 Apr 2023 15:29:52 +0200 Subject: [PATCH 05/56] Update VERSION for 5.4.23 --- src/Symfony/Component/HttpKernel/Kernel.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 4fca2b499d76c..7006a45cf2f49 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -78,12 +78,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl */ private static $freshCache = []; - public const VERSION = '5.4.23-DEV'; + public const VERSION = '5.4.23'; public const VERSION_ID = 50423; public const MAJOR_VERSION = 5; public const MINOR_VERSION = 4; public const RELEASE_VERSION = 23; - public const EXTRA_VERSION = 'DEV'; + public const EXTRA_VERSION = ''; public const END_OF_MAINTENANCE = '11/2024'; public const END_OF_LIFE = '11/2025'; From c6a452ddc7cd838b0c21ec28507e01fee413022e Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 28 Apr 2023 15:33:20 +0200 Subject: [PATCH 06/56] Bump Symfony version to 5.4.24 --- src/Symfony/Component/HttpKernel/Kernel.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 7006a45cf2f49..1173f499d023f 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -78,12 +78,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl */ private static $freshCache = []; - public const VERSION = '5.4.23'; - public const VERSION_ID = 50423; + public const VERSION = '5.4.24-DEV'; + public const VERSION_ID = 50424; public const MAJOR_VERSION = 5; public const MINOR_VERSION = 4; - public const RELEASE_VERSION = 23; - public const EXTRA_VERSION = ''; + public const RELEASE_VERSION = 24; + public const EXTRA_VERSION = 'DEV'; public const END_OF_MAINTENANCE = '11/2024'; public const END_OF_LIFE = '11/2025'; From 2407467f220f477973372aa2f5be8c57928ba0fd Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 28 Apr 2023 15:57:48 +0200 Subject: [PATCH 07/56] Bump Symfony version to 6.2.11 --- src/Symfony/Component/HttpKernel/Kernel.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index e9694e978e3c5..c0dd3e646d336 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -75,12 +75,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl */ private static array $freshCache = []; - public const VERSION = '6.2.10'; - public const VERSION_ID = 60210; + public const VERSION = '6.2.11-DEV'; + public const VERSION_ID = 60211; public const MAJOR_VERSION = 6; public const MINOR_VERSION = 2; - public const RELEASE_VERSION = 10; - public const EXTRA_VERSION = ''; + public const RELEASE_VERSION = 11; + public const EXTRA_VERSION = 'DEV'; public const END_OF_MAINTENANCE = '07/2023'; public const END_OF_LIFE = '07/2023'; From dfd7a5ba7736f0000d3ea171a56fb57cd98f7ae9 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 2 May 2023 12:33:25 +0200 Subject: [PATCH 08/56] [Messenger] Fix registering message handlers --- .../Component/Messenger/DependencyInjection/MessengerPass.php | 2 +- .../Messenger/Tests/DependencyInjection/MessengerPassTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php b/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php index f423889713972..4a4e00c81dcbf 100644 --- a/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php +++ b/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php @@ -143,7 +143,7 @@ private function registerHandlers(ContainerBuilder $container, array $busIds) } if ('__invoke' !== $method) { - $wrapperDefinition = (new Definition('callable'))->addArgument([new Reference($serviceId), $method])->setFactory('Closure::fromCallable'); + $wrapperDefinition = (new Definition('Closure'))->addArgument([new Reference($serviceId), $method])->setFactory('Closure::fromCallable'); $definitions[$definitionId = '.messenger.method_on_object_wrapper.'.ContainerBuilder::hash($message.':'.$priority.':'.$serviceId.':'.$method)] = $wrapperDefinition; } else { diff --git a/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php b/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php index 3d29497d34c1a..3411b0bbef482 100644 --- a/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php +++ b/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php @@ -255,7 +255,7 @@ public function testGetClassesAndMethodsAndPrioritiesFromTheSubscriber() $dummyHandlerReference = $dummyHandlerDescriptorDefinition->getArgument(0); $dummyHandlerDefinition = $container->getDefinition($dummyHandlerReference); - $this->assertSame('callable', $dummyHandlerDefinition->getClass()); + $this->assertSame('Closure', $dummyHandlerDefinition->getClass()); $this->assertEquals([new Reference(HandlerMappingMethods::class), 'dummyMethod'], $dummyHandlerDefinition->getArgument(0)); $this->assertSame(['Closure', 'fromCallable'], $dummyHandlerDefinition->getFactory()); From 159bf0bd487a575c22c0df7bb0e3b59dd5370b48 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 2 May 2023 12:45:29 +0200 Subject: [PATCH 09/56] [ErrorHandler] Skip Httplug deprecations for HttplugClient --- src/Symfony/Component/ErrorHandler/DebugClassLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/ErrorHandler/DebugClassLoader.php b/src/Symfony/Component/ErrorHandler/DebugClassLoader.php index e19223d3f3c53..844dbd6d23e85 100644 --- a/src/Symfony/Component/ErrorHandler/DebugClassLoader.php +++ b/src/Symfony/Component/ErrorHandler/DebugClassLoader.php @@ -416,7 +416,7 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array $this->checkClass($use); } if (isset(self::$deprecated[$use]) && strncmp($vendor, str_replace('_', '\\', $use), $vendorLen) && !isset(self::$deprecated[$class]) - && !(HttplugClient::class === $class && \in_array($use, [\Http\Message\RequestFactory::class, \Http\Message\StreamFactory::class, \Http\Message\UriFactory::class], true)) + && !(HttplugClient::class === $class && \in_array($use, [\Http\Client\HttpClient::class, \Http\Message\RequestFactory::class, \Http\Message\StreamFactory::class, \Http\Message\UriFactory::class], true)) ) { $type = class_exists($class, false) ? 'class' : (interface_exists($class, false) ? 'interface' : 'trait'); $verb = class_exists($use, false) || interface_exists($class, false) ? 'extends' : (interface_exists($use, false) ? 'implements' : 'uses'); From 3683d73416880dddf20ec49f94a23089fa195679 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 2 May 2023 15:01:16 +0200 Subject: [PATCH 10/56] [HttpClient] Dev-require php-http/message-factory --- composer.json | 1 + src/Symfony/Component/HttpClient/HttplugClient.php | 2 +- src/Symfony/Component/HttpClient/composer.json | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index d5e8a209d3be0..2f20a572b9eb7 100644 --- a/composer.json +++ b/composer.json @@ -136,6 +136,7 @@ "nyholm/psr7": "^1.0", "pda/pheanstalk": "^4.0", "php-http/httplug": "^1.0|^2.0", + "php-http/message-factory": "^1.0", "phpstan/phpdoc-parser": "^1.0", "predis/predis": "~1.1", "psr/http-client": "^1.0", diff --git a/src/Symfony/Component/HttpClient/HttplugClient.php b/src/Symfony/Component/HttpClient/HttplugClient.php index 491ea9e4033b7..2d9eec30f1238 100644 --- a/src/Symfony/Component/HttpClient/HttplugClient.php +++ b/src/Symfony/Component/HttpClient/HttplugClient.php @@ -47,7 +47,7 @@ } if (!interface_exists(RequestFactory::class)) { - throw new \LogicException('You cannot use "Symfony\Component\HttpClient\HttplugClient" as the "php-http/message-factory" package is not installed. Try running "composer require nyholm/psr7".'); + throw new \LogicException('You cannot use "Symfony\Component\HttpClient\HttplugClient" as the "php-http/message-factory" package is not installed. Try running "composer require php-http/message-factory".'); } /** diff --git a/src/Symfony/Component/HttpClient/composer.json b/src/Symfony/Component/HttpClient/composer.json index 57d31c12dfd8c..7f546b3a23981 100644 --- a/src/Symfony/Component/HttpClient/composer.json +++ b/src/Symfony/Component/HttpClient/composer.json @@ -38,6 +38,7 @@ "guzzlehttp/promises": "^1.4", "nyholm/psr7": "^1.0", "php-http/httplug": "^1.0|^2.0", + "php-http/message-factory": "^1.0", "psr/http-client": "^1.0", "symfony/dependency-injection": "^4.4|^5.0|^6.0", "symfony/http-kernel": "^4.4.13|^5.1.5|^6.0", From e331b288fb9650cd62c78117046584128ebba77e Mon Sep 17 00:00:00 2001 From: MatTheCat Date: Tue, 2 May 2023 18:13:31 +0200 Subject: [PATCH 11/56] =?UTF-8?q?Do=20not=20expose=20`Sfjs`=20as=20it=20is?= =?UTF-8?q?=20unused=20and=20conflicts=20with=20WebProfilerBundle=E2=80=99?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Resources/assets/js/exception.js | 494 +++++++++--------- 1 file changed, 241 insertions(+), 253 deletions(-) diff --git a/src/Symfony/Component/ErrorHandler/Resources/assets/js/exception.js b/src/Symfony/Component/ErrorHandler/Resources/assets/js/exception.js index a85409da3cc89..95b8ea17197c9 100644 --- a/src/Symfony/Component/ErrorHandler/Resources/assets/js/exception.js +++ b/src/Symfony/Component/ErrorHandler/Resources/assets/js/exception.js @@ -1,297 +1,285 @@ /* This file is based on WebProfilerBundle/Resources/views/Profiler/base_js.html.twig. If you make any change in this file, verify the same change is needed in the other file. */ /* .tab'); + var tabNavigation = document.createElement('ul'); + tabNavigation.className = 'tab-navigation'; + + var selectedTabId = 'tab-' + i + '-0'; /* select the first tab by default */ + for (var j = 0; j < tabs.length; j++) { + var tabId = 'tab-' + i + '-' + j; + var tabTitle = tabs[j].querySelector('.tab-title').innerHTML; + + var tabNavigationItem = document.createElement('li'); + tabNavigationItem.setAttribute('data-tab-id', tabId); + if (hasClass(tabs[j], 'active')) { selectedTabId = tabId; } + if (hasClass(tabs[j], 'disabled')) { addClass(tabNavigationItem, 'disabled'); } + tabNavigationItem.innerHTML = tabTitle; + tabNavigation.appendChild(tabNavigationItem); + + var tabContent = tabs[j].querySelector('.tab-content'); + tabContent.parentElement.setAttribute('id', tabId); + } - if (navigator.clipboard) { - document.querySelectorAll('[data-clipboard-text]').forEach(function(element) { - removeClass(element, 'hidden'); - element.addEventListener('click', function() { - navigator.clipboard.writeText(element.getAttribute('data-clipboard-text')); - }) - }); + tabGroups[i].insertBefore(tabNavigation, tabGroups[i].firstChild); + addClass(document.querySelector('[data-tab-id="' + selectedTabId + '"]'), 'active'); } - return { - addEventListener: addEventListener, + /* display the active tab and add the 'click' event listeners */ + for (i = 0; i < tabGroups.length; i++) { + tabNavigation = tabGroups[i].querySelectorAll(':scope >.tab-navigation li'); - createTabs: function() { - var tabGroups = document.querySelectorAll('.sf-tabs:not([data-processed=true])'); + for (j = 0; j < tabNavigation.length; j++) { + tabId = tabNavigation[j].getAttribute('data-tab-id'); + document.getElementById(tabId).querySelector('.tab-title').className = 'hidden'; - /* create the tab navigation for each group of tabs */ - for (var i = 0; i < tabGroups.length; i++) { - var tabs = tabGroups[i].querySelectorAll(':scope > .tab'); - var tabNavigation = document.createElement('ul'); - tabNavigation.className = 'tab-navigation'; - - var selectedTabId = 'tab-' + i + '-0'; /* select the first tab by default */ - for (var j = 0; j < tabs.length; j++) { - var tabId = 'tab-' + i + '-' + j; - var tabTitle = tabs[j].querySelector('.tab-title').innerHTML; + if (hasClass(tabNavigation[j], 'active')) { + document.getElementById(tabId).className = 'block'; + } else { + document.getElementById(tabId).className = 'hidden'; + } - var tabNavigationItem = document.createElement('li'); - tabNavigationItem.setAttribute('data-tab-id', tabId); - if (hasClass(tabs[j], 'active')) { selectedTabId = tabId; } - if (hasClass(tabs[j], 'disabled')) { addClass(tabNavigationItem, 'disabled'); } - tabNavigationItem.innerHTML = tabTitle; - tabNavigation.appendChild(tabNavigationItem); + tabNavigation[j].addEventListener('click', function(e) { + var activeTab = e.target || e.srcElement; - var tabContent = tabs[j].querySelector('.tab-content'); - tabContent.parentElement.setAttribute('id', tabId); + /* needed because when the tab contains HTML contents, user can click */ + /* on any of those elements instead of their parent '
  • ' element */ + while (activeTab.tagName.toLowerCase() !== 'li') { + activeTab = activeTab.parentNode; } - tabGroups[i].insertBefore(tabNavigation, tabGroups[i].firstChild); - addClass(document.querySelector('[data-tab-id="' + selectedTabId + '"]'), 'active'); - } + /* get the full list of tabs through the parent of the active tab element */ + var tabNavigation = activeTab.parentNode.children; + for (var k = 0; k < tabNavigation.length; k++) { + var tabId = tabNavigation[k].getAttribute('data-tab-id'); + document.getElementById(tabId).className = 'hidden'; + removeClass(tabNavigation[k], 'active'); + } - /* display the active tab and add the 'click' event listeners */ - for (i = 0; i < tabGroups.length; i++) { - tabNavigation = tabGroups[i].querySelectorAll(':scope >.tab-navigation li'); + addClass(activeTab, 'active'); + var activeTabId = activeTab.getAttribute('data-tab-id'); + document.getElementById(activeTabId).className = 'block'; + }); + } - for (j = 0; j < tabNavigation.length; j++) { - tabId = tabNavigation[j].getAttribute('data-tab-id'); - document.getElementById(tabId).querySelector('.tab-title').className = 'hidden'; + tabGroups[i].setAttribute('data-processed', 'true'); + } + })(); - if (hasClass(tabNavigation[j], 'active')) { - document.getElementById(tabId).className = 'block'; - } else { - document.getElementById(tabId).className = 'hidden'; - } + (function createToggles() { + var toggles = document.querySelectorAll('.sf-toggle:not([data-processed=true])'); - tabNavigation[j].addEventListener('click', function(e) { - var activeTab = e.target || e.srcElement; + for (var i = 0; i < toggles.length; i++) { + var elementSelector = toggles[i].getAttribute('data-toggle-selector'); + var element = document.querySelector(elementSelector); - /* needed because when the tab contains HTML contents, user can click */ - /* on any of those elements instead of their parent '
  • ' element */ - while (activeTab.tagName.toLowerCase() !== 'li') { - activeTab = activeTab.parentNode; - } + addClass(element, 'sf-toggle-content'); - /* get the full list of tabs through the parent of the active tab element */ - var tabNavigation = activeTab.parentNode.children; - for (var k = 0; k < tabNavigation.length; k++) { - var tabId = tabNavigation[k].getAttribute('data-tab-id'); - document.getElementById(tabId).className = 'hidden'; - removeClass(tabNavigation[k], 'active'); - } + if (toggles[i].hasAttribute('data-toggle-initial') && toggles[i].getAttribute('data-toggle-initial') == 'display') { + addClass(toggles[i], 'sf-toggle-on'); + addClass(element, 'sf-toggle-visible'); + } else { + addClass(toggles[i], 'sf-toggle-off'); + addClass(element, 'sf-toggle-hidden'); + } - addClass(activeTab, 'active'); - var activeTabId = activeTab.getAttribute('data-tab-id'); - document.getElementById(activeTabId).className = 'block'; - }); - } + addEventListener(toggles[i], 'click', function(e) { + e.preventDefault(); - tabGroups[i].setAttribute('data-processed', 'true'); + if ('' !== window.getSelection().toString()) { + /* Don't do anything on text selection */ + return; } - }, - - createToggles: function() { - var toggles = document.querySelectorAll('.sf-toggle:not([data-processed=true])'); - - for (var i = 0; i < toggles.length; i++) { - var elementSelector = toggles[i].getAttribute('data-toggle-selector'); - var element = document.querySelector(elementSelector); - - addClass(element, 'sf-toggle-content'); - - if (toggles[i].hasAttribute('data-toggle-initial') && toggles[i].getAttribute('data-toggle-initial') == 'display') { - addClass(toggles[i], 'sf-toggle-on'); - addClass(element, 'sf-toggle-visible'); - } else { - addClass(toggles[i], 'sf-toggle-off'); - addClass(element, 'sf-toggle-hidden'); - } - - addEventListener(toggles[i], 'click', function(e) { - e.preventDefault(); - if ('' !== window.getSelection().toString()) { - /* Don't do anything on text selection */ - return; - } + var toggle = e.target || e.srcElement; - var toggle = e.target || e.srcElement; + /* needed because when the toggle contains HTML contents, user can click */ + /* on any of those elements instead of their parent '.sf-toggle' element */ + while (!hasClass(toggle, 'sf-toggle')) { + toggle = toggle.parentNode; + } - /* needed because when the toggle contains HTML contents, user can click */ - /* on any of those elements instead of their parent '.sf-toggle' element */ - while (!hasClass(toggle, 'sf-toggle')) { - toggle = toggle.parentNode; - } + var element = document.querySelector(toggle.getAttribute('data-toggle-selector')); - var element = document.querySelector(toggle.getAttribute('data-toggle-selector')); + toggleClass(toggle, 'sf-toggle-on'); + toggleClass(toggle, 'sf-toggle-off'); + toggleClass(element, 'sf-toggle-hidden'); + toggleClass(element, 'sf-toggle-visible'); - toggleClass(toggle, 'sf-toggle-on'); - toggleClass(toggle, 'sf-toggle-off'); - toggleClass(element, 'sf-toggle-hidden'); - toggleClass(element, 'sf-toggle-visible'); + /* the toggle doesn't change its contents when clicking on it */ + if (!toggle.hasAttribute('data-toggle-alt-content')) { + return; + } - /* the toggle doesn't change its contents when clicking on it */ - if (!toggle.hasAttribute('data-toggle-alt-content')) { - return; - } + if (!toggle.hasAttribute('data-toggle-original-content')) { + toggle.setAttribute('data-toggle-original-content', toggle.innerHTML); + } - if (!toggle.hasAttribute('data-toggle-original-content')) { - toggle.setAttribute('data-toggle-original-content', toggle.innerHTML); - } + var currentContent = toggle.innerHTML; + var originalContent = toggle.getAttribute('data-toggle-original-content'); + var altContent = toggle.getAttribute('data-toggle-alt-content'); + toggle.innerHTML = currentContent !== altContent ? altContent : originalContent; + }); - var currentContent = toggle.innerHTML; - var originalContent = toggle.getAttribute('data-toggle-original-content'); - var altContent = toggle.getAttribute('data-toggle-alt-content'); - toggle.innerHTML = currentContent !== altContent ? altContent : originalContent; - }); + /* Prevents from disallowing clicks on links inside toggles */ + var toggleLinks = toggles[i].querySelectorAll('a'); + for (var j = 0; j < toggleLinks.length; j++) { + addEventListener(toggleLinks[j], 'click', function(e) { + e.stopPropagation(); + }); + } - /* Prevents from disallowing clicks on links inside toggles */ - var toggleLinks = toggles[i].querySelectorAll('a'); - for (var j = 0; j < toggleLinks.length; j++) { - addEventListener(toggleLinks[j], 'click', function(e) { - e.stopPropagation(); - }); - } + /* Prevents from disallowing clicks on "copy to clipboard" elements inside toggles */ + var copyToClipboardElements = toggles[i].querySelectorAll('span[data-clipboard-text]'); + for (var k = 0; k < copyToClipboardElements.length; k++) { + addEventListener(copyToClipboardElements[k], 'click', function(e) { + e.stopPropagation(); + }); + } - /* Prevents from disallowing clicks on "copy to clipboard" elements inside toggles */ - var copyToClipboardElements = toggles[i].querySelectorAll('span[data-clipboard-text]'); - for (var k = 0; k < copyToClipboardElements.length; k++) { - addEventListener(copyToClipboardElements[k], 'click', function(e) { - e.stopPropagation(); - }); - } + toggles[i].setAttribute('data-processed', 'true'); + } + })(); - toggles[i].setAttribute('data-processed', 'true'); + (function createFilters() { + document.querySelectorAll('[data-filters] [data-filter]').forEach(function (filter) { + var filters = filter.closest('[data-filters]'), + type = 'choice', + name = filter.dataset.filter, + ucName = name.charAt(0).toUpperCase()+name.slice(1), + list = document.createElement('ul'), + values = filters.dataset['filter'+ucName] || filters.querySelectorAll('[data-filter-'+name+']'), + labels = {}, + defaults = null, + indexed = {}, + processed = {}; + if (typeof values === 'string') { + type = 'level'; + labels = values.split(','); + values = values.toLowerCase().split(','); + defaults = values.length - 1; + } + addClass(list, 'filter-list'); + addClass(list, 'filter-list-'+type); + values.forEach(function (value, i) { + if (value instanceof HTMLElement) { + value = value.dataset['filter'+ucName]; } - }, - - createFilters: function() { - document.querySelectorAll('[data-filters] [data-filter]').forEach(function (filter) { - var filters = filter.closest('[data-filters]'), - type = 'choice', - name = filter.dataset.filter, - ucName = name.charAt(0).toUpperCase()+name.slice(1), - list = document.createElement('ul'), - values = filters.dataset['filter'+ucName] || filters.querySelectorAll('[data-filter-'+name+']'), - labels = {}, - defaults = null, - indexed = {}, - processed = {}; - if (typeof values === 'string') { - type = 'level'; - labels = values.split(','); - values = values.toLowerCase().split(','); - defaults = values.length - 1; - } - addClass(list, 'filter-list'); - addClass(list, 'filter-list-'+type); - values.forEach(function (value, i) { - if (value instanceof HTMLElement) { - value = value.dataset['filter'+ucName]; - } - if (value in processed) { + if (value in processed) { + return; + } + var option = document.createElement('li'), + label = i in labels ? labels[i] : value, + active = false, + matches; + if ('' === label) { + option.innerHTML = '(none)'; + } else { + option.innerText = label; + } + option.dataset.filter = value; + option.setAttribute('title', 1 === (matches = filters.querySelectorAll('[data-filter-'+name+'="'+value+'"]').length) ? 'Matches 1 row' : 'Matches '+matches+' rows'); + indexed[value] = i; + list.appendChild(option); + addEventListener(option, 'click', function () { + if ('choice' === type) { + filters.querySelectorAll('[data-filter-'+name+']').forEach(function (row) { + if (option.dataset.filter === row.dataset['filter'+ucName]) { + toggleClass(row, 'filter-hidden-'+name); + } + }); + toggleClass(option, 'active'); + } else if ('level' === type) { + if (i === this.parentNode.querySelectorAll('.active').length - 1) { return; } - var option = document.createElement('li'), - label = i in labels ? labels[i] : value, - active = false, - matches; - if ('' === label) { - option.innerHTML = '(none)'; - } else { - option.innerText = label; - } - option.dataset.filter = value; - option.setAttribute('title', 1 === (matches = filters.querySelectorAll('[data-filter-'+name+'="'+value+'"]').length) ? 'Matches 1 row' : 'Matches '+matches+' rows'); - indexed[value] = i; - list.appendChild(option); - addEventListener(option, 'click', function () { - if ('choice' === type) { - filters.querySelectorAll('[data-filter-'+name+']').forEach(function (row) { - if (option.dataset.filter === row.dataset['filter'+ucName]) { - toggleClass(row, 'filter-hidden-'+name); - } - }); - toggleClass(option, 'active'); - } else if ('level' === type) { - if (i === this.parentNode.querySelectorAll('.active').length - 1) { - return; + this.parentNode.querySelectorAll('li').forEach(function (currentOption, j) { + if (j <= i) { + addClass(currentOption, 'active'); + if (i === j) { + addClass(currentOption, 'last-active'); + } else { + removeClass(currentOption, 'last-active'); } - this.parentNode.querySelectorAll('li').forEach(function (currentOption, j) { - if (j <= i) { - addClass(currentOption, 'active'); - if (i === j) { - addClass(currentOption, 'last-active'); - } else { - removeClass(currentOption, 'last-active'); - } - } else { - removeClass(currentOption, 'active'); - removeClass(currentOption, 'last-active'); - } - }); - filters.querySelectorAll('[data-filter-'+name+']').forEach(function (row) { - if (i < indexed[row.dataset['filter'+ucName]]) { - addClass(row, 'filter-hidden-'+name); - } else { - removeClass(row, 'filter-hidden-'+name); - } - }); + } else { + removeClass(currentOption, 'active'); + removeClass(currentOption, 'last-active'); } }); - if ('choice' === type) { - active = null === defaults || 0 <= defaults.indexOf(value); - } else if ('level' === type) { - active = i <= defaults; - if (active && i === defaults) { - addClass(option, 'last-active'); + filters.querySelectorAll('[data-filter-'+name+']').forEach(function (row) { + if (i < indexed[row.dataset['filter'+ucName]]) { + addClass(row, 'filter-hidden-'+name); + } else { + removeClass(row, 'filter-hidden-'+name); } - } - if (active) { - addClass(option, 'active'); - } else { - filters.querySelectorAll('[data-filter-'+name+'="'+value+'"]').forEach(function (row) { - toggleClass(row, 'filter-hidden-'+name); - }); - } - processed[value] = true; - }); - - if (1 < list.childNodes.length) { - filter.appendChild(list); - filter.dataset.filtered = ''; + }); } }); + if ('choice' === type) { + active = null === defaults || 0 <= defaults.indexOf(value); + } else if ('level' === type) { + active = i <= defaults; + if (active && i === defaults) { + addClass(option, 'last-active'); + } + } + if (active) { + addClass(option, 'active'); + } else { + filters.querySelectorAll('[data-filter-'+name+'="'+value+'"]').forEach(function (row) { + toggleClass(row, 'filter-hidden-'+name); + }); + } + processed[value] = true; + }); + + if (1 < list.childNodes.length) { + filter.appendChild(list); + filter.dataset.filtered = ''; } - }; + }); })(); - - Sfjs.addEventListener(document, 'DOMContentLoaded', function() { - Sfjs.createTabs(); - Sfjs.createToggles(); - Sfjs.createFilters(); - }); -} +})(); /*]]>*/ From 98a8aa22dee799da57fa9c9d3145ae36fd1b6717 Mon Sep 17 00:00:00 2001 From: MatTheCat Date: Tue, 2 May 2023 18:38:36 +0200 Subject: [PATCH 12/56] Remove legacy filters remnants --- .../views/Collector/translation.html.twig | 10 +++--- .../views/Profiler/profiler.css.twig | 36 ------------------- 2 files changed, 4 insertions(+), 42 deletions(-) diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig index a8a5c273656b5..f48c9fce7c4df 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig @@ -155,22 +155,20 @@ - - {% endblock messages %} {% endif %} {% endblock %} {% macro render_table(messages, is_fallback) %} - +
    - + {% if is_fallback %} {% endif %} - + @@ -178,7 +176,7 @@ {% for message in messages %} - + {% if is_fallback %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig index 24ec0863d6117..2c59150b5a345 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig @@ -963,42 +963,6 @@ tr.status-warning td { display: block; } -{# Filters - ========================================================================= #} -[data-filters] { position: relative; } -[data-filtered] { cursor: pointer; } -[data-filtered]:after { content: '\00a0\25BE'; } -[data-filtered]:hover .filter-list li { display: inline-flex; } -[class*="filter-hidden-"] { display: none; } -.filter-list { position: absolute; border: var(--border); box-shadow: var(--shadow); margin: 0; padding: 0; display: flex; flex-direction: column; } -.filter-list :after { content: ''; } -.filter-list li { - background: var(--tab-disabled-background); - border-bottom: var(--border); - color: var(--tab-disabled-color); - display: none; - list-style: none; - margin: 0; - padding: 5px 10px; - text-align: left; - font-weight: normal; -} -.filter-list li.active { - background: var(--tab-background); - color: var(--tab-color); -} -.filter-list li.last-active { - background: var(--tab-active-background); - color: var(--tab-active-color); -} - -.filter-list-level li { cursor: s-resize; } -.filter-list-level li.active { cursor: n-resize; } -.filter-list-level li.last-active { cursor: default; } -.filter-list-level li.last-active:before { content: '\2714\00a0'; } -.filter-list-choice li:before { content: '\2714\00a0'; color: transparent; } -.filter-list-choice li.active:before { color: unset; } - {# Twig panel ========================================================================= #} #twig-dump pre { From 554949391ab8abd56a2563a6f47128c91bd82b62 Mon Sep 17 00:00:00 2001 From: Allison Guilhem Date: Fri, 28 Apr 2023 23:05:15 +0200 Subject: [PATCH 13/56] [Serializer] Throw NotNormalizableValueException if it doesn't concern a backedEnum in construct method --- .../Normalizer/AbstractNormalizer.php | 3 ++ .../Normalizer/BackedEnumNormalizer.php | 6 ++- .../Fixtures/DummyObjectWithEnumProperty.php | 10 ++++ .../Normalizer/BackedEnumNormalizerTest.php | 2 +- .../Serializer/Tests/SerializerTest.php | 49 ++++++++++++++++++- 5 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/DummyObjectWithEnumProperty.php diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php index efd8cbb567637..bd41b8da6fa72 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php @@ -342,6 +342,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex $constructor = $this->getConstructor($data, $class, $context, $reflectionClass, $allowedAttributes); if ($constructor) { + $context['has_constructor'] = true; if (true !== $constructor->isPublic()) { return $reflectionClass->newInstanceWithoutConstructor(); } @@ -431,6 +432,8 @@ protected function instantiateObject(array &$data, string $class, array &$contex } } + unset($context['has_constructor']); + return new $class(); } diff --git a/src/Symfony/Component/Serializer/Normalizer/BackedEnumNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/BackedEnumNormalizer.php index 21fac3248cd6e..e7efb0057c09f 100644 --- a/src/Symfony/Component/Serializer/Normalizer/BackedEnumNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/BackedEnumNormalizer.php @@ -64,7 +64,11 @@ public function denormalize($data, string $type, string $format = null, array $c try { return $type::from($data); } catch (\ValueError $e) { - throw new InvalidArgumentException('The data must belong to a backed enumeration of type '.$type); + if (isset($context['has_constructor'])) { + throw new InvalidArgumentException('The data must belong to a backed enumeration of type '.$type); + } + + throw NotNormalizableValueException::createForUnexpectedDataType('The data must belong to a backed enumeration of type '.$type, $data, [$type], $context['deserialization_path'] ?? null, true, 0, $e); } } diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/DummyObjectWithEnumProperty.php b/src/Symfony/Component/Serializer/Tests/Fixtures/DummyObjectWithEnumProperty.php new file mode 100644 index 0000000000000..f2677195f2820 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/DummyObjectWithEnumProperty.php @@ -0,0 +1,10 @@ +expectException(InvalidArgumentException::class); + $this->expectException(NotNormalizableValueException::class); $this->expectExceptionMessage('The data must belong to a backed enumeration of type '.StringBackedEnumDummy::class); $this->normalizer->denormalize('POST', StringBackedEnumDummy::class); diff --git a/src/Symfony/Component/Serializer/Tests/SerializerTest.php b/src/Symfony/Component/Serializer/Tests/SerializerTest.php index fc0b6cc5af876..b4e84132a0858 100644 --- a/src/Symfony/Component/Serializer/Tests/SerializerTest.php +++ b/src/Symfony/Component/Serializer/Tests/SerializerTest.php @@ -60,6 +60,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\DummyMessageNumberOne; use Symfony\Component\Serializer\Tests\Fixtures\DummyMessageNumberTwo; use Symfony\Component\Serializer\Tests\Fixtures\DummyObjectWithEnumConstructor; +use Symfony\Component\Serializer\Tests\Fixtures\DummyObjectWithEnumProperty; use Symfony\Component\Serializer\Tests\Fixtures\FalseBuiltInDummy; use Symfony\Component\Serializer\Tests\Fixtures\NormalizableTraversableDummy; use Symfony\Component\Serializer\Tests\Fixtures\Php74Full; @@ -1230,7 +1231,51 @@ public function testCollectDenormalizationErrorsWithEnumConstructor() /** * @requires PHP 8.1 */ - public function testNoCollectDenormalizationErrorsWithWrongEnum() + public function testCollectDenormalizationErrorsWithWrongPropertyWithoutConstruct() + { + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $reflectionExtractor = new ReflectionExtractor(); + $propertyInfoExtractor = new PropertyInfoExtractor([], [$reflectionExtractor], [], [], []); + + $serializer = new Serializer( + [ + new BackedEnumNormalizer(), + new ObjectNormalizer($classMetadataFactory, null, null, $propertyInfoExtractor), + ], + ['json' => new JsonEncoder()] + ); + + try { + $serializer->deserialize('{"get": "POST"}', DummyObjectWithEnumProperty::class, 'json', [ + DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true, + ]); + } catch (\Throwable $e) { + $this->assertInstanceOf(PartialDenormalizationException::class, $e); + } + + $exceptionsAsArray = array_map(function (NotNormalizableValueException $e): array { + return [ + 'currentType' => $e->getCurrentType(), + 'useMessageForUser' => $e->canUseMessageForUser(), + 'message' => $e->getMessage(), + ]; + }, $e->getErrors()); + + $expected = [ + [ + 'currentType' => 'string', + 'useMessageForUser' => true, + 'message' => 'The data must belong to a backed enumeration of type Symfony\Component\Serializer\Tests\Fixtures\StringBackedEnumDummy', + ], + ]; + + $this->assertSame($expected, $exceptionsAsArray); + } + + /** + * @requires PHP 8.1 + */ + public function testNoCollectDenormalizationErrorsWithWrongEnumOnConstructor() { $serializer = new Serializer( [ @@ -1241,7 +1286,7 @@ public function testNoCollectDenormalizationErrorsWithWrongEnum() ); try { - $serializer->deserialize('{"get": "invalid"}', DummyObjectWithEnumConstructor::class, 'json', [ + $serializer->deserialize('{"get": "POST"}', DummyObjectWithEnumConstructor::class, 'json', [ DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true, ]); } catch (\Throwable $th) { From f702e66369274b1d2b7c4d4af9ad3ee0f8f2476e Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 3 May 2023 10:21:12 +0200 Subject: [PATCH 14/56] [HttpClient] Ensure HttplugClient ignores invalid HTTP headers --- composer.json | 1 + .../HttpClient/Internal/HttplugWaitLoop.php | 6 +++++- .../HttpClient/Tests/HttplugClientTest.php | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 2f20a572b9eb7..b291459895cb7 100644 --- a/composer.json +++ b/composer.json @@ -165,6 +165,7 @@ }, "config": { "allow-plugins": { + "php-http/discovery": false, "symfony/runtime": true } }, diff --git a/src/Symfony/Component/HttpClient/Internal/HttplugWaitLoop.php b/src/Symfony/Component/HttpClient/Internal/HttplugWaitLoop.php index 9f5658f560fbc..c61be22e34405 100644 --- a/src/Symfony/Component/HttpClient/Internal/HttplugWaitLoop.php +++ b/src/Symfony/Component/HttpClient/Internal/HttplugWaitLoop.php @@ -120,7 +120,11 @@ public function createPsr7Response(ResponseInterface $response, bool $buffer = f foreach ($response->getHeaders(false) as $name => $values) { foreach ($values as $value) { - $psrResponse = $psrResponse->withAddedHeader($name, $value); + try { + $psrResponse = $psrResponse->withAddedHeader($name, $value); + } catch (\InvalidArgumentException $e) { + // ignore invalid header + } } } diff --git a/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php b/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php index 1f48be5c574c2..ba8fcbe3d68eb 100644 --- a/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php +++ b/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php @@ -267,4 +267,22 @@ function (\Exception $exception) use ($errorMessage, &$failureCallableCalled, $c $this->assertSame(200, $response->getStatusCode()); $this->assertSame('OK', (string) $response->getBody()); } + + public function testInvalidHeaderResponse() + { + $responseHeaders = [ + // space in header name not allowed in RFC 7230 + ' X-XSS-Protection' => '0', + 'Cache-Control' => 'no-cache', + ]; + $response = new MockResponse('body', ['response_headers' => $responseHeaders]); + $this->assertArrayHasKey(' x-xss-protection', $response->getHeaders()); + + $client = new HttplugClient(new MockHttpClient($response)); + $request = $client->createRequest('POST', 'http://localhost:8057/post') + ->withBody($client->createStream('foo=0123456789')); + + $resultResponse = $client->sendRequest($request); + $this->assertCount(1, $resultResponse->getHeaders()); + } } From ea449ca6bac80d0ea718fcf06e5fe6a9b97b329c Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 13 Apr 2023 19:22:41 +0200 Subject: [PATCH 15/56] [HttpKernel] Don't use eval() to render ESI/SSI --- .../Component/HttpKernel/HttpCache/Esi.php | 16 ++++++---------- .../HttpKernel/HttpCache/HttpCache.php | 16 +++++++++++++++- .../Component/HttpKernel/HttpCache/Ssi.php | 14 ++++++-------- .../HttpKernel/Tests/HttpCache/EsiTest.php | 19 ++++++++++++------- .../HttpKernel/Tests/HttpCache/SsiTest.php | 9 ++++++--- 5 files changed, 45 insertions(+), 29 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/HttpCache/Esi.php b/src/Symfony/Component/HttpKernel/HttpCache/Esi.php index cd6a00a10d61f..4d86508a42092 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/Esi.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/Esi.php @@ -80,8 +80,10 @@ public function process(Request $request, Response $response) $content = preg_replace('#.*?#s', '', $content); $content = preg_replace('#]+>#s', '', $content); + static $cookie; + $cookie = hash('md5', $cookie ?? $cookie = random_bytes(16), true); + $boundary = base64_encode($cookie); $chunks = preg_split('##', $content, -1, \PREG_SPLIT_DELIM_CAPTURE); - $chunks[0] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[0]); $i = 1; while (isset($chunks[$i])) { @@ -95,16 +97,10 @@ public function process(Request $request, Response $response) throw new \RuntimeException('Unable to process an ESI tag without a "src" attribute.'); } - $chunks[$i] = sprintf('surrogate->handle($this, %s, %s, %s) ?>'."\n", - var_export($options['src'], true), - var_export($options['alt'] ?? '', true), - isset($options['onerror']) && 'continue' === $options['onerror'] ? 'true' : 'false' - ); - ++$i; - $chunks[$i] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[$i]); - ++$i; + $chunks[$i] = $boundary.$options['src']."\n".($options['alt'] ?? '')."\n".('continue' === ($options['onerror'] ?? ''))."\n"; + $i += 2; } - $content = implode('', $chunks); + $content = $boundary.implode('', $chunks).$boundary; $response->setContent($content); $response->headers->set('X-Body-Eval', 'ESI'); diff --git a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php index 5688fc0c13ccd..063d4105b160b 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php @@ -636,7 +636,21 @@ private function restoreResponseBody(Request $request, Response $response) if ($response->headers->has('X-Body-File')) { include $response->headers->get('X-Body-File'); } else { - eval('; ?>'.$response->getContent().'getContent(); + + if (substr($content, -24) === $boundary = substr($content, 0, 24)) { + $j = strpos($content, $boundary, 24); + echo substr($content, 24, $j - 24); + $i = $j + 24; + + while (false !== $j = strpos($content, $boundary, $i)) { + [$uri, $alt, $ignoreErrors, $part] = explode("\n", substr($content, $i, $j - $i), 4); + $i = $j + 24; + + echo $this->surrogate->handle($this, $uri, $alt, $ignoreErrors); + echo $part; + } + } } $response->setContent(ob_get_clean()); diff --git a/src/Symfony/Component/HttpKernel/HttpCache/Ssi.php b/src/Symfony/Component/HttpKernel/HttpCache/Ssi.php index f114e05cfb2f6..bb48238ff1f4b 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/Ssi.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/Ssi.php @@ -65,8 +65,10 @@ public function process(Request $request, Response $response) // we don't use a proper XML parser here as we can have SSI tags in a plain text response $content = $response->getContent(); + static $cookie; + $cookie = hash('md5', $cookie ?? $cookie = random_bytes(16), true); + $boundary = base64_encode($cookie); $chunks = preg_split('##', $content, -1, \PREG_SPLIT_DELIM_CAPTURE); - $chunks[0] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[0]); $i = 1; while (isset($chunks[$i])) { @@ -80,14 +82,10 @@ public function process(Request $request, Response $response) throw new \RuntimeException('Unable to process an SSI tag without a "virtual" attribute.'); } - $chunks[$i] = sprintf('surrogate->handle($this, %s, \'\', false) ?>'."\n", - var_export($options['virtual'], true) - ); - ++$i; - $chunks[$i] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[$i]); - ++$i; + $chunks[$i] = $boundary.$options['virtual']."\n\n\n"; + $i += 2; } - $content = implode('', $chunks); + $content = $boundary.implode('', $chunks).$boundary; $response->setContent($content); $response->headers->set('X-Body-Eval', 'SSI'); diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/EsiTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/EsiTest.php index 290bd94bdcb97..e876f28189087 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/EsiTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/EsiTest.php @@ -102,7 +102,7 @@ public function testMultilineEsiRemoveTagsAreRemoved() $response = new Response(' Keep this'." And this"); $this->assertSame($response, $esi->process($request, $response)); - $this->assertEquals(' Keep this And this', $response->getContent()); + $this->assertEquals(' Keep this And this', substr($response->getContent(), 24, -24)); } public function testCommentTagsAreRemoved() @@ -113,7 +113,7 @@ public function testCommentTagsAreRemoved() $response = new Response(' Keep this'); $this->assertSame($response, $esi->process($request, $response)); - $this->assertEquals(' Keep this', $response->getContent()); + $this->assertEquals(' Keep this', substr($response->getContent(), 24, -24)); } public function testProcess() @@ -124,23 +124,27 @@ public function testProcess() $response = new Response('foo '); $this->assertSame($response, $esi->process($request, $response)); - $this->assertEquals('foo surrogate->handle($this, \'...\', \'alt\', true) ?>'."\n", $response->getContent()); + $content = explode(substr($response->getContent(), 0, 24), $response->getContent()); + $this->assertSame(['', 'foo ', "...\nalt\n1\n", ''], $content); $this->assertEquals('ESI', $response->headers->get('x-body-eval')); $response = new Response('foo '); $this->assertSame($response, $esi->process($request, $response)); - $this->assertEquals('foo surrogate->handle($this, \'foo\\\'\', \'bar\\\'\', true) ?>'."\n", $response->getContent()); + $content = explode(substr($response->getContent(), 0, 24), $response->getContent()); + $this->assertSame(['', 'foo ', "foo'\nbar'\n1\n", ''], $content); $response = new Response('foo '); $this->assertSame($response, $esi->process($request, $response)); - $this->assertEquals('foo surrogate->handle($this, \'...\', \'\', false) ?>'."\n", $response->getContent()); + $content = explode(substr($response->getContent(), 0, 24), $response->getContent()); + $this->assertSame(['', 'foo ', "...\n\n\n", ''], $content); $response = new Response('foo '); $this->assertSame($response, $esi->process($request, $response)); - $this->assertEquals('foo surrogate->handle($this, \'...\', \'\', false) ?>'."\n", $response->getContent()); + $content = explode(substr($response->getContent(), 0, 24), $response->getContent()); + $this->assertSame(['', 'foo ', "...\n\n\n", ''], $content); } public function testProcessEscapesPhpTags() @@ -151,7 +155,8 @@ public function testProcessEscapesPhpTags() $response = new Response(''); $this->assertSame($response, $esi->process($request, $response)); - $this->assertEquals('php cript language=php>', $response->getContent()); + $content = explode(substr($response->getContent(), 0, 24), $response->getContent()); + $this->assertSame(['', '', ''], $content); } public function testProcessWhenNoSrcInAnEsi() diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/SsiTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/SsiTest.php index a1f1f1593d3f3..97cc8fccd03d0 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/SsiTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/SsiTest.php @@ -101,13 +101,15 @@ public function testProcess() $response = new Response('foo '); $ssi->process($request, $response); - $this->assertEquals('foo surrogate->handle($this, \'...\', \'\', false) ?>'."\n", $response->getContent()); + $content = explode(substr($response->getContent(), 0, 24), $response->getContent()); + $this->assertSame(['', 'foo ', "...\n\n\n", ''], $content); $this->assertEquals('SSI', $response->headers->get('x-body-eval')); $response = new Response('foo '); $ssi->process($request, $response); - $this->assertEquals("foo surrogate->handle(\$this, 'foo\\'', '', false) ?>\n", $response->getContent()); + $content = explode(substr($response->getContent(), 0, 24), $response->getContent()); + $this->assertSame(['', 'foo ', "foo'\n\n\n", ''], $content); } public function testProcessEscapesPhpTags() @@ -118,7 +120,8 @@ public function testProcessEscapesPhpTags() $response = new Response(''); $ssi->process($request, $response); - $this->assertEquals('php cript language=php>', $response->getContent()); + $content = explode(substr($response->getContent(), 0, 24), $response->getContent()); + $this->assertSame(['', '', ''], $content); } public function testProcessWhenNoSrcInAnSsi() From 78eff396219679b660f597b0a5099d2bac169f91 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 4 May 2023 09:21:45 +0200 Subject: [PATCH 16/56] [HttpClient] Fix getting through proxies via CONNECT --- .../HttpClient/Response/AmpResponse.php | 3 +- .../HttpClient/Response/CurlResponse.php | 30 +++++++++---------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/Symfony/Component/HttpClient/Response/AmpResponse.php b/src/Symfony/Component/HttpClient/Response/AmpResponse.php index 6d0ce6e33e01f..03e5daf349b77 100644 --- a/src/Symfony/Component/HttpClient/Response/AmpResponse.php +++ b/src/Symfony/Component/HttpClient/Response/AmpResponse.php @@ -47,7 +47,6 @@ final class AmpResponse implements ResponseInterface, StreamableInterface private $multi; private $options; - private $canceller; private $onProgress; private static $delay; @@ -73,7 +72,7 @@ public function __construct(AmpClientState $multi, Request $request, array $opti $info = &$this->info; $headers = &$this->headers; - $canceller = $this->canceller = new CancellationTokenSource(); + $canceller = new CancellationTokenSource(); $handle = &$this->handle; $info['url'] = (string) $request->getUri(); diff --git a/src/Symfony/Component/HttpClient/Response/CurlResponse.php b/src/Symfony/Component/HttpClient/Response/CurlResponse.php index 7cfad581af4c4..2418203060c82 100644 --- a/src/Symfony/Component/HttpClient/Response/CurlResponse.php +++ b/src/Symfony/Component/HttpClient/Response/CurlResponse.php @@ -76,17 +76,7 @@ public function __construct(CurlClientState $multi, $ch, array $options = null, } curl_setopt($ch, \CURLOPT_HEADERFUNCTION, static function ($ch, string $data) use (&$info, &$headers, $options, $multi, $id, &$location, $resolveRedirect, $logger): int { - if (0 !== substr_compare($data, "\r\n", -2)) { - return 0; - } - - $len = 0; - - foreach (explode("\r\n", substr($data, 0, -2)) as $data) { - $len += 2 + self::parseHeaderLine($ch, $data, $info, $headers, $options, $multi, $id, $location, $resolveRedirect, $logger); - } - - return $len; + return self::parseHeaderLine($ch, $data, $info, $headers, $options, $multi, $id, $location, $resolveRedirect, $logger); }); if (null === $options) { @@ -381,19 +371,29 @@ private static function select(ClientState $multi, float $timeout): int */ private static function parseHeaderLine($ch, string $data, array &$info, array &$headers, ?array $options, CurlClientState $multi, int $id, ?string &$location, ?callable $resolveRedirect, ?LoggerInterface $logger): int { + if (!str_ends_with($data, "\r\n")) { + return 0; + } + $waitFor = @curl_getinfo($ch, \CURLINFO_PRIVATE) ?: '_0'; if ('H' !== $waitFor[0]) { return \strlen($data); // Ignore HTTP trailers } - if ('' !== $data) { + $statusCode = curl_getinfo($ch, \CURLINFO_RESPONSE_CODE); + + if ($statusCode !== $info['http_code'] && !preg_match("#^HTTP/\d+(?:\.\d+)? {$statusCode}(?: |\r\n$)#", $data)) { + return \strlen($data); // Ignore headers from responses to CONNECT requests + } + + if ("\r\n" !== $data) { // Regular header line: add it to the list - self::addResponseHeaders([$data], $info, $headers); + self::addResponseHeaders([substr($data, 0, -2)], $info, $headers); if (!str_starts_with($data, 'HTTP/')) { if (0 === stripos($data, 'Location:')) { - $location = trim(substr($data, 9)); + $location = trim(substr($data, 9, -2)); } return \strlen($data); @@ -416,7 +416,7 @@ private static function parseHeaderLine($ch, string $data, array &$info, array & // End of headers: handle informational responses, redirects, etc. - if (200 > $statusCode = curl_getinfo($ch, \CURLINFO_RESPONSE_CODE)) { + if (200 > $statusCode) { $multi->handlesActivity[$id][] = new InformationalChunk($statusCode, $headers); $location = null; From 71b44fe45729e73fb80375a39ae2905ca4ca4c85 Mon Sep 17 00:00:00 2001 From: Tugdual Saunier Date: Thu, 4 May 2023 14:37:35 -0400 Subject: [PATCH 17/56] [HttpKernel] Do not reset lazy services if they are not initialized --- .../DependencyInjection/ServicesResetter.php | 10 +++++++ .../ServicesResetterTest.php | 27 +++++++++++++++++++ .../Tests/Fixtures/LazyResettableService.php | 27 +++++++++++++++++++ .../Component/HttpKernel/composer.json | 1 + 4 files changed, 65 insertions(+) create mode 100644 src/Symfony/Component/HttpKernel/Tests/Fixtures/LazyResettableService.php diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/ServicesResetter.php b/src/Symfony/Component/HttpKernel/DependencyInjection/ServicesResetter.php index e7be3b88e7a34..799679effc0fc 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/ServicesResetter.php +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/ServicesResetter.php @@ -11,6 +11,8 @@ namespace Symfony\Component\HttpKernel\DependencyInjection; +use ProxyManager\Proxy\LazyLoadingInterface; +use Symfony\Component\VarExporter\LazyObjectInterface; use Symfony\Contracts\Service\ResetInterface; /** @@ -39,6 +41,14 @@ public function __construct(\Traversable $resettableServices, array $resetMethod public function reset() { foreach ($this->resettableServices as $id => $service) { + if ($service instanceof LazyObjectInterface && !$service->isLazyObjectInitialized(true)) { + continue; + } + + if ($service instanceof LazyLoadingInterface && !$service->isProxyInitialized()) { + continue; + } + foreach ((array) $this->resetMethods[$id] as $resetMethod) { if ('?' === $resetMethod[0] && !method_exists($service, $resetMethod = substr($resetMethod, 1))) { continue; diff --git a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ServicesResetterTest.php b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ServicesResetterTest.php index 604d2b0d13b82..3390dcc1e4d64 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ServicesResetterTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ServicesResetterTest.php @@ -14,8 +14,10 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\HttpKernel\DependencyInjection\ServicesResetter; use Symfony\Component\HttpKernel\Tests\Fixtures\ClearableService; +use Symfony\Component\HttpKernel\Tests\Fixtures\LazyResettableService; use Symfony\Component\HttpKernel\Tests\Fixtures\MultiResettableService; use Symfony\Component\HttpKernel\Tests\Fixtures\ResettableService; +use Symfony\Component\VarExporter\ProxyHelper; class ServicesResetterTest extends TestCase { @@ -46,4 +48,29 @@ public function testResetServices() $this->assertSame(1, MultiResettableService::$resetFirstCounter); $this->assertSame(1, MultiResettableService::$resetSecondCounter); } + + public function testResetLazyServices() + { + $proxyCode = ProxyHelper::generateLazyProxy(new \ReflectionClass(LazyResettableService::class)); + eval('class LazyResettableServiceProxy'.$proxyCode); + + $lazyService = \LazyResettableServiceProxy::createLazyProxy(fn (): LazyResettableService => new LazyResettableService()); + + $resetter = new ServicesResetter(new \ArrayIterator([ + 'lazy' => $lazyService, + ]), [ + 'lazy' => ['reset'], + ]); + + $resetter->reset(); + $this->assertSame(0, LazyResettableService::$counter); + + $resetter->reset(); + $this->assertSame(0, LazyResettableService::$counter); + + $this->assertTrue($lazyService->foo()); + + $resetter->reset(); + $this->assertSame(1, LazyResettableService::$counter); + } } diff --git a/src/Symfony/Component/HttpKernel/Tests/Fixtures/LazyResettableService.php b/src/Symfony/Component/HttpKernel/Tests/Fixtures/LazyResettableService.php new file mode 100644 index 0000000000000..543cf0d9538d3 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Tests/Fixtures/LazyResettableService.php @@ -0,0 +1,27 @@ + + * + * 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; + +class LazyResettableService +{ + public static $counter = 0; + + public function foo(): bool + { + return true; + } + + public function reset(): void + { + ++self::$counter; + } +} diff --git a/src/Symfony/Component/HttpKernel/composer.json b/src/Symfony/Component/HttpKernel/composer.json index b6fb8c2d1fb5a..9ee322035cd32 100644 --- a/src/Symfony/Component/HttpKernel/composer.json +++ b/src/Symfony/Component/HttpKernel/composer.json @@ -40,6 +40,7 @@ "symfony/translation": "^5.4|^6.0", "symfony/translation-contracts": "^1.1|^2|^3", "symfony/uid": "^5.4|^6.0", + "symfony/var-exporter": "^6.2", "psr/cache": "^1.0|^2.0|^3.0", "twig/twig": "^2.13|^3.0.4" }, From fe5255bb6d13d1ed00cdb8768780db9c74669e1d Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 5 May 2023 12:56:46 +0200 Subject: [PATCH 18/56] Remove usage of constant for better consistency across the codebase --- src/Symfony/Component/Console/Command/CompleteCommand.php | 4 ++-- .../Component/Console/Command/DumpCompletionCommand.php | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/Console/Command/CompleteCommand.php b/src/Symfony/Component/Console/Command/CompleteCommand.php index 11ada4e4489b3..0e35143c3335d 100644 --- a/src/Symfony/Component/Console/Command/CompleteCommand.php +++ b/src/Symfony/Component/Console/Command/CompleteCommand.php @@ -155,10 +155,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int throw $e; } - return self::FAILURE; + return 2; } - return self::SUCCESS; + return 0; } private function createCompletionInput(InputInterface $input): CompletionInput diff --git a/src/Symfony/Component/Console/Command/DumpCompletionCommand.php b/src/Symfony/Component/Console/Command/DumpCompletionCommand.php index 6f809e2f139a1..eaf22be1a9ad4 100644 --- a/src/Symfony/Component/Console/Command/DumpCompletionCommand.php +++ b/src/Symfony/Component/Console/Command/DumpCompletionCommand.php @@ -85,7 +85,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($input->getOption('debug')) { $this->tailDebugLog($commandName, $output); - return self::SUCCESS; + return 0; } $shell = $input->getArgument('shell') ?? self::guessShell(); @@ -102,12 +102,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln(sprintf('Shell not detected, Symfony shell completion only supports "%s").', implode('", "', $supportedShells))); } - return self::INVALID; + return 2; } $output->write(str_replace(['{{ COMMAND_NAME }}', '{{ VERSION }}'], [$commandName, $this->getApplication()->getVersion()], file_get_contents($completionFile))); - return self::SUCCESS; + return 0; } private static function guessShell(): string From 57b09ce59fcea8c4b672d4834284d45e2b565010 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 5 May 2023 12:57:54 +0200 Subject: [PATCH 19/56] Remove usage of constant for better consistency across the codebase --- .../Command/CachePoolInvalidateTagsCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolInvalidateTagsCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolInvalidateTagsCommand.php index a69624c8372c4..bb5c5c21f4799 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolInvalidateTagsCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolInvalidateTagsCommand.php @@ -92,12 +92,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($errors) { $io->error('Done but with errors.'); - return self::FAILURE; + return 2; } $io->success('Successfully invalidated cache tags.'); - return self::SUCCESS; + return 0; } public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void From 3c49177c306bdec8e01320b7cae61330be20f462 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 5 May 2023 15:20:02 +0200 Subject: [PATCH 20/56] [HttpKernel] Fix restoring surrogate content from cache --- .../HttpCache/AbstractSurrogate.php | 11 +++++ .../Component/HttpKernel/HttpCache/Esi.php | 4 +- .../HttpKernel/HttpCache/HttpCache.php | 30 +++++++------- .../Component/HttpKernel/HttpCache/Ssi.php | 5 +-- .../Component/HttpKernel/HttpCache/Store.php | 14 ++++++- .../Tests/HttpCache/HttpCacheTestCase.php | 2 +- .../HttpKernel/Tests/HttpCache/StoreTest.php | 41 +++++++++++++++---- 7 files changed, 74 insertions(+), 33 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/HttpCache/AbstractSurrogate.php b/src/Symfony/Component/HttpKernel/HttpCache/AbstractSurrogate.php index f2d809e8de97d..e1d73dc74827d 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/AbstractSurrogate.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/AbstractSurrogate.php @@ -133,4 +133,15 @@ protected function removeFromControl(Response $response) $response->headers->set('Surrogate-Control', preg_replace(sprintf('#content="%s/1.0",\s*#', $upperName), '', $value)); } } + + protected static function generateBodyEvalBoundary(): string + { + static $cookie; + $cookie = hash('md5', $cookie ?? $cookie = random_bytes(16), true); + $boundary = base64_encode($cookie); + + \assert(HttpCache::BODY_EVAL_BOUNDARY_LENGTH === \strlen($boundary)); + + return $boundary; + } } diff --git a/src/Symfony/Component/HttpKernel/HttpCache/Esi.php b/src/Symfony/Component/HttpKernel/HttpCache/Esi.php index 4d86508a42092..9f453249325b2 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/Esi.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/Esi.php @@ -80,9 +80,7 @@ public function process(Request $request, Response $response) $content = preg_replace('#.*?#s', '', $content); $content = preg_replace('#]+>#s', '', $content); - static $cookie; - $cookie = hash('md5', $cookie ?? $cookie = random_bytes(16), true); - $boundary = base64_encode($cookie); + $boundary = self::generateBodyEvalBoundary(); $chunks = preg_split('##', $content, -1, \PREG_SPLIT_DELIM_CAPTURE); $i = 1; diff --git a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php index 063d4105b160b..b01bd722607a9 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php @@ -29,6 +29,8 @@ */ class HttpCache implements HttpKernelInterface, TerminableInterface { + public const BODY_EVAL_BOUNDARY_LENGTH = 24; + private $kernel; private $store; private $request; @@ -631,26 +633,22 @@ protected function store(Request $request, Response $response) private function restoreResponseBody(Request $request, Response $response) { if ($response->headers->has('X-Body-Eval')) { - ob_start(); + \assert(self::BODY_EVAL_BOUNDARY_LENGTH === 24); - if ($response->headers->has('X-Body-File')) { - include $response->headers->get('X-Body-File'); - } else { - $content = $response->getContent(); + ob_start(); - if (substr($content, -24) === $boundary = substr($content, 0, 24)) { - $j = strpos($content, $boundary, 24); - echo substr($content, 24, $j - 24); - $i = $j + 24; + $content = $response->getContent(); + $boundary = substr($content, 0, 24); + $j = strpos($content, $boundary, 24); + echo substr($content, 24, $j - 24); + $i = $j + 24; - while (false !== $j = strpos($content, $boundary, $i)) { - [$uri, $alt, $ignoreErrors, $part] = explode("\n", substr($content, $i, $j - $i), 4); - $i = $j + 24; + while (false !== $j = strpos($content, $boundary, $i)) { + [$uri, $alt, $ignoreErrors, $part] = explode("\n", substr($content, $i, $j - $i), 4); + $i = $j + 24; - echo $this->surrogate->handle($this, $uri, $alt, $ignoreErrors); - echo $part; - } - } + echo $this->surrogate->handle($this, $uri, $alt, $ignoreErrors); + echo $part; } $response->setContent(ob_get_clean()); diff --git a/src/Symfony/Component/HttpKernel/HttpCache/Ssi.php b/src/Symfony/Component/HttpKernel/HttpCache/Ssi.php index bb48238ff1f4b..61909100e6157 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/Ssi.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/Ssi.php @@ -64,10 +64,7 @@ public function process(Request $request, Response $response) // we don't use a proper XML parser here as we can have SSI tags in a plain text response $content = $response->getContent(); - - static $cookie; - $cookie = hash('md5', $cookie ?? $cookie = random_bytes(16), true); - $boundary = base64_encode($cookie); + $boundary = self::generateBodyEvalBoundary(); $chunks = preg_split('##', $content, -1, \PREG_SPLIT_DELIM_CAPTURE); $i = 1; diff --git a/src/Symfony/Component/HttpKernel/HttpCache/Store.php b/src/Symfony/Component/HttpKernel/HttpCache/Store.php index 5db94f73d68c2..9d7f3e4f6949d 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/Store.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/Store.php @@ -475,15 +475,25 @@ private function persistResponse(Response $response): array /** * Restores a Response from the HTTP headers and body. */ - private function restoreResponse(array $headers, string $path = null): Response + private function restoreResponse(array $headers, string $path = null): ?Response { $status = $headers['X-Status'][0]; unset($headers['X-Status']); + $content = null; if (null !== $path) { $headers['X-Body-File'] = [$path]; + unset($headers['x-body-file']); + + if ($headers['X-Body-Eval'] ?? $headers['x-body-eval'] ?? false) { + $content = file_get_contents($path); + \assert(HttpCache::BODY_EVAL_BOUNDARY_LENGTH === 24); + if (48 > \strlen($content) || substr($content, -24) !== substr($content, 0, 24)) { + return null; + } + } } - return new Response($path, $status, $headers); + return new Response($content, $status, $headers); } } diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTestCase.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTestCase.php index e47631d1780ea..c8b48ff811c76 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTestCase.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTestCase.php @@ -18,7 +18,7 @@ use Symfony\Component\HttpKernel\HttpCache\Store; use Symfony\Component\HttpKernel\HttpKernelInterface; -class HttpCacheTestCase extends TestCase +abstract class HttpCacheTestCase extends TestCase { protected $kernel; protected $cache; diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php index 239361bc8c337..aff5329cc96f8 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php @@ -200,7 +200,7 @@ public function testRestoresResponseContentFromEntityStoreWithLookup() { $this->storeSimpleEntry(); $response = $this->store->lookup($this->request); - $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test')), $response->getContent()); + $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test')), $response->headers->get('X-Body-File')); } public function testInvalidatesMetaAndEntityStoreEntriesWithInvalidate() @@ -253,9 +253,9 @@ public function testStoresMultipleResponsesForEachVaryCombination() $res3 = new Response('test 3', 200, ['Vary' => 'Foo Bar']); $this->store->write($req3, $res3); - $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 3')), $this->store->lookup($req3)->getContent()); - $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 2')), $this->store->lookup($req2)->getContent()); - $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 1')), $this->store->lookup($req1)->getContent()); + $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 3')), $this->store->lookup($req3)->headers->get('X-Body-File')); + $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 2')), $this->store->lookup($req2)->headers->get('X-Body-File')); + $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 1')), $this->store->lookup($req1)->headers->get('X-Body-File')); $this->assertCount(3, $this->getStoreMetadata($key)); } @@ -265,17 +265,17 @@ public function testOverwritesNonVaryingResponseWithStore() $req1 = Request::create('/test', 'get', [], [], [], ['HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar']); $res1 = new Response('test 1', 200, ['Vary' => 'Foo Bar']); $this->store->write($req1, $res1); - $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 1')), $this->store->lookup($req1)->getContent()); + $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 1')), $this->store->lookup($req1)->headers->get('X-Body-File')); $req2 = Request::create('/test', 'get', [], [], [], ['HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam']); $res2 = new Response('test 2', 200, ['Vary' => 'Foo Bar']); $this->store->write($req2, $res2); - $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 2')), $this->store->lookup($req2)->getContent()); + $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 2')), $this->store->lookup($req2)->headers->get('X-Body-File')); $req3 = Request::create('/test', 'get', [], [], [], ['HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar']); $res3 = new Response('test 3', 200, ['Vary' => 'Foo Bar']); $key = $this->store->write($req3, $res3); - $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 3')), $this->store->lookup($req3)->getContent()); + $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 3')), $this->store->lookup($req3)->headers->get('X-Body-File')); $this->assertCount(2, $this->getStoreMetadata($key)); } @@ -330,6 +330,33 @@ public function testDoesNotStorePrivateHeaders() $this->assertNotEmpty($response->headers->getCookies()); } + public function testDiscardsInvalidBodyEval() + { + $request = Request::create('https://example.com/foo'); + $response = new Response('foo', 200, ['X-Body-Eval' => 'SSI']); + + $this->store->write($request, $response); + $this->assertNull($this->store->lookup($request)); + + $request = Request::create('https://example.com/foo'); + $content = str_repeat('a', 24).'b'.str_repeat('a', 24).'b'; + $response = new Response($content, 200, ['X-Body-Eval' => 'SSI']); + + $this->store->write($request, $response); + $this->assertNull($this->store->lookup($request)); + } + + public function testLoadsBodyEval() + { + $request = Request::create('https://example.com/foo'); + $content = str_repeat('a', 24).'b'.str_repeat('a', 24); + $response = new Response($content, 200, ['X-Body-Eval' => 'SSI']); + + $this->store->write($request, $response); + $response = $this->store->lookup($request); + $this->assertSame($content, $response->getContent()); + } + protected function storeSimpleEntry($path = null, $headers = []) { if (null === $path) { From 668be942ac17fd15f19ff88126b9a3b2553e7056 Mon Sep 17 00:00:00 2001 From: MatTheCat Date: Fri, 5 May 2023 16:40:17 +0200 Subject: [PATCH 21/56] =?UTF-8?q?Do=20not=20check=20errored=20definitions?= =?UTF-8?q?=E2=80=99=20type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Compiler/CheckTypeDeclarationsPass.php | 2 +- .../Compiler/CheckTypeDeclarationsPassTest.php | 15 +++++++++++++++ .../BarErroredDependency.php | 10 ++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarErroredDependency.php diff --git a/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php index b7ec85cefb489..59e39067e777e 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php @@ -210,7 +210,7 @@ private function checkType(Definition $checkedDefinition, $value, \ReflectionPar $class = null; if ($value instanceof Definition) { - if ($value->getFactory()) { + if ($value->hasErrors() || $value->getFactory()) { return; } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php index 97e167c99cb10..e3c87ef13b74f 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php @@ -23,6 +23,7 @@ use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\Bar; +use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\BarErroredDependency; use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\BarMethodCall; use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\BarOptionalArgument; use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\BarOptionalArgumentNotNull; @@ -1010,6 +1011,20 @@ public function testIgnoreDefinitionFactoryArgument() $this->addToAssertionCount(1); } + + public function testErroredDefinitionsAreNotChecked() + { + $container = new ContainerBuilder(); + $container->register('errored_dependency', BarErroredDependency::class) + ->setArguments([ + (new Definition(Foo::class)) + ->addError('error'), + ]); + + (new CheckTypeDeclarationsPass(true))->process($container); + + $this->addToAssertionCount(1); + } } class CallableClass diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarErroredDependency.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarErroredDependency.php new file mode 100644 index 0000000000000..d1368c3f7ef44 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarErroredDependency.php @@ -0,0 +1,10 @@ + Date: Fri, 5 May 2023 18:19:22 +0200 Subject: [PATCH 22/56] [FrameworkBundle] minor fix --- .../FrameworkBundle/Command/CachePoolInvalidateTagsCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolInvalidateTagsCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolInvalidateTagsCommand.php index bb5c5c21f4799..9e6ef9330e24a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolInvalidateTagsCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolInvalidateTagsCommand.php @@ -92,7 +92,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($errors) { $io->error('Done but with errors.'); - return 2; + return 1; } $io->success('Successfully invalidated cache tags.'); From 5ff1db081f333e3bd61e28dbca32a79cc0e114be Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sun, 7 May 2023 14:06:34 +0200 Subject: [PATCH 23/56] [HttpClient] Fix setting duplicate-name headers when redirecting with AmpHttpClient --- src/Symfony/Component/HttpClient/Response/AmpResponse.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/HttpClient/Response/AmpResponse.php b/src/Symfony/Component/HttpClient/Response/AmpResponse.php index 6d0ce6e33e01f..3fac5860bb188 100644 --- a/src/Symfony/Component/HttpClient/Response/AmpResponse.php +++ b/src/Symfony/Component/HttpClient/Response/AmpResponse.php @@ -358,7 +358,7 @@ private static function followRedirects(Request $originRequest, AmpClientState $ } foreach ($originRequest->getRawHeaders() as [$name, $value]) { - $request->setHeader($name, $value); + $request->addHeader($name, $value); } if ($request->getUri()->getAuthority() !== $originRequest->getUri()->getAuthority()) { From b0ec0c75d9bba1e151e8003c39b852b4db9711cf Mon Sep 17 00:00:00 2001 From: Laurent VOULLEMIER Date: Mon, 8 May 2023 21:46:50 +0200 Subject: [PATCH 24/56] Fix param type annotation --- src/Symfony/Bridge/Doctrine/Middleware/Debug/Query.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Query.php b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Query.php index 71da329a996cd..eb835caa41b25 100644 --- a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Query.php +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Query.php @@ -46,8 +46,8 @@ public function stop(): void } /** - * @param string|int $param - * @param string|int|float|bool|null $variable + * @param string|int $param + * @param mixed $variable */ public function setParam($param, &$variable, int $type): void { From 318e0e481b643b7316eca4a804b5d4440bc339ca Mon Sep 17 00:00:00 2001 From: Laurent VOULLEMIER Date: Mon, 8 May 2023 19:32:07 +0200 Subject: [PATCH 25/56] Allow resources in Query::setParam --- .../Doctrine/Middleware/Debug/Query.php | 2 +- .../Tests/Middleware/Debug/MiddlewareTest.php | 22 +++++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Query.php b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Query.php index 9813d712132f4..695a0a1535aa2 100644 --- a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Query.php +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Query.php @@ -43,7 +43,7 @@ public function stop(): void } } - public function setParam(string|int $param, null|string|int|float|bool &$variable, int $type): void + public function setParam(string|int $param, mixed &$variable, int $type): void { // Numeric indexes start at 0 in profiler $idx = \is_int($param) ? $param - 1 : $param; diff --git a/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php b/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php index e605afec5a630..2bb628442459c 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php @@ -145,23 +145,31 @@ public function testWithParamBound(callable $executeMethod) { $this->init(); - $product = 'product1'; - $price = 12.5; - $stock = 5; + $sql = <<getResourceFromString('mydata'); - $stmt = $this->conn->prepare('INSERT INTO products(name, price, stock) VALUES (?, ?, ?)'); + $stmt = $this->conn->prepare($sql); $stmt->bindParam(1, $product); $stmt->bindParam(2, $price); $stmt->bindParam(3, $stock, ParameterType::INTEGER); + $stmt->bindParam(4, $res, ParameterType::BINARY); + + $product = 'product1'; + $price = 12.5; + $stock = 5; $executeMethod($stmt); // Debug data should not be affected by these changes $debug = $this->debugDataHolder->getData()['default'] ?? []; $this->assertCount(2, $debug); - $this->assertSame('INSERT INTO products(name, price, stock) VALUES (?, ?, ?)', $debug[1]['sql']); - $this->assertSame(['product1', '12.5', 5], $debug[1]['params']); - $this->assertSame([ParameterType::STRING, ParameterType::STRING, ParameterType::INTEGER], $debug[1]['types']); + $this->assertSame($sql, $debug[1]['sql']); + $this->assertSame(['product1', 12.5, 5, $expectedRes], $debug[1]['params']); + $this->assertSame([ParameterType::STRING, ParameterType::STRING, ParameterType::INTEGER, ParameterType::BINARY], $debug[1]['types']); $this->assertGreaterThan(0, $debug[1]['executionMS']); } From 86609b6f37077e068bf4829a3a00b940aebe7200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20G=C3=BCnter?= Date: Tue, 9 May 2023 11:05:09 -0400 Subject: [PATCH 26/56] [Messenger] Add `IS_REPEATABLE` flag to `AsMessageHandler` attribute --- src/Symfony/Component/Messenger/Attribute/AsMessageHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Messenger/Attribute/AsMessageHandler.php b/src/Symfony/Component/Messenger/Attribute/AsMessageHandler.php index c25c8b2c974dc..c0acf6f6c3d3b 100644 --- a/src/Symfony/Component/Messenger/Attribute/AsMessageHandler.php +++ b/src/Symfony/Component/Messenger/Attribute/AsMessageHandler.php @@ -16,7 +16,7 @@ * * @author Alireza Mirsepassi */ -#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)] +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class AsMessageHandler { public function __construct( From f139be1eeda2e098de89a9551725b0b160cb4d00 Mon Sep 17 00:00:00 2001 From: Kevin Mian Kraiker Date: Tue, 9 May 2023 19:17:09 -0300 Subject: [PATCH 27/56] Update HeaderBag::all PhpDoc For better compliance with code quality tools. --- src/Symfony/Component/HttpFoundation/HeaderBag.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/HttpFoundation/HeaderBag.php b/src/Symfony/Component/HttpFoundation/HeaderBag.php index 0883024b3b50b..dc5a9a60a2e48 100644 --- a/src/Symfony/Component/HttpFoundation/HeaderBag.php +++ b/src/Symfony/Component/HttpFoundation/HeaderBag.php @@ -63,7 +63,7 @@ public function __toString(): string * * @param string|null $key The name of the headers to return or null to get them all * - * @return array>|array + * @return array>|list */ public function all(string $key = null): array { From 5509d1799e39f30d558cfd693adfc8f19010104e Mon Sep 17 00:00:00 2001 From: HypeMC Date: Wed, 10 May 2023 00:46:35 +0200 Subject: [PATCH 28/56] [PropertyAccess] Fix nullsafe operator on array index --- .../PropertyAccess/PropertyAccessor.php | 2 +- .../Tests/PropertyAccessorTest.php | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php index 990d77b7a5ed0..83f9a225a1569 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php +++ b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php @@ -301,7 +301,7 @@ private function readPropertiesUntil(array $zval, PropertyPathInterface $propert if (($zval[self::VALUE] instanceof \ArrayAccess && !$zval[self::VALUE]->offsetExists($property)) || (\is_array($zval[self::VALUE]) && !isset($zval[self::VALUE][$property]) && !\array_key_exists($property, $zval[self::VALUE])) ) { - if (!$ignoreInvalidIndices) { + if (!$ignoreInvalidIndices && !$isNullSafe) { if (!\is_array($zval[self::VALUE])) { if (!$zval[self::VALUE] instanceof \Traversable) { throw new NoSuchIndexException(sprintf('Cannot read index "%s" while trying to traverse path "%s".', $property, (string) $propertyPath)); diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php index c5ecd79be1bbf..ae773e91d87ec 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php +++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php @@ -573,12 +573,28 @@ public static function getValidReadPropertyPaths(): iterable yield [(object) ['foo' => null], 'foo?.bar.baz', null]; yield [(object) ['foo' => (object) ['bar' => null]], 'foo?.bar?.baz', null]; yield [(object) ['foo' => (object) ['bar' => null]], 'foo.bar?.baz', null]; + + yield from self::getNullSafeIndexPaths(); + } + + public static function getNullSafeIndexPaths(): iterable + { yield [(object) ['foo' => ['bar' => null]], 'foo[bar?].baz', null]; yield [[], '[foo?]', null]; yield [['foo' => ['firstName' => 'Bernhard']], '[foo][bar?]', null]; yield [['foo' => ['firstName' => 'Bernhard']], '[foo][bar?][baz?]', null]; } + /** + * @dataProvider getNullSafeIndexPaths + */ + public function testNullSafeIndexWithThrowOnInvalidIndex($objectOrArray, $path, $value) + { + $this->propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, PropertyAccessor::THROW_ON_INVALID_INDEX | PropertyAccessor::THROW_ON_INVALID_PROPERTY_PATH); + + $this->assertSame($value, $this->propertyAccessor->getValue($objectOrArray, $path)); + } + public function testTicket5755() { $object = new Ticket5775Object(); From adb9393a9cf81c32e6d42c3adb6d00da3b68dcd7 Mon Sep 17 00:00:00 2001 From: Lane Shukhov Date: Mon, 8 May 2023 20:35:45 +0300 Subject: [PATCH 29/56] [HttpFoundation] Fix file streaming after connection aborted --- src/Symfony/Component/HttpFoundation/BinaryFileResponse.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php b/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php index 03cf1a23ee3d8..d3caa36aa08ff 100644 --- a/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php +++ b/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php @@ -349,7 +349,7 @@ public function sendContent() while ('' !== $data) { $read = fwrite($out, $data); if (false === $read || connection_aborted()) { - break; + break 2; } if (0 < $length) { $length -= $read; From a5d1ca6caaf56c900b993ce8d8934b1147a72302 Mon Sep 17 00:00:00 2001 From: Maxim Tyugaev Date: Sat, 6 May 2023 21:20:54 +0300 Subject: [PATCH 30/56] [Serializer] Handle datetime deserialization in U format --- .../Serializer/Normalizer/DateTimeNormalizer.php | 9 ++++++++- .../Tests/Normalizer/DateTimeNormalizerTest.php | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php index 71df2e6bdfb7c..9736d8e78e4a0 100644 --- a/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php @@ -91,7 +91,14 @@ public function denormalize($data, string $type, string $format = null, array $c $dateTimeFormat = $context[self::FORMAT_KEY] ?? null; $timezone = $this->getTimezone($context); - if (null === $data || !\is_string($data) || '' === trim($data)) { + if (\is_int($data) || \is_float($data)) { + switch ($dateTimeFormat) { + case 'U': $data = sprintf('%d', $data); break; + case 'U.u': $data = sprintf('%.6F', $data); break; + } + } + + if (!\is_string($data) || '' === trim($data)) { throw NotNormalizableValueException::createForUnexpectedDataType('The data is either not an string, an empty string, or null; you should pass a string that can be parsed with the passed format or a valid DateTime string.', $data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true); } diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/DateTimeNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/DateTimeNormalizerTest.php index 674dfaab5382d..25b7c784fe0e2 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/DateTimeNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/DateTimeNormalizerTest.php @@ -178,6 +178,8 @@ public function testDenormalize() $this->assertEquals(new \DateTimeImmutable('2016/01/01', new \DateTimeZone('UTC')), $this->normalizer->denormalize('2016-01-01T00:00:00+00:00', \DateTimeImmutable::class)); $this->assertEquals(new \DateTime('2016/01/01', new \DateTimeZone('UTC')), $this->normalizer->denormalize('2016-01-01T00:00:00+00:00', \DateTime::class)); $this->assertEquals(new \DateTime('2016/01/01', new \DateTimeZone('UTC')), $this->normalizer->denormalize(' 2016-01-01T00:00:00+00:00 ', \DateTime::class)); + $this->assertEquals(new \DateTimeImmutable('2023-05-06T17:35:34.000000+0000', new \DateTimeZone('UTC')), $this->normalizer->denormalize(1683394534, \DateTimeImmutable::class, null, [DateTimeNormalizer::FORMAT_KEY => 'U'])); + $this->assertEquals(new \DateTimeImmutable('2023-05-06T17:35:34.123400+0000', new \DateTimeZone('UTC')), $this->normalizer->denormalize(1683394534.1234, \DateTimeImmutable::class, null, [DateTimeNormalizer::FORMAT_KEY => 'U.u'])); } public function testDenormalizeUsingTimezonePassedInConstructor() From e3a140d226dba20c2d3f4cf5692e001006629107 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Sat, 13 May 2023 05:59:13 +0200 Subject: [PATCH 31/56] [Security] Skip clearing CSRF Token on stateless logout --- .../Http/EventListener/CsrfTokenClearingLogoutListener.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Symfony/Component/Security/Http/EventListener/CsrfTokenClearingLogoutListener.php b/src/Symfony/Component/Security/Http/EventListener/CsrfTokenClearingLogoutListener.php index 032821a18b69e..ec00bc1d9be6e 100644 --- a/src/Symfony/Component/Security/Http/EventListener/CsrfTokenClearingLogoutListener.php +++ b/src/Symfony/Component/Security/Http/EventListener/CsrfTokenClearingLogoutListener.php @@ -13,6 +13,7 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface; +use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage; use Symfony\Component\Security\Http\Event\LogoutEvent; /** @@ -31,6 +32,10 @@ public function __construct(ClearableTokenStorageInterface $csrfTokenStorage) public function onLogout(LogoutEvent $event): void { + if ($this->csrfTokenStorage instanceof SessionTokenStorage && !$event->getRequest()->hasPreviousSession()) { + return; + } + $this->csrfTokenStorage->clear(); } From 29eaa54cd1a61ab6ecde84b4f06cd63903387ce8 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 13 May 2023 11:29:41 +0200 Subject: [PATCH 32/56] [HttpKernel] Account for Response::getDate() possibly returning a DateTimeImmutable --- .../Component/HttpKernel/HttpCache/ResponseCacheStrategy.php | 2 +- .../HttpKernel/Tests/HttpCache/ResponseCacheStrategyTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php b/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php index cf8682257ee8f..5f372c566dd44 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php @@ -147,7 +147,7 @@ public function update(Response $response) if (is_numeric($this->ageDirectives['expires'])) { $date = clone $response->getDate(); - $date->modify('+'.($this->ageDirectives['expires'] + $this->age).' seconds'); + $date = $date->modify('+'.($this->ageDirectives['expires'] + $this->age).' seconds'); $response->setExpires($date); } } diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/ResponseCacheStrategyTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/ResponseCacheStrategyTest.php index c5c510f85832e..ce9f5ba1a158a 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/ResponseCacheStrategyTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/ResponseCacheStrategyTest.php @@ -257,7 +257,7 @@ public function testCacheControlMerging(array $expects, array $master, array $su case 'expires': $expires = clone $response->getDate(); - $expires->modify('+'.$value.' seconds'); + $expires = $expires->modify('+'.$value.' seconds'); $response->setExpires($expires); break; From dcbdca60ce79e63bc6e8b75cdfbe974a739b8c97 Mon Sep 17 00:00:00 2001 From: Quentin Devos <4972091+Okhoshi@users.noreply.github.com> Date: Sat, 6 May 2023 22:37:52 +0200 Subject: [PATCH 33/56] [FrameworkBundle] Generate caches consistently on successive run of `cache:clear` command --- .../Command/CacheClearCommand.php | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php index b0d5565398d2d..053810757a0d8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php @@ -137,14 +137,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($output->isVerbose()) { $io->comment('Warming up optional cache...'); } - $warmer = $kernel->getContainer()->get('cache_warmer'); - // non optional warmers already ran during container compilation - $warmer->enableOnlyOptionalWarmers(); - $preload = (array) $warmer->warmUp($realCacheDir); - - if ($preload && file_exists($preloadFile = $realCacheDir.'/'.$kernel->getContainer()->getParameter('kernel.container_class').'.preload.php')) { - Preloader::append($preloadFile, $preload); - } + $this->warmupOptionals($realCacheDir); } } else { $fs->mkdir($warmupDir); @@ -153,7 +146,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($output->isVerbose()) { $io->comment('Warming up cache...'); } - $this->warmup($warmupDir, $realCacheDir, !$input->getOption('no-optional-warmers')); + $this->warmup($warmupDir, $realBuildDir); + + if (!$input->getOption('no-optional-warmers')) { + if ($output->isVerbose()) { + $io->comment('Warming up optional cache...'); + } + $this->warmupOptionals($realCacheDir); + } } if (!$fs->exists($warmupDir.'/'.$containerDir)) { @@ -227,7 +227,7 @@ private function isNfs(string $dir): bool return false; } - private function warmup(string $warmupDir, string $realBuildDir, bool $enableOptionalWarmers = true) + private function warmup(string $warmupDir, string $realBuildDir): void { // create a temporary kernel $kernel = $this->getApplication()->getKernel(); @@ -236,18 +236,6 @@ private function warmup(string $warmupDir, string $realBuildDir, bool $enableOpt } $kernel->reboot($warmupDir); - // warmup temporary dir - if ($enableOptionalWarmers) { - $warmer = $kernel->getContainer()->get('cache_warmer'); - // non optional warmers already ran during container compilation - $warmer->enableOnlyOptionalWarmers(); - $preload = (array) $warmer->warmUp($warmupDir); - - if ($preload && file_exists($preloadFile = $warmupDir.'/'.$kernel->getContainer()->getParameter('kernel.container_class').'.preload.php')) { - Preloader::append($preloadFile, $preload); - } - } - // fix references to cached files with the real cache directory name $search = [$warmupDir, str_replace('\\', '\\\\', $warmupDir)]; $replace = str_replace('\\', '/', $realBuildDir); @@ -258,4 +246,17 @@ private function warmup(string $warmupDir, string $realBuildDir, bool $enableOpt } } } + + private function warmupOptionals(string $realCacheDir): void + { + $kernel = $this->getApplication()->getKernel(); + $warmer = $kernel->getContainer()->get('cache_warmer'); + // non optional warmers already ran during container compilation + $warmer->enableOnlyOptionalWarmers(); + $preload = (array) $warmer->warmUp($realCacheDir); + + if ($preload && file_exists($preloadFile = $realCacheDir.'/'.$kernel->getContainer()->getParameter('kernel.container_class').'.preload.php')) { + Preloader::append($preloadFile, $preload); + } + } } From a55feaae41ccf8542f3354d2a0de7f514d85d08a Mon Sep 17 00:00:00 2001 From: HypeMC Date: Mon, 15 May 2023 22:11:03 +0200 Subject: [PATCH 34/56] [PropertyInfo] Fix `PhpStanExtractor` when constructor has no docblock --- .../Extractor/PhpStanExtractor.php | 5 ++++- .../Tests/Extractor/PhpStanExtractorTest.php | 9 +++++++++ .../ConstructorDummyWithoutDocBlock.php | 19 +++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/PropertyInfo/Tests/Fixtures/ConstructorDummyWithoutDocBlock.php diff --git a/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php index 52a43d16aee46..a964b5036893b 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php @@ -171,7 +171,10 @@ private function getDocBlockFromConstructor(string $class, string $property): ?P return null; } - $rawDocNode = $reflectionConstructor->getDocComment(); + if (!$rawDocNode = $reflectionConstructor->getDocComment()) { + return null; + } + $tokens = new TokenIterator($this->lexer->tokenize($rawDocNode)); $phpDocNode = $this->phpDocParser->parse($tokens); $tokens->consumeTokenType(Lexer::TOKEN_END); diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php index a517e7ac30469..c607f2abc3761 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor; +use Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummyWithoutDocBlock; use Symfony\Component\PropertyInfo\Tests\Fixtures\DefaultValue; use Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy; @@ -353,6 +354,14 @@ public function testExtractConstructorTypes($property, array $type = null) $this->assertEquals($type, $this->extractor->getTypesFromConstructor('Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummy', $property)); } + /** + * @dataProvider constructorTypesProvider + */ + public function testExtractConstructorTypesReturnNullOnEmptyDocBlock($property) + { + $this->assertNull($this->extractor->getTypesFromConstructor(ConstructorDummyWithoutDocBlock::class, $property)); + } + public static function constructorTypesProvider() { return [ diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ConstructorDummyWithoutDocBlock.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ConstructorDummyWithoutDocBlock.php new file mode 100644 index 0000000000000..7e2087d467f7d --- /dev/null +++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ConstructorDummyWithoutDocBlock.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\PropertyInfo\Tests\Fixtures; + +class ConstructorDummyWithoutDocBlock +{ + public function __construct(\DateTimeZone $timezone, $date, $dateObject, \DateTimeImmutable $dateTime, $mixed) + { + } +} From 0cc9e3ae1f3c5b2f159f8b4501360ab18982c7dd Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Wed, 17 May 2023 00:33:28 +0200 Subject: [PATCH 35/56] [Notifier] Update AmazonSns url in doc from de to en --- src/Symfony/Component/Notifier/Bridge/AmazonSns/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Notifier/Bridge/AmazonSns/README.md b/src/Symfony/Component/Notifier/Bridge/AmazonSns/README.md index 3dbcbb1546247..1e8e244f014a9 100644 --- a/src/Symfony/Component/Notifier/Bridge/AmazonSns/README.md +++ b/src/Symfony/Component/Notifier/Bridge/AmazonSns/README.md @@ -1,7 +1,7 @@ Amazon Notifier =============== -Provides [Amazon SNS](https://aws.amazon.com/de/sns/) integration for Symfony Notifier. +Provides [Amazon SNS](https://aws.amazon.com/en/sns/) integration for Symfony Notifier. DSN example ----------- From d3486ddfa8061d7d781238fce00ce628aa8ead87 Mon Sep 17 00:00:00 2001 From: Vitalii Zhuk Date: Wed, 17 May 2023 12:12:31 +0300 Subject: [PATCH 36/56] Fix get sender name in turbosms notifier --- .../Bridge/TurboSms/Tests/TurboSmsTransportTest.php | 11 ++++++++++- .../Notifier/Bridge/TurboSms/TurboSmsTransport.php | 3 ++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Notifier/Bridge/TurboSms/Tests/TurboSmsTransportTest.php b/src/Symfony/Component/Notifier/Bridge/TurboSms/Tests/TurboSmsTransportTest.php index 386a69bdaeaa8..b650866df567c 100644 --- a/src/Symfony/Component/Notifier/Bridge/TurboSms/Tests/TurboSmsTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/TurboSms/Tests/TurboSmsTransportTest.php @@ -71,7 +71,16 @@ public function testSuccessfulSend() ])) ; - $client = new MockHttpClient(static function () use ($response): ResponseInterface { + $client = new MockHttpClient(static function (string $method, string $url, array $options) use ($response): ResponseInterface { + $body = json_decode($options['body'], true); + self::assertSame([ + 'sms' => [ + 'sender' => 'sender', + 'recipients' => ['380931234567'], + 'text' => 'Тест/Test', + ] + ], $body); + return $response; }); diff --git a/src/Symfony/Component/Notifier/Bridge/TurboSms/TurboSmsTransport.php b/src/Symfony/Component/Notifier/Bridge/TurboSms/TurboSmsTransport.php index 1d885abd2d07d..089e84a71b70e 100644 --- a/src/Symfony/Component/Notifier/Bridge/TurboSms/TurboSmsTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/TurboSms/TurboSmsTransport.php @@ -66,7 +66,8 @@ protected function doSend(MessageInterface $message): SentMessage $this->assertValidSubject($message->getSubject()); $fromMessage = $message->getFrom(); - if (null !== $fromMessage) { + + if ($fromMessage) { $this->assertValidFrom($fromMessage); $from = $fromMessage; } else { From c89132bac4c607faaf871727d299490c15dec909 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Wed, 17 May 2023 13:26:05 +0200 Subject: [PATCH 37/56] [Process] Stop the process correctly even if underlying input stream is not closed: While checking a process to end, on posix system, process component only checks if pipes are still open, this fix ensure that if the process is terminated it correctly return, even if the underlying pipe is not closed. It can be useful when using \STDIN as a input stream as it will always be open --- src/Symfony/Component/Process/Process.php | 2 +- src/Symfony/Component/Process/Tests/ProcessTest.php | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Process/Process.php b/src/Symfony/Component/Process/Process.php index b47ecca17bfe5..9b19475ac5d78 100644 --- a/src/Symfony/Component/Process/Process.php +++ b/src/Symfony/Component/Process/Process.php @@ -428,7 +428,7 @@ public function wait(callable $callback = null) do { $this->checkTimeout(); - $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen(); + $running = $this->isRunning() && ('\\' === \DIRECTORY_SEPARATOR || $this->processPipes->areOpen()); $this->readPipes($running, '\\' !== \DIRECTORY_SEPARATOR || !$running); } while ($running); diff --git a/src/Symfony/Component/Process/Tests/ProcessTest.php b/src/Symfony/Component/Process/Tests/ProcessTest.php index 790167fcade94..36acf02a7cd6b 100644 --- a/src/Symfony/Component/Process/Tests/ProcessTest.php +++ b/src/Symfony/Component/Process/Tests/ProcessTest.php @@ -1538,6 +1538,16 @@ public function testEnvCaseInsensitiveOnWindows() } } + public function testNotTerminableInputPipe() + { + $process = $this->getProcess('echo foo'); + $process->setInput(\STDIN); + $process->start(); + $process->setTimeout(2); + $process->wait(); + $this->assertFalse($process->isRunning()); + } + /** * @param string|array $commandline * @param mixed $input From a0163929c8f252de5b0b2c4257eaf6045f6d4c8d Mon Sep 17 00:00:00 2001 From: Giorgio Premi Date: Fri, 12 May 2023 18:17:02 +0200 Subject: [PATCH 38/56] UrlHelper is now aware of RequestContext changes RequestContext in routing can change at runtime after the UrlHelper has been created, so using a RequestContextAwareInterface instance (i.e. the router) will workaround URL generation issues. --- .../Resources/config/services.php | 2 +- .../Bundle/FrameworkBundle/composer.json | 2 +- .../HttpFoundation/Tests/UrlHelperTest.php | 36 ++++++++++++++++++ .../Component/HttpFoundation/UrlHelper.php | 38 +++++++++++++------ 4 files changed, 65 insertions(+), 13 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php index a26dfb5adc612..cc2810fc6dbb4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php @@ -110,7 +110,7 @@ class_exists(WorkflowEvents::class) ? WorkflowEvents::ALIASES : [] ->set('url_helper', UrlHelper::class) ->args([ service('request_stack'), - service('router.request_context')->ignoreOnInvalid(), + service('router')->ignoreOnInvalid(), ]) ->alias(UrlHelper::class, 'url_helper') diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 089e2e0827fea..19da8ab3b66d6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -24,7 +24,7 @@ "symfony/deprecation-contracts": "^2.1|^3", "symfony/event-dispatcher": "^5.1|^6.0", "symfony/error-handler": "^4.4.1|^5.0.1|^6.0", - "symfony/http-foundation": "^5.3|^6.0", + "symfony/http-foundation": "^5.4.24|^6.2.11", "symfony/http-kernel": "^5.4|^6.0", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php80": "^1.16", diff --git a/src/Symfony/Component/HttpFoundation/Tests/UrlHelperTest.php b/src/Symfony/Component/HttpFoundation/Tests/UrlHelperTest.php index 2057dd7097cc8..080b5ffea3482 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/UrlHelperTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/UrlHelperTest.php @@ -16,6 +16,7 @@ use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\UrlHelper; use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\RequestContextAwareInterface; class UrlHelperTest extends TestCase { @@ -64,11 +65,46 @@ public function testGenerateAbsoluteUrlWithRequestContext($path, $baseUrl, $host } $requestContext = new RequestContext($baseUrl, 'GET', $host, $scheme, $httpPort, $httpsPort, $path); + $helper = new UrlHelper(new RequestStack(), $requestContext); $this->assertEquals($expected, $helper->getAbsoluteUrl($path)); } + /** + * @dataProvider getGenerateAbsoluteUrlRequestContextData + */ + public function testGenerateAbsoluteUrlWithRequestContextAwareInterface($path, $baseUrl, $host, $scheme, $httpPort, $httpsPort, $expected) + { + if (!class_exists(RequestContext::class)) { + $this->markTestSkipped('The Routing component is needed to run tests that depend on its request context.'); + } + + $requestContext = new RequestContext($baseUrl, 'GET', $host, $scheme, $httpPort, $httpsPort, $path); + $contextAware = new class($requestContext) implements RequestContextAwareInterface { + private $requestContext; + + public function __construct($requestContext) + { + $this->requestContext = $requestContext; + } + + public function setContext(RequestContext $context) + { + $this->requestContext = $context; + } + + public function getContext() + { + return $this->requestContext; + } + }; + + $helper = new UrlHelper(new RequestStack(), $contextAware); + + $this->assertEquals($expected, $helper->getAbsoluteUrl($path)); + } + /** * @dataProvider getGenerateAbsoluteUrlRequestContextData */ diff --git a/src/Symfony/Component/HttpFoundation/UrlHelper.php b/src/Symfony/Component/HttpFoundation/UrlHelper.php index c15f101cdf80b..90659947dba6d 100644 --- a/src/Symfony/Component/HttpFoundation/UrlHelper.php +++ b/src/Symfony/Component/HttpFoundation/UrlHelper.php @@ -12,6 +12,7 @@ namespace Symfony\Component\HttpFoundation; use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\RequestContextAwareInterface; /** * A helper service for manipulating URLs within and outside the request scope. @@ -23,8 +24,15 @@ final class UrlHelper private $requestStack; private $requestContext; - public function __construct(RequestStack $requestStack, RequestContext $requestContext = null) + /** + * @param RequestContextAwareInterface|RequestContext|null $requestContext + */ + public function __construct(RequestStack $requestStack, $requestContext = null) { + if (null !== $requestContext && !$requestContext instanceof RequestContext && !$requestContext instanceof RequestContextAwareInterface) { + throw new \TypeError(__METHOD__.': Argument #2 ($requestContext) must of type Symfony\Component\Routing\RequestContextAwareInterface|Symfony\Component\Routing\RequestContext|null, '.get_debug_type($requestContext).' given.'); + } + $this->requestStack = $requestStack; $this->requestContext = $requestContext; } @@ -73,28 +81,36 @@ public function getRelativePath(string $path): string private function getAbsoluteUrlFromContext(string $path): string { - if (null === $this->requestContext || '' === $host = $this->requestContext->getHost()) { + if (null === $context = $this->requestContext) { + return $path; + } + + if ($context instanceof RequestContextAwareInterface) { + $context = $context->getContext(); + } + + if ('' === $host = $context->getHost()) { return $path; } - $scheme = $this->requestContext->getScheme(); + $scheme = $context->getScheme(); $port = ''; - if ('http' === $scheme && 80 !== $this->requestContext->getHttpPort()) { - $port = ':'.$this->requestContext->getHttpPort(); - } elseif ('https' === $scheme && 443 !== $this->requestContext->getHttpsPort()) { - $port = ':'.$this->requestContext->getHttpsPort(); + if ('http' === $scheme && 80 !== $context->getHttpPort()) { + $port = ':'.$context->getHttpPort(); + } elseif ('https' === $scheme && 443 !== $context->getHttpsPort()) { + $port = ':'.$context->getHttpsPort(); } if ('#' === $path[0]) { - $queryString = $this->requestContext->getQueryString(); - $path = $this->requestContext->getPathInfo().($queryString ? '?'.$queryString : '').$path; + $queryString = $context->getQueryString(); + $path = $context->getPathInfo().($queryString ? '?'.$queryString : '').$path; } elseif ('?' === $path[0]) { - $path = $this->requestContext->getPathInfo().$path; + $path = $context->getPathInfo().$path; } if ('/' !== $path[0]) { - $path = rtrim($this->requestContext->getBaseUrl(), '/').'/'.$path; + $path = rtrim($context->getBaseUrl(), '/').'/'.$path; } return $scheme.'://'.$host.$port.$path; From 75eb46949d9fae3703fcce9907e29d72357fc0f2 Mon Sep 17 00:00:00 2001 From: Giorgio Premi Date: Fri, 12 May 2023 18:17:02 +0200 Subject: [PATCH 39/56] [HttpFoundation] UrlHelper is now aware of RequestContext changes --- .../Resources/config/services.php | 2 +- .../Bundle/FrameworkBundle/composer.json | 2 +- .../HttpFoundation/Tests/UrlHelperTest.php | 35 +++++++++++++++++ .../Component/HttpFoundation/UrlHelper.php | 38 +++++++++++++------ 4 files changed, 64 insertions(+), 13 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php index a26dfb5adc612..cc2810fc6dbb4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php @@ -110,7 +110,7 @@ class_exists(WorkflowEvents::class) ? WorkflowEvents::ALIASES : [] ->set('url_helper', UrlHelper::class) ->args([ service('request_stack'), - service('router.request_context')->ignoreOnInvalid(), + service('router')->ignoreOnInvalid(), ]) ->alias(UrlHelper::class, 'url_helper') diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 089e2e0827fea..19da8ab3b66d6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -24,7 +24,7 @@ "symfony/deprecation-contracts": "^2.1|^3", "symfony/event-dispatcher": "^5.1|^6.0", "symfony/error-handler": "^4.4.1|^5.0.1|^6.0", - "symfony/http-foundation": "^5.3|^6.0", + "symfony/http-foundation": "^5.4.24|^6.2.11", "symfony/http-kernel": "^5.4|^6.0", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php80": "^1.16", diff --git a/src/Symfony/Component/HttpFoundation/Tests/UrlHelperTest.php b/src/Symfony/Component/HttpFoundation/Tests/UrlHelperTest.php index 2057dd7097cc8..25640402af238 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/UrlHelperTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/UrlHelperTest.php @@ -16,6 +16,7 @@ use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\UrlHelper; use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\RequestContextAwareInterface; class UrlHelperTest extends TestCase { @@ -69,6 +70,40 @@ public function testGenerateAbsoluteUrlWithRequestContext($path, $baseUrl, $host $this->assertEquals($expected, $helper->getAbsoluteUrl($path)); } + /** + * @dataProvider getGenerateAbsoluteUrlRequestContextData + */ + public function testGenerateAbsoluteUrlWithRequestContextAwareInterface($path, $baseUrl, $host, $scheme, $httpPort, $httpsPort, $expected) + { + if (!class_exists(RequestContext::class)) { + $this->markTestSkipped('The Routing component is needed to run tests that depend on its request context.'); + } + + $requestContext = new RequestContext($baseUrl, 'GET', $host, $scheme, $httpPort, $httpsPort, $path); + $contextAware = new class($requestContext) implements RequestContextAwareInterface { + private $requestContext; + + public function __construct($requestContext) + { + $this->requestContext = $requestContext; + } + + public function setContext(RequestContext $context) + { + $this->requestContext = $context; + } + + public function getContext() + { + return $this->requestContext; + } + }; + + $helper = new UrlHelper(new RequestStack(), $contextAware); + + $this->assertEquals($expected, $helper->getAbsoluteUrl($path)); + } + /** * @dataProvider getGenerateAbsoluteUrlRequestContextData */ diff --git a/src/Symfony/Component/HttpFoundation/UrlHelper.php b/src/Symfony/Component/HttpFoundation/UrlHelper.php index c15f101cdf80b..90659947dba6d 100644 --- a/src/Symfony/Component/HttpFoundation/UrlHelper.php +++ b/src/Symfony/Component/HttpFoundation/UrlHelper.php @@ -12,6 +12,7 @@ namespace Symfony\Component\HttpFoundation; use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\RequestContextAwareInterface; /** * A helper service for manipulating URLs within and outside the request scope. @@ -23,8 +24,15 @@ final class UrlHelper private $requestStack; private $requestContext; - public function __construct(RequestStack $requestStack, RequestContext $requestContext = null) + /** + * @param RequestContextAwareInterface|RequestContext|null $requestContext + */ + public function __construct(RequestStack $requestStack, $requestContext = null) { + if (null !== $requestContext && !$requestContext instanceof RequestContext && !$requestContext instanceof RequestContextAwareInterface) { + throw new \TypeError(__METHOD__.': Argument #2 ($requestContext) must of type Symfony\Component\Routing\RequestContextAwareInterface|Symfony\Component\Routing\RequestContext|null, '.get_debug_type($requestContext).' given.'); + } + $this->requestStack = $requestStack; $this->requestContext = $requestContext; } @@ -73,28 +81,36 @@ public function getRelativePath(string $path): string private function getAbsoluteUrlFromContext(string $path): string { - if (null === $this->requestContext || '' === $host = $this->requestContext->getHost()) { + if (null === $context = $this->requestContext) { + return $path; + } + + if ($context instanceof RequestContextAwareInterface) { + $context = $context->getContext(); + } + + if ('' === $host = $context->getHost()) { return $path; } - $scheme = $this->requestContext->getScheme(); + $scheme = $context->getScheme(); $port = ''; - if ('http' === $scheme && 80 !== $this->requestContext->getHttpPort()) { - $port = ':'.$this->requestContext->getHttpPort(); - } elseif ('https' === $scheme && 443 !== $this->requestContext->getHttpsPort()) { - $port = ':'.$this->requestContext->getHttpsPort(); + if ('http' === $scheme && 80 !== $context->getHttpPort()) { + $port = ':'.$context->getHttpPort(); + } elseif ('https' === $scheme && 443 !== $context->getHttpsPort()) { + $port = ':'.$context->getHttpsPort(); } if ('#' === $path[0]) { - $queryString = $this->requestContext->getQueryString(); - $path = $this->requestContext->getPathInfo().($queryString ? '?'.$queryString : '').$path; + $queryString = $context->getQueryString(); + $path = $context->getPathInfo().($queryString ? '?'.$queryString : '').$path; } elseif ('?' === $path[0]) { - $path = $this->requestContext->getPathInfo().$path; + $path = $context->getPathInfo().$path; } if ('/' !== $path[0]) { - $path = rtrim($this->requestContext->getBaseUrl(), '/').'/'.$path; + $path = rtrim($context->getBaseUrl(), '/').'/'.$path; } return $scheme.'://'.$host.$port.$path; From 9cc5cbfd585ed922992aba514c7f1f505593cb59 Mon Sep 17 00:00:00 2001 From: Andrew Forster Date: Tue, 16 May 2023 22:19:41 -0500 Subject: [PATCH 40/56] [FrameworkBundle] Fix Workflow without a marking store definition uses marking store definition of previously defined workflow --- .../FrameworkExtension.php | 3 +- .../PhpFrameworkExtensionTest.php | 56 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 10d479a11c699..82b798b18e698 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -920,6 +920,7 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $ $workflows[$workflowId] = $definitionDefinition; // Create MarkingStore + $markingStoreDefinition = null; if (isset($workflow['marking_store']['type'])) { $markingStoreDefinition = new ChildDefinition('workflow.marking_store.method'); $markingStoreDefinition->setArguments([ @@ -933,7 +934,7 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $ // Create Workflow $workflowDefinition = new ChildDefinition(sprintf('%s.abstract', $type)); $workflowDefinition->replaceArgument(0, new Reference(sprintf('%s.definition', $workflowId))); - $workflowDefinition->replaceArgument(1, $markingStoreDefinition ?? null); + $workflowDefinition->replaceArgument(1, $markingStoreDefinition); $workflowDefinition->replaceArgument(3, $name); $workflowDefinition->replaceArgument(4, $workflow['events_to_dispatch']); $workflowDefinition->addTag('container.private', [ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php index c763e2bd211b4..871b62e8811da 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php @@ -85,6 +85,62 @@ public function testWorkflowValidationStateMachine() }); } + public function testWorkflowDefaultMarkingStoreDefinition() + { + $container = $this->createContainerFromClosure(function ($container) { + $container->loadFromExtension('framework', [ + 'workflows' => [ + 'workflow_a' => [ + 'type' => 'state_machine', + 'marking_store' => [ + 'type' => 'method', + 'property' => 'status', + ], + 'supports' => [ + __CLASS__, + ], + 'places' => [ + 'a', + 'b', + ], + 'transitions' => [ + 'a_to_b' => [ + 'from' => ['a'], + 'to' => ['b'], + ], + ], + ], + 'workflow_b' => [ + 'type' => 'state_machine', + 'supports' => [ + __CLASS__, + ], + 'places' => [ + 'a', + 'b', + ], + 'transitions' => [ + 'a_to_b' => [ + 'from' => ['a'], + 'to' => ['b'], + ], + ], + ], + ], + ]); + }); + + $workflowA = $container->getDefinition('state_machine.workflow_a'); + $argumentsA = $workflowA->getArguments(); + $this->assertArrayHasKey('index_1', $argumentsA, 'workflow_a has a marking_store argument'); + $this->assertNotNull($argumentsA['index_1'], 'workflow_a marking_store argument is not null'); + + $workflowB = $container->getDefinition('state_machine.workflow_b'); + $argumentsB = $workflowB->getArguments(); + $this->assertArrayHasKey('index_1', $argumentsB, 'workflow_b has a marking_store argument'); + $this->assertNull($argumentsB['index_1'], 'workflow_b marking_store argument is null'); + } + public function testRateLimiterWithLockFactory() { try { From 240c03130bafdde5fd2dd085843779f17f5e687b Mon Sep 17 00:00:00 2001 From: HypeMC Date: Sat, 6 May 2023 19:22:54 +0200 Subject: [PATCH 41/56] [Serializer] Fix SerializedPath not working with constructor arguments --- .../Normalizer/AbstractObjectNormalizer.php | 68 +++---- .../SerializedPathInConstructorDummy.php | 25 +++ .../SerializedPathInConstructorDummy.php | 22 +++ .../Tests/Fixtures/serialization.xml | 4 + .../Tests/Fixtures/serialization.yml | 4 + .../ClassMetadataFactoryCompilerTest.php | 13 +- .../Loader/AnnotationLoaderTestCase.php | 9 + .../Mapping/Loader/XmlFileLoaderTest.php | 9 + .../Mapping/Loader/YamlFileLoaderTest.php | 9 + .../AbstractObjectNormalizerTest.php | 170 +++++++++++++++++- 10 files changed, 298 insertions(+), 35 deletions(-) create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/Annotations/SerializedPathInConstructorDummy.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedPathInConstructorDummy.php diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index afd3acabc46a2..69dc534e417fb 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -128,8 +128,8 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory $this->propertyTypeExtractor = $propertyTypeExtractor; - if (null === $classDiscriminatorResolver && null !== $classMetadataFactory) { - $classDiscriminatorResolver = new ClassDiscriminatorFromClassMetadata($classMetadataFactory); + if ($classMetadataFactory) { + $classDiscriminatorResolver ??= new ClassDiscriminatorFromClassMetadata($classMetadataFactory); } $this->classDiscriminatorResolver = $classDiscriminatorResolver; $this->objectClassResolver = $objectClassResolver; @@ -217,7 +217,7 @@ public function normalize(mixed $object, string $format = null, array $context = } $preserveEmptyObjects = $context[self::PRESERVE_EMPTY_OBJECTS] ?? $this->defaultContext[self::PRESERVE_EMPTY_OBJECTS] ?? false; - if ($preserveEmptyObjects && !\count($data)) { + if ($preserveEmptyObjects && !$data) { return new \ArrayObject(); } @@ -226,19 +226,8 @@ public function normalize(mixed $object, string $format = null, array $context = protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null) { - if ($this->classDiscriminatorResolver && $mapping = $this->classDiscriminatorResolver->getMappingForClass($class)) { - if (!isset($data[$mapping->getTypeProperty()])) { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Type property "%s" not found for the abstract object "%s".', $mapping->getTypeProperty(), $class), null, ['string'], isset($context['deserialization_path']) ? $context['deserialization_path'].'.'.$mapping->getTypeProperty() : $mapping->getTypeProperty(), false); - } - - $type = $data[$mapping->getTypeProperty()]; - if (null === ($mappedClass = $mapping->getClassForType($type))) { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type "%s" is not a valid value.', $type), $type, ['string'], isset($context['deserialization_path']) ? $context['deserialization_path'].'.'.$mapping->getTypeProperty() : $mapping->getTypeProperty(), true); - } - - if ($mappedClass !== $class) { - return $this->instantiateObject($data, $mappedClass, $context, new \ReflectionClass($mappedClass), $allowedAttributes, $format); - } + if ($class !== $mappedClass = $this->getMappedClass($data, $class, $context)) { + return $this->instantiateObject($data, $mappedClass, $context, new \ReflectionClass($mappedClass), $allowedAttributes, $format); } return parent::instantiateObject($data, $class, $context, $reflectionClass, $allowedAttributes, $format); @@ -270,7 +259,7 @@ protected function getAttributes(object $object, ?string $format, array $context $attributes = $this->extractAttributes($object, $format, $context); - if ($this->classDiscriminatorResolver && $mapping = $this->classDiscriminatorResolver->getMappingForMappedObject($object)) { + if ($mapping = $this->classDiscriminatorResolver?->getMappingForMappedObject($object)) { array_unshift($attributes, $mapping->getTypeProperty()); } @@ -319,11 +308,9 @@ public function denormalize(mixed $data, string $type, string $format = null, ar $normalizedData = $this->prepareForDenormalization($data); $extraAttributes = []; - $reflectionClass = new \ReflectionClass($type); - $object = $this->instantiateObject($normalizedData, $type, $context, $reflectionClass, $allowedAttributes, $format); - $resolvedClass = $this->objectClassResolver ? ($this->objectClassResolver)($object) : $object::class; + $mappedClass = $this->getMappedClass($normalizedData, $type, $context); - $nestedAttributes = $this->getNestedAttributes($resolvedClass); + $nestedAttributes = $this->getNestedAttributes($mappedClass); $nestedData = []; $propertyAccessor = PropertyAccess::createPropertyAccessor(); foreach ($nestedAttributes as $property => $serializedPath) { @@ -336,6 +323,9 @@ public function denormalize(mixed $data, string $type, string $format = null, ar $normalizedData = array_merge($normalizedData, $nestedData); + $object = $this->instantiateObject($normalizedData, $mappedClass, $context, new \ReflectionClass($mappedClass), $allowedAttributes, $format); + $resolvedClass = $this->objectClassResolver ? ($this->objectClassResolver)($object) : $object::class; + foreach ($normalizedData as $attribute => $value) { if ($this->nameConverter) { $notConverted = $attribute; @@ -675,11 +665,8 @@ private function updateData(array $data, string $attribute, mixed $attributeValu */ private function isMaxDepthReached(array $attributesMetadata, string $class, string $attribute, array &$context): bool { - $enableMaxDepth = $context[self::ENABLE_MAX_DEPTH] ?? $this->defaultContext[self::ENABLE_MAX_DEPTH] ?? false; - if ( - !$enableMaxDepth || - !isset($attributesMetadata[$attribute]) || - null === $maxDepth = $attributesMetadata[$attribute]->getMaxDepth() + if (!($enableMaxDepth = $context[self::ENABLE_MAX_DEPTH] ?? $this->defaultContext[self::ENABLE_MAX_DEPTH] ?? false) + || null === $maxDepth = $attributesMetadata[$attribute]?->getMaxDepth() ) { return false; } @@ -755,7 +742,7 @@ private function isUninitializedValueError(\Error $e): bool */ private function getNestedAttributes(string $class): array { - if (!$this->classMetadataFactory || !$this->classMetadataFactory->hasMetadataFor($class)) { + if (!$this->classMetadataFactory?->hasMetadataFor($class)) { return []; } @@ -781,15 +768,30 @@ private function getNestedAttributes(string $class): array private function removeNestedValue(array $path, array $data): array { $element = array_shift($path); - if ([] === $path) { + if (!$path || !$data[$element] = $this->removeNestedValue($path, $data[$element])) { unset($data[$element]); - } else { - $data[$element] = $this->removeNestedValue($path, $data[$element]); - if ([] === $data[$element]) { - unset($data[$element]); - } } return $data; } + + /** + * @return class-string + */ + private function getMappedClass(array $data, string $class, array $context): string + { + if (!$mapping = $this->classDiscriminatorResolver?->getMappingForClass($class)) { + return $class; + } + + if (null === $type = $data[$mapping->getTypeProperty()] ?? null) { + throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Type property "%s" not found for the abstract object "%s".', $mapping->getTypeProperty(), $class), null, ['string'], isset($context['deserialization_path']) ? $context['deserialization_path'].'.'.$mapping->getTypeProperty() : $mapping->getTypeProperty(), false); + } + + if (null === $mappedClass = $mapping->getClassForType($type)) { + throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type "%s" is not a valid value.', $type), $type, ['string'], isset($context['deserialization_path']) ? $context['deserialization_path'].'.'.$mapping->getTypeProperty() : $mapping->getTypeProperty(), true); + } + + return $mappedClass; + } } diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/Annotations/SerializedPathInConstructorDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/Annotations/SerializedPathInConstructorDummy.php new file mode 100644 index 0000000000000..a6d5109086899 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/Annotations/SerializedPathInConstructorDummy.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Fixtures\Annotations; + +use Symfony\Component\Serializer\Annotation\SerializedPath; + +class SerializedPathInConstructorDummy +{ + public function __construct( + /** + * @SerializedPath("[one][two]") + */ + public $three, + ) { + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedPathInConstructorDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedPathInConstructorDummy.php new file mode 100644 index 0000000000000..90aee115417d4 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedPathInConstructorDummy.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Fixtures\Attributes; + +use Symfony\Component\Serializer\Annotation\SerializedPath; + +class SerializedPathInConstructorDummy +{ + public function __construct( + #[SerializedPath('[one][two]')] public $three, + ) { + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.xml b/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.xml index 4890f56bfd0f9..5ca6ac412fc07 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.xml +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.xml @@ -30,6 +30,10 @@ + + + + diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.yml b/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.yml index 7519b979efa96..e052d65a88779 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.yml +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.yml @@ -22,6 +22,10 @@ serialized_path: '[one][two]' seven: serialized_path: '[three][four]' +'Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedPathInConstructorDummy': + attributes: + three: + serialized_path: '[one][two]' 'Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummy': discriminator_map: type_property: type diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php b/src/Symfony/Component/Serializer/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php index 5ce1931ba0cab..903612c684c0d 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php @@ -19,6 +19,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\Annotations\MaxDepthDummy; use Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedNameDummy; use Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedPathDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedPathInConstructorDummy; use Symfony\Component\Serializer\Tests\Fixtures\Dummy; final class ClassMetadataFactoryCompilerTest extends TestCase @@ -46,18 +47,20 @@ public function testItDumpMetadata() $maxDepthDummyMetadata = $classMetatadataFactory->getMetadataFor(MaxDepthDummy::class); $serializedNameDummyMetadata = $classMetatadataFactory->getMetadataFor(SerializedNameDummy::class); $serializedPathDummyMetadata = $classMetatadataFactory->getMetadataFor(SerializedPathDummy::class); + $serializedPathInConstructorDummyMetadata = $classMetatadataFactory->getMetadataFor(SerializedPathInConstructorDummy::class); $code = (new ClassMetadataFactoryCompiler())->compile([ $dummyMetadata, $maxDepthDummyMetadata, $serializedNameDummyMetadata, $serializedPathDummyMetadata, + $serializedPathInConstructorDummyMetadata, ]); file_put_contents($this->dumpPath, $code); $compiledMetadata = require $this->dumpPath; - $this->assertCount(4, $compiledMetadata); + $this->assertCount(5, $compiledMetadata); $this->assertArrayHasKey(Dummy::class, $compiledMetadata); $this->assertEquals([ @@ -99,5 +102,13 @@ public function testItDumpMetadata() ], null, ], $compiledMetadata[SerializedPathDummy::class]); + + $this->assertArrayHasKey(SerializedPathInConstructorDummy::class, $compiledMetadata); + $this->assertEquals([ + [ + 'three' => [[], null, null, '[one][two]'], + ], + null, + ], $compiledMetadata[SerializedPathInConstructorDummy::class]); } } diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AnnotationLoaderTestCase.php b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AnnotationLoaderTestCase.php index de7accd844b32..2dbd03703a2ce 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AnnotationLoaderTestCase.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AnnotationLoaderTestCase.php @@ -103,6 +103,15 @@ public function testLoadSerializedPath() $this->assertEquals(new PropertyPath('[three][four]'), $attributesMetadata['seven']->getSerializedPath()); } + public function testLoadSerializedPathInConstructor() + { + $classMetadata = new ClassMetadata($this->getNamespace().'\SerializedPathInConstructorDummy'); + $this->loader->loadClassMetadata($classMetadata); + + $attributesMetadata = $classMetadata->getAttributesMetadata(); + $this->assertEquals(new PropertyPath('[one][two]'), $attributesMetadata['three']->getSerializedPath()); + } + public function testLoadClassMetadataAndMerge() { $classMetadata = new ClassMetadata($this->getNamespace().'\GroupDummy'); diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php index b1e9ed7222636..202534f56fca5 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php @@ -94,6 +94,15 @@ public function testSerializedPath() $this->assertEquals('[three][four]', $attributesMetadata['seven']->getSerializedPath()); } + public function testSerializedPathInConstructor() + { + $classMetadata = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedPathInConstructorDummy'); + $this->loader->loadClassMetadata($classMetadata); + + $attributesMetadata = $classMetadata->getAttributesMetadata(); + $this->assertEquals('[one][two]', $attributesMetadata['three']->getSerializedPath()); + } + public function testLoadDiscriminatorMap() { $classMetadata = new ClassMetadata(AbstractDummy::class); diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php index bbe0a99aeab89..dcfd2b4afac51 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php @@ -108,6 +108,15 @@ public function testSerializedPath() $this->assertEquals(new PropertyPath('[three][four]'), $attributesMetadata['seven']->getSerializedPath()); } + public function testSerializedPathInConstructor() + { + $classMetadata = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedPathInConstructorDummy'); + $this->loader->loadClassMetadata($classMetadata); + + $attributesMetadata = $classMetadata->getAttributesMetadata(); + $this->assertEquals(new PropertyPath('[one][two]'), $attributesMetadata['three']->getSerializedPath()); + } + public function testLoadDiscriminatorMap() { $classMetadata = new ClassMetadata(AbstractDummy::class); diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php index 0c342b64d8000..d876193c8a741 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -18,6 +18,7 @@ use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Serializer\Annotation\Context; +use Symfony\Component\Serializer\Annotation\DiscriminatorMap; use Symfony\Component\Serializer\Annotation\SerializedName; use Symfony\Component\Serializer\Annotation\SerializedPath; use Symfony\Component\Serializer\Exception\ExtraAttributesException; @@ -171,6 +172,53 @@ public function testDenormalizeWithNestedAttributesDuplicateKeys() $normalizer->denormalize($data, DuplicateKeyNestedDummy::class, 'any'); } + public function testDenormalizeWithNestedAttributesInConstructor() + { + $normalizer = new AbstractObjectNormalizerWithMetadata(); + $data = [ + 'one' => [ + 'two' => [ + 'three' => 'foo', + ], + 'four' => 'quux', + ], + 'foo' => 'notfoo', + 'baz' => 'baz', + ]; + $test = $normalizer->denormalize($data, NestedDummyWithConstructor::class, 'any'); + $this->assertSame('foo', $test->foo); + $this->assertSame('quux', $test->quux); + $this->assertSame('notfoo', $test->notfoo); + $this->assertSame('baz', $test->baz); + } + + public function testDenormalizeWithNestedAttributesInConstructorAndDiscriminatorMap() + { + $normalizer = new AbstractObjectNormalizerWithMetadata(); + $data = [ + 'one' => [ + 'two' => [ + 'three' => 'foo', + ], + 'four' => 'quux', + ], + 'foo' => 'notfoo', + 'baz' => 'baz', + ]; + + $test1 = $normalizer->denormalize($data + ['type' => 'first'], AbstractNestedDummyWithConstructorAndDiscriminator::class, 'any'); + $this->assertInstanceOf(FirstNestedDummyWithConstructorAndDiscriminator::class, $test1); + $this->assertSame('foo', $test1->foo); + $this->assertSame('notfoo', $test1->notfoo); + $this->assertSame('baz', $test1->baz); + + $test2 = $normalizer->denormalize($data + ['type' => 'second'], AbstractNestedDummyWithConstructorAndDiscriminator::class, 'any'); + $this->assertInstanceOf(SecondNestedDummyWithConstructorAndDiscriminator::class, $test2); + $this->assertSame('quux', $test2->quux); + $this->assertSame('notfoo', $test2->notfoo); + $this->assertSame('baz', $test2->baz); + } + public function testNormalizeWithNestedAttributesMixingArrayTypes() { $this->expectException(LogicException::class); @@ -236,6 +284,52 @@ public function testNormalizeWithNestedAttributesWithoutMetadata() $this->assertSame($data, $test); } + public function testNormalizeWithNestedAttributesInConstructor() + { + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); + + $test = $normalizer->normalize(new NestedDummyWithConstructor('foo', 'quux', 'notfoo', 'baz'), 'any'); + $this->assertSame([ + 'one' => [ + 'two' => [ + 'three' => 'foo', + ], + 'four' => 'quux', + ], + 'foo' => 'notfoo', + 'baz' => 'baz', + ], $test); + } + + public function testNormalizeWithNestedAttributesInConstructorAndDiscriminatorMap() + { + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); + + $test1 = $normalizer->normalize(new FirstNestedDummyWithConstructorAndDiscriminator('foo', 'notfoo', 'baz'), 'any'); + $this->assertSame([ + 'type' => 'first', + 'one' => [ + 'two' => [ + 'three' => 'foo', + ], + ], + 'foo' => 'notfoo', + 'baz' => 'baz', + ], $test1); + + $test2 = $normalizer->normalize(new SecondNestedDummyWithConstructorAndDiscriminator('quux', 'notfoo', 'baz'), 'any'); + $this->assertSame([ + 'type' => 'second', + 'one' => [ + 'four' => 'quux', + ], + 'foo' => 'notfoo', + 'baz' => 'baz', + ], $test2); + } + public function testDenormalizeCollectionDecodedFromXmlWithOneChild() { $denormalizer = $this->getDenormalizerForDummyCollection(); @@ -661,6 +755,78 @@ class NestedDummy public $baz; } +class NestedDummyWithConstructor +{ + public function __construct( + /** + * @SerializedPath("[one][two][three]") + */ + public $foo, + + /** + * @SerializedPath("[one][four]") + */ + public $quux, + + /** + * @SerializedPath("[foo]") + */ + public $notfoo, + + public $baz, + ) { + } +} + +/** + * @DiscriminatorMap(typeProperty="type", mapping={ + * "first" = FirstNestedDummyWithConstructorAndDiscriminator::class, + * "second" = SecondNestedDummyWithConstructorAndDiscriminator::class, + * }) + */ +abstract class AbstractNestedDummyWithConstructorAndDiscriminator +{ + public function __construct( + /** + * @SerializedPath("[foo]") + */ + public $notfoo, + + public $baz, + ) { + } +} + +class FirstNestedDummyWithConstructorAndDiscriminator extends AbstractNestedDummyWithConstructorAndDiscriminator +{ + public function __construct( + /** + * @SerializedPath("[one][two][three]") + */ + public $foo, + + $notfoo, + $baz, + ) { + parent::__construct($notfoo, $baz); + } +} + +class SecondNestedDummyWithConstructorAndDiscriminator extends AbstractNestedDummyWithConstructorAndDiscriminator +{ + public function __construct( + /** + * @SerializedPath("[one][four]") + */ + public $quux, + + $notfoo, + $baz, + ) { + parent::__construct($notfoo, $baz); + } +} + class DuplicateKeyNestedDummy { /** @@ -711,7 +877,9 @@ protected function getAttributeValue(object $object, string $attribute, string $ protected function setAttributeValue(object $object, string $attribute, $value, string $format = null, array $context = []) { - $object->$attribute = $value; + if (property_exists($object, $attribute)) { + $object->$attribute = $value; + } } } From bf1ae1ad7be2798ce3858d45a0119d79db1196a7 Mon Sep 17 00:00:00 2001 From: Maximilian Beckers Date: Fri, 19 May 2023 11:40:46 +0200 Subject: [PATCH 42/56] [Console] Remove exec and replace it by shell_exec --- src/Symfony/Component/Console/Helper/QuestionHelper.php | 6 ++---- src/Symfony/Component/Console/Terminal.php | 8 +++----- .../Component/Console/Tests/Helper/QuestionHelperTest.php | 4 ++-- src/Symfony/Component/Console/Tests/TerminalTest.php | 4 ++-- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/Symfony/Component/Console/Helper/QuestionHelper.php b/src/Symfony/Component/Console/Helper/QuestionHelper.php index 10602038c299f..ec5b418f2cbe7 100644 --- a/src/Symfony/Component/Console/Helper/QuestionHelper.php +++ b/src/Symfony/Component/Console/Helper/QuestionHelper.php @@ -500,13 +500,11 @@ private function isInteractiveInput($inputStream): bool return self::$stdinIsInteractive = @posix_isatty(fopen('php://stdin', 'r')); } - if (!\function_exists('exec')) { + if (!\function_exists('shell_exec')) { return self::$stdinIsInteractive = true; } - exec('stty 2> /dev/null', $output, $status); - - return self::$stdinIsInteractive = 1 !== $status; + return self::$stdinIsInteractive = (bool) shell_exec('stty 2> '.('\\' === \DIRECTORY_SEPARATOR ? 'NUL' : '/dev/null')); } /** diff --git a/src/Symfony/Component/Console/Terminal.php b/src/Symfony/Component/Console/Terminal.php index 04653d3bd2bbc..b91e8afc5cac4 100644 --- a/src/Symfony/Component/Console/Terminal.php +++ b/src/Symfony/Component/Console/Terminal.php @@ -64,14 +64,12 @@ public static function hasSttyAvailable(): bool return self::$stty; } - // skip check if exec function is disabled - if (!\function_exists('exec')) { + // skip check if shell_exec function is disabled + if (!\function_exists('shell_exec')) { return false; } - exec('stty 2>&1', $output, $exitcode); - - return self::$stty = 0 === $exitcode; + return self::$stty = (bool) shell_exec('stty 2> '.('\\' === \DIRECTORY_SEPARATOR ? 'NUL' : '/dev/null')); } private static function initDimensions() diff --git a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php index 9761e8f979345..74315d8982638 100644 --- a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php @@ -430,7 +430,7 @@ public function testAskHiddenResponse() $this->assertEquals('8AM', $dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream("8AM\n")), $this->createOutputInterface(), $question)); } - public function testAskHiddenResponseTrimmed() + public function testAskHiddenResponseNotTrimmed() { if ('\\' === \DIRECTORY_SEPARATOR) { $this->markTestSkipped('This test is not supported on Windows'); @@ -442,7 +442,7 @@ public function testAskHiddenResponseTrimmed() $question->setHidden(true); $question->setTrimmable(false); - $this->assertEquals(' 8AM', $dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream(' 8AM')), $this->createOutputInterface(), $question)); + $this->assertEquals(' 8AM'.\PHP_EOL, $dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream(' 8AM'.\PHP_EOL)), $this->createOutputInterface(), $question)); } public function testAskMultilineResponseWithEOF() diff --git a/src/Symfony/Component/Console/Tests/TerminalTest.php b/src/Symfony/Component/Console/Tests/TerminalTest.php index c2d36fe0ab899..34d32b08ebecb 100644 --- a/src/Symfony/Component/Console/Tests/TerminalTest.php +++ b/src/Symfony/Component/Console/Tests/TerminalTest.php @@ -77,8 +77,8 @@ public function testSttyOnWindows() $this->markTestSkipped('Must be on windows'); } - $sttyString = exec('(stty -a | grep columns) 2>&1', $output, $exitcode); - if (0 !== $exitcode) { + $sttyString = shell_exec('(stty -a | grep columns) 2> NUL'); + if (!$sttyString) { $this->markTestSkipped('Must have stty support'); } From 4be2036b61e57a86b2776bcad4ba23d2a0052f7e Mon Sep 17 00:00:00 2001 From: Robert Korulczyk Date: Sat, 13 May 2023 13:20:31 +0200 Subject: [PATCH 43/56] [Translation] Fix handling of null messages in `ArrayLoader` --- src/Symfony/Component/Translation/Loader/ArrayLoader.php | 6 ++++-- .../Translation/Tests/Loader/YamlFileLoaderTest.php | 9 +++++++++ .../Component/Translation/Tests/fixtures/non-string.yml | 4 ++++ 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 src/Symfony/Component/Translation/Tests/fixtures/non-string.yml diff --git a/src/Symfony/Component/Translation/Loader/ArrayLoader.php b/src/Symfony/Component/Translation/Loader/ArrayLoader.php index 0758da8b59a54..feed0de4b0025 100644 --- a/src/Symfony/Component/Translation/Loader/ArrayLoader.php +++ b/src/Symfony/Component/Translation/Loader/ArrayLoader.php @@ -46,9 +46,11 @@ private function flatten(array $messages): array foreach ($messages as $key => $value) { if (\is_array($value)) { foreach ($this->flatten($value) as $k => $v) { - $result[$key.'.'.$k] = $v; + if (null !== $v) { + $result[$key.'.'.$k] = $v; + } } - } else { + } elseif (null !== $value) { $result[$key] = $value; } } diff --git a/src/Symfony/Component/Translation/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/Translation/Tests/Loader/YamlFileLoaderTest.php index 230c02e539e45..cac0b6f87823a 100644 --- a/src/Symfony/Component/Translation/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/Translation/Tests/Loader/YamlFileLoaderTest.php @@ -30,6 +30,15 @@ public function testLoad() $this->assertEquals([new FileResource($resource)], $catalogue->getResources()); } + public function testLoadNonStringMessages() + { + $loader = new YamlFileLoader(); + $resource = __DIR__.'/../fixtures/non-string.yml'; + $catalogue = $loader->load($resource, 'en', 'domain1'); + + $this->assertSame(['root.foo2' => '', 'root.bar' => 'bar'], $catalogue->all('domain1')); + } + public function testLoadDoesNothingIfEmpty() { $loader = new YamlFileLoader(); diff --git a/src/Symfony/Component/Translation/Tests/fixtures/non-string.yml b/src/Symfony/Component/Translation/Tests/fixtures/non-string.yml new file mode 100644 index 0000000000000..41e245e19a4a6 --- /dev/null +++ b/src/Symfony/Component/Translation/Tests/fixtures/non-string.yml @@ -0,0 +1,4 @@ +root: + foo1: + foo2: '' + bar: 'bar' From 2c33e56e8cefa879415f1a9ce14c7d4f001a56fc Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 19 May 2023 14:39:53 +0200 Subject: [PATCH 44/56] Fix merge --- .../PhpFrameworkExtensionTest.php | 1 + .../Component/HttpFoundation/Tests/UrlHelperTest.php | 12 +++++------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php index a21b1995e15fd..506162a42cdf7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php @@ -92,6 +92,7 @@ public function testWorkflowDefaultMarkingStoreDefinition() { $container = $this->createContainerFromClosure(function ($container) { $container->loadFromExtension('framework', [ + 'http_method_override' => false, 'workflows' => [ 'workflow_a' => [ 'type' => 'state_machine', diff --git a/src/Symfony/Component/HttpFoundation/Tests/UrlHelperTest.php b/src/Symfony/Component/HttpFoundation/Tests/UrlHelperTest.php index a1a11a9aa7dec..02f6c64cfce38 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/UrlHelperTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/UrlHelperTest.php @@ -82,19 +82,17 @@ public function testGenerateAbsoluteUrlWithRequestContextAwareInterface($path, $ $requestContext = new RequestContext($baseUrl, 'GET', $host, $scheme, $httpPort, $httpsPort, $path); $contextAware = new class($requestContext) implements RequestContextAwareInterface { - private $requestContext; - - public function __construct($requestContext) - { - $this->requestContext = $requestContext; + public function __construct( + private RequestContext $requestContext, + ) { } - public function setContext(RequestContext $context) + public function setContext(RequestContext $context): void { $this->requestContext = $context; } - public function getContext() + public function getContext(): RequestContext { return $this->requestContext; } From 099ba75b2a5bb955f936981614b284770a4aa580 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Fri, 19 May 2023 17:58:21 +0200 Subject: [PATCH 45/56] [Security] Test `CsrfTokenClearingLogoutListener` with stateless logout --- .../CsrfTokenClearingLogoutListenerTest.php | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/Symfony/Component/Security/Http/Tests/EventListener/CsrfTokenClearingLogoutListenerTest.php diff --git a/src/Symfony/Component/Security/Http/Tests/EventListener/CsrfTokenClearingLogoutListenerTest.php b/src/Symfony/Component/Security/Http/Tests/EventListener/CsrfTokenClearingLogoutListenerTest.php new file mode 100644 index 0000000000000..405c7ae085510 --- /dev/null +++ b/src/Symfony/Component/Security/Http/Tests/EventListener/CsrfTokenClearingLogoutListenerTest.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage; +use Symfony\Component\Security\Http\Event\LogoutEvent; +use Symfony\Component\Security\Http\EventListener\CsrfTokenClearingLogoutListener; + +class CsrfTokenClearingLogoutListenerTest extends TestCase +{ + public function testSkipsClearingSessionTokenStorageOnStatelessRequest() + { + try { + (new CsrfTokenClearingLogoutListener( + new SessionTokenStorage(new RequestStack()) + ))->onLogout(new LogoutEvent(new Request(), null)); + } catch (SessionNotFoundException) { + $this->fail('clear() must not be called if the request is not associated with a session instance'); + } + + $this->addToAssertionCount(1); + } +} From 9371536b23c5dc1ac3ecf49d4e0d58ae854ff557 Mon Sep 17 00:00:00 2001 From: Massimiliano Arione Date: Sun, 21 May 2023 10:55:33 +0200 Subject: [PATCH 46/56] fix exception wording --- .../Component/HttpKernel/Controller/ArgumentResolver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php index 52ac242141af6..049902585a734 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php @@ -78,7 +78,7 @@ public function getArguments(Request $request, callable $controller, \Reflection $representative = get_debug_type($representative); } - throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or because there is a non optional argument after this one.', $representative, $metadata->getName())); + throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or there is a non-optional argument after this one.', $representative, $metadata->getName())); } return $arguments; From b670273f2fa8c4a6d4557e1a0c9222f4ca45d070 Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Sat, 20 May 2023 23:36:46 +0200 Subject: [PATCH 47/56] [Notifier] Document Notifier options in README files 5.4 --- .../Bridge/AmazonSns/AmazonSnsOptions.php | 4 ++-- .../Notifier/Bridge/AmazonSns/README.md | 24 +++++++++++++++++++ .../Component/Notifier/Bridge/Mobyt/README.md | 23 ++++++++++++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Notifier/Bridge/AmazonSns/AmazonSnsOptions.php b/src/Symfony/Component/Notifier/Bridge/AmazonSns/AmazonSnsOptions.php index fe82d23972325..9e482c156e78b 100644 --- a/src/Symfony/Component/Notifier/Bridge/AmazonSns/AmazonSnsOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/AmazonSns/AmazonSnsOptions.php @@ -52,7 +52,7 @@ public function recipient(string $topic): self } /** - * @see PublishInput::$Subject + * @see PublishInput::$subject * * @return $this */ @@ -64,7 +64,7 @@ public function subject(string $subject): self } /** - * @see PublishInput::$MessageStructure + * @see PublishInput::$messageStructure * * @return $this */ diff --git a/src/Symfony/Component/Notifier/Bridge/AmazonSns/README.md b/src/Symfony/Component/Notifier/Bridge/AmazonSns/README.md index 1e8e244f014a9..db4759327f502 100644 --- a/src/Symfony/Component/Notifier/Bridge/AmazonSns/README.md +++ b/src/Symfony/Component/Notifier/Bridge/AmazonSns/README.md @@ -10,6 +10,30 @@ DSN example AMAZON_SNS_DSN=sns://ACCESS_ID:ACCESS_KEY@default?region=REGION ``` +Adding Options to a Chat Message +-------------------------------- + +With an Amazon SNS Chat Message, you can use the `AmazonSnsOptions` class to add +message options. + +```php +use Symfony\Component\Notifier\Message\ChatMessage; +use Symfony\Component\Notifier\Bridge\AmazonSns\AmazonSnsOptions; + +$chatMessage = new ChatMessage('Contribute To Symfony'); + +$options = (new AmazonSnsOptions('topic_arn')) + ->subject('subject') + ->messageStructure('json') + // ... + ; + +// Add the custom options to the chat message and send the message +$chatMessage->options($options); + +$chatter->send($chatMessage); +``` + Resources --------- diff --git a/src/Symfony/Component/Notifier/Bridge/Mobyt/README.md b/src/Symfony/Component/Notifier/Bridge/Mobyt/README.md index 2d9b0b6d1488d..d9760759c9f90 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mobyt/README.md +++ b/src/Symfony/Component/Notifier/Bridge/Mobyt/README.md @@ -16,6 +16,29 @@ where: - `FROM` is the sender - `TYPE_QUALITY` is the quality of your message: `N` for high, `L` for medium, `LL` for low (default: `L`) +Adding Options to a Message +--------------------------- + +With a Mobyt Message, you can use the `MobytOptions` class to add +[message options](https://gatewayapi.com/docs/apis/rest/). + +```php +use Symfony\Component\Notifier\Message\SmsMessage; +use Symfony\Component\Notifier\Bridge\Mobyt\MobytOptions; + +$sms = new SmsMessage('+1411111111', 'My message'); + +$options = (new MobytOptions()) + ->messageType(MobytOptions::MESSAGE_TYPE_QUALITY_HIGH) + // ... + ; + +// Add the custom options to the sms message and send the message +$sms->options($options); + +$texter->send($sms); +``` + Resources --------- From 3f0cc24784b4c9a653ea605c1e76419962a553e7 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 23 May 2023 16:09:52 +0200 Subject: [PATCH 48/56] Change default branch in PR template --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 72ea7cfa3e9c0..f7b87a1ce8cd4 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,6 +1,6 @@ | Q | A | ------------- | --- -| Branch? | 6.3 for features / 5.4 or 6.2 for bug fixes +| Branch? | 6.4 for features / 5.4, 6.2, or 6.3 for bug fixes | Bug fix? | yes/no | New feature? | yes/no | Deprecations? | yes/no From 36064995e7d032ee2280e53d2e3ae50ebd245789 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 23 May 2023 18:36:03 +0200 Subject: [PATCH 49/56] Fix CI for experimental mode --- .github/workflows/unit-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 69192939d066a..3757f2523e928 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -65,7 +65,7 @@ jobs: echo COLUMNS=120 >> $GITHUB_ENV echo PHPUNIT="$(pwd)/phpunit --exclude-group tty,benchmark,intl-data,integration" >> $GITHUB_ENV - echo COMPOSER_UP='composer update --no-progress --ansi'$([[ "${{ matrix.php }}" = "8.2" ]] && echo ' --ignore-platform-req=php+') >> $GITHUB_ENV + echo COMPOSER_UP='composer update --no-progress --ansi'$([[ "${{ matrix.mode }}" = experimental ]] && echo ' --ignore-platform-req=php+') >> $GITHUB_ENV SYMFONY_VERSIONS=$(git ls-remote -q --heads | cut -f2 | grep -o '/[1-9][0-9]*\.[0-9].*' | sort -V) SYMFONY_VERSION=$(grep ' VERSION = ' src/Symfony/Component/HttpKernel/Kernel.php | cut -d "'" -f2 | cut -d '.' -f 1-2) From 23c6a35c00930ad387bd4a174abae0dc7e16841e Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 23 May 2023 20:34:37 +0200 Subject: [PATCH 50/56] Update github/workflows/scorecard --- .github/workflows/scorecards.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 87cd21cf0d442..b66e1b53b60f4 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -6,7 +6,7 @@ on: schedule: - cron: '34 4 * * 6' push: - branches: [ "6.3" ] + branches: [ "6.4" ] # Declare default permissions as read only. permissions: read-all From f09d08c34c72ac3d1469078653716a69e738da3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Wed, 24 May 2023 17:11:52 +0200 Subject: [PATCH 51/56] [Console] Fix PHP Doc of InputArgument --- src/Symfony/Component/Console/Input/InputArgument.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Console/Input/InputArgument.php b/src/Symfony/Component/Console/Input/InputArgument.php index ecfcdad58b907..8a64f7ac8a8e9 100644 --- a/src/Symfony/Component/Console/Input/InputArgument.php +++ b/src/Symfony/Component/Console/Input/InputArgument.php @@ -32,7 +32,7 @@ class InputArgument /** * @param string $name The argument name - * @param int|null $mode The argument mode: self::REQUIRED or self::OPTIONAL + * @param int|null $mode The argument mode: a bit mask of self::REQUIRED, self::OPTIONAL and self::IS_ARRAY * @param string $description A description text * @param string|bool|int|float|array|null $default The default value (for self::OPTIONAL mode only) * From 8ae36f3cdc57cce28c9cbef4e0541830cc79a2dc Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Thu, 25 May 2023 14:34:50 +0200 Subject: [PATCH 52/56] [Console] block input stream if needed When the input stream used in the question helper is not blocking, the default value is always used as the stream return false. In order to fix that, we force the stream to be in blocking state and go back to the old state after so other logic is not impacted by this change --- .../Component/Console/Helper/QuestionHelper.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Symfony/Component/Console/Helper/QuestionHelper.php b/src/Symfony/Component/Console/Helper/QuestionHelper.php index ec5b418f2cbe7..e236be92a3913 100644 --- a/src/Symfony/Component/Console/Helper/QuestionHelper.php +++ b/src/Symfony/Component/Console/Helper/QuestionHelper.php @@ -128,7 +128,18 @@ private function doAsk(OutputInterface $output, Question $question) } if (false === $ret) { + $isBlocked = stream_get_meta_data($inputStream)['blocked'] ?? true; + + if (!$isBlocked) { + stream_set_blocking($inputStream, true); + } + $ret = $this->readInput($inputStream, $question); + + if (!$isBlocked) { + stream_set_blocking($inputStream, false); + } + if (false === $ret) { throw new MissingInputException('Aborted.'); } From 044bd02a47160ef1a65842d4bc5d07eb5d093422 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 25 May 2023 15:05:00 +0200 Subject: [PATCH 53/56] [5.4] Allow PhpUnitBridge v7 --- composer.json | 4 ++-- src/Symfony/Bridge/Doctrine/composer.json | 1 - src/Symfony/Bundle/FrameworkBundle/composer.json | 1 - src/Symfony/Component/Form/composer.json | 1 - src/Symfony/Component/Validator/composer.json | 1 - src/Symfony/Component/VarDumper/composer.json | 1 - 6 files changed, 2 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index b291459895cb7..469c30715f775 100644 --- a/composer.json +++ b/composer.json @@ -143,7 +143,7 @@ "psr/simple-cache": "^1.0|^2.0", "egulias/email-validator": "^2.1.10|^3.1|^4", "symfony/mercure-bundle": "^0.3", - "symfony/phpunit-bridge": "^5.2|^6.0", + "symfony/phpunit-bridge": "^5.4|^6.0|^7.0", "symfony/runtime": "self.version", "symfony/security-acl": "~2.8|~3.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", @@ -161,7 +161,7 @@ "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", "ocramius/proxy-manager": "<2.1", - "phpunit/phpunit": "<5.4.3" + "phpunit/phpunit": "<7.5|9.1.2" }, "config": { "allow-plugins": { diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index a0dbbe9636adb..3aa2e352401ca 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -54,7 +54,6 @@ "doctrine/dbal": "<2.13.1", "doctrine/lexer": "<1.1", "doctrine/orm": "<2.7.4", - "phpunit/phpunit": "<5.4.3", "symfony/cache": "<5.4", "symfony/dependency-injection": "<4.4", "symfony/form": "<5.4.21|>=6,<6.2.7", diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 19da8ab3b66d6..320b993b1f8d1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -74,7 +74,6 @@ "doctrine/persistence": "<1.3", "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", - "phpunit/phpunit": "<5.4.3", "symfony/asset": "<5.3", "symfony/console": "<5.2.5", "symfony/dotenv": "<5.1", diff --git a/src/Symfony/Component/Form/composer.json b/src/Symfony/Component/Form/composer.json index 6b02d77e29dff..39babd350174e 100644 --- a/src/Symfony/Component/Form/composer.json +++ b/src/Symfony/Component/Form/composer.json @@ -44,7 +44,6 @@ "symfony/uid": "^5.1|^6.0" }, "conflict": { - "phpunit/phpunit": "<5.4.3", "symfony/console": "<4.4", "symfony/dependency-injection": "<4.4", "symfony/doctrine-bridge": "<5.4.21|>=6,<6.2.7", diff --git a/src/Symfony/Component/Validator/composer.json b/src/Symfony/Component/Validator/composer.json index 019a46fc15282..3e860daa30e30 100644 --- a/src/Symfony/Component/Validator/composer.json +++ b/src/Symfony/Component/Validator/composer.json @@ -49,7 +49,6 @@ "doctrine/annotations": "<1.13", "doctrine/cache": "<1.11", "doctrine/lexer": "<1.1", - "phpunit/phpunit": "<5.4.3", "symfony/dependency-injection": "<4.4", "symfony/expression-language": "<5.1", "symfony/http-kernel": "<4.4", diff --git a/src/Symfony/Component/VarDumper/composer.json b/src/Symfony/Component/VarDumper/composer.json index dc46f58d99eca..fc127d721ab16 100644 --- a/src/Symfony/Component/VarDumper/composer.json +++ b/src/Symfony/Component/VarDumper/composer.json @@ -28,7 +28,6 @@ "twig/twig": "^2.13|^3.0.4" }, "conflict": { - "phpunit/phpunit": "<5.4.3", "symfony/console": "<4.4" }, "suggest": { From abc5e151dd7aa61f06f16f4a96885c8cdb53caff Mon Sep 17 00:00:00 2001 From: aegypius Date: Fri, 26 May 2023 09:55:06 +0200 Subject: [PATCH 54/56] [SecurityBundle] Update security-1.0.xsd to include missing access-token definition --- .../Resources/config/schema/security-1.0.xsd | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/schema/security-1.0.xsd b/src/Symfony/Bundle/SecurityBundle/Resources/config/schema/security-1.0.xsd index 33140fdae8d11..af058365cc716 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/schema/security-1.0.xsd +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/schema/security-1.0.xsd @@ -140,6 +140,7 @@ + @@ -304,6 +305,17 @@ + + + + + + + + + + + From 50ff60f02ae3d87d58b6fcf0d28420e10a1d3354 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 27 May 2023 23:12:49 +0200 Subject: [PATCH 55/56] Update CHANGELOG for 6.2.11 --- CHANGELOG-6.2.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/CHANGELOG-6.2.md b/CHANGELOG-6.2.md index d685302315bcb..5a003a6343fe2 100644 --- a/CHANGELOG-6.2.md +++ b/CHANGELOG-6.2.md @@ -7,6 +7,42 @@ in 6.2 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v6.2.0...v6.2.1 +* 6.2.11 (2023-05-27) + + * bug #50442 [SecurityBundle] Update security-1.0.xsd to include missing access-token definition (aegypius) + * bug #50429 [Console] block input stream if needed (joelwurtz) + * bug #50312 [Security] Skip clearing CSRF Token on stateless logout (chalasr) + * bug #50315 [Translation] Fix handling of null messages in `ArrayLoader` (rob006) + * bug #50338 [Console] Remove ``exec`` and replace it by ``shell_exec`` (maxbeckers) + * bug #50193 [Serializer] Fix `SerializedPath` not working with constructor arguments (HypeMC) + * bug #50280 [PropertyAccess] Fix nullsafe operator on array index (HypeMC) + * bug #50362 [FrameworkBundle] Fix Workflow without a marking store definition uses marking store definition of previously defined workflow (krciga22) + * bug #50309 [HttpFoundation] UrlHelper is now aware of RequestContext changes (giosh94mhz) + * bug #50309 [HttpFoundation] UrlHelper is now aware of RequestContext changes (giosh94mhz) + * bug #50352 [Notifier][TurboSMS] Fix get sender name (ZhukV) + * bug #50354 [Process] Stop the process correctly even if underlying input stream is not closed (joelwurtz) + * bug #50332 [PropertyInfo] Fix `PhpStanExtractor` when constructor has no docblock (HypeMC) + * bug #50253 [FrameworkBundle] Generate caches consistently on successive run of `cache:clear` command (Okhoshi) + * bug #49063 [Messenger] Respect `isRetryable` decision of the retry strategy for re-delivery (FlyingDR) + * bug #50251 [Serializer] Handle datetime deserialization in U format (tugmaks) + * bug #50266 [HttpFoundation] Fix file streaming after connection aborted (rlshukhov) + * bug #50277 [Messenger] Add `IS_REPEATABLE` flag to `AsMessageHandler` attribute (adrianguenter) + * bug #50269 Fix param type annotation (l-vo) + * bug #50268 Allow resources in Query::setParam (l-vo) + * bug #50256 [HttpClient] Fix setting duplicate-name headers when redirecting with AmpHttpClient (nicolas-grekas) + * bug #50214 [WebProfilerBundle] Remove legacy filters remnants (MatTheCat) + * bug #50235 [HttpClient] Fix getting through proxies via CONNECT (nicolas-grekas) + * bug #50241 [HttpKernel] Prevent initialising lazy services during services reset (tucksaun) + * bug #50244 [HttpKernel] Fix restoring surrogate content from cache (nicolas-grekas) + * bug #50246 [DependencyInjection] Do not check errored definitions’ type (MatTheCat) + * bug #49557 [PropertyInfo] Fix phpDocExtractor nullable array value type (fabpot) + * bug #50213 [ErrorHandler] Prevent conflicts with WebProfilerBundle’s JavaScript (MatTheCat) + * bug #50192 [Serializer] backed enum throw notNormalizableValueException outside construct method (alli83) + * bug #50238 [HttpKernel] Don't use eval() to render ESI/SSI (nicolas-grekas) + * bug #50226 [HttpClient] Ensure HttplugClient ignores invalid HTTP headers (nicolas-grekas) + * bug #50203 [Messenger] Fix registering message handlers (nicolas-grekas) + * bug #50204 [ErrorHandler] Skip Httplug deprecations for HttplugClient (nicolas-grekas) + * 6.2.10 (2023-04-28) * bug #50143 [Console] trim(): Argument #1 () must be of type string, bool given (danepowell) From 3ce0d07a69d90719a542af98065e2d83df4631b8 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 27 May 2023 23:12:52 +0200 Subject: [PATCH 56/56] Update VERSION for 6.2.11 --- src/Symfony/Component/HttpKernel/Kernel.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index c0dd3e646d336..1ad714e6d98a9 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -75,12 +75,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl */ private static array $freshCache = []; - public const VERSION = '6.2.11-DEV'; + public const VERSION = '6.2.11'; public const VERSION_ID = 60211; public const MAJOR_VERSION = 6; public const MINOR_VERSION = 2; public const RELEASE_VERSION = 11; - public const EXTRA_VERSION = 'DEV'; + public const EXTRA_VERSION = ''; public const END_OF_MAINTENANCE = '07/2023'; public const END_OF_LIFE = '07/2023';
    LocaleLocaleFallback localeDomainDomain Times used Message ID Message Preview
    {{ message.locale }}{{ message.fallbackLocale|default('-') }}