Skip to content

Commit

Permalink
Merge 2368b97 into e4ea55d
Browse files Browse the repository at this point in the history
  • Loading branch information
bakura10 committed Feb 17, 2014
2 parents e4ea55d + 2368b97 commit 213fb3a
Show file tree
Hide file tree
Showing 14 changed files with 179 additions and 54 deletions.
3 changes: 2 additions & 1 deletion config/module.config.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@

'controller_plugins' => [
'invokables' => [
'paginatorWrapper' => 'ZfrRest\Mvc\Controller\Plugin\PaginatorWrapper'
'paginatorWrapper' => 'ZfrRest\Mvc\Controller\Plugin\PaginatorWrapper',
'resourceModel' => 'ZfrRest\Mvc\Controller\Plugin\ResourceModel'
]
],

Expand Down
47 changes: 26 additions & 21 deletions docs/02. Quick Start.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,23 +212,23 @@ class UserController extends AbstractRestfulController
*/
protected $userService;

public function delete(User $user, ResourceMetadataInterface $metadata)
public function delete(User $user)
{
$this->userService->delete($user);
}

public function put(User $user, ResourceMetadataInterface $metadata)
public function put(User $user)
{
$this->userService->update($user);

return $user;
return $this->resourceModel($user);
}

public function get(User $user, ResourceMetadataInterface $metadata)
public function get(User $user)
{
// Do things if you want

return $user;
return $this->resourceModel($user);
}
}
```
Expand All @@ -239,16 +239,17 @@ you from using a normal ZF2 controller for some actions).
As you can guess, each "action" is named after the corresponding HTTP method. ZfrRest is flexible enough that you can
add custom HTTP verbs by adding specific method handlers and more public methods in your controller.

The interesting thing is that, contrary to traditional ZF2 controllers, you receive parameters in each method: the
actual resource (in this case, a User) as well as the resource metadata for the resource that has been matched by
the router. You have nothing to do, this is done automatically by ZfrRest, so that your controller stays really clean.
The only thing you need to do is **passing the resource to the service for your business logic**.
The interesting thing is that, contrary to traditional ZF2 controllers, you receive parameter in each method: the
actual resource data (in this case, a User) for the resource that have been matched by the router. You have nothing
to do, this is done automatically by ZfrRest, so that your controller stays really clean. The only thing you need
to do is **passing the resource to the service for your business logic**.

For the `put` method, you don't even need to manually validate the user data, because it has already been validated
using the input filter you specified in the mapping. If data would have been invalid, it would have returned a
400 Bad Request error, with the various error messages under the `errors` key.

If you want automatic serialization of the resource, you must return a ResourceModel (it needs a resource).
If you want automatic serialization of the resource, you must return a ResourceModel (it needs a resource). ZfrRest
provides you a utility controller plugin called `resourceModel` that you can use to create one.

Now, let's see the `Application\Controller\UsersController` controller:

Expand All @@ -258,7 +259,7 @@ Now, let's see the `Application\Controller\UsersController` controller:
namespace Application\Controller;

use Application\Entity\User;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Selectable;
use DoctrineModule\Paginator\Adapter\Collection as CollectionAdapter;
use Zend\Paginator\Paginator;
use ZfrRest\Mvc\Controller\AbstractRestfulController;
Expand All @@ -270,35 +271,39 @@ class UsersController extends AbstractRestfulController
*/
protected $userService;

public function post(User $user, ResourceMetadataInterface $metadata)
public function post(User $user)
{
$this->userService->create($user);

return new ResourceModel(new Resource($user, $metadata));
return $this->resourceModel($user);
}

public function get(Collection $users, ResourceMetadataInterface $metadata)
public function get(Selectable $users)
{
// We can do filtering here on the collection, for example using the Doctrine API criteria

// We could also wrap the collection around a paginator
$paginator = $this->paginatorWrapper($users);
$paginator->setCurrentPageNumber($this->params()->fromQuery('page', 1));

return new ResourceModel(new Resource($user, $metadata));
return $this->resourceModel($paginator);
}
}
```

For the `post` method, it is exactly the same as the `put` method: you don't need to validate anything, because
it has already been done for you. Just pass the entity to your service and you're done!

For the `get` method, we receive a Collection. Actually, if you are using Doctrine ORM, you can typehint to
For the `get` method, we receive a Selectable. Actually, if you are using Doctrine ORM, you can typehint to
`Doctrine\Common\Collections\Selectable`, which is a much more useful interface because it allows to do efficient
filtering.

As you can see from the example, we can also wrap the data around a Zend paginator, and directly return the
paginator. The `paginatorWrapper` is a simple controller plugin that encapsulate the logic of paginator creation.
> Most of the time, an object that implements the `Selectable` interface also implement `Collection` interface. However,
the `Selectable` interface is much more flexible as it has powerful filtering capabilities. That's why you should
always typehint to `Selectable` if you are using Doctrine ORM or Doctrine ODM.

As you can see from the example, we can also wrap the data around a Zend paginator, and then wrap it around
a `ResourceModel`. The `paginatorWrapper` is a simple controller plugin that encapsulate the logic of paginator creation.
You can also optionally pass it a criteria to filter the collection (see the cookbook).

When you return a Paginator, ZfrRest will intelligently serialize the output. For instance, here is an example
Expand Down Expand Up @@ -468,14 +473,14 @@ defined a controller `Application\Controller\TweetsController`. We therefore nee

namespace Application\Controller;

use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Selectable;
use ZfrRest\Mvc\Controller\AbstractRestfulController;

class TweetsController extends AbstractRestfulController
{
public function get(Collection $tweets)
public function get(Selectable $tweets)
{
return $tweets;
return $this->resourceModel($tweets);
}
}
```
Expand Down
6 changes: 3 additions & 3 deletions docs/07. Cookbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public function get(Selectable $users)
$paginator = $this->paginatorWrapper($users);
$paginator->setCurrentPageNumber($this->params()->fromQuery('page', 1));

return $paginator;
return $this->resourceModel($users);
}
```

Expand All @@ -101,7 +101,7 @@ public function get(Selectable $users)
$paginator = $this->paginatorWrapper($users, $criteria);
$paginator->setCurrentPageNumber($this->params()->fromQuery('page', 1));

return $paginator;
return $this->resourceModel($users);
}
```

Expand All @@ -123,7 +123,7 @@ public function get(Selectable $users)
$paginator = $this->paginatorWrapper($users, $criteria);
$paginator->setCurrentPageNumber($this->params()->fromQuery('page', 1));

return $paginator;
return $this->resourceModel($users);
}
```

Expand Down
23 changes: 17 additions & 6 deletions src/ZfrRest/Mvc/Controller/AbstractRestfulController.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,26 @@

namespace ZfrRest\Mvc\Controller;

use Doctrine\Common\Collections\Collection;
use Zend\Http\Request as HttpRequest;
use Zend\Mvc\Controller\AbstractController;
use Zend\Mvc\MvcEvent;
use Zend\Paginator\Paginator;
use Zend\Stdlib\RequestInterface;
use Zend\Stdlib\ResponseInterface;
use ZfrRest\Http\Exception\Client\NotFoundException;
use ZfrRest\Mvc\Controller\MethodHandler\MethodHandlerPluginManager;
use ZfrRest\Mvc\Exception\RuntimeException;
use ZfrRest\Resource\ResourceInterface;
use ZfrRest\Resource\Metadata\ResourceMetadataInterface;
use ZfrRest\View\Model\ResourceModel;

/**
* @author Michaël Gallego <mic.gallego@gmail.com>
* @licence MIT
*
* @method \Zend\Paginator\Paginator paginatorWrapper(\Doctrine\Common\Collections\Collection $data, $criteria = [])
*
* @method Paginator paginatorWrapper(Collection $data, $criteria = [])
* @method ResourceModel resourceModel($data, ResourceMetadataInterface $metadata = null)
*/
class AbstractRestfulController extends AbstractController
{
Expand Down Expand Up @@ -64,11 +70,8 @@ public function onDispatch(MvcEvent $event)
$request = $this->getRequest();
$handler = $this->getMethodHandlerManager()->get($request->getMethod());

/* @var \ZfrRest\Resource\ResourceInterface $resource */
$resource = $event->getRouteMatch()->getParam('resource', null);

// We should always have a resource, otherwise throw an 404 exception
if (null === $resource) {
if (!$resource = $this->getMatchedResource()) {
throw new NotFoundException();
}

Expand All @@ -78,6 +81,14 @@ public function onDispatch(MvcEvent $event)
return $result;
}

/**
* @return ResourceInterface|null
*/
public function getMatchedResource()
{
return $this->getEvent()->getRouteMatch()->getParam('resource', null);
}

/**
* Get the method handler plugin manager
*
Expand Down
2 changes: 1 addition & 1 deletion src/ZfrRest/Mvc/Controller/MethodHandler/DeleteHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public function handleMethod(AbstractController $controller, ResourceInterface $
throw new MethodNotAllowedException();
}

$result = $controller->delete($resource->getData(), $resource->getMetadata());
$result = $controller->delete($resource->getData());

// According to http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.7, status code should
// be 204 if nothing is returned
Expand Down
2 changes: 1 addition & 1 deletion src/ZfrRest/Mvc/Controller/MethodHandler/GetHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,6 @@ public function handleMethod(AbstractController $controller, ResourceInterface $
throw new MethodNotAllowedException();
}

return $controller->get($resource->getData(), $resource->getMetadata());
return $controller->get($resource->getData());
}
}
2 changes: 1 addition & 1 deletion src/ZfrRest/Mvc/Controller/MethodHandler/PostHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public function handleMethod(AbstractController $controller, ResourceInterface $
$data = $this->validateData($singleResource, $data);
$data = $this->hydrateData($singleResource, $data);

$result = $controller->post($data, $singleResource->getMetadata());
$result = $controller->post($data);

// Set the Location header with the URL of the newly created resource
if ($result instanceof ResourceModel) {
Expand Down
2 changes: 1 addition & 1 deletion src/ZfrRest/Mvc/Controller/MethodHandler/PutHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public function handleMethod(AbstractController $controller, ResourceInterface $
$data = $this->validateData($resource, $data);
$data = $this->hydrateData($resource, $data);

return $controller->put($data, $resource->getMetadata());
return $controller->put($data);
}

/**
Expand Down
60 changes: 60 additions & 0 deletions src/ZfrRest/Mvc/Controller/Plugin/ResourceModel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license.
*/

namespace ZfrRest\Mvc\Controller\Plugin;

use Zend\Mvc\Controller\Plugin\AbstractPlugin;
use ZfrRest\Mvc\Controller\AbstractRestfulController;
use ZfrRest\Mvc\Exception\RuntimeException;
use ZfrRest\Resource\Metadata\ResourceMetadataInterface;
use ZfrRest\Resource\Resource;
use ZfrRest\View\Model\ResourceModel as ResourceViewModel;

/**
* Controller plugin that allows to create a resource model quickly.
*
* @author Michaël Gallego <mic.gallego@gmail.com>
* @licence MIT
*/
class ResourceModel extends AbstractPlugin
{
/**
* Create a resource model from the data
*
* If no resource metadata interface is passed, it will fetched it from matched resource of
* the controller (which is what you want 99% of the time).
*
* @param mixed $data
* @param ResourceMetadataInterface|null $resourceMetadata
* @return ResourceViewModel
* @throws RuntimeException
*/
public function __invoke($data, ResourceMetadataInterface $resourceMetadata = null)
{
if (!$this->controller instanceof AbstractRestfulController) {
throw new RuntimeException(
'You tried to use the ResourceModel controller plugin on a controller instance that does
not extend "ZfrRest\Mvc\Controller\AbstractRestfulController"'
);
}

$resourceMetadata = $resourceMetadata ?: $this->controller->getMatchedResource()->getMetadata();

return new ResourceViewModel(new Resource($data, $resourceMetadata));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public function testThrowNotFoundExceptionIfNoResourceIsMatched()
$event->setRouteMatch($routeMatch);

$controller = new AbstractRestfulController();
$controller->setEvent($event);

$serviceLocator = $this->getMock('Zend\ServiceManager\ServiceLocatorInterface');
$pluginManager = $this->getMock('Zend\ServiceManager\ServiceLocatorInterface');
Expand Down Expand Up @@ -76,6 +77,7 @@ public function testCanSetResultIfResource()
$pluginManager = $this->getMock('Zend\ServiceManager\ServiceLocatorInterface');

$controller->setServiceLocator($serviceLocator);
$controller->setEvent($event);

$serviceLocator->expects($this->once())
->method('get')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,9 @@ public function testCanReturnData()
->method('getData')
->will($this->returnValue($data));

$metadata = $this->getMock('ZfrRest\Resource\Metadata\ResourceMetadataInterface');
$resource->expects($this->once())
->method('getMetadata')
->will($this->returnValue($metadata));

$controller->expects($this->once())
->method('delete')
->with($data, $metadata)
->with($data)
->will($this->returnValue(['foo' => 'bar']));

$controller->expects($this->never())
Expand All @@ -84,14 +79,9 @@ public function testSetProperStatusCodeIfNothingIsReturnedFromDeleteMethod()
->method('getData')
->will($this->returnValue($data));

$metadata = $this->getMock('ZfrRest\Resource\Metadata\ResourceMetadataInterface');
$resource->expects($this->once())
->method('getMetadata')
->will($this->returnValue($metadata));

$controller->expects($this->once())
->method('delete')
->with($data, $metadata)
->with($data)
->will($this->returnValue(null));

$response = new HttpResponse();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,9 @@ public function testCanReturnData()
->method('getData')
->will($this->returnValue($data));

$metadata = $this->getMock('ZfrRest\Resource\Metadata\ResourceMetadataInterface');
$resource->expects($this->once())
->method('getMetadata')
->will($this->returnValue($metadata));

$controller->expects($this->once())
->method('get')
->with($data, $metadata)
->with($data)
->will($this->returnValue(['foo' => 'bar']));

$controller->expects($this->never())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
* @group Coverage
* @covers \ZfrRest\Mvc\Controller\Plugin\PaginatorWrapper
*/
class AbstractRestfulControllerTest extends PHPUnit_Framework_TestCase
class PaginatorWrapperTest extends PHPUnit_Framework_TestCase
{
public function testCanCreatePaginatorFromCollection()
{
Expand Down
Loading

0 comments on commit 213fb3a

Please sign in to comment.