Skip to content

[Feature] Fluent routing and custom actions #281

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

Merged
merged 8 commits into from
Jan 19, 2019
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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,22 @@ All notable changes to this project will be documented in this file. This projec
### Added
- [#271](https://github.com/cloudcreativity/laravel-json-api/issues/271)
Can now validate delete resource requests.
- [#196](https://github.com/cloudcreativity/laravel-json-api/issues/196)
Can now add custom actions to resource controllers. Refer to the [Routing](./docs/basics/routing.md) and
[Controllers](./docs/basics/controllers.md) chapters.
- [#242](https://github.com/cloudcreativity/laravel-json-api/issues/242)
Can now override the default controller for an API. Refer to the [Controllers](./docs/basics/controllers.md)
chapter for details.

### Changed
- [#254](https://github.com/cloudcreativity/laravel-json-api/pull/254)
Refactored content negotiation so that multiple media types can be supported. Refer to the
[Media Types](./docs/features/media-types.md) documentation for details.
- Simplified the validator classes into a single class: `Validators\Validator`.
- Renamed a lot of classes in the `Routing` namespace. They are also marked as `final` because they
are not meant to be extended.
- Modified the abstract `mount` method that package providers use to add routes to an API.
Also added PHP 7 type-hinting to all methods in the abstract class.

### Fixed
- [#280](https://github.com/cloudcreativity/laravel-json-api/issues/280)
Expand Down
100 changes: 94 additions & 6 deletions docs/basics/controllers.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,36 +17,38 @@ as long as it implements the methods expected for the registered routes.
The following route registration:

```php
JsonApi::register('default', ['namespace' => 'Api'], function ($api, $router) {
JsonApi::register('default')->routes(function ($api, $router) {
$api->resource('posts');
});
```

Will use the `CloudCreativity\LaravelJsonApi\Http\Controllers\JsonApiController` for the `posts` resource. This
will work for all controller actions without any customisation. So by default, no controller is needed.

> Refer to the [Routing](./routing.md) chapter for details on how to change the default controller.

## Extended Controller

If you need to customise the controller for a resource, for example to dispatch jobs or events from the controller,
you can extend the `JsonApiController`. When registering the resource routes you need to specify that a controller
is to be used:

```php
JsonApi::register('default', ['namespace' => 'Api'], function ($api, $router) {
$api->resource('posts', ['controller' => true]);
JsonApi::register('default')->withNamespace('Api')->routes(function ($api, $router) {
$api->resource('posts')->controller();
});
```

This will use the `PostsController` in the `Api` namespace. If you are using a different name for your controller,
you can specify it as follows:

```php
JsonApi::register('default', ['namespace' => 'Api'], function ($api, $router) {
$api->resource('posts', ['controller' => 'CustomPostsController']);
JsonApi::register('default')->withNamespace('Api')->routes(function ($api, $router) {
$api->resource('posts')->controller('BlogPostsController');
});
```

> The `namespace` option is identical to Laravel's namespace option when registering a route group.
> The `withNamespace` method is identical to Laravel's namespace method when registering a route group.

Your controller would then look like this:

Expand All @@ -57,6 +59,8 @@ use CloudCreativity\LaravelJsonApi\Http\Controllers\JsonApiController;

class PostsController extends JsonApiController
{

// ...
}
```

Expand Down Expand Up @@ -198,6 +202,90 @@ Content-Type: application/vnd.api+json
}
```

## Custom Actions

The [Routing Chapter](./routing.md) describes how you can register custom routes in your API. For example
if we added an action to share a `posts` resource:

```php
JsonApi::register('default')->withNamespace('Api')->routes(function ($api) {
$api->resource('posts')->controller()->routes(function ($posts) {
$posts->post('{record}/share', 'share');
});
});
```

This would expect the `share` method to be implemented on our resource's controller. For example:

```php
namespace App\Http\Controllers\Api;

use CloudCreativity\LaravelJsonApi\Http\Controllers\JsonApiController;

class PostsController extends JsonApiController
{

public function share(\App\Post $post): \Illuminate\Http\Response
{
\App\Jobs\SharePost::dispatch($post);

return $this->reply()->content($post);
}
}
```

When you do this, any query parameters sent by the client will be used when encoding the response. If you
have not validated the request, this could result in an error.

To avoid this, you will need to type-hint the JSON API request class to ensure the request is validated.
This package provides a number of request classes that validated the different *types* of request that
are defined by the JSON API specification. You should type-hint whichever is appropriate for your action.

> These request classes are validated when they are resolved out of the container. I.e. they work like
Laravel's form requests.

The example *share* action does not expect there to be any request body content, but it is going to return
a `posts` resource in the response. It is therefore the same as request to fetch a `posts` resource i.e.
`GET /api/posts/123`. (This is the case even if we have registered the action as needing to be called as
`POST /api/posts/123/share`.) We would therefore type-hint the `FetchResource` request object:

```php
namespace App\Http\Controllers\Api;

use CloudCreativity\LaravelJsonApi\Http\Controllers\JsonApiController;
use CloudCreativity\LaravelJsonApi\Http\Requests\FetchResource;

class PostsController extends JsonApiController
{

public function share(FetchResource $request, \App\Post $post): \Illuminate\Http\Response
{
\App\Jobs\SharePost::dispatch($post);

return $this->reply()->content($post);
}
}
```

All request classes are in the `CloudCreativity\LaravelJsonApi\Http\Requests` namespace. These are
the ones available:

| Action | Request Class |
| :-- | :-- |
| `index` | `FetchResources` |
| `create` | `CreateResource` |
| `read` | `FetchResource` |
| `update` | `UpdateResource` |
| `delete` | `DeleteResource` |
| `readRelatedResource` | `FetchRelated` |
| `readRelationship` | `FetchRelationship` |
| `replaceRelationship` | `UpdateRelationship` |
| `addToRelationship` | `UpdateRelationship` |
| `removeFromRelationship` | `UpdateRelationship` |

> All of these classes extended the `ValidatedRequest` abstract class. If none of them do exactly what you
need for your custom action, you can write you own request class that extends the abstract class.

## Custom Controller

If the standard controller does not provide the functionality you require, you are able to write your own controller.
Expand Down
Loading