diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index b6f39741d9dbc..ac82d7063d015 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,6 +1,6 @@
| Q | A
| ------------- | ---
-| Branch? | master for features / 2.8 up to 4.1 for bug fixes
+| Branch? | master for features / 3.4 up to 4.2 for bug fixes
| Bug fix? | yes/no
| New feature? | yes/no
| BC breaks? | no
diff --git a/.travis.yml b/.travis.yml
index 8f41396041a90..03984af88795d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -181,7 +181,7 @@ before_install:
fi
tfold ext.apcu tpecl apcu-5.1.6 apcu.so $INI
- tfold ext.mongodb tpecl mongodb-1.5.2 mongodb.so $INI
+ tfold ext.mongodb tpecl mongodb-1.6.0alpha1 mongodb.so $INI
fi
done
@@ -247,7 +247,7 @@ install:
fi
phpenv global ${PHP/hhvm*/hhvm}
if [[ $PHP = 7.* ]]; then
- ([[ $deps ]] && cd src/Symfony/Component/HttpFoundation; composer config platform.ext-mongodb 1.5.2; composer require --dev --no-update mongodb/mongodb)
+ ([[ $deps ]] && cd src/Symfony/Component/HttpFoundation; composer config platform.ext-mongodb 1.6.0; composer require --dev --no-update mongodb/mongodb)
fi
tfold 'composer update' $COMPOSER_UP
if [[ $TRAVIS_PHP_VERSION = 5.* || $TRAVIS_PHP_VERSION = hhvm* ]]; then
diff --git a/CHANGELOG-3.4.md b/CHANGELOG-3.4.md
index dcd1c3bf06293..d0d11555c73ad 100644
--- a/CHANGELOG-3.4.md
+++ b/CHANGELOG-3.4.md
@@ -7,6 +7,23 @@ in 3.4 minor versions.
To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash
To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v3.4.0...v3.4.1
+* 3.4.20 (2018-12-06)
+
+ * security #cve-2018-19790 [Security\Http] detect bad redirect targets using backslashes (xabbuh)
+ * security #cve-2018-19789 [Form] Filter file uploads out of regular form types (nicolas-grekas)
+ * bug #29436 [Cache] Fixed Memcached adapter doClear()to call flush() (raitocz)
+ * bug #29441 [Routing] ignore trailing slash for non-GET requests (nicolas-grekas)
+ * bug #29432 [DI] dont inline when lazy edges are found (nicolas-grekas)
+ * bug #29413 [Serializer] fixed DateTimeNormalizer to maintain microseconds when a different timezone required (rvitaliy)
+ * bug #29424 [Routing] fix taking verb into account when redirecting (nicolas-grekas)
+ * bug #29414 [DI] Fix dumping expressions accessing single-use private services (chalasr)
+ * bug #29375 [Validator] Allow `ConstraintViolation::__toString()` to expose codes that are not null or emtpy strings (phansys)
+ * bug #29376 [EventDispatcher] Fix eventListener wrapper loop in TraceableEventDispatcher (jderusse)
+ * bug #29343 [Form] Handle all case variants of "nan" when parsing a number (mwhudson, xabbuh)
+ * bug #29355 [PropertyAccess] calculate cache keys for property setters depending on the value (xabbuh)
+ * bug #29369 [DI] fix combinatorial explosion when analyzing the service graph (nicolas-grekas)
+ * bug #29349 [Debug] workaround opcache bug mutating "$this" !?! (nicolas-grekas)
+
* 3.4.19 (2018-11-26)
* bug #29318 [Console] Move back root exception to stack trace in verbose mode (chalasr)
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 969c6a424cefc..a6800f3acfb8b 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -14,15 +14,15 @@ Symfony is the result of the work of many people who made the code better
- Victor Berchet (victor)
- Robin Chalas (chalas_r)
- Kévin Dunglas (dunglas)
+ - Maxime Steinhausser (ogizanagi)
- Jakub Zalas (jakubzalas)
- Johannes S (johannes)
- - Maxime Steinhausser (ogizanagi)
- Kris Wallsmith (kriswallsmith)
- Ryan Weaver (weaverryan)
- Javier Eguiluz (javier.eguiluz)
- Grégoire Pineau (lyrixx)
- - Hugo Hamon (hhamon)
- Roland Franssen (ro0)
+ - Hugo Hamon (hhamon)
- Abdellatif Ait boudad (aitboudad)
- Romain Neutron (romain)
- Pascal Borreli (pborreli)
@@ -37,8 +37,8 @@ Symfony is the result of the work of many people who made the code better
- Benjamin Eberlei (beberlei)
- Igor Wiedler (igorw)
- Jules Pietri (heah)
- - Eriksen Costa (eriksencosta)
- Yonel Ceruto (yonelceruto)
+ - Eriksen Costa (eriksencosta)
- Guilhem Niot (energetick)
- Sarah Khalil (saro0h)
- Jonathan Wage (jwage)
@@ -56,12 +56,12 @@ Symfony is the result of the work of many people who made the code better
- Bulat Shakirzyanov (avalanche123)
- Matthias Pigulla (mpdude)
- Peter Rehm (rpet)
+ - Jérémy DERUSSÉ (jderusse)
- Saša Stamenković (umpirsky)
- Pierre du Plessis (pierredup)
- Kevin Bond (kbond)
- Henrik Bjørnskov (henrikbjorn)
- Miha Vrhovnik
- - Jérémy DERUSSÉ (jderusse)
- Diego Saint Esteben (dii3g0)
- Alexander M. Turek (derrabus)
- Konstantin Kudryashov (everzet)
@@ -70,6 +70,7 @@ Symfony is the result of the work of many people who made the code better
- Gábor Egyed (1ed)
- Mathieu Piot (mpiot)
- Titouan Galopin (tgalopin)
+ - Vladimir Reznichenko (kalessil)
- Michel Weimerskirch (mweimerskirch)
- Andrej Hudec (pulzarraider)
- Konstantin Myakshin (koc)
@@ -77,7 +78,6 @@ Symfony is the result of the work of many people who made the code better
- Jáchym Toušek (enumag)
- Charles Sarrazin (csarrazi)
- David Maicher (dmaicher)
- - Vladimir Reznichenko (kalessil)
- Christian Raue
- Issei Murasawa (issei_m)
- Arnout Boks (aboks)
@@ -86,13 +86,13 @@ Symfony is the result of the work of many people who made the code better
- Dariusz Górecki (canni)
- Douglas Greenshields (shieldo)
- Dariusz Ruminski
+ - Grégoire Paris (greg0ire)
- Lee McDermott
- Brandon Turner
- Luis Cordova (cordoval)
- Graham Campbell (graham)
- Daniel Holmes (dholmes)
- Toni Uebernickel (havvg)
- - Grégoire Paris (greg0ire)
- Bart van den Burg (burgov)
- Jordan Alliot (jalliot)
- Jérôme Tamarelle (gromnan)
@@ -113,15 +113,17 @@ Symfony is the result of the work of many people who made the code better
- lenar
- Alexander Schwenn (xelaris)
- Włodzimierz Gajda (gajdaw)
+ - Tomáš Votruba (tomas_votruba)
- Peter Kokot (maastermedia)
- Jacob Dreesen (jdreesen)
- Florian Voutzinos (florianv)
- Colin Frei
- Adrien Brault (adrienbrault)
- - Tomáš Votruba (tomas_votruba)
- Joshua Thijssen
- excelwebzone
- Gordon Franke (gimler)
+ - Chris Wilkinson (thewilkybarkid)
+ - Javier Spagnoletti (phansys)
- Fabien Pennequin (fabienpennequin)
- Eric GELOEN (gelo)
- Sebastiaan Stok (sstok)
@@ -129,11 +131,9 @@ Symfony is the result of the work of many people who made the code better
- Lars Strojny (lstrojny)
- Daniel Wehner (dawehner)
- Tugdual Saunier (tucksaun)
- - Javier Spagnoletti (phansys)
- Théo FIDRY (theofidry)
- Robert Schönthal (digitalkaoz)
- Florian Lonqueu-Brochard (florianlb)
- - Chris Wilkinson (thewilkybarkid)
- Stefano Sala (stefano.sala)
- Evgeniy (ewgraf)
- Alex Pott
@@ -165,6 +165,7 @@ Symfony is the result of the work of many people who made the code better
- Rouven Weßling (realityking)
- Clemens Tolboom
- Helmer Aaviksoo
+ - Alessandro Chitolina (alekitto)
- Hiromi Hishida (77web)
- Niels Keurentjes (curry684)
- Matthieu Ouellette-Vachon (maoueh)
@@ -179,8 +180,9 @@ Symfony is the result of the work of many people who made the code better
- jeremyFreeAgent (Jérémy Romey) (jeremyfreeagent)
- James Halsall (jaitsu)
- Matthieu Napoli (mnapoli)
+ - Florent Mata (fmata)
- Warnar Boekkooi (boekkooi)
- - Alessandro Chitolina (alekitto)
+ - Thomas Calvet (fancyweb)
- Dmitrii Chekaliuk (lazyhammer)
- Clément JOBEILI (dator)
- Daniel Espendiller
@@ -193,9 +195,7 @@ Symfony is the result of the work of many people who made the code better
- Dennis Benkert (denderello)
- DQNEO
- Benjamin Dulau (dbenjamin)
- - Florent Mata (fmata)
- Mathieu Lemoine (lemoinem)
- - Thomas Calvet (fancyweb)
- Christian Schmidt
- Andreas Hucks (meandmymonkey)
- Noel Guilbert (noel)
@@ -217,6 +217,7 @@ Symfony is the result of the work of many people who made the code better
- Jeremy Livingston (jeremylivingston)
- Michael Lee (zerustech)
- Matthieu Auger (matthieuauger)
+ - Oskar Stark (oskarstark)
- Leszek Prabucki (l3l0)
- François Zaninotto (fzaninotto)
- Dustin Whittle (dustinwhittle)
@@ -241,12 +242,12 @@ Symfony is the result of the work of many people who made the code better
- Rob Frawley 2nd (robfrawley)
- julien pauli (jpauli)
- Lorenz Schori
- - Oskar Stark (oskarstark)
- Sébastien Lavoie (lavoiesl)
- Gregor Harlan (gharlan)
- Dariusz
- Francois Zaninotto
- Alexander Kotynia (olden)
+ - Fabien Bourigault (fbourigault)
- Daniel Tschinder
- Christian Schmidt
- Marcos Sánchez
@@ -258,6 +259,7 @@ Symfony is the result of the work of many people who made the code better
- Benoît Burnichon (bburnichon)
- Roman Marintšenko (inori)
- Xavier Montaña Carreras (xmontana)
+ - François-Xavier de Guillebon (de-gui_f)
- Mickaël Andrieu (mickaelandrieu)
- Maxime Veber (nek-)
- Xavier Perez
@@ -295,6 +297,7 @@ Symfony is the result of the work of many people who made the code better
- mcfedr (mcfedr)
- Colin O'Dell (colinodell)
- Giorgio Premi
+ - Jan Schädlich (jschaedl)
- Beau Simensen (simensen)
- Michael Hirschler (mvhirsch)
- Robert Kiss (kepten)
@@ -305,8 +308,8 @@ Symfony is the result of the work of many people who made the code better
- Jérôme Parmentier (lctrs)
- Michael Babker (mbabker)
- Peter Kruithof (pkruithof)
- - François-Xavier de Guillebon (de-gui_f)
- Michael Holm (hollo)
+ - Remon van de Kamp (rpkamp)
- Marc Weistroff (futurecat)
- Christian Schmidt
- MatTheCat
@@ -350,8 +353,6 @@ Symfony is the result of the work of many people who made the code better
- Ricard Clau (ricardclau)
- Mark Challoner (markchalloner)
- Gennady Telegin (gtelegin)
- - Jan Schädlich (jschaedl)
- - Fabien Bourigault (fbourigault)
- Ben Davies (bendavies)
- Erin Millard
- Artur Melo (restless)
@@ -445,6 +446,7 @@ Symfony is the result of the work of many people who made the code better
- lancergr
- Zan Baldwin
- Mihai Stancu
+ - Ivan Nikolaev (destillat)
- Olivier Dolbeau (odolbeau)
- Jan Rosier (rosier)
- Alessandro Lai (jean85)
@@ -461,7 +463,6 @@ Symfony is the result of the work of many people who made the code better
- Boris Vujicic (boris.vujicic)
- Chris Sedlmayr (catchamonkey)
- Mateusz Sip (mateusz_sip)
- - Remon van de Kamp
- Kamil Kokot (pamil)
- Seb Koelen
- Christoph Mewes (xrstf)
@@ -470,6 +471,7 @@ Symfony is the result of the work of many people who made the code better
- Dirk Pahl (dirkaholic)
- cedric lombardot (cedriclombardot)
- Jonas Flodén (flojon)
+ - Gonzalo Vilaseca (gonzalovilaseca)
- Marcin Sikoń (marphi)
- Dominik Zogg (dominik.zogg)
- Marek Pietrzak
@@ -569,6 +571,7 @@ Symfony is the result of the work of many people who made the code better
- Marcin Chyłek (songoq)
- Ben Scott
- Ned Schwartz
+ - Samuel NELA (snela)
- Ziumin
- Jeremy Benoist
- fritzmg
@@ -619,6 +622,7 @@ Symfony is the result of the work of many people who made the code better
- Gunnstein Lye (glye)
- Maxime Douailin
- Jean Pasdeloup (pasdeloup)
+ - Sylvain Fabre (sylfabre)
- Benjamin Cremer (bcremer)
- Javier López (loalf)
- Reinier Kip
@@ -643,7 +647,6 @@ Symfony is the result of the work of many people who made the code better
- adev
- Stefan Warman
- Arkadius Stefanski (arkadius)
- - Gonzalo Vilaseca (gonzalovilaseca)
- Tristan Maindron (tmaindron)
- Wesley Lancel
- Ke WANG (yktd26)
@@ -665,6 +668,7 @@ Symfony is the result of the work of many people who made the code better
- Sergey (upyx)
- Michael Devery (mickadoo)
- Antoine Corcy
+ - Dmitrii Poddubnyi (karser)
- Sascha Grossenbacher
- Szijarto Tamas
- Robin Lehrmann (robinlehrmann)
@@ -696,6 +700,7 @@ Symfony is the result of the work of many people who made the code better
- Tiago Brito (blackmx)
-
- Richard van den Brand (ricbra)
+ - Thomas Bisignani (toma)
- develop
- flip111
- Greg Anderson
@@ -804,7 +809,6 @@ Symfony is the result of the work of many people who made the code better
- Sofiane HADDAG (sofhad)
- frost-nzcr4
- Bozhidar Hristov
- - Ivan Nikolaev (destillat)
- Laurent Bassin (lbassin)
- andrey1s
- Abhoryo
@@ -840,7 +844,6 @@ Symfony is the result of the work of many people who made the code better
- Jörn Lang (j.lang)
- Omar Yepez (oyepez003)
- Gawain Lynch (gawain)
- - Samuel NELA (snela)
- mwsaz
- Jelle Kapitein
- Benoît Bourgeois
@@ -861,6 +864,7 @@ Symfony is the result of the work of many people who made the code better
- Christian Morgan
- Alexander Miehe (engerim)
- Morgan Auchede (mauchede)
+ - Sascha Dens (saschadens)
- Don Pinkster
- Maksim Muruev
- Emil Einarsson
@@ -989,6 +993,7 @@ Symfony is the result of the work of many people who made the code better
- Mathieu Santostefano
- Arjan Keeman
- Máximo Cuadros (mcuadros)
+ - Lukas Mencl
- tamirvs
- julien.galenski
- Christian Neff
@@ -1032,6 +1037,7 @@ Symfony is the result of the work of many people who made the code better
- Dominic Tubach
- Nikita Konstantinov
- Martijn Evers
+ - Vitaliy Ryaboy (vitaliy)
- Benjamin Paap (benjaminpaap)
- Christian
- Denis Golubovskiy (bukashk0zzz)
@@ -1059,6 +1065,7 @@ Symfony is the result of the work of many people who made the code better
- Jakub Sacha
- Olaf Klischat
- orlovv
+ - Claude Dioudonnat
- Jonathan Hedstrom
- Peter Smeets (darkspartan)
- Jhonny Lidfors (jhonny)
@@ -1242,6 +1249,7 @@ Symfony is the result of the work of many people who made the code better
- Sandro Hopf
- Łukasz Makuch
- George Giannoulopoulos
+ - Alexander Pasichnick
- Luis Ramirez (luisdeimos)
- Daniel Richter (richtermeister)
- ChrisC
@@ -1290,6 +1298,7 @@ Symfony is the result of the work of many people who made the code better
- Adrien Samson (adriensamson)
- Samuel Gordalina (gordalina)
- Max Romanovsky (maxromanovsky)
+ - Nicolas Eeckeloo (neeckeloo)
- Mathieu Morlon
- Daniel Tschinder
- Arnaud CHASSEUX
@@ -1305,7 +1314,6 @@ Symfony is the result of the work of many people who made the code better
- Jon Gotlin (jongotlin)
- Michael Dowling (mtdowling)
- Karlos Presumido (oneko)
- - Sylvain Fabre (sylfabre)
- Thomas Counsell
- BilgeXA
- r1pp3rj4ck
@@ -1392,6 +1400,7 @@ Symfony is the result of the work of many people who made the code better
- Alan Poulain
- Martin Eckhardt
- natechicago
+ - Sergei Gorjunov
- Jonathan Poston
- Adrian Olek (adrianolek)
- Jody Mickey (jwmickey)
@@ -1448,6 +1457,7 @@ Symfony is the result of the work of many people who made the code better
- Phobetor
- Andreas
- Markus
+ - Daniel Gorgan
- Thomas Chmielowiec
- shdev
- Andrey Ryaguzov
@@ -1481,6 +1491,7 @@ Symfony is the result of the work of many people who made the code better
- me_shaon
- 蝦米
- Grayson Koonce (breerly)
+ - Andrey Helldar (helldar)
- Karim Cassam Chenaï (ka)
- Maksym Slesarenko (maksym_slesarenko)
- Michal Kurzeja (mkurzeja)
@@ -1499,6 +1510,7 @@ Symfony is the result of the work of many people who made the code better
- David Barratt
- Pavel.Batanov
- avi123
+ - Pavel Prischepa
- alsar
- downace
- Aarón Nieves Fernández
@@ -1606,6 +1618,7 @@ Symfony is the result of the work of many people who made the code better
- Joel Marcey
- David Christmann
- root
+ - pf
- Vincent Chalnot
- James Hudson
- Tom Maguire
@@ -1613,6 +1626,7 @@ Symfony is the result of the work of many people who made the code better
- David Zuelke
- Adrian
- Oleg Andreyev
+ - neFAST
- Pierre Rineau
- Maxim Lovchikov
- adenkejawen
@@ -1710,7 +1724,6 @@ Symfony is the result of the work of many people who made the code better
- Giovanni Albero (johntree)
- Jorge Martin (jorgemartind)
- Joeri Verdeyen (jverdeyen)
- - Dmitrii Poddubnyi (karser)
- Kevin Verschaeve (keversc)
- Kevin Herrera (kherge)
- Luis Ramón López López (lrlopez)
@@ -1748,6 +1761,7 @@ Symfony is the result of the work of many people who made the code better
- Damian Sromek
- Ben
- Evgeniy Tetenchuk
+ - Shrey Puranik
- dasmfm
- Mathias Geat
- Arnaud Buathier (arnapou)
@@ -1768,6 +1782,7 @@ Symfony is the result of the work of many people who made the code better
- Ulf Reimers (ureimers)
- Wotre
- goohib
+ - Tom Counsell
- Xavier HAUSHERR
- Ron Gähler
- Edwin Hageman
@@ -1805,6 +1820,7 @@ Symfony is the result of the work of many people who made the code better
- Jörg Rühl
- wesleyh
- sergey
+ - Michael Hudson-Doyle
- Daniel Bannert
- Karim Miladi
- Michael Genereux
@@ -1834,11 +1850,13 @@ Symfony is the result of the work of many people who made the code better
- Kasperki
- Tammy D
- Daniel STANCU
+ - Ryan Rud
- Ondrej Slinták
- vlechemin
- Brian Corrigan
- Ladislav Tánczos
- Skorney
+ - Lucas Matte
- fmarchalemisys
- mieszko4
- Steve Preston
@@ -1872,6 +1890,7 @@ Symfony is the result of the work of many people who made the code better
- zorn
- Yuriy Potemkin
- Emilie Lorenzo
+ - Edvin Hultberg
- Benjamin Long
- Matt Janssen
- Ben Miller
@@ -1916,6 +1935,7 @@ Symfony is the result of the work of many people who made the code better
- sualko
- Bilge
- ADmad
+ - Stéphane Delprat
- Nicolas Roudaire
- Alfonso (afgar)
- Andreas Forsblom (aforsblo)
@@ -1992,11 +2012,11 @@ Symfony is the result of the work of many people who made the code better
- Alex Carol (picard89)
- Daniel Perez Pinazo (pitiflautico)
- Phil Taylor (prazgod)
+ - Maxim Pustynnikov (pustynnikov)
- Brayden Williams (redstar504)
- Rich Sage (richsage)
- Rokas Mikalkėnas (rokasm)
- Bart Ruysseveldt (ruyss)
- - Sascha Dens (saschadens)
- scourgen hung (scourgen)
- Sebastian Busch (sebu)
- Sepehr Lajevardi (sepehr)
diff --git a/phpunit b/phpunit
index f4b80ed064121..9975195309a81 100755
--- a/phpunit
+++ b/phpunit
@@ -1,7 +1,7 @@
#!/usr/bin/env php
{% endblock %}
@@ -455,9 +461,10 @@
{% macro form_tree_details(name, data, forms_by_hash, show) %}
{% import _self as tree %}
-
- {{ name|default('(no name)') }} {% if data.type_class is defined %}({{ profiler_dump(data.type_class) }}){% endif %}
-
+
{{ name|default('(no name)') }}
+ {% if data.type_class is defined %}
+
+ {% endif %}
{% if data.errors is defined and data.errors|length > 0 %}
diff --git a/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php
index 59d28a33c1bea..1f2f4d40acfa0 100644
--- a/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php
+++ b/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php
@@ -192,4 +192,9 @@ public function provideDsnWithOptions()
array(\Memcached::OPT_SOCKET_RECV_SIZE => 1, \Memcached::OPT_SOCKET_SEND_SIZE => 2, \Memcached::OPT_RETRY_TIMEOUT => 8),
);
}
+
+ public function testClear()
+ {
+ $this->assertTrue($this->createCachePool()->clear());
+ }
}
diff --git a/src/Symfony/Component/Cache/Traits/MemcachedTrait.php b/src/Symfony/Component/Cache/Traits/MemcachedTrait.php
index cf04f1cf85664..8160f14116162 100644
--- a/src/Symfony/Component/Cache/Traits/MemcachedTrait.php
+++ b/src/Symfony/Component/Cache/Traits/MemcachedTrait.php
@@ -260,7 +260,7 @@ protected function doDelete(array $ids)
*/
protected function doClear($namespace)
{
- return false;
+ return '' === $namespace && $this->getClient()->flush();
}
private function checkResultCode($result)
diff --git a/src/Symfony/Component/Debug/DebugClassLoader.php b/src/Symfony/Component/Debug/DebugClassLoader.php
index c9fa76cfe7058..138b4d38d8fe1 100644
--- a/src/Symfony/Component/Debug/DebugClassLoader.php
+++ b/src/Symfony/Component/Debug/DebugClassLoader.php
@@ -138,14 +138,14 @@ public function loadClass($class)
try {
if ($this->isFinder && !isset($this->loaded[$class])) {
$this->loaded[$class] = true;
- if ($file = $this->classLoader[0]->findFile($class) ?: false) {
- $wasCached = \function_exists('opcache_is_script_cached') && @opcache_is_script_cached($file);
-
+ if (!$file = $this->classLoader[0]->findFile($class) ?: false) {
+ // no-op
+ } elseif (\function_exists('opcache_is_script_cached') && @opcache_is_script_cached($file)) {
require $file;
- if ($wasCached) {
- return;
- }
+ return;
+ } else {
+ require $file;
}
} else {
\call_user_func($this->classLoader, $class);
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php
index 05eb72d9746f4..a229022ed8a0b 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php
@@ -127,13 +127,19 @@ private function isInlineableDefinition($id, Definition $definition, ServiceRefe
}
$ids = array();
+ $isReferencedByConstructor = false;
foreach ($graph->getNode($id)->getInEdges() as $edge) {
- if ($edge->isWeak()) {
+ $isReferencedByConstructor = $isReferencedByConstructor || $edge->isReferencedByConstructor();
+ if ($edge->isWeak() || $edge->isLazy()) {
return false;
}
$ids[] = $edge->getSourceNode()->getId();
}
+ if (!$ids) {
+ return true;
+ }
+
if (\count(array_unique($ids)) > 1) {
return false;
}
@@ -142,6 +148,10 @@ private function isInlineableDefinition($id, Definition $definition, ServiceRefe
return false;
}
- return !$ids || $this->container->getDefinition($ids[0])->isShared();
+ if ($isReferencedByConstructor && $this->container->getDefinition($ids[0])->isLazy() && ($definition->getProperties() || $definition->getMethodCalls() || $definition->getConfigurator())) {
+ return false;
+ }
+
+ return $this->container->getDefinition($ids[0])->isShared();
}
}
diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
index fd7eec05759b7..3a3c24d0445bd 100644
--- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
+++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
@@ -155,17 +155,18 @@ public function dump(array $options = array())
}
(new AnalyzeServiceReferencesPass(false, !$this->getProxyDumper() instanceof NullDumper))->process($this->container);
+ $checkedNodes = array();
$this->circularReferences = array();
- foreach (array(true, false) as $byConstructor) {
- foreach ($this->container->getCompiler()->getServiceReferenceGraph()->getNodes() as $id => $node) {
- if (!$node->getValue() instanceof Definition) {
- continue;
- }
- $currentPath = array($id => true);
- $this->analyzeCircularReferences($node->getOutEdges(), $currentPath, $id, $byConstructor);
+ foreach ($this->container->getCompiler()->getServiceReferenceGraph()->getNodes() as $id => $node) {
+ if (!$node->getValue() instanceof Definition) {
+ continue;
+ }
+ if (!isset($checkedNodes[$id])) {
+ $this->analyzeCircularReferences($id, $node->getOutEdges(), $checkedNodes);
}
}
$this->container->getCompiler()->getServiceReferenceGraph()->clear();
+ $checkedNodes = array();
$this->docStar = $options['debug'] ? '*' : '';
@@ -301,12 +302,12 @@ private function getProxyDumper()
return $this->proxyDumper;
}
- private function analyzeCircularReferences(array $edges, &$currentPath, $sourceId, $byConstructor)
+ private function analyzeCircularReferences($sourceId, array $edges, &$checkedNodes, &$currentPath = array())
{
+ $checkedNodes[$sourceId] = true;
+ $currentPath[$sourceId] = $sourceId;
+
foreach ($edges as $edge) {
- if ($byConstructor && !$edge->isReferencedByConstructor()) {
- continue;
- }
$node = $edge->getDestNode();
$id = $node->getId();
@@ -315,20 +316,42 @@ private function analyzeCircularReferences(array $edges, &$currentPath, $sourceI
} elseif (isset($currentPath[$id])) {
$currentId = $id;
foreach (array_reverse($currentPath) as $parentId) {
- if (!isset($this->circularReferences[$parentId][$currentId])) {
- $this->circularReferences[$parentId][$currentId] = $byConstructor;
+ $this->circularReferences[$parentId][$currentId] = $currentId;
+ if ($parentId === $id) {
+ break;
}
+ $currentId = $parentId;
+ }
+ } elseif (!isset($checkedNodes[$id])) {
+ $this->analyzeCircularReferences($id, $node->getOutEdges(), $checkedNodes, $currentPath);
+ } elseif (isset($this->circularReferences[$id])) {
+ $this->connectCircularReferences($id, $currentPath);
+ }
+ }
+ unset($currentPath[$sourceId]);
+ }
+
+ private function connectCircularReferences($sourceId, &$currentPath, &$subPath = array())
+ {
+ $subPath[$sourceId] = $sourceId;
+ $currentPath[$sourceId] = $sourceId;
+
+ foreach ($this->circularReferences[$sourceId] as $id) {
+ if (isset($currentPath[$id])) {
+ $currentId = $id;
+ foreach (array_reverse($currentPath) as $parentId) {
+ $this->circularReferences[$parentId][$currentId] = $currentId;
if ($parentId === $id) {
break;
}
$currentId = $parentId;
}
- } else {
- $currentPath[$id] = $id;
- $this->analyzeCircularReferences($node->getOutEdges(), $currentPath, $id, $byConstructor);
- unset($currentPath[$id]);
+ } elseif (!isset($subPath[$id]) && isset($this->circularReferences[$id])) {
+ $this->connectCircularReferences($id, $currentPath, $subPath);
}
}
+ unset($currentPath[$sourceId]);
+ unset($subPath[$sourceId]);
}
private function collectLineage($class, array &$lineage)
@@ -569,7 +592,8 @@ private function addServiceConfigurator(Definition $definition, $variableName =
if (\is_array($callable)) {
if ($callable[0] instanceof Reference
- || ($callable[0] instanceof Definition && $this->definitionVariables->contains($callable[0]))) {
+ || ($callable[0] instanceof Definition && $this->definitionVariables->contains($callable[0]))
+ ) {
return sprintf(" %s->%s(\$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName);
}
@@ -724,7 +748,7 @@ private function addInlineReference($id, Definition $definition, $targetId, $for
$hasSelfRef = isset($this->circularReferences[$id][$targetId]);
$forConstructor = $forConstructor && !isset($this->definitionVariables[$definition]);
- $code = $hasSelfRef && $this->circularReferences[$id][$targetId] && !$forConstructor ? $this->addInlineService($id, $definition, $definition) : '';
+ $code = $hasSelfRef && !$forConstructor ? $this->addInlineService($id, $definition, $definition) : '';
if (isset($this->referenceVariables[$targetId]) || (2 > $callCount && (!$hasSelfRef || !$forConstructor))) {
return $code;
@@ -1798,6 +1822,7 @@ private function getServiceCall($id, Reference $reference = null)
if ($definition->isShared()) {
$code = sprintf('$this->services[\'%s\'] = %s', $id, $code);
}
+ $code = "($code)";
} elseif ($this->asFiles && $definition->isShared() && !$this->isHotPath($definition)) {
$code = sprintf("\$this->load('%s.php')", $this->generateMethodName($id));
} else {
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php
index 51c5fd21f6e87..60fa55c3b5451 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php
@@ -752,7 +752,7 @@ public function testExpressionReferencingPrivateService()
->setPublic(false);
$container->register('public_foo', 'stdClass')
->setPublic(true)
- ->addArgument(new Expression('service("private_foo")'));
+ ->addArgument(new Expression('service("private_foo").bar'));
$container->compile();
$dumper = new PhpDumper($container);
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php
index ad316b23c6ee9..fc04f5faeece0 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php
@@ -126,7 +126,7 @@ protected function getConfiguredServiceSimpleService()
{
$this->services['configured_service_simple'] = $instance = new \stdClass();
- ${($_ = isset($this->services['configurator_service_simple']) ? $this->services['configurator_service_simple'] : $this->services['configurator_service_simple'] = new \ConfClass('bar')) && false ?: '_'}->configureStdClass($instance);
+ ${($_ = isset($this->services['configurator_service_simple']) ? $this->services['configurator_service_simple'] : ($this->services['configurator_service_simple'] = new \ConfClass('bar'))) && false ?: '_'}->configureStdClass($instance);
return $instance;
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt
index 4b6e2f59e65a1..fa032cbc1f94d 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt
@@ -243,7 +243,7 @@ use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
return $this->services['tagged_iterator'] = new \Bar(new RewindableGenerator(function () {
yield 0 => ${($_ = isset($this->services['foo']) ? $this->services['foo'] : $this->load('getFooService.php')) && false ?: '_'};
- yield 1 => ${($_ = isset($this->services['tagged_iterator_foo']) ? $this->services['tagged_iterator_foo'] : $this->services['tagged_iterator_foo'] = new \Bar()) && false ?: '_'};
+ yield 1 => ${($_ = isset($this->services['tagged_iterator_foo']) ? $this->services['tagged_iterator_foo'] : ($this->services['tagged_iterator_foo'] = new \Bar())) && false ?: '_'};
}, 2));
[Container%s/getTaggedIteratorFooService.php] => services['tagged_iterator'] = new \Bar(new RewindableGenerator(function () {
yield 0 => ${($_ = isset($this->services['foo']) ? $this->services['foo'] : $this->getFooService()) && false ?: '_'};
- yield 1 => ${($_ = isset($this->services['tagged_iterator_foo']) ? $this->services['tagged_iterator_foo'] : $this->services['tagged_iterator_foo'] = new \Bar()) && false ?: '_'};
+ yield 1 => ${($_ = isset($this->services['tagged_iterator_foo']) ? $this->services['tagged_iterator_foo'] : ($this->services['tagged_iterator_foo'] = new \Bar())) && false ?: '_'};
}, 2));
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php
index 9c54acc6bea3b..8aa4a2c406b6f 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php
@@ -331,7 +331,7 @@ protected function getManager2Service()
*/
protected function getRootService()
{
- return $this->services['root'] = new \stdClass(${($_ = isset($this->services['level2']) ? $this->services['level2'] : $this->getLevel2Service()) && false ?: '_'}, ${($_ = isset($this->services['multiuse1']) ? $this->services['multiuse1'] : $this->services['multiuse1'] = new \stdClass()) && false ?: '_'});
+ return $this->services['root'] = new \stdClass(${($_ = isset($this->services['level2']) ? $this->services['level2'] : $this->getLevel2Service()) && false ?: '_'}, ${($_ = isset($this->services['multiuse1']) ? $this->services['multiuse1'] : ($this->services['multiuse1'] = new \stdClass())) && false ?: '_'});
}
/**
@@ -397,7 +397,7 @@ protected function getLevel3Service()
*/
protected function getLevel4Service()
{
- return $this->services['level4'] = new \stdClass(${($_ = isset($this->services['multiuse1']) ? $this->services['multiuse1'] : $this->services['multiuse1'] = new \stdClass()) && false ?: '_'}, ${($_ = isset($this->services['level5']) ? $this->services['level5'] : $this->getLevel5Service()) && false ?: '_'});
+ return $this->services['level4'] = new \stdClass(${($_ = isset($this->services['multiuse1']) ? $this->services['multiuse1'] : ($this->services['multiuse1'] = new \stdClass())) && false ?: '_'}, ${($_ = isset($this->services['level5']) ? $this->services['level5'] : $this->getLevel5Service()) && false ?: '_'});
}
/**
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php
index bd08c4d15f67b..a5de37f12c931 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php
@@ -126,7 +126,7 @@ protected function getBar3Service()
{
$this->services['bar3'] = $instance = new \BarCircular();
- $a = ${($_ = isset($this->services['foobar3']) ? $this->services['foobar3'] : $this->services['foobar3'] = new \FoobarCircular()) && false ?: '_'};
+ $a = ${($_ = isset($this->services['foobar3']) ? $this->services['foobar3'] : ($this->services['foobar3'] = new \FoobarCircular())) && false ?: '_'};
$instance->addFoobar($a, $a);
@@ -431,7 +431,7 @@ protected function getManager2Service()
*/
protected function getRootService()
{
- return $this->services['root'] = new \stdClass(${($_ = isset($this->services['level2']) ? $this->services['level2'] : $this->getLevel2Service()) && false ?: '_'}, ${($_ = isset($this->services['multiuse1']) ? $this->services['multiuse1'] : $this->services['multiuse1'] = new \stdClass()) && false ?: '_'});
+ return $this->services['root'] = new \stdClass(${($_ = isset($this->services['level2']) ? $this->services['level2'] : $this->getLevel2Service()) && false ?: '_'}, ${($_ = isset($this->services['multiuse1']) ? $this->services['multiuse1'] : ($this->services['multiuse1'] = new \stdClass())) && false ?: '_'});
}
/**
@@ -497,7 +497,7 @@ protected function getLevel3Service()
*/
protected function getLevel4Service()
{
- return $this->services['level4'] = new \stdClass(${($_ = isset($this->services['multiuse1']) ? $this->services['multiuse1'] : $this->services['multiuse1'] = new \stdClass()) && false ?: '_'}, ${($_ = isset($this->services['level5']) ? $this->services['level5'] : $this->getLevel5Service()) && false ?: '_'});
+ return $this->services['level4'] = new \stdClass(${($_ = isset($this->services['multiuse1']) ? $this->services['multiuse1'] : ($this->services['multiuse1'] = new \stdClass())) && false ?: '_'}, ${($_ = isset($this->services['level5']) ? $this->services['level5'] : $this->getLevel5Service()) && false ?: '_'});
}
/**
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_env_in_id.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_env_in_id.php
index 91114fd9788e6..dab23e1dbcc12 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_env_in_id.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_env_in_id.php
@@ -73,7 +73,7 @@ public function isFrozen()
*/
protected function getBarService()
{
- return $this->services['bar'] = new \stdClass(${($_ = isset($this->services['bar_%env(BAR)%']) ? $this->services['bar_%env(BAR)%'] : $this->services['bar_%env(BAR)%'] = new \stdClass()) && false ?: '_'});
+ return $this->services['bar'] = new \stdClass(${($_ = isset($this->services['bar_%env(BAR)%']) ? $this->services['bar_%env(BAR)%'] : ($this->services['bar_%env(BAR)%'] = new \stdClass())) && false ?: '_'});
}
/**
@@ -83,7 +83,7 @@ protected function getBarService()
*/
protected function getFooService()
{
- return $this->services['foo'] = new \stdClass(${($_ = isset($this->services['bar_%env(BAR)%']) ? $this->services['bar_%env(BAR)%'] : $this->services['bar_%env(BAR)%'] = new \stdClass()) && false ?: '_'}, array('baz_'.$this->getEnv('string:BAR') => new \stdClass()));
+ return $this->services['foo'] = new \stdClass(${($_ = isset($this->services['bar_%env(BAR)%']) ? $this->services['bar_%env(BAR)%'] : ($this->services['bar_%env(BAR)%'] = new \stdClass())) && false ?: '_'}, array('baz_'.$this->getEnv('string:BAR') => new \stdClass()));
}
/**
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_inline_requires.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_inline_requires.php
index 08a474eea33d8..c6927eb95d653 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_inline_requires.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_inline_requires.php
@@ -110,7 +110,7 @@ protected function getC2Service()
include_once $this->targetDirs[1].'/includes/HotPath/C2.php';
include_once $this->targetDirs[1].'/includes/HotPath/C3.php';
- return $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C2'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C2(${($_ = isset($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3']) ? $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3'] : $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3()) && false ?: '_'});
+ return $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C2'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C2(${($_ = isset($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3']) ? $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3'] : ($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3())) && false ?: '_'});
}
/**
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_legacy_privates.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_legacy_privates.php
index 7aa1bff87069a..d720ae396f890 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_legacy_privates.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_legacy_privates.php
@@ -98,7 +98,7 @@ public function isFrozen()
*/
protected function getBarService()
{
- return $this->services['bar'] = new \stdClass(${($_ = isset($this->services['private_not_inlined']) ? $this->services['private_not_inlined'] : $this->services['private_not_inlined'] = new \stdClass()) && false ?: '_'});
+ return $this->services['bar'] = new \stdClass(${($_ = isset($this->services['private_not_inlined']) ? $this->services['private_not_inlined'] : ($this->services['private_not_inlined'] = new \stdClass())) && false ?: '_'});
}
/**
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator.php
index 496f6aa77d306..c436aea407c83 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator.php
@@ -76,7 +76,7 @@ public function isFrozen()
*/
protected function getBarServiceService()
{
- return $this->services['bar_service'] = new \stdClass(${($_ = isset($this->services['baz_service']) ? $this->services['baz_service'] : $this->services['baz_service'] = new \stdClass()) && false ?: '_'});
+ return $this->services['bar_service'] = new \stdClass(${($_ = isset($this->services['baz_service']) ? $this->services['baz_service'] : ($this->services['baz_service'] = new \stdClass())) && false ?: '_'});
}
/**
@@ -89,7 +89,7 @@ protected function getFooServiceService()
return $this->services['foo_service'] = new \Symfony\Component\DependencyInjection\ServiceLocator(array('bar' => function () {
return ${($_ = isset($this->services['bar_service']) ? $this->services['bar_service'] : $this->getBarServiceService()) && false ?: '_'};
}, 'baz' => function () {
- $f = function (\stdClass $v) { return $v; }; return $f(${($_ = isset($this->services['baz_service']) ? $this->services['baz_service'] : $this->services['baz_service'] = new \stdClass()) && false ?: '_'});
+ $f = function (\stdClass $v) { return $v; }; return $f(${($_ = isset($this->services['baz_service']) ? $this->services['baz_service'] : ($this->services['baz_service'] = new \stdClass())) && false ?: '_'});
}, 'nil' => function () {
return NULL;
}));
@@ -133,7 +133,7 @@ protected function getTranslator_Loader3Service()
protected function getTranslator1Service()
{
return $this->services['translator_1'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\StubbedTranslator(new \Symfony\Component\DependencyInjection\ServiceLocator(array('translator.loader_1' => function () {
- return ${($_ = isset($this->services['translator.loader_1']) ? $this->services['translator.loader_1'] : $this->services['translator.loader_1'] = new \stdClass()) && false ?: '_'};
+ return ${($_ = isset($this->services['translator.loader_1']) ? $this->services['translator.loader_1'] : ($this->services['translator.loader_1'] = new \stdClass())) && false ?: '_'};
})));
}
@@ -145,10 +145,10 @@ protected function getTranslator1Service()
protected function getTranslator2Service()
{
$this->services['translator_2'] = $instance = new \Symfony\Component\DependencyInjection\Tests\Fixtures\StubbedTranslator(new \Symfony\Component\DependencyInjection\ServiceLocator(array('translator.loader_2' => function () {
- return ${($_ = isset($this->services['translator.loader_2']) ? $this->services['translator.loader_2'] : $this->services['translator.loader_2'] = new \stdClass()) && false ?: '_'};
+ return ${($_ = isset($this->services['translator.loader_2']) ? $this->services['translator.loader_2'] : ($this->services['translator.loader_2'] = new \stdClass())) && false ?: '_'};
})));
- $instance->addResource('db', ${($_ = isset($this->services['translator.loader_2']) ? $this->services['translator.loader_2'] : $this->services['translator.loader_2'] = new \stdClass()) && false ?: '_'}, 'nl');
+ $instance->addResource('db', ${($_ = isset($this->services['translator.loader_2']) ? $this->services['translator.loader_2'] : ($this->services['translator.loader_2'] = new \stdClass())) && false ?: '_'}, 'nl');
return $instance;
}
@@ -161,10 +161,10 @@ protected function getTranslator2Service()
protected function getTranslator3Service()
{
$this->services['translator_3'] = $instance = new \Symfony\Component\DependencyInjection\Tests\Fixtures\StubbedTranslator(new \Symfony\Component\DependencyInjection\ServiceLocator(array('translator.loader_3' => function () {
- return ${($_ = isset($this->services['translator.loader_3']) ? $this->services['translator.loader_3'] : $this->services['translator.loader_3'] = new \stdClass()) && false ?: '_'};
+ return ${($_ = isset($this->services['translator.loader_3']) ? $this->services['translator.loader_3'] : ($this->services['translator.loader_3'] = new \stdClass())) && false ?: '_'};
})));
- $a = ${($_ = isset($this->services['translator.loader_3']) ? $this->services['translator.loader_3'] : $this->services['translator.loader_3'] = new \stdClass()) && false ?: '_'};
+ $a = ${($_ = isset($this->services['translator.loader_3']) ? $this->services['translator.loader_3'] : ($this->services['translator.loader_3'] = new \stdClass())) && false ?: '_'};
$instance->addResource('db', $a, 'nl');
$instance->addResource('db', $a, 'en');
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_frozen.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_frozen.php
index 1275e9f2642a3..8ef589c127c54 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_frozen.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_frozen.php
@@ -67,7 +67,7 @@ public function isFrozen()
*/
protected function getBarServiceService()
{
- return $this->services['bar_service'] = new \stdClass(${($_ = isset($this->services['baz_service']) ? $this->services['baz_service'] : $this->services['baz_service'] = new \stdClass()) && false ?: '_'});
+ return $this->services['bar_service'] = new \stdClass(${($_ = isset($this->services['baz_service']) ? $this->services['baz_service'] : ($this->services['baz_service'] = new \stdClass())) && false ?: '_'});
}
/**
@@ -77,7 +77,7 @@ protected function getBarServiceService()
*/
protected function getFooServiceService()
{
- return $this->services['foo_service'] = new \stdClass(${($_ = isset($this->services['baz_service']) ? $this->services['baz_service'] : $this->services['baz_service'] = new \stdClass()) && false ?: '_'});
+ return $this->services['foo_service'] = new \stdClass(${($_ = isset($this->services['baz_service']) ? $this->services['baz_service'] : ($this->services['baz_service'] = new \stdClass())) && false ?: '_'});
}
/**
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_in_expression.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_in_expression.php
index fe84f49753c52..75cbc2730b056 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_in_expression.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_in_expression.php
@@ -67,7 +67,7 @@ public function isFrozen()
*/
protected function getPublicFooService()
{
- return $this->services['public_foo'] = new \stdClass(${($_ = isset($this->services['private_foo']) ? $this->services['private_foo'] : $this->services['private_foo'] = new \stdClass()) && false ?: '_'});
+ return $this->services['public_foo'] = new \stdClass(${($_ = isset($this->services['private_foo']) ? $this->services['private_foo'] : ($this->services['private_foo'] = new \stdClass())) && false ?: '_'}->bar);
}
/**
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_rot13_env.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_rot13_env.php
index 90836aa90debd..efbb0023164b1 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_rot13_env.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_rot13_env.php
@@ -78,7 +78,7 @@ protected function getRot13EnvVarProcessorService()
protected function getContainer_EnvVarProcessorsLocatorService()
{
return $this->services['container.env_var_processors_locator'] = new \Symfony\Component\DependencyInjection\ServiceLocator(array('rot13' => function () {
- return ${($_ = isset($this->services['Symfony\Component\DependencyInjection\Tests\Dumper\Rot13EnvVarProcessor']) ? $this->services['Symfony\Component\DependencyInjection\Tests\Dumper\Rot13EnvVarProcessor'] : $this->services['Symfony\Component\DependencyInjection\Tests\Dumper\Rot13EnvVarProcessor'] = new \Symfony\Component\DependencyInjection\Tests\Dumper\Rot13EnvVarProcessor()) && false ?: '_'};
+ return ${($_ = isset($this->services['Symfony\Component\DependencyInjection\Tests\Dumper\Rot13EnvVarProcessor']) ? $this->services['Symfony\Component\DependencyInjection\Tests\Dumper\Rot13EnvVarProcessor'] : ($this->services['Symfony\Component\DependencyInjection\Tests\Dumper\Rot13EnvVarProcessor'] = new \Symfony\Component\DependencyInjection\Tests\Dumper\Rot13EnvVarProcessor())) && false ?: '_'};
}));
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php
index 9475c923068f2..8f198f0ba8355 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php
@@ -84,13 +84,13 @@ protected function getTestServiceSubscriberService()
protected function getFooServiceService()
{
return $this->services['foo_service'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber((new \Symfony\Component\DependencyInjection\ServiceLocator(array('Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition' => function () {
- $f = function (\Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition $v = null) { return $v; }; return $f(${($_ = isset($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition']) ? $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] : $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition()) && false ?: '_'});
+ $f = function (\Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition $v = null) { return $v; }; return $f(${($_ = isset($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition']) ? $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] : ($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition())) && false ?: '_'});
}, 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\TestServiceSubscriber' => function () {
- $f = function (\Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber $v) { return $v; }; return $f(${($_ = isset($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber']) ? $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] : $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber()) && false ?: '_'});
+ $f = function (\Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber $v) { return $v; }; return $f(${($_ = isset($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber']) ? $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] : ($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber())) && false ?: '_'});
}, 'bar' => function () {
- $f = function (\Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition $v) { return $v; }; return $f(${($_ = isset($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber']) ? $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] : $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber()) && false ?: '_'});
+ $f = function (\Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition $v) { return $v; }; return $f(${($_ = isset($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber']) ? $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] : ($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber())) && false ?: '_'});
}, 'baz' => function () {
- $f = function (\Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition $v = null) { return $v; }; return $f(${($_ = isset($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition']) ? $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] : $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition()) && false ?: '_'});
+ $f = function (\Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition $v = null) { return $v; }; return $f(${($_ = isset($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition']) ? $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] : ($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition())) && false ?: '_'});
})))->withContext('foo_service', $this));
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_uninitialized_ref.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_uninitialized_ref.php
index 4d0c00b289138..c53e336d278bd 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_uninitialized_ref.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_uninitialized_ref.php
@@ -107,7 +107,7 @@ protected function getBazService()
{
$this->services['baz'] = $instance = new \stdClass();
- $instance->foo3 = ${($_ = isset($this->services['foo3']) ? $this->services['foo3'] : $this->services['foo3'] = new \stdClass()) && false ?: '_'};
+ $instance->foo3 = ${($_ = isset($this->services['foo3']) ? $this->services['foo3'] : ($this->services['foo3'] = new \stdClass())) && false ?: '_'};
return $instance;
}
diff --git a/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php b/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php
index 7577b85a12136..967bb9fba10ee 100644
--- a/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php
+++ b/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php
@@ -132,19 +132,24 @@ public function dispatch($eventName, Event $event = null)
}
$this->preProcess($eventName);
- $this->preDispatch($eventName, $event);
-
- $e = $this->stopwatch->start($eventName, 'section');
-
- $this->dispatcher->dispatch($eventName, $event);
-
- if ($e->isStarted()) {
- $e->stop();
+ try {
+ $this->preDispatch($eventName, $event);
+ try {
+ $e = $this->stopwatch->start($eventName, 'section');
+ try {
+ $this->dispatcher->dispatch($eventName, $event);
+ } finally {
+ if ($e->isStarted()) {
+ $e->stop();
+ }
+ }
+ } finally {
+ $this->postDispatch($eventName, $event);
+ }
+ } finally {
+ $this->postProcess($eventName);
}
- $this->postDispatch($eventName, $event);
- $this->postProcess($eventName);
-
return $event;
}
diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php
index 9e03606a3ec70..3e125bc82be36 100644
--- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php
+++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php
@@ -146,7 +146,7 @@ public function reverseTransform($value)
return;
}
- if ('NaN' === $value) {
+ if (\in_array($value, array('NaN', 'NAN', 'nan'), true)) {
throw new TransformationFailedException('"NaN" is not a valid number');
}
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php
index 1b699d51e84ab..56fcfe17e9bf7 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php
@@ -105,6 +105,7 @@ public function configureOptions(OptionsResolver $resolver)
'data_class' => $dataClass,
'empty_data' => $emptyData,
'multiple' => false,
+ 'allow_file_upload' => true,
));
}
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php
index 363fd46590271..89b9dd8ff5335 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php
@@ -178,6 +178,7 @@ public function configureOptions(OptionsResolver $resolver)
'attr' => array(),
'post_max_size_message' => 'The uploaded file was too large. Please try to upload a smaller file.',
'upload_max_size_message' => $uploadMaxSizeMessage, // internal
+ 'allow_file_upload' => false,
));
$resolver->setAllowedTypes('label_attr', 'array');
diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php
index 8b15447d1b27a..f3cfc7212ac17 100644
--- a/src/Symfony/Component/Form/Form.php
+++ b/src/Symfony/Component/Form/Form.php
@@ -532,6 +532,11 @@ public function submit($submittedData, $clearMissing = true)
$submittedData = null;
} elseif (is_scalar($submittedData)) {
$submittedData = (string) $submittedData;
+ } elseif ($this->config->getOption('allow_file_upload')) {
+ // no-op
+ } elseif ($this->config->getRequestHandler()->isFileUpload($submittedData)) {
+ $submittedData = null;
+ $this->transformationFailure = new TransformationFailedException('Submitted data was expected to be text or number, file upload given.');
}
$dispatcher = $this->config->getEventDispatcher();
@@ -541,6 +546,10 @@ public function submit($submittedData, $clearMissing = true)
$viewData = null;
try {
+ if (null !== $this->transformationFailure) {
+ throw $this->transformationFailure;
+ }
+
// Hook to change content of the data submitted by the browser
if ($dispatcher->hasListeners(FormEvents::PRE_SUBMIT)) {
$event = new FormEvent($this, $submittedData);
diff --git a/src/Symfony/Component/Form/Tests/CompoundFormTest.php b/src/Symfony/Component/Form/Tests/CompoundFormTest.php
index 336593f7f7639..4a6590ded5af3 100644
--- a/src/Symfony/Component/Form/Tests/CompoundFormTest.php
+++ b/src/Symfony/Component/Form/Tests/CompoundFormTest.php
@@ -707,7 +707,7 @@ public function testSubmitPostOrPutRequestWithSingleChildForm($method)
'REQUEST_METHOD' => $method,
));
- $form = $this->getBuilder('image')
+ $form = $this->getBuilder('image', null, null, array('allow_file_upload' => true))
->setMethod($method)
->setRequestHandler(new HttpFoundationRequestHandler())
->getForm();
@@ -1036,6 +1036,21 @@ public function testDisabledButtonIsNotSubmitted()
$this->assertFalse($submit->isSubmitted());
}
+ public function testFileUpload()
+ {
+ $reqHandler = new HttpFoundationRequestHandler();
+ $this->form->add($this->getBuilder('foo')->setRequestHandler($reqHandler)->getForm());
+ $this->form->add($this->getBuilder('bar')->setRequestHandler($reqHandler)->getForm());
+
+ $this->form->submit(array(
+ 'foo' => 'Foo',
+ 'bar' => new UploadedFile(__FILE__, 'upload.png', 'image/png', 123, UPLOAD_ERR_OK),
+ ));
+
+ $this->assertSame('Submitted data was expected to be text or number, file upload given.', $this->form->get('bar')->getTransformationFailure()->getMessage());
+ $this->assertNull($this->form->get('bar')->getData());
+ }
+
protected function createForm()
{
return $this->getBuilder()
diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/NumberToLocalizedStringTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/NumberToLocalizedStringTransformerTest.php
index d6a4662102232..176d3a9a58f9e 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/NumberToLocalizedStringTransformerTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/NumberToLocalizedStringTransformerTest.php
@@ -514,24 +514,24 @@ public function testReverseTransformExpectsValidNumber()
/**
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
+ * @dataProvider nanRepresentationProvider
*
* @see https://github.com/symfony/symfony/issues/3161
*/
- public function testReverseTransformDisallowsNaN()
+ public function testReverseTransformDisallowsNaN($nan)
{
$transformer = new NumberToLocalizedStringTransformer();
- $transformer->reverseTransform('NaN');
+ $transformer->reverseTransform($nan);
}
- /**
- * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
- */
- public function testReverseTransformDisallowsNaN2()
+ public function nanRepresentationProvider()
{
- $transformer = new NumberToLocalizedStringTransformer();
-
- $transformer->reverseTransform('nan');
+ return array(
+ array('nan'),
+ array('NaN'), // see https://github.com/symfony/symfony/issues/3161
+ array('NAN'),
+ );
}
/**
diff --git a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json
index 6aba3b60ecb12..e9c1a0f4963e8 100644
--- a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json
+++ b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json
@@ -29,6 +29,7 @@
"parent": {
"Symfony\\Component\\Form\\Extension\\Core\\Type\\FormType": [
"action",
+ "allow_file_upload",
"attr",
"auto_initialize",
"block_name",
diff --git a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt
index 874dede77f7f5..92759b1b0b467 100644
--- a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt
+++ b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt
@@ -8,16 +8,17 @@ Symfony\Component\Form\Extension\Core\Type\ChoiceType (Block prefix: "choice")
choice_attr FormType FormType FormTypeCsrfExtension
choice_label -------------------- ------------------------- -----------------------
choice_loader compound action csrf_field_name
- choice_name data_class attr csrf_message
- choice_translation_domain empty_data auto_initialize csrf_protection
- choice_value error_bubbling block_name csrf_token_id
- choices trim by_reference csrf_token_manager
- choices_as_values data
- expanded disabled
- group_by inherit_data
- multiple label
- placeholder label_attr
- preferred_choices label_format
+ choice_name data_class allow_file_upload csrf_message
+ choice_translation_domain empty_data attr csrf_protection
+ choice_value error_bubbling auto_initialize csrf_token_id
+ choices trim block_name csrf_token_manager
+ choices_as_values by_reference
+ expanded data
+ group_by disabled
+ multiple inherit_data
+ placeholder label
+ preferred_choices label_attr
+ label_format
mapped
method
post_max_size_message
diff --git a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_2.json b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_2.json
index bdefb5c946626..6c18e7169f394 100644
--- a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_2.json
+++ b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_2.json
@@ -4,6 +4,7 @@
"options": {
"own": [
"action",
+ "allow_file_upload",
"attr",
"auto_initialize",
"block_name",
diff --git a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_2.txt b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_2.txt
index 72b13dfef75aa..24dfb07e074d5 100644
--- a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_2.txt
+++ b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_2.txt
@@ -6,6 +6,7 @@ Symfony\Component\Form\Extension\Core\Type\FormType (Block prefix: "form")
Options
-------------------------
action
+ allow_file_upload
attr
auto_initialize
block_name
diff --git a/src/Symfony/Component/HttpKernel/HttpKernel.php b/src/Symfony/Component/HttpKernel/HttpKernel.php
index 36c673093bcc8..f34d9b32251b3 100644
--- a/src/Symfony/Component/HttpKernel/HttpKernel.php
+++ b/src/Symfony/Component/HttpKernel/HttpKernel.php
@@ -263,6 +263,9 @@ private function handleException(\Exception $e, $request, $type)
}
}
+ /**
+ * Returns a human-readable string for the specified variable.
+ */
private function varToString($var)
{
if (\is_object($var)) {
diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php
index bfc961f50511f..faea3086e5420 100644
--- a/src/Symfony/Component/HttpKernel/Kernel.php
+++ b/src/Symfony/Component/HttpKernel/Kernel.php
@@ -67,11 +67,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
private $requestStackSize = 0;
private $resetServices = false;
- const VERSION = '3.4.19';
- const VERSION_ID = 30419;
+ const VERSION = '3.4.20';
+ const VERSION_ID = 30420;
const MAJOR_VERSION = 3;
const MINOR_VERSION = 4;
- const RELEASE_VERSION = 19;
+ const RELEASE_VERSION = 20;
const EXTRA_VERSION = '';
const END_OF_MAINTENANCE = '11/2020';
diff --git a/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php b/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php
index b598d49d3e83f..0d7c3df0decfb 100644
--- a/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php
+++ b/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php
@@ -638,8 +638,8 @@ public function setSymbol($attr, $value)
/**
* Not supported. Set a text attribute.
*
- * @param int $attr An attribute specifier, one of the text attribute constants
- * @param int $value The attribute value
+ * @param int $attr An attribute specifier, one of the text attribute constants
+ * @param string $value The attribute value
*
* @return bool true on success or false on failure
*
diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php
index 90e4c234fb1dd..2e358f458600b 100644
--- a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php
+++ b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php
@@ -687,7 +687,8 @@ private function writeCollection($zval, $property, $collection, $addMethod, $rem
*/
private function getWriteAccessInfo($class, $property, $value)
{
- $key = str_replace('\\', '.', $class).'..'.$property;
+ $useAdderAndRemover = \is_array($value) || $value instanceof \Traversable;
+ $key = str_replace('\\', '.', $class).'..'.$property.'..'.(int) $useAdderAndRemover;
if (isset($this->writePropertyCache[$key])) {
return $this->writePropertyCache[$key];
@@ -707,6 +708,16 @@ private function getWriteAccessInfo($class, $property, $value)
$camelized = $this->camelize($property);
$singulars = (array) Inflector::singularize($camelized);
+ if ($useAdderAndRemover) {
+ $methods = $this->findAdderAndRemover($reflClass, $singulars);
+
+ if (null !== $methods) {
+ $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_ADDER_AND_REMOVER;
+ $access[self::ACCESS_ADDER] = $methods[0];
+ $access[self::ACCESS_REMOVER] = $methods[1];
+ }
+ }
+
if (!isset($access[self::ACCESS_TYPE])) {
$setter = 'set'.$camelized;
$getsetter = lcfirst($camelized); // jQuery style, e.g. read: last(), write: last($item)
@@ -728,22 +739,16 @@ private function getWriteAccessInfo($class, $property, $value)
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_MAGIC;
$access[self::ACCESS_NAME] = $setter;
} elseif (null !== $methods = $this->findAdderAndRemover($reflClass, $singulars)) {
- if (\is_array($value) || $value instanceof \Traversable) {
- $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_ADDER_AND_REMOVER;
- $access[self::ACCESS_ADDER] = $methods[0];
- $access[self::ACCESS_REMOVER] = $methods[1];
- } else {
- $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_NOT_FOUND;
- $access[self::ACCESS_NAME] = sprintf(
- 'The property "%s" in class "%s" can be defined with the methods "%s()" but '.
- 'the new value must be an array or an instance of \Traversable, '.
- '"%s" given.',
- $property,
- $reflClass->name,
- implode('()", "', $methods),
- \is_object($value) ? \get_class($value) : \gettype($value)
- );
- }
+ $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_NOT_FOUND;
+ $access[self::ACCESS_NAME] = sprintf(
+ 'The property "%s" in class "%s" can be defined with the methods "%s()" but '.
+ 'the new value must be an array or an instance of \Traversable, '.
+ '"%s" given.',
+ $property,
+ $reflClass->name,
+ implode('()", "', $methods),
+ \is_object($value) ? \get_class($value) : \gettype($value)
+ );
} else {
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_NOT_FOUND;
$access[self::ACCESS_NAME] = sprintf(
@@ -783,6 +788,18 @@ private function isPropertyWritable($object, $property)
$access = $this->getWriteAccessInfo(\get_class($object), $property, array());
+ $isWritable = self::ACCESS_TYPE_METHOD === $access[self::ACCESS_TYPE]
+ || self::ACCESS_TYPE_PROPERTY === $access[self::ACCESS_TYPE]
+ || self::ACCESS_TYPE_ADDER_AND_REMOVER === $access[self::ACCESS_TYPE]
+ || (!$access[self::ACCESS_HAS_PROPERTY] && property_exists($object, $property))
+ || self::ACCESS_TYPE_MAGIC === $access[self::ACCESS_TYPE];
+
+ if ($isWritable) {
+ return true;
+ }
+
+ $access = $this->getWriteAccessInfo(\get_class($object), $property, '');
+
return self::ACCESS_TYPE_METHOD === $access[self::ACCESS_TYPE]
|| self::ACCESS_TYPE_PROPERTY === $access[self::ACCESS_TYPE]
|| self::ACCESS_TYPE_ADDER_AND_REMOVER === $access[self::ACCESS_TYPE]
diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php
index cf6152380d1f2..894fbd5c0df0e 100644
--- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php
+++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php
@@ -719,7 +719,27 @@ public function testWriteToPluralPropertyWhileSingularOneExists()
$this->propertyAccessor->isWritable($object, 'emails'); //cache access info
$this->propertyAccessor->setValue($object, 'emails', array('test@email.com'));
- self::assertEquals(array('test@email.com'), $object->getEmails());
- self::assertNull($object->getEmail());
+ $this->assertEquals(array('test@email.com'), $object->getEmails());
+ $this->assertNull($object->getEmail());
+ }
+
+ public function testAdderAndRemoverArePreferredOverSetter()
+ {
+ $object = new TestPluralAdderRemoverAndSetter();
+
+ $this->propertyAccessor->isWritable($object, 'emails'); //cache access info
+ $this->propertyAccessor->setValue($object, 'emails', array('test@email.com'));
+
+ $this->assertEquals(array('test@email.com'), $object->getEmails());
+ }
+
+ public function testAdderAndRemoverArePreferredOverSetterForSameSingularAndPlural()
+ {
+ $object = new TestPluralAdderRemoverAndSetterSameSingularAndPlural();
+
+ $this->propertyAccessor->isWritable($object, 'aircraft'); //cache access info
+ $this->propertyAccessor->setValue($object, 'aircraft', array('aeroplane'));
+
+ $this->assertEquals(array('aeroplane'), $object->getAircraft());
}
}
diff --git a/src/Symfony/Component/PropertyAccess/Tests/TestPluralAdderRemoverAndSetter.php b/src/Symfony/Component/PropertyAccess/Tests/TestPluralAdderRemoverAndSetter.php
new file mode 100644
index 0000000000000..ecb3f9b4a9d32
--- /dev/null
+++ b/src/Symfony/Component/PropertyAccess/Tests/TestPluralAdderRemoverAndSetter.php
@@ -0,0 +1,37 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\PropertyAccess\Tests;
+
+class TestPluralAdderRemoverAndSetter
+{
+ private $emails = array();
+
+ public function getEmails()
+ {
+ return $this->emails;
+ }
+
+ public function setEmails(array $emails)
+ {
+ $this->emails = array('foo@email.com');
+ }
+
+ public function addEmail($email)
+ {
+ $this->emails[] = $email;
+ }
+
+ public function removeEmail($email)
+ {
+ $this->emails = array_diff($this->emails, array($email));
+ }
+}
diff --git a/src/Symfony/Component/PropertyAccess/Tests/TestPluralAdderRemoverAndSetterSameSingularAndPlural.php b/src/Symfony/Component/PropertyAccess/Tests/TestPluralAdderRemoverAndSetterSameSingularAndPlural.php
new file mode 100644
index 0000000000000..bb3b4f4688dc5
--- /dev/null
+++ b/src/Symfony/Component/PropertyAccess/Tests/TestPluralAdderRemoverAndSetterSameSingularAndPlural.php
@@ -0,0 +1,28 @@
+aircraft;
+ }
+
+ public function setAircraft(array $aircraft)
+ {
+ $this->aircraft = array('plane');
+ }
+
+ public function addAircraft($aircraft)
+ {
+ $this->aircraft[] = $aircraft;
+ }
+
+ public function removeAircraft($aircraft)
+ {
+ $this->aircraft = array_diff($this->aircraft, array($aircraft));
+ }
+}
diff --git a/src/Symfony/Component/Routing/Matcher/UrlMatcher.php b/src/Symfony/Component/Routing/Matcher/UrlMatcher.php
index 69d73906fe4ef..4b6494288ec6e 100644
--- a/src/Symfony/Component/Routing/Matcher/UrlMatcher.php
+++ b/src/Symfony/Component/Routing/Matcher/UrlMatcher.php
@@ -118,19 +118,24 @@ public function addExpressionLanguageProvider(ExpressionFunctionProviderInterfac
*/
protected function matchCollection($pathinfo, RouteCollection $routes)
{
+ // HEAD and GET are equivalent as per RFC
+ if ('HEAD' === $method = $this->context->getMethod()) {
+ $method = 'GET';
+ }
$supportsTrailingSlash = '/' !== $pathinfo && '' !== $pathinfo && $this instanceof RedirectableUrlMatcherInterface;
foreach ($routes as $name => $route) {
$compiledRoute = $route->compile();
$staticPrefix = $compiledRoute->getStaticPrefix();
+ $requiredMethods = $route->getMethods();
// check the static prefix of the URL first. Only use the more expensive preg_match when it matches
if ('' === $staticPrefix || 0 === strpos($pathinfo, $staticPrefix)) {
// no-op
- } elseif (!$supportsTrailingSlash) {
+ } elseif (!$supportsTrailingSlash || ($requiredMethods && !\in_array('GET', $requiredMethods)) || 'GET' !== $method) {
continue;
} elseif ('/' === substr($staticPrefix, -1) && substr($staticPrefix, 0, -1) === $pathinfo) {
- return;
+ return $this->allow = array();
} else {
continue;
}
@@ -148,7 +153,10 @@ protected function matchCollection($pathinfo, RouteCollection $routes)
}
if ($hasTrailingSlash && '/' !== substr($pathinfo, -1)) {
- return;
+ if ((!$requiredMethods || \in_array('GET', $requiredMethods)) && 'GET' === $method) {
+ return $this->allow = array();
+ }
+ continue;
}
$hostMatches = array();
@@ -163,12 +171,7 @@ protected function matchCollection($pathinfo, RouteCollection $routes)
}
// check HTTP method requirement
- if ($requiredMethods = $route->getMethods()) {
- // HEAD and GET are equivalent as per RFC
- if ('HEAD' === $method = $this->context->getMethod()) {
- $method = 'GET';
- }
-
+ if ($requiredMethods) {
if (!\in_array($method, $requiredMethods)) {
if (self::REQUIREMENT_MATCH === $status[0]) {
$this->allow = array_merge($this->allow, $requiredMethods);
@@ -180,6 +183,8 @@ protected function matchCollection($pathinfo, RouteCollection $routes)
return $this->getAttributes($route, $name, array_replace($matches, $hostMatches, isset($status[1]) ? $status[1] : array()));
}
+
+ return array();
}
/**
diff --git a/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php
index a7aea0ec071d2..9857f05024d72 100644
--- a/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php
+++ b/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php
@@ -128,6 +128,23 @@ public function testFallbackPage()
$this->assertSame(array('_route' => 'foo'), $matcher->match('/foo'));
}
+ public function testSlashAndVerbPrecedenceWithRedirection()
+ {
+ $coll = new RouteCollection();
+ $coll->add('a', new Route('/api/customers/{customerId}/contactpersons', array(), array(), array(), '', array(), array('post')));
+ $coll->add('b', new Route('/api/customers/{customerId}/contactpersons/', array(), array(), array(), '', array(), array('get')));
+
+ $matcher = $this->getUrlMatcher($coll);
+ $expected = array(
+ '_route' => 'b',
+ 'customerId' => '123',
+ );
+ $this->assertEquals($expected, $matcher->match('/api/customers/123/contactpersons/'));
+
+ $matcher->expects($this->once())->method('redirect')->with('/api/customers/123/contactpersons/')->willReturn(array());
+ $this->assertEquals($expected, $matcher->match('/api/customers/123/contactpersons'));
+ }
+
protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null)
{
return $this->getMockForAbstractClass('Symfony\Component\Routing\Matcher\RedirectableUrlMatcher', array($routes, $context ?: new RequestContext()));
diff --git a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php
index 0d4b44b090704..85647f0bead22 100644
--- a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php
+++ b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php
@@ -502,6 +502,31 @@ public function testSchemeAndMethodMismatch()
$matcher->match('/');
}
+ public function testSlashAndVerbPrecedence()
+ {
+ $coll = new RouteCollection();
+ $coll->add('a', new Route('/api/customers/{customerId}/contactpersons/', array(), array(), array(), '', array(), array('post')));
+ $coll->add('b', new Route('/api/customers/{customerId}/contactpersons', array(), array(), array(), '', array(), array('get')));
+
+ $matcher = $this->getUrlMatcher($coll);
+ $expected = array(
+ '_route' => 'b',
+ 'customerId' => '123',
+ );
+ $this->assertEquals($expected, $matcher->match('/api/customers/123/contactpersons'));
+
+ $coll = new RouteCollection();
+ $coll->add('a', new Route('/api/customers/{customerId}/contactpersons/', array(), array(), array(), '', array(), array('get')));
+ $coll->add('b', new Route('/api/customers/{customerId}/contactpersons', array(), array(), array(), '', array(), array('post')));
+
+ $matcher = $this->getUrlMatcher($coll, new RequestContext('', 'POST'));
+ $expected = array(
+ '_route' => 'b',
+ 'customerId' => '123',
+ );
+ $this->assertEquals($expected, $matcher->match('/api/customers/123/contactpersons'));
+ }
+
protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null)
{
return new UrlMatcher($routes, $context ?: new RequestContext());
diff --git a/src/Symfony/Component/Security/Http/HttpUtils.php b/src/Symfony/Component/Security/Http/HttpUtils.php
index a6bcffe4852dc..49ccc723b451f 100644
--- a/src/Symfony/Component/Security/Http/HttpUtils.php
+++ b/src/Symfony/Component/Security/Http/HttpUtils.php
@@ -59,7 +59,7 @@ public function __construct(UrlGeneratorInterface $urlGenerator = null, $urlMatc
*/
public function createRedirectResponse(Request $request, $path, $status = 302)
{
- if (null !== $this->domainRegexp && preg_match('#^https?://[^/]++#i', $path, $host) && !preg_match(sprintf($this->domainRegexp, preg_quote($request->getHttpHost())), $host[0])) {
+ if (null !== $this->domainRegexp && preg_match('#^https?:[/\\\\]{2,}+[^/]++#i', $path, $host) && !preg_match(sprintf($this->domainRegexp, preg_quote($request->getHttpHost())), $host[0])) {
$path = '/';
}
diff --git a/src/Symfony/Component/Security/Http/Tests/HttpUtilsTest.php b/src/Symfony/Component/Security/Http/Tests/HttpUtilsTest.php
index 28b242995d4bf..a0d6f79714414 100644
--- a/src/Symfony/Component/Security/Http/Tests/HttpUtilsTest.php
+++ b/src/Symfony/Component/Security/Http/Tests/HttpUtilsTest.php
@@ -54,14 +54,28 @@ public function testCreateRedirectResponseWithRequestsDomain()
$this->assertTrue($response->isRedirect('http://localhost/blog'));
}
- public function testCreateRedirectResponseWithBadRequestsDomain()
+ /**
+ * @dataProvider badRequestDomainUrls
+ */
+ public function testCreateRedirectResponseWithBadRequestsDomain($url)
{
$utils = new HttpUtils($this->getUrlGenerator(), null, '#^https?://%s$#i');
- $response = $utils->createRedirectResponse($this->getRequest(), 'http://pirate.net/foo');
+ $response = $utils->createRedirectResponse($this->getRequest(), $url);
$this->assertTrue($response->isRedirect('http://localhost/'));
}
+ public function badRequestDomainUrls()
+ {
+ return array(
+ array('http://pirate.net/foo'),
+ array('http:\\\\pirate.net/foo'),
+ array('http:/\\pirate.net/foo'),
+ array('http:\\/pirate.net/foo'),
+ array('http://////pirate.net/foo'),
+ );
+ }
+
public function testCreateRedirectResponseWithProtocolRelativeTarget()
{
$utils = new HttpUtils($this->getUrlGenerator(), null, '#^https?://%s$#i');
diff --git a/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php
index be469a4d53c5f..aaa4e8b940a91 100644
--- a/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php
+++ b/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php
@@ -59,7 +59,8 @@ public function normalize($object, $format = null, array $context = array())
$timezone = $this->getTimezone($context);
if (null !== $timezone) {
- $object = (new \DateTimeImmutable('@'.$object->getTimestamp()))->setTimezone($timezone);
+ $object = clone $object;
+ $object = $object->setTimezone($timezone);
}
return $object->format($format);
diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/DateTimeNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/DateTimeNormalizerTest.php
index 178519b30e687..99b224996cb1b 100644
--- a/src/Symfony/Component/Serializer/Tests/Normalizer/DateTimeNormalizerTest.php
+++ b/src/Symfony/Component/Serializer/Tests/Normalizer/DateTimeNormalizerTest.php
@@ -78,6 +78,82 @@ public function normalizeUsingTimeZonePassedInContextProvider()
yield array('2016-12-01T09:00:00+09:00', new \DateTimeImmutable('2016/12/01', new \DateTimeZone('UTC')), new \DateTimeZone('Japan'));
}
+ /**
+ * @dataProvider normalizeUsingTimeZonePassedInContextAndExpectedFormatWithMicrosecondsProvider
+ */
+ public function testNormalizeUsingTimeZonePassedInContextAndFormattedWithMicroseconds($expected, $expectedFormat, $input, $timezone)
+ {
+ $this->assertSame(
+ $expected,
+ $this->normalizer->normalize(
+ $input,
+ null,
+ array(
+ DateTimeNormalizer::TIMEZONE_KEY => $timezone,
+ DateTimeNormalizer::FORMAT_KEY => $expectedFormat,
+ )
+ )
+ );
+ }
+
+ public function normalizeUsingTimeZonePassedInContextAndExpectedFormatWithMicrosecondsProvider()
+ {
+ yield array(
+ '2018-12-01T18:03:06.067634',
+ 'Y-m-d\TH:i:s.u',
+ \DateTime::createFromFormat(
+ 'Y-m-d\TH:i:s.u',
+ '2018-12-01T18:03:06.067634',
+ new \DateTimeZone('UTC')
+ ),
+ null,
+ );
+
+ yield array(
+ '2018-12-01T18:03:06.067634',
+ 'Y-m-d\TH:i:s.u',
+ \DateTime::createFromFormat(
+ 'Y-m-d\TH:i:s.u',
+ '2018-12-01T18:03:06.067634',
+ new \DateTimeZone('UTC')
+ ),
+ new \DateTimeZone('UTC'),
+ );
+
+ yield array(
+ '2018-12-01T19:03:06.067634+01:00',
+ 'Y-m-d\TH:i:s.uP',
+ \DateTimeImmutable::createFromFormat(
+ 'Y-m-d\TH:i:s.u',
+ '2018-12-01T18:03:06.067634',
+ new \DateTimeZone('UTC')
+ ),
+ new \DateTimeZone('Europe/Rome'),
+ );
+
+ yield array(
+ '2018-12-01T20:03:06.067634+02:00',
+ 'Y-m-d\TH:i:s.uP',
+ \DateTime::createFromFormat(
+ 'Y-m-d\TH:i:s.u',
+ '2018-12-01T18:03:06.067634',
+ new \DateTimeZone('UTC')
+ ),
+ new \DateTimeZone('Europe/Kiev'),
+ );
+
+ yield array(
+ '2018-12-01T21:03:06.067634',
+ 'Y-m-d\TH:i:s.u',
+ \DateTime::createFromFormat(
+ 'Y-m-d\TH:i:s.u',
+ '2018-12-01T18:03:06.067634',
+ new \DateTimeZone('UTC')
+ ),
+ new \DateTimeZone('Europe/Moscow'),
+ );
+ }
+
/**
* @expectedException \Symfony\Component\Serializer\Exception\InvalidArgumentException
* @expectedExceptionMessage The object must implement the "\DateTimeInterface".
diff --git a/src/Symfony/Component/Validator/ConstraintViolation.php b/src/Symfony/Component/Validator/ConstraintViolation.php
index 848c77611776b..ae9ff89eb54cf 100644
--- a/src/Symfony/Component/Validator/ConstraintViolation.php
+++ b/src/Symfony/Component/Validator/ConstraintViolation.php
@@ -79,13 +79,13 @@ public function __toString()
}
$propertyPath = (string) $this->propertyPath;
- $code = $this->code;
+ $code = (string) $this->code;
if ('' !== $propertyPath && '[' !== $propertyPath[0] && '' !== $class) {
$class .= '.';
}
- if (!empty($code)) {
+ if ('' !== $code) {
$code = ' (code '.$code.')';
}
diff --git a/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php b/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php
index 544c82f6c074b..e4f7df1757da6 100644
--- a/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php
+++ b/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php
@@ -112,7 +112,7 @@ public function getValidator();
* Returns the currently validated object.
*
* If the validator is currently validating a class constraint, the
- * object of that class is returned. If it is a validating a property or
+ * object of that class is returned. If it is validating a property or
* getter constraint, the object that the property/getter belongs to is
* returned.
*
diff --git a/src/Symfony/Component/Validator/Tests/ConstraintViolationTest.php b/src/Symfony/Component/Validator/Tests/ConstraintViolationTest.php
index cef4782e0f82d..edaa7fa50d6b0 100644
--- a/src/Symfony/Component/Validator/Tests/ConstraintViolationTest.php
+++ b/src/Symfony/Component/Validator/Tests/ConstraintViolationTest.php
@@ -53,4 +53,59 @@ public function testToStringHandlesArrayRoots()
$this->assertSame($expected, (string) $violation);
}
+
+ public function testToStringHandlesCodes()
+ {
+ $violation = new ConstraintViolation(
+ '42 cannot be used here',
+ 'this is the message template',
+ array(),
+ array('some_value' => 42),
+ 'some_value',
+ null,
+ null,
+ 0
+ );
+
+ $expected = <<<'EOF'
+Array.some_value:
+ 42 cannot be used here (code 0)
+EOF;
+
+ $this->assertSame($expected, (string) $violation);
+ }
+
+ public function testToStringOmitsEmptyCodes()
+ {
+ $expected = <<<'EOF'
+Array.some_value:
+ 42 cannot be used here
+EOF;
+
+ $violation = new ConstraintViolation(
+ '42 cannot be used here',
+ 'this is the message template',
+ array(),
+ array('some_value' => 42),
+ 'some_value',
+ null,
+ null,
+ null
+ );
+
+ $this->assertSame($expected, (string) $violation);
+
+ $violation = new ConstraintViolation(
+ '42 cannot be used here',
+ 'this is the message template',
+ array(),
+ array('some_value' => 42),
+ 'some_value',
+ null,
+ null,
+ ''
+ );
+
+ $this->assertSame($expected, (string) $violation);
+ }
}