Skip to content

Commit

Permalink
Merge branch 'master' into metadata-stack-goodness
Browse files Browse the repository at this point in the history
Conflicts:
	Tests/Functional/ControllerTest.php
  • Loading branch information
adrienbrault committed Apr 9, 2013
2 parents 3e7136e + d5a6b4f commit 075f484
Show file tree
Hide file tree
Showing 24 changed files with 303 additions and 8 deletions.
5 changes: 5 additions & 0 deletions Annotation/Relation.php
Expand Up @@ -29,4 +29,9 @@ final class Relation
* @var array
*/
public $attributes;

/**
* @var array
*/
public $excludeIf = array();
}
34 changes: 33 additions & 1 deletion Factory/LinkFactory.php
Expand Up @@ -4,6 +4,9 @@

use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

use Symfony\Component\PropertyAccess\PropertyPath;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;

use FSC\HateoasBundle\Model\Link;
use FSC\HateoasBundle\Metadata\MetadataFactoryInterface;
use FSC\HateoasBundle\Metadata\ClassMetadataInterface;
Expand All @@ -12,16 +15,19 @@

class LinkFactory extends AbstractLinkFactory implements LinkFactoryInterface
{
protected $propertyAccessor;
protected $metadataFactory;
protected $parametersFactory;

public function __construct(MetadataFactoryInterface $metadataFactory,
ParametersFactoryInterface $parametersFactory,
RelationUrlGenerator $relationUrlGenerator,
PropertyAccessorInterface $propertyAccessor,
$forceAbsolute = true
) {
parent::__construct($relationUrlGenerator, $forceAbsolute);

$this->propertyAccessor = $propertyAccessor;
$this->metadataFactory = $metadataFactory;
$this->parametersFactory = $parametersFactory;
}
Expand All @@ -43,13 +49,39 @@ public function createLinksFromMetadata(ClassMetadataInterface $classMetadata, $
{
$links = array();

/**
* @var RelationMetadataInterface $relationMetadata
*/
foreach ($classMetadata->getRelations() as $relationMetadata) {
$links[] = $this->createLinkFromMetadata($relationMetadata, $object);
if (!$this->shouldExcludeLink($relationMetadata, $object) &&
$link = $this->createLinkFromMetadata($relationMetadata, $object)
) {
$links[] = $link;
}
}

return $links;
}

protected function shouldExcludeLink(RelationMetadataInterface $relationMetadata, $object)
{
if (!$fields = $relationMetadata->getExcludeIf()) {
return false;
}

foreach ($fields as $field => $valueToExclude) {
$field = trim($field, '.');
$propertyPath = new PropertyPath($field);
$value = $this->propertyAccessor->getValue($object, $propertyPath);

if ($valueToExclude === $value) {
return true;
}
}

return false;
}

public function createLinkFromMetadata(RelationMetadataInterface $relationMetadata, $object)
{
if (null !== $relationMetadata->getUrl()) {
Expand Down
6 changes: 5 additions & 1 deletion Metadata/Builder/RelationsBuilder.php
Expand Up @@ -17,7 +17,7 @@ public function __construct()
$this->relationsMetadata = array();
}

public function add($rel, $href, array $embed = null, array $attributes = null)
public function add($rel, $href, array $embed = null, array $attributes = null, array $excludeIf = null)
{
$relationMetadata = new RelationMetadata($rel);

Expand Down Expand Up @@ -86,6 +86,10 @@ public function add($rel, $href, array $embed = null, array $attributes = null)
$relationMetadata->setAttributes($attributes);
}

if (null !== $excludeIf) {
$relationMetadata->setExcludeIf($excludeIf);
}

$this->relationsMetadata[] = $relationMetadata;
}

Expand Down
2 changes: 1 addition & 1 deletion Metadata/Builder/RelationsBuilderInterface.php
Expand Up @@ -4,6 +4,6 @@

interface RelationsBuilderInterface
{
public function add($rel, $href, array $embed = null, array $attributes = null);
public function add($rel, $href, array $embed = null, array $attributes = null, array $excludeIf = null);
public function build();
}
4 changes: 4 additions & 0 deletions Metadata/Driver/AnnotationDriver.php
Expand Up @@ -37,6 +37,10 @@ public function loadMetadataForClass(\ReflectionClass $class)
if ($annotation instanceof Annotation\Relation) {
$relationMetadata = new RelationMetadata($annotation->rel);

if (null !== $annotation->excludeIf) {
$relationMetadata->setExcludeIf($annotation->excludeIf);
}

if ($annotation->href instanceof Annotation\Route) {
$relationMetadata->setRoute($annotation->href->value);
if (!empty($annotation->href->parameters)) {
Expand Down
4 changes: 4 additions & 0 deletions Metadata/Driver/YamlDriver.php
Expand Up @@ -50,6 +50,10 @@ protected function loadMetadataFromFile(\ReflectionClass $class, $file)
$relationMetadata->setAttributes($relation['attributes']);
}

if (!empty($relation['exclude_if'])) {
$relationMetadata->setExcludeIf($relation['exclude_if']);
}

if (!empty($relation['content'])) {
$relationContent = $relation['content'];

Expand Down
18 changes: 18 additions & 0 deletions Metadata/RelationMetadata.php
Expand Up @@ -11,11 +11,13 @@ class RelationMetadata implements RelationMetadataInterface
private $content;
private $options;
private $attributes;
private $excludeIf;

public function __construct($rel)
{
$this->rel = $rel;
$this->params = array();
$this->excludeIf = array();
}

public function setParams($params)
Expand Down Expand Up @@ -114,4 +116,20 @@ public function setAttributes($attributes)
{
$this->attributes = $attributes;
}

/**
* @param array $excludeIf
*/
public function setExcludeIf(array $excludeIf)
{
$this->excludeIf = (array) $excludeIf;
}

/**
* {@inheritdoc}
*/
public function getExcludeIf()
{
return $this->excludeIf;
}
}
6 changes: 6 additions & 0 deletions Metadata/RelationMetadataInterface.php
Expand Up @@ -38,4 +38,10 @@ public function getOptions();
* @return array|null
*/
public function getAttributes();

/**
* @return array
*/
public function getExcludeIf();

}
29 changes: 29 additions & 0 deletions README.md
Expand Up @@ -666,3 +666,32 @@ class User

}
```

## Conditionally excluding links

You can add conditions on relations that will determine whether the link should be excluded or not. For example:

```php
/**
* @Rest\Relation(
* "parent",
* href = @Rest\Route("api_post_get", parameters = { "id" = ".parent.id"}),
* excludeIf = { ".parent" = null }
* )
*/
class Post
{
}
```

This will not include the `parent` link if the value of `parent` is `null`. This can also be done through the YAML configuration:

```yml
relations:
- rel: parent
href:
route: api_post_get
parameters: { id: .parent.id }
exclude_if:
".parent": ~
```
1 change: 1 addition & 0 deletions Resources/config/services.yml
Expand Up @@ -31,6 +31,7 @@ services:
- @fsc_hateoas.metadata.factory
- @fsc_hateoas.factory.parameters
- @fsc_hateoas.routing.relation_url_generator
- @fsc_hateoas.property_accessor

fsc_hateoas.factory.parameters:
class: %fsc_hateoas.factory.parameters.class%
Expand Down
7 changes: 6 additions & 1 deletion Serializer/Handler/HalPagerfantaHandler.php
Expand Up @@ -71,7 +71,12 @@ public function serializeToArray(GenericSerializationVisitor $visitor, HalPagerf
$data[$this->relationsJsonKey] = $relations;
}

$resultsType = isset($type['params'][0]) ? $type['params'][0] : null;
$resultsType = array(
'name' => 'array',
);
if (isset($type['params'])) {
$resultsType['params'] = $type['params'];
}
$data[$this->relationsJsonKey][$halPager->getRel()] = $visitor->getNavigator()->accept($pager->getCurrentPageResults(), $resultsType, $context);

if ($shouldSetRoot) {
Expand Down
7 changes: 6 additions & 1 deletion Serializer/Handler/PagerfantaHandler.php
Expand Up @@ -101,7 +101,12 @@ public function serializeToXML(XmlSerializationVisitor $visitor, Pagerfanta $pag

public function serializeToArray(GenericSerializationVisitor $visitor, Pagerfanta $pager, array $type, Context $context)
{
$resultsType = isset($type['params'][0]) ? $type['params'][0] : null;
$resultsType = array(
'name' => 'array',
);
if (isset($type['params'])) {
$resultsType['params'] = $type['params'];
}

$shouldSetRoot = null === $visitor->getRoot();

Expand Down
44 changes: 42 additions & 2 deletions Tests/Factory/LinkFactoryTest.php
Expand Up @@ -26,7 +26,9 @@ public function testCreateLinksFromMetadata()
$relationUrlGenerator = new \FSC\HateoasBundle\Routing\RelationUrlGenerator($metadataFactory, $parametersFactory);
$relationUrlGenerator->setUrlGenerator('default', $FSCUrlGenerator);

$linkFactory = new LinkFactory($metadataFactory, $parametersFactory, $relationUrlGenerator);
$propertyAccessor = new \Symfony\Component\PropertyAccess\PropertyAccessor();

$linkFactory = new LinkFactory($metadataFactory, $parametersFactory, $relationUrlGenerator, $propertyAccessor);

$object = (object) array('id' => $id = 3);

Expand Down Expand Up @@ -67,7 +69,9 @@ public function testCreateLinksWithSameRelFromMetadata()
$relationUrlGenerator = new \FSC\HateoasBundle\Routing\RelationUrlGenerator($metadataFactory, $parametersFactory);
$relationUrlGenerator->setUrlGenerator('default', $FSCUrlGenerator);

$linkFactory = new LinkFactory($metadataFactory, $parametersFactory, $relationUrlGenerator);
$propertyAccessor = new \Symfony\Component\PropertyAccess\PropertyAccessor();

$linkFactory = new LinkFactory($metadataFactory, $parametersFactory, $relationUrlGenerator, $propertyAccessor);

$object = (object) array('id' => $id = 3);

Expand Down Expand Up @@ -107,4 +111,40 @@ public function testLinkAttributes()
$this->assertEquals($href, $link->getHref());
$this->assertEquals($attributes, $link->getAttributes());
}

public function testLinkNotCreatedWhenShouldBeExcluded()
{
$urlGenerator = $this->getMock('Symfony\Component\Routing\Generator\UrlGeneratorInterface');
$metadataFactory = $this->getMock('FSC\HateoasBundle\Metadata\MetadataFactoryInterface');
$parametersFactory = new \FSC\HateoasBundle\Factory\ParametersFactory(new PropertyAccessor());

$FSCUrlGenerator = new \FSC\HateoasBundle\Routing\UrlGenerator($urlGenerator);

$relationUrlGenerator = new \FSC\HateoasBundle\Routing\RelationUrlGenerator($metadataFactory, $parametersFactory);
$relationUrlGenerator->setUrlGenerator('default', $FSCUrlGenerator);

$propertyAccessor = new \Symfony\Component\PropertyAccess\PropertyAccessor();

$linkFactory = new LinkFactory($metadataFactory, $parametersFactory, $relationUrlGenerator, $propertyAccessor);

$object = (object) array('id' => $id = 3, 'parent' => null);

$relationMetadata = $this->getMock('FSC\HateoasBundle\Metadata\RelationMetadataInterface');
$relationMetadata->expects($this->any())->method('getRel')->will($this->returnValue($rel = 'self'));
$relationMetadata->expects($this->any())->method('getRoute')->will($this->returnValue($route = 'bar'));
$relationMetadata->expects($this->any())->method('getParams')->will($this->returnValue(array('identifier' => '.id')));
$relationMetadata->expects($this->any())->method('getExcludeIf')->will($this->returnValue(array('.parent' => null)));

$classMetadata = $this->getMock('FSC\HateoasBundle\Metadata\ClassMetadataInterface');
$classMetadata->expects($this->once())->method('getRelations')->will($this->returnValue(array($relationMetadata, $relationMetadata)));

$urlGenerator
->expects($this->never())
->method('generate')
;

$links = $linkFactory->createLinksFromMetadata($classMetadata, $object);

$this->assertEquals(0, count($links));
}
}
1 change: 1 addition & 0 deletions Tests/Fixtures/User.php
Expand Up @@ -34,6 +34,7 @@
* embed = @Rest\Content(property = ".property")
* )
* @Rest\Relation("templated", href = @Rest\Route("homepage"), attributes = { "isTemplated" = true })
* @Rest\Relation("excluded", href = @Rest\Route("homepage"), excludeIf = { ".parent" = null } )
*/
class User
{
Expand Down
41 changes: 41 additions & 0 deletions Tests/Functional/ControllerTest.php
Expand Up @@ -461,4 +461,45 @@ public function testListPostsJson()

$this->assertEquals(200, $response->getStatusCode());
}

public function testExcludingLinksConditionTrue()
{
$client = $this->createClient();
$client->request('GET', '/api/posts/2/exclude?_format=xml');

$response = $client->getResponse(); /** */

$this->assertEquals(200, $response->getStatusCode());

$this->assertEquals(<<<XML
<?xml version="1.0" encoding="UTF-8"?>
<post id="2">
<title><![CDATA[How to create awesome symfony2 application]]></title>
<link rel="self" href="http://localhost/api/posts/2"/>
</post>
XML
, $response->getContent());
}

public function testExcludingLinksConditionFalse()
{
$client = $this->createClient();
$client->request('GET', '/api/posts/1/exclude?_format=xml');

$response = $client->getResponse(); /** */

$this->assertEquals(200, $response->getStatusCode());

$this->assertEquals(<<<XML
<?xml version="1.0" encoding="UTF-8"?>
<post id="1">
<title><![CDATA[Welcome on the blog!]]></title>
<link rel="self" href="http://localhost/api/posts/1"/>
<link rel="parent" href="http://localhost/api/posts/2"/>
</post>
XML
, $response->getContent());
}
}
7 changes: 7 additions & 0 deletions Tests/Functional/TestBundle/Controller/PostController.php
Expand Up @@ -69,4 +69,11 @@ public function getPostTemplatedAction(Request $request, $id)

return new Response($this->get('serializer')->serialize($post, $request->get('_format')));
}

public function getPostExcludeAction(Request $request, $id)
{
$post = $this->get('test.provider.post')->getPostExcluded($id);

return new Response($this->get('serializer')->serialize($post, $request->get('_format')));
}
}

0 comments on commit 075f484

Please sign in to comment.