Skip to content

Commit

Permalink
Add doc
Browse files Browse the repository at this point in the history
  • Loading branch information
bakura10 committed May 4, 2014
1 parent fa51553 commit 5fae48d
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 60 deletions.
179 changes: 120 additions & 59 deletions docs/04. Controllers.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,44 +79,6 @@ return [

Now, you can have a `funkyVerb` method in your REST controllers, and it will be interpreted by ZfrRest.

## Configuring controller behaviours

By default, ZfrRest does a lot of things automatically for you. For instance, when doing a `POST` or `PUT` request,
it automatically validates the data and create a new object using a hydrator, so that your controller directly
receive a ready-to-consume object.

However, you may want to keep control on those steps, and doing the validation and hydration yourself. To that extent,
you can override two properties in controllers, namely `autoValidate` and `autoHydrate`. For instance:

```php
use ZfrRest\Mvc\Controller\AbstractRestfulController;

class PasswordResetListController extends AbstractRestfulController
{
protected $autoHydrate = false;

public function post(array $data)
{
// For instance
$passwordReset = $this->passwordResetService->createFromEmail($data['email']);

return $this->resourceModel($passwordReset);
}
}
```

With this config, ZfrRest will still validate data using the input filter from the mapping, however it won't
hydrate data. As a consequence, instead of receiving an object in your action, you will receive an array. This
is very handy when data coming from POST request needs heavy customization and cannot be mapped directly to an
entity!

You can configure those settings per controller, which make things extremely flexible!

> NOTE: By default, both `$autoValidate` and `$autoHydrate` are set to true. While there are a lot of use cases
where setting `autoHydrate` to false makes sense, it is not recommended to set `autoValidate` to false, because
you will receive potentially malicious data. It's therefore up to you to manually validate and filter your data
if you turn off this option!

## Context resource

When dealing with requests such as POST `/users/1/tweets`, you will receive a Tweet instance as the parameter
Expand Down Expand Up @@ -164,67 +126,166 @@ but the `/users/4/tweets` will be dispatched to the `Tweet\Controller\UserTweetL
> As of now, you cannot override the controller on association for single resource. This means that `/users/2/tweets/4`
will be dispatched to the controller defined on the `Resource` mapping of the Tweet entity.

## Configuring input filters based on context
## Using controller events

ZfrRest offers a lot of events that you can use to override default behaviours of ZfrRest. Those events can be listened
in a any controller. Here is a full list of events:

* `ZfrRest\Mvc\Controller\Event\ValidationEvent::EVENT_VALIDATE_PRE`: this event is triggered before ZfrRest validates
any data. You can use this event to override the input filter (that is fetched from mapping, by default), to prevent
auto filtering...
* `ZfrRest\Mvc\Controller\Event\ValidationEvent::EVENT_VALIDATE_ERROR`: this event is triggered when an input filter
validation fails.
* `ZfrRest\Mvc\Controller\Event\ValidationEvent::EVENT_VALIDATE_SUCCESS`: this event is triggered when an input filter
validation is successful.
* `ZfrRest\Mvc\Controller\Event\HydrationEvent::EVENT_HYDRATE_PRE`: this event is triggered before ZfrRest automatically
hydrates an object. You can use this event to prevent this behaviour.
* `ZfrRest\Mvc\Controller\Event\HydrationEvent::EVENt_HYDRATE_POST`: this event is triggered when an object has been
hydrated.

The next few sections will show you some specific use cases.

### Preventing auto filtering and/or auto validation

By default, ZfrRest does a lot of things automatically for you. For instance, when doing a `POST` or `PUT` request,
it automatically validates the data and create a new object using a hydrator, so that your controller directly
receive a ready-to-consume object.

However, you may want to keep control on those steps, and doing the validation and hydration yourself. You can use
the `ValidationEvent::EVENT_VALIDATE_PRE`. Here is the full example:

```php
use ZfrRest\Mvc\Controller\AbstractRestfulController;
use ZfrRest\Mvc\Controller\Event\ValidationEvent;
use ZfrRest\Mvc\Controller\Event\HydrationEvent;

class PasswordResetListController extends AbstractRestfulController
{
public function post(array $data)
{
// For instance
$passwordReset = $this->passwordResetService->createFromEmail($data['email']);

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

protected function attachDefaultListeners()
{
parent::attachDefaultListeners(); // Don't forget that!

$eventManager = $this->getEventManager();
$eventManager->attach(ValidationEvent::EVENT_VALIDATION_PRE, [$this, 'preventAutoFiltering']);
$eventManager->attach(HydrationEvent::EVENT_HYDRATION_PRE, [$this, 'preventAutoHydration']);
}

public function preventAutoFiltering(ValidationEvent $event)
{
$method = strtolower($this->request->getMethod());

if ($method === 'post') {
$event->setAutoValidate(false);
}
}

public function preventAutoHydration(HydrationEvent $event)
{
$method = strtolower($this->request->getMethod());

if ($method === 'post') {
$event->setAutoHydrate(false);
}
}
}
```

As you can see, we are overriding the `attachDefaultListeners` and attach two new listeners. If the method is POST,
then we disable auto filtering and auto hydration.

With this code, ZfrRest won't validate nor won't hydrate any data, only for the POST method (this is therefore very
flexible!).

> NOTE: Please note that while disabling auto-hydration can be useful, we do not recommend you to disable auto filtering,
as it can causes security issues if you don't correctly validate and filter data yourself!

### Configuring input filters based on context

Quite often, you need different validation rules depending on the HTTP method or things like users's permissions... For
instance, POST method for creating a user may require a first name, a last name and a password. However, you want
restrict updating (PUT method) to only password and last name, so that they cannot change their first name.

Zend Framework 2 input filters offer this feature through so-called validation groups. Validation groups allow to
skip unwanted values and do not return them. ZfrRest allows you to do this through a hook called `getInputFilter`
that you can override in your controller. This method is called by POST and PUT handlers. For instance:
skip unwanted values and do not return them. As for the previous example, you can use events to do that:

```php
use Zend\InputFilter\InputFilterPluginManager;
use ZfrRest\Mvc\Controller\AbstractRestfulController;
use ZfrRest\Mvc\Controller\Event\ValidationEvent;
use ZfrRest\Mvc\Controller\Event\HydrationEvent;

class UserController extends AbstractRestfulController
{
public function getInputFilter(InputFilterPluginManager $manager, $inputFilterName)
protected function attachDefaultListeners()
{
$inputFilter = parent::getInputFilter($manager, $inputFilterName);
parent::attachDefaultListeners(); // Don't forget that!

$eventManager = $this->getEventManager();
$eventManager->attach(ValidationEvent::EVENT_VALIDATION_PRE, [$this, 'configureInputFilter']);
}

public function configureInputFilter(ValidationEvent $event)
{
$method = strtolower($this->request->getMethod());

// We want to keep all the fields for POST, but limit them for PUT
if ($method === 'put') {
$inputFilter = $inputFilter->setValidationGroup(['last_name', 'password']);
// If method is not PUT, we can return early
if ($method !== 'put') {
return;
}

return $inputFilter;
// We get the default input filter from event, and we can configure it
$inputFilter = $event->getInputFilter();
$inputFilter = $inputFilter->setValidationGroup(['last_name', 'password']);
}
}
```

What you receive is the input filter plugin manager (transferred to you by method handlers) as well as the
input filter name that was extracted from mapping.
By default, `getInputFilter` method from the event will create an input filter from the resource mapping. You can
then configure it.

You are not restricted to using validation groups. In some cases, you may need a completely different input filter,
because rules are dramatically different. You can do that too:
because rules are dramatically different. To do that, just use the `setInputFilter` method of the event:

```php
use Zend\InputFilter\InputFilterPluginManager;
use ZfrRest\Mvc\Controller\AbstractRestfulController;

class UserController extends AbstractRestfulController
{
public function getInputFilter(InputFilterPluginManager $manager, $inputFilterName)
protected function attachDefaultListeners()
{
parent::attachDefaultListeners(); // Don't forget that!

$eventManager = $this->getEventManager();
$eventManager->attach(ValidationEvent::EVENT_VALIDATION_PRE, [$this, 'configureInputFilter']);
}

public function configureInputFilter(ValidationEvent $event)
{
$method = strtolower($this->request->getMethod());

// We want to keep all the fields for POST, but limit them for PUT
if ($method === 'put') {
$inputFilter = $manager->get('UserInputFilter');
} else {
$inputFilter = parent::getInputFilter($manager, $inputFilterName);
// If method is not PUT, we can return early
if ($method !== 'put') {
return;
}

return $inputFilter;
// Otherwise, we create the very specific input filter for PUT method
$inputFilterManager = $event->getInputFilterManager();
$inputFilter = $inputFilterManager->get('UpdateUserInputFilter');

$event->setInputFilter($inputFilter);
}
}
```

By doing this, you can avoid unnecessary instantiation of input filters.
> By doing this, the input filter defined in the mapping is never instantiated if method is "PUT".
### Navigation

Expand Down
3 changes: 2 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ If you are looking for some information that is not listed in the documentation,

4. [Controllers](/docs/04. Controllers.md)
1. [Understanding method handlers](/docs/04. Controllers.md#method-handlers)
2. [Configuring controller behaviours](/docs/04. Controllers.md#configuring-controller-behaviours)
2. [Preventing auto hydration and auto filtering](/docs/04. Controllers.md#preventing-auto-hydration-and-auto-filtering)
3. [Context resource](/docs/04. Controllers.md#context-resource)
4. [Override controllers on associations](/docs/04. Controllers.md#override-controllers-on-associations)
5. [Configuring input filters based on context](/docs/04. Controllers.md#configuring-input-filters-based-on-context)
5. [Using controller events](/docs/04.Controllers.md#using-controller-events)

5. [Built-in listeners](/docs/05. Built-in listeners.md)
1. [CreateResourceModelListener](/docs/05. Built-in listeners.md)
Expand Down

0 comments on commit 5fae48d

Please sign in to comment.