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 allows to get more information - Andrei Igna - azine - Wojciech Zimoń + - Vladimir Melnik - Pierre Tachoire - Dawid Sajdak - Ludek Stepan @@ -2378,6 +2397,7 @@ The Symfony Connect username in parenthesis allows to get more information - karolsojko - Marco Jantke - Saem Ghani + - Claudiu Cristea - Zacharias Luiten - Sebastian Utz - Adrien Gallou (agallou) @@ -2462,6 +2482,8 @@ The Symfony Connect username in parenthesis allows to get more information - Max Summe - Ema Panz - Chihiro Adachi (chihiro-adachi) + - Thomas Trautner (thomastr) + - mfettig - Raphaëll Roussel - Tadcka - Abudarham Yuval @@ -2490,6 +2512,7 @@ The Symfony Connect username in parenthesis allows to get more information - Nicolas Eeckeloo (neeckeloo) - Andriy Prokopenko (sleepyboy) - Dariusz Ruminski + - Starfox64 - Thomas Hanke - Daniel Tschinder - Arnaud CHASSEUX @@ -2547,6 +2570,7 @@ The Symfony Connect username in parenthesis allows to get more information - Simon Neidhold - Valentin VALCIU - Jeremiah VALERIE + - Cas van Dongen - Patrik Patie Gmitter - Yannick Snobbert - Kevin Dew @@ -2574,8 +2598,8 @@ The Symfony Connect username in parenthesis allows to get more information - Kirk Madera - Keith Maika - Mephistofeles + - Oleh Korneliuk - Hoffmann András - - Cédric Anne - LubenZA - Flavian Sierk - Rik van der Heijden @@ -2595,6 +2619,7 @@ The Symfony Connect username in parenthesis allows to get more information - Olivier Scherler (oscherler) - Shane Preece (shane) - Johannes Goslar + - Mike Gladysch - Geoff - georaldc - wusuopu @@ -2667,6 +2692,7 @@ The Symfony Connect username in parenthesis allows to get more information - Dan Blows - Matt Wells - Nicolas Appriou + - Javier Alfonso Bellota de Frutos - stloyd - Andreas - Chris Tickner @@ -2826,11 +2852,13 @@ The Symfony Connect username in parenthesis allows to get more information - Michael van Tricht - ReScO - Tim Strehle + - cay89 - Sam Ward - Hans N. Hjort - Walther Lalk - Adam - Ivo + - Markus Staab - Sören Bernstein - michael.kubovic - devel @@ -2918,6 +2946,7 @@ The Symfony Connect username in parenthesis allows to get more information - Götz Gottwald - Adrien Peyre - Christoph Krapp + - andreyserdjuk - Nick Chiu - Robert Campbell - Matt Lehner @@ -2939,7 +2968,6 @@ The Symfony Connect username in parenthesis allows to get more information - Alessandro Loffredo - Ian Phillips - Remi Collet - - Nicolas Dousson - Haritz - Matthieu Prat - Brieuc Thomas @@ -2964,6 +2992,7 @@ The Symfony Connect username in parenthesis allows to get more information - tourze - Erik van Wingerden - Valouleloup + - Roland Franssen :) - Alexis MARQUIS - Matheus Gontijo - Gerrit Drost @@ -2998,7 +3027,6 @@ The Symfony Connect username in parenthesis allows to get more information - Tomáš Polívka (draczris) - Dennis Smink (dsmink) - Franz Liedke (franzliedke) - - Alex (garrett) - Gaylord Poillon (gaylord_p) - gondo (gondo) - Joris Garonian (grifx) @@ -3034,6 +3062,7 @@ The Symfony Connect username in parenthesis allows to get more information - Yorkie Chadwick (yorkie76) - Pavel Barton - GuillaumeVerdon + - Marien Fressinaud - ureimers - akimsko - Youpie @@ -3175,8 +3204,8 @@ The Symfony Connect username in parenthesis allows to get more information - Arrilot - andrey-tech - Shaun Simmons - - Markus Staab - Pierre-Louis LAUNAY + - A. Pauly - djama - Michael Gwynne - Eduardo Conceição @@ -3242,6 +3271,7 @@ The Symfony Connect username in parenthesis allows to get more information - Alfonso Fernández García - phc - Дмитрий Пацура + - db306 - Michaël VEROUX - Julia - Lin Lu From 5c68164c6fcb76c372c82b7a03a590d990a007f6 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 28 Apr 2023 15:29:52 +0200 Subject: [PATCH 05/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 function 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 = ''): 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 testCommandDumpsInformation() + { + $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'; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy

LocaleLocaleFallback localeDomainDomain Times used Message ID Message Preview
{{ message.locale }}{{ message.fallbackLocale|default('-') }}