diff --git a/DependencyInjection/CmfRoutingExtension.php b/DependencyInjection/CmfRoutingExtension.php
index e9d5176b..316fdb88 100644
--- a/DependencyInjection/CmfRoutingExtension.php
+++ b/DependencyInjection/CmfRoutingExtension.php
@@ -34,10 +34,12 @@ public function load(array $configs, ContainerBuilder $container)
{
$config = $this->processConfiguration(new Configuration(), $configs);
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
+ $loader->load('metadata.xml');
if ($config['dynamic']['enabled']) {
// load this even if no explicit enabled value but some configuration
$this->setupDynamicRouter($config['dynamic'], $container, $loader);
+ $this->setupMetadataDrivers($config['dynamic'], $container);
}
/* set up the chain router */
@@ -53,6 +55,52 @@ public function load(array $configs, ContainerBuilder $container)
$this->setupFormTypes($config, $container, $loader);
}
+ public function setupMetadataDrivers($config, ContainerBuilder $container)
+ {
+ $configDriver = $container->getDefinition($this->getAlias() . '.metadata.driver.configuration');
+ $annotationDriver = $container->getDefinition($this->getAlias() . '.metadata.driver.annotation');
+
+ $mapping = array();
+
+ $controller = $container->getParameter($this->getAlias() . '.generic_controller');;
+
+ // configure annotation driver
+ $annotationDriver->addMethodCall('setGenericController', array($controller));
+
+ $mappings = array();
+
+ // configure configuration driver
+ if (!empty($config['controllers_by_class'])) {
+ foreach ($config['controllers_by_class'] as $classFqn => $controller) {
+ if (!isset($mappings[$classFqn])) {
+ $mappings[$classFqn] = array();
+ }
+
+ $mappings[$classFqn]['controller'] = $controller;
+ }
+ }
+
+ if (!empty($config['templates_by_class'])) {
+ foreach ($config['templates_by_class'] as $classFqn => $template) {
+ if (!isset($mappings[$classFqn])) {
+ $mappings[$classFqn] = array();
+ }
+
+ $mappings[$classFqn]['template'] = $template;
+ }
+ }
+
+ foreach ($mappings as $classFqn => &$mapping) {
+ if (!isset($mapping['controller'])) {
+ $mapping['controller'] = $container->getParameter(
+ $this->getAlias() . '.generic_controller'
+ );
+ }
+
+ $configDriver->addMethodCall('registerMapping', array($classFqn, $mapping));
+ }
+ }
+
public function setupFormTypes(array $config, ContainerBuilder $container, LoaderInterface $loader)
{
$loader->load('form-type.xml');
@@ -76,7 +124,7 @@ public function setupFormTypes(array $config, ContainerBuilder $container, Loade
private function setupDynamicRouter(array $config, ContainerBuilder $container, LoaderInterface $loader)
{
// strip whitespace (XML support)
- foreach (array('controllers_by_type', 'controllers_by_class', 'templates_by_class', 'route_filters_by_id') as $option) {
+ foreach (array('controllers_by_type', 'route_filters_by_id') as $option) {
$config[$option] = array_map(function ($value) {
return trim($value);
}, $config[$option]);
@@ -86,11 +134,10 @@ private function setupDynamicRouter(array $config, ContainerBuilder $container,
if (null === $defaultController) {
$defaultController = $config['generic_controller'];
}
+
$container->setParameter($this->getAlias() . '.default_controller', $defaultController);
$container->setParameter($this->getAlias() . '.generic_controller', $config['generic_controller']);
$container->setParameter($this->getAlias() . '.controllers_by_type', $config['controllers_by_type']);
- $container->setParameter($this->getAlias() . '.controllers_by_class', $config['controllers_by_class']);
- $container->setParameter($this->getAlias() . '.templates_by_class', $config['templates_by_class']);
$container->setParameter($this->getAlias() . '.uri_filter_regexp', $config['uri_filter_regexp']);
$container->setParameter($this->getAlias() . '.route_collection_limit', $config['route_collection_limit']);
@@ -150,55 +197,23 @@ private function setupDynamicRouter(array $config, ContainerBuilder $container,
)
);
}
- if (!empty($config['controllers_by_class'])) {
- $dynamic->addMethodCall(
- 'addRouteEnhancer',
- array(
- new Reference($this->getAlias() . '.enhancer.controllers_by_class'),
- 50
- )
- );
- }
- if (!empty($config['templates_by_class'])) {
- $dynamic->addMethodCall(
- 'addRouteEnhancer',
- array(
- new Reference($this->getAlias() . '.enhancer.templates_by_class'),
- 40
- )
- );
- /*
- * The CoreBundle prepends the controller from ContentBundle if the
- * ContentBundle is present in the project.
- * If you are sure you do not need a generic controller, set the field
- * to false to disable this check explicitly. But you would need
- * something else like the default_controller to set the controller,
- * as no controller will be set here.
- */
- if (null === $config['generic_controller']) {
- throw new InvalidConfigurationException('If you want to configure templates_by_class, you need to configure the generic_controller option.');
- }
+ $dynamic->addMethodCall(
+ 'addRouteEnhancer',
+ array(
+ new Reference($this->getAlias() . '.enhancer.controllers_by_class'),
+ 50
+ )
+ );
+
+ $dynamic->addMethodCall(
+ 'addRouteEnhancer',
+ array(
+ new Reference($this->getAlias() . '.enhancer.templates_by_class'),
+ 40
+ )
+ );
- if (is_string($config['generic_controller'])) {
- // if the content class defines the template, we also need to make sure we use the generic controller for those routes
- $controllerForTemplates = array();
- foreach ($config['templates_by_class'] as $key => $value) {
- $controllerForTemplates[$key] = $config['generic_controller'];
- }
-
- $definition = $container->getDefinition($this->getAlias() . '.enhancer.controller_for_templates_by_class');
- $definition->replaceArgument(2, $controllerForTemplates);
-
- $dynamic->addMethodCall(
- 'addRouteEnhancer',
- array(
- new Reference($this->getAlias() . '.enhancer.controller_for_templates_by_class'),
- 30
- )
- );
- }
- }
if (!empty($config['generic_controller']) && $config['generic_controller'] !== $defaultController) {
$dynamic->addMethodCall(
'addRouteEnhancer',
diff --git a/Enhancer/FieldByClassEnhancer.php b/Enhancer/FieldByClassEnhancer.php
new file mode 100644
index 00000000..bb73eeda
--- /dev/null
+++ b/Enhancer/FieldByClassEnhancer.php
@@ -0,0 +1,84 @@
+
+ */
+class FieldByClassEnhancer implements RouteEnhancerInterface
+{
+ /**
+ * @var string field for the className class
+ */
+ protected $classField;
+
+ /**
+ * @var string field to write hashmap lookup result into
+ */
+ protected $targetField;
+
+ /**
+ * @var string field to write hashmap lookup result into
+ */
+ protected $metaField;
+
+ /**
+ * @var Metadata\MetadataFactoryInterface
+ */
+ protected $metadataFactory;
+
+ /**
+ * @param string $className the field name of the class
+ * @param string $targetField the field name to set from the map
+ * @param array $map the map of class names to field values
+ */
+ public function __construct(
+ $classField,
+ $targetField,
+ $metaField,
+ MetadataFactoryInterface $metadataFactory
+ )
+ {
+ $this->classField = $classField;
+ $this->targetField = $targetField;
+ $this->metaField = $metaField;
+ $this->metadataFactory = $metadataFactory;
+ }
+
+ /**
+ * If the class name is mapped
+ *
+ * {@inheritDoc}
+ */
+ public function enhance(array $defaults, Request $request)
+ {
+ if (isset($defaults[$this->targetField])) {
+ return $defaults;
+ }
+
+ // return if the field containg the class name has not been set
+ if (! isset($defaults[$this->classField])) {
+ return $defaults;
+ }
+
+ $className = get_class($defaults[$this->classField]);
+
+ $meta = $this->metadataFactory->getMetadataForClass($className);
+
+ if (null !== $meta) {
+ $meta = $meta->getOutsideClassMetadata();
+ if (null !== $meta->{$this->metaField}) {
+ $defaults[$this->targetField] = $meta->{$this->metaField};
+ }
+ }
+
+ return $defaults;
+ }
+}
diff --git a/Metadata/Annotations/Controller.php b/Metadata/Annotations/Controller.php
new file mode 100644
index 00000000..561bd604
--- /dev/null
+++ b/Metadata/Annotations/Controller.php
@@ -0,0 +1,11 @@
+template &&
+ null == $meta->controller
+ ) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Template specified for class "%s" but no controller specified.' .PHP_EOL.
+ 'You must either explicitly map a controller to the class or define'.PHP_EOL.
+ 'a "generic_controller" in the main configuration.'
+ , $meta->name));
+ }
+ }
+}
diff --git a/Metadata/Driver/AnnotationDriver.php b/Metadata/Driver/AnnotationDriver.php
new file mode 100644
index 00000000..f7aa967b
--- /dev/null
+++ b/Metadata/Driver/AnnotationDriver.php
@@ -0,0 +1,56 @@
+reader = $reader;
+ }
+
+ public function setGenericController($genericController)
+ {
+ $this->genericController = $genericController;
+ }
+
+ public function loadMetadataForClass(\ReflectionClass $class)
+ {
+ $templateAnnotation = $this->reader->getClassAnnotation(
+ $class,
+ 'Symfony\Cmf\Bundle\RoutingBundle\Metadata\Annotations\Template'
+ );
+ $controllerAnnotation = $this->reader->getClassAnnotation(
+ $class,
+ 'Symfony\Cmf\Bundle\RoutingBundle\Metadata\Annotations\Controller'
+ );
+
+
+ $meta = new ClassMetadata($class->name);
+
+ if (null !== $templateAnnotation) {
+ $meta->template = $templateAnnotation->resource;
+ }
+
+ if (null !== $controllerAnnotation) {
+ $meta->controller = $controllerAnnotation->controller;
+ }
+
+ if (null === $meta->controller) {
+ $meta->controller = $this->genericController;
+ }
+
+ $this->validateMetadata($meta);
+
+ return $meta;
+ }
+}
diff --git a/Metadata/Driver/ConfigurationDriver.php b/Metadata/Driver/ConfigurationDriver.php
new file mode 100644
index 00000000..60bda400
--- /dev/null
+++ b/Metadata/Driver/ConfigurationDriver.php
@@ -0,0 +1,46 @@
+
+ */
+class ConfigurationDriver extends AbstractDriver
+{
+ protected $reader;
+ protected $metadata = array();
+
+ /**
+ * Eagerly loads the class metadata
+ */
+ public function registerMapping($classFqn, $mapping)
+ {
+ $meta = new ClassMetadata($classFqn);
+
+ if (isset($mapping['template'])) {
+ $meta->template = $mapping['template'];
+ }
+
+ if (isset($mapping['controller'])) {
+ $meta->controller = $mapping['controller'];
+ }
+
+ $this->validateMetadata($meta);
+
+ $this->metadata[$classFqn] = $meta;
+ }
+
+ public function loadMetadataForClass(\ReflectionClass $class)
+ {
+ if (!isset($this->metadata[$class->name])) {
+ return null;
+ }
+
+ return $this->metadata[$class->name];
+ }
+}
+
diff --git a/Resources/config/metadata.xml b/Resources/config/metadata.xml
new file mode 100644
index 00000000..4951b89a
--- /dev/null
+++ b/Resources/config/metadata.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Resources/config/routing-dynamic.xml b/Resources/config/routing-dynamic.xml
index 894512a7..2a824135 100644
--- a/Resources/config/routing-dynamic.xml
+++ b/Resources/config/routing-dynamic.xml
@@ -15,7 +15,7 @@
Symfony\Cmf\Component\Routing\Enhancer\FieldPresenceEnhancer
Symfony\Cmf\Component\Routing\Enhancer\FieldPresenceEnhancer
Symfony\Cmf\Component\Routing\Enhancer\FieldMapEnhancer
- Symfony\Cmf\Component\Routing\Enhancer\FieldByClassEnhancer
+ Symfony\Cmf\Bundle\RoutingBundle\Enhancer\FieldByClassEnhancer
Symfony\Cmf\Bundle\RoutingBundle\Controller\RedirectController
@@ -46,19 +46,22 @@
_content
_controller
- %cmf_routing.controllers_by_class%
+ controller
+
_content
_controller
-
+ controller
+
_content
_template
- %cmf_routing.templates_by_class%
+ template
+
diff --git a/Tests/Functional/Metadata/Driver/AnnotationDriverTest.php b/Tests/Functional/Metadata/Driver/AnnotationDriverTest.php
new file mode 100644
index 00000000..ffaeda4d
--- /dev/null
+++ b/Tests/Functional/Metadata/Driver/AnnotationDriverTest.php
@@ -0,0 +1,71 @@
+chainDriver = $this->getContainer()->get('cmf_routing.metadata.factory');
+ $this->annotDriver = $this->getContainer()->get('cmf_routing.metadata.driver.annotation');
+ }
+
+ public function testDriverTemplateAndContent()
+ {
+ $meta = $this->chainDriver->getMetadataForClass(
+ 'Symfony\Cmf\Bundle\RoutingBundle\Tests\Resources\Document\AnnotatedContent'
+ );
+
+ $this->assertNotNull($meta);
+
+ $meta = $meta->getOutsideClassMetadata();
+
+ $this->assertEquals('TestBundle:Content:index.html.twig', $meta->template);
+ $this->assertEquals('ThisIsAController', $meta->controller);
+ }
+
+ public function testDriverTemplateOnlyWithGenericController()
+ {
+ $meta = $this->chainDriver->getMetadataForClass(
+ 'Symfony\Cmf\Bundle\RoutingBundle\Tests\Resources\Document\AnnotatedContentTemplateOnly'
+ );
+
+ $this->assertNotNull($meta);
+
+ $meta = $meta->getOutsideClassMetadata();
+
+ $this->assertEquals('TestBundle:Content:index.html.twig', $meta->template);
+ $this->assertEquals('cmf_content.controller:indexAction', $meta->controller);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ * @expectedExceptionMessage but no controller specified
+ */
+ public function testDriverTemplateOnlyNoController()
+ {
+ $this->annotDriver->setGenericController(null);
+ $meta = $this->chainDriver->getMetadataForClass(
+ 'Symfony\Cmf\Bundle\RoutingBundle\Tests\Resources\Document\AnnotatedContentTemplateOnly'
+ );
+ }
+}
diff --git a/Tests/Resources/Document/AnnotatedContent.php b/Tests/Resources/Document/AnnotatedContent.php
new file mode 100644
index 00000000..ff25853c
--- /dev/null
+++ b/Tests/Resources/Document/AnnotatedContent.php
@@ -0,0 +1,46 @@
+path = $path;
+ }
+
+ public function setTitle($title)
+ {
+ $this->title = $title;
+ }
+}
diff --git a/Tests/Resources/Document/AnnotatedContentTemplateOnly.php b/Tests/Resources/Document/AnnotatedContentTemplateOnly.php
new file mode 100644
index 00000000..ef76ac92
--- /dev/null
+++ b/Tests/Resources/Document/AnnotatedContentTemplateOnly.php
@@ -0,0 +1,45 @@
+path = $path;
+ }
+
+ public function setTitle($title)
+ {
+ $this->title = $title;
+ }
+}
diff --git a/Tests/Unit/Metadata/Driver/ConfigurationDriverTest.php b/Tests/Unit/Metadata/Driver/ConfigurationDriverTest.php
new file mode 100644
index 00000000..b3379401
--- /dev/null
+++ b/Tests/Unit/Metadata/Driver/ConfigurationDriverTest.php
@@ -0,0 +1,43 @@
+driver = new ConfigurationDriver;
+ }
+
+ public function testDriver()
+ {
+ $this->driver->registerMapping('stdClass', array(
+ 'template' => 'foobar.html.twig',
+ 'controller' => 'FoobarController',
+ ));
+
+ $refl = new \ReflectionClass('stdClass');
+ $meta = $this->driver->loadMetadataForClass($refl);
+
+ $this->assertEquals('foobar.html.twig', $meta->template);
+ $this->assertEquals('FoobarController', $meta->controller);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ * @expectedExceptionMessage but no controller specified
+ */
+ public function testDriverMissingController()
+ {
+ $this->driver->registerMapping('stdClass', array(
+ 'template' => 'foobar.html.twig',
+ ));
+
+ $refl = new \ReflectionClass('stdClass');
+ $meta = $this->driver->loadMetadataForClass($refl);
+ }
+}
diff --git a/composer.json b/composer.json
index 07afa7c0..2ce8a7e1 100644
--- a/composer.json
+++ b/composer.json
@@ -16,7 +16,8 @@
"require": {
"php": ">=5.3.3",
"symfony-cmf/routing": "~1.2.0",
- "symfony/framework-bundle": "~2.2"
+ "symfony/framework-bundle": "~2.2",
+ "jms/metadata": "1.5.0"
},
"require-dev": {
"symfony-cmf/core-bundle": "1.0.*",
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 15fce6e1..893d12f2 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -19,6 +19,10 @@
./Tests/Functional/Doctrine/Orm
+
+
+ ./Tests/Functional/Metadata
+