8000 Controller traits. · sablesoft/laravel-json-api@1a719f8 · GitHub
[go: up one dir, main page]

Skip to content

Commit 1a719f8

Browse files
8000
committed
Controller traits.
1 parent 491ea57 commit 1a719f8

File tree

9 files changed

+558
-30
lines changed

9 files changed

+558
-30
lines changed

README.md

Lines changed: 87 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,12 @@ Per resource type, the following endpoints will be registered (using the `articl
8686
| /articles/:id | PATCH | `update($id)` |
8787
| /articles/:id | DELETE | `delete($id)` |
8888
| /articles/:id/author | GET | `readAuthor($id)` |
89-
| /articles/:id/relationships/author | GET | `readAuthorIdentifier($id)` |
90-
| /articles/:id/relationships/author | PATCH | `updateAuthorIdentifier($id)` |
89+
| /articles/:id/relationships/author | GET | `readAuthorRelationship($id)` |
90+
| /articles/:id/relationships/author | PATCH | `updateAuthorRelationship($id)` |
9191
| /articles/:id/comments | GET | `readComments($id)` |
92-
| /articles/:id/relationships/comments | GET | `readCommentIdentifiers($id)` |
93-
| /articles/:id/relationships/comments | PATCH | `updateCommentIdentifiers($id)` |
94-
| /articles/:id/relationships/comments | DELETE | `deleteCommentIdentifiers($id)` |
92+
| /articles/:id/relationships/comments | GET | `readCommentRelationship($id)` |
93+
| /articles/:id/relationships/comments | PATCH | `updateCommentsRelationship($id)` |
94+
| /articles/:id/relationships/comments | DELETE | `deleteCommentsRelationship($id)` |
9595

9696
**You do not need to implement all these methods** if extending this package's `Http\Controllers\JsonApiController`. The controller is configured to send a `501 Not Implemented` response for any missing methods.
9797

@@ -129,13 +129,94 @@ Middleware makes it easy to register multiple routes (or even an entire API) tha
129129

130130
### Controller
131131

132+
JSON API support in controllers can be added by extending `CloudCreativity\JsonApi\Http\Controllers\JsonApiController`. This has a number of helper methods to assist in handling JSON API requests and sending responses.
133+
134+
Each of the following pieces of functionality are implemented using traits. So if you want to include any of the functionality in your own custom controllers, just include the relevant trait. The traits are in the same namespace as the `JsonApiController`.
135+
132136
#### Query Parameters
133137

138+
The `JsonApiController` will automatically check the query parameters before your controller action method is invoked. To define what query parameters your controller allows, set the properties that are defined in the `QueryCheckerTrait`.
139+
140+
This automatic checking means that by the time your controller action method is invoked, the request headers and query parameters have all been checked. Your action method can get the query parameters by calling `$this->getParameters()`.
141+
142+
If you want to disable automatic checking of query parameters before your controller methods are invoked, then set the `$autoCheckQueryParameters` property of your controller to `false`.
143+
144+
Note that if you are adding the trait to your own custom controller, you will need to call `$this->checkParameters()` to trigger the checking of parameters.
145+
134146
#### HTTP Content Body
135147

148+
To decode the request content body with the decoder that matched the request `Content-Type` header, then call `$this->getContentBody()`. If no decoder has matched the request header, calling this method will result in a `400 Bad Request` response to the client.
149+
150+
If you want to use a `CloudCreativity\JsonApi\Contracts\Object\DocumentInterface` object to handle the request content in your controller action, use `$this->getDocumentObject()`. This method ensures the decoder has returned a `DocumentInterface` object, or if it has returned a `stdClass` object it will be cast to a `DocumentInterface` object.
151+
Shorthands are also provided if you are expecting the document to contain a resource object in its data member, or if the provided document represents a relationship. Use `$this->getResourceObject()` and `$this->getRelationshipObject()` respectively.
152+
153+
These helper methods are implemented in the `DocumentDecoderTrait`.
154+
136155
#### Content Body Validation
137156

138-
### Responses
157+
If desired, you can validate the body content as it is decoded. All of the content body getter methods accept an optional validator that is applied when the document content is decoded. All of methods for getting the request body content take an optional validator argument.
158+
159+
If the received content is a resource object, you can use the `$this->getResourceObjectValidator()` method. For example:
160+
161+
``` php
162+
class ArticlesController extends JsonApiController
163+
{
164+
// ...
165+
166+
public function update($id)
167+
{
168+
$validator = $this
169+
->getResourceObjectValidator(
170+
// the expected resource type
171+
'articles',
172+
// the expected id (use null for new resources)
173+
$id,
174+
// the rules for the attributes - uses the Laravel validation implementation.
175+
[
176+
'title' => 'string|max:250',
177+
'content' => 'string',
178+
'draft' => 'boolean',
179+
],
180+
// Laravel validation messages, if you need to customise them
181+
[],
182+
// whether the attributes member must be present in the resource object
183+
true
184+
);
185+
186+
$object = $this->getResourceObject($validator);
187+
// ...
188+
}
189+
}
190+
```
191+
192+
The validator returned by `$this->getResourceObjectValidator()` provides helper methods for also defining relationship validation rules.
193+
194+
For relationship endpoints, you can get a validator for the relationship provided using one of the following helper methods:
195+
196+
``` php
197+
class ArticlesController extends JsonApiController
198+
{
199+
// ...
200+
201+
public function updateAuthorRelationship($id)
202+
{
203+
$validator = $this->getHasOneValidator('person');
204+
$relationship = $this->getRelationshipObject($validator);
205+
// ...
206+
}
207+
208+
public function updateCommentsRelationship($id)
209+
{
210+
$validator = $this->getHasManyValidator('comments');
211+
$relationship = $this->getRelationshipObject($validator);
212+
// ...
213+
}
214+
}
215+
```
216+
217+
These helper methods are provided by the `DocumentValidatorTrait`, which uses the `DocumentDecoderTrait`.
218+
219+
#### Responses
139220

140221
### Exception Handling
141222

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
namespace CloudCreativity\JsonApi\Http\Controllers;
4+
5+
use App;
6+
use CloudCreativity\JsonApi\Contracts\Object\Document\DocumentInterface;
7+
use CloudCreativity\JsonApi\Contracts\Validator\ValidatorAwareInterface;
8+
use CloudCreativity\JsonApi\Contracts\Validator\ValidatorInterface;
9+
use CloudCreativity\JsonApi\Object\Document\Document;
10+
use CloudCreativity\JsonApi\Validator\Document\DocumentValidator;
11+
use Illuminate\Http\Request;
12+
use JsonApi;
13+
use Neomerx\JsonApi\Contracts\Codec\CodecMatcherInterface;
14+
use Neomerx\JsonApi\Contracts\Decoder\DecoderInterface;
15+
use Neomerx\JsonApi\Contracts\Integration\ExceptionThrowerInterface;
16+
use RuntimeException;
17+
18+
/**
19+
* Class DocumentDecoderTrait
20+
* @package CloudCreativity\JsonApi
21+
*/
22+
trait DocumentDecoderTrait
23+
{
24+
25+
/**
26+
* @param ValidatorInterface|null $validator
27+
* @return mixed
28+
*/
29+
public function getContentBody(ValidatorInterface $validator = null)
30+
{
31+
/** @var CodecMatcherInterface $codecMatcher */
32+
$codecMatcher = JsonApi::getCodecMatcher();
33+
$decoder = $codecMatcher->getDecoder();
34+
35+
if (!$decoder instanceof DecoderInterface) {
36+
/** @var ExceptionThrowerInterface $thrower */
37+
$thrower = App::make(ExceptionThrowerInterface::class);
38+
$thrower->throwBadRequest();
39+
}
40+
41+
if ($validator && !$decoder instanceof ValidatorAwareInterface) {
42+
throw new RuntimeException('To use a validator on content body, your decoder must implement the ValidatorAwareInterface.');
43+
} elseif ($validator) {
44+
$decoder->setValidator($validator);
45+
}
46+
47+
/** @var Request $request */
48+
$request = App::make('request');
49+
50+
return $decoder->decode($request->getContent());
51+
}
52+
53+
/**
54+
* @param ValidatorInterface|null $documentValidator
55+
* @return DocumentInterface
56+
*/
57+
public function getDocumentObject(ValidatorInterface $documentValidator = null)
58+
{
59+
$content = $this->getContentBody($documentValidator);
60+
61+
return ($content instanceof DocumentInterface) ? $content : new Document($content);
62+
}
63+
64+
/**
65+
* @param ValidatorInterface|null $resourceValidator
66+
* the validator for the "data" member in the document, which is expected to be a resource object.
67+
* @return \CloudCreativity\JsonApi\Contracts\Object\Resource\ResourceObjectInterface
68+
*/
69+
public function getResourceObject(ValidatorInterface $resourceValidator = null)
70+
{
71+
$validator = ($resourceValidator) ? new DocumentValidator($resourceValidator) : null;
72+
73+
return $this
74+
->getDocumentObject($validator)
75+
->getResourceObject();
76+
}
77+
78+
/**
79+
* @param ValidatorInterface|null $relationshipValidator
80+
* @return \CloudCreativity\JsonApi\Contracts\Object\Relationships\RelationshipInterface
81+
*/
82+
public function getRelationshipObject(ValidatorInterface $relationshipValidator = null)
83+
{
84+
return $this
85+
->getDocumentObject($relationshipValidator)
86+
->getRelationship();
87+
}
88+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
namespace CloudCreativity\JsonApi\Http\Controllers;
4+
5+
use CloudCreativity\JsonApi\Validator\Relationships\BelongsToValidator;
6+
use CloudCreativity\JsonApi\Validator\Relationships\HasManyValidator;
7+
use CloudCreativity\JsonApi\Validator\Resource\IlluminateResourceValidator;
8+
9+
/**
10+
* Class DocumentValidatorTrait
11+
* @package CloudCreativity\JsonApi
12+
*/
13+
trait DocumentValidatorTrait
14+
{
15+
16+
use DocumentDecoderTrait;
17+
18+
/**
19+
* @see CloudCreativity\JsonApi\Validator\Resource\IlluminateResourceValidator
20+
* @param string $expectedType
21+
* the resource type that is expected.
22+
* @param string|int|null $expectedId
23+
* the resource id that is expected, or null if validating a new resource.
24+
* @param array $attributesValidationRules
25+
* Laravel validation rules for the resource's attributes.
26+
* @param array $attributesValidationMessages
27+
* Laravel validation messages for the resource's attributes.
28+
* @param bool $attributesMemberRequired
29+
* Whether an attributes member is expected in the resource received from the client
30+
* @return IlluminateResourceValidator
31+
*/
32+
public function getResourceObjectValidator(
33+
$expectedType,
34+
$expectedId = null,
35+
array $attributesValidationRules = [],
36+
array $attributesValidationMessages = [],
37+
$attributesMemberRequired = true
38+
) {
39+
return new IlluminateResourceValidator(
40+
$expectedType,
41+
$expectedId,
42+
$attributesValidationRules,
43+
$attributesValidationMessages,
44+
$attributesMemberRequired
45+
);
46+
}
47+
48+
/**
49+
* @param string|string[]|null $expectedTypeOrTypes
50+
* @return BelongsToValidator
51+
*/
52+
public function getHasOneValidator($expectedTypeOrTypes = null)
53+
{
54+
return new BelongsToValidator($expectedTypeOrTypes);
55+
}
56+
57+
/**
58+
* @param string|string[]|null $expectedTypeOrTypes
59+
* @return HasManyValidator
60+
*/
61+
public function getHasManyValidator($expectedTypeOrTypes = null)
62+
{
63+
return new HasManyValidator($expectedTypeOrTypes);
64+
}
65+
}

src/Http/Controllers/JsonApiController.php

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,60 @@
22

33
namespace CloudCreativity\JsonApi\Http\Controllers;
44

5-
use CloudCreativity\JsonApi\Contracts\Error\ErrorObjectInterface;
65
use CloudCreativity\JsonApi\Error\ErrorException;
6+
use CloudCreativity\JsonApi\Error\ThrowableError;
77
use Illuminate\Routing\Controller;
8+
use Neomerx\JsonApi\Contracts\Document\ErrorInterface;
89

10+
/**
11+
* Class JsonApiController
12+
* @package CloudCreativity\JsonApi
13+
*/
914
class JsonApiController extends Controller
1015
{
1116

17+
use QueryCheckerTrait,
18+
DocumentValidatorTrait;
19+
20+
/**
21+
* Whether query parameters should automatically be checked before the controller action method is invoked.
22+
*
23+
* @var bool
24+
*/
25+
protected $autoCheckQueryParameters = true;
26+
27+
/**
28+
* @param string $method
29+
* @param array $parameters
30+
* @return \Symfony\Component\HttpFoundation\Response
31+
*/
32+
public function callAction($method, $parameters)
33+
{
34+
if (true === $this->autoCheckQueryParameters) {
35+
$this->checkParameters();
36+
}
37+
38+
return parent::callAction($method, $parameters);
39+
}
40+
1241
/**
1342
* @param array $parameters
1443
* @return void
15-
* @throws ErrorException
44+
* @throws ErrorInterface
1645
*/
1746
public function missingMethod($parameters = [])
1847
{
19-
throw new ErrorException([
20-
ErrorObjectInterface::TITLE => 'Not Implemented',
21-
ErrorObjectInterface::DETAIL => 'This JSON API endpoint is not yet implemented.',
22-
ErrorObjectInterface::STATUS => 501,
23-
]);
48+
throw new ThrowableError('Method Not Allowed', 405);
49+
}
50+
51+
/**
52+
* @param string $method
53+
* @param array $parameters
54+
* @return void
55+
* @throws ErrorException
56+
*/
57+
public function __call($method, $parameters)
58+
{
59+
throw new ThrowableError('Not Implemented', 501);
2460
}
2561
}

0 commit comments

Comments
 (0)
0