diff --git a/composer.json b/composer.json index b55c14f..7b72230 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,8 @@ "require": { "php": ">= 8.1", "jms/metadata": "^2.6.1", - "symfony/property-access": "^3.4|^4.0|^5.0|^6.0|^7.0" + "symfony/property-access": "^3.4|^4.0|^5.0|^6.0|^7.0", + "symfony/expression-language": "^3.4|^4.0|^5.0|^6.0|^7.0" }, "require-dev": { diff --git a/src/JMS/ObjectRouting/Attribute/ObjectRoute.php b/src/JMS/ObjectRouting/Attribute/ObjectRoute.php index 01458a4..89ad9ce 100644 --- a/src/JMS/ObjectRouting/Attribute/ObjectRoute.php +++ b/src/JMS/ObjectRouting/Attribute/ObjectRoute.php @@ -30,10 +30,14 @@ final class ObjectRoute /** @var array */ public $params = array(); - public function __construct(string $type, string $name, array $params = []) + /** @var array */ + public $paramExpressions = array(); + + public function __construct(string $type, string $name, array $params = [], array $paramExpressions = []) { $this->type = $type; $this->name = $name; $this->params = $params; + $this->paramExpressions = $paramExpressions; } } diff --git a/src/JMS/ObjectRouting/Metadata/ClassMetadata.php b/src/JMS/ObjectRouting/Metadata/ClassMetadata.php index dc7856e..0f733b0 100644 --- a/src/JMS/ObjectRouting/Metadata/ClassMetadata.php +++ b/src/JMS/ObjectRouting/Metadata/ClassMetadata.php @@ -25,11 +25,12 @@ class ClassMetadata extends MergeableClassMetadata { public $routes = array(); - public function addRoute($type, $name, array $params = array()) + public function addRoute($type, $name, array $params = array(), array $paramExpressions = array()) { $this->routes[$type] = array( 'name' => $name, 'params' => $params, + 'paramExpressions' => $paramExpressions, ); } diff --git a/src/JMS/ObjectRouting/Metadata/Driver/AttributeDriver.php b/src/JMS/ObjectRouting/Metadata/Driver/AttributeDriver.php index 3a74288..47013eb 100644 --- a/src/JMS/ObjectRouting/Metadata/Driver/AttributeDriver.php +++ b/src/JMS/ObjectRouting/Metadata/Driver/AttributeDriver.php @@ -31,7 +31,7 @@ public function loadMetadataForClass(\ReflectionClass $class): ?ClassMetadata $hasMetadata = false; foreach ($this->fetchAttributes($class) as $attribute) { $hasMetadata = true; - $metadata->addRoute($attribute->type, $attribute->name, $attribute->params); + $metadata->addRoute($attribute->type, $attribute->name, $attribute->params, $attribute->paramExpressions); } return $hasMetadata ? $metadata : null; diff --git a/src/JMS/ObjectRouting/Metadata/Driver/XmlDriver.php b/src/JMS/ObjectRouting/Metadata/Driver/XmlDriver.php index 5c9435e..b3b2f04 100644 --- a/src/JMS/ObjectRouting/Metadata/Driver/XmlDriver.php +++ b/src/JMS/ObjectRouting/Metadata/Driver/XmlDriver.php @@ -76,7 +76,12 @@ protected function loadMetadataFromFile(\ReflectionClass $class, string $file): $params[(string)$p->attributes()] = (string)$p; } - $metadata->addRoute($type, $name, $params); + $paramExpressions = array(); + foreach ($r->xpath('./paramExpression') as $p) { + $paramExpressions[(string)$p->attributes()] = (string)$p; + } + + $metadata->addRoute($type, $name, $params, $paramExpressions); } return $metadata; diff --git a/src/JMS/ObjectRouting/Metadata/Driver/YamlDriver.php b/src/JMS/ObjectRouting/Metadata/Driver/YamlDriver.php index 57ea4dc..254079c 100644 --- a/src/JMS/ObjectRouting/Metadata/Driver/YamlDriver.php +++ b/src/JMS/ObjectRouting/Metadata/Driver/YamlDriver.php @@ -58,7 +58,12 @@ protected function loadMetadataFromFile(\ReflectionClass $class, string $file): if (!array_key_exists('name', $value)) { throw new RuntimeException('Could not find key "type" inside yaml element.'); } - $metadata->addRoute($type, $value['name'], array_key_exists('params', $value) ? $value['params'] : array()); + $metadata->addRoute( + $type, + $value['name'], + array_key_exists('params', $value) ? $value['params'] : array(), + array_key_exists('paramExpressions', $value) ? $value['paramExpressions'] : array() + ); } return $metadata; diff --git a/src/JMS/ObjectRouting/ObjectRouter.php b/src/JMS/ObjectRouting/ObjectRouter.php index 19bddf7..89403a9 100644 --- a/src/JMS/ObjectRouting/ObjectRouter.php +++ b/src/JMS/ObjectRouting/ObjectRouter.php @@ -23,6 +23,7 @@ use Metadata\Driver\DriverChain; use Metadata\MetadataFactory; use Metadata\MetadataFactoryInterface; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; use Symfony\Component\PropertyAccess\PropertyAccessor; class ObjectRouter @@ -30,6 +31,7 @@ class ObjectRouter private $router; private $metadataFactory; private $accessor; + private $expressionLanguage; public static function create(RouterInterface $router) { @@ -46,6 +48,7 @@ public function __construct(RouterInterface $router, MetadataFactoryInterface $m $this->router = $router; $this->metadataFactory = $metadataFactory; $this->accessor = new PropertyAccessor(); + $this->expressionLanguage = new ExpressionLanguage(); } /** @@ -86,6 +89,18 @@ public function generate($type, $object, $absolute = false, array $extraParams = $params[$k] = $this->accessor->getValue($object, $path); } + foreach ($route['paramExpressions'] as $k => $expression) { + $evaluated = $this->expressionLanguage->evaluate($expression, ['this' => $object]); + if ($k[0] === '?') { + if ($evaluated === null) { + continue; + } + $params[substr($k, 1)] = $evaluated; + } else { + $params[$k] = $evaluated; + } + } + return $this->router->generate($route['name'], $params, $absolute); } diff --git a/tests/JMS/Tests/ObjectRouting/Metadata/ClassMetadataTest.php b/tests/JMS/Tests/ObjectRouting/Metadata/ClassMetadataTest.php index b2b17f3..3dd2299 100644 --- a/tests/JMS/Tests/ObjectRouting/Metadata/ClassMetadataTest.php +++ b/tests/JMS/Tests/ObjectRouting/Metadata/ClassMetadataTest.php @@ -18,6 +18,6 @@ public function testMerge() $base->merge($merged); $this->assertEquals(self::class, $base->name); - $this->assertEquals(['test' => ['name' => 'merged-route', 'params' => []]], $base->routes); + $this->assertEquals(['test' => ['name' => 'merged-route', 'params' => [], 'paramExpressions' => []]], $base->routes); } } diff --git a/tests/JMS/Tests/ObjectRouting/Metadata/Driver/AttributeDriverTest.php b/tests/JMS/Tests/ObjectRouting/Metadata/Driver/AttributeDriverTest.php index 7755b0a..e26ac40 100644 --- a/tests/JMS/Tests/ObjectRouting/Metadata/Driver/AttributeDriverTest.php +++ b/tests/JMS/Tests/ObjectRouting/Metadata/Driver/AttributeDriverTest.php @@ -16,8 +16,8 @@ public function testLoad() $this->assertCount(2, $metadata->routes); $routes = [ - 'view' => ['name' => 'blog_post_view', 'params' => ['slug' => 'slug']], - 'edit' => ['name' => 'blog_post_edit', 'params' => ['slug' => 'slug']], + 'view' => ['name' => 'blog_post_view', 'params' => ['slug' => 'slug'], 'paramExpressions' => ['?year' => 'this.isArchived ? this.year : null']], + 'edit' => ['name' => 'blog_post_edit', 'params' => ['slug' => 'slug'], 'paramExpressions' => []], ]; $this->assertEquals($routes, $metadata->routes); } diff --git a/tests/JMS/Tests/ObjectRouting/Metadata/Driver/Fixture/BlogPostWithAttributes.php b/tests/JMS/Tests/ObjectRouting/Metadata/Driver/Fixture/BlogPostWithAttributes.php index 90c61be..9848065 100644 --- a/tests/JMS/Tests/ObjectRouting/Metadata/Driver/Fixture/BlogPostWithAttributes.php +++ b/tests/JMS/Tests/ObjectRouting/Metadata/Driver/Fixture/BlogPostWithAttributes.php @@ -4,19 +4,33 @@ use JMS\ObjectRouting\Attribute\ObjectRoute; -#[ObjectRoute(type: "view", name: "blog_post_view", params: ['slug' => 'slug'])] +#[ObjectRoute(type: "view", name: "blog_post_view", params: ['slug' => 'slug'], paramExpressions: ['?year' => 'this.isArchived ? this.year : null'])] #[ObjectRoute(type: "edit", name: "blog_post_edit", params: ['slug' => 'slug'])] class BlogPostWithAttributes { private $slug; + private $archived; + private $year; - public function __construct($slug) + public function __construct($slug, $archived, $year) { $this->slug = $slug; + $this->archived = $archived; + $this->year = $year; } public function getSlug() { return $this->slug; } + + public function isArchived() + { + return $this->archived; + } + + public function getYear() + { + return $this->year; + } } diff --git a/tests/JMS/Tests/ObjectRouting/Metadata/Driver/PhpDriverTest.php b/tests/JMS/Tests/ObjectRouting/Metadata/Driver/PhpDriverTest.php index f112ed6..f895072 100644 --- a/tests/JMS/Tests/ObjectRouting/Metadata/Driver/PhpDriverTest.php +++ b/tests/JMS/Tests/ObjectRouting/Metadata/Driver/PhpDriverTest.php @@ -16,10 +16,10 @@ public function testLoad() $metadata = $this->driver->loadMetadataForClass(new \ReflectionClass('JMS\Tests\ObjectRouting\Metadata\Driver\Fixture\BlogPost')); $this->assertCount(2, $metadata->routes); - $routes = array( - 'view' => array('name' => 'blog_post_view', 'params' => array('slug' => 'slug')), - 'edit' => array('name' => 'blog_post_edit', 'params' => array('slug' => 'slug')), - ); + $routes = [ + 'view' => ['name' => 'blog_post_view', 'params' => ['slug' => 'slug'], 'paramExpressions' => ['?year' => 'this.isArchived ? this.year : null']], + 'edit' => ['name' => 'blog_post_edit', 'params' => ['slug' => 'slug'], 'paramExpressions' => []], + ]; $this->assertEquals($routes, $metadata->routes); } diff --git a/tests/JMS/Tests/ObjectRouting/Metadata/Driver/XmlDriverTest.php b/tests/JMS/Tests/ObjectRouting/Metadata/Driver/XmlDriverTest.php index 8892fa2..65a15f7 100644 --- a/tests/JMS/Tests/ObjectRouting/Metadata/Driver/XmlDriverTest.php +++ b/tests/JMS/Tests/ObjectRouting/Metadata/Driver/XmlDriverTest.php @@ -18,10 +18,10 @@ public function testLoad() $metadata = $this->driver->loadMetadataForClass(new \ReflectionClass('JMS\Tests\ObjectRouting\Metadata\Driver\Fixture\BlogPost')); $this->assertCount(2, $metadata->routes); - $routes = array( - 'view' => array('name' => 'blog_post_view', 'params' => array('slug' => 'slug')), - 'edit' => array('name' => 'blog_post_edit', 'params' => array('slug' => 'slug')), - ); + $routes = [ + 'view' => ['name' => 'blog_post_view', 'params' => ['slug' => 'slug'], 'paramExpressions' => ['?year' => 'this.isArchived ? this.year : null']], + 'edit' => ['name' => 'blog_post_edit', 'params' => ['slug' => 'slug'], 'paramExpressions' => []], + ]; $this->assertEquals($routes, $metadata->routes); } diff --git a/tests/JMS/Tests/ObjectRouting/Metadata/Driver/YamlDriverTest.php b/tests/JMS/Tests/ObjectRouting/Metadata/Driver/YamlDriverTest.php index 3e65704..eade4b6 100644 --- a/tests/JMS/Tests/ObjectRouting/Metadata/Driver/YamlDriverTest.php +++ b/tests/JMS/Tests/ObjectRouting/Metadata/Driver/YamlDriverTest.php @@ -18,10 +18,10 @@ public function testLoad() $metadata = $this->driver->loadMetadataForClass(new \ReflectionClass('JMS\Tests\ObjectRouting\Metadata\Driver\Fixture\BlogPost')); $this->assertCount(2, $metadata->routes); - $routes = array( - 'view' => array('name' => 'blog_post_view', 'params' => array('slug' => 'slug')), - 'edit' => array('name' => 'blog_post_edit', 'params' => array('slug' => 'slug')), - ); + $routes = [ + 'view' => ['name' => 'blog_post_view', 'params' => ['slug' => 'slug'], 'paramExpressions' => ['?year' => 'this.isArchived ? this.year : null']], + 'edit' => ['name' => 'blog_post_edit', 'params' => ['slug' => 'slug'], 'paramExpressions' => []], + ]; $this->assertEquals($routes, $metadata->routes); } diff --git a/tests/JMS/Tests/ObjectRouting/ObjectRouterTest.php b/tests/JMS/Tests/ObjectRouting/ObjectRouterTest.php index 2b35cb0..90d0ece 100644 --- a/tests/JMS/Tests/ObjectRouting/ObjectRouterTest.php +++ b/tests/JMS/Tests/ObjectRouting/ObjectRouterTest.php @@ -53,6 +53,47 @@ public function testGenerateWithParams() $this->assertEquals('/foobar', $this->router->generate('view', $object)); } + public function testGenerateWithParamExpression() + { + $metadata = new ClassMetadata('stdClass'); + $metadata->addRoute('view', 'view_name', [], ['foo' => 'this.bar']); + + $object = new \stdClass; + $object->bar = 'baz'; + + $this->factory->expects($this->once()) + ->method('getMetadataForClass') + ->will($this->returnValue($metadata)); + + $this->adapter->expects($this->once()) + ->method('generate') + ->with('view_name', array('foo' => 'baz'), false) + ->will($this->returnValue('/foobar')); + + $this->assertEquals('/foobar', $this->router->generate('view', $object)); + } + + public function testGenerateWithNullableParamExpression() + { + $metadata = new ClassMetadata('stdClass'); + $metadata->addRoute('view', 'view_name', [], ['?foo' => 'this.bar', '?quux' => 'this.barbaz']); + + $object = new \stdClass; + $object->bar = 'baz'; + $object->barbaz = null; + + $this->factory->expects($this->once()) + ->method('getMetadataForClass') + ->will($this->returnValue($metadata)); + + $this->adapter->expects($this->once()) + ->method('generate') + ->with('view_name', array('foo' => 'baz'), false) + ->will($this->returnValue('/foobar')); + + $this->assertEquals('/foobar', $this->router->generate('view', $object)); + } + public function testGenerateNonExistentType() { $this->expectException(\RuntimeException::class); diff --git a/tests/JMS/Tests/ObjectRouting/Resources/config/JMS.Tests.ObjectRouting.Metadata.Driver.Fixture.BlogPost.php b/tests/JMS/Tests/ObjectRouting/Resources/config/JMS.Tests.ObjectRouting.Metadata.Driver.Fixture.BlogPost.php index 5604334..6d28507 100644 --- a/tests/JMS/Tests/ObjectRouting/Resources/config/JMS.Tests.ObjectRouting.Metadata.Driver.Fixture.BlogPost.php +++ b/tests/JMS/Tests/ObjectRouting/Resources/config/JMS.Tests.ObjectRouting.Metadata.Driver.Fixture.BlogPost.php @@ -2,7 +2,7 @@ $metadata = new JMS\ObjectRouting\Metadata\ClassMetadata('JMS\Tests\ObjectRouting\Metadata\Driver\Fixture\BlogPost'); -$metadata->addRoute('view', 'blog_post_view', array('slug' => 'slug')); -$metadata->addRoute('edit', 'blog_post_edit', array('slug' => 'slug')); +$metadata->addRoute('view', 'blog_post_view', ['slug' => 'slug'], ['?year' => 'this.isArchived ? this.year : null']); +$metadata->addRoute('edit', 'blog_post_edit', ['slug' => 'slug']); -return $metadata; \ No newline at end of file +return $metadata; diff --git a/tests/JMS/Tests/ObjectRouting/Resources/config/JMS.Tests.ObjectRouting.Metadata.Driver.Fixture.BlogPost.xml b/tests/JMS/Tests/ObjectRouting/Resources/config/JMS.Tests.ObjectRouting.Metadata.Driver.Fixture.BlogPost.xml index 653e00e..4ae1855 100644 --- a/tests/JMS/Tests/ObjectRouting/Resources/config/JMS.Tests.ObjectRouting.Metadata.Driver.Fixture.BlogPost.xml +++ b/tests/JMS/Tests/ObjectRouting/Resources/config/JMS.Tests.ObjectRouting.Metadata.Driver.Fixture.BlogPost.xml @@ -3,9 +3,10 @@ slug + this.isArchived ? this.year : null slug - \ No newline at end of file + diff --git a/tests/JMS/Tests/ObjectRouting/Resources/config/JMS.Tests.ObjectRouting.Metadata.Driver.Fixture.BlogPost.yml b/tests/JMS/Tests/ObjectRouting/Resources/config/JMS.Tests.ObjectRouting.Metadata.Driver.Fixture.BlogPost.yml index f6edd7a..ca1985f 100644 --- a/tests/JMS/Tests/ObjectRouting/Resources/config/JMS.Tests.ObjectRouting.Metadata.Driver.Fixture.BlogPost.yml +++ b/tests/JMS/Tests/ObjectRouting/Resources/config/JMS.Tests.ObjectRouting.Metadata.Driver.Fixture.BlogPost.yml @@ -3,6 +3,8 @@ JMS\Tests\ObjectRouting\Metadata\Driver\Fixture\BlogPost: name: "blog_post_view" params: slug: "slug" + paramExpressions: + ?year: "this.isArchived ? this.year : null" edit: name: "blog_post_edit" params: