Vendors update

Signed-off-by: Polonkai Gergely <polesz@w00d5t0ck.info>
master
Polonkai Gergely 11 years ago
parent 33b90a5c9f
commit 623b78e939

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

@ -119,6 +119,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));
}

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

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

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

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

@ -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'));
@ -763,6 +786,20 @@ abstract class AbstractDoctrineExtensionTest extends \PHPUnit_Framework_TestCase
$this->assertDICDefinitionMethodCallOnce($definition, 'addResolveTargetEntity', array('Symfony\Component\Security\Core\User\UserInterface', 'MyUserClass', array()));
$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)
{

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

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

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

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

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

@ -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()
{

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

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

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

@ -94,7 +94,7 @@ class QueryException extends \Doctrine\ORM\ORMException
}
/**
* @param \Doctrine\ORM\Mapping\AssociationMapping $assoc
* @param array $assoc
*/
public static function iterateWithFetchJoinCollectionNotAllowed($assoc)
{
@ -151,4 +151,4 @@ class QueryException extends \Doctrine\ORM\ORMException
return new self("Cannot check if a child of '" . $rootClass . "' is instanceof '" . $className . "', " .
"inheritance hierachy exists between these two classes.");
}
}
}

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

@ -23,7 +23,6 @@
namespace Doctrine\ORM\Tools\Export\Driver;
use Doctrine\ORM\Mapping\ClassMetadataInfo,
Doctrine\ORM\Mapping\AssociationMapping,
Doctrine\ORM\Tools\EntityGenerator;
/**
@ -69,4 +68,4 @@ class AnnotationExporter extends AbstractExporter
{
$this->_entityGenerator = $entityGenerator;
}
}
}

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

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

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

@ -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()));
}
}

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

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

@ -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())
{

@ -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())
{

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

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

@ -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()
{

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

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

@ -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));
}
}
}
}

@ -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) {
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) {
$request->attributes->set('_'.$configuration->getAliasName(), $configuration);
$configurations['_'.$configuration->getAliasName()][] = $configuration;
}
}
return $configurations;
}
}

@ -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());

@ -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();
}
}

@ -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());
}
}

@ -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()
));
}
$converter = $this->namedConverters[$converterName];
if (!$converter->supports($configuration)) {
throw new \RuntimeException(sprintf(
"Converter '%s' does not support conversion of parameter '%s'.",
$converterName, $configuration->getName()
));
}
foreach ($this->all() as $converter) {
if ($converter->supports($configuration)) {
if ($converter->apply($request, $configuration)) {
continue 2;
}
$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;
}
}
/**

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

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

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

@ -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());

@ -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])));

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

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