8000 From daf7c767771968bb6790a2175b3bed8a99df2ce5 Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Tue, 21 Mar 2023 18:29:25 +0100 Subject: [PATCH 01/48] [Serializer] Add withSaveOptions to XmlEncoderContextBuilder --- .../Context/Encoder/XmlEncoderContextBuilder.php | 12 ++++++++++++ .../Context/Encoder/XmlEncoderContextBuilderTest.php | 3 +++ 2 files changed, 15 insertions(+) diff --git a/src/Symfony/Component/Serializer/Context/Encoder/XmlEncoderContextBuilder.php b/src/Symfony/Component/Serializer/Context/Encoder/XmlEncoderContextBuilder.php index 3f8e92f4e21c9..78617a2bbc816 100644 --- a/src/Symfony/Component/Serializer/Context/Encoder/XmlEncoderContextBuilder.php +++ b/src/Symfony/Component/Serializer/Context/Encoder/XmlEncoderContextBuilder.php @@ -89,6 +89,18 @@ public function withLoadOptions(?int $loadOptions): static return $this->with(XmlEncoder::LOAD_OPTIONS, $loadOptions); } + /** + * Configures the DOMDocument::saveXml options bitmask. + * + * @see https://www.php.net/manual/en/libxml.constants.php + * + * @param positive-int|null $saveOptions + */ + public function withSaveOptions(?int $saveOptions): static + { + return $this->with(XmlEncoder::SAVE_OPTIONS, $saveOptions); + } + /** * Configures whether to keep empty nodes. */ diff --git a/src/Symfony/Component/Serializer/Tests/Context/Encoder/XmlEncoderContextBuilderTest.php b/src/Symfony/Component/Serializer/Tests/Context/Encoder/XmlEncoderContextBuilderTest.php index c730695d81c95..1701733a89402 100644 --- a/src/Symfony/Component/Serializer/Tests/Context/Encoder/XmlEncoderContextBuilderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Context/Encoder/XmlEncoderContextBuilderTest.php @@ -41,6 +41,7 @@ public function testWithers(array $values) ->withEncoding($values[XmlEncoder::ENCODING]) ->withFormatOutput($values[XmlEncoder::FORMAT_OUTPUT]) ->withLoadOptions($values[XmlEncoder::LOAD_OPTIONS]) + ->withSaveOptions($values[XmlEncoder::SAVE_OPTIONS]) ->withRemoveEmptyTags($values[XmlEncoder::REMOVE_EMPTY_TAGS]) ->withRootNodeName($values[XmlEncoder::ROOT_NODE_NAME]) ->withStandalone($values[XmlEncoder::STANDALONE]) @@ -63,6 +64,7 @@ public static function withersDataProvider(): iterable XmlEncoder::ENCODING => 'UTF-8', XmlEncoder::FORMAT_OUTPUT => false, XmlEncoder::LOAD_OPTIONS => \LIBXML_COMPACT, + XmlEncoder::SAVE_OPTIONS => \LIBXML_NOERROR, XmlEncoder::REMOVE_EMPTY_TAGS => true, XmlEncoder::ROOT_NODE_NAME => 'root', XmlEncoder::STANDALONE => false, @@ -77,6 +79,7 @@ public static function withersDataProvider(): iterable XmlEncoder::ENCODING => null, XmlEncoder::FORMAT_OUTPUT => null, XmlEncoder::LOAD_OPTIONS => null, + XmlEncoder::SAVE_OPTIONS => null, XmlEncoder::REMOVE_EMPTY_TAGS => null, XmlEncoder::ROOT_NODE_NAME => null, XmlEncoder::STANDALONE => null, From 6d5400a5d9ee0c20f4439f7161a81eed58add375 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 28 Feb 2023 14:26:05 +0100 Subject: [PATCH 02/48] [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/48] 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/48] 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 all 8000 ows 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/48] 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/48] 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 707245c2cf7576999e553d78fa9a512f5ddd1b68 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 1 May 2023 09:20:33 +0200 Subject: [PATCH 07/48] Bump Symfony version to 6.3.0 --- 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 c88671d8b4d13..f9a969b659130 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -76,12 +76,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl */ private static array $freshCache = []; - public const VERSION = '6.3.0-BETA1'; + public const VERSION = '6.3.0-DEV'; public const VERSION_ID = 60300; public const MAJOR_VERSION = 6; public const MINOR_VERSION = 3; public const RELEASE_VERSION = 0; - public const EXTRA_VERSION = 'BETA1'; + public const EXTRA_VERSION = 'DEV'; public const END_OF_MAINTENANCE = '01/2024'; public const END_OF_LIFE = '01/2024'; From 12fc94d98f4ee9a49770f7ca3deaa06b470a4746 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 26 Apr 2023 12:12:51 +0200 Subject: [PATCH 08/48] Fix registering traceable voters, argument resolvers and normalizers --- .../Compiler/AddSecurityVotersPass.php | 2 +- .../Compiler/AddSecurityVotersPassTest.php | 4 +- .../ControllerArgumentValueResolverPass.php | 26 +++++----- ...ontrollerArgumentValueResolverPassTest.php | 12 ++--- .../DependencyInjection/SerializerPass.php | 47 ++++++++----------- .../SerializerPassTest.php | 10 ++-- 6 files changed, 46 insertions(+), 55 deletions(-) diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSecurityVotersPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSecurityVotersPass.php index ccf474087af5d..8a2bad79a140c 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSecurityVotersPass.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSecurityVotersPass.php @@ -56,7 +56,7 @@ public function process(ContainerBuilder $container) } if ($debug) { - $voterServices[] = new Reference($debugVoterServiceId = 'debug.security.voter.'.$voterServiceId); + $voterServices[] = new Reference($debugVoterServiceId = '.debug.security.voter.'.$voterServiceId); $container ->register($debugVoterServiceId, TraceableVoter::class) diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSecurityVotersPassTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSecurityVotersPassTest.php index 62e1c9cfcf721..b4c2009584f5e 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSecurityVotersPassTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSecurityVotersPassTest.php @@ -91,11 +91,11 @@ public function testThatVotersAreTraceableInDebugMode() $compilerPass = new AddSecurityVotersPass(); $compilerPass->process($container); - $def1 = $container->getDefinition('debug.security.voter.voter1'); + $def1 = $container->getDefinition('.debug.security.voter.voter1'); $this->assertNull($def1->getDecoratedService(), 'voter1: should not be decorated'); $this->assertEquals(new Reference('voter1'), $def1->getArgument(0), 'voter1: wrong argument'); - $def2 = $container->getDefinition('debug.security.voter.voter2'); + $def2 = $container->getDefinition('.debug.security.voter.voter2'); $this->assertNull($def2->getDecoratedService(), 'voter2: should not be decorated'); $this->assertEquals(new Reference('voter2'), $def2->getArgument(0), 'voter2: wrong argument'); diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/ControllerArgumentValueResolverPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/ControllerArgumentValueResolverPass.php index dff3e248aee1a..d3b157418ebc6 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/ControllerArgumentValueResolverPass.php +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/ControllerArgumentValueResolverPass.php @@ -43,30 +43,30 @@ public function process(ContainerBuilder $container) $namedResolvers = $this->findAndSortTaggedServices(new TaggedIteratorArgument('controller.targeted_value_resolver', 'name', needsIndexes: true), $container); $resolvers = $this->findAndSortTaggedServices(new TaggedIteratorArgument('controller.argument_value_resolver', 'name', needsIndexes: true), $container); - foreach ($resolvers as $name => $resolverReference) { - $id = (string) $resolverReference; - - if ($definitions[$id]->hasTag('controller.targeted_value_resolver')) { + foreach ($resolvers as $name => $resolver) { + if ($definitions[(string) $resolver]->hasTag('controller.targeted_value_resolver')) { unset($resolvers[$name]); } else { - $namedResolvers[$name] ??= clone $resolverReference; + $namedResolvers[$name] ??= clone $resolver; } } - $resolvers = array_values($resolvers); - if ($container->getParameter('kernel.debug') && class_exists(Stopwatch::class) && $container->has('debug.stopwatch')) { - foreach ($resolvers + $namedResolvers as $resolverReference) { - $id = (string) $resolverReference; - $container->register("debug.$id", TraceableValueResolver::class) - ->setDecoratedService($id) - ->setArguments([new Reference("debug.$id.inner"), new Reference('debug.stopwatch')]); + foreach ($resolvers as $name => $resolver) { + $resolvers[$name] = new Reference('.debug.value_resolver.'.$resolver); + $container->register('.debug.value_resolver.'.$resolver, TraceableValueResolver::class) + ->setArguments([$resolver, new Reference('debug.stopwatch')]); + } + foreach ($namedResolvers as $name => $resolver) { + $namedResolvers[$name] = new Reference('.debug.value_resolver.'.$resolver); + $container->register('.debug.value_resolver.'.$resolver, TraceableValueResolver::class) + ->setArguments([$resolver, new Reference('debug.stopwatch')]); } } $container ->getDefinition('argument_resolver') - ->replaceArgument(1, new IteratorArgument($resolvers)) + ->replaceArgument(1, new IteratorArgument(array_values($resolvers))) ->setArgument(2, new ServiceLocatorArgument($namedResolvers)) ; } diff --git a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ControllerArgumentValueResolverPassTest.php b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ControllerArgumentValueResolverPassTest.php index c95a7fb52468c..3c2d6738f5d1e 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ControllerArgumentValueResolverPassTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ControllerArgumentValueResolverPassTest.php @@ -62,9 +62,9 @@ public function testInDebugWithStopWatchDefinition() ]; $expected = [ - new Reference('n1'), - new Reference('n2'), - new Reference('n3'), + new Reference('.debug.value_resolver.n1'), + new Reference('.debug.value_resolver.n2'), + new Reference('.debug.value_resolver.n3'), ]; $definition = new Definition(ArgumentResolver::class, [null, []]); @@ -81,9 +81,9 @@ public function testInDebugWithStopWatchDefinition() (new ControllerArgumentValueResolverPass())->process($container); $this->assertEquals($expected, $definition->getArgument(1)->getValues()); - $this->assertTrue($container->hasDefinition('debug.n1')); - $this->assertTrue($container->hasDefinition('debug.n2')); - $this->assertTrue($container->hasDefinition('debug.n3')); + $this->assertTrue($container->hasDefinition('.debug.value_resolver.n1')); + $this->assertTrue($container->hasDefinition('.debug.value_resolver.n2')); + $this->assertTrue($container->hasDefinition('.debug.value_resolver.n3')); $this->assertTrue($container->hasDefinition('n1')); $this->assertTrue($container->hasDefinition('n2')); diff --git a/src/Symfony/Component/Serializer/DependencyInjection/SerializerPass.php b/src/Symfony/Component/Serializer/DependencyInjection/SerializerPass.php index 71376d496559c..d0b0deb48cf6d 100644 --- a/src/Symfony/Component/Serializer/DependencyInjection/SerializerPass.php +++ b/src/Symfony/Component/Serializer/DependencyInjection/SerializerPass.php @@ -40,45 +40,38 @@ public function process(ContainerBuilder $container) return; } - if (!$normalizers = $container->findTaggedServiceIds('serializer.normalizer')) { + if (!$normalizers = $this->findAndSortTaggedServices('serializer.normalizer', $container)) { throw new RuntimeException('You must tag at least one service as "serializer.normalizer" to use the "serializer" service.'); } - if ($container->getParameter('kernel.debug') && $container->hasDefinition('serializer.data_collector')) { - foreach (array_keys($normalizers) as $normalizer) { - $container->register('debug.'.$normalizer, TraceableNormalizer::class) - ->setDecoratedService($normalizer) - ->setArguments([new Reference('debug.'.$normalizer.'.inner'), new Reference('serializer.data_collector')]); - } + if (!$encoders = $this->findAndSortTaggedServices('serializer.encoder', $container)) { + throw new RuntimeException('You must tag at least one service as "serializer.encoder" to use the "serializer" service.'); } - $serializerDefinition = $container->getDefinition('serializer'); - $serializerDefinition->replaceArgument(0, $this->findAndSortTaggedServices('serializer.normalizer', $container)); + if ($container->hasParameter('serializer.default_context')) { + $defaultContext = $container->getParameter('serializer.default_context'); + foreach (array_merge($normalizers, $encoders) as $service) { + $definition = $container->getDefinition($service); + $definition->setBindings(['array $defaultContext' => new BoundArgument($defaultContext, false)] + $definition->getBindings()); + } - if (!$encoders = $container->findTaggedServiceIds('serializer.encoder')) { - throw new RuntimeException('You must tag at least one service as "serializer.encoder" to use the "serializer" service.'); + $container->getParameterBag()->remove('serializer.default_context'); } if ($container->getParameter('kernel.debug') && $container->hasDefinition('serializer.data_collector')) { - foreach (array_keys($encoders) as $encoder) { - $container->register('debug.'.$encoder, TraceableEncoder::class) - ->setDecoratedService($encoder) - ->setArguments([new Reference('debug.'.$encoder.'.inner'), new Reference('serializer.data_collector')]); + foreach ($normalizers as $i => $normalizer) { + $normalizers[$i] = $container->register('.debug.serializer.normalizer.'.$normalizer, TraceableNormalizer::class) + ->setArguments([$normalizer, new Reference('serializer.data_collector')]); } - } - $serializerDefinition->replaceArgument(1, $this->findAndSortTaggedServices('serializer.encoder', $container)); - - if (!$container->hasParameter('serializer.default_context')) { - return; - } - - $defaultContext = $container->getParameter('serializer.default_context'); - foreach (array_keys(array_merge($container->findTaggedServiceIds('serializer.normalizer'), $container->findTaggedServiceIds('serializer.encoder'))) as $service) { - $definition = $container->getDefinition($service); - $definition->setBindings(['array $defaultContext' => new BoundArgument($defaultContext, false)] + $definition->getBindings()); + foreach ($encoders as $i => $encoder) { + $encoders[$i] = $container->register('.debug.serializer.encoder.'.$encoder, TraceableEncoder::class) + ->setArguments([$encoder, new Reference('serializer.data_collector')]); + } } - $container->getParameterBag()->remove('serializer.default_context'); + $serializerDefinition = $container->getDefinition('serializer'); + $serializerDefinition->replaceArgument(0, $normalizers); + $serializerDefinition->replaceArgument(1, $encoders); } } diff --git a/src/Symfony/Component/Serializer/Tests/DependencyInjection/SerializerPassTest.php b/src/Symfony/Component/Serializer/Tests/DependencyInjection/SerializerPassTest.php index abb1ade964bc9..eb77263f49fc9 100644 --- a/src/Symfony/Component/Serializer/Tests/DependencyInjection/SerializerPassTest.php +++ b/src/Symfony/Component/Serializer/Tests/DependencyInjection/SerializerPassTest.php @@ -104,17 +104,15 @@ public function testNormalizersAndEncodersAreDecoredAndOrderedWhenCollectingData $serializerPass = new SerializerPass(); $serializerPass->process($container); - $traceableNormalizerDefinition = $container->getDefinition('debug.n'); - $traceableEncoderDefinition = $container->getDefinition('debug.e'); + $traceableNormalizerDefinition = $container->getDefinition('.debug.serializer.normalizer.n'); + $traceableEncoderDefinition = $container->getDefinition('.debug.serializer.encoder.e'); $this->assertEquals(TraceableNormalizer::class, $traceableNormalizerDefinition->getClass()); - $this->assertEquals(['n', null, 0], $traceableNormalizerDefinition->getDecoratedService()); - $this->assertEquals(new Reference('debug.n.inner'), $traceableNormalizerDefinition->getArgument(0)); + $this->assertEquals(new Reference('n'), $traceableNormalizerDefinition->getArgument(0)); $this->assertEquals(new Reference('serializer.data_collector'), $traceableNormalizerDefinition->getArgument(1)); $this->assertEquals(TraceableEncoder::class, $traceableEncoderDefinition->getClass()); - $this->assertEquals(['e', null, 0], $traceableEncoderDefinition->getDecoratedService()); - $this->assertEquals(new Reference('debug.e.inner'), $traceableEncoderDefinition->getArgument(0)); + $this->assertEquals(new Reference('e'), $traceableEncoderDefinition->getArgument(0)); $this->assertEquals(new Reference('serializer.data_collector'), $traceableEncoderDefinition->getArgument(1)); } } From dfd7a5ba7736f0000d3ea171a56fb57cd98f7ae9 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 2 May 2023 12:33:25 +0200 Subject: [PATCH 09/48] [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 10/48] [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 4cc0a8c387f4bd4904b488ca1d4f4df8d8bb585b Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 2 May 2023 06:59:18 -0400 Subject: [PATCH 11/48] [AssetMapper] Fix import map package parsing with an @ namespace --- .../ImportMap/ImportMapManager.php | 4 +-- .../Tests/ImportMap/ImportMapManagerTest.php | 34 +++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/AssetMapper/ImportMap/ImportMapManager.php b/src/Symfony/Component/AssetMapper/ImportMap/ImportMapManager.php index 1dbbf34d28d92..278d81e41c870 100644 --- a/src/Symfony/Component/AssetMapper/ImportMap/ImportMapManager.php +++ b/src/Symfony/Component/AssetMapper/ImportMap/ImportMapManager.php @@ -111,8 +111,8 @@ public function update(): array */ public static function parsePackageName(string $packageName): ?array { - // https://regex101.com/r/58bl9L/1 - $regex = '/(?:(?P[^:\n]+):)?(?P[^@\n]+)(?:@(?P[^\s\n]+))?/'; + // https://regex101.com/r/d99BEc/1 + $regex = '/(?:(?P[^:\n]+):)?((?P@?[^@\n]+))(?:@(?P[^\s\n]+))?/'; return preg_match($regex, $packageName, $matches) ? $matches : null; } diff --git a/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapManagerTest.php b/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapManagerTest.php index 4c75084c8681d..857d15e77d8d9 100644 --- a/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapManagerTest.php +++ b/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapManagerTest.php @@ -422,6 +422,40 @@ public static function getPackageNameTests(): iterable 'version' => '^1.2.3', ], ]; + + yield 'namespaced_package_simple' => [ + '@hotwired/stimulus', + [ + 'package' => '@hotwired/stimulus', + 'registry' => '', + ], + ]; + + yield 'namespaced_package_with_version_constraint' => [ + '@hotwired/stimulus@^1.2.3', + [ + 'package' => '@hotwired/stimulus', + 'registry' => '', + 'version' => '^1.2.3', + ], + ]; + + yield 'namespaced_package_with_registry' => [ + 'npm:@hotwired/stimulus', + [ + 'package' => '@hotwired/stimulus', + 'registry' => 'npm', + ], + ]; + + yield 'namespaced_package_with_registry_and_version' => [ + 'npm:@hotwired/stimulus@^1.2.3', + [ + 'package' => '@hotwired/stimulus', + 'registry' => 'npm', + 'version' => '^1.2.3', + ], + ]; } private function createImportMapManager(array $dirs, string $rootDir, string $publicPrefix = '/assets/', string $publicDirName = 'public'): ImportMapManager From 7583301e3287d11b19dd1b48caa3530e093e7a36 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 2 May 2023 13:54:56 +0200 Subject: [PATCH 12/48] [AssetMapper] CS fix --- .../Compiler/AssetCompilerInterface.php | 1 - .../Compiler/JavaScriptImportPathCompiler.php | 3 +- .../Compiler/SourceMappingUrlsCompiler.php | 1 - .../ImportMap/ImportMapManager.php | 13 +++--- .../ImportMap/ImportMapRenderer.php | 5 ++- .../Component/AssetMapper/MappedAsset.php | 6 +-- .../Tests/AssetMapperRepositoryTest.php | 8 ++-- .../AssetsMapperCompileCommandTest.php | 1 - .../JavaScriptImportPathCompilerTest.php | 8 ++-- .../Tests/ImportMap/ImportMapManagerTest.php | 45 +++++++++---------- .../Tests/ImportMap/ImportMapRendererTest.php | 9 ++-- .../Tests/MapperAwareAssetPackageTest.php | 1 - .../Tests/fixtures/importmaps/importmap.php | 11 ++++- 13 files changed, 60 insertions(+), 52 deletions(-) diff --git a/src/Symfony/Component/AssetMapper/Compiler/AssetCompilerInterface.php b/src/Symfony/Component/AssetMapper/Compiler/AssetCompilerInterface.php index 247161dffa329..52dfe0e880460 100644 --- a/src/Symfony/Component/AssetMapper/Compiler/AssetCompilerInterface.php +++ b/src/Symfony/Component/AssetMapper/Compiler/AssetCompilerInterface.php @@ -11,7 +11,6 @@ namespace Symfony\Component\AssetMapper\Compiler; -use Symfony\Component\AssetMapper\AssetMapper; use Symfony\Component\AssetMapper\AssetMapperInterface; use Symfony\Component\AssetMapper\MappedAsset; diff --git a/src/Symfony/Component/AssetMapper/Compiler/JavaScriptImportPathCompiler.php b/src/Symfony/Component/AssetMapper/Compiler/JavaScriptImportPathCompiler.php index f252bb7bb9abe..ddb82c77d59cf 100644 --- a/src/Symfony/Component/AssetMapper/Compiler/JavaScriptImportPathCompiler.php +++ b/src/Symfony/Component/AssetMapper/Compiler/JavaScriptImportPathCompiler.php @@ -11,7 +11,6 @@ namespace Symfony\Component\AssetMapper\Compiler; -use Symfony\Component\AssetMapper\AssetMapper; use Symfony\Component\AssetMapper\AssetMapperInterface; use Symfony\Component\AssetMapper\MappedAsset; @@ -64,6 +63,6 @@ public function compile(string $content, MappedAsset $asset, AssetMapperInterfac public function supports(MappedAsset $asset): bool { - return 'application/javascript' === $asset->getMimeType() || 'text/javascript' === $asset->getMimeType(); + return 'application/javascript' === $asset->getMimeType() || 'text/javascript' === $asset->getMimeType(); } } diff --git a/src/Symfony/Component/AssetMapper/Compiler/SourceMappingUrlsCompiler.php b/src/Symfony/Component/AssetMapper/Compiler/SourceMappingUrlsCompiler.php index 31dab5a79c7e2..643cf233045bf 100644 --- a/src/Symfony/Component/AssetMapper/Compiler/SourceMappingUrlsCompiler.php +++ b/src/Symfony/Component/AssetMapper/Compiler/SourceMappingUrlsCompiler.php @@ -11,7 +11,6 @@ namespace Symfony\Component\AssetMapper\Compiler; -use Symfony\Component\AssetMapper\AssetMapper; use Symfony\Component\AssetMapper\AssetMapperInterface; use Symfony\Component\AssetMapper\MappedAsset; diff --git a/src/Symfony/Component/AssetMapper/ImportMap/ImportMapManager.php b/src/Symfony/Component/AssetMapper/ImportMap/ImportMapManager.php index 278d81e41c870..27b5dab82b103 100644 --- a/src/Symfony/Component/AssetMapper/ImportMap/ImportMapManager.php +++ b/src/Symfony/Component/AssetMapper/ImportMap/ImportMapManager.php @@ -22,6 +22,7 @@ * * @author Kévin Dunglas * @author Ryan Weaver + * * @final */ class ImportMapManager @@ -81,6 +82,7 @@ public function getImportMapJson(): string * Adds or updates packages. * * @param PackageRequireOptions[] $packages + * * @return ImportMapEntry[] */ public function require(array $packages): array @@ -143,7 +145,8 @@ private function buildImportMapJson(): void /** * @param PackageRequireOptions[] $packagesToRequire - * @param string[] $packagesToRemove + * @param string[] $packagesToRemove + * * @return ImportMapEntry[] */ private fun 8000 ction updateImportMapConfig(bool $update, array $packagesToRequire, array $packagesToRemove): array @@ -202,7 +205,7 @@ private function updateImportMapConfig(bool $update, array $packagesToRequire, a * * Returns an array of the entries that were added. * - * @param PackageRequireOptions[] $packagesToRequire + * @param PackageRequireOptions[] $packagesToRequire * @param array $importMapEntries */ private function requirePackages(array $packagesToRequire, array &$importMapEntries): array @@ -216,7 +219,7 @@ private function requirePackages(array $packagesToRequire, array &$importMapEntr foreach ($packagesToRequire as $requireOptions) { $constraint = $requireOptions->packageName; if (null !== $requireOptions->versionConstraint) { - $constraint .= '@' . $requireOptions->versionConstraint; + $constraint .= '@'.$requireOptions->versionConstraint; } if (null !== $requireOptions->registryName) { $constraint = sprintf('%s:%s', $requireOptions->registryName, $constraint); @@ -243,7 +246,7 @@ private function requirePackages(array $packagesToRequire, array &$importMapEntr $data = $response->toArray(false); if (isset($data['error'])) { - throw new \RuntimeException(sprintf('Error requiring JavaScript package: "%s"', $data['error'])); + throw new \RuntimeException('Error requiring JavaScript package: '.$data['error']); } // Throws the original HttpClient exception @@ -251,7 +254,7 @@ private function requirePackages(array $packagesToRequire, array &$importMapEntr } // if we're requiring just one package, in case it has any peer deps, match the preload - $defaultPreload = 1 === count($packagesToRequire) ? $packagesToRequire[0]->preload : false; + $defaultPreload = 1 === \count($packagesToRequire) ? $packagesToRequire[0]->preload : false; $addedEntries = []; foreach ($response->toArray()['map']['imports'] as $packageName => $url) { diff --git a/src/Symfony/Component/AssetMapper/ImportMap/ImportMapRenderer.php b/src/Symfony/Component/AssetMapper/ImportMap/ImportMapRenderer.php index 08c75f52771ba..7a5dc43001001 100644 --- a/src/Symfony/Component/AssetMapper/ImportMap/ImportMapRenderer.php +++ b/src/Symfony/Component/AssetMapper/ImportMap/ImportMapRenderer.php @@ -16,6 +16,7 @@ * * @author Kévin Dunglas * @author Ryan Weaver + * * @final */ class ImportMapRenderer @@ -28,7 +29,7 @@ public function __construct( ) { } - public function render(?string $entryPoint = null): string + public function render(string $entryPoint = null): string { $attributeString = ''; @@ -69,7 +70,7 @@ public function render(?string $entryPoint = null): string } if (null !== $entryPoint) { - $output .= "\n"; + $output .= "\n"; } return $output; diff --git a/src/Symfony/Component/AssetMapper/MappedAsset.php b/src/Symfony/Component/AssetMapper/MappedAsset.php index 7d09bd3acb6f0..966dcfe8267b8 100644 --- a/src/Symfony/Component/AssetMapper/MappedAsset.php +++ b/src/Symfony/Component/AssetMapper/MappedAsset.php @@ -22,14 +22,14 @@ final class MappedAsset { public string $publicPath; /** - * @var string The filesystem path to the source file. + * @var string the filesystem path to the source file */ private string $sourcePath; private string $content; private string $digest; private bool $isPredigested; private ?string $mimeType; - /** @var AssetDependency[] */ + /** @var AssetDependency[] */ private array $dependencies = []; public function __construct(public readonly string $logicalPath) @@ -125,7 +125,7 @@ public function setContent(string $content): void $this->content = $content; } - public function addDependency(MappedAsset $asset, bool $isLazy = false): void + public function addDependency(self $asset, bool $isLazy = false): void { $this->dependencies[] = new AssetDependency($asset, $isLazy); } diff --git a/src/Symfony/Component/AssetMapper/Tests/AssetMapperRepositoryTest.php b/src/Symfony/Component/AssetMapper/Tests/AssetMapperRepositoryTest.php index f2110f1e2a2a6..3118c9c812646 100644 --- a/src/Symfony/Component/AssetMapper/Tests/AssetMapperRepositoryTest.php +++ b/src/Symfony/Component/AssetMapper/Tests/AssetMapperRepositoryTest.php @@ -83,8 +83,8 @@ public function testAll() 'already-abcdefVWXYZ0123456789.digested.css' => __DIR__.'/fixtures/dir2/already-abcdefVWXYZ0123456789.digested.css', 'file3.css' => __DIR__.'/fixtures/dir2/file3.css', 'file4.js' => __DIR__.'/fixtures/dir2/file4.js', - 'subdir'.DIRECTORY_SEPARATOR.'file5.js' => __DIR__.'/fixtures/dir2/subdir/file5.js', - 'subdir'.DIRECTORY_SEPARATOR.'file6.js' => __DIR__.'/fixtures/dir2/subdir/file6.js', + 'subdir'.\DIRECTORY_SEPARATOR.'file5.js' => __DIR__.'/fixtures/dir2/subdir/file5.js', + 'subdir'.\DIRECTORY_SEPARATOR.'file6.js' => __DIR__.'/fixtures/dir2/subdir/file6.js', 'test.gif.foo' => __DIR__.'/fixtures/dir3/test.gif.foo', ]); $this->assertEquals($expectedAllAssets, array_map('realpath', $actualAllAssets)); @@ -111,13 +111,13 @@ public function testAllWithNamespaces() $normalizedExpectedAllAssets = []; foreach ($expectedAllAssets as $key => $val) { - $normalizedExpectedAllAssets[str_replace('/', DIRECTORY_SEPARATOR, $key)] = realpath($val); + $normalizedExpectedAllAssets[str_replace('/', \DIRECTORY_SEPARATOR, $key)] = realpath($val); } $actualAssets = $repository->all(); $normalizedActualAssets = []; foreach ($actualAssets as $key => $val) { - $normalizedActualAssets[str_replace('/', DIRECTORY_SEPARATOR, $key)] = realpath($val); + $normalizedActualAssets[str_replace('/', \DIRECTORY_SEPARATOR, $key)] = realpath($val); } $this->assertEquals($normalizedExpectedAllAssets, $normalizedActualAssets); diff --git a/src/Symfony/Component/AssetMapper/Tests/Command/AssetsMapperCompileCommandTest.php b/src/Symfony/Component/AssetMapper/Tests/Command/AssetsMapperCompileCommandTest.php index 3bbaac82d723e..ea02d86491d29 100644 --- a/src/Symfony/Component/AssetMapper/Tests/Command/AssetsMapperCompileCommandTest.php +++ b/src/Symfony/Component/AssetMapper/Tests/Command/AssetsMapperCompileCommandTest.php @@ -13,7 +13,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\Console\Application; -use Symfony\Bundle\FrameworkBundle\Tests\Command\AssetsMapperCompileCommand\Fixture\TestAppKernel; use Symfony\Component\AssetMapper\Tests\fixtures\AssetMapperTestAppKernel; use Symfony\Component\Console\Tester\CommandTester; use Symfony\Component\Filesystem\Filesystem; diff --git a/src/Symfony/Component/AssetMapper/Tests/Compiler/JavaScriptImportPathCompilerTest.php b/src/Symfony/Component/AssetMapper/Tests/Compiler/JavaScriptImportPathCompilerTest.php index da34a4ef5a8ec..d1d2fffdb8764 100644 --- a/src/Symfony/Component/AssetMapper/Tests/Compiler/JavaScriptImportPathCompilerTest.php +++ b/src/Symfony/Component/AssetMapper/Tests/Compiler/JavaScriptImportPathCompilerTest.php @@ -40,7 +40,7 @@ public static function provideCompileTests(): iterable yield 'dynamic_simple_double_quotes' => [ 'sourceLogicalName' => 'app.js', 'input' => 'import("./other.js");', - 'expectedDependencies' => ['other.js' => true] + 'expectedDependencies' => ['other.js' => true], ]; yield 'dynamic_simple_multiline' => [ @@ -50,19 +50,19 @@ public static function provideCompileTests(): iterable import("./other.js"); EOF , - 'expectedDependencies' => ['other.js' => true] + 'expectedDependencies' => ['other.js' => true], ]; yield 'dynamic_simple_single_quotes' => [ 'sourceLogicalName' => 'app.js', 'input' => 'import(\'./other.js\');', - 'expectedDependencies' => ['other.js' => true] + 'expectedDependencies' => ['other.js' => true], ]; yield 'dynamic_simple_tick_quotes' => [ 'sourceLogicalName' => 'app.js', 'input' => 'import(`./other.js`);', - 'expectedDependencies' => ['other.js' => true] + 'expectedDependencies' => ['other.js' => true], ]; yield 'dynamic_resolves_multiple' => [ diff --git a/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapManagerTest.php b/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapManagerTest.php index 857d15e77d8d9..715c86edd0692 100644 --- a/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapManagerTest.php +++ b/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapManagerTest.php @@ -27,25 +27,24 @@ class ImportMapManagerTest extends TestCase private MockHttpClient $httpClient; private Filesystem $filesystem; - protected function setUp(): void { $this->filesystem = new Filesystem(); - if (!file_exists(__DIR__ . '/../fixtures/importmaps_for_writing')) { - $this->filesystem->mkdir(__DIR__ . '/../fixtures/importmaps_for_writing'); + if (!file_exists(__DIR__.'/../fixtures/importmaps_for_writing')) { + $this->filesystem->mkdir(__DIR__.'/../fixtures/importmaps_for_writing'); } } protected function tearDown(): void { - $this->filesystem->remove(__DIR__ . '/../fixtures/importmaps_for_writing'); + $this->filesystem->remove(__DIR__.'/../fixtures/importmaps_for_writing'); } public function testGetModulesToPreload() { $manager = $this->createImportMapManager( ['assets' => '', 'assets2' => 'namespaced_assets2'], - __DIR__ . '/../fixtures/importmaps/' + __DIR__.'/../fixtures/importmaps/' ); $this->assertEquals([ 'https://unpkg.com/@hotwired/stimulus@3.2.1/dist/stimulus.js', @@ -60,7 +59,7 @@ public function testGetImportMapJson() { $manager = $this->createImportMapManager( ['assets' => '', 'assets2' => 'namespaced_assets2'], - __DIR__ . '/../fixtures/importmaps/' + __DIR__.'/../fixtures/importmaps/' ); $this->assertEquals(['imports' => [ '@hotwired/stimulus' => 'https://unpkg.com/@hotwired/stimulus@3.2.1/dist/stimulus.js', @@ -77,7 +76,7 @@ public function testGetImportMapJsonUsesDumpedFile() { $manager = $this->createImportMapManager( ['assets' => ''], - __DIR__ . '/../fixtures/', + __DIR__.'/../fixtures/', '/final-assets', 'test_public' ); @@ -92,7 +91,7 @@ public function testGetImportMapJsonUsesDumpedFile() */ public function testRequire(array $packages, array $expectedInstallRequest, array $responseMap, array $expectedImportMap, array $expectedDownloadedFiles) { - $rootDir = __DIR__ . '/../fixtures/importmaps_for_writing'; + $rootDir = __DIR__.'/../fixtures/importmaps_for_writing'; $manager = $this->createImportMapManager(['assets' => ''], $rootDir); $expectedRequestBody = [ @@ -120,11 +119,11 @@ public function testRequire(array $packages, array $expectedInstallRequest, arra $this->httpClient->setResponseFactory($responses); $manager->require($packages); - $actualImportMap = require($rootDir.'/importmap.php'); + $actualImportMap = require $rootDir.'/importmap.php'; $this->assertEquals($expectedImportMap, $actualImportMap); foreach ($expectedDownloadedFiles as $file) { - $this->assertFileExists($rootDir.'/' . $file); - $actualContents = file_get_contents($rootDir.'/' . $file); + $this->assertFileExists($rootDir.'/'.$file); + $actualContents = file_get_contents($rootDir.'/'.$file); $this->assertSame(sprintf('contents of %s', $file), $actualContents); } } @@ -140,7 +139,7 @@ public static function getRequirePackageTests(): iterable 'expectedImportMap' => [ 'lodash' => [ 'url' => 'https://ga.jspm.io/npm:lodash@1.2.3/lodash.js', - ] + ], ], 'expectedDownloadedFiles' => [], ]; @@ -163,7 +162,7 @@ public static function getRequirePackageTests(): iterable 'expectedDownloadedFiles' => [], ]; - yield 'single_package_that_returns_as_two' => [ + yield 'single_package_that_returns_as_two' => [ 'packages' => [new PackageRequireOptions('lodash')], 'expectedInstallRequest' => ['lodash'], 'responseMap' => [ @@ -258,7 +257,7 @@ public static function getRequirePackageTests(): iterable public function testRemove() { - $rootDir = __DIR__ . '/../fixtures/importmaps_for_writing'; + $rootDir = __DIR__.'/../fixtures/importmaps_for_writing'; $manager = $this->createImportMapManager(['assets' => ''], $rootDir); $map = [ @@ -289,7 +288,7 @@ public function testRemove() touch($rootDir.'/assets/other.js'); $manager->remove(['cowsay', 'app']); - $actualImportMap = require($rootDir.'/importmap.php'); + $actualImportMap = require $rootDir.'/importmap.php'; $expectedImportMap = $map; unset($expectedImportMap['cowsay'], $expectedImportMap['app']); $this->assertEquals($expectedImportMap, $actualImportMap); @@ -301,7 +300,7 @@ public function testRemove() public function testUpdate() { - $rootDir = __DIR__ . '/../fixtures/importmaps_for_writing'; + $rootDir = __DIR__.'/../fixtures/importmaps_for_writing'; $manager = $this->createImportMapManager(['assets' => ''], $rootDir); $map = [ @@ -342,11 +341,11 @@ public function testUpdate() ])); }; // 1 file will be downloaded - $responses[] = new MockResponse(sprintf('contents of cowsay.js')); + $responses[] = new MockResponse('contents of cowsay.js'); $this->httpClient->setResponseFactory($responses); $manager->update(); - $actualImportMap = require($rootDir.'/importmap.php'); + $actualImportMap = require $rootDir.'/importmap.php'; $expectedImportMap = [ 'lodash' => [ 'url' => 'https://ga.jspm.io/npm:lodash@1.2.9/lodash.js', @@ -379,10 +378,10 @@ public function testParsePackageName(string $packageName, array $expectedReturn) $parsed = ImportMapManager::parsePackageName($packageName); // remove integer keys - they're noise - if (is_array($parsed)) { + if (\is_array($parsed)) { $parsed = array_filter($parsed, function ($key) { - return !is_int($key); - }, ARRAY_FILTER_USE_KEY); + return !\is_int($key); + }, \ARRAY_FILTER_USE_KEY); } $this->assertEquals($expectedReturn, $parsed); } @@ -465,8 +464,8 @@ private function createImportMapManager(array $dirs, string $rootDir, string $pu return new ImportMapManager( $mapper, - $rootDir . '/importmap.php', - $rootDir . '/assets/vendor', + $rootDir.'/importmap.php', + $rootDir.'/assets/vendor', ImportMapManager::PROVIDER_JSPM, $this->httpClient ); diff --git a/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapRendererTest.php b/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapRendererTest.php index 7742e9e7841ab..2df0be10449a8 100644 --- a/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapRendererTest.php +++ b/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapRendererTest.php @@ -25,8 +25,9 @@ public function testBasicRenderNoEntry() - EOF - , $html); + EOF, + $html + ); $this->assertStringContainsString(' - {% 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 23/48] [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 50a4aafef5aac68d0d0b5b7f39d2b8649ccbd596 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 2 May 2023 12:57:44 -0400 Subject: [PATCH 24/48] [AssetMapper] Fixing wrong values being output in command --- .../Component/AssetMapper/Command/ImportMapRequireCommand.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/AssetMapper/Command/ImportMapRequireCommand.php b/src/Symfony/Component/AssetMapper/Command/ImportMapRequireCommand.php index 529a90bca7f57..88efaccdab50b 100644 --- a/src/Symfony/Component/AssetMapper/Command/ImportMapRequireCommand.php +++ b/src/Symfony/Component/AssetMapper/Command/ImportMapRequireCommand.php @@ -13,6 +13,7 @@ use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\AssetMapper\AssetMapperInterface; +use Symfony\Component\AssetMapper\ImportMap\ImportMapEntry; use Symfony\Component\AssetMapper\ImportMap\ImportMapManager; use Symfony\Component\AssetMapper\ImportMap\PackageRequireOptions; use Symfony\Component\Console\Attribute\AsCommand; @@ -95,7 +96,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $message .= '.'; } else { - $message = sprintf('%d new packages (%s) added to the importmap.php!', \count($newPackages), implode(', ', array_keys($newPackages))); + $names = array_map(fn (ImportMapEntry $package) => $package->importName, $newPackages); + $message = sprintf('%d new packages (%s) added to the importmap.php!', \count($newPackages), implode(', ', $names)); } $messages = [$message]; From 276ffb3b82764f0ee11cdd9c162e3fb66475d054 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Tue, 2 May 2023 19:11:24 +0200 Subject: [PATCH 25/48] [DependencyInjection] Allow AutowireCallable without method --- .../Attribute/AutowireCallable.php | 4 +- .../Tests/Attribute/AutowireCallableTest.php | 96 +++++++++++++++++++ .../Tests/Dumper/PhpDumperTest.php | 5 + .../Fixtures/includes/autowiring_classes.php | 7 ++ .../Tests/Fixtures/php/autowire_closure.php | 13 ++- 5 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Attribute/AutowireCallableTest.php diff --git a/src/Symfony/Component/DependencyInjection/Attribute/AutowireCallable.php b/src/Symfony/Component/DependencyInjection/Attribute/AutowireCallable.php index c4a7632fa45d7..c472cb776e2bb 100644 --- a/src/Symfony/Component/DependencyInjection/Attribute/AutowireCallable.php +++ b/src/Symfony/Component/DependencyInjection/Attribute/AutowireCallable.php @@ -32,8 +32,8 @@ public function __construct( if (!(null !== $callable xor null !== $service)) { throw new LogicException('#[AutowireCallable] attribute must declare exactly one of $callable or $service.'); } - if (!(null !== $callable xor null !== $method)) { - throw new LogicException('#[AutowireCallable] attribute must declare one of $callable or $method.'); + if (null === $service && null !== $method) { + throw new LogicException('#[AutowireCallable] attribute cannot have a $method without a $service.'); } parent::__construct($callable ?? [new Reference($service), $method ?? '__invoke'], lazy: $lazy); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Attribute/AutowireCallableTest.php b/src/Symfony/Component/DependencyInjection/Tests/Attribute/AutowireCallableTest.php new file mode 100644 index 0000000000000..f5aeb35d44939 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Attribute/AutowireCallableTest.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Attribute; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Attribute\AutowireCallable; +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\DependencyInjection\Reference; + +class AutowireCallableTest extends TestCase +{ + public function testNoArguments() + { + $this->expectException(LogicException::class); + + new AutowireCallable(); + } + + public function testCallableAndService() + { + $this->expectException(LogicException::class); + + new AutowireCallable(callable: 'my_callable', service: 'my_service', method: 'my_method'); + } + + public function testMethodOnly() + { + $this->expectException(LogicException::class); + + new AutowireCallable(method: 'my_method'); + } + + public function testCallableAndMethod() + { + $this->expectException(LogicException::class); + + new AutowireCallable(callable: 'my_callable', method: 'my_method'); + } + + public function testStringCallable() + { + $attribute = new AutowireCallable(callable: 'my_callable'); + + self::assertSame('my_callable', $attribute->value); + self::assertFalse($attribute->lazy); + } + + public function testArrayCallable() + { + $attribute = new AutowireCallable(callable: ['My\StaticClass', 'my_callable']); + + self::assertSame(['My\StaticClass', 'my_callable'], $attribute->value); + self::assertFalse($attribute->lazy); + } + + public function testArrayCallableWithReferenceAndMethod() + { + $attribute = new AutowireCallable(callable: [new Reference('my_service'), 'my_callable']); + + self::assertEquals([new Reference('my_service'), 'my_callable'], $attribute->value); + self::assertFalse($attribute->lazy); + } + + public function testArrayCallableWithReferenceOnly() + { + $attribute = new AutowireCallable(callable: [new Reference('my_service')]); + + self::assertEquals([new Reference('my_service')], $attribute->value); + self::assertFalse($attribute->lazy); + } + + public function testArrayCallableWithServiceAndMethod() + { + $attribute = new AutowireCallable(service: 'my_service', method: 'my_callable'); + + self::assertEquals([new Reference('my_service'), 'my_callable'], $attribute->value); + self::assertFalse($attribute->lazy); + } + + public function testArrayCallableWithServiceOnly() + { + $attribute = new AutowireCallable(service: 'my_service'); + + self::assertEquals([new Reference('my_service'), '__invoke'], $attribute->value); + self::assertFalse($attribute->lazy); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index 1e188c8f78f03..c16c902b2016f 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -48,6 +48,7 @@ use Symfony\Component\DependencyInjection\Tests\Compiler\Foo; use Symfony\Component\DependencyInjection\Tests\Compiler\FooAnnotation; use Symfony\Component\DependencyInjection\Tests\Compiler\IInterface; +use Symfony\Component\DependencyInjection\Tests\Compiler\MyCallable; use Symfony\Component\DependencyInjection\Tests\Compiler\SingleMethodInterface; use Symfony\Component\DependencyInjection\Tests\Compiler\Wither; use Symfony\Component\DependencyInjection\Tests\Compiler\WitherAnnotation; @@ -1720,6 +1721,8 @@ public function testAutowireClosure() $container = new ContainerBuilder(); $container->register('foo', Foo::class) ->setPublic('true'); + $container->register('my_callable', MyCallable::class) + ->setPublic('true'); $container->register('baz', \Closure::class) ->setFactory(['Closure', 'fromCallable']) ->setArguments(['var_dump']) @@ -1873,6 +1876,8 @@ public function __construct( public \Closure $baz, #[AutowireCallable(service: 'foo', method: 'cloneFoo')] public \Closure $buz, + #[AutowireCallable(service: 'my_callable')] + public \Closure $bar, ) { } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php index e76b58eb68c74..8e284247a61c8 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php @@ -558,3 +558,10 @@ interface SingleMethodInterface { public function theMethod(); } + +class MyCallable +{ + public function __invoke(): void + { + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/autowire_closure.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/autowire_closure.php index 4cdc6e28d78ac..d5c461e64f80d 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/autowire_closure.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/autowire_closure.php @@ -25,6 +25,7 @@ public function __construct() 'bar' => 'getBarService', 'baz' => 'getBazService', 'foo' => 'getFooService', + 'my_callable' => 'getMyCallableService', ]; $this->aliases = []; @@ -53,7 +54,7 @@ protected static function getBarService($container) $container = $containerRef->get(); return ($container->services['foo'] ??= new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo()); - }, ($container->services['baz'] ?? self::getBazService($container)), ($container->services['foo'] ??= new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo())->cloneFoo(...)); + }, ($container->services['baz'] ?? self::getBazService($container)), ($container->services['foo'] ??= new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo())->cloneFoo(...), ($container->services['my_callable'] ??= new \Symfony\Component\DependencyInjection\Tests\Compiler\MyCallable())->__invoke(...)); } /** @@ -75,4 +76,14 @@ protected static function getFooService($container) { return $container->services['foo'] = new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo(); } + + /** + * Gets the public 'my_callable' shared service. + * + * @return \Symfony\Component\DependencyInjection\Tests\Compiler\MyCallable + */ + protected static function getMyCallableService($container) + { + return $container->services['my_callable'] = new \Symfony\Component\DependencyInjection\Tests\Compiler\MyCallable(); + } } From f702e66369274b1d2b7c4d4af9ad3ee0f8f2476e Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 3 May 2023 10:21:12 +0200 Subject: [PATCH 26/48] [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 5733ff7be7e10aa16607a71a0b73dd8793ca52d3 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 3 May 2023 10:06:43 +0200 Subject: [PATCH 27/48] [HttpClient] Favor php-http/discovery instead of nyholm/psr7 --- composer.json | 2 + .../ErrorHandler/DebugClassLoader.php | 5 +- .../Component/HttpClient/HttplugClient.php | 64 ++++++++----------- .../Internal/LegacyHttplugInterface.php | 37 +++++++++++ .../Component/HttpClient/Psr18Client.php | 54 ++++++++-------- .../HttpClient/Tests/HttpClientTraitTest.php | 5 ++ .../Component/HttpClient/composer.json | 2 +- 7 files changed, 99 insertions(+), 70 deletions(-) create mode 100644 src/Symfony/Component/HttpClient/Internal/LegacyHttplugInterface.php diff --git a/composer.json b/composer.json index 5d10d8b2a366e..4f81d054cd008 100644 --- a/composer.json +++ b/composer.json @@ -141,6 +141,7 @@ "monolog/monolog": "^1.25.1|^2", "nyholm/psr7": "^1.0", "pda/pheanstalk": "^4.0", + "php-http/discovery": "^1.15", "php-http/httplug": "^1.0|^2.0", "php-http/message-factory": "^1.0", "phpdocumentor/reflection-docblock": "^5.2", @@ -171,6 +172,7 @@ }, "config": { "allow-plugins": { + "php-http/discovery": false, "symfony/runtime": true } }, diff --git a/src/Symfony/Component/ErrorHandler/DebugClassLoader.php b/src/Symfony/Component/ErrorHandler/DebugClassLoader.php index 3a17b6be418df..16af2d06321e4 100644 --- a/src/Symfony/Component/ErrorHandler/DebugClassLoader.php +++ b/src/Symfony/Component/ErrorHandler/DebugClassLoader.php @@ -21,7 +21,6 @@ use Prophecy\Prophecy\ProphecySubjectInterface; use ProxyManager\Proxy\ProxyInterface; use Symfony\Component\ErrorHandler\Internal\TentativeTypes; -use Symfony\Component\HttpClient\HttplugClient; use Symfony\Component\VarExporter\LazyObjectInterface; /** @@ -422,9 +421,7 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array if (!isset(self::$checkedClasses[$use])) { $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\Client\HttpClient::class, \Http\Message\RequestFactory::class, \Http\Message\StreamFactory::class, \Http\Message\UriFactory::class], true)) - ) { + if (isset(self::$deprecated[$use]) && strncmp($vendor, str_replace('_', '\\', $use), $vendorLen) && !isset(self::$deprecated[$class])) { $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'); diff --git a/src/Symfony/Component/HttpClient/HttplugClient.php b/src/Symfony/Component/HttpClient/HttplugClient.php index 89014b92d1e6c..d4fa4b2bed31a 100644 --- a/src/Symfony/Component/HttpClient/HttplugClient.php +++ b/src/Symfony/Component/HttpClient/HttplugClient.php @@ -17,13 +17,9 @@ use Http\Client\Exception\NetworkException; use Http\Client\Exception\RequestException; use Http\Client\HttpAsyncClient; -use Http\Client\HttpClient as HttplugInterface; -use Http\Discovery\Exception\NotFoundException; +use Http\Discovery\Psr17Factory; use Http\Discovery\Psr17FactoryDiscovery; -use Http\Message\RequestFactory; -use Http\Message\StreamFactory; -use Http\Message\UriFactory; -use Nyholm\Psr7\Factory\Psr17Factory; +use Nyholm\Psr7\Factory\Psr17Factory as NyholmPsr17Factory; use Nyholm\Psr7\Request; use Nyholm\Psr7\Uri; use Psr\Http\Client\ClientInterface; @@ -36,37 +32,32 @@ use Psr\Http\Message\UriFactoryInterface; use Psr\Http\Message\UriInterface; use Symfony\Component\HttpClient\Internal\HttplugWaitLoop; +use Symfony\Component\HttpClient\Internal\LegacyHttplugInterface; use Symfony\Component\HttpClient\Response\HttplugPromise; use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\ResponseInterface; use Symfony\Contracts\Service\ResetInterface; -if (!interface_exists(HttplugInterface::class)) { - throw new \LogicException('You cannot use "Symfony\Component\HttpClient\HttplugClient" as the "php-http/httplug" package is not installed. Try running "composer require php-http/httplug".'); -} - -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 php-http/message-factory".'); +if (!interface_exists(HttpAsyncClient::class)) { + throw new \LogicException('You cannot use "Symfony\Component\HttpClient\HttplugClient" as the "php-http/httplug" package is not installed. Try running "php-http/discovery php-http/async-client-implementation:*".'); } if (!interface_exists(RequestFactoryInterface::class)) { - throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\HttplugClient" as the "psr/http-factory" package is not installed. Try running "composer require nyholm/psr7".'); -} - -if (!interface_exists(ClientInterface::class)) { - throw new \LogicException('You cannot use "Symfony\Component\HttpClient\HttplugClient" as the "psr/http-client" package is not installed. Try running "composer require psr/http-client".'); + throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\HttplugClient" as the "psr/http-factory" package is not installed. Try running "composer require php-http/discovery psr/http-factory-implementation:*".'); } /** * An adapter to turn a Symfony HttpClientInterface into an Httplug client. * - * Run "composer require nyholm/psr7" to install an efficient implementation of response - * and stream factories with flex-provided autowiring aliases. + * In comparison to Psr18Client, this client supports asynchronous requests. + * + * Run "composer require php-http/discovery php-http/async-client-implementation:*" + * to get the required dependencies. * * @author Nicolas Grekas */ -final class HttplugClient implements ClientInterface, HttplugInterface, HttpAsyncClient, RequestFactoryInterface, StreamFactoryInterface, UriFactoryInterface, RequestFactory, StreamFactory, UriFactory, ResetInterface +final class HttplugClient implements ClientInterface, HttpAsyncClient, RequestFactoryInterface, StreamFactoryInterface, UriFactoryInterface, ResetInterface, LegacyHttplugInterface { private HttpClientInterface $client; private ResponseFactoryInterface $responseFactory; @@ -86,17 +77,16 @@ public function __construct(HttpClientInterface $client = null, ResponseFactoryI $this->promisePool = class_exists(Utils::class) ? new \SplObjectStorage() : null; if (null === $responseFactory || null === $streamFactory) { - if (!class_exists(Psr17Factory::class) && !class_exists(Psr17FactoryDiscovery::class)) { - throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\HttplugClient" as no PSR-17 factories have been provided. Try running "composer require nyholm/psr7".'); + if (class_exists(Psr17Factory::class)) { + $psr17Factory = new Psr17Factory(); + } elseif (class_exists(NyholmPsr17Factory::class)) { + $psr17Factory = new NyholmPsr17Factory(); + } else { + throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\HttplugClient" as no PSR-17 factories have been provided. Try running "composer require php-http/discovery psr/http-factory-implementation:*".'); } - try { - $psr17Factory = class_exists(Psr17Factory::class, false) ? new Psr17Factory() : null; - $responseFactory ??= $psr17Factory ?? Psr17FactoryDiscovery::findResponseFactory(); - $streamFactory ??= $psr17Factory ?? Psr17FactoryDiscovery::findStreamFactory(); - } catch (NotFoundException $e) { - throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\HttplugClient" as no PSR-17 factories have been found. Try running "composer require nyholm/psr7".', 0, $e); - } + $responseFactory ??= $psr17Factory; + $streamFactory ??= $psr17Factory; } $this->responseFactory = $responseFactory; @@ -170,12 +160,12 @@ public function createRequest($method, $uri, array $headers = [], $body = null, } if ($this->responseFactory instanceof RequestFactoryInterface) { $request = $this->responseFactory->createRequest($method, $uri); - } elseif (class_exists(Request::class)) { - $request = new Request($method, $uri); } elseif (class_exists(Psr17FactoryDiscovery::class)) { $request = Psr17FactoryDiscovery::findRequestFactory()->createRequest($method, $uri); + } elseif (class_exists(Request::class)) { + $request = new Request($method, $uri); } else { - throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__)); + throw new \LogicException(sprintf('You cannot use "%s()" as no PSR-17 factories have been found. Try running "composer require php-http/discovery psr/http-factory-implementation:*".', __METHOD__)); } $request = $request @@ -245,15 +235,15 @@ public function createUri($uri = ' 10000 '): UriInterface return $this->responseFactory->createUri($uri); } - if (class_exists(Uri::class)) { - return new Uri($uri); - } - if (class_exists(Psr17FactoryDiscovery::class)) { return Psr17FactoryDiscovery::findUrlFactory()->createUri($uri); } - throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__)); + if (class_exists(Uri::class)) { + return new Uri($uri); + } + + throw new \LogicException(sprintf('You cannot use "%s()" as no PSR-17 factories have been found. Try running "composer require php-http/discovery psr/http-factory-implementation:*".', __METHOD__)); } public function __sleep(): array diff --git a/src/Symfony/Component/HttpClient/Internal/LegacyHttplugInterface.php b/src/Symfony/Component/HttpClient/Internal/LegacyHttplugInterface.php new file mode 100644 index 0000000000000..44512cb512495 --- /dev/null +++ b/src/Symfony/Component/HttpClient/Internal/LegacyHttplugInterface.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +use Http\Client\HttpClient; +use Http\Message\RequestFactory; +use Http\Message\StreamFactory; +use Http\Message\UriFactory; + +if (interface_exists(RequestFactory::class)) { + /** + * @internal + * + * @deprecated since Symfony 6.3 + */ + interface LegacyHttplugInterface extends HttpClient, RequestFactory, StreamFactory, UriFactory + { + } +} else { + /** + * @internal + * + * @deprecated since Symfony 6.3 + */ + interface LegacyHttplugInterface extends HttpClient + { + } +} diff --git a/src/Symfony/Component/HttpClient/Psr18Client.php b/src/Symfony/Component/HttpClient/Psr18Client.php index 0be916acb8d9f..13bf5b578a2c9 100644 --- a/src/Symfony/Component/HttpClient/Psr18Client.php +++ b/src/Symfony/Component/HttpClient/Psr18Client.php @@ -11,9 +11,9 @@ namespace Symfony\Component\HttpClient; -use Http\Discovery\Exception\NotFoundException; +use Http\Discovery\Psr17Factory; use Http\Discovery\Psr17FactoryDiscovery; -use Nyholm\Psr7\Factory\Psr17Factory; +use Nyholm\Psr7\Factory\Psr17Factory as NyholmPsr17Factory; use Nyholm\Psr7\Request; use Nyholm\Psr7\Uri; use Psr\Http\Client\ClientInterface; @@ -33,20 +33,19 @@ use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\Service\ResetInterface; -if (!interface_exists(RequestFactoryInterface::class)) { - throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\Psr18Client" as the "psr/http-factory" package is not installed. Try running "composer require nyholm/psr7".'); +if (!interface_exists(ClientInterface::class)) { + throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\Psr18Client" as the "psr/http-client" package is not installed. Try running "composer require php-http/discovery psr/http-client-implementation:*".'); } -if (!interface_exists(ClientInterface::class)) { - throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\Psr18Client" as the "psr/http-client" package is not installed. Try running "composer require psr/http-client".'); +if (!interface_exists(RequestFactoryInterface::class)) { + throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\Psr18Client" as the "psr/http-factory" package is not installed. Try running "composer require php-http/discovery psr/http-factory-implementation:*".'); } /** * An adapter to turn a Symfony HttpClientInterface into a PSR-18 ClientInterface. * - * Run "composer require psr/http-client" to install the base ClientInterface. Run - * "composer require nyholm/psr7" to install an efficient implementation of response - * and stream factories with flex-provided autowiring aliases. + * Run "composer require php-http/discovery psr/http-client-implementation:*" + * to get the required dependencies. * * @author Nicolas Grekas */ @@ -62,17 +61,16 @@ public function __construct(HttpClientInterface $client = null, ResponseFactoryI $streamFactory ??= $responseFactory instanceof StreamFactoryInterface ? $responseFactory : null; if (null === $responseFactory || null === $streamFactory) { - if (!class_exists(Psr17Factory::class) && !class_exists(Psr17FactoryDiscovery::class)) { - throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\Psr18Client" as no PSR-17 factories have been provided. Try running "composer require nyholm/psr7".'); + if (class_exists(Psr17Factory::class)) { + $psr17Factory = new Psr17Factory(); + } elseif (class_exists(NyholmPsr17Factory::class)) { + $psr17Factory = new NyholmPsr17Factory(); + } else { + throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\Psr18Client" as no PSR-17 factories have been provided. Try running "composer require php-http/discovery psr/http-factory-implementation:*".'); } - try { - $psr17Factory = class_exists(Psr17Factory::class, false) ? new Psr17Factory() : null; - $responseFactory ??= $psr17Factory ?? Psr17FactoryDiscovery::findResponseFactory(); - $streamFactory ??= $psr17Factory ?? Psr17FactoryDiscovery::findStreamFactory(); - } catch (NotFoundException $e) { - throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\HttplugClient" as no PSR-17 factories have been found. Try running "composer require nyholm/psr7".', 0, $e); - } + $responseFactory ??= $psr17Factory; + $streamFactory ??= $psr17Factory; } $this->responseFactory = $responseFactory; @@ -142,15 +140,15 @@ public function createRequest(string $method, $uri): RequestInterface return $this->responseFactory->createRequest($method, $uri); } - if (class_exists(Request::class)) { - return new Request($method, $uri); - } - if (class_exists(Psr17FactoryDiscovery::class)) { return Psr17FactoryDiscovery::findRequestFactory()->createRequest($method, $uri); } - throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__)); + if (class_exists(Request::class)) { + return new Request($method, $uri); + } + + throw new \LogicException(sprintf('You cannot use "%s()" as no PSR-17 factories have been found. Try running "composer require php-http/discovery psr/http-factory-implementation:*".', __METHOD__)); } public function createStream(string $content = ''): StreamInterface @@ -180,15 +178,15 @@ public function createUri(string $uri = ''): UriInterface return $this->responseFactory->createUri($uri); } - if (class_exists(Uri::class)) { - return new Uri($uri); - } - if (class_exists(Psr17FactoryDiscovery::class)) { return Psr17FactoryDiscovery::findUrlFactory()->createUri($uri); } - throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__)); + if (class_exists(Uri::class)) { + return new Uri($uri); + } + + throw new \LogicException(sprintf('You cannot use "%s()" as no PSR-17 factories have been found. Try running "composer require php-http/discovery psr/http-factory-implementation:*".', __METHOD__)); } public function reset(): void diff --git a/src/Symfony/Component/HttpClient/Tests/HttpClientTraitTest.php b/src/Symfony/Component/HttpClient/Tests/HttpClientTraitTest.php index 9453297d7dc8b..fbef0230332ed 100644 --- a/src/Symfony/Component/HttpClient/Tests/HttpClientTraitTest.php +++ b/src/Symfony/Component/HttpClient/Tests/HttpClientTraitTest.php @@ -156,6 +156,11 @@ public function testNormalizeBodyMultipartForwardStream($stream) public static function provideNormalizeBodyMultipartForwardStream() { yield 'native' => [fopen('https://github.githubassets.com/images/icons/emoji/unicode/1f44d.png', 'r')]; + + if (!\defined('OPENSSL_DEFAULT_STREAM_CIPHERS')) { + return; + } + yield 'symfony' => [HttpClient::create()->request('GET', 'https://github.githubassets.com/images/icons/emoji/unicode/1f44d.png')->toStream()]; } diff --git a/src/Symfony/Component/HttpClient/composer.json b/src/Symfony/Component/HttpClient/composer.json index 91c79bb53c169..ea2b940cae926 100644 --- a/src/Symfony/Component/HttpClient/composer.json +++ b/src/Symfony/Component/HttpClient/composer.json @@ -36,7 +36,6 @@ "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": "^5.4|^6.0", "symfony/http-kernel": "^5.4|^6.0", @@ -44,6 +43,7 @@ "symfony/stopwatch": "^5.4|^6.0" }, "conflict": { + "php-http/discovery": "<1.15", "symfony/http-foundation": "<6.3" }, "autoload": { From 5a867c58dd72822818d97bd4ca421985b62fda41 Mon Sep 17 00:00:00 2001 From: Allison Guilhem Date: Wed, 3 May 2023 09:50:11 +0200 Subject: [PATCH 28/48] [DoctrineBridge] skip subscriber if listener already defined --- ...gisterEventListenersAndSubscribersPass.php | 22 ++++++----- ...erEventListenersAndSubscribersPassTest.php | 38 +++++++++++++++++++ 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php index 16a37b524acc6..b6946cc4dec56 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php @@ -75,7 +75,7 @@ private function addTaggedServices(ContainerBuilder $container): array $listenerTag = $this->tagPrefix.'.event_listener'; $subscriberTag = $this->tagPrefix.'.event_subscriber'; $listenerRefs = []; - $taggedServices = $this->findAndSortTags([$subscriberTag, $listenerTag], $container); + $taggedServices = $this->findAndSortTags($subscriberTag, $listenerTag, $container); $managerDefs = []; foreach ($taggedServices as $taggedSubscriber) { @@ -144,12 +144,17 @@ private function getEventManagerDef(ContainerBuilder $container, string $name): * @see https://bugs.php.net/53710 * @see https://bugs.php.net/60926 */ - private function findAndSortTags(array $tagNames, ContainerBuilder $container): array + private function findAndSortTags(string $subscriberTag, string $listenerTag, ContainerBuilder $container): array { $sortedTags = []; - - foreach ($tagNames as $tagName) { - foreach ($container->findTaggedServiceIds($tagName, true) as $serviceId => $tags) { + $taggedIds = [ + $subscriberTag => $container->findTaggedServiceIds($subscriberTag, true), + $listenerTag => $container->findTaggedServiceIds($listenerTag, true), + ]; + $taggedIds[$subscriberTag] = array_diff_key($taggedIds[$subscriberTag], $taggedIds[$listenerTag]); + + foreach ($taggedIds as $tagName => $serviceIds) { + foreach ($serviceIds as $serviceId => $tags) { foreach ($tags as $attributes) { $priority = $attributes['priority'] ?? 0; $sortedTags[$priority][] = [$tagName, $serviceId, $attributes]; @@ -157,11 +162,8 @@ private function findAndSortTags(array $tagNames, ContainerBuilder $container): } } - if ($sortedTags) { - krsort($sortedTags); - $sortedTags = array_merge(...$sortedTags); - } + krsort($sortedTags); - return $sortedTags; + return array_merge(...$sortedTags); } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php index 02cd5acf0365d..d2b3473fba880 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php @@ -454,6 +454,44 @@ public function testProcessEventSubscribersAndListenersWithPriorities() ); } + public function testSubscribersAreSkippedIfListenerDefinedForSameDefinition() + { + $container = $this->createBuilder(); + + $container + ->register('a', 'stdClass') + ->setPublic(false) + ->addTag('doctrine.event_listener', [ + 'event' => 'bar', + 'priority' => 3, + ]) + ; + $container + ->register('b', 'stdClass') + ->setPublic(false) + ->addTag('doctrine.event_listener', [ + 'event' => 'bar', + ]) + ->addTag('doctrine.event_listener', [ + 'event' => 'foo', + 'priority' => -5, + ]) + ->addTag('doctrine.event_subscriber') + ; + $this->process($container); + + $eventManagerDef = $container->getDefinition('doctrine.dbal.default_connection.event_manager'); + + $this->assertEquals( + [ + [['bar'], 'a'], + [['bar'], 'b'], + [['foo'], 'b'], + ], + $eventManagerDef->getArgument(1) + ); + } + public function testProcessNoTaggedServices() { $container = $this->createBuilder(true); From 7820776bbd523332bb860854bcd482d1b4128527 Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Wed, 3 May 2023 19:59:23 +0200 Subject: [PATCH 29/48] [WebProfilerBundle] Profiler respect stateless attribute --- .../Controller/ProfilerController.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php index b9a861df38dd2..4f0e052226c92 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php @@ -128,7 +128,9 @@ public function toolbarAction(Request $request, string $token = null): Response throw new NotFoundHttpException('The profiler must be enabled.'); } - if ($request->hasSession() && ($session = $request->getSession())->isStarted() && $session->getFlashBag() instanceof AutoExpireFlashBag) { + if (!$request->attributes->getBoolean('_stateless') && $request->hasSession() + && ($session = $request->getSession())->isStarted() && $session->getFlashBag() instanceof AutoExpireFlashBag + ) { // keep current flashes for one more request if using AutoExpireFlashBag $session->getFlashBag()->setAll($session->getFlashBag()->peekAll()); } @@ -172,7 +174,11 @@ public function searchBarAction(Request $request): Response $this->cspHandler?->disableCsp(); - $session = $request->hasSession() ? $request->getSession() : null; + + $session = null; + if ($request->attributes->getBoolean('_stateless') && $request->hasSession()) { + $session = $request->getSession(); + } return new Response( $this->twig->render('@WebProfiler/Profiler/search.html.twig', [ @@ -247,7 +253,7 @@ public function searchAction(Request $request): Response $limit = $request->query->get('limit'); $token = $request->query->get('token'); - if ($request->hasSession()) { + if (!$request->attributes->getBoolean('_stateless') && $request->hasSession()) { $session = $request->getSession(); $session->set('_profiler_search_ip', $ip); From 8703b1864daa86a8b20e30b69a795c388d43e9b9 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 4 May 2023 08:30:04 +0200 Subject: [PATCH 30/48] Fix typo --- src/Symfony/Component/HttpClient/HttplugClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/HttpClient/HttplugClient.php b/src/Symfony/Component/HttpClient/HttplugClient.php index d4fa4b2bed31a..9179b0ed4007c 100644 --- a/src/Symfony/Component/HttpClient/HttplugClient.php +++ b/src/Symfony/Component/HttpClient/HttplugClient.php @@ -40,7 +40,7 @@ use Symfony\Contracts\Service\ResetInterface; if (!interface_exists(HttpAsyncClient::class)) { - throw new \LogicException('You cannot use "Symfony\Component\HttpClient\HttplugClient" as the "php-http/httplug" package is not installed. Try running "php-http/discovery php-http/async-client-implementation:*".'); + throw new \LogicException('You cannot use "Symfony\Component\HttpClient\HttplugClient" as the "php-http/httplug" package is not installed. Try running "composer require php-http/discovery php-http/async-client-implementation:*".'); } if (!interface_exists(RequestFactoryInterface::class)) { From ea449ca6bac80d0ea718fcf06e5fe6a9b97b329c Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 13 Apr 2023 19:22:41 +0200 Subject: [PATCH 31/48] [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 32/48] [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 33/48] [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 b61852a25b576a296268315871848a0eccc01d61 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Fri, 5 May 2023 10:58:22 +0200 Subject: [PATCH 34/48] [ErrorHandler] Fix the design of the exception page tabs --- .../Resources/assets/css/exception.css | 139 +++++++++++++++--- 1 file changed, 121 insertions(+), 18 deletions(-) diff --git a/src/Symfony/Component/ErrorHandler/Resources/assets/css/exception.css b/src/Symfony/Component/ErrorHandler/Resources/assets/css/exception.css index bcdb8e1482553..3e6eae5a92273 100644 --- a/src/Symfony/Component/ErrorHandler/Resources/assets/css/exception.css +++ b/src/Symfony/Component/ErrorHandler/Resources/assets/css/exception.css @@ -9,12 +9,23 @@ --color-warning: #a46a1f; --color-error: #b0413e; --color-muted: #999; - --tab-background: #fff; + --tab-background: #f0f0f0; + --tab-border-color: #e5e5e5; + --tab-active-border-color: #d4d4d4; --tab-color: #444; - --tab-active-background: #666; - --tab-active-color: #fafafa; + --tab-active-background: #fff; + --tab-active-color: var(--color-text); --tab-disabled-background: #f5f5f5; --tab-disabled-color: #999; + --selected-badge-background: #e5e5e5; + --selected-badge-color: #525252; + --selected-badge-shadow: inset 0 0 0 1px #d4d4d4; + --selected-badge-warning-background: #fde496; + --selected-badge-warning-color: #785b02; + --selected-badge-warning-shadow: inset 0 0 0 1px #e6af05; + --selected-badge-danger-background: #FCE9ED; + --selected-badge-danger-color: #83122A; + --selected-badge-danger-shadow: inset 0 0 0 1px #F5B8C5; --metric-value-background: #fff; --metric-value-color: inherit; --metric-unit-color: #999; @@ -47,12 +58,23 @@ --color-text: #e0e0e0; --color-muted: #777; --color-error: #d43934; - --tab-background: #555; - --tab-color: #ccc; - --tab-active-background: #888; - --tab-active-color: #fafafa; + --tab-background: #404040; + --tab-border-color: #737373; + --tab-active-border-color: #171717; + --tab-color: var(--color-text); + --tab-active-background: #d4d4d4; + --tab-active-color: #262626; --tab-disabled-background: var(--page-background); - --tab-disabled-color: #777; + --tab-disabled-color: #a3a3a3; + --selected-badge-background: #555; + --selected-badge-color: #ddd; + --selected-badge-shadow: none; + --selected-badge-warning-background: #fcd55f; + --selected-badge-warning-color: #785b02; + --selected-badge-warning-shadow: inset 0 0 0 1px #af8503; + --selected-badge-danger-background: #B41939; + --selected-badge-danger-color: #FCE9ED; + --selected-badge-danger-shadow: none; --metric-value-background: #555; --metric-value-color: inherit; --metric-unit-color: #999; @@ -132,15 +154,96 @@ thead.sf-toggle-content.sf-toggle-visible, tbody.sf-toggle-content.sf-toggle-vis .sf-toggle-off .icon-close, .sf-toggle-on .icon-open { display: none; } .sf-toggle-off .icon-open, .sf-toggle-on .icon-close { display: block; } -.tab-navigation { margin: 0 0 1em 0; padding: 0; } -.tab-navigation li { background: var(--tab-background); border: 1px solid var(--table-border); color: var(--tab-color); cursor: pointer; display: inline-block; font-size: 16px; margin: 0 0 0 -1px; padding: .5em .75em; z-index: 1; } -.tab-navigation li .badge { background-color: var(--base-1); color: var(--base-4); display: inline-block; font-size: 14px; font-weight: bold; margin-left: 8px; min-width: 10px; padding: 1px 6px; text-align: center; white-space: nowrap; } -.tab-navigation li.disabled { background: var(--tab-disabled-background); color: var(--tab-disabled-color); } -.tab-navigation li.active { background: var(--tab-active-background); color: var(--tab-active-color); z-index: 1100; } -.tab-navigation li.active .badge { background-color: var(--base-5); color: var(--base-2); } -.tab-content > *:first-child { margin-top: 0; } -.tab-navigation li .badge.status-warning { background: var(--color-warning); color: #FFF; } -.tab-navigation li .badge.status-error { background: var(--background-error); color: #FFF; } +.tab-navigation { + background-color: var(--tab-background); + border-radius: 6px; + box-shadow: inset 0 0 0 1px var(--tab-border-color), 0 0 0 5px var(--page-background); + display: inline-flex; + flex-wrap: wrap; + margin: 0 0 15px; + padding: 0; + user-select: none; + -webkit-user-select: none; +} +.sf-tabs-sm .tab-navigation { + box-shadow: inset 0 0 0 1px var(--tab-border-color), 0 0 0 4px var(--page-background); + margin: 0 0 10px; +} +.tab-navigation .tab-control { + background: transparent; + border: 0; + box-shadow: none; + transition: box-shadow .05s ease-in, background-color .05s ease-in; + cursor: pointer; + font-size: 14px; + font-weight: 500; + line-height: 1.4; + margin: 0; + padding: 4px 14px; + position: relative; + text-align: center; + z-index: 1; +} +.sf-tabs-sm .tab-navigation .tab-control { + font-size: 13px; + padding: 2.5px 10px; +} +.tab-navigation .tab-control:before { + background: var(--tab-border-color); + bottom: 15%; + content: ""; + left: 0; + position: absolute; + top: 15%; + width: 1px; +} +.tab-navigation .tab-control:first-child:before, +.tab-navigation .tab-control.active + .tab-control:before, +.tab-navigation .tab-control.active:before { + width: 0; +} +.tab-navigation .tab-control .badge { + background: var(--selected-badge-background); + box-shadow: var(--selected-badge-shadow); + color: var(--selected-badge-color); + display: inline-block; + font-size: 12px; + font-weight: bold; + line-height: 1; + margin-left: 8px; + min-width: 10px; + padding: 2px 6px; + text-align: center; + white-space: nowrap; +} +.tab-navigation .tab-control.disabled { + color: var(--tab-disabled-color); +} +.tab-navigation .tab-control.active { + background-color: var(--tab-active-background); + border-radius: 6px; + box-shadow: inset 0 0 0 1.5px var(--tab-active-border-color); + color: var(--tab-active-color); + position: relative; + z-index: 1; +} +.theme-dark .tab-navigation li.active { + box-shadow: inset 0 0 0 1px var(--tab-border-color); +} +.tab-content > *:first-child { + margin-top: 0; +} +.tab-navigation .tab-control .badge.status-warning { + background: var(--selected-badge-warning-background); + box-shadow: var(--selected-badge-warning-shadow); + color: var(--selected-badge-warning-color); +} +.tab-navigation .tab-control .badge.status-error { + background: var(--selected-badge-danger-background); + box-shadow: var(--selected-badge-danger-shadow); + color: var(--selected-badge-danger-color); +} + .sf-tabs .tab:not(:first-child) { display: none; } [data-filters] { position: relative; } @@ -221,7 +324,7 @@ header .container { display: flex; justify-content: space-between; } .trace-head .icon { position: absolute; right: 0; top: 0; } .trace-head .icon svg { fill: var(--base-5); height: 24px; width: 24px; } -.trace-details { background: var(--base-0); border: var(--border); box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 1em 0; table-layout: fixed; } +.trace-details { background: var(--base-0); border: var(--border); box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 0 0 1em; table-layout: fixed; } .trace-message { font-size: 14px; font-weight: normal; margin: .5em 0 0; } From 7f485657e37303e8636ec923b51508c6cf169652 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 2 May 2023 16:15:54 -0400 Subject: [PATCH 35/48] [AssetMapper] Adding debug:assetmap command + normalize paths --- .../Resources/config/asset_mapper.php | 9 ++ .../AssetMapper/AssetMapperRepository.php | 34 +++++- .../Command/AssetMapperCompileCommand.php | 4 +- .../Command/DebugAssetMapperCommand.php | 114 ++++++++++++++++++ .../Tests/AssetMapperRepositoryTest.php | 46 ++++--- .../AssetsMapperCompileCommandTest.php | 17 ++- .../Command/DebugAssetsMapperCommandTest.php | 34 ++++++ 7 files changed, 230 insertions(+), 28 deletions(-) create mode 100644 src/Symfony/Component/AssetMapper/Command/DebugAssetMapperCommand.php create mode 100644 src/Symfony/Component/AssetMapper/Tests/Command/DebugAssetsMapperCommandTest.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/asset_mapper.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/asset_mapper.php index 5d471ea623258..807cb77fc3a8d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/asset_mapper.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/asset_mapper.php @@ -17,6 +17,7 @@ use Symfony\Component\AssetMapper\AssetMapperInterface; use Symfony\Component\AssetMapper\AssetMapperRepository; use Symfony\Component\AssetMapper\Command\AssetMapperCompileCommand; +use Symfony\Component\AssetMapper\Command\DebugAssetMapperCommand; use Symfony\Component\AssetMapper\Command\ImportMapExportCommand; use Symfony\Component\AssetMapper\Command\ImportMapRemoveCommand; use Symfony\Component\AssetMapper\Command\ImportMapRequireCommand; @@ -70,6 +71,14 @@ ]) ->tag('console.command') + ->set('asset_mapper.command.debug', DebugAssetMapperCommand::class) + ->args([ + service('asset_mapper'), + service('asset_mapper.repository'), + param('kernel.project_dir'), + ]) + ->tag('console.command') + ->set('asset_mapper_compiler', AssetMapperCompiler::class) ->args([ tagged_iterator('asset_mapper.compiler'), diff --git a/src/Symfony/Component/AssetMapper/AssetMapperRepository.php b/src/Symfony/Component/AssetMapper/AssetMapperRepository.php index 87c165ccf23e3..b7440a9263844 100644 --- a/src/Symfony/Component/AssetMapper/AssetMapperRepository.php +++ b/src/Symfony/Component/AssetMapper/AssetMapperRepository.php @@ -55,7 +55,7 @@ public function find(string $logicalPath): ?string $file = rtrim($path, '/').'/'.$localLogicalPath; if (file_exists($file)) { - return $file; + return realpath($file); } } @@ -64,17 +64,24 @@ public function find(string $logicalPath): ?string public function findLogicalPath(string $filesystemPath): ?string { + if (!is_file($filesystemPath)) { + return null; + } + + $filesystemPath = realpath($filesystemPath); + foreach ($this->getDirectories() as $path => $namespace) { if (!str_starts_with($filesystemPath, $path)) { continue; } $logicalPath = substr($filesystemPath, \strlen($path)); + if ('' !== $namespace) { - $logicalPath = $namespace.'/'.$logicalPath; + $logicalPath = $namespace.'/'.ltrim($logicalPath, '/\\'); } - return ltrim($logicalPath, '/'); + return $this->normalizeLogicalPath($logicalPath); } return null; @@ -100,6 +107,7 @@ public function all(): array /** @var RecursiveDirectoryIterator $innerIterator */ $innerIterator = $iterator->getInnerIterator(); $logicalPath = ($namespace ? rtrim($namespace, '/').'/' : '').$innerIterator->getSubPathName(); + $logicalPath = $this->normalizeLogicalPath($logicalPath); $paths[$logicalPath] = $file->getPathname(); } } @@ -107,6 +115,14 @@ public function all(): array return $paths; } + /** + * @internal + */ + public function allDirectories(): array + { + return $this->getDirectories(); + } + private function getDirectories(): array { $filesystem = new Filesystem(); @@ -120,13 +136,13 @@ private function getDirectories(): array if (!file_exists($path)) { throw new \InvalidArgumentException(sprintf('The asset mapper directory "%s" does not exist.', $path)); } - $this->absolutePaths[$path] = $namespace; + $this->absolutePaths[realpath($path)] = $namespace; continue; } if (file_exists($this->projectRootDir.'/'.$path)) { - $this->absolutePaths[$this->projectRootDir.'/'.$path] = $namespace; + $this->absolutePaths[realpath($this->projectRootDir.'/'.$path)] = $namespace; continue; } @@ -136,4 +152,12 @@ private function getDirectories(): array return $this->absolutePaths; } + + /** + * Normalize slashes to / for logical paths. + */ + private function normalizeLogicalPath(string $logicalPath): string + { + return ltrim(str_replace('\\', '/', $logicalPath), '/\\'); + } } diff --git a/src/Symfony/Component/AssetMapper/Command/AssetMapperCompileCommand.php b/src/Symfony/Component/AssetMapper/Command/AssetMapperCompileCommand.php index 6c08da1c7d68b..b6ed6c8dc9c65 100644 --- a/src/Symfony/Component/AssetMapper/Command/AssetMapperCompileCommand.php +++ b/src/Symfony/Component/AssetMapper/Command/AssetMapperCompileCommand.php @@ -31,7 +31,7 @@ * * @author Ryan Weaver */ -#[AsCommand(name: 'assetmap:compile', description: 'Compiles all mapped assets and writes them to the final public output directory.')] +#[AsCommand(name: 'asset-map:compile', description: 'Compiles all mapped assets and writes them to the final public output directory.')] final class AssetMapperCompileCommand extends Command { public function __construct( @@ -105,6 +105,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int )); } - return self::SUCCESS; + return 0; } } diff --git a/src/Symfony/Component/AssetMapper/Command/DebugAssetMapperCommand.php b/src/Symfony/Component/AssetMapper/Command/DebugAssetMapperCommand.php new file mode 100644 index 0000000000000..54b2e7e98038d --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Command/DebugAssetMapperCommand.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\Command; + +use Symfony\Component\AssetMapper\AssetMapperInterface; +use Symfony\Component\AssetMapper\AssetMapperRepository; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * Outputs all the assets in the asset mapper. + * + * @experimental + * + * @author Ryan Weaver + */ +#[AsCommand(name: 'debug:asset-map', description: 'Outputs all mapped assets.')] +final class DebugAssetMapperCommand extends Command +{ + private bool $didShortenPaths = false; + + public function __construct( + private readonly AssetMapperInterface $assetMapper, + private readonly AssetMapperRepository $assetMapperRepository, + private readonly string $projectDir, + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->addOption('full', null, null, 'Whether to show the full paths') + ->setHelp(<<<'EOT' +The %command.name% command outputs all of the assets in +asset mapper for debugging purposes. +EOT + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $allAssets = $this->assetMapper->allAssets(); + + $pathRows = []; + foreach ($this->assetMapperRepository->allDirectories() as $path => $namespace) { + $path = $this->relativizePath($path); + if (!$input->getOption('full')) { + $path = $this->shortenPath($path); + } + + $pathRows[] = [$path, $namespace]; + } + $io->section('Asset Mapper Paths'); + $io->table(['Path', 'Namespace prefix'], $pathRows); + + $rows = []; + foreach ($allAssets as $asset) { + $logicalPath = $asset->logicalPath; + $sourcePath = $this->relativizePath($asset->getSourcePath()); + + if (!$input->getOption('full')) { + $logicalPath = $this->shortenPath($logicalPath); + $sourcePath = $this->shortenPath($sourcePath); + } + + $rows[] = [ + $logicalPath, + $sourcePath, + ]; + } + $io->section('Mapped Assets'); + $io->table(['Logical Path', 'Filesystem Path'], $rows); + + if ($this->didShortenPaths) { + $io->note('To see the full paths, re-run with the --full option.'); + } + + return 0; + } + + private function relativizePath(string $path): string + { + return str_replace($this->projectDir.'/', '', $path); + } + + private function shortenPath($path): string + { + $limit = 50; + + if (\strlen($path) <= $limit) { + return $path; + } + + $this->didShortenPaths = true; + $limit = floor(($limit - 3) / 2); + + return substr($path, 0, $limit).'...'.substr($path, -$limit); + } +} diff --git a/src/Symfony/Component/AssetMapper/Tests/AssetMapperRepositoryTest.php b/src/Symfony/Component/AssetMapper/Tests/AssetMapperRepositoryTest.php index 3118c9c812646..e92b419fddadb 100644 --- a/src/Symfony/Component/AssetMapper/Tests/AssetMapperRepositoryTest.php +++ b/src/Symfony/Component/AssetMapper/Tests/AssetMapperRepositoryTest.php @@ -23,9 +23,9 @@ public function testFindWithAbsolutePaths() __DIR__.'/fixtures/dir2' => '', ], __DIR__); - $this->assertSame(__DIR__.'/fixtures/dir1/file1.css', $repository->find('file1.css')); - $this->assertSame(__DIR__.'/fixtures/dir2/file4.js', $repository->find('file4.js')); - $this->assertSame(__DIR__.'/fixtures/dir2/subdir/file5.js', $repository->find('subdir/file5.js')); + $this->assertSame(realpath(__DIR__.'/fixtures/dir1/file1.css'), $repository->find('file1.css')); + $this->assertSame(realpath(__DIR__.'/fixtures/dir2/file4.js'), $repository->find('file4.js')); + $this->assertSame(realpath(__DIR__.'/fixtures/dir2/subdir/file5.js'), $repository->find('subdir/file5.js')); $this->assertNull($repository->find('file5.css')); } @@ -36,12 +36,22 @@ public function testFindWithRelativePaths() 'dir2' => '', ], __DIR__.'/fixtures'); - $this->assertSame(__DIR__.'/fixtures/dir1/file1.css', $repository->find('file1.css')); - $this->assertSame(__DIR__.'/fixtures/dir2/file4.js', $repository->find('file4.js')); - $this->assertSame(__DIR__.'/fixtures/dir2/subdir/file5.js', $repository->find('subdir/file5.js')); + $this->assertSame(realpath(__DIR__.'/fixtures/dir1/file1.css'), $repository->find('file1.css')); + $this->assertSame(realpath(__DIR__.'/fixtures/dir2/file4.js'), $repository->find('file4.js')); + $this->assertSame(realpath(__DIR__.'/fixtures/dir2/subdir/file5.js'), $repository->find('subdir/file5.js')); $this->assertNull($repository->find('file5.css')); } + public function testFindWithMovingPaths() + { + $repository = new AssetMapperRepository([ + __DIR__.'/../Tests/fixtures/dir2' => '', + ], __DIR__); + + $this->assertSame(realpath(__DIR__.'/fixtures/dir2/file4.js'), $repository->find('file4.js')); + $this->assertSame(realpath(__DIR__.'/fixtures/dir2/file4.js'), $repository->find('subdir/../file4.js')); + } + public function testFindWithNamespaces() { $repository = new AssetMapperRepository([ @@ -49,9 +59,9 @@ public function testFindWithNamespaces() 'dir2' => 'dir2_namespace', ], __DIR__.'/fixtures'); - $this->assertSame(__DIR__.'/fixtures/dir1/file1.css', $repository->find('dir1_namespace/file1.css')); - $this->assertSame(__DIR__.'/fixtures/dir2/file4.js', $repository->find('dir2_namespace/file4.js')); - $this->assertSame(__DIR__.'/fixtures/dir2/subdir/file5.js', $repository->find('dir2_namespace/subdir/file5.js')); + $this->assertSame(realpath(__DIR__.'/fixtures/dir1/file1.css'), $repository->find('dir1_namespace/file1.css')); + $this->assertSame(realpath(__DIR__.'/fixtures/dir2/file4.js'), $repository->find('dir2_namespace/file4.js')); + $this->assertSame(realpath(__DIR__.'/fixtures/dir2/subdir/file5.js'), $repository->find('dir2_namespace/subdir/file5.js')); // non-namespaced path does not work $this->assertNull($repository->find('file4.js')); } @@ -59,10 +69,12 @@ public function testFindWithNamespaces() public function testFindLogicalPath() { $repository = new AssetMapperRepository([ - 'dir1' => '', + 'dir1' => 'some_namespace', 'dir2' => '', ], __DIR__.'/fixtures'); $this->assertSame('subdir/file5.js', $repository->findLogicalPath(__DIR__.'/fixtures/dir2/subdir/file5.js')); + $this->assertSame('some_namespace/file2.js', $repository->findLogicalPath(__DIR__.'/fixtures/dir1/file2.js')); + $this->assertSame('some_namespace/file2.js', $repository->findLogicalPath(__DIR__.'/../Tests/fixtures/dir1/file2.js')); } public function testAll() @@ -83,8 +95,8 @@ public function testAll() 'already-abcdefVWXYZ0123456789.digested.css' => __DIR__.'/fixtures/dir2/already-abcdefVWXYZ0123456789.digested.css', 'file3.css' => __DIR__.'/fixtures/dir2/file3.css', 'file4.js' => __DIR__.'/fixtures/dir2/file4.js', - 'subdir'.\DIRECTORY_SEPARATOR.'file5.js' => __DIR__.'/fixtures/dir2/subdir/file5.js', - 'subdir'.\DIRECTORY_SEPARATOR.'file6.js' => __DIR__.'/fixtures/dir2/subdir/file6.js', + 'subdir/file5.js' => __DIR__.'/fixtures/dir2/subdir/file5.js', + 'subdir/file6.js' => __DIR__.'/fixtures/dir2/subdir/file6.js', 'test.gif.foo' => __DIR__.'/fixtures/dir3/test.gif.foo', ]); $this->assertEquals($expectedAllAssets, array_map('realpath', $actualAllAssets)); @@ -109,16 +121,10 @@ public function testAllWithNamespaces() 'dir3_namespace/test.gif.foo' => __DIR__.'/fixtures/dir3/test.gif.foo', ]; - $normalizedExpectedAllAssets = []; - foreach ($expectedAllAssets as $key => $val) { - $normalizedExpectedAllAssets[str_replace('/', \DIRECTORY_SEPARATOR, $key)] = realpath($val); - } + $normalizedExpectedAllAssets = array_map('realpath', $expectedAllAssets); $actualAssets = $repository->all(); - $normalizedActualAssets = []; - foreach ($actualAssets as $key => $val) { - $normalizedActualAssets[str_replace('/', \DIRECTORY_SEPARATOR, $key)] = realpath($val); - } + $normalizedActualAssets = array_map('realpath', $actualAssets); $this->assertEquals($normalizedExpectedAllAssets, $normalizedActualAssets); } diff --git a/src/Symfony/Component/AssetMapper/Tests/Command/AssetsMapperCompileCommandTest.php b/src/Symfony/Component/AssetMapper/Tests/Command/AssetsMapperCompileCommandTest.php index ea02d86491d29..810132c7bfe65 100644 --- a/src/Symfony/Component/AssetMapper/Tests/Command/AssetsMapperCompileCommandTest.php +++ b/src/Symfony/Component/AssetMapper/Tests/Command/AssetsMapperCompileCommandTest.php @@ -40,7 +40,7 @@ public function testAssetsAreCompiled() { $application = new Application($this->kernel); - $command = $application->find('assetmap:compile'); + $command = $application->find('asset-map:compile'); $tester = new CommandTester($command); $res = $tester->execute([]); $this->assertSame(0, $res); @@ -59,6 +59,21 @@ public function testAssetsAreCompiled() $finder->in($targetBuildDir)->files(); $this->assertCount(9, $finder); $this->assertFileExists($targetBuildDir.'/manifest.json'); + + $expected = [ + 'file1.css', + 'file2.js', + 'file3.css', + 'subdir/file6.js', + 'subdir/file5.js', + 'file4.js', + 'already-abcdefVWXYZ0123456789.digested.css', + ]; + $actual = array_keys(json_decode(file_get_contents($targetBuildDir.'/manifest.json'), true)); + sort($expected); + sort($actual); + + $this->assertSame($expected, $actual); $this->assertFileExists($targetBuildDir.'/importmap.json'); } } diff --git a/src/Symfony/Component/AssetMapper/Tests/Command/DebugAssetsMapperCommandTest.php b/src/Symfony/Component/AssetMapper/Tests/Command/DebugAssetsMapperCommandTest.php new file mode 100644 index 0000000000000..8f375876078ff --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/Command/DebugAssetsMapperCommandTest.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Command; + +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\Console\Application; +use Symfony\Component\AssetMapper\Tests\fixtures\AssetMapperTestAppKernel; +use Symfony\Component\Console\Tester\CommandTester; + +class DebugAssetsMapperCommandTest extends TestCase +{ + public function testComman E1AD dDumpsInformation() + { + $application = new Application(new AssetMapperTestAppKernel('test', true)); + + $command = $application->find('debug:asset-map'); + $tester = new CommandTester($command); + $res = $tester->execute([]); + $this->assertSame(0, $res); + + $this->assertStringContainsString('dir1', $tester->getDisplay()); + $this->assertStringContainsString('subdir/file6.js', $tester->getDisplay()); + $this->assertStringContainsString('dir2'.\DIRECTORY_SEPARATOR.'subdir'.\DIRECTORY_SEPARATOR.'file6.js', $tester->getDisplay()); + } +} From fe5255bb6d13d1ed00cdb8768780db9c74669e1d Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 5 May 2023 12:56:46 +0200 Subject: [PATCH 36/48] 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 37/48] 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 32dc97a104308eb0235ecc65b82b2ab92e80869a Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 5 May 2023 12:58:31 +0200 Subject: [PATCH 38/48] Remove usage of constant for better consistency across the codebase --- src/Symfony/Component/Scheduler/Command/DebugCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Scheduler/Command/DebugCommand.php b/src/Symfony/Component/Scheduler/Command/DebugCommand.php index 195d4baee9c39..5b00ac43f6013 100644 --- a/src/Symfony/Component/Scheduler/Command/DebugCommand.php +++ b/src/Symfony/Component/Scheduler/Command/DebugCommand.php @@ -69,7 +69,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if (!$names = $input->getArgument('schedule') ?: $this->scheduleNames) { $io->error('No schedules found.'); - return self::FAILURE; + return 2; } foreach ($names as $name) { @@ -88,7 +88,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int ); } - return self::SUCCESS; + return 0; } /** From 3c49177c306bdec8e01320b7cae61330be20f462 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 5 May 2023 15:20:02 +0200 Subject: [PATCH 39/48] [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 40/48] =?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: Wed, 3 May 2023 13:53:46 -0400 Subject: [PATCH 41/48] [AssetMapper] Better public without digest --- .../Component/AssetMapper/AssetMapper.php | 12 ++++++---- .../Component/AssetMapper/MappedAsset.php | 24 +++++++++---------- .../AssetMapper/Tests/AssetMapperTest.php | 3 +++ .../AssetMapper/Tests/MappedAssetTest.php | 16 +++++++++---- 4 files changed, 34 insertions(+), 21 deletions(-) diff --git a/src/Symfony/Component/AssetMapper/AssetMapper.php b/src/Symfony/Component/AssetMapper/AssetMapper.php index f05348d2df68f..85ebea3a08225 100644 --- a/src/Symfony/Component/AssetMapper/AssetMapper.php +++ b/src/Symfony/Component/AssetMapper/AssetMapper.php @@ -138,6 +138,7 @@ public function getAsset(string $logicalPath): ?MappedAsset $asset->setSourcePath($filePath); $asset->setMimeType($this->getMimeType($logicalPath)); + $asset->setPublicPathWithoutDigest($this->getPublicPathWithoutDigest($logicalPath)); $publicPath = $this->getPublicPath($logicalPath); $asset->setPublicPath($publicPath); [$digest, $isPredigested] = $this->getDigest($asset); @@ -202,6 +203,11 @@ public function getPublicPath(string $logicalPath): ?string }, $logicalPath); } + private function getPublicPathWithoutDigest(string $logicalPath): string + { + return $this->publicPrefix.$logicalPath; + } + public static function isPathPredigested(string $path): bool { return 1 === preg_match(self::PREDIGESTED_REGEX, $path); @@ -239,11 +245,7 @@ private function getMimeType(string $logicalPath): ?string $extension = pathinfo($logicalPath, \PATHINFO_EXTENSION); - if (!isset($this->extensionsMap[$extension])) { - throw new \LogicException(sprintf('The file extension "%s" from "%s" does not correspond to any known types in the asset mapper. To support this extension, configure framework.asset_mapper.extensions.', $extension, $logicalPath)); - } - - return $this->extensionsMap[$extension]; + return $this->extensionsMap[$extension] ?? null; } private function calculateContent(MappedAsset $asset): string diff --git a/src/Symfony/Component/AssetMapper/MappedAsset.php b/src/Symfony/Component/AssetMapper/MappedAsset.php index 966dcfe8267b8..e3f31938d6fa0 100644 --- a/src/Symfony/Component/AssetMapper/MappedAsset.php +++ b/src/Symfony/Component/AssetMapper/MappedAsset.php @@ -20,7 +20,8 @@ */ final class MappedAsset { - public string $publicPath; + private string $publicPath; + private string $publicPathWithoutDigest; /** * @var string the filesystem path to the source file */ @@ -88,6 +89,15 @@ public function setPublicPath(string $publicPath): void $this->publicPath = $publicPath; } + public function setPublicPathWithoutDigest(string $publicPathWithoutDigest): void + { + if (isset($this->publicPathWithoutDigest)) { + throw new \LogicException('Cannot set public path without digest: it was already set on the asset.'); + } + + $this->publicPathWithoutDigest = $publicPathWithoutDigest; + } + public function setSourcePath(string $sourcePath): void { if (isset($this->sourcePath)) { @@ -132,16 +142,6 @@ public function addDependency(self $asset, bool $isLazy = false): void public function getPublicPathWithoutDigest(): string { - if ($this->isPredigested()) { - return $this->getPublicPath(); - } - - // remove last part of publicPath and replace with last part of logicalPath - $publicPathParts = explode('/', $this->getPublicPath()); - $logicalPathParts = explode('/', $this->logicalPath); - array_pop($publicPathParts); - $publicPathParts[] = array_pop($logicalPathParts); - - return implode('/', $publicPathParts); + return $this->publicPathWithoutDigest; } } diff --git a/src/Symfony/Component/AssetMapper/Tests/AssetMapperTest.php b/src/Symfony/Component/AssetMapper/Tests/AssetMapperTest.php index 79cf7267135fd..272c07e20c1e0 100644 --- a/src/Symfony/Component/AssetMapper/Tests/AssetMapperTest.php +++ b/src/Symfony/Component/AssetMapper/Tests/AssetMapperTest.php @@ -65,6 +65,7 @@ public function testGetAsset() $asset = $assetMapper->getAsset('file2.js'); $this->assertSame('file2.js', $asset->logicalPath); $this->assertMatchesRegularExpression('/^\/final-assets\/file2-[a-zA-Z0-9]{7,128}\.js$/', $asset->getPublicPath()); + $this->assertSame('/final-assets/file2.js', $asset->getPublicPathWithoutDigest()); } public function testGetAssetRespectsPreDigestedPaths() @@ -73,6 +74,8 @@ public function testGetAssetRespectsPreDigestedPaths() $asset = $assetMapper->getAsset('already-abcdefVWXYZ0123456789.digested.css'); $this->assertSame('already-abcdefVWXYZ0123456789.digested.css', $asset->logicalPath); $this->assertSame('/final-assets/already-abcdefVWXYZ0123456789.digested.css', $asset->getPublicPath()); + // for pre-digested files, the digest *is* part of the public path + $this->assertSame('/final-assets/already-abcdefVWXYZ0123456789.digested.css', $asset->getPublicPathWithoutDigest()); } public function testGetAssetUsesManifestIfAvailable() diff --git a/src/Symfony/Component/AssetMapper/Tests/MappedAssetTest.php b/src/Symfony/Component/AssetMapper/Tests/MappedAssetTest.php index 244c70dd2ad69..d098286f70432 100644 --- a/src/Symfony/Component/AssetMapper/Tests/MappedAssetTest.php +++ b/src/Symfony/Component/AssetMapper/Tests/MappedAssetTest.php @@ -31,6 +31,14 @@ public function testGetPublicPath() $this->assertSame('/assets/foo.1234567.css', $asset->getPublicPath()); } + public function testGetPublicPathWithoutDigest() + { + $asset = new MappedAsset('anything'); + $asset->setPublicPathWithoutDigest('/assets/foo.css'); + + $this->assertSame('/assets/foo.css', $asset->getPublicPathWithoutDigest()); + } + /** * @dataProvider getExtensionTests */ @@ -48,21 +56,21 @@ public static function getExtensionTests(): iterable yield 'with_directory' => ['foo/bar.css', 'css']; } - public function testGetSourcePath(): void + public function testGetSourcePath() { $asset = new MappedAsset('foo.css'); $asset->setSourcePath('/path/to/source.css'); $this->assertSame('/path/to/source.css', $asset->getSourcePath()); } - public function testGetMimeType(): void + public function testGetMimeType() { $asset = new MappedAsset('foo.css'); $asset->setMimeType('text/css'); $this->assertSame('text/css', $asset->getMimeType()); } - public function testGetDigest(): void + public function testGetDigest() { $asset = new MappedAsset('foo.css'); $asset->setDigest('1234567', false); @@ -70,7 +78,7 @@ public function testGetDigest(): void $this->assertFalse($asset->isPredigested()); } - public function testGetContent(): void + public function testGetContent() { $asset = new MappedAsset('foo.css'); $asset->setContent('body { color: red; }'); From 38355735c42eca7853adcbbac367fc0b23e89bd0 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 5 May 2023 18:19:22 +0200 Subject: [PATCH 42/48] [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 d62410aa7acd43f80b75a685534a6aef777745a1 Mon Sep 17 00:00:00 2001 From: Christian Kolb Date: Tue, 28 Feb 2023 07:59:29 +0100 Subject: [PATCH 43/48] [Serializer] Add flag to require all properties to be listed in the input --- src/Symfony/Component/Serializer/CHANGELOG.md | 1 + .../Normalizer/AbstractNormalizerContextBuilder.php | 9 +++++++++ .../Serializer/Normalizer/AbstractNormalizer.php | 8 +++++++- .../AbstractNormalizerContextBuilderTest.php | 3 +++ .../Tests/Normalizer/AbstractNormalizerTest.php | 10 ++++++++++ 5 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md index 692226968f2c0..8154d3688fce8 100644 --- a/src/Symfony/Component/Serializer/CHANGELOG.md +++ b/src/Symfony/Component/Serializer/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 6.3 --- + * Add `AbstractNormalizer::REQUIRE_ALL_PROPERTIES` context flag to require all properties to be listed in the input instead of falling back to null for nullable ones * Add `XmlEncoder::SAVE_OPTIONS` context option * Add `BackedEnumNormalizer::ALLOW_INVALID_VALUES` context option * Add `UnsupportedFormatException` which is thrown when there is no decoder for a given format diff --git a/src/Symfony/Component/Serializer/Context/Normalizer/AbstractNormalizerContextBuilder.php b/src/Symfony/Component/Serializer/Context/Normalizer/AbstractNormalizerContextBuilder.php index 670543540b5aa..ecb328dd651f4 100644 --- a/src/Symfony/Component/Serializer/Context/Normalizer/AbstractNormalizerContextBuilder.php +++ b/src/Symfony/Component/Serializer/Context/Normalizer/AbstractNormalizerContextBuilder.php @@ -164,4 +164,13 @@ public function withIgnoredAttributes(?array $ignoredAttributes): static { return $this->with(AbstractNormalizer::IGNORED_ATTRIBUTES, $ignoredAttributes); } + + /** + * Configures requiring all properties to be listed in the input instead + * of falling back to null for nullable ones. + */ + public function withRequireAllProperties(?bool $requireAllProperties = true): static + { + return $this->with(AbstractNormalizer::REQUIRE_ALL_PROPERTIES, $requireAllProperties); + } } diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php index bd33dbc1def0e..079b1e7a9e9d9 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php @@ -112,6 +112,12 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn */ public const IGNORED_ATTRIBUTES = 'ignored_attributes'; + /** + * Require all properties to be listed in the input instead of falling + * back to null for nullable ones. + */ + public const REQUIRE_ALL_PROPERTIES = 'require_all_properties'; + /** * @internal */ @@ -383,7 +389,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex $params[] = $this->defaultContext[self::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class][$key]; } elseif ($constructorParameter->isDefaultValueAvailable()) { $params[] = $constructorParameter->getDefaultValue(); - } elseif ($constructorParameter->hasType() && $constructorParameter->getType()->allowsNull()) { + } elseif (!($context[self::REQUIRE_ALL_PROPERTIES] ?? $this->defaultContext[self::REQUIRE_ALL_PROPERTIES] ?? false) && $constructorParameter->hasType() && $constructorParameter->getType()->allowsNull()) { $params[] = null; } else { if (!isset($context['not_normalizable_value_exceptions'])) { diff --git a/src/Symfony/Component/Serializer/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php b/src/Symfony/Component/Serializer/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php index 4c36a8ff9b933..158fa8feacf7a 100644 --- a/src/Symfony/Component/Serializer/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php @@ -45,6 +45,7 @@ public function testWithers(array $values) ->withCallbacks($values[AbstractNormalizer::CALLBACKS]) ->withCircularReferenceHandler($values[AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER]) ->withIgnoredAttributes($values[AbstractNormalizer::IGNORED_ATTRIBUTES]) + ->withRequireAllProperties($values[AbstractNormalizer::REQUIRE_ALL_PROPERTIES]) ->toArray(); $this->assertEquals($values, $context); @@ -65,6 +66,7 @@ public static function withersDataProvider(): iterable AbstractNormalizer::CALLBACKS => [static function (): void {}], AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => static function (): void {}, AbstractNormalizer::IGNORED_ATTRIBUTES => ['attribute3'], + AbstractNormalizer::REQUIRE_ALL_PROPERTIES => true, ]]; yield 'With null values' => [[ @@ -77,6 +79,7 @@ public static function withersDataProvider(): iterable AbstractNormalizer::CALLBACKS => null, AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => null, AbstractNormalizer::IGNORED_ATTRIBUTES => null, + AbstractNormalizer::REQUIRE_ALL_PROPERTIES => null, ]]; } diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractNormalizerTest.php index 33f44430591f8..16e39440e56b3 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractNormalizerTest.php @@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException; use Symfony\Component\Serializer\Mapping\AttributeMetadata; use Symfony\Component\Serializer\Mapping\ClassMetadata; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; @@ -166,6 +167,15 @@ public function testObjectWithNullableNonOptionalConstructorArgumentWithoutInput $this->assertNull($dummy->getFoo()); } + public function testObjectWithNullableNonOptionalConstructorArgumentWithoutInputAndRequireAllProperties() + { + $normalizer = new ObjectNormalizer(); + + $this->expectException(MissingConstructorArgumentsException::class); + + $normalizer->denormalize([], NullableConstructorArgumentDummy::class, null, [AbstractNormalizer::REQUIRE_ALL_PROPERTIES => true]); + } + /** * @dataProvider getNormalizer */ From f9f7274da33669613f89ce333c830c2409a6c79b Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Wed, 3 May 2023 13:37:09 -0400 Subject: [PATCH 44/48] [AssetMapper] Fixing 2 bugs related to the compile command and importmaps --- .../Component/AssetMapper/AssetMapper.php | 2 +- .../AssetMapper/AssetMapperRepository.php | 2 +- .../Command/AssetMapperCompileCommand.php | 62 +++++++++++++------ .../ImportMap/ImportMapManager.php | 9 ++- .../AssetsMapperCompileCommandTest.php | 40 ++++++++---- .../Tests/ImportMap/ImportMapManagerTest.php | 3 + .../AssetMapper/Tests/fixtures/importmap.php | 25 ++++++++ .../final-assets/importmap.preload.json | 3 + 8 files changed, 112 insertions(+), 34 deletions(-) create mode 100644 src/Symfony/Component/AssetMapper/Tests/fixtures/importmap.php create mode 100644 src/Symfony/Component/AssetMapper/Tests/fixtures/test_public/final-assets/importmap.preload.json diff --git a/src/Symfony/Component/AssetMapper/AssetMapper.php b/src/Symfony/Component/AssetMapper/AssetMapper.php index 85ebea3a08225..a500f008a5c26 100644 --- a/src/Symfony/Component/AssetMapper/AssetMapper.php +++ b/src/Symfony/Component/AssetMapper/AssetMapper.php @@ -267,7 +267,7 @@ private function loadManifest(): array if (null === $this->manifestData) { $path = $this->getPublicAssetsFilesystemPath().'/'.self::MANIFEST_FILE_NAME; - if (!file_exists($path)) { + if (!is_file($path)) { $this->manifestData = []; } else { $this->manifestData = json_decode(file_get_contents($path), true); diff --git a/src/Symfony/Component/AssetMapper/AssetMapperRepository.php b/src/Symfony/Component/AssetMapper/AssetMapperRepository.php index b7440a9263844..70ef44b000060 100644 --- a/src/Symfony/Component/AssetMapper/AssetMapperRepository.php +++ b/src/Symfony/Component/AssetMapper/AssetMapperRepository.php @@ -54,7 +54,7 @@ public function find(string $logicalPath): ?string } $file = rtrim($path, '/').'/'.$localLogicalPath; - if (file_exists($file)) { + if (is_file($file)) { return realpath($file); } } diff --git a/src/Symfony/Component/AssetMapper/Command/AssetMapperCompileCommand.php b/src/Symfony/Component/AssetMapper/Command/AssetMapperCompileCommand.php index b6ed6c8dc9c65..d0cb9f64631ad 100644 --- a/src/Symfony/Component/AssetMapper/Command/AssetMapperCompileCommand.php +++ b/src/Symfony/Component/AssetMapper/Command/AssetMapperCompileCommand.php @@ -66,13 +66,54 @@ protected function execute(InputInterface $input, OutputInterface $output): int throw new InvalidArgumentException(sprintf('The public directory "%s" does not exist.', $publicDir)); } + $outputDir = $publicDir.$this->assetMapper->getPublicPrefix(); if ($input->getOption('clean')) { - $outputDir = $publicDir.$this->assetMapper->getPublicPrefix(); $io->comment(sprintf('Cleaning %s', $outputDir)); $this->filesystem->remove($outputDir); $this->filesystem->mkdir($outputDir); } + $manifestPath = $publicDir.$this->assetMapper->getPublicPrefix().AssetMapper::MANIFEST_FILE_NAME; + if (is_file($manifestPath)) { + $this->filesystem->remove($manifestPath); + } + $manifest = $this->createManifestAndWriteFiles($io, $publicDir); + $this->filesystem->dumpFile($manifestPath, json_encode($manifest, \JSON_PRETTY_PRINT)); + $io->comment(sprintf('Manifest written to %s', $manifestPath)); + + $importMapPath = $outputDir.ImportMapManager::IMPORT_MAP_FILE_NAME; + if (is_file($importMapPath)) { + $this->filesystem->remove($importMapPath); + } + $this->filesystem->dumpFile($importMapPath, $this->importMapManager->getImportMapJson()); + + $importMapPreloadPath = $outputDir.ImportMapManager::IMPORT_MAP_PRELOAD_FILE_NAME; + if (is_file($importMapPreloadPath)) { + $this->filesystem->remove($importMapPreloadPath); + } + $this->filesystem->dumpFile( + $importMapPreloadPath, + json_encode($this->importMapManager->getModulesToPreload(), \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES) + ); + $io->comment(sprintf('Import map written to %s and %s for quick importmap dumping onto the page.', $this->shortenPath($importMapPath), $this->shortenPath($importMapPreloadPath))); + + if ($this->isDebug) { + $io->warning(sprintf( + 'You are compiling assets in development. Symfony will not serve any changed assets until you delete the "%s" directory.', + $this->shortenPath($outputDir) + )); + } + + return 0; + } + + private function shortenPath(string $path): string + { + return str_replace($this->projectDir.'/', '', $path); + } + + private function createManifestAndWriteFiles(SymfonyStyle $io, string $publicDir): array + { $allAssets = $this->assetMapper->allAssets(); $io->comment(sprintf('Compiling %d assets to %s%s', \count($allAssets), $publicDir, $this->assetMapper->getPublicPrefix())); @@ -88,23 +129,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->filesystem->dumpFile($targetPath, $asset->getContent()); $manifest[$asset->logicalPath] = $asset->getPublicPath(); } + ksort($manifest); - $manifestPath = $publicDir.$this->assetMapper->getPublicPrefix().AssetMapper::MANIFEST_FILE_NAME; - $this->filesystem->dumpFile($manifestPath, json_encode($manifest, \JSON_PRETTY_PRINT)); - $io->comment(sprintf('Manifest written to %s', $manifestPath)); - - $importMapPath = $publicDir.$this->assetMapper->getPublicPrefix().ImportMapManager::IMPORT_MAP_FILE_NAME; - $this->filesystem->dumpFile($importMapPath, $this->importMapManager->getImportMapJson()); - $io->comment(sprintf('Import map written to %s', $importMapPath)); - - if ($this->isDebug) { - $io->warning(sprintf( - 'You are compiling assets in development. Symfony will not serve any changed assets until you delete %s and %s.', - $manifestPath, - $importMapPath - )); - } - - return 0; + return $manifest; } } diff --git a/src/Symfony/Component/AssetMapper/ImportMap/ImportMapManager.php b/src/Symfony/Component/AssetMapper/ImportMap/ImportMapManager.php index 27b5dab82b103..d71496f4a2281 100644 --- a/src/Symfony/Component/AssetMapper/ImportMap/ImportMapManager.php +++ b/src/Symfony/Component/AssetMapper/ImportMap/ImportMapManager.php @@ -49,6 +49,7 @@ class ImportMapManager */ private const PACKAGE_PATTERN = '/^(?:https?:\/\/[\w\.-]+\/)?(?:(?\w+):)?(?(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*)(?:@(?[\w\._-]+))?(?:(?\/.*))?$/'; public const IMPORT_MAP_FILE_NAME = 'importmap.json'; + public const IMPORT_MAP_PRELOAD_FILE_NAME = 'importmap.preload.json'; private array $importMapEntries; private array $modulesToPreload; @@ -125,9 +126,11 @@ private function buildImportMapJson(): void return; } - $dumpedPath = $this->assetMapper->getPublicAssetsFilesystemPath().'/'.self::IMPORT_MAP_FILE_NAME; - if (file_exists($dumpedPath)) { - $this->json = file_get_contents($dumpedPath); + $dumpedImportMapPath = $this->assetMapper->getPublicAssetsFilesystemPath().'/'.self::IMPORT_MAP_FILE_NAME; + $dumpedModulePreloadPath = $this->assetMapper->getPublicAssetsFilesystemPath().'/'.self::IMPORT_MAP_PRELOAD_FILE_NAME; + if (is_file($dumpedImportMapPath) && is_file($dumpedModulePreloadPath)) { + $this->json = file_get_contents($dumpedImportMapPath); + $this->modulesToPreload = json_decode(file_get_contents($dumpedModulePreloadPath), true, 512, \JSON_THROW_ON_ERROR); return; } diff --git a/src/Symfony/Component/AssetMapper/Tests/Command/AssetsMapperCompileCommandTest.php b/src/Symfony/Component/AssetMapper/Tests/Command/AssetsMapperCompileCommandTest.php index 810132c7bfe65..6475a9110276f 100644 --- a/src/Symfony/Component/AssetMapper/Tests/Command/AssetsMapperCompileCommandTest.php +++ b/src/Symfony/Component/AssetMapper/Tests/Command/AssetsMapperCompileCommandTest.php @@ -40,6 +40,13 @@ public function testAssetsAreCompiled() { $application = new Application($this->kernel); + $targetBuildDir = $this->kernel->getProjectDir().'/public/assets'; + // put old "built" versions to make sure the system skips using these + $this->filesystem->mkdir($targetBuildDir); + file_put_contents($targetBuildDir.'/manifest.json', '{}'); + file_put_contents($targetBuildDir.'/importmap.json', '{"imports": {}}'); + file_put_contents($targetBuildDir.'/importmap.preload.json', '{}'); + $command = $application->find('asset-map:compile'); $tester = new CommandTester($command); $res = $tester->execute([]); @@ -47,7 +54,6 @@ public function testAssetsAreCompiled() // match Compiling \d+ assets $this->assertMatchesRegularExpression('/Compiling \d+ assets/', $tester->getDisplay()); - $targetBuildDir = $this->kernel->getProjectDir().'/public/assets'; $this->assertFileExists($targetBuildDir.'/subdir/file5-f4fdc37375c7f5f2629c5659a0579967.js'); $this->assertSame(<<in($targetBuildDir)->files(); - $this->assertCount(9, $finder); + $this->assertCount(10, $finder); $this->assertFileExists($targetBuildDir.'/manifest.json'); - $expected = [ + $this->assertSame([ + 'already-abcdefVWXYZ0123456789.digested.css', 'file1.css', 'file2.js', 'file3.css', - 'subdir/file6.js', - 'subdir/file5.js', 'file4.js', - 'already-abcdefVWXYZ0123456789.digested.css', - ]; - $actual = array_keys(json_decode(file_get_contents($targetBuildDir.'/manifest.json'), true)); - sort($expected); - sort($actual); + 'subdir/file5.js', + 'subdir/file6.js', + ], array_keys(json_decode(file_get_contents($targetBuildDir.'/manifest.json'), true))); - $this->assertSame($expected, $actual); $this->assertFileExists($targetBuildDir.'/importmap.json'); + $actualImportMap = json_decode(file_get_contents($targetBuildDir.'/importmap.json'), true); + $this->assertSame([ + '@hotwired/stimulus', + 'lodash', + 'file6', + '/assets/subdir/file5.js', // imported by file6 + '/assets/file4.js', // imported by file5 + ], array_keys($actualImportMap['imports'])); + + $this->assertFileExists($targetBuildDir.'/importmap.preload.json'); + $actualPreload = json_decode(file_get_contents($targetBuildDir.'/importmap.preload.json'), true); + $this->assertCount(4, $actualPreload); + $this->assertStringStartsWith('https://unpkg.com/@hotwired/stimulus', $actualPreload[0]); + $this->assertStringStartsWith('/assets/subdir/file6-', $actualPreload[1]); + $this->assertStringStartsWith('/assets/subdir/file5-', $actualPreload[2]); + $this->assertStringStartsWith('/assets/file4-', $actualPreload[3]); } } diff --git a/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapManagerTest.php b/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapManagerTest.php index 715c86edd0692..e47a5f233123b 100644 --- a/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapManagerTest.php +++ b/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapManagerTest.php @@ -84,6 +84,9 @@ public function testGetImportMapJsonUsesDumpedFile() '@hotwired/stimulus' => 'https://unpkg.com/@hotwired/stimulus@3.2.1/dist/stimulus.js', 'app' => '/assets/app-ea9ebe6156adc038aba53164e2be0867.js', ]], json_decode($manager->getImportMapJson(), true)); + $this->assertEquals([ + '/assets/app-ea9ebe6156adc038aba53164e2be0867.js', + ], $manager->getModulesToPreload()); } /** diff --git a/src/Symfony/Component/AssetMapper/Tests/fixtures/importmap.php b/src/Symfony/Component/AssetMapper/Tests/fixtures/importmap.php new file mode 100644 index 0000000000000..9806750ba2413 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/fixtures/importmap.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + '@hotwired/stimulus' => [ + 'url' => 'https://unpkg.com/@hotwired/stimulus@3.2.1/dist/stimulus.js', + 'preload' => true, + ], + 'lodash' => [ + 'url' => 'https://ga.jspm.io/npm:lodash@4.17.21/lodash.js', + 'preload' => false, + ], + 'file6' => [ + 'path' => 'subdir/file6.js', + 'preload' => true, + ], +]; diff --git a/src/Symfony/Component/AssetMapper/Tests/fixtures/test_public/final-assets/importmap.preload.json b/src/Symfony/Component/AssetMapper/Tests/fixtures/test_public/final-assets/importmap.preload.json new file mode 100644 index 0000000000000..ae6114c616115 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/fixtures/test_public/final-assets/importmap.preload.json @@ -0,0 +1,3 @@ +[ + "/assets/app-ea9ebe6156adc038aba53164e2be0867.js" +] From c05f4849bd80cc80e1d537c749f737bda458c4e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sama=C3=ABl=20Villette?= Date: Sat, 6 May 2023 11:53:23 +0200 Subject: [PATCH 45/48] fix(twig-bundle): fixed wrong `symfony/twig-bridge` dependency --- src/Symfony/Bundle/TwigBundle/composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/TwigBundle/composer.json b/src/Symfony/Bundle/TwigBundle/composer.json index e7297860ff44b..af4b61e9c2cbf 100644 --- a/src/Symfony/Bundle/TwigBundle/composer.json +++ b/src/Symfony/Bundle/TwigBundle/composer.json @@ -20,7 +20,7 @@ "composer-runtime-api": ">=2.1", "symfony/config": "^6.1", "symfony/dependency-injection": "^6.1", - "symfony/twig-bridge": "^6.2", + "symfony/twig-bridge": "^6.3", "symfony/http-foundation": "^5.4|^6.0", "symfony/http-kernel": "^6.2", "twig/twig": "^2.13|^3.0.4" From f34af1c1e71f01922860854b7920a530a30d51ce Mon Sep 17 00:00:00 2001 From: MatTheCat Date: Sat, 6 May 2023 12:35:46 +0200 Subject: [PATCH 46/48] =?UTF-8?q?Explicit=20tab=20controls=E2=80=99=20colo?= =?UTF-8?q?r=20as=20they=20can=20be=20buttons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WebProfilerBundle/Resources/views/Profiler/profiler.css.twig | 1 + 1 file changed, 1 insertion(+) 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 16664e3831166..45d8ce3e2a751 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig @@ -1463,6 +1463,7 @@ tr.status-warning td { } .tab-navigation .tab-control { background: transparent; + color: inherit; border: 0; box-shadow: none; transition: box-shadow .05s ease-in, background-color .05s ease-in; From 9d8b3480d0f1dfc0804969d081704a7c39be3078 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 7 May 2023 13:56:58 +0200 Subject: [PATCH 47/48] Update CHANGELOG for 6.3.0-BETA2 --- CHANGELOG-6.3.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/CHANGELOG-6.3.md b/CHANGELOG-6.3.md index 44d759f00f7bf..d69715ff263a1 100644 --- a/CHANGELOG-6.3.md +++ b/CHANGELOG-6.3.md @@ -7,6 +7,36 @@ in 6.3 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.3.0...v6.3.1 +* 6.3.0-BETA2 (2023-05-07) + + * bug #50249 [WebProfilerBundle] Explicit tab controls’ color as they can be buttons (MatTheCat) + * bug #50248 [TwigBundle] fixed wrong `symfony/twig-bridge` dependency version (SVillette) + * bug #50231 [AssetMapper] Fixing 2 bugs related to the compile command and importmaps (weaverryan) + * feature #49553 [Serializer] Add flag to require all properties to be listed in the input (Christian Kolb) + * feature #50232 [AssetMapper] Better public without digest (weaverryan) + * 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) + * feature #49608 [OptionsResolver] add `ignoreUndefined()` method to allow skip not interesting options (Constantine Shtompel) + * bug #50216 [DependencyInjection] Allow `AutowireCallable` without method (derrabus) + * 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 #50224 [DoctrineBridge] skip subscriber if listener already defined (alli83) + * bug #50218 Profiler respect stateless attribute (alamirault) + * bug #50242 [ErrorHandler] Fix the design of the exception page tabs (javiereguiluz) + * feature #50219 [AssetMapper] Adding debug:assetmap command + normalize paths (weaverryan) + * bug #49760 [Serializer] Add missing withSaveOptions method to XmlEncoderContextBuilder (mtarld) + * bug #50226 [HttpClient] Ensure HttplugClient ignores invalid HTTP headers (nicolas-grekas) + * bug #50125 [HttpKernel] Fix handling of `MapRequest*` attributes (nicolas-grekas) + * bug #50215 [AssetMapper] Fixing wrong values being output in command (weaverryan) + * bug #50203 [Messenger] Fix registering message handlers (nicolas-grekas) + * bug #50204 [ErrorHandler] Skip Httplug deprecations for HttplugClient (nicolas-grekas) + * bug #50206 [AssetMapper] Fix import map package parsing with an @ namespace (weaverryan) + * 6.3.0-BETA1 (2023-05-01) * feature #49729 [Scheduler] Add a simple Scheduler class for when the component is used standalone (fabpot) From 335f0dc37dabac0b7e6771e031b1aab1d09a7c67 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 7 May 2023 13:57:03 +0200 Subject: [PATCH 48/48] Update VERSION for 6.3.0-BETA2 --- 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 f9a969b659130..34db55f198f99 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -76,12 +76,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl */ private static array $freshCache = []; - public const VERSION = '6.3.0-DEV'; + public const VERSION = '6.3.0-BETA2'; public const VERSION_ID = 60300; public const MAJOR_VERSION = 6; public const MINOR_VERSION = 3; public const RELEASE_VERSION = 0; - public const EXTRA_VERSION = 'DEV'; + public const EXTRA_VERSION = 'BETA2'; public const END_OF_MAINTENANCE = '01/2024'; public const END_OF_LIFE = '01/2024'; 0
LocaleLocaleFallback localeDomainDomain Times used Message ID Message Preview
{{ message.locale }}{{ message.fallbackLocale|default('-') }}