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

View File

@ -495,7 +495,7 @@ class Configuration implements ConfigurationInterface
->scalarNode('cookie_domain')->end() ->scalarNode('cookie_domain')->end()
->enumNode('cookie_secure')->values([true, false, 'auto'])->end() ->enumNode('cookie_secure')->values([true, false, 'auto'])->end()
->booleanNode('cookie_httponly')->defaultTrue()->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() ->booleanNode('use_cookies')->end()
->scalarNode('gc_divisor')->end() ->scalarNode('gc_divisor')->end()
->scalarNode('gc_probability')->defaultValue(1)->end() ->scalarNode('gc_probability')->defaultValue(1)->end()

View File

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

View File

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

View File

@ -11,33 +11,32 @@
namespace Symfony\Component\Cache\Adapter; namespace Symfony\Component\Cache\Adapter;
use Predis;
use Predis\Connection\Aggregate\ClusterInterface; use Predis\Connection\Aggregate\ClusterInterface;
use Predis\Connection\Aggregate\PredisCluster;
use Predis\Response\Status; use Predis\Response\Status;
use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\Exception\LogicException;
use Symfony\Component\Cache\Marshaller\MarshallerInterface; use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\Traits\RedisTrait; 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 * 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 * 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). * relationship survives eviction (cache cleanup when Redis runs out of memory).
* *
* Requirements: * Requirements:
* - Server: Redis 3.2+ * - Client: PHP Redis or Predis
* - Client: PHP Redis 3.1.3+ OR Predis * Note: Due to lack of RENAME support it is NOT recommended to use Cluster on Predis, instead use phpredis.
* - Redis Server(s) configured with any `volatile-*` eviction policy, OR `noeviction` if it will NEVER fill up memory * - Server: Redis 2.8+
* Configured with any `volatile-*` eviction policy, OR `noeviction` if it will NEVER fill up memory
* *
* Design limitations: * Design limitations:
* - Max 2 billion cache keys per cache tag * - 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 2 billion cache items as well * 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/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/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 Nicolas Grekas <p@tchwork.com>
* @author André Rømcke <andre.romcke+symfony@gmail.com> * @author André Rømcke <andre.romcke+symfony@gmail.com>
@ -46,11 +45,6 @@ class RedisTagAwareAdapter extends AbstractTagAwareAdapter
{ {
use RedisTrait; 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. * Limits for how many keys are deleted in batch.
*/ */
@ -62,26 +56,18 @@ class RedisTagAwareAdapter extends AbstractTagAwareAdapter
*/ */
private const DEFAULT_CACHE_TTL = 8640000; private const DEFAULT_CACHE_TTL = 8640000;
/**
* @var bool|null
*/
private $redisServerSupportSPOP = null;
/** /**
* @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redisClient The redis client * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redisClient The redis client
* @param string $namespace The default namespace * @param string $namespace The default namespace
* @param int $defaultLifetime The default lifetime * @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) public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
{ {
$this->init($redisClient, $namespace, $defaultLifetime, $marshaller); 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())));
// 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');
} }
$this->init($redisClient, $namespace, $defaultLifetime, $marshaller);
} }
/** /**
@ -121,7 +107,7 @@ class RedisTagAwareAdapter extends AbstractTagAwareAdapter
continue; continue;
} }
// setEx results // setEx results
if (true !== $result && (!$result instanceof Status || $result !== Status::get('OK'))) { if (true !== $result && (!$result instanceof Status || Status::get('OK') !== $result)) {
$failed[] = $id; $failed[] = $id;
} }
} }
@ -138,9 +124,10 @@ class RedisTagAwareAdapter extends AbstractTagAwareAdapter
return true; 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) { $this->pipeline(static function () use ($ids, $tagData, $predisCluster) {
if ($predisCluster) { if ($predisCluster) {
// Unlike phpredis, Predis does not handle bulk calls for us against cluster
foreach ($ids as $id) { foreach ($ids as $id) {
yield 'del' => [$id]; yield 'del' => [$id];
} }
@ -161,46 +148,76 @@ class RedisTagAwareAdapter extends AbstractTagAwareAdapter
*/ */
protected function doInvalidate(array $tagIds): bool 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; return false;
} }
// Pop all tag info at once to avoid race conditions // 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 ($tagIds) { $tagIdSets = $this->pipeline(static function () use ($movedTagSetIds) {
foreach ($tagIds as $tagId) { foreach ($movedTagSetIds as $movedTagId) {
// Client: Predis or PHP Redis 3.1.3+ (https://github.com/phpredis/phpredis/commit/d2e203a6) yield 'sMembers' => [$movedTagId];
// Server: Redis 3.2 or higher (https://redis.io/commands/spop)
yield 'sPop' => [$tagId, self::POP_MAX_LIMIT];
} }
}); });
// Flatten generator result from pipeline, ignore keys (tag ids) // Return combination of the temporary Tag Set ids and their values (cache ids)
$ids = array_unique(array_merge(...iterator_to_array($tagIdSets, false))); $ids = array_merge($movedTagSetIds, ...iterator_to_array($tagIdSets, false));
// Delete cache in chunks to avoid overloading the connection // 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); $this->doDelete($chunkIds);
} }
return true; 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) { $newIds = [];
return $this->redisServerSupportSPOP; $uniqueToken = bin2hex(random_bytes(10));
}
foreach ($this->getHosts() as $host) { $results = $this->pipeline(static function () use ($ids, $uniqueToken) {
$info = $host->info('Server'); foreach ($ids as $id) {
$info = isset($info['Server']) ? $info['Server'] : $info; yield 'rename' => [$id, '{'.$id.'}'.$uniqueToken];
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']); }, $redis);
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 support for connecting to Redis Sentinel clusters
* added argument `$prefix` to `AdapterInterface::clear()` * 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 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) private function init(string $namespace, ?string $directory)
{ {
if (!isset($directory[0])) { if (!isset($directory[0])) {
$directory = sys_get_temp_dir().'/symfony-cache'; $directory = sys_get_temp_dir().\DIRECTORY_SEPARATOR.'symfony-cache';
} else { } else {
$directory = realpath($directory) ?: $directory; $directory = realpath($directory) ?: $directory;
} }
@ -55,12 +55,12 @@ trait FilesystemCommonTrait
{ {
$ok = true; $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)) { if ('' !== $namespace && 0 !== strpos($this->getFileKey($file), $namespace)) {
continue; continue;
} }
$ok = ($file->isDir() || $this->doUnlink($file) || !file_exists($file)) && $ok; $ok = ($this->doUnlink($file) || !file_exists($file)) && $ok;
} }
return $ok; return $ok;
@ -123,6 +123,33 @@ trait FilesystemCommonTrait
return ''; 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 * @internal
*/ */

View File

@ -33,7 +33,7 @@ trait FilesystemTrait
$time = time(); $time = time();
$pruned = true; $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')) { if (!$h = @fopen($file, 'rb')) {
continue; continue;
} }

View File

@ -55,9 +55,17 @@ trait RedisTrait
if (preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) { 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])); 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) { 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))); 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->redis = $redisClient;
$this->marshaller = $marshaller ?? new DefaultMarshaller(); $this->marshaller = $marshaller ?? new DefaultMarshaller();
} }
@ -277,6 +285,7 @@ trait RedisTrait
$params['replication'] = true; $params['replication'] = true;
$hosts[0] += ['alias' => 'master']; $hosts[0] += ['alias' => 'master'];
} }
$params['exceptions'] = false;
$redis = new $class($hosts, array_diff_key($params, self::$defaultConnectionOptions)); $redis = new $class($hosts, array_diff_key($params, self::$defaultConnectionOptions));
if (isset($params['redis_sentinel'])) { if (isset($params['redis_sentinel'])) {
@ -414,8 +423,9 @@ trait RedisTrait
} }
} }
}); });
foreach ($results as $id => $result) { 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; $failed[] = $id;
} }
} }
@ -423,31 +433,32 @@ trait RedisTrait
return $failed; return $failed;
} }
private function pipeline(\Closure $generator): \Generator private function pipeline(\Closure $generator, $redis = null): \Generator
{ {
$ids = []; $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 // phpredis & predis don't support pipelining with RedisCluster
// see https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#pipelining // see https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#pipelining
// see https://github.com/nrk/predis/issues/267#issuecomment-123781423 // see https://github.com/nrk/predis/issues/267#issuecomment-123781423
$results = []; $results = [];
foreach ($generator() as $command => $args) { foreach ($generator() as $command => $args) {
$results[] = $this->redis->{$command}(...$args); $results[] = $redis->{$command}(...$args);
$ids[] = $args[0]; $ids[] = $args[0];
} }
} elseif ($this->redis instanceof \Predis\ClientInterface) { } elseif ($redis instanceof \Predis\ClientInterface) {
$results = $this->redis->pipeline(function ($redis) use ($generator, &$ids) { $results = $redis->pipeline(static function ($redis) use ($generator, &$ids) {
foreach ($generator() as $command => $args) { foreach ($generator() as $command => $args) {
$redis->{$command}(...$args); $redis->{$command}(...$args);
$ids[] = $args[0]; $ids[] = $args[0];
} }
}); });
} elseif ($this->redis instanceof \RedisArray) { } elseif ($redis instanceof \RedisArray) {
$connections = $results = $ids = []; $connections = $results = $ids = [];
foreach ($generator() as $command => $args) { foreach ($generator() as $command => $args) {
if (!isset($connections[$h = $this->redis->_target($args[0])])) { if (!isset($connections[$h = $redis->_target($args[0])])) {
$connections[$h] = [$this->redis->_instance($h), -1]; $connections[$h] = [$redis->_instance($h), -1];
$connections[$h][0]->multi(\Redis::PIPELINE); $connections[$h][0]->multi(\Redis::PIPELINE);
} }
$connections[$h][0]->{$command}(...$args); $connections[$h][0]->{$command}(...$args);
@ -461,12 +472,12 @@ trait RedisTrait
$results[$k] = $connections[$h][$c]; $results[$k] = $connections[$h][$c];
} }
} else { } else {
$this->redis->multi(\Redis::PIPELINE); $redis->multi(\Redis::PIPELINE);
foreach ($generator() as $command => $args) { foreach ($generator() as $command => $args) {
$this->redis->{$command}(...$args); $redis->{$command}(...$args);
$ids[] = $args[0]; $ids[] = $args[0];
} }
$results = $this->redis->exec(); $results = $redis->exec();
} }
foreach ($ids as $k => $id) { foreach ($ids as $k => $id) {

View File

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

View File

@ -187,7 +187,6 @@ class ProgressBarTest extends TestCase
{ {
$expected = $expected =
' 0/10 [>---------------------------] 0%'. ' 0/10 [>---------------------------] 0%'.
$this->generateOutput(' 10/10 [============================] 100%').
$this->generateOutput(' 10/10 [============================] 100%') $this->generateOutput(' 10/10 [============================] 100%')
; ;
@ -296,7 +295,6 @@ class ProgressBarTest extends TestCase
rewind($output->getStream()); rewind($output->getStream());
$this->assertEquals( $this->assertEquals(
' 0/50 [>---------------------------] 0%'. ' 0/50 [>---------------------------] 0%'.
$this->generateOutput(' 0/50 [>---------------------------] 0%').
$this->generateOutput(' 1/50 [>---------------------------] 2%'). $this->generateOutput(' 1/50 [>---------------------------] 2%').
$this->generateOutput(' 2/50 [=>--------------------------] 4%'), $this->generateOutput(' 2/50 [=>--------------------------] 4%'),
stream_get_contents($output->getStream()) stream_get_contents($output->getStream())
@ -318,7 +316,6 @@ class ProgressBarTest extends TestCase
rewind($output->getStream()); rewind($output->getStream());
$this->assertEquals( $this->assertEquals(
' 0/50 [>---------------------------] 0%'. ' 0/50 [>---------------------------] 0%'.
$this->generateOutput(' 0/50 [>---------------------------] 0%').
$this->generateOutput(' 1/50 [>---------------------------] 2%'). $this->generateOutput(' 1/50 [>---------------------------] 2%').
$this->generateOutput(' 2/50 [=>--------------------------]'), $this->generateOutput(' 2/50 [=>--------------------------]'),
stream_get_contents($output->getStream()) stream_get_contents($output->getStream())
@ -340,7 +337,6 @@ class ProgressBarTest extends TestCase
rewind($output->getStream()); rewind($output->getStream());
$this->assertEquals( $this->assertEquals(
' 0/50 [>---------------------------] 0%'.PHP_EOL. ' 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".' 1/50 [>---------------------------] 2%'.PHP_EOL.
"\x1b[1A\x1b[0J".' 2/50 [=>--------------------------] 4%'.PHP_EOL, "\x1b[1A\x1b[0J".' 2/50 [=>--------------------------] 4%'.PHP_EOL,
stream_get_contents($output->getStream()) stream_get_contents($output->getStream())
@ -434,7 +430,6 @@ class ProgressBarTest extends TestCase
rewind($output->getStream()); rewind($output->getStream());
$this->assertEquals( $this->assertEquals(
' 0/50 [>---------------------------] 0%'. ' 0/50 [>---------------------------] 0%'.
$this->generateOutput(' 0/50 [>---------------------------] 0%').
$this->generateOutput(' 1/50 [>---------------------------] 2%'). $this->generateOutput(' 1/50 [>---------------------------] 2%').
$this->generateOutput(' 15/50 [========>-------------------] 30%'). $this->generateOutput(' 15/50 [========>-------------------] 30%').
$this->generateOutput(' 25/50 [==============>-------------] 50%'), $this->generateOutput(' 25/50 [==============>-------------] 50%'),
@ -541,7 +536,6 @@ class ProgressBarTest extends TestCase
rewind($output->getStream()); rewind($output->getStream());
$this->assertEquals( $this->assertEquals(
' 0/200 [>---------------------------] 0%'. ' 0/200 [>---------------------------] 0%'.
$this->generateOutput(' 0/200 [>---------------------------] 0%').
$this->generateOutput(' 199/200 [===========================>] 99%'). $this->generateOutput(' 199/200 [===========================>] 99%').
$this->generateOutput(' 200/200 [============================] 100%'), $this->generateOutput(' 200/200 [============================] 100%'),
stream_get_contents($output->getStream()) stream_get_contents($output->getStream())
@ -888,7 +882,6 @@ class ProgressBarTest extends TestCase
$this->assertEquals( $this->assertEquals(
' 0/2 [>---------------------------] 0%'. ' 0/2 [>---------------------------] 0%'.
$this->generateOutput(' 1/2 [==============>-------------] 50%'). $this->generateOutput(' 1/2 [==============>-------------] 50%').
$this->generateOutput(' 2/2 [============================] 100%').
$this->generateOutput(' 2/2 [============================] 100%'), $this->generateOutput(' 2/2 [============================] 100%'),
stream_get_contents($output->getStream()) stream_get_contents($output->getStream())
); );
@ -996,4 +989,18 @@ class ProgressBarTest extends TestCase
stream_get_contents($output->getStream()) 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 * added support for binding iterable and tagged services
* made singly-implemented interfaces detection be scoped by file * made singly-implemented interfaces detection be scoped by file
* added ability to define a static priority method for tagged service * 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 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)); 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'])) { if (isset($call['method'])) {
$method = $call['method']; $method = $call['method'];
$args = isset($call['arguments']) ? $this->resolveServices($call['arguments'], $file) : []; $args = $call['arguments'] ?? [];
$returnsClone = $call['returns_clone'] ?? false; $returnsClone = $call['returns_clone'] ?? false;
} else { } else {
$method = $call[0]; if (1 === \count($call) && \is_string(key($call))) {
$args = isset($call[1]) ? $this->resolveServices($call[1], $file) : []; $method = key($call);
$returnsClone = $call[2] ?? false; $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 = $call[1] ?? [];
$returnsClone = $call[2] ?? false;
}
} }
if (!\is_array($args)) { 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)); 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); $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); $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. * 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 bool $normalizeWhitespace Whether whitespaces should be trimmed and normalized to single spaces *
* @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 * @return string The node value
* *
* @throws \InvalidArgumentException When current node is empty * @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 (!$this->nodes) {
if (0 < \func_num_args()) { if (null !== $default) {
return $default; return $default;
} }
@ -600,16 +602,16 @@ class Crawler implements \Countable, \IteratorAggregate
/** /**
* Returns the first node of the list as HTML. * 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 * @return string The node html
* *
* @throws \InvalidArgumentException When current node is empty * @throws \InvalidArgumentException When current node is empty
*/ */
public function html($default = null) public function html(string $default = null)
{ {
if (!$this->nodes) { if (!$this->nodes) {
if (0 < \func_num_args()) { if (null !== $default) {
return $default; return $default;
} }

View File

@ -427,7 +427,7 @@ final class Dotenv
(?!\() # no opening parenthesis (?!\() # no opening parenthesis
(?P<opening_brace>\{)? # optional brace (?P<opening_brace>\{)? # optional brace
(?P<name>'.self::VARNAME_REGEX.')? # var name (?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 (?P<closing_brace>\})? # optional closing brace
/x'; /x';
@ -464,6 +464,10 @@ final class Dotenv
} }
$value = substr($matches['default_value'], 2); $value = substr($matches['default_value'], 2);
if ('=' === $matches['default_value'][1]) {
$this->values[$name] = $value;
}
} }
if (!$matches['opening_brace'] && isset($matches['closing_brace'])) { 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=BAR\nBAR=\${NOTDEFINED:-TEST}", ['FOO' => 'BAR', 'BAR' => 'TEST']],
["FOO=\nBAR=\${FOO:-TEST}", ['FOO' => '', 'BAR' => 'TEST']], ["FOO=\nBAR=\${FOO:-TEST}", ['FOO' => '', '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) { 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`. * `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 4.3.0
----- -----

View File

@ -16,8 +16,10 @@ use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher\Event as LegacyEvent;
use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Contracts\EventDispatcher\Event;
/** /**
* Compiler pass to register tagged services for an event dispatcher. * 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; $priority = isset($event['priority']) ? $event['priority'] : 0;
if (!isset($event['event'])) { 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']; $event['event'] = $aliases[$event['event']] ?? $event['event'];
if (!isset($event['method'])) { if (!isset($event['method'])) {
@ -122,6 +130,24 @@ class RegisterListenersPass implements CompilerPassInterface
ExtractingEventDispatcher::$aliases = []; 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 PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher\DependencyInjection\AddEventAliasesPass; use Symfony\Component\EventDispatcher\DependencyInjection\AddEventAliasesPass;
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass; use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
@ -244,6 +245,116 @@ class RegisterListenersPassTest extends TestCase
]; ];
$this->assertEquals($expectedCalls, $definition->getMethodCalls()); $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 class SubscriberService implements EventSubscriberInterface
@ -285,3 +396,43 @@ final class AliasedEvent
final class CustomEvent 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.'); 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 class DateTimeImmutableToDateTimeTransformerTest extends TestCase
{ {
public function testTransform() /**
* @dataProvider provider
*/
public function testTransform(\DateTime $expectedOutput, \DateTimeImmutable $input)
{ {
$transformer = new DateTimeImmutableToDateTimeTransformer(); $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); $actualOutput = $transformer->transform($input);
$this->assertInstanceOf(\DateTime::class, $actualOutput);
$this->assertEquals($expectedOutput, $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() public function testTransformEmpty()
@ -43,16 +60,17 @@ class DateTimeImmutableToDateTimeTransformerTest extends TestCase
$transformer->transform(new \DateTime()); $transformer->transform(new \DateTime());
} }
public function testReverseTransform() /**
* @dataProvider provider
*/
public function testReverseTransform(\DateTime $input, \DateTimeImmutable $expectedOutput)
{ {
$transformer = new DateTimeImmutableToDateTimeTransformer(); $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); $actualOutput = $transformer->reverseTransform($input);
$this->assertInstanceOf(\DateTimeImmutable::class, $actualOutput);
$this->assertEquals($expectedOutput, $actualOutput); $this->assertEquals($expectedOutput, $actualOutput);
$this->assertEquals($expectedOutput->getTimezone(), $actualOutput->getTimezone());
} }
public function testReverseTransformEmpty() public function testReverseTransformEmpty()

View File

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

View File

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

View File

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

View File

@ -334,6 +334,38 @@
<source>This value should be valid JSON.</source> <source>This value should be valid JSON.</source>
<target>Verdien er ikke gyldig JSON.</target> <target>Verdien er ikke gyldig JSON.</target>
</trans-unit> </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> </body>
</file> </file>
</xliff> </xliff>

View File

@ -334,6 +334,38 @@
<source>This value should be valid JSON.</source> <source>This value should be valid JSON.</source>
<target>Detta värde ska vara giltig JSON.</target> <target>Detta värde ska vara giltig JSON.</target>
</trans-unit> </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> </body>
</file> </file>
</xliff> </xliff>

View File

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