Skip to content

Commit

Permalink
feat: add new HTTP actions implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
lindyhopchris committed Aug 26, 2023
1 parent 217a3bf commit 0cb189e
Show file tree
Hide file tree
Showing 42 changed files with 976 additions and 554 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: Tests

on:
push:
branches: [ main, develop, 3.x ]
branches: [ main, develop, 4.x ]
pull_request:
branches: [ main, develop, 3.x ]
branches: [ main, develop, 4.x ]

jobs:
build:
Expand Down
17 changes: 9 additions & 8 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@
"require": {
"php": "^8.1",
"ext-json": "*",
"laravel-json-api/core": "^3.2",
"laravel-json-api/eloquent": "^3.0",
"laravel-json-api/encoder-neomerx": "^3.0",
"laravel-json-api/exceptions": "^2.0",
"laravel-json-api/spec": "^2.0",
"laravel-json-api/validation": "^3.0",
"laravel-json-api/core": "^4.0",
"laravel-json-api/eloquent": "^4.0",
"laravel-json-api/encoder-neomerx": "^4.0",
"laravel-json-api/exceptions": "^3.0",
"laravel-json-api/spec": "^3.0",
"laravel-json-api/validation": "^4.0",
"laravel/framework": "^10.0"
},
"require-dev": {
Expand All @@ -53,7 +53,8 @@
},
"extra": {
"branch-alias": {
"dev-develop": "3.x-dev"
"dev-develop": "3.x-dev",
"dev-4.x": "4.x-dev"
},
"laravel": {
"aliases": {
Expand All @@ -65,7 +66,7 @@
]
}
},
"minimum-stability": "stable",
"minimum-stability": "dev",
"prefer-stable": true,
"config": {
"sort-packages": true
Expand Down
17 changes: 12 additions & 5 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false"
beStrictAboutTestsThatDoNotTestAnything="true" bootstrap="vendor/autoload.php" colors="true"
processIsolation="false" stopOnError="false" stopOnFailure="false"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.2/phpunit.xsd" cacheDirectory=".phpunit.cache"
backupStaticProperties="false">
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
backupGlobals="false"
beStrictAboutTestsThatDoNotTestAnything="true"
bootstrap="vendor/autoload.php"
colors="true"
processIsolation="false"
stopOnError="false"
stopOnFailure="false"
cacheDirectory=".phpunit.cache"
backupStaticProperties="false"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.2/phpunit.xsd"
>
<coverage/>
<testsuites>
<testsuite name="Unit">
Expand Down
62 changes: 14 additions & 48 deletions src/Http/Controllers/Actions/AttachRelationship.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,61 +19,27 @@

namespace LaravelJsonApi\Laravel\Http\Controllers\Actions;

use Illuminate\Contracts\Support\Responsable;
use Illuminate\Http\Response;
use LaravelJsonApi\Contracts\Routing\Route;
use LaravelJsonApi\Contracts\Store\Store as StoreContract;
use LaravelJsonApi\Core\Support\Str;
use LaravelJsonApi\Laravel\Http\Requests\ResourceQuery;
use LaravelJsonApi\Laravel\Http\Requests\ResourceRequest;
use LogicException;
use LaravelJsonApi\Contracts\Http\Actions\AttachRelationship as AttachRelationshipContract;
use LaravelJsonApi\Core\Responses\NoContentResponse;
use LaravelJsonApi\Core\Responses\RelationshipResponse;
use LaravelJsonApi\Laravel\Http\Requests\JsonApiRequest;

trait AttachRelationship
{

/**
* Attach records to a to-many relationship.
*
* @param Route $route
* @param StoreContract $store
* @return Response|Responsable
* @param JsonApiRequest $request
* @param AttachRelationshipContract $action
* @return RelationshipResponse|NoContentResponse
*/
public function attachRelationship(Route $route, StoreContract $store)
public function attachRelationship(
JsonApiRequest $request,
AttachRelationshipContract $action,
): RelationshipResponse|NoContentResponse
{
$relation = $route
->schema()
->relationship($fieldName = $route->fieldName());

if (!$relation->toMany()) {
throw new LogicException('Expecting a to-many relation for an attach action.');
}

$request = ResourceRequest::forResource(
$resourceType = $route->resourceType()
);

$query = ResourceQuery::queryMany($relation->inverse());

$model = $route->model();
$response = null;

if (method_exists($this, $hook = 'attaching' . Str::classify($fieldName))) {
$response = $this->{$hook}($model, $request, $query);
}

if ($response) {
return $response;
}

$result = $store
->modifyToMany($resourceType, $model, $fieldName)
->withRequest($query)
->attach($request->validatedForRelation());

if (method_exists($this, $hook = 'attached' . Str::classify($fieldName))) {
$response = $this->{$hook}($model, $result, $request, $query);
}

return $response ?: response('', Response::HTTP_NO_CONTENT);
return $action
->withHooks($this)
->execute($request);
}
}
79 changes: 9 additions & 70 deletions src/Http/Controllers/Actions/Destroy.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,87 +19,26 @@

namespace LaravelJsonApi\Laravel\Http\Controllers\Actions;

use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Contracts\Support\Responsable;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
use LaravelJsonApi\Contracts\Routing\Route;
use LaravelJsonApi\Contracts\Store\Store as StoreContract;
use LaravelJsonApi\Laravel\Exceptions\HttpNotAcceptableException;
use LaravelJsonApi\Laravel\Http\Requests\ResourceRequest;
use LaravelJsonApi\Contracts\Http\Actions\Destroy as DestroyContract;
use LaravelJsonApi\Laravel\Http\Requests\JsonApiRequest;
use Symfony\Component\HttpFoundation\Response;

trait Destroy
{

/**
* Destroy a resource.
*
* @param Route $route
* @param StoreContract $store
* @param JsonApiRequest $request
* @param DestroyContract $action
* @return Response|Responsable
* @throws AuthenticationException|AuthorizationException|HttpNotAcceptableException
*/
public function destroy(Route $route, StoreContract $store)
public function destroy(JsonApiRequest $request, DestroyContract $action): Responsable|Response
{
/**
* As we do not have a query request class for a delete request,
* we need to manually check that the request Accept header
* is the JSON:API media type.
*/
$acceptable = false;

foreach (request()->getAcceptableContentTypes() as $contentType) {
if ($contentType === ResourceRequest::JSON_API_MEDIA_TYPE) {
$acceptable = true;
break;
}
}

throw_unless($acceptable, new HttpNotAcceptableException());

$request = ResourceRequest::forResourceIfExists(
$resourceType = $route->resourceType()
);

$model = $route->model();

/**
* The resource request class is optional for deleting,
* as delete validation is optional. However, if we do not have
* a resource request then the action will not have been authorized.
* So we need to trigger authorization in this case.
*/
if (!$request) {
$check = $route->authorizer()->destroy(
$request = \request(),
$model,
);

throw_if(false === $check && Auth::guest(), new AuthenticationException());
throw_if(false === $check, new AuthorizationException());
}

$response = null;

if (method_exists($this, 'deleting')) {
$response = $this->deleting($model, $request);
}

if ($response) {
return $response;
}

$store->delete(
$resourceType,
$route->modelOrResourceId()
);

if (method_exists($this, 'deleted')) {
$response = $this->deleted($model, $request);
}

return $response ?: response(null, Response::HTTP_NO_CONTENT);
return $action
->withHooks($this)
->execute($request);
}

}
64 changes: 15 additions & 49 deletions src/Http/Controllers/Actions/DetachRelationship.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,61 +19,27 @@

namespace LaravelJsonApi\Laravel\Http\Controllers\Actions;

use Illuminate\Contracts\Support\Responsable;
use Illuminate\Http\Response;
use LaravelJsonApi\Contracts\Routing\Route;
use LaravelJsonApi\Contracts\Store\Store as StoreContract;
use LaravelJsonApi\Core\Support\Str;
use LaravelJsonApi\Laravel\Http\Requests\ResourceQuery;
use LaravelJsonApi\Laravel\Http\Requests\ResourceRequest;
use LogicException;
use LaravelJsonApi\Contracts\Http\Actions\DetachRelationship as DetachRelationshipContract;
use LaravelJsonApi\Core\Responses\NoContentResponse;
use LaravelJsonApi\Core\Responses\RelationshipResponse;
use LaravelJsonApi\Laravel\Http\Requests\JsonApiRequest;

trait DetachRelationship
{

/**
* Detach records to a has-many relationship.
* Detach records from a to-many relationship.
*
* @param Route $route
* @param StoreContract $store
* @return Response|Responsable
* @param JsonApiRequest $request
* @param DetachRelationshipContract $action
* @return RelationshipResponse|NoContentResponse
*/
public function detachRelationship(Route $route, StoreContract $store)
public function detachRelationship(
JsonApiRequest $request,
DetachRelationshipContract $action,
): RelationshipResponse|NoContentResponse
{
$relation = $route
->schema()
->relationship($fieldName = $route->fieldName());

if (!$relation->toMany()) {
throw new LogicException('Expecting a to-many relation for an attach action.');
}

$request = ResourceRequest::forResource(
$resourceType = $route->resourceType()
);

$query = ResourceQuery::queryMany($relation->inverse());

$model = $route->model();
$response = null;

if (method_exists($this, $hook = 'detaching' . Str::classify($fieldName))) {
$response = $this->{$hook}($model, $request, $query);
}

if ($response) {
return $response;
}

$result = $store
->modifyToMany($resourceType, $model, $fieldName)
->withRequest($query)
->detach($request->validatedForRelation());

if (method_exists($this, $hook = 'detached' . Str::classify($fieldName))) {
$response = $this->{$hook}($model, $result, $request, $query);
}

return $response ?: response('', Response::HTTP_NO_CONTENT);
return $action
->withHooks($this)
->execute($request);
}
}
45 changes: 10 additions & 35 deletions src/Http/Controllers/Actions/FetchMany.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,48 +19,23 @@

namespace LaravelJsonApi\Laravel\Http\Controllers\Actions;

use Illuminate\Contracts\Support\Responsable;
use Illuminate\Http\Response;
use LaravelJsonApi\Contracts\Routing\Route;
use LaravelJsonApi\Contracts\Store\Store as StoreContract;
use LaravelJsonApi\Contracts\Http\Actions\FetchMany as FetchManyContract;
use LaravelJsonApi\Core\Responses\DataResponse;
use LaravelJsonApi\Laravel\Http\Requests\ResourceQuery;
use LaravelJsonApi\Laravel\Http\Requests\JsonApiRequest;

trait FetchMany
{

/**
* Fetch zero to many JSON API resources.
* Fetch zero-to-many JSON:API resources.
*
* @param Route $route
* @param StoreContract $store
* @return Responsable|Response
* @param JsonApiRequest $request
* @param FetchManyContract $action
* @return DataResponse
*/
public function index(Route $route, StoreContract $store)
public function index(JsonApiRequest $request, FetchManyContract $action): DataResponse
{
$request = ResourceQuery::queryMany(
$resourceType = $route->resourceType()
);

$response = null;

if (method_exists($this, 'searching')) {
$response = $this->searching($request);
}

if ($response) {
return $response;
}

$data = $store
->queryAll($resourceType)
->withRequest($request)
->firstOrPaginate($request->page());

if (method_exists($this, 'searched')) {
$response = $this->searched($data, $request);
}

return $response ?: DataResponse::make($data)->withQueryParameters($request);
return $action
->withHooks($this)
->execute($request);
}
}
Loading

0 comments on commit 0cb189e

Please sign in to comment.