Vendors update

Signed-off-by: Polonkai Gergely <polesz@w00d5t0ck.info>
This commit is contained in:
Polonkai Gergely 2012-07-31 09:40:41 +02:00
parent 33b90a5c9f
commit 623b78e939
234 changed files with 3120 additions and 1292 deletions

0
vendor/doctrine/dbal/run-all.sh vendored Executable file → Normal file
View File

View File

@ -134,6 +134,7 @@ class Configuration implements ConfigurationInterface
->children()
->scalarNode('driver')->defaultValue('pdo_mysql')->end()
->scalarNode('platform_service')->end()
->scalarNode('schema_filter')->end()
->booleanNode('logging')->defaultValue($this->debug)->end()
->booleanNode('profiling')->defaultValue($this->debug)->end()
->scalarNode('driver_class')->end()
@ -302,6 +303,7 @@ class Configuration implements ConfigurationInterface
->scalarNode('class_metadata_factory_name')->defaultValue('Doctrine\ORM\Mapping\ClassMetadataFactory')->end()
->scalarNode('default_repository_class')->defaultValue('Doctrine\ORM\EntityRepository')->end()
->scalarNode('auto_mapping')->defaultFalse()->end()
->scalarNode('naming_strategy')->defaultValue('doctrine.orm.naming_strategy.default')->end()
->end()
->fixXmlConfig('hydrator')
->children()

View File

@ -120,6 +120,12 @@ class DoctrineExtension extends AbstractDoctrineExtension
}
unset($connection['profiling']);
if (isset($connection['schema_filter']) && $connection['schema_filter']) {
$configuration->addMethodCall('setFilterSchemaAssetsExpression', array($connection['schema_filter']));
}
unset($connection['schema_filter']);
if ($logger) {
$configuration->addMethodCall('setSQLLogger', array($logger));
}
@ -282,6 +288,12 @@ class DoctrineExtension extends AbstractDoctrineExtension
'setClassMetadataFactoryName' => $entityManager['class_metadata_factory_name'],
'setDefaultRepositoryClassName' => $entityManager['default_repository_class'],
);
// check for version to keep BC
if (version_compare(\Doctrine\ORM\Version::VERSION, "2.3.0-DEV") >= 0) {
$methods = array_merge($methods, array(
'setNamingStrategy' => new Reference($entityManager['naming_strategy']),
));
}
foreach ($methods as $method => $arg) {
$ormConfigDef->addMethodCall($method, array($arg));
}

View File

@ -45,6 +45,10 @@
<!-- listeners -->
<parameter key="doctrine.orm.listeners.resolve_target_entity.class">Doctrine\ORM\Tools\ResolveTargetEntityListener</parameter>
<!-- naming strategy -->
<parameter key="doctrine.orm.naming_strategy.default.class">Doctrine\ORM\Mapping\DefaultNamingStrategy</parameter>
<parameter key="doctrine.orm.naming_strategy.underscore.class">Doctrine\ORM\Mapping\UnderscoreNamingStrategy</parameter>
</parameters>
<services>
@ -93,5 +97,9 @@
<!-- listeners -->
<service id="doctrine.orm.listeners.resolve_target_entity" class="%doctrine.orm.listeners.resolve_target_entity.class%" public="false" />
<!-- naming strategy -->
<service id="doctrine.orm.naming_strategy.default" class="%doctrine.orm.naming_strategy.default.class%" public="false" />
<service id="doctrine.orm.naming_strategy.underscore" class="%doctrine.orm.naming_strategy.underscore.class%" public="false" />
</services>
</container>

View File

@ -19,6 +19,7 @@
<xsd:attribute name="driver-class" type="xsd:string" />
<xsd:attribute name="wrapper-class" type="xsd:string" />
<xsd:attribute name="platform-service" type="xsd:string" />
<xsd:attribute name="schema-filter" type="xsd:string" />
<xsd:attribute name="logging" type="xsd:string" default="false" />
<xsd:attribute name="profiling" type="xsd:string" default="false" />
<xsd:attributeGroup ref="driver-config" />
@ -161,6 +162,7 @@
<xsd:attribute name="result-cache-driver" type="xsd:string" />
<xsd:attribute name="metadata-cache-driver" type="xsd:string" />
<xsd:attribute name="query-cache-driver" type="xsd:string" />
<xsd:attribute name="naming-strategy" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="filter">

View File

@ -30,6 +30,7 @@ Configuration Reference
charset: UTF8
logging: %kernel.debug%
platform_service: MyOwnDatabasePlatformService
schema_filter: ^sf2_
mapping_types:
enum: string
conn1:
@ -64,6 +65,7 @@ Configuration Reference
test_numeric: Acme\HelloBundle\DQL\NumericFunction
datetime_functions:
test_datetime: Acme\HelloBundle\DQL\DatetimeFunction
naming_strategy: doctrine.orm.naming_strategy.default # Service Reference
em2:
# ...
@ -93,6 +95,7 @@ Configuration Reference
charset="UTF8"
logging="%kernel.debug%"
platform-service="MyOwnDatabasePlatformService"
schema-filter="^sf2_"
>
<doctrine:option key="foo">bar</doctrine:option>
<doctrine:mapping-type name="enum">string</doctrine:mapping-type>
@ -102,7 +105,7 @@ Configuration Reference
</doctrine:dbal>
<doctrine:orm default-entity-manager="default" auto-generate-proxy-classes="false" proxy-namespace="Proxies" proxy-dir="%kernel.cache_dir%/doctrine/orm/Proxies">
<doctrine:entity-manager name="default" query-cache-driver="array" result-cache-driver="array" connection="conn1" class-metadata-factory-name="Doctrine\ORM\Mapping\ClassMetadataFactory">
<doctrine:entity-manager name="default" query-cache-driver="array" result-cache-driver="array" connection="conn1" class-metadata-factory-name="Doctrine\ORM\Mapping\ClassMetadataFactory" naming-strategy="doctrine.orm.naming_strategy.default">
<doctrine:metadata-cache-driver type="memcache" host="localhost" port="11211" instance-class="Memcache" class="Doctrine\Common\Cache\MemcacheCache" />
<doctrine:mapping name="AcmeHelloBundle" />
<doctrine:dql>
@ -241,6 +244,7 @@ can configure. The following block shows all possible configuration keys:
charset: UTF8
logging: %kernel.debug%
platform_service: MyOwnDatabasePlatformService
schema_filter: ^sf2_
mapping_types:
enum: string
types:
@ -268,6 +272,7 @@ can configure. The following block shows all possible configuration keys:
charset="UTF8"
logging="%kernel.debug%"
platform-service="MyOwnDatabasePlatformService"
schema-filter="^sf2_"
>
<doctrine:option key="foo">bar</doctrine:option>
<doctrine:mapping-type name="enum">string</doctrine:mapping-type>

View File

@ -3,7 +3,7 @@
{% block toolbar %}
{% set icon %}
<img width="20" height="28" alt="Database" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAcCAYAAABh2p9gAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQRJREFUeNpi/P//PwM1ARMDlcGogZQDlpMnT7pxc3NbA9nhQKxOpL5rQLwJiPeBsI6Ozl+YBOOOHTv+AOllQNwtLS39F2owKYZ/gRq8G4i3ggxEToggWzvc3d2Pk+1lNL4fFAs6ODi8JzdS7mMRVyDVoAMHDsANdAPiOCC+jCQvQKqBQB/BDbwBxK5AHA3E/kB8nKJkA8TMQBwLxaBIKQbi70AvTADSBiSadwFXpCikpKQU8PDwkGTaly9fHFigkaKIJid4584dkiMFFI6jkTJII0WVmpHCAixZQEXWYhDeuXMnyLsVlEQKI45qFBQZ8eRECi4DBaAlDqle/8A48ip6gAADANdQY88Uc0oGAAAAAElFTkSuQmCC"/>
<span class="sf-toolbar-status">{{ collector.querycount }}</span>
<span class="sf-toolbar-status{% if 50 < collector.querycount %} sf-toolbar-status-yellow{% endif %}">{{ collector.querycount }}</span>
<span class="sf-toolbar-info-piece-additional-detail">in {{ '%0.2f'|format(collector.time * 1000) }} ms</span>
{% endset %}
{% set text %}

View File

@ -194,6 +194,10 @@ abstract class AbstractDoctrineExtensionTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('doctrine.orm.default_query_cache', (string) $calls[2][1][0]);
$this->assertEquals('doctrine.orm.default_result_cache', (string) $calls[3][1][0]);
if (version_compare(\Doctrine\ORM\Version::VERSION, "2.3.0-DEV") >= 0) {
$this->assertEquals('doctrine.orm.naming_strategy.default', (string) $calls[10][1][0]);
}
$definition = $container->getDefinition('doctrine.orm.default_metadata_cache');
$this->assertEquals('%doctrine.orm.cache.array.class%', $definition->getClass());
@ -701,6 +705,25 @@ abstract class AbstractDoctrineExtensionTest extends \PHPUnit_Framework_TestCase
$this->assertDICDefinitionMethodCallOnce($definition, 'addCustomDatetimeFunction', array('test_datetime', 'Symfony\Bundle\DoctrineBundle\Tests\DependencyInjection\TestDatetimeFunction'));
}
public function testSetNamingStrategy()
{
if (version_compare(\Doctrine\ORM\Version::VERSION, "2.3.0-DEV") < 0) {
$this->markTestSkipped('Naming Strategies are not available');
}
$container = $this->getContainer(array('YamlBundle'));
$loader = new DoctrineExtension();
$container->registerExtension($loader);
$this->loadFromFile($container, 'orm_namingstrategy');
$this->compileContainer($container);
$def1 = $container->getDefinition('doctrine.orm.em1_configuration');
$def2 = $container->getDefinition('doctrine.orm.em2_configuration');
$this->assertDICDefinitionMethodCallOnce($def1, 'setNamingStrategy', array(0 => new Reference('doctrine.orm.naming_strategy.default')));
$this->assertDICDefinitionMethodCallOnce($def2, 'setNamingStrategy', array(0 => new Reference('doctrine.orm.naming_strategy.underscore')));
}
public function testSingleEMSetCustomFunctions()
{
$container = $this->getContainer(array('YamlBundle'));
@ -764,6 +787,20 @@ abstract class AbstractDoctrineExtensionTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(array('doctrine.event_listener' => array( array('event' => 'loadClassMetadata') ) ), $definition->getTags());
}
public function testDbalSchemaFilter()
{
$container = $this->getContainer();
$loader = new DoctrineExtension();
$container->registerExtension($loader);
$this->loadFromFile($container, 'dbal_schema_filter');
$this->compileContainer($container);
$definition = $container->getDefinition('doctrine.dbal.default_connection.configuration');
$this->assertDICDefinitionMethodCallOnce($definition, 'setFilterSchemaAssetsExpression', array('^sf2_'));
}
protected function getContainer($bundles = 'YamlBundle', $vendor = null)
{
$bundles = (array) $bundles;

View File

@ -0,0 +1,12 @@
<?xml version="1.0" ?>
<srv:container xmlns="http://symfony.com/schema/dic/doctrine"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:srv="http://symfony.com/schema/dic/services"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/doctrine http://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd">
<config>
<dbal schema-filter="^sf2_" />
</config>
</srv:container>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" ?>
<srv:container xmlns="http://symfony.com/schema/dic/doctrine"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:srv="http://symfony.com/schema/dic/services"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/doctrine http://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd">
<config>
<dbal default-connection="default">
<connection name="default" dbname="db" />
</dbal>
<orm default-entity-manager="em1">
<entity-manager name="em1" naming-strategy="doctrine.orm.naming_strategy.default">
<mapping name="YamlBundle" />
</entity-manager>
<entity-manager name="em2" naming-strategy="doctrine.orm.naming_strategy.underscore">
<mapping name="YamlBundle" />
</entity-manager>
</orm>
</config>
</srv:container>

View File

@ -0,0 +1,3 @@
doctrine:
dbal:
schema_filter: ^sf2_

View File

@ -0,0 +1,18 @@
doctrine:
dbal:
default_connection: default
connections:
default:
dbname: db
orm:
default_entity_manager: em1
entity_managers:
em1:
mappings:
YamlBundle: ~
naming_strategy: doctrine.orm.naming_strategy.default
em2:
mappings:
YamlBundle: ~
naming_strategy: doctrine.orm.naming_strategy.underscore

View File

@ -38,23 +38,28 @@ class XMLSchemaTest extends \PHPUnit_Framework_TestCase
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->load($file);
$xmlns = "http://symfony.com/schema/dic/doctrine";
$dbalElements = $dom->getElementsByTagNameNS("http://symfony.com/schema/dic/doctrine", "config");
$dbalElements = $dom->getElementsByTagNameNS($xmlns, 'dbal');
if ($dbalElements->length) {
$dbalDom = new \DOMDocument('1.0', 'UTF-8');
$dbalNode = $dbalDom->importNode($dbalElements->item(0));
$dbalDom->appendChild($dbalNode);
$configNode = $dbalDom->createElementNS($xmlns, 'config');
$configNode->appendChild($dbalNode);
$dbalDom->appendChild($configNode);
$ret = $dbalDom->schemaValidate(__DIR__."/../../Resources/config/schema/doctrine-1.0.xsd");
$this->assertTrue($ret, "DoctrineBundle Dependency Injection XMLSchema did not validate this XML instance.");
$found = true;
}
$ormElements = $dom->getElementsByTagNameNS("http://symfony.com/schema/dic/doctrine", "config");
$ormElements = $dom->getElementsByTagNameNS($xmlns, 'orm');
if ($ormElements->length) {
$ormDom = new \DOMDocument('1.0', 'UTF-8');
$ormNode = $ormDom->importNode($ormElements->item(0));
$ormDom->appendChild($ormNode);
$configNode = $ormDom->createElementNS($xmlns, 'config');
$configNode->appendChild($ormNode);
$ormDom->appendChild($configNode);
$ret = $ormDom->schemaValidate(__DIR__."/../../Resources/config/schema/doctrine-1.0.xsd");
$this->assertTrue($ret, "DoctrineBundle Dependency Injection XMLSchema did not validate this XML instance.");

@ -1 +1 @@
Subproject commit fe98141b1e460baf5ab52f9139e1ae238101b28b
Subproject commit b86b4cc0a39714f0aa53b908d495beacfa0e4011

View File

@ -142,8 +142,12 @@ class AnnotationDriver implements Driver
$classAnnotations = $this->_reader->getClassAnnotations($class);
if ($classAnnotations && is_numeric(key($classAnnotations))) {
foreach ($classAnnotations as $annot) {
if ($classAnnotations) {
foreach ($classAnnotations as $key => $annot) {
if ( ! is_numeric($key)) {
continue;
}
$classAnnotations[get_class($annot)] = $annot;
}
}
@ -432,8 +436,11 @@ class AnnotationDriver implements Driver
if ($method->isPublic() && $method->getDeclaringClass()->getName() == $class->name) {
$annotations = $this->_reader->getMethodAnnotations($method);
if ($annotations && is_numeric(key($annotations))) {
foreach ($annotations as $annot) {
if ($annotations) {
foreach ($annotations as $key => $annot) {
if ( ! is_numeric($key)) {
continue;
}
$annotations[get_class($annot)] = $annot;
}
}

View File

@ -284,7 +284,7 @@ final class PersistentCollection implements Collection
/**
* INTERNAL: Gets the association mapping of the collection.
*
* @return \Doctrine\ORM\Mapping\AssociationMapping
* @return array
*/
public function getMapping()
{

View File

@ -178,7 +178,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
}
// Composite identifier
$sourceClass = $this->_em->getClassMetadata(get_class($mapping->getOwner()));
$sourceClass = $this->_em->getClassMetadata(get_class($coll->getOwner()));
foreach ($mapping['relationToSourceKeyColumns'] as $relColumn => $srcColumn) {
$params[] = $identifier[$sourceClass->fieldNames[$srcColumn]];

View File

@ -21,7 +21,6 @@ namespace Doctrine\ORM\Proxy;
use Doctrine\ORM\EntityManager,
Doctrine\ORM\Mapping\ClassMetadata,
Doctrine\ORM\Mapping\AssociationMapping,
Doctrine\Common\Util\ClassUtils;
/**

View File

@ -21,6 +21,7 @@ namespace Doctrine\ORM;
use Doctrine\DBAL\LockMode,
Doctrine\ORM\Query\Parser,
Doctrine\ORM\Query\ParserResult,
Doctrine\ORM\Query\QueryException;
/**
@ -218,7 +219,7 @@ final class Query extends AbstractQuery
$hash = $this->_getQueryCacheId();
$cached = $this->_expireQueryCache ? false : $queryCache->fetch($hash);
if ($cached !== false) {
if ($cached instanceof ParserResult) {
// Cache hit.
$this->_parserResult = $cached;

View File

@ -94,7 +94,7 @@ class QueryException extends \Doctrine\ORM\ORMException
}
/**
* @param \Doctrine\ORM\Mapping\AssociationMapping $assoc
* @param array $assoc
*/
public static function iterateWithFetchJoinCollectionNotAllowed($assoc)
{

View File

@ -20,8 +20,8 @@
namespace Doctrine\ORM\Tools;
use Doctrine\ORM\Mapping\ClassMetadataInfo,
Doctrine\ORM\Mapping\AssociationMapping,
Doctrine\Common\Util\Inflector;
Doctrine\Common\Util\Inflector,
Doctrine\DBAL\Types\Type;
/**
* Generic class used to generate PHP5 entity classes from ClassMetadataInfo instances
@ -143,7 +143,7 @@ public function <methodName>(<methodTypeHint>$<variableName>)
'/**
* <description>
*
* @param <variableType$<variableName>
* @param <variableType>$<variableName>
*/
public function <methodName>(<methodTypeHint>$<variableName>)
{

View File

@ -23,7 +23,6 @@
namespace Doctrine\ORM\Tools\Export\Driver;
use Doctrine\ORM\Mapping\ClassMetadataInfo,
Doctrine\ORM\Mapping\AssociationMapping,
Doctrine\ORM\Tools\EntityGenerator;
/**

View File

@ -36,7 +36,7 @@ class Version
/**
* Current Doctrine Version
*/
const VERSION = '2.2.3-DEV';
const VERSION = '2.2.4-DEV';
/**
* Compares a Doctrine version with the current one.

View File

@ -30,12 +30,25 @@ class NavPointOfInterest
*/
private $country;
/**
* @ManyToMany(targetEntity="NavUser", cascade={"persist"})
* @JoinTable(name="navigation_pois_visitors",
* inverseJoinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* joinColumns={
* @JoinColumn(name="poi_long", referencedColumnName="nav_long"),
* @JoinColumn(name="poi_lat", referencedColumnName="nav_lat")
* }
* )
*/
private $visitors;
public function __construct($lat, $long, $name, $country)
{
$this->lat = $lat;
$this->long = $long;
$this->name = $name;
$this->country = $country;
$this->visitors = new \Doctrine\Common\Collections\ArrayCollection;
}
public function getLong() {
@ -53,4 +66,14 @@ class NavPointOfInterest
public function getCountry() {
return $this->country;
}
public function addVisitor(NavUser $user)
{
$this->visitors[] = $user;
}
public function getVisitors()
{
return $this->visitors;
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace Doctrine\Tests\Models\Navigation;
/**
* @Entity
* @Table(name="navigation_users")
*/
class NavUser
{
/**
* @Id
* @Column(type="integer")
* @generatedValue
*/
private $id;
/**
* @column(type="string")
*/
private $name;
public function __construct($name)
{
$this->name = $name;
}
}

View File

@ -5,6 +5,7 @@ use Doctrine\Tests\Models\Navigation\NavCountry;
use Doctrine\Tests\Models\Navigation\NavPointOfInterest;
use Doctrine\Tests\Models\Navigation\NavTour;
use Doctrine\Tests\Models\Navigation\NavPhotos;
use Doctrine\Tests\Models\Navigation\NavUser;
require_once __DIR__ . '/../../TestInit.php';
@ -118,4 +119,26 @@ class CompositePrimaryKeyTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->setExpectedException('Doctrine\ORM\ORMException', 'The identifier long is missing for a query of Doctrine\Tests\Models\Navigation\NavPointOfInterest');
$poi = $this->_em->find('Doctrine\Tests\Models\Navigation\NavPointOfInterest', array('key1' => 100));
}
/**
* @group DDC-1939
*/
public function testDeleteCompositePersistentCollection()
{
$this->putGermanysBrandenburderTor();
$poi = $this->_em->find('Doctrine\Tests\Models\Navigation\NavPointOfInterest', array('lat' => 100, 'long' => 200));
$poi->addVisitor(new NavUser("test1"));
$poi->addVisitor(new NavUser("test2"));
$this->_em->flush();
$poi->getVisitors()->clear();
$this->_em->flush();
$this->_em->clear();
$poi = $this->_em->find('Doctrine\Tests\Models\Navigation\NavPointOfInterest', array('lat' => 100, 'long' => 200));
$this->assertEquals(0, count($poi->getVisitors()));
}
}

View File

@ -88,6 +88,7 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
'Doctrine\Tests\Models\Routing\RoutingRouteBooking',
),
'navigation' => array(
'Doctrine\Tests\Models\Navigation\NavUser',
'Doctrine\Tests\Models\Navigation\NavCountry',
'Doctrine\Tests\Models\Navigation\NavPhotos',
'Doctrine\Tests\Models\Navigation\NavTour',

View File

@ -37,7 +37,7 @@ interface AssetCollectionInterface extends AssetInterface, \Traversable
*
* @param AssetInterface $needle The leaf to remove
*
* @throws InvalidArgumentException If the asset cannot be found
* @throws \InvalidArgumentException If the asset cannot be found
*/
function removeLeaf(AssetInterface $leaf);

View File

@ -31,7 +31,7 @@ class FileAsset extends BaseAsset
* @param string $sourceRoot The source asset root directory
* @param string $sourcePath The source asset path
*
* @throws InvalidArgumentException If the supplied root doesn't match the source when guessing the path
* @throws \InvalidArgumentException If the supplied root doesn't match the source when guessing the path
*/
public function __construct($source, $filters = array(), $sourceRoot = null, $sourcePath = null, array $vars = array())
{

View File

@ -31,7 +31,7 @@ class HttpAsset extends BaseAsset
* @param string $sourceUrl The source URL
* @param array $filters An array of filters
*
* @throws InvalidArgumentException If the first argument is not an URL
* @throws \InvalidArgumentException If the first argument is not an URL
*/
public function __construct($sourceUrl, $filters = array(), $ignoreErrors = false, array $vars = array())
{

View File

@ -29,7 +29,7 @@ class AssetManager
*
* @return AssetInterface The asset
*
* @throws InvalidArgumentException If there is no asset by that name
* @throws \InvalidArgumentException If there is no asset by that name
*/
public function get($name)
{

View File

@ -25,7 +25,7 @@ class AsseticNode extends \Twig_Node
* * var_name: The name of the variable to expose to the body node
*
* @param AssetInterface $asset The asset
* @param Twig_NodeInterface $body The body node
* @param \Twig_NodeInterface $body The body node
* @param array $inputs An array of input strings
* @param array $filters An array of filter strings
* @param string $name The name of the asset

View File

@ -111,7 +111,7 @@ class LazyAssetManager extends AssetManager
*
* @return array The formula
*
* @throws InvalidArgumentException If there is no formula by that name
* @throws \InvalidArgumentException If there is no formula by that name
*/
public function getFormula($name)
{
@ -140,7 +140,7 @@ class LazyAssetManager extends AssetManager
/**
* Loads formulae from resources.
*
* @throws LogicException If a resource has been added to an invalid loader
* @throws \LogicException If a resource has been added to an invalid loader
*/
public function load()
{

View File

@ -53,7 +53,7 @@ class FilterManager
*
* @param string $name An asset name candidate
*
* @throws InvalidArgumentException If the asset name is invalid
* @throws \InvalidArgumentException If the asset name is invalid
*/
protected function checkName($name)
{

View File

@ -49,6 +49,13 @@ class ParamConverter extends ConfigurationAnnotation
*/
protected $optional = false;
/**
* Use explicitly named converter instead of iterating by priorities.
*
* @var string
*/
protected $converter;
/**
* Returns the parameter name.
*
@ -139,6 +146,26 @@ class ParamConverter extends ConfigurationAnnotation
return $this->optional;
}
/**
* Get explicit converter name.
*
* @return string
*/
public function getConverter()
{
return $this->converter;
}
/**
* Set explicit converter name
*
* @param string $converter
*/
public function setConverter($converter)
{
$this->converter = $converter;
}
/**
* Returns the annotation alias name.
*

View File

@ -29,8 +29,18 @@ class AddParamConverterPass implements CompilerPassInterface
}
$definition = $container->getDefinition('sensio_framework_extra.converter.manager');
foreach ($container->findTaggedServiceIds('request.param_converter') as $id => $attributes) {
$definition->addMethodCall('add', array(new Reference($id), isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0));
foreach ($container->findTaggedServiceIds('request.param_converter') as $id => $converters) {
foreach ($converters as $converter) {
$name = isset($converter['converter']) ? $converter['converter'] : null;
$priority = isset($converter['priority']) ? $converter['priority'] : 0;
if ($priority === "false") {
$priority = null;
}
$definition->addMethodCall('add', array(new Reference($id), $priority, $name));
}
}
}
}

View File

@ -5,6 +5,7 @@ namespace Sensio\Bundle\FrameworkExtraBundle\EventListener;
use Doctrine\Common\Annotations\Reader;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ConfigurationInterface;
use Doctrine\Common\Util\ClassUtils;
/*
* This file is part of the Symfony framework.
@ -51,14 +52,33 @@ class ControllerListener
return;
}
$object = new \ReflectionObject($controller[0]);
$method = $object->getMethod($controller[1]);
$className = class_exists('Doctrine\Common\Util\ClassUtils') ? ClassUtils::getClass($controller[0]) : get_class($controller[0]);
$object = new \ReflectionClass($className);
$method = $object->getMethod($controller[1]);
$classConfigurations = $this->getConfigurations($this->reader->getClassAnnotations($object));
$methodConfigurations = $this->getConfigurations($this->reader->getMethodAnnotations($method));
$configurations = array_merge($classConfigurations, $methodConfigurations);
$request = $event->getRequest();
foreach (array_merge($this->reader->getClassAnnotations($object), $this->reader->getMethodAnnotations($method)) as $configuration) {
if ($configuration instanceof ConfigurationInterface) {
$request->attributes->set('_'.$configuration->getAliasName(), $configuration);
foreach ($configurations as $key => $attributes) {
if (is_array($attributes) && count($attributes) == 1) {
$attributes = $attributes[0];
}
$request->attributes->set($key, $attributes);
}
}
protected function getConfigurations(array $annotations)
{
$configurations = array();
foreach ($annotations as $configuration) {
if ($configuration instanceof ConfigurationInterface) {
$configurations['_'.$configuration->getAliasName()][] = $configuration;
}
}
return $configurations;
}
}

View File

@ -63,7 +63,7 @@ class ParamConverterListener
// automatically apply conversion for non-configured objects
foreach ($r->getParameters() as $param) {
if (!$param->getClass()) {
if (!$param->getClass() || $param->getClass()->isInstance($request)) {
continue;
}
@ -75,6 +75,8 @@ class ParamConverterListener
$configuration->setClass($param->getClass()->getName());
$configurations[$name] = $configuration;
} elseif (null === $configurations[$name]->getClass()) {
$configurations[$name]->setClass($param->getClass()->getName());
}
$configurations[$name]->setIsOptional($param->isOptional());

View File

@ -0,0 +1,59 @@
<?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 Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ConfigurationInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use DateTime;
/**
* Convert DateTime instances from request attribute variable.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class DateTimeParamConverter implements ParamConverterInterface
{
public function apply(Request $request, ConfigurationInterface $configuration)
{
$param = $configuration->getName();
if (!$request->attributes->has($param)) {
return false;
}
$options = $configuration->getOptions();
$value = $request->attributes->get($param);
$date = isset($options['format'])
? DateTime::createFromFormat($options['format'], $value)
: new DateTime($value);
if (!$date) {
throw new NotFoundHttpException('Invalid date given.');
}
$request->attributes->set($param, $date);
return true;
}
public function supports(ConfigurationInterface $configuration)
{
if (null === $configuration->getClass()) {
return false;
}
return "DateTime" === $configuration->getClass();
}
}

View File

@ -35,14 +35,19 @@ class DoctrineParamConverter implements ParamConverterInterface
public function apply(Request $request, ConfigurationInterface $configuration)
{
$class = $configuration->getClass();
$name = $configuration->getName();
$class = $configuration->getClass();
$options = $this->getOptions($configuration);
// find by identifier?
if (false === $object = $this->find($class, $request, $options)) {
if (false === $object = $this->find($class, $request, $options, $name)) {
// find by criteria
if (false === $object = $this->findOneBy($class, $request, $options)) {
throw new \LogicException('Unable to guess how to get a Doctrine instance from the request information.');
if ($configuration->isOptional()) {
$object = null;
} else {
throw new \LogicException('Unable to guess how to get a Doctrine instance from the request information.');
}
}
}
@ -50,28 +55,72 @@ class DoctrineParamConverter implements ParamConverterInterface
throw new NotFoundHttpException(sprintf('%s object not found.', $class));
}
$request->attributes->set($configuration->getName(), $object);
$request->attributes->set($name, $object);
return true;
}
protected function find($class, Request $request, $options)
protected function find($class, Request $request, $options, $name)
{
$key = isset($options['id']) ? $options['id'] : 'id';
if (!$request->attributes->has($key)) {
if ($options['mapping'] || $options['exclude']) {
return false;
}
return $this->registry->getRepository($class, $options['entity_manager'])->find($request->attributes->get($key));
$id = $this->getIdentifier($request, $options, $name);
if (!$id) {
return false;
}
return $this->registry->getRepository($class, $options['entity_manager'])->find($id);
}
protected function getIdentifier(Request $request, $options, $name)
{
if (isset($options['id'])) {
if (!is_array($options['id'])) {
$name = $options['id'];
} elseif (is_array($options['id'])) {
$id = array();
foreach ($options['id'] as $field) {
$id[$field] = $request->attributes->get($field);
}
return $id;
}
}
if ($request->attributes->has($name)) {
return $request->attributes->get($name);
}
if ($request->attributes->has('id')) {
return $request->attributes->get('id');
}
return false;
}
protected function findOneBy($class, Request $request, $options)
{
if (!$options['mapping']) {
$keys = $request->attributes->keys();
$options['mapping'] = $keys ? array_combine($keys, $keys) : array();
}
foreach ($options['exclude'] as $exclude) {
unset($options['mapping'][$exclude]);
}
if (!$options['mapping']) {
return false;
}
$criteria = array();
$metadata = $this->registry->getManager($options['entity_manager'])->getClassMetadata($class);
foreach ($request->attributes->all() as $key => $value) {
if ($metadata->hasField($key)) {
$criteria[$key] = $value;
foreach ($options['mapping'] as $attribute => $field) {
if ($metadata->hasField($field) || ($metadata->hasAssociation($field) && $metadata->isSingleValuedAssociation($field))) {
$criteria[$field] = $request->attributes->get($attribute);
}
}
@ -84,7 +133,8 @@ class DoctrineParamConverter implements ParamConverterInterface
public function supports(ConfigurationInterface $configuration)
{
if (null === $this->registry) {
// if there is no manager, this means that only Doctrine DBAL is configured
if (null === $this->registry || !count($this->registry->getManagers())) {
return false;
}
@ -104,6 +154,8 @@ class DoctrineParamConverter implements ParamConverterInterface
{
return array_replace(array(
'entity_manager' => null,
'exclude' => array(),
'mapping' => array(),
), $configuration->getOptions());
}
}

View File

@ -26,6 +26,11 @@ class ParamConverterManager
*/
protected $converters = array();
/**
* @var array
*/
protected $namedConverters = array();
/**
* Applies all converters to the passed configurations and stops when a
* converter is applied it will move on to the next configuration and so on.
@ -40,19 +45,52 @@ class ParamConverterManager
}
foreach ($configurations as $configuration) {
// If the value is already an instance of the class we are trying to convert it into
// we should continue as no convertion is required
$value = $request->attributes->get($configuration->getName());
$className = $configuration->getClass();
if (is_object($value) && $value instanceof $className) {
continue;
$this->applyConverter($request, $configuration);
}
}
/**
* Apply converter on request based on the given configuration.
*
* @param Request $request
* @param ConfigurationInterface $configuration
*/
protected function applyConverter(Request $request, $configuration)
{
$value = $request->attributes->get($configuration->getName());
$className = $configuration->getClass();
// If the value is already an instance of the class we are trying to convert it into
// we should continue as no convertion is required
if (is_object($value) && $value instanceof $className) {
return;
}
if ($converterName = $configuration->getConverter()) {
if (!isset($this->namedConverters[$converterName])) {
throw new \RuntimeException(sprintf(
"No converter named '%s' found for conversion of parameter '%s'.",
$converterName, $configuration->getName()
));
}
foreach ($this->all() as $converter) {
if ($converter->supports($configuration)) {
if ($converter->apply($request, $configuration)) {
continue 2;
}
$converter = $this->namedConverters[$converterName];
if (!$converter->supports($configuration)) {
throw new \RuntimeException(sprintf(
"Converter '%s' does not support conversion of parameter '%s'.",
$converterName, $configuration->getName()
));
}
$converter->apply($request, $configuration);
return;
}
foreach ($this->all() as $converter) {
if ($converter->supports($configuration)) {
if ($converter->apply($request, $configuration)) {
return;
}
}
}
@ -61,16 +99,28 @@ class ParamConverterManager
/**
* Adds a parameter converter.
*
* Converters match either explicitly via $name or by iteration over all
* converters with a $priority. If you pass a $priority = null then the
* added converter will not be part of the iteration chain and can only
* be invoked explicitly.
*
* @param ParamConverterInterface $converter A ParamConverterInterface instance
* @param integer $priority The priority (between -10 and 10)
* @param integer $priority The priority (between -10 and 10).
* @param string $name Name of the converter.
*/
public function add(ParamConverterInterface $converter, $priority = 0)
public function add(ParamConverterInterface $converter, $priority = 0, $name = null)
{
if (!isset($this->converters[$priority])) {
$this->converters[$priority] = array();
}
if ($priority !== null) {
if (!isset($this->converters[$priority])) {
$this->converters[$priority] = array();
}
$this->converters[$priority][] = $converter;
$this->converters[$priority][] = $converter;
}
if (null !== $name) {
$this->namedConverters[$name] = $converter;
}
}
/**

View File

@ -8,6 +8,7 @@
<parameter key="sensio_framework_extra.converter.listener.class">Sensio\Bundle\FrameworkExtraBundle\EventListener\ParamConverterListener</parameter>
<parameter key="sensio_framework_extra.converter.manager.class">Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterManager</parameter>
<parameter key="sensio_framework_extra.converter.doctrine.class">Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\DoctrineParamConverter</parameter>
<parameter key="sensio_framework_extra.converter.datetime.class">Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\DateTimeParamConverter</parameter>
</parameters>
<services>
@ -19,8 +20,12 @@
<service id="sensio_framework_extra.converter.manager" class="%sensio_framework_extra.converter.manager.class%" />
<service id="sensio_framework_extra.converter.doctrine.orm" class="%sensio_framework_extra.converter.doctrine.class%">
<tag name="request.param_converter" />
<tag name="request.param_converter" converter="doctrine.orm" />
<argument type="service" id="doctrine" on-invalid="ignore" />
</service>
<service id="sensio_framework_extra.converter.datetime" class="%sensio_framework_extra.converter.datetime.class%">
<tag name="request.param_converter" converter="datetime" />
</service>
</services>
</container>

View File

@ -41,14 +41,42 @@ If you use type hinting as in the example above, you can even omit the
{
}
To detect which converter is run on a parameter the following process is run:
* If an explicit converter choice was made with
``@ParamConverter(converter="name")`` the converter with the given name is
chosen.
* Otherwise all registered parameter converters are iterated by priority.
The ``supports()`` method is invoked to check if a param converter can
convert the request into the required parameter. If it returns ``true``
the param converter is invoked.
Built-in Converters
-------------------
The bundle has only one built-in converter, the Doctrine one.
The bundle has two built-in converter, the Doctrine one and a DateTime
converter.
Doctrine Converter
~~~~~~~~~~~~~~~~~~
Converter Name: ``doctrine.orm``
The Doctrine Converter attempts to convert request attributes to Doctrine
entities fetched from the database. Two different approaches are possible:
- Fetch object by primary key
- Fetch object by one or several fields which contain unique values in the
database.
The following algorithm determines which operation will be performed.
- If an ``{id}`` parameter is present in the route, find object by primary key.
- If an option ``'id'`` is configured and matches route parameters, find object by primary key.
- If the previous rules do not apply, attempt to find one entity by matching
route parameters to entity fields. You can control this process by
configuring ``exclude`` parameters or a attribute to field name ``mapping``.
By default, the Doctrine converter uses the *default* entity manager. This can
be configured with the ``entity_manager`` option::
@ -87,6 +115,55 @@ This also allows you to have multiple converters in one action::
In the example above, the post parameter is handled automatically, but the comment is
configured with the annotation since they can not both follow the default convention.
If you want to match an entity using multiple fields use ``mapping``::
/**
* @Route("/blog/{date}/{slug}/comments/{comment_slug}")
* @ParamConverter("post", options={"mapping": {"date": "date", "slug": "slug"})
* @ParamConverter("comment", options={"mapping": {"comment_slug": "slug"})
*/
public function showAction(Post $post, Comment $comment)
{
}
If you are matching an entity using several fields, but you want to exclude a
route parameter from being part of the criteria::
/**
* @Route("/blog/{date}/{slug}")
* @ParamConverter("post", options={"exclude": ["date"]})
*/
public function showAction(Post $post, \DateTime $date)
{
}
DateTime Converter
~~~~~~~~~~~~~~~~~~
Converter Name: ``datetime``
The datetime converter converts any route or request attribute into a datetime
instance::
/**
* @Route("/blog/archive/{start}/{end}")
*/
public function archiveAction(DateTime $start, DateTime $end)
{
}
By default any date format that can be parsed by the ``DateTime`` constructor
is accepted. You can be stricter with input given through the options::
/**
* @Route("/blog/archive/{start}/{end}")
* @ParamConverter("start", options={"format": "Y-m-d"})
* @ParamConverter("end", options={"format": "Y-m-d"})
*/
public function archiveAction(DateTime $start, DateTime $end)
{
}
Creating a Converter
--------------------
@ -120,6 +197,21 @@ on the request attributes, it should set an attribute named
``$configuration->getName()``, which stores an object of class
``$configuration->getClass()``.
To register your converter service you must add a tag to your service
.. configuration-block::
.. code-block:: xml
<service id="my_converter" class="MyBundle/Request/ParamConverter/MyConverter">
<tag name="request.param_converter" priority="-2" name="my_converter" />
</service>
You can register a converter by priority, by name or both. If you don't
specifiy a priority or name the converter will be added to the converter stack
with a priority of `0`. To explicitly disable the registration by priority you
have to set `priority="false"` in your tag definition.
.. tip::
Use the ``DoctrineParamConverter`` class as a template for your own converters.

View File

@ -40,6 +40,19 @@ case for the above example, you can even omit the annotation value::
return array('post' => $post);
}
..note::
If you are using PHP as a templating system, you need to make it
explicit::
/**
* @Template(engine="php")
*/
public function showAction($id)
{
// ...
}
And if the only parameters to pass to the template are method arguments, you
can use the ``vars`` attribute instead of returning an array. This is very
useful in combination with the ``@ParamConverter`` :doc:`annotation

View File

@ -4,6 +4,7 @@ namespace Sensio\Bundle\FrameworkExtraBundle\Routing;
use Symfony\Component\Routing\Loader\AnnotationClassLoader;
use Symfony\Component\Routing\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route as FrameworkExtraBundleRoute;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
/*
@ -37,7 +38,7 @@ class AnnotatedRouteControllerLoader extends AnnotationClassLoader
{
// controller
$classAnnot = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass);
if ($classAnnot && $service = $classAnnot->getService()) {
if ($classAnnot instanceof FrameworkExtraBundleRoute && $service = $classAnnot->getService()) {
$route->setDefault('_controller', $service.':'.$method->getName());
} else {
$route->setDefault('_controller', $class->getName().'::'.$method->getName());

View File

@ -5,8 +5,7 @@ namespace Sensio\Bundle\FrameworkExtraBundle\Templating;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Templating\TemplateReference;
use CG\Core\ClassUtils;
use Doctrine\Common\Util\ClassUtils;
/*
* This file is part of the Symfony framework.
@ -51,12 +50,7 @@ class TemplateGuesser
*/
public function guessTemplateName($controller, Request $request, $engine = 'twig')
{
$className = get_class($controller[0]);
// When JMSSecurityExtraBundle is used it generates Controller classes as MyAccountController__CG__
if (class_exists('CG\\Core\\ClassUtils')) {
$className = ClassUtils::getUserClass($className);
}
$className = class_exists('Doctrine\Common\Util\ClassUtils') ? ClassUtils::getClass($controller[0]) : get_class($controller[0]);
if (!preg_match('/Controller\\\(.+)Controller$/', $className, $matchController)) {
throw new \InvalidArgumentException(sprintf('The "%s" class does not look like a controller class (it must be in a "Controller" sub-namespace and the class name must end with "Controller")', get_class($controller[0])));

View File

@ -64,6 +64,25 @@ class ControllerListenerTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(FooControllerCacheAtClassAndMethod::CLASS_SMAXAGE, $this->getReadedCache()->getSMaxAge());
}
public function testMultipleAnnotationsOnMethod()
{
$controller = new FooControllerCacheAtClassAndMethod();
$this->event = $this->getFilterControllerEvent(array($controller, 'bar3Action'), $this->request);
$this->listener->onKernelController($this->event);
$annotations = $this->getReadedCache();
$this->assertNotNull($annotations);
$this->assertArrayHasKey(0, $annotations);
$this->assertInstanceOf('Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache', $annotations[0]);
$this->assertEquals(FooControllerCacheAtClassAndMethod::METHOD_SMAXAGE, $annotations[0]->getSMaxAge());
$this->assertArrayHasKey(1, $annotations);
$this->assertInstanceOf('Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache', $annotations[1]);
$this->assertEquals(FooControllerCacheAtClassAndMethod::METHOD_SECOND_SMAXAGE, $annotations[1]->getSMaxAge());
$this->assertEquals(2, count($annotations));
}
protected function createRequest(Cache $cache = null)
{
return new Request(array(), array(), array(

View File

@ -11,6 +11,7 @@ class FooControllerCacheAtClassAndMethod
{
const CLASS_SMAXAGE = 20;
const METHOD_SMAXAGE = 15;
const METHOD_SECOND_SMAXAGE = 25;
/**
* @Cache(smaxage="15")
@ -22,4 +23,12 @@ class FooControllerCacheAtClassAndMethod
public function bar2Action()
{
}
/**
* @Cache(smaxage="15")
* @Cache(smaxage="25")
*/
public function bar3Action()
{
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterManager;
use Sensio\Bundle\FrameworkExtraBundle\EventListener\ParamConverterListener;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
class ParamConverterListenerTest extends \PHPUnit_Framework_TestCase
{
public function testRequestIsSkipped()
{
$kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface');
$request = new Request();
$manager = $this->getMock('Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterManager');
$manager->expects($this->once())
->method('apply')
->with($this->equalTo($request), $this->equalTo(array()));
$listener = new ParamConverterListener($manager);
$event = new FilterControllerEvent($kernel, array(new TestController(), 'execute'), $request, null);
$listener->onKernelController($event);
}
}
class TestController
{
public function execute(Request $request) {}
}

View File

@ -0,0 +1,69 @@
<?php
namespace Sensio\Bundle\FrameworkExtraBundle\Tests\Request\ParamConverter;
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\DateTimeParamConverter;
class DateTimeParamConverterTest extends \PHPUnit_Framework_TestCase
{
private $converter;
public function setUp()
{
$this->converter = new DateTimeParamConverter();
}
public function testSupports()
{
$config = $this->createConfiguration("DateTime");
$this->assertTrue($this->converter->supports($config));
$config = $this->createConfiguration(__CLASS__);
$this->assertFalse($this->converter->supports($config));
$config = $this->createConfiguration();
$this->assertFalse($this->converter->supports($config));
}
public function testApply()
{
$request = new Request(array(), array(), array('start' => '2012-07-21 00:00:00'));
$config = $this->createConfiguration("DateTime", "start");
$this->converter->apply($request, $config);
$this->assertInstanceOf("DateTime", $request->attributes->get('start'));
$this->assertEquals('2012-07-21', $request->attributes->get('start')->format('Y-m-d'));
}
public function testApplyWithFormatInvalidDate404Exception()
{
$request = new Request(array(), array(), array('start' => '2012-07-21'));
$config = $this->createConfiguration("DateTime", "start");
$config->expects($this->any())->method('getOptions')->will($this->returnValue(array('format' => 'd.m.Y')));
$this->setExpectedException('Symfony\Component\HttpKernel\Exception\NotFoundHttpException', 'Invalid date given.');
$this->converter->apply($request, $config);
}
public function createConfiguration($class = null, $name = null)
{
$config = $this->getMock(
'Sensio\Bundle\FrameworkExtraBundle\Configuration\ConfigurationInterface', array(
'getClass', 'getAliasName', 'getOptions', 'getName',
));
if ($name !== null) {
$config->expects($this->any())
->method('getName')
->will($this->returnValue($name));
}
if ($class !== null) {
$config->expects($this->any())
->method('getClass')
->will($this->returnValue($class));
}
return $config;
}
}

View File

@ -27,11 +27,11 @@ class DoctrineParamConverterTest extends \PHPUnit_Framework_TestCase
$this->converter = new DoctrineParamConverter($this->manager);
}
public function createConfiguration($class = null, array $options = null)
public function createConfiguration($class = null, array $options = null, $name = 'arg', $isOptional = false)
{
$config = $this->getMock(
'Sensio\Bundle\FrameworkExtraBundle\Configuration\ConfigurationInterface', array(
'getClass', 'getAliasName', 'getOptions'
'getClass', 'getAliasName', 'getOptions', 'isOptional', 'getName',
));
if ($options !== null) {
$config->expects($this->once())
@ -43,6 +43,12 @@ class DoctrineParamConverterTest extends \PHPUnit_Framework_TestCase
->method('getClass')
->will($this->returnValue($class));
}
$config->expects($this->any())
->method('getName')
->will($this->returnValue($name));
$config->expects($this->any())
->method('isOptional')
->will($this->returnValue($isOptional));
return $config;
}
@ -53,13 +59,86 @@ class DoctrineParamConverterTest extends \PHPUnit_Framework_TestCase
$config = $this->createConfiguration(null, array());
$objectManager = $this->getMock('Doctrine\Common\Persistence\ObjectManager');
$this->manager->expects($this->never())->method('find');
$this->setExpectedException('LogicException');
$this->converter->apply($request, $config);
}
public function testApplyWithNoIdAndDataOptional()
{
$request = new Request();
$config = $this->createConfiguration(null, array(), 'arg', true);
$objectManager = $this->getMock('Doctrine\Common\Persistence\ObjectManager');
$ret = $this->converter->apply($request, $config);
$this->assertTrue($ret);
$this->assertNull($request->attributes->get('arg'));
}
public function testApplyWithId()
{
$request = new Request();
$request->attributes->set('id', 1);
$config = $this->createConfiguration('stdClass', array('id' => 'id'), 'arg');
$objectRepository = $this->getMock('Doctrine\Common\Persistence\ObjectRepository');
$this->manager->expects($this->once())
->method('getRepository')
->will($this->returnValue($objectRepository));
$objectRepository->expects($this->once())
->method('find')
->with($this->equalTo(1))
->will($this->returnValue($object =new \stdClass));
$ret = $this->converter->apply($request, $config);
$this->assertTrue($ret);
$this->assertSame($object, $request->attributes->get('arg'));
}
public function testApplyWithMappingAndExclude()
{
$request = new Request();
$request->attributes->set('foo', 1);
$request->attributes->set('bar', 2);
$config = $this->createConfiguration(
'stdClass',
array('mapping' => array('foo' => 'Foo'), 'exclude' => array('bar')),
'arg'
);
$objectManager = $this->getMock('Doctrine\Common\Persistence\ObjectManager');
$metadata = $this->getMock('Doctrine\Common\Persistence\Mapping\ClassMetadata');
$this->manager->expects($this->once())
->method('getManager')
->will($this->returnValue($objectManager));
$objectManager->expects($this->once())
->method('getClassMetadata')
->will($this->returnValue($metadata));
$this->setExpectedException('LogicException');
$this->converter->apply($request, $config);
$metadata->expects($this->once())
->method('hasField')
->with($this->equalTo('Foo'))
->will($this->returnValue(true));
$objectRepository = $this->getMock('Doctrine\Common\Persistence\ObjectRepository');
$this->manager->expects($this->once())
->method('getRepository')
->will($this->returnValue($objectRepository));
$objectRepository->expects($this->once())
->method('findOneBy')
->with($this->equalTo(array('Foo' => 1)))
->will($this->returnValue($object =new \stdClass));
$ret = $this->converter->apply($request, $config);
$this->assertTrue($ret);
$this->assertSame($object, $request->attributes->get('arg'));
}
public function testSupports()
@ -76,6 +155,10 @@ class DoctrineParamConverterTest extends \PHPUnit_Framework_TestCase
->method('getMetadataFactory')
->will($this->returnValue($metadataFactory));
$this->manager->expects($this->once())
->method('getManagers')
->will($this->returnValue(array($objectManager)));
$this->manager->expects($this->once())
->method('getManager')
->will($this->returnValue($objectManager));

View File

@ -65,6 +65,73 @@ class ParamConverterManagerTest extends \PHPUnit_Framework_TestCase
$this->manager->apply(new Request(), $configurations);
}
public function testApplyNamedConverter()
{
$converter = $this->createParamConverterMock();
$converter
->expects($this->any())
->method('supports')
->will($this->returnValue(True))
;
$converter
->expects($this->any())
->method('apply')
;
$this->manager->add($converter, 0, "test");
$request = new Request();
$request->attributes->set('param', '1234');
$configuration = new Configuration\ParamConverter(array(
'name' => 'param',
'class' => 'stdClass',
'converter' => 'test',
));
$this->manager->apply($request, array($configuration));
}
public function testApplyNamedConverterNotSupportsParameter()
{
$converter = $this->createParamConverterMock();
$converter
->expects($this->any())
->method('supports')
->will($this->returnValue(false))
;
$this->manager->add($converter, 0, "test");
$request = new Request();
$request->attributes->set('param', '1234');
$configuration = new Configuration\ParamConverter(array(
'name' => 'param',
'class' => 'stdClass',
'converter' => 'test',
));
$this->setExpectedException("RuntimeException", "Converter 'test' does not support conversion of parameter 'param'.");
$this->manager->apply($request, array($configuration));
}
public function testApplyNamedConverterNoConverter()
{
$request = new Request();
$request->attributes->set('param', '1234');
$configuration = new Configuration\ParamConverter(array(
'name' => 'param',
'class' => 'stdClass',
'converter' => 'test',
));
$this->setExpectedException("RuntimeException", "No converter named 'test' found for conversion of parameter 'param'.");
$this->manager->apply($request, array($configuration));
}
public function testApplyNotCalledOnAlreadyConvertedObjects()
{

View File

@ -0,0 +1,50 @@
UPGRADE FROM 2.0 to 2.1
=======================
### DoctrineParamConverter: Request Attributes with same name as Arguments
Previously the DoctrineParamConverter defaulted to finding objects by 'id'
parameter. This is unintuitive and is now overwritten by a behavior where
the request attributes with the same name as entity arguments is matched
with higher priority.
This might cause problems if you are using this parameter for another object conversion.
### DoctrineParamConverter with multiple Arguments may clash
In 2.0 the parameter converter matched only entity fields against route parameters.
With 2.1, the matching now also includes single-valued associations. Depending
on fields in entities this might lead to clashes when you update to the latest version.
Example that may break with the latest (2.1) version:
/**
* @Route("/user/{email}/{address}")
* @ParamConverter("address", class="MyBundle:Address", options={"id": "address"})
*/
public function showAction(User $user, Address $address)
{
}
class User
{
/** @ORM\Column(type="string") */
public $email;
/** @ORM\ManyToOne(targetEntity="Address") */
public $address;
}
Since address exists as field in `User` and User is not searched by primary key but
by field, this scenario now adds `address` to the criteria to find a user instance.
In scenarios of related entities this might even (just) work, but you never know.
You can fix this by configuring explicit mapping for `User`:
/**
* @Route("/user/{email}/{address}")
* @ParamConverter("address", options={"id": "address"})
* @ParamConverter("email", options={"exclude": ["address"]})
*/
public function showAction(User $user, Address $address)
{
}

View File

@ -22,5 +22,6 @@
"branch-alias": {
"dev-master": "2.1.x-dev"
}
}
},
"minimum-stability": "dev"
}

View File

@ -148,7 +148,7 @@ EOT
'See http://symfony.com/doc/current/cookbook/bundles/best_practices.html#index-1 for more',
'details on bundle naming conventions.',
'',
'Use <comment>/</comment> instead of <comment>\\</comment> for the namespace delimiter to avoid any problem.',
'Use <comment>/</comment> instead of <comment>\\ </comment> for the namespace delimiter to avoid any problem.',
'',
));

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

View File

View File

@ -64,11 +64,7 @@
<argument /> <!-- filter -->
</service>
<service id="assetic.parameter_bag" class="Symfony\Component\DependencyInjection\ParameterBag\ParameterBag" public="false">
<argument type="service" id="assetic.parameters" />
</service>
<service id="assetic.parameters" class="stdClass" factory-service="service_container" factory-method="getDefaultParameters" public="false" />
<service id="assetic.parameter_bag" class="Symfony\Component\DependencyInjection\ParameterBag\ParameterBag" factory-service="service_container" factory-method="getParameterBag" public="false"/>
<service id="assetic.value_supplier.default" class="%assetic.value_supplier.class%" public="false">
<argument type="service" id="service_container" />

View File

@ -247,11 +247,10 @@
reasons. It is now not possible anymore to use custom implementations of
`FormBuilderInterface` for specific form types.
If you are in such a situation, you can subclass `FormRegistry` instead and override
`resolveType` to return a custom `ResolvedFormTypeInterface` implementation, within
which you can create your own `FormBuilderInterface` implementation. You should
register this custom registry class under the service name "form.registry" in order
to replace the default implementation.
If you are in such a situation, you can implement a custom `ResolvedFormTypeInterface`
where you create your own `FormBuilderInterface` implementation. You also need to
register a custom `ResolvedFormTypeFactoryInterface` implementation under the service
name "form.resolved_type_factory" in order to replace the default implementation.
* If you previously inherited from `FieldType`, you should now inherit from
`FormType`. You should also set the option `compound` to `false` if your field
@ -1199,8 +1198,8 @@
}
```
* Core translation messages are changed. Dot is added at the end of each message.
Overwritten core translations should be fixed if any. More info here.
* Core translation messages changed. A dot is added at the end of each message.
Overwritten core translations need to be fixed.
* Collections (arrays or instances of `\Traversable`) in properties
annotated with `Valid` are not traversed recursively by default anymore.
@ -1353,6 +1352,11 @@
decoded twice before. Note that the `urldecode()` calls have been changed for a
single `rawurldecode()` in order to support `+` for input paths.
* Two new parameters have been added to the DIC: `router.request_context.host`
and `router.request_context.scheme`. You can customize them for your
functional tests or for generating urls with the right host and scheme
when your are in the cli context.
### FrameworkBundle
* session options: lifetime, path, domain, secure, httponly were deprecated.

View File

@ -312,7 +312,7 @@ class EntityChoiceList extends ObjectChoiceList
*
* Otherwise a new integer is generated.
*
* @param mixed $choice The choice to create an index for
* @param mixed $entity The choice to create an index for
*
* @return integer|string A unique index containing only ASCII letters,
* digits and underscores.
@ -333,7 +333,7 @@ class EntityChoiceList extends ObjectChoiceList
*
* Otherwise a new integer is generated.
*
* @param mixed $choice The choice to create a value for
* @param mixed $entity The choice to create a value for
*
* @return integer|string A unique value without character limitations.
*/

View File

@ -186,8 +186,8 @@ class EntityChoiceListTest extends DoctrineOrmTestCase
$this->assertSame(array(1 => $entity1, 2 => $entity2), $choiceList->getChoices());
$this->assertEquals(array(
'group1' => array(1 => new ChoiceView('1', 'Foo')),
'group2' => array(2 => new ChoiceView('2', 'Bar'))
'group1' => array(1 => new ChoiceView($entity1, '1', 'Foo')),
'group2' => array(2 => new ChoiceView($entity2, '2', 'Bar'))
), $choiceList->getRemainingViews());
}
@ -219,9 +219,9 @@ class EntityChoiceListTest extends DoctrineOrmTestCase
$this->assertEquals(array(1 => $item1, 2 => $item2, 3 => $item3, 4 => $item4), $choiceList->getChoices());
$this->assertEquals(array(
'Group1' => array(1 => new ChoiceView('1', 'Foo'), 2 => new ChoiceView('2', 'Bar')),
'Group2' => array(3 => new ChoiceView('3', 'Baz')),
4 => new ChoiceView('4', 'Boo!')
'Group1' => array(1 => new ChoiceView($item1, '1', 'Foo'), 2 => new ChoiceView($item2, '2', 'Bar')),
'Group2' => array(3 => new ChoiceView($item3, '3', 'Baz')),
4 => new ChoiceView($item4, '4', 'Boo!')
), $choiceList->getRemainingViews());
}

View File

@ -124,7 +124,7 @@ class EntityTypeTest extends TypeTestCase
'property' => 'name'
));
$this->assertEquals(array(1 => new ChoiceView('1', 'Foo'), 2 => new ChoiceView('2', 'Bar')), $field->createView()->vars['choices']);
$this->assertEquals(array(1 => new ChoiceView($entity1, '1', 'Foo'), 2 => new ChoiceView($entity2, '2', 'Bar')), $field->createView()->vars['choices']);
}
public function testSetDataToUninitializedEntityWithNonRequiredToString()
@ -140,7 +140,7 @@ class EntityTypeTest extends TypeTestCase
'required' => false,
));
$this->assertEquals(array(1 => new ChoiceView('1', 'Foo'), 2 => new ChoiceView('2', 'Bar')), $field->createView()->vars['choices']);
$this->assertEquals(array(1 => new ChoiceView($entity1, '1', 'Foo'), 2 => new ChoiceView($entity2, '2', 'Bar')), $field->createView()->vars['choices']);
}
public function testSetDataToUninitializedEntityWithNonRequiredQueryBuilder()
@ -159,7 +159,7 @@ class EntityTypeTest extends TypeTestCase
'query_builder' => $qb
));
$this->assertEquals(array(1 => new ChoiceView('1', 'Foo'), 2 => new ChoiceView('2', 'Bar')), $field->createView()->vars['choices']);
$this->assertEquals(array(1 => new ChoiceView($entity1, '1', 'Foo'), 2 => new ChoiceView($entity2, '2', 'Bar')), $field->createView()->vars['choices']);
}
/**
@ -503,7 +503,7 @@ class EntityTypeTest extends TypeTestCase
$field->bind('2');
$this->assertEquals(array(1 => new ChoiceView('1', 'Foo'), 2 => new ChoiceView('2', 'Bar')), $field->createView()->vars['choices']);
$this->assertEquals(array(1 => new ChoiceView($entity1, '1', 'Foo'), 2 => new ChoiceView($entity2, '2', 'Bar')), $field->createView()->vars['choices']);
$this->assertTrue($field->isSynchronized());
$this->assertSame($entity2, $field->getData());
$this->assertSame('2', $field->getClientData());
@ -530,9 +530,9 @@ class EntityTypeTest extends TypeTestCase
$this->assertSame('2', $field->getClientData());
$this->assertEquals(array(
'Group1' => array(1 => new ChoiceView('1', 'Foo'), 2 => new ChoiceView('2', 'Bar')),
'Group2' => array(3 => new ChoiceView('3', 'Baz')),
'4' => new ChoiceView('4', 'Boo!')
'Group1' => array(1 => new ChoiceView($item1, '1', 'Foo'), 2 => new ChoiceView($item2, '2', 'Bar')),
'Group2' => array(3 => new ChoiceView($item3, '3', 'Baz')),
'4' => new ChoiceView($item4, '4', 'Boo!')
), $field->createView()->vars['choices']);
}

View File

@ -75,6 +75,7 @@ class UniqueValidatorTest extends DoctrineOrmTestCase
;
$refl = $this->getMockBuilder('Doctrine\Common\Reflection\StaticReflectionProperty')
->disableOriginalConstructor()
->setMethods(array('getValue'))
->getMock()
;
$refl

View File

@ -276,7 +276,7 @@ class ModelChoiceList extends ObjectChoiceList
*
* Otherwise a new integer is generated.
*
* @param mixed $choice The choice to create an index for
* @param mixed $model The choice to create an index for
*
* @return integer|string A unique index containing only ASCII letters,
* digits and underscores.
@ -297,7 +297,7 @@ class ModelChoiceList extends ObjectChoiceList
*
* Otherwise a new integer is generated.
*
* @param mixed $choice The choice to create a value for
* @param mixed $model The choice to create a value for
*
* @return integer|string A unique value without character limitations.
*/

View File

@ -86,8 +86,8 @@ class ModelChoiceListTest extends Propel1TestCase
$this->assertSame(array(1 => $item1, 2 => $item2), $choiceList->getChoices());
$this->assertEquals(array(
'group1' => array(1 => new ChoiceView('1', 'Foo')),
'group2' => array(2 => new ChoiceView('2', 'Bar'))
'group1' => array(1 => new ChoiceView($item1, '1', 'Foo')),
'group2' => array(2 => new ChoiceView($item2, '2', 'Bar'))
), $choiceList->getRemainingViews());
}
@ -113,9 +113,9 @@ class ModelChoiceListTest extends Propel1TestCase
$this->assertEquals(array(1 => $item1, 2 => $item2, 3 => $item3, 4 => $item4), $choiceList->getChoices());
$this->assertEquals(array(
'Group1' => array(1 => new ChoiceView('1', 'Foo'), 2 => new ChoiceView('2', 'Bar')),
'Group2' => array(3 => new ChoiceView('3', 'Baz')),
4 => new ChoiceView('4', 'Boo!')
'Group1' => array(1 => new ChoiceView($item1, '1', 'Foo'), 2 => new ChoiceView($item2, '2', 'Bar')),
'Group2' => array(3 => new ChoiceView($item3, '3', 'Baz')),
4 => new ChoiceView($item4, '4', 'Boo!')
), $choiceList->getRemainingViews());
}

View File

@ -32,21 +32,71 @@ class SearchAndRenderBlockNode extends \Twig_Node_Expression_Function
$compiler->raw(', \'' . $blockNameSuffix . '\'');
if (isset($arguments[1])) {
$compiler->raw(', ');
// The "label" function allows one extra argument here, the label
if ('label' === $blockNameSuffix) {
if (isset($arguments[2])) {
$compiler->subcompile($arguments[2]);
$compiler->raw(' + ');
// The "label" function expects the label in the second and
// the variables in the third argument
$label = $arguments[1];
$variables = isset($arguments[2]) ? $arguments[2] : null;
$lineno = $label->getLine();
if ($label instanceof \Twig_Node_Expression_Constant) {
// If the label argument is given as a constant, we can either
// strip it away if it is empty, or integrate it into the array
// of variables at compile time.
$labelIsExpression = false;
// Only insert the label into the array if it is not empty
if (!twig_test_empty($label->getAttribute('value'))) {
$originalVariables = $variables;
$variables = new \Twig_Node_Expression_Array(array(), $lineno);
$labelKey = new \Twig_Node_Expression_Constant('label', $lineno);
if (null !== $originalVariables) {
foreach ($originalVariables->getKeyValuePairs() as $pair) {
// Don't copy the original label attribute over if it exists
if ((string) $labelKey !== (string) $pair['key']) {
$variables->addElement($pair['value'], $pair['key']);
}
}
}
// Insert the label argument into the array
$variables->addElement($label, $labelKey);
}
} else {
// The label argument is not a constant, but some kind of
// expression. This expression needs to be evaluated at runtime.
// Depending on the result (whether it is null or not), the
// label in the arguments should take precedence over the label
// in the attributes or not.
$labelIsExpression = true;
}
} else {
// All other functions than "label" expect the variables
// in the second argument
$label = null;
$variables = $arguments[1];
$labelIsExpression = false;
}
if (null !== $variables || $labelIsExpression) {
$compiler->raw(', ');
if (null !== $variables) {
$compiler->subcompile($variables);
}
// Add the label to the variable array
$compiler->raw('array(\'label\' => ');
$compiler->subcompile($arguments[1]);
$compiler->raw(')');
} else {
$compiler->subcompile($arguments[1]);
if ($labelIsExpression) {
if (null !== $variables) {
$compiler->raw(' + ');
}
// Check at runtime whether the label is empty.
// If not, add it to the array at runtime.
$compiler->raw('(twig_test_empty($_label_ = ');
$compiler->subcompile($label);
$compiler->raw(') ? array() : array("label" => $_label_))');
}
}
}
}

View File

@ -249,7 +249,7 @@
{% block form_row %}
{% spaceless %}
<div>
{{ form_label(form, label|default(null)) }}
{{ form_label(form) }}
{{ form_errors(form) }}
{{ form_widget(form) }}
</div>

View File

@ -4,7 +4,7 @@
{% spaceless %}
<tr>
<td>
{{ form_label(form, label|default(null)) }}
{{ form_label(form) }}
</td>
<td>
{{ form_errors(form) }}

View File

@ -134,7 +134,7 @@ class FormExtensionDivLayoutTest extends AbstractDivLayoutTest
*/
public function testIsChoiceSelected($expected, $choice, $value)
{
$choice = new ChoiceView($choice, $choice . ' label');
$choice = new ChoiceView($choice, $choice, $choice . ' label');
$this->assertSame($expected, $this->extension->isSelectedChoice($choice, $value));
}

View File

@ -0,0 +1,282 @@
<?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\Twig\Tests\Node;
use Symfony\Bridge\Twig\Tests\TestCase;
use Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode;
class SearchAndRenderBlockNodeTest extends TestCase
{
protected function setUp()
{
parent::setUp();
if (version_compare(\Twig_Environment::VERSION, '1.5.0', '<')) {
$this->markTestSkipped('Requires Twig version to be at least 1.5.0.');
}
}
public function testCompileWidget()
{
$arguments = new \Twig_Node(array(
new \Twig_Node_Expression_Name('form', 0),
));
$node = new SearchAndRenderBlockNode('form_widget', $arguments, 0);
$compiler = new \Twig_Compiler(new \Twig_Environment());
$this->assertEquals(
sprintf(
'$this->env->getExtension(\'form\')->renderer->searchAndRenderBlock(%s, \'widget\')',
$this->getVariableGetter('form')
),
trim($compiler->compile($node)->getSource())
);
}
public function testCompileWidgetWithVariables()
{
$arguments = new \Twig_Node(array(
new \Twig_Node_Expression_Name('form', 0),
new \Twig_Node_Expression_Array(array(
new \Twig_Node_Expression_Constant('foo', 0),
new \Twig_Node_Expression_Constant('bar', 0),
), 0),
));
$node = new SearchAndRenderBlockNode('form_widget', $arguments, 0);
$compiler = new \Twig_Compiler(new \Twig_Environment());
$this->assertEquals(
sprintf(
'$this->env->getExtension(\'form\')->renderer->searchAndRenderBlock(%s, \'widget\', array("foo" => "bar"))',
$this->getVariableGetter('form')
),
trim($compiler->compile($node)->getSource())
);
}
public function testCompileLabelWithLabel()
{
$arguments = new \Twig_Node(array(
new \Twig_Node_Expression_Name('form', 0),
new \Twig_Node_Expression_Constant('my label', 0),
));
$node = new SearchAndRenderBlockNode('form_label', $arguments, 0);
$compiler = new \Twig_Compiler(new \Twig_Environment());
$this->assertEquals(
sprintf(
'$this->env->getExtension(\'form\')->renderer->searchAndRenderBlock(%s, \'label\', array("label" => "my label"))',
$this->getVariableGetter('form')
),
trim($compiler->compile($node)->getSource())
);
}
public function testCompileLabelWithNullLabel()
{
$arguments = new \Twig_Node(array(
new \Twig_Node_Expression_Name('form', 0),
new \Twig_Node_Expression_Constant(null, 0),
));
$node = new SearchAndRenderBlockNode('form_label', $arguments, 0);
$compiler = new \Twig_Compiler(new \Twig_Environment());
// "label" => null must not be included in the output!
// Otherwise the default label is overwritten with null.
$this->assertEquals(
sprintf(
'$this->env->getExtension(\'form\')->renderer->searchAndRenderBlock(%s, \'label\')',
$this->getVariableGetter('form')
),
trim($compiler->compile($node)->getSource())
);
}
public function testCompileLabelWithEmptyStringLabel()
{
$arguments = new \Twig_Node(array(
new \Twig_Node_Expression_Name('form', 0),
new \Twig_Node_Expression_Constant('', 0),
));
$node = new SearchAndRenderBlockNode('form_label', $arguments, 0);
$compiler = new \Twig_Compiler(new \Twig_Environment());
// "label" => null must not be included in the output!
// Otherwise the default label is overwritten with null.
$this->assertEquals(
sprintf(
'$this->env->getExtension(\'form\')->renderer->searchAndRenderBlock(%s, \'label\')',
$this->getVariableGetter('form')
),
trim($compiler->compile($node)->getSource())
);
}
public function testCompileLabelWithDefaultLabel()
{
$arguments = new \Twig_Node(array(
new \Twig_Node_Expression_Name('form', 0),
));
$node = new SearchAndRenderBlockNode('form_label', $arguments, 0);
$compiler = new \Twig_Compiler(new \Twig_Environment());
$this->assertEquals(
sprintf(
'$this->env->getExtension(\'form\')->renderer->searchAndRenderBlock(%s, \'label\')',
$this->getVariableGetter('form')
),
trim($compiler->compile($node)->getSource())
);
}
public function testCompileLabelWithAttributes()
{
$arguments = new \Twig_Node(array(
new \Twig_Node_Expression_Name('form', 0),
new \Twig_Node_Expression_Constant(null, 0),
new \Twig_Node_Expression_Array(array(
new \Twig_Node_Expression_Constant('foo', 0),
new \Twig_Node_Expression_Constant('bar', 0),
), 0),
));
$node = new SearchAndRenderBlockNode('form_label', $arguments, 0);
$compiler = new \Twig_Compiler(new \Twig_Environment());
// "label" => null must not be included in the output!
// Otherwise the default label is overwritten with null.
// https://github.com/symfony/symfony/issues/5029
$this->assertEquals(
sprintf(
'$this->env->getExtension(\'form\')->renderer->searchAndRenderBlock(%s, \'label\', array("foo" => "bar"))',
$this->getVariableGetter('form')
),
trim($compiler->compile($node)->getSource())
);
}
public function testCompileLabelWithLabelAndAttributes()
{
$arguments = new \Twig_Node(array(
new \Twig_Node_Expression_Name('form', 0),
new \Twig_Node_Expression_Constant('value in argument', 0),
new \Twig_Node_Expression_Array(array(
new \Twig_Node_Expression_Constant('foo', 0),
new \Twig_Node_Expression_Constant('bar', 0),
new \Twig_Node_Expression_Constant('label', 0),
new \Twig_Node_Expression_Constant('value in attributes', 0),
), 0),
));
$node = new SearchAndRenderBlockNode('form_label', $arguments, 0);
$compiler = new \Twig_Compiler(new \Twig_Environment());
$this->assertEquals(
sprintf(
'$this->env->getExtension(\'form\')->renderer->searchAndRenderBlock(%s, \'label\', array("foo" => "bar", "label" => "value in argument"))',
$this->getVariableGetter('form')
),
trim($compiler->compile($node)->getSource())
);
}
public function testCompileLabelWithLabelThatEvaluatesToNull()
{
$arguments = new \Twig_Node(array(
new \Twig_Node_Expression_Name('form', 0),
new \Twig_Node_Expression_Conditional(
// if
new \Twig_Node_Expression_Constant(true, 0),
// then
new \Twig_Node_Expression_Constant(null, 0),
// else
new \Twig_Node_Expression_Constant(null, 0),
0
),
));
$node = new SearchAndRenderBlockNode('form_label', $arguments, 0);
$compiler = new \Twig_Compiler(new \Twig_Environment());
// "label" => null must not be included in the output!
// Otherwise the default label is overwritten with null.
// https://github.com/symfony/symfony/issues/5029
$this->assertEquals(
sprintf(
'$this->env->getExtension(\'form\')->renderer->searchAndRenderBlock(%s, \'label\', (twig_test_empty($_label_ = ((true) ? (null) : (null))) ? array() : array("label" => $_label_)))',
$this->getVariableGetter('form')
),
trim($compiler->compile($node)->getSource())
);
}
public function testCompileLabelWithLabelThatEvaluatesToNullAndAttributes()
{
$arguments = new \Twig_Node(array(
new \Twig_Node_Expression_Name('form', 0),
new \Twig_Node_Expression_Conditional(
// if
new \Twig_Node_Expression_Constant(true, 0),
// then
new \Twig_Node_Expression_Constant(null, 0),
// else
new \Twig_Node_Expression_Constant(null, 0),
0
),
new \Twig_Node_Expression_Array(array(
new \Twig_Node_Expression_Constant('foo', 0),
new \Twig_Node_Expression_Constant('bar', 0),
new \Twig_Node_Expression_Constant('label', 0),
new \Twig_Node_Expression_Constant('value in attributes', 0),
), 0),
));
$node = new SearchAndRenderBlockNode('form_label', $arguments, 0);
$compiler = new \Twig_Compiler(new \Twig_Environment());
// "label" => null must not be included in the output!
// Otherwise the default label is overwritten with null.
// https://github.com/symfony/symfony/issues/5029
$this->assertEquals(
sprintf(
'$this->env->getExtension(\'form\')->renderer->searchAndRenderBlock(%s, \'label\', array("foo" => "bar", "label" => "value in attributes") + (twig_test_empty($_label_ = ((true) ? (null) : (null))) ? array() : array("label" => $_label_)))',
$this->getVariableGetter('form')
),
trim($compiler->compile($node)->getSource())
);
}
protected function getVariableGetter($name)
{
if (version_compare(phpversion(), '5.4.0RC1', '>=')) {
return sprintf('(isset($context["%s"]) ? $context["%s"] : null)', $name, $name);
}
return sprintf('$this->getContext($context, "%s")', $name);
}
}

View File

@ -33,7 +33,7 @@ class AssetsInstallCommand extends ContainerAwareCommand
$this
->setName('assets:install')
->setDefinition(array(
new InputArgument('target', InputArgument::REQUIRED, 'The target directory (usually "web")'),
new InputArgument('target', InputArgument::OPTIONAL, 'The target directory', 'web'),
))
->addOption('symlink', null, InputOption::VALUE_NONE, 'Symlinks the assets instead of copying it')
->addOption('relative', null, InputOption::VALUE_NONE, 'Make relative symlinks')

View File

@ -44,7 +44,7 @@ class RegisterKernelListenersPass implements CompilerPassInterface
}
foreach ($container->findTaggedServiceIds('kernel.event_subscriber') as $id => $attributes) {
// We must assume that the class value has been correcly filled, even if the service is created by a factory
// We must assume that the class value has been correctly filled, even if the service is created by a factory
$class = $container->getDefinition($id)->getClass();
$refClass = new \ReflectionClass($class);

View File

@ -196,9 +196,10 @@ class HttpKernel extends BaseHttpKernel
*
* This method uses the "_internal" route, which should be available.
*
* @param string $controller A controller name to execute (a string like BlogBundle:Post:index), or a relative URI
* @param array $attributes An array of request attributes
* @param array $query An array of request query parameters
* @param string $controller A controller name to execute (a string like BlogBundle:Post:index), or a relative URI
* @param array $attributes An array of request attributes
* @param array $query An array of request query parameters
* @param boolean $secure
*
* @return string An internal URI
*/
@ -226,6 +227,7 @@ class HttpKernel extends BaseHttpKernel
* Renders an HInclude tag.
*
* @param string $uri A URI
* @param string $defaultContent Default content
*/
public function renderHIncludeTag($uri, $defaultContent = null)
{

View File

@ -5,47 +5,45 @@
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="form.extension.class">Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension</parameter>
<parameter key="form.resolved_type_factory.class">Symfony\Component\Form\ResolvedFormTypeFactory</parameter>
<parameter key="form.registry.class">Symfony\Component\Form\FormRegistry</parameter>
<parameter key="form.factory.class">Symfony\Component\Form\FormFactory</parameter>
<parameter key="form.extension.class">Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension</parameter>
<parameter key="form.type_guesser.validator.class">Symfony\Component\Form\Extension\Validator\ValidatorTypeGuesser</parameter>
</parameters>
<services>
<!-- ResolvedFormTypeFactory -->
<service id="form.resolved_type_factory" class="%form.resolved_type_factory.class%" />
<!-- FormRegistry -->
<service id="form.registry" class="%form.registry.class%">
<argument type="collection">
<!--
We don't need to be able to add more extensions.
* more types can be registered with the form.type tag
* more type extensions can be registered with the form.type_extension tag
* more type_guessers can be registered with the form.type.type_guesser tag
-->
<argument type="service" id="form.extension" />
</argument>
<argument type="service" id="form.resolved_type_factory" />
</service>
<!-- FormFactory -->
<service id="form.factory" class="%form.factory.class%">
<argument type="service" id="form.registry" />
<argument type="service" id="form.resolved_type_factory" />
</service>
<!-- DependencyInjectionExtension -->
<service id="form.extension" class="%form.extension.class%" public="false">
<argument type="service" id="service_container" />
<!--
All services with tag "form.type" are inserted here by
InitFormsPass
-->
<!-- All services with tag "form.type" are inserted here by FormPass -->
<argument type="collection" />
<!--
All services with tag "form.type_extension" are inserted here by
InitFormsPass
-->
<!-- All services with tag "form.type_extension" are inserted here by FormPass -->
<argument type="collection" />
<!--
All services with tag "form.type_guesser" are inserted here by
InitFormsPass
-->
<!-- All services with tag "form.type_guesser" are inserted here by FormPass -->
<argument type="collection" />
</service>
@ -138,6 +136,11 @@
<tag name="form.type" alias="url" />
</service>
<!-- FormTypeHttpFoundationExtension -->
<service id="form.type_extension.form.http_foundation" class="Symfony\Component\Form\Extension\HttpFoundation\Type\FormTypeHttpFoundationExtension">
<tag name="form.type_extension" alias="form" />
</service>
<!-- FormTypeValidatorExtension -->
<service id="form.type_extension.form.validator" class="Symfony\Component\Form\Extension\Validator\Type\FormTypeValidatorExtension">
<tag name="form.type_extension" alias="form" />

View File

@ -22,6 +22,8 @@
<parameter key="router.options.matcher.cache_class">%kernel.name%%kernel.environment%UrlMatcher</parameter>
<parameter key="router.options.generator.cache_class">%kernel.name%%kernel.environment%UrlGenerator</parameter>
<parameter key="router_listener.class">Symfony\Component\HttpKernel\EventListener\RouterListener</parameter>
<parameter key="router.request_context.host">localhost</parameter>
<parameter key="router.request_context.scheme">http</parameter>
</parameters>
<services>
@ -74,8 +76,8 @@
<service id="router.request_context" class="%router.request_context.class%" public="false">
<argument></argument>
<argument>GET</argument>
<argument>localhost</argument>
<argument>http</argument>
<argument>%router.request_context.host%</argument>
<argument>%router.request_context.scheme%</argument>
<argument>%request_listener.http_port%</argument>
<argument>%request_listener.https_port%</argument>
</service>

View File

@ -1,5 +1,5 @@
<div>
<?php echo $view['form']->label($form, isset($label) ? $label : null) ?>
<?php echo $view['form']->label($form) ?>
<?php echo $view['form']->errors($form) ?>
<?php echo $view['form']->widget($form) ?>
</div>

View File

@ -1,6 +1,6 @@
<tr>
<td>
<?php echo $view['form']->label($form, isset($label) ? $label : null) ?>
<?php echo $view['form']->label($form) ?>
</td>
<td>
<?php echo $view['form']->errors($form) ?>

View File

@ -125,7 +125,7 @@ class FormHelper extends Helper
*/
public function label(FormView $view, $label = null, array $variables = array())
{
if ($label !== null) {
if (null !== $label) {
$variables += array('label' => $label);
}

View File

@ -224,6 +224,25 @@ class ClientTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->click() clicks on links');
}
public function testClickForm()
{
if (!class_exists('Symfony\Component\DomCrawler\Crawler')) {
$this->markTestSkipped('The "DomCrawler" component is not available');
}
if (!class_exists('Symfony\Component\CssSelector\CssSelector')) {
$this->markTestSkipped('The "CssSelector" component is not available');
}
$client = new TestClient();
$client->setNextResponse(new Response('<html><form action="/foo"><input type="submit" /></form></html>'));
$crawler = $client->request('GET', 'http://www.example.com/foo/foobar');
$client->click($crawler->filter('input')->form());
$this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->click() Form submit forms');
}
public function testSubmit()
{
if (!class_exists('Symfony\Component\DomCrawler\Crawler')) {

View File

@ -144,6 +144,29 @@ class CookieJarTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(array('foo' => 'bar%3Dbaz'), $cookieJar->allRawValues('/'));
}
public function testCookieExpireWithSameNameButDifferentPaths()
{
$cookieJar = new CookieJar();
$cookieJar->set($cookie1 = new Cookie('foo', 'bar1', null, '/foo'));
$cookieJar->set($cookie2 = new Cookie('foo', 'bar2', null, '/bar'));
$cookieJar->expire('foo', '/foo');
$this->assertNull($cookieJar->get('foo'), '->get() returns null if the cookie is expired');
$this->assertEquals(array(), array_keys($cookieJar->allValues('http://example.com/')));
$this->assertEquals(array(), $cookieJar->allValues('http://example.com/foo'));
$this->assertEquals(array('foo' => 'bar2'), $cookieJar->allValues('http://example.com/bar'));
}
public function testCookieExpireWithNullPaths()
{
$cookieJar = new CookieJar();
$cookieJar->set($cookie1 = new Cookie('foo', 'bar1', null, '/'));
$cookieJar->expire('foo', null);
$this->assertNull($cookieJar->get('foo'), '->get() returns null if the cookie is expired');
$this->assertEquals(array(), array_keys($cookieJar->allValues('http://example.com/')));
}
public function testCookieWithSameNameButDifferentPaths()
{
$cookieJar = new CookieJar();

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