Merge branch '4.4'

* 4.4: (26 commits)
  cs fix
  [Validator] sync NO and NB translations
  [Cache] improve perf of pruning for fs-based adapters
  [Cache] cs fix
  [Cache] clean tags folder on invalidation
  [Cache] remove implicit dependency on symfony/filesystem
  Allow to set cookie_samesite to 'none'
  [Dotenv] support setting default env var values
  [VarDumper] fix array key error for class SymfonyCaster
  [Cache] Improve RedisTagAwareAdapter invalidation logic & requirements
  Adds missing translations for no nb
  [HttpKernel] fix $dotenvVars in data collector
  Add the missing translations for the Swedish ("sv") locale
  Prevent ProgressBar redraw when message is same
  [DI] enable improved syntax for defining method calls in Yaml
  bumped Symfony version to 4.3.6
  updated VERSION for 4.3.5
  updated CHANGELOG for 4.3.5
  bumped Symfony version to 3.4.33
  updated VERSION for 3.4.32
  ...
This commit is contained in:
Nicolas Grekas 2019-10-09 16:45:46 +02:00
commit 68c6e72cd5
33 changed files with 808 additions and 248 deletions

View File

@ -7,6 +7,91 @@ 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.5 (2019-10-07)
* bug #33742 [Crawler] document $default as string|null (nicolas-grekas)
* bug #32308 [Messenger] DoctrineTransport: ensure auto setup is only done once (bendavies)
* bug #33871 [HttpClient] bugfix exploding values of headers (michaljusiega)
* bug #33834 [Validator] Fix ValidValidator group cascading usage (fancyweb)
* bug #33863 [Routing] gracefully handle docref_root ini setting (nicolas-grekas)
* bug #33846 [Cache] give 100ms before starting the expiration countdown (nicolas-grekas)
* bug #33853 [HttpClient] fix "no_proxy" option ignored in NativeHttpClient (Harry-Dunne)
* bug #33841 [VarDumper] fix dumping uninitialized SplFileInfo (nicolas-grekas)
* bug #33842 [Cache] fix logger usage in CacheTrait::doGet() (nicolas-grekas)
* bug #33835 [Workflow] Fixed BC break on WorkflowInterface (lyrixx)
* bug #33799 [Security]: Don't let falsy usernames slip through impersonation (j4nr6n)
* bug #33814 [HttpFoundation] Check if data passed to SessionBagProxy::initialize is an array (mynameisbogdan)
* bug #33744 [DI] Add CSV env var processor tests / support PHP 7.4 (ro0NL)
* bug #33805 [FrameworkBundle] Fix wrong returned status code in ConfigDebugCommand (jschaedl)
* bug #33781 [AnnotationCacheWarmer] add RedirectController to annotation cache (jenschude)
* bug #33777 Fix the :only-of-type pseudo class selector (jakzal)
* bug #32051 [Serializer] Add CsvEncoder tests for PHP 7.4 (ro0NL)
* feature #33776 Copy phpunit.xsd to a predictable path (julienfalque)
* bug #33759 [Security/Http] fix parsing X509 emailAddress (nicolas-grekas)
* bug #33733 [Serializer] fix denormalization of string-arrays with only one element (mkrauser)
* bug #33754 [Cache] fix known tag versions ttl check (SwenVanZanten)
* bug #33646 [HttpFoundation] allow additinal characters in not raw cookies (marie)
* bug #33748 [Console] Do not include hidden commands in suggested alternatives (m-vo)
* bug #33625 [DependencyInjection] Fix wrong exception when service is synthetic (k0d3r1s)
* bug #32979 [Messenger] return empty envelopes when RetryableException occurs (surikman)
* bug #32522 [Validator] Accept underscores in the URL validator, as the URL will load (battye)
* bug #32437 Fix toolbar load when GET params are present in "_wdt" route (Molkobain)
* bug #32925 [Translation] Collect original locale in case of fallback translation (digilist)
* bug #33691 [HttpClient] fix race condition when reading response with informational status (nicolas-grekas)
* bug #33727 [HttpClient] workaround bad Content-Length sent by old libcurl (nicolas-grekas)
* bug #31198 [FrameworkBundle] Fix framework bundle lock configuration not working as expected (HypeMC)
* bug #33719 [Cache] dont override native Memcached options (nicolas-grekas)
* bug #33703 [Cache] fail gracefully when locking is not supported (nicolas-grekas)
* bug #33713 Fix exceptions (PDOException) error code type (fruty)
* bug #32335 [Form] Names for buttons should start with lowercase (mcfedr)
* bug #33706 [Mailer][Messenger] ensure legacy event dispatcher compatibility (xabbuh)
* bug #33688 Add missing row_attr option to FormType (mcsky)
* bug #33693 [Security] use LegacyEventDispatcherProxy (dmaicher)
* bug #33675 [PhpUnit] Fix usleep mock return value (fabpot)
* bug #33652 [Cache] skip igbinary on PHP 7.4.0 (nicolas-grekas)
* bug #33643 [HttpClient] fix throwing HTTP exceptions when the 1st chunk is emitted (nicolas-grekas)
* bug #33618 fix tests depending on other components' tests (xabbuh)
* bug #33626 [PropertyInfo] ensure compatibility with type resolver 0.5 (xabbuh)
* bug #33620 [Twig] Fix Twig config extra keys (fabpot)
* bug #33600 [Messenger] Fix exception message of failed message is dropped on retry (tienvx)
* bug #33601 [HttpClient] Add default value for Accept header (numerogeek)
* bug #33340 [Finder] Adjust regex to correctly match comments in gitignore contents (Jeroeny)
* bug #33588 [PropertyInfo] ensure compatibility with type resolver 0.5 (xabbuh)
* bug #33575 [WebProfilerBundle] Fix time panel legend buttons (fancyweb)
* bug #33571 [Inflector] add support 'see' to 'ee' for singularize 'fees' to 'fee' (maxhelias)
* bug #32763 [Console] Get dimensions from stty on windows if possible (rtek)
* bug #33570 Fixed cache pools affecting each other due to an overwritten seed variable (roed)
* bug #33517 [Yaml] properly catch legacy tag syntax usages (xabbuh)
* bug #33546 [DependencyInjection] Accept existing interfaces as valid named args (fancyweb)
* bug #33547 [HttpClient] Re-enable Server Push support (dunglas)
* bug #33521 Fixed incompatibility between ServiceSubscriberTrait and classes with protected $container property (a-menshchikov)
* bug #33518 [Yaml] don't dump a scalar tag value on its own line (xabbuh)
* bug #33505 [HttpClient] fallbackto CURLMOPT_MAXCONNECTS when CURLMOPT_MAX_HOST_CONNECTIONS is not available (nicolas-grekas)
* bug #32818 [HttpKernel] Fix getFileLinkFormat() to avoid returning the wrong URL in Profiler (Arman-Hosseini)
* bug #33487 [HttpKernel] Fix Apache mod_expires Session Cache-Control issue (pbowyer)
* bug #33469 [FrameworkBundle] Fixed suggested package for missing server:dump command (lyrixx)
* bug #31964 [Router] routing cache crash when using generator_class (dFayet)
* bug #33481 [Messenger] fix empty amqp body returned as false (Tobion)
* bug #33387 [Mailer] maintain sender/recipient name in SMTP envelopes (xabbuh)
* bug #33449 Fix gmail relay (Beno!t POLASZEK)
* bug #33391 [HttpClient] fix support for 103 Early Hints and other informational status codes (nicolas-grekas)
* bug #33444 [HttpClient] improve handling of HTTP/2 PUSH, disable it by default (nicolas-grekas)
* bug #33435 [Validator] Only handle numeric values in DivisibleBy (fancyweb)
* bug #33437 Fix #33427 (sylfabre)
* bug #33439 [Validator] Sync string to date behavior and throw a better exception (fancyweb)
* bug #33436 [DI] fix support for "!tagged_locator foo" (nicolas-grekas)
* bug #32903 [PHPUnit Bridge] Avoid registering listener twice (alexpott)
* bug #33432 [Mailer] Fix Mailgun support when a response is not JSON as expected (fabpot)
* bug #33402 [Finder] Prevent unintentional file locks in Windows (jspringe)
* bug #33376 [Mailer] Remove the default dispatcher in AbstractTransport (fabpot)
* bug #33357 [FrameworkBundle] Fix about command not showing .env vars (brentybh)
* bug #33396 Fix #33395 PHP 5.3 compatibility (kylekatarnls)
* bug #33363 [Routing] fix static route reordering when a previous dynamic route conflicts (nicolas-grekas)
* bug #33385 [Console] allow Command::getName() to return null (nicolas-grekas)
* bug #33353 Return null as Expire header if it was set to null (danrot)
* bug #33382 [ProxyManager] remove ProxiedMethodReturnExpression polyfill (nicolas-grekas)
* bug #33377 [Yaml] fix dumping not inlined scalar tag values (xabbuh)
* 4.3.4 (2019-08-26)
* bug #33335 [DependencyInjection] Fixed the `getServiceIds` implementation to always return aliases (pdommelen)

View File

@ -6,36 +6,36 @@ Symfony is the result of the work of many people who made the code better
- Fabien Potencier (fabpot)
- Nicolas Grekas (nicolas-grekas)
- Bernhard Schussek (bschussek)
- Christian Flothmann (xabbuh)
- Bernhard Schussek (bschussek)
- Tobias Schultze (tobion)
- Christophe Coevoet (stof)
- Robin Chalas (chalas_r)
- Jordi Boggiano (seldaek)
- Victor Berchet (victor)
- Kévin Dunglas (dunglas)
- Victor Berchet (victor)
- Maxime Steinhausser (ogizanagi)
- Ryan Weaver (weaverryan)
- Javier Eguiluz (javier.eguiluz)
- Jakub Zalas (jakubzalas)
- Roland Franssen (ro0)
- Jakub Zalas (jakubzalas)
- Johannes S (johannes)
- Kris Wallsmith (kriswallsmith)
- Grégoire Pineau (lyrixx)
- Kris Wallsmith (kriswallsmith)
- Hugo Hamon (hhamon)
- Yonel Ceruto (yonelceruto)
- Abdellatif Ait boudad (aitboudad)
- Samuel ROZE (sroze)
- Yonel Ceruto (yonelceruto)
- Romain Neutron (romain)
- Pascal Borreli (pborreli)
- Wouter De Jong (wouterj)
- Joseph Bielawski (stloyd)
- Karma Dordrak (drak)
- Lukas Kahwe Smith (lsmith)
- Alexander M. Turek (derrabus)
- Martin Hasoň (hason)
- Hamza Amrouche (simperfit)
- Jeremy Mikola (jmikola)
- Alexander M. Turek (derrabus)
- Jean-François Simon (jfsimon)
- Jules Pietri (heah)
- Benjamin Eberlei (beberlei)
@ -44,21 +44,21 @@ Symfony is the result of the work of many people who made the code better
- Eriksen Costa (eriksencosta)
- Guilhem Niot (energetick)
- Sarah Khalil (saro0h)
- Thomas Calvet (fancyweb)
- Tobias Nyholm (tobias)
- Jonathan Wage (jwage)
- Lynn van der Berg (kjarli)
- Diego Saint Esteben (dosten)
- Thomas Calvet (fancyweb)
- Alexandre Salomé (alexandresalome)
- William Durand (couac)
- ornicar
- Pierre du Plessis (pierredup)
- Dany Maillard (maidmaid)
- Francis Besset (francisbesset)
- stealth35 (stealth35)
- Alexander Mols (asm89)
- Pierre du Plessis (pierredup)
- Matthias Pigulla (mpdude)
- Konstantin Myakshin (koc)
- Matthias Pigulla (mpdude)
- Bulat Shakirzyanov (avalanche123)
- Grégoire Paris (greg0ire)
- Saša Stamenković (umpirsky)
@ -69,13 +69,13 @@ Symfony is the result of the work of many people who made the code better
- Miha Vrhovnik
- Diego Saint Esteben (dii3g0)
- Gábor Egyed (1ed)
- Konstantin Kudryashov (everzet)
- Titouan Galopin (tgalopin)
- Konstantin Kudryashov (everzet)
- David Maicher (dmaicher)
- Bilal Amarni (bamarni)
- Mathieu Piot (mpiot)
- David Maicher (dmaicher)
- Florin Patan (florinpatan)
- Gabriel Ostrolucký (gadelat)
- Florin Patan (florinpatan)
- Vladimir Reznichenko (kalessil)
- Jáchym Toušek (enumag)
- Michel Weimerskirch (mweimerskirch)
@ -83,6 +83,7 @@ Symfony is the result of the work of many people who made the code better
- Issei Murasawa (issei_m)
- Eric Clemmons (ericclemmons)
- Charles Sarrazin (csarrazi)
- Jan Schädlich (jschaedl)
- Christian Raue
- Arnout Boks (aboks)
- Deni
@ -90,7 +91,6 @@ Symfony is the result of the work of many people who made the code better
- Dariusz Górecki (canni)
- Douglas Greenshields (shieldo)
- David Buchmann (dbu)
- Jan Schädlich (jschaedl)
- Dariusz Ruminski
- Lee McDermott
- Brandon Turner
@ -114,30 +114,30 @@ Symfony is the result of the work of many people who made the code better
- Baptiste Clavié (talus)
- marc.weistroff
- Tomáš Votruba (tomas_votruba)
- Jérôme Vasseur (jvasseur)
- lenar
- Alexander Schwenn (xelaris)
- Włodzimierz Gajda (gajdaw)
- Jérôme Vasseur (jvasseur)
- Sebastiaan Stok (sstok)
- Adrien Brault (adrienbrault)
- Peter Kokot (maastermedia)
- Jacob Dreesen (jdreesen)
- Florian Voutzinos (florianv)
- Sebastiaan Stok (sstok)
- Colin Frei
- Javier Spagnoletti (phansys)
- Adrien Brault (adrienbrault)
- Joshua Thijssen
- Daniel Wehner (dawehner)
- excelwebzone
- Gordon Franke (gimler)
- Teoh Han Hui (teohhanhui)
- Oskar Stark (oskarstark)
- Alex Pott
- Fabien Pennequin (fabienpennequin)
- Théo FIDRY (theofidry)
- Teoh Han Hui (teohhanhui)
- Eric GELOEN (gelo)
- Joel Wurtz (brouznouf)
- Lars Strojny (lstrojny)
- Tugdual Saunier (tucksaun)
- Alex Pott
- Jannik Zschiesche (apfelbox)
- Robert Schönthal (digitalkaoz)
- Florian Lonqueu-Brochard (florianlb)
@ -152,6 +152,7 @@ Symfony is the result of the work of many people who made the code better
- Daniel Gomes (danielcsgomes)
- Hidenori Goto (hidenorigoto)
- Andréia Bohner (andreia)
- Julien Falque (julienfalque)
- Arnaud Kleinpeter (nanocom)
- Guilherme Blanco (guilhermeblanco)
- SpacePossum
@ -160,7 +161,6 @@ Symfony is the result of the work of many people who made the code better
- François-Xavier de Guillebon (de-gui_f)
- Oleg Voronkovich
- Philipp Wahala (hifi)
- Julien Falque (julienfalque)
- Rafael Dohms (rdohms)
- jwdeitch
- Mikael Pajunen
@ -172,6 +172,7 @@ Symfony is the result of the work of many people who made the code better
- Richard Shank (iampersistent)
- Thomas Rabaix (rande)
- Vincent Touzet (vincenttouzet)
- Gregor Harlan (gharlan)
- jeremyFreeAgent (jeremyfreeagent)
- Rouven Weßling (realityking)
- Alexander Schranz (alexander-schranz)
@ -193,6 +194,7 @@ Symfony is the result of the work of many people who made the code better
- James Halsall (jaitsu)
- Matthieu Napoli (mnapoli)
- Florent Mata (fmata)
- Arman Hosseini
- Warnar Boekkooi (boekkooi)
- Dmitrii Chekaliuk (lazyhammer)
- Clément JOBEILI (dator)
@ -206,11 +208,11 @@ Symfony is the result of the work of many people who made the code better
- Mario A. Alvarez Garcia (nomack84)
- Dennis Benkert (denderello)
- DQNEO
- Gregor Harlan (gharlan)
- mcfedr (mcfedr)
- Gary PEGEOT (gary-p)
- Ruben Gonzalez (rubenrua)
- Benjamin Dulau (dbenjamin)
- Arman Hosseini
- Andreas Braun
- Mathieu Lemoine (lemoinem)
- Christian Schmidt
- Andreas Hucks (meandmymonkey)
@ -241,7 +243,6 @@ Symfony is the result of the work of many people who made the code better
- jeff
- John Kary (johnkary)
- Andreas Schempp (aschempp)
- Andreas Braun
- Justin Hileman (bobthecow)
- Blanchon Vincent (blanchonvincent)
- Michele Orselli (orso)
@ -265,7 +266,6 @@ Symfony is the result of the work of many people who made the code better
- julien pauli (jpauli)
- Lorenz Schori
- Sébastien Lavoie (lavoiesl)
- mcfedr (mcfedr)
- Dariusz
- Michael Babker (mbabker)
- Francois Zaninotto
@ -314,12 +314,14 @@ Symfony is the result of the work of many people who made the code better
- Jhonny Lidfors (jhonne)
- Diego Agulló (aeoris)
- jdhoek
- Maxime Helias (maxhelias)
- David Prévot
- Bob den Otter (bopp)
- Thomas Schulz (king2500)
- Frank de Jonge (frenkynet)
- Nikita Konstantinov
- Wodor Wodorski
- dFayet
- Thomas Lallement (raziel057)
- Colin O'Dell (colinodell)
- Giorgio Premi
@ -343,6 +345,7 @@ Symfony is the result of the work of many people who made the code better
- Christian Schmidt
- Patrick Landolt (scube)
- MatTheCat
- David Badura (davidbadura)
- Chad Sikorra (chadsikorra)
- Chris Smith (cs278)
- Florian Klein (docteurklein)
@ -361,6 +364,7 @@ Symfony is the result of the work of many people who made the code better
- Jerzy Zawadzki (jzawadzki)
- Wouter J
- Ismael Ambrosi (iambrosi)
- Ruud Kamphuis (ruudk)
- Emmanuel BORGES (eborges78)
- Aurelijus Valeiša (aurelijus)
- Jan Decavele (jandc)
@ -374,7 +378,6 @@ Symfony is the result of the work of many people who made the code better
- Francesc Rosàs (frosas)
- Romain Pierre (romain-pierre)
- Julien Galenski (ruian)
- Maxime Helias
- Bongiraud Dominique
- janschoenherr
- Emanuele Gaspari (inmarelibero)
@ -382,7 +385,6 @@ Symfony is the result of the work of many people who made the code better
- Berny Cantos (xphere81)
- Thierry Thuon (lepiaf)
- Ricard Clau (ricardclau)
- dFayet
- Mark Challoner (markchalloner)
- Gennady Telegin (gtelegin)
- Erin Millard
@ -410,10 +412,10 @@ Symfony is the result of the work of many people who made the code better
- Robbert Klarenbeek (robbertkl)
- Eric Masoero (eric-masoero)
- JhonnyL
- David Badura (davidbadura)
- hossein zolfi (ocean)
- Clément Gautier (clementgautier)
- Thomas Bisignani (toma)
- Dāvis Zālītis (k0d3r1s)
- Sanpi
- Eduardo Gulias (egulias)
- giulio de donato (liuggio)
@ -436,6 +438,7 @@ Symfony is the result of the work of many people who made the code better
- Tamas Szijarto
- Michele Locati
- Pavel Volokitin (pvolok)
- Valentine Boineau (valentineboineau)
- Arthur de Moulins (4rthem)
- Matthias Althaus (althaus)
- Nicolas Dewez (nicolas_dewez)
@ -447,13 +450,13 @@ Symfony is the result of the work of many people who made the code better
- Joe Lencioni
- Daniel Tschinder
- vladimir.reznichenko
- Ruud Kamphuis (ruudk)
- Kai
- Lee Rowlands
- Krzysztof Piasecki (krzysztek)
- Maximilian Reichel (phramz)
- Loick Piera (pyrech)
- Alain Hippolyte (aloneh)
- Grenier Kévin (mcsky_biig)
- Karoly Negyesi (chx)
- Ivan Kurnosov
- Xavier HAUSHERR
@ -481,9 +484,11 @@ Symfony is the result of the work of many people who made the code better
- Jeanmonod David (jeanmonod)
- Christopher Davis (chrisguitarguy)
- Webnet team (webnet)
- Farhad Safarov
- Jan Schumann
- Niklas Fiekas
- Markus Bachmann (baachi)
- Kévin THERAGE (kevin_therage)
- lancergr
- Mihai Stancu
- Ivan Nikolaev (destillat)
@ -499,6 +504,7 @@ Symfony is the result of the work of many people who made the code better
- EdgarPE
- Florian Pfitzer (marmelatze)
- Asier Illarramendi (doup)
- Sylvain Fabre (sylfabre)
- Martijn Cuppens
- Vlad Gregurco (vgregurco)
- Maciej Malarz (malarzm)
@ -517,6 +523,7 @@ Symfony is the result of the work of many people who made the code better
- Tobias Weichart
- Gonzalo Vilaseca (gonzalovilaseca)
- Marcin Sikoń (marphi)
- Tien Vo (tienvx)
- Denis Brumann (dbrumann)
- Dominik Zogg (dominik.zogg)
- Marek Pietrzak
@ -533,6 +540,7 @@ Symfony is the result of the work of many people who made the code better
- Anton Bakai
- Martin Auswöger
- Rhodri Pugh (rodnaph)
- battye
- Sam Fleming (sam_fleming)
- Alex Bakhturin
- Patrick Reimers (preimers)
@ -606,7 +614,6 @@ Symfony is the result of the work of many people who made the code better
- Nate (frickenate)
- Timothée Barray (tyx)
- jhonnyL
- Grenier Kévin (mcsky_biig)
- sasezaki
- Dawid Pakuła (zulusx)
- Florian Rey (nervo)
@ -621,6 +628,7 @@ Symfony is the result of the work of many people who made the code better
- Kevin Saliou (kbsali)
- Shawn Iwinski
- Gawain Lynch (gawain)
- mmokhi
- NothingWeAre
- Ryan
- Alexander Deruwe (aderuwe)
@ -643,9 +651,11 @@ Symfony is the result of the work of many people who made the code better
- Disquedur
- Michiel Boeckaert (milio)
- Geoffrey Tran (geoff)
- Kyle
- Jan Behrens
- Mantas Var (mvar)
- Chris Tanaskoski
- Terje Bråten
- Sebastian Krebs
- Piotr Stankowski
- Baptiste Leduc (bleduc)
@ -669,6 +679,7 @@ Symfony is the result of the work of many people who made the code better
- Kyle Evans (kevans91)
- Charles-Henri Bruyand
- Max Rath (drak3)
- Oleg Andreyev
- Stéphane Escandell (sescandell)
- Konstantin S. M. Möllers (ksmmoellers)
- James Johnston
@ -676,7 +687,6 @@ Symfony is the result of the work of many people who made the code better
- Alexandre Dupuy (satchette)
- Malte Blättermann
- Desjardins Jérôme (jewome62)
- Kévin THERAGE (kevin_therage)
- Simeon Kolev (simeon_kolev9)
- Joost van Driel (j92)
- Jonas Elfering
@ -693,7 +703,6 @@ Symfony is the result of the work of many people who made the code better
- Gunnstein Lye (glye)
- Maxime Douailin
- Jean Pasdeloup (pasdeloup)
- Sylvain Fabre (sylfabre)
- Benjamin Cremer (bcremer)
- Javier López (loalf)
- Reinier Kip
@ -712,6 +721,7 @@ Symfony is the result of the work of many people who made the code better
- DerManoMann
- Rostyslav Kinash
- Dennis Fridrich (dfridrich)
- Mardari Dorel (dorumd)
- Daisuke Ohata
- Vincent Simonin
- Alex Bogomazov (alebo)
@ -730,6 +740,7 @@ Symfony is the result of the work of many people who made the code better
- Miquel Rodríguez Telep (mrtorrent)
- Sergey Kolodyazhnyy (skolodyazhnyy)
- umpirski
- M. Vondano
- Quentin de Longraye (quentinus95)
- Chris Heng (gigablah)
- Shaun Simmons (simshaun)
@ -750,7 +761,7 @@ Symfony is the result of the work of many people who made the code better
- Kristijan Kanalas
- Stephan Vock
- Benjamin Zikarsky (bzikarsky)
- battye
- Ruben Jacobs (rubenj)
- Simon Schick (simonsimcity)
- redstar504
- Tristan Roussel
@ -799,6 +810,7 @@ Symfony is the result of the work of many people who made the code better
- Indra Gunawan (guind)
- Peter Ward
- Davide Borsatto (davide.borsatto)
- Markus Fasselt (digilist)
- Julien DIDIER (juliendidier)
- Dominik Ritter (dritter)
- Sebastian Grodzicki (sgrodzicki)
@ -935,7 +947,6 @@ Symfony is the result of the work of many people who made the code better
- Benoît Bourgeois
- mantulo
- Stefan Kruppa
- mmokhi
- corphi
- JoppeDC
- grizlik
@ -1005,10 +1016,8 @@ Symfony is the result of the work of many people who made the code better
- Pablo Lozano (arkadis)
- Erik Saunier (snickers)
- Rootie
- Kyle
- Daniel Alejandro Castro Arellano (lexcast)
- sensio
- Terje Bråten
- Thomas Jarrand
- Antoine Bluchet (soyuka)
- Sebastien Morel (plopix)
@ -1031,7 +1040,6 @@ Symfony is the result of the work of many people who made the code better
- The Whole Life to Learn
- Mikkel Paulson
- ergiegonzaga
- Farhad Safarov
- Liverbool (liverbool)
- Sam Malone
- Phan Thanh Ha (haphan)
@ -1039,7 +1047,9 @@ Symfony is the result of the work of many people who made the code better
- neghmurken
- xaav
- Mahmoud Mostafa (mahmoud)
- Julien Turby
- Ahmed Abdou
- Daniel Iwaniec
- Pieter
- Michael Tibben
- Billie Thompson
@ -1115,6 +1125,7 @@ Symfony is the result of the work of many people who made the code better
- Chris Tiearney
- Oliver Hoff
- Ole Rößner (basster)
- rtek
- Faton (notaf)
- Tom Houdmont
- Per Sandström (per)
@ -1133,6 +1144,7 @@ Symfony is the result of the work of many people who made the code better
- ilyes kooli
- gr1ev0us
- mlazovla
- Alejandro Diaz Torres
- Max Beutel
- Antanas Arvasevicius
- Pierre Dudoret
@ -1152,6 +1164,7 @@ Symfony is the result of the work of many people who made the code better
- Ken Marfilla (marfillaster)
- benatespina (benatespina)
- Denis Kop
- HypeMC
- Jean-Guilhem Rouel (jean-gui)
- jfcixmedia
- Dominic Tubach
@ -1167,6 +1180,7 @@ Symfony is the result of the work of many people who made the code better
- Soner Sayakci
- hugofonseca (fonsecas72)
- Marc Duboc (icemad)
- Matthias Krauser (mkrauser)
- Martynas Narbutas
- Toon Verwerft (veewee)
- Bailey Parker
@ -1216,6 +1230,7 @@ Symfony is the result of the work of many people who made the code better
- Mathias STRASSER (roukmoute)
- Thomason, James
- Gordienko Vladislav
- marie
- Viacheslav Sychov
- Alexandre Quercia (alquerci)
- Helmut Hummel (helhum)
@ -1307,8 +1322,8 @@ Symfony is the result of the work of many people who made the code better
- Ilia (aliance)
- Chris McCafferty (cilefen)
- Mo Di (modi)
- Tien Vo (tienvx)
- Pablo Schläpfer
- SuRiKmAn
- Gert de Pagter
- Jelte Steijaert (jelte)
- David Négrier (moufmouf)
@ -1319,11 +1334,13 @@ Symfony is the result of the work of many people who made the code better
- Alex Vasilchenko
- sez-open
- Xavier Coureau
- fruty
- ConneXNL
- Aharon Perkel
- matze
- Rubén Calvo (rubencm)
- Abdul.Mohsen B. A. A
- Swen van Zanten
- Benoît Burnichon
- pthompson
- Malaney J. Hill
@ -1360,6 +1377,7 @@ Symfony is the result of the work of many people who made the code better
- sl_toto (sl_toto)
- Walter Dal Mut (wdalmut)
- abluchet
- Ruud Arentsen
- Matthieu
- Albin Kerouaton
- Sébastien HOUZÉ
@ -1385,11 +1403,11 @@ Symfony is the result of the work of many people who made the code better
- Constantine Shtompel
- Jules Lamur
- Renato Mendes Figueiredo
- pdommelen
- Eric Stern
- ShiraNai7
- Cedrick Oka
- Antal Áron (antalaron)
- Markus Fasselt (digilist)
- Vašek Purchart (vasek-purchart)
- Janusz Jabłoński (yanoosh)
- Fleuv
@ -1408,6 +1426,7 @@ Symfony is the result of the work of many people who made the code better
- Andreas Frömer
- Philip Frank
- Lance McNearney
- Jeroen Spee (jeroens)
- Giorgio Premi
- ncou
- Ian Carroll
@ -1420,6 +1439,7 @@ Symfony is the result of the work of many people who made the code better
- Luis Galeas
- Martin Pärtel
- Bastien Jaillot (bastnic)
- Daniel Rotter (danrot)
- Frédéric Bouchery (fbouchery)
- Patrick Daley (padrig)
- Xavier Briand (xavierbriand)
@ -1440,7 +1460,6 @@ Symfony is the result of the work of many people who made the code better
- Daniel González Zaballos (dem3trio)
- Emmanuel Vella (emmanuel.vella)
- Guillaume BRETOU (guiguiboy)
- Dāvis Zālītis (k0d3r1s)
- Carsten Nielsen (phreaknerd)
- Roger Guasch (rogerguasch)
- Jay Severson
@ -1559,6 +1578,7 @@ Symfony is the result of the work of many people who made the code better
- povilas
- Gavin Staniforth
- Alessandro Tagliapietra (alex88)
- Andy Palmer (andyexeter)
- Biji (biji)
- Jérôme Tanghe (deuchnord)
- Alex Teterin (errogaht)
@ -1606,7 +1626,6 @@ Symfony is the result of the work of many people who made the code better
- Jibé Barth (jibbarth)
- Matthew Foster (mfoster)
- Reyo Stallenberg (reyostallenberg)
- Ruben Jacobs (rubenj)
- Paul Seiffert (seiffert)
- Vasily Khayrulin (sirian)
- Stefan Koopmanschap (skoop)
@ -1648,6 +1667,7 @@ Symfony is the result of the work of many people who made the code better
- Peter Bex
- Manatsawin Hanmongkolchai
- Gunther Konig
- Joe Springe
- Mickael GOETZ
- Maciej Schmidt
- Dennis Væversted
@ -1677,12 +1697,12 @@ Symfony is the result of the work of many people who made the code better
- me_shaon
- 蝦米
- Grayson Koonce (breerly)
- Mardari Dorel (dorumd)
- Andrey Helldar (helldar)
- Karim Cassam Chenaï (ka)
- Maksym Slesarenko (maksym_slesarenko)
- Michal Kurzeja (mkurzeja)
- Nicolas Bastien (nicolas_bastien)
- Peter Bowyer (pbowyer)
- Nikola Svitlica (thecelavi)
- Denis (yethee)
- Andrew Zhilin (zhil)
@ -1760,7 +1780,6 @@ Symfony is the result of the work of many people who made the code better
- Yannick Warnier (ywarnier)
- Kevin Decherf
- Jason Woods
- Oleg Andreyev
- klemens
- dened
- Dmitry Korotovsky
@ -1777,6 +1796,7 @@ Symfony is the result of the work of many people who made the code better
- taiiiraaa
- Trevor Suarez
- gedrox
- Bohan Yang
- Alan Bondarchuk
- Joe Bennett
- dropfen
@ -1810,6 +1830,7 @@ Symfony is the result of the work of many people who made the code better
- Jan Marek (janmarek)
- Mark de Haan (markdehaan)
- Dan Patrick (mdpatrick)
- Geoffrey Monte (numerogeek)
- Pedro Magalhães (pmmaga)
- Rares Vlaseanu (raresvla)
- tante kinast (tante)
@ -1907,6 +1928,7 @@ Symfony is the result of the work of many people who made the code better
- hainey
- Juan M Martínez
- Gilles Gauthier
- Pavinthan
- ddebree
- Kuba Werłos
- Gyula Szucs
@ -2032,11 +2054,13 @@ Symfony is the result of the work of many people who made the code better
- jc
- BenjaminBeck
- Aurelijus Rožėnas
- Beno!t POLASZEK
- Jordan Hoff
- znerol
- Christian Eikermann
- Kai Eichinger
- Antonio Angelino
- Jens Schulze
- Matt Fields
- Niklas Keller
- Andras Debreczeni
@ -2115,6 +2139,7 @@ Symfony is the result of the work of many people who made the code better
- Lebnik
- nsbx
- Shude
- Richard Hodgson
- Ondřej Führer
- Sema
- Elan Ruusamäe
@ -2134,6 +2159,7 @@ Symfony is the result of the work of many people who made the code better
- Benjamin Long
- Ben Miller
- Peter Gribanov
- Matteo Galli
- kwiateusz
- jspee
- Ilya Bulakh
@ -2172,6 +2198,7 @@ Symfony is the result of the work of many people who made the code better
- Lin Lu
- arduanov
- sualko
- Molkobain
- Bilge
- ADmad
- Nicolas Roudaire
@ -2282,6 +2309,7 @@ Symfony is the result of the work of many people who made the code better
- Wouter Sioen (wouter_sioen)
- Xavier Amado (xamado)
- Jesper Søndergaard Pedersen (zerrvox)
- Alexander Menshchikov (zmey_kk)
- Florent Cailhol
- szymek
- Kovacs Nicolas

View File

@ -495,7 +495,7 @@ class Configuration implements ConfigurationInterface
->scalarNode('cookie_domain')->end()
->enumNode('cookie_secure')->values([true, false, 'auto'])->end()
->booleanNode('cookie_httponly')->defaultTrue()->end()
->enumNode('cookie_samesite')->values([null, Cookie::SAMESITE_LAX, Cookie::SAMESITE_STRICT])->defaultNull()->end()
->enumNode('cookie_samesite')->values([null, Cookie::SAMESITE_LAX, Cookie::SAMESITE_STRICT, Cookie::SAMESITE_NONE])->defaultNull()->end()
->booleanNode('use_cookies')->end()
->scalarNode('gc_divisor')->end()
->scalarNode('gc_probability')->defaultValue(1)->end()

View File

@ -144,7 +144,7 @@ class RememberMeFactory implements SecurityFactoryInterface
if ('secure' === $name) {
$builder->enumNode($name)->values([true, false, 'auto'])->defaultValue('auto' === $value ? null : $value);
} elseif ('samesite' === $name) {
$builder->enumNode($name)->values([null, Cookie::SAMESITE_LAX, Cookie::SAMESITE_STRICT])->defaultValue($value);
$builder->enumNode($name)->values([null, Cookie::SAMESITE_LAX, Cookie::SAMESITE_STRICT, Cookie::SAMESITE_NONE])->defaultValue($value);
} elseif (\is_bool($value)) {
$builder->booleanNode($name)->defaultValue($value);
} else {

View File

@ -11,12 +11,10 @@
namespace Symfony\Component\Cache\Adapter;
use Symfony\Component\Cache\Exception\LogicException;
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\Traits\FilesystemTrait;
use Symfony\Component\Filesystem\Filesystem;
/**
* Stores tag id <> cache id relationship as a symlink, and lookup on invalidation calls.
@ -27,8 +25,9 @@ use Symfony\Component\Filesystem\Filesystem;
class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements PruneableInterface
{
use FilesystemTrait {
doSave as doSaveCache;
doDelete as doDeleteCache;
doClear as private doClearCache;
doSave as private doSaveCache;
doDelete as private doDeleteCache;
}
/**
@ -36,11 +35,6 @@ class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements Prune
*/
private const TAG_FOLDER = 'tags';
/**
* @var Filesystem|null
*/
private $fs;
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null)
{
$this->marshaller = $marshaller ?? new DefaultMarshaller();
@ -48,6 +42,55 @@ class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements Prune
$this->init($namespace, $directory);
}
/**
* {@inheritdoc}
*/
protected function doClear(string $namespace)
{
$ok = $this->doClearCache($namespace);
if ('' !== $namespace) {
return $ok;
}
set_error_handler(static function () {});
$chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
try {
foreach ($this->scanHashDir($this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR) as $dir) {
if (rename($dir, $renamed = substr_replace($dir, bin2hex(random_bytes(4)), -8))) {
$dir = $renamed.\DIRECTORY_SEPARATOR;
} else {
$dir .= \DIRECTORY_SEPARATOR;
$renamed = null;
}
for ($i = 0; $i < 38; ++$i) {
if (!file_exists($dir.$chars[$i])) {
continue;
}
for ($j = 0; $j < 38; ++$j) {
if (!file_exists($d = $dir.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j])) {
continue;
}
foreach (scandir($d, SCANDIR_SORT_NONE) ?: [] as $link) {
if ('.' !== $link && '..' !== $link && (null !== $renamed || !realpath($d.\DIRECTORY_SEPARATOR.$link))) {
unlink($d.\DIRECTORY_SEPARATOR.$link);
}
}
null === $renamed ?: rmdir($d);
}
null === $renamed ?: rmdir($dir.$chars[$i]);
}
null === $renamed ?: rmdir($renamed);
}
} finally {
restore_error_handler();
}
return $ok;
}
/**
* {@inheritdoc}
*/
@ -55,7 +98,6 @@ class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements Prune
{
$failed = $this->doSaveCache($values, $lifetime);
$fs = $this->getFilesystem();
// Add Tags as symlinks
foreach ($addTagData as $tagId => $ids) {
$tagFolder = $this->getTagFolder($tagId);
@ -65,12 +107,15 @@ class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements Prune
}
$file = $this->getFile($id);
$fs->symlink($file, $this->getFile($id, true, $tagFolder));
if (!@symlink($file, $this->getFile($id, true, $tagFolder))) {
@unlink($file);
$failed[] = $id;
}
}
}
// Unlink removed Tags
$files = [];
foreach ($removeTagData as $tagId => $ids) {
$tagFolder = $this->getTagFolder($tagId);
foreach ($ids as $id) {
@ -78,10 +123,9 @@ class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements Prune
continue;
}
$files[] = $this->getFile($id, false, $tagFolder);
@unlink($this->getFile($id, false, $tagFolder));
}
}
$fs->remove($files);
return $failed;
}
@ -94,15 +138,12 @@ class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements Prune
$ok = $this->doDeleteCache($ids);
// Remove tags
$files = [];
$fs = $this->getFilesystem();
foreach ($tagData as $tagId => $idMap) {
$tagFolder = $this->getTagFolder($tagId);
foreach ($idMap as $id) {
$files[] = $this->getFile($id, false, $tagFolder);
@unlink($this->getFile($id, false, $tagFolder));
}
}
$fs->remove($files);
return $ok;
}
@ -113,33 +154,45 @@ class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements Prune
protected function doInvalidate(array $tagIds): bool
{
foreach ($tagIds as $tagId) {
$tagsFolder = $this->getTagFolder($tagId);
if (!file_exists($tagsFolder)) {
if (!file_exists($tagFolder = $this->getTagFolder($tagId))) {
continue;
}
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($tagsFolder, \FilesystemIterator::SKIP_DOTS)) as $itemLink) {
if (!$itemLink->isLink()) {
throw new LogicException('Expected a (sym)link when iterating over tag folder, non link found: '.$itemLink);
set_error_handler(static function () {});
try {
if (rename($tagFolder, $renamed = substr_replace($tagFolder, bin2hex(random_bytes(4)), -9))) {
$tagFolder = $renamed.\DIRECTORY_SEPARATOR;
} else {
$renamed = null;
}
$valueFile = $itemLink->getRealPath();
if ($valueFile && file_exists($valueFile)) {
@unlink($valueFile);
foreach ($this->scanHashDir($tagFolder) as $itemLink) {
unlink(realpath($itemLink) ?: $itemLink);
unlink($itemLink);
}
@unlink((string) $itemLink);
if (null === $renamed) {
continue;
}
$chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
for ($i = 0; $i < 38; ++$i) {
for ($j = 0; $j < 38; ++$j) {
rmdir($tagFolder.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j]);
}
rmdir($tagFolder.$chars[$i]);
}
rmdir($renamed);
} finally {
restore_error_handler();
}
}
return true;
}
private function getFilesystem(): Filesystem
{
return $this->fs ?? $this->fs = new Filesystem();
}
private function getTagFolder(string $tagId): string
{
return $this->getFile($tagId, false, $this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR).\DIRECTORY_SEPARATOR;

View File

@ -71,7 +71,7 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface
set_error_handler($this->includeHandler);
try {
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
foreach ($this->scanHashDir($this->directory) as $file) {
try {
if (\is_array($expiresAt = include $file)) {
$expiresAt = $expiresAt[0];

View File

@ -11,33 +11,32 @@
namespace Symfony\Component\Cache\Adapter;
use Predis;
use Predis\Connection\Aggregate\ClusterInterface;
use Predis\Connection\Aggregate\PredisCluster;
use Predis\Response\Status;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\LogicException;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\Traits\RedisTrait;
/**
* Stores tag id <> cache id relationship as a Redis Set, lookup on invalidation using sPOP.
* Stores tag id <> cache id relationship as a Redis Set, lookup on invalidation using RENAME+SMEMBERS.
*
* Set (tag relation info) is stored without expiry (non-volatile), while cache always gets an expiry (volatile) even
* if not set by caller. Thus if you configure redis with the right eviction policy you can be safe this tag <> cache
* relationship survives eviction (cache cleanup when Redis runs out of memory).
*
* Requirements:
* - Server: Redis 3.2+
* - Client: PHP Redis 3.1.3+ OR Predis
* - Redis Server(s) configured with any `volatile-*` eviction policy, OR `noeviction` if it will NEVER fill up memory
* - Client: PHP Redis or Predis
* Note: Due to lack of RENAME support it is NOT recommended to use Cluster on Predis, instead use phpredis.
* - Server: Redis 2.8+
* Configured with any `volatile-*` eviction policy, OR `noeviction` if it will NEVER fill up memory
*
* Design limitations:
* - Max 2 billion cache keys per cache tag
* E.g. If you use a "all" items tag for expiry instead of clear(), that limits you to 2 billion cache items as well
* - Max 4 billion cache keys per cache tag as limited by Redis Set datatype.
* E.g. If you use a "all" items tag for expiry instead of clear(), that limits you to 4 billion cache items also.
*
* @see https://redis.io/topics/lru-cache#eviction-policies Documentation for Redis eviction policies.
* @see https://redis.io/topics/data-types#sets Documentation for Redis Set datatype.
* @see https://redis.io/commands/spop Documentation for sPOP operation, capable of retriving AND emptying a Set at once.
*
* @author Nicolas Grekas <p@tchwork.com>
* @author André Rømcke <andre.romcke+symfony@gmail.com>
@ -46,11 +45,6 @@ class RedisTagAwareAdapter extends AbstractTagAwareAdapter
{
use RedisTrait;
/**
* Redis "Set" can hold more than 4 billion members, here we limit ourselves to PHP's > 2 billion max int (32Bit).
*/
private const POP_MAX_LIMIT = 2147483647 - 1;
/**
* Limits for how many keys are deleted in batch.
*/
@ -62,26 +56,18 @@ class RedisTagAwareAdapter extends AbstractTagAwareAdapter
*/
private const DEFAULT_CACHE_TTL = 8640000;
/**
* @var bool|null
*/
private $redisServerSupportSPOP = null;
/**
* @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redisClient The redis client
* @param string $namespace The default namespace
* @param int $defaultLifetime The default lifetime
*
* @throws \Symfony\Component\Cache\Exception\LogicException If phpredis with version lower than 3.1.3.
*/
public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
{
$this->init($redisClient, $namespace, $defaultLifetime, $marshaller);
// Make sure php-redis is 3.1.3 or higher configured for Redis classes
if (!$this->redis instanceof \Predis\ClientInterface && version_compare(phpversion('redis'), '3.1.3', '<')) {
throw new LogicException('RedisTagAwareAdapter requires php-redis 3.1.3 or higher, alternatively use predis/predis');
if ($redisClient instanceof \Predis\ClientInterface && $redisClient->getConnection() instanceof ClusterInterface && !$redisClient->getConnection() instanceof PredisCluster) {
throw new InvalidArgumentException(sprintf('Unsupported Predis cluster connection: only "%s" is, "%s" given.', PredisCluster::class, \get_class($redisClient->getConnection())));
}
$this->init($redisClient, $namespace, $defaultLifetime, $marshaller);
}
/**
@ -121,7 +107,7 @@ class RedisTagAwareAdapter extends AbstractTagAwareAdapter
continue;
}
// setEx results
if (true !== $result && (!$result instanceof Status || $result !== Status::get('OK'))) {
if (true !== $result && (!$result instanceof Status || Status::get('OK') !== $result)) {
$failed[] = $id;
}
}
@ -138,9 +124,10 @@ class RedisTagAwareAdapter extends AbstractTagAwareAdapter
return true;
}
$predisCluster = $this->redis instanceof \Predis\ClientInterface && $this->redis->getConnection() instanceof ClusterInterface;
$predisCluster = $this->redis instanceof \Predis\ClientInterface && $this->redis->getConnection() instanceof PredisCluster;
$this->pipeline(static function () use ($ids, $tagData, $predisCluster) {
if ($predisCluster) {
// Unlike phpredis, Predis does not handle bulk calls for us against cluster
foreach ($ids as $id) {
yield 'del' => [$id];
}
@ -161,46 +148,76 @@ class RedisTagAwareAdapter extends AbstractTagAwareAdapter
*/
protected function doInvalidate(array $tagIds): bool
{
if (!$this->redisServerSupportSPOP()) {
if (!$this->redis instanceof \Predis\ClientInterface || !$this->redis->getConnection() instanceof PredisCluster) {
$movedTagSetIds = $this->renameKeys($this->redis, $tagIds);
} else {
$clusterConnection = $this->redis->getConnection();
$tagIdsByConnection = new \SplObjectStorage();
$movedTagSetIds = [];
foreach ($tagIds as $id) {
$connection = $clusterConnection->getConnectionByKey($id);
$slot = $tagIdsByConnection[$connection] ?? $tagIdsByConnection[$connection] = new \ArrayObject();
$slot[] = $id;
}
foreach ($tagIdsByConnection as $connection) {
$slot = $tagIdsByConnection[$connection];
$movedTagSetIds = array_merge($movedTagSetIds, $this->renameKeys(new $this->redis($connection, $this->redis->getOptions()), $slot->getArrayCopy()));
}
}
// No Sets found
if (!$movedTagSetIds) {
return false;
}
// Pop all tag info at once to avoid race conditions
$tagIdSets = $this->pipeline(static function () use ($tagIds) {
foreach ($tagIds as $tagId) {
// Client: Predis or PHP Redis 3.1.3+ (https://github.com/phpredis/phpredis/commit/d2e203a6)
// Server: Redis 3.2 or higher (https://redis.io/commands/spop)
yield 'sPop' => [$tagId, self::POP_MAX_LIMIT];
// Now safely take the time to read the keys in each set and collect ids we need to delete
$tagIdSets = $this->pipeline(static function () use ($movedTagSetIds) {
foreach ($movedTagSetIds as $movedTagId) {
yield 'sMembers' => [$movedTagId];
}
});
// Flatten generator result from pipeline, ignore keys (tag ids)
$ids = array_unique(array_merge(...iterator_to_array($tagIdSets, false)));
// Return combination of the temporary Tag Set ids and their values (cache ids)
$ids = array_merge($movedTagSetIds, ...iterator_to_array($tagIdSets, false));
// Delete cache in chunks to avoid overloading the connection
foreach (array_chunk($ids, self::BULK_DELETE_LIMIT) as $chunkIds) {
foreach (array_chunk(array_unique($ids), self::BULK_DELETE_LIMIT) as $chunkIds) {
$this->doDelete($chunkIds);
}
return true;
}
private function redisServerSupportSPOP(): bool
/**
* Renames several keys in order to be able to operate on them without risk of race conditions.
*
* Filters out keys that do not exist before returning new keys.
*
* @see https://redis.io/commands/rename
* @see https://redis.io/topics/cluster-spec#keys-hash-tags
*
* @return array Filtered list of the valid moved keys (only those that existed)
*/
private function renameKeys($redis, array $ids): array
{
if (null !== $this->redisServerSupportSPOP) {
return $this->redisServerSupportSPOP;
$newIds = [];
$uniqueToken = bin2hex(random_bytes(10));
$results = $this->pipeline(static function () use ($ids, $uniqueToken) {
foreach ($ids as $id) {
yield 'rename' => [$id, '{'.$id.'}'.$uniqueToken];
}
}, $redis);
foreach ($this->getHosts() as $host) {
$info = $host->info('Server');
$info = isset($info['Server']) ? $info['Server'] : $info;
if (version_compare($info['redis_version'], '3.2', '<')) {
CacheItem::log($this->logger, 'Redis server needs to be version 3.2 or higher, your Redis server was detected as '.$info['redis_version']);
return $this->redisServerSupportSPOP = false;
foreach ($results as $id => $result) {
if (true === $result || ($result instanceof Status && Status::get('OK') === $result)) {
// Only take into account if ok (key existed), will be false on phpredis if it did not exist
$newIds[] = '{'.$id.'}'.$uniqueToken;
}
}
return $this->redisServerSupportSPOP = true;
return $newIds;
}
}

View File

@ -14,6 +14,8 @@ CHANGELOG
* added support for connecting to Redis Sentinel clusters
* added argument `$prefix` to `AdapterInterface::clear()`
* improved `RedisTagAwareAdapter` to support Redis server >= 2.8 and up to 4B items per tag
* [BC BREAK] `RedisTagAwareAdapter` is not compatible with `RedisCluster` from `Predis` anymore, use `phpredis` instead
4.3.0
-----

View File

@ -1,35 +0,0 @@
<?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\Component\Cache\Tests\Adapter;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\Adapter\RedisTagAwareAdapter;
use Symfony\Component\Cache\Tests\Traits\TagAwareTestTrait;
class PredisTagAwareRedisClusterAdapterTest extends PredisRedisClusterAdapterTest
{
use TagAwareTestTrait;
protected function setUp(): void
{
parent::setUp();
$this->skippedTests['testTagItemExpiry'] = 'Testing expiration slows down the test suite';
}
public function createCachePool(int $defaultLifetime = 0): CacheItemPoolInterface
{
$this->assertInstanceOf(\Predis\Client::class, self::$redis);
$adapter = new RedisTagAwareAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime);
return $adapter;
}
}

View File

@ -26,7 +26,7 @@ trait FilesystemCommonTrait
private function init(string $namespace, ?string $directory)
{
if (!isset($directory[0])) {
$directory = sys_get_temp_dir().'/symfony-cache';
$directory = sys_get_temp_dir().\DIRECTORY_SEPARATOR.'symfony-cache';
} else {
$directory = realpath($directory) ?: $directory;
}
@ -55,12 +55,12 @@ trait FilesystemCommonTrait
{
$ok = true;
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS)) as $file) {
foreach ($this->scanHashDir($this->directory) as $file) {
if ('' !== $namespace && 0 !== strpos($this->getFileKey($file), $namespace)) {
continue;
}
$ok = ($file->isDir() || $this->doUnlink($file) || !file_exists($file)) && $ok;
$ok = ($this->doUnlink($file) || !file_exists($file)) && $ok;
}
return $ok;
@ -123,6 +123,33 @@ trait FilesystemCommonTrait
return '';
}
private function scanHashDir(string $directory): \Generator
{
if (!file_exists($directory)) {
return;
}
$chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
for ($i = 0; $i < 38; ++$i) {
if (!file_exists($directory.$chars[$i])) {
continue;
}
for ($j = 0; $j < 38; ++$j) {
if (!file_exists($dir = $directory.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j])) {
continue;
}
foreach (@scandir($dir, SCANDIR_SORT_NONE) ?: [] as $file) {
if ('.' !== $file && '..' !== $file) {
yield $dir.\DIRECTORY_SEPARATOR.$file;
}
}
}
}
}
/**
* @internal
*/

View File

@ -33,7 +33,7 @@ trait FilesystemTrait
$time = time();
$pruned = true;
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
foreach ($this->scanHashDir($this->directory) as $file) {
if (!$h = @fopen($file, 'rb')) {
continue;
}

View File

@ -55,9 +55,17 @@ trait RedisTrait
if (preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) {
throw new InvalidArgumentException(sprintf('RedisAdapter namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0]));
}
if (!$redisClient instanceof \Redis && !$redisClient instanceof \RedisArray && !$redisClient instanceof \RedisCluster && !$redisClient instanceof \Predis\ClientInterface && !$redisClient instanceof RedisProxy && !$redisClient instanceof RedisClusterProxy) {
throw new InvalidArgumentException(sprintf('%s() expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\ClientInterface, %s given.', __METHOD__, \is_object($redisClient) ? \get_class($redisClient) : \gettype($redisClient)));
}
if ($redisClient instanceof \Predis\ClientInterface && $redisClient->getOptions()->exceptions) {
$options = clone $redisClient->getOptions();
\Closure::bind(function () { $this->options['exceptions'] = false; }, $options, $options)();
$redisClient = new $redisClient($redisClient->getConnection(), $options);
}
$this->redis = $redisClient;
$this->marshaller = $marshaller ?? new DefaultMarshaller();
}
@ -277,6 +285,7 @@ trait RedisTrait
$params['replication'] = true;
$hosts[0] += ['alias' => 'master'];
}
$params['exceptions'] = false;
$redis = new $class($hosts, array_diff_key($params, self::$defaultConnectionOptions));
if (isset($params['redis_sentinel'])) {
@ -414,8 +423,9 @@ trait RedisTrait
}
}
});
foreach ($results as $id => $result) {
if (true !== $result && (!$result instanceof Status || $result !== Status::get('OK'))) {
if (true !== $result && (!$result instanceof Status || Status::get('OK') !== $result)) {
$failed[] = $id;
}
}
@ -423,31 +433,32 @@ trait RedisTrait
return $failed;
}
private function pipeline(\Closure $generator): \Generator
private function pipeline(\Closure $generator, $redis = null): \Generator
{
$ids = [];
$redis = $redis ?? $this->redis;
if ($this->redis instanceof RedisClusterProxy || $this->redis instanceof \RedisCluster || ($this->redis instanceof \Predis\ClientInterface && $this->redis->getConnection() instanceof RedisCluster)) {
if ($redis instanceof RedisClusterProxy || $redis instanceof \RedisCluster || ($redis instanceof \Predis\ClientInterface && $redis->getConnection() instanceof RedisCluster)) {
// phpredis & predis don't support pipelining with RedisCluster
// see https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#pipelining
// see https://github.com/nrk/predis/issues/267#issuecomment-123781423
$results = [];
foreach ($generator() as $command => $args) {
$results[] = $this->redis->{$command}(...$args);
$results[] = $redis->{$command}(...$args);
$ids[] = $args[0];
}
} elseif ($this->redis instanceof \Predis\ClientInterface) {
$results = $this->redis->pipeline(function ($redis) use ($generator, &$ids) {
} elseif ($redis instanceof \Predis\ClientInterface) {
$results = $redis->pipeline(static function ($redis) use ($generator, &$ids) {
foreach ($generator() as $command => $args) {
$redis->{$command}(...$args);
$ids[] = $args[0];
}
});
} elseif ($this->redis instanceof \RedisArray) {
} elseif ($redis instanceof \RedisArray) {
$connections = $results = $ids = [];
foreach ($generator() as $command => $args) {
if (!isset($connections[$h = $this->redis->_target($args[0])])) {
$connections[$h] = [$this->redis->_instance($h), -1];
if (!isset($connections[$h = $redis->_target($args[0])])) {
$connections[$h] = [$redis->_instance($h), -1];
$connections[$h][0]->multi(\Redis::PIPELINE);
}
$connections[$h][0]->{$command}(...$args);
@ -461,12 +472,12 @@ trait RedisTrait
$results[$k] = $connections[$h][$c];
}
} else {
$this->redis->multi(\Redis::PIPELINE);
$redis->multi(\Redis::PIPELINE);
foreach ($generator() as $command => $args) {
$this->redis->{$command}(...$args);
$redis->{$command}(...$args);
$ids[] = $args[0];
}
$results = $this->redis->exec();
$results = $redis->exec();
}
foreach ($ids as $k => $id) {

View File

@ -46,7 +46,7 @@ final class ProgressBar
private $messages = [];
private $overwrite = true;
private $terminal;
private $firstRun = true;
private $previousMessage;
private static $formatters;
private static $formats;
@ -432,8 +432,14 @@ final class ProgressBar
*/
private function overwrite(string $message): void
{
if ($this->previousMessage === $message) {
return;
}
$originalMessage = $message;
if ($this->overwrite) {
if (!$this->firstRun) {
if (null !== $this->previousMessage) {
if ($this->output instanceof ConsoleSectionOutput) {
$lines = floor(Helper::strlen($message) / $this->terminal->getWidth()) + $this->formatLineCount + 1;
$this->output->clear($lines);
@ -451,7 +457,7 @@ final class ProgressBar
$message = PHP_EOL.$message;
}
$this->firstRun = false;
$this->previousMessage = $originalMessage;
$this->lastWriteTime = microtime(true);
$this->output->write($message);

View File

@ -187,7 +187,6 @@ class ProgressBarTest extends TestCase
{
$expected =
' 0/10 [>---------------------------] 0%'.
$this->generateOutput(' 10/10 [============================] 100%').
$this->generateOutput(' 10/10 [============================] 100%')
;
@ -296,7 +295,6 @@ class ProgressBarTest extends TestCase
rewind($output->getStream());
$this->assertEquals(
' 0/50 [>---------------------------] 0%'.
$this->generateOutput(' 0/50 [>---------------------------] 0%').
$this->generateOutput(' 1/50 [>---------------------------] 2%').
$this->generateOutput(' 2/50 [=>--------------------------] 4%'),
stream_get_contents($output->getStream())
@ -318,7 +316,6 @@ class ProgressBarTest extends TestCase
rewind($output->getStream());
$this->assertEquals(
' 0/50 [>---------------------------] 0%'.
$this->generateOutput(' 0/50 [>---------------------------] 0%').
$this->generateOutput(' 1/50 [>---------------------------] 2%').
$this->generateOutput(' 2/50 [=>--------------------------]'),
stream_get_contents($output->getStream())
@ -340,7 +337,6 @@ class ProgressBarTest extends TestCase
rewind($output->getStream());
$this->assertEquals(
' 0/50 [>---------------------------] 0%'.PHP_EOL.
"\x1b[1A\x1b[0J".' 0/50 [>---------------------------] 0%'.PHP_EOL.
"\x1b[1A\x1b[0J".' 1/50 [>---------------------------] 2%'.PHP_EOL.
"\x1b[1A\x1b[0J".' 2/50 [=>--------------------------] 4%'.PHP_EOL,
stream_get_contents($output->getStream())
@ -434,7 +430,6 @@ class ProgressBarTest extends TestCase
rewind($output->getStream());
$this->assertEquals(
' 0/50 [>---------------------------] 0%'.
$this->generateOutput(' 0/50 [>---------------------------] 0%').
$this->generateOutput(' 1/50 [>---------------------------] 2%').
$this->generateOutput(' 15/50 [========>-------------------] 30%').
$this->generateOutput(' 25/50 [==============>-------------] 50%'),
@ -541,7 +536,6 @@ class ProgressBarTest extends TestCase
rewind($output->getStream());
$this->assertEquals(
' 0/200 [>---------------------------] 0%'.
$this->generateOutput(' 0/200 [>---------------------------] 0%').
$this->generateOutput(' 199/200 [===========================>] 99%').
$this->generateOutput(' 200/200 [============================] 100%'),
stream_get_contents($output->getStream())
@ -888,7 +882,6 @@ class ProgressBarTest extends TestCase
$this->assertEquals(
' 0/2 [>---------------------------] 0%'.
$this->generateOutput(' 1/2 [==============>-------------] 50%').
$this->generateOutput(' 2/2 [============================] 100%').
$this->generateOutput(' 2/2 [============================] 100%'),
stream_get_contents($output->getStream())
);
@ -996,4 +989,18 @@ class ProgressBarTest extends TestCase
stream_get_contents($output->getStream())
);
}
public function testNoWriteWhenMessageIsSame(): void
{
$bar = new ProgressBar($output = $this->getOutputStream(), 2);
$bar->start();
$bar->advance();
$bar->display();
rewind($output->getStream());
$this->assertEquals(
' 0/2 [>---------------------------] 0%'.
$this->generateOutput(' 1/2 [==============>-------------] 50%'),
stream_get_contents($output->getStream())
);
}
}

View File

@ -24,6 +24,7 @@ CHANGELOG
* added support for binding iterable and tagged services
* made singly-implemented interfaces detection be scoped by file
* added ability to define a static priority method for tagged service
* added support for improved syntax to define method calls in Yaml
4.3.0
-----

View File

@ -459,20 +459,48 @@ class YamlFileLoader extends FileLoader
throw new InvalidArgumentException(sprintf('Parameter "calls" must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file));
}
foreach ($service['calls'] as $call) {
foreach ($service['calls'] as $k => $call) {
if (!\is_array($call) && (!\is_string($k) || !$call instanceof TaggedValue)) {
throw new InvalidArgumentException(sprintf('Invalid method call for service "%s": expected map or array, %s given in %s.', $id, $call instanceof TaggedValue ? '!'.$call->getTag() : \gettype($call), $file));
}
if (\is_string($k)) {
throw new InvalidArgumentException(sprintf('Invalid method call for service "%s", did you forgot a leading dash before "%s: ..." in %s?', $id, $k, $file));
}
if (isset($call['method'])) {
$method = $call['method'];
$args = isset($call['arguments']) ? $this->resolveServices($call['arguments'], $file) : [];
$args = $call['arguments'] ?? [];
$returnsClone = $call['returns_clone'] ?? false;
} else {
if (1 === \count($call) && \is_string(key($call))) {
$method = key($call);
$args = $call[$method];
if ($args instanceof TaggedValue) {
if ('returns_clone' !== $args->getTag()) {
throw new InvalidArgumentException(sprintf('Unsupported tag "!%s", did you mean "!returns_clone" for service "%s" in %s?', $args->getTag(), $id, $file));
}
$returnsClone = true;
$args = $args->getValue();
} else {
$returnsClone = false;
}
} elseif (empty($call[0])) {
throw new InvalidArgumentException(sprintf('Invalid call for service "%s": the method must be defined as the first index of an array or as the only key of a map in %s.', $id, $file));
} else {
$method = $call[0];
$args = isset($call[1]) ? $this->resolveServices($call[1], $file) : [];
$args = $call[1] ?? [];
$returnsClone = $call[2] ?? false;
}
}
if (!\is_array($args)) {
throw new InvalidArgumentException(sprintf('The second parameter for function call "%s" must be an array of its arguments for service "%s" in %s. Check your YAML syntax.', $method, $id, $file));
}
$args = $this->resolveServices($args, $file);
$definition->addMethodCall($method, $args, $returnsClone);
}
}

View File

@ -0,0 +1,5 @@
services:
foo:
calls:
- foo: [1, 2, 3]
- bar: !returns_clone [1, 2, 3]

View File

@ -868,4 +868,19 @@ class YamlFileLoaderTest extends TestCase
$this->assertSame(Prototype\SinglyImplementedInterface\Adapter\Adapter::class, (string) $alias);
}
public function testAlternativeMethodCalls()
{
$container = new ContainerBuilder();
$loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'));
$loader->load('alt_call.yaml');
$expected = [
['foo', [1, 2, 3]],
['bar', [1, 2, 3], true],
];
$this->assertSame($expected, $container->getDefinition('foo')->getMethodCalls());
}
}

View File

@ -571,17 +571,19 @@ class Crawler implements \Countable, \IteratorAggregate
/**
* Returns the text of the first node of the list.
*
* @param mixed $default When provided and the current node is empty, this value is returned and no exception is thrown
* Pass true as the 2nd argument to normalize whitespaces.
*
* @param string|null $default When not null: the value to return when the current node is empty
* @param bool $normalizeWhitespace Whether whitespaces should be trimmed and normalized to single spaces
*
* @return string The node value
*
* @throws \InvalidArgumentException When current node is empty
*/
public function text($default = null, bool $normalizeWhitespace = false)
public function text(string $default = null, bool $normalizeWhitespace = false)
{
if (!$this->nodes) {
if (0 < \func_num_args()) {
if (null !== $default) {
return $default;
}
@ -600,16 +602,16 @@ class Crawler implements \Countable, \IteratorAggregate
/**
* Returns the first node of the list as HTML.
*
* @param mixed $default When provided and the current node is empty, this value is returned and no exception is thrown
* @param string|null $default When not null: the value to return when the current node is empty
*
* @return string The node html
*
* @throws \InvalidArgumentException When current node is empty
*/
public function html($default = null)
public function html(string $default = null)
{
if (!$this->nodes) {
if (0 < \func_num_args()) {
if (null !== $default) {
return $default;
}

View File

@ -427,7 +427,7 @@ final class Dotenv
(?!\() # no opening parenthesis
(?P<opening_brace>\{)? # optional brace
(?P<name>'.self::VARNAME_REGEX.')? # var name
(?P<default_value>:-[^\}]++)? # optional default value
(?P<default_value>:[-=][^\}]++)? # optional default value
(?P<closing_brace>\})? # optional closing brace
/x';
@ -464,6 +464,10 @@ final class Dotenv
}
$value = substr($matches['default_value'], 2);
if ('=' === $matches['default_value'][1]) {
$this->values[$name] = $value;
}
}
if (!$matches['opening_brace'] && isset($matches['closing_brace'])) {

View File

@ -166,6 +166,10 @@ class DotenvTest extends TestCase
["FOO=BAR\nBAR=\${NOTDEFINED:-TEST}", ['FOO' => 'BAR', 'BAR' => 'TEST']],
["FOO=\nBAR=\${FOO:-TEST}", ['FOO' => '', 'BAR' => 'TEST']],
["FOO=\nBAR=\$FOO:-TEST}", ['FOO' => '', 'BAR' => 'TEST}']],
["FOO=BAR\nBAR=\${FOO:=TEST}", ['FOO' => 'BAR', 'BAR' => 'BAR']],
["FOO=BAR\nBAR=\${NOTDEFINED:=TEST}", ['FOO' => 'BAR', 'NOTDEFINED' => 'TEST', 'BAR' => 'TEST']],
["FOO=\nBAR=\${FOO:=TEST}", ['FOO' => 'TEST', 'BAR' => 'TEST']],
["FOO=\nBAR=\$FOO:=TEST}", ['FOO' => 'TEST', 'BAR' => 'TEST}']],
];
if ('\\' !== \DIRECTORY_SEPARATOR) {

View File

@ -13,6 +13,7 @@ CHANGELOG
-----
* `AddEventAliasesPass` has been added, allowing applications and bundles to extend the event alias mapping used by `RegisterListenersPass`.
* Made the `event` attribute of the `kernel.event_listener` tag optional for FQCN events.
4.3.0
-----

View File

@ -16,8 +16,10 @@ use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher\Event as LegacyEvent;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Contracts\EventDispatcher\Event;
/**
* Compiler pass to register tagged services for an event dispatcher.
@ -67,8 +69,14 @@ class RegisterListenersPass implements CompilerPassInterface
$priority = isset($event['priority']) ? $event['priority'] : 0;
if (!isset($event['event'])) {
throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag));
if ($container->getDefinition($id)->hasTag($this->subscriberTag)) {
continue;
}
$event['method'] = $event['method'] ?? '__invoke';
$event['event'] = $this->getEventFromTypeDeclaration($container, $id, $event['method']);
}
$event['event'] = $aliases[$event['event']] ?? $event['event'];
if (!isset($event['method'])) {
@ -122,6 +130,24 @@ class RegisterListenersPass implements CompilerPassInterface
ExtractingEventDispatcher::$aliases = [];
}
}
private function getEventFromTypeDeclaration(ContainerBuilder $container, string $id, string $method): string
{
if (
null === ($class = $container->getDefinition($id)->getClass())
|| !($r = $container->getReflectionClass($class, false))
|| !$r->hasMethod($method)
|| 1 > ($m = $r->getMethod($method))->getNumberOfParameters()
|| !($type = $m->getParameters()[0]->getType())
|| $type->isBuiltin()
|| Event::class === ($name = $type->getName())
|| LegacyEvent::class === $name
) {
throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag));
}
return $name;
}
}
/**

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\EventDispatcher\Tests\DependencyInjection;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher\DependencyInjection\AddEventAliasesPass;
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
@ -244,6 +245,116 @@ class RegisterListenersPassTest extends TestCase
];
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
}
public function testOmitEventNameOnTypedListener(): void
{
$container = new ContainerBuilder();
$container->setParameter('event_dispatcher.event_aliases', [AliasedEvent::class => 'aliased_event']);
$container->register('foo', TypedListener::class)->addTag('kernel.event_listener', ['method' => 'onEvent']);
$container->register('bar', TypedListener::class)->addTag('kernel.event_listener');
$container->register('event_dispatcher');
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($container);
$definition = $container->getDefinition('event_dispatcher');
$expectedCalls = [
[
'addListener',
[
CustomEvent::class,
[new ServiceClosureArgument(new Reference('foo')), 'onEvent'],
0,
],
],
[
'addListener',
[
'aliased_event',
[new ServiceClosureArgument(new Reference('bar')), '__invoke'],
0,
],
],
];
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
}
public function testOmitEventNameOnUntypedListener(): void
{
$container = new ContainerBuilder();
$container->register('foo', InvokableListenerService::class)->addTag('kernel.event_listener', ['method' => 'onEvent']);
$container->register('event_dispatcher');
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Service "foo" must define the "event" attribute on "kernel.event_listener" tags.');
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($container);
}
public function testOmitEventNameAndMethodOnUntypedListener(): void
{
$container = new ContainerBuilder();
$container->register('foo', InvokableListenerService::class)->addTag('kernel.event_listener');
$container->register('event_dispatcher');
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Service "foo" must define the "event" attribute on "kernel.event_listener" tags.');
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($container);
}
/**
* @requires PHP 7.2
*/
public function testOmitEventNameAndMethodOnGenericListener(): void
{
$container = new ContainerBuilder();
$container->register('foo', GenericListener::class)->addTag('kernel.event_listener');
$container->register('event_dispatcher');
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Service "foo" must define the "event" attribute on "kernel.event_listener" tags.');
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($container);
}
public function testOmitEventNameOnSubscriber(): void
{
$container = new ContainerBuilder();
$container->register('subscriber', IncompleteSubscriber::class)
->addTag('kernel.event_subscriber')
->addTag('kernel.event_listener')
->addTag('kernel.event_listener', ['event' => 'bar', 'method' => 'onBar'])
;
$container->register('event_dispatcher');
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($container);
$definition = $container->getDefinition('event_dispatcher');
$expectedCalls = [
[
'addListener',
[
'bar',
[new ServiceClosureArgument(new Reference('subscriber')), 'onBar'],
0,
],
],
[
'addListener',
[
'foo',
[new ServiceClosureArgument(new Reference('subscriber')), 'onFoo'],
0,
],
],
];
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
}
}
class SubscriberService implements EventSubscriberInterface
@ -285,3 +396,43 @@ final class AliasedEvent
final class CustomEvent
{
}
final class TypedListener
{
public function __invoke(AliasedEvent $event): void
{
}
public function onEvent(CustomEvent $event): void
{
}
}
final class GenericListener
{
public function __invoke(object $event): void
{
}
}
final class IncompleteSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
'foo' => 'onFoo',
];
}
public function onFoo(): void
{
}
public function onBar(): void
{
}
public function __invoke(CustomEvent $event): void
{
}
}

View File

@ -38,7 +38,11 @@ final class DateTimeImmutableToDateTimeTransformer implements DataTransformerInt
throw new TransformationFailedException('Expected a \DateTimeImmutable.');
}
return \DateTime::createFromFormat(\DateTime::RFC3339, $value->format(\DateTime::RFC3339));
if (\PHP_VERSION_ID >= 70300) {
return \DateTime::createFromImmutable($value);
}
return \DateTime::createFromFormat('U.u', $value->format('U.u'))->setTimezone($value->getTimezone());
}
/**

View File

@ -16,16 +16,33 @@ use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeImmutableToDat
class DateTimeImmutableToDateTimeTransformerTest extends TestCase
{
public function testTransform()
/**
* @dataProvider provider
*/
public function testTransform(\DateTime $expectedOutput, \DateTimeImmutable $input)
{
$transformer = new DateTimeImmutableToDateTimeTransformer();
$input = new \DateTimeImmutable('2010-02-03 04:05:06 UTC');
$expectedOutput = new \DateTime('2010-02-03 04:05:06 UTC');
$actualOutput = $transformer->transform($input);
$this->assertInstanceOf(\DateTime::class, $actualOutput);
$this->assertEquals($expectedOutput, $actualOutput);
$this->assertEquals($expectedOutput->getTimezone(), $actualOutput->getTimezone());
}
public function provider()
{
return [
[
new \DateTime('2010-02-03 04:05:06 UTC'),
new \DateTimeImmutable('2010-02-03 04:05:06 UTC'),
],
[
(new \DateTime('2019-10-07 +11:00'))
->setTime(14, 27, 11, 10042),
(new \DateTimeImmutable('2019-10-07 +11:00'))
->setTime(14, 27, 11, 10042),
],
];
}
public function testTransformEmpty()
@ -43,16 +60,17 @@ class DateTimeImmutableToDateTimeTransformerTest extends TestCase
$transformer->transform(new \DateTime());
}
public function testReverseTransform()
/**
* @dataProvider provider
*/
public function testReverseTransform(\DateTime $input, \DateTimeImmutable $expectedOutput)
{
$transformer = new DateTimeImmutableToDateTimeTransformer();
$input = new \DateTime('2010-02-03 04:05:06 UTC');
$expectedOutput = new \DateTimeImmutable('2010-02-03 04:05:06 UTC');
$actualOutput = $transformer->reverseTransform($input);
$this->assertInstanceOf(\DateTimeImmutable::class, $actualOutput);
$this->assertEquals($expectedOutput, $actualOutput);
$this->assertEquals($expectedOutput->getTimezone(), $actualOutput->getTimezone());
}
public function testReverseTransformEmpty()

View File

@ -80,9 +80,9 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
}
$dotenvVars = [];
foreach (explode(',', getenv('SYMFONY_DOTENV_VARS')) as $name) {
if ('' !== $name && false !== $value = getenv($name)) {
$dotenvVars[$name] = $value;
foreach (explode(',', $_SERVER['SYMFONY_DOTENV_VARS'] ?? $_ENV['SYMFONY_DOTENV_VARS'] ?? '') as $name) {
if ('' !== $name && isset($_ENV[$name])) {
$dotenvVars[$name] = $_ENV[$name];
}
}

View File

@ -21,29 +21,27 @@ use Symfony\Component\Messenger\Transport\Doctrine\Connection;
*/
class DoctrineIntegrationTest extends TestCase
{
/** @var \Doctrine\DBAL\Connection */
private $driverConnection;
/** @var Connection */
private $connection;
/** @var string */
private $sqliteFile;
/**
* @after
*/
public function cleanup()
protected function setUp(): void
{
@unlink(sys_get_temp_dir().'/symfony.messenger.sqlite');
}
/**
* @before
*/
public function createConnection()
{
$dsn = getenv('MESSENGER_DOCTRINE_DSN') ?: 'sqlite:///'.sys_get_temp_dir().'/symfony.messenger.sqlite';
$this->sqliteFile = sys_get_temp_dir().'/symfony.messenger.sqlite';
$dsn = getenv('MESSENGER_DOCTRINE_DSN') ?: 'sqlite:///'.$this->sqliteFile;
$this->driverConnection = DriverManager::getConnection(['url' => $dsn]);
$this->connection = new Connection([], $this->driverConnection);
// call send to auto-setup the table
$this->connection->setup();
// ensure the table is clean for tests
$this->driverConnection->exec('DELETE FROM messenger_messages');
}
protected function tearDown(): void
{
$this->driverConnection->close();
if (file_exists($this->sqliteFile)) {
unlink($this->sqliteFile);
}
}
public function testConnectionSendAndGet()
@ -75,6 +73,7 @@ class DoctrineIntegrationTest extends TestCase
public function testItRetrieveTheFirstAvailableMessage()
{
$this->connection->setup();
// insert messages
// one currently handled
$this->driverConnection->insert('messenger_messages', [
@ -108,6 +107,7 @@ class DoctrineIntegrationTest extends TestCase
public function testItCountMessages()
{
$this->connection->setup();
// insert messages
// one currently handled
$this->driverConnection->insert('messenger_messages', [
@ -148,6 +148,7 @@ class DoctrineIntegrationTest extends TestCase
public function testItRetrieveTheMessageThatIsOlderThanRedeliverTimeout()
{
$this->connection->setup();
$twoHoursAgo = new \DateTime('now');
$twoHoursAgo->modify('-2 hours');
$this->driverConnection->insert('messenger_messages', [
@ -173,10 +174,7 @@ class DoctrineIntegrationTest extends TestCase
public function testTheTransportIsSetupOnGet()
{
// If the table does not exist and we call the get (i.e run messenger:consume) the table must be setup
// so first delete the tables
$this->driverConnection->exec('DROP TABLE messenger_messages');
$this->assertFalse($this->driverConnection->getSchemaManager()->tablesExist('messenger_messages'));
$this->assertNull($this->connection->get());
$this->connection->send('the body', ['my' => 'header']);

View File

@ -51,12 +51,14 @@ class Connection
private $configuration = [];
private $driverConnection;
private $schemaSynchronizer;
private $autoSetup;
public function __construct(array $configuration, DBALConnection $driverConnection, SchemaSynchronizer $schemaSynchronizer = null)
{
$this->configuration = array_replace_recursive(self::DEFAULT_OPTIONS, $configuration);
$this->driverConnection = $driverConnection;
$this->schemaSynchronizer = $schemaSynchronizer ?? new SingleDatabaseSynchronizer($this->driverConnection);
$this->autoSetup = $this->configuration['auto_setup'];
}
public function getConfiguration(): array
@ -130,9 +132,7 @@ class Connection
public function get(): ?array
{
if ($this->configuration['auto_setup']) {
$this->setup();
}
get:
$this->driverConnection->beginTransaction();
try {
$query = $this->createAvailableMessagesQueryBuilder()
@ -169,6 +169,11 @@ class Connection
} catch (\Throwable $e) {
$this->driverConnection->rollBack();
if ($this->autoSetup && $e instanceof TableNotFoundException) {
$this->setup();
goto get;
}
throw $e;
}
}
@ -212,6 +217,8 @@ class Connection
} else {
$this->driverConnection->getConfiguration()->setFilterSchemaAssetsExpression($assetFilter);
}
$this->autoSetup = false;
}
public function getMessageCount(): int
@ -225,10 +232,6 @@ class Connection
public function findAll(int $limit = null): array
{
if ($this->configuration['auto_setup']) {
$this->setup();
}
$queryBuilder = $this->createAvailableMessagesQueryBuilder();
if (null !== $limit) {
$queryBuilder->setMaxResults($limit);
@ -243,10 +246,6 @@ class Connection
public function find($id): ?array
{
if ($this->configuration['auto_setup']) {
$this->setup();
}
$queryBuilder = $this->createQueryBuilder()
->where('m.id = ?');
@ -287,8 +286,12 @@ class Connection
$stmt = $this->driverConnection->prepare($sql);
$stmt->execute($parameters);
} catch (TableNotFoundException $e) {
if ($this->driverConnection->isTransactionActive()) {
throw $e;
}
// create table
if (!$this->driverConnection->isTransactionActive() && $this->configuration['auto_setup']) {
if ($this->autoSetup) {
$this->setup();
}
// statement not prepared ? SQLite throw on exception on prepare if the table does not exist

View File

@ -334,6 +334,38 @@
<source>This value should be valid JSON.</source>
<target>Verdien er ikke gyldig JSON.</target>
</trans-unit>
<trans-unit id="87">
<source>This collection should contain only unique elements.</source>
<target>Samlingen kan kun inneholde unike elementer.</target>
</trans-unit>
<trans-unit id="88">
<source>This value should be positive.</source>
<target>Denne verdien må være positiv.</target>
</trans-unit>
<trans-unit id="89">
<source>This value should be either positive or zero.</source>
<target>Denne verdien må være positiv eller null.</target>
</trans-unit>
<trans-unit id="90">
<source>This value should be negative.</source>
<target>Denne verdien må være negativ.</target>
</trans-unit>
<trans-unit id="91">
<source>This value should be either negative or zero.</source>
<target>Denne verdien må være negativ eller null.</target>
</trans-unit>
<trans-unit id="92">
<source>This value is not a valid timezone.</source>
<target>Verdien er ikke en gyldig tidssone.</target>
</trans-unit>
<trans-unit id="93">
<source>This password has been leaked in a data breach, it must not be used. Please use another password.</source>
<target>Dette passordet er lekket i et datainnbrudd, det må ikke tas i bruk. Vennligst bruk et annet passord.</target>
</trans-unit>
<trans-unit id="94">
<source>This value should be between {{ min }} and {{ max }}.</source>
<target>Verdien må være mellom {{ min }} og {{ max }}.</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@ -334,6 +334,38 @@
<source>This value should be valid JSON.</source>
<target>Verdien er ikke gyldig JSON.</target>
</trans-unit>
<trans-unit id="87">
<source>This collection should contain only unique elements.</source>
<target>Samlingen kan kun inneholde unike elementer.</target>
</trans-unit>
<trans-unit id="88">
<source>This value should be positive.</source>
<target>Denne verdien må være positiv.</target>
</trans-unit>
<trans-unit id="89">
<source>This value should be either positive or zero.</source>
<target>Denne verdien må være positiv eller null.</target>
</trans-unit>
<trans-unit id="90">
<source>This value should be negative.</source>
<target>Denne verdien må være negativ.</target>
</trans-unit>
<trans-unit id="91">
<source>This value should be either negative or zero.</source>
<target>Denne verdien må være negativ eller null.</target>
</trans-unit>
<trans-unit id="92">
<source>This value is not a valid timezone.</source>
<target>Verdien er ikke en gyldig tidssone.</target>
</trans-unit>
<trans-unit id="93">
<source>This password has been leaked in a data breach, it must not be used. Please use another password.</source>
<target>Dette passordet er lekket i et datainnbrudd, det må ikke tas i bruk. Vennligst bruk et annet passord.</target>
</trans-unit>
<trans-unit id="94">
<source>This value should be between {{ min }} and {{ max }}.</source>
<target>Verdien må være mellom {{ min }} og {{ max }}.</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@ -334,6 +334,38 @@
<source>This value should be valid JSON.</source>
<target>Detta värde ska vara giltig JSON.</target>
</trans-unit>
<trans-unit id="87">
<source>This collection should contain only unique elements.</source>
<target>Denna samling bör endast innehålla unika element.</target>
</trans-unit>
<trans-unit id="88">
<source>This value should be positive.</source>
<target>Detta värde bör vara positivt.</target>
</trans-unit>
<trans-unit id="89">
<source>This value should be either positive or zero.</source>
<target>Detta värde bör vara antingen positivt eller noll.</target>
</trans-unit>
<trans-unit id="90">
<source>This value should be negative.</source>
<target>Detta värde bör vara negativt.</target>
</trans-unit>
<trans-unit id="91">
<source>This value should be either negative or zero.</source>
<target>Detta värde bör vara antingen negativt eller noll.</target>
</trans-unit>
<trans-unit id="92">
<source>This value is not a valid timezone.</source>
<target>Detta värde är inte en giltig tidszon.</target>
</trans-unit>
<trans-unit id="93">
<source>This password has been leaked in a data breach, it must not be used. Please use another password.</source>
<target>Det här lösenordet har läckt ut vid ett dataintrång, det får inte användas. Använd ett annat lösenord.</target>
</trans-unit>
<trans-unit id="94">
<source>This value should be between {{ min }} and {{ max }}.</source>
<target>Detta värde bör ligga mellan {{ min }} och {{ max }}.</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@ -33,7 +33,8 @@ class SymfonyCaster
$clone = null;
foreach (self::$requestGetters as $prop => $getter) {
if (null === $a[Caster::PREFIX_PROTECTED.$prop]) {
$key = Caster::PREFIX_PROTECTED.$prop;
if (\array_key_exists($key, $a) && null === $a[$key]) {
if (null === $clone) {
$clone = clone $request;
}
@ -47,7 +48,9 @@ class SymfonyCaster
public static function castHttpClient($client, array $a, Stub $stub, bool $isNested)
{
$multiKey = sprintf("\0%s\0multi", \get_class($client));
if (isset($a[$multiKey])) {
$a[$multiKey] = new CutStub($a[$multiKey]);
}
return $a;
}