Merge remote branch 'origin/master' into annotations

Conflicts:
	UPDATE.md
	src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
	src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
This commit is contained in:
Johannes Schmitt 2011-05-19 22:49:59 +02:00
commit 6c0b0449a6
161 changed files with 2138 additions and 676 deletions

View File

@ -87,6 +87,31 @@ beta1 to beta2
*/
private $foo;
* Forms must now be explicitly enabled (automatically done in Symfony SE):
form: ~
# equivalent to
form:
enabled: true
* The Routing Exceptions have been moved:
Before:
Symfony\Component\Routing\Matcher\Exception\Exception
Symfony\Component\Routing\Matcher\Exception\NotFoundException
Symfony\Component\Routing\Matcher\Exception\MethodNotAllowedException
After:
Symfony\Component\Routing\Exception\Exception
Symfony\Component\Routing\Exception\NotFoundException
Symfony\Component\Routing\Exception\MethodNotAllowedException
* The form component's ``csrf_page_id`` option has been renamed to
``intention``.
* The ``error_handler`` setting has been removed. The ``ErrorHandler`` class
is now managed directly by Symfony SE in ``AppKernel``.
@ -220,6 +245,9 @@ beta1 to beta2
* Form: Renamed option value "text" of "widget" option of the "date" type was
renamed to "single-text". "text" indicates to use separate text boxes now
(like for the "time" type).
* Form: Renamed view variable "name" to "full_name". The variable "name" now
contains the local, short name (equivalent to $form->getName()).
PR12 to beta1
-------------

View File

@ -180,4 +180,12 @@ class DoctrineOrmTypeGuesser implements FormTypeGuesserInterface
}
}
}
/**
* @inheritDoc
*/
public function guessMinLength($class, $property)
{
return;
}
}

View File

@ -32,8 +32,10 @@ class EntityType extends AbstractType
public function buildForm(FormBuilder $builder, array $options)
{
if ($options['multiple']) {
$builder->addEventSubscriber(new MergeCollectionListener())
->prependClientTransformer(new EntitiesToArrayTransformer($options['choice_list']));
$builder
->addEventSubscriber(new MergeCollectionListener())
->prependClientTransformer(new EntitiesToArrayTransformer($options['choice_list']))
;
} else {
$builder->prependClientTransformer(new EntityToIdTransformer($options['choice_list']));
}
@ -42,16 +44,16 @@ class EntityType extends AbstractType
public function getDefaultOptions(array $options)
{
$defaultOptions = array(
'multiple' => false,
'expanded' => false,
'em' => $this->em,
'class' => null,
'property' => null,
'query_builder' => null,
'choices' => array(),
'multiple' => false,
'expanded' => false,
'em' => $this->em,
'class' => null,
'property' => null,
'query_builder' => null,
'choices' => array(),
'preferred_choices' => array(),
'multiple' => false,
'expanded' => false,
'multiple' => false,
'expanded' => false,
);
$options = array_replace($defaultOptions, $options);

View File

@ -0,0 +1,56 @@
<?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\Bridge\Doctrine\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
/**
* Constraint for the Unique Entity validator
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class UniqueEntity extends Constraint
{
public $message = 'This value is already used.';
public $em = null;
public $fields = array();
public function getRequiredOptions()
{
return array('fields');
}
/**
* The validator must be defined as a service with this name.
*
* @return string
*/
public function validatedBy()
{
return 'doctrine.orm.validator.unique';
}
/**
* {@inheritDoc}
*/
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
public function getDefaultOption()
{
return 'fields';
}
}

View File

@ -0,0 +1,81 @@
<?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\Bridge\Doctrine\Validator\Constraints;
use Symfony\Bundle\DoctrineBundle\Registry;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\ConstraintValidator;
/**
* Unique Entity Validator checks if one or a set of fields contain unique values.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class UniqueEntityValidator extends ConstraintValidator
{
/**
* @var Registry
*/
private $registry;
/**
* @param Registry $registry
*/
public function __construct(Registry $registry)
{
$this->registry = $registry;
}
/**
* @param object $entity
* @param Constraint $constraint
* @return bool
*/
public function isValid($entity, Constraint $constraint)
{
if (!is_array($constraint->fields) && !is_string($constraint->fields)) {
throw new UnexpectedTypeException($constraint->fields, 'array');
}
$fields = (array)$constraint->fields;
if (count($constraint->fields) == 0) {
throw new ConstraintDefinitionException("At least one field has to specified.");
}
$em = $this->registry->getEntityManager($constraint->em);
$className = $this->context->getCurrentClass();
$class = $em->getClassMetadata($className);
$criteria = array();
foreach ($fields as $fieldName) {
if (!isset($class->reflFields[$fieldName])) {
throw new ConstraintDefinitionException("Only field names mapped by Doctrine can be validated for uniqueness.");
}
$criteria[$fieldName] = $class->reflFields[$fieldName]->getValue($entity);
}
$repository = $em->getRepository($className);
$result = $repository->findBy($criteria);
if (count($result) > 0 && $result[0] !== $entity) {
$oldPath = $this->context->getPropertyPath();
$this->context->setPropertyPath( empty($oldPath) ? $fields[0] : $oldPath . "." . $fields[0]);
$this->context->addViolation($constraint->message, array(), $criteria[$constraint->fields[0]]);
$this->context->setPropertyPath($oldPath);
}
return true; // all true, we added the violation already!
}
}

View File

@ -34,16 +34,9 @@ class TransChoiceTokenParser extends TransTokenParser
$vars = new \Twig_Node_Expression_Array(array(), $lineno);
$body = null;
$count = $this->parser->getExpressionParser()->parseExpression();
$domain = new \Twig_Node_Expression_Constant('messages', $lineno);
if (!$stream->test(\Twig_Token::BLOCK_END_TYPE) && $stream->test('for')) {
// {% transchoice count for "message" %}
// {% transchoice count for message %}
$stream->next();
$body = $this->parser->getExpressionParser()->parseExpression();
}
$domain = new \Twig_Node_Expression_Constant('messages', $lineno);
if ($stream->test('with')) {
// {% transchoice count with vars %}
@ -57,11 +50,9 @@ class TransChoiceTokenParser extends TransTokenParser
$domain = $this->parser->getExpressionParser()->parseExpression();
}
if (null === $body) {
// {% transchoice count %}message{% endtranschoice %}
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
$body = $this->parser->subparse(array($this, 'decideTransChoiceFork'), true);
}
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
$body = $this->parser->subparse(array($this, 'decideTransChoiceFork'), true);
if (!$body instanceof \Twig_Node_Text && !$body instanceof \Twig_Node_Expression) {
throw new \Twig_Error_Syntax('A message must be a simple text.');

View File

@ -36,6 +36,8 @@ class DumpCommand extends Command
->setDescription('Dumps all assets to the filesystem')
->addArgument('write_to', InputArgument::OPTIONAL, 'Override the configured asset root')
->addOption('watch', null, InputOption::VALUE_NONE, 'Check for changes every second, debug mode only')
->addOption('force', null, InputOption::VALUE_NONE, 'Force an initial generation of all assets (used with --watch)')
->addOption('period', null, InputOption::VALUE_REQUIRED, 'Set the polling period in seconds (used with --watch)', 1)
;
}
@ -49,6 +51,10 @@ class DumpCommand extends Command
protected function execute(InputInterface $input, OutputInterface $output)
{
$output->writeln(sprintf('Dumping all <comment>%s</comment> assets.', $input->getOption('env')));
$output->writeln(sprintf('Debug mode is <comment>%s</comment>.', $input->getOption('no-debug') ? 'off' : 'on'));
$output->writeln('');
if (!$input->getOption('watch')) {
foreach ($this->am->getNames() as $name) {
$this->dumpAsset($name, $output);
@ -61,7 +67,7 @@ class DumpCommand extends Command
throw new \RuntimeException('The --watch option is only available in debug mode.');
}
$this->watch($output);
$this->watch($input, $output);
}
/**
@ -72,25 +78,25 @@ class DumpCommand extends Command
*
* @param OutputInterface $output The command output
*/
private function watch(OutputInterface $output)
private function watch(InputInterface $input, OutputInterface $output)
{
$refl = new \ReflectionClass('Assetic\\AssetManager');
$prop = $refl->getProperty('assets');
$prop->setAccessible(true);
$cache = sys_get_temp_dir().'/assetic_watch_'.substr(sha1($this->basePath), 0, 7);
if (file_exists($cache)) {
$previously = unserialize(file_get_contents($cache));
} else {
if ($input->getOption('force') || !file_exists($cache)) {
$previously = array();
} else {
$previously = unserialize(file_get_contents($cache));
}
$error = '';
while (true) {
try {
foreach ($this->am->getNames() as $name) {
if ($asset = $this->checkAsset($name, $previously)) {
$this->dumpAsset($asset, $output);
if ($this->checkAsset($name, $previously)) {
$this->dumpAsset($name, $output);
}
}
@ -101,7 +107,7 @@ class DumpCommand extends Command
file_put_contents($cache, serialize($previously));
$error = '';
sleep(1);
sleep($input->getOption('period'));
} catch (\Exception $e) {
if ($error != $msg = $e->getMessage()) {
$output->writeln('<error>[error]</error> '.$msg);
@ -117,7 +123,7 @@ class DumpCommand extends Command
* @param string $name The asset name
* @param array &$previously An array of previous visits
*
* @return AssetInterface|Boolean The asset if it should be dumped
* @return Boolean Whether the asset should be dumped
*/
private function checkAsset($name, array &$previously)
{
@ -133,7 +139,7 @@ class DumpCommand extends Command
$previously[$name] = array('mtime' => $mtime, 'formula' => $formula);
return $changed ? $asset : false;
return $changed;
}
/**

View File

@ -0,0 +1,16 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="assetic.filter.cssimport.class">Assetic\Filter\CssImportFilter</parameter>
</parameters>
<services>
<service id="assetic.filter.cssimport" class="%assetic.filter.cssimport.class%">
<tag name="assetic.filter" alias="cssimport" />
</service>
</services>
</container>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="assetic.filter.packager.class">Assetic\Filter\PackagerFilter</parameter>
<parameter key="assetic.filter.packager.packages" type="collection" />
</parameters>
<services>
<service id="assetic.filter.packager" class="%assetic.filter.packager.class%">
<tag name="assetic.filter" alias="packager" />
<argument>%assetic.filter.packager.packages%</argument>
</service>
</services>
</container>

View File

@ -15,14 +15,12 @@
<tag name="assetic.templating.php" />
<argument type="service" id="templating.helper.router" />
<argument type="service" id="assetic.asset_factory" />
<argument>%assetic.debug%</argument>
</service>
<service id="assetic.helper.static" class="%assetic.helper.static.class%">
<tag name="assetic.templating.php" />
<argument type="service" id="templating.helper.assets" />
<argument type="service" id="assetic.asset_factory" />
<argument>%assetic.debug%</argument>
</service>
<service id="assetic.php_formula_loader" class="%assetic.cached_formula_loader.class%" public="false">

View File

@ -67,8 +67,11 @@ class AsseticLoader extends Loader
$this->loadRouteForAsset($routes, $asset, $name);
$debug = isset($formula[2]['debug']) ? $formula[2]['debug'] : $this->am->isDebug();
$combine = isset($formula[2]['combine']) ? $formula[2]['combine'] : !$debug;
// add a route for each "leaf" in debug mode
if (isset($formula[2]['debug']) ? $formula[2]['debug'] : $this->am->isDebug()) {
if (!$combine) {
$i = 0;
foreach ($asset as $leaf) {
$this->loadRouteForAsset($routes, $leaf, $name, $i++);

View File

@ -13,6 +13,7 @@ namespace Symfony\Bundle\AsseticBundle\Templating;
use Assetic\Asset\AssetInterface;
use Assetic\Factory\AssetFactory;
use Assetic\Util\TraversableString;
use Symfony\Component\Templating\Helper\Helper;
/**
@ -23,18 +24,15 @@ use Symfony\Component\Templating\Helper\Helper;
abstract class AsseticHelper extends Helper
{
protected $factory;
protected $debug;
/**
* Constructor.
*
* @param AssetFactory $factory The asset factory
* @param Boolean $debug The debug mode
*/
public function __construct(AssetFactory $factory, $debug = false)
public function __construct(AssetFactory $factory)
{
$this->factory = $factory;
$this->debug = $debug;
}
/**
@ -43,7 +41,7 @@ abstract class AsseticHelper extends Helper
public function javascripts($inputs = array(), $filters = array(), array $options = array())
{
if (!isset($options['output'])) {
$options['output'] = 'js/*';
$options['output'] = 'js/*.js';
}
return $this->getAssetUrls($inputs, $filters, $options);
@ -55,7 +53,7 @@ abstract class AsseticHelper extends Helper
public function stylesheets($inputs = array(), $filters = array(), array $options = array())
{
if (!isset($options['output'])) {
$options['output'] = 'css/*';
$options['output'] = 'css/*.css';
}
return $this->getAssetUrls($inputs, $filters, $options);
@ -109,7 +107,11 @@ abstract class AsseticHelper extends Helper
}
if (!isset($options['debug'])) {
$options['debug'] = $this->debug;
$options['debug'] = $this->factory->isDebug();
}
if (!isset($options['combine'])) {
$options['combine'] = !$options['debug'];
}
if (isset($options['single']) && $options['single'] && 1 < count($inputs)) {
@ -117,23 +119,25 @@ abstract class AsseticHelper extends Helper
}
if (!isset($options['name'])) {
$options['name'] = $this->factory->generateAssetName($inputs, $filters);
$options['name'] = $this->factory->generateAssetName($inputs, $filters, $options);
}
$coll = $this->factory->createAsset($inputs, $filters, $options);
$asset = $this->factory->createAsset($inputs, $filters, $options);
if (!$options['debug']) {
return array($this->getAssetUrl($coll, $options));
$one = $this->getAssetUrl($asset, $options);
$many = array();
if ($options['combine']) {
$many[] = $one;
} else {
$i = 0;
foreach ($asset as $leaf) {
$many[] = $this->getAssetUrl($leaf, array_replace($options, array(
'name' => $options['name'].'_'.$i++,
)));
}
}
$urls = array();
foreach ($coll as $leaf) {
$urls[] = $this->getAssetUrl($leaf, array_replace($options, array(
'name' => $options['name'].'_'.count($urls),
)));
}
return $urls;
return new TraversableString($one, $many);
}
/**

View File

@ -29,13 +29,12 @@ class DynamicAsseticHelper extends AsseticHelper
*
* @param RouterHelper $routerHelper The router helper
* @param AssetFactory $factory The asset factory
* @param Boolean $debug The debug mode
*/
public function __construct(RouterHelper $routerHelper, AssetFactory $factory, $debug = false)
public function __construct(RouterHelper $routerHelper, AssetFactory $factory)
{
$this->routerHelper = $routerHelper;
parent::__construct($factory, $debug);
parent::__construct($factory);
}
protected function getAssetUrl(AssetInterface $asset, $options = array())

View File

@ -29,13 +29,12 @@ class StaticAsseticHelper extends AsseticHelper
*
* @param AssetsHelper $assetsHelper The assets helper
* @param AssetFactory $factory The asset factory
* @param Boolean $debug The debug mode
*/
public function __construct(AssetsHelper $assetsHelper, AssetFactory $factory, $debug = false)
public function __construct(AssetsHelper $assetsHelper, AssetFactory $factory)
{
$this->assetsHelper = $assetsHelper;
parent::__construct($factory, $debug);
parent::__construct($factory);
}
protected function getAssetUrl(AssetInterface $asset, $options = array())

View File

@ -13,6 +13,7 @@ namespace Symfony\Bundle\AsseticBundle\Tests\Command;
use Symfony\Bundle\AsseticBundle\Command\DumpCommand;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\NullOutput;
class DumpCommandTest extends \PHPUnit_Framework_TestCase
@ -52,7 +53,10 @@ class DumpCommandTest extends \PHPUnit_Framework_TestCase
->will($this->returnValue(array()));
$this->definition->expects($this->any())
->method('getOptions')
->will($this->returnValue(array()));
->will($this->returnValue(array(
new InputOption('--env', '-e', InputOption::VALUE_REQUIRED, 'The Environment name.', 'dev'),
new InputOption('--no-debug', null, InputOption::VALUE_NONE, 'Switches off debug mode.'),
)));
$this->application->expects($this->any())
->method('getKernel')
->will($this->returnValue($this->kernel));

View File

@ -102,12 +102,16 @@ class AsseticExtensionTest extends \PHPUnit_Framework_TestCase
return array(
array('closure', array('jar' => '/path/to/closure.jar')),
array('coffee'),
array('compass'),
array('cssembed', array('jar' => '/path/to/cssembed.jar')),
array('cssimport'),
array('cssrewrite'),
array('jpegtran'),
array('jpegoptim'),
array('jpegtran'),
array('less'),
array('lessphp'),
array('optipng'),
array('packager'),
array('pngout'),
array('sass'),
array('scss'),

View File

@ -43,41 +43,6 @@ class FunctionalTest extends \PHPUnit_Framework_TestCase
$filesystem->remove($this->cacheDir);
}
public function testRoutes()
{
$countRoutes = function($router)
{
$count = 0;
foreach ($router->getRouteCollection()->all() as $name => $route) {
if (0 === strpos($name, '_assetic_')) {
++$count;
}
}
return $count;
};
$kernel = new TestKernel('test', false);
$kernel->boot();
$am = $kernel->getContainer()->get('assetic.asset_manager');
$names = $am->getNames();
$baseline = $expected = count($names);
foreach ($names as $name) {
$asset = $am->get($name);
foreach ($asset as $leaf) {
++$expected;
}
}
$this->assertEquals($baseline, $countRoutes($kernel->getContainer()->get('router')));
$kernel = new TestKernel('test', true);
$kernel->boot();
$this->assertEquals($expected, $countRoutes($kernel->getContainer()->get('router')));
}
public function testTwigRenderDebug()
{
$kernel = new TestKernel('test', true);

View File

@ -34,7 +34,7 @@ class AsseticHelperTest extends \PHPUnit_Framework_TestCase
$helper = new AsseticHelperForTest(new AssetFactory('/foo', $debug), $debug);
$urls = $helper->javascripts(array('js/jquery.js', 'js/jquery.plugin.js'));
$this->assertInternalType('array', $urls, '->javascripts() returns an array');
$this->assertInstanceOf('Traversable', $urls, '->javascripts() returns an array');
$this->assertEquals($count, count($urls), $message);
}

View File

@ -0,0 +1,31 @@
<?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\DoctrineBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
class AddValidatorNamespaceAliasPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('validator.mapping.loader.annotation_loader')) {
return;
}
$loader = $container->getDefinition('validator.mapping.loader.annotation_loader');
$args = $container->getParameterBag()->resolveValue($loader->getArguments());
$args[0]['assertORM'] = 'Symfony\\Bridge\\Doctrine\\Validator\\Constraints\\';
$loader->replaceArgument(0, $args[0]);
}
}

View File

@ -13,6 +13,7 @@ namespace Symfony\Bundle\DoctrineBundle;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Bundle\DoctrineBundle\DependencyInjection\Compiler\RegisterEventListenersAndSubscribersPass;
use Symfony\Bundle\DoctrineBundle\DependencyInjection\Compiler\AddValidatorNamespaceAliasPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
@ -29,5 +30,6 @@ class DoctrineBundle extends Bundle
parent::build($container);
$container->addCompilerPass(new RegisterEventListenersAndSubscribersPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION);
$container->addCompilerPass(new AddValidatorNamespaceAliasPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION);
}
}

View File

@ -31,6 +31,9 @@
<!-- form field factory guesser -->
<parameter key="form.type_guesser.doctrine.class">Symfony\Bridge\Doctrine\Form\DoctrineOrmTypeGuesser</parameter>
<!-- validator -->
<parameter key="doctrine.orm.validator.unique.class">Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntityValidator</parameter>
</parameters>
<services>
@ -57,5 +60,11 @@
<service id="doctrine.orm.configuration" class="%doctrine.orm.configuration.class%" abstract="true" public="false" />
<service id="doctrine.orm.entity_manager.abstract" class="%doctrine.orm.entity_manager.class%" factory-class="%doctrine.orm.entity_manager.class%" factory-method="create" abstract="true" />
<!-- validator -->
<service id="doctrine.orm.validator.unique" class="%doctrine.orm.validator.unique.class%">
<tag name="validator.constraint_validator" alias="doctrine.orm.validator.unique" />
<argument type="service" id="doctrine" />
</service>
</services>
</container>

View File

@ -37,6 +37,8 @@ class ContainerTest extends TestCase
$this->assertInstanceOf('Doctrine\Common\EventManager', $container->get('doctrine.dbal.event_manager'));
$this->assertInstanceOf('Doctrine\DBAL\Event\Listeners\MysqlSessionInit', $container->get('doctrine.dbal.default_connection.events.mysqlsessioninit'));
$this->assertInstanceOf('Symfony\Bundle\DoctrineBundle\CacheWarmer\ProxyCacheWarmer', $container->get('doctrine.orm.proxy_cache_warmer'));
$this->assertInstanceOf('Symfony\Bundle\DoctrineBundle\Registry', $container->get('doctrine'));
$this->assertInstanceOf('Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntityValidator', $container->get('doctrine.orm.validator.unique'));
$this->assertSame($container->get('my.platform'), $container->get('doctrine.dbal.default_connection')->getDatabasePlatform());

View File

@ -46,7 +46,7 @@ class Configuration implements ConfigurationInterface
->end()
;
$this->addCsrfProtectionSection($rootNode);
$this->addFormSection($rootNode);
$this->addEsiSection($rootNode);
$this->addProfilerSection($rootNode);
$this->addRouterSection($rootNode);
@ -59,10 +59,18 @@ class Configuration implements ConfigurationInterface
return $treeBuilder;
}
private function addCsrfProtectionSection(ArrayNodeDefinition $rootNode)
private function addFormSection(ArrayNodeDefinition $rootNode)
{
$rootNode
->children()
->arrayNode('form')
->canBeUnset()
->treatNullLike(array('enabled' => true))
->treatTrueLike(array('enabled' => true))
->children()
->booleanNode('enabled')->defaultTrue()->end()
->end()
->end()
->arrayNode('csrf_protection')
->canBeUnset()
->treatNullLike(array('enabled' => true))
@ -85,7 +93,7 @@ class Configuration implements ConfigurationInterface
->treatNullLike(array('enabled' => true))
->treatTrueLike(array('enabled' => true))
->children()
->booleanNode('enabled')->end()
->booleanNode('enabled')->defaultTrue()->end()
->end()
->end()
->end()
@ -229,6 +237,8 @@ class Configuration implements ConfigurationInterface
->children()
->arrayNode('translator')
->canBeUnset()
->treatNullLike(array('enabled' => true))
->treatTrueLike(array('enabled' => true))
->children()
->booleanNode('enabled')->defaultTrue()->end()
->scalarNode('fallback')->defaultValue('en')->end()
@ -244,8 +254,10 @@ class Configuration implements ConfigurationInterface
->children()
->arrayNode('validation')
->canBeUnset()
->treatNullLike(array('enabled' => true))
->treatTrueLike(array('enabled' => true))
->children()
->booleanNode('enabled')->end()
->booleanNode('enabled')->defaultTrue()->end()
->scalarNode('cache')->end()
->booleanNode('enable_annotations')->defaultFalse()->end()
->end()

View File

@ -12,7 +12,6 @@
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Parameter;
@ -43,7 +42,6 @@ class FrameworkExtension extends Extension
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('web.xml');
$loader->load('form.xml');
$loader->load('services.xml');
// A translator must always be registered (as support is included by
@ -72,8 +70,17 @@ class FrameworkExtension extends Extension
$loader->load('test.xml');
}
if (isset($config['csrf_protection'])) {
$this->registerCsrfProtectionConfiguration($config['csrf_protection'], $container);
if (isset($config['session'])) {
$this->registerSessionConfiguration($config['session'], $container, $loader);
}
if ($hasForm = isset($config['form']) && !empty($config['form']['enabled'])) {
$this->registerFormConfiguration($config, $container, $loader);
$config['validation']['enabled'] = true;
}
if (!empty($config['validation']['enabled'])) {
$this->registerValidationConfiguration($config['validation'], $container, $loader);
}
if (isset($config['esi'])) {
@ -88,10 +95,6 @@ class FrameworkExtension extends Extension
$this->registerRouterConfiguration($config['router'], $container, $loader);
}
if (isset($config['session'])) {
$this->registerSessionConfiguration($config['session'], $container, $loader);
}
if (isset($config['templating'])) {
$this->registerTemplatingConfiguration($config['templating'], $config['ide'], $container, $loader);
}
@ -100,10 +103,6 @@ class FrameworkExtension extends Extension
$this->registerTranslatorConfiguration($config['translator'], $container);
}
if (isset($config['validation'])) {
$this->registerValidationConfiguration($config['validation'], $container, $loader);
}
$this->registerAnnotationsConfiguration($config['annotations'], $container, $loader);
$this->addClassesToCompile(array(
@ -139,15 +138,32 @@ class FrameworkExtension extends Extension
}
/**
* Loads the CSRF protection configuration.
* Loads Form configuration.
*
* @param array $config A CSRF protection configuration array
* @param array $config A configuration array
* @param ContainerBuilder $container A ContainerBuilder instance
* @param XmlFileLoader $loader An XmlFileLoader instance
*/
private function registerCsrfProtectionConfiguration(array $config, ContainerBuilder $container)
private function registerFormConfiguration($config, ContainerBuilder $container, XmlFileLoader $loader)
{
$container->setParameter('form.type_extension.csrf.enabled', $config['enabled']);
$container->setParameter('form.type_extension.csrf.field_name', $config['field_name']);
$loader->load('form.xml');
if (isset($config['csrf_protection'])) {
if (!isset($config['session'])) {
throw new \LogicException('CSRF protection needs that sessions are enabled.');
}
$loader->load('form_csrf.xml');
$container->setParameter('form.type_extension.csrf.enabled', $config['csrf_protection']['enabled']);
$container->setParameter('form.type_extension.csrf.field_name', $config['csrf_protection']['field_name']);
}
if ($container->hasDefinition('session')) {
$container->removeDefinition('file.temporary_storage');
$container->setDefinition('file.temporary_storage', $container->getDefinition('file.temporary_storage.session'));
$container->removeDefinition('file.temporary_storage.session');
} else {
$container->removeDefinition('file.temporary_storage.session');
}
}
/**
@ -325,7 +341,7 @@ class FrameworkExtension extends Extension
$container->setParameter('templating.helper.assets.assets_base_urls', isset($config['assets_base_urls']) ? $config['assets_base_urls'] : array());
$container->setParameter('templating.helper.assets.assets_version', $config['assets_version']);
$container->setParameter('templating.helper.assets.packages', $packages);
$container->getDefinition('templating.helper.assets')->replaceArgument(3, $packages);
if (!empty($config['loaders'])) {
$loaders = array_map(function($loader) { return new Reference($loader); }, $config['loaders']);
@ -443,10 +459,6 @@ class FrameworkExtension extends Extension
*/
private function registerValidationConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader)
{
if (empty($config['enabled'])) {
return;
}
$loader->load('validator.xml');
$container->setParameter('validator.mapping.loader.xml_files_loader.mapping_files', $this->getValidatorXmlMappingFiles($container));

View File

@ -18,8 +18,8 @@ use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Routing\Matcher\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Matcher\Exception\NotFoundException;
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Routing\RequestContext;
@ -107,7 +107,7 @@ class RequestListener
}
$request->attributes->add($parameters);
} catch (NotFoundException $e) {
} catch (ResourceNotFoundException $e) {
$message = sprintf('No route found for "%s %s"', $request->getMethod(), $request->getPathInfo());
if (null !== $this->logger) {
$this->logger->err($message);

View File

@ -8,8 +8,8 @@
<parameter key="form.extension.class">Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension</parameter>
<parameter key="form.factory.class">Symfony\Component\Form\FormFactory</parameter>
<parameter key="form.type_guesser.validator.class">Symfony\Component\Form\Extension\Validator\ValidatorTypeGuesser</parameter>
<parameter key="form.csrf_provider.class">Symfony\Component\Form\Extension\Csrf\CsrfProvider\SessionCsrfProvider</parameter>
<parameter key="file.temporary_storage.class">Symfony\Component\HttpFoundation\File\SessionBasedTemporaryStorage</parameter>
<parameter key="file.temporary_storage.class">Symfony\Component\HttpFoundation\File\TemporaryStorage</parameter>
<parameter key="file.temporary_storage.session.class">Symfony\Component\HttpFoundation\File\SessionBasedTemporaryStorage</parameter>
</parameters>
<services>
@ -51,15 +51,14 @@
<argument type="service" id="validator.mapping.class_metadata_factory" />
</service>
<!-- CsrfProvider -->
<service id="form.csrf_provider" class="%form.csrf_provider.class%">
<!-- TemporaryStorage - where should we put this? -->
<service id="file.temporary_storage.session" class="%file.temporary_storage.session.class%">
<argument type="service" id="session" />
<argument>%kernel.secret%</argument>
<argument>%kernel.cache_dir%/upload</argument>
</service>
<!-- TemporaryStorage - where should we put this? -->
<service id="file.temporary_storage" class="%file.temporary_storage.class%">
<argument type="service" id="session" />
<argument>%kernel.secret%</argument>
<argument>%kernel.cache_dir%/upload</argument>
</service>
@ -130,6 +129,9 @@
<service id="form.type.repeated" class="Symfony\Component\Form\Extension\Core\Type\RepeatedType">
<tag name="form.type" alias="repeated" />
</service>
<service id="form.type.search" class="Symfony\Component\Form\Extension\Core\Type\SearchType">
<tag name="form.type" alias="search" />
</service>
<service id="form.type.textarea" class="Symfony\Component\Form\Extension\Core\Type\TextareaType">
<tag name="form.type" alias="textarea" />
</service>
@ -151,16 +153,5 @@
<tag name="form.type_extension" alias="field" />
<argument type="service" id="validator" />
</service>
<!-- CsrfExtension -->
<service id="form.type.csrf" class="Symfony\Component\Form\Extension\Csrf\Type\CsrfType">
<tag name="form.type" alias="csrf" />
<argument type="service" id="form.csrf_provider" />
</service>
<service id="form.type_extension.csrf" class="Symfony\Component\Form\Extension\Csrf\Type\FormTypeCsrfExtension">
<tag name="form.type_extension" alias="form" />
<argument>%form.type_extension.csrf.enabled%</argument>
<argument>%form.type_extension.csrf.field_name%</argument>
</service>
</services>
</container>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="form.csrf_provider.class">Symfony\Component\Form\Extension\Csrf\CsrfProvider\SessionCsrfProvider</parameter>
</parameters>
<services>
<service id="form.csrf_provider" class="%form.csrf_provider.class%">
<argument type="service" id="session" />
<argument>%kernel.secret%</argument>
</service>
<service id="form.type.csrf" class="Symfony\Component\Form\Extension\Csrf\Type\CsrfType">
<tag name="form.type" alias="csrf" />
<argument type="service" id="form.csrf_provider" />
</service>
<service id="form.type_extension.csrf" class="Symfony\Component\Form\Extension\Csrf\Type\FormTypeCsrfExtension">
<tag name="form.type_extension" alias="form" />
<argument>%form.type_extension.csrf.enabled%</argument>
<argument>%form.type_extension.csrf.field_name%</argument>
</service>
</services>
</container>

View File

@ -9,6 +9,7 @@
<xsd:complexType name="config">
<xsd:all>
<xsd:element name="form" type="form" minOccurs="0" maxOccurs="1" />
<xsd:element name="csrf-protection" type="csrf_protection" minOccurs="0" maxOccurs="1" />
<xsd:element name="esi" type="esi" minOccurs="0" maxOccurs="1" />
<xsd:element name="profiler" type="profiler" minOccurs="0" maxOccurs="1" />
@ -36,6 +37,10 @@
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="form">
<xsd:attribute name="enabled" type="xsd:boolean" />
</xsd:complexType>
<xsd:complexType name="csrf_protection">
<xsd:attribute name="enabled" type="xsd:boolean" />
<xsd:attribute name="field-name" type="xsd:string" />

View File

@ -37,7 +37,7 @@
<argument type="service" id="request" strict="false" />
<argument>%templating.helper.assets.assets_base_urls%</argument>
<argument>%templating.helper.assets.assets_version%</argument>
<argument>%templating.helper.assets.packages%</argument>
<argument type="collection" /> <!-- packages -->
</service>
<service id="templating.helper.request" class="%templating.helper.request.class%">

View File

@ -1,6 +1,6 @@
<input type="checkbox"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
name="<?php echo $view->escape($full_name) ?>"
<?php if ($value): ?>value="<?php echo $view->escape($value) ?>"<?php endif ?>
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>

View File

@ -8,7 +8,7 @@
<?php else: ?>
<select
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
name="<?php echo $view->escape($full_name) ?>"
<?php if ($read_only): ?> disabled="disabled"<?php endif ?>
<?php if ($multiple): ?> multiple="multiple"<?php endif ?>
>

View File

@ -1,7 +1,7 @@
<?php if ($widget == 'single-text'): ?>
<input type="text"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
name="<?php echo $view->escape($full_name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>

View File

@ -1,6 +1,6 @@
<input type="email"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
name="<?php echo $view->escape($full_name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($max_length): ?>maxlength="<?php echo $view->escape($max_length) ?>"<?php endif ?>
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>

View File

@ -1,6 +1,6 @@
<input
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
name="<?php echo $view->escape($full_name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>

View File

@ -1,6 +1,6 @@
<input type="hidden"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
name="<?php echo $view->escape($full_name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
/>

View File

@ -1,6 +1,6 @@
<input type="number"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
name="<?php echo $view->escape($full_name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>

View File

@ -1,8 +1,9 @@
<input type="text"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
name="<?php echo $view->escape($full_name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>
<?php if ($max_length): ?>maxlength="<?php echo $max_length ?>"<?php endif ?>
<?php if ($pattern): ?>pattern="<?php echo $pattern ?>"<?php endif ?>
/>

View File

@ -1,8 +1,9 @@
<input type="password"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
name="<?php echo $view->escape($full_name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>
<?php if ($max_length): ?>maxlength="<?php echo $max_length ?>"<?php endif ?>
<?php if ($pattern): ?>pattern="<?php echo $pattern ?>"<?php endif ?>
/>

View File

@ -1,6 +1,6 @@
<input type="radio"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
name="<?php echo $view->escape($full_name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>

View File

@ -0,0 +1,8 @@
<input type="search"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>
<?php if ($max_length): ?>maxlength="<?php echo $max_length ?>"<?php endif ?>
/>

View File

@ -1,8 +1,9 @@
<input type="text"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
name="<?php echo $view->escape($full_name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>
<?php if ($max_length): ?>maxlength="<?php echo $max_length ?>"<?php endif ?>
<?php if ($pattern): ?>pattern="<?php echo $pattern ?>"<?php endif ?>
/>

View File

@ -1,6 +1,6 @@
<textarea
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
name="<?php echo $view->escape($full_name) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>
><?php echo $view->escape($value) ?></textarea>

View File

@ -1,6 +1,6 @@
<input type="url"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
name="<?php echo $view->escape($full_name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>

View File

@ -135,7 +135,7 @@ class FormHelper extends Helper
$variables
);
array_push($this->viewStack, $view);
$this->viewStack[] = $view;
$html = $this->engine->render($template, $this->varStack[$view]);

View File

@ -2,6 +2,7 @@
$container->loadFromExtension('framework', array(
'secret' => 's3cr3t',
'form' => null,
'csrf_protection' => array(
'enabled' => true,
'field_name' => '_csrf',

View File

@ -8,6 +8,7 @@
<framework:config secret="s3cr3t">
<framework:csrf-protection enabled="true" field-name="_csrf" />
<framework:form />
<framework:esi enabled="true" />
<framework:profiler only-exceptions="true" />
<framework:router cache-warmer="true" resource="%kernel.root_dir%/config/routing.xml" type="xml" />

View File

@ -1,5 +1,6 @@
framework:
secret: s3cr3t
form: ~
csrf_protection:
enabled: true
field_name: _csrf

View File

@ -53,7 +53,7 @@ class LoggerChannelPass implements CompilerPassInterface
$logger = new DefinitionDecorator('monolog.logger_prototype');
$logger->replaceArgument(0, $channel);
$container->setDefinition($loggerId, $logger);
array_push($this->channels, $channel);
$this->channels[] = $channel;
}
}
}

View File

@ -140,7 +140,7 @@ class MonologExtension extends Extension
case 'fingers_crossed':
$handler['action_level'] = is_int($handler['action_level']) ? $handler['action_level'] : constant('Monolog\Logger::'.strtoupper($handler['action_level']));
$nestedHandlerId = $this->getHandlerId($handler['handler']);
array_push($this->nestedHandlers, $nestedHandlerId);
$this->nestedHandlers[] = $nestedHandlerId;
$definition->setArguments(array(
new Reference($nestedHandlerId),
@ -153,7 +153,7 @@ class MonologExtension extends Extension
case 'buffer':
$nestedHandlerId = $this->getHandlerId($handler['handler']);
array_push($this->nestedHandlers, $nestedHandlerId);
$this->nestedHandlers[] = $nestedHandlerId;
$definition->setArguments(array(
new Reference($nestedHandlerId),
@ -167,7 +167,7 @@ class MonologExtension extends Extension
$references = array();
foreach ($handler['members'] as $nestedHandler) {
$nestedHandlerId = $this->getHandlerId($nestedHandler);
array_push($this->nestedHandlers, $nestedHandlerId);
$this->nestedHandlers[] = $nestedHandlerId;
$references[] = new Reference($nestedHandlerId);
}

View File

@ -30,7 +30,7 @@ class FormLoginFactory extends AbstractFactory
$this->addOption('username_parameter', '_username');
$this->addOption('password_parameter', '_password');
$this->addOption('csrf_parameter', '_csrf_token');
$this->addOption('csrf_page_id', 'form_login');
$this->addOption('intention', 'authenticate');
$this->addOption('post_only', true);
}

View File

@ -53,12 +53,34 @@ class TemplatingExtension extends \Twig_Extension
{
return array(
'asset' => new \Twig_Function_Method($this, 'getAssetUrl'),
'assets_version' => new \Twig_Function_Method($this, 'getAssetsVersion'),
);
}
public function getAssetUrl($location, $packageName = null)
/**
* Returns the public path of an asset
*
* Absolute paths (i.e. http://...) are returned unmodified.
*
* @param string $path A public path
* @param string $packageName The name of the asset package to use
*
* @return string A public path which takes into account the base path and URL path
*/
public function getAssetUrl($path, $packageName = null)
{
return $this->container->get('templating.helper.assets')->getUrl($location, $packageName);
return $this->container->get('templating.helper.assets')->getUrl($path, $packageName);
}
/**
* Returns the version of the assets in a package
*
* @param string $packageName
* @return int
*/
public function getAssetsVersion($packageName = null)
{
return $this->container->get('templating.helper.assets')->getVersion($packageName);
}
/**

View File

@ -43,11 +43,18 @@
{% block attributes %}
{% spaceless %}
id="{{ id }}" name="{{ name }}"{% if read_only %} disabled="disabled"{% endif %}{% if required %} required="required"{% endif %}{% if max_length %} maxlength="{{ max_length }}"{% endif %}
id="{{ id }}" name="{{ full_name }}"{% if read_only %} disabled="disabled"{% endif %}{% if required %} required="required"{% endif %}{% if max_length %} maxlength="{{ max_length }}"{% endif %}{% if pattern %} pattern="{{ pattern }}"{% endif %}
{% for attrname,attrvalue in attr %}{{attrname}}="{{attrvalue}}" {% endfor %}
{% endspaceless %}
{% endblock attributes %}
{% block container_attributes %}
{% spaceless %}
id="{{ id }}"
{% for attrname,attrvalue in attr %}{{attrname}}="{{attrvalue}}" {% endfor %}
{% endspaceless %}
{% endblock container_attributes %}
{% block field_widget %}
{% spaceless %}
{% set type = type|default('text') %}
@ -103,7 +110,7 @@
{% block choice_widget %}
{% spaceless %}
{% if expanded %}
<div {{ block('attributes') }}>
<div {{ block('container_attributes') }}>
{% for choice, child in form %}
{{ form_widget(child) }}
{{ form_label(child) }}
@ -140,7 +147,7 @@
{% block datetime_widget %}
{% spaceless %}
<div {{ block('attributes') }}>
<div {{ block('container_attributes') }}>
{{ form_errors(form.date) }}
{{ form_errors(form.time) }}
{{ form_widget(form.date) }}
@ -154,7 +161,7 @@
{% if widget == 'single-text' %}
{{ block('text_widget') }}
{% else %}
<div {{ block('attributes') }}>
<div {{ block('container_attributes') }}>
{{ date_pattern|replace({
'{{ year }}': form_widget(form.year),
'{{ month }}': form_widget(form.month),
@ -167,7 +174,7 @@
{% block time_widget %}
{% spaceless %}
<div {{ block('attributes') }}>
<div {{ block('container_attributes') }}>
{{ form_widget(form.hour, { 'attr': { 'size': '1' } }) }}:{{ form_widget(form.minute, { 'attr': { 'size': '1' } }) }}{% if with_seconds %}:{{ form_widget(form.second, { 'attr': { 'size': '1' } }) }}{% endif %}
</div>
{% endspaceless %}
@ -201,6 +208,13 @@
{% endspaceless %}
{% endblock url_widget %}
{% block search_widget %}
{% spaceless %}
{% set type = type|default('search') %}
{{ block('field_widget') }}
{% endspaceless %}
{% endblock search_widget %}
{% block percent_widget %}
{% spaceless %}
{% set type = type|default('text') %}
@ -210,7 +224,7 @@
{% block file_widget %}
{% spaceless %}
<div {{ block('attributes') }}>
<div {{ block('container_attributes') }}>
{{ form_widget(form.file) }}
{{ form_widget(form.token) }}
{{ form_widget(form.name) }}
@ -244,7 +258,7 @@
{% block form_widget %}
{% spaceless %}
<div {{ block('attributes') }}>
<div {{ block('container_attributes') }}>
{{ block('field_rows') }}
{{ form_rest(form) }}
</div>

View File

@ -44,7 +44,7 @@
{% block form_widget %}
{% spaceless %}
<table {{ block('attributes') }}>
<table {{ block('container_attributes') }}>
{{ block('field_rows') }}
{{ form_rest(form) }}
</table>

View File

@ -49,7 +49,8 @@ class ConfigCache
/**
* Checks if the cache is still fresh.
*
* This method always returns true is debug is on and the cache file exists.
* This method always returns true when debug is off and the
* cache file exists.
*
* @return Boolean true if the cache is fresh, false otherwise
*/

View File

@ -70,16 +70,20 @@ class DialogHelper extends Helper
/**
* Asks for a value and validates the response.
*
* The validator receives the data to validate. It must return the
* validated data when the data is valid and throw an exception
* otherwise.
*
* @param OutputInterface $output
* @param string|array $question
* @param Closure $validator
* @param callback $validator A PHP callback
* @param integer $attempts Max number of times to ask before giving up (false by default, which means infinite)
*
* @return mixed
*
* @throws \Exception When any of the validator returns an error
*/
public function askAndValidate(OutputInterface $output, $question, \Closure $validator, $attempts = false)
public function askAndValidate(OutputInterface $output, $question, $validator, $attempts = false)
{
// @codeCoverageIgnoreStart
$error = null;
@ -91,7 +95,7 @@ class DialogHelper extends Helper
$value = $this->ask($output, $question, null);
try {
return $validator($value);
return call_user_func($validator, $value);
} catch (\Exception $error) {
}
}

View File

@ -4,7 +4,7 @@ namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\NonExistentServiceException;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@ -47,7 +47,7 @@ class CheckExceptionOnInvalidReferenceBehaviorPass implements CompilerPassInterf
$destId = (string) $argument;
if (!$this->container->has($destId)) {
throw new NonExistentServiceException($destId, $this->sourceId);
throw new ServiceNotFoundException($destId, $this->sourceId);
}
}
}

View File

@ -12,7 +12,7 @@
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\NonExistentParameterException;
use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
/**
* Resolves all parameter placeholders "%somevalue%" to their real values.
@ -45,7 +45,7 @@ class ResolveParameterPlaceHoldersPass implements CompilerPassInterface
$definition->setMethodCalls($calls);
$definition->setProperties($this->resolveValue($definition->getProperties()));
} catch (NonExistentParameterException $e) {
} catch (ParameterNotFoundException $e) {
$e->setSourceId($id);
throw $e;
@ -62,7 +62,7 @@ class ResolveParameterPlaceHoldersPass implements CompilerPassInterface
foreach ($parameterBag->all() as $key => $value) {
try {
$parameterBag->set($key, $this->resolveValue($value));
} catch (NonExistentParameterException $e) {
} catch (ParameterNotFoundException $e) {
$e->setSourceKey($key);
throw $e;

View File

@ -11,7 +11,7 @@
namespace Symfony\Component\DependencyInjection;
use Symfony\Component\DependencyInjection\Exception\NonExistentServiceException;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\DependencyInjection\Exception\CircularReferenceException;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
@ -238,7 +238,7 @@ class Container implements ContainerInterface
}
if (self::EXCEPTION_ON_INVALID_REFERENCE === $invalidBehavior) {
throw new NonExistentServiceException($id);
throw new ServiceNotFoundException($id);
}
}

View File

@ -12,11 +12,11 @@
namespace Symfony\Component\DependencyInjection\Exception;
/**
* Exception
* ExceptionInterface
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Bulat Shakirzyanov <bulat@theopenskyproject.com>
*/
interface Exception
interface ExceptionInterface
{
}

View File

@ -18,6 +18,6 @@ use \InvalidArgumentException as BaseInvalidArgumentException;
*
* @author Bulat Shakirzyanov <bulat@theopenskyproject.com>
*/
class InvalidArgumentException extends BaseInvalidArgumentException implements Exception
class InvalidArgumentException extends BaseInvalidArgumentException implements ExceptionInterface
{
}

View File

@ -16,7 +16,7 @@ namespace Symfony\Component\DependencyInjection\Exception;
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class NonExistentParameterException extends InvalidArgumentException
class ParameterNotFoundException extends InvalidArgumentException
{
private $key;
private $sourceId;

View File

@ -7,6 +7,6 @@ namespace Symfony\Component\DependencyInjection\Exception;
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class RuntimeException extends \RuntimeException implements Exception
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}

View File

@ -7,7 +7,7 @@ namespace Symfony\Component\DependencyInjection\Exception;
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class NonExistentServiceException extends InvalidArgumentException
class ServiceNotFoundException extends InvalidArgumentException
{
private $id;
private $sourceId;

View File

@ -11,7 +11,7 @@
namespace Symfony\Component\DependencyInjection\ParameterBag;
use Symfony\Component\DependencyInjection\Exception\NonExistentParameterException;
use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
/**
*
@ -69,14 +69,14 @@ class ParameterBag implements ParameterBagInterface
*
* @return mixed The parameter value
*
* @throws \InvalidArgumentException if the parameter is not defined
* @throws ParameterNotFoundException if the parameter is not defined
*/
public function get($name)
{
$name = strtolower($name);
if (!array_key_exists($name, $this->parameters)) {
throw new NonExistentParameterException($name);
throw new ParameterNotFoundException($name);
}
return $this->parameters[$name];
@ -113,7 +113,7 @@ class ParameterBag implements ParameterBagInterface
foreach ($this->parameters as $key => $value) {
try {
$this->parameters[$key] = $this->resolveValue($value);
} catch (NonExistentParameterException $e) {
} catch (ParameterNotFoundException $e) {
$e->setSourceKey($key);
throw $e;
@ -126,7 +126,7 @@ class ParameterBag implements ParameterBagInterface
*
* @param mixed $value A value
*
* @throws \InvalidArgumentException if a placeholder references a parameter that does not exist
* @throws ParameterNotFoundException if a placeholder references a parameter that does not exist
*/
public function resolveValue($value)
{

View File

@ -11,7 +11,7 @@
namespace Symfony\Component\DependencyInjection\ParameterBag;
use Symfony\Component\DependencyInjection\Exception\NonExistentParameterException;
use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
/**
* ParameterBagInterface.
@ -46,7 +46,7 @@ interface ParameterBagInterface
*
* @return mixed The parameter value
*
* @throws NonExistentParameterException if the parameter is not defined
* @throws ParameterNotFoundException if the parameter is not defined
*/
function get($name);
@ -66,4 +66,18 @@ interface ParameterBagInterface
* @return Boolean true if the parameter name is defined, false otherwise
*/
function has($name);
/**
* Replaces parameter placeholders (%name%) by their values for all parameters.
*/
function resolve();
/**
* Replaces parameter placeholders (%name%) by their values.
*
* @param mixed $value A value
*
* @throws ParameterNotFoundException if a placeholder references a parameter that does not exist
*/
function resolveValue($value);
}

View File

@ -52,6 +52,7 @@ class CoreExtension extends AbstractExtension
new Type\PercentType(),
new Type\RadioType(),
new Type\RepeatedType(),
new Type\SearchType(),
new Type\TextareaType(),
new Type\TextType(),
new Type\TimeType(),

View File

@ -35,6 +35,11 @@ class ResizeFormListener implements EventSubscriberInterface
*/
private $type;
/**
* @var array
*/
private $options;
/**
* Whether children could be added to the group
* @var Boolean
@ -47,12 +52,13 @@ class ResizeFormListener implements EventSubscriberInterface
*/
private $allowDelete;
public function __construct(FormFactoryInterface $factory, $type, $allowAdd = false, $allowDelete = false)
public function __construct(FormFactoryInterface $factory, $type, array $options = array(), $allowAdd = false, $allowDelete = false)
{
$this->factory = $factory;
$this->type = $type;
$this->allowAdd = $allowAdd;
$this->allowDelete = $allowDelete;
$this->options = $options;
}
public static function getSubscribedEvents()
@ -86,9 +92,9 @@ class ResizeFormListener implements EventSubscriberInterface
// Then add all rows again in the correct order
foreach ($data as $name => $value) {
$form->add($this->factory->createNamed($this->type, $name, null, array(
$form->add($this->factory->createNamed($this->type, $name, null, array_replace(array(
'property_path' => '['.$name.']',
)));
), $this->options)));
}
}
@ -118,9 +124,9 @@ class ResizeFormListener implements EventSubscriberInterface
if ($this->allowAdd) {
foreach ($data as $name => $value) {
if (!$form->has($name)) {
$form->add($this->factory->createNamed($this->type, $name, null, array(
$form->add($this->factory->createNamed($this->type, $name, null, array_replace(array(
'property_path' => '['.$name.']',
)));
), $this->options)));
}
}
}
@ -149,4 +155,4 @@ class ResizeFormListener implements EventSubscriberInterface
$event->setData($data);
}
}
}

View File

@ -21,14 +21,18 @@ class CheckboxType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->appendClientTransformer(new BooleanToStringTransformer())
->setAttribute('value', $options['value']);
$builder
->appendClientTransformer(new BooleanToStringTransformer())
->setAttribute('value', $options['value'])
;
}
public function buildView(FormView $view, FormInterface $form)
{
$view->set('value', $form->getAttribute('value'));
$view->set('checked', (Boolean) $form->getData());
$view
->set('value', $form->getAttribute('value'))
->set('checked', (Boolean) $form->getData())
;
}
public function getDefaultOptions(array $options)

View File

@ -46,15 +46,15 @@ class ChoiceType extends AbstractType
foreach ($options['choices'] as $choice => $value) {
if ($options['multiple']) {
$builder->add((string)$choice, 'checkbox', array(
'value' => $choice,
'label' => $value,
$builder->add((string) $choice, 'checkbox', array(
'value' => $choice,
'label' => $value,
// The user can check 0 or more checkboxes. If required
// is true, he is required to check all of them.
'required' => false,
'required' => false,
));
} else {
$builder->add((string)$choice, 'radio', array(
$builder->add((string) $choice, 'radio', array(
'value' => $choice,
'label' => $value,
));
@ -62,17 +62,21 @@ class ChoiceType extends AbstractType
}
}
$builder->setAttribute('choice_list', $options['choice_list'])
$builder
->setAttribute('choice_list', $options['choice_list'])
->setAttribute('preferred_choices', $options['preferred_choices'])
->setAttribute('multiple', $options['multiple'])
->setAttribute('expanded', $options['expanded']);
->setAttribute('expanded', $options['expanded'])
;
if ($options['expanded']) {
if ($options['multiple']) {
$builder->appendClientTransformer(new ArrayToBooleanChoicesTransformer($options['choice_list']));
} else {
$builder->appendClientTransformer(new ScalarToBooleanChoicesTransformer($options['choice_list']));
$builder->addEventSubscriber(new FixRadioInputListener(), 10);
$builder
->appendClientTransformer(new ScalarToBooleanChoicesTransformer($options['choice_list']))
->addEventSubscriber(new FixRadioInputListener(), 10)
;
}
} else {
if ($options['multiple']) {
@ -89,18 +93,20 @@ class ChoiceType extends AbstractType
$choices = $form->getAttribute('choice_list')->getChoices();
$preferred = array_flip($form->getAttribute('preferred_choices'));
$view->set('multiple', $form->getAttribute('multiple'));
$view->set('expanded', $form->getAttribute('expanded'));
$view->set('preferred_choices', array_intersect_key($choices, $preferred));
$view->set('choices', array_diff_key($choices, $preferred));
$view->set('separator', '-------------------');
$view->set('empty_value', '');
$view
->set('multiple', $form->getAttribute('multiple'))
->set('expanded', $form->getAttribute('expanded'))
->set('preferred_choices', array_intersect_key($choices, $preferred))
->set('choices', array_diff_key($choices, $preferred))
->set('separator', '-------------------')
->set('empty_value', '')
;
if ($view->get('multiple') && !$view->get('expanded')) {
// Add "[]" to the name in case a select tag with multiple options is
// displayed. Otherwise only one of the selected options is sent in the
// POST request.
$view->set('name', $view->get('name').'[]');
$view->set('full_name', $view->get('full_name').'[]');
}
}
@ -110,14 +116,13 @@ class ChoiceType extends AbstractType
$expanded = isset($options['expanded']) && $options['expanded'];
return array(
'multiple' => false,
'expanded' => false,
'choice_list' => null,
'choices' => array(),
'multiple' => false,
'expanded' => false,
'choice_list' => null,
'choices' => array(),
'preferred_choices' => array(),
'csrf_protection' => false,
'empty_data' => $multiple || $expanded ? array() : '',
'error_bubbling' => false,
'empty_data' => $multiple || $expanded ? array() : '',
'error_bubbling' => false,
);
}

View File

@ -22,33 +22,43 @@ class CollectionType extends AbstractType
public function buildForm(FormBuilder $builder, array $options)
{
if ($options['allow_add'] && $options['prototype']) {
$builder->add('$$name$$', $options['type'], array(
$builder->add('$$name$$', $options['type'], array_replace(array(
'property_path' => false,
'required' => false,
));
), $options['options']));
}
$listener = new ResizeFormListener($builder->getFormFactory(),
$options['type'], $options['allow_add'], $options['allow_delete']);
$listener = new ResizeFormListener(
$builder->getFormFactory(),
$options['type'],
$options['options'],
$options['allow_add'],
$options['allow_delete']
);
$builder->addEventSubscriber($listener)
$builder
->addEventSubscriber($listener)
->setAttribute('allow_add', $options['allow_add'])
->setAttribute('allow_delete', $options['allow_delete']);
->setAttribute('allow_delete', $options['allow_delete'])
;
}
public function buildView(FormView $view, FormInterface $form)
{
$view->set('allow_add', $form->getAttribute('allow_add'));
$view->set('allow_delete', $form->getAttribute('allow_delete'));
$view
->set('allow_add', $form->getAttribute('allow_add'))
->set('allow_delete', $form->getAttribute('allow_delete'))
;
}
public function getDefaultOptions(array $options)
{
return array(
'allow_add' => false,
'allow_delete' => false,
'prototype' => true,
'type' => 'text',
'allow_add' => false,
'allow_delete' => false,
'prototype' => true,
'type' => 'text',
'options' => array(),
);
}

View File

@ -37,9 +37,6 @@ class DateTimeType extends AbstractType
'with_seconds',
)));
if (isset($options['date_pattern'])) {
$dateOptions['pattern'] = $options['date_pattern'];
}
if (isset($options['date_widget'])) {
$dateOptions['widget'] = $options['date_widget'];
}
@ -49,9 +46,6 @@ class DateTimeType extends AbstractType
$dateOptions['input'] = 'array';
if (isset($options['time_pattern'])) {
$timeOptions['pattern'] = $options['time_pattern'];
}
if (isset($options['time_widget'])) {
$timeOptions['widget'] = $options['time_widget'];
}
@ -66,7 +60,8 @@ class DateTimeType extends AbstractType
$timeParts[] = 'second';
}
$builder->appendClientTransformer(new DataTransformerChain(array(
$builder
->appendClientTransformer(new DataTransformerChain(array(
new DateTimeToArrayTransformer($options['data_timezone'], $options['user_timezone'], $parts),
new ArrayToPartsTransformer(array(
'date' => array('year', 'month', 'day'),
@ -74,7 +69,8 @@ class DateTimeType extends AbstractType
)),
)))
->add('date', 'date', $dateOptions)
->add('time', 'time', $timeOptions);
->add('time', 'time', $timeOptions)
;
if ($options['input'] === 'string') {
$builder->appendNormTransformer(new ReversedTransformer(
@ -94,52 +90,50 @@ class DateTimeType extends AbstractType
public function getDefaultOptions(array $options)
{
return array(
'input' => 'datetime',
'with_seconds' => false,
'input' => 'datetime',
'with_seconds' => false,
'data_timezone' => null,
'user_timezone' => null,
// Don't modify \DateTime classes by reference, we treat
// them like immutable value objects
'by_reference' => false,
'date_pattern' => null,
'date_widget' => null,
'date_format' => null,
'time_pattern' => null,
'time_widget' => null,
'by_reference' => false,
'date_widget' => null,
'date_format' => null,
'time_widget' => null,
/* Defaults for date field */
'years' => range(date('Y') - 5, date('Y') + 5),
'months' => range(1, 12),
'days' => range(1, 31),
'years' => range(date('Y') - 5, date('Y') + 5),
'months' => range(1, 12),
'days' => range(1, 31),
/* Defaults for time field */
'hours' => range(0, 23),
'minutes' => range(0, 59),
'seconds' => range(0, 59),
'with_seconds' => false,
'hours' => range(0, 23),
'minutes' => range(0, 59),
'seconds' => range(0, 59),
'with_seconds' => false,
);
}
public function getAllowedOptionValues(array $options)
{
return array(
'input' => array(
'input' => array(
'datetime',
'string',
'timestamp',
'array',
),
'date_widget' => array(
'date_widget' => array(
null, // inherit default from DateType
'text',
'choice',
),
'date_format' => array(
'date_format' => array(
null, // inherit default from DateType
\IntlDateFormatter::FULL,
\IntlDateFormatter::LONG,
\IntlDateFormatter::MEDIUM,
\IntlDateFormatter::SHORT,
),
'time_widget' => array(
'time_widget' => array(
null, // inherit default from TimeType
'text',
'choice',

View File

@ -60,10 +60,12 @@ class DateType extends AbstractType
);
}
$builder->add('year', $widget, $yearOptions)
$builder
->add('year', $widget, $yearOptions)
->add('month', $widget, $monthOptions)
->add('day', $widget, $dayOptions)
->appendClientTransformer(new DateTimeToArrayTransformer($options['data_timezone'], $options['user_timezone'], array('year', 'month', 'day')));
->appendClientTransformer(new DateTimeToArrayTransformer($options['data_timezone'], $options['user_timezone'], array('year', 'month', 'day')))
;
}
if ($options['input'] === 'string') {
@ -84,7 +86,8 @@ class DateType extends AbstractType
$builder
->setAttribute('formatter', $formatter)
->setAttribute('widget', $options['widget']);
->setAttribute('widget', $options['widget'])
;
}
public function buildViewBottomUp(FormView $view, FormInterface $form)
@ -111,37 +114,35 @@ class DateType extends AbstractType
public function getDefaultOptions(array $options)
{
return array(
'years' => range(date('Y') - 5, date('Y') + 5),
'months' => range(1, 12),
'days' => range(1, 31),
'widget' => 'choice',
'input' => 'datetime',
'pattern' => null,
'format' => \IntlDateFormatter::MEDIUM,
'data_timezone' => null,
'user_timezone' => null,
'csrf_protection' => false,
'years' => range(date('Y') - 5, date('Y') + 5),
'months' => range(1, 12),
'days' => range(1, 31),
'widget' => 'choice',
'input' => 'datetime',
'format' => \IntlDateFormatter::MEDIUM,
'data_timezone' => null,
'user_timezone' => null,
// Don't modify \DateTime classes by reference, we treat
// them like immutable value objects
'by_reference' => false,
'by_reference' => false,
);
}
public function getAllowedOptionValues(array $options)
{
return array(
'input' => array(
'input' => array(
'datetime',
'string',
'timestamp',
'array',
),
'widget' => array(
'widget' => array(
'single-text',
'text',
'choice',
),
'format' => array(
'format' => array(
\IntlDateFormatter::FULL,
\IntlDateFormatter::LONG,
\IntlDateFormatter::MEDIUM,

View File

@ -35,7 +35,8 @@ class FieldType extends AbstractType
$options['property_path'] = new PropertyPath($options['property_path']);
}
$builder->setRequired($options['required'])
$builder
->setRequired($options['required'])
->setReadOnly($options['read_only'])
->setErrorBubbling($options['error_bubbling'])
->setEmptyData($options['empty_data'])
@ -43,9 +44,11 @@ class FieldType extends AbstractType
->setAttribute('property_path', $options['property_path'])
->setAttribute('error_mapping', $options['error_mapping'])
->setAttribute('max_length', $options['max_length'])
->setAttribute('pattern', $options['pattern'])
->setAttribute('label', $options['label'] ?: $this->humanize($builder->getName()))
->setData($options['data'])
->addValidator(new DefaultValidator());
->addValidator(new DefaultValidator())
;
if ($options['trim']) {
$builder->addEventSubscriber(new TrimListener());
@ -56,7 +59,7 @@ class FieldType extends AbstractType
{
if ($view->hasParent()) {
$parentId = $view->getParent()->get('id');
$parentName = $view->getParent()->get('name');
$parentName = $view->getParent()->get('full_name');
$id = sprintf('%s_%s', $parentId, $form->getName());
$name = sprintf('%s[%s]', $parentName, $form->getName());
} else {
@ -64,18 +67,22 @@ class FieldType extends AbstractType
$name = $form->getName();
}
$view->set('form', $view);
$view->set('id', $id);
$view->set('name', $name);
$view->set('errors', $form->getErrors());
$view->set('value', $form->getClientData());
$view->set('read_only', $form->isReadOnly());
$view->set('required', $form->isRequired());
$view->set('max_length', $form->getAttribute('max_length'));
$view->set('size', null);
$view->set('label', $form->getAttribute('label'));
$view->set('multipart', false);
$view->set('attr', array());
$view
->set('form', $view)
->set('id', $id)
->set('name', $form->getName())
->set('full_name', $name)
->set('errors', $form->getErrors())
->set('value', $form->getClientData())
->set('read_only', $form->isReadOnly())
->set('required', $form->isRequired())
->set('max_length', $form->getAttribute('max_length'))
->set('pattern', $form->getAttribute('pattern'))
->set('size', null)
->set('label', $form->getAttribute('label'))
->set('multipart', false)
->set('attr', array())
;
$types = array();
foreach (array_reverse((array) $form->getTypes()) as $type) {
@ -87,17 +94,18 @@ class FieldType extends AbstractType
public function getDefaultOptions(array $options)
{
$defaultOptions = array(
'data' => null,
'data_class' => null,
'trim' => true,
'required' => true,
'read_only' => false,
'max_length' => null,
'property_path' => null,
'by_reference' => true,
'error_bubbling' => false,
'error_mapping' => array(),
'label' => null,
'data' => null,
'data_class' => null,
'trim' => true,
'required' => true,
'read_only' => false,
'max_length' => null,
'pattern' => null,
'property_path' => null,
'by_reference' => true,
'error_bubbling' => false,
'error_mapping' => array(),
'label' => null,
);
$class = isset($options['data_class']) ? $options['data_class'] : null;

View File

@ -44,20 +44,21 @@ class FileType extends AbstractType
->add('file', 'field')
->add('token', 'hidden')
->add('name', 'hidden')
->add('originalName', 'hidden');
->add('originalName', 'hidden')
;
}
public function buildViewBottomUp(FormView $view, FormInterface $form)
{
$view->set('multipart', true);
$view['file']->set('type', 'file');
$view['file']->set('value', '');
}
public function getDefaultOptions(array $options)
{
return array(
'type' => 'string',
'csrf_protection' => false,
'type' => 'string',
);
}

View File

@ -42,10 +42,10 @@ class FormType extends AbstractType
public function getDefaultOptions(array $options)
{
$defaultOptions = array(
'virtual' => false,
'virtual' => false,
// Errors in forms bubble by default, so that form errors will
// end up as global errors in the root form
'error_bubbling' => true,
'error_bubbling' => true,
);
if (empty($options['data_class'])) {

View File

@ -19,15 +19,20 @@ class IntegerType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->appendClientTransformer(new IntegerToLocalizedStringTransformer($options['precision'], $options['grouping'], $options['rounding_mode']));
$builder->appendClientTransformer(
new IntegerToLocalizedStringTransformer(
$options['precision'],
$options['grouping'],
$options['rounding_mode']
));
}
public function getDefaultOptions(array $options)
{
return array(
// default precision is locale specific (usually around 3)
'precision' => null,
'grouping' => false,
'precision' => null,
'grouping' => false,
// Integer cast rounds towards 0, so do the same when displaying fractions
'rounding_mode' => \NumberFormatter::ROUND_DOWN,
);

View File

@ -23,8 +23,15 @@ class MoneyType extends AbstractType
public function buildForm(FormBuilder $builder, array $options)
{
$builder->appendClientTransformer(new MoneyToLocalizedStringTransformer($options['precision'], $options['grouping'], null, $options['divisor']))
->setAttribute('currency', $options['currency']);
$builder
->appendClientTransformer(new MoneyToLocalizedStringTransformer(
$options['precision'],
$options['grouping'],
null,
$options['divisor']
))
->setAttribute('currency', $options['currency'])
;
}
public function buildView(FormView $view, FormInterface $form)
@ -36,9 +43,9 @@ class MoneyType extends AbstractType
{
return array(
'precision' => 2,
'grouping' => false,
'divisor' => 1,
'currency' => 'EUR',
'grouping' => false,
'divisor' => 1,
'currency' => 'EUR',
);
}

View File

@ -19,15 +19,19 @@ class NumberType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->appendClientTransformer(new NumberToLocalizedStringTransformer($options['precision'], $options['grouping'], $options['rounding_mode']));
$builder->appendClientTransformer(new NumberToLocalizedStringTransformer(
$options['precision'],
$options['grouping'],
$options['rounding_mode']
));
}
public function getDefaultOptions(array $options)
{
return array(
// default precision is locale specific (usually around 3)
'precision' => null,
'grouping' => false,
'precision' => null,
'grouping' => false,
'rounding_mode' => \NumberFormatter::ROUND_HALFUP,
);
}

View File

@ -26,7 +26,7 @@ class PercentType extends AbstractType
{
return array(
'precision' => 0,
'type' => 'fractional',
'type' => 'fractional',
);
}

View File

@ -21,17 +21,21 @@ class RadioType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->appendClientTransformer(new BooleanToStringTransformer())
->setAttribute('value', $options['value']);
$builder
->appendClientTransformer(new BooleanToStringTransformer())
->setAttribute('value', $options['value'])
;
}
public function buildView(FormView $view, FormInterface $form)
{
$view->set('value', $form->getAttribute('value'));
$view->set('checked', (Boolean) $form->getData());
$view
->set('value', $form->getAttribute('value'))
->set('checked', (Boolean) $form->getData())
;
if ($view->hasParent()) {
$view->set('name', $view->getParent()->get('name'));
$view->set('full_name', $view->getParent()->get('full_name'));
}
}

View File

@ -19,23 +19,24 @@ class RepeatedType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->appendClientTransformer(new ValueToDuplicatesTransformer(array(
$builder
->appendClientTransformer(new ValueToDuplicatesTransformer(array(
$options['first_name'],
$options['second_name'],
)))
->add($options['first_name'], $options['type'], $options['options'])
->add($options['second_name'], $options['type'], $options['options']);
->add($options['second_name'], $options['type'], $options['options'])
;
}
public function getDefaultOptions(array $options)
{
return array(
'type' => 'text',
'options' => array(),
'first_name' => 'first',
'second_name' => 'second',
'csrf_protection' => false,
'error_bubbling' => false,
'type' => 'text',
'options' => array(),
'first_name' => 'first',
'second_name' => 'second',
'error_bubbling' => false,
);
}

View File

@ -0,0 +1,27 @@
<?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\Extension\Core\Type;
use Symfony\Component\Form\AbstractType;
class SearchType extends AbstractType
{
public function getParent(array $options)
{
return 'text';
}
public function getName()
{
return 'search';
}
}

View File

@ -66,33 +66,39 @@ class TimeType extends AbstractType
}
$builder
->appendClientTransformer(new DateTimeToArrayTransformer($options['data_timezone'], $options['user_timezone'], $parts, $options['widget'] === 'text'))
->appendClientTransformer(new DateTimeToArrayTransformer(
$options['data_timezone'],
$options['user_timezone'],
$parts,
$options['widget'] === 'text'
))
->setAttribute('widget', $options['widget'])
->setAttribute('with_seconds', $options['with_seconds']);
->setAttribute('with_seconds', $options['with_seconds'])
;
}
public function buildView(FormView $view, FormInterface $form)
{
$view->set('widget', $form->getAttribute('widget'));
$view->set('with_seconds', $form->getAttribute('with_seconds'));
$view
->set('widget', $form->getAttribute('widget'))
->set('with_seconds', $form->getAttribute('with_seconds'))
;
}
public function getDefaultOptions(array $options)
{
return array(
'hours' => range(0, 23),
'minutes' => range(0, 59),
'seconds' => range(0, 59),
'widget' => 'choice',
'input' => 'datetime',
'with_seconds' => false,
'pattern' => null,
'data_timezone' => null,
'user_timezone' => null,
'csrf_protection' => false,
'hours' => range(0, 23),
'minutes' => range(0, 59),
'seconds' => range(0, 59),
'widget' => 'choice',
'input' => 'datetime',
'with_seconds' => false,
'data_timezone' => null,
'user_timezone' => null,
// Don't modify \DateTime classes by reference, we treat
// them like immutable value objects
'by_reference' => false,
'by_reference' => false,
);
}

View File

@ -15,15 +15,26 @@ use Symfony\Component\Form\Extension\Csrf\Type;
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
use Symfony\Component\Form\AbstractExtension;
/**
* This extension protects forms by using a CSRF token
*/
class CsrfExtension extends AbstractExtension
{
private $csrfProvider;
/**
* Constructor.
*
* @param CsrfProviderInterface $csrfProvider The CSRF provider
*/
public function __construct(CsrfProviderInterface $csrfProvider)
{
$this->csrfProvider = $csrfProvider;
}
/**
* {@inheritDoc}
*/
protected function loadTypes()
{
return array(
@ -31,10 +42,18 @@ class CsrfExtension extends AbstractExtension
);
}
/**
* {@inheritDoc}
*/
protected function loadTypeExtensions()
{
return array(
new Type\ChoiceTypeCsrfExtension(),
new Type\DateTypeCsrfExtension(),
new Type\FileTypeCsrfExtension(),
new Type\FormTypeCsrfExtension(),
new Type\RepeatedTypeCsrfExtension(),
new Type\TimeTypeCsrfExtension(),
);
}
}

View File

@ -21,31 +21,29 @@ namespace Symfony\Component\Form\Extension\Csrf\CsrfProvider;
* secret information.
*
* If you want to secure a form submission against CSRF attacks, you could
* use the class name of the form as page ID. This way you make sure that the
* form can only be bound to pages that are designed to handle the form,
* that is, that use the same class name to validate the CSRF token with
* isCsrfTokenValid().
* supply an "intention" string. This way you make sure that the form can only
* be bound to pages that are designed to handle the form, that is, that use
* the same intention string to validate the CSRF token with isCsrfTokenValid().
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
*/
interface CsrfProviderInterface
{
/**
* Generates a CSRF token for a page of your application
* Generates a CSRF token for a page of your application.
*
* @param string $pageId Some value that identifies the page (for example,
* the class name of the form). Doesn't have to be
* a secret value.
* @param string $intention Some value that identifies the action intention
* (i.e. "authenticate"). Doesn't have to be a secret value.
*/
public function generateCsrfToken($pageId);
public function generateCsrfToken($intention);
/**
* Validates a CSRF token
* Validates a CSRF token.
*
* @param string $pageId The page ID used when generating the CSRF token
* @param string $token The token supplied by the browser
* @return Boolean Whether the token supplied by the browser is
* correct
* @param string $intention The intention used when generating the CSRF token
* @param string $token The token supplied by the browser
*
* @return Boolean Whether the token supplied by the browser is correct
*/
public function isCsrfTokenValid($pageId, $token);
public function isCsrfTokenValid($intention, $token);
}

View File

@ -43,17 +43,17 @@ class DefaultCsrfProvider implements CsrfProviderInterface
/**
* {@inheritDoc}
*/
public function generateCsrfToken($pageId)
public function generateCsrfToken($intention)
{
return sha1($this->secret.$pageId.$this->getSessionId());
return sha1($this->secret.$intention.$this->getSessionId());
}
/**
* {@inheritDoc}
*/
public function isCsrfTokenValid($pageId, $token)
public function isCsrfTokenValid($intention, $token)
{
return $token === $this->generateCsrfToken($pageId);
return $token === $this->generateCsrfToken($intention);
}
/**

View File

@ -0,0 +1,27 @@
<?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\Extension\Csrf\Type;
use Symfony\Component\Form\AbstractTypeExtension;
class ChoiceTypeCsrfExtension extends AbstractTypeExtension
{
public function getDefaultOptions(array $options)
{
return array('csrf_protection' => false);
}
public function getExtendedType()
{
return 'choice';
}
}

View File

@ -22,43 +22,70 @@ class CsrfType extends AbstractType
{
private $csrfProvider;
/**
* Constructor.
*
* @param CsrfProviderInterface $csrfProvider The provider to use to generate the token
*/
public function __construct(CsrfProviderInterface $csrfProvider)
{
$this->csrfProvider = $csrfProvider;
}
/**
* Builds the CSRF field.
*
* A validator is added to check the token value when the CSRF field is added to
* a root form
*
* @param FormBuilder $builder The form builder
* @param array $options The options
*/
public function buildForm(FormBuilder $builder, array $options)
{
$csrfProvider = $options['csrf_provider'];
$pageId = $options['page_id'];
$intention = $options['intention'];
$validator = function (FormInterface $form) use ($csrfProvider, $intention)
{
if ((!$form->hasParent() || $form->getParent()->isRoot())
&& !$csrfProvider->isCsrfTokenValid($intention, $form->getData())) {
$form->addError(new FormError('The CSRF token is invalid. Please try to resubmit the form'));
$form->setData($csrfProvider->generateCsrfToken($intention));
}
};
$builder
->setData($csrfProvider->generateCsrfToken($pageId))
->addValidator(new CallbackValidator(
function (FormInterface $form) use ($csrfProvider, $pageId) {
if ((!$form->hasParent() || $form->getParent()->isRoot())
&& !$csrfProvider->isCsrfTokenValid($pageId, $form->getData())) {
$form->addError(new FormError('The CSRF token is invalid. Please try to resubmit the form'));
$form->setData($csrfProvider->generateCsrfToken($pageId));
}
}
));
->setData($csrfProvider->generateCsrfToken($intention))
->addValidator(new CallbackValidator($validator))
;
}
/**
* {@inheritDoc}
*/
public function getDefaultOptions(array $options)
{
return array(
'csrf_provider' => $this->csrfProvider,
'page_id' => null,
'intention' => null,
'property_path' => false,
);
}
/**
* {@inheritDoc}
*/
public function getParent(array $options)
{
return 'hidden';
}
/**
* Returns the name of this form.
*
* @return string 'csrf'
*/
public function getName()
{
return 'csrf';

View File

@ -0,0 +1,27 @@
<?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\Extension\Csrf\Type;
use Symfony\Component\Form\AbstractTypeExtension;
class DateTypeCsrfExtension extends AbstractTypeExtension
{
public function getDefaultOptions(array $options)
{
return array('csrf_protection' => false);
}
public function getExtendedType()
{
return 'date';
}
}

View File

@ -0,0 +1,27 @@
<?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\Extension\Csrf\Type;
use Symfony\Component\Form\AbstractTypeExtension;
class FileTypeCsrfExtension extends AbstractTypeExtension
{
public function getDefaultOptions(array $options)
{
return array('csrf_protection' => false);
}
public function getExtendedType()
{
return 'file';
}
}

View File

@ -27,20 +27,34 @@ class FormTypeCsrfExtension extends AbstractTypeExtension
$this->fieldName = $fieldName;
}
/**
* Adds a CSRF field to the form when the CSRF protection is enabled.
*
* @param FormBuilder $builder The form builder
* @param array $options The options
*/
public function buildForm(FormBuilder $builder, array $options)
{
if ($options['csrf_protection']) {
$csrfOptions = array('page_id' => $options['csrf_page_id']);
$csrfOptions = array('intention' => $options['intention']);
if ($options['csrf_provider']) {
$csrfOptions['csrf_provider'] = $options['csrf_provider'];
}
$builder->add($options['csrf_field_name'], 'csrf', $csrfOptions)
->setAttribute('csrf_field_name', $options['csrf_field_name']);
$builder
->add($options['csrf_field_name'], 'csrf', $csrfOptions)
->setAttribute('csrf_field_name', $options['csrf_field_name'])
;
}
}
/**
* Removes CSRF fields from all the form views except the root one.
*
* @param FormView $view The form view
* @param FormInterface $form The form
*/
public function buildViewBottomUp(FormView $view, FormInterface $form)
{
if ($view->hasParent() && $form->hasAttribute('csrf_field_name')) {
@ -52,16 +66,22 @@ class FormTypeCsrfExtension extends AbstractTypeExtension
}
}
/**
* {@inheritDoc}
*/
public function getDefaultOptions(array $options)
{
return array(
'csrf_protection' => $this->enabled,
'csrf_field_name' => $this->fieldName,
'csrf_provider' => null,
'csrf_page_id' => get_class($this),
'csrf_protection' => $this->enabled,
'csrf_field_name' => $this->fieldName,
'csrf_provider' => null,
'intention' => 'unknown',
);
}
/**
* {@inheritDoc}
*/
public function getExtendedType()
{
return 'form';

View File

@ -0,0 +1,27 @@
<?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\Extension\Csrf\Type;
use Symfony\Component\Form\AbstractTypeExtension;
class RepeatedTypeCsrfExtension extends AbstractTypeExtension
{
public function getDefaultOptions(array $options)
{
return array('csrf_protection' => false);
}
public function getExtendedType()
{
return 'repeated';
}
}

View File

@ -0,0 +1,27 @@
<?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\Extension\Csrf\Type;
use Symfony\Component\Form\AbstractTypeExtension;
class TimeTypeCsrfExtension extends AbstractTypeExtension
{
public function getDefaultOptions(array $options)
{
return array('csrf_protection' => false);
}
public function getExtendedType()
{
return 'time';
}
}

View File

@ -63,6 +63,18 @@ class ValidatorTypeGuesser implements FormTypeGuesserInterface
});
}
/**
* @inheritDoc
*/
public function guessMinLength($class, $property)
{
$guesser = $this;
return $this->guess($class, $property, function (Constraint $constraint) use ($guesser) {
return $guesser->guessMinLengthForConstraint($constraint);
});
}
/**
* Guesses a field class name for a given constraint
*
@ -266,6 +278,28 @@ class ValidatorTypeGuesser implements FormTypeGuesserInterface
}
}
/**
* Guesses a field's minimum length based on the given constraint
*
* @param Constraint $constraint The constraint to guess for
* @return Guess The guess for the minimum length
*/
public function guessMinLengthForConstraint(Constraint $constraint)
{
switch (get_class($constraint)) {
case 'Symfony\Component\Validator\Constraints\MinLength':
return new ValueGuess(
$constraint->limit,
Guess::HIGH_CONFIDENCE
);
case 'Symfony\Component\Validator\Constraints\Min':
return new ValueGuess(
strlen((string)$constraint->limit),
Guess::HIGH_CONFIDENCE
);
}
}
/**
* Iterates over the constraints of a property, executes a constraints on
* them and returns the best guess

View File

@ -24,13 +24,6 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
*
* A form is composed of a validator schema and a widget form schema.
*
* Form also takes care of CSRF protection by default.
*
* A CSRF secret can be any random string. If set to false, it disables the
* CSRF protection, and if set to null, it forces the form to use the global
* CSRF secret. If the global CSRF secret is also null, then a random one
* is generated on the fly.
*
* To implement your own form fields, you need to have a thorough understanding
* of the data flow within a form field. A form field stores its data in three
* different representations:
@ -459,7 +452,9 @@ class Form implements \IteratorAggregate, FormInterface
public function bind($clientData)
{
if ($this->readOnly) {
return;
$this->bound = true;
return $this;
}
if (is_scalar($clientData) || null === $clientData) {
@ -558,6 +553,8 @@ class Form implements \IteratorAggregate, FormInterface
foreach ($this->validators as $validator) {
$validator->validate($this);
}
return $this;
}
/**
@ -568,6 +565,8 @@ class Form implements \IteratorAggregate, FormInterface
*
* @param Request $request The request to bind to the form
*
* @return Form This form
*
* @throws FormException if the method of the request is not one of GET, POST or PUT
*/
public function bindRequest(Request $request)
@ -588,7 +587,7 @@ class Form implements \IteratorAggregate, FormInterface
throw new FormException(sprintf('The request method "%s" is not supported', $request->getMethod()));
}
$this->bind($data);
return $this->bind($data);
}
/**
@ -604,9 +603,11 @@ class Form implements \IteratorAggregate, FormInterface
}
/**
* Adds an error to the field.
* Adds an error to this form.
*
* @see FormInterface
* @param FormError $error
*
* @return Form The current form
*/
public function addError(FormError $error)
{
@ -615,6 +616,8 @@ class Form implements \IteratorAggregate, FormInterface
} else {
$this->errors[] = $error;
}
return $this;
}
/**
@ -754,6 +757,8 @@ class Form implements \IteratorAggregate, FormInterface
* Adds a child to the form.
*
* @param FormInterface $child The FormInterface to add as a child
*
* @return Form the current form
*/
public function add(FormInterface $child)
{
@ -764,12 +769,16 @@ class Form implements \IteratorAggregate, FormInterface
if ($this->dataMapper) {
$this->dataMapper->mapDataToForm($this->getClientData(), $child);
}
return $this;
}
/**
* Removes a child from the form.
*
* @param string $name The name of the child to remove
*
* @return Form the current form
*/
public function remove($name)
{
@ -778,6 +787,8 @@ class Form implements \IteratorAggregate, FormInterface
unset($this->children[$name]);
}
return $this;
}
/**

View File

@ -372,9 +372,16 @@ class FormBuilder
return $this;
}
/**
* Clears the client transformers.
*
* @return FormBuilder The current builder
*/
public function resetClientTransformers()
{
$this->clientTransformers = array();
return $this;
}
/**
@ -610,12 +617,16 @@ class FormBuilder
* Removes the field with the given name.
*
* @param string $name
*
* @return FormBuilder The current builder
*/
public function remove($name)
{
if (isset($this->children[$name])) {
unset($this->children[$name]);
}
return $this;
}
/**

View File

@ -305,6 +305,7 @@ class FormFactory implements FormFactoryInterface
$typeGuess = $this->guesser->guessType($class, $property);
$maxLengthGuess = $this->guesser->guessMaxLength($class, $property);
$minLengthGuess = $this->guesser->guessMinLength($class, $property);
$requiredGuess = $this->guesser->guessRequired($class, $property);
$type = $typeGuess ? $typeGuess->getType() : 'text';
@ -313,6 +314,14 @@ class FormFactory implements FormFactoryInterface
$options = array_merge(array('max_length' => $maxLengthGuess->getValue()), $options);
}
if ($minLengthGuess) {
if ($maxLengthGuess) {
$options = array_merge(array('pattern' => '.{'.$minLengthGuess->getValue().','.$maxLengthGuess->getValue().'}'), $options);
} else {
$options = array_merge(array('pattern' => '.{'.$minLengthGuess->getValue().',}'), $options);
}
}
if ($requiredGuess) {
$options = array_merge(array('required' => $requiredGuess->getValue()), $options);
}

View File

@ -61,6 +61,13 @@ class FormTypeGuesserChain implements FormTypeGuesserInterface
});
}
public function guessMinLength($class, $property)
{
return $this->guess(function ($guesser) use ($class, $property) {
return $guesser->guessMinLength($class, $property);
});
}
/**
* Executes a closure for each guesser and returns the best guess from the
* return values

View File

@ -42,4 +42,14 @@ interface FormTypeGuesserInterface
* @return Guess A guess for the field's maximum length
*/
function guessMaxLength($class, $property);
/**
* Returns a guess about the field's minimum length
*
* @param string $class The fully qualified class name
* @param string $property The name of the property to guess for
* @return Guess A guess for the field's minimum length
*/
function guessMinLength($class, $property);
}

View File

@ -38,10 +38,14 @@ class FormView implements \ArrayAccess, \IteratorAggregate, \Countable
/**
* @param string $name
* @param mixed $value
*
* @return FormView The current view
*/
public function set($name, $value)
{
$this->vars[$name] = $value;
return $this;
}
/**
@ -91,10 +95,14 @@ class FormView implements \ArrayAccess, \IteratorAggregate, \Countable
*
* @param string $name The name of the attribute
* @param string $value The value
*
* @return FormView The current view
*/
public function setAttribute($name, $value)
{
$this->vars['attr'][$name] = $value;
return $this;
}
/**
@ -109,20 +117,28 @@ class FormView implements \ArrayAccess, \IteratorAggregate, \Countable
/**
* Marks the attached form as rendered
*
* @return FormView The current view
*/
public function setRendered()
{
$this->rendered = true;
return $this;
}
/**
* Sets the parent view.
*
* @param FormView $parent The parent view
*
* @return FormView The current view
*/
public function setParent(FormView $parent = null)
{
$this->parent = $parent;
return $this;
}
/**
@ -149,10 +165,14 @@ class FormView implements \ArrayAccess, \IteratorAggregate, \Countable
* Sets the children view.
*
* @param array $children The children as instances of FormView
*
* @return FormView The current view
*/
public function setChildren(array $children)
{
$this->children = $children;
return $this;
}
/**

Some files were not shown because too many files have changed in this diff Show More