Skip to content

Commit

Permalink
Initial POC for expression language support
Browse files Browse the repository at this point in the history
  • Loading branch information
mpdude committed Jun 26, 2024
1 parent aa16d3b commit 2c0eaf4
Show file tree
Hide file tree
Showing 17 changed files with 116 additions and 27 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
6 changes: 5 additions & 1 deletion src/JMS/ObjectRouting/Attribute/ObjectRoute.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
3 changes: 2 additions & 1 deletion src/JMS/ObjectRouting/Metadata/ClassMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/JMS/ObjectRouting/Metadata/Driver/AttributeDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 6 additions & 1 deletion src/JMS/ObjectRouting/Metadata/Driver/XmlDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 6 additions & 1 deletion src/JMS/ObjectRouting/Metadata/Driver/YamlDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
15 changes: 15 additions & 0 deletions src/JMS/ObjectRouting/ObjectRouter.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@
use Metadata\Driver\DriverChain;
use Metadata\MetadataFactory;
use Metadata\MetadataFactoryInterface;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\PropertyAccess\PropertyAccessor;

class ObjectRouter
{
private $router;
private $metadataFactory;
private $accessor;
private $expressionLanguage;

public static function create(RouterInterface $router)
{
Expand All @@ -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();
}

/**
Expand Down Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
41 changes: 41 additions & 0 deletions tests/JMS/Tests/ObjectRouting/ObjectRouterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
return $metadata;
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
<class name="JMS\Tests\ObjectRouting\Metadata\Driver\Fixture\BlogPost">
<route type="view" name="blog_post_view">
<param name="slug">slug</param>
<paramExpression name="?year">this.isArchived ? this.year : null</paramExpression>
</route>
<route type="edit" name="blog_post_edit">
<param name="slug">slug</param>
</route>
</class>
</object-routing>
</object-routing>
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit 2c0eaf4

Please sign in to comment.