diff --git a/CHANGELOG-6.3.md b/CHANGELOG-6.3.md index 44d759f00f7b..d69715ff263a 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) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 4f274c985c33..24afd6490714 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 diff --git a/composer.json b/composer.json index 3f1cfb489f29..4f81d054cd00 100644 --- a/composer.json +++ b/composer.json @@ -141,7 +141,9 @@ "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", "phpstan/phpdoc-parser": "^1.0", "predis/predis": "^1.1|^2.0", @@ -170,6 +172,7 @@ }, "config": { "allow-plugins": { + "php-http/discovery": false, "symfony/runtime": true } }, diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php index 16a37b524acc..b6946cc4dec5 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 02cd5acf0365..d2b3473fba88 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); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolInvalidateTagsCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolInvalidateTagsCommand.php index a69624c8372c..9e6ef9330e24 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 1; } $io->success('Successfully invalidated cache tags.'); - return self::SUCCESS; + return 0; } public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 7acee5c278e0..668f2be32241 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -381,7 +381,8 @@ public function load(array $configs, ContainerBuilder $container) ); $container->getDefinition('argument_resolver.request_payload') - ->replaceArgument(0, new Reference('.argument_resolver.request_payload.no_serializer', ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE)); + ->replaceArgument(0, new Reference('.argument_resolver.request_payload.no_serializer', ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE)) + ->clearTag('kernel.event_subscriber'); $container->removeDefinition('console.command.serializer_debug'); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/asset_mapper.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/asset_mapper.php index 5d471ea62325..807cb77fc3a8 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/Bundle/FrameworkBundle/Resources/config/web.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php index 1044ded97472..a3a6ef773561 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php @@ -70,6 +70,8 @@ service('translator')->nullOnInvalid(), ]) ->tag('controller.targeted_value_resolver', ['name' => RequestPayloadValueResolver::class]) + ->tag('kernel.event_subscriber') + ->lazy() ->set('argument_resolver.request_attribute', RequestAttributeValueResolver::class) ->tag('controller.argument_value_resolver', ['priority' => 100, 'name' => RequestAttributeValueResolver::class]) diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSecurityVotersPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSecurityVotersPass.php index ccf474087af5..8a2bad79a140 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 62e1c9cfcf72..b4c2009584f5 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/Bundle/TwigBundle/composer.json b/src/Symfony/Bundle/TwigBundle/composer.json index e7297860ff44..af4b61e9c2cb 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" diff --git a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php index b9a861df38dd..4f0e052226c9 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); diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig index c8190f5bfec6..eeb8a06a88de 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig @@ -153,22 +153,20 @@ - - {% endblock messages %} {% endif %} {% endblock %} {% macro render_table(messages, is_fallback) %} - +
- + {% if is_fallback %} {% endif %} - + @@ -176,7 +174,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 b706620496b7..45d8ce3e2a75 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; @@ -1552,42 +1553,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; } - {# Badges ========================================================================= #} .badge { diff --git a/src/Symfony/Component/AssetMapper/AssetMapper.php b/src/Symfony/Component/AssetMapper/AssetMapper.php index f05348d2df68..a500f008a5c2 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 @@ -265,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 87c165ccf23e..70ef44b00006 100644 --- a/src/Symfony/Component/AssetMapper/AssetMapperRepository.php +++ b/src/Symfony/Component/AssetMapper/AssetMapperRepository.php @@ -54,8 +54,8 @@ public function find(string $logicalPath): ?string } $file = rtrim($path, '/').'/'.$localLogicalPath; - if (file_exists($file)) { - return $file; + if (is_file($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 6c08da1c7d68..d0cb9f64631a 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( @@ -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 self::SUCCESS; + return $manifest; } } diff --git a/src/Symfony/Component/AssetMapper/Command/DebugAssetMapperCommand.php b/src/Symfony/Component/AssetMapper/Command/DebugAssetMapperCommand.php new file mode 100644 index 000000000000..54b2e7e98038 --- /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/Command/ImportMapRequireCommand.php b/src/Symfony/Component/AssetMapper/Command/ImportMapRequireCommand.php index 529a90bca7f5..88efaccdab50 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]; diff --git a/src/Symfony/Component/AssetMapper/Compiler/AssetCompilerInterface.php b/src/Symfony/Component/AssetMapper/Compiler/AssetCompilerInterface.php index 247161dffa32..52dfe0e88046 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 f252bb7bb9ab..ddb82c77d59c 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 31dab5a79c7e..643cf233045b 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 1dbbf34d28d9..d71496f4a228 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 @@ -48,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; @@ -81,6 +83,7 @@ public function getImportMapJson(): string * Adds or updates packages. * * @param PackageRequireOptions[] $packages + * * @return ImportMapEntry[] */ public function require(array $packages): array @@ -111,8 +114,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; } @@ -123,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; } @@ -143,7 +148,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 +208,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 +222,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 +249,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 +257,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 08c75f52771b..7a5dc4300100 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 7d09bd3acb6f..e3f31938d6fa 100644 --- a/src/Symfony/Component/AssetMapper/MappedAsset.php +++ b/src/Symfony/Component/AssetMapper/MappedAsset.php @@ -20,16 +20,17 @@ */ final class MappedAsset { - public string $publicPath; + private string $publicPath; + private string $publicPathWithoutDigest; /** - * @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) @@ -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)) { @@ -125,23 +135,13 @@ 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); } 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/AssetMapperRepositoryTest.php b/src/Symfony/Component/AssetMapper/Tests/AssetMapperRepositoryTest.php index f2110f1e2a2a..e92b419fddad 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/AssetMapperTest.php b/src/Symfony/Component/AssetMapper/Tests/AssetMapperTest.php index 79cf7267135f..272c07e20c1e 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/Command/AssetsMapperCompileCommandTest.php b/src/Symfony/Component/AssetMapper/Tests/Command/AssetsMapperCompileCommandTest.php index 3bbaac82d723..6475a9110276 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; @@ -41,14 +40,20 @@ public function testAssetsAreCompiled() { $application = new Application($this->kernel); - $command = $application->find('assetmap:compile'); + $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([]); $this->assertSame(0, $res); // 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'); + + $this->assertSame([ + 'already-abcdefVWXYZ0123456789.digested.css', + 'file1.css', + 'file2.js', + 'file3.css', + 'file4.js', + 'subdir/file5.js', + 'subdir/file6.js', + ], array_keys(json_decode(file_get_contents($targetBuildDir.'/manifest.json'), true))); + $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/Command/DebugAssetsMapperCommandTest.php b/src/Symfony/Component/AssetMapper/Tests/Command/DebugAssetsMapperCommandTest.php new file mode 100644 index 000000000000..8f375876078f --- /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()); + } +} diff --git a/src/Symfony/Component/AssetMapper/Tests/Compiler/JavaScriptImportPathCompilerTest.php b/src/Symfony/Component/AssetMapper/Tests/Compiler/JavaScriptImportPathCompilerTest.php index da34a4ef5a8e..d1d2fffdb876 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 4c75084c8681..e47a5f233123 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' ); @@ -85,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()); } /** @@ -92,7 +94,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 +122,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 +142,7 @@ public static function getRequirePackageTests(): iterable 'expectedImportMap' => [ 'lodash' => [ 'url' => 'https://ga.jspm.io/npm:lodash@1.2.3/lodash.js', - ] + ], ], 'expectedDownloadedFiles' => [], ]; @@ -163,7 +165,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 +260,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 +291,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 +303,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 +344,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 +381,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); } @@ -422,6 +424,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 @@ -431,8 +467,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 7742e9e7841a..2df0be10449a 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('
LocaleLocaleFallback localeDomainDomain Times used Message ID Message Preview
{{ message.locale }}{{ message.fallbackLocale|default('-') }}