Skip to content

Commit 470bcd9

Browse files
committed
Merge branch 'release/1.4.0'
2 parents ec24e28 + a87684a commit 470bcd9

37 files changed

+657
-162
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
vendor/
22
composer.lock
3+
.phpunit.result.cache

.travis.yml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,6 @@ matrix:
3636
env:
3737
- LARAVEL_VERSION=^6.0
3838
- PHPUNIT_VERSION=^8.0
39-
allow_failures:
40-
- php: "7.3"
41-
env:
42-
- LARAVEL_VERSION=^6.0
43-
- PHPUNIT_VERSION=^8.0
4439

4540
install:
4641
- composer require "laravel/framework:${LARAVEL_VERSION}" --no-update -n

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22
All notable changes to this project will be documented in this file. This project adheres to
33
[Semantic Versioning](http://semver.org/) and [this changelog format](http://keepachangelog.com/).
44

5+
## [1.4.0] - 2019-09-04
6+
7+
### Added
8+
- Package now supports Laravel 6.
9+
- [#333](https://github.com/cloudcreativity/laravel-json-api/issues/333)
10+
Eloquent adapters now have a `filterWithScopes()` method, that maps JSON API filters to
11+
model scopes and the Eloquent `where*` method names. This is opt-in: i.e. to use, the
12+
developer must call `filterWithScopes()` within their adapter's `filter()` method.
13+
- Added `put`, `putAttr` and `putRelation` methods to the `ResourceObject` class.
14+
515
## [1.3.1] - 2019-08-19
616

717
### Fixed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,10 @@ A demo application is available at [here](https://github.com/cloudcreativity/dem
6969
| `5.5.*` | `^1.0` |
7070

7171
Make sure you consult the [Upgrade Guide](http://laravel-json-api.readthedocs.io/en/latest/upgrade/)
72-
when upgrading.
72+
when upgrading between major or pre-release versions.
73+
74+
> You may notice that there are `2.0.0-alpha` tags. We **do not** recommend using these versions
75+
> until we hit `beta` releases.
7376
7477
## Lumen
7578

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
},
4040
"require-dev": {
4141
"ext-sqlite3": "*",
42-
"cloudcreativity/json-api-testing": "^1.1",
42+
"cloudcreativity/json-api-testing": "^1.2",
4343
"composer/semver": "^1.5",
4444
"guzzlehttp/guzzle": "^6.3",
4545
"mockery/mockery": "^1.1",
@@ -78,7 +78,7 @@
7878
}
7979
}
8080
},
81-
"minimum-stability": "dev",
81+
"minimum-stability": "stable",
8282
"prefer-stable": true,
8383
"config": {
8484
"sort-packages": true

docs/basics/adapters.md

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ class Adapter extends AbstractAdapter
9696
}
9797
```
9898

99+
If your resource supports client-generated ids, refer to the client-generated ids section in the
100+
[Creating Resources chapter.](../crud/creating.md)
101+
99102
### Attributes
100103

101104
When filling a model with attributes received in a JSON API request, the adapter will convert the JSON API
@@ -707,19 +710,8 @@ class Adapter extends AbstractAdapter
707710
}
708711
```
709712

710-
Or if your resource uses a client-generated ID, you can assign the id in the `creating` hook:
711-
712-
```php
713-
class Adapter extends AbstractAdapter
714-
{
715-
// ...
716-
717-
protected function creating(Comment $comment, $resource): void
718-
{
719-
$comment->{$comment->getRouteKeyName()} = $resource['id'];
720-
}
721-
}
722-
```
713+
> If your resource uses a [client-generated ID](../crud/creating.md#client-generated-ids), you
714+
will need to use the `creating` hook to assign the id to the model.
723715

724716
There are two additional hooks that are invoked when an adapter is deleting a resource: `deleting` and `deleted`.
725717
These receive the record being deleted as the first function argument.

docs/crud/creating.md

Lines changed: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,133 @@
11
# Creating Resources
22

3-
@todo
3+
A resource can be created by sending a `POST` request to the URL that represents the collection
4+
of resources. For example, a new `posts` resource might be created with the following request:
5+
6+
```php
7+
POST /posts HTTP/1.1
8+
Content-Type: application/vnd.api+json
9+
Accept: application/vnd.api+json
10+
11+
{
12+
"data": {
13+
"type": "posts",
14+
"attributes": {
15+
"title": "Hello World",
16+
"content": "..."
17+
},
18+
"relationships": {
19+
"tags": {
20+
"data": [
21+
{
22+
"type": "tags",
23+
"id": "1"
24+
}
25+
]
26+
}
27+
}
28+
}
29+
}
30+
```
31+
32+
This will result in the following response (depending on what is in your `posts` `Schema` class):
33+
34+
```php
35+
HTTP/1.1 201 Created
36+
Location: http://example.com/api/posts/1
37+
Content-Type: application/vnd.api+json
38+
39+
{
40+
"data": {
41+
"type": "posts",
42+
"id": "1",
43+
"attributes": {
44+
"title": "Hello World",
45+
"content": "..."
46+
},
47+
"relationships": {
48+
"tags": {
49+
"links": {
50+
"self": "http://example.com/api/posts/1/relationships/tags",
51+
"related": "http://example.com/api/posts/1/tags"
52+
}
53+
}
54+
},
55+
"links": {
56+
"self": "http://example.com/api/posts/1"
57+
}
58+
}
59+
}
60+
```
61+
62+
## Requirements
63+
64+
For this to work, you must:
65+
66+
- add the `posts` resource in your API's config, in the `resources` array; and
67+
- register [routes](../basics/routing.md) for your `posts` resource; and
68+
- create the [validators](../basics/validators.md), [adapter](../basics/adapters.md) and
69+
[schema](../basics/schemas.md) classes for the `posts` resource; and
70+
- optionally set up an [authorizer](../basics/security.md) class if you want to authorize the request.
71+
72+
## Supported Query Parameters
73+
74+
The following JSON API query parameters are supported for this request:
75+
76+
- [inclusion of related resources](../fetching/inclusion.md)
77+
- [sparse fieldsets](../fetching/sparse-fieldsets.md)
78+
79+
> Filter, page and sort parameters are not supported because this endpoint returns a single resource
80+
object as its primary data.
81+
82+
## Client Generated IDs
83+
84+
By default the server will reject a request to create a resource with a client-generated id. Any
85+
such requests will receive a `403 Forbidden` response, as defined in the JSON API spec.
86+
87+
However you can easily enable client-generated ids for a resource by adding a rule to your
88+
validators class, and using the `creating` hook on your adapter.
89+
90+
On your validators class, add a validation rule for the `id`. This tells the validators that
91+
it can accept a client-generated id. For example:
92+
93+
```php
94+
class Validators extends AbstractValidators
95+
{
96+
// ...
97+
98+
protected function rules($record = null): array
99+
{
100+
return [
101+
'id' => 'required|regex:/' . \Ramsey\Uuid\Uuid::VALID_PATTERN . '/',
102+
// ... other rules
103+
];
104+
}
105+
}
106+
```
107+
108+
> Note that you **do not** need to check whether the id already exists. The JSON API spec
109+
defines that the server must respond with a `409 Conflict` response when processing
110+
a request to create a resource with a client-generated id that already exists. This package
111+
therefore checks any ids provided by the client and sends the `409 Conflict` response if
112+
they already exist.
113+
114+
The final step is to use the `creating` hook in your adapter class to transfer the
115+
client-generated id to your model:
116+
117+
```php
118+
class Adapter extends AbstractAdapter
119+
{
120+
121+
// ...
122+
123+
/**
124+
* @param Video $video
125+
* @param $resource
126+
* @return void
127+
*/
128+
protected function creating(Video $video, $resource)
129+
{
130+
$video->{$video->getKeyName()} = $resource['id'];
131+
}
132+
}
133+
```

docs/fetching/filtering.md

Lines changed: 80 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ highly coupled with the application's logic and choice of data storage.
1111
This package therefore provides the following capabilities:
1212

1313
- Validation of the `filter` parameter.
14-
- An easy hook in the Eloquent adapter to convert validated filter parameters to database queries.
14+
- An easy hook in the Eloquent adapter to convert validated filter parameters to database queries.
15+
- An opt-in implementation to map JSON API filters to model scopes and/or Eloquent's magic `where*` method.
1516

1617
## Example Requests
1718

@@ -72,37 +73,15 @@ class Validators extends AbstractValidators
7273

7374
## Validation
7475

75-
Filter parameters should always be validated to ensure that their use in database queries is valid. You can
76-
validate them in your [Validators](../basics/validators.md) query rules. For example:
76+
Filter parameters should always be validated to ensure that their use in database queries is valid.
77+
You can validate them in your [Validators](../basics/validators.md) query rules. For example:
7778

7879
```php
7980
class Validators extends AbstractValidators
8081
{
8182
// ...
8283

83-
protected function queryRules(): array
84-
{
85-
return [
86-
'filter.title' => 'filled|string',
87-
'filter.slug' => 'filled|string',
88-
'filter.authors' => 'array|min:1',
89-
'filter.authors.*' => 'integer',
90-
];
91-
}
92-
93-
}
94-
```
95-
96-
By default we allow a client to submit any filter parameters as we assume that you will validate the values
97-
of expected filters as in the example above. However, you can whitelist expected filter parameters by listing
98-
them on the `$allowedFilteringParameters` of your validators class. For example:
99-
100-
```php
101-
class Validators extends AbstractValidators
102-
{
103-
// ...
104-
105-
protected $allowedFilteringParameters = ['title', 'authors'];
84+
protected $allowedFilteringParameters = ['title', 'slug', 'authors'];
10685

10786
protected function queryRules(): array
10887
{
@@ -117,6 +96,9 @@ class Validators extends AbstractValidators
11796
}
11897
```
11998

99+
The above whitelists the allowed filter parameters, and then also validates the values that can be
100+
submitted for each.
101+
120102
Any requests that contain filter keys that are not in your allowed filtering parameters list will be rejected
121103
with a `400 Bad Request` response, for example:
122104

@@ -143,7 +125,75 @@ Content-Type: application/vnd.api+json
143125
The Eloquent adapter provides a `filter` method that allows you to implement your filtering logic.
144126
This method is provided with an Eloquent query builder and the filters provided by the client.
145127

146-
For example, our `posts` adapter filtering implementation could be:
128+
### Filter Scopes
129+
130+
A newly generated Eloquent adapter will use our `filterWithScopes()` implementation. For example:
131+
132+
```php
133+
class Adapter extends AbstractAdapter
134+
{
135+
136+
// ...
137+
138+
/**
139+
* Mapping of JSON API filter names to model scopes.
140+
*
141+
* @var array
142+
*/
143+
protected $filterScopes = [];
144+
145+
/**
146+
* @param Builder $query
147+
* @param Collection $filters
148+
* @return void
149+
*/
150+
protected function filter($query, Collection $filters)
151+
{
152+
$this->filterWithScopes($query, $filters);
153+
}
154+
}
155+
```
156+
157+
The `filterWithScopes` method will map JSON API filters to model scopes, and pass the filter value to that scope.
158+
For example, if the client has sent a `filter[slug]` query parameter, we expect either there to be a
159+
`scopeSlug` method on the model, or we will use Eloquent's magic `whereSlug` method.
160+
161+
If you need to map a filter parameter to a different scope name, then you can define it here.
162+
For example if `filter[slug]` needed to be passed to the `onlySlug` scope, it can be defined
163+
as follows:
164+
165+
```php
166+
protected $filterScopes = [
167+
'slug' => 'onlySlug'
168+
];
169+
```
170+
171+
If you want a filter parameter to not be mapped, define the mapping as `null`, for example:
172+
173+
```php
174+
protected $filterScopes = [
175+
'slug' => null
176+
];
177+
```
178+
179+
Alternatively you could let some filters be applied using scopes, and then implement your own logic
180+
for others. For example:
181+
182+
```php
183+
protected function filter($query, Collection $filters)
184+
{
185+
$this->filterWithScopes($query, $filters->only('foo', 'bar', 'bat'));
186+
187+
if ($baz = $filters->get('baz')) {
188+
// filter logic for baz.
189+
}
190+
}
191+
```
192+
193+
### Custom Filter Logic
194+
195+
If you do not want to use our filter by scope implementation, then it is easy to implement your
196+
own logic. Remove the call to `filterWithScopes()` and insert your own logic. For example:
147197

148198
```php
149199
class Adapter extends AbstractAdapter
@@ -166,5 +216,7 @@ class Adapter extends AbstractAdapter
166216
}
167217
```
168218

169-
> As filters are also applied when filtering the resource through a relationship, it is good practice
219+
### Relationships
220+
221+
Filters are also applied when filtering the resource through a relationship. It is good practice
170222
to qualify any column names.

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ For full information on the spec, plus examples, see [http://jsonapi.org](http:/
2020
## Tutorial
2121

2222
Want a tutorial to get started? Read the
23-
[Laravel tutorial on the *How to JSON:API* website.]((https://howtojsonapi.com/laravel.html))
23+
[Laravel tutorial on the *How to JSON:API* website.](https://howtojsonapi.com/laravel.html)
2424

2525
## Demo
2626

0 commit comments

Comments
 (0)