Merge branch '5.0'

* 5.0: (38 commits)
  [Security] Check UserInterface::getPassword is not null before calling needsRehash
  gracefully handle missing event dispatchers
  Fix TokenStorage::reset not called in stateless firewall
  [DotEnv] Remove `usePutEnv` property default value
  [HttpFoundation] get currently session.gc_maxlifetime if ttl doesnt exists
  Set up typo fix
  [DependencyInjection] Handle env var placeholders in CheckTypeDeclarationsPass
  [Cache] fix memory leak when using PhpArrayAdapter
  [Validator] Allow underscore character "_" in URL username and password
  [TwigBridge] Update bootstrap_4_layout.html.twig
  [DoctrineBridge] Removed QueryBuilder type hint in getLoader()
  [FrameworkBundle][SodiumVault] Create secrets directory only when needed
  fix parsing negative octal numbers
  [String] implement __sleep()/__wakeup() on strings
  Fixed translations file dumper behavior
  [Routing][ObjectLoader] Remove forgotten deprecation after merge
  [SecurityBundle] Passwords are not encoded when algorithm set to \"true\"
  [DependencyInjection] Resolve expressions in CheckTypeDeclarationsPass
  [SecurityBundle] Properly escape regex in AddSessionDomainConstraintPass
  do not validate passwords when the hash is null
  ...
This commit is contained in:
Nicolas Grekas 2019-12-07 17:42:39 +01:00
commit ae00ff4cfa
51 changed files with 652 additions and 143 deletions

View File

@ -7,6 +7,46 @@ in 4.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/v4.3.0...v4.3.1
* 4.3.9 (2019-12-01)
* bug #34649 more robust initialization from request (dbu)
* bug #34671 [Security] Fix clearing remember-me cookie after deauthentication (chalasr)
* bug #34711 Fix the translation commands when a template contains a syntax error (fabpot)
* bug #34560 [Config][ReflectionClassResource] Handle parameters with undefined constant as their default values (fancyweb)
* bug #34695 [Config] don't break on virtual stack frames in ClassExistenceResource (nicolas-grekas)
* bug #34716 [DependencyInjection] fix dumping number-like string parameters (xabbuh)
* bug #34558 [Console] Fix autocomplete multibyte input support (fancyweb)
* bug #34130 [Console] Fix commands description with numeric namespaces (fancyweb)
* bug #34677 [EventDispatcher] Better error reporting when arguments to dispatch() are swapped (rimas-kudelis)
* bug #33573 [TwigBridge] Add row_attr to all form themes (fancyweb)
* bug #34019 [Serializer] CsvEncoder::NO_HEADERS_KEY ignored when used in constructor (Dario Savella)
* bug #34083 [Form] Keep preferred_choices order for choice groups (vilius-g)
* bug #34091 [Debug] work around failing chdir() on Darwin (mary2501)
* bug #34305 [PhpUnitBridge] Read configuration CLI directive (ro0NL)
* bug #34490 [Serializer] Fix MetadataAwareNameConverter usage with string group (antograssiot)
* bug #34632 [Console] Fix trying to access array offset on value of type int (Tavafi)
* bug #34669 [HttpClient] turn exception into log when the request has no content-type (nicolas-grekas)
* bug #34636 [VarDumper] notice on potential undefined index (sylvainmetayer)
* bug #34668 [Cache] Make sure we get the correct number of values from redis::mget() (thePanz)
* bug #34569 [Workflow] Apply the same logic of precedence between the apply() and the buildTransitionBlockerList() method (lyrixx)
* bug #34533 [Monolog Bridge] Fixed accessing static property as non static. (Sander-Toonen)
* bug #34546 [Serializer] Add DateTimeZoneNormalizer into Dependency Injection (jewome62)
* bug #34547 [Messenger] Error when specified default bus is not among the configured (vudaltsov)
* bug #34551 [Security] SwitchUser is broken when the User Provider always returns a valid user (tucksaun)
* bug #34385 Avoid empty "If-Modified-Since" header in validation request (mpdude)
* bug #34458 [Validator] ConstraintValidatorTestCase: add missing return value to mocked validate method calls (ogizanagi)
* bug #34451 [DependencyInjection] Fix dumping multiple deprecated aliases (shyim)
* bug #34448 [Form] allow button names to start with uppercase letter (xabbuh)
* bug #34419 [Cache] Disable igbinary on PHP >= 7.4 (nicolas-grekas)
* bug #34366 [HttpFoundation] Allow redirecting to URLs that contain a semicolon (JayBizzle)
* bug #34397 [FrameworkBundle] Remove project dir from Translator cache vary scanned directories (fancyweb)
* bug #34408 [Cache] catch exceptions when using PDO directly (xabbuh)
* bug #34410 [HttpFoundation] Fix MySQL column type definition. (jbroutier)
* bug #34398 [Config] fix id-generation for GlobResource (nicolas-grekas)
* bug #34396 [Finder] Allow ssh2 stream wrapper for sftp (damienalexandre)
* bug #34383 [DI] Use reproducible entropy to generate env placeholders (nicolas-grekas)
* bug #34381 [WebProfilerBundle] Require symfony/twig-bundle (fancyweb)
* 4.3.8 (2019-11-13)
* bug #34344 [Console] Constant STDOUT might be undefined (nicolas-grekas)

View File

@ -19,8 +19,8 @@ Symfony is the result of the work of many people who made the code better
- Jakub Zalas (jakubzalas)
- Javier Eguiluz (javier.eguiluz)
- Roland Franssen (ro0)
- Johannes S (johannes)
- Grégoire Pineau (lyrixx)
- Johannes S (johannes)
- Kris Wallsmith (kriswallsmith)
- Yonel Ceruto (yonelceruto)
- Hugo Hamon (hhamon)
@ -36,13 +36,13 @@ Symfony is the result of the work of many people who made the code better
- Martin Hasoň (hason)
- Hamza Amrouche (simperfit)
- Jeremy Mikola (jmikola)
- Jean-François Simon (jfsimon)
- Jules Pietri (heah)
- Jean-François Simon (jfsimon)
- Benjamin Eberlei (beberlei)
- Igor Wiedler (igorw)
- Jérémy DERUSSÉ (jderusse)
- Eriksen Costa (eriksencosta)
- Thomas Calvet (fancyweb)
- Eriksen Costa (eriksencosta)
- Guilhem Niot (energetick)
- Sarah Khalil (saro0h)
- Tobias Nyholm (tobias)
@ -57,15 +57,15 @@ Symfony is the result of the work of many people who made the code better
- Francis Besset (francisbesset)
- stealth35 (stealth35)
- Alexander Mols (asm89)
- Konstantin Myakshin (koc)
- Matthias Pigulla (mpdude)
- Konstantin Myakshin (koc)
- Bulat Shakirzyanov (avalanche123)
- Valentin Udaltsov (vudaltsov)
- Grégoire Paris (greg0ire)
- Saša Stamenković (umpirsky)
- Peter Rehm (rpet)
- Kevin Bond (kbond)
- Henrik Bjørnskov (henrikbjorn)
- Valentin Udaltsov (vudaltsov)
- Miha Vrhovnik
- Diego Saint Esteben (dii3g0)
- Gábor Egyed (1ed)
@ -91,11 +91,11 @@ Symfony is the result of the work of many people who made the code better
- Henrik Westphal (snc)
- Dariusz Górecki (canni)
- David Buchmann (dbu)
- Graham Campbell (graham)
- Dariusz Ruminski
- Lee McDermott
- Brandon Turner
- Luis Cordova (cordoval)
- Graham Campbell (graham)
- Daniel Holmes (dholmes)
- Toni Uebernickel (havvg)
- Bart van den Burg (burgov)
@ -109,20 +109,21 @@ Symfony is the result of the work of many people who made the code better
- Maxime STEINHAUSSER
- Michal Piotrowski (eventhorizon)
- Tim Nagel (merk)
- Baptiste Clavié (talus)
- Chris Wilkinson (thewilkybarkid)
- Brice BERNARD (brikou)
- Baptiste Clavié (talus)
- marc.weistroff
- Tomáš Votruba (tomas_votruba)
- Peter Kokot (maastermedia)
- Jérôme Vasseur (jvasseur)
- lenar
- Alexander Schwenn (xelaris)
- Włodzimierz Gajda (gajdaw)
- Sebastiaan Stok (sstok)
- Adrien Brault (adrienbrault)
- Peter Kokot (maastermedia)
- Jacob Dreesen (jdreesen)
- Florian Voutzinos (florianv)
- Teoh Han Hui (teohhanhui)
- Colin Frei
- Oskar Stark (oskarstark)
- Javier Spagnoletti (phansys)
@ -131,13 +132,12 @@ Symfony is the result of the work of many people who made the code better
- Daniel Wehner (dawehner)
- excelwebzone
- Gordon Franke (gimler)
- Teoh Han Hui (teohhanhui)
- Tugdual Saunier (tucksaun)
- Fabien Pennequin (fabienpennequin)
- Théo FIDRY (theofidry)
- Eric GELOEN (gelo)
- Joel Wurtz (brouznouf)
- Lars Strojny (lstrojny)
- Tugdual Saunier (tucksaun)
- Jannik Zschiesche (apfelbox)
- Robert Schönthal (digitalkaoz)
- Gregor Harlan (gharlan)
@ -209,6 +209,7 @@ Symfony is the result of the work of many people who made the code better
- Mario A. Alvarez Garcia (nomack84)
- Dennis Benkert (denderello)
- DQNEO
- Andre Rømcke (andrerom)
- mcfedr (mcfedr)
- Ben Davies (bendavies)
- Gary PEGEOT (gary-p)
@ -233,7 +234,6 @@ Symfony is the result of the work of many people who made the code better
- Pierre Minnieur (pminnieur)
- fivestar
- Dominique Bongiraud
- Andre Rømcke (andrerom)
- Jeremy Livingston (jeremylivingston)
- Michael Lee (zerustech)
- Matthieu Auger (matthieuauger)
@ -249,6 +249,7 @@ Symfony is the result of the work of many people who made the code better
- Michele Orselli (orso)
- Sven Paulus (subsven)
- Maxime Veber (nek-)
- Anthony GRASSIOT (antograssiot)
- Rui Marinho (ruimarinho)
- Eugene Wissner
- Pascal Montoya
@ -257,8 +258,10 @@ Symfony is the result of the work of many people who made the code better
- Tristan Darricau (nicofuma)
- Victor Bocharsky (bocharsky_bw)
- Marcel Beerta (mazen)
- Maxime Helias (maxhelias)
- Pavel Batanov (scaytrase)
- Mantis Development
- David Prévot
- Loïc Faugeron
- Hidde Wieringa (hiddewie)
- dFayet
@ -285,7 +288,6 @@ Symfony is the result of the work of many people who made the code better
- Xavier Montaña Carreras (xmontana)
- Rémon van de Kamp (rpkamp)
- Mickaël Andrieu (mickaelandrieu)
- Anthony GRASSIOT (antograssiot)
- Xavier Perez
- Arjen Brouwer (arjenjb)
- Katsuhiro OGAWA
@ -311,13 +313,12 @@ Symfony is the result of the work of many people who made the code better
- Smaine Milianni (ismail1432)
- Chekote
- François Pluchino (francoispluchino)
- Christopher Hertel (chertel)
- Antoine Makdessi (amakdessi)
- Thomas Adam
- Jhonny Lidfors (jhonne)
- Diego Agulló (aeoris)
- jdhoek
- Maxime Helias (maxhelias)
- David Prévot
- Bob den Otter (bopp)
- Thomas Schulz (king2500)
- Frank de Jonge (frenkynet)
@ -342,6 +343,7 @@ Symfony is the result of the work of many people who made the code better
- Arjen van der Meijden
- Mathieu Lechat
- Marc Weistroff (futurecat)
- Damien Alexandre (damienalexandre)
- Simon Mönch (sm)
- Christian Schmidt
- Patrick Landolt (scube)
@ -349,6 +351,7 @@ Symfony is the result of the work of many people who made the code better
- David Badura (davidbadura)
- Chad Sikorra (chadsikorra)
- Chris Smith (cs278)
- Thomas Bisignani (toma)
- Florian Klein (docteurklein)
- Manuel Kiessling (manuelkiessling)
- Atsuhiro KUBO (iteman)
@ -407,18 +410,17 @@ Symfony is the result of the work of many people who made the code better
- Tomasz Kowalczyk (thunderer)
- Artur Eshenbrener
- Timo Bakx (timobakx)
- Damien Alexandre (damienalexandre)
- Thomas Perez (scullwm)
- Saif Eddin Gmati (azjezz)
- Felix Labrecque
- Yaroslav Kiliba
- Terje Bråten
- Tien Vo (tienvx)
- Robbert Klarenbeek (robbertkl)
- Eric Masoero (eric-masoero)
- JhonnyL
- hossein zolfi (ocean)
- Clément Gautier (clementgautier)
- Thomas Bisignani (toma)
- Dāvis Zālītis (k0d3r1s)
- Sanpi
- Eduardo Gulias (egulias)
@ -429,6 +431,7 @@ Symfony is the result of the work of many people who made the code better
- Grzegorz (Greg) Zdanowski (kiler129)
- Iker Ibarguren (ikerib)
- Kirill chEbba Chebunin (chebba)
- Rokas Mikalkėnas (rokasm)
- Greg Thornton (xdissent)
- Martin Hujer (martinhujer)
- Alex Bowers
@ -443,7 +446,6 @@ Symfony is the result of the work of many people who made the code better
- Michele Locati
- Pavel Volokitin (pvolok)
- Valentine Boineau (valentineboineau)
- Christopher Hertel (chertel)
- Arthur de Moulins (4rthem)
- Matthias Althaus (althaus)
- Nicolas Dewez (nicolas_dewez)
@ -476,6 +478,7 @@ Symfony is the result of the work of many people who made the code better
- Oscar Cubo Medina (ocubom)
- Karel Souffriau
- Christophe L. (christophelau)
- Sander Toonen (xatoo)
- Anthon Pang (robocoder)
- Michael Käfer (michael_kaefer)
- Sébastien Santoro (dereckson)
@ -502,6 +505,7 @@ Symfony is the result of the work of many people who made the code better
- Olivier Dolbeau (odolbeau)
- Jan Rosier (rosier)
- Alessandro Lai (jean85)
- Desjardins Jérôme (jewome62)
- Arturs Vonda
- Josip Kruslin
- Matthew Smeets
@ -529,7 +533,6 @@ Symfony is the result of the work of many people who made the code better
- Gonzalo Vilaseca (gonzalovilaseca)
- Tarmo Leppänen (tarlepp)
- Marcin Sikoń (marphi)
- Tien Vo (tienvx)
- Denis Brumann (dbrumann)
- Dominik Zogg (dominik.zogg)
- Marek Pietrzak
@ -540,6 +543,7 @@ Symfony is the result of the work of many people who made the code better
- Gintautas Miselis
- Rob Bast
- Roberto Espinoza (respinoza)
- Emanuele Panzeri (thepanz)
- Soufian EZ-ZANTAR (soezz)
- Zander Baldwin
- Gocha Ossinkine (ossinkine)
@ -603,7 +607,6 @@ Symfony is the result of the work of many people who made the code better
- Adam Szaraniec (mimol)
- Dariusz Ruminski
- Erik Trapman (eriktrapman)
- Rokas Mikalkėnas (rokasm)
- De Cock Xavier (xdecock)
- Almog Baku (almogbaku)
- Karoly Gossler (connorhu)
@ -648,11 +651,11 @@ Symfony is the result of the work of many people who made the code better
- Jeremy Benoist
- fritzmg
- Lenar Lõhmus
- Sander Toonen (xatoo)
- Benjamin Laugueux (yzalis)
- Zach Badgett (zachbadgett)
- Aurélien Fredouelle
- Pavel Campr (pcampr)
- Andrii Dembitskyi
- Johnny Robeson (johnny)
- Marko Kaznovac (kaznovac)
- Disquedur
@ -694,7 +697,6 @@ Symfony is the result of the work of many people who made the code better
- Sinan Eldem
- Alexandre Dupuy (satchette)
- Malte Blättermann
- Desjardins Jérôme (jewome62)
- Simeon Kolev (simeon_kolev9)
- Joost van Driel (j92)
- Jonas Elfering
@ -762,11 +764,12 @@ Symfony is the result of the work of many people who made the code better
- Giso Stallenberg (gisostallenberg)
- Michael Devery (mickadoo)
- Antoine Corcy
- Ahmed Ashraf (ahmedash95)
- Sascha Grossenbacher
- Emanuele Panzeri (thepanz)
- Szijarto Tamas
- Robin Lehrmann (robinlehrmann)
- Catalin Dan
- Soner Sayakci
- Jaroslav Kuba
- Kristijan Kanalas
- Stephan Vock
@ -825,11 +828,13 @@ Symfony is the result of the work of many people who made the code better
- Markus Fasselt (digilist)
- Julien DIDIER (juliendidier)
- Dominik Ritter (dritter)
- Dimitri Gritsajuk (ottaviano)
- Sebastian Grodzicki (sgrodzicki)
- Jeroen van den Enden (stoefke)
- Pascal Helfenstein
- Baldur Rensch (brensch)
- Pierre Rineau
- Vilius Grigaliūnas
- Vladyslav Petrovych
- Alex Xandra Albert Sim
- Carson Full
@ -946,6 +951,7 @@ Symfony is the result of the work of many people who made the code better
- Patrick Allaert
- Gustavo Falco (gfalco)
- Matt Robinson (inanimatt)
- Kristof Van Cauwenbergh (kristofvc)
- Aleksey Podskrebyshev
- Calin Mihai Pristavu
- David Marín Carreño (davefx)
@ -984,6 +990,7 @@ Symfony is the result of the work of many people who made the code better
- Thomas Landauer
- 243083df
- Thibault Duplessis
- Rimas Kudelis
- Marc Abramowitz
- Martijn Evers
- Tony Tran
@ -996,12 +1003,12 @@ Symfony is the result of the work of many people who made the code better
- Johnson Page (jwpage)
- Ruben Gonzalez (rubenruateltek)
- Michael Roterman (wtfzdotnet)
- Andrii Dembitskyi
- Arno Geurts
- Adán Lobato (adanlobato)
- Ian Jenkins (jenkoian)
- Marcos Gómez Vilches (markitosgv)
- Matthew Davis (mdavis1982)
- Markus S. (staabm)
- Maks
- Antoine LA
- den
@ -1187,7 +1194,6 @@ Symfony is the result of the work of many people who made the code better
- Sergii Smertin (nfx)
- Mikkel Paulson
- Michał Strzelecki
- Soner Sayakci
- hugofonseca (fonsecas72)
- Marc Duboc (icemad)
- Matthias Krauser (mkrauser)
@ -1241,6 +1247,7 @@ Symfony is the result of the work of many people who made the code better
- Jeremy Bush
- wizhippo
- Thomason, James
- Dario Savella
- Gordienko Vladislav
- marie
- Viacheslav Sychov
@ -1291,6 +1298,7 @@ Symfony is the result of the work of many people who made the code better
- Oxan van Leeuwen
- pkowalczyk
- Soner Sayakci
- Koen Reiniers (koenre)
- Max Voloshin (maxvoloshin)
- Nicolas Fabre (nfabre)
- Raul Rodriguez (raul782)
@ -1306,6 +1314,8 @@ Symfony is the result of the work of many people who made the code better
- Felicitus
- Krzysztof Przybyszewski
- alexpozzi
- Vladimir
- Jorge Vahldick (jvahldick)
- Frederic Godfrin
- Paul Matthews
- Jakub Kisielewski
@ -1752,7 +1762,6 @@ Symfony is the result of the work of many people who made the code better
- downace
- Aarón Nieves Fernández
- Mike Meier
- Vilius Grigaliūnas
- Kirill Saksin
- Koalabaerchen
- michalmarcinkowski
@ -1765,6 +1774,7 @@ Symfony is the result of the work of many people who made the code better
- efeen
- Nicolas Pion
- Muhammed Akbulut
- Roy-Orbison
- Aaron Somi
- Michał Dąbrowski (defrag)
- Konstantin Grachev (grachevko)
@ -1802,12 +1812,14 @@ Symfony is the result of the work of many people who made the code better
- Alexander Li (aweelex)
- Bram Van der Sype (brammm)
- Guile (guile)
- Mark Beech (jaybizzle)
- Julien Moulin (lizjulien)
- Raito Akehanareru (raito)
- Mauro Foti (skler)
- Yannick Warnier (ywarnier)
- Kevin Decherf
- Jason Woods
- Maria Grazia Patteri
- klemens
- dened
- Dmitry Korotovsky
@ -1823,6 +1835,7 @@ Symfony is the result of the work of many people who made the code better
- Sören Bernstein
- devel
- taiiiraaa
- Ali Tavafi
- Trevor Suarez
- gedrox
- Bohan Yang
@ -1959,6 +1972,7 @@ Symfony is the result of the work of many people who made the code better
- Juan M Martínez
- Gilles Gauthier
- Pavinthan
- Sylvain METAYER
- ddebree
- Kuba Werłos
- Gyula Szucs
@ -2015,6 +2029,7 @@ Symfony is the result of the work of many people who made the code better
- Marcin Szepczynski (szepczynski)
- Cyrille Jouineau (tuxosaurus)
- Vladimir Chernyshev (volch)
- Wim Godden (wimg)
- Yorkie Chadwick (yorkie76)
- GuillaumeVerdon
- Philipp Keck
@ -2026,6 +2041,7 @@ Symfony is the result of the work of many people who made the code better
- Taylan Kasap
- Michael Orlitzky
- Nicolas A. Bérard-Nault
- Quentin Favrie
- Saem Ghani
- Stefan Oderbolz
- Curtis
@ -2051,12 +2067,10 @@ Symfony is the result of the work of many people who made the code better
- Jeffrey Moelands (jeffreymoelands)
- Hugo Alliaume (kocal)
- Simon CONSTANS (kosssi)
- Kristof Van Cauwenbergh (kristofvc)
- Dennis Langen (nijusan)
- Paulius Jarmalavičius (pjarmalavicius)
- Ramon Henrique Ornelas (ramonornela)
- Ricardo de Vries (ricknox)
- Markus S. (staabm)
- Thomas Dutrion (theocrite)
- Till Klampaeckel (till)
- Tobias Weinert (tweini)
@ -2074,6 +2088,7 @@ Symfony is the result of the work of many people who made the code better
- Cas
- Dusan Kasan
- Michael Steininger
- Nardberjean
- Karolis
- Myke79
- Brian Debuire
@ -2145,7 +2160,6 @@ Symfony is the result of the work of many people who made the code better
- Daniel STANCU
- Ryan Rud
- Ondrej Slinták
- Rimas Kudelis
- vlechemin
- Brian Corrigan
- Ladislav Tánczos
@ -2263,6 +2277,7 @@ Symfony is the result of the work of many people who made the code better
- Sebastian Landwehr (dword123)
- Adel ELHAIBA (eadel)
- Damián Nohales (eagleoneraptor)
- Jordane VASPARD (elementaire)
- Elliot Anderson (elliot)
- Fabien D. (fabd)
- Carsten Eilers (fnc)
@ -2277,6 +2292,7 @@ Symfony is the result of the work of many people who made the code better
- Peter Orosz (ill_logical)
- Imangazaliev Muhammad (imangazaliev)
- j0k (j0k)
- Jeremie Broutier (jbroutier)
- joris de wit (jdewit)
- Jérémy CROMBEZ (jeremy)
- Jose Manuel Gonzalez (jgonzalez)
@ -2293,6 +2309,7 @@ Symfony is the result of the work of many people who made the code better
- samuel laulhau (lalop)
- Laurent Bachelier (laurentb)
- Luís Cobucci (lcobucci)
- Jérémy (libertjeremy)
- Mehdi Achour (machour)
- Matthieu Mota (matthieumota)
- Matthieu Moquet (mattketmo)
@ -2300,12 +2317,12 @@ Symfony is the result of the work of many people who made the code better
- Michal Čihař (mcihar)
- Matt Drollette (mdrollette)
- Adam Monsen (meonkeys)
- Hugo Monteiro (monteiro)
- Ala Eddine Khefifi (nayzo)
- emilienbouard (neime)
- Nicholas Byfleet (nickbyfleet)
- Marco Petersen (ocrampete16)
- ollie harridge (ollietb)
- Dimitri Gritsajuk (ottaviano)
- Paul Andrieux (paulandrieux)
- Paulo Ribeiro (paulo)
- Paweł Szczepanek (pauluz)
@ -2333,6 +2350,7 @@ Symfony is the result of the work of many people who made the code better
- Julien Sanchez (sumbobyboys)
- Guillermo Gisinger (t3chn0r)
- Markus Tacker (tacker)
- Thiago Cordeiro (thiagocordeiro)
- Tom Newby (tomnewbyau)
- Andrew Clark (tqt_andrew_clark)
- David Lumaye (tux1124)
@ -2341,6 +2359,7 @@ Symfony is the result of the work of many people who made the code better
- Moritz Kraft (userfriendly)
- Víctor Mateo (victormateo)
- Vincent (vincent1870)
- Vincent MOULENE (vints24)
- David Herrmann (vworldat)
- Eugene Babushkin (warl)
- Wouter Sioen (wouter_sioen)
@ -2365,6 +2384,7 @@ Symfony is the result of the work of many people who made the code better
- damaya
- Kevin Weber
- Ben Scott
- Alexandru Năstase
- Dionysis Arvanitis
- Sergey Fedotov
- Konstantin Scheumann
@ -2378,6 +2398,7 @@ Symfony is the result of the work of many people who made the code better
- Zander Baldwin
- Philipp Scheit
- max
- Alexander Bauer (abauer)
- Ahmad Mayahi (ahmadmayahi)
- Mohamed Karnichi (amiral)
- Andrew Carter (andrewcarteruk)

21
link
View File

@ -23,11 +23,14 @@ use Symfony\Component\Filesystem\Filesystem;
* @author Kévin Dunglas <dunglas@gmail.com>
*/
$copy = false !== $k = array_search('--copy', $argv, true);
$copy && array_splice($argv, $k, 1);
$pathToProject = $argv[1] ?? getcwd();
if (!is_dir("$pathToProject/vendor/symfony")) {
echo 'Link dependencies to components to a local clone of the main symfony/symfony GitHub repository.'.PHP_EOL.PHP_EOL;
echo "Usage: $argv[0] /path/to/the/project".PHP_EOL.PHP_EOL;
echo 'Link (or copy) dependencies to components to a local clone of the main symfony/symfony GitHub repository.'.PHP_EOL.PHP_EOL;
echo "Usage: $argv[0] /path/to/the/project".PHP_EOL;
echo ' Use `--copy` to copy dependencies instead of symlink'.PHP_EOL.PHP_EOL;
echo "The directory \"$pathToProject\" does not exist or the dependencies are not installed, did you forget to run \"composer install\" in your project?".PHP_EOL;
exit(1);
}
@ -50,7 +53,7 @@ foreach ($directories as $dir) {
foreach (glob("$pathToProject/vendor/symfony/*", GLOB_ONLYDIR | GLOB_NOSORT) as $dir) {
$package = 'symfony/'.basename($dir);
if (is_link($dir)) {
if (!$copy && is_link($dir)) {
echo "\"$package\" is already a symlink, skipping.".PHP_EOL;
continue;
}
@ -59,11 +62,17 @@ foreach (glob("$pathToProject/vendor/symfony/*", GLOB_ONLYDIR | GLOB_NOSORT) as
continue;
}
$sfDir = '\\' === DIRECTORY_SEPARATOR ? $sfPackages[$package] : $filesystem->makePathRelative($sfPackages[$package], dirname(realpath($dir)));
$sfDir = ('\\' === DIRECTORY_SEPARATOR || $copy) ? $sfPackages[$package] : $filesystem->makePathRelative($sfPackages[$package], dirname(realpath($dir)));
$filesystem->remove($dir);
$filesystem->symlink($sfDir, $dir);
echo "\"$package\" has been linked to \"$sfPackages[$package]\".".PHP_EOL;
if ($copy) {
$filesystem->mirror($sfDir, $dir);
echo "\"$package\" has been copied from \"$sfPackages[$package]\".".PHP_EOL;
} else {
$filesystem->symlink($sfDir, $dir);
echo "\"$package\" has been linked to \"$sfPackages[$package]\".".PHP_EOL;
}
}
foreach (glob("$pathToProject/var/cache/*", GLOB_NOSORT) as $cacheDir) {

View File

@ -263,9 +263,11 @@ abstract class DoctrineType extends AbstractType implements ResetInterface
/**
* Return the default loader object.
*
* @param mixed $queryBuilder
*
* @return EntityLoaderInterface
*/
abstract public function getLoader(ObjectManager $manager, QueryBuilder $queryBuilder, string $class);
abstract public function getLoader(ObjectManager $manager, $queryBuilder, string $class);
public function getParent()
{

View File

@ -46,9 +46,11 @@ class EntityType extends DoctrineType
/**
* Return the default loader object.
*
* @param QueryBuilder $queryBuilder
*
* @return ORMQueryBuilderLoader
*/
public function getLoader(ObjectManager $manager, QueryBuilder $queryBuilder, string $class)
public function getLoader(ObjectManager $manager, $queryBuilder, string $class)
{
if (!$queryBuilder instanceof QueryBuilder) {
throw new \TypeError(sprintf('Expected an instance of %s, but got %s.', QueryBuilder::class, \is_object($queryBuilder) ? \get_class($queryBuilder) : \gettype($queryBuilder)));

View File

@ -0,0 +1,110 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Monolog\Tests\Handler;
use Monolog\Formatter\JsonFormatter;
use Monolog\Logger;
use Monolog\Processor\ProcessIdProcessor;
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\Monolog\Formatter\VarDumperFormatter;
use Symfony\Bridge\Monolog\Handler\ServerLogHandler;
use Symfony\Component\VarDumper\Cloner\Data;
/**
* Tests the ServerLogHandler.
*/
class ServerLogHandlerTest extends TestCase
{
public function testFormatter()
{
$handler = new ServerLogHandler('tcp://127.0.0.1:9999');
$this->assertInstanceOf(VarDumperFormatter::class, $handler->getFormatter());
$formatter = new JsonFormatter();
$handler->setFormatter($formatter);
$this->assertSame($formatter, $handler->getFormatter());
}
public function testIsHandling()
{
$handler = new ServerLogHandler('tcp://127.0.0.1:9999', Logger::INFO);
$this->assertFalse($handler->isHandling(['level' => Logger::DEBUG]), '->isHandling returns false when no output is set');
}
public function testGetFormatter()
{
$handler = new ServerLogHandler('tcp://127.0.0.1:9999');
$this->assertInstanceOf(VarDumperFormatter::class, $handler->getFormatter(),
'-getFormatter returns VarDumperFormatter by default'
);
}
public function testWritingAndFormatting()
{
$host = 'tcp://127.0.0.1:9999';
$handler = new ServerLogHandler($host, Logger::INFO, false);
$handler->pushProcessor(new ProcessIdProcessor());
$infoRecord = [
'message' => 'My info message',
'context' => [],
'level' => Logger::INFO,
'level_name' => Logger::getLevelName(Logger::INFO),
'channel' => 'app',
'datetime' => new \DateTime('2013-05-29 16:21:54'),
'extra' => [],
];
$socket = stream_socket_server($host, $errno, $errstr);
$this->assertIsResource($socket, sprintf('Server start failed on "%s": %s %s.', $host, $errstr, $errno));
$this->assertTrue($handler->handle($infoRecord), 'The handler finished handling the log as bubble is false.');
$sockets = [(int) $socket => $socket];
$write = [];
for ($i = 0; $i < 10; ++$i) {
$read = $sockets;
stream_select($read, $write, $write, null);
foreach ($read as $stream) {
if ($socket === $stream) {
$stream = stream_socket_accept($socket);
$sockets[(int) $stream] = $stream;
} elseif (feof($stream)) {
unset($sockets[(int) $stream]);
fclose($stream);
} else {
$message = fgets($stream);
fclose($stream);
$record = unserialize(base64_decode($message));
$this->assertIsArray($record);
$this->assertArrayHasKey('message', $record);
$this->assertEquals('My info message', $record['message']);
$this->assertArrayHasKey('extra', $record);
$this->assertInstanceOf(Data::class, $record['extra']);
$extra = $record['extra']->getValue(true);
$this->assertIsArray($extra);
$this->assertArrayHasKey('process_id', $extra);
$this->assertSame(getmypid(), $extra['process_id']);
return;
}
}
usleep(100000);
}
$this->fail('Fail to read message from server');
}
}

View File

@ -242,7 +242,7 @@
{% block checkbox_radio_label -%}
{#- Do not display the label if widget is not defined in order to prevent double label rendering -#}
{%- if widget is defined -%}
{% set is_parent_custom = parent_label_class is defined and ('checkbox-custom' in parent_label_class or 'radio-custom' in parent_label_class) %}
{% set is_parent_custom = parent_label_class is defined and ('checkbox-custom' in parent_label_class or 'radio-custom' in parent_label_class or 'switch-custom' in parent_label_class) %}
{% set is_custom = label_attr.class is defined and ('checkbox-custom' in label_attr.class or 'radio-custom' in label_attr.class or 'switch-custom' in label_attr.class) %}
{%- if is_parent_custom or is_custom -%}
{%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' custom-control-label')|trim}) -%}

View File

@ -21,6 +21,7 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
@ -228,6 +229,8 @@ EOF
$container->compile();
} else {
(new XmlFileLoader($container = new ContainerBuilder(), new FileLocator()))->load($kernel->getContainer()->getParameter('debug.container.dump'));
$locatorPass = new ServiceLocatorTagPass();
$locatorPass->process($container);
}
return $this->containerBuilder = $container;

View File

@ -20,6 +20,7 @@ use Symfony\Component\DependencyInjection\Compiler\CheckTypeDeclarationsPass;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
final class ContainerLintCommand extends Command
{
@ -71,7 +72,11 @@ final class ContainerLintCommand extends Command
$buildContainer = \Closure::bind(function () { return $this->buildContainer(); }, $kernel, \get_class($kernel));
$container = $buildContainer();
} else {
(new XmlFileLoader($container = new ContainerBuilder(), new FileLocator()))->load($kernel->getContainer()->getParameter('debug.container.dump'));
(new XmlFileLoader($container = new ContainerBuilder($parameterBag = new EnvPlaceholderParameterBag()), new FileLocator()))->load($kernel->getContainer()->getParameter('debug.container.dump'));
$refl = new \ReflectionProperty($parameterBag, 'resolved');
$refl->setAccessible(true);
$refl->setValue($parameterBag, true);
}
return $this->containerBuilder = $container;

View File

@ -25,6 +25,7 @@ class SodiumVault extends AbstractVault implements EnvVarLoaderInterface
private $encryptionKey;
private $decryptionKey;
private $pathPrefix;
private $secretsDir;
/**
* @param string|object|null $decryptionKey A string or a stringable object that defines the private key to use to decrypt the vault
@ -36,12 +37,9 @@ class SodiumVault extends AbstractVault implements EnvVarLoaderInterface
throw new \TypeError(sprintf('Decryption key should be a string or an object that implements the __toString() method, %s given.', \gettype($decryptionKey)));
}
if (!is_dir($secretsDir) && !@mkdir($secretsDir, 0777, true) && !is_dir($secretsDir)) {
throw new \RuntimeException(sprintf('Unable to create the secrets directory (%s)', $secretsDir));
}
$this->pathPrefix = rtrim(strtr($secretsDir, '/', \DIRECTORY_SEPARATOR), \DIRECTORY_SEPARATOR).\DIRECTORY_SEPARATOR.basename($secretsDir).'.';
$this->decryptionKey = $decryptionKey;
$this->secretsDir = $secretsDir;
}
public function generateKeys(bool $override = false): bool
@ -203,9 +201,20 @@ class SodiumVault extends AbstractVault implements EnvVarLoaderInterface
$data = str_replace('%', '\x', rawurlencode($data));
$data = sprintf("<?php // %s on %s\n\nreturn \"%s\";\n", $name, date('r'), $data);
$this->createSecretsDir();
if (false === file_put_contents($this->pathPrefix.$file.'.php', $data, LOCK_EX)) {
$e = error_get_last();
throw new \ErrorException($e['message'] ?? 'Failed to write secrets data.', 0, $e['type'] ?? E_USER_WARNING);
}
}
private function createSecretsDir(): void
{
if ($this->secretsDir && !is_dir($this->secretsDir) && !@mkdir($this->secretsDir, 0777, true) && !is_dir($this->secretsDir)) {
throw new \RuntimeException(sprintf('Unable to create the secrets directory (%s)', $this->secretsDir));
}
$this->secretsDir = null;
}
}

View File

@ -31,7 +31,7 @@ class AddSessionDomainConstraintPass implements CompilerPassInterface
}
$sessionOptions = $container->getParameter('session.storage.options');
$domainRegexp = empty($sessionOptions['cookie_domain']) ? '%s' : sprintf('(?:%%s|(?:.+\.)?%s)', preg_quote(trim($sessionOptions['cookie_domain'], '.')));
$domainRegexp = empty($sessionOptions['cookie_domain']) ? '%%s' : sprintf('(?:%%%%s|(?:.+\.)?%s)', preg_quote(trim($sessionOptions['cookie_domain'], '.')));
if ('auto' === ($sessionOptions['cookie_secure'] ?? null)) {
$secureDomainRegexp = sprintf('{^https://%s$}i', $domainRegexp);

View File

@ -43,6 +43,7 @@ class RegisterTokenUsageTrackingPass implements CompilerPassInterface
if (!$container->has('session')) {
$container->setAlias('security.token_storage', 'security.untracked_token_storage')->setPublic(true);
$container->getDefinition('security.untracked_token_storage')->addTag('kernel.reset', ['method' => 'reset']);
} elseif ($container->hasDefinition('security.context_listener')) {
$container->getDefinition('security.context_listener')
->setArgument(6, [new Reference('security.token_storage'), 'enableUsageTracking']);

View File

@ -362,7 +362,13 @@ class MainConfiguration implements ConfigurationInterface
->performNoDeepMerging()
->beforeNormalization()->ifString()->then(function ($v) { return ['algorithm' => $v]; })->end()
->children()
->scalarNode('algorithm')->cannotBeEmpty()->end()
->scalarNode('algorithm')
->cannotBeEmpty()
->validate()
->ifTrue(function ($v) { return !\is_string($v); })
->thenInvalid('You must provide a string value.')
->end()
->end()
->arrayNode('migrate_from')
->prototype('scalar')->end()
->beforeNormalization()->castToArray()->end()

View File

@ -24,19 +24,19 @@ class AnonymousFactory implements SecurityFactoryInterface
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
{
if (null === $config['secret']) {
$firewall['anonymous']['secret'] = new Parameter('container.build_hash');
$config['secret'] = new Parameter('container.build_hash');
}
$listenerId = 'security.authentication.listener.anonymous.'.$id;
$container
->setDefinition($listenerId, new ChildDefinition('security.authentication.listener.anonymous'))
->replaceArgument(1, $firewall['anonymous']['secret'])
->replaceArgument(1, $config['secret'])
;
$providerId = 'security.authentication.provider.anonymous.'.$id;
$container
->setDefinition($providerId, new ChildDefinition('security.authentication.provider.anonymous'))
->replaceArgument(0, $firewall['anonymous']['secret'])
->replaceArgument(0, $config['secret'])
;
return [$providerId, $listenerId, $defaultEntryPoint];

View File

@ -676,7 +676,7 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
return $exceptionListenerId;
}
private function createSwitchUserListener(ContainerBuilder $container, string $id, array $config, string $defaultProvider, bool $stateless): string
private function createSwitchUserListener(ContainerBuilder $container, string $id, array $config, ?string $defaultProvider, bool $stateless): string
{
$userProvider = isset($config['provider']) ? $this->getUserProviderId($config['provider']) : $defaultProvider;

View File

@ -389,6 +389,30 @@ class SecurityExtensionTest extends TestCase
];
}
public function testSwitchUserWithSeveralDefinedProvidersButNoFirewallRootProviderConfigured()
{
$container = $this->getRawContainer();
$container->loadFromExtension('security', [
'providers' => [
'first' => ['id' => 'foo'],
'second' => ['id' => 'bar'],
],
'firewalls' => [
'foobar' => [
'switch_user' => [
'provider' => 'second',
],
'anonymous' => true,
],
],
]);
$container->compile();
$this->assertEquals(new Reference('security.user.provider.concrete.second'), $container->getDefinition('security.authentication.switchuser_listener.foobar')->getArgument(1));
}
protected function getRawContainer()
{
$container = new ContainerBuilder();

View File

@ -39,6 +39,8 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
private $values;
private $createCacheItem;
private static $valuesCache = [];
/**
* @param string $file The PHP file were values are cached
* @param AdapterInterface $fallbackPool A pool to fallback on when an item is not hit
@ -65,22 +67,17 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
* This adapter takes advantage of how PHP stores arrays in its latest versions.
*
* @param string $file The PHP file were values are cached
* @param CacheItemPoolInterface $fallbackPool Fallback when opcache is disabled
* @param CacheItemPoolInterface $fallbackPool A pool to fallback on when an item is not hit
*
* @return CacheItemPoolInterface
*/
public static function create(string $file, CacheItemPoolInterface $fallbackPool)
{
// Shared memory is available in PHP 7.0+ with OPCache enabled
if (filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN)) {
if (!$fallbackPool instanceof AdapterInterface) {
$fallbackPool = new ProxyAdapter($fallbackPool);
}
return new static($file, $fallbackPool);
if (!$fallbackPool instanceof AdapterInterface) {
$fallbackPool = new ProxyAdapter($fallbackPool);
}
return $fallbackPool;
return new static($file, $fallbackPool);
}
/**
@ -281,6 +278,7 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
$this->keys = $this->values = [];
$cleared = @unlink($this->file) || !file_exists($this->file);
unset(self::$valuesCache[$this->file]);
if ($this->pool instanceof AdapterInterface) {
return $this->pool->clear($prefix) && $cleared;
@ -375,6 +373,7 @@ EOF;
unset($serialized, $value, $dump);
@rename($tmpFile, $this->file);
unset(self::$valuesCache[$this->file]);
$this->initialize();
}
@ -384,12 +383,15 @@ EOF;
*/
private function initialize()
{
if (!file_exists($this->file)) {
if (isset(self::$valuesCache[$this->file])) {
$values = self::$valuesCache[$this->file];
} elseif (!file_exists($this->file)) {
$this->keys = $this->values = [];
return;
} else {
$values = self::$valuesCache[$this->file] = (include $this->file) ?: [[], []];
}
$values = (include $this->file) ?: [[], []];
if (2 !== \count($values) || !isset($values[0], $values[1])) {
$this->keys = $this->values = [];

View File

@ -66,6 +66,8 @@ class PhpArrayAdapterTest extends AdapterTestCase
protected function tearDown(): void
{
$this->createCachePool()->clear();
if (file_exists(sys_get_temp_dir().'/symfony-cache')) {
FilesystemAdapterTest::rmdir(sys_get_temp_dir().'/symfony-cache');
}

View File

@ -38,6 +38,8 @@ class PhpArrayAdapterWithFallbackTest extends AdapterTestCase
protected function tearDown(): void
{
$this->createCachePool()->clear();
if (file_exists(sys_get_temp_dir().'/symfony-cache')) {
FilesystemAdapterTest::rmdir(sys_get_temp_dir().'/symfony-cache');
}

View File

@ -37,7 +37,9 @@ class ClassExistenceResource implements SelfCheckingResourceInterface
public function __construct(string $resource, bool $exists = null)
{
$this->resource = $resource;
$this->exists = $exists;
if (null !== $exists) {
$this->exists = [(bool) $exists, null];
}
}
/**
@ -65,9 +67,13 @@ class ClassExistenceResource implements SelfCheckingResourceInterface
{
$loaded = class_exists($this->resource, false) || interface_exists($this->resource, false) || trait_exists($this->resource, false);
if (null !== $exists = &self::$existsCache[(int) (0 >= $timestamp)][$this->resource]) {
$exists = $exists || $loaded;
} elseif (!$exists = $loaded) {
if (null !== $exists = &self::$existsCache[$this->resource]) {
if ($loaded) {
$exists = [true, null];
} elseif (0 >= $timestamp && !$exists[0] && null !== $exists[1]) {
throw new \ReflectionException($exists[1]);
}
} elseif ([false, null] === $exists = [$loaded, null]) {
if (!self::$autoloadLevel++) {
spl_autoload_register(__CLASS__.'::throwOnRequiredClass');
}
@ -75,16 +81,19 @@ class ClassExistenceResource implements SelfCheckingResourceInterface
self::$autoloadedClass = ltrim($this->resource, '\\');
try {
$exists = class_exists($this->resource) || interface_exists($this->resource, false) || trait_exists($this->resource, false);
$exists[0] = class_exists($this->resource) || interface_exists($this->resource, false) || trait_exists($this->resource, false);
} catch (\Exception $e) {
$exists[1] = $e->getMessage();
try {
self::throwOnRequiredClass($this->resource, $e);
} catch (\ReflectionException $e) {
if (0 >= $timestamp) {
unset(self::$existsCache[1][$this->resource]);
throw $e;
}
}
} catch (\Throwable $e) {
$exists[1] = $e->getMessage();
} finally {
self::$autoloadedClass = $autoloadedClass;
if (!--self::$autoloadLevel) {
@ -97,7 +106,7 @@ class ClassExistenceResource implements SelfCheckingResourceInterface
$this->exists = $exists;
}
return $this->exists xor !$exists;
return $this->exists[0] xor !$exists[0];
}
/**
@ -112,6 +121,16 @@ class ClassExistenceResource implements SelfCheckingResourceInterface
return ['resource', 'exists'];
}
/**
* @internal
*/
public function __wakeup()
{
if (\is_bool($this->exists)) {
$this->exists = [$this->exists, null];
}
}
/**
* Throws a reflection exception when the passed class does not exist but is required.
*
@ -147,7 +166,17 @@ class ClassExistenceResource implements SelfCheckingResourceInterface
throw $previous;
}
$e = new \ReflectionException(sprintf('Class "%s" not found while loading "%s".', $class, self::$autoloadedClass), 0, $previous);
$message = sprintf('Class "%s" not found.', $class);
if (self::$autoloadedClass !== $class) {
$message = substr_replace($message, sprintf(' while loading "%s"', self::$autoloadedClass), -1, 0);
}
if (null !== $previous) {
$message = $previous->getMessage();
}
$e = new \ReflectionException($message, 0, $previous);
if (null !== $previous) {
throw $e;

View File

@ -0,0 +1,9 @@
<?php
namespace Symfony\Component\Config\Tests\Fixtures;
class FileNameMismatchOnPurpose
{
}
throw new \RuntimeException('Mismatch between file name and class name.');

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\Config\Tests\Resource;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Resource\ClassExistenceResource;
use Symfony\Component\Config\Tests\Fixtures\BadFileName;
use Symfony\Component\Config\Tests\Fixtures\BadParent;
use Symfony\Component\Config\Tests\Fixtures\Resource\ConditionalClass;
@ -90,6 +91,24 @@ EOF
$res->isFresh(0);
}
public function testBadFileName()
{
$this->expectException('ReflectionException');
$this->expectExceptionMessage('Mismatch between file name and class name.');
$res = new ClassExistenceResource(BadFileName::class, false);
$res->isFresh(0);
}
public function testBadFileNameBis()
{
$this->expectException('ReflectionException');
$this->expectExceptionMessage('Mismatch between file name and class name.');
$res = new ClassExistenceResource(BadFileName::class, false);
$res->isFresh(0);
}
public function testConditionalClass()
{
$res = new ClassExistenceResource(ConditionalClass::class, false);

View File

@ -14,12 +14,16 @@ namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\EnvNotFoundException;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\InvalidParameterTypeException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ExpressionLanguage;
use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\ExpressionLanguage\Expression;
/**
* Checks whether injected parameters are compatible with type declarations.
@ -39,6 +43,8 @@ final class CheckTypeDeclarationsPass extends AbstractRecursivePass
private $autoload;
private $expressionLanguage;
/**
* @param bool $autoload Whether services who's class in not loaded should be checked or not.
* Defaults to false to save loading code during compilation.
@ -100,19 +106,21 @@ final class CheckTypeDeclarationsPass extends AbstractRecursivePass
$reflectionParameters = $reflectionFunction->getParameters();
$checksCount = min($reflectionFunction->getNumberOfParameters(), \count($values));
$envPlaceholderUniquePrefix = $this->container->getParameterBag() instanceof EnvPlaceholderParameterBag ? $this->container->getParameterBag()->getEnvPlaceholderUniquePrefix() : null;
for ($i = 0; $i < $checksCount; ++$i) {
if (!$reflectionParameters[$i]->hasType() || $reflectionParameters[$i]->isVariadic()) {
continue;
}
$this->checkType($checkedDefinition, $values[$i], $reflectionParameters[$i]);
$this->checkType($checkedDefinition, $values[$i], $reflectionParameters[$i], $envPlaceholderUniquePrefix);
}
if ($reflectionFunction->isVariadic() && ($lastParameter = end($reflectionParameters))->hasType()) {
$variadicParameters = \array_slice($values, $lastParameter->getPosition());
foreach ($variadicParameters as $variadicParameter) {
$this->checkType($checkedDefinition, $variadicParameter, $lastParameter);
$this->checkType($checkedDefinition, $variadicParameter, $lastParameter, $envPlaceholderUniquePrefix);
}
}
}
@ -120,7 +128,7 @@ final class CheckTypeDeclarationsPass extends AbstractRecursivePass
/**
* @throws InvalidParameterTypeException When a parameter is not compatible with the declared type
*/
private function checkType(Definition $checkedDefinition, $value, \ReflectionParameter $parameter): void
private function checkType(Definition $checkedDefinition, $value, \ReflectionParameter $parameter, ?string $envPlaceholderUniquePrefix): void
{
$type = $parameter->getType()->getName();
@ -172,8 +180,24 @@ final class CheckTypeDeclarationsPass extends AbstractRecursivePass
if ($value instanceof Parameter) {
$value = $this->container->getParameter($value);
} elseif (\is_string($value) && '%' === ($value[0] ?? '') && preg_match('/^%([^%]+)%$/', $value, $match)) {
$value = $this->container->getParameter($match[1]);
} elseif ($value instanceof Expression) {
$value = $this->getExpressionLanguage()->evaluate($value, ['container' => $this->container]);
} elseif (\is_string($value)) {
if ('%' === ($value[0] ?? '') && preg_match('/^%([^%]+)%$/', $value, $match)) {
// Only array parameters are not inlined when dumped.
$value = [];
} elseif ($envPlaceholderUniquePrefix && false !== strpos($value, 'env_')) {
// If the value is an env placeholder that is either mixed with a string or with another env placeholder, then its resolved value will always be a string, so we don't need to resolve it.
// We don't need to change the value because it is already a string.
if ('' === preg_replace('/'.$envPlaceholderUniquePrefix.'_\w+_[a-f0-9]{32}/U', '', $value, -1, $c) && 1 === $c) {
try {
$value = $this->container->resolveEnvPlaceholders($value, true);
} catch (EnvNotFoundException | RuntimeException $e) {
// If an env placeholder cannot be resolved, we skip the validation.
return;
}
}
}
}
if (null === $value && $parameter->allowsNull()) {
@ -202,4 +226,13 @@ final class CheckTypeDeclarationsPass extends AbstractRecursivePass
throw new InvalidParameterTypeException($this->currentId, \is_object($value) ? \get_class($value) : \gettype($value), $parameter);
}
}
private function getExpressionLanguage(): ExpressionLanguage
{
if (null === $this->expressionLanguage) {
$this->expressionLanguage = new ExpressionLanguage(null, $this->container->getExpressionLanguageProviders());
}
return $this->expressionLanguage;
}
}

View File

@ -96,6 +96,11 @@ class ResolveBindingsPass extends AbstractRecursivePass
if ($value instanceof TypedReference && $value->getType() === (string) $value) {
// Already checked
$bindings = $this->container->getDefinition($this->currentId)->getBindings();
$name = $value->getName();
if (isset($name, $bindings[$name = $value.' $'.$name])) {
return $this->getBindingValue($bindings[$name]);
}
if (isset($bindings[$value->getType()])) {
return $this->getBindingValue($bindings[$value->getType()]);

View File

@ -203,14 +203,14 @@ class PhpDumper extends Dumper
if (!empty($options['file']) && is_dir($dir = \dirname($options['file']))) {
// Build a regexp where the first root dirs are mandatory,
// but every other sub-dir is optional up to the full path in $dir
// Mandate at least 2 root dirs and not more that 5 optional dirs.
// Mandate at least 1 root dir and not more than 5 optional dirs.
$dir = explode(\DIRECTORY_SEPARATOR, realpath($dir));
$i = \count($dir);
if (3 <= $i) {
if (2 + (int) ('\\' === \DIRECTORY_SEPARATOR) <= $i) {
$regex = '';
$lastOptionalDir = $i > 8 ? $i - 5 : 3;
$lastOptionalDir = $i > 8 ? $i - 5 : (2 + (int) ('\\' === \DIRECTORY_SEPARATOR));
$this->targetDirMaxMatches = $i - $lastOptionalDir;
while (--$i >= $lastOptionalDir) {

View File

@ -14,8 +14,10 @@ namespace Symfony\Component\DependencyInjection\Tests\Compiler;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Compiler\CheckTypeDeclarationsPass;
use Symfony\Component\DependencyInjection\Compiler\ResolveParameterPlaceHoldersPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
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\BarMethodCall;
@ -23,6 +25,7 @@ use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPa
use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\BarOptionalArgumentNotNull;
use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\Foo;
use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\FooObject;
use Symfony\Component\ExpressionLanguage\Expression;
/**
* @author Nicolas Grekas <p@tchwork.com>
@ -569,4 +572,101 @@ class CheckTypeDeclarationsPassTest extends TestCase
$this->assertInstanceOf(\stdClass::class, $container->get('bar')->foo);
}
public function testProcessResolveArrayParameters()
{
$container = new ContainerBuilder();
$container->setParameter('ccc', ['foobar']);
$container
->register('foobar', BarMethodCall::class)
->addMethodCall('setArray', ['%ccc%']);
(new CheckTypeDeclarationsPass(true))->process($container);
$this->addToAssertionCount(1);
}
public function testProcessResolveExpressions()
{
$container = new ContainerBuilder();
$container->setParameter('ccc', ['array']);
$container
->register('foobar', BarMethodCall::class)
->addMethodCall('setArray', [new Expression("parameter('ccc')")]);
(new CheckTypeDeclarationsPass(true))->process($container);
$this->addToAssertionCount(1);
}
public function testProcessHandleMixedEnvPlaceholder()
{
$this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
$this->expectExceptionMessage('Invalid definition for service "foobar": argument 1 of "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\BarMethodCall::setArray" accepts "array", "string" passed.');
$container = new ContainerBuilder(new EnvPlaceholderParameterBag([
'ccc' => '%env(FOO)%',
]));
$container
->register('foobar', BarMethodCall::class)
->addMethodCall('setArray', ['foo%ccc%']);
(new CheckTypeDeclarationsPass(true))->process($container);
}
public function testProcessHandleMultipleEnvPlaceholder()
{
$this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
$this->expectExceptionMessage('Invalid definition for service "foobar": argument 1 of "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\BarMethodCall::setArray" accepts "array", "string" passed.');
$container = new ContainerBuilder(new EnvPlaceholderParameterBag([
'ccc' => '%env(FOO)%',
'fcy' => '%env(int:BAR)%',
]));
$container
->register('foobar', BarMethodCall::class)
->addMethodCall('setArray', ['%ccc%%fcy%']);
(new CheckTypeDeclarationsPass(true))->process($container);
}
public function testProcessHandleExistingEnvPlaceholder()
{
putenv('ARRAY={"foo":"bar"}');
$container = new ContainerBuilder(new EnvPlaceholderParameterBag([
'ccc' => '%env(json:ARRAY)%',
]));
$container
->register('foobar', BarMethodCall::class)
->addMethodCall('setArray', ['%ccc%']);
(new ResolveParameterPlaceHoldersPass())->process($container);
(new CheckTypeDeclarationsPass(true))->process($container);
$this->addToAssertionCount(1);
putenv('ARRAY=');
}
public function testProcessHandleNotFoundEnvPlaceholder()
{
$container = new ContainerBuilder(new EnvPlaceholderParameterBag([
'ccc' => '%env(json:ARRAY)%',
]));
$container
->register('foobar', BarMethodCall::class)
->addMethodCall('setArray', ['%ccc%']);
(new ResolveParameterPlaceHoldersPass())->process($container);
(new CheckTypeDeclarationsPass(true))->process($container);
$this->addToAssertionCount(1);
}
}

View File

@ -84,7 +84,10 @@ class ResolveBindingsPassTest extends TestCase
{
$container = new ContainerBuilder();
$bindings = [CaseSensitiveClass::class => new BoundArgument(new Reference('foo'))];
$bindings = [
CaseSensitiveClass::class => new BoundArgument(new Reference('foo')),
CaseSensitiveClass::class.' $c' => new BoundArgument(new Reference('bar')),
];
// Explicit service id
$definition1 = $container->register('def1', NamedArgumentsDummy::class);
@ -95,11 +98,16 @@ class ResolveBindingsPassTest extends TestCase
$definition2->addArgument(new TypedReference(CaseSensitiveClass::class, CaseSensitiveClass::class));
$definition2->setBindings($bindings);
$definition3 = $container->register('def3', NamedArgumentsDummy::class);
$definition3->addArgument(new TypedReference(CaseSensitiveClass::class, CaseSensitiveClass::class, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, 'c'));
$definition3->setBindings($bindings);
$pass = new ResolveBindingsPass();
$pass->process($container);
$this->assertEquals([$typedRef], $container->getDefinition('def1')->getArguments());
$this->assertEquals([new Reference('foo')], $container->getDefinition('def2')->getArguments());
$this->assertEquals([new Reference('bar')], $container->getDefinition('def3')->getArguments());
}
public function testScalarSetter()

View File

@ -35,7 +35,7 @@ final class Dotenv
private $data;
private $end;
private $values;
private $usePutenv = true;
private $usePutenv;
/**
* @var bool If `putenv()` should be used to define environment variables or not.

View File

@ -63,7 +63,7 @@ class RedisSessionHandler extends AbstractSessionHandler
$this->redis = $redis;
$this->prefix = $options['prefix'] ?? 'sf_s';
$this->ttl = $options['ttl'] ?? (int) ini_get('session.gc_maxlifetime');
$this->ttl = $options['ttl'] ?? null;
}
/**
@ -79,7 +79,7 @@ class RedisSessionHandler extends AbstractSessionHandler
*/
protected function doWrite(string $sessionId, string $data): bool
{
$result = $this->redis->setEx($this->prefix.$sessionId, $this->ttl, $data);
$result = $this->redis->setEx($this->prefix.$sessionId, (int) ($this->ttl ?? ini_get('session.gc_maxlifetime')), $data);
return $result && !$result instanceof ErrorInterface;
}
@ -115,6 +115,6 @@ class RedisSessionHandler extends AbstractSessionHandler
*/
public function updateTimestamp($sessionId, $data)
{
return (bool) $this->redis->expire($this->prefix.$sessionId, $this->ttl);
return (bool) $this->redis->expire($this->prefix.$sessionId, (int) ($this->ttl ?? ini_get('session.gc_maxlifetime')));
}
}

View File

@ -115,6 +115,7 @@ class EsmtpTransport extends SmtpTransport
try {
$response = $this->executeCommand(sprintf("EHLO %s\r\n", $this->getLocalDomain()), [250]);
$capabilities = $this->getCapabilities($response);
} catch (TransportExceptionInterface $e) {
parent::doHeloCommand();

View File

@ -59,7 +59,7 @@ EOF
$io = new SymfonyStyle($input, $output);
$transportNames = $this->transportNames;
// do we want to setup only one transport?
// do we want to set up only one transport?
if ($transport = $input->getArgument('transport')) {
if (!$this->transportLocator->has($transport)) {
throw new \RuntimeException(sprintf('The "%s" transport does not exist.', $transport));
@ -71,7 +71,7 @@ EOF
$transport = $this->transportLocator->get($transportName);
if ($transport instanceof SetupableTransportInterface) {
$transport->setup();
$io->success(sprintf('The "%s" transport was setup successfully.', $transportName));
$io->success(sprintf('The "%s" transport was set up successfully.', $transportName));
} else {
$io->note(sprintf('The "%s" transport does not support setup.', $transportName));
}

View File

@ -35,7 +35,13 @@ class SendMessageMiddleware implements MiddlewareInterface
public function __construct(SendersLocatorInterface $sendersLocator, EventDispatcherInterface $eventDispatcher = null)
{
$this->sendersLocator = $sendersLocator;
$this->eventDispatcher = LegacyEventDispatcherProxy::decorate($eventDispatcher);
if (null !== $eventDispatcher && class_exists(LegacyEventDispatcherProxy::class)) {
$this->eventDispatcher = LegacyEventDispatcherProxy::decorate($eventDispatcher);
} else {
$this->eventDispatcher = $eventDispatcher;
}
$this->logger = new NullLogger();
}

View File

@ -42,7 +42,7 @@ class SetupTransportsCommandTest extends TestCase
$tester->execute([]);
$display = $tester->getDisplay();
$this->assertStringContainsString('The "amqp" transport was setup successfully.', $display);
$this->assertStringContainsString('The "amqp" transport was set up successfully.', $display);
$this->assertStringContainsString('The "other_transport" transport does not support setup.', $display);
}
@ -66,7 +66,7 @@ class SetupTransportsCommandTest extends TestCase
$tester->execute(['transport' => 'amqp']);
$display = $tester->getDisplay();
$this->assertStringContainsString('The "amqp" transport was setup successfully.', $display);
$this->assertStringContainsString('The "amqp" transport was set up successfully.', $display);
}
public function testReceiverNameArgumentNotFound()

View File

@ -322,7 +322,7 @@ class Connection
}
} catch (\AMQPQueueException $e) {
if (404 === $e->getCode() && $this->shouldSetup()) {
// If we get a 404 for the queue, it means we need to setup the exchange & queue.
// If we get a 404 for the queue, it means we need to set up the exchange & queue.
$this->setupExchangeAndQueues();
return $this->get();

View File

@ -47,8 +47,13 @@ class Worker
{
$this->receivers = $receivers;
$this->bus = $bus;
$this->eventDispatcher = LegacyEventDispatcherProxy::decorate($eventDispatcher);
$this->logger = $logger;
if (null !== $eventDispatcher && class_exists(LegacyEventDispatcherProxy::class)) {
$this->eventDispatcher = LegacyEventDispatcherProxy::decorate($eventDispatcher);
} else {
$this->eventDispatcher = $eventDispatcher;
}
}
/**

View File

@ -42,15 +42,10 @@ abstract class ObjectLoader extends Loader
*/
public function load($resource, string $type = null)
{
if (!preg_match('/^[^\:]+(?:::?(?:[^\:]+))?$/', $resource)) {
if (!preg_match('/^[^\:]+(?:::(?:[^\:]+))?$/', $resource)) {
throw new \InvalidArgumentException(sprintf('Invalid resource "%s" passed to the %s route loader: use the format "object_id::method" or "object_id" if your object class has an "__invoke" method.', $resource, \is_string($type) ? '"'.$type.'"' : 'object'));
}
if (1 === substr_count($resource, ':')) {
$resource = str_replace(':', '::', $resource);
@trigger_error(sprintf('Referencing object route loaders with a single colon is deprecated since Symfony 4.1. Use %s instead.', $resource), E_USER_DEPRECATED);
}
$parts = explode('::', $resource);
$method = $parts[1] ?? '__invoke';

View File

@ -55,6 +55,10 @@ class DaoAuthenticationProvider extends UserAuthenticationProvider
throw new BadCredentialsException('The presented password cannot be empty.');
}
if (null === $user->getPassword()) {
throw new BadCredentialsException('The presented password is invalid.');
}
$encoder = $this->encoderFactory->getEncoder($user);
if (!$encoder->isPasswordValid($user->getPassword(), $presentedPassword, $user->getSalt())) {

View File

@ -42,6 +42,10 @@ class UserPasswordEncoder implements UserPasswordEncoderInterface
*/
public function isPasswordValid(UserInterface $user, string $raw)
{
if (null === $user->getPassword()) {
return false;
}
$encoder = $this->encoderFactory->getEncoder($user);
return $encoder->isPasswordValid($user->getPassword(), $raw, $user->getSalt());
@ -52,6 +56,10 @@ class UserPasswordEncoder implements UserPasswordEncoderInterface
*/
public function needsRehash(UserInterface $user): bool
{
if (null === $user->getPassword()) {
return false;
}
$encoder = $this->encoderFactory->getEncoder($user);
return $encoder->needsRehash($user->getPassword());

View File

@ -147,7 +147,11 @@ class DaoAuthenticationProviderTest extends TestCase
->willReturn('0')
;
$method->invoke($provider, new TestUser(), $token);
$method->invoke(
$provider,
new User('username', 'password'),
$token
);
}
public function testCheckAuthenticationWhenCredentialsAreNotValid()
@ -169,7 +173,7 @@ class DaoAuthenticationProviderTest extends TestCase
->willReturn('foo')
;
$method->invoke($provider, new TestUser(), $token);
$method->invoke($provider, new User('username', 'password'), $token);
}
public function testCheckAuthenticationDoesNotReauthenticateWhenPasswordHasChanged()
@ -241,7 +245,7 @@ class DaoAuthenticationProviderTest extends TestCase
->willReturn('foo')
;
$method->invoke($provider, new TestUser(), $token);
$method->invoke($provider, new User('username', 'password'), $token);
}
public function testPasswordUpgrades()

View File

@ -53,7 +53,7 @@ class UserPasswordValidator extends ConstraintValidator
$encoder = $this->encoderFactory->getEncoder($user);
if (!$encoder->isPasswordValid($user->getPassword(), $password, $user->getSalt())) {
if (null === $user->getPassword() || !$encoder->isPasswordValid($user->getPassword(), $password, $user->getSalt())) {
$this->context->addViolation($constraint->message);
}
}

View File

@ -66,7 +66,13 @@ class ContextListener extends AbstractListener
$this->userProviders = $userProviders;
$this->sessionKey = '_security_'.$contextKey;
$this->logger = $logger;
$this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher);
if (null !== $dispatcher && class_exists(LegacyEventDispatcherProxy::class)) {
$this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher);
} else {
$this->dispatcher = $dispatcher;
}
$this->trustResolver = $trustResolver ?: new AuthenticationTrustResolver(AnonymousToken::class, RememberMeToken::class);
$this->sessionTrackerEnabler = $sessionTrackerEnabler;
}

View File

@ -692,6 +692,11 @@ abstract class AbstractString implements \JsonSerializable
return $str;
}
public function __sleep(): array
{
return ['string'];
}
public function __clone()
{
$this->ignoreCase = false;

View File

@ -35,14 +35,20 @@ abstract class AbstractUnicodeString extends AbstractString
public const NFKC = \Normalizer::NFKC;
public const NFKD = \Normalizer::NFKD;
// all ASCII letters sorted by typical frequency of occurrence
private const ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F";
// the subset of folded case mappings that is not in lower case mappings
private const FOLD_FROM = ['İ', 'µ', 'ſ', "\xCD\x85", 'ς', 'ϐ', 'ϑ', 'ϕ', 'ϖ', 'ϰ', 'ϱ', 'ϵ', 'ẛ', "\xE1\xBE\xBE", 'ß', 'İ', 'ʼn', 'ǰ', 'ΐ', 'ΰ', 'և', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'ẚ', 'ẞ', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'ᾐ', 'ᾑ', 'ᾒ', 'ᾓ', 'ᾔ', 'ᾕ', 'ᾖ', 'ᾗ', 'ᾘ', 'ᾙ', 'ᾚ', 'ᾛ', 'ᾜ', 'ᾝ', 'ᾞ', 'ᾟ', 'ᾠ', 'ᾡ', 'ᾢ', 'ᾣ', 'ᾤ', 'ᾥ', 'ᾦ', 'ᾧ', 'ᾨ', 'ᾩ', 'ᾪ', 'ᾫ', 'ᾬ', 'ᾭ', 'ᾮ', 'ᾯ', 'ᾲ', 'ᾳ', 'ᾴ', 'ᾶ', 'ᾷ', 'ᾼ', 'ῂ', 'ῃ', 'ῄ', 'ῆ', 'ῇ', 'ῌ', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'ῢ', 'ΰ', 'ῤ', 'ῦ', 'ῧ', 'ῲ', 'ῳ', 'ῴ', 'ῶ', 'ῷ', 'ῼ', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'ſt', 'st', 'ﬓ', 'ﬔ', 'ﬕ', 'ﬖ', 'ﬗ'];
private const FOLD_TO = ['i̇', 'μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', 'ṡ', 'ι', 'ss', 'i̇', 'ʼn', 'ǰ', 'ΐ', 'ΰ', 'եւ', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'aʾ', 'ss', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὰι', 'αι', 'άι', 'ᾶ', 'ᾶι', 'αι', 'ὴι', 'ηι', 'ήι', 'ῆ', 'ῆι', 'ηι', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'ῢ', 'ΰ', 'ῤ', 'ῦ', 'ῧ', 'ὼι', 'ωι', 'ώι', 'ῶ', 'ῶι', 'ωι', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'st', 'st', 'մն', 'մե', 'մի', 'վն', 'մխ'];
private const UPPER_FROM = ['ß', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'ſt', 'st', 'և', 'ﬓ', 'ﬔ', 'ﬕ', 'ﬖ', 'ﬗ', 'ʼn', 'ΐ', 'ΰ', 'ǰ', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'ẚ', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ᾶ', 'ῆ', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'ῢ', 'ΰ', 'ῤ', 'ῦ', 'ῧ', '
'];
// the subset of upper case mappings that map one code point to many code points
private const UPPER_FROM = ['ß', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'ſt', 'st', 'և', 'ﬓ', 'ﬔ', 'ﬕ', 'ﬖ', 'ﬗ', 'ʼn', 'ΐ', 'ΰ', 'ǰ', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'ẚ', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ᾶ', 'ῆ', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'ῢ', 'ΰ', 'ῤ', 'ῦ', 'ῧ', 'ῶ'];
private const UPPER_TO = ['SS', 'FF', 'FI', 'FL', 'FFI', 'FFL', 'ST', 'ST', 'ԵՒ', 'ՄՆ', 'ՄԵ', 'ՄԻ', 'ՎՆ', 'ՄԽ', 'ʼN', 'Ϊ́', 'Ϋ́', 'J̌', 'H̱', 'T̈', 'W̊', 'Y̊', 'Aʾ', 'Υ̓', 'Υ̓̀', 'Υ̓́', 'Υ̓͂', 'Α͂', 'Η͂', 'Ϊ̀', 'Ϊ́', 'Ι͂', 'Ϊ͂', 'Ϋ̀', 'Ϋ́', 'Ρ̓', 'Υ͂', 'Ϋ͂', 'Ω͂'];
private const TRANSLIT_FROM = ['Ð', 'Ø', 'Þ', 'ð', 'ø', 'þ', 'Đ', 'đ', 'Ħ', 'ħ', 'ı', 'ĸ', 'Ŋ', 'ŋ', 'Ŧ', 'ŧ', 'ƀ', 'Ɓ', 'Ƃ', 'ƃ', 'Ƈ', 'ƈ', 'Ɖ', 'Ɗ', 'Ƌ', 'ƌ', 'Ɛ', 'Ƒ', 'ƒ', 'Ɠ', 'ƕ', 'Ɩ', 'Ɨ', 'Ƙ', 'ƙ', 'ƚ', 'Ɲ', 'ƞ', 'Ƣ', 'ƣ', 'Ƥ', 'ƥ', 'ƫ', 'Ƭ', 'ƭ', 'Ʈ', 'Ʋ', 'Ƴ', 'ƴ', 'Ƶ', 'ƶ', 'Ǥ', 'ǥ', 'ȡ', 'Ȥ', 'ȥ', 'ȴ', 'ȵ', 'ȶ', 'ȷ', 'ȸ', 'ȹ', 'Ⱥ', 'Ȼ', 'ȼ', 'Ƚ', 'Ⱦ', 'ȿ', 'ɀ', 'Ƀ', 'Ʉ', 'Ɇ', 'ɇ', 'Ɉ', 'ɉ', 'Ɍ', 'ɍ', 'Ɏ', 'ɏ', 'ɓ', 'ɕ', 'ɖ', 'ɗ', 'ɛ', 'ɟ', 'ɠ', 'ɡ', 'ɢ', 'ɦ', 'ɧ', 'ɨ', 'ɪ', 'ɫ', 'ɬ', 'ɭ', 'ɱ', 'ɲ', 'ɳ', 'ɴ', 'ɶ', 'ɼ', 'ɽ', 'ɾ', 'ʀ', 'ʂ', 'ʈ', 'ʉ', 'ʋ', 'ʏ', 'ʐ', 'ʑ', 'ʙ', 'ʛ', 'ʜ', 'ʝ', 'ʟ', 'ʠ', 'ʣ', 'ʥ', 'ʦ', 'ʪ', 'ʫ', 'ᴀ', 'ᴁ', 'ᴃ', '', 'ᴅ', 'ᴆ', 'ᴇ', 'ᴊ', 'ᴋ', 'ᴌ', 'ᴍ', '', 'ᴘ', 'ᴛ', '', '', '', '', 'ᵫ', 'ᵬ', 'ᵭ', 'ᵮ', 'ᵯ', 'ᵰ', 'ᵱ', 'ᵲ', 'ᵳ', 'ᵴ', 'ᵵ', 'ᵶ', 'ᵺ', 'ᵻ', 'ᵽ', 'ᵾ', 'ᶀ', 'ᶁ', 'ᶂ', '', 'ᶄ', 'ᶅ', 'ᶆ', 'ᶇ', 'ᶈ', 'ᶉ', 'ᶊ', '', 'ᶍ', 'ᶎ', 'ᶏ', 'ᶑ', 'ᶒ', 'ᶓ', 'ᶖ', 'ᶙ', 'ẜ', '', 'ẞ', 'Ỻ', 'ỻ', 'Ỽ', 'ỽ', 'Ỿ', 'ỿ', '₠', '₢', '₣', '₤', '₧', '₹', '℞', '', '', '〝', '〞', '‖', '⁅', '⁆', '', '、', '。', '〈', '〉', '《', '》', '', '', '〘', '〙', '〚', '〛', '︑', '︒', '︹', '︺', '︽', '︾', '︿', '﹀', '÷', '∥', '⦅', '⦆'];
private const TRANSLIT_TO = ['D', 'O', 'TH', 'd', 'o', 'th', 'D', 'd', 'H', 'h', 'i', 'q', 'N', 'n', 'T', 't', 'b', 'B', 'B', 'b', 'C', 'c', 'D', 'D', 'D', 'd', 'E', 'F', 'f', 'G', 'hv', 'I', 'I', 'K', 'k', 'l', 'N', 'n', 'OI', 'oi', 'P', 'p', 't', 'T', 't', 'T', 'V', 'Y', 'y', 'Z', 'z', 'G', 'g', 'd', 'Z', 'z', 'l', 'n', 't', 'j', 'db', 'qp', 'A', 'C', 'c', 'L', 'T', 's', 'z', 'B', 'U', 'E', 'e', 'J', 'j', 'R', 'r', 'Y', 'y', 'b', 'c', 'd', 'd', 'e', 'j', 'g', 'g', 'G', 'h', 'h', 'i', 'I', 'l', 'l', 'l', 'm', 'n', 'n', 'N', 'OE', 'r', 'r', 'r', 'R', 's', 't', 'u', 'v', 'Y', 'z', 'z', 'B', 'G', 'H', 'j', 'L', 'q', 'dz', 'dz', 'ts', 'ls', 'lz', 'A', 'AE', 'B', 'C', 'D', 'D', 'E', 'J', 'K', 'L', 'M', 'O', 'P', 'T', 'U', 'V', 'W', 'Z', 'ue', 'b', 'd', 'f', 'm', 'n', 'p', 'r', 'r', 's', 't', 'z', 'th', 'I', 'p', 'U', 'b', 'd', 'f', 'g', 'k', 'l', 'm', 'n', 'p', 'r', 's', 'v', 'x', 'z', 'a', 'd', 'e', 'e', 'i', 'u', 's', 's', 'SS', 'LL', 'll', 'V', 'v', 'Y', 'y', 'CE', 'Cr', 'Fr.', 'L.', 'Pts', 'Rs', 'Rx', '0', '\'', '"', '"', '||', '[', ']', '*', ',', '.', '<', '>', '<<', '>>', '[', ']', '[', ']', '[', ']', ',', '.', '[', ']', '<<', '>>', '<', '>', '/', '||', '((', '))'];
// the subset of https://github.com/unicode-org/cldr/blob/master/common/transforms/Latin-ASCII.xml that is not in NFKD
private const TRANSLIT_FROM = ['Æ', 'Ð', 'Ø', 'Þ', 'ß', 'æ', 'ð', 'ø', 'þ', 'Đ', 'đ', 'Ħ', 'ħ', 'ı', 'ĸ', 'Ŀ', 'ŀ', 'Ł', 'ł', 'ʼn', 'Ŋ', 'ŋ', 'Œ', 'œ', 'Ŧ', 'ŧ', 'ƀ', 'Ɓ', 'Ƃ', 'ƃ', 'Ƈ', 'ƈ', 'Ɖ', 'Ɗ', 'Ƌ', 'ƌ', 'Ɛ', 'Ƒ', 'ƒ', 'Ɠ', 'ƕ', 'Ɩ', 'Ɨ', 'Ƙ', 'ƙ', 'ƚ', 'Ɲ', 'ƞ', 'Ƣ', 'ƣ', 'Ƥ', 'ƥ', 'ƫ', 'Ƭ', 'ƭ', 'Ʈ', 'Ʋ', 'Ƴ', 'ƴ', 'Ƶ', 'ƶ', 'DŽ', 'Dž', 'dž', 'Ǥ', 'ǥ', 'ȡ', 'Ȥ', 'ȥ', 'ȴ', 'ȵ', 'ȶ', 'ȷ', 'ȸ', 'ȹ', 'Ⱥ', 'Ȼ', 'ȼ', 'Ƚ', 'Ⱦ', 'ȿ', 'ɀ', 'Ƀ', 'Ʉ', 'Ɇ', 'ɇ', 'Ɉ', 'ɉ', 'Ɍ', 'ɍ', 'Ɏ', 'ɏ', 'ɓ', 'ɕ', 'ɖ', 'ɗ', 'ɛ', 'ɟ', 'ɠ', 'ɡ', 'ɢ', 'ɦ', 'ɧ', 'ɨ', 'ɪ', 'ɫ', 'ɬ', 'ɭ', 'ɱ', 'ɲ', 'ɳ', 'ɴ', 'ɶ', 'ɼ', 'ɽ', 'ɾ', 'ʀ', 'ʂ', 'ʈ', 'ʉ', 'ʋ', 'ʏ', 'ʐ', 'ʑ', 'ʙ', 'ʛ', 'ʜ', 'ʝ', 'ʟ', 'ʠ', 'ʣ', 'ʥ', 'ʦ', 'ʪ', 'ʫ', 'ᴀ', 'ᴁ', 'ᴃ', '', 'ᴅ', 'ᴆ', 'ᴇ', 'ᴊ', 'ᴋ', 'ᴌ', 'ᴍ', '', 'ᴘ', 'ᴛ', '', '', '', '', 'ᵫ', 'ᵬ', 'ᵭ', 'ᵮ', 'ᵯ', 'ᵰ', 'ᵱ', 'ᵲ', 'ᵳ', 'ᵴ', 'ᵵ', 'ᵶ', 'ᵺ', 'ᵻ', 'ᵽ', 'ᵾ', 'ᶀ', 'ᶁ', 'ᶂ', '', 'ᶄ', 'ᶅ', 'ᶆ', 'ᶇ', 'ᶈ', 'ᶉ', 'ᶊ', '', 'ᶍ', 'ᶎ', 'ᶏ', 'ᶑ', 'ᶒ', 'ᶓ', 'ᶖ', 'ᶙ', 'ẚ', 'ẜ', '', 'ẞ', 'Ỻ', 'ỻ', 'Ỽ', 'ỽ', 'Ỿ', 'ỿ', '©', '®', '₠', '₢', '₣', '₤', '₧', '₺', '₹', '', '℞', '㎧', '㎮', '㏆', '㏗', '㏞', '㏟', '¼', '½', '¾', '⅓', '⅔', '⅕', '⅖', '⅗', '⅘', '⅙', '⅚', '⅛', '⅜', '⅝', '⅞', '⅟', '', '', '', '', '', '“', '”', '„', '‟', '', '″', '〝', '〞', '«', '»', '', '', '', '', '', '', '—', '―', '︱', '︲', '', '‖', '', '⁅', '⁆', '', '、', '。', '〈', '〉', '《', '》', '', '', '〘', '〙', '〚', '〛', '︑', '︒', '︹', '︺', '︽', '︾', '︿', '﹀', '﹑', '﹝', '﹞', '⦅', '⦆', '。', '、', '×', '÷', '', '', '', '', '∥', '≪', '≫', '⦅', '⦆'];
private const TRANSLIT_TO = ['AE', 'D', 'O', 'TH', 'ss', 'ae', 'd', 'o', 'th', 'D', 'd', 'H', 'h', 'i', 'q', 'L', 'l', 'L', 'l', '\'n', 'N', 'n', 'OE', 'oe', 'T', 't', 'b', 'B', 'B', 'b', 'C', 'c', 'D', 'D', 'D', 'd', 'E', 'F', 'f', 'G', 'hv', 'I', 'I', 'K', 'k', 'l', 'N', 'n', 'OI', 'oi', 'P', 'p', 't', 'T', 't', 'T', 'V', 'Y', 'y', 'Z', 'z', 'DZ', 'Dz', 'dz', 'G', 'g', 'd', 'Z', 'z', 'l', 'n', 't', 'j', 'db', 'qp', 'A', 'C', 'c', 'L', 'T', 's', 'z', 'B', 'U', 'E', 'e', 'J', 'j', 'R', 'r', 'Y', 'y', 'b', 'c', 'd', 'd', 'e', 'j', 'g', 'g', 'G', 'h', 'h', 'i', 'I', 'l', 'l', 'l', 'm', 'n', 'n', 'N', 'OE', 'r', 'r', 'r', 'R', 's', 't', 'u', 'v', 'Y', 'z', 'z', 'B', 'G', 'H', 'j', 'L', 'q', 'dz', 'dz', 'ts', 'ls', 'lz', 'A', 'AE', 'B', 'C', 'D', 'D', 'E', 'J', 'K', 'L', 'M', 'O', 'P', 'T', 'U', 'V', 'W', 'Z', 'ue', 'b', 'd', 'f', 'm', 'n', 'p', 'r', 'r', 's', 't', 'z', 'th', 'I', 'p', 'U', 'b', 'd', 'f', 'g', 'k', 'l', 'm', 'n', 'p', 'r', 's', 'v', 'x', 'z', 'a', 'd', 'e', 'e', 'i', 'u', 'a', 's', 's', 'SS', 'LL', 'll', 'V', 'v', 'Y', 'y', '(C)', '(R)', 'CE', 'Cr', 'Fr.', 'L.', 'Pts', 'TL', 'Rs', 'x', 'Rx', 'm/s', 'rad/s', 'C/kg', 'pH', 'V/m', 'A/m', ' 1/4', ' 1/2', ' 3/4', ' 1/3', ' 2/3', ' 1/5', ' 2/5', ' 3/5', ' 4/5', ' 1/6', ' 5/6', ' 1/8', ' 3/8', ' 5/8', ' 7/8', ' 1/', '0', '\'', '\'', ',', '\'', '"', '"', ',,', '"', '\'', '"', '"', '"', '<<', '>>', '<', '>', '-', '-', '-', '-', '-', '-', '-', '-', '-', '||', '/', '[', ']', '*', ',', '.', '<', '>', '<<', '>>', '[', ']', '[', ']', '[', ']', ',', '.', '[', ']', '<<', '>>', '<', '>', ',', '[', ']', '((', '))', '.', ',', '*', '/', '-', '/', '\\', '|', '||', '<<', '>>', '((', '))'];
private static $transliterators = [];
@ -81,14 +87,14 @@ abstract class AbstractUnicodeString extends AbstractString
$s = $str->string;
$str->string = '';
array_unshift($rules, 'nfd');
$rules[] = 'latin-ascii';
if (\function_exists('transliterator_transliterate')) {
array_unshift($rules, 'nfd');
$rules[] = 'any-latin/bgn';
$rules[] = 'nfkd';
} else {
array_unshift($rules, 'nfkd');
}
$rules[] = 'nfkd';
$rules[] = '[:nonspacing mark:] remove';
while (\strlen($s) - 1 > $i = strspn($s, self::ASCII)) {
@ -110,6 +116,8 @@ abstract class AbstractUnicodeString extends AbstractString
normalizer_is_normalized($s, self::NFKD) ?: $s = normalizer_normalize($s, self::NFKD);
} elseif ('[:nonspacing mark:] remove' === $rule) {
$s = preg_replace('/\p{Mn}++/u', '', $s);
} elseif ('latin-ascii' === $rule) {
$s = str_replace(self::TRANSLIT_FROM, self::TRANSLIT_TO, $s);
} elseif ('de-ascii' === $rule) {
$s = preg_replace("/([AUO])\u{0308}(?=\p{Ll})/u", '$1e', $s);
$s = str_replace(["a\u{0308}", "o\u{0308}", "u\u{0308}", "A\u{0308}", "O\u{0308}", "U\u{0308}"], ['ae', 'oe', 'ue', 'AE', 'OE', 'UE'], $s);
@ -130,12 +138,10 @@ abstract class AbstractUnicodeString extends AbstractString
$s = $transliterator->transliterate($s);
}
} elseif (!\function_exists('iconv')) {
$s = str_replace(self::TRANSLIT_FROM, self::TRANSLIT_TO, $s);
$s = preg_replace('/[^\x00-\x7F]/u', '?', $s);
} elseif (\ICONV_IMPL === 'glibc') {
$s = iconv('UTF-8', 'ASCII//TRANSLIT', $s);
} else {
$s = str_replace(self::TRANSLIT_FROM, self::TRANSLIT_TO, $s);
$s = preg_replace_callback('/[^\x00-\x7F]/u', static function ($c) {
$c = iconv('UTF-8', 'ASCII//IGNORE//TRANSLIT', $c[0]);

View File

@ -36,6 +36,7 @@ class SluggerTest extends TestCase
['Αυτή η τιμή πρέπει να είναι ψευδής', 'el', 'Avti-i-timi-prepi-na-inai-psevdhis'],
['该变量的值应为', 'zh', 'gai-bian-liang-de-zhi-ying-wei'],
['該變數的值應為', 'zh_TW', 'gai-bian-shu-de-zhi-ying-wei'],
['Wôrķšƥáçè ~~sèťtïñğš~~', 'C', 'Workspace-settings'],
];
}

View File

@ -347,6 +347,11 @@ class UnicodeString extends AbstractUnicodeString
return $prefix === grapheme_extract($this->string, \strlen($prefix), GRAPHEME_EXTR_MAXBYTES);
}
public function __wakeup()
{
normalizer_is_normalized($this->string) ?: $this->string = normalizer_normalize($this->string);
}
public function __clone()
{
if (null === $this->ignoreCase) {

View File

@ -51,36 +51,37 @@ abstract class FileDumper implements DumperInterface
throw new InvalidArgumentException('The file dumper needs a path option.');
}
$hasMessageFormatter = class_exists(\MessageFormatter::class);
// save a file for each domain
foreach ($messages->getDomains() as $domain) {
$fullpath = $options['path'].'/'.$this->getRelativePath($domain, $messages->getLocale());
if (!file_exists($fullpath)) {
$directory = \dirname($fullpath);
if ($hasMessageFormatter) {
$defaultDomain = $domain.MessageCatalogue::INTL_DOMAIN_SUFFIX;
$altDomain = $domain;
} else {
$defaultDomain = $domain;
$altDomain = $domain.MessageCatalogue::INTL_DOMAIN_SUFFIX;
}
$defaultPath = $options['path'].'/'.$this->getRelativePath($defaultDomain, $messages->getLocale());
$altPath = $options['path'].'/'.$this->getRelativePath($altDomain, $messages->getLocale());
if (!file_exists($defaultPath) && file_exists($altPath)) {
[$defaultPath, $altPath] = [$altPath, $defaultPath];
}
if (!file_exists($defaultPath)) {
$directory = \dirname($defaultPath);
if (!file_exists($directory) && !@mkdir($directory, 0777, true)) {
throw new RuntimeException(sprintf('Unable to create directory "%s".', $directory));
}
}
$intlDomain = $domain.MessageCatalogue::INTL_DOMAIN_SUFFIX;
$intlMessages = $messages->all($intlDomain);
if ($intlMessages) {
$intlPath = $options['path'].'/'.$this->getRelativePath($intlDomain, $messages->getLocale());
file_put_contents($intlPath, $this->formatCatalogue($messages, $intlDomain, $options));
$messages->replace([], $intlDomain);
try {
if ($messages->all($domain)) {
file_put_contents($fullpath, $this->formatCatalogue($messages, $domain, $options));
}
continue;
} finally {
$messages->replace($intlMessages, $intlDomain);
}
if (file_exists($altPath)) {
// clear alternative translation file
file_put_contents($altPath, $this->formatCatalogue(new MessageCatalogue($messages->getLocale()), $altDomain, $options));
}
file_put_contents($fullpath, $this->formatCatalogue($messages, $domain, $options));
file_put_contents($defaultPath, $this->formatCatalogue($messages, $domain, $options));
}
}

View File

@ -27,11 +27,15 @@ class FileDumperTest extends TestCase
$dumper = new ConcreteFileDumper();
$dumper->dump($catalogue, ['path' => $tempDir]);
$this->assertFileExists($tempDir.'/messages.en.concrete');
$suffix = class_exists(\MessageFormatter::class) ? '+intl-icu' : '';
$this->assertFileExists($tempDir."/messages$suffix.en.concrete");
@unlink($tempDir.'/messages.en.concrete');
@unlink($tempDir."/messages$suffix.en.concrete");
}
/**
* @requires extension intl
*/
public function testDumpIntl()
{
$tempDir = sys_get_temp_dir();
@ -42,13 +46,11 @@ class FileDumperTest extends TestCase
$catalogue->add(['bar' => 'foo'], 'd2+intl-icu');
$dumper = new ConcreteFileDumper();
@unlink($tempDir.'/d2.en.concrete');
$dumper->dump($catalogue, ['path' => $tempDir]);
$this->assertStringEqualsFile($tempDir.'/d1.en.concrete', 'foo=bar');
@unlink($tempDir.'/d1.en.concrete');
$this->assertFileNotExists($tempDir.'/d1.en.concrete');
$this->assertStringEqualsFile($tempDir.'/d1+intl-icu.en.concrete', 'bar=foo');
$this->assertStringEqualsFile($tempDir.'/d1+intl-icu.en.concrete', 'bar=foo&foo=bar');
@unlink($tempDir.'/d1+intl-icu.en.concrete');
$this->assertFileNotExists($tempDir.'/d2.en.concrete');
@ -60,7 +62,8 @@ class FileDumperTest extends TestCase
{
$tempDir = sys_get_temp_dir();
$translationsDir = $tempDir.'/test/translations';
$file = $translationsDir.'/messages.en.concrete';
$suffix = class_exists(\MessageFormatter::class) ? '+intl-icu' : '';
$file = $translationsDir."/messages$suffix.en.concrete";
$catalogue = new MessageCatalogue('en');
$catalogue->add(['foo' => 'bar']);

View File

@ -23,7 +23,7 @@ class UrlValidator extends ConstraintValidator
{
const PATTERN = '~^
(%s):// # protocol
(([\.\pL\pN-]+:)?([\.\pL\pN-]+)@)? # basic auth
(([\_\.\pL\pN-]+:)?([\_\.\pL\pN-]+)@)? # basic auth
(
([\pL\pN\pS\-\_\.])+(\.?([\pL\pN]|xn\-\-[\pL\pN-]+)+\.?) # a domain name
| # or

View File

@ -148,9 +148,11 @@ class UrlValidatorTest extends ConstraintValidatorTestCase
['http://☎.com/'],
['http://username:password@symfony.com'],
['http://user.name:password@symfony.com'],
['http://user_name:pass_word@symfony.com'],
['http://username:pass.word@symfony.com'],
['http://user.name:pass.word@symfony.com'],
['http://user-name@symfony.com'],
['http://user_name@symfony.com'],
['http://symfony.com?'],
['http://symfony.com?query=1'],
['http://symfony.com/?query=1'],

View File

@ -617,11 +617,11 @@ class Inline
// Optimize for returning strings.
// no break
case '+' === $scalar[0] || '-' === $scalar[0] || '.' === $scalar[0] || is_numeric($scalar[0]):
if (Parser::preg_match('{^[+-]?[0-9][0-9_]*$}', $scalar)) {
$scalar = str_replace('_', '', (string) $scalar);
}
switch (true) {
case Parser::preg_match('{^[+-]?[0-9][0-9_]*$}', $scalar):
$scalar = str_replace('_', '', (string) $scalar);
// omitting the break / return as integers are handled in the next case
// no break
case ctype_digit($scalar):
$raw = $scalar;
$cast = (int) $scalar;
@ -631,7 +631,7 @@ class Inline
$raw = $scalar;
$cast = (int) $scalar;
return '0' == $scalar[1] ? octdec($scalar) : (((string) $raw === (string) $cast) ? $cast : $raw);
return '0' == $scalar[1] ? -octdec(substr($scalar, 1)) : (($raw === (string) $cast) ? $cast : $raw);
case is_numeric($scalar):
case Parser::preg_match(self::getHexRegex(), $scalar):
$scalar = str_replace('_', '', $scalar);

View File

@ -720,4 +720,20 @@ class InlineTest extends TestCase
$this->expectExceptionMessage("Unexpected end of line, expected one of \",}\n\" at line 1 (near \"{abc: 'def'\").");
Inline::parse("{abc: 'def'");
}
/**
* @dataProvider getTestsForOctalNumbers
*/
public function testParseOctalNumbers($expected, $yaml)
{
self::assertSame($expected, Inline::parse($yaml));
}
public function getTestsForOctalNumbers()
{
return [
'positive octal number' => [28, '034'],
'negative octal number' => [-28, '-034'],
];
}
}