diff --git a/.travis.yml b/.travis.yml index 8ee26e5249af2..f359dea238156 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: php dist: trusty -sudo: false git: depth: 2 @@ -159,7 +158,7 @@ before_install: echo extension = $ext_cache >> $INI elif [[ $PHP = 7.* ]]; then tfold ext.apcu tpecl apcu-5.1.6 apcu.so $INI - tfold ext.mongodb tpecl mongodb-1.5.0 mongodb.so $INI + tfold ext.mongodb tpecl mongodb-1.5.2 mongodb.so $INI fi done @@ -218,7 +217,7 @@ install: fi phpenv global ${PHP/hhvm*/hhvm} if [[ $PHP = 7.* ]]; then - ([[ $deps ]] && cd src/Symfony/Component/HttpFoundation; composer config platform.ext-mongodb 1.5.0; composer require --dev --no-update mongodb/mongodb) + ([[ $deps ]] && cd src/Symfony/Component/HttpFoundation; composer config platform.ext-mongodb 1.5.2; composer require --dev --no-update mongodb/mongodb) fi tfold 'composer update' $COMPOSER_UP if [[ $TRAVIS_PHP_VERSION = 5.* || $TRAVIS_PHP_VERSION = hhvm* ]]; then diff --git a/CHANGELOG-2.8.md b/CHANGELOG-2.8.md index 358b477b1467b..aa884108b6dd5 100644 --- a/CHANGELOG-2.8.md +++ b/CHANGELOG-2.8.md @@ -7,6 +7,21 @@ in 2.8 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.8.0...v2.8.1 +* 2.8.48 (2018-11-26) + + * bug #28917 [DoctrineBridge] catch errors while converting to db values in data collector (alekitto) + * bug #27314 [DoctrineBridge] fix case sensitivity issue in RememberMe\DoctrineTokenProvider (PF4Public) + * bug #29308 [Translation] Use XLIFF source rather than resname when there's no target (thewilkybarkid) + * bug #26244 [BrowserKit] fixed BC Break for HTTP_HOST header (brizzz) + * bug #28147 [DomCrawler] exclude fields inside "template" tags (Gorjunov) + * bug #29271 [HttpFoundation] Fix trailing space for mime-type with parameters (Sascha Dens) + * bug #29223 [Validator] Added the missing constraints instance checks (thomasbisignani) + * bug #29182 [Form] Fixed empty data for compound date types (HeahDude) + * bug #29185 [Form] Fixed keeping hash of equal \DateTimeInterface on submit (HeahDude) + * bug #28731 [Form] invalidate forms on transformation failures (xabbuh) + * bug #29152 [Config] Unset key during normalization (ro0NL) + * bug #29057 [HttpFoundation] replace any preexisting Content-Type headers (nicolas-grekas) + * 2.8.47 (2018-11-03) * bug #29020 Fix ini_get() for boolean values (deguif) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 969c6a424cefc..1c329f2146c3e 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -14,9 +14,9 @@ Symfony is the result of the work of many people who made the code better - Victor Berchet (victor) - Robin Chalas (chalas_r) - Kévin Dunglas (dunglas) + - Maxime Steinhausser (ogizanagi) - Jakub Zalas (jakubzalas) - Johannes S (johannes) - - Maxime Steinhausser (ogizanagi) - Kris Wallsmith (kriswallsmith) - Ryan Weaver (weaverryan) - Javier Eguiluz (javier.eguiluz) @@ -37,8 +37,8 @@ Symfony is the result of the work of many people who made the code better - Benjamin Eberlei (beberlei) - Igor Wiedler (igorw) - Jules Pietri (heah) - - Eriksen Costa (eriksencosta) - Yonel Ceruto (yonelceruto) + - Eriksen Costa (eriksencosta) - Guilhem Niot (energetick) - Sarah Khalil (saro0h) - Jonathan Wage (jwage) @@ -70,6 +70,7 @@ Symfony is the result of the work of many people who made the code better - Gábor Egyed (1ed) - Mathieu Piot (mpiot) - Titouan Galopin (tgalopin) + - Vladimir Reznichenko (kalessil) - Michel Weimerskirch (mweimerskirch) - Andrej Hudec (pulzarraider) - Konstantin Myakshin (koc) @@ -77,7 +78,6 @@ Symfony is the result of the work of many people who made the code better - Jáchym Toušek (enumag) - Charles Sarrazin (csarrazi) - David Maicher (dmaicher) - - Vladimir Reznichenko (kalessil) - Christian Raue - Issei Murasawa (issei_m) - Arnout Boks (aboks) @@ -86,13 +86,13 @@ Symfony is the result of the work of many people who made the code better - Dariusz Górecki (canni) - Douglas Greenshields (shieldo) - Dariusz Ruminski + - Grégoire Paris (greg0ire) - Lee McDermott - Brandon Turner - Luis Cordova (cordoval) - Graham Campbell (graham) - Daniel Holmes (dholmes) - Toni Uebernickel (havvg) - - Grégoire Paris (greg0ire) - Bart van den Burg (burgov) - Jordan Alliot (jalliot) - Jérôme Tamarelle (gromnan) @@ -113,12 +113,12 @@ Symfony is the result of the work of many people who made the code better - lenar - Alexander Schwenn (xelaris) - Włodzimierz Gajda (gajdaw) + - Tomáš Votruba (tomas_votruba) - Peter Kokot (maastermedia) - Jacob Dreesen (jdreesen) - Florian Voutzinos (florianv) - Colin Frei - Adrien Brault (adrienbrault) - - Tomáš Votruba (tomas_votruba) - Joshua Thijssen - excelwebzone - Gordon Franke (gimler) @@ -179,6 +179,7 @@ Symfony is the result of the work of many people who made the code better - jeremyFreeAgent (Jérémy Romey) (jeremyfreeagent) - James Halsall (jaitsu) - Matthieu Napoli (mnapoli) + - Florent Mata (fmata) - Warnar Boekkooi (boekkooi) - Alessandro Chitolina (alekitto) - Dmitrii Chekaliuk (lazyhammer) @@ -193,7 +194,6 @@ Symfony is the result of the work of many people who made the code better - Dennis Benkert (denderello) - DQNEO - Benjamin Dulau (dbenjamin) - - Florent Mata (fmata) - Mathieu Lemoine (lemoinem) - Thomas Calvet (fancyweb) - Christian Schmidt @@ -258,6 +258,7 @@ Symfony is the result of the work of many people who made the code better - Benoît Burnichon (bburnichon) - Roman Marintšenko (inori) - Xavier Montaña Carreras (xmontana) + - François-Xavier de Guillebon (de-gui_f) - Mickaël Andrieu (mickaelandrieu) - Maxime Veber (nek-) - Xavier Perez @@ -294,7 +295,9 @@ Symfony is the result of the work of many people who made the code better - Thomas Lallement (raziel057) - mcfedr (mcfedr) - Colin O'Dell (colinodell) + - Fabien Bourigault (fbourigault) - Giorgio Premi + - Jan Schädlich (jschaedl) - Beau Simensen (simensen) - Michael Hirschler (mvhirsch) - Robert Kiss (kepten) @@ -305,8 +308,8 @@ Symfony is the result of the work of many people who made the code better - Jérôme Parmentier (lctrs) - Michael Babker (mbabker) - Peter Kruithof (pkruithof) - - François-Xavier de Guillebon (de-gui_f) - Michael Holm (hollo) + - Remon van de Kamp (rpkamp) - Marc Weistroff (futurecat) - Christian Schmidt - MatTheCat @@ -350,8 +353,6 @@ Symfony is the result of the work of many people who made the code better - Ricard Clau (ricardclau) - Mark Challoner (markchalloner) - Gennady Telegin (gtelegin) - - Jan Schädlich (jschaedl) - - Fabien Bourigault (fbourigault) - Ben Davies (bendavies) - Erin Millard - Artur Melo (restless) @@ -445,6 +446,7 @@ Symfony is the result of the work of many people who made the code better - lancergr - Zan Baldwin - Mihai Stancu + - Ivan Nikolaev (destillat) - Olivier Dolbeau (odolbeau) - Jan Rosier (rosier) - Alessandro Lai (jean85) @@ -461,7 +463,6 @@ Symfony is the result of the work of many people who made the code better - Boris Vujicic (boris.vujicic) - Chris Sedlmayr (catchamonkey) - Mateusz Sip (mateusz_sip) - - Remon van de Kamp - Kamil Kokot (pamil) - Seb Koelen - Christoph Mewes (xrstf) @@ -470,6 +471,7 @@ Symfony is the result of the work of many people who made the code better - Dirk Pahl (dirkaholic) - cedric lombardot (cedriclombardot) - Jonas Flodén (flojon) + - Gonzalo Vilaseca (gonzalovilaseca) - Marcin Sikoń (marphi) - Dominik Zogg (dominik.zogg) - Marek Pietrzak @@ -643,7 +645,6 @@ Symfony is the result of the work of many people who made the code better - adev - Stefan Warman - Arkadius Stefanski (arkadius) - - Gonzalo Vilaseca (gonzalovilaseca) - Tristan Maindron (tmaindron) - Wesley Lancel - Ke WANG (yktd26) @@ -665,6 +666,7 @@ Symfony is the result of the work of many people who made the code better - Sergey (upyx) - Michael Devery (mickadoo) - Antoine Corcy + - Dmitrii Poddubnyi (karser) - Sascha Grossenbacher - Szijarto Tamas - Robin Lehrmann (robinlehrmann) @@ -804,7 +806,6 @@ Symfony is the result of the work of many people who made the code better - Sofiane HADDAG (sofhad) - frost-nzcr4 - Bozhidar Hristov - - Ivan Nikolaev (destillat) - Laurent Bassin (lbassin) - andrey1s - Abhoryo @@ -989,6 +990,7 @@ Symfony is the result of the work of many people who made the code better - Mathieu Santostefano - Arjan Keeman - Máximo Cuadros (mcuadros) + - Lukas Mencl - tamirvs - julien.galenski - Christian Neff @@ -1290,6 +1292,7 @@ Symfony is the result of the work of many people who made the code better - Adrien Samson (adriensamson) - Samuel Gordalina (gordalina) - Max Romanovsky (maxromanovsky) + - Nicolas Eeckeloo (neeckeloo) - Mathieu Morlon - Daniel Tschinder - Arnaud CHASSEUX @@ -1351,6 +1354,7 @@ Symfony is the result of the work of many people who made the code better - Andrew (drew) - kor3k kor3k (kor3k) - Stelian Mocanita (stelian) + - Thomas Bisignani (toma) - Justin (wackymole) - Flavian (2much) - Gautier Deuette @@ -1448,6 +1452,7 @@ Symfony is the result of the work of many people who made the code better - Phobetor - Andreas - Markus + - Daniel Gorgan - Thomas Chmielowiec - shdev - Andrey Ryaguzov @@ -1499,6 +1504,7 @@ Symfony is the result of the work of many people who made the code better - David Barratt - Pavel.Batanov - avi123 + - Pavel Prischepa - alsar - downace - Aarón Nieves Fernández @@ -1613,6 +1619,7 @@ Symfony is the result of the work of many people who made the code better - David Zuelke - Adrian - Oleg Andreyev + - neFAST - Pierre Rineau - Maxim Lovchikov - adenkejawen @@ -1710,7 +1717,6 @@ Symfony is the result of the work of many people who made the code better - Giovanni Albero (johntree) - Jorge Martin (jorgemartind) - Joeri Verdeyen (jverdeyen) - - Dmitrii Poddubnyi (karser) - Kevin Verschaeve (keversc) - Kevin Herrera (kherge) - Luis Ramón López López (lrlopez) @@ -1872,6 +1878,7 @@ Symfony is the result of the work of many people who made the code better - zorn - Yuriy Potemkin - Emilie Lorenzo + - Edvin Hultberg - Benjamin Long - Matt Janssen - Ben Miller @@ -1992,6 +1999,7 @@ Symfony is the result of the work of many people who made the code better - Alex Carol (picard89) - Daniel Perez Pinazo (pitiflautico) - Phil Taylor (prazgod) + - Maxim Pustynnikov (pustynnikov) - Brayden Williams (redstar504) - Rich Sage (richsage) - Rokas Mikalkėnas (rokasm) diff --git a/UPGRADE-2.7.md b/UPGRADE-2.7.md index 0121a39e2057e..8e6a99c62e33c 100644 --- a/UPGRADE-2.7.md +++ b/UPGRADE-2.7.md @@ -646,6 +646,34 @@ FrameworkBundle } ``` + * The assets settings under `framework.templating` were deprecated and will be removed in Symfony 3.0. Use `framework.assets` instead. + + Before: + + ```yml + framework: + templating: + assets_version: 'v123' + assets_version_format: '%%s?version=%%s' + assets_base_urls: + http: ['http://cdn.example.com'] + ssl: ['https://secure.example.com'] + packages: + # ... + ``` + + After: + + ```yml + framework: + assets: + version: 'v123' + version_format: '%%s?version=%%s' + base_urls: ['http://cdn.example.com', 'https://secure.example.com'] + packages: + # ... + ``` + Security --------------- diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index 5d36e2675a525..1d97d78a82f45 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -883,6 +883,34 @@ UPGRADE FROM 2.x to 3.0 engines: ['php'] ``` + * The assets settings under `framework.templating` were moved to `framework.assets`. + + Before: + + ```yml + framework: + templating: + assets_version: 'v123' + assets_version_format: '%%s?version=%%s' + assets_base_urls: + http: ['http://cdn.example.com'] + ssl: ['https://secure.example.com'] + packages: + # ... + ``` + + After: + + ```yml + framework: + assets: + version: 'v123' + version_format: '%%s?version=%%s' + base_urls: ['http://cdn.example.com', 'https://secure.example.com'] + packages: + # ... + ``` + * The `form.csrf_provider` service is removed as it implements an adapter for the new token manager to the deprecated `Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface` diff --git a/phpunit.xml.dist b/phpunit.xml.dist index ada17c766aec4..9adb7837d83d9 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,7 +1,7 @@ - Symfony\Component\Console - Symfony\Component\HttpFoundation + Symfony\Component\Console + Symfony\Component\HttpFoundation diff --git a/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php b/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php index 38c5af47345ee..65df5c5ea6966 100644 --- a/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php +++ b/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php @@ -13,6 +13,7 @@ use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\DBAL\Logging\DebugStack; +use Doctrine\DBAL\Types\ConversionException; use Doctrine\DBAL\Types\Type; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -132,7 +133,14 @@ private function sanitizeQuery($connectionName, $query) } if ($type instanceof Type) { $query['types'][$j] = $type->getBindingType(); - $param = $type->convertToDatabaseValue($param, $this->registry->getConnection($connectionName)->getDatabasePlatform()); + try { + $param = $type->convertToDatabaseValue($param, $this->registry->getConnection($connectionName)->getDatabasePlatform()); + } catch (\TypeError $e) { + // Error thrown while processing params, query is not explainable. + $query['explainable'] = false; + } catch (ConversionException $e) { + $query['explainable'] = false; + } } } diff --git a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php index c3b7588e95049..5e41b10e14bb2 100644 --- a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php +++ b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php @@ -50,7 +50,8 @@ public function __construct(Connection $conn) */ public function loadTokenBySeries($series) { - $sql = 'SELECT class, username, value, lastUsed' + // the alias for lastUsed works around case insensitivity in PostgreSQL + $sql = 'SELECT class, username, value, lastUsed AS last_used' .' FROM rememberme_token WHERE series=:series'; $paramValues = array('series' => $series); $paramTypes = array('series' => \PDO::PARAM_STR); @@ -58,7 +59,7 @@ public function loadTokenBySeries($series) $row = $stmt->fetch(\PDO::FETCH_ASSOC); if ($row) { - return new PersistentToken($row['class'], $row['username'], $series, $row['value'], new \DateTime($row['lastUsed'])); + return new PersistentToken($row['class'], $row['username'], $series, $row['value'], new \DateTime($row['last_used'])); } throw new TokenNotFoundException('No token found.'); diff --git a/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTest.php b/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTest.php index 7557390bc6ab6..f968bbd257abd 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTest.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Doctrine\Tests\DataCollector; use Doctrine\DBAL\Platforms\MySqlPlatform; +use Doctrine\DBAL\Version; use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\DataCollector\DoctrineDataCollector; use Symfony\Component\HttpFoundation\Request; @@ -120,7 +121,7 @@ public function testSerialization($param, $types, $expected, $explainable) public function paramProvider() { - return array( + $tests = array( array('some value', array(), 'some value', true), array(1, array(), 1, true), array(true, array(), true, true), @@ -129,6 +130,13 @@ public function paramProvider() array(fopen(__FILE__, 'r'), array(), 'Resource(stream)', false), array(new \SplFileInfo(__FILE__), array(), 'Object(SplFileInfo)', false), ); + + if (version_compare(Version::VERSION, '2.6', '>=')) { + $tests[] = array('this is not a date', array('date'), 'this is not a date', false); + $tests[] = array(new \stdClass(), array('date'), 'Object(stdClass)', false); + } + + return $tests; } private function createCollector($queries) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php index 3377549187b48..351ff941d55b1 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php @@ -1543,4 +1543,43 @@ public function testSetDataNonEmptyArraySubmitNullMultiple() $this->assertEquals(array(), $form->getNormData()); $this->assertSame(array(), $form->getViewData(), 'View data is always an array'); } + + public function testSubmitNullUsesDefaultEmptyData($emptyData = 'empty', $expectedData = null) + { + $emptyData = '1'; + $entity1 = new SingleIntIdEntity(1, 'Foo'); + $this->persist(array($entity1)); + + $form = $this->factory->create(static::TESTED_TYPE, null, array( + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + 'empty_data' => $emptyData, + )); + $form->submit(null); + + $this->assertSame($emptyData, $form->getViewData()); + $this->assertSame($entity1, $form->getNormData()); + $this->assertSame($entity1, $form->getData()); + } + + public function testSubmitNullMultipleUsesDefaultEmptyData() + { + $emptyData = array('1'); + $entity1 = new SingleIntIdEntity(1, 'Foo'); + $this->persist(array($entity1)); + + $form = $this->factory->create(static::TESTED_TYPE, null, array( + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + 'multiple' => true, + 'empty_data' => $emptyData, + )); + $form->submit(null); + + $collection = new ArrayCollection(array($entity1)); + + $this->assertSame($emptyData, $form->getViewData()); + $this->assertEquals($collection, $form->getNormData()); + $this->assertEquals($collection, $form->getData()); + } } diff --git a/src/Symfony/Bridge/Doctrine/phpunit.xml.dist b/src/Symfony/Bridge/Doctrine/phpunit.xml.dist index 24f92f2ab8e2f..fa76fa9b500e7 100644 --- a/src/Symfony/Bridge/Doctrine/phpunit.xml.dist +++ b/src/Symfony/Bridge/Doctrine/phpunit.xml.dist @@ -1,7 +1,7 @@ + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/phpunit.xml.dist b/src/Symfony/Bundle/FrameworkBundle/phpunit.xml.dist index cd0ded87eee1e..ccfb091951885 100644 --- a/src/Symfony/Bundle/FrameworkBundle/phpunit.xml.dist +++ b/src/Symfony/Bundle/FrameworkBundle/phpunit.xml.dist @@ -1,7 +1,7 @@ redirectCount; } + $originalUri = $uri; + $uri = $this->getAbsoluteUri($uri); $server = array_merge($this->server, $server); - if (isset($server['HTTPS'])) { + if (!empty($server['HTTP_HOST']) && null === parse_url($originalUri, PHP_URL_HOST)) { + $uri = preg_replace('{^(https?\://)'.preg_quote($this->extractHost($uri)).'}', '${1}'.$server['HTTP_HOST'], $uri); + } + + if (isset($server['HTTPS']) && null === parse_url($originalUri, PHP_URL_SCHEME)) { $uri = preg_replace('{^'.parse_url($uri, PHP_URL_SCHEME).'}', $server['HTTPS'] ? 'https' : 'http', $uri); } diff --git a/src/Symfony/Component/BrowserKit/Tests/ClientTest.php b/src/Symfony/Component/BrowserKit/Tests/ClientTest.php index 8e36eec41b506..5a1844e1c46c4 100644 --- a/src/Symfony/Component/BrowserKit/Tests/ClientTest.php +++ b/src/Symfony/Component/BrowserKit/Tests/ClientTest.php @@ -638,7 +638,7 @@ public function testSetServerParameterInRequest() $this->assertEquals('', $client->getServerParameter('HTTP_HOST')); $this->assertEquals('Symfony2 BrowserKit', $client->getServerParameter('HTTP_USER_AGENT')); - $this->assertEquals('http://www.example.com/https/www.example.com', $client->getRequest()->getUri()); + $this->assertEquals('https://www.example.com/https/www.example.com', $client->getRequest()->getUri()); $server = $client->getRequest()->getServer(); @@ -652,7 +652,24 @@ public function testSetServerParameterInRequest() $this->assertEquals('new-server-key-value', $server['NEW_SERVER_KEY']); $this->assertArrayHasKey('HTTPS', $server); - $this->assertFalse($server['HTTPS']); + $this->assertTrue($server['HTTPS']); + } + + public function testRequestWithRelativeUri() + { + $client = new TestClient(); + + $client->request('GET', '/', array(), array(), array( + 'HTTP_HOST' => 'testhost', + 'HTTPS' => true, + )); + $this->assertEquals('https://testhost/', $client->getRequest()->getUri()); + + $client->request('GET', 'https://www.example.com/', array(), array(), array( + 'HTTP_HOST' => 'testhost', + 'HTTPS' => false, + )); + $this->assertEquals('https://www.example.com/', $client->getRequest()->getUri()); } public function testInternalRequest() diff --git a/src/Symfony/Component/BrowserKit/phpunit.xml.dist b/src/Symfony/Component/BrowserKit/phpunit.xml.dist index fa6d06a8068c8..ec1dbcb2c7660 100644 --- a/src/Symfony/Component/BrowserKit/phpunit.xml.dist +++ b/src/Symfony/Component/BrowserKit/phpunit.xml.dist @@ -1,7 +1,7 @@ $val) { if (isset($this->children[$name])) { - $normalized[$name] = $this->children[$name]->normalize($val); + try { + $normalized[$name] = $this->children[$name]->normalize($val); + } catch (UnsetKeyException $e) { + } unset($value[$name]); } elseif (!$this->removeExtraKeys) { $normalized[$name] = $val; diff --git a/src/Symfony/Component/Config/Definition/BaseNode.php b/src/Symfony/Component/Config/Definition/BaseNode.php index c78b6e762738c..7ca956e2113b0 100644 --- a/src/Symfony/Component/Config/Definition/BaseNode.php +++ b/src/Symfony/Component/Config/Definition/BaseNode.php @@ -245,7 +245,7 @@ final public function normalize($value) * * @param $value * - * @return $value The normalized array value + * @return The normalized array value */ protected function preNormalize($value) { diff --git a/src/Symfony/Component/Config/Definition/Builder/ExprBuilder.php b/src/Symfony/Component/Config/Definition/Builder/ExprBuilder.php index fedbe0cc1bba2..ddbe5b0401ab6 100644 --- a/src/Symfony/Component/Config/Definition/Builder/ExprBuilder.php +++ b/src/Symfony/Component/Config/Definition/Builder/ExprBuilder.php @@ -149,7 +149,7 @@ public function thenEmptyArray() } /** - * Sets a closure marking the value as invalid at validation time. + * Sets a closure marking the value as invalid at processing time. * * if you want to add the value of the node in your message just use a %s placeholder. * @@ -167,7 +167,7 @@ public function thenInvalid($message) } /** - * Sets a closure unsetting this key of the array at validation time. + * Sets a closure unsetting this key of the array at processing time. * * @return $this * diff --git a/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php b/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php index 1fac66fd3702f..95863d68f9bba 100644 --- a/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php +++ b/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php @@ -133,7 +133,7 @@ public function variableNode($name) /** * Returns the parent node. * - * @return ParentNodeDefinitionInterface|NodeDefinition The parent node + * @return NodeDefinition&ParentNodeDefinitionInterface The parent node */ public function end() { diff --git a/src/Symfony/Component/Config/Definition/Builder/NodeDefinition.php b/src/Symfony/Component/Config/Definition/Builder/NodeDefinition.php index f94d3f01f803a..a14161f082b9d 100644 --- a/src/Symfony/Component/Config/Definition/Builder/NodeDefinition.php +++ b/src/Symfony/Component/Config/Definition/Builder/NodeDefinition.php @@ -327,7 +327,7 @@ protected function normalization() /** * Instantiate and configure the node according to this definition. * - * @return NodeInterface $node The node instance + * @return NodeInterface The node instance * * @throws InvalidDefinitionException When the definition is invalid */ diff --git a/src/Symfony/Component/Config/Tests/Definition/Builder/ArrayNodeDefinitionTest.php b/src/Symfony/Component/Config/Tests/Definition/Builder/ArrayNodeDefinitionTest.php index 482ead310be1b..05fcd94e901f5 100644 --- a/src/Symfony/Component/Config/Tests/Definition/Builder/ArrayNodeDefinitionTest.php +++ b/src/Symfony/Component/Config/Tests/Definition/Builder/ArrayNodeDefinitionTest.php @@ -231,6 +231,25 @@ public function testNormalizeKeys() $this->assertFalse($this->getField($node, 'normalizeKeys')); } + public function testUnsetChild() + { + $node = new ArrayNodeDefinition('root'); + $node + ->children() + ->scalarNode('value') + ->beforeNormalization() + ->ifTrue(function ($value) { + return empty($value); + }) + ->thenUnset() + ->end() + ->end() + ->end() + ; + + $this->assertSame(array(), $node->getNode()->normalize(array('value' => null))); + } + public function getEnableableNodeFixtures() { return array( diff --git a/src/Symfony/Component/Config/phpunit.xml.dist b/src/Symfony/Component/Config/phpunit.xml.dist index 36ef339fd78e4..1cfdb3cdc6727 100644 --- a/src/Symfony/Component/Config/phpunit.xml.dist +++ b/src/Symfony/Component/Config/phpunit.xml.dist @@ -1,7 +1,7 @@ node->getAttribute('id')); - $fieldNodes = $xpath->query(sprintf('descendant::input[@form=%s] | descendant::button[@form=%1$s] | descendant::textarea[@form=%1$s] | descendant::select[@form=%1$s] | //form[@id=%1$s]//input[not(@form)] | //form[@id=%1$s]//button[not(@form)] | //form[@id=%1$s]//textarea[not(@form)] | //form[@id=%1$s]//select[not(@form)]', $formId)); + $fieldNodes = $xpath->query(sprintf('( descendant::input[@form=%s] | descendant::button[@form=%1$s] | descendant::textarea[@form=%1$s] | descendant::select[@form=%1$s] | //form[@id=%1$s]//input[not(@form)] | //form[@id=%1$s]//button[not(@form)] | //form[@id=%1$s]//textarea[not(@form)] | //form[@id=%1$s]//select[not(@form)] )[not(ancestor::template)]', $formId)); foreach ($fieldNodes as $node) { $this->addField($node); } } else { // do the xpath query with $this->node as the context node, to only find descendant elements // however, descendant elements with form attribute are not part of this form - $fieldNodes = $xpath->query('descendant::input[not(@form)] | descendant::button[not(@form)] | descendant::textarea[not(@form)] | descendant::select[not(@form)]', $this->node); + $fieldNodes = $xpath->query('( descendant::input[not(@form)] | descendant::button[not(@form)] | descendant::textarea[not(@form)] | descendant::select[not(@form)] )[not(ancestor::template)]', $this->node); foreach ($fieldNodes as $node) { $this->addField($node); } diff --git a/src/Symfony/Component/DomCrawler/Tests/FormTest.php b/src/Symfony/Component/DomCrawler/Tests/FormTest.php index 02d2ebe593b08..7e259de196310 100644 --- a/src/Symfony/Component/DomCrawler/Tests/FormTest.php +++ b/src/Symfony/Component/DomCrawler/Tests/FormTest.php @@ -394,6 +394,10 @@ public function testGetValues() $form = $this->createForm('
'); $this->assertEquals(array('bar' => 'bar'), $form->getValues(), '->getValues() does not include disabled fields'); + + $form = $this->createForm('
'); + $this->assertEquals(array('bar' => 'bar'), $form->getValues(), '->getValues() does not include template fields'); + $this->assertFalse($form->has('foo')); } public function testSetValues() @@ -444,6 +448,10 @@ public function testGetFiles() $form = $this->createForm('
'); $this->assertEquals(array(), $form->getFiles(), '->getFiles() does not include disabled file fields'); + + $form = $this->createForm('
'); + $this->assertEquals(array(), $form->getFiles(), '->getFiles() does not include template file fields'); + $this->assertFalse($form->has('foo')); } public function testGetPhpFiles() @@ -857,7 +865,7 @@ protected function getFormFieldMock($name, $value = null) protected function createForm($form, $method = null, $currentUri = null) { $dom = new \DOMDocument(); - $dom->loadHTML(''.$form.''); + @$dom->loadHTML(''.$form.''); $xPath = new \DOMXPath($dom); $nodes = $xPath->query('//input | //button'); diff --git a/src/Symfony/Component/DomCrawler/phpunit.xml.dist b/src/Symfony/Component/DomCrawler/phpunit.xml.dist index ad714a8fd81c0..5f63490ce99d5 100644 --- a/src/Symfony/Component/DomCrawler/phpunit.xml.dist +++ b/src/Symfony/Component/DomCrawler/phpunit.xml.dist @@ -1,7 +1,7 @@ array('preFoo', 10), 'post.foo' => array('postFoo'), - ); + ); } } diff --git a/src/Symfony/Component/EventDispatcher/phpunit.xml.dist b/src/Symfony/Component/EventDispatcher/phpunit.xml.dist index b3ad1bdf5a8e3..f2eb1692cdbba 100644 --- a/src/Symfony/Component/EventDispatcher/phpunit.xml.dist +++ b/src/Symfony/Component/EventDispatcher/phpunit.xml.dist @@ -1,7 +1,7 @@ propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); $this->choiceListFactory = $choiceListFactory ?: new CachingFactoryDecorator(new PropertyAccessDecorator(new DefaultChoiceListFactory(), $this->propertyAccessor)); + $this->translator = $translator; } protected function loadTypes() @@ -71,4 +75,11 @@ protected function loadTypes() new Type\CurrencyType(), ); } + + protected function loadTypeExtensions() + { + return array( + new TransformationFailureExtension($this->translator), + ); + } } diff --git a/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php b/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php index f9721e52b1769..e8dbabdb9fb90 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php +++ b/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php @@ -73,16 +73,17 @@ public function mapFormsToData($forms, &$data) // Write-back is disabled if the form is not synchronized (transformation failed), // if the form was not submitted and if the form is disabled (modification not allowed) if (null !== $propertyPath && $config->getMapped() && $form->isSubmitted() && $form->isSynchronized() && !$form->isDisabled()) { - // If the field is of type DateTime and the data is the same skip the update to + $propertyValue = $form->getData(); + // If the field is of type DateTime or DateTimeInterface and the data is the same skip the update to // keep the original object hash - if ($form->getData() instanceof \DateTime && $form->getData() == $this->propertyAccessor->getValue($data, $propertyPath)) { + if (($propertyValue instanceof \DateTime || $propertyValue instanceof \DateTimeInterface) && $propertyValue == $this->propertyAccessor->getValue($data, $propertyPath)) { continue; } // If the data is identical to the value in $data, we are // dealing with a reference - if (!\is_object($data) || !$config->getByReference() || $form->getData() !== $this->propertyAccessor->getValue($data, $propertyPath)) { - $this->propertyAccessor->setValue($data, $propertyPath, $form->getData()); + if (!\is_object($data) || !$config->getByReference() || $propertyValue !== $this->propertyAccessor->getValue($data, $propertyPath)) { + $this->propertyAccessor->setValue($data, $propertyPath, $propertyValue); } } } diff --git a/src/Symfony/Component/Form/Extension/Core/EventListener/TransformationFailureListener.php b/src/Symfony/Component/Form/Extension/Core/EventListener/TransformationFailureListener.php new file mode 100644 index 0000000000000..f46eb499e0cf9 --- /dev/null +++ b/src/Symfony/Component/Form/Extension/Core/EventListener/TransformationFailureListener.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\FormError; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; +use Symfony\Component\Translation\TranslatorInterface; + +/** + * @author Christian Flothmann + */ +class TransformationFailureListener implements EventSubscriberInterface +{ + private $translator; + + public function __construct(TranslatorInterface $translator = null) + { + $this->translator = $translator; + } + + public static function getSubscribedEvents() + { + return array( + FormEvents::POST_SUBMIT => array('convertTransformationFailureToFormError', -1024), + ); + } + + public function convertTransformationFailureToFormError(FormEvent $event) + { + $form = $event->getForm(); + + if (null === $form->getTransformationFailure() || !$form->isValid()) { + return; + } + + foreach ($form as $child) { + if (!$child->isSynchronized()) { + return; + } + } + + $clientDataAsString = is_scalar($form->getViewData()) ? (string) $form->getViewData() : \gettype($form->getViewData()); + $messageTemplate = 'The value {{ value }} is not valid.'; + + if (null !== $this->translator) { + $message = $this->translator->trans($messageTemplate, array('{{ value }}' => $clientDataAsString)); + } else { + $message = strtr($messageTemplate, array('{{ value }}' => $clientDataAsString)); + } + + $form->addError(new FormError($message, $messageTemplate, array('{{ value }}' => $clientDataAsString), null, $form->getTransformationFailure())); + } +} diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php index c3f95f59af03c..64d2c2d2f4d20 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php @@ -90,6 +90,9 @@ public function buildForm(FormBuilderInterface $builder, array $options) )); } } else { + // when the form is compound the entries of the array are ignored in favor of children data + // so we need to handle the cascade setting here + $emptyData = $builder->getEmptyData() ?: array(); // Only pass a subset of the options to children $dateOptions = array_intersect_key($options, array_flip(array( 'years', @@ -105,6 +108,10 @@ public function buildForm(FormBuilderInterface $builder, array $options) 'invalid_message_parameters', ))); + if (isset($emptyData['date'])) { + $dateOptions['empty_data'] = $emptyData['date']; + } + $timeOptions = array_intersect_key($options, array_flip(array( 'hours', 'minutes', @@ -121,6 +128,10 @@ public function buildForm(FormBuilderInterface $builder, array $options) 'invalid_message_parameters', ))); + if (isset($emptyData['time'])) { + $timeOptions['empty_data'] = $emptyData['time']; + } + if (false === $options['label']) { $dateOptions['label'] = false; $timeOptions['label'] = false; @@ -226,6 +237,9 @@ public function configureOptions(OptionsResolver $resolver) // this option. 'data_class' => null, 'compound' => $compound, + 'empty_data' => function (Options $options) { + return $options['compound'] ? array() : ''; + }, )); // Don't add some defaults in order to preserve the defaults diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php index ad4499899a3ba..ce41c3c7d3058 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php @@ -75,7 +75,21 @@ public function buildForm(FormBuilderInterface $builder, array $options) $yearOptions = $monthOptions = $dayOptions = array( 'error_bubbling' => true, + 'empty_data' => '', ); + // when the form is compound the entries of the array are ignored in favor of children data + // so we need to handle the cascade setting here + $emptyData = $builder->getEmptyData() ?: array(); + + if (isset($emptyData['year'])) { + $yearOptions['empty_data'] = $emptyData['year']; + } + if (isset($emptyData['month'])) { + $monthOptions['empty_data'] = $emptyData['month']; + } + if (isset($emptyData['day'])) { + $dayOptions['empty_data'] = $emptyData['day']; + } if (isset($options['invalid_message'])) { $dayOptions['invalid_message'] = $options['invalid_message']; @@ -272,6 +286,9 @@ public function configureOptions(OptionsResolver $resolver) // this option. 'data_class' => null, 'compound' => $compound, + 'empty_data' => function (Options $options) { + return $options['compound'] ? array() : ''; + }, 'choice_translation_domain' => false, )); diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php index 4a27cd440b665..facfc098b7693 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php @@ -70,7 +70,15 @@ public function buildForm(FormBuilderInterface $builder, array $options) } else { $hourOptions = $minuteOptions = $secondOptions = array( 'error_bubbling' => true, + 'empty_data' => '', ); + // when the form is compound the entries of the array are ignored in favor of children data + // so we need to handle the cascade setting here + $emptyData = $builder->getEmptyData() ?: array(); + + if (isset($emptyData['hour'])) { + $hourOptions['empty_data'] = $emptyData['hour']; + } if (isset($options['invalid_message'])) { $hourOptions['invalid_message'] = $options['invalid_message']; @@ -138,10 +146,16 @@ public function buildForm(FormBuilderInterface $builder, array $options) $builder->add('hour', self::$widgets[$options['widget']], $hourOptions); if ($options['with_minutes']) { + if (isset($emptyData['minute'])) { + $minuteOptions['empty_data'] = $emptyData['minute']; + } $builder->add('minute', self::$widgets[$options['widget']], $minuteOptions); } if ($options['with_seconds']) { + if (isset($emptyData['second'])) { + $secondOptions['empty_data'] = $emptyData['second']; + } $builder->add('second', self::$widgets[$options['widget']], $secondOptions); } @@ -265,6 +279,9 @@ public function configureOptions(OptionsResolver $resolver) // representation is not \DateTime, but an array, we need to unset // this option. 'data_class' => null, + 'empty_data' => function (Options $options) { + return $options['compound'] ? array() : ''; + }, 'compound' => $compound, 'choice_translation_domain' => false, )); diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TransformationFailureExtension.php b/src/Symfony/Component/Form/Extension/Core/Type/TransformationFailureExtension.php new file mode 100644 index 0000000000000..98875594d6f70 --- /dev/null +++ b/src/Symfony/Component/Form/Extension/Core/Type/TransformationFailureExtension.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\Form\Extension\Core\EventListener\TransformationFailureListener; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Translation\TranslatorInterface; + +/** + * @author Christian Flothmann + */ +class TransformationFailureExtension extends AbstractTypeExtension +{ + private $translator; + + public function __construct(TranslatorInterface $translator = null) + { + $this->translator = $translator; + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + if (!isset($options['invalid_message']) && !isset($options['invalid_message_parameters'])) { + $builder->addEventSubscriber(new TransformationFailureListener($this->translator)); + } + } + + public function getExtendedType() + { + return 'Symfony\Component\Form\Extension\Core\Type\FormType'; + } +} diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index 923407383159b..95186fae7684d 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -32,16 +32,18 @@ * * (1) the "model" format required by the form's object * (2) the "normalized" format for internal processing - * (3) the "view" format used for display + * (3) the "view" format used for display simple fields + * or map children model data for compound fields * * A date field, for example, may store a date as "Y-m-d" string (1) in the * object. To facilitate processing in the field, this value is normalized * to a DateTime object (2). In the HTML representation of your form, a - * localized string (3) is presented to and modified by the user. + * localized string (3) may be presented to and modified by the user, or it could be an array of values + * to be mapped to choices fields. * * In most cases, format (1) and format (2) will be the same. For example, * a checkbox field uses a Boolean value for both internal processing and - * storage in the object. In these cases you simply need to set a value + * storage in the object. In these cases you simply need to set a view * transformer to convert between formats (2) and (3). You can do this by * calling addViewTransformer(). * @@ -49,7 +51,7 @@ * demonstrate this, let's extend our above date field to store the value * either as "Y-m-d" string or as timestamp. Internally we still want to * use a DateTime object for processing. To convert the data from string/integer - * to DateTime you can set a normalization transformer by calling + * to DateTime you can set a model transformer by calling * addModelTransformer(). The normalized data is then converted to the displayed * data as described before. * @@ -218,7 +220,7 @@ public function getPropertyPath() } if (null === $this->getName() || '' === $this->getName()) { - return; + return null; } $parent = $this->parent; @@ -341,8 +343,8 @@ public function setData($modelData) $modelData = $event->getData(); } - // Treat data as strings unless a value transformer exists - if (!$this->config->getViewTransformers() && !$this->config->getModelTransformers() && is_scalar($modelData)) { + // Treat data as strings unless a transformer exists + if (is_scalar($modelData) && !$this->config->getViewTransformers() && !$this->config->getModelTransformers()) { $modelData = (string) $modelData; } @@ -1068,7 +1070,7 @@ public function createView(FormView $parent = null) } /** - * Normalizes the value if a normalization transformer is set. + * Normalizes the value if a model transformer is set. * * @param mixed $value The value to transform * @@ -1090,7 +1092,7 @@ private function modelToNorm($value) } /** - * Reverse transforms a value if a normalization transformer is set. + * Reverse transforms a value if a model transformer is set. * * @param string $value The value to reverse transform * @@ -1114,7 +1116,7 @@ private function normToModel($value) } /** - * Transforms the value if a value transformer is set. + * Transforms the value if a view transformer is set. * * @param mixed $value The value to transform * @@ -1145,7 +1147,7 @@ private function normToView($value) } /** - * Reverse transforms a value if a value transformer is set. + * Reverse transforms a value if a view transformer is set. * * @param string $value The value to reverse transform * diff --git a/src/Symfony/Component/Form/NativeRequestHandler.php b/src/Symfony/Component/Form/NativeRequestHandler.php index ccebdab6d80a1..94210d51e85a5 100644 --- a/src/Symfony/Component/Form/NativeRequestHandler.php +++ b/src/Symfony/Component/Form/NativeRequestHandler.php @@ -15,7 +15,7 @@ use Symfony\Component\Form\Util\ServerParams; /** - * A request handler using PHP's super globals $_GET, $_POST and $_SERVER. + * A request handler using PHP super globals $_GET, $_POST and $_SERVER. * * @author Bernhard Schussek */ @@ -213,7 +213,7 @@ private static function stripEmptyFiles($data) if (self::$fileKeys === $keys) { if (UPLOAD_ERR_NO_FILE === $data['error']) { - return; + return null; } return $data; diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/CoreExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/CoreExtensionTest.php new file mode 100644 index 0000000000000..ff85149e21c63 --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Extension/Core/CoreExtensionTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\Extension\Core; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Form\Extension\Core\CoreExtension; +use Symfony\Component\Form\FormFactoryBuilder; + +class CoreExtensionTest extends TestCase +{ + public function testTransformationFailuresAreConvertedIntoFormErrors() + { + $formFactoryBuilder = new FormFactoryBuilder(); + $formFactory = $formFactoryBuilder->addExtension(new CoreExtension()) + ->getFormFactory(); + + $form = $formFactory->createBuilder() + ->add('foo', 'Symfony\Component\Form\Extension\Core\Type\DateType') + ->getForm(); + $form->submit('foo'); + + $this->assertFalse($form->isValid()); + } +} diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/PropertyPathMapperTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/PropertyPathMapperTest.php index 979adcdceedd9..1dcff2f4020a4 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/PropertyPathMapperTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/PropertyPathMapperTest.php @@ -357,4 +357,44 @@ public function testMapFormsToDataIgnoresDisabled() $this->mapper->mapFormsToData(array($form), $car); } + + /** + * @dataProvider provideDate + */ + public function testMapFormsToDataDoesNotChangeEqualDateTimeInstance($date) + { + $article = array(); + $publishedAt = $date; + $article['publishedAt'] = clone $publishedAt; + $propertyPath = $this->getPropertyPath('[publishedAt]'); + + $this->propertyAccessor->expects($this->once()) + ->method('getValue') + ->willReturn($article['publishedAt']) + ; + $this->propertyAccessor->expects($this->never()) + ->method('setValue') + ; + + $config = new FormConfigBuilder('publishedAt', \get_class($publishedAt), $this->dispatcher); + $config->setByReference(false); + $config->setPropertyPath($propertyPath); + $config->setData($publishedAt); + $form = $this->getForm($config); + + $this->mapper->mapFormsToData(array($form), $article); + } + + public function provideDate() + { + $data = array( + '\DateTime' => array(new \DateTime()), + ); + + if (class_exists('DateTimeImmutable')) { + $data['\DateTimeImmutable'] = array(new \DateTimeImmutable()); + } + + return $data; + } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/BaseTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/BaseTypeTest.php index facab9730908b..da3f48b486621 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/BaseTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/BaseTypeTest.php @@ -145,6 +145,28 @@ public function testSubmitNull($expected = null, $norm = null, $view = null) $this->assertSame($view, $form->getViewData()); } + public function testSubmitNullUsesDefaultEmptyData($emptyData = 'empty', $expectedData = null) + { + $builder = $this->factory->createBuilder($this->getTestedType()); + + if ($builder->getCompound()) { + $emptyData = array(); + foreach ($builder as $field) { + // empty children should map null (model data) in the compound view data + $emptyData[$field->getName()] = null; + } + } else { + // simple fields share the view and the model format, unless they use a transformer + $expectedData = $emptyData; + } + + $form = $builder->setEmptyData($emptyData)->getForm()->submit(null); + + $this->assertSame($emptyData, $form->getViewData()); + $this->assertSame($expectedData, $form->getNormData()); + $this->assertSame($expectedData, $form->getData()); + } + protected function getTestedType() { return static::TESTED_TYPE; diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ButtonTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ButtonTypeTest.php index 9f105272fe592..dbef3ff9ed6d2 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ButtonTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ButtonTypeTest.php @@ -32,4 +32,16 @@ public function testCreateButtonInstances() { $this->assertInstanceOf('Symfony\Component\Form\Button', $this->factory->create(static::TESTED_TYPE)); } + + /** + * @expectedException \Symfony\Component\Form\Exception\BadMethodCallException + * @expectedExceptionMessage Buttons do not support empty data. + * + * @param string $emptyData + * @param null $expectedData + */ + public function testSubmitNullUsesDefaultEmptyData($emptyData = 'empty', $expectedData = null) + { + parent::testSubmitNullUsesDefaultEmptyData($emptyData, $expectedData); + } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CheckboxTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CheckboxTypeTest.php index 95f4ad201e4c9..38dbd67db80a0 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CheckboxTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CheckboxTypeTest.php @@ -187,4 +187,17 @@ public function testSubmitNull($expected = null, $norm = null, $view = null) { parent::testSubmitNull(false, false, null); } + + public function testSubmitNullUsesDefaultEmptyData($emptyData = 'empty', $expectedData = true) + { + $form = $this->factory->create(static::TESTED_TYPE, null, array( + 'empty_data' => $emptyData, + )); + $form->submit(null); + + // view data is transformed to the string true value + $this->assertSame('1', $form->getViewData()); + $this->assertSame($expectedData, $form->getNormData()); + $this->assertSame($expectedData, $form->getData()); + } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php index 59dc05cb681c4..2ef745efa5785 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php @@ -684,19 +684,20 @@ public function testSubmitSingleNonExpandedObjectChoices() $this->assertTrue($form->isSynchronized()); } - public function testSubmitSingleChoiceWithEmptyData() + public function testSubmitNullUsesDefaultEmptyData($emptyData = 'empty', $expectedData = null) { $form = $this->factory->create(static::TESTED_TYPE, null, array( 'multiple' => false, 'expanded' => false, - 'choices' => array('test'), + // empty data must match string choice value + 'choices' => array($emptyData), 'choices_as_values' => true, - 'empty_data' => 'test', + 'empty_data' => $emptyData, )); $form->submit(null); - $this->assertSame('test', $form->getData()); + $this->assertSame($emptyData, $form->getData()); } public function testSubmitSingleChoiceWithEmptyDataAndInitialData() diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php index 6fcc4dc6d32a1..bdeb4bd1a67ba 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php @@ -410,4 +410,10 @@ public function testSubmitNull($expected = null, $norm = null, $view = null) { parent::testSubmitNull(array(), array(), array()); } + + public function testSubmitNullUsesDefaultEmptyData($emptyData = array(), $expectedData = array()) + { + // resize form listener always set an empty array + parent::testSubmitNullUsesDefaultEmptyData($emptyData, $expectedData); + } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CountryTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CountryTypeTest.php index 2db6daa258103..e9cbc2afd2d9b 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CountryTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CountryTypeTest.php @@ -66,4 +66,9 @@ public function testSubmitNull($expected = null, $norm = null, $view = null) { parent::testSubmitNull($expected, $norm, ''); } + + public function testSubmitNullUsesDefaultEmptyData($emptyData = 'FR', $expectedData = 'FR') + { + parent::testSubmitNullUsesDefaultEmptyData($emptyData, $expectedData); + } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CurrencyTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CurrencyTypeTest.php index 739cc408ba5af..ab2b389710549 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CurrencyTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CurrencyTypeTest.php @@ -49,4 +49,9 @@ public function testSubmitNull($expected = null, $norm = null, $view = null) { parent::testSubmitNull($expected, $norm, ''); } + + public function testSubmitNullUsesDefaultEmptyData($emptyData = 'EUR', $expectedData = 'EUR') + { + parent::testSubmitNullUsesDefaultEmptyData($emptyData, $expectedData); + } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php index 27c6581f0bea0..f0e45f5d5600d 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php @@ -615,4 +615,47 @@ public function testSubmitNullWithSingleText() $this->assertNull($form->getNormData()); $this->assertSame('', $form->getViewData()); } + + public function testSubmitNullUsesDefaultEmptyData($emptyData = array(), $expectedData = null) + { + $form = $this->factory->create(static::TESTED_TYPE, null, array( + 'empty_data' => $emptyData, + )); + $form->submit(null); + + // view transformer writes back empty strings in the view data + $this->assertSame( + array('date' => array('year' => '', 'month' => '', 'day' => ''), 'time' => array('hour' => '', 'minute' => '')), + $form->getViewData() + ); + $this->assertSame($expectedData, $form->getNormData()); + $this->assertSame($expectedData, $form->getData()); + } + + /** + * @dataProvider provideEmptyData + */ + public function testSubmitNullUsesDateEmptyData($widget, $emptyData, $expectedData) + { + $form = $this->factory->create(static::TESTED_TYPE, null, array( + 'widget' => $widget, + 'empty_data' => $emptyData, + )); + $form->submit(null); + + $this->assertSame($emptyData, $form->getViewData()); + $this->assertEquals($expectedData, $form->getNormData()); + $this->assertEquals($expectedData, $form->getData()); + } + + public function provideEmptyData() + { + $expectedData = \DateTime::createFromFormat('Y-m-d H:i', '2018-11-11 21:23'); + + return array( + 'Simple field' => array('single_text', '2018-11-11T21:23:00', $expectedData), + 'Compound text field' => array('text', array('date' => array('year' => '2018', 'month' => '11', 'day' => '11'), 'time' => array('hour' => '21', 'minute' => '23')), $expectedData), + 'Compound choice field' => array('choice', array('date' => array('year' => '2018', 'month' => '11', 'day' => '11'), 'time' => array('hour' => '21', 'minute' => '23')), $expectedData), + ); + } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php index 1eb2e7887bad4..a15a6c2b32034 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php @@ -1003,4 +1003,44 @@ public function testSubmitNullWithSingleText() $this->assertNull($form->getNormData()); $this->assertSame('', $form->getViewData()); } + + public function testSubmitNullUsesDefaultEmptyData($emptyData = array(), $expectedData = null) + { + $form = $this->factory->create(static::TESTED_TYPE, null, array( + 'empty_data' => $emptyData, + )); + $form->submit(null); + + // view transformer writes back empty strings in the view data + $this->assertSame(array('year' => '', 'month' => '', 'day' => ''), $form->getViewData()); + $this->assertSame($expectedData, $form->getNormData()); + $this->assertSame($expectedData, $form->getData()); + } + + /** + * @dataProvider provideEmptyData + */ + public function testSubmitNullUsesDateEmptyData($widget, $emptyData, $expectedData) + { + $form = $this->factory->create(static::TESTED_TYPE, null, array( + 'widget' => $widget, + 'empty_data' => $emptyData, + )); + $form->submit(null); + + $this->assertSame($emptyData, $form->getViewData()); + $this->assertEquals($expectedData, $form->getNormData()); + $this->assertEquals($expectedData, $form->getData()); + } + + public function provideEmptyData() + { + $expectedData = \DateTime::createFromFormat('Y-m-d H:i:s', '2018-11-11 00:00:00'); + + return array( + 'Simple field' => array('single_text', '2018-11-11', $expectedData), + 'Compound text fields' => array('text', array('year' => '2018', 'month' => '11', 'day' => '11'), $expectedData), + 'Compound choice fields' => array('choice', array('year' => '2018', 'month' => '11', 'day' => '11'), $expectedData), + ); + } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php index 7d75dce26dcf2..52722b042065b 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php @@ -227,6 +227,11 @@ public function testDataClassMustBeValidClassOrInterface() )); } + public function testSubmitNullUsesDefaultEmptyData($emptyData = array(), $expectedData = array()) + { + parent::testSubmitNullUsesDefaultEmptyData($emptyData, $expectedData); + } + public function testSubmitWithEmptyDataCreatesObjectIfClassAvailable() { $form = $this->factory->createBuilder(static::TESTED_TYPE, null, array( diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/IntegerTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/IntegerTypeTest.php index 3059e79ce989c..247f956a3bd55 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/IntegerTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/IntegerTypeTest.php @@ -48,4 +48,16 @@ public function testSubmitNull($expected = null, $norm = null, $view = null) { parent::testSubmitNull($expected, $norm, ''); } + + public function testSubmitNullUsesDefaultEmptyData($emptyData = '10', $expectedData = 10) + { + $form = $this->factory->create(static::TESTED_TYPE, null, array( + 'empty_data' => $emptyData, + )); + $form->submit(null); + + $this->assertSame($emptyData, $form->getViewData()); + $this->assertSame($expectedData, $form->getNormData()); + $this->assertSame($expectedData, $form->getData()); + } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/LanguageTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/LanguageTypeTest.php index beffecdd7ecb5..d8127aa6d7bb0 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/LanguageTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/LanguageTypeTest.php @@ -59,4 +59,9 @@ public function testSubmitNull($expected = null, $norm = null, $view = null) { parent::testSubmitNull($expected, $norm, ''); } + + public function testSubmitNullUsesDefaultEmptyData($emptyData = 'en', $expectedData = 'en') + { + parent::testSubmitNullUsesDefaultEmptyData($emptyData, $expectedData); + } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/LocaleTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/LocaleTypeTest.php index 0439e7d2ddcf1..ede22932f8056 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/LocaleTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/LocaleTypeTest.php @@ -49,4 +49,9 @@ public function testSubmitNull($expected = null, $norm = null, $view = null) { parent::testSubmitNull($expected, $norm, ''); } + + public function testSubmitNullUsesDefaultEmptyData($emptyData = 'en', $expectedData = 'en') + { + parent::testSubmitNullUsesDefaultEmptyData($emptyData, $expectedData); + } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/MoneyTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/MoneyTypeTest.php index a865a93c9c651..0b02fffddfc76 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/MoneyTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/MoneyTypeTest.php @@ -80,4 +80,16 @@ public function testMoneyPatternWithoutCurrency() $this->assertSame('{{ widget }}', $view->vars['money_pattern']); } + + public function testSubmitNullUsesDefaultEmptyData($emptyData = '10.00', $expectedData = 10.0) + { + $form = $this->factory->create(static::TESTED_TYPE, null, array( + 'empty_data' => $emptyData, + )); + $form->submit(null); + + $this->assertSame($emptyData, $form->getViewData()); + $this->assertSame($expectedData, $form->getNormData()); + $this->assertSame($expectedData, $form->getData()); + } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/NumberTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/NumberTypeTest.php index 9d53297e8ba24..09c8f2dd2829b 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/NumberTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/NumberTypeTest.php @@ -73,4 +73,16 @@ public function testSubmitNull($expected = null, $norm = null, $view = null) { parent::testSubmitNull($expected, $norm, ''); } + + public function testSubmitNullUsesDefaultEmptyData($emptyData = '10', $expectedData = 10.0) + { + $form = $this->factory->create(static::TESTED_TYPE, null, array( + 'empty_data' => $emptyData, + )); + $form->submit(null); + + $this->assertSame($emptyData, $form->getViewData()); + $this->assertSame($expectedData, $form->getNormData()); + $this->assertSame($expectedData, $form->getData()); + } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php index 611c6e42ac7bd..5ed4a236f1b31 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php @@ -797,4 +797,44 @@ public function testSubmitNull($expected = null, $norm = null, $view = null) parent::testSubmitNull($expected, $norm, $view); } + + public function testSubmitNullUsesDefaultEmptyData($emptyData = array(), $expectedData = null) + { + $form = $this->factory->create(static::TESTED_TYPE, null, array( + 'empty_data' => $emptyData, + )); + $form->submit(null); + + // view transformer writes back empty strings in the view data + $this->assertSame(array('hour' => '', 'minute' => ''), $form->getViewData()); + $this->assertSame($expectedData, $form->getNormData()); + $this->assertSame($expectedData, $form->getData()); + } + + /** + * @dataProvider provideEmptyData + */ + public function testSubmitNullUsesDateEmptyData($widget, $emptyData, $expectedData) + { + $form = $this->factory->create(static::TESTED_TYPE, null, array( + 'widget' => $widget, + 'empty_data' => $emptyData, + )); + $form->submit(null); + + $this->assertSame($emptyData, $form->getViewData()); + $this->assertEquals($expectedData, $form->getNormData()); + $this->assertEquals($expectedData, $form->getData()); + } + + public function provideEmptyData() + { + $expectedData = \DateTime::createFromFormat('Y-m-d H:i', '1970-01-01 21:23'); + + return array( + 'Simple field' => array('single_text', '21:23', $expectedData), + 'Compound text field' => array('text', array('hour' => '21', 'minute' => '23'), $expectedData), + 'Compound choice field' => array('choice', array('hour' => '21', 'minute' => '23'), $expectedData), + ); + } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimezoneTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimezoneTypeTest.php index 507b740505ae9..b6b61cb171d84 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimezoneTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimezoneTypeTest.php @@ -43,4 +43,16 @@ public function testSubmitNull($expected = null, $norm = null, $view = null) { parent::testSubmitNull($expected, $norm, ''); } + + public function testSubmitNullUsesDefaultEmptyData($emptyData = 'Africa/Kinshasa', $expectedData = 'Africa/Kinshasa') + { + $form = $this->factory->create(static::TESTED_TYPE, null, array( + 'empty_data' => $emptyData, + )); + $form->submit(null); + + $this->assertSame($emptyData, $form->getViewData()); + $this->assertSame($expectedData, $form->getNormData()); + $this->assertSame($expectedData, $form->getData()); + } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/UrlTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/UrlTypeTest.php index a3336970dcab5..95be3775a44c3 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/UrlTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/UrlTypeTest.php @@ -100,4 +100,17 @@ public function testSubmitWithNonStringDataDoesNotBreakTheFixUrlProtocolListener $this->assertSame(array('domain.com', 'www.domain.com'), $form->getData()); } + + public function testSubmitNullUsesDefaultEmptyData($emptyData = 'empty', $expectedData = 'http://empty') + { + $form = $this->factory->create(static::TESTED_TYPE, null, array( + 'empty_data' => $emptyData, + )); + $form->submit(null); + + // listener normalizes data on submit + $this->assertSame($expectedData, $form->getViewData()); + $this->assertSame($expectedData, $form->getNormData()); + $this->assertSame($expectedData, $form->getData()); + } } diff --git a/src/Symfony/Component/Form/Util/FormUtil.php b/src/Symfony/Component/Form/Util/FormUtil.php index 0862179f545c1..53053f9d5b791 100644 --- a/src/Symfony/Component/Form/Util/FormUtil.php +++ b/src/Symfony/Component/Form/Util/FormUtil.php @@ -27,7 +27,7 @@ private function __construct() * Returns whether the given data is empty. * * This logic is reused multiple times throughout the processing of - * a form and needs to be consistent. PHP's keyword `empty` cannot + * a form and needs to be consistent. PHP keyword `empty` cannot * be used as it also considers 0 and "0" to be empty. * * @param mixed $data diff --git a/src/Symfony/Component/Form/Util/OrderedHashMap.php b/src/Symfony/Component/Form/Util/OrderedHashMap.php index 6a97559850a31..26e45a462250d 100644 --- a/src/Symfony/Component/Form/Util/OrderedHashMap.php +++ b/src/Symfony/Component/Form/Util/OrderedHashMap.php @@ -128,7 +128,7 @@ public function offsetSet($key, $value) $key = array() === $this->orderedKeys // If the array is empty, use 0 as key ? 0 - // Imitate PHP's behavior of generating a key that equals + // Imitate PHP behavior of generating a key that equals // the highest existing integer key + 1 : 1 + (int) max($this->orderedKeys); } diff --git a/src/Symfony/Component/Form/Util/OrderedHashMapIterator.php b/src/Symfony/Component/Form/Util/OrderedHashMapIterator.php index 3de636392dd85..93a7caa58dabe 100644 --- a/src/Symfony/Component/Form/Util/OrderedHashMapIterator.php +++ b/src/Symfony/Component/Form/Util/OrderedHashMapIterator.php @@ -56,8 +56,6 @@ class OrderedHashMapIterator implements \Iterator private $current; /** - * Creates a new iterator. - * * @param array $elements The elements of the map, indexed by their * keys * @param array $orderedKeys The keys of the map in the order in which @@ -84,7 +82,7 @@ public function __construct(array &$elements, array &$orderedKeys, array &$manag */ public function __destruct() { - // Use array_splice() instead of isset() to prevent holes in the + // Use array_splice() instead of unset() to prevent holes in the // array indices, which would break the initialization of $cursorId array_splice($this->managedCursors, $this->cursorId, 1); } diff --git a/src/Symfony/Component/Form/phpunit.xml.dist b/src/Symfony/Component/Form/phpunit.xml.dist index b834e2f33a5b4..ede79e207de25 100644 --- a/src/Symfony/Component/Form/phpunit.xml.dist +++ b/src/Symfony/Component/Form/phpunit.xml.dist @@ -1,7 +1,7 @@ headers->allPreserveCase() as $name => $values) { + $replace = 0 === strcasecmp($name, 'Content-Type'); foreach ($values as $value) { - header($name.': '.$value, false, $this->statusCode); + header($name.': '.$value, $replace, $this->statusCode); } } diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php index 26335db903dd0..ac6f1a74ed0c8 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php @@ -323,6 +323,9 @@ public function testGetFormatFromMimeTypeWithParameters() { $request = new Request(); $this->assertEquals('json', $request->getFormat('application/json; charset=utf-8')); + $this->assertEquals('json', $request->getFormat('application/json;charset=utf-8')); + $this->assertEquals('json', $request->getFormat('application/json ; charset=utf-8')); + $this->assertEquals('json', $request->getFormat('application/json ;charset=utf-8')); } public function testGetFormatWithCustomMimeType() diff --git a/src/Symfony/Component/HttpFoundation/phpunit.xml.dist b/src/Symfony/Component/HttpFoundation/phpunit.xml.dist index c1d61f8bf1da0..f57bc9e62d5eb 100644 --- a/src/Symfony/Component/HttpFoundation/phpunit.xml.dist +++ b/src/Symfony/Component/HttpFoundation/phpunit.xml.dist @@ -1,7 +1,7 @@ $this->matcherClass); diff --git a/src/Symfony/Component/Routing/phpunit.xml.dist b/src/Symfony/Component/Routing/phpunit.xml.dist index bcc0959522cb6..df742eab00331 100644 --- a/src/Symfony/Component/Routing/phpunit.xml.dist +++ b/src/Symfony/Component/Routing/phpunit.xml.dist @@ -1,7 +1,7 @@ source; // If the xlf file has another encoding specified, try to convert it because // simple_xml will always return utf-8 encoded values - $target = $this->utf8ToCharset((string) (isset($translation->target) ? $translation->target : $source), $encoding); + $target = $this->utf8ToCharset((string) (isset($translation->target) ? $translation->target : $translation->source), $encoding); $catalogue->set((string) $source, $target, $domain); diff --git a/src/Symfony/Component/Translation/LoggingTranslator.php b/src/Symfony/Component/Translation/LoggingTranslator.php index 1cefcb6b99196..50a878092effc 100644 --- a/src/Symfony/Component/Translation/LoggingTranslator.php +++ b/src/Symfony/Component/Translation/LoggingTranslator.php @@ -88,7 +88,7 @@ public function getCatalogue($locale = null) /** * Gets the fallback locales. * - * @return array $locales The fallback locales + * @return array The fallback locales */ public function getFallbackLocales() { diff --git a/src/Symfony/Component/Translation/Tests/Loader/XliffFileLoaderTest.php b/src/Symfony/Component/Translation/Tests/Loader/XliffFileLoaderTest.php index dc9a06bcb8427..29bec0e61227e 100644 --- a/src/Symfony/Component/Translation/Tests/Loader/XliffFileLoaderTest.php +++ b/src/Symfony/Component/Translation/Tests/Loader/XliffFileLoaderTest.php @@ -66,7 +66,7 @@ public function testLoadWithResname() $loader = new XliffFileLoader(); $catalogue = $loader->load(__DIR__.'/../fixtures/resname.xlf', 'en', 'domain1'); - $this->assertEquals(array('foo' => 'bar', 'bar' => 'baz', 'baz' => 'foo'), $catalogue->all('domain1')); + $this->assertEquals(array('foo' => 'bar', 'bar' => 'baz', 'baz' => 'foo', 'qux' => 'qux source'), $catalogue->all('domain1')); } public function testIncompleteResource() diff --git a/src/Symfony/Component/Translation/Tests/fixtures/resname.xlf b/src/Symfony/Component/Translation/Tests/fixtures/resname.xlf index 2df16af942f43..4fa5c0017eff0 100644 --- a/src/Symfony/Component/Translation/Tests/fixtures/resname.xlf +++ b/src/Symfony/Component/Translation/Tests/fixtures/resname.xlf @@ -14,6 +14,9 @@ baz foo + + qux source + diff --git a/src/Symfony/Component/Translation/Translator.php b/src/Symfony/Component/Translation/Translator.php index a3aacd4b16650..5310b16615aa5 100644 --- a/src/Symfony/Component/Translation/Translator.php +++ b/src/Symfony/Component/Translation/Translator.php @@ -181,7 +181,7 @@ public function setFallbackLocales(array $locales) /** * Gets the fallback locales. * - * @return array $locales The fallback locales + * @return array The fallback locales */ public function getFallbackLocales() { diff --git a/src/Symfony/Component/Translation/phpunit.xml.dist b/src/Symfony/Component/Translation/phpunit.xml.dist index 1fafa4691bc1d..21d32461825ce 100644 --- a/src/Symfony/Component/Translation/phpunit.xml.dist +++ b/src/Symfony/Component/Translation/phpunit.xml.dist @@ -1,7 +1,7 @@ @@ -26,6 +27,10 @@ class BicValidator extends ConstraintValidator */ public function validate($value, Constraint $constraint) { + if (!$constraint instanceof Bic) { + throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Bic'); + } + if (null === $value || '' === $value) { return; } diff --git a/src/Symfony/Component/Validator/Constraints/CountValidator.php b/src/Symfony/Component/Validator/Constraints/CountValidator.php index cda0f05669685..45be99678bf75 100644 --- a/src/Symfony/Component/Validator/Constraints/CountValidator.php +++ b/src/Symfony/Component/Validator/Constraints/CountValidator.php @@ -26,6 +26,10 @@ class CountValidator extends ConstraintValidator */ public function validate($value, Constraint $constraint) { + if (!$constraint instanceof Count) { + throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Count'); + } + if (null === $value) { return; } diff --git a/src/Symfony/Component/Validator/Constraints/UuidValidator.php b/src/Symfony/Component/Validator/Constraints/UuidValidator.php index 2c8e44a10c1ad..8e2e5da334737 100644 --- a/src/Symfony/Component/Validator/Constraints/UuidValidator.php +++ b/src/Symfony/Component/Validator/Constraints/UuidValidator.php @@ -83,14 +83,14 @@ class UuidValidator extends ConstraintValidator */ public function validate($value, Constraint $constraint) { - if (null === $value || '' === $value) { - return; - } - if (!$constraint instanceof Uuid) { throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Uuid'); } + if (null === $value || '' === $value) { + return; + } + if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { throw new UnexpectedTypeException($value, 'string'); } diff --git a/src/Symfony/Component/Validator/phpunit.xml.dist b/src/Symfony/Component/Validator/phpunit.xml.dist index 0e82129d79d8e..5d07c4e64803d 100644 --- a/src/Symfony/Component/Validator/phpunit.xml.dist +++ b/src/Symfony/Component/Validator/phpunit.xml.dist @@ -1,7 +1,7 @@