Skip to content
This repository has been archived by the owner on Sep 16, 2021. It is now read-only.

move, rename delete resource of editable repository #36

Merged
merged 3 commits into from
Jun 26, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ php:
- 5.6
- 7.0
- hhvm

sudo: false

cache:
Expand Down
122 changes: 115 additions & 7 deletions Controller/ResourceController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@

namespace Symfony\Cmf\Bundle\ResourceRestBundle\Controller;

use Puli\Repository\Api\EditableRepository;
use Puli\Repository\Api\ResourceRepository;
use Symfony\Cmf\Component\Resource\RepositoryRegistryInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use JMS\Serializer\SerializerInterface;
use JMS\Serializer\SerializationContext;
use Symfony\Component\Routing\Exception\RouteNotFoundException;

class ResourceController
{
Expand All @@ -29,21 +33,125 @@ class ResourceController
private $serializer;

/**
* @param RepositoryInterface
* @param SerializerInterface $serializer
* @param RepositoryRegistryInterface $registry
*/
public function __construct(
SerializerInterface $serializer,
RepositoryRegistryInterface $registry
) {
public function __construct(SerializerInterface $serializer, RepositoryRegistryInterface $registry)
{
$this->serializer = $serializer;
$this->registry = $registry;
}

public function resourceAction($repositoryName, $path)
/**
* Provides resource information.
*
* @param string $repositoryName
* @param string $path
*/
public function getResourceAction($repositoryName, $path)
{
$repository = $this->registry->get($repositoryName);
$resource = $repository->get('/'.$path);

return $this->createResponse($resource);
}

/**
* Changes the current resource.
*
* The request body should contain a JSON list of operations
* like:
*
* [{"operation": "move", "target": "/cms/new/id"}]
*
* Currently supported operations:
*
* - move (options: target)
*
* changing payload properties isn't supported yet.
*
* @param string $repositoryName
* @param string $path
* @param Request $request
*
* @return Response
*/
public function patchResourceAction($repositoryName, $path, Request $request)
{
$repository = $this->registry->get($repositoryName);
$this->failOnNotEditable($repository, $repositoryName);

$path = '/'.ltrim($path, '/');

$requestContent = json_decode($request->getContent(), true);
if (!$requestContent) {
return $this->badRequestResponse('Only JSON request bodies are supported.');
}

foreach ($requestContent as $action) {
if (!isset($action['operation'])) {
return $this->badRequestResponse('Malformed request body. It should contain a list of operations.');
}

switch ($action['operation']) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we really need that operation? Is there no chance to change the path of the resource information or the target path of the request to use that as an information? to have some kind of operation key in the body is not very RESTful.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you read http://williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot/ ?

You can use whatever format you want as [description of changes], as far as its semantics is well-defined. That is why using PATCH to send updated values only is not suitable.

RFC 6902 defines a JSON document structure for expressing a sequence of operations to apply to a JSON document, suitable for use with the PATCH method. Here is how it looks like:

[
    { "op": "test", "path": "/a/b/c", "value": "foo" },
    { "op": "remove", "path": "/a/b/c" },
    { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] },
    { "op": "replace", "path": "/a/b/c", "value": 42 },
    { "op": "move", "from": "/a/b/c", "path": "/a/b/d" },
    { "op": "copy", "from": "/a/b/d", "path": "/a/b/e" }
]

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK i understand it now.

case 'move':
$targetPath = $action['target'];
$repository->move($path, $targetPath);

break;
default:
return $this->badRequestResponse(sprintf('Only operation "%s" is not supported, supported operations: move.', $action['operation']));
}
}

return $this->createResponse('', Response::HTTP_NO_CONTENT);
}

/**
* Deletes the resource.
*
* @param string $repositoryName
* @param string $path
*
* @return Response
*/
public function deleteResourceAction($repositoryName, $path)
{
$repository = $this->registry->get($repositoryName);
$this->failOnNotEditable($repository, $repositoryName);

$path = '/'.ltrim($path, '/');

$repository->remove($path);

return $this->createResponse('', Response::HTTP_NO_CONTENT);
}

private function failOnNotEditable(ResourceRepository $repository, $repositoryName)
{
if (!$repository instanceof EditableRepository) {
throw new RouteNotFoundException(sprintf('Repository "%s" is not editable.', $repositoryName));
}
}

/**
* @param string $message
*
* @return Response
*/
private function badRequestResponse($message)
{
return $this->createResponse(['message' => $message], Response::HTTP_BAD_REQUEST);
}

/**
* @param mixed $resource
* @param int $httpStatusCode
*
* @return Response
*/
private function createResponse($resource, $httpStatusCode = Response::HTTP_OK)
{
$context = SerializationContext::create();
$context->enableMaxDepthChecks();
$context->setSerializeNull(true);
Expand All @@ -53,7 +161,7 @@ public function resourceAction($repositoryName, $path)
$context
);

$response = new Response($json);
$response = new Response($json, $httpStatusCode);
$response->headers->set('Content-Type', 'application/json');

return $response;
Expand Down
5 changes: 2 additions & 3 deletions Enhancer/EnhancerInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

namespace Symfony\Cmf\Bundle\ResourceRestBundle\Enhancer;

use JMS\Serializer\Context;
use Puli\Repository\Api\Resource\PuliResource;

/**
Expand All @@ -28,8 +27,8 @@ interface EnhancerInterface
*
* $context->addData('foobar', 'Some value');
*
* @param Context Serialization context
* @param resource The resource being serialized
* @param array $data Context Serialization context
* @param PuliResource $resource The resource being serialized
*/
public function enhance(array $data, PuliResource $resource);
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Symfony CMF Resource REST API Bundle

[![Build Status](https://secure.travis-ci.org/symfony-cmf/ResourceRestBundle.png?branch=master)](http://travis-ci.org/symfony-cmf/ResourceRestBundle)
[![Build Status](https://travis-ci.org/symfony-cmf/resource-rest-bundle.svg?branch=master)](https://travis-ci.org/symfony-cmf/resource-rest-bundle)
[![StyleCI](https://styleci.io/repos/29090266/shield)](https://styleci.io/repos/29090266)
[![Latest Stable Version](https://poser.pugx.org/symfony-cmf/resource-rest-bundle/version.png)](https://packagist.org/packages/symfony-cmf/resource-rest-bundle)
[![Total Downloads](https://poser.pugx.org/symfony-cmf/resource-rest-bundle/d/total.png)](https://packagist.org/packages/symfony-cmf/resource-rest-bundle)
Expand Down
9 changes: 4 additions & 5 deletions Registry/EnhancerRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Symfony\Cmf\Bundle\ResourceRestBundle\Registry;

use Symfony\Cmf\Bundle\ResourceRestBundle\Enhancer\EnhancerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
Expand Down Expand Up @@ -40,11 +41,8 @@ class EnhancerRegistry
* @param array $enhancerMap Map of enhancer aliases to repository names
* @param array $aliasMap Serice ID map for enhancer aliases
*/
public function __construct(
ContainerInterface $container,
$enhancerMap = array(),
$aliasMap = array()
) {
public function __construct(ContainerInterface $container, $enhancerMap = array(), $aliasMap = array())
{
$this->container = $container;
$this->enhancerMap = $enhancerMap;
$this->aliasMap = $aliasMap;
Expand All @@ -65,6 +63,7 @@ public function getEnhancers($repositoryAlias)
}

$aliases = $this->enhancerMap[$repositoryAlias];
$enhancers = [];

foreach ($aliases as $alias) {
if (!isset($this->aliasMap[$alias])) {
Expand Down
10 changes: 4 additions & 6 deletions Registry/PayloadAliasRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@

namespace Symfony\Cmf\Bundle\ResourceRestBundle\Registry;

use Puli\Repository\Api\Resource\PuliResource;
use Symfony\Cmf\Component\Resource\RepositoryRegistryInterface;
use Symfony\Cmf\Component\Resource\Repository\Resource\CmfResource;
use Puli\Repository\Api\Resource\PuliResource;

/**
* Registry for resource payload aliases.
Expand All @@ -36,10 +36,8 @@ class PayloadAliasRegistry
* @param RepositoryRegistryInterface $repositoryRegistry
* @param array $aliases
*/
public function __construct(
RepositoryRegistryInterface $repositoryRegistry,
array $aliases = array()
) {
public function __construct(RepositoryRegistryInterface $repositoryRegistry, array $aliases = array())
{
$this->repositoryRegistry = $repositoryRegistry;

foreach ($aliases as $alias => $config) {
Expand All @@ -54,7 +52,7 @@ public function __construct(
/**
* Return the alias for the given PHPCR resource.
*
* @param resource $resource
* @param PuliResource $resource
*
* @return string
*/
Expand Down
22 changes: 19 additions & 3 deletions Resources/config/routing.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
_cmf_resource:
_cmf_delete_resource:
path: /api/{repositoryName}/{path}
methods: ['delete']
requirements:
path: .*
defaults:
_controller: cmf_resource_rest.controller.resource:resourceAction
_format: json
_controller: cmf_resource_rest.controller.resource:deleteResourceAction

_cmf_patch_resource:
path: /api/{repositoryName}/{path}
methods: ['patch']
requirements:
path: .*
defaults:
_controller: cmf_resource_rest.controller.resource:patchResourceAction

_cmf_get_resource:
path: /api/{repositoryName}/{path}
methods: ['get']
requirements:
path: .*
defaults:
_controller: cmf_resource_rest.controller.resource:getResourceAction
1 change: 0 additions & 1 deletion Serializer/Jms/EventSubscriber/ResourceSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
use JMS\Serializer\EventDispatcher\EventSubscriberInterface;
use JMS\Serializer\EventDispatcher\Events;
use JMS\Serializer\EventDispatcher\PreSerializeEvent;
use Puli\Repository\Api\ResourceCollection;
use Puli\Repository\Api\Resource\PuliResource;

/**
Expand Down
2 changes: 1 addition & 1 deletion Serializer/Jms/Handler/ResourceHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
use PHPCR\NodeInterface;
use PHPCR\Util\PathHelper;
use Puli\Repository\Api\Resource\BodyResource;
use Puli\Repository\Api\Resource\PuliResource;
use Symfony\Cmf\Component\Resource\RepositoryRegistryInterface;
use Symfony\Cmf\Bundle\ResourceRestBundle\Registry\PayloadAliasRegistry;
use Symfony\Cmf\Bundle\ResourceRestBundle\Registry\EnhancerRegistry;
use Symfony\Cmf\Component\Resource\Repository\Resource\CmfResource;
use Puli\Repository\Api\Resource\PuliResource;

/**
* Handle PHPCR resource serialization.
Expand Down
64 changes: 63 additions & 1 deletion Tests/Features/Context/ResourceContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder;
use Webmozart\Assert\Assert;

class ResourceContext implements Context, KernelAwareContext
{
Expand Down Expand Up @@ -72,6 +73,14 @@ public function beforeScenario(BeforeScenarioScope $scope)
}
}

/**
* @AfterScenario
*/
public function refreshSession()
{
$this->session->refresh(true);
}

/**
* @Given the test application has the following configuration:
*/
Expand All @@ -93,7 +102,7 @@ public function createFile($filename, PyStringNode $content)
}

/**
* @Given there exists a :class document at :path:
* @Given there exists a/an :class document at :path:
*/
public function createDocument($class, $path, TableNode $fields)
{
Expand Down Expand Up @@ -122,6 +131,59 @@ public function createDocument($class, $path, TableNode $fields)

$this->manager->persist($document);
$this->manager->flush();
$this->manager->clear();
}

/**
* @Then there is a/an :class document at :path
* @Then there is a/an :class document at :path:
*/
public function thereIsADocumentAt($class, $path, TableNode $fields = null)
{
$class = 'Symfony\\Cmf\\Bundle\\ResourceRestBundle\\Tests\\Resources\\TestBundle\\Document\\'.$class;
$path = '/tests'.$path;

if (!class_exists($class)) {
throw new \InvalidArgumentException(sprintf(
'Class "%s" does not exist',
$class
));
}

$document = $this->manager->find($class, $path);

Assert::notNull($document, sprintf('No "%s" document exists at "%s"', $class, $path));

if (null === $fields) {
return;
}

foreach ($fields->getRowsHash() as $field => $value) {
Assert::eq($document->$field, $value);
}
}

/**
* @Then there is no :class document at :path
*/
public function thereIsNoDocumentAt($class, $path)
{
$class = 'Symfony\\Cmf\\Bundle\\ResourceRestBundle\\Tests\\Resources\\TestBundle\\Document\\'.$class;
$path = '/tests'.$path;

if (!class_exists($class)) {
throw new \InvalidArgumentException(sprintf(
'Class "%s" does not exist',
$class
));
}

$this->session->refresh(true);
$this->manager->clear();

$document = $this->manager->find($class, $path);

Assert::null($document, sprintf('A "%s" document does exist at "%s".', $class, $path));
}

private function clearDiCache()
Expand Down
Loading