Merge remote branch 'origin/master' into doctrine

This commit is contained in:
Johannes Schmitt 2011-05-23 07:09:58 +02:00
commit 51f1add9b3
132 changed files with 1606 additions and 522 deletions

View File

@ -9,6 +9,84 @@ timeline closely anyway.
beta1 to beta2
--------------
* The annotation parsing process has been changed. All annotations which are used
in a class must now be imported (just like you import PHP namespaces with the
"use" statement):
Before:
/**
* @orm:Entity
*/
class MyUser
{
/**
* @orm:Id
* @orm:GeneratedValue(strategy = "AUTO")
* @orm:Column(type="integer")
* @var integer
*/
private $id;
/**
* @orm:Column(type="string", nullable=false)
* @assert:NotBlank
* @var string
*/
private $name;
}
After:
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity
*/
class MyUser
{
/**
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
* @ORM\Column(type="integer")
*
* @var integer
*/
private $id;
/**
* @ORM\Column(type="string", nullable=false)
* @Assert\NotBlank
*
* @var string
*/
private $name;
}
* The config under "framework.validation.annotations" has been removed and was
replaced with a boolean flag "framework.validation.enable_annotations" which
defaults to false.
* The Set constraint has been removed as it is not required anymore.
Before:
/**
* @assert:Set({@assert:Callback(...), @assert:Callback(...)})
*/
private $foo;
After:
use Symfony\Component\Validator\Constraints\Callback;
/**
* @Callback(...)
* @Callback(...)
*/
private $foo;
* Forms must now be explicitly enabled (automatically done in Symfony SE):
form: ~
@ -168,6 +246,9 @@ beta1 to beta2
* Form: Renamed option value "text" of "widget" option of the "date" type was
renamed to "single-text". "text" indicates to use separate text boxes now
(like for the "time" type).
* Form: Renamed view variable "name" to "full_name". The variable "name" now
contains the local, short name (equivalent to $form->getName()).
PR12 to beta1
-------------

View File

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

View File

@ -0,0 +1,56 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Doctrine\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
/**
* Constraint for the Unique Entity validator
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class UniqueEntity extends Constraint
{
public $message = 'This value is already used.';
public $em = null;
public $fields = array();
public function getRequiredOptions()
{
return array('fields');
}
/**
* The validator must be defined as a service with this name.
*
* @return string
*/
public function validatedBy()
{
return 'doctrine.orm.validator.unique';
}
/**
* {@inheritDoc}
*/
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
public function getDefaultOption()
{
return 'fields';
}
}

View File

@ -0,0 +1,81 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Doctrine\Validator\Constraints;
use Symfony\Bundle\DoctrineBundle\Registry;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\ConstraintValidator;
/**
* Unique Entity Validator checks if one or a set of fields contain unique values.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class UniqueEntityValidator extends ConstraintValidator
{
/**
* @var Registry
*/
private $registry;
/**
* @param Registry $registry
*/
public function __construct(Registry $registry)
{
$this->registry = $registry;
}
/**
* @param object $entity
* @param Constraint $constraint
* @return bool
*/
public function isValid($entity, Constraint $constraint)
{
if (!is_array($constraint->fields) && !is_string($constraint->fields)) {
throw new UnexpectedTypeException($constraint->fields, 'array');
}
$fields = (array)$constraint->fields;
if (count($constraint->fields) == 0) {
throw new ConstraintDefinitionException("At least one field has to specified.");
}
$em = $this->registry->getEntityManager($constraint->em);
$className = $this->context->getCurrentClass();
$class = $em->getClassMetadata($className);
$criteria = array();
foreach ($fields as $fieldName) {
if (!isset($class->reflFields[$fieldName])) {
throw new ConstraintDefinitionException("Only field names mapped by Doctrine can be validated for uniqueness.");
}
$criteria[$fieldName] = $class->reflFields[$fieldName]->getValue($entity);
}
$repository = $em->getRepository($className);
$result = $repository->findBy($criteria);
if (count($result) > 0 && $result[0] !== $entity) {
$oldPath = $this->context->getPropertyPath();
$this->context->setPropertyPath( empty($oldPath) ? $fields[0] : $oldPath . "." . $fields[0]);
$this->context->addViolation($constraint->message, array(), $criteria[$constraint->fields[0]]);
$this->context->setPropertyPath($oldPath);
}
return true; // all true, we added the violation already!
}
}

View File

@ -33,6 +33,8 @@ class FormExtension extends \Twig_Extension
{
$this->themes = new \SplObjectStorage();
$this->varStack = new \SplObjectStorage();
$this->templates = new \SplObjectStorage();
$this->resources = $resources;
}
@ -53,6 +55,7 @@ class FormExtension extends \Twig_Extension
public function setTheme(FormView $view, array $resources)
{
$this->themes->attach($view, $resources);
$this->templates->detach($view);
}
/**
@ -183,35 +186,48 @@ class FormExtension extends \Twig_Extension
throw new FormException(sprintf('Unable to render form as none of the following blocks exist: "%s".', implode('", "', $blocks)));
}
/**
* Returns the templates used by the view.
*
* templates are looked for in the following resources:
* * resources from the themes (and its parents)
* * default resources
*
* @param FormView $view The view
*
* @return array An array of Twig_TemplateInterface instances
*/
protected function getTemplates(FormView $view)
{
// templates are looked for in the following resources:
// * resources from the themes (and its parents)
// * default resources
if (!$this->templates->contains($view)) {
// defaults
$all = $this->resources;
// defaults
$all = $this->resources;
// themes
$parent = $view;
do {
if (isset($this->themes[$parent])) {
$all = array_merge($all, $this->themes[$parent]);
}
} while ($parent = $parent->getParent());
// themes
$parent = $view;
do {
if (isset($this->themes[$parent])) {
$all = array_merge($all, $this->themes[$parent]);
}
} while ($parent = $parent->getParent());
$templates = array();
foreach ($all as $resource) {
if (!$resource instanceof \Twig_Template) {
$resource = $this->environment->loadTemplate($resource);
}
$templates = array();
foreach ($all as $resource) {
if (!$resource instanceof \Twig_Template) {
$resource = $this->environment->loadTemplate($resource);
$blocks = array();
foreach ($this->getBlockNames($resource) as $name) {
$blocks[$name] = $resource;
}
$templates = array_replace($templates, $blocks);
}
$blocks = array();
foreach ($this->getBlockNames($resource) as $name) {
$blocks[$name] = $resource;
}
$templates = array_replace($templates, $blocks);
$this->templates->attach($view, $templates);
} else {
$templates = $this->templates[$view];
}
return $templates;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@ framework:
csrf_protection:
enabled: true
router: { resource: "%kernel.root_dir%/config/routing.yml" }
validation: { enabled: true, annotations: true }
validation: { enabled: true, enable_annotations: true }
templating: { engines: ['twig', 'php'] }
session:
default_locale: en

View File

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

View File

@ -0,0 +1,67 @@
<?php
namespace Symfony\Bundle\DoctrineBundle\Annotations;
use Doctrine\Common\Annotations\Reader;
/**
* Allows the reader to be used in-place of Doctrine's reader.
*
* This can be removed once the BC layer is in place.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class IndexedReader implements Reader
{
private $delegate;
public function __construct(Reader $reader)
{
$this->delegate = $reader;
}
public function getClassAnnotations(\ReflectionClass $class)
{
$annotations = array();
foreach ($this->delegate->getClassAnnotations($class) as $annot) {
$annotations[get_class($annot)] = $annot;
}
return $annotations;
}
public function getClassAnnotation(\ReflectionClass $class, $annotation)
{
return $this->delegate->getClassAnnotation($class, $annotation);
}
public function getMethodAnnotations(\ReflectionMethod $method)
{
$annotations = array();
foreach ($this->delegate->getMethodAnnotations($method) as $annot) {
$annotations[get_class($annot)] = $annot;
}
return $annotations;
}
public function getMethodAnnotation(\ReflectionMethod $method, $annotation)
{
return $this->delegate->getMethodAnnotation($method, $annotation);
}
public function getPropertyAnnotations(\ReflectionProperty $property)
{
$annotations = array();
foreach ($this->delegate->getPropertyAnnotations($property) as $annot) {
$annotations[get_class($annot)] = $annot;
}
return $annotations;
}
public function getPropertyAnnotation(\ReflectionProperty $property, $annotation)
{
return $this->delegate->getPropertyAnnotation($property, $annotation);
}
}

View File

@ -20,7 +20,7 @@
<!-- metadata -->
<parameter key="doctrine.orm.metadata.driver_chain.class">Doctrine\ORM\Mapping\Driver\DriverChain</parameter>
<parameter key="doctrine.orm.metadata.annotation.class">Doctrine\ORM\Mapping\Driver\AnnotationDriver</parameter>
<parameter key="doctrine.orm.metadata.annotation_reader.class">Doctrine\Common\Annotations\AnnotationReader</parameter>
<parameter key="doctrine.orm.metadata.annotation_reader.class">Symfony\Bundle\DoctrineBundle\Annotations\IndexedReader</parameter>
<parameter key="doctrine.orm.metadata.xml.class">Symfony\Bundle\DoctrineBundle\Mapping\Driver\XmlDriver</parameter>
<parameter key="doctrine.orm.metadata.yml.class">Symfony\Bundle\DoctrineBundle\Mapping\Driver\YamlDriver</parameter>
<parameter key="doctrine.orm.metadata.php.class">Doctrine\ORM\Mapping\Driver\PHPDriver</parameter>
@ -31,15 +31,15 @@
<!-- form field factory guesser -->
<parameter key="form.type_guesser.doctrine.class">Symfony\Bridge\Doctrine\Form\DoctrineOrmTypeGuesser</parameter>
<!-- validator -->
<parameter key="doctrine.orm.validator.unique.class">Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntityValidator</parameter>
</parameters>
<services>
<!--- Annotation Metadata Reader Service -->
<service id="doctrine.orm.metadata.annotation_reader" class="%doctrine.orm.metadata.annotation_reader.class%" public="false">
<call method="setAnnotationNamespaceAlias">
<argument>Doctrine\ORM\Mapping\</argument>
<argument>orm</argument>
</call>
<argument type="service" id="annotation_reader" />
</service>
<service id="doctrine.orm.proxy_cache_warmer" class="%doctrine.orm.proxy_cache_warmer.class%" public="false">
@ -60,5 +60,11 @@
<service id="doctrine.orm.configuration" class="%doctrine.orm.configuration.class%" abstract="true" public="false" />
<service id="doctrine.orm.entity_manager.abstract" class="%doctrine.orm.entity_manager.class%" factory-class="%doctrine.orm.entity_manager.class%" factory-method="create" abstract="true" />
<!-- validator -->
<service id="doctrine.orm.validator.unique" class="%doctrine.orm.validator.unique.class%">
<tag name="validator.constraint_validator" alias="doctrine.orm.validator.unique" />
<argument type="service" id="doctrine" />
</service>
</services>
</container>

View File

@ -24,7 +24,7 @@ class ContainerTest extends TestCase
$this->assertInstanceOf('Doctrine\DBAL\Configuration', $container->get('doctrine.dbal.default_connection.configuration'));
$this->assertInstanceOf('Doctrine\Common\EventManager', $container->get('doctrine.dbal.default_connection.event_manager'));
$this->assertInstanceOf('Doctrine\DBAL\Connection', $container->get('doctrine.dbal.default_connection'));
$this->assertInstanceOf('Doctrine\Common\Annotations\AnnotationReader', $container->get('doctrine.orm.metadata.annotation_reader'));
$this->assertInstanceOf('Doctrine\Common\Annotations\Reader', $container->get('doctrine.orm.metadata.annotation_reader'));
$this->assertInstanceOf('Doctrine\ORM\Configuration', $container->get('doctrine.orm.default_configuration'));
$this->assertInstanceOf('Doctrine\ORM\Mapping\Driver\DriverChain', $container->get('doctrine.orm.default_metadata_driver'));
$this->assertInstanceOf('Doctrine\Common\Cache\ArrayCache', $container->get('doctrine.orm.default_metadata_cache'));
@ -37,6 +37,8 @@ class ContainerTest extends TestCase
$this->assertInstanceOf('Doctrine\Common\EventManager', $container->get('doctrine.dbal.event_manager'));
$this->assertInstanceOf('Doctrine\DBAL\Event\Listeners\MysqlSessionInit', $container->get('doctrine.dbal.default_connection.events.mysqlsessioninit'));
$this->assertInstanceOf('Symfony\Bundle\DoctrineBundle\CacheWarmer\ProxyCacheWarmer', $container->get('doctrine.orm.proxy_cache_warmer'));
$this->assertInstanceOf('Symfony\Bundle\DoctrineBundle\Registry', $container->get('doctrine'));
$this->assertInstanceOf('Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntityValidator', $container->get('doctrine.orm.validator.unique'));
$this->assertSame($container->get('my.platform'), $container->get('doctrine.dbal.default_connection')->getDatabasePlatform());

View File

@ -35,7 +35,7 @@ abstract class AbstractDoctrineExtensionTest extends TestCase
// doctrine.dbal.default_connection
$this->assertEquals('%doctrine.default_connection%', $container->getDefinition('doctrine')->getArgument(3), '->load() overrides existing configuration options');
$this->assertEquals('foo', $container->getParameter('doctrine.default_connection'), '->load() overrides existing configuration options');
}
public function testDbalLoad()
@ -121,7 +121,6 @@ abstract class AbstractDoctrineExtensionTest extends TestCase
$this->assertEquals('Doctrine\Common\Cache\XcacheCache', $container->getParameter('doctrine.orm.cache.xcache.class'));
$this->assertEquals('Doctrine\ORM\Mapping\Driver\DriverChain', $container->getParameter('doctrine.orm.metadata.driver_chain.class'));
$this->assertEquals('Doctrine\ORM\Mapping\Driver\AnnotationDriver', $container->getParameter('doctrine.orm.metadata.annotation.class'));
$this->assertEquals('Doctrine\Common\Annotations\AnnotationReader', $container->getParameter('doctrine.orm.metadata.annotation_reader.class'));
$this->assertEquals('Symfony\Bundle\DoctrineBundle\Mapping\Driver\XmlDriver', $container->getParameter('doctrine.orm.metadata.xml.class'));
$this->assertEquals('Symfony\Bundle\DoctrineBundle\Mapping\Driver\YamlDriver', $container->getParameter('doctrine.orm.metadata.yml.class'));

View File

@ -11,6 +11,10 @@
namespace Symfony\Bundle\DoctrineBundle\Tests;
use Symfony\Bundle\DoctrineBundle\Annotations\IndexedReader;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
use Doctrine\ORM\EntityManager;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
@ -36,7 +40,7 @@ class TestCase extends \PHPUnit_Framework_TestCase
$config->setAutoGenerateProxyClasses(true);
$config->setProxyDir(\sys_get_temp_dir());
$config->setProxyNamespace('SymfonyTests\Doctrine');
$config->setMetadataDriverImpl($config->newDefaultAnnotationDriver($paths));
$config->setMetadataDriverImpl(new AnnotationDriver(new IndexedReader(new AnnotationReader()), $paths));
$config->setQueryCacheImpl(new \Doctrine\Common\Cache\ArrayCache());
$config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ArrayCache());
@ -56,6 +60,7 @@ class TestCase extends \PHPUnit_Framework_TestCase
'kernel.cache_dir' => sys_get_temp_dir(),
'kernel.root_dir' => __DIR__ . "/../../../../" // src dir
)));
$container->set('annotation_reader', new AnnotationReader());
$loader = new DoctrineExtension();
$container->registerExtension($loader);
$loader->load(array(array(

View File

@ -16,7 +16,7 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\Output;
use Symfony\Bundle\FrameworkBundle\Util\Mustache;
use Symfony\Bundle\FrameworkBundle\Generator\Generator;
/**
* Initializes a new bundle.
@ -105,7 +105,7 @@ EOT
$filesystem->mirror(__DIR__.'/../Resources/skeleton/bundle/generic', $targetDir);
$filesystem->mirror(__DIR__.'/../Resources/skeleton/bundle/'.$input->getOption('format'), $targetDir);
Mustache::renderDir($targetDir, array(
Generator::renderDir($targetDir, array(
'namespace' => $namespace,
'bundle' => $bundle,
));

View File

@ -54,6 +54,7 @@ class Configuration implements ConfigurationInterface
$this->addTemplatingSection($rootNode);
$this->addTranslatorSection($rootNode);
$this->addValidationSection($rootNode);
$this->addAnnotationsSection($rootNode);
return $treeBuilder;
}
@ -255,28 +256,29 @@ class Configuration implements ConfigurationInterface
->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']); })
->then(function($v){
$v['annotations'] = array('namespace' => $v['namespace']);
unset($v['namespace']);
return $v;
})
->end()
->children()
->booleanNode('enabled')->defaultTrue()->end()
->booleanNode('enabled')->defaultTrue()->end()
->scalarNode('cache')->end()
->arrayNode('annotations')
->canBeUnset()
->treatNullLike(array())
->treatTrueLike(array())
->fixXmlConfig('namespace')
->booleanNode('enable_annotations')->defaultFalse()->end()
->end()
->end()
->end()
;
}
private function addAnnotationsSection(ArrayNodeDefinition $rootNode)
{
$rootNode
->children()
->arrayNode('annotations')
->addDefaultsIfNotSet()
->children()
->scalarNode('cache')->defaultValue('file')->end()
->arrayNode('file_cache')
->addDefaultsIfNotSet()
->children()
->arrayNode('namespaces')
->useAttributeAsKey('prefix')
->prototype('scalar')->end()
->end()
->scalarNode('dir')->defaultValue('%kernel.cache_dir%/annotations')->end()
->booleanNode('debug')->defaultValue($this->debug)->end()
->end()
->end()
->end()

View File

@ -11,6 +11,7 @@
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Parameter;
@ -102,6 +103,8 @@ class FrameworkExtension extends Extension
$this->registerTranslatorConfiguration($config['translator'], $container);
}
$this->registerAnnotationsConfiguration($config['annotations'], $container, $loader);
$this->addClassesToCompile(array(
'Symfony\\Component\\HttpFoundation\\ParameterBag',
'Symfony\\Component\\HttpFoundation\\HeaderBag',
@ -461,22 +464,11 @@ class FrameworkExtension extends Extension
$container->setParameter('validator.mapping.loader.xml_files_loader.mapping_files', $this->getValidatorXmlMappingFiles($container));
$container->setParameter('validator.mapping.loader.yaml_files_loader.mapping_files', $this->getValidatorYamlMappingFiles($container));
if (isset($config['annotations'])) {
$namespaces = array('assert' => 'Symfony\\Component\\Validator\\Constraints\\');
// Register prefixes for constraint namespaces
if (!empty($config['annotations']['namespaces'])) {
$namespaces = array_merge($namespaces, $config['annotations']['namespaces']);
}
// Register annotation loader
$container->setParameter('validator.mapping.loader.annotation_loader.namespaces', $namespaces);
if ($config['enable_annotations']) {
$loaderChain = $container->getDefinition('validator.mapping.loader.loader_chain');
$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'])) {
@ -520,6 +512,31 @@ class FrameworkExtension extends Extension
return $files;
}
private function registerAnnotationsConfiguration(array $config, ContainerBuilder $container,$loader)
{
$loader->load('annotations.xml');
if ('file' === $config['cache']) {
$cacheDir = $container->getParameterBag()->resolveValue($config['file_cache']['dir']);
if (!is_dir($cacheDir) && false === @mkdir($cacheDir, 0777, true)) {
throw new \RuntimeException(sprintf('Could not create cache directory "%s".', $cacheDir));
}
$container
->getDefinition('annotations.cache.file_cache')
->replaceArgument(0, $cacheDir)
->replaceArgument(1, $config['file_cache']['debug'])
;
} else if ('none' === $config['cache']) {
$container->setAlias('annotation_reader', 'annotations.reader');
} else {
$container
->getDefinition('annotations.cached_reader')
->replaceArgument(1, new Reference($config['cache']))
;
}
}
/**
* Returns the base path for the XSD files.
*

View File

@ -9,14 +9,14 @@
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Util;
namespace Symfony\Bundle\FrameworkBundle\Generator;
/**
* Mustache.
* Generator is the base class for all generators.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Mustache
class Generator
{
/**
* Renders a single line. Looks for {{ var }}

View File

@ -0,0 +1,29 @@
<?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="annotations.cache.file_cache.class">Doctrine\Common\Annotations\Cache\FileCache</parameter>
<parameter key="annotations.reader.class">Doctrine\Common\Annotations\AnnotationReader</parameter>
<parameter key="annotations.cached_reader.class">Doctrine\Common\Annotations\CachedReader</parameter>
</parameters>
<services>
<service id="annotations.cache.file_cache" class="%annotations.cache.file_cache.class%" public="false">
<argument /><!-- Cache Directory -->
<argument /><!-- Debug-Flag -->
</service>
<service id="annotations.reader" class="%annotations.reader.class%" public="false" />
<service id="annotations.cached_reader" class="%annotations.cached_reader.class%" public="false">
<argument type="service" id="annotations.reader" />
<argument type="service" id="annotations.cache.file_cache" />
</service>
<service id="annotation_reader" alias="annotations.cached_reader" />
</services>
</container>

View File

@ -129,6 +129,9 @@
<service id="form.type.repeated" class="Symfony\Component\Form\Extension\Core\Type\RepeatedType">
<tag name="form.type" alias="repeated" />
</service>
<service id="form.type.search" class="Symfony\Component\Form\Extension\Core\Type\SearchType">
<tag name="form.type" alias="search" />
</service>
<service id="form.type.textarea" class="Symfony\Component\Form\Extension\Core\Type\TextareaType">
<tag name="form.type" alias="textarea" />
</service>

View File

@ -18,6 +18,7 @@
<xsd:element name="templating" type="templating" minOccurs="0" maxOccurs="1" />
<xsd:element name="translator" type="translator" minOccurs="0" maxOccurs="1" />
<xsd:element name="validation" type="validation" minOccurs="0" maxOccurs="1" />
<xsd:element name="annotations" type="annotations" minOccurs="0" maxOccurs="1" />
</xsd:all>
<xsd:attribute name="cache-warmer" type="cache_warmer" />
@ -121,20 +122,21 @@
</xsd:complexType>
<xsd:complexType name="validation">
<xsd:sequence>
<xsd:element name="namespace" type="validation_namespace" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
<xsd:attribute name="enabled" type="xsd:boolean" />
<xsd:attribute name="cache" type="xsd:string" />
<xsd:attribute name="annotations" type="xsd:boolean" />
<xsd:attribute name="enable-annotations" type="xsd:boolean" />
</xsd:complexType>
<xsd:complexType name="annotations">
<xsd:sequence>
<xsd:element name="file-cache" type="annotations.file_cache" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
<xsd:complexType name="validation_namespace">
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="prefix" type="xsd:string" />
</xsd:extension>
</xsd:simpleContent>
<xsd:attribute name="cache" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="annotations.file_cache">
<xsd:attribute name="dir" type="xsd:string" />
<xsd:attribute name="debug" type="xsd:string" />
</xsd:complexType>
</xsd:schema>

View File

@ -15,7 +15,6 @@
<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>
@ -51,7 +50,7 @@
<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>%validator.mapping.loader.annotation_loader.namespaces%</argument>
<argument type="service" id="annotation_reader" />
</service>
<service id="validator.mapping.loader.xml_files_loader" class="%validator.mapping.loader.xml_files_loader.class%" public="false">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -135,7 +135,7 @@ class FormHelper extends Helper
$variables
);
array_push($this->viewStack, $view);
$this->viewStack[] = $view;
$html = $this->engine->render($template, $this->varStack[$view]);
@ -156,7 +156,7 @@ class FormHelper extends Helper
$template = $this->templateDir.':'.$template;
}
*/
$template = 'FrameworkBundle:Form:'.$template;
$template = 'FrameworkBundle:Form:'.$template;
if (!$this->engine->exists($template)) {
$template = false;
}

View File

@ -57,4 +57,11 @@ $container->loadFromExtension('framework', array(
'enabled' => true,
'cache' => 'apc',
),
'annotations' => array(
'cache' => 'file',
'file_cache' => array(
'dir' => '%kernel.cache_dir%/annotations',
'debug' => true,
)
),
));

View File

@ -4,10 +4,6 @@ $container->loadFromExtension('framework', array(
'secret' => 's3cr3t',
'validation' => array(
'enabled' => true,
'annotations' => array(
'namespaces' => array(
'app' => 'Application\\Validator\\Constraints\\',
),
),
'enable_annotations' => true,
),
));

View File

@ -31,5 +31,8 @@
</framework:templating>
<framework:translator enabled="true" fallback="fr" />
<framework:validation enabled="true" cache="apc" />
<framework:annotations cache="file">
<framework:file-cache dir="%kernel.cache_dir%/annotations" debug="true" />
</framework:annotations>
</framework:config>
</container>

View File

@ -7,8 +7,6 @@
http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
<framework:config secret="s3cr3t">
<framework:validation enabled="true" annotations="true">
<framework:namespace prefix="app">Application\Validator\Constraints\</framework:namespace>
</framework:validation>
<framework:validation enabled="true" enable-annotations="true" />
</framework:config>
</container>

View File

@ -43,3 +43,8 @@ framework:
validation:
enabled: true
cache: apc
annotations:
cache: file
file_cache:
dir: %kernel.cache_dir%/annotations
debug: true

View File

@ -2,6 +2,4 @@ framework:
secret: s3cr3t
validation:
enabled: true
annotations:
namespaces:
app: Application\Validator\Constraints\
enable_annotations: true

View File

@ -168,15 +168,27 @@ abstract class FrameworkExtensionTest extends TestCase
);
}
public function testAnnotations()
{
$container = $this->createContainerFromFile('full');
$this->assertEquals($container->getParameter('kernel.cache_dir').'/annotations', $container->getDefinition('annotations.cache.file_cache')->getArgument(0));
$this->assertEquals('annotations.cached_reader', (string) $container->getAlias('annotation_reader'));
}
public function testValidationAnnotations()
{
$container = $this->createContainerFromFile('validation_annotations');
$this->assertTrue($container->hasDefinition('validator.mapping.loader.annotation_loader'), '->registerValidationConfiguration() defines the annotation loader');
$argument = $container->getParameter('validator.mapping.loader.annotation_loader.namespaces');
$this->assertEquals('Symfony\\Component\\Validator\\Constraints\\', $argument['assert'], '->registerValidationConfiguration() loads the default "assert" prefix');
$this->assertEquals('Application\\Validator\\Constraints\\', $argument['app'], '->registerValidationConfiguration() loads custom validation namespaces');
$loaders = $container->getDefinition('validator.mapping.loader.loader_chain')->getArgument(0);
$found = false;
foreach ($loaders as $loader) {
if ('validator.mapping.loader.annotation_loader' === (string) $loader) {
$found = true;
}
}
$this->assertTrue($found, 'validator.mapping.loader.annotation_loader is added to the loader chain.');
}
public function testValidationPaths()

View File

@ -9,13 +9,13 @@
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Tests\Util;
namespace Symfony\Bundle\FrameworkBundle\Tests\Generator;
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
use Symfony\Bundle\FrameworkBundle\Util\Mustache;
use Symfony\Bundle\FrameworkBundle\Generator\Generator;
use Symfony\Component\HttpKernel\Util\Filesystem;
class MustacheTest extends TestCase
class GeneratorTest extends TestCase
{
protected $dir;
@ -23,7 +23,7 @@ class MustacheTest extends TestCase
{
$dir = __DIR__.'/fixtures/';
$this->dir = sys_get_temp_dir().'/mustache';
$this->dir = sys_get_temp_dir().'/symfony2gen';
$filesystem = new Filesystem();
$filesystem->mirror($dir, $this->dir);
}
@ -39,19 +39,19 @@ class MustacheTest extends TestCase
$template = 'Hi {{ you }}, my name is {{ me }}!';
$expected = 'Hi {{ you }}, my name is Kris!';
$this->assertEquals(Mustache::renderString($template, array('me' => 'Kris')), $expected, '::renderString() does not modify unknown parameters');
$this->assertEquals(Generator::renderString($template, array('me' => 'Kris')), $expected, '::renderString() does not modify unknown parameters');
}
public function testRenderFile()
{
Mustache::renderFile($this->dir.'/template.txt', array('me' => 'Fabien'));
Generator::renderFile($this->dir.'/template.txt', array('me' => 'Fabien'));
$this->assertEquals('Hello Fabien', file_get_contents($this->dir.'/template.txt'), '::renderFile() renders a file');
}
public function testRenderDir()
{
Mustache::renderDir($this->dir, array('me' => 'Fabien'));
Generator::renderDir($this->dir, array('me' => 'Fabien'));
$this->assertEquals('Hello Fabien', file_get_contents($this->dir.'/template.txt'), '::renderDir() renders a directory');
$this->assertEquals('Hello Fabien', file_get_contents($this->dir.'/foo/bar.txt'), '::renderDir() renders a directory');

View File

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

View File

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

View File

@ -43,7 +43,7 @@
{% block attributes %}
{% spaceless %}
id="{{ id }}" name="{{ name }}"{% if read_only %} disabled="disabled"{% endif %}{% if required %} required="required"{% endif %}{% if max_length %} maxlength="{{ max_length }}"{% endif %}
id="{{ id }}" name="{{ full_name }}"{% if read_only %} disabled="disabled"{% endif %}{% if required %} required="required"{% endif %}{% if max_length %} maxlength="{{ max_length }}"{% endif %}{% if pattern %} pattern="{{ pattern }}"{% endif %}
{% for attrname,attrvalue in attr %}{{attrname}}="{{attrvalue}}" {% endfor %}
{% endspaceless %}
{% endblock attributes %}
@ -208,6 +208,13 @@
{% endspaceless %}
{% endblock url_widget %}
{% block search_widget %}
{% spaceless %}
{% set type = type|default('search') %}
{{ block('field_widget') }}
{% endspaceless %}
{% endblock search_widget %}
{% block percent_widget %}
{% spaceless %}
{% set type = type|default('text') %}

View File

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

View File

@ -32,7 +32,7 @@ class DialogHelper extends Helper
public function ask(OutputInterface $output, $question, $default = null)
{
// @codeCoverageIgnoreStart
$output->writeln($question);
$output->write($question);
$ret = trim(fgets(STDIN));
@ -78,12 +78,13 @@ class DialogHelper extends Helper
* @param string|array $question
* @param callback $validator A PHP callback
* @param integer $attempts Max number of times to ask before giving up (false by default, which means infinite)
* @param string $default The default answer if none is given by the user
*
* @return mixed
*
* @throws \Exception When any of the validator returns an error
*/
public function askAndValidate(OutputInterface $output, $question, $validator, $attempts = false)
public function askAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $default = null)
{
// @codeCoverageIgnoreStart
$error = null;
@ -92,7 +93,7 @@ class DialogHelper extends Helper
$output->writeln($this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'));
}
$value = $this->ask($output, $question, null);
$value = $this->ask($output, $question, $default);
try {
return call_user_func($validator, $value);

View File

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

View File

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

View File

@ -106,7 +106,7 @@ class ChoiceType extends AbstractType
// Add "[]" to the name in case a select tag with multiple options is
// displayed. Otherwise only one of the selected options is sent in the
// POST request.
$view->set('name', $view->get('name').'[]');
$view->set('full_name', $view->get('full_name').'[]');
}
}
@ -121,7 +121,6 @@ class ChoiceType extends AbstractType
'choice_list' => null,
'choices' => array(),
'preferred_choices' => array(),
'csrf_protection' => false,
'empty_data' => $multiple || $expanded ? array() : '',
'error_bubbling' => false,
);

View File

@ -22,15 +22,16 @@ class CollectionType extends AbstractType
public function buildForm(FormBuilder $builder, array $options)
{
if ($options['allow_add'] && $options['prototype']) {
$builder->add('$$name$$', $options['type'], array(
$builder->add('$$name$$', $options['type'], array_replace(array(
'property_path' => false,
'required' => false,
));
'required' => false,
), $options['options']));
}
$listener = new ResizeFormListener(
$builder->getFormFactory(),
$options['type'],
$options['options'],
$options['allow_add'],
$options['allow_delete']
);
@ -57,6 +58,7 @@ class CollectionType extends AbstractType
'allow_delete' => false,
'prototype' => true,
'type' => 'text',
'options' => array(),
);
}

View File

@ -37,9 +37,6 @@ class DateTimeType extends AbstractType
'with_seconds',
)));
if (isset($options['date_pattern'])) {
$dateOptions['pattern'] = $options['date_pattern'];
}
if (isset($options['date_widget'])) {
$dateOptions['widget'] = $options['date_widget'];
}
@ -49,9 +46,6 @@ class DateTimeType extends AbstractType
$dateOptions['input'] = 'array';
if (isset($options['time_pattern'])) {
$timeOptions['pattern'] = $options['time_pattern'];
}
if (isset($options['time_widget'])) {
$timeOptions['widget'] = $options['time_widget'];
}
@ -103,10 +97,8 @@ class DateTimeType extends AbstractType
// 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,
/* Defaults for date field */
'years' => range(date('Y') - 5, date('Y') + 5),
@ -131,6 +123,7 @@ class DateTimeType extends AbstractType
),
'date_widget' => array(
null, // inherit default from DateType
'single-text',
'text',
'choice',
),

View File

@ -119,11 +119,9 @@ class DateType extends AbstractType
'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,

View File

@ -44,6 +44,7 @@ class FieldType extends AbstractType
->setAttribute('property_path', $options['property_path'])
->setAttribute('error_mapping', $options['error_mapping'])
->setAttribute('max_length', $options['max_length'])
->setAttribute('pattern', $options['pattern'])
->setAttribute('label', $options['label'] ?: $this->humanize($builder->getName()))
->setData($options['data'])
->addValidator(new DefaultValidator())
@ -56,36 +57,40 @@ class FieldType extends AbstractType
public function buildView(FormView $view, FormInterface $form)
{
$name = $form->getName();
if ($view->hasParent()) {
$parentId = $view->getParent()->get('id');
$parentName = $view->getParent()->get('name');
$id = sprintf('%s_%s', $parentId, $form->getName());
$name = sprintf('%s[%s]', $parentName, $form->getName());
$parentFullName = $view->getParent()->get('full_name');
$id = sprintf('%s_%s', $parentId, $name);
$fullName = sprintf('%s[%s]', $parentFullName, $name);
} else {
$id = $form->getName();
$name = $form->getName();
$id = $name;
$fullName = $name;
}
$types = array();
foreach (array_reverse((array) $form->getTypes()) as $type) {
$types[] = $type->getName();
}
$view
->set('form', $view)
->set('id', $id)
->set('name', $name)
->set('full_name', $fullName)
->set('errors', $form->getErrors())
->set('value', $form->getClientData())
->set('read_only', $form->isReadOnly())
->set('required', $form->isRequired())
->set('max_length', $form->getAttribute('max_length'))
->set('pattern', $form->getAttribute('pattern'))
->set('size', null)
->set('label', $form->getAttribute('label'))
->set('multipart', false)
->set('attr', array())
->set('types', $types)
;
$types = array();
foreach (array_reverse((array) $form->getTypes()) as $type) {
$types[] = $type->getName();
}
$view->set('types', $types);
}
public function getDefaultOptions(array $options)
@ -97,6 +102,7 @@ class FieldType extends AbstractType
'required' => true,
'read_only' => false,
'max_length' => null,
'pattern' => null,
'property_path' => null,
'by_reference' => true,
'error_bubbling' => false,

View File

@ -52,13 +52,13 @@ class FileType extends AbstractType
{
$view->set('multipart', true);
$view['file']->set('type', 'file');
$view['file']->set('value', '');
}
public function getDefaultOptions(array $options)
{
return array(
'type' => 'string',
'csrf_protection' => false,
);
}

View File

@ -29,7 +29,7 @@ class FormType extends AbstractType
{
$multipart = false;
foreach ($view as $child) {
foreach ($view->getChildren() as $child) {
if ($child->get('multipart')) {
$multipart = true;
break;

View File

@ -35,7 +35,7 @@ class RadioType extends AbstractType
;
if ($view->hasParent()) {
$view->set('name', $view->getParent()->get('name'));
$view->set('full_name', $view->getParent()->get('full_name'));
}
}

View File

@ -36,7 +36,6 @@ class RepeatedType extends AbstractType
'options' => array(),
'first_name' => 'first',
'second_name' => 'second',
'csrf_protection' => false,
'error_bubbling' => false,
);
}

View File

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Extension\Core\Type;
use Symfony\Component\Form\AbstractType;
class SearchType extends AbstractType
{
public function getParent(array $options)
{
return 'text';
}
public function getName()
{
return 'search';
}
}

View File

@ -94,10 +94,8 @@ class TimeType extends AbstractType
'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,

View File

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

View File

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Extension\Csrf\Type;
use Symfony\Component\Form\AbstractTypeExtension;
class ChoiceTypeCsrfExtension extends AbstractTypeExtension
{
public function getDefaultOptions(array $options)
{
return array('csrf_protection' => false);
}
public function getExtendedType()
{
return 'choice';
}
}

View File

@ -22,11 +22,25 @@ class CsrfType extends AbstractType
{
private $csrfProvider;
/**
* Constructor.
*
* @param CsrfProviderInterface $csrfProvider The provider to use to generate the token
*/
public function __construct(CsrfProviderInterface $csrfProvider)
{
$this->csrfProvider = $csrfProvider;
}
/**
* Builds the CSRF field.
*
* A validator is added to check the token value when the CSRF field is added to
* a root form
*
* @param FormBuilder $builder The form builder
* @param array $options The options
*/
public function buildForm(FormBuilder $builder, array $options)
{
$csrfProvider = $options['csrf_provider'];
@ -47,20 +61,31 @@ class CsrfType extends AbstractType
;
}
/**
* {@inheritDoc}
*/
public function getDefaultOptions(array $options)
{
return array(
'csrf_provider' => $this->csrfProvider,
'intention' => null,
'intention' => null,
'property_path' => false,
);
}
/**
* {@inheritDoc}
*/
public function getParent(array $options)
{
return 'hidden';
}
/**
* Returns the name of this form.
*
* @return string 'csrf'
*/
public function getName()
{
return 'csrf';

View File

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Extension\Csrf\Type;
use Symfony\Component\Form\AbstractTypeExtension;
class DateTypeCsrfExtension extends AbstractTypeExtension
{
public function getDefaultOptions(array $options)
{
return array('csrf_protection' => false);
}
public function getExtendedType()
{
return 'date';
}
}

View File

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Extension\Csrf\Type;
use Symfony\Component\Form\AbstractTypeExtension;
class FileTypeCsrfExtension extends AbstractTypeExtension
{
public function getDefaultOptions(array $options)
{
return array('csrf_protection' => false);
}
public function getExtendedType()
{
return 'file';
}
}

View File

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

View File

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Extension\Csrf\Type;
use Symfony\Component\Form\AbstractTypeExtension;
class RepeatedTypeCsrfExtension extends AbstractTypeExtension
{
public function getDefaultOptions(array $options)
{
return array('csrf_protection' => false);
}
public function getExtendedType()
{
return 'repeated';
}
}

View File

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Extension\Csrf\Type;
use Symfony\Component\Form\AbstractTypeExtension;
class TimeTypeCsrfExtension extends AbstractTypeExtension
{
public function getDefaultOptions(array $options)
{
return array('csrf_protection' => false);
}
public function getExtendedType()
{
return 'time';
}
}

View File

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

View File

@ -24,13 +24,6 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
*
* A form is composed of a validator schema and a widget form schema.
*
* Form also takes care of CSRF protection by default.
*
* A CSRF secret can be any random string. If set to false, it disables the
* CSRF protection, and if set to null, it forces the form to use the global
* CSRF secret. If the global CSRF secret is also null, then a random one
* is generated on the fly.
*
* To implement your own form fields, you need to have a thorough understanding
* of the data flow within a form field. A form field stores its data in three
* different representations:
@ -459,6 +452,8 @@ class Form implements \IteratorAggregate, FormInterface
public function bind($clientData)
{
if ($this->readOnly) {
$this->bound = true;
return $this;
}

View File

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

View File

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

View File

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

View File

@ -142,7 +142,7 @@ class PropertyPath implements \IteratorAggregate
*/
public function isProperty($index)
{
return !$this->isIndex($index);
return !$this->isIndex[$index];
}
/**
@ -186,7 +186,25 @@ class PropertyPath implements \IteratorAggregate
*/
public function getValue($objectOrArray)
{
return $this->readPropertyPath($objectOrArray, 0);
for ($i = 0; $i < $this->length; ++$i) {
if (is_object($objectOrArray)) {
$value = $this->readProperty($objectOrArray, $i);
// arrays need to be treated separately (due to PHP bug?)
// http://bugs.php.net/bug.php?id=52133
} else if (is_array($objectOrArray)){
$property = $this->elements[$i];
if (!array_key_exists($property, $objectOrArray)) {
$objectOrArray[$property] = $i + 1 < $this->length ? array() : null;
}
$value =& $objectOrArray[$property];
} else {
throw new UnexpectedTypeException($objectOrArray, 'object or array');
}
$objectOrArray =& $value;
}
return $value;
}
/**
@ -219,78 +237,30 @@ class PropertyPath implements \IteratorAggregate
*/
public function setValue(&$objectOrArray, $value)
{
$this->writePropertyPath($objectOrArray, 0, $value);
}
for ($i = 0, $l = $this->length - 1; $i < $l; ++$i) {
/**
* Recursive implementation of getValue()
*
* @param object|array $objectOrArray The object or array to traverse
* @param integer $currentIndex The current index in the property path
* @return mixed The value at the end of the path
*/
protected function readPropertyPath(&$objectOrArray, $currentIndex)
{
if (!is_object($objectOrArray) && !is_array($objectOrArray)) {
throw new UnexpectedTypeException($objectOrArray, 'object or array');
}
$property = $this->elements[$currentIndex];
if (is_object($objectOrArray)) {
$value = $this->readProperty($objectOrArray, $currentIndex);
// arrays need to be treated separately (due to PHP bug?)
// http://bugs.php.net/bug.php?id=52133
} else {
if (!array_key_exists($property, $objectOrArray)) {
$objectOrArray[$property] = $currentIndex + 1 < $this->length ? array() : null;
}
$value =& $objectOrArray[$property];
}
++$currentIndex;
if ($currentIndex < $this->length) {
return $this->readPropertyPath($value, $currentIndex);
}
return $value;
}
/**
* Recursive implementation of setValue()
*
* @param object|array $objectOrArray The object or array to traverse
* @param integer $currentIndex The current index in the property path
* @param mixed $value The value to set at the end of the
* property path
*/
protected function writePropertyPath(&$objectOrArray, $currentIndex, $value)
{
if (!is_object($objectOrArray) && !is_array($objectOrArray)) {
throw new UnexpectedTypeException($objectOrArray, 'object or array');
}
$property = $this->elements[$currentIndex];
if ($currentIndex + 1 < $this->length) {
if (is_object($objectOrArray)) {
$nestedObject = $this->readProperty($objectOrArray, $currentIndex);
$nestedObject = $this->readProperty($objectOrArray, $i);
// arrays need to be treated separately (due to PHP bug?)
// http://bugs.php.net/bug.php?id=52133
} else {
} else if (is_array($objectOrArray)) {
$property = $this->elements[$i];
if (!array_key_exists($property, $objectOrArray)) {
$objectOrArray[$property] = array();
}
$nestedObject =& $objectOrArray[$property];
} else {
throw new UnexpectedTypeException($objectOrArray, 'object or array');
}
$this->writePropertyPath($nestedObject, $currentIndex + 1, $value);
} else {
$this->writeProperty($objectOrArray, $currentIndex, $value);
$objectOrArray =& $nestedObject;
}
if (!is_object($objectOrArray) && !is_array($objectOrArray)) {
throw new UnexpectedTypeException($objectOrArray, 'object or array');
}
$this->writeProperty($objectOrArray, $i, $value);
}
/**
@ -311,9 +281,10 @@ class PropertyPath implements \IteratorAggregate
return $object[$property];
} else {
$camelProp = $this->camelize($property);
$reflClass = new \ReflectionClass($object);
$getter = 'get'.$this->camelize($property);
$isser = 'is'.$this->camelize($property);
$getter = 'get'.$camelProp;
$isser = 'is'.$camelProp;
if ($reflClass->hasMethod($getter)) {
if (!$reflClass->getMethod($getter)->isPublic()) {

View File

@ -61,6 +61,39 @@ class Cookie
$this->httpOnly = (Boolean) $httpOnly;
}
public function __toString()
{
$str = urlencode($this->getName()).'=';
if ('' === (string) $this->getValue()) {
$str .= 'deleted; expires='.gmdate("D, d-M-Y H:i:s T", time() - 31536001);
} else {
$str .= urlencode($this->getValue());
if ($this->getExpiresTime() !== 0) {
$str .= '; expires='.gmdate("D, d-M-Y H:i:s T", $this->getExpiresTime());
}
}
if (null !== $this->getPath()) {
$str .= '; path='.$this->getPath();
}
if (null !== $this->getDomain()) {
$str .= '; domain='.$this->getDomain();
}
if (true === $this->isSecure()) {
$str .= '; secure';
}
if (true === $this->isHttpOnly()) {
$str .= '; httponly';
}
return $str;
}
public function getName()
{
return $this->name;

View File

@ -602,7 +602,7 @@ class Request
if (null === $this->method) {
$this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET'));
if ('POST' === $this->method) {
$this->method = strtoupper($this->request->get('_method', 'POST'));
$this->method = strtoupper($this->server->get('X-HTTP-METHOD-OVERRIDE', $this->request->get('_method', 'POST')));
}
}

View File

@ -33,6 +33,19 @@ class ResponseHeaderBag extends HeaderBag
$this->set('cache-control', '');
}
}
/**
* {@inheritdoc}
*/
public function __toString()
{
$cookies = '';
foreach ($this->cookies as $cookie) {
$cookies .= 'Set-Cookie: '.$cookie."\r\n";
}
return parent::__toString().$cookies;
}
/**
* {@inheritdoc}

View File

@ -36,7 +36,7 @@ class Session implements \Serializable
{
$this->storage = $storage;
$this->defaultLocale = $defaultLocale;
$this->attributes = array('_flash' => array(), '_locale' => $this->getDefaultLocale());
$this->attributes = array('_flash' => array(), '_locale' => $this->defaultLocale);
$this->started = false;
}
@ -58,7 +58,7 @@ class Session implements \Serializable
}
if (!isset($this->attributes['_locale'])) {
$this->attributes['_locale'] = $this->getDefaultLocale();
$this->attributes['_locale'] = $this->defaultLocale;
}
// flag current flash messages to be removed at shutdown
@ -194,6 +194,10 @@ class Session implements \Serializable
*/
public function getLocale()
{
if (!isset($this->attributes['_locale'])) {
$this->attributes['_locale'] = $this->defaultLocale;
}
return $this->attributes['_locale'];
}
@ -228,7 +232,7 @@ class Session implements \Serializable
public function getFlash($name, $default = null)
{
return array_key_exists($name, $this->attributes['_flash']) ? $this->attributes['_flash'][$name] : $default;
return array_key_exists($name, $this->getFlashes()) ? $this->attributes['_flash'][$name] : $default;
}
public function setFlash($name, $value)
@ -283,7 +287,9 @@ class Session implements \Serializable
public function __destruct()
{
$this->save();
if (true === $this->started) {
$this->save();
}
}
public function serialize()
@ -297,9 +303,4 @@ class Session implements \Serializable
$this->attributes = array();
$this->started = false;
}
private function getDefaultLocale()
{
return $this->defaultLocale;
}
}

View File

@ -0,0 +1,45 @@
<?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.
*/
/**
* Stub implementation for the intl_is_failure function of the intl extension
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
* @param integer $errorCode The error code returned by intl_get_error_code()
* @return Boolean Whether the error code indicates an error
* @see Symfony\Component\Locale\Stub\StubIntl::isFailure
*/
function intl_is_failure($errorCode) {
return \Symfony\Component\Locale\Stub\StubIntl::isFailure($errorCode);
}
/**
* Stub implementation for the intl_get_error_code function of the intl extension
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
* @return Boolean The error code of the last intl function call or
* StubIntl::U_ZERO_ERROR if no error occurred
* @see Symfony\Component\Locale\Stub\StubIntl::getErrorCode
*/
function intl_get_error_code() {
return \Symfony\Component\Locale\Stub\StubIntl::getErrorCode();
}
/**
* Stub implementation for the intl_get_error_code function of the intl extension
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
* @return Boolean The error message of the last intl function call or
* "U_ZERO_ERROR" if no error occurred
* @see Symfony\Component\Locale\Stub\StubIntl::getErrorMessage
*/
function intl_get_error_message() {
return \Symfony\Component\Locale\Stub\StubIntl::getErrorMessage();
}

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Locale\Stub\DateFormat;
use Symfony\Component\Locale\Exception\NotImplementedException;
use Symfony\Component\Locale\Stub\StubIntl;
use Symfony\Component\Locale\Stub\DateFormat\MonthTransformer;
/**
@ -275,6 +276,8 @@ class FullTransformer
// If month is false, return immediately (intl behavior)
if (false === $month) {
StubIntl::setErrorCode(StubIntl::U_PARSE_ERROR);
return false;
}

View File

@ -0,0 +1,112 @@
<?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\Locale\Stub;
/**
* Provides fake static versions of the global functions in the intl extension
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
*/
abstract class StubIntl
{
/**
* Indicates that no error occurred
* @var integer
*/
const U_ZERO_ERROR = 0;
/**
* Indicates that an invalid argument was passed
* @var integer
*/
const U_ILLEGAL_ARGUMENT_ERROR = 1;
/**
* Indicates that the parse() operation failed
* @var integer
*/
const U_PARSE_ERROR = 9;
/**
* All known error codes
* @var array
*/
private static $errorCodes = array(
self::U_ZERO_ERROR,
self::U_ILLEGAL_ARGUMENT_ERROR,
self::U_PARSE_ERROR,
);
/**
* The error messages of all known error codes
* @var array
*/
private static $errorMessages = array(
self::U_ZERO_ERROR => 'U_ZERO_ERROR',
self::U_ILLEGAL_ARGUMENT_ERROR => 'datefmt_format: takes either an array or an integer timestamp value : U_ILLEGAL_ARGUMENT_ERROR',
self::U_PARSE_ERROR => 'Date parsing failed: U_PARSE_ERROR',
);
/**
* The error code of the last operation
* @var integer
*/
private static $errorCode = self::U_ZERO_ERROR;
/**
* Returns whether the error code indicates a failure
*
* @param integer $errorCode The error code returned by StubIntl::getErrorCode()
* @return Boolean
*/
public static function isFailure($errorCode) {
return in_array($errorCode, static::$errorCodes, true)
&& $errorCode !== self::U_ZERO_ERROR;
}
/**
* Returns the error code of the last operation
*
* Returns StubIntl::U_ZERO_ERROR if no error occurred.
*
* @return integer
*/
public static function getErrorCode() {
return static::$errorCode;
}
/**
* Returns the error message of the last operation
*
* Returns "U_ZERO_ERROR" if no error occurred.
*
* @return string
*/
public static function getErrorMessage() {
return static::$errorMessages[static::$errorCode];
}
/**
* Sets the current error code
*
* @param integer $code One of the error constants in this class
* @throws \InvalidArgumentException If the code is not one of the error
* constants in this class
*/
public static function setErrorCode($code) {
if (!isset(static::$errorMessages[$code])) {
throw new \InvalidArgumentException(sprintf('No such error code: "%s"', $code));
}
static::$errorCode = $code;
}
}

View File

@ -160,10 +160,18 @@ class StubIntlDateFormatter
*/
public function format($timestamp)
{
if (!is_int($timestamp)) {
// intl allows timestamps to be passed as arrays - we don't
if (is_array($timestamp)) {
throw new MethodArgumentValueNotImplementedException(__METHOD__, 'timestamp', $timestamp, 'Only integer unix timestamps are supported');
}
if (!is_int($timestamp)) {
// behave like the intl extension
StubIntl::setErrorCode(StubIntl::U_ILLEGAL_ARGUMENT_ERROR);
return false;
}
$transformer = new FullTransformer($this->getPattern(), $this->getTimeZoneId());
$formatted = $transformer->format($this->createDateTime($timestamp));
@ -311,6 +319,8 @@ class StubIntlDateFormatter
throw new MethodArgumentNotImplementedException(__METHOD__, 'position');
}
StubIntl::setErrorCode(StubIntl::U_ZERO_ERROR);
$dateTime = $this->createDateTime(0);
$transformer = new FullTransformer($this->getPattern(), $this->getTimeZoneId());
return $transformer->parse($dateTime, $value);

View File

@ -46,7 +46,7 @@ class Process
*
* @api
*/
public function __construct($commandline, $cwd = null, array $env = array(), $stdin = null, $timeout = 60, array $options = array())
public function __construct($commandline, $cwd = null, array $env = null, $stdin = null, $timeout = 60, array $options = array())
{
if (!function_exists('proc_open')) {
throw new \RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.');
@ -54,13 +54,17 @@ class Process
$this->commandline = $commandline;
$this->cwd = null === $cwd ? getcwd() : $cwd;
$this->env = array();
foreach ($env as $key => $value) {
$this->env[(binary) $key] = (binary) $value;
if (null !== $env) {
$this->env = array();
foreach ($env as $key => $value) {
$this->env[(binary) $key] = (binary) $value;
}
} else {
$this->env = null;
}
$this->stdin = $stdin;
$this->timeout = $timeout;
$this->options = array_merge(array('suppress_errors' => true, 'binary_pipes' => true, 'bypass_shell' => true), $options);
$this->options = array_merge(array('suppress_errors' => true, 'binary_pipes' => true, 'bypass_shell' => false), $options);
}
/**

View File

@ -1,46 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Routing\Annotation;
/**
* Annotation class for @Routes().
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Routes
{
private $routes;
/**
* Constructor.
*
* @param array $data An array of key/value parameters.
*/
public function __construct(array $data)
{
if (!isset($data['value']) || !is_array($data['value'])) {
throw new \LogicException('A @Routes annotation must have an array of @Route annotation as argument.');
}
$this->routes = $data['value'];
}
public function setRoutes($routes)
{
$this->routes = $routes;
}
public function getRoutes()
{
return $this->routes;
}
}

View File

@ -11,7 +11,7 @@
namespace Symfony\Component\Routing\Loader;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\Reader;
use Symfony\Component\Routing\Annotation\Route as RouteAnnotation;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\Routing\Route;
@ -59,14 +59,13 @@ abstract class AnnotationClassLoader implements LoaderInterface
{
protected $reader;
protected $routeAnnotationClass = 'Symfony\\Component\\Routing\\Annotation\\Route';
protected $routesAnnotationClass = 'Symfony\\Component\\Routing\\Annotation\\Routes';
/**
* Constructor.
*
* @param AnnotationReader $reader
* @param Reader $reader
*/
public function __construct(AnnotationReader $reader)
public function __construct(Reader $reader)
{
$this->reader = $reader;
}
@ -81,16 +80,6 @@ abstract class AnnotationClassLoader implements LoaderInterface
$this->routeAnnotationClass = $class;
}
/**
* Sets the annotation class to read routes properties from.
*
* @param string $class A fully-qualified class name
*/
public function setRoutesAnnotationClass($class)
{
$this->routesAnnotationClass = $class;
}
/**
* Loads from annotations from a class.
*
@ -137,12 +126,10 @@ abstract class AnnotationClassLoader implements LoaderInterface
$collection->addResource(new FileResource($class->getFileName()));
foreach ($class->getMethods() as $method) {
if ($annots = $this->reader->getMethodAnnotation($method, $this->routesAnnotationClass)) {
foreach ($annots->getRoutes() as $annot) {
foreach ($this->reader->getMethodAnnotations($method) as $annot) {
if ($annot instanceof $this->routeAnnotationClass) {
$this->addRoute($collection, $annot, $globals, $class, $method);
}
} elseif ($annot = $this->reader->getMethodAnnotation($method, $this->routeAnnotationClass)) {
$this->addRoute($collection, $annot, $globals, $class, $method);
}
}

View File

@ -65,10 +65,8 @@ class YamlFileLoader extends FileLoader
$prefix = isset($config['prefix']) ? $config['prefix'] : null;
$this->setCurrentDir(dirname($path));
$collection->addCollection($this->import($config['resource'], $type, false, $file), $prefix);
} elseif (isset($config['pattern'])) {
$this->parseRoute($collection, $name, $config, $path);
} else {
throw new \InvalidArgumentException(sprintf('Unable to parse the "%s" route.', $name));
$this->parseRoute($collection, $name, $config, $path);
}
}

View File

@ -156,7 +156,7 @@ class Serializer implements SerializerInterface
return $this->denormalizerCache[$class][$format]->denormalize($data, $class, $format);
}
foreach ($this->normalizers as $normalizer) {
if ($normalizer->supportsDenormalization($class, $format)) {
if ($normalizer->supportsDenormalization($data, $class, $format)) {
$this->denormalizerCache[$class][$format] = $normalizer;
return $normalizer->denormalize($data, $class, $format);
}

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