bug #11436 fix signal handling in wait() on calls to stop() (xabbuh, romainneutron)

This PR was merged into the 2.3 branch.

Discussion
----------

fix signal handling in wait() on calls to stop()

| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #11286
| License       | MIT
| Doc PR        |

``wait()`` throws an exception when the process was terminated by a signal. This should not happen when the termination was requested by calling the ``stop()`` method (for example, inside a callback which is passed to ``wait()``).

Commits
-------

5939d34 [Process] Fix unit tests in sigchild environment
eb68662 [Process] fix signal handling in wait()
94ffc4f bug #11469  [BrowserKit] Fixed server HTTP_HOST port uri conversion (bcremer, fabpot)
103fd88 [BrowserKit] refactor code and fix unquoted regex
f401ab9 Fixed server HTTP_HOST port uri conversion
045cbc5 bug #11425 Fix issue described in #11421 (Ben, ben-rosio)
f5bfa9b bug #11423 Pass a Scope instance instead of a scope name when cloning a container in the GrahpvizDumper (jakzal)
3177be5 minor #11464 [Translator] Use quote to surround invalid locale (lyrixx)
c9742ef [Translator] Use quote to surround invalid locale
4dbe0e1 bug #11120 [2.3][Process] Reduce I/O load on Windows platform (romainneutron)
797d814 bug #11342 [2.3][Form] Check if IntlDateFormatter constructor returned a valid object before using it (romainneutron)
0b5348e minor #11441 [Translator] Optimize assertLocale regexp (Jérémy Derussé)
537c39b Optimize assertLocale regexp
4cf50e8 Bring code into standard
9f4313c [Process] Add test to verify fix for issue #11421
02eb765 [Process] Fixes issue #11421
6787669 [DependencyInjection] Pass a Scope instance instead of a scope name.
9572918 bug #11411 [Validator] Backported #11410 to 2.3: Object initializers are called only once per object (webmozart)
291cbf9 [Validator] Backported #11410 to 2.3: Object initializers are called only once per object
efab884 bug #11403 [Translator][FrameworkBundle] Added @ to the list of allowed chars in Translator (takeit)
3176f8b [Translator][FrameworkBundle] Added @ to the list of allowed chars in Translator
91e32f8 bug #11381 [2.3] [Process] Use correct test for empty string in UnixPipes (whs, romainneutron)
45df2f3 minor #11397 [2.3][Process] Fix unit tests on Windows platform (romainneutron)
cec0a45 [Process] Adjust PR #11264, make it Windows compatible and fix CS
d418935 [Process] Fix unit tests on Windows platform
ff0bb01 [Process] Reduce I/O load on Windows platform
ace5a29 bumped Symfony version to 2.3.19
75e07e6 updated VERSION for 2.3.18
4a12f4d update CONTRIBUTORS for 2.3.18
98b891d updated CHANGELOG for 2.3.18
06a80fb Validate locales sets intos translator
06fc97e feature #11367 [HttpFoundation] Fix to prevent magic bytes injection in JSONP responses... (CVE-2014-4671) (Andrew Moore)
3c54659 minor #11387 [2.3] [Validator] Fix UserPassword validator translation (redstar504)
73d50ed Fix UserPassword validator translation
93a970c bug #11386 Remove Spaceless Blocks from Twig Form Templates (chrisguitarguy)
8f9ed3e Remove Spaceless Blocks from Twig Form Templates
9e1ea4a [Process] Use correct test for empty string in UnixPipes
6af3d05 [HttpFoundation] Fix to prevent magic bytes injection in JSONP responses (Prevents CVE-2014-4671)
ebf967d [Form] Check if IntlDateFormatter constructor returned a valid object before using it
This commit is contained in:
Romain Neutron 2014-07-25 11:23:56 +02:00
commit c548bd861a
32 changed files with 744 additions and 327 deletions

View File

@ -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 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 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) * 2.3.17 (2014-07-07)
* bug #11238 [Translation] Added unescaping of ids in PoFileLoader (JustBlackBird) * bug #11238 [Translation] Added unescaping of ids in PoFileLoader (JustBlackBird)

View File

@ -19,8 +19,8 @@ Symfony2 is the result of the work of many people who made the code better
- Ryan Weaver (weaverryan) - Ryan Weaver (weaverryan)
- Lukas Kahwe Smith (lsmith) - Lukas Kahwe Smith (lsmith)
- Jeremy Mikola (jmikola) - Jeremy Mikola (jmikola)
- Jean-François Simon (jfsimon)
- Romain Neutron (romain) - Romain Neutron (romain)
- Jean-François Simon (jfsimon)
- Benjamin Eberlei (beberlei) - Benjamin Eberlei (beberlei)
- Igor Wiedler (igorw) - Igor Wiedler (igorw)
- Hugo Hamon (hhamon) - 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) - Wouter De Jong (wouterj)
- Eric Clemmons (ericclemmons) - Eric Clemmons (ericclemmons)
- Nicolas Grekas (nicolas-grekas) - Nicolas Grekas (nicolas-grekas)
- Andrej Hudec (pulzarraider)
- Deni - Deni
- Henrik Westphal (snc) - Henrik Westphal (snc)
- Dariusz Górecki (canni) - Dariusz Górecki (canni)
- Andrej Hudec (pulzarraider)
- Arnout Boks (aboks) - Arnout Boks (aboks)
- Christian Raue
- Michel Weimerskirch (mweimerskirch) - Michel Weimerskirch (mweimerskirch)
- Lee McDermott - Lee McDermott
- Brandon Turner - Brandon Turner
- Douglas Greenshields (shieldo) - Douglas Greenshields (shieldo)
- Daniel Holmes (dholmes) - Daniel Holmes (dholmes)
- Jordan Alliot (jalliot) - Jordan Alliot (jalliot)
- Christian Raue
- John Wards (johnwards) - John Wards (johnwards)
- Fran Moreno (franmomu) - Fran Moreno (franmomu)
- Bart van den Burg (burgov) - 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) - Fabien Pennequin (fabienpennequin)
- Jacob Dreesen (jdreesen) - Jacob Dreesen (jdreesen)
- Gábor Egyed (1ed) - Gábor Egyed (1ed)
- Ait Boudad Abdellatif (aitboudad)
- Adrien Brault (adrienbrault) - Adrien Brault (adrienbrault)
- Michal Piotrowski (eventhorizon) - Michal Piotrowski (eventhorizon)
- Ait Boudad Abdellatif (aitboudad)
- Robert Schönthal (digitalkaoz) - Robert Schönthal (digitalkaoz)
- Juti Noppornpitak (shiroyuki) - Juti Noppornpitak (shiroyuki)
- Sebastian Hörl (blogsh) - Sebastian Hörl (blogsh)
- Daniel Gomes (danielcsgomes) - Daniel Gomes (danielcsgomes)
- Hidenori Goto (hidenorigoto) - Hidenori Goto (hidenorigoto)
- Peter Kokot (maastermedia) - Peter Kokot (maastermedia)
- Christian Flothmann (xabbuh)
- Jérémie Augustin (jaugustin) - Jérémie Augustin (jaugustin)
- David Buchmann (dbu) - David Buchmann (dbu)
- Jérôme Tamarelle (gromnan) - Jérôme Tamarelle (gromnan)
- Tigran Azatyan (tigranazatyan) - Tigran Azatyan (tigranazatyan)
- Javier Eguiluz (javier.eguiluz) - Javier Eguiluz (javier.eguiluz)
- Christian Flothmann (xabbuh)
- Rafael Dohms (rdohms) - Rafael Dohms (rdohms)
- Richard Shank (iampersistent) - Richard Shank (iampersistent)
- Gordon Franke (gimler) - 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) - Manuel Reinhard (sprain)
- Danny Berger (dpb587) - Danny Berger (dpb587)
- Xavier Montaña Carreras (xmontana) - Xavier Montaña Carreras (xmontana)
- Michele Orselli (orso)
- Xavier Perez - Xavier Perez
- Arjen Brouwer (arjenjb) - Arjen Brouwer (arjenjb)
- Katsuhiro OGAWA - Katsuhiro OGAWA
@ -168,7 +169,6 @@ Symfony2 is the result of the work of many people who made the code better
- Robert Kiss (kepten) - Robert Kiss (kepten)
- Kim Hemsø Rasmussen (kimhemsoe) - Kim Hemsø Rasmussen (kimhemsoe)
- Wouter Van Hecke - Wouter Van Hecke
- Michele Orselli (orso)
- Michael Holm (hollo) - Michael Holm (hollo)
- Marc Weistroff (futurecat) - Marc Weistroff (futurecat)
- Pierre-Yves LEBECQ (pylebecq) - 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) - Ismael Ambrosi (iambrosi)
- Shein Alexey - Shein Alexey
- Issei Murasawa (issei_m) - Issei Murasawa (issei_m)
- hacfi (hifi)
- Joe Lencioni - Joe Lencioni
- Kai - Kai
- Xavier HAUSHERR - 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) - François-Xavier de Guillebon (de-gui_f)
- boombatower - boombatower
- Fabrice Bernhard (fabriceb) - Fabrice Bernhard (fabriceb)
- Clément Gautier (clementgautier)
- Fabian Lange (codingfabian) - Fabian Lange (codingfabian)
- Yoshio HANAWA - Yoshio HANAWA
- Baptiste Clavié (talus) - Baptiste Clavié (talus)
@ -294,11 +296,11 @@ Symfony2 is the result of the work of many people who made the code better
- Maks Slesarenko - Maks Slesarenko
- Markus Lanthaler (lanthaler) - Markus Lanthaler (lanthaler)
- Vicent Soria Durá (vicentgodella) - Vicent Soria Durá (vicentgodella)
- Chris Wilkinson (thewilkybarkid)
- Ioan Negulescu - Ioan Negulescu
- Andrew Udvare (audvare) - Andrew Udvare (audvare)
- alexpods - alexpods
- Erik Trapman (eriktrapman) - Erik Trapman (eriktrapman)
- hacfi (hifi)
- De Cock Xavier (xdecock) - De Cock Xavier (xdecock)
- Alex Pott - Alex Pott
- Norbert Orzechowicz (norzechowicz) - Norbert Orzechowicz (norzechowicz)
@ -365,6 +367,7 @@ Symfony2 is the result of the work of many people who made the code better
- Arturs Vonda - Arturs Vonda
- Sascha Grossenbacher - Sascha Grossenbacher
- Ben Davies (bendavies) - Ben Davies (bendavies)
- Simon Schick (simonsimcity)
- Hossein Bukhamsin - Hossein Bukhamsin
- Paweł Wacławczyk (pwc) - Paweł Wacławczyk (pwc)
- Oleg Zinchenko (cystbear) - 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) - Johannes Klauss (cloppy)
- Evan Villemez - Evan Villemez
- fzerorubigd - fzerorubigd
- Benjamin Grandfond (benjamin)
- Tiago Brito (blackmx) - Tiago Brito (blackmx)
- Richard van den Brand (ricbra) - Richard van den Brand (ricbra)
- develop - develop
@ -403,7 +407,6 @@ Symfony2 is the result of the work of many people who made the code better
- Marek Štípek (maryo) - Marek Štípek (maryo)
- John Bohn (jbohn) - John Bohn (jbohn)
- Jakub Škvára (jskvara) - Jakub Škvára (jskvara)
- Chris Wilkinson (thewilkybarkid)
- Andrew Hilobok (hilobok) - Andrew Hilobok (hilobok)
- Christian Soronellas (theunic) - Christian Soronellas (theunic)
- Jérôme Vieilledent (lolautruche) - 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 - jfcixmedia
- Martijn Evers - Martijn Evers
- Benjamin Paap (benjaminpaap) - Benjamin Paap (benjaminpaap)
- Simon Schick (simonsimcity)
- Christian - Christian
- Sergii Smertin (nfx) - Sergii Smertin (nfx)
- Eddie Jaoude - Eddie Jaoude
@ -550,6 +552,7 @@ Symfony2 is the result of the work of many people who made the code better
- Benoit Garret - Benoit Garret
- DerManoMann - DerManoMann
- Asmir Mustafic (goetas) - Asmir Mustafic (goetas)
- Julien Bianchi (jubianchi)
- Marcin Chwedziak - Marcin Chwedziak
- Roland Franssen (ro0) - Roland Franssen (ro0)
- Maciej Malarz - Maciej Malarz
@ -562,20 +565,22 @@ Symfony2 is the result of the work of many people who made the code better
- kaiwa - kaiwa
- Albert Ganiev (helios-ag) - Albert Ganiev (helios-ag)
- Neil Katin - Neil Katin
- David Otton
- peter - peter
- Artem Kolesnikov (tyomo4ka) - Artem Kolesnikov (tyomo4ka)
- Gustavo Adrian - Gustavo Adrian
- Clément Gautier (clementgautier)
- Yannick - Yannick
- Luc Vieillescazes (iamluc) - Luc Vieillescazes (iamluc)
- Eduardo García Sanz (coma) - Eduardo García Sanz (coma)
- David de Boer (ddeboer) - David de Boer (ddeboer)
- Brooks Boyd - Brooks Boyd
- Roger Webb - Roger Webb
- Dmitriy Simushev
- Max Voloshin (maxvoloshin) - Max Voloshin (maxvoloshin)
- Nicolas Fabre (nfabre) - Nicolas Fabre (nfabre)
- Raul Rodriguez (raul782) - Raul Rodriguez (raul782)
- Patrick Landolt (scube) - Patrick Landolt (scube)
- WybrenKoelmans
- Derek Lambert - Derek Lambert
- Felicitus - Felicitus
- Krzysztof Przybyszewski - Krzysztof Przybyszewski
@ -730,6 +735,7 @@ Symfony2 is the result of the work of many people who made the code better
- catch - catch
- Alexandre Segura - Alexandre Segura
- Josef Cech - Josef Cech
- Nate (frickenate)
- Matthew Foster (mfoster) - Matthew Foster (mfoster)
- Maximilian Reichel (phramz) - Maximilian Reichel (phramz)
- Paul Seiffert (seiffert) - Paul Seiffert (seiffert)
@ -750,7 +756,6 @@ Symfony2 is the result of the work of many people who made the code better
- Andrey Ryaguzov - Andrey Ryaguzov
- Gunther Konig - Gunther Konig
- František Bereň - František Bereň
- Benjamin Grandfond (benjamin)
- Christoph Nissle (derstoffel) - Christoph Nissle (derstoffel)
- Ionel Scutelnicu (ionelscutelnicu) - Ionel Scutelnicu (ionelscutelnicu)
- Johnny Peck (johnnypeck) - 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) - Yorkie Chadwick (yorkie76)
- Yanick Witschi - Yanick Witschi
- Ondrej Mirtes - Ondrej Mirtes
- akimsko
- Youpie - Youpie
- srsbiz - srsbiz
- Nicolas A. Bérard-Nault - 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) - Adam Monsen (meonkeys)
- ollie harridge (ollietb) - ollie harridge (ollietb)
- Paweł Szczepanek (pauluz) - Paweł Szczepanek (pauluz)
- Christian López Espínola (penyaskito)
- Petr Jaroš (petajaros) - Petr Jaroš (petajaros)
- Philipp Hoffmann (philipphoffmann) - Philipp Hoffmann (philipphoffmann)
- Alex Carol (picard89) - Alex Carol (picard89)

View File

@ -1,393 +1,313 @@
{# Widgets #} {# Widgets #}
{% block form_widget %} {% block form_widget -%}
{% spaceless %}
{% if compound %} {% if compound %}
{{ block('form_widget_compound') }} {{- block('form_widget_compound') -}}
{% else %} {% else %}
{{ block('form_widget_simple') }} {{- block('form_widget_simple') -}}
{% endif %} {% endif %}
{% endspaceless %} {%- endblock form_widget %}
{% endblock form_widget %}
{% block form_widget_simple %} {% block form_widget_simple -%}
{% spaceless %} {%- set type = type|default('text') -%}
{% set type = type|default('text') %}
<input type="{{ type }}" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %}/> <input type="{{ type }}" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %}/>
{% endspaceless %} {%- endblock form_widget_simple %}
{% endblock form_widget_simple %}
{% block form_widget_compound %} {% block form_widget_compound -%}
{% spaceless %}
<div {{ block('widget_container_attributes') }}> <div {{ block('widget_container_attributes') }}>
{% if form.parent is empty %} {%- if form.parent is empty -%}
{{ form_errors(form) }} {{ form_errors(form) }}
{% endif %} {%- endif -%}
{{ block('form_rows') }} {{- block('form_rows') -}}
{{ form_rest(form) }} {{- form_rest(form) -}}
</div> </div>
{% endspaceless %} {%- endblock form_widget_compound %}
{% endblock form_widget_compound %}
{% block collection_widget %} {% block collection_widget -%}
{% spaceless %}
{% if prototype is defined %} {% if prototype is defined %}
{% set attr = attr|merge({'data-prototype': form_row(prototype) }) %} {%- set attr = attr|merge({'data-prototype': form_row(prototype) }) -%}
{% endif %} {% endif %}
{{ block('form_widget') }} {{- block('form_widget') -}}
{% endspaceless %} {%- endblock collection_widget %}
{% endblock collection_widget %}
{% block textarea_widget %} {% block textarea_widget -%}
{% spaceless %}
<textarea {{ block('widget_attributes') }}>{{ value }}</textarea> <textarea {{ block('widget_attributes') }}>{{ value }}</textarea>
{% endspaceless %} {%- endblock textarea_widget %}
{% endblock textarea_widget %}
{% block choice_widget %} {% block choice_widget -%}
{% spaceless %}
{% if expanded %} {% if expanded %}
{{ block('choice_widget_expanded') }} {{- block('choice_widget_expanded') -}}
{% else %} {% else %}
{{ block('choice_widget_collapsed') }} {{- block('choice_widget_collapsed') -}}
{% endif %} {% endif %}
{% endspaceless %} {%- endblock choice_widget %}
{% endblock choice_widget %}
{% block choice_widget_expanded %} {% block choice_widget_expanded -%}
{% spaceless %}
<div {{ block('widget_container_attributes') }}> <div {{ block('widget_container_attributes') }}>
{% for child in form %} {% for child in form %}
{{ form_widget(child) }} {{- form_widget(child) -}}
{{ form_label(child) }} {{- form_label(child) -}}
{% endfor %} {% endfor %}
</div> </div>
{% endspaceless %} {%- endblock choice_widget_expanded %}
{% endblock choice_widget_expanded %}
{% block choice_widget_collapsed %} {% block choice_widget_collapsed -%}
{% spaceless %}
{% if required and empty_value is none and not empty_value_in_choices and not multiple %} {% if required and empty_value is none and not empty_value_in_choices and not multiple %}
{% set required = false %} {% set required = false %}
{% endif %} {% endif %}
<select {{ block('widget_attributes') }}{% if multiple %} multiple="multiple"{% endif %}> <select {{ block('widget_attributes') }}{% if multiple %} multiple="multiple"{% endif %}>
{% if empty_value is not none %} {% if empty_value is not none -%}
<option value=""{% if required and value is empty %} selected="selected"{% endif %}>{{ empty_value|trans({}, translation_domain) }}</option> <option value=""{% if required and value is empty %} selected="selected"{% endif %}>{{ empty_value|trans({}, translation_domain) }}</option>
{% endif %} {%- endif %}
{% if preferred_choices|length > 0 %} {%- if preferred_choices|length > 0 -%}
{% set options = preferred_choices %} {% set options = preferred_choices %}
{{ block('choice_widget_options') }} {{- block('choice_widget_options') -}}
{% if choices|length > 0 and separator is not none %} {% if choices|length > 0 and separator is not none -%}
<option disabled="disabled">{{ separator }}</option> <option disabled="disabled">{{ separator }}</option>
{% endif %} {%- endif %}
{% endif %} {%- endif -%}
{% set options = choices %} {% set options = choices -%}
{{ block('choice_widget_options') }} {{- block('choice_widget_options') -}}
</select> </select>
{% endspaceless %} {%- endblock choice_widget_collapsed %}
{% endblock choice_widget_collapsed %}
{% block choice_widget_options %} {% block choice_widget_options -%}
{% spaceless %}
{% for group_label, choice in options %} {% for group_label, choice in options %}
{% if choice is iterable %} {%- if choice is iterable -%}
<optgroup label="{{ group_label|trans({}, translation_domain) }}"> <optgroup label="{{ group_label|trans({}, translation_domain) }}">
{% set options = choice %} {% set options = choice %}
{{ block('choice_widget_options') }} {{- block('choice_widget_options') -}}
</optgroup> </optgroup>
{% else %} {%- else -%}
<option value="{{ choice.value }}"{% if choice is selectedchoice(value) %} selected="selected"{% endif %}>{{ choice.label|trans({}, translation_domain) }}</option> <option value="{{ choice.value }}"{% if choice is selectedchoice(value) %} selected="selected"{% endif %}>{{ choice.label|trans({}, translation_domain) }}</option>
{% endif %} {%- endif -%}
{% endfor %} {% endfor %}
{% endspaceless %} {%- endblock choice_widget_options %}
{% endblock choice_widget_options %}
{% block checkbox_widget %} {% block checkbox_widget -%}
{% spaceless %}
<input type="checkbox" {{ block('widget_attributes') }}{% if value is defined %} value="{{ value }}"{% endif %}{% if checked %} checked="checked"{% endif %} /> <input type="checkbox" {{ block('widget_attributes') }}{% if value is defined %} value="{{ value }}"{% endif %}{% if checked %} checked="checked"{% endif %} />
{% endspaceless %} {%- endblock checkbox_widget %}
{% endblock checkbox_widget %}
{% block radio_widget %} {% block radio_widget -%}
{% spaceless %}
<input type="radio" {{ block('widget_attributes') }}{% if value is defined %} value="{{ value }}"{% endif %}{% if checked %} checked="checked"{% endif %} /> <input type="radio" {{ block('widget_attributes') }}{% if value is defined %} value="{{ value }}"{% endif %}{% if checked %} checked="checked"{% endif %} />
{% endspaceless %} {%- endblock radio_widget %}
{% endblock radio_widget %}
{% block datetime_widget %} {% block datetime_widget -%}
{% spaceless %}
{% if widget == 'single_text' %} {% if widget == 'single_text' %}
{{ block('form_widget_simple') }} {{- block('form_widget_simple') -}}
{% else %} {% else -%}
<div {{ block('widget_container_attributes') }}> <div {{ block('widget_container_attributes') }}>
{{ form_errors(form.date) }} {{- form_errors(form.date) -}}
{{ form_errors(form.time) }} {{- form_errors(form.time) -}}
{{ form_widget(form.date) }} {{- form_widget(form.date) -}}
{{ form_widget(form.time) }} {{- form_widget(form.time) -}}
</div> </div>
{% endif %} {%- endif %}
{% endspaceless %} {%- endblock datetime_widget %}
{% endblock datetime_widget %}
{% block date_widget %} {% block date_widget -%}
{% spaceless %} {% if widget == 'single_text' -%}
{% if widget == 'single_text' %}
{{ block('form_widget_simple') }} {{ block('form_widget_simple') }}
{% else %} {%- else -%}
<div {{ block('widget_container_attributes') }}> <div {{ block('widget_container_attributes') }}>
{{ date_pattern|replace({ {{- date_pattern|replace({
'{{ year }}': form_widget(form.year), '{{ year }}': form_widget(form.year),
'{{ month }}': form_widget(form.month), '{{ month }}': form_widget(form.month),
'{{ day }}': form_widget(form.day), '{{ day }}': form_widget(form.day),
})|raw }} })|raw -}}
</div> </div>
{% endif %} {%- endif %}
{% endspaceless %} {%- endblock date_widget %}
{% endblock date_widget %}
{% block time_widget %} {% block time_widget -%}
{% spaceless %} {% if widget == 'single_text' -%}
{% if widget == 'single_text' %}
{{ block('form_widget_simple') }} {{ block('form_widget_simple') }}
{% else %} {%- else -%}
{% set vars = widget == 'text' ? { 'attr': { 'size': 1 }} : {} %} {% set vars = widget == 'text' ? { 'attr': { 'size': 1 }} : {} -%}
<div {{ block('widget_container_attributes') }}> <div {{ block('widget_container_attributes') }}>
{{ form_widget(form.hour, vars) }}{% if with_minutes %}:{{ form_widget(form.minute, vars) }}{% endif %}{% if with_seconds %}:{{ form_widget(form.second, vars) }}{% endif %} {{ form_widget(form.hour, vars) }}{% if with_minutes %}:{{ form_widget(form.minute, vars) }}{% endif %}{% if with_seconds %}:{{ form_widget(form.second, vars) }}{% endif %}
</div> </div>
{% endif %} {%- endif %}
{% endspaceless %} {%- endblock time_widget %}
{% endblock time_widget %}
{% block number_widget %} {% block number_widget -%}
{% spaceless %}
{# type="number" doesn't work with floats #} {# type="number" doesn't work with floats #}
{% set type = type|default('text') %} {%- set type = type|default('text') -%}
{{ block('form_widget_simple') }} {{ block('form_widget_simple') }}
{% endspaceless %} {%- endblock number_widget %}
{% endblock number_widget %}
{% block integer_widget %} {% block integer_widget -%}
{% spaceless %}
{% set type = type|default('number') %} {% set type = type|default('number') %}
{{ block('form_widget_simple') }} {{- block('form_widget_simple') -}}
{% endspaceless %} {%- endblock integer_widget %}
{% endblock integer_widget %}
{% block money_widget %} {% block money_widget -%}
{% spaceless %}
{{ money_pattern|replace({ '{{ widget }}': block('form_widget_simple') })|raw }} {{ money_pattern|replace({ '{{ widget }}': block('form_widget_simple') })|raw }}
{% endspaceless %} {%- endblock money_widget %}
{% endblock money_widget %}
{% block url_widget %} {% block url_widget -%}
{% spaceless %} {% set type = type|default('url') -%}
{% set type = type|default('url') %}
{{ block('form_widget_simple') }} {{ block('form_widget_simple') }}
{% endspaceless %} {%- endblock url_widget %}
{% endblock url_widget %}
{% block search_widget %} {% block search_widget -%}
{% spaceless %} {% set type = type|default('search') -%}
{% set type = type|default('search') %}
{{ block('form_widget_simple') }} {{ block('form_widget_simple') }}
{% endspaceless %} {%- endblock search_widget %}
{% endblock search_widget %}
{% block percent_widget %} {% block percent_widget -%}
{% spaceless %} {% set type = type|default('text') -%}
{% set type = type|default('text') %}
{{ block('form_widget_simple') }} % {{ block('form_widget_simple') }} %
{% endspaceless %} {%- endblock percent_widget %}
{% endblock percent_widget %}
{% block password_widget %} {% block password_widget -%}
{% spaceless %} {% set type = type|default('password') -%}
{% set type = type|default('password') %}
{{ block('form_widget_simple') }} {{ block('form_widget_simple') }}
{% endspaceless %} {%- endblock password_widget %}
{% endblock password_widget %}
{% block hidden_widget %} {% block hidden_widget -%}
{% spaceless %} {% set type = type|default('hidden') -%}
{% set type = type|default('hidden') %}
{{ block('form_widget_simple') }} {{ block('form_widget_simple') }}
{% endspaceless %} {%- endblock hidden_widget %}
{% endblock hidden_widget %}
{% block email_widget %} {% block email_widget -%}
{% spaceless %} {% set type = type|default('email') -%}
{% set type = type|default('email') %}
{{ block('form_widget_simple') }} {{ block('form_widget_simple') }}
{% endspaceless %} {%- endblock email_widget %}
{% endblock email_widget %}
{% block button_widget %} {% block button_widget -%}
{% spaceless %} {% if label is empty -%}
{% if label is empty %}
{% set label = name|humanize %} {% set label = name|humanize %}
{% endif %} {%- endif -%}
<button type="{{ type|default('button') }}" {{ block('button_attributes') }}>{{ label|trans({}, translation_domain) }}</button> <button type="{{ type|default('button') }}" {{ block('button_attributes') }}>{{ label|trans({}, translation_domain) }}</button>
{% endspaceless %} {%- endblock button_widget %}
{% endblock button_widget %}
{% block submit_widget %} {% block submit_widget -%}
{% spaceless %} {% set type = type|default('submit') -%}
{% set type = type|default('submit') %}
{{ block('button_widget') }} {{ block('button_widget') }}
{% endspaceless %} {%- endblock submit_widget %}
{% endblock submit_widget %}
{% block reset_widget %} {% block reset_widget -%}
{% spaceless %} {% set type = type|default('reset') -%}
{% set type = type|default('reset') %}
{{ block('button_widget') }} {{ block('button_widget') }}
{% endspaceless %} {%- endblock reset_widget %}
{% endblock reset_widget %}
{# Labels #} {# Labels #}
{% block form_label %} {% block form_label -%}
{% spaceless %}
{% if label is not sameas(false) %} {% if label is not sameas(false) %}
{% if not compound %} {%- if not compound -%}
{% set label_attr = label_attr|merge({'for': id}) %} {% set label_attr = label_attr|merge({'for': id}) %}
{% endif %} {%- endif -%}
{% if required %} {%- if required -%}
{% set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' required')|trim}) %} {% set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' required')|trim}) %}
{% endif %} {%- endif -%}
{% if label is empty %} {%- if label is empty -%}
{% set label = name|humanize %} {% set label = name|humanize %}
{% endif %} {%- endif -%}
<label{% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}>{{ label|trans({}, translation_domain) }}</label> <label{% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}>{{ label|trans({}, translation_domain) }}</label>
{% endif %} {%- endif %}
{% endspaceless %} {%- endblock form_label %}
{% endblock form_label %}
{% block button_label %}{% endblock %} {% block button_label -%}{%- endblock %}
{# Rows #} {# Rows #}
{% block repeated_row %} {% block repeated_row -%}
{% spaceless %}
{# {#
No need to render the errors here, as all errors are mapped No need to render the errors here, as all errors are mapped
to the first child (see RepeatedTypeValidatorExtension). to the first child (see RepeatedTypeValidatorExtension).
#} #}
{{ block('form_rows') }} {{- block('form_rows') }}
{% endspaceless %} {%- endblock repeated_row %}
{% endblock repeated_row %}
{% block form_row %} {% block form_row -%}
{% spaceless %}
<div> <div>
{{ form_label(form) }} {{- form_label(form) -}}
{{ form_errors(form) }} {{- form_errors(form) -}}
{{ form_widget(form) }} {{- form_widget(form) -}}
</div> </div>
{% endspaceless %} {%- endblock form_row %}
{% endblock form_row %}
{% block button_row %} {% block button_row -%}
{% spaceless %}
<div> <div>
{{ form_widget(form) }} {{- form_widget(form) -}}
</div> </div>
{% endspaceless %} {%- endblock button_row %}
{% endblock button_row %}
{% block hidden_row %} {% block hidden_row -%}
{{ form_widget(form) }} {{ form_widget(form) }}
{% endblock hidden_row %} {%- endblock hidden_row %}
{# Misc #} {# Misc #}
{% block form %} {% block form -%}
{% spaceless %}
{{ form_start(form) }} {{ form_start(form) }}
{{ form_widget(form) }} {{- form_widget(form) -}}
{{ form_end(form) }} {{ form_end(form) }}
{% endspaceless %} {%- endblock form %}
{% endblock form %}
{% block form_start %} {% block form_start -%}
{% spaceless %}
{% set method = method|upper %} {% set method = method|upper %}
{% if method in ["GET", "POST"] %} {%- if method in ["GET", "POST"] -%}
{% set form_method = method %} {% set form_method = method %}
{% else %} {%- else -%}
{% set form_method = "POST" %} {% set form_method = "POST" %}
{% endif %} {%- endif -%}
<form method="{{ form_method|lower }}" action="{{ action }}"{% for attrname, attrvalue in attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}{% if multipart %} enctype="multipart/form-data"{% endif %}> <form method="{{ form_method|lower }}" action="{{ action }}"{% for attrname, attrvalue in attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}{% if multipart %} enctype="multipart/form-data"{% endif %}>
{% if form_method != method %} {%- if form_method != method -%}
<input type="hidden" name="_method" value="{{ method }}" /> <input type="hidden" name="_method" value="{{ method }}" />
{% endif %} {%- endif %}
{% endspaceless %} {%- endblock form_start %}
{% endblock form_start %}
{% block form_end %} {% block form_end -%}
{% spaceless %} {% if not render_rest is defined or render_rest -%}
{% if not render_rest is defined or render_rest %}
{{ form_rest(form) }} {{ form_rest(form) }}
{% endif %} {%- endif -%}
</form> </form>
{% endspaceless %} {%- endblock form_end %}
{% endblock form_end %}
{% block form_enctype %} {% block form_enctype -%}
{% spaceless %}
{% if multipart %}enctype="multipart/form-data"{% endif %} {% if multipart %}enctype="multipart/form-data"{% endif %}
{% endspaceless %} {%- endblock form_enctype %}
{% endblock form_enctype %}
{% block form_errors %} {% block form_errors -%}
{% spaceless %} {% if errors|length > 0 -%}
{% if errors|length > 0 %}
<ul> <ul>
{% for error in errors %} {%- for error in errors -%}
<li>{{ error.message }}</li> <li>{{ error.message }}</li>
{% endfor %} {%- endfor -%}
</ul> </ul>
{% endif %} {%- endif %}
{% endspaceless %} {%- endblock form_errors %}
{% endblock form_errors %}
{% block form_rest %} {% block form_rest -%}
{% spaceless %} {% for child in form -%}
{% for child in form %} {% if not child.rendered -%}
{% if not child.rendered %}
{{ form_row(child) }} {{ form_row(child) }}
{% endif %} {%- endif %}
{% endfor %} {%- endfor %}
{% endspaceless %} {%- endblock form_rest %}
{% endblock form_rest %}
{# Support #} {# Support #}
{% block form_rows %} {% block form_rows -%}
{% spaceless %} {% for child in form -%}
{% for child in form %}
{{ form_row(child) }} {{ form_row(child) }}
{% endfor %} {%- endfor %}
{% endspaceless %} {%- endblock form_rows %}
{% endblock form_rows %}
{% block widget_attributes %} {% block widget_attributes -%}
{% spaceless %}
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 %} 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 %} {% 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 %} {% block widget_container_attributes -%}
{% spaceless %}
{% if id is not empty %}id="{{ id }}" {% endif %} {% if id is not empty %}id="{{ id }}" {% endif %}
{% for attrname, attrvalue in attr %}{{ attrname }}="{{ attrvalue }}" {% endfor %} {%- for attrname, attrvalue in attr %}{{ attrname }}="{{ attrvalue }}" {% endfor %}
{% endspaceless %} {%- endblock widget_container_attributes %}
{% endblock widget_container_attributes %}
{% block button_attributes %} {% block button_attributes -%}
{% spaceless %}
id="{{ id }}" name="{{ full_name }}"{% if disabled %} disabled="disabled"{% endif %} id="{{ id }}" name="{{ full_name }}"{% if disabled %} disabled="disabled"{% endif %}
{% for attrname, attrvalue in attr %}{{ attrname }}="{{ attrvalue }}" {% endfor %} {%- for attrname, attrvalue in attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}
{% endspaceless %} {%- endblock button_attributes %}
{% endblock button_attributes %}

View File

@ -1,52 +1,44 @@
{% use "form_div_layout.html.twig" %} {% use "form_div_layout.html.twig" %}
{% block form_row %} {% block form_row -%}
{% spaceless %}
<tr> <tr>
<td> <td>
{{ form_label(form) }} {{- form_label(form) -}}
</td> </td>
<td> <td>
{{ form_errors(form) }} {{- form_errors(form) -}}
{{ form_widget(form) }} {{- form_widget(form) -}}
</td> </td>
</tr> </tr>
{% endspaceless %} {%- endblock form_row %}
{% endblock form_row %}
{% block button_row %} {% block button_row -%}
{% spaceless %}
<tr> <tr>
<td></td> <td></td>
<td> <td>
{{ form_widget(form) }} {{- form_widget(form) -}}
</td> </td>
</tr> </tr>
{% endspaceless %} {%- endblock button_row %}
{% endblock button_row %}
{% block hidden_row %} {% block hidden_row -%}
{% spaceless %}
<tr style="display: none"> <tr style="display: none">
<td colspan="2"> <td colspan="2">
{{ form_widget(form) }} {{- form_widget(form) -}}
</td> </td>
</tr> </tr>
{% endspaceless %} {%- endblock hidden_row %}
{% endblock hidden_row %}
{% block form_widget_compound %} {% block form_widget_compound -%}
{% spaceless %}
<table {{ block('widget_container_attributes') }}> <table {{ block('widget_container_attributes') }}>
{% if form.parent is empty and errors|length > 0 %} {% if form.parent is empty and errors|length > 0 -%}
<tr> <tr>
<td colspan="2"> <td colspan="2">
{{ form_errors(form) }} {{- form_errors(form) -}}
</td> </td>
</tr> </tr>
{% endif %} {%- endif %}
{{ block('form_rows') }} {{- block('form_rows') -}}
{{ form_rest(form) }} {{- form_rest(form) -}}
</table> </table>
{% endspaceless %} {%- endblock form_widget_compound %}
{% endblock form_widget_compound %}

View File

@ -45,7 +45,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
{ {
$translator = $this->getTranslator($this->getLoader()); $translator = $this->getTranslator($this->getLoader());
$translator->setLocale('fr'); $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('foo (FR)', $translator->trans('foo'));
$this->assertEquals('bar (EN)', $translator->trans('bar')); $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('no translation', $translator->trans('no translation'));
$this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo')); $this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo'));
$this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1)); $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() public function testTransWithCaching()
@ -61,7 +63,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
// prime the cache // prime the cache
$translator = $this->getTranslator($this->getLoader(), array('cache_dir' => $this->tmpDir)); $translator = $this->getTranslator($this->getLoader(), array('cache_dir' => $this->tmpDir));
$translator->setLocale('fr'); $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('foo (FR)', $translator->trans('foo'));
$this->assertEquals('bar (EN)', $translator->trans('bar')); $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('no translation', $translator->trans('no translation'));
$this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo')); $this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo'));
$this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1)); $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 // do it another time as the cache is primed now
$loader = $this->getMock('Symfony\Component\Translation\Loader\LoaderInterface'); $loader = $this->getMock('Symfony\Component\Translation\Loader\LoaderInterface');
$translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir)); $translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir));
$translator->setLocale('fr'); $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('foo (FR)', $translator->trans('foo'));
$this->assertEquals('bar (EN)', $translator->trans('bar')); $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('no translation', $translator->trans('no translation'));
$this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo')); $this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo'));
$this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1)); $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() 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)', '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; return $loader;
} }
@ -205,6 +225,8 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
$translator->addResource('loader', 'foo', 'es'); $translator->addResource('loader', 'foo', 'es');
$translator->addResource('loader', 'foo', 'pt-PT'); // European Portuguese $translator->addResource('loader', 'foo', 'pt-PT'); // European Portuguese
$translator->addResource('loader', 'foo', 'pt_BR'); // Brazilian 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; return $translator;
} }

View File

@ -97,8 +97,10 @@ class Translator extends BaseTranslator
$fallbackContent = ''; $fallbackContent = '';
$current = ''; $current = '';
$replacementPattern = '/[^a-z0-9_]/i';
foreach ($this->computeFallbackLocales($locale) as $fallback) { 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(<<<EOF $fallbackContent .= sprintf(<<<EOF
\$catalogue%s = new MessageCatalogue('%s', %s); \$catalogue%s = new MessageCatalogue('%s', %s);
@ -110,7 +112,7 @@ EOF
$fallbackSuffix, $fallbackSuffix,
$fallback, $fallback,
var_export($this->catalogues[$fallback]->all(), true), var_export($this->catalogues[$fallback]->all(), true),
ucfirst(str_replace('-', '_', $current)), $currentSuffix,
$fallbackSuffix $fallbackSuffix
); );
$current = $fallback; $current = $fallback;

View File

@ -304,7 +304,7 @@ abstract class Client
$uri = $this->getAbsoluteUri($uri); $uri = $this->getAbsoluteUri($uri);
if (isset($server['HTTP_HOST'])) { 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'])) { if (isset($server['HTTPS'])) {
@ -317,12 +317,7 @@ abstract class Client
$server['HTTP_REFERER'] = $this->history->current()->getUri(); $server['HTTP_REFERER'] = $this->history->current()->getUri();
} }
$server['HTTP_HOST'] = parse_url($uri, PHP_URL_HOST); $server['HTTP_HOST'] = $this->extractHost($uri);
if ($port = parse_url($uri, PHP_URL_PORT)) {
$server['HTTP_HOST'] .= ':'.$port;
}
$server['HTTPS'] = 'https' == parse_url($uri, PHP_URL_SCHEME); $server['HTTPS'] = 'https' == parse_url($uri, PHP_URL_SCHEME);
$this->internalRequest = new Request($uri, $method, $parameters, $files, $this->cookieJar->allValues($uri), $server, $content); $this->internalRequest = new Request($uri, $method, $parameters, $files, $this->cookieJar->allValues($uri), $server, $content);
@ -619,4 +614,15 @@ abstract class Client
return $server; 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;
}
} }

View File

@ -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'); $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() public function testRequestReferer()
{ {
$client = new TestClient(); $client = new TestClient();

View File

@ -17,6 +17,7 @@ use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\Scope;
/** /**
* GraphvizDumper dumps a service container as a graphviz file. * GraphvizDumper dumps a service container as a graphviz file.
@ -193,8 +194,8 @@ class GraphvizDumper extends Dumper
$container->setDefinitions($this->container->getDefinitions()); $container->setDefinitions($this->container->getDefinitions());
$container->setAliases($this->container->getAliases()); $container->setAliases($this->container->getAliases());
$container->setResources($this->container->getResources()); $container->setResources($this->container->getResources());
foreach ($this->container->getScopes() as $scope) { foreach ($this->container->getScopes() as $scope => $parentScope) {
$container->addScope($scope); $container->addScope(new Scope($scope, $parentScope));
} }
foreach ($this->container->getExtensions() as $extension) { foreach ($this->container->getExtensions() as $extension) {
$container->registerExtension($extension); $container->registerExtension($extension);

View File

@ -62,4 +62,11 @@ class GraphvizDumperTest extends \PHPUnit_Framework_TestCase
$dumper = new GraphvizDumper($container); $dumper = new GraphvizDumper($container);
$this->assertEquals(str_replace('%path%', __DIR__, file_get_contents(self::$fixturesPath.'/graphviz/services14.dot')), $dumper->dump(), '->dump() dumps services'); $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');
}
} }

View File

@ -0,0 +1,14 @@
<?php
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Scope;
$container = new ContainerBuilder();
$container->addScope(new Scope('request'));
$container->
register('foo', 'FooClass')->
setScope('request')
;
$container->compile();
return $container;

View File

@ -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"];
}

View File

@ -152,6 +152,8 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer
* Returns a preconfigured IntlDateFormatter instance * Returns a preconfigured IntlDateFormatter instance
* *
* @return \IntlDateFormatter * @return \IntlDateFormatter
*
* @throws TransformationFailedException in case the date formatter can not be constructed.
*/ */
protected function getIntlDateFormatter() protected function getIntlDateFormatter()
{ {
@ -162,6 +164,12 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer
$pattern = $this->pattern; $pattern = $this->pattern;
$intlDateFormatter = new \IntlDateFormatter(\Locale::getDefault(), $dateFormat, $timeFormat, $timezone, $calendar, $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); $intlDateFormatter->setLenient(false);
return $intlDateFormatter; return $intlDateFormatter;

View File

@ -77,6 +77,12 @@ class DateType extends AbstractType
$calendar, $calendar,
$pattern $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); $formatter->setLenient(false);
if ('choice' === $options['widget']) { if ('choice' === $options['widget']) {

View File

@ -111,7 +111,7 @@ class JsonResponse extends Response
// Not using application/javascript for compatibility reasons with older browsers. // Not using application/javascript for compatibility reasons with older browsers.
$this->headers->set('Content-Type', 'text/javascript'); $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) // Only set the header when there is none or when it equals 'text/javascript' (from a previous update with callback)

View File

@ -155,7 +155,7 @@ class JsonResponseTest extends \PHPUnit_Framework_TestCase
{ {
$response = JsonResponse::create(array('foo' => 'bar'))->setCallback('callback'); $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')); $this->assertEquals('text/javascript', $response->headers->get('Content-Type'));
} }

View File

@ -59,11 +59,11 @@ abstract class Kernel implements KernelInterface, TerminableInterface
protected $startTime; protected $startTime;
protected $loadClassCache; protected $loadClassCache;
const VERSION = '2.3.18-DEV'; const VERSION = '2.3.19-DEV';
const VERSION_ID = '20318'; const VERSION_ID = '20319';
const MAJOR_VERSION = '2'; const MAJOR_VERSION = '2';
const MINOR_VERSION = '3'; const MINOR_VERSION = '3';
const RELEASE_VERSION = '18'; const RELEASE_VERSION = '19';
const EXTRA_VERSION = 'DEV'; const EXTRA_VERSION = 'DEV';
/** /**

View File

@ -59,8 +59,7 @@ class ExecutableFinder
if (is_dir($path)) { if (is_dir($path)) {
$dirs[] = $path; $dirs[] = $path;
} else { } else {
$file = str_replace(dirname($path), '', $path); if (basename($path) == $name && is_executable($path)) {
if ($file == $name && is_executable($path)) {
return $path; return $path;
} }
} }

View File

@ -64,6 +64,8 @@ class Process
/** @var ProcessPipes */ /** @var ProcessPipes */
private $processPipes; private $processPipes;
private $latestSignal;
private static $sigchild; private static $sigchild;
/** /**
@ -321,7 +323,7 @@ class Process
usleep(1000); 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'])); 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'); 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 { do {
usleep(1000); usleep(1000);
} while ($this->isRunning() && microtime(true) < $timeoutMicro); } while ($this->isRunning() && microtime(true) < $timeoutMicro);
@ -1158,6 +1161,7 @@ class Process
$this->stdout = null; $this->stdout = null;
$this->stderr = null; $this->stderr = null;
$this->process = null; $this->process = null;
$this->latestSignal = null;
$this->status = self::STATUS_READY; $this->status = self::STATUS_READY;
$this->incrementalOutputOffset = 0; $this->incrementalOutputOffset = 0;
$this->incrementalErrorOutputOffset = 0; $this->incrementalErrorOutputOffset = 0;
@ -1201,6 +1205,8 @@ class Process
return false; return false;
} }
$this->latestSignal = $signal;
return true; return true;
} }

View File

@ -284,6 +284,8 @@ class ProcessPipes
private function readStreams($blocking, $close = false) private function readStreams($blocking, $close = false)
{ {
if (empty($this->pipes)) { if (empty($this->pipes)) {
usleep(Process::TIMEOUT_PRECISION * 1E4);
return array(); return array();
} }
@ -313,11 +315,11 @@ class ProcessPipes
$type = array_search($pipe, $this->pipes); $type = array_search($pipe, $this->pipes);
$data = ''; $data = '';
while ($dataread = fread($pipe, self::CHUNK_SIZE)) { while ('' !== $dataread = (string) fread($pipe, self::CHUNK_SIZE)) {
$data .= $dataread; $data .= $dataread;
} }
if ($data) { if ('' !== $data) {
$read[$type] = $data; $read[$type] = $data;
} }

View File

@ -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() public function testExitCodeCommandFailed()
{ {
if (defined('PHP_WINDOWS_VERSION_BUILD')) { if (defined('PHP_WINDOWS_VERSION_BUILD')) {
@ -583,7 +596,14 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
} }
$duration = microtime(true) - $start; $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() public function testCheckTimeoutOnNonStartedProcess()

View File

@ -47,9 +47,9 @@ class ExecutableFinderTest extends \PHPUnit_Framework_TestCase
$this->setPath(dirname(PHP_BINARY)); $this->setPath(dirname(PHP_BINARY));
$finder = new ExecutableFinder; $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() public function testFindWithDefault()
@ -83,9 +83,9 @@ class ExecutableFinderTest extends \PHPUnit_Framework_TestCase
$extraDirs = array(dirname(PHP_BINARY)); $extraDirs = array(dirname(PHP_BINARY));
$finder = new ExecutableFinder; $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() public function testFindWithOpenBaseDir()
@ -105,8 +105,43 @@ class ExecutableFinderTest extends \PHPUnit_Framework_TestCase
ini_set('open_basedir', dirname(PHP_BINARY).PATH_SEPARATOR.'/'); ini_set('open_basedir', dirname(PHP_BINARY).PATH_SEPARATOR.'/');
$finder = new ExecutableFinder; $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' : '');
} }
} }

View File

@ -211,6 +211,11 @@ class SigchildDisabledProcessTest extends AbstractProcessTest
$this->markTestSkipped('Signal is not supported in sigchild environment'); $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} * {@inheritdoc}
*/ */

View File

@ -120,6 +120,21 @@ class SigchildEnabledProcessTest extends AbstractProcessTest
parent::testStartAfterATimeout(); 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} * {@inheritdoc}
*/ */

View File

@ -147,6 +147,57 @@ class SimpleProcessTest extends AbstractProcessTest
parent::testSignalWithWrongNonIntSignal(); 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} * {@inheritdoc}
*/ */

View File

@ -17,6 +17,33 @@ use Symfony\Component\Translation\Loader\ArrayLoader;
class TranslatorTest extends \PHPUnit_Framework_TestCase 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() public function testSetGetLocale()
{ {
$translator = new Translator('en', new MessageSelector()); $translator = new Translator('en', new MessageSelector());
@ -27,6 +54,27 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('fr', $translator->getLocale()); $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() public function testSetFallbackLocales()
{ {
$translator = new Translator('en', new MessageSelector()); $translator = new Translator('en', new MessageSelector());
@ -55,6 +103,26 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('bar (fr)', $translator->trans('bar')); $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() public function testTransWithFallbackLocale()
{ {
$translator = new Translator('fr_FR', new MessageSelector()); $translator = new Translator('fr_FR', new MessageSelector());
@ -67,6 +135,26 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('foobar', $translator->trans('bar')); $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() public function testAddResourceAfterTrans()
{ {
$translator = new Translator('fr', new MessageSelector()); $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)); $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 * @dataProvider getFlattenedTransTests
*/ */
@ -188,6 +302,32 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($expected, $translator->transChoice($id, $number, $parameters, $domain, $locale)); $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() public function getTransFileTests()
{ {
return array( 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() public function testTransChoiceFallback()
{ {
$translator = new Translator('ru', new MessageSelector()); $translator = new Translator('ru', new MessageSelector());

View File

@ -59,11 +59,13 @@ class Translator implements TranslatorInterface
* @param string $locale The locale * @param string $locale The locale
* @param MessageSelector|null $selector The message selector for pluralization * @param MessageSelector|null $selector The message selector for pluralization
* *
* @throws \InvalidArgumentException If a locale contains invalid characters
*
* @api * @api
*/ */
public function __construct($locale, MessageSelector $selector = null) public function __construct($locale, MessageSelector $selector = null)
{ {
$this->locale = $locale; $this->setLocale($locale);
$this->selector = $selector ?: new MessageSelector(); $this->selector = $selector ?: new MessageSelector();
} }
@ -88,6 +90,8 @@ class Translator implements TranslatorInterface
* @param string $locale The locale * @param string $locale The locale
* @param string $domain The domain * @param string $domain The domain
* *
* @throws \InvalidArgumentException If the locale contains invalid characters
*
* @api * @api
*/ */
public function addResource($format, $resource, $locale, $domain = null) public function addResource($format, $resource, $locale, $domain = null)
@ -96,6 +100,8 @@ class Translator implements TranslatorInterface
$domain = 'messages'; $domain = 'messages';
} }
$this->assertValidLocale($locale);
$this->resources[$locale][] = array($format, $resource, $domain); $this->resources[$locale][] = array($format, $resource, $domain);
if (in_array($locale, $this->fallbackLocales)) { if (in_array($locale, $this->fallbackLocales)) {
@ -112,6 +118,7 @@ class Translator implements TranslatorInterface
*/ */
public function setLocale($locale) public function setLocale($locale)
{ {
$this->assertValidLocale($locale);
$this->locale = $locale; $this->locale = $locale;
} }
@ -130,6 +137,8 @@ class Translator implements TranslatorInterface
* *
* @param string|array $locales The fallback locale(s) * @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. * @deprecated since 2.3, to be removed in 3.0. Use setFallbackLocales() instead.
* *
* @api * @api
@ -144,6 +153,8 @@ class Translator implements TranslatorInterface
* *
* @param array $locales The fallback locales * @param array $locales The fallback locales
* *
* @throws \InvalidArgumentException If a locale contains invalid characters
*
* @api * @api
*/ */
public function setFallbackLocales(array $locales) 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 // needed as the fallback locales are linked to the already loaded catalogues
$this->catalogues = array(); $this->catalogues = array();
foreach ($locales as $locale) {
$this->assertValidLocale($locale);
}
$this->fallbackLocales = $locales; $this->fallbackLocales = $locales;
} }
@ -175,6 +190,8 @@ class Translator implements TranslatorInterface
{ {
if (null === $locale) { if (null === $locale) {
$locale = $this->getLocale(); $locale = $this->getLocale();
} else {
$this->assertValidLocale($locale);
} }
if (null === $domain) { if (null === $domain) {
@ -197,6 +214,8 @@ class Translator implements TranslatorInterface
{ {
if (null === $locale) { if (null === $locale) {
$locale = $this->getLocale(); $locale = $this->getLocale();
} else {
$this->assertValidLocale($locale);
} }
if (null === $domain) { if (null === $domain) {
@ -279,4 +298,18 @@ class Translator implements TranslatorInterface
return array_unique($locales); 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));
}
}
} }

View File

@ -28,6 +28,8 @@ interface TranslatorInterface
* @param string $domain The domain for the message * @param string $domain The domain for the message
* @param string $locale The locale * @param string $locale The locale
* *
* @throws \InvalidArgumentException If the locale contains invalid characters
*
* @return string The translated string * @return string The translated string
* *
* @api * @api
@ -43,6 +45,8 @@ interface TranslatorInterface
* @param string $domain The domain for the message * @param string $domain The domain for the message
* @param string $locale The locale * @param string $locale The locale
* *
* @throws \InvalidArgumentException If the locale contains invalid characters
*
* @return string The translated string * @return string The translated string
* *
* @api * @api
@ -54,6 +58,8 @@ interface TranslatorInterface
* *
* @param string $locale The locale * @param string $locale The locale
* *
* @throws \InvalidArgumentException If the locale contains invalid characters
*
* @api * @api
*/ */
public function setLocale($locale); public function setLocale($locale);

View File

@ -176,7 +176,7 @@
</trans-unit> </trans-unit>
<trans-unit id="47"> <trans-unit id="47">
<source>This value should be the user current password.</source> <source>This value should be the user current password.</source>
<target>This value should be the user current password.</target> <target>This value should be the user's current password.</target>
</trans-unit> </trans-unit>
<trans-unit id="48"> <trans-unit id="48">
<source>This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters.</source> <source>This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters.</source>

View File

@ -35,6 +35,7 @@ class Entity extends EntityParent implements EntityInterface
public $reference; public $reference;
private $internal; private $internal;
public $data = 'Overridden data'; public $data = 'Overridden data';
public $initialized = false;
public function __construct($internal = null) public function __construct($internal = null)
{ {

View File

@ -11,6 +11,8 @@
namespace Symfony\Component\Validator\Tests; 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\Tests\Fixtures\FakeMetadataFactory;
use Symfony\Component\Validator\Constraints\Valid; use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Tests\Fixtures\Reference; use Symfony\Component\Validator\Tests\Fixtures\Reference;
@ -561,4 +563,50 @@ class ValidationVisitorTest extends \PHPUnit_Framework_TestCase
$this->visitor->validate($entity, 'Default', ''); $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);
}
} }

View File

@ -127,16 +127,19 @@ class ValidationVisitor implements ValidationVisitorInterface, GlobalExecutionCo
return; 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 // Remember validating this object before starting and possibly
// traversing the object graph // traversing the object graph
$this->validatedObjects[$hash][$group] = true; $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 // Validate arrays recursively by default, otherwise every driver needs