Skip to content

Commit 0cb189e

Browse files
committed
feat: add new HTTP actions implementation
1 parent 217a3bf commit 0cb189e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+976
-554
lines changed

.github/workflows/tests.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ name: Tests
22

33
on:
44
push:
5-
branches: [ main, develop, 3.x ]
5+
branches: [ main, develop, 4.x ]
66
pull_request:
7-
branches: [ main, develop, 3.x ]
7+
branches: [ main, develop, 4.x ]
88

99
jobs:
1010
build:

composer.json

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@
2525
"require": {
2626
"php": "^8.1",
2727
"ext-json": "*",
28-
"laravel-json-api/core": "^3.2",
29-
"laravel-json-api/eloquent": "^3.0",
30-
"laravel-json-api/encoder-neomerx": "^3.0",
31-
"laravel-json-api/exceptions": "^2.0",
32-
"laravel-json-api/spec": "^2.0",
33-
"laravel-json-api/validation": "^3.0",
28+
"laravel-json-api/core": "^4.0",
29+
"laravel-json-api/eloquent": "^4.0",
30+
"laravel-json-api/encoder-neomerx": "^4.0",
31+
"laravel-json-api/exceptions": "^3.0",
32+
"laravel-json-api/spec": "^3.0",
33+
"laravel-json-api/validation": "^4.0",
3434
"laravel/framework": "^10.0"
3535
},
3636
"require-dev": {
@@ -53,7 +53,8 @@
5353
},
5454
"extra": {
5555
"branch-alias": {
56-
"dev-develop": "3.x-dev"
56+
"dev-develop": "3.x-dev",
57+
"dev-4.x": "4.x-dev"
5758
},
5859
"laravel": {
5960
"aliases": {
@@ -65,7 +66,7 @@
6566
]
6667
}
6768
},
68-
"minimum-stability": "stable",
69+
"minimum-stability": "dev",
6970
"prefer-stable": true,
7071
"config": {
7172
"sort-packages": true

phpunit.xml

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false"
3-
beStrictAboutTestsThatDoNotTestAnything="true" bootstrap="vendor/autoload.php" colors="true"
4-
processIsolation="false" stopOnError="false" stopOnFailure="false"
5-
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.2/phpunit.xsd" cacheDirectory=".phpunit.cache"
6-
backupStaticProperties="false">
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
backupGlobals="false"
4+
beStrictAboutTestsThatDoNotTestAnything="true"
5+
bootstrap="vendor/autoload.php"
6+
colors="true"
7+
processIsolation="false"
8+
stopOnError="false"
9+
stopOnFailure="false"
10+
cacheDirectory=".phpunit.cache"
11+
backupStaticProperties="false"
12+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.2/phpunit.xsd"
13+
>
714
<coverage/>
815
<testsuites>
916
<testsuite name="Unit">

src/Http/Controllers/Actions/AttachRelationship.php

Lines changed: 14 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -19,61 +19,27 @@
1919

2020
namespace LaravelJsonApi\Laravel\Http\Controllers\Actions;
2121

22-
use Illuminate\Contracts\Support\Responsable;
23-
use Illuminate\Http\Response;
24-
use LaravelJsonApi\Contracts\Routing\Route;
25-
use LaravelJsonApi\Contracts\Store\Store as StoreContract;
26-
use LaravelJsonApi\Core\Support\Str;
27-
use LaravelJsonApi\Laravel\Http\Requests\ResourceQuery;
28-
use LaravelJsonApi\Laravel\Http\Requests\ResourceRequest;
29-
use LogicException;
22+
use LaravelJsonApi\Contracts\Http\Actions\AttachRelationship as AttachRelationshipContract;
23+
use LaravelJsonApi\Core\Responses\NoContentResponse;
24+
use LaravelJsonApi\Core\Responses\RelationshipResponse;
25+
use LaravelJsonApi\Laravel\Http\Requests\JsonApiRequest;
3026

3127
trait AttachRelationship
3228
{
33-
3429
/**
3530
* Attach records to a to-many relationship.
3631
*
37-
* @param Route $route
38-
* @param StoreContract $store
39-
* @return Response|Responsable
32+
* @param JsonApiRequest $request
33+
* @param AttachRelationshipContract $action
34+
* @return RelationshipResponse|NoContentResponse
4035
*/
41-
public function attachRelationship(Route $route, StoreContract $store)
36+
public function attachRelationship(
37+
JsonApiRequest $request,
38+
AttachRelationshipContract $action,
39+
): RelationshipResponse|NoContentResponse
4240
{
43-
$relation = $route
44-
->schema()
45-
->relationship($fieldName = $route->fieldName());
46-
47-
if (!$relation->toMany()) {
48-
throw new LogicException('Expecting a to-many relation for an attach action.');
49-
}
50-
51-
$request = ResourceRequest::forResource(
52-
$resourceType = $route->resourceType()
53-
);
54-
55-
$query = ResourceQuery::queryMany($relation->inverse());
56-
57-
$model = $route->model();
58-
$response = null;
59-
60-
if (method_exists($this, $hook = 'attaching' . Str::classify($fieldName))) {
61-
$response = $this->{$hook}($model, $request, $query);
62-
}
63-
64-
if ($response) {
65-
return $response;
66-
}
67-
68-
$result = $store
69-
->modifyToMany($resourceType, $model, $fieldName)
70-
->withRequest($query)
71-
->attach($request->validatedForRelation());
72-
73-
if (method_exists($this, $hook = 'attached' . Str::classify($fieldName))) {
74-
$response = $this->{$hook}($model, $result, $request, $query);
75-
}
76-
77-
return $response ?: response('', Response::HTTP_NO_CONTENT);
41+
return $action
42+
->withHooks($this)
43+
->execute($request);
7844
}
7945
}

src/Http/Controllers/Actions/Destroy.php

Lines changed: 9 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -19,87 +19,26 @@
1919

2020
namespace LaravelJsonApi\Laravel\Http\Controllers\Actions;
2121

22-
use Illuminate\Auth\Access\AuthorizationException;
23-
use Illuminate\Auth\AuthenticationException;
2422
use Illuminate\Contracts\Support\Responsable;
25-
use Illuminate\Http\Response;
26-
use Illuminate\Support\Facades\Auth;
27-
use LaravelJsonApi\Contracts\Routing\Route;
28-
use LaravelJsonApi\Contracts\Store\Store as StoreContract;
29-
use LaravelJsonApi\Laravel\Exceptions\HttpNotAcceptableException;
30-
use LaravelJsonApi\Laravel\Http\Requests\ResourceRequest;
23+
use LaravelJsonApi\Contracts\Http\Actions\Destroy as DestroyContract;
24+
use LaravelJsonApi\Laravel\Http\Requests\JsonApiRequest;
25+
use Symfony\Component\HttpFoundation\Response;
3126

3227
trait Destroy
3328
{
3429

3530
/**
3631
* Destroy a resource.
3732
*
38-
* @param Route $route
39-
* @param StoreContract $store
33+
* @param JsonApiRequest $request
34+
* @param DestroyContract $action
4035
* @return Response|Responsable
41-
* @throws AuthenticationException|AuthorizationException|HttpNotAcceptableException
4236
*/
43-
public function destroy(Route $route, StoreContract $store)
37+
public function destroy(JsonApiRequest $request, DestroyContract $action): Responsable|Response
4438
{
45-
/**
46-
* As we do not have a query request class for a delete request,
47-
* we need to manually check that the request Accept header
48-
* is the JSON:API media type.
49-
*/
50-
$acceptable = false;
51-
52-
foreach (request()->getAcceptableContentTypes() as $contentType) {
53-
if ($contentType === ResourceRequest::JSON_API_MEDIA_TYPE) {
54-
$acceptable = true;
55-
break;
56-
}
57-
}
58-
59-
throw_unless($acceptable, new HttpNotAcceptableException());
60-
61-
$request = ResourceRequest::forResourceIfExists(
62-
$resourceType = $route->resourceType()
63-
);
64-
65-
$model = $route->model();
66-
67-
/**
68-
* The resource request class is optional for deleting,
69-
* as delete validation is optional. However, if we do not have
70-
* a resource request then the action will not have been authorized.
71-
* So we need to trigger authorization in this case.
72-
*/
73-
if (!$request) {
74-
$check = $route->authorizer()->destroy(
75-
$request = \request(),
76-
$model,
77-
);
78-
79-
throw_if(false === $check && Auth::guest(), new AuthenticationException());
80-
throw_if(false === $check, new AuthorizationException());
81-
}
82-
83-
$response = null;
84-
85-
if (method_exists($this, 'deleting')) {
86-
$response = $this->deleting($model, $request);
87-
}
88-
89-
if ($response) {
90-
return $response;
91-
}
92-
93-
$store->delete(
94-
$resourceType,
95-
$route->modelOrResourceId()
96-
);
97-
98-
if (method_exists($this, 'deleted')) {
99-
$response = $this->deleted($model, $request);
100-
}
101-
102-
return $response ?: response(null, Response::HTTP_NO_CONTENT);
39+
return $action
40+
->withHooks($this)
41+
->execute($request);
10342
}
10443

10544
}

src/Http/Controllers/Actions/DetachRelationship.php

Lines changed: 15 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -19,61 +19,27 @@
1919

2020
namespace LaravelJsonApi\Laravel\Http\Controllers\Actions;
2121

22-
use Illuminate\Contracts\Support\Responsable;
23-
use Illuminate\Http\Response;
24-
use LaravelJsonApi\Contracts\Routing\Route;
25-
use LaravelJsonApi\Contracts\Store\Store as StoreContract;
26-
use LaravelJsonApi\Core\Support\Str;
27-
use LaravelJsonApi\Laravel\Http\Requests\ResourceQuery;
28-
use LaravelJsonApi\Laravel\Http\Requests\ResourceRequest;
29-
use LogicException;
22+
use LaravelJsonApi\Contracts\Http\Actions\DetachRelationship as DetachRelationshipContract;
23+
use LaravelJsonApi\Core\Responses\NoContentResponse;
24+
use LaravelJsonApi\Core\Responses\RelationshipResponse;
25+
use LaravelJsonApi\Laravel\Http\Requests\JsonApiRequest;
3026

3127
trait DetachRelationship
3228
{
33-
3429
/**
35-
* Detach records to a has-many relationship.
30+
* Detach records from a to-many relationship.
3631
*
37-
* @param Route $route
38-
* @param StoreContract $store
39-
* @return Response|Responsable
32+
* @param JsonApiRequest $request
33+
* @param DetachRelationshipContract $action
34+
* @return RelationshipResponse|NoContentResponse
4035
*/
41-
public function detachRelationship(Route $route, StoreContract $store)
36+
public function detachRelationship(
37+
JsonApiRequest $request,
38+
DetachRelationshipContract $action,
39+
): RelationshipResponse|NoContentResponse
4240
{
43-
$relation = $route
44-
->schema()
45-
->relationship($fieldName = $route->fieldName());
46-
47-
if (!$relation->toMany()) {
48-
throw new LogicException('Expecting a to-many relation for an attach action.');
49-
}
50-
51-
$request = ResourceRequest::forResource(
52-
$resourceType = $route->resourceType()
53-
);
54-
55-
$query = ResourceQuery::queryMany($relation->inverse());
56-
57-
$model = $route->model();
58-
$response = null;
59-
60-
if (method_exists($this, $hook = 'detaching' . Str::classify($fieldName))) {
61-
$response = $this->{$hook}($model, $request, $query);
62-
}
63-
64-
if ($response) {
65-
return $response;
66-
}
67-
68-
$result = $store
69-
->modifyToMany($resourceType, $model, $fieldName)
70-
->withRequest($query)
71-
->detach($request->validatedForRelation());
72-
73-
if (method_exists($this, $hook = 'detached' . Str::classify($fieldName))) {
74-
$response = $this->{$hook}($model, $result, $request, $query);
75-
}
76-
77-
return $response ?: response('', Response::HTTP_NO_CONTENT);
41+
return $action
42+
->withHooks($this)
43+
->execute($request);
7844
}
7945
}

src/Http/Controllers/Actions/FetchMany.php

Lines changed: 10 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -19,48 +19,23 @@
1919

2020
namespace LaravelJsonApi\Laravel\Http\Controllers\Actions;
2121

22-
use Illuminate\Contracts\Support\Responsable;
23-
use Illuminate\Http\Response;
24-
use LaravelJsonApi\Contracts\Routing\Route;
25-
use LaravelJsonApi\Contracts\Store\Store as StoreContract;
22+
use LaravelJsonApi\Contracts\Http\Actions\FetchMany as FetchManyContract;
2623
use LaravelJsonApi\Core\Responses\DataResponse;
27-
use LaravelJsonApi\Laravel\Http\Requests\ResourceQuery;
24+
use LaravelJsonApi\Laravel\Http\Requests\JsonApiRequest;
2825

2926
trait FetchMany
3027
{
31-
3228
/**
33-
* Fetch zero to many JSON API resources.
29+
* Fetch zero-to-many JSON:API resources.
3430
*
35-
* @param Route $route
36-
* @param StoreContract $store
37-
* @return Responsable|Response
31+
* @param JsonApiRequest $request
32+
* @param FetchManyContract $action
33+
* @return DataResponse
3834
*/
39-
public function index(Route $route, StoreContract $store)
35+
public function index(JsonApiRequest $request, FetchManyContract $action): DataResponse
4036
{
41-
$request = ResourceQuery::queryMany(
42-
$resourceType = $route->resourceType()
43-
);
44-
45-
$response = null;
46-
47-
if (method_exists($this, 'searching')) {
48-
$response = $this->searching($request);
49-
}
50-
51-
if ($response) {
52-
return $response;
53-
}
54-
55-
$data = $store
56-
->queryAll($resourceType)
57-
->withRequest($request)
58-
->firstOrPaginate($request->page());
59-
60-
if (method_exists($this, 'searched')) {
61-
$response = $this->searched($data, $request);
62-
}
63-
64-
return $response ?: DataResponse::make($data)->withQueryParameters($request);
37+
return $action
38+
->withHooks($this)
39+
->execute($request);
6540
}
6641
}

0 commit comments

Comments
 (0)