Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PoC integration with FSCHateoasBundle #327

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 28 additions & 0 deletions Controller/Annotations/Hateoas.php
@@ -0,0 +1,28 @@
<?php

/*
* This file is part of the FOSRestBundle package.
*
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace FOS\RestBundle\Controller\Annotations;

use Doctrine\Common\Annotations\Annotation;

/**
* Hateoas annotation class.
* @Annotation
*/
class Hateoas extends Annotation
{
/** @var string */
public $subject;
/** @var string */
public $identifier = 'id';
/** @var string */
public $relName;
}
48 changes: 48 additions & 0 deletions EventListener/ViewResponseListener.php
Expand Up @@ -12,6 +12,7 @@
namespace FOS\RestBundle\EventListener;

use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use FOS\RestBundle\Routing\HateoasCollectionInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Bundle\FrameworkBundle\Templating\TemplateReference;
Expand Down Expand Up @@ -125,6 +126,53 @@ public function onKernelView(GetResponseForControllerResultEvent $event)

$view->setTemplate($template);
}
} elseif ($this->container->has('fsc_hateoas.metadata.factory')) {
$data = $view->getData();
if (is_object($data)) {
$class = $data instanceof HateoasCollectionInterface
? $data->getSubject() : get_class($data);

$cacheDir = $this->container->getParameter('kernel.cache_dir');
$file = $cacheDir.'/fos_rest/hateoas/'.str_replace('\\', '', $class);
if (file_exists($file)) {
$collection = file_get_contents($file);
$collection = unserialize($collection);

$relationsBuilder = $this->container->get('fsc_hateoas.metadata.relation_builder.factory')->create();
$subject = strtolower($collection->getSingularName());
$baseParameters = array();
if ($collection->isFormatInRoute()
&& null !== $request->attributes->get('_format')
) {
$baseParameters['_format'] = $view->getFormat();
}
foreach ($collection as $routeName => $route) {
$relName = $route->getRelName();
if (!$relName) {
continue;
}

$relName = $routeName === $request->attributes->get('_route') ? 'self' : $relName;
$parameters = array(
'route' => $routeName,
'parameters' => $baseParameters,
);
foreach ($route->getPlaceholders() as $placeholder) {
if ($placeholder === $subject) {
$value = $data instanceof HateoasCollectionInterface
? '{'.$placeholder.'}' : $data->{$collection->getIdentifier()}()
;
} else {
$value = $request->attributes->get($placeholder);
}
$parameters['parameters'][$placeholder] = $value;
}
$relationsBuilder->add($relName, $parameters);
}

$this->container->get('fsc_hateoas.metadata.factory')->addObjectRelations($data, $relationsBuilder->build());
}
}
}

$response = $viewHandler->handle($view, $request);
Expand Down
27 changes: 27 additions & 0 deletions Routing/HateoasCollectionInterface.php
@@ -0,0 +1,27 @@
<?php

/*
* This file is part of the FOSRestBundle package.
*
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace FOS\RestBundle\Routing;

/**
* Hateoas
*
* @author Lukas Kahwe Smith <smith@pooteeweet.org>
*/
interface HateoasCollectionInterface
{
/**
* Get subject class
*
* @return string
*/
public function getSubject();
}
25 changes: 20 additions & 5 deletions Routing/Loader/Reader/RestActionReader.php
Expand Up @@ -17,6 +17,7 @@

use FOS\RestBundle\Util\Inflector\InflectorInterface;
use FOS\RestBundle\Routing\RestRouteCollection;
use FOS\RestBundle\Routing\RestRoute;
use FOS\RestBundle\Request\ParamReader;

/**
Expand Down Expand Up @@ -167,8 +168,18 @@ public function read(RestRouteCollection $collection, \ReflectionMethod $method,
$resources[] = null;
}

$hateoas = $this->readMethodAnnotation($method, 'Hateoas');
if ($hateoas && $hateoas->relName) {
$relName = $hateoas->relName;
} elseif ('get' === $httpMethod) {
$relName = empty($arguments) ? 'collection' : 'entity';
} else {
$relName = false;
}

$routeName = $httpMethod.$this->generateRouteName($resources);
$urlParts = $this->generateUrlParts($resources, $arguments, $httpMethod);
$placeholders = array();
$urlParts = $this->generateUrlParts($resources, $arguments, $httpMethod, $placeholders, $relName);

// if passed method is not valid HTTP method then it's either
// a hypertext driver, a custom object (PUT) or collection (GET)
Expand Down Expand Up @@ -208,9 +219,12 @@ public function read(RestRouteCollection $collection, \ReflectionMethod $method,
}

// add route to collection
$collection->add($routeName, new Route(
$pattern, $defaults, $requirements, $options
));
$route = new RestRoute($pattern, $defaults, $requirements, $options);
$route->setPlaceholders($placeholders);
if ($relName) {
$route->setRelName($relName);
}
$collection->add($routeName, $route);
}

/**
Expand Down Expand Up @@ -338,7 +352,7 @@ private function generateRouteName(array $resources)
*
* @return array
*/
private function generateUrlParts(array $resources, array $arguments, $httpMethod)
private function generateUrlParts(array $resources, array $arguments, $httpMethod, array &$placeholders)
{
$urlParts = array();
foreach ($resources as $i => $resource) {
Expand All @@ -358,6 +372,7 @@ private function generateUrlParts(array $resources, array $arguments, $httpMetho
} else {
$urlParts[] = '{'.$arguments[$i]->getName().'}';
}
$placeholders[] = $arguments[$i]->getName();
} elseif (null !== $resource) {
if ((0 === count($arguments) && !in_array($httpMethod, $this->availableHTTPMethods))
|| 'new' === $httpMethod
Expand Down
6 changes: 6 additions & 0 deletions Routing/Loader/Reader/RestControllerReader.php
Expand Up @@ -69,6 +69,12 @@ public function read(\ReflectionClass $reflection)
$this->actionReader->setNamePrefix($annotation->value);
}

// read hateoas annotation
if ($annotation = $this->readClassAnnotation($reflection, 'Hateoas')) {
$collection->setSubject($annotation->subject);
$collection->setIdentifier($annotation->identifier);
}

$resource = array();
// read route-resource annotation
if ($annotation = $this->readClassAnnotation($reflection, 'RouteResource')) {
Expand Down
11 changes: 11 additions & 0 deletions Routing/Loader/RestRouteLoader.php
Expand Up @@ -80,6 +80,17 @@ public function load($controller, $type = null)
$collection->prependRouteControllersWithPrefix($prefix);
$collection->setDefaultFormat($this->defaultFormat);

if ($collection->getSubject()) {
$cacheDir = $this->container->getParameter('kernel.cache_dir');
$dir = $cacheDir.'/fos_rest/hateoas';
if (!is_dir($dir) && false === $this->container->get('filesystem')->mkdir($dir)) {
throw new \RuntimeException(sprintf(
'Could not create hateoas cache directory %s', $dir
));
}
file_put_contents($dir.'/'.str_replace('\\', '', $collection->getSubject()), serialize($collection));
}

return $collection;
}

Expand Down
82 changes: 82 additions & 0 deletions Routing/RestRoute.php
@@ -0,0 +1,82 @@
<?php

/*
* This file is part of the FOSRestBundle package.
*
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace FOS\RestBundle\Routing;

use Symfony\Component\Routing\Route;

/**
* Restful route.
*
* @author Lukas Kahwe Smith <smith@pooteeweet.org>
*/
class RestRoute extends Route
{
private $placeholders;
private $relName;

/**
* Set argument names of the route.
*
* @param array $placeholders argument names
*/
public function setPlaceholders($placeholders)
{
$this->placeholders = $placeholders;
}

/**
* Returns argument names of the route.
*
* @return array
*/
public function getPlaceholders()
{
return $this->placeholders;
}

/**
* Returns rel name of the route.
*
* @return string
*/
public function getRelName()
{
return $this->relName;
}

/**
* Set rel name of the route.
*
* @param string $relName rel name
*/
public function setRelName($relName)
{
$this->relName = $relName;
}

public function serialize()
{
$data = parent::serialize();
$data = unserialize($data);
$data['placeholders'] = $this->placeholders;
$data['relName'] = $this->relName;
return serialize($data);
}

public function unserialize($data)
{
parent::unserialize($data);
$data = unserialize($data);
$this->placeholders = $data['placeholders'];
$this->relName = $data['relName'];
}
}
54 changes: 54 additions & 0 deletions Routing/RestRouteCollection.php
Expand Up @@ -21,6 +21,9 @@
class RestRouteCollection extends RouteCollection
{
private $singularName;
private $subject;
private $identifier;
private $isFormatInRoute = false;

/**
* Set collection singular name.
Expand All @@ -42,6 +45,56 @@ public function getSingularName()
return $this->singularName;
}

/**
* Set route subject class.
*
* @param string $subject route subject class
*/
public function setSubject($subject)
{
$this->subject = $subject;
}

/**
* Set route subject class.
*
* @return string
*/
public function getSubject()
{
return $this->subject;
}

/**
* Set subject identifier.
*
* @param string $identifier route identifier class
*/
public function setIdentifier($identifier)
{
$this->identifier = $identifier;
}

/**
* Set subject identifier.
*
* @return string
*/
public function getIdentifier()
{
return $this->identifier;
}

/**
* Check if the format should be set in the route
*
* @return Boolean
*/
public function isFormatInRoute()
{
return $this->isFormatInRoute;
}

/**
* Adds controller prefix to all collection routes.
*
Expand All @@ -61,6 +114,7 @@ public function prependRouteControllersWithPrefix($prefix)
*/
public function setDefaultFormat($format)
{
$this->isFormatInRoute = true;
foreach (parent::all() as $route) {
$route->setDefault('_format', $format);
}
Expand Down