Merge branch '2.3'

* 2.3:
  Clear lazy loading initializer after the service is successfully initialized
  [FrameworkBundle] added support for double-quoted strings in the extractor (closes #8797)
  [SecurityBundle] Move format-dependent tests from SecurityExtensionTest
  bumped Symfony version to 2.3.5-DEV
  updated VERSION for 2.3.4
  updated CHANGELOG for 2.3.4
  bumped Symfony version to 2.2.7
  updated VERSION for 2.2.6
  update CONTRIBUTORS for 2.2.6
  updated CHANGELOG for 2.2.6
  clearToken exception is thrown at wrong place.
  fix typo in test skipped message
  [Form] Fixed Form::all() signature for PHP 5.3.3
  [Form] Fixed Form::all() signature for PHP 5.3.3
  [Locale] Fixed: Locale::setDefault() throws no exception when "en" is passed
  [Locale] Fixed: StubLocale::setDefault() throws no exception when "en" is passed
  [Translation] Grammar fix
  [Yaml] fixed embedded folded string parsing
  [Validator] fixed Boolean handling in XML constraint mappings (closes #5603)
  [Translation] Fixed regression: When only one rule is passed to transChoice(), this rule should be used

Conflicts:
	src/Symfony/Component/HttpKernel/Kernel.php
This commit is contained in:
Fabien Potencier 2013-08-29 08:54:01 +02:00
commit feff411dfc
43 changed files with 1419 additions and 525 deletions

View File

@ -7,6 +7,41 @@ in 2.2 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.2.0...v2.2.1
* 2.2.6 (2013-08-26)
* f936b41: clearToken exception is thrown at wrong place.
* d0faf55: [Locale] Fixed: StubLocale::setDefault() throws no exception when "en" is passed
* 566d79c: [Yaml] fixed embedded folded string parsing
* 0951b8d: [Translation] Fixed regression: When only one rule is passed to transChoice(), this rule should be used
* 4563f1b: [Yaml] Fix comment containing a colon on a scalar line being parsed as a hash.
* 7e87eb1: fixed request format when forwarding a request
* ccaaedf: [Form] PropertyPathMapper::mapDataToForms() *always* calls setData() on every child to ensure that all *_DATA events were fired when the initialization phase is over (except for virtual forms)
* 00bc270: [Form] Fixed: submit() reacts to dynamic modifications of the form children
* 05fdb12: Fixed issue #6932 - Inconsistent locale handling in subrequests
* b3c3159: fixed locale of sub-requests when explicitely set by the developer (refs #8821)
* b72bc0b: [Locale] fixed build-data exit code in case of an error
* 9bb7a3d: fixed request format of sub-requests when explicitely set by the developer (closes #8787)
* fa35597: Sets _format attribute only if it wasn't set previously by the user.
* f946108: fixed the format of the request used to render an exception
* 51022c3: Fix typo in the check_path validator
* 5f7219e: added a missing use statement (closes #8808)
* 262879d: fix for Process:isSuccessful()
* 0723c10: [Process] Use a consistent way to reset data of the process latest run
* 85a9c9d: [HttpFoundation] Fixed removing a nonexisting namespaced attribute.
* 191d320: [Validation] Fixed IdentityTranslator to pass correct Locale to MessageSelector
* c6ecd83: SwiftMailerHandler in Monolog bridge now able to react to kernel.terminate event
* 99adcf1: {HttpFoundation] [Session] fixed session compatibility with memcached/redis session storage
* ab9a96b: Fixes for hasParameterOption and getParameterOption methods of ArgvInput
* dbd0855: Added sleep() workaround for windows php rename bug
* fa769a2: [Process] Add more precision to Process::stop timeout
* 3ef517b: [Process] Fix #8739
* 18896d5a: [Validator] fixed the wrong isAbstract() check against the class (fixed #8589)
* e8e76ec: [TwigBridge] Prevent code extension to display warning
* 1a73b44: added missing support for the new output API in PHP 5.4+
* e0c7d3d: Fixed bug introduced in #8675
* 0b965fb: made the filesystem loader compatible with Twig 2.0
* 322f880: replaced deprecated Twig features
* 2.2.5 (2013-08-07)
* c35cc5b: added trusted hosts check

View File

@ -7,6 +7,55 @@ 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.4 (2013-08-27)
* f936b41: clearToken exception is thrown at wrong place.
* ea480bd: [Form] Fixed Form::all() signature for PHP 5.3.3
* e1f40f2: [Locale] Fixed: Locale::setDefault() throws no exception when "en" is passed
* d0faf55: [Locale] Fixed: StubLocale::setDefault() throws no exception when "en" is passed
* 566d79c: [Yaml] fixed embedded folded string parsing
* 33b0a17: [Validator] fixed Boolean handling in XML constraint mappings (closes #5603)
* 0951b8d: [Translation] Fixed regression: When only one rule is passed to transChoice(), this rule should be used
* 4563f1b: [Yaml] Fix comment containing a colon on a scalar line being parsed as a hash.
* 7e87eb1: fixed request format when forwarding a request
* 07d14e5: [Form] Removed exception in Button::setData(): setData() is now always called for all elements in the form tree during the initialization of the tree
* ccaaedf: [Form] PropertyPathMapper::mapDataToForms() *always* calls setData() on every child to ensure that all *_DATA events were fired when the initialization phase is over (except for virtual forms)
* 00bc270: [Form] Fixed: submit() reacts to dynamic modifications of the form children
* c4636e1: added a functional test for locale handling in sub-requests
* 05fdb12: Fixed issue #6932 - Inconsistent locale handling in subrequests
* b3c3159: fixed locale of sub-requests when explicitely set by the developer (refs #8821)
* 9bb7a3d: fixed request format of sub-requests when explicitely set by the developer (closes #8787)
* fa35597: Sets _format attribute only if it wasn't set previously by the user.
* f946108: fixed the format of the request used to render an exception
* 51022c3: Fix typo in the check_path validator
* 5f7219e: added a missing use statement (closes #8808)
* 262879d: fix for Process:isSuccessful()
* 0723c10: [Process] Use a consistent way to reset data of the process latest run
* 85a9c9d: [HttpFoundation] Fixed removing a nonexisting namespaced attribute.
* 191d320: [Validation] Fixed IdentityTranslator to pass correct Locale to MessageSelector
* c6ecd83: SwiftMailerHandler in Monolog bridge now able to react to kernel.terminate event
* 99adcf1: {HttpFoundation] [Session] fixed session compatibility with memcached/redis session storage
* ab9a96b: Fixes for hasParameterOption and getParameterOption methods of ArgvInput
* dbd0855: Added sleep() workaround for windows php rename bug
* c342715: [Form] Fixed: Added "validation_groups" option to submit button
* fa01e6b: [Process] Fix for #8754 (Timed-out processes are successful)
* 909fab6: [Process] Fix #8742 : Signal-terminated processes are not successful
* fa769a2: [Process] Add more precision to Process::stop timeout
* 3ef517b: [Process] Fix #8739
* 572ba68: [TwigBridge] removed superflous ; when rendering form_enctype() (closes #8660)
* 18896d5a: [Validator] fixed the wrong isAbstract() check against the class (fixed #8589)
* e8e76ec: [TwigBridge] Prevent code extension to display warning
* 96aec0f: Fix internal sub-request creation
* 6ed0fdf: [Form] Moved auto_initialize option to the BaseType
* e47657d: Make sure ContextErrorException is loaded during compile time errors
* 98f6969: Fix empty process argument escaping on Windows
* 1a73b44: added missing support for the new output API in PHP 5.4+
* e0c7d3d: Fixed bug introduced in #8675
* 0b965fb: made the filesystem loader compatible with Twig 2.0
* 8fa0453: [Intl] Updated stubs to reflect ICU 51.2
* 322f880: replaced deprecated Twig features
* 48338fc: Ignore null value in comparison validators
* 2.3.3 (2013-08-07)
* c35cc5b: added trusted hosts check

View File

@ -23,10 +23,11 @@ Symfony2 is the result of the work of many people who made the code better
- Benjamin Eberlei (beberlei)
- Jakub Zalas (jakubzalas)
- Hugo Hamon (hhamon)
- Eriksen Costa (eriksencosta)
- Martin Hasoň (hason)
- Jonathan Wage (jwage)
- Eriksen Costa (eriksencosta)
- William Durand (couac)
- Jonathan Wage (jwage)
- Romain Neutron (romain)
- Alexandre Salomé (alexandresalome)
- ornicar
- stealth35 (stealth35)
@ -36,7 +37,6 @@ Symfony2 is the result of the work of many people who made the code better
- Henrik Bjørnskov (henrikbjorn)
- Miha Vrhovnik
- Bilal Amarni (bamarni)
- Romain Neutron (romain)
- Florin Patan (florinpatan)
- Konstantin Kudryashov (everzet)
- Saša Stamenković (umpirsky)
@ -44,13 +44,13 @@ Symfony2 is the result of the work of many people who made the code better
- Eric Clemmons (ericclemmons)
- Dariusz Górecki (canni)
- Henrik Westphal (snc)
- Grégoire Pineau (lyrixx)
- Deni
- Marc Weistroff (futurecat)
- Jordan Alliot (jalliot)
- Arnout Boks (aboks)
- Hidenori Goto (hidenorigoto)
- Fran Moreno (franmomu)
- Grégoire Pineau (lyrixx)
- Andrej Hudec (pulzarraider)
- Lee McDermott
- Brandon Turner
@ -69,8 +69,8 @@ Symfony2 is the result of the work of many people who made the code better
- lenar
- Fabien Pennequin (fabienpennequin)
- excelwebzone
- woodspire
- Douglas Greenshields (shieldo)
- woodspire
- Mario A. Alvarez Garcia (nomack84)
- Kevin Bond (kbond)
- Richard Miller (mr_r_miller)
@ -91,27 +91,27 @@ Symfony2 is the result of the work of many people who made the code better
- Larry Garfield (crell)
- Arnaud Kleinpeter (nanocom)
- Jonathan Ingram (jonathaningram)
- Wouter De Jong (wouterj)
- Sebastiaan Stok (sstok)
- Helmer Aaviksoo
- Javier Eguiluz (javier.eguiluz)
- Matthieu Ouellette-Vachon (maoueh)
- Amal Raghav (kertz)
- Artur Kotyrba
- Pablo Godel (pgodel)
- Helmer Aaviksoo
- Dmitrii Chekaliuk (lazyhammer)
- Clément JOBEILI (dator)
- Hiromi Hishida (77web)
- Julien Brochet (mewt)
- Rafael Dohms (rdohms)
- Wouter De Jong (wouterj)
- Dennis Benkert (denderello)
- Benjamin Dulau (dbenjamin)
- Andreas Hucks (meandmymonkey)
- Noel Guilbert (noel)
- Dmitrii Chekaliuk (lazyhammer)
- Dorian Villet (gnutix)
- Guilherme Blanco (guilhermeblanco)
- Martin Schuhfuß (usefulthink)
- Thomas Rabaix (rande)
- Dennis Benkert (denderello)
- Marcel Beerta (mazen)
- Albert Casademont (acasademont)
- Matthieu Bontemps (mbontemps)
@ -194,9 +194,12 @@ Symfony2 is the result of the work of many people who made the code better
- Peter Kruithof (pkruithof)
- Kristen Gilden (kgilden)
- hossein zolfi (ocean)
- Philipp Kräutli (pkraeutli)
- Christian Flothmann (xabbuh)
- Greg Thornton (xdissent)
- Atsuhiro KUBO (iteman)
- Lars Strojny
- Loïc Chardonnet (gnusat)
- Costin Bereveanu (schniper)
- Markus Lanthaler (lanthaler)
- Jérôme Vieilledent (lolautruche)
@ -262,6 +265,7 @@ Symfony2 is the result of the work of many people who made the code better
- mcben
- Maks Slesarenko
- Vicent Soria Durá (vicentgodella)
- Andrew Udvare
- alexpods
- Erik Trapman (eriktrapman)
- De Cock Xavier (xdecock)
@ -274,7 +278,6 @@ Symfony2 is the result of the work of many people who made the code better
- Luis Cordova (cordoval)
- Michaël Perrin (michael.perrin)
- sasezaki
- Loïc Chardonnet (gnusat)
- Xavier HAUSHERR
- Steven Surowiec
- Marek Kalnik (marekkalnik)
@ -339,13 +342,14 @@ Symfony2 is the result of the work of many people who made the code better
- Johannes Klauss (cloppy)
- fzerorubigd
- develop
- Atsuhiro KUBO (iteman)
- Tomasz Kowalczyk (thunderer)
- Samy Dindane (dinduks)
- yclian
- Pascal Helfenstein
- Matt Daum (daum)
- Baldur Rensch
- Alex Xandra Albert Sim
- Gabor Toth (tgabi333)
- Yuen-Chi Lian
- Joshua Nye
- avorobiev
@ -363,8 +367,8 @@ Symfony2 is the result of the work of many people who made the code better
- Christian Soronellas Vallespí (theunic)
- Benjamin Grandfond (benjamin)
- Degory Valentine
- hacfi (hifi)
- Krzysiek Łabuś
- Andrew Udvare
- Xavier Lacot (xavier)
- Olivier Maisonneuve
- Iwan van Staveren (istaveren)
@ -381,7 +385,6 @@ Symfony2 is the result of the work of many people who made the code better
- Reen Lokum
- Pierre Vanliefland (pvanliefland)
- Martin Parsiegla (spea)
- Philipp Kräutli (pkraeutli)
- Stefano Sala (stefano.sala)
- frost-nzcr4
- Abhoryo
@ -398,6 +401,7 @@ Symfony2 is the result of the work of many people who made the code better
- Gustavo Falco (gfalco)
- Matt Robinson (inanimatt)
- Bob den Otter (bopp)
- David Marín Carreño (davefx)
- Jörn Lang (j.lang)
- julien pauli (jpauli)
- mwsaz
@ -408,6 +412,7 @@ Symfony2 is the result of the work of many people who made the code better
- Derek ROTH
- Shin Ohno (ganchiku)
- Drew Butler (nodrew)
- Christian Morgan
- Alexander Miehe (engerim)
- Titouan Galopin (tgalopin)
- Don Pinkster
@ -473,6 +478,7 @@ Symfony2 is the result of the work of many people who made the code better
- Dan Finnie
- Martijn Evers
- Benjamin Paap (benjaminpaap)
- Christian
- Sergii Smertin (nfx)
- Eddie Jaoude
- Nerijus Arlauskas
@ -485,7 +491,6 @@ Symfony2 is the result of the work of many people who made the code better
- matteo giachino
- Daniel Mecke (daniel_mecke)
- Alex Demchenko (pilot)
- Tomasz Kowalczyk (thunderer)
- Vincent AUBERT (vincent)
- Benoit Garret
- DerManoMann
@ -501,6 +506,7 @@ Symfony2 is the result of the work of many people who made the code better
- Neil Katin
- peter
- Gustavo Adrian
- Nikita Konstantinov
- Brooks Boyd
- Roger Webb
- Nicolas Fabre (nfabre)
@ -525,15 +531,18 @@ Symfony2 is the result of the work of many people who made the code better
- Sebastian Göttschkes (sgoettschkes)
- erikaheidi
- Pierre Tachoire
- marcj
- Ludek Stepan
- Balázs Benyó (duplabe)
- Marc Morera (mmoreram)
- Daniel Wehner
- Saem Ghani
- Sebastian Utz
- Keri Henare (kerihenare)
- Cédric Lahouste (rapotor)
- Anthony Ferrara
- Janusz Jablonski
- ShiraNai7
- George Giannoulopoulos
- Chris Wilkinson (thewilkybarkid)
- Ilya Biryukov
@ -541,13 +550,13 @@ Symfony2 is the result of the work of many people who made the code better
- m.chwedziak
- Lance McNearney
- Alberto Pirovano (geezmo)
- Philipp W (hifi)
- Gabor Toth (tgabi333)
- Martin Pärtel
- Xavier Briand (xavierbriand)
- Evan Kaufman
- Romain Geissler
- Charles Sarrazin (csarrazi)
- Marcus Stöhr (dafish)
- Emmanuel Vella (emmanuel.vella)
- Carsten Nielsen (phreaknerd)
- Jay Severson
- René Kerner
@ -580,6 +589,7 @@ Symfony2 is the result of the work of many people who made the code better
- jskvara
- Mephistofeles
- Hoffmann András
- Olivier
- pscheit
- Ramon Kleiss (akathos)
- Nicolas Badey (nico-b)
@ -602,6 +612,7 @@ Symfony2 is the result of the work of many people who made the code better
- Andrey Esaulov (andremaha)
- hicham ELGUAROUANI (hiiimoo)
- Paul Seiffert (seiffert)
- Vasily (sirian)
- Stefan Koopmanschap (skoop)
- Ivan Kurnosov
- stloyd
@ -672,7 +683,6 @@ Symfony2 is the result of the work of many people who made the code better
- cyrillej
- Alex Pods
- timaschew
- Christian Morgan
- Ian Phillips
- Haritz
- Grummfy
@ -696,7 +706,6 @@ Symfony2 is the result of the work of many people who made the code better
- Rafał
- Masao Maeda (brtriver)
- Dave Marshall (davedevelopment)
- David Marín Carreño (davefx)
- Denis Klementjev (dklementjev)
- Kévin Dunglas (dunglas)
- Vincent Composieux (eko)
@ -737,11 +746,14 @@ Symfony2 is the result of the work of many people who made the code better
- Christian Eikermann
- Antonio Angelino
- Vladimir Sazhin
- Jáchym Toušek
- Vyacheslav Slinko
- Johannes
- Jörg Rühl
- patrick-mcdougle
- Daniel Basten
- Giacomo Gallico
- Steve Müller
- andreabreu98
- Michael Schneider
- Jerome Tamarelle
@ -787,6 +799,7 @@ Symfony2 is the result of the work of many people who made the code better
- Besnik Br
- sualko
- Nicolas Roudaire
- Lee Rowlands
- Alex Olmos (alexolmos)
- Juan Ases García (ases)
- Bernd Matzner (bmatzner)
@ -802,10 +815,12 @@ Symfony2 is the result of the work of many people who made the code better
- Yohan Giarelli (frequence-web)
- Massimiliano Arione (garak)
- Vladislav Krupenkin (ideea)
- Jaik Dean (jaikdean)
- joris de wit (jdewit)
- Jérémy CROMBEZ (jeremy)
- Jorge Maiden (jorgemaiden)
- Justin Rainbow (jrainbow)
- JuntaTom (juntatom)
- Sébastien Armand (khepin)
- Krzysztof Menżyk (krymen)
- Martin Ledgard (le6o)
@ -819,8 +834,10 @@ Symfony2 is the result of the work of many people who made the code better
- Rich Sage (richsage)
- Ruud Kamphuis (ruudk)
- Sarah Khalil (saro0h)
- scourgen hung (scourgen)
- Sebastian Busch (sebu)
- Andrea Giuliano (shark)
- Julien Sanchez (sumbobyboys)
- Markus Tacker (tacker)
- Tyler Stroud (tystr)
- Víctor Mateo (victormateo)

View File

@ -51,10 +51,10 @@ class RuntimeInstantiator implements InstantiatorInterface
return $this->factory->createProxy(
$definition->getClass(),
function (&$wrappedInstance, LazyLoadingInterface $proxy) use ($realInstantiator) {
$proxy->setProxyInitializer(null);
$wrappedInstance = call_user_func($realInstantiator);
$proxy->setProxyInitializer(null);
return true;
}
);

View File

@ -75,10 +75,10 @@ class ProxyDumper implements DumperInterface
$instantiation new $proxyClass(
function (&\$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface \$proxy) use (\$container) {
\$proxy->setProxyInitializer(null);
\$wrappedInstance = \$container->$methodName(false);
\$proxy->setProxyInitializer(null);
return true;
}
);

View File

@ -50,10 +50,10 @@ class LazyServiceProjectServiceContainer extends Container
return $this->services['foo'] = new stdClass_c1d194250ee2e2b7d2eab8b8212368a8(
function (& $wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($container) {
$proxy->setProxyInitializer(null);
$wrappedInstance = $container->getFooService(false);
$proxy->setProxyInitializer(null);
return true;
}
);

View File

@ -10,10 +10,10 @@ class ProjectServiceContainer extends Container
return $this->services['foo'] = new stdClass_%s(
function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($container) {
$proxy->setProxyInitializer(null);
$wrappedInstance = $container->getFooService(false);
$proxy->setProxyInitializer(null);
return true;
}
);

View File

@ -74,7 +74,7 @@ class ProxyDumperTest extends \PHPUnit_Framework_TestCase
'%wif ($lazyLoad) {%w$container = $this;%wreturn $this->services[\'foo\'] = new '
. 'SymfonyBridgeProxyManagerLazyProxyTestsInstantiatorProxyDumperTest_%s(%wfunction '
. '(&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($container) {'
. '%w$proxy->setProxyInitializer(null);%w$wrappedInstance = $container->getFooService(false);'
. '%w$wrappedInstance = $container->getFooService(false);%w$proxy->setProxyInitializer(null);'
. '%wreturn true;%w}%w);%w}%w',
$code
);

View File

@ -1,2 +1,3 @@
This template is used for translation message extraction tests
<?php echo $view['translator']->trans('new key') ?>
<?php echo $view['translator']->trans('single-quoted key') ?>
<?php echo $view['translator']->trans("double-quoted key") ?>

View File

@ -28,8 +28,9 @@ class PhpExtractorTest extends TestCase
$extractor->extract(__DIR__.'/../Fixtures/Resources/views/', $catalogue);
// Assert
$this->assertCount(1, $catalogue->all('messages'), '->extract() should find 1 translation');
$this->assertTrue($catalogue->has('new key'), '->extract() should find at leat "new key" message');
$this->assertEquals('prefixnew key', $catalogue->get('new key'), '->extract() should apply "prefix" as prefix');
$this->assertCount(2, $catalogue->all('messages'), '->extract() should find 1 translation');
$this->assertTrue($catalogue->has('single-quoted key'), '->extract() should find the "single-quoted key" message');
$this->assertTrue($catalogue->has('double-quoted key'), '->extract() should find the "double-quoted key" message');
$this->assertEquals('prefixsingle-quoted key', $catalogue->get('single-quoted key'), '->extract() should apply "prefix" as prefix');
}
}

View File

@ -111,7 +111,7 @@ class PhpExtractor implements ExtractorInterface
}
}
$message = trim($message, '\'');
$message = trim($message, '\'"');
if ($message) {
$catalog->set($message, $this->prefix.$message);

View File

@ -13,6 +13,7 @@ namespace Symfony\Bundle\SecurityBundle\DependencyInjection;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\UserProviderFactoryInterface;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\DependencyInjection\Alias;
@ -413,7 +414,7 @@ class SecurityExtension extends Extension
}
if (false === $hasListeners) {
throw new \LogicException(sprintf('No authentication listener registered for firewall "%s".', $id));
throw new InvalidConfigurationException(sprintf('No authentication listener registered for firewall "%s".', $id));
}
return array($listeners, $defaultEntryPoint);

View File

@ -0,0 +1,200 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\Tests\DependencyInjection;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Bundle\SecurityBundle\SecurityBundle;
use Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
abstract class CompleteConfigurationTest extends \PHPUnit_Framework_TestCase
{
abstract protected function loadFromFile(ContainerBuilder $container, $file);
public function testRolesHierarchy()
{
$container = $this->getContainer('container1');
$this->assertEquals(array(
'ROLE_ADMIN' => array('ROLE_USER'),
'ROLE_SUPER_ADMIN' => array('ROLE_USER', 'ROLE_ADMIN', 'ROLE_ALLOWED_TO_SWITCH'),
'ROLE_REMOTE' => array('ROLE_USER', 'ROLE_ADMIN'),
), $container->getParameter('security.role_hierarchy.roles'));
}
public function testUserProviders()
{
$container = $this->getContainer('container1');
$providers = array_values(array_filter($container->getServiceIds(), function ($key) { return 0 === strpos($key, 'security.user.provider.concrete'); }));
$expectedProviders = array(
'security.user.provider.concrete.default',
'security.user.provider.concrete.default_foo',
'security.user.provider.concrete.digest',
'security.user.provider.concrete.digest_foo',
'security.user.provider.concrete.basic',
'security.user.provider.concrete.basic_foo',
'security.user.provider.concrete.basic_bar',
'security.user.provider.concrete.service',
'security.user.provider.concrete.chain',
);
$this->assertEquals(array(), array_diff($expectedProviders, $providers));
$this->assertEquals(array(), array_diff($providers, $expectedProviders));
// chain provider
$this->assertEquals(array(array(
new Reference('security.user.provider.concrete.service'),
new Reference('security.user.provider.concrete.basic'),
)), $container->getDefinition('security.user.provider.concrete.chain')->getArguments());
}
public function testFirewalls()
{
$container = $this->getContainer('container1');
$arguments = $container->getDefinition('security.firewall.map')->getArguments();
$listeners = array();
foreach (array_keys($arguments[1]) as $contextId) {
$contextDef = $container->getDefinition($contextId);
$arguments = $contextDef->getArguments();
$listeners[] = array_map(function ($ref) { return (string) $ref; }, $arguments['index_0']);
}
$this->assertEquals(array(
array(),
array(
'security.channel_listener',
'security.logout_listener.secure',
'security.authentication.listener.x509.secure',
'security.authentication.listener.form.secure',
'security.authentication.listener.basic.secure',
'security.authentication.listener.digest.secure',
'security.authentication.listener.anonymous.secure',
'security.access_listener',
'security.authentication.switchuser_listener.secure',
),
), $listeners);
}
public function testAccess()
{
$container = $this->getContainer('container1');
$rules = array();
foreach ($container->getDefinition('security.access_map')->getMethodCalls() as $call) {
if ($call[0] == 'add') {
$rules[] = array((string) $call[1][0], $call[1][1], $call[1][2]);
}
}
$matcherIds = array();
foreach ($rules as $rule) {
list($matcherId, $roles, $channel) = $rule;
$requestMatcher = $container->getDefinition($matcherId);
$this->assertFalse(isset($matcherIds[$matcherId]));
$matcherIds[$matcherId] = true;
$i = count($matcherIds);
if (1 === $i) {
$this->assertEquals(array('ROLE_USER'), $roles);
$this->assertEquals('https', $channel);
$this->assertEquals(
array('/blog/524', null, array('GET', 'POST')),
$requestMatcher->getArguments()
);
} elseif (2 === $i) {
$this->assertEquals(array('IS_AUTHENTICATED_ANONYMOUSLY'), $roles);
$this->assertNull($channel);
$this->assertEquals(
array('/blog/.*'),
$requestMatcher->getArguments()
);
}
}
}
public function testMerge()
{
$container = $this->getContainer('merge');
$this->assertEquals(array(
'FOO' => array('MOO'),
'ADMIN' => array('USER'),
), $container->getParameter('security.role_hierarchy.roles'));
}
public function testEncoders()
{
$container = $this->getContainer('container1');
$this->assertEquals(array(array(
'JMS\FooBundle\Entity\User1' => array(
'class' => new Parameter('security.encoder.plain.class'),
'arguments' => array(false),
),
'JMS\FooBundle\Entity\User2' => array(
'class' => new Parameter('security.encoder.digest.class'),
'arguments' => array('sha1', false, 5),
),
'JMS\FooBundle\Entity\User3' => array(
'class' => new Parameter('security.encoder.digest.class'),
'arguments' => array('md5', true, 5000),
),
'JMS\FooBundle\Entity\User4' => new Reference('security.encoder.foo'),
'JMS\FooBundle\Entity\User5' => array(
'class' => new Parameter('security.encoder.pbkdf2.class'),
'arguments' => array('sha1', false, 5, 30),
),
'JMS\FooBundle\Entity\User6' => array(
'class' => new Parameter('security.encoder.bcrypt.class'),
'arguments' => array(15),
),
)), $container->getDefinition('security.encoder_factory.generic')->getArguments());
}
public function testAcl()
{
$container = $this->getContainer('container1');
$this->assertTrue($container->hasDefinition('security.acl.dbal.provider'));
$this->assertEquals('security.acl.dbal.provider', (string) $container->getAlias('security.acl.provider'));
}
public function testCustomAclProvider()
{
$container = $this->getContainer('custom_acl_provider');
$this->assertFalse($container->hasDefinition('security.acl.dbal.provider'));
$this->assertEquals('foo', (string) $container->getAlias('security.acl.provider'));
}
protected function getContainer($file)
{
$container = new ContainerBuilder();
$security = new SecurityExtension();
$container->registerExtension($security);
$bundle = new SecurityBundle();
$bundle->build($container); // Attach all default factories
$this->loadFromFile($container, $file);
$container->getCompilerPassConfig()->setOptimizationPasses(array());
$container->getCompilerPassConfig()->setRemovingPasses(array());
$container->compile();
return $container;
}
}

View File

@ -1,16 +0,0 @@
<?php
$container->loadFromExtension('security', array(
'providers' => array(
'default' => array('id' => 'foo'),
),
'firewalls' => array(
'some_firewall' => array(
'pattern' => '/secured_area/.*',
'form_login' => array(
'check_path' => '/some_area/login_check',
)
)
)
));

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<srv:container xmlns="http://symfony.com/schema/dic/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:srv="http://symfony.com/schema/dic/services"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<config>
<provider name="default" id="foo" />
<firewall name="some_firewall" pattern="/secured_area/.*">
<form-login check-path="/some_area/login_check" />
</firewall>
</config>
</srv:container>

View File

@ -1,9 +0,0 @@
security:
providers:
default: { id: foo }
firewalls:
some_firewall:
pattern: /secured_area/.*
form_login:
check_path: /some_area/login_check

View File

@ -14,7 +14,7 @@ namespace Symfony\Bundle\SecurityBundle\Tests\DependencyInjection;
use Symfony\Bundle\SecurityBundle\DependencyInjection\MainConfiguration;
use Symfony\Component\Config\Definition\Processor;
class ConfigurationTest extends \PHPUnit_Framework_TestCase
class MainConfigurationTest extends \PHPUnit_Framework_TestCase
{
/**
* The minimal, required config needed to not have any required validation

View File

@ -15,7 +15,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
use Symfony\Component\Config\FileLocator;
class PhpSecurityExtensionTest extends SecurityExtensionTest
class PhpCompleteConfigurationTest extends CompleteConfigurationTest
{
protected function loadFromFile(ContainerBuilder $container, $file)
{

View File

@ -11,199 +11,81 @@
namespace Symfony\Bundle\SecurityBundle\Tests\DependencyInjection;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Bundle\SecurityBundle\SecurityBundle;
use Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension;
use Symfony\Bundle\SecurityBundle\SecurityBundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
abstract class SecurityExtensionTest extends \PHPUnit_Framework_TestCase
class SecurityExtensionTest extends \PHPUnit_Framework_TestCase
{
abstract protected function loadFromFile(ContainerBuilder $container, $file);
public function testRolesHierarchy()
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
* @expectedExceptionMessage The check_path "/some_area/login_check" for login method "form_login" is not matched by the firewall pattern "/secured_area/.*".
*/
public function testInvalidCheckPath()
{
$container = $this->getContainer('container1');
$this->assertEquals(array(
'ROLE_ADMIN' => array('ROLE_USER'),
'ROLE_SUPER_ADMIN' => array('ROLE_USER', 'ROLE_ADMIN', 'ROLE_ALLOWED_TO_SWITCH'),
'ROLE_REMOTE' => array('ROLE_USER', 'ROLE_ADMIN'),
), $container->getParameter('security.role_hierarchy.roles'));
}
$container = $this->getRawContainer();
public function testUserProviders()
{
$container = $this->getContainer('container1');
$providers = array_values(array_filter($container->getServiceIds(), function ($key) { return 0 === strpos($key, 'security.user.provider.concrete'); }));
$expectedProviders = array(
'security.user.provider.concrete.default',
'security.user.provider.concrete.default_foo',
'security.user.provider.concrete.digest',
'security.user.provider.concrete.digest_foo',
'security.user.provider.concrete.basic',
'security.user.provider.concrete.basic_foo',
'security.user.provider.concrete.basic_bar',
'security.user.provider.concrete.service',
'security.user.provider.concrete.chain',
);
$this->assertEquals(array(), array_diff($expectedProviders, $providers));
$this->assertEquals(array(), array_diff($providers, $expectedProviders));
// chain provider
$this->assertEquals(array(array(
new Reference('security.user.provider.concrete.service'),
new Reference('security.user.provider.concrete.basic'),
)), $container->getDefinition('security.user.provider.concrete.chain')->getArguments());
}
public function testFirewalls()
{
$container = $this->getContainer('container1');
$arguments = $container->getDefinition('security.firewall.map')->getArguments();
$listeners = array();
foreach (array_keys($arguments[1]) as $contextId) {
$contextDef = $container->getDefinition($contextId);
$arguments = $contextDef->getArguments();
$listeners[] = array_map(function ($ref) { return (string) $ref; }, $arguments['index_0']);
}
$this->assertEquals(array(
array(),
array(
'security.channel_listener',
'security.logout_listener.secure',
'security.authentication.listener.x509.secure',
'security.authentication.listener.form.secure',
'security.authentication.listener.basic.secure',
'security.authentication.listener.digest.secure',
'security.authentication.listener.anonymous.secure',
'security.access_listener',
'security.authentication.switchuser_listener.secure',
$container->loadFromExtension('security', array(
'providers' => array(
'default' => array('id' => 'foo'),
),
), $listeners);
}
public function testAccess()
{
$container = $this->getContainer('container1');
'firewalls' => array(
'some_firewall' => array(
'pattern' => '/secured_area/.*',
'form_login' => array(
'check_path' => '/some_area/login_check',
)
)
)
));
$rules = array();
foreach ($container->getDefinition('security.access_map')->getMethodCalls() as $call) {
if ($call[0] == 'add') {
$rules[] = array((string) $call[1][0], $call[1][1], $call[1][2]);
}
}
$matcherIds = array();
foreach ($rules as $rule) {
list($matcherId, $roles, $channel) = $rule;
$requestMatcher = $container->getDefinition($matcherId);
$this->assertFalse(isset($matcherIds[$matcherId]));
$matcherIds[$matcherId] = true;
$i = count($matcherIds);
if (1 === $i) {
$this->assertEquals(array('ROLE_USER'), $roles);
$this->assertEquals('https', $channel);
$this->assertEquals(
array('/blog/524', null, array('GET', 'POST')),
$requestMatcher->getArguments()
);
} elseif (2 === $i) {
$this->assertEquals(array('IS_AUTHENTICATED_ANONYMOUSLY'), $roles);
$this->assertNull($channel);
$this->assertEquals(
array('/blog/.*'),
$requestMatcher->getArguments()
);
}
}
}
public function testMerge()
{
$container = $this->getContainer('merge');
$this->assertEquals(array(
'FOO' => array('MOO'),
'ADMIN' => array('USER'),
), $container->getParameter('security.role_hierarchy.roles'));
}
public function testEncoders()
{
$container = $this->getContainer('container1');
$this->assertEquals(array(array(
'JMS\FooBundle\Entity\User1' => array(
'class' => new Parameter('security.encoder.plain.class'),
'arguments' => array(false),
),
'JMS\FooBundle\Entity\User2' => array(
'class' => new Parameter('security.encoder.digest.class'),
'arguments' => array('sha1', false, 5),
),
'JMS\FooBundle\Entity\User3' => array(
'class' => new Parameter('security.encoder.digest.class'),
'arguments' => array('md5', true, 5000),
),
'JMS\FooBundle\Entity\User4' => new Reference('security.encoder.foo'),
'JMS\FooBundle\Entity\User5' => array(
'class' => new Parameter('security.encoder.pbkdf2.class'),
'arguments' => array('sha1', false, 5, 30),
),
'JMS\FooBundle\Entity\User6' => array(
'class' => new Parameter('security.encoder.bcrypt.class'),
'arguments' => array(15),
),
)), $container->getDefinition('security.encoder_factory.generic')->getArguments());
}
public function testAcl()
{
$container = $this->getContainer('container1');
$this->assertTrue($container->hasDefinition('security.acl.dbal.provider'));
$this->assertEquals('security.acl.dbal.provider', (string) $container->getAlias('security.acl.provider'));
}
public function testCustomAclProvider()
{
$container = $this->getContainer('custom_acl_provider');
$this->assertFalse($container->hasDefinition('security.acl.dbal.provider'));
$this->assertEquals('foo', (string) $container->getAlias('security.acl.provider'));
$container->compile();
}
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
* @expectedExceptionMessage not matched by the firewall pattern
* @expectedExceptionMessage No authentication listener registered for firewall "some_firewall"
*/
public function testInvalidCheckPath()
public function testFirewallWithoutAuthenticationListener()
{
$container = $this->getContainer('invalid_check_path');
$container = $this->getRawContainer();
$container->loadFromExtension('security', array(
'providers' => array(
'default' => array('id' => 'foo'),
),
'firewalls' => array(
'some_firewall' => array(
'pattern' => '/.*',
)
)
));
$container->compile();
}
protected function getContainer($file)
protected function getRawContainer()
{
$container = new ContainerBuilder();
$security = new SecurityExtension();
$container->registerExtension($security);
$bundle = new SecurityBundle();
$bundle->build($container); // Attach all default factories
$this->loadFromFile($container, $file);
$bundle->build($container);
$container->getCompilerPassConfig()->setOptimizationPasses(array());
$container->getCompilerPassConfig()->setRemovingPasses(array());
$container->compile();
return $container;
}
protected function getContainer()
{
$containter = $this->getRawContainer();
$containter->compile();
return $containter;
}
}

View File

@ -15,7 +15,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\Config\FileLocator;
class XmlSecurityExtensionTest extends SecurityExtensionTest
class XmlCompleteConfigurationTest extends CompleteConfigurationTest
{
protected function loadFromFile(ContainerBuilder $container, $file)
{

View File

@ -15,7 +15,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\Config\FileLocator;
class YamlSecurityExtensionTest extends SecurityExtensionTest
class YamlCompleteConfigurationTest extends CompleteConfigurationTest
{
protected function loadFromFile(ContainerBuilder $container, $file)
{

View File

@ -167,7 +167,7 @@ class ViolationMapper implements ViolationMapperInterface
// Skip forms inheriting their parent data when iterating the children
$childIterator = new \RecursiveIteratorIterator(
new InheritDataAwareIterator($form->all())
new InheritDataAwareIterator($form)
);
// Make the path longer until we find a matching child

View File

@ -19,6 +19,7 @@ use Symfony\Component\Form\Exception\LogicException;
use Symfony\Component\Form\Exception\OutOfBoundsException;
use Symfony\Component\Form\Util\FormUtil;
use Symfony\Component\Form\Util\InheritDataAwareIterator;
use Symfony\Component\Form\Util\OrderedHashMap;
use Symfony\Component\PropertyAccess\PropertyPath;
/**
@ -73,9 +74,9 @@ class Form implements \IteratorAggregate, FormInterface
/**
* The children of this form
* @var FormInterface[] An array of FormInterface instances
* @var FormInterface[] A map of FormInterface instances
*/
private $children = array();
private $children;
/**
* The errors of this form
@ -164,6 +165,7 @@ class Form implements \IteratorAggregate, FormInterface
}
$this->config = $config;
$this->children = new OrderedHashMap();
}
public function __clone()
@ -370,9 +372,9 @@ class Form implements \IteratorAggregate, FormInterface
// even if the form is compound.
if (count($this->children) > 0) {
// Update child forms from the data
$childrenIterator = new InheritDataAwareIterator($this->children);
$childrenIterator = new \RecursiveIteratorIterator($childrenIterator);
$this->config->getDataMapper()->mapDataToForms($viewData, $childrenIterator);
$iterator = new InheritDataAwareIterator($this->children);
$iterator = new \RecursiveIteratorIterator($iterator);
$this->config->getDataMapper()->mapDataToForms($viewData, $iterator);
}
if ($dispatcher->hasListeners(FormEvents::POST_SET_DATA)) {
@ -536,10 +538,7 @@ class Form implements \IteratorAggregate, FormInterface
$submittedData = array();
}
for (reset($this->children); false !== current($this->children); next($this->children)) {
$child = current($this->children);
$name = key($this->children);
foreach ($this->children as $name => $child) {
if (array_key_exists($name, $submittedData) || $clearMissing) {
$child->submit(isset($submittedData[$name]) ? $submittedData[$name] : null, $clearMissing);
unset($submittedData[$name]);
@ -587,9 +586,9 @@ class Form implements \IteratorAggregate, FormInterface
// descendants that inherit this form's data.
// These descendants will not be submitted normally (see the check
// for $this->config->getInheritData() above)
$childrenIterator = new InheritDataAwareIterator($this->children);
$childrenIterator = new \RecursiveIteratorIterator($childrenIterator);
$this->config->getDataMapper()->mapFormsToData($childrenIterator, $viewData);
$iterator = new InheritDataAwareIterator($this->children);
$iterator = new \RecursiveIteratorIterator($iterator);
$this->config->getDataMapper()->mapFormsToData($iterator, $viewData);
}
$modelData = null;
@ -765,9 +764,9 @@ class Form implements \IteratorAggregate, FormInterface
/**
* {@inheritdoc}
*/
public function &all()
public function all()
{
return $this->children;
return iterator_to_array($this->children);
}
/**
@ -836,10 +835,9 @@ class Form implements \IteratorAggregate, FormInterface
$child->setParent($this);
if (!$this->lockSetData && $this->defaultDataSet && !$this->config->getInheritData()) {
$children = array($child);
$childrenIterator = new InheritDataAwareIterator($children);
$childrenIterator = new \RecursiveIteratorIterator($childrenIterator);
$this->config->getDataMapper()->mapDataToForms($viewData, $childrenIterator);
$iterator = new InheritDataAwareIterator(new \ArrayIterator(array($child)));
$iterator = new \RecursiveIteratorIterator($iterator);
$this->config->getDataMapper()->mapDataToForms($viewData, $iterator);
}
return $this;
@ -940,11 +938,11 @@ class Form implements \IteratorAggregate, FormInterface
/**
* Returns the iterator for this group.
*
* @return \ArrayIterator
* @return \Iterator
*/
public function getIterator()
{
return new \ArrayIterator($this->children);
return $this->children;
}
/**

View File

@ -1,122 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Tests\Util;
use Symfony\Component\Form\Util\InheritDataAwareIterator;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class InheritDataAwareIteratorTest extends \PHPUnit_Framework_TestCase
{
public function testSupportDynamicModification()
{
$form = $this->getMockForm('form');
$formToBeAdded = $this->getMockForm('added');
$formToBeRemoved = $this->getMockForm('removed');
$forms = array('form' => $form, 'removed' => $formToBeRemoved);
$iterator = new InheritDataAwareIterator($forms);
$iterator->rewind();
$this->assertTrue($iterator->valid());
$this->assertSame('form', $iterator->key());
$this->assertSame($form, $iterator->current());
// dynamic modification
unset($forms['removed']);
$forms['added'] = $formToBeAdded;
// continue iteration
$iterator->next();
$this->assertTrue($iterator->valid());
$this->assertSame('added', $iterator->key());
$this->assertSame($formToBeAdded, $iterator->current());
// end of array
$iterator->next();
$this->assertFalse($iterator->valid());
}
public function testSupportDynamicModificationInRecursiveCall()
{
$inheritingForm = $this->getMockForm('inheriting');
$form = $this->getMockForm('form');
$formToBeAdded = $this->getMockForm('added');
$formToBeRemoved = $this->getMockForm('removed');
$inheritingForm->getConfig()->expects($this->any())
->method('getInheritData')
->will($this->returnValue(true));
$inheritingForm->add($form);
$inheritingForm->add($formToBeRemoved);
$forms = array('inheriting' => $inheritingForm);
$iterator = new InheritDataAwareIterator($forms);
$iterator->rewind();
$this->assertTrue($iterator->valid());
$this->assertSame('inheriting', $iterator->key());
$this->assertSame($inheritingForm, $iterator->current());
$this->assertTrue($iterator->hasChildren());
// enter nested iterator
$nestedIterator = $iterator->getChildren();
$this->assertSame('form', $nestedIterator->key());
$this->assertSame($form, $nestedIterator->current());
$this->assertFalse($nestedIterator->hasChildren());
// dynamic modification
$inheritingForm->remove('removed');
$inheritingForm->add($formToBeAdded);
// continue iteration - nested iterator discovers change in the form
$nestedIterator->next();
$this->assertTrue($nestedIterator->valid());
$this->assertSame('added', $nestedIterator->key());
$this->assertSame($formToBeAdded, $nestedIterator->current());
// end of array
$nestedIterator->next();
$this->assertFalse($nestedIterator->valid());
}
/**
* @param string $name
*
* @return \PHPUnit_Framework_MockObject_MockObject
*/
protected function getMockForm($name = 'name')
{
$config = $this->getMock('Symfony\Component\Form\FormConfigInterface');
$config->expects($this->any())
->method('getName')
->will($this->returnValue($name));
$config->expects($this->any())
->method('getCompound')
->will($this->returnValue(true));
$config->expects($this->any())
->method('getDataMapper')
->will($this->returnValue($this->getMock('Symfony\Component\Form\DataMapperInterface')));
$config->expects($this->any())
->method('getEventDispatcher')
->will($this->returnValue($this->getMock('Symfony\Component\EventDispatcher\EventDispatcher')));
return $this->getMockBuilder('Symfony\Component\Form\Form')
->setConstructorArgs(array($config))
->disableArgumentCloning()
->setMethods(array('getViewData'))
->getMock();
}
}

View File

@ -0,0 +1,487 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Tests\Util;
use Symfony\Component\Form\Util\OrderedHashMap;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class OrderedHashMapTest extends \PHPUnit_Framework_TestCase
{
public function testGet()
{
$map = new OrderedHashMap();
$map['first'] = 1;
$this->assertSame(1, $map['first']);
}
/**
* @expectedException \OutOfBoundsException
*/
public function testGetNonExistingFails()
{
$map = new OrderedHashMap();
$map['first'];
}
public function testInsertStringKeys()
{
$map = new OrderedHashMap();
$map['first'] = 1;
$map['second'] = 2;
$this->assertSame(array('first' => 1, 'second' => 2), iterator_to_array($map));
}
public function testInsertNullKeys()
{
$map = new OrderedHashMap();
$map[] = 1;
$map['foo'] = 2;
$map[] = 3;
$this->assertSame(array(0 => 1, 'foo' => 2, 1 => 3), iterator_to_array($map));
}
/**
* Updates should not change the position of an element, otherwise we could
* turn foreach loops into endless loops if they change the current
* element:
*
* foreach ($map as $index => $value) {
* $map[$index] = $value + 1;
* }
*
* And we don't want this, right? :)
*/
public function testUpdateDoesNotChangeElementPosition()
{
$map = new OrderedHashMap();
$map['first'] = 1;
$map['second'] = 2;
$map['first'] = 1;
$this->assertSame(array('first' => 1, 'second' => 2), iterator_to_array($map));
}
public function testIsset()
{
$map = new OrderedHashMap();
$map['first'] = 1;
$this->assertTrue(isset($map['first']));
}
public function testIssetReturnsFalseForNonExisting()
{
$map = new OrderedHashMap();
$this->assertFalse(isset($map['first']));
}
public function testIssetReturnsFalseForNull()
{
$map = new OrderedHashMap();
$map['first'] = null;
$this->assertFalse(isset($map['first']));
}
public function testUnset()
{
$map = new OrderedHashMap();
$map['first'] = 1;
$map['second'] = 2;
unset($map['first']);
$this->assertSame(array('second' => 2), iterator_to_array($map));
}
public function testUnsetNonExistingSucceeds()
{
$map = new OrderedHashMap();
unset($map['first']);
}
public function testEmptyIteration()
{
$map = new OrderedHashMap();
$it = $map->getIterator();
$it->rewind();
$this->assertFalse($it->valid());
$this->assertNull($it->key());
$this->assertNull($it->current());
}
public function testIterationSupportsInsertion()
{
$map = new OrderedHashMap(array('first' => 1));
$it = $map->getIterator();
$it->rewind();
$this->assertTrue($it->valid());
$this->assertSame('first', $it->key());
$this->assertSame(1, $it->current());
// dynamic modification
$map['added'] = 2;
// iterator is unchanged
$this->assertTrue($it->valid());
$this->assertSame('first', $it->key());
$this->assertSame(1, $it->current());
// continue iteration
$it->next();
$this->assertTrue($it->valid());
$this->assertSame('added', $it->key());
$this->assertSame(2, $it->current());
// end of map
$it->next();
$this->assertFalse($it->valid());
$this->assertNull($it->key());
$this->assertNull($it->current());
}
public function testIterationSupportsDeletionAndInsertion()
{
$map = new OrderedHashMap(array('first' => 1, 'removed' => 2));
$it = $map->getIterator();
$it->rewind();
$this->assertTrue($it->valid());
$this->assertSame('first', $it->key());
$this->assertSame(1, $it->current());
// dynamic modification
unset($map['removed']);
$map['added'] = 3;
// iterator is unchanged
$this->assertTrue($it->valid());
$this->assertSame('first', $it->key());
$this->assertSame(1, $it->current());
// continue iteration
$it->next();
$this->assertTrue($it->valid());
$this->assertSame('added', $it->key());
$this->assertSame(3, $it->current());
// end of map
$it->next();
$this->assertFalse($it->valid());
$this->assertNull($it->key());
$this->assertNull($it->current());
}
public function testIterationSupportsDeletionOfCurrentElement()
{
$map = new OrderedHashMap(array('removed' => 1, 'next' => 2));
$it = $map->getIterator();
$it->rewind();
$this->assertTrue($it->valid());
$this->assertSame('removed', $it->key());
$this->assertSame(1, $it->current());
unset($map['removed']);
// iterator is unchanged
$this->assertTrue($it->valid());
$this->assertSame('removed', $it->key());
$this->assertSame(1, $it->current());
// continue iteration
$it->next();
$this->assertTrue($it->valid());
$this->assertSame('next', $it->key());
$this->assertSame(2, $it->current());
// end of map
$it->next();
$this->assertFalse($it->valid());
$this->assertNull($it->key());
$this->assertNull($it->current());
}
public function testIterationIgnoresReplacementOfCurrentElement()
{
$map = new OrderedHashMap(array('replaced' => 1, 'next' => 2));
$it = $map->getIterator();
$it->rewind();
$this->assertTrue($it->valid());
$this->assertSame('replaced', $it->key());
$this->assertSame(1, $it->current());
$map['replaced'] = 3;
// iterator is unchanged
$this->assertTrue($it->valid());
$this->assertSame('replaced', $it->key());
$this->assertSame(1, $it->current());
// continue iteration
$it->next();
$this->assertTrue($it->valid());
$this->assertSame('next', $it->key());
$this->assertSame(2, $it->current());
// end of map
$it->next();
$this->assertFalse($it->valid());
$this->assertNull($it->key());
$this->assertNull($it->current());
}
public function testIterationSupportsDeletionOfCurrentAndLastElement()
{
$map = new OrderedHashMap(array('removed' => 1));
$it = $map->getIterator();
$it->rewind();
$this->assertTrue($it->valid());
$this->assertSame('removed', $it->key());
$this->assertSame(1, $it->current());
unset($map['removed']);
// iterator is unchanged
$this->assertTrue($it->valid());
$this->assertSame('removed', $it->key());
$this->assertSame(1, $it->current());
// end of map
$it->next();
$this->assertFalse($it->valid());
$this->assertNull($it->key());
$this->assertNull($it->current());
}
public function testIterationIgnoresReplacementOfCurrentAndLastElement()
{
$map = new OrderedHashMap(array('replaced' => 1));
$it = $map->getIterator();
$it->rewind();
$this->assertTrue($it->valid());
$this->assertSame('replaced', $it->key());
$this->assertSame(1, $it->current());
$map['replaced'] = 2;
// iterator is unchanged
$this->assertTrue($it->valid());
$this->assertSame('replaced', $it->key());
$this->assertSame(1, $it->current());
// end of map
$it->next();
$this->assertFalse($it->valid());
$this->assertNull($it->key());
$this->assertNull($it->current());
}
public function testIterationSupportsDeletionOfPreviousElement()
{
$map = new OrderedHashMap(array('removed' => 1, 'next' => 2, 'onemore' => 3));
$it = $map->getIterator();
$it->rewind();
$this->assertTrue($it->valid());
$this->assertSame('removed', $it->key());
$this->assertSame(1, $it->current());
// continue iteration
$it->next();
$this->assertTrue($it->valid());
$this->assertSame('next', $it->key());
$this->assertSame(2, $it->current());
unset($map['removed']);
// iterator is unchanged
$this->assertTrue($it->valid());
$this->assertSame('next', $it->key());
$this->assertSame(2, $it->current());
// continue iteration
$it->next();
$this->assertTrue($it->valid());
$this->assertSame('onemore', $it->key());
$this->assertSame(3, $it->current());
// end of map
$it->next();
$this->assertFalse($it->valid());
$this->assertNull($it->key());
$this->assertNull($it->current());
}
public function testIterationIgnoresReplacementOfPreviousElement()
{
$map = new OrderedHashMap(array('replaced' => 1, 'next' => 2, 'onemore' => 3));
$it = $map->getIterator();
$it->rewind();
$this->assertTrue($it->valid());
$this->assertSame('replaced', $it->key());
$this->assertSame(1, $it->current());
// continue iteration
$it->next();
$this->assertTrue($it->valid());
$this->assertSame('next', $it->key());
$this->assertSame(2, $it->current());
$map['replaced'] = 4;
// iterator is unchanged
$this->assertTrue($it->valid());
$this->assertSame('next', $it->key());
$this->assertSame(2, $it->current());
// continue iteration
$it->next();
$this->assertTrue($it->valid());
$this->assertSame('onemore', $it->key());
$this->assertSame(3, $it->current());
// end of map
$it->next();
$this->assertFalse($it->valid());
$this->assertNull($it->key());
$this->assertNull($it->current());
}
public function testIterationSupportsDeletionOfMultiplePreviousElements()
{
$map = new OrderedHashMap(array('removed' => 1, 'alsoremoved' => 2, 'next' => 3, 'onemore' => 4));
$it = $map->getIterator();
$it->rewind();
$this->assertTrue($it->valid());
$this->assertSame('removed', $it->key());
$this->assertSame(1, $it->current());
// continue iteration
$it->next();
$this->assertTrue($it->valid());
$this->assertSame('alsoremoved', $it->key());
$this->assertSame(2, $it->current());
// continue iteration
$it->next();
$this->assertTrue($it->valid());
$this->assertSame('next', $it->key());
$this->assertSame(3, $it->current());
unset($map['removed'], $map['alsoremoved']);
// iterator is unchanged
$this->assertTrue($it->valid());
$this->assertSame('next', $it->key());
$this->assertSame(3, $it->current());
// continue iteration
$it->next();
$this->assertTrue($it->valid());
$this->assertSame('onemore', $it->key());
$this->assertSame(4, $it->current());
// end of map
$it->next();
$this->assertFalse($it->valid());
$this->assertNull($it->key());
$this->assertNull($it->current());
}
public function testParallelIteration()
{
$map = new OrderedHashMap(array('first' => 1, 'second' => 2));
$it1 = $map->getIterator();
$it2 = $map->getIterator();
$it1->rewind();
$this->assertTrue($it1->valid());
$this->assertSame('first', $it1->key());
$this->assertSame(1, $it1->current());
$it2->rewind();
$this->assertTrue($it2->valid());
$this->assertSame('first', $it2->key());
$this->assertSame(1, $it2->current());
// 1: continue iteration
$it1->next();
$this->assertTrue($it1->valid());
$this->assertSame('second', $it1->key());
$this->assertSame(2, $it1->current());
// 2: remains unchanged
$this->assertTrue($it2->valid());
$this->assertSame('first', $it2->key());
$this->assertSame(1, $it2->current());
// 1: advance to end of map
$it1->next();
$this->assertFalse($it1->valid());
$this->assertNull($it1->key());
$this->assertNull($it1->current());
// 2: remains unchanged
$this->assertTrue($it2->valid());
$this->assertSame('first', $it2->key());
$this->assertSame(1, $it2->current());
// 2: continue iteration
$it2->next();
$this->assertTrue($it2->valid());
$this->assertSame('second', $it2->key());
$this->assertSame(2, $it2->current());
// 1: remains unchanged
$this->assertFalse($it1->valid());
$this->assertNull($it1->key());
$this->assertNull($it1->current());
// 2: advance to end of map
$it2->next();
$this->assertFalse($it2->valid());
$this->assertNull($it2->key());
$this->assertNull($it2->current());
// 1: remains unchanged
$this->assertFalse($it1->valid());
$this->assertNull($it1->key());
$this->assertNull($it1->current());
}
public function testCount()
{
$map = new OrderedHashMap();
$map[] = 1;
$map['foo'] = 2;
unset($map[0]);
$map[] = 3;
$this->assertSame(2, count($map));
}
}

View File

@ -0,0 +1,190 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Util;
/**
* A hash map which keeps track of deletions and additions.
*
* Like in associative arrays, elements can be mapped to integer or string keys.
* Unlike associative arrays, the map keeps track of the order in which keys
* were added and removed. This order is reflected during iteration.
*
* The map supports concurrent modification during iteration. That means that
* you can insert and remove elements from within a foreach loop and the
* iterator will reflect those changes accordingly.
*
* While elements that are added during the loop are recognized by the iterator,
* changed elements are not. Otherwise the loop could be infinite if each loop
* changes the current element:
*
* $map = new OrderedHashMap();
* $map[1] = 1;
* $map[2] = 2;
* $map[3] = 3;
*
* foreach ($map as $index => $value) {
* echo "$index: $value\n"
* if (1 === $index) {
* $map[1] = 4;
* $map[] = 5;
* }
* }
*
* print_r(iterator_to_array($map));
*
* // => 1: 1
* // 2: 2
* // 3: 3
* // 4: 5
* // Array
* // (
* // [1] => 4
* // [2] => 2
* // [3] => 3
* // [4] => 5
* // )
*
* The map also supports multiple parallel iterators. That means that you can
* nest foreach loops without affecting each other's iteration:
*
* foreach ($map as $index => $value) {
* foreach ($map as $index2 => $value2) {
* // ...
* }
* }
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @since 2.2.6
*/
class OrderedHashMap implements \ArrayAccess, \IteratorAggregate, \Countable
{
/**
* The elements of the map, indexed by their keys.
*
* @var array
*/
private $elements = array();
/**
* The keys of the map in the order in which they were inserted or changed.
*
* @var array
*/
private $orderedKeys = array();
/**
* References to the cursors of all open iterators.
*
* @var array
*/
private $managedCursors = array();
/**
* Creates a new map.
*
* @param array $elements The elements to insert initially.
*
* @since 2.2.6
*/
public function __construct(array $elements = array())
{
$this->elements = $elements;
$this->orderedKeys = array_keys($elements);
}
/**
* {@inheritdoc}
*
* @since 2.2.6
*/
public function offsetExists($key)
{
return isset($this->elements[$key]);
}
/**
* {@inheritdoc}
*
* @since 2.2.6
*/
public function offsetGet($key)
{
if (!isset($this->elements[$key])) {
throw new \OutOfBoundsException('The offset "' . $key . '" does not exist.');
}
return $this->elements[$key];
}
/**
* {@inheritdoc}
*
* @since 2.2.6
*/
public function offsetSet($key, $value)
{
if (null === $key || !isset($this->elements[$key])) {
if (null === $key) {
$key = array() === $this->orderedKeys
// If the array is empty, use 0 as key
? 0
// Imitate PHP's behavior of generating a key that equals
// the highest existing integer key + 1
: max($this->orderedKeys) + 1;
}
$this->orderedKeys[] = $key;
}
$this->elements[$key] = $value;
}
/**
* {@inheritdoc}
*
* @since 2.2.6
*/
public function offsetUnset($key)
{
if (false !== ($position = array_search($key, $this->orderedKeys))) {
array_splice($this->orderedKeys, $position, 1);
unset($this->elements[$key]);
foreach ($this->managedCursors as $i => $cursor) {
if ($cursor >= $position) {
--$this->managedCursors[$i];
}
}
}
}
/**
* {@inheritdoc}
*
* @since 2.2.6
*/
public function getIterator()
{
return new OrderedHashMapIterator($this->elements, $this->orderedKeys, $this->managedCursors);
}
/**
* {@inheritdoc}
*
* @since 2.2.6
*/
public function count()
{
return count($this->elements);
}
}

View File

@ -0,0 +1,163 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Util;
/**
* Iterator for {@link OrderedHashMap} objects.
*
* This class is internal and should not be used.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @since 2.2.6
*/
class OrderedHashMapIterator implements \Iterator
{
/**
* @var array
*/
private $elements;
/**
* @var array
*/
private $orderedKeys;
/**
* @var integer
*/
private $cursor;
/**
* @var integer
*/
private $cursorId;
/**
* @var array
*/
private $managedCursors;
/**
* @var string|integer|null
*/
private $key;
/**
* @var mixed
*/
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
* they should be iterated.
* @param array $managedCursors An array from which to reference the
* iterator's cursor as long as it is alive.
* This array is managed by the corresponding
* {@link OrderedHashMap} instance to support
* recognizing the deletion of elements.
*
* @since 2.2.6
*/
public function __construct(array &$elements, array &$orderedKeys, array &$managedCursors)
{
$this->elements = &$elements;
$this->orderedKeys = &$orderedKeys;
$this->managedCursors = &$managedCursors;
$this->cursorId = count($managedCursors);
$this->managedCursors[$this->cursorId] = &$this->cursor;
}
/**
* Removes the iterator's cursors from the managed cursors of the
* corresponding {@link OrderedHashMap} instance.
*
* @since 2.2.6
*/
public function __destruct()
{
// Use array_splice() instead of isset() to prevent holes in the
// array indices, which would break the initialization of $cursorId
array_splice($this->managedCursors, $this->cursorId, 1);
}
/**
*{@inheritdoc}
*
* @since 2.2.6
*/
public function current()
{
return $this->current;
}
/**
* {@inheritdoc}
*
* @since 2.2.6
*/
public function next()
{
++$this->cursor;
if (isset($this->orderedKeys[$this->cursor])) {
$this->key = $this->orderedKeys[$this->cursor];
$this->current = $this->elements[$this->key];
} else {
$this->key = null;
$this->current = null;
}
}
/**
*{@inheritdoc}
*
* @since 2.2.6
*/
public function key()
{
return $this->key;
}
/**
*{@inheritdoc}
*
* @since 2.2.6
*/
public function valid()
{
return null !== $this->key;
}
/**
*{@inheritdoc}
*
* @since 2.2.6
*/
public function rewind()
{
$this->cursor = 0;
if (isset($this->orderedKeys[0])) {
$this->key = $this->orderedKeys[0];
$this->current = $this->elements[$this->key];
} else {
$this->key = null;
$this->current = null;
}
}
}

View File

@ -1,78 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Util;
/**
* Iterator that traverses an array.
*
* Contrary to {@link \ArrayIterator}, this iterator recognizes changes in the
* original array during iteration.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ReferencingArrayIterator implements \Iterator
{
/**
* @var array
*/
private $array;
/**
* Creates a new iterator.
*
* @param array $array An array
*/
public function __construct(array &$array)
{
$this->array = &$array;
}
/**
*{@inheritdoc}
*/
public function current()
{
return current($this->array);
}
/**
*{@inheritdoc}
*/
public function next()
{
next($this->array);
}
/**
*{@inheritdoc}
*/
public function key()
{
return key($this->array);
}
/**
*{@inheritdoc}
*/
public function valid()
{
return null !== key($this->array);
}
/**
*{@inheritdoc}
*/
public function rewind()
{
reset($this->array);
}
}

View File

@ -14,9 +14,6 @@ namespace Symfony\Component\Form\Util;
/**
* Iterator that traverses an array of forms.
*
* Contrary to {@link \ArrayIterator}, this iterator recognizes changes in the
* original array during iteration.
*
* You can wrap the iterator into a {@link \RecursiveIterator} in order to
* enter any child form that inherits its parent's data and iterate the children
* of that form as well.
@ -26,14 +23,14 @@ namespace Symfony\Component\Form\Util;
* @deprecated Deprecated since version 2.3, to be removed in 3.0. Use
* {@link InheritDataAwareIterator} instead.
*/
class VirtualFormAwareIterator extends ReferencingArrayIterator implements \RecursiveIterator
class VirtualFormAwareIterator extends \IteratorIterator implements \RecursiveIterator
{
/**
* {@inheritdoc}
*/
public function getChildren()
{
return new static($this->current()->all());
return new static($this->current());
}
/**

View File

@ -28,7 +28,7 @@ class MemcachedSessionHandlerTest extends \PHPUnit_Framework_TestCase
protected function setUp()
{
if (!class_exists('Memcached')) {
$this->markTestSkipped('Skipped tests Memcache class is not present');
$this->markTestSkipped('Skipped tests Memcached class is not present');
}
$this->memcached = $this->getMock('Memcached');

View File

@ -312,6 +312,8 @@ class Locale
*/
public static function setDefault($locale)
{
throw new MethodNotImplementedException(__METHOD__);
if ('en' !== $locale) {
throw new MethodNotImplementedException(__METHOD__);
}
}
}

View File

@ -150,6 +150,11 @@ class LocaleTest extends AbstractLocaleTest
$this->call('setDefault', 'pt_BR');
}
public function testSetDefaultAcceptsEn()
{
$this->call('setDefault', 'en');
}
protected function call($methodName)
{
$args = array_slice(func_get_args(), 1);

View File

@ -63,7 +63,7 @@ abstract class AbstractPreAuthenticatedListener implements ListenerInterface
try {
list($user, $credentials) = $this->getPreAuthenticatedData($request);
} catch (BadCredentialsException $exception) {
$this->clearToken();
$this->clearToken($exception);
return;
}
@ -91,21 +91,23 @@ abstract class AbstractPreAuthenticatedListener implements ListenerInterface
$this->dispatcher->dispatch(SecurityEvents::INTERACTIVE_LOGIN, $loginEvent);
}
} catch (AuthenticationException $failed) {
$this->clearToken();
$this->clearToken($failed);
}
}
/**
* Clears a PreAuthenticatedToken for this provider (if present)
*
* @param AuthenticationException $exception
*/
protected function clearToken()
private function clearToken(AuthenticationException $exception)
{
$token = $this->securityContext->getToken();
if ($token instanceof PreAuthenticatedToken && $this->providerKey === $token->getProviderKey()) {
$this->securityContext->setToken(null);
if (null !== $this->logger) {
$this->logger->info(sprintf("Cleared security context due to exception: %s", $failed->getMessage()));
$this->logger->info(sprintf("Cleared security context due to exception: %s", $exception->getMessage()));
}
}
}

View File

@ -15,6 +15,7 @@ namespace Symfony\Component\Translation;
* MessageSelector.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @api
*/
@ -73,7 +74,14 @@ class MessageSelector
}
$position = PluralizationRules::get($number, $locale);
if (!isset($standardRules[$position])) {
// when there's exactly one rule given, and that rule is a standard
// rule, use this rule
if (1 === count($parts) && isset($standardRules[0])) {
return $standardRules[0];
}
throw new \InvalidArgumentException(sprintf('Unable to choose a translation for "%s" with locale "%s". Double check that this translation has the correct plural options (e.g. "There is one apple|There are %%count%% apples").', $message, $locale));
}

View File

@ -79,12 +79,14 @@ class IdentityTranslatorTest extends \PHPUnit_Framework_TestCase
public function getTransChoiceTests()
{
return array(
array('There is no apple', '{0} There is no apple|{1} There is one apple|]1,Inf] There are %count% apples', 0, array('%count%' => 0)),
array('There is one apple', '{0} There is no apple|{1} There is one apple|]1,Inf] There are %count% apples', 1, array('%count%' => 1)),
array('There are 10 apples', '{0} There is no apple|{1} There is one apple|]1,Inf] There are %count% apples', 10, array('%count%' => 10)),
array('There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0, array('%count%' => 0)),
array('There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1, array('%count%' => 1)),
array('There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10, array('%count%' => 10)),
array('There are 0 apples', 'There is 1 apple|There are %count% apples', 0, array('%count%' => 0)),
array('There is 1 apple', 'There is 1 apple|There are %count% apples', 1, array('%count%' => 1)),
array('There are 10 apples', 'There is 1 apple|There are %count% apples', 10, array('%count%' => 10)),
// custom validation messages may be coded with a fixed value
array('There are 2 apples', 'There are 2 apples', 2, array('%count%' => 2)),
);
}
}

View File

@ -25,43 +25,61 @@ class MessageSelectorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($expected, $selector->choose($id, $number, 'en'));
}
/**
* @expectedException InvalidArgumentException
*/
public function testChooseWhenNoEnoughChoices()
public function testReturnMessageIfExactlyOneStandardRuleIsGiven()
{
$selector = new MessageSelector();
$selector->choose('foo', 10, 'en');
$this->assertEquals('There are two apples', $selector->choose('There are two apples', 2, 'en'));
}
/**
* @dataProvider getNonMatchingMessages
* @expectedException \InvalidArgumentException
*/
public function testThrowExceptionIfMatchingMessageCannotBeFound($id, $number)
{
$selector = new MessageSelector();
$selector->choose($id, $number, 'en');
}
public function getNonMatchingMessages()
{
return array(
array('{0} There are no apples|{1} There is one apple', 2),
array('{1} There is one apple|]1,Inf] There are %count% apples', 0),
array('{1} There is one apple|]2,Inf] There are %count% apples', 2),
array('{0} There are no apples|There is one apple', 2),
);
}
public function getChooseTests()
{
return array(
array('There is no apples', '{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples', 0),
array('There is no apples', '{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples', 0),
array('There is no apples', '{0}There is no apples|{1} There is one apple|]1,Inf] There is %count% apples', 0),
array('There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0),
array('There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0),
array('There are no apples', '{0}There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0),
array('There is one apple', '{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples', 1),
array('There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1),
array('There is %count% apples', '{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples', 10),
array('There is %count% apples', '{0} There is no apples|{1} There is one apple|]1,Inf]There is %count% apples', 10),
array('There is %count% apples', '{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples', 10),
array('There are %count% apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10),
array('There are %count% apples', '{0} There are no apples|{1} There is one apple|]1,Inf]There are %count% apples', 10),
array('There are %count% apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10),
array('There is %count% apples', 'There is one apple|There is %count% apples', 0),
array('There is one apple', 'There is one apple|There is %count% apples', 1),
array('There is %count% apples', 'There is one apple|There is %count% apples', 10),
array('There are %count% apples', 'There is one apple|There are %count% apples', 0),
array('There is one apple', 'There is one apple|There are %count% apples', 1),
array('There are %count% apples', 'There is one apple|There are %count% apples', 10),
array('There is %count% apples', 'one: There is one apple|more: There is %count% apples', 0),
array('There is one apple', 'one: There is one apple|more: There is %count% apples', 1),
array('There is %count% apples', 'one: There is one apple|more: There is %count% apples', 10),
array('There are %count% apples', 'one: There is one apple|more: There are %count% apples', 0),
array('There is one apple', 'one: There is one apple|more: There are %count% apples', 1),
array('There are %count% apples', 'one: There is one apple|more: There are %count% apples', 10),
array('There is no apples', '{0} There is no apples|one: There is one apple|more: There is %count% apples', 0),
array('There is one apple', '{0} There is no apples|one: There is one apple|more: There is %count% apples', 1),
array('There is %count% apples', '{0} There is no apples|one: There is one apple|more: There is %count% apples', 10),
array('There are no apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 0),
array('There is one apple', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 1),
array('There are %count% apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 10),
array('', '{0}|{1} There is one apple|]1,Inf] There is %count% apples', 0),
array('', '{0} There is no apples|{1}|]1,Inf] There is %count% apples', 1),
array('', '{0}|{1} There is one apple|]1,Inf] There are %count% apples', 0),
array('', '{0} There are no apples|{1}|]1,Inf] There are %count% apples', 1),
// Indexed only tests which are Gettext PoFile* compatible strings.
array('There are %count% apples', 'There is one apple|There are %count% apples', 0),
@ -69,12 +87,12 @@ class MessageSelectorTest extends \PHPUnit_Framework_TestCase
array('There are %count% apples', 'There is one apple|There are %count% apples', 2),
// Tests for float numbers
array('There is almost one apple', '{0} There is no apples|]0,1[ There is almost one apple|{1} There is one apple|[1,Inf] There is more than one apple', 0.7),
array('There is one apple', '{0} There is no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 1),
array('There is more than one apple', '{0} There is no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 1.7),
array('There is no apples', '{0} There is no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0),
array('There is no apples', '{0} There is no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0.0),
array('There is no apples', '{0.0} There is no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0),
array('There is almost one apple', '{0} There are no apples|]0,1[ There is almost one apple|{1} There is one apple|[1,Inf] There is more than one apple', 0.7),
array('There is one apple', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 1),
array('There is more than one apple', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 1.7),
array('There are no apples', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0),
array('There are no apples', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0.0),
array('There are no apples', '{0.0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0),
);
}
}

View File

@ -237,9 +237,9 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
public function getTransChoiceTests()
{
return array(
array('Il y a 0 pomme', '{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples', '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 0, array('%count%' => 0), 'fr', ''),
array('Il y a 1 pomme', '{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples', '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 1, array('%count%' => 1), 'fr', ''),
array('Il y a 10 pommes', '{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples', '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 10, array('%count%' => 10), 'fr', ''),
array('Il y a 0 pomme', '{0} There are no appless|{1} There is one apple|]1,Inf] There is %count% apples', '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 0, array('%count%' => 0), 'fr', ''),
array('Il y a 1 pomme', '{0} There are no appless|{1} There is one apple|]1,Inf] There is %count% apples', '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 1, array('%count%' => 1), 'fr', ''),
array('Il y a 10 pommes', '{0} There are no appless|{1} There is one apple|]1,Inf] There is %count% apples', '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 10, array('%count%' => 10), 'fr', ''),
array('Il y a 0 pomme', 'There is one apple|There is %count% apples', 'Il y a %count% pomme|Il y a %count% pommes', 0, array('%count%' => 0), 'fr', ''),
array('Il y a 1 pomme', 'There is one apple|There is %count% apples', 'Il y a %count% pomme|Il y a %count% pommes', 1, array('%count%' => 1), 'fr', ''),
@ -249,11 +249,11 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
array('Il y a 1 pomme', 'one: There is one apple|more: There is %count% apples', 'one: Il y a %count% pomme|more: Il y a %count% pommes', 1, array('%count%' => 1), 'fr', ''),
array('Il y a 10 pommes', 'one: There is one apple|more: There is %count% apples', 'one: Il y a %count% pomme|more: Il y a %count% pommes', 10, array('%count%' => 10), 'fr', ''),
array('Il n\'y a aucune pomme', '{0} There is no apple|one: There is one apple|more: There is %count% apples', '{0} Il n\'y a aucune pomme|one: Il y a %count% pomme|more: Il y a %count% pommes', 0, array('%count%' => 0), 'fr', ''),
array('Il y a 1 pomme', '{0} There is no apple|one: There is one apple|more: There is %count% apples', '{0} Il n\'y a aucune pomme|one: Il y a %count% pomme|more: Il y a %count% pommes', 1, array('%count%' => 1), 'fr', ''),
array('Il y a 10 pommes', '{0} There is no apple|one: There is one apple|more: There is %count% apples', '{0} Il n\'y a aucune pomme|one: Il y a %count% pomme|more: Il y a %count% pommes', 10, array('%count%' => 10), 'fr', ''),
array('Il n\'y a aucune pomme', '{0} There are no apples|one: There is one apple|more: There is %count% apples', '{0} Il n\'y a aucune pomme|one: Il y a %count% pomme|more: Il y a %count% pommes', 0, array('%count%' => 0), 'fr', ''),
array('Il y a 1 pomme', '{0} There are no apples|one: There is one apple|more: There is %count% apples', '{0} Il n\'y a aucune pomme|one: Il y a %count% pomme|more: Il y a %count% pommes', 1, array('%count%' => 1), 'fr', ''),
array('Il y a 10 pommes', '{0} There are no apples|one: There is one apple|more: There is %count% apples', '{0} Il n\'y a aucune pomme|one: Il y a %count% pomme|more: Il y a %count% pommes', 10, array('%count%' => 10), 'fr', ''),
array('Il y a 0 pomme', new String('{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples'), '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 0, array('%count%' => 0), 'fr', ''),
array('Il y a 0 pomme', new String('{0} There are no appless|{1} There is one apple|]1,Inf] There is %count% apples'), '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 0, array('%count%' => 0), 'fr', ''),
);
}
@ -277,16 +277,15 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('10 things', $translator->transChoice('some_message2', 10, array('%count%' => 10)));
}
/**
* @expectedException \InvalidArgumentException
*/
public function testTransChoiceFallbackWithNoTranslation()
{
$translator = new Translator('ru', new MessageSelector());
$translator->setFallbackLocales(array('en'));
$translator->addLoader('array', new ArrayLoader());
$this->assertEquals('10 things', $translator->transChoice('some_message2', 10, array('%count%' => 10)));
// consistent behavior with Translator::trans(), which returns the string
// unchanged if it can't be found
$this->assertEquals('some_message2', $translator->transChoice('some_message2', 10, array('%count%' => 10)));
}
}

View File

@ -166,7 +166,10 @@ class XmlFileLoader extends FileLoader
$value = array();
}
} else {
$value = trim($node);
$value = XmlUtils::phpize($node);
if (is_string($value)) {
$value = trim($value);
}
}
$options[(string) $node['name']] = $value;

View File

@ -16,6 +16,7 @@ use Symfony\Component\Validator\Constraints\Collection;
use Symfony\Component\Validator\Constraints\NotNull;
use Symfony\Component\Validator\Constraints\Range;
use Symfony\Component\Validator\Constraints\Choice;
use Symfony\Component\Validator\Constraints\Regex;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\Loader\XmlFileLoader;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintA;
@ -68,6 +69,22 @@ class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($expected, $metadata);
}
public function testLoadClassMetadataWithNonStrings()
{
$loader = new XmlFileLoader(__DIR__.'/constraint-mapping-non-strings.xml');
$metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity');
$loader->loadClassMetadata($metadata);
$expected = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity');
$expected->addPropertyConstraint('firstName', new Regex(array('pattern' => '/^1/', 'match' => false)));
$properties = $metadata->getPropertyMetadata('firstName');
$constraints = $properties[0]->getConstraints();
$this->assertFalse($constraints[0]->match);
}
public function testLoadGroupSequenceProvider()
{
$loader = new XmlFileLoader(__DIR__.'/constraint-mapping.xml');

View File

@ -0,0 +1,19 @@
<?xml version="1.0" ?>
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
<namespace prefix="custom">Symfony\Component\Validator\Tests\Fixtures\</namespace>
<class name="Symfony\Component\Validator\Tests\Fixtures\Entity">
<property name="firstName">
<!-- Constraint with a Boolean -->
<constraint name="Regex">
<option name="pattern">/^1/</option>
<option name="match">false</option>
</constraint>
</property>
</class>
</constraint-mapping>

View File

@ -78,7 +78,7 @@
<!-- Constraint with options -->
<constraint name="Choice">
<!-- Option with single value -->
<option name="message">Must be one of %choices%</option>
<option name="message"> Must be one of %choices% </option>
<!-- Option with multiple values -->
<option name="choices">
<value>A</value>

View File

@ -19,6 +19,8 @@ use Symfony\Component\Yaml\Exception\ParseException;
*/
class Parser
{
const FOLDED_SCALAR_PATTERN = '(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?';
private $offset = 0;
private $lines = array();
private $currentLineNb = -1;
@ -304,10 +306,15 @@ class Parser
$isItUnindentedCollection = $this->isStringUnIndentedCollectionItem($this->currentLine);
// We are in string block (ie. after a line ending with "|")
$removeComments = !preg_match('~(.*)\|[\s]*$~', $this->currentLine);
// Comments must not be removed inside a string block (ie. after a line ending with "|")
$removeCommentsPattern = '~'.self::FOLDED_SCALAR_PATTERN.'$~';
$removeComments = !preg_match($removeCommentsPattern, $this->currentLine);
while ($this->moveToNextLine()) {
if ($this->getCurrentLineIndentation() === $newIndent) {
$removeComments = !preg_match($removeCommentsPattern, $this->currentLine);
}
if ($isItUnindentedCollection && !$this->isStringUnIndentedCollectionItem($this->currentLine)) {
$this->moveToPreviousLine();
break;
@ -391,7 +398,7 @@ class Parser
return $this->refs[$value];
}
if (preg_match('/^(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?$/', $value, $matches)) {
if (preg_match('/^'.self::FOLDED_SCALAR_PATTERN.'$/', $value, $matches)) {
$modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : '';
return $this->parseFoldedScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), intval(abs($modifiers)));

View File

@ -551,7 +551,7 @@ EOF
));
}
public function testNestedStringBlockWithComments()
public function testFoldedStringBlockWithComments()
{
$this->assertEquals(array(array('content' => <<<EOT
# comment 1
@ -576,6 +576,38 @@ EOT
</body>
footer # comment3
EOF
));
}
public function testNestedFoldedStringBlockWithComments()
{
$this->assertEquals(array(array(
'title' => 'some title',
'content' => <<<EOT
# comment 1
header
# comment 2
<body>
<h1>title</h1>
</body>
footer # comment3
EOT
)), Yaml::parse(<<<EOF
-
title: some title
content: |
# comment 1
header
# comment 2
<body>
<h1>title</h1>
</body>
footer # comment3
EOF
));
}