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 +