Skip to content

Commit 19699dd

Browse files
committed
Merge branch 'release/1.0.0-alpha.3' into main
2 parents d094eb7 + 937641e commit 19699dd

File tree

25 files changed

+1173
-208
lines changed

25 files changed

+1173
-208
lines changed

CHANGELOG.md

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

6+
## [1.0.0-alpha.3] - 2021-02-09
7+
8+
### Added
9+
10+
- [#12](https://github.com/laravel-json-api/laravel/pull/12) Can now register routes for custom actions on a resource,
11+
using the `actions()` helper method when registering resources. See the PR for examples.
12+
- The `JsonApiController` now has the Laravel `AuthorizesRequests`, `DispatchesJobs` and `ValidatesRequests` traits
13+
applied.
14+
- [#6](https://github.com/laravel-json-api/laravel/issues/6) Resource class can now use conditional fields in their
15+
relationships. This works in the same way as conditional attributes: the resource's `when()` and `mergeWhen()` method
16+
should be used to add conditional relationships.
17+
- [#13](https://github.com/laravel-json-api/laravel/issues/13) Added French translations for JSON:API errors generated
18+
by specification parsing and resource/query parameter validation.
19+
- [#7](https://github.com/laravel-json-api/laravel/issues/7) Eloquent schemas now support default eager loading via
20+
their `$with` property.
21+
- [#15](https://github.com/laravel-json-api/laravel/issues/15) When parsing a JSON:API document for compliance with the
22+
specification, the client will now receive a clearer error message if they submit a *to-one* relationship object for a
23+
*to-many* relationship (and vice-versa).
24+
25+
### Changed
26+
27+
- [#2](https://github.com/laravel-json-api/laravel/issues/2) **BREAKING** Improved the extraction of existing resource
28+
field values when constructing validation data for update requests:
29+
- The `existingAttributes()` and `existingRelationships()` methods on the resource request class has been removed.
30+
If you need to modify the existing values before the client values are merged, implement the `withExisting()`
31+
method instead. This receives the model and its JSON representation (as an array).
32+
- The `mustValidate()` method must now be called on a schema relationship field. (Previously this was on the
33+
resource relation.) By default, belongs-to and morph-to relations will be included when extracting existing
34+
values; all other relations will not. Use the `mustValidate()` or `notValidated()` method on the schema relation
35+
to alter whether a relation is included in the extracted values.
36+
637
## [1.0.0-alpha.2] - 2021-02-02
738

839
### Added

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
![Tests](https://github.com/laravel-json-api/laravel/workflows/Tests/badge.svg)
2+
13
# JSON:API for Web Artisans
24

35
Implement feature-rich [JSON:API](https://jsonapi.org) compliant APIs in your

composer.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@
2525
"require": {
2626
"php": "^7.4|^8.0",
2727
"ext-json": "*",
28-
"laravel-json-api/core": "^1.0.0-alpha.2",
29-
"laravel-json-api/eloquent": "^1.0.0-alpha.2",
30-
"laravel-json-api/encoder-neomerx": "^1.0.0-alpha.2",
31-
"laravel-json-api/exceptions": "^1.0.0-alpha.1",
32-
"laravel-json-api/spec": "^1.0.0-alpha.2",
33-
"laravel-json-api/validation": "^1.0.0-alpha.2",
28+
"laravel-json-api/core": "^1.0.0-alpha.3",
29+
"laravel-json-api/eloquent": "^1.0.0-alpha.3",
30+
"laravel-json-api/encoder-neomerx": "^1.0.0-alpha.3",
31+
"laravel-json-api/exceptions": "^1.0.0-alpha.2",
32+
"laravel-json-api/spec": "^1.0.0-alpha.3",
33+
"laravel-json-api/validation": "^1.0.0-alpha.3",
3434
"laravel/framework": "^8.0"
3535
},
3636
"require-dev": {
@@ -65,7 +65,7 @@
6565
]
6666
}
6767
},
68-
"minimum-stability": "dev",
68+
"minimum-stability": "stable",
6969
"prefer-stable": true,
7070
"config": {
7171
"sort-packages": true

src/Http/Controllers/JsonApiController.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,16 @@
1919

2020
namespace LaravelJsonApi\Laravel\Http\Controllers;
2121

22+
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
23+
use Illuminate\Foundation\Bus\DispatchesJobs;
24+
use Illuminate\Foundation\Validation\ValidatesRequests;
25+
2226
class JsonApiController
2327
{
2428

29+
/**
30+
* JSON:API Actions
31+
*/
2532
use Actions\FetchMany;
2633
use Actions\FetchOne;
2734
use Actions\Store;
@@ -33,4 +40,11 @@ class JsonApiController
3340
use Actions\AttachRelationship;
3441
use Actions\DetachRelationship;
3542

43+
/**
44+
* Laravel Controller Traits
45+
*/
46+
use AuthorizesRequests;
47+
use DispatchesJobs;
48+
use ValidatesRequests;
49+
3650
}

src/Http/Requests/ResourceRequest.php

Lines changed: 34 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,10 @@
2424
use Illuminate\Database\Eloquent\Model;
2525
use Illuminate\Http\Response;
2626
use LaravelJsonApi\Contracts\Auth\Authorizer;
27-
use LaravelJsonApi\Contracts\Resources\Container as ResourceContainer;
27+
use LaravelJsonApi\Contracts\Schema\Relation;
2828
use LaravelJsonApi\Core\Document\ResourceObject;
2929
use LaravelJsonApi\Core\Exceptions\JsonApiException;
30-
use LaravelJsonApi\Core\Resources\JsonApiResource;
31-
use LaravelJsonApi\Core\Resources\Relation;
30+
use LaravelJsonApi\Core\Query\IncludePaths;
3231
use LaravelJsonApi\Core\Support\Str;
3332
use LaravelJsonApi\Spec\RelationBuilder;
3433
use LaravelJsonApi\Spec\ResourceBuilder;
@@ -41,11 +40,6 @@
4140
class ResourceRequest extends FormRequest
4241
{
4342

44-
/**
45-
* @var bool
46-
*/
47-
protected bool $validateExisting = true;
48-
4943
/**
5044
* @var callable|null
5145
*/
@@ -353,43 +347,37 @@ protected function dataForCreate(array $document): array
353347
* @param Model|object $model
354348
* the model being updated.
355349
* @param array $document
356-
* the JSON API document to validate.
350+
* the JSON:API document to validate.
357351
* @return array
358352
*/
359353
protected function dataForUpdate(object $model, array $document): array
360354
{
361-
$data = $document['data'] ?? [];
362-
363-
if ($this->mustValidateExisting($model, $document)) {
364-
$data['attributes'] = $this->extractAttributes(
365-
$model,
366-
$data['attributes'] ?? []
367-
);
355+
$existing = $this->extractForUpdate($model);
368356

369-
$data['relationships'] = $this->extractRelationships(
370-
$model,
371-
$data['relationships'] ?? []
372-
);
357+
if (method_exists($this, 'withExisting')) {
358+
$existing = $this->withExisting($model, $existing) ?? $existing;
373359
}
374360

375-
return $data;
361+
return ResourceObject::fromArray($existing)->merge(
362+
$document['data']
363+
)->jsonSerialize();
376364
}
377365

378366
/**
379367
* Get validation data for modifying a relationship.
380368
*
381-
* @param Model|object $record
369+
* @param Model|object $model
382370
* @param string $fieldName
383371
* @param array $document
384372
* @return array
385373
*/
386-
protected function dataForRelationship(object $record, string $fieldName, array $document): array
374+
protected function dataForRelationship(object $model, string $fieldName, array $document): array
387375
{
388-
$resource = $this->resources()->create($record);
376+
$route = $this->jsonApi()->route();
389377

390378
return [
391-
'type' => $resource->type(),
392-
'id' => $resource->id(),
379+
'type' => $route->resourceType(),
380+
'id' => $route->resourceId(),
393381
'relationships' => [
394382
$fieldName => [
395383
'data' => $document['data'],
@@ -406,14 +394,7 @@ protected function dataForRelationship(object $record, string $fieldName, array
406394
*/
407395
protected function dataForDelete(object $record): array
408396
{
409-
$route = $this->jsonApi()->route();
410-
411-
$data = $this->dataForUpdate($record, [
412-
'data' => [
413-
'type' => $route->resourceType(),
414-
'id' => $route->resourceId(),
415-
],
416-
]);
397+
$data = $this->extractForUpdate($record);
417398

418399
if (method_exists($this, 'metaForDelete')) {
419400
$data['meta'] = (array) $this->metaForDelete($record);
@@ -423,80 +404,36 @@ protected function dataForDelete(object $record): array
423404
}
424405

425406
/**
426-
* Should existing resource values be provided to the validator for an update request?
407+
* Extract the existing values for the provided model.
427408
*
428-
* Child classes can overload this method if they need to programmatically work out
429-
* if existing values must be provided to the validator instance for an update request.
430-
*
431-
* @param Model|object $model
432-
* the model being updated
433-
* @param array $document
434-
* the JSON API document provided by the client.
435-
* @return bool
436-
*/
437-
protected function mustValidateExisting(object $model, array $document): bool
438-
{
439-
return false !== $this->validateExisting;
440-
}
441-
442-
/**
443-
* Extract existing attributes for the provided model.
444-
*
445-
* @param Model|object $model
446-
* @return iterable
409+
* @param object $model
410+
* @return array
447411
*/
448-
protected function existingAttributes(object $model): iterable
412+
private function extractForUpdate(object $model): array
449413
{
450-
$resource = $this->resources()->create($model);
414+
$encoder = $this->jsonApi()->server()->encoder();
451415

452-
return $resource->attributes($this);
416+
return $encoder
417+
->withRequest($this)
418+
->withIncludePaths($this->includePathsToExtract($model))
419+
->withResource($model)
420+
->toArray()['data'];
453421
}
454422

455423
/**
456-
* Extract existing relationships for the provided model.
424+
* Get the relationship paths that must be included when extracting the current field values.
457425
*
458-
* @param Model|object $model
459-
* @return iterable
426+
* @param object $model
427+
* @return IncludePaths
460428
*/
461-
protected function existingRelationships(object $model): iterable
429+
private function includePathsToExtract(object $model): IncludePaths
462430
{
463-
$resource = $this->resources()->create($model);
464-
465-
/** @var Relation $relationship */
466-
foreach ($resource->relationships($this) as $relationship) {
467-
if ($relationship->isValidated()) {
468-
yield $relationship->fieldName() => $relationship->data();
469-
}
470-
}
471-
}
431+
$paths = collect($this->schema()->relationships())
432+
->filter(fn (Relation $relation) => $relation->isValidated())
433+
->map(fn (Relation $relation) => $relation->name())
434+
->values();
472435

473-
/**
474-
* Extract attributes for a resource update.
475-
*
476-
* @param Model|object $model
477-
* @param array $new
478-
* @return array
479-
*/
480-
private function extractAttributes(object $model, array $new): array
481-
{
482-
return collect($this->existingAttributes($model))
483-
->merge($new)
484-
->all();
485-
}
486-
487-
/**
488-
* Extract relationships for a resource update.
489-
*
490-
* @param Model|object $model
491-
* @param array $new
492-
* @return array
493-
*/
494-
private function extractRelationships(object $model, array $new): array
495-
{
496-
return collect($this->existingRelationships($model))
497-
->map(fn($value) => $this->convertExistingRelationships($value))
498-
->merge($new)
499-
->all();
436+
return IncludePaths::fromArray($paths);
500437
}
501438

502439
/**
@@ -514,64 +451,6 @@ private function relationshipRules(): array
514451
->all();
515452
}
516453

517-
/**
518-
* Convert relationships returned by the `existingRelationships()` method.
519-
*
520-
* We support the method returning JSON API formatted relationships, e.g.:
521-
*
522-
* ```
523-
* return [
524-
* 'author' => [
525-
* 'data' => [
526-
* 'type' => 'users',
527-
* 'id' => (string) $record->author->getRouteKey(),
528-
* ],
529-
* ],
530-
* ];
531-
* ```
532-
*
533-
* Or this shorthand:
534-
*
535-
* ```php
536-
* return [
537-
* 'author' => $record->author,
538-
* ];
539-
* ```
540-
*
541-
* This method converts the shorthand into the JSON API formatted relationships.
542-
*
543-
* @param $value
544-
* @return array
545-
*/
546-
private function convertExistingRelationships($value)
547-
{
548-
if (is_array($value) && array_key_exists('data', $value)) {
549-
return $value;
550-
}
551-
552-
if (is_null($value)) {
553-
return ['data' => null];
554-
}
555-
556-
$value = $this->resources()->resolve($value);
557-
558-
if ($value instanceof JsonApiResource) {
559-
return [
560-
'data' => [
561-
'type' => $value->type(),
562-
'id' => $value->id(),
563-
],
564-
];
565-
}
566-
567-
return [
568-
'data' => collect($value)->map(fn(JsonApiResource $resource) => [
569-
'type' => $resource->type(),
570-
'id' => $resource->id()
571-
])->all(),
572-
];
573-
}
574-
575454
/**
576455
* Validate the JSON API document for a resource request.
577456
*
@@ -618,12 +497,4 @@ private function validateRelationshipDocument(): void
618497
}
619498
}
620499

621-
/**
622-
* @return ResourceContainer
623-
*/
624-
final protected function resources(): ResourceContainer
625-
{
626-
return $this->jsonApi()->server()->resources();
627-
}
628-
629500
}

0 commit comments

Comments
 (0)