diff --git a/CHANGELOG-2.3.md b/CHANGELOG-2.3.md
index 9b0b1f994e..c132013aa0 100644
--- a/CHANGELOG-2.3.md
+++ b/CHANGELOG-2.3.md
@@ -7,6 +7,14 @@ in 2.3 minor versions.
To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash
To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v2.3.0...v2.3.1
+* 2.3.18 (2014-07-15)
+
+ * [Security] Forced validate of locales passed to the translator
+ * feature #11367 [HttpFoundation] Fix to prevent magic bytes injection in JSONP responses... (CVE-2014-4671) (Andrew Moore)
+ * bug #11386 Remove Spaceless Blocks from Twig Form Templates (chrisguitarguy)
+ * bug #9719 [TwigBundle] fix configuration tree for paths (mdavis1982, cordoval)
+ * bug #11244 [HttpFoundation] Remove body-related headers when sending the response, if body is empty (SimonSimCity)
+
* 2.3.17 (2014-07-07)
* bug #11238 [Translation] Added unescaping of ids in PoFileLoader (JustBlackBird)
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 007ceffdf4..ca9e441c93 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -19,8 +19,8 @@ Symfony2 is the result of the work of many people who made the code better
- Ryan Weaver (weaverryan)
- Lukas Kahwe Smith (lsmith)
- Jeremy Mikola (jmikola)
- - Jean-François Simon (jfsimon)
- Romain Neutron (romain)
+ - Jean-François Simon (jfsimon)
- Benjamin Eberlei (beberlei)
- Igor Wiedler (igorw)
- Hugo Hamon (hhamon)
@@ -44,18 +44,18 @@ Symfony2 is the result of the work of many people who made the code better
- Wouter De Jong (wouterj)
- Eric Clemmons (ericclemmons)
- Nicolas Grekas (nicolas-grekas)
+ - Andrej Hudec (pulzarraider)
- Deni
- Henrik Westphal (snc)
- Dariusz Górecki (canni)
- - Andrej Hudec (pulzarraider)
- Arnout Boks (aboks)
+ - Christian Raue
- Michel Weimerskirch (mweimerskirch)
- Lee McDermott
- Brandon Turner
- Douglas Greenshields (shieldo)
- Daniel Holmes (dholmes)
- Jordan Alliot (jalliot)
- - Christian Raue
- John Wards (johnwards)
- Fran Moreno (franmomu)
- Bart van den Burg (burgov)
@@ -75,21 +75,21 @@ Symfony2 is the result of the work of many people who made the code better
- Fabien Pennequin (fabienpennequin)
- Jacob Dreesen (jdreesen)
- Gábor Egyed (1ed)
+ - Ait Boudad Abdellatif (aitboudad)
- Adrien Brault (adrienbrault)
- Michal Piotrowski (eventhorizon)
- - Ait Boudad Abdellatif (aitboudad)
- Robert Schönthal (digitalkaoz)
- Juti Noppornpitak (shiroyuki)
- Sebastian Hörl (blogsh)
- Daniel Gomes (danielcsgomes)
- Hidenori Goto (hidenorigoto)
- Peter Kokot (maastermedia)
+ - Christian Flothmann (xabbuh)
- Jérémie Augustin (jaugustin)
- David Buchmann (dbu)
- Jérôme Tamarelle (gromnan)
- Tigran Azatyan (tigranazatyan)
- Javier Eguiluz (javier.eguiluz)
- - Christian Flothmann (xabbuh)
- Rafael Dohms (rdohms)
- Richard Shank (iampersistent)
- Gordon Franke (gimler)
@@ -146,6 +146,7 @@ Symfony2 is the result of the work of many people who made the code better
- Manuel Reinhard (sprain)
- Danny Berger (dpb587)
- Xavier Montaña Carreras (xmontana)
+ - Michele Orselli (orso)
- Xavier Perez
- Arjen Brouwer (arjenjb)
- Katsuhiro OGAWA
@@ -168,7 +169,6 @@ Symfony2 is the result of the work of many people who made the code better
- Robert Kiss (kepten)
- Kim Hemsø Rasmussen (kimhemsoe)
- Wouter Van Hecke
- - Michele Orselli (orso)
- Michael Holm (hollo)
- Marc Weistroff (futurecat)
- Pierre-Yves LEBECQ (pylebecq)
@@ -231,6 +231,7 @@ Symfony2 is the result of the work of many people who made the code better
- Ismael Ambrosi (iambrosi)
- Shein Alexey
- Issei Murasawa (issei_m)
+ - hacfi (hifi)
- Joe Lencioni
- Kai
- Xavier HAUSHERR
@@ -274,6 +275,7 @@ Symfony2 is the result of the work of many people who made the code better
- François-Xavier de Guillebon (de-gui_f)
- boombatower
- Fabrice Bernhard (fabriceb)
+ - Clément Gautier (clementgautier)
- Fabian Lange (codingfabian)
- Yoshio HANAWA
- Baptiste Clavié (talus)
@@ -294,11 +296,11 @@ Symfony2 is the result of the work of many people who made the code better
- Maks Slesarenko
- Markus Lanthaler (lanthaler)
- Vicent Soria Durá (vicentgodella)
+ - Chris Wilkinson (thewilkybarkid)
- Ioan Negulescu
- Andrew Udvare (audvare)
- alexpods
- Erik Trapman (eriktrapman)
- - hacfi (hifi)
- De Cock Xavier (xdecock)
- Alex Pott
- Norbert Orzechowicz (norzechowicz)
@@ -365,6 +367,7 @@ Symfony2 is the result of the work of many people who made the code better
- Arturs Vonda
- Sascha Grossenbacher
- Ben Davies (bendavies)
+ - Simon Schick (simonsimcity)
- Hossein Bukhamsin
- Paweł Wacławczyk (pwc)
- Oleg Zinchenko (cystbear)
@@ -372,6 +375,7 @@ Symfony2 is the result of the work of many people who made the code better
- Johannes Klauss (cloppy)
- Evan Villemez
- fzerorubigd
+ - Benjamin Grandfond (benjamin)
- Tiago Brito (blackmx)
- Richard van den Brand (ricbra)
- develop
@@ -403,7 +407,6 @@ Symfony2 is the result of the work of many people who made the code better
- Marek Štípek (maryo)
- John Bohn (jbohn)
- Jakub Škvára (jskvara)
- - Chris Wilkinson (thewilkybarkid)
- Andrew Hilobok (hilobok)
- Christian Soronellas (theunic)
- Jérôme Vieilledent (lolautruche)
@@ -532,7 +535,6 @@ Symfony2 is the result of the work of many people who made the code better
- jfcixmedia
- Martijn Evers
- Benjamin Paap (benjaminpaap)
- - Simon Schick (simonsimcity)
- Christian
- Sergii Smertin (nfx)
- Eddie Jaoude
@@ -550,6 +552,7 @@ Symfony2 is the result of the work of many people who made the code better
- Benoit Garret
- DerManoMann
- Asmir Mustafic (goetas)
+ - Julien Bianchi (jubianchi)
- Marcin Chwedziak
- Roland Franssen (ro0)
- Maciej Malarz
@@ -562,20 +565,22 @@ Symfony2 is the result of the work of many people who made the code better
- kaiwa
- Albert Ganiev (helios-ag)
- Neil Katin
+ - David Otton
- peter
- Artem Kolesnikov (tyomo4ka)
- Gustavo Adrian
- - Clément Gautier (clementgautier)
- Yannick
- Luc Vieillescazes (iamluc)
- Eduardo García Sanz (coma)
- David de Boer (ddeboer)
- Brooks Boyd
- Roger Webb
+ - Dmitriy Simushev
- Max Voloshin (maxvoloshin)
- Nicolas Fabre (nfabre)
- Raul Rodriguez (raul782)
- Patrick Landolt (scube)
+ - WybrenKoelmans
- Derek Lambert
- Felicitus
- Krzysztof Przybyszewski
@@ -730,6 +735,7 @@ Symfony2 is the result of the work of many people who made the code better
- catch
- Alexandre Segura
- Josef Cech
+ - Nate (frickenate)
- Matthew Foster (mfoster)
- Maximilian Reichel (phramz)
- Paul Seiffert (seiffert)
@@ -750,7 +756,6 @@ Symfony2 is the result of the work of many people who made the code better
- Andrey Ryaguzov
- Gunther Konig
- František Bereň
- - Benjamin Grandfond (benjamin)
- Christoph Nissle (derstoffel)
- Ionel Scutelnicu (ionelscutelnicu)
- Johnny Peck (johnnypeck)
@@ -875,6 +880,7 @@ Symfony2 is the result of the work of many people who made the code better
- Yorkie Chadwick (yorkie76)
- Yanick Witschi
- Ondrej Mirtes
+ - akimsko
- Youpie
- srsbiz
- Nicolas A. Bérard-Nault
@@ -1010,6 +1016,7 @@ Symfony2 is the result of the work of many people who made the code better
- Adam Monsen (meonkeys)
- ollie harridge (ollietb)
- Paweł Szczepanek (pauluz)
+ - Christian López Espínola (penyaskito)
- Petr Jaroš (petajaros)
- Philipp Hoffmann (philipphoffmann)
- Alex Carol (picard89)
diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
index 9440658cbe..c14031a713 100644
--- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
+++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
@@ -1,393 +1,313 @@
{# Widgets #}
-{% block form_widget %}
-{% spaceless %}
+{% block form_widget -%}
{% if compound %}
- {{ block('form_widget_compound') }}
+ {{- block('form_widget_compound') -}}
{% else %}
- {{ block('form_widget_simple') }}
+ {{- block('form_widget_simple') -}}
{% endif %}
-{% endspaceless %}
-{% endblock form_widget %}
+{%- endblock form_widget %}
-{% block form_widget_simple %}
-{% spaceless %}
- {% set type = type|default('text') %}
+{% block form_widget_simple -%}
+ {%- set type = type|default('text') -%}
-{% endspaceless %}
-{% endblock form_widget_simple %}
+{%- endblock form_widget_simple %}
-{% block form_widget_compound %}
-{% spaceless %}
+{% block form_widget_compound -%}
- {% if form.parent is empty %}
+ {%- if form.parent is empty -%}
{{ form_errors(form) }}
- {% endif %}
- {{ block('form_rows') }}
- {{ form_rest(form) }}
+ {%- endif -%}
+ {{- block('form_rows') -}}
+ {{- form_rest(form) -}}
-{% endspaceless %}
-{% endblock form_widget_compound %}
+{%- endblock form_widget_compound %}
-{% block collection_widget %}
-{% spaceless %}
+{% block collection_widget -%}
{% if prototype is defined %}
- {% set attr = attr|merge({'data-prototype': form_row(prototype) }) %}
+ {%- set attr = attr|merge({'data-prototype': form_row(prototype) }) -%}
{% endif %}
- {{ block('form_widget') }}
-{% endspaceless %}
-{% endblock collection_widget %}
+ {{- block('form_widget') -}}
+{%- endblock collection_widget %}
-{% block textarea_widget %}
-{% spaceless %}
+{% block textarea_widget -%}
-{% endspaceless %}
-{% endblock textarea_widget %}
+{%- endblock textarea_widget %}
-{% block choice_widget %}
-{% spaceless %}
+{% block choice_widget -%}
{% if expanded %}
- {{ block('choice_widget_expanded') }}
+ {{- block('choice_widget_expanded') -}}
{% else %}
- {{ block('choice_widget_collapsed') }}
+ {{- block('choice_widget_collapsed') -}}
{% endif %}
-{% endspaceless %}
-{% endblock choice_widget %}
+{%- endblock choice_widget %}
-{% block choice_widget_expanded %}
-{% spaceless %}
+{% block choice_widget_expanded -%}
{% for child in form %}
- {{ form_widget(child) }}
- {{ form_label(child) }}
+ {{- form_widget(child) -}}
+ {{- form_label(child) -}}
{% endfor %}
-{% endspaceless %}
-{% endblock choice_widget_expanded %}
+{%- endblock choice_widget_expanded %}
-{% block choice_widget_collapsed %}
-{% spaceless %}
+{% block choice_widget_collapsed -%}
{% if required and empty_value is none and not empty_value_in_choices and not multiple %}
{% set required = false %}
{% endif %}
- {% if empty_value is not none %}
+ {% if empty_value is not none -%}
{{ empty_value|trans({}, translation_domain) }}
- {% endif %}
- {% if preferred_choices|length > 0 %}
+ {%- endif %}
+ {%- if preferred_choices|length > 0 -%}
{% set options = preferred_choices %}
- {{ block('choice_widget_options') }}
- {% if choices|length > 0 and separator is not none %}
+ {{- block('choice_widget_options') -}}
+ {% if choices|length > 0 and separator is not none -%}
{{ separator }}
- {% endif %}
- {% endif %}
- {% set options = choices %}
- {{ block('choice_widget_options') }}
+ {%- endif %}
+ {%- endif -%}
+ {% set options = choices -%}
+ {{- block('choice_widget_options') -}}
-{% endspaceless %}
-{% endblock choice_widget_collapsed %}
+{%- endblock choice_widget_collapsed %}
-{% block choice_widget_options %}
-{% spaceless %}
+{% block choice_widget_options -%}
{% for group_label, choice in options %}
- {% if choice is iterable %}
+ {%- if choice is iterable -%}
{% set options = choice %}
- {{ block('choice_widget_options') }}
+ {{- block('choice_widget_options') -}}
- {% else %}
+ {%- else -%}
{{ choice.label|trans({}, translation_domain) }}
- {% endif %}
+ {%- endif -%}
{% endfor %}
-{% endspaceless %}
-{% endblock choice_widget_options %}
+{%- endblock choice_widget_options %}
-{% block checkbox_widget %}
-{% spaceless %}
+{% block checkbox_widget -%}
-{% endspaceless %}
-{% endblock checkbox_widget %}
+{%- endblock checkbox_widget %}
-{% block radio_widget %}
-{% spaceless %}
+{% block radio_widget -%}
-{% endspaceless %}
-{% endblock radio_widget %}
+{%- endblock radio_widget %}
-{% block datetime_widget %}
-{% spaceless %}
+{% block datetime_widget -%}
{% if widget == 'single_text' %}
- {{ block('form_widget_simple') }}
- {% else %}
+ {{- block('form_widget_simple') -}}
+ {% else -%}
- {{ form_errors(form.date) }}
- {{ form_errors(form.time) }}
- {{ form_widget(form.date) }}
- {{ form_widget(form.time) }}
+ {{- form_errors(form.date) -}}
+ {{- form_errors(form.time) -}}
+ {{- form_widget(form.date) -}}
+ {{- form_widget(form.time) -}}
- {% endif %}
-{% endspaceless %}
-{% endblock datetime_widget %}
+ {%- endif %}
+{%- endblock datetime_widget %}
-{% block date_widget %}
-{% spaceless %}
- {% if widget == 'single_text' %}
+{% block date_widget -%}
+ {% if widget == 'single_text' -%}
{{ block('form_widget_simple') }}
- {% else %}
+ {%- else -%}
- {{ date_pattern|replace({
+ {{- date_pattern|replace({
'{{ year }}': form_widget(form.year),
'{{ month }}': form_widget(form.month),
'{{ day }}': form_widget(form.day),
- })|raw }}
+ })|raw -}}
- {% endif %}
-{% endspaceless %}
-{% endblock date_widget %}
+ {%- endif %}
+{%- endblock date_widget %}
-{% block time_widget %}
-{% spaceless %}
- {% if widget == 'single_text' %}
+{% block time_widget -%}
+ {% if widget == 'single_text' -%}
{{ block('form_widget_simple') }}
- {% else %}
- {% set vars = widget == 'text' ? { 'attr': { 'size': 1 }} : {} %}
+ {%- else -%}
+ {% set vars = widget == 'text' ? { 'attr': { 'size': 1 }} : {} -%}
{{ form_widget(form.hour, vars) }}{% if with_minutes %}:{{ form_widget(form.minute, vars) }}{% endif %}{% if with_seconds %}:{{ form_widget(form.second, vars) }}{% endif %}
- {% endif %}
-{% endspaceless %}
-{% endblock time_widget %}
+ {%- endif %}
+{%- endblock time_widget %}
-{% block number_widget %}
-{% spaceless %}
+{% block number_widget -%}
{# type="number" doesn't work with floats #}
- {% set type = type|default('text') %}
+ {%- set type = type|default('text') -%}
{{ block('form_widget_simple') }}
-{% endspaceless %}
-{% endblock number_widget %}
+{%- endblock number_widget %}
-{% block integer_widget %}
-{% spaceless %}
+{% block integer_widget -%}
{% set type = type|default('number') %}
- {{ block('form_widget_simple') }}
-{% endspaceless %}
-{% endblock integer_widget %}
+ {{- block('form_widget_simple') -}}
+{%- endblock integer_widget %}
-{% block money_widget %}
-{% spaceless %}
+{% block money_widget -%}
{{ money_pattern|replace({ '{{ widget }}': block('form_widget_simple') })|raw }}
-{% endspaceless %}
-{% endblock money_widget %}
+{%- endblock money_widget %}
-{% block url_widget %}
-{% spaceless %}
- {% set type = type|default('url') %}
+{% block url_widget -%}
+ {% set type = type|default('url') -%}
{{ block('form_widget_simple') }}
-{% endspaceless %}
-{% endblock url_widget %}
+{%- endblock url_widget %}
-{% block search_widget %}
-{% spaceless %}
- {% set type = type|default('search') %}
+{% block search_widget -%}
+ {% set type = type|default('search') -%}
{{ block('form_widget_simple') }}
-{% endspaceless %}
-{% endblock search_widget %}
+{%- endblock search_widget %}
-{% block percent_widget %}
-{% spaceless %}
- {% set type = type|default('text') %}
+{% block percent_widget -%}
+ {% set type = type|default('text') -%}
{{ block('form_widget_simple') }} %
-{% endspaceless %}
-{% endblock percent_widget %}
+{%- endblock percent_widget %}
-{% block password_widget %}
-{% spaceless %}
- {% set type = type|default('password') %}
+{% block password_widget -%}
+ {% set type = type|default('password') -%}
{{ block('form_widget_simple') }}
-{% endspaceless %}
-{% endblock password_widget %}
+{%- endblock password_widget %}
-{% block hidden_widget %}
-{% spaceless %}
- {% set type = type|default('hidden') %}
+{% block hidden_widget -%}
+ {% set type = type|default('hidden') -%}
{{ block('form_widget_simple') }}
-{% endspaceless %}
-{% endblock hidden_widget %}
+{%- endblock hidden_widget %}
-{% block email_widget %}
-{% spaceless %}
- {% set type = type|default('email') %}
+{% block email_widget -%}
+ {% set type = type|default('email') -%}
{{ block('form_widget_simple') }}
-{% endspaceless %}
-{% endblock email_widget %}
+{%- endblock email_widget %}
-{% block button_widget %}
-{% spaceless %}
- {% if label is empty %}
+{% block button_widget -%}
+ {% if label is empty -%}
{% set label = name|humanize %}
- {% endif %}
+ {%- endif -%}
{{ label|trans({}, translation_domain) }}
-{% endspaceless %}
-{% endblock button_widget %}
+{%- endblock button_widget %}
-{% block submit_widget %}
-{% spaceless %}
- {% set type = type|default('submit') %}
+{% block submit_widget -%}
+ {% set type = type|default('submit') -%}
{{ block('button_widget') }}
-{% endspaceless %}
-{% endblock submit_widget %}
+{%- endblock submit_widget %}
-{% block reset_widget %}
-{% spaceless %}
- {% set type = type|default('reset') %}
+{% block reset_widget -%}
+ {% set type = type|default('reset') -%}
{{ block('button_widget') }}
-{% endspaceless %}
-{% endblock reset_widget %}
+{%- endblock reset_widget %}
{# Labels #}
-{% block form_label %}
-{% spaceless %}
+{% block form_label -%}
{% if label is not sameas(false) %}
- {% if not compound %}
+ {%- if not compound -%}
{% set label_attr = label_attr|merge({'for': id}) %}
- {% endif %}
- {% if required %}
+ {%- endif -%}
+ {%- if required -%}
{% set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' required')|trim}) %}
- {% endif %}
- {% if label is empty %}
+ {%- endif -%}
+ {%- if label is empty -%}
{% set label = name|humanize %}
- {% endif %}
+ {%- endif -%}
{{ label|trans({}, translation_domain) }}
- {% endif %}
-{% endspaceless %}
-{% endblock form_label %}
+ {%- endif %}
+{%- endblock form_label %}
-{% block button_label %}{% endblock %}
+{% block button_label -%}{%- endblock %}
{# Rows #}
-{% block repeated_row %}
-{% spaceless %}
+{% block repeated_row -%}
{#
No need to render the errors here, as all errors are mapped
to the first child (see RepeatedTypeValidatorExtension).
#}
- {{ block('form_rows') }}
-{% endspaceless %}
-{% endblock repeated_row %}
+ {{- block('form_rows') }}
+{%- endblock repeated_row %}
-{% block form_row %}
-{% spaceless %}
+{% block form_row -%}
- {{ form_label(form) }}
- {{ form_errors(form) }}
- {{ form_widget(form) }}
+ {{- form_label(form) -}}
+ {{- form_errors(form) -}}
+ {{- form_widget(form) -}}
-{% endspaceless %}
-{% endblock form_row %}
+{%- endblock form_row %}
-{% block button_row %}
-{% spaceless %}
+{% block button_row -%}
- {{ form_widget(form) }}
+ {{- form_widget(form) -}}
-{% endspaceless %}
-{% endblock button_row %}
+{%- endblock button_row %}
-{% block hidden_row %}
+{% block hidden_row -%}
{{ form_widget(form) }}
-{% endblock hidden_row %}
+{%- endblock hidden_row %}
{# Misc #}
-{% block form %}
-{% spaceless %}
+{% block form -%}
{{ form_start(form) }}
- {{ form_widget(form) }}
+ {{- form_widget(form) -}}
{{ form_end(form) }}
-{% endspaceless %}
-{% endblock form %}
+{%- endblock form %}
-{% block form_start %}
-{% spaceless %}
+{% block form_start -%}
{% set method = method|upper %}
- {% if method in ["GET", "POST"] %}
+ {%- if method in ["GET", "POST"] -%}
{% set form_method = method %}
- {% else %}
+ {%- else -%}
{% set form_method = "POST" %}
- {% endif %}
+ {%- endif -%}
-{% endspaceless %}
-{% endblock form_end %}
+{%- endblock form_end %}
-{% block form_enctype %}
-{% spaceless %}
+{% block form_enctype -%}
{% if multipart %}enctype="multipart/form-data"{% endif %}
-{% endspaceless %}
-{% endblock form_enctype %}
+{%- endblock form_enctype %}
-{% block form_errors %}
-{% spaceless %}
- {% if errors|length > 0 %}
+{% block form_errors -%}
+ {% if errors|length > 0 -%}
- {% for error in errors %}
+ {%- for error in errors -%}
{{ error.message }}
- {% endfor %}
+ {%- endfor -%}
- {% endif %}
-{% endspaceless %}
-{% endblock form_errors %}
+ {%- endif %}
+{%- endblock form_errors %}
-{% block form_rest %}
-{% spaceless %}
- {% for child in form %}
- {% if not child.rendered %}
+{% block form_rest -%}
+ {% for child in form -%}
+ {% if not child.rendered -%}
{{ form_row(child) }}
- {% endif %}
- {% endfor %}
-{% endspaceless %}
-{% endblock form_rest %}
+ {%- endif %}
+ {%- endfor %}
+{%- endblock form_rest %}
{# Support #}
-{% block form_rows %}
-{% spaceless %}
- {% for child in form %}
+{% block form_rows -%}
+ {% for child in form -%}
{{ form_row(child) }}
- {% endfor %}
-{% endspaceless %}
-{% endblock form_rows %}
+ {%- endfor %}
+{%- endblock form_rows %}
-{% block widget_attributes %}
-{% spaceless %}
+{% block widget_attributes -%}
id="{{ id }}" name="{{ full_name }}"{% if read_only %} readonly="readonly"{% endif %}{% if disabled %} disabled="disabled"{% endif %}{% if required %} required="required"{% endif %}{% if max_length %} maxlength="{{ max_length }}"{% endif %}{% if pattern %} pattern="{{ pattern }}"{% endif %}
{% for attrname, attrvalue in attr %}{% if attrname in ['placeholder', 'title'] %}{{ attrname }}="{{ attrvalue|trans({}, translation_domain) }}" {% else %}{{ attrname }}="{{ attrvalue }}" {% endif %}{% endfor %}
-{% endspaceless %}
-{% endblock widget_attributes %}
+{%- endblock widget_attributes %}
-{% block widget_container_attributes %}
-{% spaceless %}
+{% block widget_container_attributes -%}
{% if id is not empty %}id="{{ id }}" {% endif %}
- {% for attrname, attrvalue in attr %}{{ attrname }}="{{ attrvalue }}" {% endfor %}
-{% endspaceless %}
-{% endblock widget_container_attributes %}
+ {%- for attrname, attrvalue in attr %}{{ attrname }}="{{ attrvalue }}" {% endfor %}
+{%- endblock widget_container_attributes %}
-{% block button_attributes %}
-{% spaceless %}
+{% block button_attributes -%}
id="{{ id }}" name="{{ full_name }}"{% if disabled %} disabled="disabled"{% endif %}
- {% for attrname, attrvalue in attr %}{{ attrname }}="{{ attrvalue }}" {% endfor %}
-{% endspaceless %}
-{% endblock button_attributes %}
+ {%- for attrname, attrvalue in attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}
+{%- endblock button_attributes %}
diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig
index aed4f8d770..d3d7a34f40 100644
--- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig
+++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig
@@ -1,52 +1,44 @@
{% use "form_div_layout.html.twig" %}
-{% block form_row %}
-{% spaceless %}
+{% block form_row -%}
- {{ form_label(form) }}
+ {{- form_label(form) -}}
- {{ form_errors(form) }}
- {{ form_widget(form) }}
+ {{- form_errors(form) -}}
+ {{- form_widget(form) -}}
-{% endspaceless %}
-{% endblock form_row %}
+{%- endblock form_row %}
-{% block button_row %}
-{% spaceless %}
+{% block button_row -%}
- {{ form_widget(form) }}
+ {{- form_widget(form) -}}
-{% endspaceless %}
-{% endblock button_row %}
+{%- endblock button_row %}
-{% block hidden_row %}
-{% spaceless %}
+{% block hidden_row -%}
- {{ form_widget(form) }}
+ {{- form_widget(form) -}}
-{% endspaceless %}
-{% endblock hidden_row %}
+{%- endblock hidden_row %}
-{% block form_widget_compound %}
-{% spaceless %}
+{% block form_widget_compound -%}
- {% if form.parent is empty and errors|length > 0 %}
+ {% if form.parent is empty and errors|length > 0 -%}
- {{ form_errors(form) }}
+ {{- form_errors(form) -}}
- {% endif %}
- {{ block('form_rows') }}
- {{ form_rest(form) }}
+ {%- endif %}
+ {{- block('form_rows') -}}
+ {{- form_rest(form) -}}
-{% endspaceless %}
-{% endblock form_widget_compound %}
+{%- endblock form_widget_compound %}
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php
index 614f6dbc2d..9f2012816a 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php
@@ -45,7 +45,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
{
$translator = $this->getTranslator($this->getLoader());
$translator->setLocale('fr');
- $translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR'));
+ $translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR', 'fr.UTF-8', 'sr@latin'));
$this->assertEquals('foo (FR)', $translator->trans('foo'));
$this->assertEquals('bar (EN)', $translator->trans('bar'));
@@ -54,6 +54,8 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('no translation', $translator->trans('no translation'));
$this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo'));
$this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1));
+ $this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz'));
+ $this->assertEquals('foobarbax (sr@latin)', $translator->trans('foobarbax'));
}
public function testTransWithCaching()
@@ -61,7 +63,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
// prime the cache
$translator = $this->getTranslator($this->getLoader(), array('cache_dir' => $this->tmpDir));
$translator->setLocale('fr');
- $translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR'));
+ $translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR', 'fr.UTF-8', 'sr@latin'));
$this->assertEquals('foo (FR)', $translator->trans('foo'));
$this->assertEquals('bar (EN)', $translator->trans('bar'));
@@ -70,12 +72,14 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('no translation', $translator->trans('no translation'));
$this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo'));
$this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1));
+ $this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz'));
+ $this->assertEquals('foobarbax (sr@latin)', $translator->trans('foobarbax'));
// do it another time as the cache is primed now
$loader = $this->getMock('Symfony\Component\Translation\Loader\LoaderInterface');
$translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir));
$translator->setLocale('fr');
- $translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR'));
+ $translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR', 'fr.UTF-8', 'sr@latin'));
$this->assertEquals('foo (FR)', $translator->trans('foo'));
$this->assertEquals('bar (EN)', $translator->trans('bar'));
@@ -84,6 +88,8 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('no translation', $translator->trans('no translation'));
$this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo'));
$this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1));
+ $this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz'));
+ $this->assertEquals('foobarbax (sr@latin)', $translator->trans('foobarbax'));
}
public function testGetLocale()
@@ -175,6 +181,20 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
'other choice' => '{0} other choice 0 (PT-BR)|{1} other choice 1 (PT-BR)|]1,Inf] other choice inf (PT-BR)',
))))
;
+ $loader
+ ->expects($this->at(5))
+ ->method('load')
+ ->will($this->returnValue($this->getCatalogue('fr.UTF-8', array(
+ 'foobarbaz' => 'foobarbaz (fr.UTF-8)',
+ ))))
+ ;
+ $loader
+ ->expects($this->at(6))
+ ->method('load')
+ ->will($this->returnValue($this->getCatalogue('sr@latin', array(
+ 'foobarbax' => 'foobarbax (sr@latin)',
+ ))))
+ ;
return $loader;
}
@@ -205,6 +225,8 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
$translator->addResource('loader', 'foo', 'es');
$translator->addResource('loader', 'foo', 'pt-PT'); // European Portuguese
$translator->addResource('loader', 'foo', 'pt_BR'); // Brazilian Portuguese
+ $translator->addResource('loader', 'foo', 'fr.UTF-8');
+ $translator->addResource('loader', 'foo', 'sr@latin'); // Latin Serbian
return $translator;
}
diff --git a/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php b/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php
index 1705c1ac06..0f99f6428e 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php
@@ -97,8 +97,10 @@ class Translator extends BaseTranslator
$fallbackContent = '';
$current = '';
+ $replacementPattern = '/[^a-z0-9_]/i';
foreach ($this->computeFallbackLocales($locale) as $fallback) {
- $fallbackSuffix = ucfirst(str_replace('-', '_', $fallback));
+ $fallbackSuffix = ucfirst(preg_replace($replacementPattern, '_', $fallback));
+ $currentSuffix = ucfirst(preg_replace($replacementPattern, '_', $current));
$fallbackContent .= sprintf(<<catalogues[$fallback]->all(), true),
- ucfirst(str_replace('-', '_', $current)),
+ $currentSuffix,
$fallbackSuffix
);
$current = $fallback;
diff --git a/src/Symfony/Component/BrowserKit/Client.php b/src/Symfony/Component/BrowserKit/Client.php
index a69e1625c0..6e91305a74 100644
--- a/src/Symfony/Component/BrowserKit/Client.php
+++ b/src/Symfony/Component/BrowserKit/Client.php
@@ -304,7 +304,7 @@ abstract class Client
$uri = $this->getAbsoluteUri($uri);
if (isset($server['HTTP_HOST'])) {
- $uri = preg_replace('{^(https?\://)'.parse_url($uri, PHP_URL_HOST).'}', '${1}'.$server['HTTP_HOST'], $uri);
+ $uri = preg_replace('{^(https?\://)'.preg_quote($this->extractHost($uri)).'}', '${1}'.$server['HTTP_HOST'], $uri);
}
if (isset($server['HTTPS'])) {
@@ -317,12 +317,7 @@ abstract class Client
$server['HTTP_REFERER'] = $this->history->current()->getUri();
}
- $server['HTTP_HOST'] = parse_url($uri, PHP_URL_HOST);
-
- if ($port = parse_url($uri, PHP_URL_PORT)) {
- $server['HTTP_HOST'] .= ':'.$port;
- }
-
+ $server['HTTP_HOST'] = $this->extractHost($uri);
$server['HTTPS'] = 'https' == parse_url($uri, PHP_URL_SCHEME);
$this->internalRequest = new Request($uri, $method, $parameters, $files, $this->cookieJar->allValues($uri), $server, $content);
@@ -619,4 +614,15 @@ abstract class Client
return $server;
}
+
+ private function extractHost($uri)
+ {
+ $host = parse_url($uri, PHP_URL_HOST);
+
+ if ($port = parse_url($uri, PHP_URL_PORT)) {
+ return $host.':'.$port;
+ }
+
+ return $host;
+ }
}
diff --git a/src/Symfony/Component/BrowserKit/Tests/ClientTest.php b/src/Symfony/Component/BrowserKit/Tests/ClientTest.php
index 2612bb26c4..cd37fcf76a 100644
--- a/src/Symfony/Component/BrowserKit/Tests/ClientTest.php
+++ b/src/Symfony/Component/BrowserKit/Tests/ClientTest.php
@@ -210,6 +210,24 @@ class ClientTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('http://www.example.com/foo/bar', $client->getRequest()->getUri(), '->request() uses the previous request for relative URLs');
}
+ public function testRequestURIConversionByServerHost()
+ {
+ $client = new TestClient();
+
+ $server = array('HTTP_HOST' => 'www.exampl+e.com:8000');
+ $parameters = array();
+ $files = array();
+
+ $client->request('GET', 'http://exampl+e.com', $parameters, $files, $server);
+ $this->assertEquals('http://www.exampl+e.com:8000', $client->getRequest()->getUri(), '->request() uses HTTP_HOST to add port');
+
+ $client->request('GET', 'http://exampl+e.com:8888', $parameters, $files, $server);
+ $this->assertEquals('http://www.exampl+e.com:8000', $client->getRequest()->getUri(), '->request() uses HTTP_HOST to modify existing port');
+
+ $client->request('GET', 'http://exampl+e.com:8000', $parameters, $files, $server);
+ $this->assertEquals('http://www.exampl+e.com:8000', $client->getRequest()->getUri(), '->request() uses HTTP_HOST respects correct set port');
+ }
+
public function testRequestReferer()
{
$client = new TestClient();
diff --git a/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php
index 5ee1b7eea0..1ffe142af4 100644
--- a/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php
+++ b/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php
@@ -17,6 +17,7 @@ use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
+use Symfony\Component\DependencyInjection\Scope;
/**
* GraphvizDumper dumps a service container as a graphviz file.
@@ -193,8 +194,8 @@ class GraphvizDumper extends Dumper
$container->setDefinitions($this->container->getDefinitions());
$container->setAliases($this->container->getAliases());
$container->setResources($this->container->getResources());
- foreach ($this->container->getScopes() as $scope) {
- $container->addScope($scope);
+ foreach ($this->container->getScopes() as $scope => $parentScope) {
+ $container->addScope(new Scope($scope, $parentScope));
}
foreach ($this->container->getExtensions() as $extension) {
$container->registerExtension($extension);
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/GraphvizDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/GraphvizDumperTest.php
index 0dc1ce8de1..3db11504ad 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/GraphvizDumperTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/GraphvizDumperTest.php
@@ -62,4 +62,11 @@ class GraphvizDumperTest extends \PHPUnit_Framework_TestCase
$dumper = new GraphvizDumper($container);
$this->assertEquals(str_replace('%path%', __DIR__, file_get_contents(self::$fixturesPath.'/graphviz/services14.dot')), $dumper->dump(), '->dump() dumps services');
}
+
+ public function testDumpWithScopes()
+ {
+ $container = include self::$fixturesPath.'/containers/container18.php';
+ $dumper = new GraphvizDumper($container);
+ $this->assertEquals(str_replace('%path%', __DIR__, file_get_contents(self::$fixturesPath.'/graphviz/services18.dot')), $dumper->dump(), '->dump() dumps services');
+ }
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container18.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container18.php
new file mode 100644
index 0000000000..0248ed4627
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container18.php
@@ -0,0 +1,14 @@
+addScope(new Scope('request'));
+$container->
+ register('foo', 'FooClass')->
+ setScope('request')
+;
+$container->compile();
+
+return $container;
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/graphviz/services18.dot b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/graphviz/services18.dot
new file mode 100644
index 0000000000..4fbcceef36
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/graphviz/services18.dot
@@ -0,0 +1,8 @@
+digraph sc {
+ ratio="compress"
+ node [fontsize="11" fontname="Arial" shape="record"];
+ edge [fontsize="9" fontname="Arial" color="grey" arrowhead="open" arrowsize="0.5"];
+
+ node_foo [label="foo\nFooClass\n", shape=record, fillcolor="#eeeeee", style="filled"];
+ node_service_container [label="service_container\nSymfony\\Component\\DependencyInjection\\ContainerBuilder\n", shape=record, fillcolor="#9999ff", style="filled"];
+}
diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php
index 56a3ca928b..e992b01fc1 100644
--- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php
+++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php
@@ -152,6 +152,8 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer
* Returns a preconfigured IntlDateFormatter instance
*
* @return \IntlDateFormatter
+ *
+ * @throws TransformationFailedException in case the date formatter can not be constructed.
*/
protected function getIntlDateFormatter()
{
@@ -162,6 +164,12 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer
$pattern = $this->pattern;
$intlDateFormatter = new \IntlDateFormatter(\Locale::getDefault(), $dateFormat, $timeFormat, $timezone, $calendar, $pattern);
+
+ // new \intlDateFormatter may return null instead of false in case of failure, see https://bugs.php.net/bug.php?id=66323
+ if (!$intlDateFormatter) {
+ throw new TransformationFailedException(intl_get_error_message(), intl_get_error_code());
+ }
+
$intlDateFormatter->setLenient(false);
return $intlDateFormatter;
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php
index 022a414932..1748d2e31f 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php
@@ -77,6 +77,12 @@ class DateType extends AbstractType
$calendar,
$pattern
);
+
+ // new \intlDateFormatter may return null instead of false in case of failure, see https://bugs.php.net/bug.php?id=66323
+ if (!$formatter) {
+ throw new InvalidOptionsException(intl_get_error_message(), intl_get_error_code());
+ }
+
$formatter->setLenient(false);
if ('choice' === $options['widget']) {
diff --git a/src/Symfony/Component/HttpFoundation/JsonResponse.php b/src/Symfony/Component/HttpFoundation/JsonResponse.php
index 15e7b582f7..83a607cd6e 100644
--- a/src/Symfony/Component/HttpFoundation/JsonResponse.php
+++ b/src/Symfony/Component/HttpFoundation/JsonResponse.php
@@ -111,7 +111,7 @@ class JsonResponse extends Response
// Not using application/javascript for compatibility reasons with older browsers.
$this->headers->set('Content-Type', 'text/javascript');
- return $this->setContent(sprintf('%s(%s);', $this->callback, $this->data));
+ return $this->setContent(sprintf('/**/%s(%s);', $this->callback, $this->data));
}
// Only set the header when there is none or when it equals 'text/javascript' (from a previous update with callback)
diff --git a/src/Symfony/Component/HttpFoundation/Tests/JsonResponseTest.php b/src/Symfony/Component/HttpFoundation/Tests/JsonResponseTest.php
index ef392ca59d..2cb6b68550 100644
--- a/src/Symfony/Component/HttpFoundation/Tests/JsonResponseTest.php
+++ b/src/Symfony/Component/HttpFoundation/Tests/JsonResponseTest.php
@@ -155,7 +155,7 @@ class JsonResponseTest extends \PHPUnit_Framework_TestCase
{
$response = JsonResponse::create(array('foo' => 'bar'))->setCallback('callback');
- $this->assertEquals('callback({"foo":"bar"});', $response->getContent());
+ $this->assertEquals('/**/callback({"foo":"bar"});', $response->getContent());
$this->assertEquals('text/javascript', $response->headers->get('Content-Type'));
}
diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php
index 3718155ade..e14f57967b 100644
--- a/src/Symfony/Component/HttpKernel/Kernel.php
+++ b/src/Symfony/Component/HttpKernel/Kernel.php
@@ -59,11 +59,11 @@ abstract class Kernel implements KernelInterface, TerminableInterface
protected $startTime;
protected $loadClassCache;
- const VERSION = '2.3.18-DEV';
- const VERSION_ID = '20318';
+ const VERSION = '2.3.19-DEV';
+ const VERSION_ID = '20319';
const MAJOR_VERSION = '2';
const MINOR_VERSION = '3';
- const RELEASE_VERSION = '18';
+ const RELEASE_VERSION = '19';
const EXTRA_VERSION = 'DEV';
/**
diff --git a/src/Symfony/Component/Process/ExecutableFinder.php b/src/Symfony/Component/Process/ExecutableFinder.php
index 44f1605fa9..6ae74dca17 100644
--- a/src/Symfony/Component/Process/ExecutableFinder.php
+++ b/src/Symfony/Component/Process/ExecutableFinder.php
@@ -59,8 +59,7 @@ class ExecutableFinder
if (is_dir($path)) {
$dirs[] = $path;
} else {
- $file = str_replace(dirname($path), '', $path);
- if ($file == $name && is_executable($path)) {
+ if (basename($path) == $name && is_executable($path)) {
return $path;
}
}
diff --git a/src/Symfony/Component/Process/Process.php b/src/Symfony/Component/Process/Process.php
index 2ccad53d77..3185633fbf 100644
--- a/src/Symfony/Component/Process/Process.php
+++ b/src/Symfony/Component/Process/Process.php
@@ -64,6 +64,8 @@ class Process
/** @var ProcessPipes */
private $processPipes;
+ private $latestSignal;
+
private static $sigchild;
/**
@@ -321,7 +323,7 @@ class Process
usleep(1000);
}
- if ($this->processInformation['signaled']) {
+ if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) {
throw new RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig']));
}
@@ -661,7 +663,8 @@ class Process
throw new RuntimeException('Unable to kill the process');
}
}
- proc_terminate($this->process);
+ // given `SIGTERM` may not be defined and that `proc_terminate` uses the constant value and not the constant itself, we use the same here
+ $this->doSignal(15, false);
do {
usleep(1000);
} while ($this->isRunning() && microtime(true) < $timeoutMicro);
@@ -1158,6 +1161,7 @@ class Process
$this->stdout = null;
$this->stderr = null;
$this->process = null;
+ $this->latestSignal = null;
$this->status = self::STATUS_READY;
$this->incrementalOutputOffset = 0;
$this->incrementalErrorOutputOffset = 0;
@@ -1201,6 +1205,8 @@ class Process
return false;
}
+ $this->latestSignal = $signal;
+
return true;
}
diff --git a/src/Symfony/Component/Process/ProcessPipes.php b/src/Symfony/Component/Process/ProcessPipes.php
index f35d1c1287..37ab8ca83c 100644
--- a/src/Symfony/Component/Process/ProcessPipes.php
+++ b/src/Symfony/Component/Process/ProcessPipes.php
@@ -284,6 +284,8 @@ class ProcessPipes
private function readStreams($blocking, $close = false)
{
if (empty($this->pipes)) {
+ usleep(Process::TIMEOUT_PRECISION * 1E4);
+
return array();
}
@@ -313,11 +315,11 @@ class ProcessPipes
$type = array_search($pipe, $this->pipes);
$data = '';
- while ($dataread = fread($pipe, self::CHUNK_SIZE)) {
+ while ('' !== $dataread = (string) fread($pipe, self::CHUNK_SIZE)) {
$data .= $dataread;
}
- if ($data) {
+ if ('' !== $data) {
$read[$type] = $data;
}
diff --git a/src/Symfony/Component/Process/Tests/AbstractProcessTest.php b/src/Symfony/Component/Process/Tests/AbstractProcessTest.php
index 91b8aca20d..25de48eb14 100644
--- a/src/Symfony/Component/Process/Tests/AbstractProcessTest.php
+++ b/src/Symfony/Component/Process/Tests/AbstractProcessTest.php
@@ -287,6 +287,19 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
}
}
+ public function testZeroAsOutput()
+ {
+ if (defined('PHP_WINDOWS_VERSION_BUILD')) {
+ // see http://stackoverflow.com/questions/7105433/windows-batch-echo-without-new-line
+ $p = $this->getProcess('echo | set /p dummyName=0');
+ } else {
+ $p = $this->getProcess('printf 0');
+ }
+
+ $p->run();
+ $this->assertSame('0', $p->getOutput());
+ }
+
public function testExitCodeCommandFailed()
{
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
@@ -583,7 +596,14 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
}
$duration = microtime(true) - $start;
- $this->assertLessThan($timeout + Process::TIMEOUT_PRECISION, $duration);
+ if (defined('PHP_WINDOWS_VERSION_BUILD')) {
+ // Windows is a bit slower as it read file handles, then allow twice the precision
+ $maxDuration = $timeout + 2 * Process::TIMEOUT_PRECISION;
+ } else {
+ $maxDuration = $timeout + Process::TIMEOUT_PRECISION;
+ }
+
+ $this->assertLessThan($maxDuration, $duration);
}
public function testCheckTimeoutOnNonStartedProcess()
diff --git a/src/Symfony/Component/Process/Tests/ExecutableFinderTest.php b/src/Symfony/Component/Process/Tests/ExecutableFinderTest.php
index abaa47d8dd..3b5bff26e9 100644
--- a/src/Symfony/Component/Process/Tests/ExecutableFinderTest.php
+++ b/src/Symfony/Component/Process/Tests/ExecutableFinderTest.php
@@ -47,9 +47,9 @@ class ExecutableFinderTest extends \PHPUnit_Framework_TestCase
$this->setPath(dirname(PHP_BINARY));
$finder = new ExecutableFinder;
- $result = $finder->find(basename(PHP_BINARY));
+ $result = $finder->find($this->getPhpBinaryName());
- $this->assertEquals($result, PHP_BINARY);
+ $this->assertSamePath(PHP_BINARY, $result);
}
public function testFindWithDefault()
@@ -83,9 +83,9 @@ class ExecutableFinderTest extends \PHPUnit_Framework_TestCase
$extraDirs = array(dirname(PHP_BINARY));
$finder = new ExecutableFinder;
- $result = $finder->find(basename(PHP_BINARY), null, $extraDirs);
+ $result = $finder->find($this->getPhpBinaryName(), null, $extraDirs);
- $this->assertEquals(PHP_BINARY, $result);
+ $this->assertSamePath(PHP_BINARY, $result);
}
public function testFindWithOpenBaseDir()
@@ -105,8 +105,43 @@ class ExecutableFinderTest extends \PHPUnit_Framework_TestCase
ini_set('open_basedir', dirname(PHP_BINARY).PATH_SEPARATOR.'/');
$finder = new ExecutableFinder;
- $result = $finder->find(basename(PHP_BINARY));
+ $result = $finder->find($this->getPhpBinaryName());
- $this->assertEquals(PHP_BINARY, $result);
+ $this->assertSamePath(PHP_BINARY, $result);
+ }
+
+ public function testFindProcessInOpenBasedir()
+ {
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+
+ if (!defined('PHP_BINARY')) {
+ $this->markTestSkipped('Requires the PHP_BINARY constant');
+ }
+
+ $execPath = __DIR__.'/SignalListener.php';
+
+ $this->setPath('');
+ ini_set('open_basedir', PHP_BINARY.PATH_SEPARATOR.'/');
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find($this->getPhpBinaryName(), false);
+
+ $this->assertSamePath(PHP_BINARY, $result);
+ }
+
+ private function assertSamePath($expected, $tested)
+ {
+ if (defined('PHP_WINDOWS_VERSION_BUILD')) {
+ $this->assertEquals(strtolower($expected), strtolower($tested));
+ } else {
+ $this->assertEquals($expected, $tested);
+ }
+ }
+
+ private function getPhpBinaryName()
+ {
+ return basename(PHP_BINARY, defined('PHP_WINDOWS_VERSION_BUILD') ? '.exe' : '');
}
}
diff --git a/src/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php b/src/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php
index 798e66a571..383a532203 100644
--- a/src/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php
+++ b/src/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php
@@ -211,6 +211,11 @@ class SigchildDisabledProcessTest extends AbstractProcessTest
$this->markTestSkipped('Signal is not supported in sigchild environment');
}
+ public function testRunProcessWithTimeout()
+ {
+ $this->markTestSkipped('Signal (required for timeout) is not supported in sigchild environment');
+ }
+
/**
* {@inheritdoc}
*/
diff --git a/src/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php b/src/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php
index 65dd4bb573..37a064188f 100644
--- a/src/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php
+++ b/src/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php
@@ -120,6 +120,21 @@ class SigchildEnabledProcessTest extends AbstractProcessTest
parent::testStartAfterATimeout();
}
+ public function testStopWithTimeoutIsActuallyWorking()
+ {
+ $this->markTestSkipped('Stopping with signal is not supported in sigchild environment');
+ }
+
+ public function testRunProcessWithTimeout()
+ {
+ $this->markTestSkipped('Signal (required for timeout) is not supported in sigchild environment');
+ }
+
+ public function testCheckTimeoutOnStartedProcess()
+ {
+ $this->markTestSkipped('Signal (required for timeout) is not supported in sigchild environment');
+ }
+
/**
* {@inheritdoc}
*/
diff --git a/src/Symfony/Component/Process/Tests/SimpleProcessTest.php b/src/Symfony/Component/Process/Tests/SimpleProcessTest.php
index 69ad3d5b09..ccaa78df24 100644
--- a/src/Symfony/Component/Process/Tests/SimpleProcessTest.php
+++ b/src/Symfony/Component/Process/Tests/SimpleProcessTest.php
@@ -147,6 +147,57 @@ class SimpleProcessTest extends AbstractProcessTest
parent::testSignalWithWrongNonIntSignal();
}
+ public function testStopTerminatesProcessCleanly()
+ {
+ try {
+ $process = $this->getProcess('php -r "echo \'foo\'; sleep(1); echo \'bar\';"');
+ $process->run(function () use ($process) {
+ $process->stop();
+ });
+ } catch (RuntimeException $e) {
+ $this->fail('A call to stop() is not expected to cause wait() to throw a RuntimeException');
+ }
+ }
+
+ public function testKillSignalTerminatesProcessCleanly()
+ {
+ $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.');
+
+ try {
+ $process = $this->getProcess('php -r "echo \'foo\'; sleep(1); echo \'bar\';"');
+ $process->run(function () use ($process) {
+ if ($process->isRunning()) {
+ $process->signal(SIGKILL);
+ }
+ });
+ } catch (RuntimeException $e) {
+ $this->fail('A call to signal() is not expected to cause wait() to throw a RuntimeException');
+ }
+ }
+
+ public function testTermSignalTerminatesProcessCleanly()
+ {
+ $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.');
+
+ try {
+ $process = $this->getProcess('php -r "echo \'foo\'; sleep(1); echo \'bar\';"');
+ $process->run(function () use ($process) {
+ if ($process->isRunning()) {
+ $process->signal(SIGTERM);
+ }
+ });
+ } catch (RuntimeException $e) {
+ $this->fail('A call to signal() is not expected to cause wait() to throw a RuntimeException');
+ }
+ }
+
+ public function testStopWithTimeoutIsActuallyWorking()
+ {
+ $this->skipIfPHPSigchild();
+
+ parent::testStopWithTimeoutIsActuallyWorking();
+ }
+
/**
* {@inheritdoc}
*/
diff --git a/src/Symfony/Component/Translation/Tests/TranslatorTest.php b/src/Symfony/Component/Translation/Tests/TranslatorTest.php
index e45ab3b481..cf9c8df659 100644
--- a/src/Symfony/Component/Translation/Tests/TranslatorTest.php
+++ b/src/Symfony/Component/Translation/Tests/TranslatorTest.php
@@ -17,6 +17,33 @@ use Symfony\Component\Translation\Loader\ArrayLoader;
class TranslatorTest extends \PHPUnit_Framework_TestCase
{
+
+ /**
+ * @dataProvider getInvalidLocalesTests
+ * @expectedException \InvalidArgumentException
+ */
+ public function testConstructorInvalidLocale($locale)
+ {
+ $translator = new Translator($locale, new MessageSelector());
+ }
+
+ /**
+ * @dataProvider getValidLocalesTests
+ */
+ public function testConstructorValidLocale($locale)
+ {
+ $translator = new Translator($locale, new MessageSelector());
+
+ $this->assertEquals($locale, $translator->getLocale());
+ }
+
+ public function testConstructorWithoutLocale()
+ {
+ $translator = new Translator(null, new MessageSelector());
+
+ $this->assertNull($translator->getLocale());
+ }
+
public function testSetGetLocale()
{
$translator = new Translator('en', new MessageSelector());
@@ -27,6 +54,27 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('fr', $translator->getLocale());
}
+ /**
+ * @dataProvider getInvalidLocalesTests
+ * @expectedException \InvalidArgumentException
+ */
+ public function testSetInvalidLocale($locale)
+ {
+ $translator = new Translator('fr', new MessageSelector());
+ $translator->setLocale($locale);
+ }
+
+ /**
+ * @dataProvider getValidLocalesTests
+ */
+ public function testSetValidLocale($locale)
+ {
+ $translator = new Translator($locale, new MessageSelector());
+ $translator->setLocale($locale);
+
+ $this->assertEquals($locale, $translator->getLocale());
+ }
+
public function testSetFallbackLocales()
{
$translator = new Translator('en', new MessageSelector());
@@ -55,6 +103,26 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('bar (fr)', $translator->trans('bar'));
}
+ /**
+ * @dataProvider getInvalidLocalesTests
+ * @expectedException \InvalidArgumentException
+ */
+ public function testSetFallbackInvalidLocales($locale)
+ {
+ $translator = new Translator('fr', new MessageSelector());
+ $translator->setFallbackLocales(array('fr', $locale));
+ }
+
+ /**
+ * @dataProvider getValidLocalesTests
+ */
+ public function testSetFallbackValidLocales($locale)
+ {
+ $translator = new Translator($locale, new MessageSelector());
+ $translator->setFallbackLocales(array('fr', $locale));
+ // no assertion. this method just asserts that no exception is thrown
+ }
+
public function testTransWithFallbackLocale()
{
$translator = new Translator('fr_FR', new MessageSelector());
@@ -67,6 +135,26 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('foobar', $translator->trans('bar'));
}
+ /**
+ * @dataProvider getInvalidLocalesTests
+ * @expectedException \InvalidArgumentException
+ */
+ public function testAddResourceInvalidLocales($locale)
+ {
+ $translator = new Translator('fr', new MessageSelector());
+ $translator->addResource('array', array('foo' => 'foofoo'), $locale);
+ }
+
+ /**
+ * @dataProvider getValidLocalesTests
+ */
+ public function testAddResourceValidLocales($locale)
+ {
+ $translator = new Translator('fr', new MessageSelector());
+ $translator->addResource('array', array('foo' => 'foofoo'), $locale);
+ // no assertion. this method just asserts that no exception is thrown
+ }
+
public function testAddResourceAfterTrans()
{
$translator = new Translator('fr', new MessageSelector());
@@ -164,6 +252,32 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($expected, $translator->trans($id, $parameters, $domain, $locale));
}
+ /**
+ * @dataProvider getInvalidLocalesTests
+ * @expectedException \InvalidArgumentException
+ */
+ public function testTransInvalidLocale($locale)
+ {
+ $translator = new Translator('en', new MessageSelector());
+ $translator->addLoader('array', new ArrayLoader());
+ $translator->addResource('array', array('foo' => 'foofoo'), 'en');
+
+ $translator->trans('foo', array(), '', $locale);
+ }
+
+ /**
+ * @dataProvider getValidLocalesTests
+ */
+ public function testTransValidLocale($locale)
+ {
+ $translator = new Translator('en', new MessageSelector());
+ $translator->addLoader('array', new ArrayLoader());
+ $translator->addResource('array', array('foo' => 'foofoo'), 'en');
+
+ $translator->trans('foo', array(), '', $locale);
+ // no assertion. this method just asserts that no exception is thrown
+ }
+
/**
* @dataProvider getFlattenedTransTests
*/
@@ -188,6 +302,32 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($expected, $translator->transChoice($id, $number, $parameters, $domain, $locale));
}
+ /**
+ * @dataProvider getInvalidLocalesTests
+ * @expectedException \InvalidArgumentException
+ */
+ public function testTransChoiceInvalidLocale($locale)
+ {
+ $translator = new Translator('en', new MessageSelector());
+ $translator->addLoader('array', new ArrayLoader());
+ $translator->addResource('array', array('foo' => 'foofoo'), 'en');
+
+ $translator->transChoice('foo', 1, array(), '', $locale);
+ }
+
+ /**
+ * @dataProvider getValidLocalesTests
+ */
+ public function testTransChoiceValidLocale($locale)
+ {
+ $translator = new Translator('en', new MessageSelector());
+ $translator->addLoader('array', new ArrayLoader());
+ $translator->addResource('array', array('foo' => 'foofoo'), 'en');
+
+ $translator->transChoice('foo', 1, array(), '', $locale);
+ // no assertion. this method just asserts that no exception is thrown
+ }
+
public function getTransFileTests()
{
return array(
@@ -257,6 +397,40 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
);
}
+ public function getInvalidLocalesTests()
+ {
+ return array(
+ array('fr FR'),
+ array('français'),
+ array('fr+en'),
+ array('utf#8'),
+ array('fr&en'),
+ array('fr~FR'),
+ array(' fr'),
+ array('fr '),
+ array('fr*'),
+ array('fr/FR'),
+ array('fr\\FR'),
+ );
+ }
+
+ public function getValidLocalesTests()
+ {
+ return array(
+ array(''),
+ array(null),
+ array('fr'),
+ array('francais'),
+ array('FR'),
+ array('frFR'),
+ array('fr-FR'),
+ array('fr_FR'),
+ array('fr.FR'),
+ array('fr-FR.UTF8'),
+ array('sr@latin'),
+ );
+ }
+
public function testTransChoiceFallback()
{
$translator = new Translator('ru', new MessageSelector());
diff --git a/src/Symfony/Component/Translation/Translator.php b/src/Symfony/Component/Translation/Translator.php
index 8e74b79f60..0ed30526d2 100644
--- a/src/Symfony/Component/Translation/Translator.php
+++ b/src/Symfony/Component/Translation/Translator.php
@@ -59,11 +59,13 @@ class Translator implements TranslatorInterface
* @param string $locale The locale
* @param MessageSelector|null $selector The message selector for pluralization
*
+ * @throws \InvalidArgumentException If a locale contains invalid characters
+ *
* @api
*/
public function __construct($locale, MessageSelector $selector = null)
{
- $this->locale = $locale;
+ $this->setLocale($locale);
$this->selector = $selector ?: new MessageSelector();
}
@@ -88,6 +90,8 @@ class Translator implements TranslatorInterface
* @param string $locale The locale
* @param string $domain The domain
*
+ * @throws \InvalidArgumentException If the locale contains invalid characters
+ *
* @api
*/
public function addResource($format, $resource, $locale, $domain = null)
@@ -96,6 +100,8 @@ class Translator implements TranslatorInterface
$domain = 'messages';
}
+ $this->assertValidLocale($locale);
+
$this->resources[$locale][] = array($format, $resource, $domain);
if (in_array($locale, $this->fallbackLocales)) {
@@ -112,6 +118,7 @@ class Translator implements TranslatorInterface
*/
public function setLocale($locale)
{
+ $this->assertValidLocale($locale);
$this->locale = $locale;
}
@@ -130,6 +137,8 @@ class Translator implements TranslatorInterface
*
* @param string|array $locales The fallback locale(s)
*
+ * @throws \InvalidArgumentException If a locale contains invalid characters
+ *
* @deprecated since 2.3, to be removed in 3.0. Use setFallbackLocales() instead.
*
* @api
@@ -144,6 +153,8 @@ class Translator implements TranslatorInterface
*
* @param array $locales The fallback locales
*
+ * @throws \InvalidArgumentException If a locale contains invalid characters
+ *
* @api
*/
public function setFallbackLocales(array $locales)
@@ -151,6 +162,10 @@ class Translator implements TranslatorInterface
// needed as the fallback locales are linked to the already loaded catalogues
$this->catalogues = array();
+ foreach ($locales as $locale) {
+ $this->assertValidLocale($locale);
+ }
+
$this->fallbackLocales = $locales;
}
@@ -175,6 +190,8 @@ class Translator implements TranslatorInterface
{
if (null === $locale) {
$locale = $this->getLocale();
+ } else {
+ $this->assertValidLocale($locale);
}
if (null === $domain) {
@@ -197,6 +214,8 @@ class Translator implements TranslatorInterface
{
if (null === $locale) {
$locale = $this->getLocale();
+ } else {
+ $this->assertValidLocale($locale);
}
if (null === $domain) {
@@ -279,4 +298,18 @@ class Translator implements TranslatorInterface
return array_unique($locales);
}
+
+ /**
+ * Asserts that the locale is valid, throws an Exception if not.
+ *
+ * @param string $locale Locale to tests
+ *
+ * @throws \InvalidArgumentException If the locale contains invalid characters
+ */
+ private function assertValidLocale($locale)
+ {
+ if (1 !== preg_match('/^[a-z0-9@_\\.\\-]*$/i', $locale)) {
+ throw new \InvalidArgumentException(sprintf('Invalid "%s" locale.', $locale));
+ }
+ }
}
diff --git a/src/Symfony/Component/Translation/TranslatorInterface.php b/src/Symfony/Component/Translation/TranslatorInterface.php
index a97fd10b8a..436d48872e 100644
--- a/src/Symfony/Component/Translation/TranslatorInterface.php
+++ b/src/Symfony/Component/Translation/TranslatorInterface.php
@@ -28,6 +28,8 @@ interface TranslatorInterface
* @param string $domain The domain for the message
* @param string $locale The locale
*
+ * @throws \InvalidArgumentException If the locale contains invalid characters
+ *
* @return string The translated string
*
* @api
@@ -43,6 +45,8 @@ interface TranslatorInterface
* @param string $domain The domain for the message
* @param string $locale The locale
*
+ * @throws \InvalidArgumentException If the locale contains invalid characters
+ *
* @return string The translated string
*
* @api
@@ -54,6 +58,8 @@ interface TranslatorInterface
*
* @param string $locale The locale
*
+ * @throws \InvalidArgumentException If the locale contains invalid characters
+ *
* @api
*/
public function setLocale($locale);
diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf
index 3b8b5d97a8..756b80b37f 100644
--- a/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf
+++ b/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf
@@ -176,7 +176,7 @@
This value should be the user current password.
- This value should be the user current password.
+ This value should be the user's current password.
This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters.
diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/Entity.php b/src/Symfony/Component/Validator/Tests/Fixtures/Entity.php
index e1cb3e0490..f9e9e7ff3b 100644
--- a/src/Symfony/Component/Validator/Tests/Fixtures/Entity.php
+++ b/src/Symfony/Component/Validator/Tests/Fixtures/Entity.php
@@ -35,6 +35,7 @@ class Entity extends EntityParent implements EntityInterface
public $reference;
private $internal;
public $data = 'Overridden data';
+ public $initialized = false;
public function __construct($internal = null)
{
diff --git a/src/Symfony/Component/Validator/Tests/ValidationVisitorTest.php b/src/Symfony/Component/Validator/Tests/ValidationVisitorTest.php
index 2868f57a82..f2838737a2 100644
--- a/src/Symfony/Component/Validator/Tests/ValidationVisitorTest.php
+++ b/src/Symfony/Component/Validator/Tests/ValidationVisitorTest.php
@@ -11,6 +11,8 @@
namespace Symfony\Component\Validator\Tests;
+use Symfony\Component\Validator\Constraints\Callback;
+use Symfony\Component\Validator\ExecutionContextInterface;
use Symfony\Component\Validator\Tests\Fixtures\FakeMetadataFactory;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Tests\Fixtures\Reference;
@@ -561,4 +563,50 @@ class ValidationVisitorTest extends \PHPUnit_Framework_TestCase
$this->visitor->validate($entity, 'Default', '');
}
+
+ public function testInitializeObjectsOnFirstValidation()
+ {
+ $test = $this;
+ $entity = new Entity();
+ $entity->initialized = false;
+
+ // prepare initializers that set "initialized" to true
+ $initializer1 = $this->getMock('Symfony\\Component\\Validator\\ObjectInitializerInterface');
+ $initializer2 = $this->getMock('Symfony\\Component\\Validator\\ObjectInitializerInterface');
+
+ $initializer1->expects($this->once())
+ ->method('initialize')
+ ->with($entity)
+ ->will($this->returnCallback(function ($object) {
+ $object->initialized = true;
+ }));
+
+ $initializer2->expects($this->once())
+ ->method('initialize')
+ ->with($entity);
+
+ $this->visitor = new ValidationVisitor('Root', $this->metadataFactory, new ConstraintValidatorFactory(), new DefaultTranslator(), null, array(
+ $initializer1,
+ $initializer2
+ ));
+
+ // prepare constraint which
+ // * checks that "initialized" is set to true
+ // * validates the object again
+ $callback = function ($object, ExecutionContextInterface $context) use ($test) {
+ $test->assertTrue($object->initialized);
+
+ // validate again in same group
+ $context->validate($object);
+
+ // validate again in other group
+ $context->validate($object, '', 'SomeGroup');
+ };
+
+ $this->metadata->addConstraint(new Callback(array($callback)));
+
+ $this->visitor->validate($entity, 'Default', '');
+
+ $this->assertTrue($entity->initialized);
+ }
}
diff --git a/src/Symfony/Component/Validator/ValidationVisitor.php b/src/Symfony/Component/Validator/ValidationVisitor.php
index ddff8adc60..510c61e2eb 100644
--- a/src/Symfony/Component/Validator/ValidationVisitor.php
+++ b/src/Symfony/Component/Validator/ValidationVisitor.php
@@ -127,16 +127,19 @@ class ValidationVisitor implements ValidationVisitorInterface, GlobalExecutionCo
return;
}
+ // Initialize if the object wasn't initialized before
+ if (!isset($this->validatedObjects[$hash])) {
+ foreach ($this->objectInitializers as $initializer) {
+ if (!$initializer instanceof ObjectInitializerInterface) {
+ throw new \LogicException('Validator initializers must implement ObjectInitializerInterface.');
+ }
+ $initializer->initialize($value);
+ }
+ }
+
// Remember validating this object before starting and possibly
// traversing the object graph
$this->validatedObjects[$hash][$group] = true;
-
- foreach ($this->objectInitializers as $initializer) {
- if (!$initializer instanceof ObjectInitializerInterface) {
- throw new \LogicException('Validator initializers must implement ObjectInitializerInterface.');
- }
- $initializer->initialize($value);
- }
}
// Validate arrays recursively by default, otherwise every driver needs