diff --git a/.travis.yml b/.travis.yml index 9d5b2fff64..14c7704c4c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,16 +14,19 @@ matrix: services: mongodb -before_script: +before_install: - travis_retry sudo apt-get install parallel - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm-nightly" ]; then echo "" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini; fi;' - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm-nightly" ]; then echo "extension = mongo.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;' - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm-nightly" ] && [ $(php -r "echo PHP_MINOR_VERSION;") -le 4 ]; then echo "extension = apc.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;' - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm-nightly" ]; then echo "extension = memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;' - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm-nightly" ]; then echo "extension = memcache.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;' - - COMPOSER_ROOT_VERSION=dev-master composer --prefer-source --dev install + - sudo locale-gen fr_FR.UTF-8 && sudo update-locale # - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "5.3.3" ]; then phpunit --self-update; fi;' +install: + - COMPOSER_ROOT_VERSION=dev-master composer --prefer-source install + script: - ls -d src/Symfony/*/* | parallel --gnu --keep-order 'echo "Running {} tests"; phpunit --exclude-group tty,benchmark {};' - echo "Running tests requiring tty"; phpunit --group tty diff --git a/CHANGELOG-2.3.md b/CHANGELOG-2.3.md index c132013aa0..833ded8052 100644 --- a/CHANGELOG-2.3.md +++ b/CHANGELOG-2.3.md @@ -7,6 +7,59 @@ 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.19 (2014-09-03) + + * security #11832 CVE-2014-6072 (fabpot) + * security #11831 CVE-2014-5245 (stof) + * security #11830 CVE-2014-4931 (aitboudad, Jérémy Derussé) + * security #11829 CVE-2014-6061 (damz, fabpot) + * security #11828 CVE-2014-5244 (nicolas-grekas, larowlan) + * bug #10197 [FrameworkBundle] PhpExtractor bugfix and improvements (mtibben) + * bug #11772 [Filesystem] Add FTP stream wrapper context option to enable overwrite (Damian Sromek) + * bug #11788 [Yaml] fixed mapping keys containing a quoted # (hvt, fabpot) + * bug #11160 [DoctrineBridge] Abstract Doctrine Subscribers with tags (merk) + * bug #11768 [ClassLoader] Add a __call() method to XcacheClassLoader (tstoeckler) + * bug #11726 [Filesystem Component] mkdir race condition fix #11626 (kcassam) + * bug #11677 [YAML] resolve variables in inlined YAML (xabbuh) + * bug #11639 [DependencyInjection] Fixed factory service not within the ServiceReferenceGraph. (boekkooi) + * bug #11778 [Validator] Fixed wrong translations for Collection constraints (samicemalone) + * bug #11756 [DependencyInjection] fix @return anno created by PhpDumper (jakubkulhan) + * bug #11711 [DoctrineBridge] Fix empty parameter logging in the dbal logger (jakzal) + * bug #11692 [DomCrawler] check for the correct field type (xabbuh) + * bug #11672 [Routing] fix handling of nullable XML attributes (xabbuh) + * bug #11624 [DomCrawler] fix the axes handling in a bc way (xabbuh) + * bug #11676 [Form] Fixed #11675 ValueToDuplicatesTransformer accept "0" value (Nek-) + * bug #11695 [Validators] Fixed failing tests requiring ICU 52.1 which are skipped otherwise (webmozart) + * bug #11529 [WebProfilerBundle] Fixed double height of canvas (hason) + * bug #11641 [WebProfilerBundle ] Fix toolbar vertical alignment (blaugueux) + * bug #11559 [Validator] Convert objects to string in comparison validators (webmozart) + * feature #11510 [HttpFoundation] MongoDbSessionHandler supports auto expiry via configurable expiry_field (catchamonkey) + * bug #11408 [HttpFoundation] Update QUERY_STRING when overrideGlobals (yguedidi) + * bug #11633 [FrameworkBundle] add missing attribute to XSD (xabbuh) + * bug #11601 [Validator] Allow basic auth in url when using UrlValidator. (blaugueux) + * bug #11609 [Console] fixed style creation when providing an unknown tag option (fabpot) + * bug #10914 [HttpKernel] added an analyze of environment parameters for built-in server (mauchede) + * bug #11598 [Finder] Shell escape and windows support (Gordon Franke, gimler) + * bug #11499 [BrowserKit] Fixed relative redirects for ambiguous paths (pkruithof) + * bug #11516 [BrowserKit] Fix browser kit redirect with ports (dakota) + * bug #11545 [Bundle][FrameworkBundle] built-in server: exit when docroot does not exist (xabbuh) + * bug #11560 Plural fix (1emming) + * bug #11558 [DependencyInjection] Fixed missing 'factory-class' attribute in XmlDumper output (kerdany) + * bug #11548 [Component][DomCrawler] fix axes handling in Crawler::filterXPath() (xabbuh) + * bug #11422 [DependencyInjection] Self-referenced 'service_container' service breaks garbage collection (sun) + * bug #11428 [Serializer] properly handle null data when denormalizing (xabbuh) + * bug #10687 [Validator] Fixed string conversion in constraint violations (eagleoneraptor, webmozart) + * bug #11475 [EventDispatcher] don't count empty listeners (xabbuh) + * bug #11436 fix signal handling in wait() on calls to stop() (xabbuh, romainneutron) + * bug #11469 [BrowserKit] Fixed server HTTP_HOST port uri conversion (bcremer, fabpot) + * bug #11425 Fix issue described in #11421 (Ben, ben-rosio) + * bug #11423 Pass a Scope instance instead of a scope name when cloning a container in the GrahpvizDumper (jakzal) + * bug #11120 [Process] Reduce I/O load on Windows platform (romainneutron) + * bug #11342 [Form] Check if IntlDateFormatter constructor returned a valid object before using it (romainneutron) + * bug #11411 [Validator] Backported #11410 to 2.3: Object initializers are called only once per object (webmozart) + * bug #11403 [Translator][FrameworkBundle] Added @ to the list of allowed chars in Translator (takeit) + * bug #11381 [Process] Use correct test for empty string in UnixPipes (whs, romainneutron) + * 2.3.18 (2014-07-15) * [Security] Forced validate of locales passed to the translator diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index ca9e441c93..a9526431c1 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -18,38 +18,39 @@ Symfony2 is the result of the work of many people who made the code better - Joseph Bielawski (stloyd) - Ryan Weaver (weaverryan) - Lukas Kahwe Smith (lsmith) - - Jeremy Mikola (jmikola) - Romain Neutron (romain) + - Jeremy Mikola (jmikola) - Jean-François Simon (jfsimon) - Benjamin Eberlei (beberlei) - Igor Wiedler (igorw) - Hugo Hamon (hhamon) - - Eriksen Costa (eriksencosta) - Martin Hasoň (hason) + - Eriksen Costa (eriksencosta) - Jonathan Wage (jwage) - Alexandre Salomé (alexandresalome) - William Durand (couac) - ornicar - stealth35 ‏ (stealth35) + - Grégoire Pineau (lyrixx) - Alexander Mols (asm89) - Bulat Shakirzyanov (avalanche123) - - Grégoire Pineau (lyrixx) + - Nicolas Grekas (nicolas-grekas) - Francis Besset (francisbesset) - Saša Stamenković (umpirsky) - Miha Vrhovnik - Henrik Bjørnskov (henrikbjorn) - Konstantin Kudryashov (everzet) + - Wouter De Jong (wouterj) + - Christian Flothmann (xabbuh) - Bilal Amarni (bamarni) - Florin Patan (florinpatan) - - Wouter De Jong (wouterj) - Eric Clemmons (ericclemmons) - - Nicolas Grekas (nicolas-grekas) - Andrej Hudec (pulzarraider) - Deni - Henrik Westphal (snc) - Dariusz Górecki (canni) - - Arnout Boks (aboks) - Christian Raue + - Arnout Boks (aboks) - Michel Weimerskirch (mweimerskirch) - Lee McDermott - Brandon Turner @@ -61,11 +62,11 @@ Symfony2 is the result of the work of many people who made the code better - Bart van den Burg (burgov) - Antoine Hérault (herzult) - Toni Uebernickel (havvg) - - Arnaud Le Blanc (arnaud-lb) - Luis Cordova (cordoval) + - Arnaud Le Blanc (arnaud-lb) + - Tim Nagel (merk) - Kevin Bond (kbond) - Brice BERNARD (brikou) - - Tim Nagel (merk) - marc.weistroff - lenar - Włodzimierz Gajda (gajdaw) @@ -74,25 +75,25 @@ Symfony2 is the result of the work of many people who made the code better - Florian Voutzinos (florianv) - Fabien Pennequin (fabienpennequin) - Jacob Dreesen (jdreesen) + - Adrien Brault (adrienbrault) - Gábor Egyed (1ed) - Ait Boudad Abdellatif (aitboudad) - - Adrien Brault (adrienbrault) - Michal Piotrowski (eventhorizon) + - Gordon Franke (gimler) - 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) + - Pablo Godel (pgodel) + - Jérémie Augustin (jaugustin) + - Rafael Dohms (rdohms) - Jérôme Tamarelle (gromnan) - Tigran Azatyan (tigranazatyan) - Javier Eguiluz (javier.eguiluz) - - Rafael Dohms (rdohms) - Richard Shank (iampersistent) - - Gordon Franke (gimler) - Eric GELOEN (gelo) - Helmer Aaviksoo - Sebastiaan Stok (sstok) @@ -103,7 +104,6 @@ Symfony2 is the result of the work of many people who made the code better - Jonathan Ingram (jonathaningram) - Artur Kotyrba - Guilherme Blanco (guilhermeblanco) - - Pablo Godel (pgodel) - Clemens Tolboom - Dmitrii Chekaliuk (lazyhammer) - Clément JOBEILI (dator) @@ -114,10 +114,13 @@ Symfony2 is the result of the work of many people who made the code better - Dennis Benkert (denderello) - Rouven Weßling (realityking) - Benjamin Dulau (dbenjamin) + - Stefano Sala (stefano.sala) - Andreas Hucks (meandmymonkey) + - Andréia Bohner (andreia) - Noel Guilbert (noel) - Charles Sarrazin (csarrazi) - bronze1man + - sun (sun) - Larry Garfield (crell) - Martin Schuhfuß (usefulthink) - Thomas Rabaix (rande) @@ -131,10 +134,9 @@ Symfony2 is the result of the work of many people who made the code better - jeff - Justin Hileman (bobthecow) - Sven Paulus (subsven) - - Andréia Bohner (andreia) + - Lars Strojny (lstrojny) - Joel Wurtz (brouznouf) - Rui Marinho (ruimarinho) - - sun (sun) - Julien Brochet (mewt) - Tugdual Saunier (tucksaun) - Sergey Linnik (linniksa) @@ -152,12 +154,11 @@ Symfony2 is the result of the work of many people who made the code better - Katsuhiro OGAWA - Peter Rehm (rpet) - Alif Rachmawadi + - Pierre-Yves LEBECQ (pylebecq) - Matthias Pigulla (mpdude) - - Stefano Sala (stefano.sala) - Joseph Rouff (rouffj) - Félix Labrecque (woodspire) - GordonsLondon - - Lars Strojny (lstrojny) - Jan Sorgalla (jsor) - Ray - Chekote @@ -168,14 +169,17 @@ Symfony2 is the result of the work of many people who made the code better - Beau Simensen (simensen) - Robert Kiss (kepten) - Kim Hemsø Rasmussen (kimhemsoe) + - Tom Van Looy (tvlooy) - Wouter Van Hecke + - Peter Kruithof (pkruithof) - Michael Holm (hollo) - Marc Weistroff (futurecat) - - Pierre-Yves LEBECQ (pylebecq) - Roman Marintšenko (inori) - Chris Smith (cs278) - Florian Klein (docteurklein) - Manuel Kiessling (manuelkiessling) + - Atsuhiro KUBO (iteman) + - Issei Murasawa (issei_m) - Bertrand Zuchuat (garfield-fr) - Gabor Toth (tgabi333) - realmfoo @@ -184,6 +188,7 @@ Symfony2 is the result of the work of many people who made the code better - Grégoire Passault (gregwar) - Uwe Jäger (uwej711) - Aurelijus Valeiša (aurelijus) + - Jan Decavele (jandc) - Gustavo Piltcher - Stepan Tanasiychuk (stfalcon) - Tiago Ribeiro (fixe) @@ -191,10 +196,12 @@ Symfony2 is the result of the work of many people who made the code better - Adrian Rudnik (kreischweide) - Francesc Rosàs (frosas) - Julien Galenski (ruian) + - Eugene Leonovich (rybakit) - Bongiraud Dominique - janschoenherr - Marco Pivetta (ocramius) - Ricard Clau (ricardclau) + - Jérémy DERUSSÉ (jderusse) - Kévin Dunglas (dunglas) - Erin Millard - Matthew Lewinski (lewinski) @@ -204,8 +211,6 @@ Symfony2 is the result of the work of many people who made the code better - Gyula Sallai (salla) - Inal DJAFAR (inalgnu) - Christian Gärtner (dagardner) - - Tom Van Looy (tvlooy) - - Peter Kruithof (pkruithof) - Felix Labrecque - Yaroslav Kiliba - Sébastien Lavoie (lavoiesl) @@ -219,18 +224,17 @@ Symfony2 is the result of the work of many people who made the code better - Philipp Kräutli (pkraeutli) - Kirill chEbba Chebunin (chebba) - Greg Thornton (xdissent) - - Atsuhiro KUBO (iteman) - Grégoire Paris (greg0ire) - julien pauli (jpauli) - Costin Bereveanu (schniper) - Loïc Chardonnet (gnusat) - Marek Kalnik (marekkalnik) + - Andrew Moore (finewolf) - Tamas Szijarto - Pavel Volokitin (pvolok) - Tobias Naumann (tna) - Ismael Ambrosi (iambrosi) - Shein Alexey - - Issei Murasawa (issei_m) - hacfi (hifi) - Joe Lencioni - Kai @@ -240,7 +244,6 @@ Symfony2 is the result of the work of many people who made the code better - Miha Vrhovnik - Alessandro Desantis - hubert lecorche (hlecorche) - - Eugene Leonovich (rybakit) - Oscar Cubo Medina (ocubom) - Karel Souffriau - Christophe L. (christophelau) @@ -264,6 +267,8 @@ Symfony2 is the result of the work of many people who made the code better - Tobias Sjösten (tobiassjosten) - vagrant - Asier Illarramendi (doup) + - Chris Sedlmayr (catchamonkey) + - Seb Koelen - Christoph Mewes (xrstf) - Vitaliy Tverdokhlib (vitaliytv) - Dirk Pahl (dirkaholic) @@ -289,15 +294,17 @@ Symfony2 is the result of the work of many people who made the code better - Iker Ibarguren (ikerib) - Ricardo Oliveira (ricardolotr) - ondrowan - - Andrew Moore (finewolf) - Vyacheslav Salakhutdinov (megazoll) + - Daniel Wehner - Evan S Kaufman (evanskaufman) - mcben - Maks Slesarenko + - mmoreram - Markus Lanthaler (lanthaler) - Vicent Soria Durá (vicentgodella) - Chris Wilkinson (thewilkybarkid) - Ioan Negulescu + - Jakub Škvára (jskvara) - Andrew Udvare (audvare) - alexpods - Erik Trapman (eriktrapman) @@ -326,11 +333,13 @@ Symfony2 is the result of the work of many people who made the code better - Pavel Campr (pcampr) - Geoffrey Tran (geoff) - Jan Behrens + - Sebastian Krebs + - Christopher Davis (chrisguitarguy) - Thomas Lallement (raziel057) - alcaeus - vitaliytv - Markus Bachmann (baachi) - - Jérémy DERUSSÉ (jderusse) + - Sebastian Blum - aubx - Ricky Su (ricky) - Gildas Quéméner (gquemener) @@ -342,6 +351,7 @@ Symfony2 is the result of the work of many people who made the code better - Chris Boden (cboden) - Pierre du Plessis (pierredup) - Josip Kruslin + - Hany el-Kerdany - Wang Jingyu - Åsmund Garfors - Javier López (loalf) @@ -368,6 +378,7 @@ Symfony2 is the result of the work of many people who made the code better - Sascha Grossenbacher - Ben Davies (bendavies) - Simon Schick (simonsimcity) + - redstar504 - Hossein Bukhamsin - Paweł Wacławczyk (pwc) - Oleg Zinchenko (cystbear) @@ -375,10 +386,12 @@ Symfony2 is the result of the work of many people who made the code better - Johannes Klauss (cloppy) - Evan Villemez - fzerorubigd + - Thomas Ploch - Benjamin Grandfond (benjamin) - Tiago Brito (blackmx) - Richard van den Brand (ricbra) - develop + - Vincent AUBERT (vincent) - Tomasz Kowalczyk (thunderer) - Mark Sonnabaum - Mathieu Lemoine @@ -390,8 +403,8 @@ Symfony2 is the result of the work of many people who made the code better - yclian - Pascal Helfenstein - Baldur Rensch (brensch) + - Barry vd. Heuvel (barryvdh) - Alex Xandra Albert Sim - - Daniel Wehner - Yuen-Chi Lian - Besnik Br - Joshua Nye @@ -406,8 +419,8 @@ Symfony2 is the result of the work of many people who made the code better - Josiah (josiah) - Marek Štípek (maryo) - John Bohn (jbohn) - - Jakub Škvára (jskvara) - Andrew Hilobok (hilobok) + - Matthieu Auger (matthieuauger) - Christian Soronellas (theunic) - Jérôme Vieilledent (lolautruche) - Degory Valentine @@ -418,8 +431,11 @@ Symfony2 is the result of the work of many people who made the code better - Olivier Maisonneuve (olineuve) - Francis Turmel (fturmel) - cgonzalez + - Ben - Jayson Xu (superjavason) + - Tobias Nyholm (tobias) - Jaik Dean (jaikdean) + - Harm van Tilborg - Jan Prieser - James Michael DuPont - Tom Klingenberg @@ -436,6 +452,7 @@ Symfony2 is the result of the work of many people who made the code better - Maksim Kotlyar (makasim) - Neil Ferreira - Dmitry Parnas (parnas) + - Emanuele Iannone - Tony Malzhacker - Cyril Quintin (cyqui) - Gerard van Helden (drm) @@ -454,6 +471,7 @@ Symfony2 is the result of the work of many people who made the code better - abdul malik ikhsan (samsonasik) - Sarah Khalil (saro0h) - Timothée Barray (tyx) + - Benjamin Laugueux (yzalis) - Christian Morgan - Alexander Miehe (engerim) - Titouan Galopin (tgalopin) @@ -468,6 +486,7 @@ Symfony2 is the result of the work of many people who made the code better - Michael Roterman (wtfzdotnet) - Arno Geurts - Adán Lobato (adanlobato) + - Matthew Davis (mdavis1982) - Maks - Gábor Tóth - Daniel Cestari @@ -486,6 +505,7 @@ Symfony2 is the result of the work of many people who made the code better - Christian Jul Jensen - The Whole Life to Learn - Liverbool (liverbool) + - Sam Malone - Phan Thanh Ha (haphan) - Chris Jones (leek) - Colin O'Dell (colinodell) @@ -499,11 +519,13 @@ Symfony2 is the result of the work of many people who made the code better - fabios - Sander Coolen (scoolen) - Nicolas Le Goff (nlegoff) + - Ariel Ferrandini (aferrandini) - Manuele Menozzi - Anton Babenko (antonbabenko) - Irmantas Šiupšinskas (irmantas) - Danilo Silva - Zachary Tong (polyfractal) + - Hryhorii Hrebiniuk - dantleech - Tero Alén (tero) - DerManoMann @@ -524,11 +546,14 @@ Symfony2 is the result of the work of many people who made the code better - Jeremy David (jeremy.david) - Troy McCabe - Ville Mattila + - Boris Vujicic (boris.vujicic) - Max Beutel - Catalin Dan + - Warnar Boekkooi - Piotr Antosik (antek88) - Artem Lopata - Marcos Quesada (marcos_quesada) + - Matthew Vickery (mattvick) - Dan Finnie - Ken Marfilla (marfillaster) - benatespina (benatespina) @@ -548,7 +573,6 @@ Symfony2 is the result of the work of many people who made the code better - Daniel Mecke (daniel_mecke) - Matteo Giachino (matteosister) - Alex Demchenko (pilot) - - Vincent AUBERT (vincent) - Benoit Garret - DerManoMann - Asmir Mustafic (goetas) @@ -573,6 +597,7 @@ Symfony2 is the result of the work of many people who made the code better - Luc Vieillescazes (iamluc) - Eduardo García Sanz (coma) - David de Boer (ddeboer) + - Gilles Doge (gido) - Brooks Boyd - Roger Webb - Dmitriy Simushev @@ -596,7 +621,9 @@ Symfony2 is the result of the work of many people who made the code better - heccjj - Alexandre Melard - Sergey Yuferev + - Tobias Stöckler - Mario Young + - Jakub Kulhan - Mo Di (modi) - Jeroen van den Enden (stoefke) - Quique Porta (quiqueporta) @@ -604,6 +631,7 @@ Symfony2 is the result of the work of many people who made the code better - ConneXNL - Aharon Perkel - Abdul.Mohsen B. A. A + - Benoît Burnichon - Malaney J. Hill - Cédric Girard (enk_) - Oriol Mangas Abellan (oriolman) @@ -640,12 +668,14 @@ Symfony2 is the result of the work of many people who made the code better - Ilya Biryukov - Jason Desrosiers - m.chwedziak + - Endre Fejes - Lance McNearney - Giorgio Premi - caponica - Matt Daum (daum) - Alberto Pirovano (geezmo) - Pete Mitchell (peterjmit) + - Tom Corrigan (tomcorrigan) - Martin Pärtel - Evgeniy (ewgraf) - Patrick Daley (padrig) @@ -653,9 +683,11 @@ Symfony2 is the result of the work of many people who made the code better - Max Summe - WedgeSama - Felds Liscia + - Maxime Veber (nek-) - Tadcka - Beth Binkovitz - Romain Geissler + - Benjamin Cremer (bcremer) - Marcus Stöhr (dafish) - Emmanuel Vella (emmanuel.vella) - Carsten Nielsen (phreaknerd) @@ -665,8 +697,8 @@ Symfony2 is the result of the work of many people who made the code better - Adrien Samson (adriensamson) - Samuel Gordalina (gordalina) - Max Romanovsky (maxromanovsky) + - Rafał Muszyński (rafmus90) - Timothy Anido (xanido) - - Sebastian Krebs - Rick Prent - Martin Eckhardt - Jon Gotlin (jongotlin) @@ -689,7 +721,6 @@ Symfony2 is the result of the work of many people who made the code better - Sebastian Ionescu - Thomas Ploch - Simon Neidhold - - Seb Koelen - Kevin Dew - James Cowgill - Jeremy Livingston (jeremylivingston) @@ -707,6 +738,7 @@ Symfony2 is the result of the work of many people who made the code better - Hoffmann András - Olivier - pscheit + - moldcraft - Ramon Kleiss (akathos) - Nicolas Badey (nico-b) - Shane Preece (shane) @@ -717,7 +749,6 @@ Symfony2 is the result of the work of many people who made the code better - Gunnar Lium (gunnarlium) - Tiago Garcia (tiagojsag) - Bouke Haarsma - - Harm van Tilborg - Martin Eckhardt - Denis Zunke - Jonathan Poston @@ -748,12 +779,15 @@ Symfony2 is the result of the work of many people who made the code better - Michal Gebauer - Gleb Sidora - David Stone + - Javier Spagnoletti (phansys) - Pablo Maria Martelletti (pmartelletti) + - Yassine Guedidi (yguedidi) - Luis Muñoz - Andreas - Strate - Thomas Chmielowiec - Andrey Ryaguzov + - Manatsawin Hanmongkolchai - Gunther Konig - František Bereň - Christoph Nissle (derstoffel) @@ -766,11 +800,13 @@ Symfony2 is the result of the work of many people who made the code better - Grayson Koonce (breerly) - Benjamin Zikarsky (bzikarsky) - Matt Robinson (inanimatt) + - Karim Cassam Chenaï (ka) - Nicolas Bastien (nicolas_bastien) - Andy Stanberry - Luiz “Felds” Liscia - Thomas Rothe - alefranz + - avi123 - alsar - Mike Meier - Warwick @@ -798,10 +834,10 @@ Symfony2 is the result of the work of many people who made the code better - Daan van Renterghem - Bram Van der Sype (brammm) - Julien Moulin (lizjulien) - - Matthieu Auger (matthieuauger) - Kevin Decherf - dened - Sam Ward + - Walther Lalk - devel - Trevor Suarez - gedrox @@ -858,6 +894,7 @@ Symfony2 is the result of the work of many people who made the code better - Klaas Naaijkens - Rafał - Adria Lopez (adlpz) + - Rosio (ben-rosio) - Masao Maeda (brtriver) - Darius Leskauskas (darles) - Dave Hulbert (dave1010) @@ -871,7 +908,6 @@ Symfony2 is the result of the work of many people who made the code better - Ian Jenkins (jenkoian) - Jorge Martin (jorgemartind) - Kevin Herrera (kherge) - - Matthew Davis (mdavis1982) - Muriel (metalmumu) - Michaël Perrin (michael.perrin) - Pablo Monterde Perez (plebs) @@ -904,6 +940,7 @@ Symfony2 is the result of the work of many people who made the code better - goohib - Xavier HAUSHERR - Cas + - Maxime Douailin - Myke79 - Brian Debuire - Sylvain Lorinet @@ -928,6 +965,7 @@ Symfony2 is the result of the work of many people who made the code better - cmfcmf - Drew Butler - Steve Müller + - Andras Ratz - andreabreu98 - Thomas Schulz - Michael Schneider @@ -939,12 +977,12 @@ Symfony2 is the result of the work of many people who made the code better - Ondrej Slinták - vlechemin - Brian Corrigan + - Brian Freytag - Skorney - mieszko4 - datibbaw - Markus Staab - Pierre-Louis LAUNAY - - Thomas Ploch - djama - Eduardo Conceição - Jon Cave @@ -960,6 +998,7 @@ Symfony2 is the result of the work of many people who made the code better - Sergiy Sokolenko - dinitrol - Penny Leach + - g123456789l - oscartv - DanSync - Peter Zwosta @@ -982,7 +1021,6 @@ Symfony2 is the result of the work of many people who made the code better - Daniel Basten (axhm3a) - Bill Hance (billhance) - Bernd Matzner (bmatzner) - - Chris Sedlmayr (catchamonkey) - Choong Wei Tjeng (choonge) - Kousuke Ebihara (co3k) - Loïc Vernet (coil) @@ -991,6 +1029,7 @@ Symfony2 is the result of the work of many people who made the code better - Damon Jones (damon__jones) - Daniel Londero (dlondero) - Adel ELHAIBA (eadel) + - Damián Nohales (eagleoneraptor) - Elliot Anderson (elliot) - Fabien D. (fabd) - Sorin Gitlan (forapathy) @@ -1006,11 +1045,14 @@ Symfony2 is the result of the work of many people who made the code better - Jorge Maiden (jorgemaiden) - Justin Rainbow (jrainbow) - JuntaTom (juntatom) + - Johnson Page (jwpage) - Sébastien Armand (khepin) - Krzysztof Menżyk (krymen) - samuel laulhau (lalop) - Laurent Bachelier (laurentb) + - Jérôme Parmentier (lctrs) - Matthieu Moquet (mattketmo) + - Morgan Auchede (mauchede) - Moritz Borgmann (mborgmann) - Matt Drollette (mdrollette) - Adam Monsen (meonkeys) @@ -1053,8 +1095,10 @@ Symfony2 is the result of the work of many people who made the code better - Philipp Scheit - max - Mohamed Karnichi (amiral) + - Jeroen De Dauw (jeroendedauw) - Muharrem Demirci (mdemirci) - Evgeny Z (meze) + - Michiel Boeckaert (milio) - Nicolas de Marqué (nicola) - Kevin (oxfouzer) - Pierre Geyer (ptheg) diff --git a/src/Symfony/Bridge/Swiftmailer/composer.json b/src/Symfony/Bridge/Swiftmailer/composer.json index 8890da191a..f6d0024b34 100644 --- a/src/Symfony/Bridge/Swiftmailer/composer.json +++ b/src/Symfony/Bridge/Swiftmailer/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": ">=5.3.3", - "swiftmailer/swiftmailer": ">=4.2.0,<5.1-dev" + "swiftmailer/swiftmailer": ">=4.2.0,<6.0-dev" }, "suggest": { "symfony/http-kernel": "" diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ServerRunCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ServerRunCommand.php index 3535167910..bbb701ba4a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ServerRunCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ServerRunCommand.php @@ -101,15 +101,34 @@ EOF ->locateResource(sprintf('@FrameworkBundle/Resources/config/router_%s.php', $env)) ; + if (!file_exists($router)) { + $output->writeln(sprintf('The given router script "%s" does not exist', $router)); + + return 1; + } + + $router = realpath($router); + $output->writeln(sprintf("Server running on http://%s\n", $input->getArgument('address'))); $builder = new ProcessBuilder(array(PHP_BINARY, '-S', $input->getArgument('address'), $router)); $builder->setWorkingDirectory($documentRoot); $builder->setTimeout(null); - $builder->getProcess()->run(function ($type, $buffer) use ($output) { + $process = $builder->getProcess(); + $process->run(function ($type, $buffer) use ($output) { if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { $output->write($buffer); } }); + + if (!$process->isSuccessful()) { + $output->writeln('Built-in server terminated unexpectedly'); + + if (OutputInterface::VERBOSITY_VERBOSE > $output->getVerbosity()) { + $output->writeln('Run the command again with -v option for more details'); + } + } + + return $process->getExitCode(); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_widget_compound.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_widget_compound.html.php index 45a821032e..7a4f7cd51f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_widget_compound.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_widget_compound.html.php @@ -1,10 +1,6 @@
block($form, 'widget_container_attributes') ?>> parent && $errors): ?> - - - errors($form) ?> - - + errors($form) ?> block($form, 'form_rows') ?> rest($form) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_widget_compound.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_widget_compound.html.php index a4878e57db..20b9668aa4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_widget_compound.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_widget_compound.html.php @@ -1,6 +1,10 @@ block($form, 'widget_container_attributes') ?>> - parent): ?> - errors($form) ?> + parent && $errors): ?> + + + block($form, 'form_rows') ?> rest($form) ?> diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/config/routing/profiler.xml b/src/Symfony/Bundle/WebProfilerBundle/Resources/config/routing/profiler.xml index 1819e481c8..69f934e53f 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/config/routing/profiler.xml +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/config/routing/profiler.xml @@ -24,14 +24,6 @@ web_profiler.controller.profiler:infoAction - - web_profiler.controller.profiler:importAction - - - - web_profiler.controller.profiler:exportAction - - web_profiler.controller.profiler:phpinfoAction diff --git a/src/Symfony/Component/Console/Command/Command.php b/src/Symfony/Component/Console/Command/Command.php index ebab4ad669..4f7e5c6af7 100644 --- a/src/Symfony/Component/Console/Command/Command.php +++ b/src/Symfony/Component/Console/Command/Command.php @@ -491,11 +491,11 @@ class Command $placeholders = array( '%command.name%', - '%command.full_name%' + '%command.full_name%', ); $replacements = array( $name, - $_SERVER['PHP_SELF'].' '.$name + $_SERVER['PHP_SELF'].' '.$name, ); return str_replace($placeholders, $replacements, $this->getHelp()); @@ -504,7 +504,7 @@ class Command /** * Sets the aliases for the command. * - * @param array $aliases An array of aliases for the command + * @param string[] $aliases An array of aliases for the command * * @return Command The current instance * @@ -514,6 +514,10 @@ class Command */ public function setAliases($aliases) { + if (!is_array($aliases) && !$aliases instanceof \Traversable) { + throw new \InvalidArgumentException('$aliases must be an array or an instance of \Traversable'); + } + foreach ($aliases as $alias) { $this->validateName($alias); } diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index 4f399a59fc..4f042689ce 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -56,6 +56,10 @@ class YamlFileLoader extends FileLoader // parameters if (isset($content['parameters'])) { + if (!is_array($content['parameters'])) { + throw new InvalidArgumentException(sprintf('The "parameters" key should contain an array in %s. Check your YAML syntax.', $file)); + } + foreach ($content['parameters'] as $key => $value) { $this->container->setParameter($key, $this->resolveServices($value)); } @@ -93,7 +97,15 @@ class YamlFileLoader extends FileLoader return; } + if (!is_array($content['imports'])) { + throw new InvalidArgumentException(sprintf('The "imports" key should contain an array in %s. Check your YAML syntax.', $file)); + } + foreach ($content['imports'] as $import) { + if (!is_array($import)) { + throw new InvalidArgumentException(sprintf('The values in the "imports" key should be arrays in %s. Check your YAML syntax.', $file)); + } + $this->setCurrentDir(dirname($file)); $this->import($import['resource'], null, isset($import['ignore_errors']) ? (bool) $import['ignore_errors'] : false, $file); } @@ -111,6 +123,10 @@ class YamlFileLoader extends FileLoader return; } + if (!is_array($content['services'])) { + throw new InvalidArgumentException(sprintf('The "services" key should contain an array in %s. Check your YAML syntax.', $file)); + } + foreach ($content['services'] as $id => $service) { $this->parseDefinition($id, $service, $file); } @@ -131,7 +147,13 @@ class YamlFileLoader extends FileLoader $this->container->setAlias($id, substr($service, 1)); return; - } elseif (isset($service['alias'])) { + } + + if (!is_array($service)) { + throw new InvalidArgumentException(sprintf('A service definition must be an array or a string starting with "@" but %s found for service "%s" in %s. Check your YAML syntax.', gettype($service), $id, $file)); + } + + if (isset($service['alias'])) { $public = !array_key_exists('public', $service) || (bool) $service['public']; $this->container->setAlias($id, new Alias($service['alias'], $public)); @@ -205,6 +227,10 @@ class YamlFileLoader extends FileLoader } if (isset($service['calls'])) { + if (!is_array($service['calls'])) { + throw new InvalidArgumentException(sprintf('Parameter "calls" must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file)); + } + foreach ($service['calls'] as $call) { $args = isset($call[1]) ? $this->resolveServices($call[1]) : array(); $definition->addMethodCall($call[0], $args); @@ -213,10 +239,14 @@ class YamlFileLoader extends FileLoader if (isset($service['tags'])) { if (!is_array($service['tags'])) { - throw new InvalidArgumentException(sprintf('Parameter "tags" must be an array for service "%s" in %s.', $id, $file)); + throw new InvalidArgumentException(sprintf('Parameter "tags" must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file)); } foreach ($service['tags'] as $tag) { + if (!is_array($tag)) { + throw new InvalidArgumentException(sprintf('A "tags" entry must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file)); + } + if (!isset($tag['name'])) { throw new InvalidArgumentException(sprintf('A "tags" entry is missing a "name" key for service "%s" in %s.', $id, $file)); } @@ -226,7 +256,7 @@ class YamlFileLoader extends FileLoader foreach ($tag as $attribute => $value) { if (!is_scalar($value) && null !== $value) { - throw new InvalidArgumentException(sprintf('A "tags" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s" in %s.', $id, $name, $attribute, $file)); + throw new InvalidArgumentException(sprintf('A "tags" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s" in %s. Check your YAML syntax.', $id, $name, $attribute, $file)); } } @@ -280,7 +310,7 @@ class YamlFileLoader extends FileLoader } if (!is_array($content)) { - throw new InvalidArgumentException(sprintf('The service file "%s" is not valid.', $file)); + throw new InvalidArgumentException(sprintf('The service file "%s" is not valid. It should contain an array. Check your YAML syntax.', $file)); } foreach (array_keys($content) as $namespace) { @@ -306,9 +336,9 @@ class YamlFileLoader extends FileLoader /** * Resolves services. * - * @param string $value + * @param string|array $value * - * @return Reference + * @return array|string|Reference */ private function resolveServices($value) { diff --git a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd index f7b578767b..034880fc88 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd +++ b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd @@ -156,7 +156,6 @@ - diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services2.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services2.xml index 43cf86c547..f1ac14dd5d 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services2.xml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services2.xml @@ -23,7 +23,6 @@ bar - value diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/bad_calls.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/bad_calls.yml new file mode 100644 index 0000000000..3f34b07eb0 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/bad_calls.yml @@ -0,0 +1,4 @@ +services: + method_call1: + class: FooClass + calls: foo diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/bad_import.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/bad_import.yml new file mode 100644 index 0000000000..0765dc8dd0 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/bad_import.yml @@ -0,0 +1,2 @@ +imports: + - foo.yml diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/bad_imports.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/bad_imports.yml new file mode 100644 index 0000000000..1ce9d57c6c --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/bad_imports.yml @@ -0,0 +1,2 @@ +imports: + foo:bar diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/bad_parameters.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/bad_parameters.yml new file mode 100644 index 0000000000..bbd13ac0b3 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/bad_parameters.yml @@ -0,0 +1,2 @@ +parameters: + foo:bar diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/bad_service.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/bad_service.yml new file mode 100644 index 0000000000..811af3c0ef --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/bad_service.yml @@ -0,0 +1,2 @@ +services: + foo: bar diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/bad_services.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/bad_services.yml new file mode 100644 index 0000000000..cfbf17ce5f --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/bad_services.yml @@ -0,0 +1 @@ +services: foo diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/badtag4.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/badtag4.yml new file mode 100644 index 0000000000..e8e99395b1 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/badtag4.yml @@ -0,0 +1,6 @@ +services: + foo_service: + class: FooClass + tags: + # tag is not an array + - foo diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php index b1f32e283e..f166f742d8 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php @@ -110,7 +110,6 @@ class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase 'a string', array('foo', 'bar'), ), - 'foo_bar' => new Reference('foo_bar'), 'mixedcase' => array('MixedCaseKey' => 'value'), 'constant' => PHP_EOL, ); @@ -147,7 +146,6 @@ class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase 'a string', array('foo', 'bar'), ), - 'foo_bar' => new Reference('foo_bar'), 'mixedcase' => array('MixedCaseKey' => 'value'), 'constant' => PHP_EOL, 'bar' => '%foo%', diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php index 37abca7483..83a7ea4f66 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php @@ -13,7 +13,6 @@ namespace Symfony\Component\DependencyInjection\Tests\Loader; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\Config\Loader\Loader; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; @@ -70,6 +69,29 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase } } + /** + * @dataProvider provideInvalidFiles + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + */ + public function testLoadInvalidFile($file) + { + $loader = new YamlFileLoader(new ContainerBuilder(), new FileLocator(self::$fixturesPath.'/yaml')); + + $loader->load($file.'.yml'); + } + + public function provideInvalidFiles() + { + return array( + array('bad_parameters'), + array('bad_imports'), + array('bad_import'), + array('bad_services'), + array('bad_service'), + array('bad_calls'), + ); + } + public function testLoadParameters() { $container = new ContainerBuilder(); @@ -169,7 +191,7 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase $this->assertFalse($loader->supports('foo.foo'), '->supports() returns true if the resource is loadable'); } - public function testNonArrayTagThrowsException() + public function testNonArrayTagsThrowsException() { $loader = new YamlFileLoader(new ContainerBuilder(), new FileLocator(self::$fixturesPath.'/yaml')); try { @@ -181,6 +203,16 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase } } + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + * @expectedExceptionMessage A "tags" entry must be an array for service + */ + public function testNonArrayTagThrowsException() + { + $loader = new YamlFileLoader(new ContainerBuilder(), new FileLocator(self::$fixturesPath.'/yaml')); + $loader->load('badtag4.yml'); + } + public function testTagWithoutNameThrowsException() { $loader = new YamlFileLoader(new ContainerBuilder(), new FileLocator(self::$fixturesPath.'/yaml')); diff --git a/src/Symfony/Component/Form/ButtonBuilder.php b/src/Symfony/Component/Form/ButtonBuilder.php index 69730d9d2e..806510d586 100644 --- a/src/Symfony/Component/Form/ButtonBuilder.php +++ b/src/Symfony/Component/Form/ButtonBuilder.php @@ -417,7 +417,7 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface * * This method should not be invoked. * - * @param array $data + * @param mixed $data * * @throws BadMethodCallException */ diff --git a/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPath.php b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPath.php index 662443ed72..1cb1baf025 100644 --- a/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPath.php +++ b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPath.php @@ -70,9 +70,17 @@ class ViolationPath implements \IteratorAggregate, PropertyPathInterface break; } - $this->elements[] = $elements[$i]; - $this->isIndex[] = true; - $this->mapsForm[] = true; + // All the following index items (regardless if .children is + // explicitly used) are children and grand-children + for (; $i < $l && $path->isIndex($i); ++$i) { + $this->elements[] = $elements[$i]; + $this->isIndex[] = true; + $this->mapsForm[] = true; + } + + // Rewind the pointer as the last element above didn't match + // (even if the pointer was moved forward) + --$i; } elseif ('data' === $elements[$i] && $path->isProperty($i)) { // Skip element "data" ++$i; diff --git a/src/Symfony/Component/Form/FormBuilder.php b/src/Symfony/Component/Form/FormBuilder.php index 434afa861a..e73c03fd7a 100644 --- a/src/Symfony/Component/Form/FormBuilder.php +++ b/src/Symfony/Component/Form/FormBuilder.php @@ -242,7 +242,7 @@ class FormBuilder extends FormConfigBuilder implements \IteratorAggregate, FormB throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); } - return new \ArrayIterator($this->children); + return new \ArrayIterator($this->all()); } /** diff --git a/src/Symfony/Component/Form/FormConfigBuilderInterface.php b/src/Symfony/Component/Form/FormConfigBuilderInterface.php index da2da0b668..50793a2c49 100644 --- a/src/Symfony/Component/Form/FormConfigBuilderInterface.php +++ b/src/Symfony/Component/Form/FormConfigBuilderInterface.php @@ -212,7 +212,7 @@ interface FormConfigBuilderInterface extends FormConfigInterface /** * Sets the initial data of the form. * - * @param array $data The data of the form in application format. + * @param mixed $data The data of the form in application format. * * @return self The configuration object. */ diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationMapperTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationMapperTest.php index a84d5b951a..315b565062 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationMapperTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationMapperTest.php @@ -708,14 +708,14 @@ class ViolationMapperTest extends \PHPUnit_Framework_TestCase array(self::LEVEL_2, 'address', '[address]', 'street', '[office][street]', 'data[address][office][street].prop'), // Edge cases which must not occur - array(self::LEVEL_1, 'address', 'address', 'street', 'street', 'children[address][street]'), - array(self::LEVEL_1, 'address', 'address', 'street', 'street', 'children[address][street].prop'), - array(self::LEVEL_1, 'address', 'address', 'street', '[street]', 'children[address][street]'), - array(self::LEVEL_1, 'address', 'address', 'street', '[street]', 'children[address][street].prop'), - array(self::LEVEL_1, 'address', '[address]', 'street', 'street', 'children[address][street]'), - array(self::LEVEL_1, 'address', '[address]', 'street', 'street', 'children[address][street].prop'), - array(self::LEVEL_1, 'address', '[address]', 'street', '[street]', 'children[address][street]'), - array(self::LEVEL_1, 'address', '[address]', 'street', '[street]', 'children[address][street].prop'), + array(self::LEVEL_2, 'address', 'address', 'street', 'street', 'children[address][street]'), + array(self::LEVEL_2, 'address', 'address', 'street', 'street', 'children[address][street].prop'), + array(self::LEVEL_2, 'address', 'address', 'street', '[street]', 'children[address][street]'), + array(self::LEVEL_2, 'address', 'address', 'street', '[street]', 'children[address][street].prop'), + array(self::LEVEL_2, 'address', '[address]', 'street', 'street', 'children[address][street]'), + array(self::LEVEL_2, 'address', '[address]', 'street', 'street', 'children[address][street].prop'), + array(self::LEVEL_2, 'address', '[address]', 'street', '[street]', 'children[address][street]'), + array(self::LEVEL_2, 'address', '[address]', 'street', '[street]', 'children[address][street].prop'), array(self::LEVEL_0, 'address', 'person.address', 'street', 'street', 'children[person].children[address].children[street]'), array(self::LEVEL_0, 'address', 'person.address', 'street', 'street', 'children[person].children[address].data.street'), diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationPathTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationPathTest.php index 2dab4ffad7..fdbf747f20 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationPathTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationPathTest.php @@ -30,7 +30,8 @@ class ViolationPathTest extends \PHPUnit_Framework_TestCase )), array('children[address][street]', array( array('address', true, true), - ), 'children[address]'), + array('street', true, true), + ), 'children[address].children[street]'), array('children[address].data', array( array('address', true, true), ), 'children[address]'), diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index 06d98c8580..98c747306c 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -1033,9 +1033,9 @@ class Request } /** - * Returns the requested URI. + * Returns the requested URI (path and query string). * - * @return string The raw URI (i.e. not urldecoded) + * @return string The raw URI (i.e. not URI decoded) * * @api */ @@ -1062,9 +1062,9 @@ class Request } /** - * Generates a normalized URI for the Request. + * Generates a normalized URI (URL) for the Request. * - * @return string A normalized URI for the Request + * @return string A normalized URI (URL) for the Request * * @see getQueryString() * diff --git a/src/Symfony/Component/HttpKernel/HttpCache/Esi.php b/src/Symfony/Component/HttpKernel/HttpCache/Esi.php index 3ba9ecd818..9dd99d64cc 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/Esi.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/Esi.php @@ -236,9 +236,9 @@ class Esi throw new \RuntimeException('Unable to process an ESI tag without a "src" attribute.'); } - return sprintf('esi->handle($this, \'%s\', \'%s\', %s) ?>'."\n", - $options['src'], - isset($options['alt']) ? $options['alt'] : null, + return sprintf('esi->handle($this, %s, %s, %s) ?>'."\n", + var_export($options['src'], true), + var_export(isset($options['alt']) ? $options['alt'] : '', true), isset($options['onerror']) && 'continue' == $options['onerror'] ? 'true' : 'false' ); } diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/EsiTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/EsiTest.php index c509706389..ad400c69ae 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/EsiTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/EsiTest.php @@ -103,6 +103,11 @@ class EsiTest extends \PHPUnit_Framework_TestCase $this->assertEquals('foo esi->handle($this, \'...\', \'alt\', true) ?>'."\n", $response->getContent()); $this->assertEquals('ESI', $response->headers->get('x-body-eval')); + $response = new Response('foo '); + $esi->process($request, $response); + + $this->assertEquals("foo esi->handle(\$this, 'foo\\'', 'bar\\'', true) ?>"."\n", $response->getContent()); + $response = new Response('foo '); $esi->process($request, $response); diff --git a/src/Symfony/Component/Intl/Exception/MissingResourceException.php b/src/Symfony/Component/Intl/Exception/MissingResourceException.php new file mode 100644 index 0000000000..e2eb3f210e --- /dev/null +++ b/src/Symfony/Component/Intl/Exception/MissingResourceException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Exception; + +/** + * Thrown when an invalid entry of a resource bundle was requested. + * + * @author Bernhard Schussek + */ +class MissingResourceException extends RuntimeException +{ +} diff --git a/src/Symfony/Component/Intl/Exception/ResourceBundleNotFoundException.php b/src/Symfony/Component/Intl/Exception/ResourceBundleNotFoundException.php new file mode 100644 index 0000000000..59da5ec0d5 --- /dev/null +++ b/src/Symfony/Component/Intl/Exception/ResourceBundleNotFoundException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Exception; + +/** + * @author Bernhard Schussek + */ +class ResourceBundleNotFoundException extends RuntimeException +{ +} diff --git a/src/Symfony/Component/Intl/Exception/UnexpectedTypeException.php b/src/Symfony/Component/Intl/Exception/UnexpectedTypeException.php new file mode 100644 index 0000000000..645d4926da --- /dev/null +++ b/src/Symfony/Component/Intl/Exception/UnexpectedTypeException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Exception; + +/** + * Thrown when a method argument had an unexpected type. + * + * @author Bernhard Schussek + */ +class UnexpectedTypeException extends InvalidArgumentException +{ + public function __construct($value, $expectedType) + { + parent::__construct(sprintf('Expected argument of type "%s", "%s" given', $expectedType, is_object($value) ? get_class($value) : gettype($value))); + } +} diff --git a/src/Symfony/Component/Intl/Locale.php b/src/Symfony/Component/Intl/Locale.php new file mode 100644 index 0000000000..d7e0d33e84 --- /dev/null +++ b/src/Symfony/Component/Intl/Locale.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl; + +/** + * Provides access to locale-related data. + * + * @author Bernhard Schussek + * + * @internal + */ +final class Locale extends \Locale +{ + /** + * Returns the fallback locale for a given locale, if any + * + * @param string $locale The ICU locale code to find the fallback for. + * + * @return string|null The ICU locale code of the fallback locale, or null + * if no fallback exists + */ + public static function getFallback($locale) + { + if (false === $pos = strrpos($locale, '_')) { + if ('root' === $locale) { + return; + } + + return 'root'; + } + + return substr($locale, 0, $pos); + } + + /** + * This class must not be instantiated. + */ + private function __construct() {} +} diff --git a/src/Symfony/Component/Intl/ResourceBundle/AbstractBundle.php b/src/Symfony/Component/Intl/ResourceBundle/AbstractBundle.php index 1aaadee20b..53be149d8d 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/AbstractBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle/AbstractBundle.php @@ -17,6 +17,8 @@ use Symfony\Component\Intl\ResourceBundle\Reader\StructuredBundleReaderInterface * Base class for {@link ResourceBundleInterface} implementations. * * @author Bernhard Schussek + * + * @internal */ abstract class AbstractBundle implements ResourceBundleInterface { diff --git a/src/Symfony/Component/Intl/ResourceBundle/Compiler/BundleCompiler.php b/src/Symfony/Component/Intl/ResourceBundle/Compiler/BundleCompiler.php index 174aa179f4..8249f66d0d 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Compiler/BundleCompiler.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Compiler/BundleCompiler.php @@ -17,6 +17,8 @@ use Symfony\Component\Intl\Exception\RuntimeException; * Compiles .txt resource bundles to binary .res files. * * @author Bernhard Schussek + * + * @internal */ class BundleCompiler implements BundleCompilerInterface { diff --git a/src/Symfony/Component/Intl/ResourceBundle/Compiler/BundleCompilerInterface.php b/src/Symfony/Component/Intl/ResourceBundle/Compiler/BundleCompilerInterface.php index 6184ea3eb1..d9cde44d82 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Compiler/BundleCompilerInterface.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Compiler/BundleCompilerInterface.php @@ -15,6 +15,8 @@ namespace Symfony\Component\Intl\ResourceBundle\Compiler; * Compiles a resource bundle. * * @author Bernhard Schussek + * + * @internal */ interface BundleCompilerInterface { diff --git a/src/Symfony/Component/Intl/ResourceBundle/CurrencyBundle.php b/src/Symfony/Component/Intl/ResourceBundle/CurrencyBundle.php index eec6710c4d..bcd486e996 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/CurrencyBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle/CurrencyBundle.php @@ -15,6 +15,8 @@ namespace Symfony\Component\Intl\ResourceBundle; * Default implementation of {@link CurrencyBundleInterface}. * * @author Bernhard Schussek + * + * @internal */ class CurrencyBundle extends AbstractBundle implements CurrencyBundleInterface { diff --git a/src/Symfony/Component/Intl/ResourceBundle/LanguageBundle.php b/src/Symfony/Component/Intl/ResourceBundle/LanguageBundle.php index b09381fa34..060b214ae8 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/LanguageBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle/LanguageBundle.php @@ -15,6 +15,8 @@ namespace Symfony\Component\Intl\ResourceBundle; * Default implementation of {@link LanguageBundleInterface}. * * @author Bernhard Schussek + * + * @internal */ class LanguageBundle extends AbstractBundle implements LanguageBundleInterface { diff --git a/src/Symfony/Component/Intl/ResourceBundle/LocaleBundle.php b/src/Symfony/Component/Intl/ResourceBundle/LocaleBundle.php index 0a7ed806b4..b1d72a3539 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/LocaleBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle/LocaleBundle.php @@ -15,6 +15,8 @@ namespace Symfony\Component\Intl\ResourceBundle; * Default implementation of {@link LocaleBundleInterface}. * * @author Bernhard Schussek + * + * @internal */ class LocaleBundle extends AbstractBundle implements LocaleBundleInterface { diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/AbstractBundleReader.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/AbstractBundleReader.php deleted file mode 100644 index c30693ac57..0000000000 --- a/src/Symfony/Component/Intl/ResourceBundle/Reader/AbstractBundleReader.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Intl\ResourceBundle\Reader; - -/** - * Base class for {@link BundleReaderInterface} implementations. - * - * @author Bernhard Schussek - */ -abstract class AbstractBundleReader implements BundleReaderInterface -{ - /** - * {@inheritdoc} - */ - public function getLocales($path) - { - $extension = '.' . $this->getFileExtension(); - $locales = glob($path . '/*' . $extension); - - // Remove file extension and sort - array_walk($locales, function (&$locale) use ($extension) { $locale = basename($locale, $extension); }); - sort($locales); - - return $locales; - } - - /** - * Returns the extension of locale files in this bundle. - * - * @return string The file extension (without leading dot). - */ - abstract protected function getFileExtension(); -} diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/BinaryBundleReader.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/BinaryBundleReader.php index 16ea1dd3aa..77b86aee42 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Reader/BinaryBundleReader.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Reader/BinaryBundleReader.php @@ -11,15 +11,17 @@ namespace Symfony\Component\Intl\ResourceBundle\Reader; -use Symfony\Component\Intl\Exception\RuntimeException; +use Symfony\Component\Intl\Exception\ResourceBundleNotFoundException; use Symfony\Component\Intl\ResourceBundle\Util\ArrayAccessibleResourceBundle; /** * Reads binary .res resource bundles. * * @author Bernhard Schussek + * + * @internal */ -class BinaryBundleReader extends AbstractBundleReader implements BundleReaderInterface +class BinaryBundleReader implements BundleReaderInterface { /** * {@inheritdoc} @@ -29,28 +31,39 @@ class BinaryBundleReader extends AbstractBundleReader implements BundleReaderInt // Point for future extension: Modify this class so that it works also // if the \ResourceBundle class is not available. try { - $bundle = new \ResourceBundle($locale, $path); + // Never enable fallback. We want to know if a bundle cannot be found + $bundle = new \ResourceBundle($locale, $path, false); } catch (\Exception $e) { // HHVM compatibility: constructor throws on invalid resource $bundle = null; } + // The bundle is NULL if the path does not look like a resource bundle + // (i.e. contain a bunch of *.res files) if (null === $bundle) { - throw new RuntimeException(sprintf( - 'Could not load the resource bundle "%s/%s.res".', + throw new ResourceBundleNotFoundException(sprintf( + 'The resource bundle "%s/%s.res" could not be found.', $path, $locale )); } + // Other possible errors are U_USING_FALLBACK_WARNING and U_ZERO_ERROR, + // which are OK for us. return new ArrayAccessibleResourceBundle($bundle); } /** * {@inheritdoc} */ - protected function getFileExtension() + public function getLocales($path) { - return 'res'; + $locales = glob($path.'/*.res'); + + // Remove file extension and sort + array_walk($locales, function (&$locale) { $locale = basename($locale, '.res'); }); + sort($locales); + + return $locales; } } diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/BufferedBundleReader.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/BufferedBundleReader.php index f005eeb4ac..19f6d672af 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Reader/BufferedBundleReader.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Reader/BufferedBundleReader.php @@ -15,6 +15,8 @@ use Symfony\Component\Intl\ResourceBundle\Util\RingBuffer; /** * @author Bernhard Schussek + * + * @internal */ class BufferedBundleReader implements BundleReaderInterface { diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleReaderInterface.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleReaderInterface.php index bc485cd526..c11d90e046 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleReaderInterface.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleReaderInterface.php @@ -15,6 +15,8 @@ namespace Symfony\Component\Intl\ResourceBundle\Reader; * Reads resource bundle files. * * @author Bernhard Schussek + * + * @internal */ interface BundleReaderInterface { diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/PhpBundleReader.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/PhpBundleReader.php index 663bcc9d78..2082916827 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Reader/PhpBundleReader.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Reader/PhpBundleReader.php @@ -11,29 +11,27 @@ namespace Symfony\Component\Intl\ResourceBundle\Reader; -use Symfony\Component\Intl\Exception\InvalidArgumentException; +use Symfony\Component\Intl\Exception\ResourceBundleNotFoundException; use Symfony\Component\Intl\Exception\RuntimeException; /** * Reads .php resource bundles. * * @author Bernhard Schussek + * + * @internal */ -class PhpBundleReader extends AbstractBundleReader implements BundleReaderInterface +class PhpBundleReader implements BundleReaderInterface { /** * {@inheritdoc} */ public function read($path, $locale) { - if ('en' !== $locale) { - throw new InvalidArgumentException('Only the locale "en" is supported.'); - } - - $fileName = $path . '/' . $locale . '.php'; + $fileName = $path.'/'.$locale.'.php'; if (!file_exists($fileName)) { - throw new RuntimeException(sprintf( + throw new ResourceBundleNotFoundException(sprintf( 'The resource bundle "%s/%s.php" does not exist.', $path, $locale @@ -54,8 +52,14 @@ class PhpBundleReader extends AbstractBundleReader implements BundleReaderInterf /** * {@inheritdoc} */ - protected function getFileExtension() + public function getLocales($path) { - return 'php'; + $locales = glob($path.'/*.php'); + + // Remove file extension and sort + array_walk($locales, function (&$locale) { $locale = basename($locale, '.php'); }); + sort($locales); + + return $locales; } } diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReader.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReader.php index 4b71c9bcb2..01ec26190a 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReader.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReader.php @@ -11,14 +11,20 @@ namespace Symfony\Component\Intl\ResourceBundle\Reader; +use Symfony\Component\Intl\Exception\MissingResourceException; +use Symfony\Component\Intl\Exception\OutOfBoundsException; +use Symfony\Component\Intl\Exception\ResourceBundleNotFoundException; +use Symfony\Component\Intl\Locale; use Symfony\Component\Intl\ResourceBundle\Util\RecursiveArrayAccess; /** - * A structured reader wrapping an existing resource bundle reader. + * Default implementation of {@link StructuredBundleReaderInterface}. * * @author Bernhard Schussek * * @see StructuredResourceBundleBundleReaderInterface + * + * @internal */ class StructuredBundleReader implements StructuredBundleReaderInterface { @@ -27,6 +33,13 @@ class StructuredBundleReader implements StructuredBundleReaderInterface */ private $reader; + /** + * A mapping of locale aliases to locales + * + * @var array + */ + private $localeAliases = array(); + /** * Creates an entry reader based on the given resource bundle reader. * @@ -37,6 +50,21 @@ class StructuredBundleReader implements StructuredBundleReaderInterface $this->reader = $reader; } + /** + * Stores a mapping of locale aliases to locales. + * + * This mapping is used when reading entries and merging them with their + * fallback locales. If an entry is read for a locale alias (e.g. "mo") + * that points to a locale with a fallback locale ("ro_MD"), the reader + * can continue at the correct fallback locale ("ro"). + * + * @param array $localeAliases A mapping of locale aliases to locales + */ + public function setLocaleAliases($localeAliases) + { + $this->localeAliases = $localeAliases; + } + /** * {@inheritdoc} */ @@ -48,66 +76,117 @@ class StructuredBundleReader implements StructuredBundleReaderInterface /** * {@inheritdoc} */ - public function getLocales($path) + public function readEntry($path, $locale, array $indices, $fallback = true) { - return $this->reader->getLocales($path); + $entry = null; + $isMultiValued = false; + $readSucceeded = false; + $exception = null; + $currentLocale = $locale; + $testedLocales = array(); + + while (null !== $currentLocale) { + // Resolve any aliases to their target locales + if (isset($this->localeAliases[$currentLocale])) { + $currentLocale = $this->localeAliases[$currentLocale]; + } + + try { + $data = $this->reader->read($path, $currentLocale); + $currentEntry = RecursiveArrayAccess::get($data, $indices); + $readSucceeded = true; + + $isCurrentTraversable = $currentEntry instanceof \Traversable; + $isCurrentMultiValued = $isCurrentTraversable || is_array($currentEntry); + + // Return immediately if fallback is disabled or we are dealing + // with a scalar non-null entry + if (!$fallback || (!$isCurrentMultiValued && null !== $currentEntry)) { + return $currentEntry; + } + + // ========================================================= + // Fallback is enabled, entry is either multi-valued or NULL + // ========================================================= + + // If entry is multi-valued, convert to array + if ($isCurrentTraversable) { + $currentEntry = iterator_to_array($currentEntry); + } + + // If previously read entry was multi-valued too, merge them + if ($isCurrentMultiValued && $isMultiValued) { + $currentEntry = array_merge($currentEntry, $entry); + } + + // Keep the previous entry if the current entry is NULL + if (null !== $currentEntry) { + $entry = $currentEntry; + } + + // If this or the previous entry was multi-valued, we are dealing + // with a merged, multi-valued entry now + $isMultiValued = $isMultiValued || $isCurrentMultiValued; + } catch (ResourceBundleNotFoundException $e) { + // Continue if there is a fallback locale for the current + // locale + $exception = $e; + } catch (OutOfBoundsException $e) { + // Remember exception and rethrow if we cannot find anything in + // the fallback locales either + $exception = $e; + } + + // Remember which locales we tried + $testedLocales[] = $currentLocale.'.res'; + + // Check whether fallback is allowed + if (!$fallback) { + break; + } + + // Then determine fallback locale + $currentLocale = Locale::getFallback($currentLocale); + } + + // Multi-valued entry was merged + if ($isMultiValued) { + return $entry; + } + + // Entry is still NULL, but no read error occurred + if ($readSucceeded) { + return $entry; + } + + // Entry is still NULL, read error occurred. Throw an exception + // containing the detailed path and locale + $errorMessage = sprintf( + 'Couldn\'t read the indices [%s] from "%s/%s.res".', + implode('][', $indices), + $path, + $locale + ); + + // Append fallback locales, if any + if (count($testedLocales) > 1) { + // Remove original locale + array_shift($testedLocales); + + $errorMessage .= sprintf( + ' The indices also couldn\'t be found in the fallback locale(s) "%s".', + implode('", "', $testedLocales) + ); + } + + throw new MissingResourceException($errorMessage, 0, $exception); } /** * {@inheritdoc} */ - public function readEntry($path, $locale, array $indices, $fallback = true) + public function getLocales($path) { - $data = $this->reader->read($path, $locale); - - $entry = RecursiveArrayAccess::get($data, $indices); - $multivalued = is_array($entry) || $entry instanceof \Traversable; - - if (!($fallback && (null === $entry || $multivalued))) { - return $entry; - } - - if (null !== ($fallbackLocale = $this->getFallbackLocale($locale))) { - $parentEntry = $this->readEntry($path, $fallbackLocale, $indices, true); - - if ($entry || $parentEntry) { - $multivalued = $multivalued || is_array($parentEntry) || $parentEntry instanceof \Traversable; - - if ($multivalued) { - if ($entry instanceof \Traversable) { - $entry = iterator_to_array($entry); - } - - if ($parentEntry instanceof \Traversable) { - $parentEntry = iterator_to_array($parentEntry); - } - - $entry = array_merge( - $parentEntry ?: array(), - $entry ?: array() - ); - } else { - $entry = null === $entry ? $parentEntry : $entry; - } - } - } - - return $entry; - } - - /** - * Returns the fallback locale for a given locale, if any - * - * @param string $locale The locale to find the fallback for. - * - * @return string|null The fallback locale, or null if no parent exists - */ - private function getFallbackLocale($locale) - { - if (false === $pos = strrpos($locale, '_')) { - return; - } - - return substr($locale, 0, $pos); + return $this->reader->getLocales($path); } } diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReaderInterface.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReaderInterface.php index 5d435c485c..19bb3fb49b 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReaderInterface.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReaderInterface.php @@ -11,10 +11,14 @@ namespace Symfony\Component\Intl\ResourceBundle\Reader; +use Symfony\Component\Intl\Exception\MissingResourceException; + /** * Reads individual entries of a resource file. * * @author Bernhard Schussek + * + * @internal */ interface StructuredBundleReaderInterface extends BundleReaderInterface { @@ -43,8 +47,9 @@ interface StructuredBundleReaderInterface extends BundleReaderInterface * in the requested locale. * * @return mixed Returns an array or {@link \ArrayAccess} instance for - * complex data, a scalar value for simple data and NULL - * if the given path could not be accessed. + * complex data and a scalar value for simple data. + * + * @throws MissingResourceException If the indices cannot be accessed */ public function readEntry($path, $locale, array $indices, $fallback = true); } diff --git a/src/Symfony/Component/Intl/ResourceBundle/RegionBundle.php b/src/Symfony/Component/Intl/ResourceBundle/RegionBundle.php index bbfbedeed9..0a4cce8a97 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/RegionBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle/RegionBundle.php @@ -15,6 +15,8 @@ namespace Symfony\Component\Intl\ResourceBundle; * Default implementation of {@link RegionBundleInterface}. * * @author Bernhard Schussek + * + * @internal */ class RegionBundle extends AbstractBundle implements RegionBundleInterface { diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/BundleTransformer.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/BundleTransformer.php index 0692d6fe50..c613486f18 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/BundleTransformer.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/BundleTransformer.php @@ -19,6 +19,8 @@ use Symfony\Component\Intl\ResourceBundle\Writer\PhpBundleWriter; * Compiles a number of resource bundles based on predefined compilation rules. * * @author Bernhard Schussek + * + * @internal */ class BundleTransformer { diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/CompilationContext.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/CompilationContext.php index cdc1951b96..2b336e8138 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/CompilationContext.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/CompilationContext.php @@ -18,6 +18,8 @@ use Symfony\Component\Intl\ResourceBundle\Compiler\BundleCompilerInterface; * Default implementation of {@link CompilationContextInterface}. * * @author Bernhard Schussek + * + * @internal */ class CompilationContext implements CompilationContextInterface { diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/CompilationContextInterface.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/CompilationContextInterface.php index f05c28079a..cd13ace6f5 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/CompilationContextInterface.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/CompilationContextInterface.php @@ -15,6 +15,8 @@ namespace Symfony\Component\Intl\ResourceBundle\Transformer; * Stores contextual information for resource bundle compilation. * * @author Bernhard Schussek + * + * @internal */ interface CompilationContextInterface { diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php index 95783b3b06..d38cfc7b70 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php @@ -21,6 +21,8 @@ use Symfony\Component\Intl\Util\IcuVersion; * The rule for compiling the currency bundle. * * @author Bernhard Schussek + * + * @internal */ class CurrencyBundleTransformationRule implements TransformationRuleInterface { diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LanguageBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LanguageBundleTransformationRule.php index 5e6f901849..7d78d74880 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LanguageBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LanguageBundleTransformationRule.php @@ -20,6 +20,8 @@ use Symfony\Component\Intl\Util\IcuVersion; * The rule for compiling the language bundle. * * @author Bernhard Schussek + * + * @internal */ class LanguageBundleTransformationRule implements TransformationRuleInterface { diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php index af4dfea557..22ec911372 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php @@ -21,6 +21,8 @@ use Symfony\Component\Intl\ResourceBundle\Writer\TextBundleWriter; * The rule for compiling the locale bundle. * * @author Bernhard Schussek + * + * @internal */ class LocaleBundleTransformationRule implements TransformationRuleInterface { diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/RegionBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/RegionBundleTransformationRule.php index 52fdbed8c3..300ad02563 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/RegionBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/RegionBundleTransformationRule.php @@ -20,6 +20,8 @@ use Symfony\Component\Intl\Util\IcuVersion; * The rule for compiling the region bundle. * * @author Bernhard Schussek + * + * @internal */ class RegionBundleTransformationRule implements TransformationRuleInterface { diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/TransformationRuleInterface.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/TransformationRuleInterface.php index 3965e0d2b7..f02bf285d9 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/TransformationRuleInterface.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/TransformationRuleInterface.php @@ -18,6 +18,8 @@ use Symfony\Component\Intl\ResourceBundle\Transformer\StubbingContextInterface; * Contains instruction for compiling a resource bundle. * * @author Bernhard Schussek + * + * @internal */ interface TransformationRuleInterface { diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/StubbingContext.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/StubbingContext.php index 25ab68dbfc..de2604e0b7 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/StubbingContext.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/StubbingContext.php @@ -15,6 +15,8 @@ use Symfony\Component\Filesystem\Filesystem; /** * @author Bernhard Schussek + * + * @internal */ class StubbingContext implements StubbingContextInterface { diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/StubbingContextInterface.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/StubbingContextInterface.php index dc49255620..596ee1bb7e 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/StubbingContextInterface.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/StubbingContextInterface.php @@ -13,6 +13,8 @@ namespace Symfony\Component\Intl\ResourceBundle\Transformer; /** * @author Bernhard Schussek + * + * @internal */ interface StubbingContextInterface { diff --git a/src/Symfony/Component/Intl/ResourceBundle/Util/ArrayAccessibleResourceBundle.php b/src/Symfony/Component/Intl/ResourceBundle/Util/ArrayAccessibleResourceBundle.php index 9a4cccb461..44f1b2c580 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Util/ArrayAccessibleResourceBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Util/ArrayAccessibleResourceBundle.php @@ -20,6 +20,8 @@ use Symfony\Component\Intl\Exception\BadMethodCallException; * This class can be removed once that bug is fixed. * * @author Bernhard Schussek + * + * @internal */ class ArrayAccessibleResourceBundle implements \ArrayAccess, \IteratorAggregate, \Countable { @@ -30,16 +32,16 @@ class ArrayAccessibleResourceBundle implements \ArrayAccess, \IteratorAggregate, $this->bundleImpl = $bundleImpl; } - public function get($offset, $fallback = null) + public function get($offset) { - $value = $this->bundleImpl->get($offset, $fallback); + $value = $this->bundleImpl->get($offset); return $value instanceof \ResourceBundle ? new static($value) : $value; } public function offsetExists($offset) { - return null !== $this->bundleImpl[$offset]; + return null !== $this->bundleImpl->get($offset); } public function offsetGet($offset) diff --git a/src/Symfony/Component/Intl/ResourceBundle/Util/RecursiveArrayAccess.php b/src/Symfony/Component/Intl/ResourceBundle/Util/RecursiveArrayAccess.php index 5257e9c1ca..0c22550401 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Util/RecursiveArrayAccess.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Util/RecursiveArrayAccess.php @@ -11,19 +11,35 @@ namespace Symfony\Component\Intl\ResourceBundle\Util; +use Symfony\Component\Intl\Exception\OutOfBoundsException; + /** * @author Bernhard Schussek + * + * @internal */ class RecursiveArrayAccess { public static function get($array, array $indices) { foreach ($indices as $index) { - if (!$array instanceof \ArrayAccess && !is_array($array)) { - return; + // Use array_key_exists() for arrays, isset() otherwise + if (is_array($array)) { + if (array_key_exists($index, $array)) { + $array = $array[$index]; + continue; + } + } elseif ($array instanceof \ArrayAccess) { + if (isset($array[$index])) { + $array = $array[$index]; + continue; + } } - - $array = $array[$index]; + + throw new OutOfBoundsException(sprintf( + 'The index %s does not exist.', + $index + )); } return $array; diff --git a/src/Symfony/Component/Intl/ResourceBundle/Util/RingBuffer.php b/src/Symfony/Component/Intl/ResourceBundle/Util/RingBuffer.php index 7ccbd1e702..59cfdaac90 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Util/RingBuffer.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Util/RingBuffer.php @@ -21,6 +21,8 @@ use Symfony\Component\Intl\Exception\OutOfBoundsException; * then the second and so on. * * @author Bernhard Schussek + * + * @internal */ class RingBuffer implements \ArrayAccess { diff --git a/src/Symfony/Component/Intl/ResourceBundle/Writer/BundleWriterInterface.php b/src/Symfony/Component/Intl/ResourceBundle/Writer/BundleWriterInterface.php index cc3b958657..f612aacae7 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Writer/BundleWriterInterface.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Writer/BundleWriterInterface.php @@ -15,6 +15,8 @@ namespace Symfony\Component\Intl\ResourceBundle\Writer; * Writes resource bundle files. * * @author Bernhard Schussek + * + * @internal */ interface BundleWriterInterface { diff --git a/src/Symfony/Component/Intl/ResourceBundle/Writer/PhpBundleWriter.php b/src/Symfony/Component/Intl/ResourceBundle/Writer/PhpBundleWriter.php index d2688b49bc..d5a30b907d 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Writer/PhpBundleWriter.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Writer/PhpBundleWriter.php @@ -15,6 +15,8 @@ namespace Symfony\Component\Intl\ResourceBundle\Writer; * Writes .php resource bundles. * * @author Bernhard Schussek + * + * @internal */ class PhpBundleWriter implements BundleWriterInterface { diff --git a/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php b/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php index c2fff38c7b..2aa702089f 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php @@ -11,26 +11,31 @@ namespace Symfony\Component\Intl\ResourceBundle\Writer; +use Symfony\Component\Intl\Exception\UnexpectedTypeException; + /** * Writes .txt resource bundles. * - * The resulting files can be converted to binary .res files using the - * {@link \Symfony\Component\Intl\ResourceBundle\Transformer\BundleCompiler}. + * The resulting files can be converted to binary .res files using a + * {@link \Symfony\Component\Intl\ResourceBundle\Compiler\BundleCompilerInterface} + * implementation. * * @author Bernhard Schussek * * @see http://source.icu-project.org/repos/icu/icuhtml/trunk/design/bnf_rb.txt + * + * @internal */ class TextBundleWriter implements BundleWriterInterface { /** * {@inheritdoc} */ - public function write($path, $locale, $data) + public function write($path, $locale, $data, $fallback = true) { $file = fopen($path.'/'.$locale.'.txt', 'w'); - $this->writeResourceBundle($file, $locale, $data); + $this->writeResourceBundle($file, $locale, $data, $fallback); fclose($file); } @@ -41,14 +46,16 @@ class TextBundleWriter implements BundleWriterInterface * @param resource $file The file handle to write to. * @param string $bundleName The name of the bundle. * @param mixed $value The value of the node. + * @param bool $fallback Whether the resource bundle should be merged + * with the fallback locale. * * @see http://source.icu-project.org/repos/icu/icuhtml/trunk/design/bnf_rb.txt */ - private function writeResourceBundle($file, $bundleName, $value) + private function writeResourceBundle($file, $bundleName, $value, $fallback) { fwrite($file, $bundleName); - $this->writeTable($file, $value, 0); + $this->writeTable($file, $value, 0, $fallback); fwrite($file, "\n"); } @@ -72,16 +79,25 @@ class TextBundleWriter implements BundleWriterInterface return; } + if ($value instanceof \Traversable) { + $value = iterator_to_array($value); + } + if (is_array($value)) { - if (count($value) === count(array_filter($value, 'is_int'))) { + $intValues = count($value) === count(array_filter($value, 'is_int')); + + $keys = array_keys($value); + + // check that the keys are 0-indexed and ascending + $intKeys = $keys === range(0, count($keys) - 1); + + if ($intValues && $intKeys) { $this->writeIntVector($file, $value, $indentation); return; } - $keys = array_keys($value); - - if (count($keys) === count(array_filter($keys, 'is_int'))) { + if ($intKeys) { $this->writeArray($file, $value, $indentation); return; @@ -180,16 +196,35 @@ class TextBundleWriter implements BundleWriterInterface /** * Writes a "table" node. * - * @param resource $file The file handle to write to. - * @param array $value The value of the node. - * @param int $indentation The number of levels to indent. + * @param resource $file The file handle to write to. + * @param array|\Traversable $value The value of the node. + * @param int $indentation The number of levels to indent. + * @param bool $fallback Whether the table should be merged + * with the fallback locale. + * + * @throws UnexpectedTypeException When $value is not an array and not a + * \Traversable instance. */ - private function writeTable($file, array $value, $indentation) + private function writeTable($file, $value, $indentation, $fallback = true) { + if (!is_array($value) && !$value instanceof \Traversable) { + throw new UnexpectedTypeException($value, 'array or \Traversable'); + } + + if (!$fallback) { + fwrite($file, ":table(nofallback)"); + } + fwrite($file, "{\n"); foreach ($value as $key => $entry) { fwrite($file, str_repeat(' ', $indentation + 1)); + + // escape colons, otherwise they are interpreted as resource types + if (false !== strpos($key, ':') || false !== strpos($key, ' ')) { + $key = '"'.$key.'"'; + } + fwrite($file, $key); $this->writeResource($file, $entry, $indentation + 1); diff --git a/src/Symfony/Component/Intl/Resources/bin/common.php b/src/Symfony/Component/Intl/Resources/bin/common.php index eb7643dfbd..1e25265d57 100644 --- a/src/Symfony/Component/Intl/Resources/bin/common.php +++ b/src/Symfony/Component/Intl/Resources/bin/common.php @@ -11,11 +11,11 @@ define('LINE_WIDTH', 75); -define('LINE', str_repeat('-', LINE_WIDTH) . "\n"); +define('LINE', str_repeat('-', LINE_WIDTH)."\n"); function bailout($message) { - echo wordwrap($message, LINE_WIDTH) . " Aborting.\n"; + echo wordwrap($message, LINE_WIDTH)." Aborting.\n"; exit(1); } @@ -31,7 +31,7 @@ function centered($text) { $padding = (int) ((LINE_WIDTH - strlen($text))/2); - return str_repeat(' ', $padding) . $text; + return str_repeat(' ', $padding).$text; } function cd($dir) @@ -47,7 +47,7 @@ function run($command) if (0 !== $status) { $output = implode("\n", $output); - echo "Error while running:\n " . getcwd() . '$ ' . $command . "\nOutput:\n" . LINE . "$output\n" . LINE; + echo "Error while running:\n ".getcwd().'$ '.$command."\nOutput:\n".LINE."$output\n".LINE; bailout("\"$command\" failed."); } @@ -55,10 +55,10 @@ function run($command) function get_icu_version_from_genrb($genrb) { - exec($genrb . ' --version 2>&1', $output, $status); + exec($genrb.' --version 2>&1', $output, $status); if (0 !== $status) { - bailout($genrb . ' failed.'); + bailout($genrb.' failed.'); } if (!preg_match('/ICU version ([\d\.]+)/', implode('', $output), $matches)) { @@ -67,3 +67,27 @@ function get_icu_version_from_genrb($genrb) return $matches[1]; } + +set_exception_handler(function (\Exception $exception) { + echo "\n"; + + $cause = $exception; + $root = true; + + while (null !== $cause) { + if (!$root) { + echo "Caused by\n"; + } + + echo get_class($cause).": ".$cause->getMessage()."\n"; + echo "\n"; + echo $cause->getFile().":".$cause->getLine()."\n"; + foreach ($cause->getTrace() as $trace) { + echo $trace['file'].":".$trace['line']."\n"; + } + echo "\n"; + + $cause = $cause->getPrevious(); + $root = false; + } +}); diff --git a/src/Symfony/Component/Intl/Resources/bin/icu.ini b/src/Symfony/Component/Intl/Resources/bin/icu.ini index 7f1ad90a6a..adfb71643d 100644 --- a/src/Symfony/Component/Intl/Resources/bin/icu.ini +++ b/src/Symfony/Component/Intl/Resources/bin/icu.ini @@ -8,3 +8,5 @@ 49 = http://source.icu-project.org/repos/icu/icu/tags/release-49-1-2/source 50 = http://source.icu-project.org/repos/icu/icu/tags/release-50-1-2/source 51 = http://source.icu-project.org/repos/icu/icu/tags/release-51-2/source +52 = http://source.icu-project.org/repos/icu/icu/tags/release-52-1/source +53 = http://source.icu-project.org/repos/icu/icu/tags/release-53-1/source diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/AbstractBundleReaderTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/AbstractBundleReaderTest.php deleted file mode 100644 index 2da7f90de4..0000000000 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/AbstractBundleReaderTest.php +++ /dev/null @@ -1,64 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Intl\Tests\ResourceBundle\Reader; - -use Symfony\Component\Filesystem\Filesystem; - -/** - * @author Bernhard Schussek - */ -class AbstractBundleReaderTest extends \PHPUnit_Framework_TestCase -{ - private $directory; - - /** - * @var Filesystem - */ - private $filesystem; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - private $reader; - - protected function setUp() - { - $this->directory = sys_get_temp_dir() . '/AbstractBundleReaderTest/' . rand(1000, 9999); - $this->filesystem = new Filesystem(); - $this->reader = $this->getMockForAbstractClass('Symfony\Component\Intl\ResourceBundle\Reader\AbstractBundleReader'); - - $this->filesystem->mkdir($this->directory); - } - - protected function tearDown() - { - $this->filesystem->remove($this->directory); - } - - public function testGetLocales() - { - $this->filesystem->touch($this->directory . '/en.foo'); - $this->filesystem->touch($this->directory . '/de.foo'); - $this->filesystem->touch($this->directory . '/fr.foo'); - $this->filesystem->touch($this->directory . '/bo.txt'); - $this->filesystem->touch($this->directory . '/gu.bin'); - $this->filesystem->touch($this->directory . '/s.lol'); - - $this->reader->expects($this->any()) - ->method('getFileExtension') - ->will($this->returnValue('foo')); - - $sortedLocales = array('de', 'en', 'fr'); - - $this->assertSame($sortedLocales, $this->reader->getLocales($this->directory)); - } -} diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/BinaryBundleReaderTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/BinaryBundleReaderTest.php index 3aefbae7fd..526424d1a3 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/BinaryBundleReaderTest.php +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/BinaryBundleReaderTest.php @@ -33,19 +33,61 @@ class BinaryBundleReaderTest extends \PHPUnit_Framework_TestCase public function testReadReturnsArrayAccess() { - $data = $this->reader->read(__DIR__ . '/Fixtures', 'en'); + $data = $this->reader->read(__DIR__.'/Fixtures/res', 'ro'); $this->assertInstanceOf('\ArrayAccess', $data); $this->assertSame('Bar', $data['Foo']); $this->assertFalse(isset($data['ExistsNot'])); } + public function testReadFollowsAlias() + { + // "alias" = "ro" + $data = $this->reader->read(__DIR__.'/Fixtures/res', 'alias'); + + $this->assertInstanceOf('\ArrayAccess', $data); + $this->assertSame('Bar', $data['Foo']); + $this->assertFalse(isset($data['ExistsNot'])); + } + + public function testReadDoesNotFollowFallback() + { + // "ro_MD" -> "ro" + $data = $this->reader->read(__DIR__.'/Fixtures/res', 'ro_MD'); + + $this->assertInstanceOf('\ArrayAccess', $data); + $this->assertSame('Bam', $data['Baz']); + $this->assertFalse(isset($data['Foo'])); + $this->assertNull($data['Foo']); + $this->assertFalse(isset($data['ExistsNot'])); + } + + public function testReadDoesNotFollowFallbackAlias() + { + // "mo" = "ro_MD" -> "ro" + $data = $this->reader->read(__DIR__.'/Fixtures/res', 'mo'); + + $this->assertInstanceOf('\ArrayAccess', $data); + $this->assertSame('Bam', $data['Baz'], 'data from the aliased locale can be accessed'); + $this->assertFalse(isset($data['Foo'])); + $this->assertNull($data['Foo']); + $this->assertFalse(isset($data['ExistsNot'])); + } + /** - * @expectedException \Symfony\Component\Intl\Exception\RuntimeException + * @expectedException \Symfony\Component\Intl\Exception\ResourceBundleNotFoundException */ public function testReadFailsIfNonExistingLocale() { - $this->reader->read(__DIR__ . '/Fixtures', 'foo'); + $this->reader->read(__DIR__.'/Fixtures/res', 'foo'); + } + + /** + * @expectedException \Symfony\Component\Intl\Exception\ResourceBundleNotFoundException + */ + public function testReadFailsIfNonExistingFallbackLocale() + { + $this->reader->read(__DIR__.'/Fixtures/res', 'ro_AT'); } /** @@ -53,6 +95,6 @@ class BinaryBundleReaderTest extends \PHPUnit_Framework_TestCase */ public function testReadFailsIfNonExistingDirectory() { - $this->reader->read(__DIR__ . '/foo', 'en'); + $this->reader->read(__DIR__.'/foo', 'ro'); } } diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/build.sh b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/build.sh new file mode 100755 index 0000000000..50513e7a94 --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/build.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +if [ -z "$ICU_BUILD_DIR" ]; then + echo "Please set the ICU_BUILD_DIR environment variable" + exit +fi + +if [ ! -d "$ICU_BUILD_DIR" ]; then + echo "The directory $ICU_BUILD_DIR pointed at by ICU_BUILD_DIR does not exist" + exit +fi + +DIR=`dirname $0` + +rm $DIR/res/*.res + +LD_LIBRARY_PATH=$ICU_BUILD_DIR/lib $ICU_BUILD_DIR/bin/genrb -d $DIR/res $DIR/txt/*.txt diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/php/en.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/php/en.php new file mode 100644 index 0000000000..f2b06a91ad --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/php/en.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + 'Foo' => 'Bar', +); diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/res/alias.res b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/res/alias.res new file mode 100644 index 0000000000..4f0ab7eaa3 Binary files /dev/null and b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/res/alias.res differ diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/res/mo.res b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/res/mo.res new file mode 100644 index 0000000000..3f8911a731 Binary files /dev/null and b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/res/mo.res differ diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/res/ro.res b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/res/ro.res new file mode 100644 index 0000000000..c78e9045bf Binary files /dev/null and b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/res/ro.res differ diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/res/ro_MD.res b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/res/ro_MD.res new file mode 100644 index 0000000000..c8b0810ec6 Binary files /dev/null and b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/res/ro_MD.res differ diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/res/root.res b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/res/root.res new file mode 100644 index 0000000000..81ba7eaedb Binary files /dev/null and b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/res/root.res differ diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/alias.txt b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/alias.txt new file mode 100644 index 0000000000..d6e216f4cb --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/alias.txt @@ -0,0 +1,3 @@ +alias{ + "%%ALIAS"{"ro"} +} diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/mo.txt b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/mo.txt new file mode 100644 index 0000000000..3ce23bcc63 --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/mo.txt @@ -0,0 +1,3 @@ +mo{ + "%%ALIAS"{"ro_MD"} +} diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/ro.txt b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/ro.txt new file mode 100644 index 0000000000..80d28889cf --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/ro.txt @@ -0,0 +1,3 @@ +ro{ + Foo{"Bar"} +} diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/ro_MD.txt b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/ro_MD.txt new file mode 100644 index 0000000000..fcbb3bc07d --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/ro_MD.txt @@ -0,0 +1,3 @@ +ro_MD{ + Baz{"Bam"} +} diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/root.txt b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/root.txt new file mode 100644 index 0000000000..4d8265997f --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/root.txt @@ -0,0 +1,6 @@ +root{ + /** + * so genrb doesn't issue warnings + */ + ___{""} +} diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/PhpBundleReaderTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/PhpBundleReaderTest.php index 2fee35599f..fd5a0843c1 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/PhpBundleReaderTest.php +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/PhpBundleReaderTest.php @@ -30,7 +30,7 @@ class PhpBundleReaderTest extends \PHPUnit_Framework_TestCase public function testReadReturnsArray() { - $data = $this->reader->read(__DIR__ . '/Fixtures', 'en'); + $data = $this->reader->read(__DIR__.'/Fixtures/php', 'en'); $this->assertTrue(is_array($data)); $this->assertSame('Bar', $data['Foo']); @@ -38,11 +38,11 @@ class PhpBundleReaderTest extends \PHPUnit_Framework_TestCase } /** - * @expectedException \Symfony\Component\Intl\Exception\InvalidArgumentException + * @expectedException \Symfony\Component\Intl\Exception\ResourceBundleNotFoundException */ - public function testReadFailsIfLocaleOtherThanEn() + public function testReadFailsIfNonExistingLocale() { - $this->reader->read(__DIR__ . '/Fixtures', 'foo'); + $this->reader->read(__DIR__.'/Fixtures/php', 'foo'); } /** @@ -50,7 +50,7 @@ class PhpBundleReaderTest extends \PHPUnit_Framework_TestCase */ public function testReadFailsIfNonExistingDirectory() { - $this->reader->read(__DIR__ . '/foo', 'en'); + $this->reader->read(__DIR__.'/foo', 'en'); } /** @@ -58,6 +58,6 @@ class PhpBundleReaderTest extends \PHPUnit_Framework_TestCase */ public function testReadFailsIfNotAFile() { - $this->reader->read(__DIR__ . '/Fixtures/NotAFile', 'en'); + $this->reader->read(__DIR__.'/Fixtures/NotAFile', 'en'); } } diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/StructuredBundleReaderTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/StructuredBundleReaderTest.php index 600236eb3e..e51e7ef2ee 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/StructuredBundleReaderTest.php +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/StructuredBundleReaderTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Intl\Tests\ResourceBundle\Reader; +use Symfony\Component\Intl\Exception\ResourceBundleNotFoundException; use Symfony\Component\Intl\ResourceBundle\Reader\StructuredBundleReader; /** @@ -30,73 +31,146 @@ class StructuredBundleReaderTest extends \PHPUnit_Framework_TestCase */ private $readerImpl; + private static $data = array( + 'Entries' => array( + 'Foo' => 'Bar', + 'Bar' => 'Baz', + ), + 'Foo' => 'Bar', + 'Version' => '2.0', + ); + + private static $fallbackData = array( + 'Entries' => array( + 'Foo' => 'Foo', + 'Bam' => 'Lah', + ), + 'Baz' => 'Foo', + 'Version' => '1.0', + ); + + private static $mergedData = array( + // no recursive merging -> too complicated + 'Entries' => array( + 'Foo' => 'Bar', + 'Bar' => 'Baz', + ), + 'Baz' => 'Foo', + 'Version' => '2.0', + 'Foo' => 'Bar', + ); + protected function setUp() { $this->readerImpl = $this->getMock('Symfony\Component\Intl\ResourceBundle\Reader\StructuredBundleReaderInterface'); $this->reader = new StructuredBundleReader($this->readerImpl); } - public function testGetLocales() + public function testForwardCallToRead() { - $locales = array('en', 'de', 'fr'); - - $this->readerImpl->expects($this->once()) - ->method('getLocales') - ->with(self::RES_DIR) - ->will($this->returnValue($locales)); - - $this->assertSame($locales, $this->reader->getLocales(self::RES_DIR)); - } - - public function testRead() - { - $data = array('foo', 'bar'); - $this->readerImpl->expects($this->once()) ->method('read') - ->with(self::RES_DIR, 'en') - ->will($this->returnValue($data)); + ->with(self::RES_DIR, 'root') + ->will($this->returnValue(self::$data)); - $this->assertSame($data, $this->reader->read(self::RES_DIR, 'en')); + $this->assertSame(self::$data, $this->reader->read(self::RES_DIR, 'root')); } - public function testReadEntryNoParams() + public function testReadEntireDataFileIfNoIndicesGiven() { - $data = array('foo', 'bar'); - - $this->readerImpl->expects($this->once()) + $this->readerImpl->expects($this->at(0)) ->method('read') ->with(self::RES_DIR, 'en') - ->will($this->returnValue($data)); + ->will($this->returnValue(self::$data)); - $this->assertSame($data, $this->reader->readEntry(self::RES_DIR, 'en', array())); + $this->readerImpl->expects($this->at(1)) + ->method('read') + ->with(self::RES_DIR, 'root') + ->will($this->returnValue(self::$fallbackData)); + + $this->assertSame(self::$mergedData, $this->reader->readEntry(self::RES_DIR, 'en', array())); } - public function testReadEntryWithParam() + public function testReadExistingEntry() { - $data = array('Foo' => array('Bar' => 'Baz')); - $this->readerImpl->expects($this->once()) ->method('read') - ->with(self::RES_DIR, 'en') - ->will($this->returnValue($data)); + ->with(self::RES_DIR, 'root') + ->will($this->returnValue(self::$data)); - $this->assertSame('Baz', $this->reader->readEntry(self::RES_DIR, 'en', array('Foo', 'Bar'))); + $this->assertSame('Bar', $this->reader->readEntry(self::RES_DIR, 'root', array('Entries', 'Foo'))); } - public function testReadEntryWithUnresolvablePath() + /** + * @expectedException \Symfony\Component\Intl\Exception\MissingResourceException + */ + public function testReadNonExistingEntry() { - $data = array('Foo' => 'Baz'); - $this->readerImpl->expects($this->once()) ->method('read') - ->with(self::RES_DIR, 'en') - ->will($this->returnValue($data)); + ->with(self::RES_DIR, 'root') + ->will($this->returnValue(self::$data)); - $this->assertNull($this->reader->readEntry(self::RES_DIR, 'en', array('Foo', 'Bar'))); + $this->reader->readEntry(self::RES_DIR, 'root', array('Entries', 'NonExisting')); } - public function readMergedEntryProvider() + public function testFallbackIfEntryDoesNotExist() + { + $this->readerImpl->expects($this->at(0)) + ->method('read') + ->with(self::RES_DIR, 'en_GB') + ->will($this->returnValue(self::$data)); + + $this->readerImpl->expects($this->at(1)) + ->method('read') + ->with(self::RES_DIR, 'en') + ->will($this->returnValue(self::$fallbackData)); + + $this->assertSame('Lah', $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Entries', 'Bam'))); + } + + /** + * @expectedException \Symfony\Component\Intl\Exception\MissingResourceException + */ + public function testDontFallbackIfEntryDoesNotExistAndFallbackDisabled() + { + $this->readerImpl->expects($this->once()) + ->method('read') + ->with(self::RES_DIR, 'en_GB') + ->will($this->returnValue(self::$data)); + + $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Entries', 'Bam'), false); + } + + public function testFallbackIfLocaleDoesNotExist() + { + $this->readerImpl->expects($this->at(0)) + ->method('read') + ->with(self::RES_DIR, 'en_GB') + ->will($this->throwException(new ResourceBundleNotFoundException())); + + $this->readerImpl->expects($this->at(1)) + ->method('read') + ->with(self::RES_DIR, 'en') + ->will($this->returnValue(self::$fallbackData)); + + $this->assertSame('Lah', $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Entries', 'Bam'))); + } + + /** + * @expectedException \Symfony\Component\Intl\Exception\MissingResourceException + */ + public function testDontFallbackIfLocaleDoesNotExistAndFallbackDisabled() + { + $this->readerImpl->expects($this->once()) + ->method('read') + ->with(self::RES_DIR, 'en_GB') + ->will($this->throwException(new ResourceBundleNotFoundException())); + + $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Entries', 'Bam'), false); + } + + public function provideMergeableValues() { return array( array('foo', null, 'foo'), @@ -110,114 +184,182 @@ class StructuredBundleReaderTest extends \PHPUnit_Framework_TestCase } /** - * @dataProvider readMergedEntryProvider + * @dataProvider provideMergeableValues */ - public function testReadMergedEntryNoParams($childData, $parentData, $result) + public function testMergeDataWithFallbackData($childData, $parentData, $result) { - $this->readerImpl->expects($this->at(0)) + if (null === $childData || is_array($childData)) { + $this->readerImpl->expects($this->at(0)) + ->method('read') + ->with(self::RES_DIR, 'en') + ->will($this->returnValue($childData)); + + $this->readerImpl->expects($this->at(1)) + ->method('read') + ->with(self::RES_DIR, 'root') + ->will($this->returnValue($parentData)); + } else { + $this->readerImpl->expects($this->once()) + ->method('read') + ->with(self::RES_DIR, 'en') + ->will($this->returnValue($childData)); + } + + $this->assertSame($result, $this->reader->readEntry(self::RES_DIR, 'en', array(), true)); + } + + /** + * @dataProvider provideMergeableValues + */ + public function testDontMergeDataIfFallbackDisabled($childData, $parentData, $result) + { + $this->readerImpl->expects($this->once()) ->method('read') ->with(self::RES_DIR, 'en_GB') ->will($this->returnValue($childData)); - if (null === $childData || is_array($childData)) { - $this->readerImpl->expects($this->at(1)) - ->method('read') - ->with(self::RES_DIR, 'en') - ->will($this->returnValue($parentData)); - } - - $this->assertSame($result, $this->reader->readEntry(self::RES_DIR, 'en_GB', array(), true)); + $this->assertSame($childData, $this->reader->readEntry(self::RES_DIR, 'en_GB', array(), false)); } /** - * @dataProvider readMergedEntryProvider + * @dataProvider provideMergeableValues */ - public function testReadMergedEntryWithParams($childData, $parentData, $result) + public function testMergeExistingEntryWithExistingFallbackEntry($childData, $parentData, $result) { - $this->readerImpl->expects($this->at(0)) - ->method('read') - ->with(self::RES_DIR, 'en_GB') - ->will($this->returnValue(array('Foo' => array('Bar' => $childData)))); - if (null === $childData || is_array($childData)) { - $this->readerImpl->expects($this->at(1)) + $this->readerImpl->expects($this->at(0)) ->method('read') ->with(self::RES_DIR, 'en') + ->will($this->returnValue(array('Foo' => array('Bar' => $childData)))); + + $this->readerImpl->expects($this->at(1)) + ->method('read') + ->with(self::RES_DIR, 'root') ->will($this->returnValue(array('Foo' => array('Bar' => $parentData)))); + } else { + $this->readerImpl->expects($this->once()) + ->method('read') + ->with(self::RES_DIR, 'en') + ->will($this->returnValue(array('Foo' => array('Bar' => $childData)))); } - $this->assertSame($result, $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Foo', 'Bar'), true)); - } - - public function testReadMergedEntryWithUnresolvablePath() - { - $this->readerImpl->expects($this->at(0)) - ->method('read') - ->with(self::RES_DIR, 'en_GB') - ->will($this->returnValue(array('Foo' => 'Baz'))); - - $this->readerImpl->expects($this->at(1)) - ->method('read') - ->with(self::RES_DIR, 'en') - ->will($this->returnValue(array('Foo' => 'Bar'))); - - $this->assertNull($this->reader->readEntry(self::RES_DIR, 'en_GB', array('Foo', 'Bar'), true)); - } - - public function testReadMergedEntryWithUnresolvablePathInParent() - { - $this->readerImpl->expects($this->at(0)) - ->method('read') - ->with(self::RES_DIR, 'en_GB') - ->will($this->returnValue(array('Foo' => array('Bar' => array('three'))))); - - $this->readerImpl->expects($this->at(1)) - ->method('read') - ->with(self::RES_DIR, 'en') - ->will($this->returnValue(array('Foo' => 'Bar'))); - - $result = array('three'); - - $this->assertSame($result, $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Foo', 'Bar'), true)); - } - - public function testReadMergedEntryWithUnresolvablePathInChild() - { - $this->readerImpl->expects($this->at(0)) - ->method('read') - ->with(self::RES_DIR, 'en_GB') - ->will($this->returnValue(array('Foo' => 'Baz'))); - - $this->readerImpl->expects($this->at(1)) - ->method('read') - ->with(self::RES_DIR, 'en') - ->will($this->returnValue(array('Foo' => array('Bar' => array('one', 'two'))))); - - $result = array('one', 'two'); - - $this->assertSame($result, $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Foo', 'Bar'), true)); + $this->assertSame($result, $this->reader->readEntry(self::RES_DIR, 'en', array('Foo', 'Bar'), true)); } /** - * @dataProvider readMergedEntryProvider + * @dataProvider provideMergeableValues */ - public function testReadMergedEntryWithTraversables($childData, $parentData, $result) + public function testMergeNonExistingEntryWithExistingFallbackEntry($childData, $parentData, $result) + { + $this->readerImpl->expects($this->at(0)) + ->method('read') + ->with(self::RES_DIR, 'en_GB') + ->will($this->returnValue(array('Foo' => 'Baz'))); + + $this->readerImpl->expects($this->at(1)) + ->method('read') + ->with(self::RES_DIR, 'en') + ->will($this->returnValue(array('Foo' => array('Bar' => $parentData)))); + + $this->assertSame($parentData, $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Foo', 'Bar'), true)); + } + + /** + * @dataProvider provideMergeableValues + */ + public function testMergeExistingEntryWithNonExistingFallbackEntry($childData, $parentData, $result) + { + if (null === $childData || is_array($childData)) { + $this->readerImpl->expects($this->at(0)) + ->method('read') + ->with(self::RES_DIR, 'en_GB') + ->will($this->returnValue(array('Foo' => array('Bar' => $childData)))); + + $this->readerImpl->expects($this->at(1)) + ->method('read') + ->with(self::RES_DIR, 'en') + ->will($this->returnValue(array('Foo' => 'Bar'))); + } else { + $this->readerImpl->expects($this->once()) + ->method('read') + ->with(self::RES_DIR, 'en_GB') + ->will($this->returnValue(array('Foo' => array('Bar' => $childData)))); + } + + $this->assertSame($childData, $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Foo', 'Bar'), true)); + } + + /** + * @expectedException \Symfony\Component\Intl\Exception\MissingResourceException + */ + public function testFailIfEntryFoundNeitherInParentNorChild() + { + $this->readerImpl->expects($this->at(0)) + ->method('read') + ->with(self::RES_DIR, 'en_GB') + ->will($this->returnValue(array('Foo' => 'Baz'))); + + $this->readerImpl->expects($this->at(1)) + ->method('read') + ->with(self::RES_DIR, 'en') + ->will($this->returnValue(array('Foo' => 'Bar'))); + + $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Foo', 'Bar'), true); + } + + /** + * @dataProvider provideMergeableValues + */ + public function testMergeTraversables($childData, $parentData, $result) { $parentData = is_array($parentData) ? new \ArrayObject($parentData) : $parentData; $childData = is_array($childData) ? new \ArrayObject($childData) : $childData; - $this->readerImpl->expects($this->at(0)) - ->method('read') - ->with(self::RES_DIR, 'en_GB') - ->will($this->returnValue(array('Foo' => array('Bar' => $childData)))); - if (null === $childData || $childData instanceof \ArrayObject) { + $this->readerImpl->expects($this->at(0)) + ->method('read') + ->with(self::RES_DIR, 'en_GB') + ->will($this->returnValue(array('Foo' => array('Bar' => $childData)))); + $this->readerImpl->expects($this->at(1)) ->method('read') ->with(self::RES_DIR, 'en') ->will($this->returnValue(array('Foo' => array('Bar' => $parentData)))); + } else { + $this->readerImpl->expects($this->once()) + ->method('read') + ->with(self::RES_DIR, 'en_GB') + ->will($this->returnValue(array('Foo' => array('Bar' => $childData)))); } $this->assertSame($result, $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Foo', 'Bar'), true)); } + + /** + * @dataProvider provideMergeableValues + */ + public function testFollowLocaleAliases($childData, $parentData, $result) + { + $this->reader->setLocaleAliases(array('mo' => 'ro_MD')); + + if (null === $childData || is_array($childData)) { + $this->readerImpl->expects($this->at(0)) + ->method('read') + ->with(self::RES_DIR, 'ro_MD') + ->will($this->returnValue(array('Foo' => array('Bar' => $childData)))); + + // Read fallback locale of aliased locale ("ro_MD" -> "ro") + $this->readerImpl->expects($this->at(1)) + ->method('read') + ->with(self::RES_DIR, 'ro') + ->will($this->returnValue(array('Foo' => array('Bar' => $parentData)))); + } else { + $this->readerImpl->expects($this->once()) + ->method('read') + ->with(self::RES_DIR, 'ro_MD') + ->will($this->returnValue(array('Foo' => array('Bar' => $childData)))); + } + + $this->assertSame($result, $this->reader->readEntry(self::RES_DIR, 'mo', array('Foo', 'Bar'), true)); + } } diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/Fixtures/en.txt b/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/Fixtures/en.txt index 0ee0d7f2f5..09c1275fa5 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/Fixtures/en.txt +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/Fixtures/en.txt @@ -14,6 +14,22 @@ en{ 2, 3, } + NotAnIntVector{ + 0:int{0} + 2:int{1} + 1:int{2} + 3:int{3} + } + IntVectorWithStringKeys{ + a:int{0} + b:int{1} + c:int{2} + } + TableWithIntKeys{ + 0:int{0} + 1:int{1} + 3:int{3} + } FalseBoolean{"false"} TrueBoolean{"true"} Null{""} diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/Fixtures/en_nofallback.txt b/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/Fixtures/en_nofallback.txt new file mode 100644 index 0000000000..85386f2074 --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/Fixtures/en_nofallback.txt @@ -0,0 +1,3 @@ +en_nofallback:table(nofallback){ + Entry{"Value"} +} diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/Fixtures/escaped.txt b/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/Fixtures/escaped.txt new file mode 100644 index 0000000000..6669bfdd83 --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/Fixtures/escaped.txt @@ -0,0 +1,4 @@ +escaped{ + "EntryWith:Colon"{"Value"} + "Entry With Spaces"{"Value"} +} diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/TextBundleWriterTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/TextBundleWriterTest.php index cbe0c8d8bf..f42b2738d7 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/TextBundleWriterTest.php +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/TextBundleWriterTest.php @@ -36,7 +36,7 @@ class TextBundleWriterTest extends \PHPUnit_Framework_TestCase protected function setUp() { $this->writer = new TextBundleWriter(); - $this->directory = sys_get_temp_dir() . '/TextBundleWriterTest/' . rand(1000, 9999); + $this->directory = sys_get_temp_dir().'/TextBundleWriterTest/'.rand(1000, 9999); $this->filesystem = new Filesystem(); $this->filesystem->mkdir($this->directory); @@ -54,6 +54,9 @@ class TextBundleWriterTest extends \PHPUnit_Framework_TestCase 'Array' => array('foo', 'bar', array('Key' => 'value')), 'Integer' => 5, 'IntVector' => array(0, 1, 2, 3), + 'NotAnIntVector' => array(0 => 0, 2 => 1, 1 => 2, 3 => 3), + 'IntVectorWithStringKeys' => array('a' => 0, 'b' => 1, 'c' => 2), + 'TableWithIntKeys' => array(0 => 0, 1 => 1, 3 => 3), 'FalseBoolean' => false, 'TrueBoolean' => true, 'Null' => null, @@ -62,6 +65,51 @@ class TextBundleWriterTest extends \PHPUnit_Framework_TestCase 'Entry2' => 'String', )); - $this->assertFileEquals(__DIR__ . '/Fixtures/en.txt', $this->directory . '/en.txt'); + $this->assertFileEquals(__DIR__.'/Fixtures/en.txt', $this->directory.'/en.txt'); + } + + public function testWriteTraversable() + { + $this->writer->write($this->directory, 'en', new \ArrayIterator(array( + 'Entry1' => new \ArrayIterator(array( + 'Array' => array('foo', 'bar', array('Key' => 'value')), + 'Integer' => 5, + 'IntVector' => array(0, 1, 2, 3), + 'NotAnIntVector' => array(0 => 0, 2 => 1, 1 => 2, 3 => 3), + 'IntVectorWithStringKeys' => array('a' => 0, 'b' => 1, 'c' => 2), + 'TableWithIntKeys' => array(0 => 0, 1 => 1, 3 => 3), + 'FalseBoolean' => false, + 'TrueBoolean' => true, + 'Null' => null, + 'Float' => 1.23, + )), + 'Entry2' => 'String', + ))); + + $this->assertFileEquals(__DIR__.'/Fixtures/en.txt', $this->directory.'/en.txt'); + } + + public function testWriteNoFallback() + { + $data = array( + 'Entry' => 'Value' + ); + + $this->writer->write($this->directory, 'en_nofallback', $data, $fallback = false); + + $this->assertFileEquals(__DIR__.'/Fixtures/en_nofallback.txt', $this->directory.'/en_nofallback.txt'); + } + + public function testEscapeKeysIfNecessary() + { + $this->writer->write($this->directory, 'escaped', array( + // Keys with colons must be escaped, otherwise the part after the + // colon is interpreted as resource type + 'EntryWith:Colon' => 'Value', + // Keys with spaces must be escaped + 'Entry With Spaces' => 'Value', + )); + + $this->assertFileEquals(__DIR__.'/Fixtures/escaped.txt', $this->directory.'/escaped.txt'); } } diff --git a/src/Symfony/Component/Process/PhpExecutableFinder.php b/src/Symfony/Component/Process/PhpExecutableFinder.php index 9001f41095..7854487a26 100644 --- a/src/Symfony/Component/Process/PhpExecutableFinder.php +++ b/src/Symfony/Component/Process/PhpExecutableFinder.php @@ -29,13 +29,15 @@ class PhpExecutableFinder /** * Finds The PHP executable. * + * @param bool $includeArgs Whether or not include command arguments + * * @return string|false The PHP executable path or false if it cannot be found */ - public function find() + public function find($includeArgs = true) { // HHVM support - if (defined('HHVM_VERSION') && false !== $hhvm = getenv('PHP_BINARY')) { - return $hhvm; + if (defined('HHVM_VERSION')) { + return (false !== ($hhvm = getenv('PHP_BINARY')) ? $hhvm : PHP_BINARY).($includeArgs ? ' '.implode(' ', $this->findArguments()) : ''); } // PHP_BINARY return the current sapi executable @@ -64,4 +66,21 @@ class PhpExecutableFinder return $this->executableFinder->find('php', false, $dirs); } + + /** + * Finds the PHP executable arguments. + * + * @return array The PHP executable arguments + */ + public function findArguments() + { + $arguments = array(); + + // HHVM support + if (defined('HHVM_VERSION')) { + $arguments[] = '--php'; + } + + return $arguments; + } } diff --git a/src/Symfony/Component/Process/Tests/PhpExecutableFinderTest.php b/src/Symfony/Component/Process/Tests/PhpExecutableFinderTest.php index df48c4f556..e16f3f8bed 100644 --- a/src/Symfony/Component/Process/Tests/PhpExecutableFinderTest.php +++ b/src/Symfony/Component/Process/Tests/PhpExecutableFinderTest.php @@ -34,10 +34,43 @@ class PhpExecutableFinderTest extends \PHPUnit_Framework_TestCase //not executable PHP_PATH putenv('PHP_PATH=/not/executable/php'); $this->assertFalse($f->find(), '::find() returns false for not executable PHP'); + $this->assertFalse($f->find(false), '::find() returns false for not executable PHP'); //executable PHP_PATH putenv('PHP_PATH='.$current); $this->assertEquals($f->find(), $current, '::find() returns the executable PHP'); + $this->assertEquals($f->find(false), $current, '::find() returns the executable PHP'); + } + + /** + * tests find() with the env var PHP_PATH + */ + public function testFindWithHHVM() + { + if (!defined('HHVM_VERSION')) { + $this->markTestSkipped('Should be executed in HHVM context.'); + } + + $f = new PhpExecutableFinder(); + + $current = $f->find(); + + $this->assertEquals($f->find(), $current.' --php', '::find() returns the executable PHP'); + $this->assertEquals($f->find(false), $current, '::find() returns the executable PHP'); + } + + /** + * tests find() with the env var PHP_PATH + */ + public function testFindArguments() + { + $f = new PhpExecutableFinder(); + + if (defined('HHVM_VERSION')) { + $this->assertEquals($f->findArguments(), array('--php'), '::findArguments() returns HHVM arguments'); + } else { + $this->assertEquals($f->findArguments(), array(), '::findArguments() returns no arguments'); + } } /** diff --git a/src/Symfony/Component/Security/Acl/Domain/DoctrineAclCache.php b/src/Symfony/Component/Security/Acl/Domain/DoctrineAclCache.php index 9e14af5652..0c69773c47 100644 --- a/src/Symfony/Component/Security/Acl/Domain/DoctrineAclCache.php +++ b/src/Symfony/Component/Security/Acl/Domain/DoctrineAclCache.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Security\Acl\Domain; use Doctrine\Common\Cache\Cache; +use Doctrine\Common\Cache\CacheProvider; use Symfony\Component\Security\Acl\Model\AclCacheInterface; use Symfony\Component\Security\Acl\Model\AclInterface; use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface; @@ -55,7 +56,9 @@ class DoctrineAclCache implements AclCacheInterface */ public function clearCache() { - $this->cache->deleteByPrefix($this->prefix); + if ($this->cache instanceof CacheProvider) { + $this->cache->deleteAll(); + } } /** diff --git a/src/Symfony/Component/Security/Core/Tests/Authorization/AccessDecisionManagerTest.php b/src/Symfony/Component/Security/Core/Tests/Authorization/AccessDecisionManagerTest.php index b0557a3dcb..0db50cf7c2 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authorization/AccessDecisionManagerTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authorization/AccessDecisionManagerTest.php @@ -73,6 +73,48 @@ class AccessDecisionManagerTest extends \PHPUnit_Framework_TestCase $this->assertSame($expected, $manager->decide($token, array('ROLE_FOO'))); } + /** + * @dataProvider getStrategiesWith2RolesTests + */ + public function testStrategiesWith2Roles($token, $strategy, $voter, $expected) + { + $manager = new AccessDecisionManager(array($voter), $strategy); + + $this->assertSame($expected, $manager->decide($token, array('ROLE_FOO', 'ROLE_BAR'))); + } + + public function getStrategiesWith2RolesTests() + { + $token = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); + + return array( + array($token, 'affirmative', $this->getVoter(VoterInterface::ACCESS_DENIED), false), + array($token, 'affirmative', $this->getVoter(VoterInterface::ACCESS_GRANTED), true), + + array($token, 'consensus', $this->getVoter(VoterInterface::ACCESS_DENIED), false), + array($token, 'consensus', $this->getVoter(VoterInterface::ACCESS_GRANTED), true), + + array($token, 'unanimous', $this->getVoterFor2Roles($token, VoterInterface::ACCESS_DENIED, VoterInterface::ACCESS_DENIED), false), + array($token, 'unanimous', $this->getVoterFor2Roles($token, VoterInterface::ACCESS_DENIED, VoterInterface::ACCESS_GRANTED), false), + array($token, 'unanimous', $this->getVoterFor2Roles($token, VoterInterface::ACCESS_GRANTED, VoterInterface::ACCESS_DENIED), false), + array($token, 'unanimous', $this->getVoterFor2Roles($token, VoterInterface::ACCESS_GRANTED, VoterInterface::ACCESS_GRANTED), true), + ); + } + + protected function getVoterFor2Roles($token, $vote1, $vote2) + { + $voter = $this->getMock('Symfony\Component\Security\Core\Authorization\Voter\VoterInterface'); + $voter->expects($this->exactly(2)) + ->method('vote') + ->will($this->returnValueMap(array( + array($token, null, array("ROLE_FOO"),$vote1), + array($token, null, array("ROLE_BAR"),$vote2), + ))) + ; + + return $voter; + } + public function getStrategyTests() { return array( diff --git a/src/Symfony/Component/Security/Core/Tests/Util/StringUtilsTest.php b/src/Symfony/Component/Security/Core/Tests/Util/StringUtilsTest.php index 89da98de66..e0366a52c1 100644 --- a/src/Symfony/Component/Security/Core/Tests/Util/StringUtilsTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Util/StringUtilsTest.php @@ -13,11 +13,49 @@ namespace Symfony\Component\Security\Core\Tests\Util; use Symfony\Component\Security\Core\Util\StringUtils; +/** + * Data from PHP.net's hash_equals tests + */ class StringUtilsTest extends \PHPUnit_Framework_TestCase { - public function testEquals() + public function dataProviderTrue() { - $this->assertTrue(StringUtils::equals('password', 'password')); - $this->assertFalse(StringUtils::equals('password', 'foo')); + return array( + array('same', 'same'), + array('', ''), + array(123, 123), + array(null, ''), + array(null, null), + ); + } + + public function dataProviderFalse() + { + return array( + array('not1same', 'not2same'), + array('short', 'longer'), + array('longer', 'short'), + array('', 'notempty'), + array('notempty', ''), + array(123, 'NaN'), + array('NaN', 123), + array(null, 123), + ); + } + + /** + * @dataProvider dataProviderTrue + */ + public function testEqualsTrue($known, $user) + { + $this->assertTrue(StringUtils::equals($known, $user)); + } + + /** + * @dataProvider dataProviderFalse + */ + public function testEqualsFalse($known, $user) + { + $this->assertFalse(StringUtils::equals($known, $user)); } } diff --git a/src/Symfony/Component/Security/Core/Util/StringUtils.php b/src/Symfony/Component/Security/Core/Util/StringUtils.php index 5e130375b7..acf8e9eed8 100644 --- a/src/Symfony/Component/Security/Core/Util/StringUtils.php +++ b/src/Symfony/Component/Security/Core/Util/StringUtils.php @@ -27,6 +27,7 @@ class StringUtils * Compares two strings. * * This method implements a constant-time algorithm to compare strings. + * Regardless of the used implementation, it will leak length information. * * @param string $knownString The string of known length to compare against * @param string $userInput The string that the user can control @@ -35,6 +36,13 @@ class StringUtils */ public static function equals($knownString, $userInput) { + $knownString = (string) $knownString; + $userInput = (string) $userInput; + + if (function_exists('hash_equals')) { + return hash_equals($knownString, $userInput); + } + $knownLen = strlen($knownString); $userLen = strlen($userInput); @@ -45,7 +53,7 @@ class StringUtils $result = $knownLen - $userLen; // Note that we ALWAYS iterate over the user-supplied length - // This is to prevent leaking length information + // This is to mitigate leaking length information for ($i = 0; $i < $userLen; $i++) { $result |= (ord($knownString[$i]) ^ ord($userInput[$i])); } diff --git a/src/Symfony/Component/Translation/Dumper/XliffFileDumper.php b/src/Symfony/Component/Translation/Dumper/XliffFileDumper.php index 96c0c8a387..9b3237fda1 100644 --- a/src/Symfony/Component/Translation/Dumper/XliffFileDumper.php +++ b/src/Symfony/Component/Translation/Dumper/XliffFileDumper.php @@ -47,8 +47,11 @@ class XliffFileDumper extends FileDumper $s = $translation->appendChild($dom->createElement('source')); $s->appendChild($dom->createTextNode($source)); + // Does the target contain characters requiring a CDATA section? + $text = 1 === preg_match('/[&<>]/', $target) ? $dom->createCDATASection($target) : $dom->createTextNode($target); + $t = $translation->appendChild($dom->createElement('target')); - $t->appendChild($dom->createTextNode($target)); + $t->appendChild($text); $xliffBody->appendChild($translation); } diff --git a/src/Symfony/Component/Translation/Tests/Dumper/XliffFileDumperTest.php b/src/Symfony/Component/Translation/Tests/Dumper/XliffFileDumperTest.php index bef31358c5..5eddb56968 100644 --- a/src/Symfony/Component/Translation/Tests/Dumper/XliffFileDumperTest.php +++ b/src/Symfony/Component/Translation/Tests/Dumper/XliffFileDumperTest.php @@ -19,13 +19,20 @@ class XliffFileDumperTest extends \PHPUnit_Framework_TestCase public function testDump() { $catalogue = new MessageCatalogue('en'); - $catalogue->add(array('foo' => 'bar', 'key' => '')); + $catalogue->add(array( + 'foo' => 'bar', + 'key' => '', + 'key.with.cdata' => ' & ', + )); $tempDir = sys_get_temp_dir(); $dumper = new XliffFileDumper(); $dumper->dump($catalogue, array('path' => $tempDir)); - $this->assertEquals(file_get_contents(__DIR__.'/../fixtures/resources-clean.xlf'), file_get_contents($tempDir.'/messages.en.xlf')); + $this->assertSame( + file_get_contents(__DIR__.'/../fixtures/resources-clean.xlf'), + file_get_contents($tempDir.'/messages.en.xlf') + ); unlink($tempDir.'/messages.en.xlf'); } diff --git a/src/Symfony/Component/Translation/Tests/fixtures/resources-clean.xlf b/src/Symfony/Component/Translation/Tests/fixtures/resources-clean.xlf index 464b079200..c5f83efe60 100644 --- a/src/Symfony/Component/Translation/Tests/fixtures/resources-clean.xlf +++ b/src/Symfony/Component/Translation/Tests/fixtures/resources-clean.xlf @@ -10,6 +10,10 @@ key + + key.with.cdata + & ]]> + diff --git a/src/Symfony/Component/Yaml/Escaper.php b/src/Symfony/Component/Yaml/Escaper.php index 4a6b621613..15a121792f 100644 --- a/src/Symfony/Component/Yaml/Escaper.php +++ b/src/Symfony/Component/Yaml/Escaper.php @@ -26,13 +26,13 @@ class Escaper // first to ensure proper escaping because str_replace operates iteratively // on the input arrays. This ordering of the characters avoids the use of strtr, // which performs more slowly. - private static $escapees = array('\\\\', '\\"', '"', + private static $escapees = array('\\', '\\\\', '\\"', '"', "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", "\x08", "\x09", "\x0a", "\x0b", "\x0c", "\x0d", "\x0e", "\x0f", "\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17", "\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f", "\xc2\x85", "\xc2\xa0", "\xe2\x80\xa8", "\xe2\x80\xa9"); - private static $escaped = array('\\"', '\\\\', '\\"', + private static $escaped = array('\\\\', '\\"', '\\\\', '\\"', "\\0", "\\x01", "\\x02", "\\x03", "\\x04", "\\x05", "\\x06", "\\a", "\\b", "\\t", "\\n", "\\v", "\\f", "\\r", "\\x0e", "\\x0f", "\\x10", "\\x11", "\\x12", "\\x13", "\\x14", "\\x15", "\\x16", "\\x17", diff --git a/src/Symfony/Component/Yaml/Inline.php b/src/Symfony/Component/Yaml/Inline.php index 1b34edebd2..fc00405a8b 100644 --- a/src/Symfony/Component/Yaml/Inline.php +++ b/src/Symfony/Component/Yaml/Inline.php @@ -392,7 +392,7 @@ class Inline * * @return string A YAML string * - * @throws ParseException when object parsing support was disabled and the parser detected a PHP object + * @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved */ private static function evaluateScalar($scalar, $references = array()) { @@ -406,6 +406,11 @@ class Inline $value = substr($scalar, 1); } + // an unquoted * + if (false === $value || '' === $value) { + throw new ParseException('A reference must contain at least one character.'); + } + if (!array_key_exists($value, $references)) { throw new ParseException(sprintf('Reference "%s" does not exist.', $value)); } diff --git a/src/Symfony/Component/Yaml/Parser.php b/src/Symfony/Component/Yaml/Parser.php index addb387a48..5c578e35d3 100644 --- a/src/Symfony/Component/Yaml/Parser.php +++ b/src/Symfony/Component/Yaml/Parser.php @@ -93,7 +93,7 @@ class Parser $c = $this->getRealCurrentLineNb() + 1; $parser = new Parser($c); $parser->refs =& $this->refs; - $data[] = $parser->parse($this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport); + $data[] = $parser->parse($this->getNextEmbedBlock(null, true), $exceptionOnInvalidType, $objectSupport); } else { if (isset($values['leadspaces']) && ' ' == $values['leadspaces'] @@ -281,15 +281,20 @@ class Parser /** * Returns the next embed block of YAML. * - * @param int $indentation The indent level at which the block is to be read, or null for default + * @param int $indentation The indent level at which the block is to be read, or null for default + * @param bool $inSequence True if the enclosing data structure is a sequence * * @return string A YAML string * * @throws ParseException When indentation problem are detected */ - private function getNextEmbedBlock($indentation = null) + private function getNextEmbedBlock($indentation = null, $inSequence = false) { - $this->moveToNextLine(); + $oldLineIndentation = $this->getCurrentLineIndentation(); + + if (!$this->moveToNextLine()) { + return; + } if (null === $indentation) { $newIndent = $this->getCurrentLineIndentation(); @@ -305,6 +310,14 @@ class Parser $data = array(substr($this->currentLine, $newIndent)); + if ($inSequence && $oldLineIndentation === $newIndent && '-' === $data[0][0]) { + // the previous line contained a dash but no item content, this line is a sequence item with the same indentation + // and therefore no nested list or mapping + $this->moveToPreviousLine(); + + return; + } + $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem($this->currentLine); // Comments must not be removed inside a string block (ie. after a line ending with "|") diff --git a/src/Symfony/Component/Yaml/Tests/DumperTest.php b/src/Symfony/Component/Yaml/Tests/DumperTest.php index ec2c65e3d0..53d164c4ba 100644 --- a/src/Symfony/Component/Yaml/Tests/DumperTest.php +++ b/src/Symfony/Component/Yaml/Tests/DumperTest.php @@ -199,6 +199,37 @@ EOF; { $this->dumper->dump(array('foo' => new A(), 'bar' => 1), 0, 0, true, false); } + + /** + * @dataProvider getEscapeSequences + */ + public function testEscapedEscapeSequencesInQuotedScalar($input, $expected) + { + $this->assertEquals($expected, $this->dumper->dump($input)); + } + + public function getEscapeSequences() + { + return array( + 'null' => array("\t\\0", '"\t\\\\0"'), + 'bell' => array("\t\\a", '"\t\\\\a"'), + 'backspace' => array("\t\\b", '"\t\\\\b"'), + 'horizontal-tab' => array("\t\\t", '"\t\\\\t"'), + 'line-feed' => array("\t\\n", '"\t\\\\n"'), + 'vertical-tab' => array("\t\\v", '"\t\\\\v"'), + 'form-feed' => array("\t\\f", '"\t\\\\f"'), + 'carriage-return' => array("\t\\r", '"\t\\\\r"'), + 'escape' => array("\t\\e", '"\t\\\\e"'), + 'space' => array("\t\\ ", '"\t\\\\ "'), + 'double-quote' => array("\t\\\"", '"\t\\\\\\""'), + 'slash' => array("\t\\/", '"\t\\\\/"'), + 'backslash' => array("\t\\\\", '"\t\\\\\\\\"'), + 'next-line' => array("\t\\N", '"\t\\\\N"'), + 'non-breaking-space' => array("\t\\�", '"\t\\\\�"'), + 'line-separator' => array("\t\\L", '"\t\\\\L"'), + 'paragraph-separator' => array("\t\\P", '"\t\\\\P"'), + ); + } } class A diff --git a/src/Symfony/Component/Yaml/Tests/Fixtures/YtsBasicTests.yml b/src/Symfony/Component/Yaml/Tests/Fixtures/YtsBasicTests.yml index ac0efa88d1..dfd93021d1 100644 --- a/src/Symfony/Component/Yaml/Tests/Fixtures/YtsBasicTests.yml +++ b/src/Symfony/Component/Yaml/Tests/Fixtures/YtsBasicTests.yml @@ -11,6 +11,30 @@ yaml: | php: | array('apple', 'banana', 'carrot') --- +test: Sequence With Item Being Null In The Middle +brief: | + You can specify a list in YAML by placing each + member of the list on a new line with an opening + dash. These lists are called sequences. +yaml: | + - apple + - + - carrot +php: | + array('apple', null, 'carrot') +--- +test: Sequence With Last Item Being Null +brief: | + You can specify a list in YAML by placing each + member of the list on a new line with an opening + dash. These lists are called sequences. +yaml: | + - apple + - banana + - +php: | + array('apple', 'banana', null) +--- test: Nested Sequences brief: | You can include a sequence within another diff --git a/src/Symfony/Component/Yaml/Tests/InlineTest.php b/src/Symfony/Component/Yaml/Tests/InlineTest.php index affe4f9c23..5b261e4343 100644 --- a/src/Symfony/Component/Yaml/Tests/InlineTest.php +++ b/src/Symfony/Component/Yaml/Tests/InlineTest.php @@ -147,6 +147,24 @@ class InlineTest extends \PHPUnit_Framework_TestCase $this->assertSame(array($foo), Inline::parse('[*foo]', false, false, array('foo' => $foo))); } + /** + * @expectedException \Symfony\Component\Yaml\Exception\ParseException + * @expectedExceptionMessage A reference must contain at least one character. + */ + public function testParseUnquotedAsterisk() + { + Inline::parse('{ foo: * }'); + } + + /** + * @expectedException \Symfony\Component\Yaml\Exception\ParseException + * @expectedExceptionMessage A reference must contain at least one character. + */ + public function testParseUnquotedAsteriskFollowedByAComment() + { + Inline::parse('{ foo: * #foo }'); + } + protected function getTestsForParse() { return array(
+ errors($form) ?> +