10000 Merge branch 'release/3.2.0' · rubinred/laravel-json-api@2189d7d · GitHub
[go: up one dir, main page]

Skip to content

Commit 2189d7d

Browse files
committed
Merge branch 'release/3.2.0'
2 parents 94757b6 + 64de938 commit 2189d7d

File tree

14 files changed

+199
-26
lines changed

14 files changed

+199
-26
lines changed

.gitattributes

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/docs export-ignore
2+
/tests export-ignore
3+
.editorconfig export-ignore
4+
.gitattributes export-ignore
5+
.gitignore export-ignore
6+
.travis.yml export-ignore
7+
mkdocs.yml export-ignore
8+
phpunit.xml export-ignore

.travis.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
language: php
2-
dist: trusty
2+
dist: xenial
33
sudo: false
44

55
matrix:
66
include:
7-
- php: "7.3"
7+
- php: 7.3
88
env:
99
- LARAVEL_VERSION=^8.0
1010
- PHPUNIT_VERSION=^9.0
11-
- php: "7.4"
11+
- php: 7.4
12+
env:
13+
- LARAVEL_VERSION=^8.0
14+
- PHPUNIT_VERSION=^9.0
15+
- php: 8.0snapshot
1216
env:
1317
- LARAVEL_VERSION=^8.0
1418
- PHPUNIT_VERSION=^9.0

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
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+
## [3.2.0] - 2020-11-26
6+
7+
### Added
8+
- Package now supports PHP 8.
9+
- [#570](https://github.com/cloudcreativity/laravel-json-api/issues/570)
10+
Exception parser now handles the Symfony request exception interface.
11+
- [#507](https://github.com/cloudcreativity/laravel-json-api/issues/507)
12+
Can now specify the resource type and relationship URIs when registering routes. This allows
13+
the URI fragment to be different from the resource type or relationship name.
14+
15+
### Fixed
16+
- Fixed qualifying column for morph-to-many relations. This was caused by Laravel introducing
17+
a breaking change of adding a `qualifyColumn` method to the relation builder. Previously
18+
calling `qualifyColumn` on the relation forwarded the call to the model.
19+
520
## [3.1.0] - 2020-10-28
621

722
### Added

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
}
2323
],
2424
"require": {
25-
"php": "^7.3",
25+
"php": "^7.3|^8.0",
2626
"ext-json": "*",
2727
"illuminate/console": "^8.0",
2828
"illuminate/contracts": "^8.0",
@@ -41,7 +41,7 @@
4141
"cloudcreativity/json-api-testing": "^3.1",
4242
"guzzlehttp/guzzle": "^7.0",
4343
"laravel/legacy-factories": "^1.0.4",
44-
"laravel/ui": "^2.0",
44+
"laravel/ui": "^3.0",
4545
"mockery/mockery": "^1.1",
4646
"orchestra/testbench": "^6.0",
4747
"phpunit/phpunit": "^9.0"

docs/basics/routing.md

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ to it.
2222

2323
### API Route Prefix
2424

25-
When registering a JSON API, we automatically read the URL prefix and route name prefix from your
26-
[API's URL configuration](./api#url) and apply this to the route group for your API. The URL prefix in your JSON API
25+
When registering a JSON API, we automatically read the URL prefix and route name prefix from your
26+
[API's URL configuration](./api#url) and apply this to the route group for your API. The URL prefix in your JSON API
2727
config is **always** relative to the root URL on a host, i.e. from `/`.
2828
**This means when registering your routes, you need to ensure that no prefix has already been applied.**
2929

@@ -42,7 +42,7 @@ JsonApi::register('default')->withNamespace('Api')->routes(function ($api) {
4242
```
4343

4444
> We use `withNamespace()` instead of Laravel's usual `namespace()` method because `namespace` is a
45-
[Reserved Keyword](http://php.net/manual/en/reserved.keywords.php).
45+
[Reserved Keyword](http://php.net/manual/en/reserved.keywords.php).
4646

4747
## Resource Routes
4848

@@ -67,6 +67,16 @@ JsonApi::register('default')->routes(function ($api) {
6767
});
6868
```
6969

70+
By default the resource type is used as the URI fragment: i.e. the `posts` resource will have a URI of
71+
`/posts`. If you want to use a different URI fragment, use the `uri()` method. In the following example,
72+
the resource type is `posts` but the URI will be `/blog_posts`:
73+
74+
```php
75+
JsonApi::register('default')->routes(function ($api) {
76+
$api->resource('posts')->uri('blog_posts');
77+
});
78+
```
79+
7080
## Relationship Routes
7181

7282
The JSON API spec also defines routes for relationships on a resource type. There are two types of relationships:
@@ -87,6 +97,19 @@ JsonApi::register('default')->routes(function ($api) {
8797
});
8898
```
8999

100+
By default the relationship name is used as the URI fragment: i.e. for the `comments` relationship on the
101+
`posts` resource, the related resource URI is `/posts/{record}/comments`. To customise the URI framgent,
102+
use the `uri()` method. In the following example, the relationship name is `comments` but the URI
103+
will be `/posts/{record}/blog_comments`:
104+
105+
```php
106+
JsonApi::register('default')->routes(function ($api) {
107+
$api->resource('posts')->relationships(function ($relations) {
108+
$relations->hasMany('comments')->uri('blog_comments');
109+
});
110+
});
111+
```
112+
90113
### Related Resource Type
91114

92115
When registering relationship routes, it is assumed that the resource type returned in the response is the
@@ -252,7 +275,7 @@ JsonApi::register('default')->withNamespace('Api')->routes(function ($api, $rout
252275

253276
### Controller Names
254277

255-
If you call `controller()` without any arguments, we assume your controller is the camel case name version of
278+
If you call `controller()` without any arguments, we assume your controller is the camel case name version of
256279
the resource type with `Controller` on the end. I.e. `posts` would expect `PostsController` and
257280
`blog-posts` would expect `BlogPostsController`. Or if your resource type was `post`,
258281
we would guess `PostController`.
@@ -332,7 +355,7 @@ If you are using these, you will also need to refer to the *Custom Actions* sect
332355

333356
Also note that custom routes are registered *before* the routes defined by the JSON API specification,
334357
i.e. those that are added when you call `$api->resource('posts')`. You will need to ensure that your
335-
custom route definitions do not collide with these defined routes.
358+
custom route definitions do not collide with these defined routes.
336359

337360
> Generally we advise against registering custom routes. This is because the JSON API specification may
338361
have additional routes added to it in the future, which might collide with your custom routes.
@@ -396,7 +419,7 @@ does not contain an `@` symbol we add the controller name to it.
396419

397420
Secondly, if you are defining a custom relationship route, you must use the `field` method. This takes
398421
the relationship name as its first argument. The inverse resource type can be specified as the second argument,
399-
for example:
422+
for example:
400423

401424
```php
402425
JsonApi::register('default')->withNamespace('Api')->routes(function ($api) {

src/Eloquent/Concerns/SortsModels.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ protected function getQualifiedSortColumn($query, $field)
127127
{
128128
$key = $this->getSortColumn($field, $query->getModel());
129129

130-
return $query->qualifyColumn($key);
130+
return $query->getModel()->qualifyColumn($key);
131131
}
132132

133133
/**

src/Exceptions/ExceptionParser.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
use Neomerx\JsonApi\Contracts\Document\ErrorInterface;
3030
use Neomerx\JsonApi\Document\Error;
3131
use Neomerx\JsonApi\Exceptions\JsonApiException as NeomerxJsonApiException;
32+
use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface;
3233
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
3334

3435
/**
@@ -101,6 +102,10 @@ protected function getErrors(\Throwable $e): array
101102
return [$this->getHttpError F43B ($e)];
102103
}
103104

105+
if ($e instanceof RequestExceptionInterface) {
106+
return [$this->getRequestError($e)];
107+
}
108+
104109
return [$this->getDefaultError()];
105110
}
106111

@@ -125,6 +130,22 @@ protected function getHttpError(HttpExceptionInterface $e): ErrorInterface
125130
return new Error(null, null, $status, null, $title, $e->getMessage() ?: null);
126131
}
127132

133+
/**
134+
* @param RequestExceptionInterface|\Throwable $e
135+
* @return ErrorInterface
136+
*/
137+
protected function getRequestError(RequestExceptionInterface $e): ErrorInterface
138+
{
139+
return new Error(
140+
null,
141+
null,
142+
$status = Response::HTTP_BAD_REQUEST,
143+
null,
144+
$this->getDefaultTitle($status),
145+
$e->getMessage() ?: null
146+
);
147+
}
148+
128149
/**
129150
* @return ErrorInterface
130151
*/

src/Routing/RelationshipRegistration.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,17 @@ public function __construct(array $options = [])
3737
$this->options = $options;
3838
}
3939

40+
/**
41+
* @param string $uri
42+
* @return $this
43+
*/
44+
public function uri(string $uri): self
45+
{
46+
$this->options['relationship_uri'] = $uri;
47+
48+
return $this;
49+
}
50+
4051
/**
4152
* @param string $resourceType
4253
* @return $this

src/Routing/RelationshipsRegistrar.php

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ private function add(string $field, array $options): void
115115

116116
$this->router->group([], function () use ($field, $options, $inverse) {
117117
foreach ($options['actions'] as $action) {
118-
$this->route($field, $action, $inverse);
118+
$this->route($field, $action, $inverse, $options);
119119
}
120120
});
121121
}
@@ -125,13 +125,14 @@ private function add(string $field, array $options): void
125125
* @param string $action
126126
* @param string $inverse
127127
* the inverse resource type
128+
* @param array $options
128129
* @return Route
129130
*/
130-
private function route(string $field, string $action, string $inverse): Route
131+
private function route(string $field, string $action, string $inverse, array $options): Route
131132
{
132133
$route = $this->createRoute(
133134
$this->methodForAction($action),
134-
$this->urlForAction($field, $action),
135+
$this->urlForAction($field, $action, $options),
135136
$this->actionForRoute($field, $action)
136137
);
137138

@@ -161,24 +162,30 @@ private function hasManyActions(array $options): array
161162

162163
/**
163164
* @param string $relationship
165+
* @param array $options
164166
* @return string
165167
*/
166-
private function relatedUrl($relationship): string
168+
private function relatedUrl(string $relationship, array $options): string
167169
{
168-
return sprintf('%s/%s', $this->resourceUrl(), $relationship);
170+
return sprintf(
171+
'%s/%s',
172+
$this->resourceUrl(),
173+
$options['relationship_uri'] ?? $relationship
174+
);
169175
}
170176

171177
/**
172-
* @param $relationship
178+
* @param string $relationship
179+
* @param array $options
173180
* @return string
174181
*/
175-
private function relationshipUrl($relationship): string
182+
private function relationshipUrl(string $relationship, array $options): string
176183
{
177184
return sprintf(
178185
'%s/%s/%s',
179186
$this->resourceUrl(),
180187
ResourceRegistrar::KEYWORD_RELATIONSHIPS,
181-
$relationship
188+
$options['relationship_uri'] ?? $relationship
182189
);
183190
}
184191

@@ -194,15 +201,16 @@ private function methodForAction(string $action): string
194201
/**
195202
* @param string $field
196203
* @param string $action
204+
* @param array $options
197205
* @return string
198206
*/
199-
private function urlForAction(string $field, string $action): string
207+
private function urlForAction(string $field, string $action, array $options): string
200208
{
201209
if ('related' === $action) {
202-
return $this->relatedUrl($field);
210+
return $this->relatedUrl($field, $options);
203211
}
204212

205-
return $this->relationshipUrl($field);
213+
return $this->relationshipUrl($field, $options);
206214
}
207215

208216
/**

src/Routing/ResourceRegistrar.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,10 +168,12 @@ private function contentNegotiation(): string
168168
*/
169169
private function attributes(): array
170170
{
171+
$prefix = $this->options['resource_uri'] ?? $this->resourceType;
172+
171173
return [
172174
'middleware' => $this->middleware(),
173175
'as' => "{$this->resourceType}.",
174-
'prefix' => $this->resourceType,
176+
'prefix' => $prefix,
175177
];
176178
}
177179

src/Routing/ResourceRegistration.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,19 @@ public function authorizer(string $authorizer): self
102102
return $this->middleware("json-api.auth:{$authorizer}");
103103
}
104104

105+
/**
106+
* Set the URI fragment, if different from the resource type.
107+
*
108+
* @param string $uri
109+
* @return $this
110+
*/
111+
public function uri(string $uri): self
112+
{
113+
$this->options['resource_uri'] = $uri;
114+
115+
return $this;
116+
}
117+
105118
/**
106119
* Add middleware.
107120
*

tests/lib/Integration/Eloquent/MorphToManyTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,8 @@ public function testReadRelated()
238238

239239
$post->tags()->sync($tags);
240240

241-
$this->doReadRelated($post, 'tags')
241+
$this->withoutExceptionHandling()
242+
->doReadRelated($post, 'tags')
242243
->willSeeType('tags')
243244
->assertFetchedMany($expected);
244245
}

tests/lib/Integration/ErrorsTest.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
use Illuminate\Validation\ValidationException;
3434
use Neomerx\JsonApi\Document\Error as NeomerxError;
3535
use Neomerx\JsonApi\Exceptions\JsonApiException as NeomerxException;
36+
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
3637
use Symfony\Component\HttpKernel\Exception\HttpException;
3738

3839
class ErrorsTest extends TestCase
@@ -354,6 +355,25 @@ public function testTokenMismatch()
354355
->assertHeader('Content-Type', 'application/vnd.api+json');
355356
}
356357

358+
/**
359+
* The Symfony bad request exception does not implement the HTTP exception
360+
* interface, so we need to ensure we handle it.
361+
*/
362+
public function testBadRequestException(): void
363+
{
364+
$ex = new BadRequestException('The request format is bad.');
365+
366+
$expected = [
367+
'title' => 'Bad Request',
368+
'detail' => 'The request format is bad.',
369+
'status' => '400',
370+
];
371+
372+
$this->request($ex)
373+
->assertExactErrorStatus($expected)
374+
->assertHeader('Content-Type', 'application/vnd.api+json');
375+
}
376+
357377
/**
358378
* If we get a Laravel validation exception we need to convert this to
359379
* JSON API errors.

0 commit comments

Comments
 (0)
0