185 lines
5.7 KiB
PHP
185 lines
5.7 KiB
PHP
<?php
|
|
|
|
/*
|
|
* This file is part of tobyz/json-api-server.
|
|
*
|
|
* (c) Toby Zerner <toby.zerner@gmail.com>
|
|
*
|
|
* For the full copyright and license information, please view the LICENSE
|
|
* file that was distributed with this source code.
|
|
*/
|
|
|
|
namespace Tobyz\JsonApiServer\Extension;
|
|
|
|
use Nyholm\Psr7\Uri;
|
|
use Psr\Http\Message\ResponseInterface as Response;
|
|
use Tobyz\JsonApiServer\Context;
|
|
use Tobyz\JsonApiServer\Endpoint;
|
|
use Tobyz\JsonApiServer\Endpoint\Concerns\FindsResources;
|
|
use Tobyz\JsonApiServer\Exception\BadRequestException;
|
|
use Tobyz\JsonApiServer\Exception\MethodNotAllowedException;
|
|
use Tobyz\JsonApiServer\Exception\NotImplementedException;
|
|
|
|
use function Tobyz\JsonApiServer\json_api_response;
|
|
|
|
final class Atomic extends Extension
|
|
{
|
|
use FindsResources;
|
|
|
|
private $path;
|
|
|
|
public function __construct(string $path = 'operations')
|
|
{
|
|
$this->path = $path;
|
|
}
|
|
|
|
public function uri(): string
|
|
{
|
|
return 'https://jsonapi.org/ext/atomic';
|
|
}
|
|
|
|
public function handle(Context $context): ?Response
|
|
{
|
|
if ($context->getPath() !== '/operations') {
|
|
return null;
|
|
}
|
|
|
|
$request = $context->getRequest();
|
|
|
|
if ($request->getMethod() !== 'POST') {
|
|
throw new MethodNotAllowedException();
|
|
}
|
|
|
|
$body = $context->getBody();
|
|
$operations = $body['atomic:operations'] ?? null;
|
|
|
|
if (! is_array($operations)) {
|
|
throw new BadRequestException('atomic:operations must be an array of operation objects');
|
|
}
|
|
|
|
$results = [];
|
|
$lids = [];
|
|
|
|
foreach ($operations as $i => $operation) {
|
|
switch ($operation['op'] ?? null) {
|
|
case 'add':
|
|
$response = $this->add($context, $operation, $lids);
|
|
break;
|
|
|
|
case 'update':
|
|
$response = $this->update($context, $operation, $lids);
|
|
break;
|
|
|
|
case 'remove':
|
|
$response = $this->remove($context, $operation, $lids);
|
|
break;
|
|
|
|
default:
|
|
throw (new BadRequestException('Invalid operation'))->setSourcePointer("/atomic:operations/$i");
|
|
}
|
|
|
|
$results[] = json_decode($response->getBody(), true);
|
|
}
|
|
|
|
return json_api_response(
|
|
['atomic:results' => $results]
|
|
);
|
|
}
|
|
|
|
private function add(Context $context, array $operation, array &$lids): Response
|
|
{
|
|
// TODO: support href and ref
|
|
if (isset($operation['href']) || isset($operation['ref'])) {
|
|
throw new NotImplementedException('href and ref are not currently supported');
|
|
}
|
|
|
|
$type = $operation['data']['type'];
|
|
$resourceType = $context->getApi()->getResourceType($type);
|
|
|
|
$request = $context->getRequest()
|
|
->withMethod('POST')
|
|
->withUri(new Uri("/$type"))
|
|
->withQueryParams($operation['params'] ?? [])
|
|
->withParsedBody(array_diff_key($this->replaceLids($operation, $lids), ['op', 'href', 'ref', 'params']));
|
|
|
|
$context = $context->withRequest($request);
|
|
|
|
$response = (new Endpoint\Create())->handle($context, $resourceType);
|
|
|
|
if ($lid = $operation['data']['lid'] ?? null) {
|
|
if ($id = json_decode($response->getBody(), true)['data']['id'] ?? null) {
|
|
$lids[$lid] = $id;
|
|
}
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
|
|
private function update(Context $context, array $operation, array $lids): Response
|
|
{
|
|
// TODO: support href and ref
|
|
if (isset($operation['href']) || isset($operation['ref'])) {
|
|
throw new NotImplementedException('href and ref are not currently supported');
|
|
}
|
|
|
|
$operation = $this->replaceLids($operation, $lids);
|
|
$type = $operation['data']['type'];
|
|
$id = $operation['data']['id'];
|
|
$resourceType = $context->getApi()->getResourceType($type);
|
|
|
|
$request = $context->getRequest()
|
|
->withMethod('PATCH')
|
|
->withUri(new Uri("/$type/$id"))
|
|
->withQueryParams($operation['params'] ?? [])
|
|
->withParsedBody(array_diff_key($operation, ['op', 'href', 'ref', 'params']));
|
|
|
|
$context = $context->withRequest($request);
|
|
|
|
$model = $this->findResource($resourceType, $id, $context);
|
|
|
|
return (new Endpoint\Update())->handle($context, $resourceType, $model);
|
|
}
|
|
|
|
private function remove(Context $context, array $operation, array $lids): Response
|
|
{
|
|
// TODO: support href
|
|
if (isset($operation['href'])) {
|
|
throw new NotImplementedException('href is not currently supported');
|
|
}
|
|
|
|
$operation = $this->replaceLids($operation, $lids);
|
|
$type = $operation['ref']['type'];
|
|
$id = $operation['ref']['id'];
|
|
$resourceType = $context->getApi()->getResourceType($type);
|
|
|
|
$request = $context->getRequest()
|
|
->withMethod('DELETE')
|
|
->withUri(new Uri("/$type/$id"))
|
|
->withQueryParams($operation['params'] ?? [])
|
|
->withParsedBody(array_diff_key($operation, ['op', 'href', 'ref', 'params']));
|
|
|
|
$context = $context->withRequest($request);
|
|
|
|
$model = $this->findResource($resourceType, $id, $context);
|
|
|
|
return (new Endpoint\Delete())->handle($context, $resourceType, $model);
|
|
}
|
|
|
|
private function replaceLids(array &$array, array $lids): array
|
|
{
|
|
foreach ($array as $k => &$v) {
|
|
if ($k === 'lid' && isset($lids[$v])) {
|
|
$array['id'] = $lids[$v];
|
|
unset($array['lid']);
|
|
continue;
|
|
}
|
|
|
|
if (is_array($v)) {
|
|
$v = $this->replaceLids($v, $lids);
|
|
}
|
|
}
|
|
|
|
return $array;
|
|
}
|
|
}
|