Merge remote branch 'origin/master' into doctrine

This commit is contained in:
Johannes Schmitt 2011-05-19 14:34:24 +02:00
commit 5b007e3ae2
117 changed files with 1738 additions and 548 deletions

View File

@ -115,6 +115,20 @@ beta1 から beta2
'allow_delete' => true,
));
* Request::hasSession() メソッドの名前は Request::hasPreviousSession() に変更されました。hasSession() メソッドはまだ存在しますが、
セッションが以前のリクエストから開始されたかどうかではなく、リクエストがセッションオブジェクトを含んでいるかチェックするのみです。
* Serializer: NormalizerInterface の `supports()` メソッドは `supportsNormalization``supportsDenormalization` の 2 つのメソッドに分割されました。
* ParameterBag::getDeep() メソッドは削除され、ParameterBag::get() メソッドの真偽値の引数に置き換えられました。
* Serializer: `AbstractEncoder``AbstractNormalizer` はそれぞれ `SerializerAwareEncoder``SerializerAwareNormalizer` に名前が変更されました。
* Serializer: すべてのインターフェイスから `$properties` という引数が除かれました。
* Form: オプションの値である "date" タイプの "widget" の "text" は "single-text" に名前が変更されました。
"text" は現在は個々のテキストボックスを示します ("time" タイプのように) 。
PR12 から beta1
---------------
@ -133,6 +147,9 @@ PR12 から beta1
* `File::getWebPath()` メソッドと `File::rename()` メソッドは削除されました。同様に `framework.document_root` コンフィギュレーションも削除されました。
* `File::getDefaultExtension()` メソッドの名前は `File::guessExtension()` に変更されました。
また、拡張子を推測できなかった場合は null を返すように変更されました。
* `session` のコンフィギュレーションがリファクタリングされました
* `class` オプションが削除されました(代わりに `session.class` パラメータを使ってください)
@ -141,7 +158,7 @@ PR12 から beta1
* `storage_id` オプションには、サービスIDの一部ではなく、サービスIDそのものを指定するように変更されました。
* `DoctrineMigrationsBundle``DoctrineFixturesBundle` の 2 つのバンドルは、symfony コアから独立し、個別のリポジトリで管理されるようになりました。
* `DoctrineMigrationsBundle``DoctrineFixturesBundle` の 2 つのバンドルは、Symfony コアから独立し、個別のリポジトリで管理されるようになりました。
* フォームフレームワークの大きなリファクタリングが行われました(詳細はドキュメントを参照してください)

View File

@ -9,6 +9,31 @@ timeline closely anyway.
beta1 to beta2
--------------
* 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``.

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

@ -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

@ -15,6 +15,7 @@ use Symfony\Bundle\AsseticBundle\DependencyInjection\Compiler\AssetFactoryPass;
use Symfony\Bundle\AsseticBundle\DependencyInjection\Compiler\AssetManagerPass;
use Symfony\Bundle\AsseticBundle\DependencyInjection\Compiler\CheckYuiFilterPass;
use Symfony\Bundle\AsseticBundle\DependencyInjection\Compiler\FilterManagerPass;
use Symfony\Bundle\AsseticBundle\DependencyInjection\Compiler\CheckCssEmbedFilterPass;
use Symfony\Bundle\AsseticBundle\DependencyInjection\Compiler\CheckClosureFilterPass;
use Symfony\Bundle\AsseticBundle\DependencyInjection\Compiler\TemplatingPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@ -32,6 +33,7 @@ class AsseticBundle extends Bundle
parent::build($container);
$container->addCompilerPass(new CheckClosureFilterPass());
$container->addCompilerPass(new CheckCssEmbedFilterPass());
$container->addCompilerPass(new CheckYuiFilterPass());
$container->addCompilerPass(new TemplatingPass());
$container->addCompilerPass(new AssetFactoryPass());

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

@ -15,6 +15,7 @@ use Symfony\Component\Config\FileLocator;
use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
@ -79,6 +80,23 @@ class AsseticExtension extends Extension
unset($filter['file']);
}
if (isset($filter['apply_to'])) {
if (!is_array($filter['apply_to'])) {
$filter['apply_to'] = array($filter['apply_to']);
}
foreach ($filter['apply_to'] as $i => $pattern) {
$worker = new DefinitionDecorator('assetic.worker.ensure_filter');
$worker->replaceArgument(0, '/'.$pattern.'/');
$worker->replaceArgument(1, new Reference('assetic.filter.'.$name));
$worker->addTag('assetic.factory_worker');
$container->setDefinition('assetic.filter.'.$name.'.worker'.$i, $worker);
}
unset($filter['apply_to']);
}
foreach ($filter as $key => $value) {
$container->setParameter('assetic.filter.'.$name.'.'.$key, $value);
}

View File

@ -23,8 +23,9 @@ class CheckClosureFilterPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if ($container->hasDefinition('assetic.filter.closure.jar') &&
$container->getParameterBag()->resolveValue($container->getParameter('assetic.filter.closure.jar'))) {
if ($container->hasDefinition('assetic.filter.closure.jar')
&& $container->hasParameter('assetic.filter.closure.jar')
&& $container->getParameterBag()->resolveValue($container->getParameter('assetic.filter.closure.jar'))) {
$container->removeDefinition('assetic.filter.closure.api');
} elseif ($container->hasDefinition('assetic.filter.closure.api')) {
$container->removeDefinition('assetic.filter.closure.jar');

View File

@ -0,0 +1,31 @@
<?php
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Symfony\Bundle\AsseticBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* Checks that the location of the CssEmbed JAR has been configured.
*
* @author Kris Wallsmith <kris@symfony.com>
*/
class CheckCssEmbedFilterPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if ($container->hasDefinition('assetic.filter.cssembed') &&
!$container->getParameterBag()->resolveValue($container->getParameter('assetic.filter.cssembed.jar'))) {
throw new \RuntimeException('The "assetic.filters.cssembed" configuration requires a "jar" value.');
}
}
}

View File

@ -15,6 +15,7 @@
<parameter key="assetic.coalescing_directory_resource.class">Assetic\Factory\Resource\CoalescingDirectoryResource</parameter>
<parameter key="assetic.directory_resource.class">Symfony\Bundle\AsseticBundle\Factory\Resource\DirectoryResource</parameter>
<parameter key="assetic.filter_manager.class">Symfony\Bundle\AsseticBundle\FilterManager</parameter>
<parameter key="assetic.worker.ensure_filter.class">Assetic\Factory\Worker\EnsureFilterWorker</parameter>
<parameter key="assetic.node.paths" type="collection"></parameter>
<parameter key="assetic.cache_dir">%kernel.cache_dir%/assetic</parameter>
@ -54,5 +55,10 @@
<tag name="kernel.cache_warmer" priority="10" />
<argument type="service" id="service_container" />
</service>
<service id="assetic.worker.ensure_filter" class="%assetic.worker.ensure_filter.class%" abstract="true" public="false">
<argument /> <!-- pattern -->
<argument /> <!-- filter -->
</service>
</services>
</container>

View File

@ -0,0 +1,20 @@
<?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.cssembed.class">Assetic\Filter\CssEmbedFilter</parameter>
<parameter key="assetic.filter.cssembed.java">%assetic.java.bin%</parameter>
<parameter key="assetic.filter.cssembed.jar" />
</parameters>
<services>
<service id="assetic.filter.cssembed" class="%assetic.filter.cssembed.class%">
<tag name="assetic.filter" alias="cssembed" />
<argument>%assetic.filter.cssembed.jar%</argument>
<argument>%assetic.filter.cssembed.java%</argument>
</service>
</services>
</container>

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

@ -32,7 +32,8 @@ assetic:
inputs: css/widget.sass
filters: sass
filters:
sass: ~
sass:
apply_to: "\.sass$"
yui_css:
jar: %kernel.root_dir/java/yui-compressor-2.4.6.jar
twig:

View File

@ -19,7 +19,8 @@ use Symfony\Component\Console\Output\Output;
use Doctrine\ORM\Tools\Console\Command\SchemaTool\CreateCommand;
/**
* Command to create the database schema for a set of classes based on their mappings.
* Command to execute the SQL needed to generate the database schema for
* a given entity manager.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jonathan H. Wage <jonwage@gmail.com>
@ -32,17 +33,21 @@ class CreateSchemaDoctrineCommand extends CreateCommand
$this
->setName('doctrine:schema:create')
->setDescription('Executes (or dumps) the SQL needed to generate the database schema.')
->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command')
->setHelp(<<<EOT
The <info>doctrine:schema:create</info> command creates the default entity
managers schema:
The <info>doctrine:schema:create</info> command executes the SQL needed to
generate the database schema for the default entity manager:
<info>./app/console doctrine:schema:create</info>
You can also optionally specify the name of a entity manager to create the
schema for:
You can also generate the database schema for a specific entity manager:
<info>./app/console doctrine:schema:create --em=default</info>
Finally, instead of executing the SQL, you can output the SQL:
<info>./app/console doctrine:schema:create --dump-sql</info>
EOT
);
}

View File

@ -32,12 +32,17 @@ class DropSchemaDoctrineCommand extends DropCommand
$this
->setName('doctrine:schema:drop')
->setDescription('Executes (or dumps) the SQL needed to drop the current database schema.')
->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command')
->setHelp(<<<EOT
The <info>doctrine:schema:drop</info> command drops the default entity
managers schema:
The <info>doctrine:schema:drop</info> command generates the SQL needed to
drop the database schema of the default entity manager:
<info>./app/console doctrine:schema:drop</info>
<info>./app/console doctrine:schema:drop --dump-sql</info>
Alternatively, you can execute the generated queries:
<info>./app/console doctrine:schema:drop --force</info>
You can also optionally specify the name of a entity manager to drop the
schema for:

View File

@ -19,7 +19,8 @@ use Symfony\Component\Console\Output\Output;
use Doctrine\ORM\Tools\Console\Command\SchemaTool\UpdateCommand;
/**
* Command to update the database schema for a set of classes based on their mappings.
* Command to generate the SQL needed to update the database schema to match
* the current mapping information.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jonathan H. Wage <jonwage@gmail.com>
@ -32,15 +33,23 @@ class UpdateSchemaDoctrineCommand extends UpdateCommand
$this
->setName('doctrine:schema:update')
->setDescription('Executes (or dumps) the SQL needed to update the database schema to match the current mapping metadata.')
->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command')
->setHelp(<<<EOT
The <info>doctrine:schema:update</info> command updates the default entity
managers schema:
The <info>doctrine:schema:update</info> command generates the SQL needed to
synchronize the database schema with the current mapping metadata of the
default entity manager.
<info>./app/console doctrine:schema:update</info>
For example, if you add metadata for a new column to an entity, this command
would generate and output the SQL needed to add the new column to the database:
You can also optionally specify the name of a entity manager to update the
schema for:
<info>./app/console doctrine:schema:update --dump-sql</info>
Alternatively, you can execute the generated queries:
<info>./app/console doctrine:schema:update --force</info>
You can also update the database schema for a specific entity manager:
<info>./app/console doctrine:schema:update --em=default</info>
EOT

View File

@ -86,7 +86,7 @@ class RedirectController extends ContainerAware
$port = ':'.$httpsPort;
}
$url = $scheme.'://'.$request->getHttpHost().$port.$request->getBaseUrl().$path.$qs;
$url = $scheme.'://'.$request->getHost().$port.$request->getBaseUrl().$path.$qs;
return new RedirectResponse($url, $permanent ? 301 : 302);
}

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);
@ -58,10 +58,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))
@ -84,7 +92,7 @@ class Configuration implements ConfigurationInterface
->treatNullLike(array('enabled' => true))
->treatTrueLike(array('enabled' => true))
->children()
->booleanNode('enabled')->end()
->booleanNode('enabled')->defaultTrue()->end()
->end()
->end()
->end()
@ -228,6 +236,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()
@ -243,6 +253,8 @@ class Configuration implements ConfigurationInterface
->children()
->arrayNode('validation')
->canBeUnset()
->treatNullLike(array('enabled' => true))
->treatTrueLike(array('enabled' => true))
// For XML, namespace is a child of validation, so it must be moved under annotations
->beforeNormalization()
->ifTrue(function($v) { return is_array($v) && !empty($v['annotations']) && !empty($v['namespace']); })
@ -253,7 +265,7 @@ class Configuration implements ConfigurationInterface
})
->end()
->children()
->booleanNode('enabled')->end()
->booleanNode('enabled')->defaultTrue()->end()
->scalarNode('cache')->end()
->arrayNode('annotations')
->canBeUnset()

View File

@ -41,7 +41,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
@ -70,8 +69,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'])) {
@ -86,10 +94,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);
}
@ -98,10 +102,6 @@ class FrameworkExtension extends Extension
$this->registerTranslatorConfiguration($config['translator'], $container);
}
if (isset($config['validation'])) {
$this->registerValidationConfiguration($config['validation'], $container, $loader);
}
$this->addClassesToCompile(array(
'Symfony\\Component\\HttpFoundation\\ParameterBag',
'Symfony\\Component\\HttpFoundation\\HeaderBag',
@ -135,15 +135,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');
}
}
/**
@ -321,7 +338,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']);
@ -439,10 +456,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));
@ -462,6 +475,8 @@ class FrameworkExtension extends Extension
$arguments = $loaderChain->getArguments();
array_unshift($arguments[0], new Reference('validator.mapping.loader.annotation_loader'));
$loaderChain->setArguments($arguments);
} else {
$container->setParameter('validator.mapping.loader.annotation_loader.namespaces', array());
}
if (isset($config['cache'])) {

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>
@ -151,16 +150,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" />
@ -35,6 +36,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

@ -15,6 +15,9 @@
<parameter key="validator.mapping.loader.xml_files_loader.class">Symfony\Component\Validator\Mapping\Loader\XmlFilesLoader</parameter>
<parameter key="validator.mapping.loader.yaml_files_loader.class">Symfony\Component\Validator\Mapping\Loader\YamlFilesLoader</parameter>
<parameter key="validator.validator_factory.class">Symfony\Bundle\FrameworkBundle\Validator\ConstraintValidatorFactory</parameter>
<parameter key="validator.mapping.loader.annotation_loader.namespaces" type="collection" />
<parameter key="validator.mapping.loader.xml_files_loader.mapping_files" type="collection" />
<parameter key="validator.mapping.loader.yaml_files_loader.mapping_files" type="collection" />
</parameters>
<services>
@ -48,15 +51,15 @@
<service id="validator.mapping.loader.static_method_loader" class="%validator.mapping.loader.static_method_loader.class%" public="false" />
<service id="validator.mapping.loader.annotation_loader" class="%validator.mapping.loader.annotation_loader.class%" public="false">
<argument type="collection">%validator.mapping.loader.annotation_loader.namespaces%</argument>
<argument>%validator.mapping.loader.annotation_loader.namespaces%</argument>
</service>
<service id="validator.mapping.loader.xml_files_loader" class="%validator.mapping.loader.xml_files_loader.class%" public="false">
<argument type="collection">%validator.mapping.loader.xml_files_loader.mapping_files%</argument>
<argument>%validator.mapping.loader.xml_files_loader.mapping_files%</argument>
</service>
<service id="validator.mapping.loader.yaml_files_loader" class="%validator.mapping.loader.yaml_files_loader.class%" public="false">
<argument type="collection">%validator.mapping.loader.yaml_files_loader.mapping_files%</argument>
<argument>%validator.mapping.loader.yaml_files_loader.mapping_files%</argument>
</service>
</services>
</container>

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

@ -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

@ -48,6 +48,13 @@
{% 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 %}
@ -210,7 +217,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 +251,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

@ -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

@ -20,25 +20,38 @@ use Symfony\Component\Form\Exception\UnexpectedTypeException;
abstract class AbstractExtension implements FormExtensionInterface
{
/**
* @var array
* The types provided by this extension
* @var array An array of FormTypeInterface
*/
private $types;
/**
* @var array
* The type extensions provided by this extension
* @var array An array of FormTypeExtensionInterface
*/
private $typeExtensions;
/**
* The type guesser provided by this extension
* @var FormTypeGuesserInterface
*/
private $typeGuesser;
/**
* Whether the type guesser has been loaded
* @var Boolean
*/
private $typeGuesserLoaded = false;
/**
* Returns a type by name.
*
* @param string $name The name of the type
*
* @return FormTypeInterface The type
*
* @throws FormException if the given type is not supported by this extension
*/
public function getType($name)
{
if (null === $this->types) {
@ -52,6 +65,13 @@ abstract class AbstractExtension implements FormExtensionInterface
return $this->types[$name];
}
/**
* Returns whether the given type is supported.
*
* @param string $name The name of the type
*
* @return Boolean Whether the type is supported by this extension
*/
public function hasType($name)
{
if (null === $this->types) {
@ -61,6 +81,13 @@ abstract class AbstractExtension implements FormExtensionInterface
return isset($this->types[$name]);
}
/**
* Returns the extensions for the given type.
*
* @param string $name The name of the type
*
* @return array An array of extensions as FormTypeExtensionInterface instances
*/
public function getTypeExtensions($name)
{
if (null === $this->typeExtensions) {
@ -72,6 +99,13 @@ abstract class AbstractExtension implements FormExtensionInterface
: array();
}
/**
* Returns whether this extension provides type extensions for the given type.
*
* @param string $name The name of the type
*
* @return Boolean Whether the given type has extensions
*/
public function hasTypeExtensions($name)
{
if (null === $this->typeExtensions) {
@ -81,6 +115,11 @@ abstract class AbstractExtension implements FormExtensionInterface
return isset($this->typeExtensions[$name]) && count($this->typeExtensions[$name]) > 0;
}
/**
* Returns the type guesser provided by this extension.
*
* @return FormTypeGuesserInterface|null The type guesser
*/
public function getTypeGuesser()
{
if (!$this->typeGuesserLoaded) {
@ -90,69 +129,92 @@ abstract class AbstractExtension implements FormExtensionInterface
return $this->typeGuesser;
}
/**
* Registers the types.
*
* @return array An array of FormTypeInterface instances
*/
protected function loadTypes()
{
return array();
}
/**
* Registers the type extensions.
*
* @return array An array of FormTypeExtensionInterface instances
*/
protected function loadTypeExtensions()
{
return array();
}
/**
* Registers the type guesser.
*
* @return FormTypeGuesserInterface|null A type guesser
*/
protected function loadTypeGuesser()
{
return null;
}
/**
* Initializes the types.
*
* @throws UnexpectedTypeException if any registered type is not an instance of FormTypeInterface
*/
private function initTypes()
{
$types = $this->loadTypes();
$typesByName = array();
$this->types = array();
foreach ($types as $type) {
foreach ($this->loadTypes() as $type) {
if (!$type instanceof FormTypeInterface) {
throw new UnexpectedTypeException($type, 'Symfony\Component\Form\FormTypeInterface');
}
$typesByName[$type->getName()] = $type;
$this->types[$type->getName()] = $type;
}
$this->types = $typesByName;
}
/**
* Initializes the type extensions.
*
* @throws UnexpectedTypeException if any registered type extension is not
* an instance of FormTypeExtensionInterface
*/
private function initTypeExtensions()
{
$extensions = $this->loadTypeExtensions();
$extensionsByType = array();
$this->typeExtensions = array();
foreach ($extensions as $extension) {
foreach ($this->loadTypeExtensions() as $extension) {
if (!$extension instanceof FormTypeExtensionInterface) {
throw new UnexpectedTypeException($extension, 'Symfony\Component\Form\FormTypeExtensionInterface');
}
$type = $extension->getExtendedType();
if (!isset($extensionsByType[$type])) {
$extensionsByType[$type] = array();
if (!isset($this->typeExtensions[$type])) {
$this->typeExtensions[$type] = array();
}
$extensionsByType[$type][] = $extension;
$this->typeExtensions[$type][] = $extension;
}
$this->typeExtensions = $extensionsByType;
}
/**
* Initializes the type guesser.
*
* @throws UnexpectedTypeException if the type guesser is not an instance of FormTypeGuesserInterface
*/
private function initTypeGuesser()
{
$this->typeGuesserLoaded = true;
$guesser = $this->loadTypeGuesser();
$this->guesser = $this->loadTypeGuesser();
if (!$guesser instanceof FormTypeGuesserInterface) {
throw new UnexpectedTypeException($guesser, 'Symfony\Component\Form\FormTypeGuesserInterface');
if (!$this->guesser instanceof FormTypeGuesserInterface) {
throw new UnexpectedTypeException($this->guesser, 'Symfony\Component\Form\FormTypeGuesserInterface');
}
$this->guesser = $guesser;
}
}
}

View File

@ -15,20 +15,75 @@ use Symfony\Component\Form\Exception\UnexpectedTypeException;
abstract class AbstractType implements FormTypeInterface
{
/**
* The extensions for this type
* @var array An array of FormTypeExtensionInterface instances
*/
private $extensions = array();
/**
* Builds the form.
*
* This method gets called for each type in the hierarchy starting form the
* top most type.
* Type extensions can further modify the form.
*
* @see FormTypeExtensionInterface::buildForm()
*
* @param FormBuilder $builder The form builder
* @param array $options The options
*/
public function buildForm(FormBuilder $builder, array $options)
{
}
/**
* Builds the form view.
*
* This method gets called for each type in the hierarchy starting form the
* top most type.
* Type extensions can further modify the view.
*
* @see FormTypeExtensionInterface::buildView()
*
* @param FormView $view The view
* @param FormInterface $form The form
*/
public function buildView(FormView $view, FormInterface $form)
{
}
/**
* Builds the form view.
*
* This method gets called for each type in the hierarchy starting form the
* top most type.
* Type extensions can further modify the view.
*
* Children views have been built while this method gets called so you get
* a chance to modify them.
*
* @see FormTypeExtensionInterface::buildViewBottomUp()
*
* @param FormView $view The view
* @param FormInterface $form The form
*/
public function buildViewBottomUp(FormView $view, FormInterface $form)
{
}
/**
* Returns a builder for the current type.
*
* The builder is retrieved by going up in the type hierarchy when a type does
* not provide one.
*
* @param string $name The name of the builder
* @param FormFactoryInterface $factory The form factory
* @param array $options The options
*
* @return FormBuilder|null A form builder or null when the type does not have a builder
*/
public function createBuilder($name, FormFactoryInterface $factory, array $options)
{
return null;
@ -63,7 +118,7 @@ abstract class AbstractType implements FormTypeInterface
*
* @param array $options
*
* @return string The name of the parent type
* @return string|null The name of the parent type if any otherwise null
*/
public function getParent(array $options)
{
@ -73,6 +128,8 @@ abstract class AbstractType implements FormTypeInterface
/**
* Returns the name of this type.
*
* The default name type is the class name without the Form nor Type suffix
*
* @return string The name of this type
*/
public function getName()

View File

@ -13,18 +13,58 @@ namespace Symfony\Component\Form;
abstract class AbstractTypeExtension implements FormTypeExtensionInterface
{
/**
* Builds the form.
*
* This method gets called after the extended type has built the form to
* further modify it.
*
* @see FormTypeInterface::buildForm()
*
* @param FormBuilder $builder The form builder
* @param array $options The options
*/
public function buildForm(FormBuilder $builder, array $options)
{
}
/**
* Builds the view.
*
* This method gets called after the extended type has built the view to
* further modify it.
*
* @see FormTypeInterface::buildView()
*
* @param FormView $view The view
* @param FormInterface $form The form
*/
public function buildView(FormView $view, FormInterface $form)
{
}
/**
* Builds the view.
*
* This method gets called after the extended type has built the view to
* further modify it.
*
* @see FormTypeInterface::buildViewBottomUp()
*
* @param FormView $view The view
* @param FormInterface $form The form
*/
public function buildViewBottomUp(FormView $view, FormInterface $form)
{
}
/**
* Overrides the default options form the extended type.
*
* @param array $options
*
* @return array
*/
public function getDefaultOptions(array $options)
{
return array();

View File

@ -13,21 +13,56 @@ namespace Symfony\Component\Form;
class CallbackTransformer implements DataTransformerInterface
{
/**
* The callback used for forward transform
* @var \Closure
*/
private $transform;
/**
* The callback used for reverse transform
* @var \Closure
*/
private $reverseTransform;
/**
* Constructor.
*
* @param \Closure $transform The forward transform callback
* @param \Closure $reverseTransform The reverse transform callback
*/
public function __construct(\Closure $transform, \Closure $reverseTransform)
{
$this->transform = $transform;
$this->reverseTransform = $reverseTransform;
}
/**
* Transforms a value from the original representation to a transformed representation.
*
* @param mixed $value The value in the original representation
*
* @return mixed The value in the transformed representation
*
* @throws UnexpectedTypeException when the argument is not a string
* @throws DataTransformerException when the transformation fails
*/
public function transform($data)
{
return call_user_func($this->transform, $data);
}
/**
* Transforms a value from the transformed representation to its original
* representation.
*
* @param mixed $value The value in the transformed representation
*
* @return mixed The value in the original representation
*
* @throws UnexpectedTypeException when the argument is not of the expected type
* @throws DataTransformerException when the transformation fails
*/
public function reverseTransform($data)
{
return call_user_func($this->reverseTransform, $data);

View File

@ -40,8 +40,10 @@ interface DataTransformerInterface
* passed.
*
* @param mixed $value The value in the original representation
*
* @return mixed The value in the transformed representation
* @throws UnexpectedTypeException when the argument is no string
*
* @throws UnexpectedTypeException when the argument is not a string
* @throws DataTransformerException when the transformation fails
*/
function transform($value);
@ -65,8 +67,10 @@ interface DataTransformerInterface
* is passed.
*
* @param mixed $value The value in the transformed representation
* @throws UnexpectedTypeException when the argument is not of the
* expected type
*
* @return mixed The value in the original representation
*
* @throws UnexpectedTypeException when the argument is not of the expected type
* @throws DataTransformerException when the transformation fails
*/
function reverseTransform($value);

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,12 +93,14 @@ 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
@ -110,14 +116,14 @@ 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,
'csrf_protection' => false,
'empty_data' => $multiple || $expanded ? array() : '',
'error_bubbling' => false,
);
}

View File

@ -24,31 +24,39 @@ class CollectionType extends AbstractType
if ($options['allow_add'] && $options['prototype']) {
$builder->add('$$name$$', $options['type'], array(
'property_path' => false,
'required' => false,
'required' => false,
));
}
$listener = new ResizeFormListener($builder->getFormFactory(),
$options['type'], $options['allow_add'], $options['allow_delete']);
$listener = new ResizeFormListener(
$builder->getFormFactory(),
$options['type'],
$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',
);
}

View File

@ -66,7 +66,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 +75,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 +96,52 @@ 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_pattern' => null,
'date_widget' => null,
'date_format' => null,
'time_pattern' => 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,37 @@ 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',
'pattern' => null,
'format' => \IntlDateFormatter::MEDIUM,
'data_timezone' => null,
'user_timezone' => null,
'csrf_protection' => false,
// 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'])
@ -45,7 +46,8 @@ class FieldType extends AbstractType
->setAttribute('max_length', $options['max_length'])
->setAttribute('label', $options['label'] ?: $this->humanize($builder->getName()))
->setData($options['data'])
->addValidator(new DefaultValidator());
->addValidator(new DefaultValidator())
;
if ($options['trim']) {
$builder->addEventSubscriber(new TrimListener());
@ -64,18 +66,20 @@ 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', $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('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 +91,17 @@ 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,
'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,7 +44,8 @@ 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)
@ -56,8 +57,8 @@ class FileType extends AbstractType
public function getDefaultOptions(array $options)
{
return array(
'type' => 'string',
'csrf_protection' => false,
'type' => 'string',
'csrf_protection' => false,
);
}

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,14 +21,18 @@ 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 File

@ -19,23 +19,25 @@ 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',
'csrf_protection' => false,
'error_bubbling' => false,
);
}

View File

@ -66,33 +66,41 @@ 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,
'pattern' => null,
'data_timezone' => null,
'user_timezone' => null,
'csrf_protection' => false,
// Don't modify \DateTime classes by reference, we treat
// them like immutable value objects
'by_reference' => false,
'by_reference' => false,
);
}

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

@ -30,26 +30,28 @@ class CsrfType extends AbstractType
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))
;
}
public function getDefaultOptions(array $options)
{
return array(
'csrf_provider' => $this->csrfProvider,
'page_id' => null,
'intention' => null,
'property_path' => false,
);
}

View File

@ -30,7 +30,7 @@ class FormTypeCsrfExtension extends AbstractTypeExtension
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'];
@ -58,7 +58,7 @@ class FormTypeCsrfExtension extends AbstractTypeExtension
'csrf_protection' => $this->enabled,
'csrf_field_name' => $this->fieldName,
'csrf_provider' => null,
'csrf_page_id' => get_class($this),
'intention' => 'unknown',
);
}

View File

@ -77,7 +77,7 @@ class Form implements \IteratorAggregate, FormInterface
/**
* The children of this form
* @var array
* @var array An array of FormInterface instances
*/
private $children = array();
@ -89,7 +89,7 @@ class Form implements \IteratorAggregate, FormInterface
/**
* The errors of this form
* @var array
* @var array An array of FromError instances
*/
private $errors = array();
@ -165,7 +165,7 @@ class Form implements \IteratorAggregate, FormInterface
/**
* The validators attached to this form
* @var array
* @var array An array of FormValidatorInterface instances
*/
private $validators;
@ -252,7 +252,7 @@ class Form implements \IteratorAggregate, FormInterface
}
/**
* Returns the supported types.
* Returns the types used by this form.
*
* @return array An array of FormTypeInterface
*/
@ -459,7 +459,7 @@ class Form implements \IteratorAggregate, FormInterface
public function bind($clientData)
{
if ($this->readOnly) {
return;
return $this;
}
if (is_scalar($clientData) || null === $clientData) {
@ -558,6 +558,8 @@ class Form implements \IteratorAggregate, FormInterface
foreach ($this->validators as $validator) {
$validator->validate($this);
}
return $this;
}
/**
@ -568,6 +570,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 +592,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 +608,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 +621,8 @@ class Form implements \IteratorAggregate, FormInterface
} else {
$this->errors[] = $error;
}
return $this;
}
/**
@ -754,6 +762,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 +774,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 +792,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

@ -11,15 +11,51 @@
namespace Symfony\Component\Form;
/**
* Interface for extensions which provide types, type extensions and a guesser.
*/
interface FormExtensionInterface
{
/**
* Returns a type by name.
*
* @param string $name The name of the type
*
* @return FormTypeInterface The type
*/
function getType($name);
/**
* Returns whether the given type is supported.
*
* @param string $name The name of the type
*
* @return Boolean Whether the type is supported by this extension
*/
function hasType($name);
/**
* Returns the extensions for the given type.
*
* @param string $name The name of the type
*
* @return array An array of extensions as FormTypeExtensionInterface instances
*/
function getTypeExtensions($name);
/**
* Returns whether this extension provides type extensions for the given type.
*
* @param string $name The name of the type
*
* @return Boolean Whether the given type has extensions
*/
function hasTypeExtensions($name);
/**
* Returns the type guesser provided by this extension.
*
* @return FormTypeGuesserInterface|null The type guesser
*/
function getTypeGuesser();
}

View File

@ -24,10 +24,22 @@ class FormFactory implements FormFactoryInterface
'max_length',
);
/**
* Extensions
* @var array An array of FormExtensionInterface
*/
private $extensions = array();
/**
* All known types (cache)
* @var array An array of FormTypeInterface
*/
private $types = array();
/**
* The guesser chain
* @var FormTypeGuesserChain
*/
private $guesser;
/**
@ -48,6 +60,13 @@ class FormFactory implements FormFactoryInterface
$this->extensions = $extensions;
}
/**
* Returns whether the given type is supported.
*
* @param string $name The name of the type
*
* @return Boolean Whether the type is supported
*/
public function hasType($name)
{
if (isset($this->types[$name])) {
@ -63,6 +82,11 @@ class FormFactory implements FormFactoryInterface
return true;
}
/**
* Add a type.
*
* @param FormTypeInterface $type The type
*/
public function addType(FormTypeInterface $type)
{
$this->loadTypeExtensions($type);
@ -70,6 +94,17 @@ class FormFactory implements FormFactoryInterface
$this->types[$type->getName()] = $type;
}
/**
* Returns a type by name.
*
* This methods registers the type extensions from the form extensions.
*
* @param string|FormTypeInterface $name The name of the type or a type instance
*
* @return FormTypeInterface The type
*
* @throws FormException if the type can not be retrieved from any extension
*/
public function getType($name)
{
if (!is_string($name)) {
@ -83,24 +118,74 @@ class FormFactory implements FormFactoryInterface
return $this->types[$name];
}
/**
* Returns a form.
*
* @see createBuilder()
*
* @param string|FormTypeInterface $type The type of the form
* @param mixed $data The initial data
* @param array $options The options
*
* @return Form The form named after the type
*
* @throws FormException if any given option is not applicable to the given type
*/
public function create($type, $data = null, array $options = array())
{
return $this->createBuilder($type, $data, $options)->getForm();
}
/**
* Returns a form.
*
* @see createNamedBuilder()
*
* @param string|FormTypeInterface $type The type of the form
* @param string $name The name of the form
* @param mixed $data The initial data
* @param array $options The options
*
* @return Form The form
*
* @throws FormException if any given option is not applicable to the given type
*/
public function createNamed($type, $name, $data = null, array $options = array())
{
return $this->createNamedBuilder($type, $name, $data, $options)->getForm();
}
/**
* @inheritDoc
* Returns a form for a property of a class.
*
* @see createBuilderForProperty()
*
* @param string $class The fully qualified class name
* @param string $property The name of the property to guess for
* @param mixed $data The initial data
* @param array $options The options for the builder
*
* @return Form The form named after the property
*
* @throws FormException if any given option is not applicable to the form type
*/
public function createForProperty($class, $property, $data = null, array $options = array())
{
return $this->createBuilderForProperty($class, $property, $data, $options)->getForm();
}
/**
* Returns a form builder
*
* @param string|FormTypeInterface $type The type of the form
* @param string $name The name of the form
* @param mixed $data The initial data
* @param array $options The options
*
* @return FormBuilder The form builder
*
* @throws FormException if any given option is not applicable to the given type
*/
public function createBuilder($type, $data = null, array $options = array())
{
$name = is_object($type) ? $type->getName() : $type;
@ -108,6 +193,18 @@ class FormFactory implements FormFactoryInterface
return $this->createNamedBuilder($type, $name, $data, $options);
}
/**
* Returns a form builder.
*
* @param string|FormTypeInterface $type The type of the form
* @param string $name The name of the form
* @param mixed $data The initial data
* @param array $options The options
*
* @return FormBuilder The form builder
*
* @throws FormException if any given option is not applicable to the given type
*/
public function createNamedBuilder($type, $name, $data = null, array $options = array())
{
$builder = null;
@ -185,6 +282,21 @@ class FormFactory implements FormFactoryInterface
return $builder;
}
/**
* Returns a form builder for a property of a class.
*
* If any of the 'max_length', 'required' and type options can be guessed,
* and are not provided in the options argument, the guessed value is used.
*
* @param string $class The fully qualified class name
* @param string $property The name of the property to guess for
* @param mixed $data The initial data
* @param array $options The options for the builder
*
* @return FormBuilder The form builder named after the property
*
* @throws FormException if any given option is not applicable to the form type
*/
public function createBuilderForProperty($class, $property, $data = null, array $options = array())
{
if (!$this->guesser) {
@ -213,6 +325,9 @@ class FormFactory implements FormFactoryInterface
return $this->createNamedBuilder($type, $property, $data, $options);
}
/**
* Initializes the guesser chain.
*/
private function loadGuesser()
{
$guessers = array();
@ -228,6 +343,13 @@ class FormFactory implements FormFactoryInterface
$this->guesser = new FormTypeGuesserChain($guessers);
}
/**
* Loads a type.
*
* @param string $name The type name
*
* @throws FormException if the type is not provided by any registered extension
*/
private function loadType($name)
{
$type = null;
@ -248,6 +370,11 @@ class FormFactory implements FormFactoryInterface
$this->types[$name] = $type;
}
/**
* Loads the extensions for a given type.
*
* @param FormTypeInterface $type The type
*/
private function loadTypeExtensions(FormTypeInterface $type)
{
$typeExtensions = array();

View File

@ -13,16 +13,92 @@ namespace Symfony\Component\Form;
interface FormFactoryInterface
{
/**
* Returns a form.
*
* @see createBuilder()
*
* @param string|FormTypeInterface $type The type of the form
* @param mixed $data The initial data
* @param array $options The options
*
* @return Form The form named after the type
*
* @throws FormException if any given option is not applicable to the given type
*/
function create($type, $data = null, array $options = array());
/**
* Returns a form.
*
* @param string|FormTypeInterface $type The type of the form
* @param string $name The name of the form
* @param mixed $data The initial data
* @param array $options The options
*
* @return Form The form
*
* @throws FormException if any given option is not applicable to the given type
*/
function createNamed($type, $name, $data = null, array $options = array());
/**
* Returns a form for a property of a class.
*
* @param string $class The fully qualified class name
* @param string $property The name of the property to guess for
* @param mixed $data The initial data
* @param array $options The options for the builder
*
* @return Form The form named after the property
*
* @throws FormException if any given option is not applicable to the form type
*/
function createForProperty($class, $property, $data = null, array $options = array());
/**
* Returns a form builder
*
* @param string|FormTypeInterface $type The type of the form
* @param string $name The name of the form
* @param mixed $data The initial data
* @param array $options The options
*
* @return FormBuilder The form builder
*
* @throws FormException if any given option is not applicable to the given type
*/
function createBuilder($type, $data = null, array $options = array());
/**
* Returns a form builder.
*
* @param string|FormTypeInterface $type The type of the form
* @param string $name The name of the form
* @param mixed $data The initial data
* @param array $options The options
*
* @return FormBuilder The form builder
*
* @throws FormException if any given option is not applicable to the given type
*/
function createNamedBuilder($type, $name, $data = null, array $options = array());
/**
* Returns a form builder for a property of a class.
*
* If any of the 'max_length', 'required' and type options can be guessed,
* and are not provided in the options argument, the guessed value is used.
*
* @param string $class The fully qualified class name
* @param string $property The name of the property to guess for
* @param mixed $data The initial data
* @param array $options The options for the builder
*
* @return FormBuilder The form builder named after the property
*
* @throws FormException if any given option is not applicable to the form type
*/
function createBuilderForProperty($class, $property, $data = null, array $options = array());
function getType($name);

View File

@ -21,14 +21,14 @@ interface FormInterface extends \ArrayAccess, \Traversable, \Countable
/**
* Sets the parent form.
*
* @param FormInterface $parent The parent form
* @param FormInterface $parent The parent form
*/
function setParent(FormInterface $parent = null);
/**
* Returns the parent form.
*
* @return FormInterface The parent form
* @return FormInterface The parent form
*/
function getParent();
@ -49,7 +49,7 @@ interface FormInterface extends \ArrayAccess, \Traversable, \Countable
/**
* Returns whether a child with the given name exists.
*
* @param string $name
* @param string $name
*
* @return Boolean
*/
@ -65,7 +65,7 @@ interface FormInterface extends \ArrayAccess, \Traversable, \Countable
/**
* Returns all children in this group.
*
* @return array
* @return array An array of FormInterface instances
*/
function getChildren();
@ -79,7 +79,7 @@ interface FormInterface extends \ArrayAccess, \Traversable, \Countable
/**
* Returns all errors.
*
* @return array An array of FormError instances that occurred during binding
* @return array An array of FormError instances that occurred during binding
*/
function getErrors();

View File

@ -13,12 +13,52 @@ namespace Symfony\Component\Form;
interface FormTypeExtensionInterface
{
/**
* Builds the form.
*
* This method gets called after the extended type has built the form to
* further modify it.
*
* @see FormTypeInterface::buildForm()
*
* @param FormBuilder $builder The form builder
* @param array $options The options
*/
function buildForm(FormBuilder $builder, array $options);
/**
* Builds the view.
*
* This method gets called after the extended type has built the view to
* further modify it.
*
* @see FormTypeInterface::buildView()
*
* @param FormView $view The view
* @param FormInterface $form The form
*/
function buildView(FormView $view, FormInterface $form);
/**
* Builds the view.
*
* This method gets called after the extended type has built the view to
* further modify it.
*
* @see FormTypeInterface::buildViewBottomUp()
*
* @param FormView $view The view
* @param FormInterface $form The form
*/
function buildViewBottomUp(FormView $view, FormInterface $form);
/**
* Overrides the default options form the extended type.
*
* @param array $options
*
* @return array
*/
function getDefaultOptions(array $options);
/**
@ -30,5 +70,11 @@ interface FormTypeExtensionInterface
*/
function getAllowedOptionValues(array $options);
/**
* Returns the name of the type being extended
*
* @return string The name of the type being extended
*/
function getExtendedType();
}

View File

@ -67,6 +67,7 @@ class FormTypeGuesserChain implements FormTypeGuesserInterface
*
* @param \Closure $closure The closure to execute. Accepts a guesser
* as argument and should return a Guess instance
*
* @return FieldFactoryGuess The guess with the highest confidence
*/
private function guess(\Closure $closure)

View File

@ -16,17 +16,19 @@ interface FormTypeGuesserInterface
/**
* Returns a field guess for a property name of a class
*
* @param string $class The fully qualified class name
* @param string $property The name of the property to guess for
* @return TypeGuess A guess for the field's type and options
* @param string $class The fully qualified class name
* @param string $property The name of the property to guess for
*
* @return TypeGuess A guess for the field's type and options
*/
function guessType($class, $property);
/**
* Returns a guess whether a property of a class is required
*
* @param string $class The fully qualified class name
* @param string $property The name of the property to guess for
* @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 required setting
*/
function guessRequired($class, $property);
@ -34,8 +36,9 @@ interface FormTypeGuesserInterface
/**
* Returns a guess about the field's maximum length
*
* @param string $class The fully qualified class name
* @param string $property The name of the property to guess for
* @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 maximum length
*/
function guessMaxLength($class, $property);

View File

@ -13,10 +13,49 @@ namespace Symfony\Component\Form;
interface FormTypeInterface
{
/**
* Builds the form.
*
* This method gets called for each type in the hierarchy starting form the
* top most type.
* Type extensions can further modify the form.
*
* @see FormTypeExtensionInterface::buildForm()
*
* @param FormBuilder $builder The form builder
* @param array $options The options
*/
function buildForm(FormBuilder $builder, array $options);
/**
* Builds the form view.
*
* This method gets called for each type in the hierarchy starting form the
* top most type.
* Type extensions can further modify the view.
*
* @see FormTypeExtensionInterface::buildView()
*
* @param FormView $view The view
* @param FormInterface $form The form
*/
function buildView(FormView $view, FormInterface $form);
/**
* Builds the form view.
*
* This method gets called for each type in the hierarchy starting form the
* top most type.
* Type extensions can further modify the view.
*
* Children views have been built when this method gets called so you get
* a chance to modify them.
*
* @see FormTypeExtensionInterface::buildViewBottomUp()
*
* @param FormView $view The view
* @param FormInterface $form The form
*/
function buildViewBottomUp(FormView $view, FormInterface $form);
function createBuilder($name, FormFactoryInterface $factory, array $options);
@ -44,7 +83,7 @@ interface FormTypeInterface
*
* @param array $options
*
* @return string The name of the parent type
* @return string|null The name of the parent type if any otherwise null
*/
function getParent(array $options);

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;
}
/**

View File

@ -34,7 +34,7 @@ class TypeGuess extends Guess
/**
* Constructor
*
* @param string $type The guessed field type
* @param string $type The guessed field type
* @param array $options The options for creating instances of the
* guessed class
* @param integer $confidence The confidence that the guessed class name

View File

@ -37,6 +37,33 @@ class HeaderBag
}
}
/**
* Returns the headers as a string.
*
* @return string The headers
*/
public function __toString()
{
if (!$this->headers) {
return '';
}
$beautifier = function ($name) {
return preg_replace('/\-(.)/e', "'-'.strtoupper('\\1')", ucfirst($name));
};
$max = max(array_map('strlen', array_keys($this->headers))) + 1;
$content = '';
ksort($this->headers);
foreach ($this->headers as $name => $values) {
foreach ($values as $value) {
$content .= sprintf("%-{$max}s %s\r\n", $beautifier($name).':', $value);
}
}
return $content;
}
/**
* Returns the headers.
*

View File

@ -165,6 +165,7 @@ class Request
'REMOTE_ADDR' => '127.0.0.1',
'SCRIPT_NAME' => '',
'SCRIPT_FILENAME' => '',
'SERVER_PROTOCOL' => 'HTTP/1.1',
);
$components = parse_url($uri);
@ -280,6 +281,19 @@ class Request
$this->headers = clone $this->headers;
}
/**
* Returns the request as a string.
*
* @return string The request
*/
public function __toString()
{
return
sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL'))."\r\n".
$this->headers."\r\n".
$this->getContent();
}
/**
* Overrides the PHP global variables according to this request instance.
*
@ -377,6 +391,8 @@ class Request
/**
* Returns the path being requested relative to the executed script.
*
* The path info always starts with a /.
*
* Suppose this request is instantiated from /mysite on localhost:
*
* * http://localhost/mysite returns an empty string
@ -417,6 +433,8 @@ class Request
/**
* Returns the root url from which this request is executed.
*
* The base URL never ends with a /.
*
* This is similar to getBasePath(), except that it also includes the
* script filename (e.g. index.php) if one exists.
*
@ -450,11 +468,7 @@ class Request
*/
public function getHttpHost()
{
$host = $this->headers->get('HOST');
if (!empty($host)) {
return $host;
}
$host = $this->getHost();
$scheme = $this->getScheme();
$name = $this->server->get('SERVER_NAME');
$port = $this->getPort();
@ -976,10 +990,10 @@ class Request
$baseUrl = $this->getBaseUrl();
if (null === ($requestUri = $this->getRequestUri())) {
return '';
return '/';
}
$pathInfo = '';
$pathInfo = '/';
// Remove the query string from REQUEST_URI
if ($pos = strpos($requestUri, '?')) {
@ -988,7 +1002,7 @@ class Request
if ((null !== $baseUrl) && (false === ($pathInfo = substr($requestUri, strlen($baseUrl))))) {
// If substr() returns false then PATH_INFO is set to an empty string
return '';
return '/';
} elseif (null === $baseUrl) {
return $requestUri;
}

View File

@ -82,10 +82,13 @@ class Response
*/
public function __construct($content = '', $status = 200, $headers = array())
{
$this->headers = new ResponseHeaderBag($headers);
$this->setContent($content);
$this->setStatusCode($status);
$this->setProtocolVersion('1.0');
$this->headers = new ResponseHeaderBag($headers);
if (!$this->headers->has('Date')) {
$this->setDate(new \DateTime(null, new \DateTimeZone('UTC')));
}
$this->charset = 'UTF-8';
}
@ -96,23 +99,12 @@ class Response
*/
public function __toString()
{
$content = '';
$this->fixContentType();
// status
$content .= sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n";
// headers
foreach ($this->headers->all() as $name => $values) {
foreach ($values as $value) {
$content .= "$name: $value\r\n";
}
}
$content .= "\r\n".$this->getContent();
return $content;
return
sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n".
$this->headers."\r\n".
$this->getContent();
}
/**
@ -340,20 +332,24 @@ class Response
/**
* Returns the Date header as a DateTime instance.
*
* When no Date header is present, the current time is returned.
*
* @return \DateTime A \DateTime instance
*
* @throws \RuntimeException when the header is not parseable
*/
public function getDate()
{
if (null === $date = $this->headers->getDate('Date')) {
$date = new \DateTime(null, new \DateTimeZone('UTC'));
$this->headers->set('Date', $date->format('D, d M Y H:i:s').' GMT');
}
return $this->headers->getDate('Date');
}
return $date;
/**
* Sets the Date header.
*
* @param \DateTime $date A \DateTime instance
*/
public function setDate(\DateTime $date)
{
$date->setTimezone(new \DateTimeZone('UTC'));
$this->headers->set('Date', $date->format('D, d M Y H:i:s').' GMT');
}
/**

View File

@ -212,7 +212,10 @@ class PdoSessionStorage extends NativeSessionStorage
$dbIdCol = $this->dbOptions['db_id_col'];
$dbTimeCol = $this->dbOptions['db_time_col'];
$sql = "UPDATE $dbTable SET $dbDataCol = :data, $dbTimeCol = :time WHERE $dbIdCol = :id";
$sql = ('mysql' === $this->db->getAttribute(\PDO::ATTR_DRIVER_NAME))
? "INSERT INTO $dbTable ($dbIdCol, $dbDataCol, $dbTimeCol) VALUES (:id, :data, :time) "
."ON DUPLICATE KEY UPDATE $dbDataCol = VALUES($dbDataCol), $dbTimeCol = CASE WHEN $dbTimeCol = :time THEN (VALUES($dbTimeCol) + 1) ELSE VALUES($dbTimeCol) END"
: "UPDATE $dbTable SET $dbDataCol = :data, $dbTimeCol = :time WHERE $dbIdCol = :id";
try {
$stmt = $this->db->prepare($sql);

View File

@ -182,6 +182,8 @@ class HttpCache implements HttpKernelInterface
$this->restoreResponseBody($request, $response);
$response->setDate(new \DateTime(null, new \DateTimeZone('UTC')));
if (HttpKernelInterface::MASTER_REQUEST === $type && $this->options['debug']) {
$response->headers->set('X-Symfony-Cache', $this->getLog());
}
@ -323,10 +325,11 @@ class HttpCache implements HttpKernelInterface
// Add our cached etag validator to the environment.
// We keep the etags from the client to handle the case when the client
// has a different private valid entry which is not cached here.
$cachedEtags = array($entry->getEtag());
$cachedEtags = $entry->getEtag() ? array($entry->getEtag()) : array();
$requestEtags = $request->getEtags();
$etags = array_unique(array_merge($cachedEtags, $requestEtags));
$subRequest->headers->set('if_none_match', $etags ? implode(', ', $etags) : '');
if ($etags = array_unique(array_merge($cachedEtags, $requestEtags))) {
$subRequest->headers->set('if_none_match', implode(', ', $etags));
}
$response = $this->forward($subRequest, $catch, $entry);
@ -494,7 +497,9 @@ class HttpCache implements HttpKernelInterface
$entry->setContent($new->getContent());
$entry->setStatusCode($new->getStatusCode());
$entry->setProtocolVersion($new->getProtocolVersion());
$entry->setCookies($new->getCookies());
foreach ($new->headers->getCookies() as $cookie) {
$entry->headers->setCookie($cookie);
}
} else {
// backend is slow as hell, send a 503 response (to avoid the dog pile effect)
$entry->setStatusCode(503);

View File

@ -100,7 +100,14 @@ class Process
}
};
$descriptors = array(array('pipe', 'r'), array('pipe', 'w'), array('pipe', 'w'));
// Workaround for http://bugs.php.net/bug.php?id=51800
if (strstr(PHP_OS, 'WIN')) {
$stderrPipeMode = 'a';
} else {
$stderrPipeMode = 'w';
}
$descriptors = array(array('pipe', 'r'), array('pipe', 'w'), array('pipe', $stderrPipeMode));
$process = proc_open($this->commandline, $descriptors, $pipes, $this->cwd, $this->env, $this->options);

View File

@ -9,13 +9,13 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\Routing\Matcher\Exception;
namespace Symfony\Component\Routing\Exception;
/**
* A matching exception.
* ExceptionInterface
*
* @author Kris Wallsmith <kris@symfony.com>
* @author Alexandre Salomé <alexandre.salome@gmail.com>
*/
interface Exception
interface ExceptionInterface
{
}

View File

@ -0,0 +1,21 @@
<?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\Routing\Exception;
/**
* Exception thrown when a parameter is not valid
*
* @author Alexandre Salomé <alexandre.salome@gmail.com>
*/
class InvalidParameterException extends \InvalidArgumentException implements ExceptionInterface
{
}

View File

@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\Routing\Matcher\Exception;
namespace Symfony\Component\Routing\Exception;
/**
* The resource was found but the request method is not allowed.
@ -18,7 +18,7 @@ namespace Symfony\Component\Routing\Matcher\Exception;
*
* @author Kris Wallsmith <kris@symfony.com>
*/
class MethodNotAllowedException extends \RuntimeException implements Exception
class MethodNotAllowedException extends \RuntimeException implements ExceptionInterface
{
protected $allowedMethods;

View File

@ -0,0 +1,22 @@
<?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\Routing\Exception;
/**
* Exception thrown when a route cannot be generated because of missing
* mandatory parameters.
*
* @author Alexandre Salomé <alexandre.salome@gmail.com>
*/
class MissingMandatoryParametersException extends \InvalidArgumentException implements ExceptionInterface
{
}

View File

@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\Routing\Matcher\Exception;
namespace Symfony\Component\Routing\Exception;
/**
* The resource was not found.
@ -18,6 +18,6 @@ namespace Symfony\Component\Routing\Matcher\Exception;
*
* @author Kris Wallsmith <kris@symfony.com>
*/
class NotFoundException extends \RuntimeException implements Exception
class ResourceNotFoundException extends \RuntimeException implements ExceptionInterface
{
}

View File

@ -0,0 +1,21 @@
<?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\Routing\Exception;
/**
* Exception thrown when a route does not exists
*
* @author Alexandre Salomé <alexandre.salome@gmail.com>
*/
class RouteNotFoundException extends \InvalidArgumentException implements ExceptionInterface
{
}

View File

@ -77,7 +77,7 @@ EOF
public function generate(\$name, array \$parameters = array(), \$absolute = false)
{
if (!isset(self::\$declaredRouteNames[\$name])) {
throw new \InvalidArgumentException(sprintf('Route "%s" does not exist.', \$name));
throw new RouteNotFoundException(sprintf('Route "%s" does not exist.', \$name));
}
\$escapedName = str_replace('.', '__', \$name);
@ -103,6 +103,8 @@ EOF;
<?php
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
/**
* $class

View File

@ -14,6 +14,9 @@ namespace Symfony\Component\Routing\Generator;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Exception\InvalidParameterException;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
use Symfony\Component\Routing\Exception\MissingMandatoryParametersException;
/**
* UrlGenerator generates URL based on a set of routes.
@ -69,12 +72,12 @@ class UrlGenerator implements UrlGeneratorInterface
*
* @return string The generated URL
*
* @throws \InvalidArgumentException When route doesn't exist
* @throws Symfony\Component\Routing\Exception\RouteNotFoundException When route doesn't exist
*/
public function generate($name, array $parameters = array(), $absolute = false)
{
if (null === $route = $this->routes->get($name)) {
throw new \InvalidArgumentException(sprintf('Route "%s" does not exist.', $name));
throw new RouteNotFoundException(sprintf('Route "%s" does not exist.', $name));
}
if (!isset($this->cache[$name])) {
@ -85,7 +88,8 @@ class UrlGenerator implements UrlGeneratorInterface
}
/**
* @throws \InvalidArgumentException When route has some missing mandatory parameters
* @throws Symfony\Component\Routing\Exception\MissingMandatoryParametersException When route has some missing mandatory parameters
* @throws Symfony\Component\Routing\Exception\InvalidParameterException When a parameter value is not correct
*/
protected function doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $absolute)
{
@ -97,7 +101,7 @@ class UrlGenerator implements UrlGeneratorInterface
// all params must be given
if ($diff = array_diff_key($variables, $tparams)) {
throw new \InvalidArgumentException(sprintf('The "%s" route has some missing mandatory parameters (%s).', $name, implode(', ', $diff)));
throw new MissingMandatoryParametersException(sprintf('The "%s" route has some missing mandatory parameters (%s).', $name, implode(', ', $diff)));
}
$url = '';
@ -108,7 +112,7 @@ class UrlGenerator implements UrlGeneratorInterface
if (!$isEmpty = in_array($tparams[$token[3]], array(null, '', false), true)) {
// check requirement
if ($tparams[$token[3]] && !preg_match('#^'.$token[2].'$#', $tparams[$token[3]])) {
throw new \InvalidArgumentException(sprintf('Parameter "%s" for route "%s" must match "%s" ("%s" given).', $token[3], $name, $token[2], $tparams[$token[3]]));
throw new InvalidParameterException(sprintf('Parameter "%s" for route "%s" must match "%s" ("%s" given).', $token[3], $name, $token[2], $tparams[$token[3]]));
}
}

View File

@ -11,7 +11,7 @@
namespace Symfony\Component\Routing\Matcher;
use Symfony\Component\Routing\Matcher\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;

View File

@ -63,7 +63,7 @@ class PhpMatcherDumper extends MatcherDumper
\$allow = array();
$code
throw 0 < count(\$allow) ? new MethodNotAllowedException(array_unique(\$allow)) : new NotFoundException();
throw 0 < count(\$allow) ? new MethodNotAllowedException(array_unique(\$allow)) : new ResourceNotFoundException();
}
EOF;
@ -206,8 +206,8 @@ EOF
return <<<EOF
<?php
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\RequestContext;
/**

View File

@ -11,7 +11,7 @@
namespace Symfony\Component\Routing\Matcher;
use Symfony\Component\Routing\Matcher\Exception\NotFoundException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
/**
* @author Fabien Potencier <fabien@symfony.com>
@ -27,7 +27,7 @@ abstract class RedirectableUrlMatcher extends UrlMatcher implements Redirectable
{
try {
$parameters = parent::match($pathinfo);
} catch (NotFoundException $e) {
} catch (ResourceNotFoundException $e) {
if ('/' === substr($pathinfo, -1)) {
throw $e;
}

View File

@ -11,8 +11,8 @@
namespace Symfony\Component\Routing\Matcher;
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\Route;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\RequestContext;
@ -67,7 +67,7 @@ class UrlMatcher implements UrlMatcherInterface
*
* @return array An array of parameters
*
* @throws NotFoundException If the resource could not be found
* @throws ResourceNotFoundException If the resource could not be found
* @throws MethodNotAllowedException If the resource was found but the request method is not allowed
*/
public function match($pathinfo)
@ -80,7 +80,7 @@ class UrlMatcher implements UrlMatcherInterface
throw 0 < count($this->allow)
? new MethodNotAllowedException(array_unique(array_map('strtolower', $this->allow)))
: new NotFoundException();
: new ResourceNotFoundException();
}
protected function matchCollection($pathinfo, RouteCollection $routes)

View File

@ -27,7 +27,7 @@ interface UrlMatcherInterface extends RequestContextAwareInterface
*
* @return array An array of parameters
*
* @throws NotFoundException If the resource could not be found
* @throws ResourceNotFoundException If the resource could not be found
* @throws MethodNotAllowedException If the resource was found but the request method is not allowed
*/
function match($pathinfo);

View File

@ -24,13 +24,14 @@ class Router implements RouterInterface
{
private $matcher;
private $generator;
private $options;
private $defaults;
private $context;
private $loader;
private $collection;
private $resource;
protected $options;
/**
* Constructor.
*

View File

@ -42,7 +42,7 @@ class UsernamePasswordFormAuthenticationListener extends AbstractAuthenticationL
'username_parameter' => '_username',
'password_parameter' => '_password',
'csrf_parameter' => '_csrf_token',
'csrf_page_id' => 'form_login',
'intention' => 'authenticate',
'post_only' => true,
), $options), $successHandler, $failureHandler, $logger, $dispatcher);
@ -65,7 +65,7 @@ class UsernamePasswordFormAuthenticationListener extends AbstractAuthenticationL
if (null !== $this->csrfProvider) {
$csrfToken = $request->get($this->options['csrf_parameter']);
if (false === $this->csrfProvider->isCsrfTokenValid($this->options['csrf_page_id'], $csrfToken)) {
if (false === $this->csrfProvider->isCsrfTokenValid($this->options['intention'], $csrfToken)) {
throw new InvalidCsrfTokenException('Invalid CSRF token.');
}
}

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