- WIP on request/response handling
This commit is contained in:
parent
5d9c0f0dbf
commit
7e93e57515
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\Api;
|
||||
|
||||
use Psr\Http\Message\{ ResponseInterface, RequestInterface };
|
||||
|
||||
use Ulmus\Api\Attribute\Obj\Api\ApiAction;
|
||||
|
||||
interface ApiHandlerInterface
|
||||
{
|
||||
public function handleRequest(RequestInterface $request, ApiAction $attribute) : RequestInterface;
|
||||
public function handleResponse(ResponseInterface $response, ApiAction $attribute) : ResponseInterface;
|
||||
}
|
|
@ -8,6 +8,7 @@ use Psr\Http\Message\StreamInterface;
|
|||
use Psr\Http\Message\UriInterface;
|
||||
use Ulmus\Api\Attribute\Obj\Api\ApiAction;
|
||||
use Ulmus\Api\Common\MethodEnum;
|
||||
use Ulmus\Api\Stream\JsonStream;
|
||||
use Ulmus\Api\Stream\Stream;
|
||||
use Ulmus\Api\Request\JsonRequest;
|
||||
use Ulmus\Api\Request\Request;
|
||||
|
@ -28,56 +29,44 @@ class ApiRepository extends \Ulmus\Repository
|
|||
public function __construct(string $entity, string $alias = self::DEFAULT_ALIAS, ConnectionAdapter $adapter = null)
|
||||
{
|
||||
parent::__construct($entity, $alias, $adapter);
|
||||
|
||||
$this->client = $adapter->adapter()->connect();
|
||||
}
|
||||
|
||||
public function loadOne(): ?object
|
||||
{
|
||||
# Must manager URL here !
|
||||
$attribute = $this->getApiAttribute(Attribute\Obj\Api\Read::class);
|
||||
$response = $this->executeRequest(Attribute\Obj\Api\Read::class);
|
||||
|
||||
$request = $this->prepareRequest($this->buildRequestUrl($attribute->url), $attribute->method);
|
||||
|
||||
$request = $this->callApiRequestCallback($request, $attribute);
|
||||
|
||||
$this->lastResponse = $response = $this->client->fromRequest($request);
|
||||
|
||||
$response = $this->callApiResponseCallback($response, $attribute);
|
||||
|
||||
return $this->instanciateEntity()->fromArray($response->render());
|
||||
return $this->instanciateEntity()->fromArray($response->getParsedBody());
|
||||
}
|
||||
|
||||
public function loadAll() : EntityCollection
|
||||
{
|
||||
$attribute = $this->getApiAttribute(Attribute\Obj\Api\Collection::class);
|
||||
$response = $this->executeRequest(Attribute\Obj\Api\Collection::class);
|
||||
|
||||
$request = $this->prepareRequest($this->buildRequestUrl($attribute->url), $attribute->method);
|
||||
|
||||
$request = $this->callApiRequestCallback($request, $attribute);
|
||||
|
||||
$this->lastResponse = $response = $this->client->fromRequest($request);
|
||||
|
||||
$response = $this->callApiResponseCallback($response, $attribute);
|
||||
|
||||
return $this->instanciateEntityCollection()->fromArray($response->render());
|
||||
return $this->instanciateEntityCollection()->fromArray($response->getParsedBody());
|
||||
}
|
||||
|
||||
public function save(object|array $entity, ?array $fieldsAndValue = null, bool $replace = false): bool
|
||||
{
|
||||
$attribute = $this->getApiAttribute(Attribute\Obj\Api\Create::class);
|
||||
$response = $this->executeRequest(Attribute\Obj\Api\Create::class, $entity->entityLoadedDataset);
|
||||
|
||||
$request = $this->prepareRequest($this->buildRequestUrl($attribute->url), $attribute->method);
|
||||
$entity->fromArray($response->getParsedBody());
|
||||
|
||||
$this->callApiRequestCallback($request, $attribute);
|
||||
return $response->getStatusCode() === 200;
|
||||
}
|
||||
|
||||
$this->lastResponse = $response = $this->client->fromRequest($request);
|
||||
public function executeRequest(string $attributeClass, mixed $data = null) : ResponseInterface
|
||||
{
|
||||
$attribute = $this->getApiAttribute($attributeClass);
|
||||
|
||||
$ret = $this->callApiResponseCallback($response, $attribute);
|
||||
$request = $this->prepareRequest($this->buildRequestUrl($attribute->url), $attribute->method, $data);
|
||||
|
||||
$entity->fromArray($ret);
|
||||
$request = $this->callApiRequestCallback($request, $attribute);
|
||||
|
||||
return true;
|
||||
$this->lastResponse = $response = $this->adapter->adapter()->connect()->fromRequest($request);
|
||||
|
||||
$response = $this->callApiResponseCallback($response, $attribute);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function bindUrl(string $parameter, mixed $value) : self
|
||||
|
@ -110,9 +99,9 @@ class ApiRepository extends \Ulmus\Repository
|
|||
}
|
||||
|
||||
if ( array_key_exists($variable, $arguments) ) {
|
||||
$value = $arguments[ $variable ];
|
||||
$value = $arguments[$variable];
|
||||
|
||||
unset($arguments[ $variable ]);
|
||||
unset($arguments[$variable]);
|
||||
}
|
||||
else {
|
||||
if ($default ?? false) {
|
||||
|
@ -132,9 +121,9 @@ class ApiRepository extends \Ulmus\Repository
|
|||
return $route;
|
||||
}
|
||||
|
||||
protected function prepareRequest(string|UriInterface $uri, MethodEnum $method, null|string $body = null, array $headers = []) : RequestInterface
|
||||
protected function prepareRequest(string|UriInterface $uri, MethodEnum $method, mixed $body = null, array $headers = []) : RequestInterface
|
||||
{
|
||||
return new JsonRequest($uri, $method, $body === null ? Stream::fromTemp() : Stream::fromMemory($body), $headers);
|
||||
return new JsonRequest($uri, $method, $body === null ? Stream::fromTemp() : JsonStream::fromContent($body), $headers);
|
||||
}
|
||||
|
||||
public function getApiAttribute(string $type) : object
|
||||
|
@ -142,22 +131,16 @@ class ApiRepository extends \Ulmus\Repository
|
|||
return $this->entityClass::resolveEntity()->getAttributeImplementing($type);
|
||||
}
|
||||
|
||||
protected function callApiRequestCallback(RequestInterface $request, ApiAction $attribute) : mixed
|
||||
protected function callApiRequestCallback(RequestInterface $request, ApiAction $attribute) : RequestInterface
|
||||
{
|
||||
return call_user_func_array($this->adapter->apiRequestCallback(), func_get_args());
|
||||
return $this->adapter->apiHandler->handleRequest($request, $attribute);
|
||||
}
|
||||
|
||||
protected function callApiResponseCallback(ResponseInterface $response, ApiAction $attribute) : mixed
|
||||
protected function callApiResponseCallback(ResponseInterface $response, ApiAction $attribute) : ResponseInterface
|
||||
{
|
||||
return call_user_func_array($this->adapter->apiResponseCallback(), func_get_args());
|
||||
return $this->adapter->apiHandler->handleResponse($response, $attribute);
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
public function filterServerRequest(SearchRequestInterface $searchRequest, bool $count = true) : self
|
||||
{
|
||||
return $searchRequest->filter($this)
|
||||
|
@ -168,4 +151,9 @@ class ApiRepository extends \Ulmus\Repository
|
|||
->offset($searchRequest->offset())
|
||||
->limit($searchRequest->limit());
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -7,4 +7,15 @@ enum ContentTypeEnum : string
|
|||
case Json = "application/json";
|
||||
case Multipart = "multipart/form-data";
|
||||
case UrlEncoded = "application/x-www-form-urlencoded";
|
||||
|
||||
public function defaultBody() : string
|
||||
{
|
||||
switch($this) {
|
||||
case static::Json:
|
||||
return "{}";
|
||||
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\Api\Common;
|
||||
|
||||
use Psr\Http\Message\MessageInterface;
|
||||
use Ulmus\Api\Stream\JsonStream;
|
||||
|
||||
trait JsonMessageTrait
|
||||
{
|
||||
public function getParsedBody(): mixed
|
||||
{
|
||||
return JsonStream::fromJsonEncoded( $this->getBody()->getContents() )->decode();
|
||||
}
|
||||
|
||||
public function withParsedBody(mixed $data) : MessageInterface
|
||||
{
|
||||
return (clone $this)->setBody(
|
||||
JsonStream::fromContent($data)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -2,7 +2,10 @@
|
|||
|
||||
namespace Ulmus\Api\Common;
|
||||
|
||||
use Psr\Http\Message\MessageInterface;
|
||||
|
||||
interface StreamOutputInterface
|
||||
{
|
||||
public function render() : mixed;
|
||||
public function getParsedBody() : mixed;
|
||||
public function withParsedBody(mixed $data) : MessageInterface;
|
||||
}
|
|
@ -2,7 +2,13 @@
|
|||
|
||||
namespace Ulmus\Api;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Ulmus\Adapter\AdapterInterface;
|
||||
use Ulmus\Api\Request\JsonRequest;
|
||||
use Ulmus\Api\Response\JsonResponse;
|
||||
use Ulmus\Api\Response\Response;
|
||||
use Ulmus\Ulmus;
|
||||
|
||||
class ConnectionAdapter extends \Ulmus\ConnectionAdapter
|
||||
{
|
||||
|
@ -14,9 +20,15 @@ class ConnectionAdapter extends \Ulmus\ConnectionAdapter
|
|||
|
||||
private array $connection;
|
||||
|
||||
protected mixed $apiResponseCallback;
|
||||
public readonly ApiHandlerInterface $apiHandler;
|
||||
|
||||
public function __construct(ApiHandlerInterface $apiHandler, $name = "default", array $configuration = [], bool $default = false)
|
||||
{
|
||||
parent::__construct($name, $configuration, $default);
|
||||
|
||||
$this->apiHandler = $apiHandler;
|
||||
}
|
||||
|
||||
protected mixed $apiRequestCallback;
|
||||
|
||||
public function resolveConfiguration(): void
|
||||
{
|
||||
|
@ -64,13 +76,13 @@ class ConnectionAdapter extends \Ulmus\ConnectionAdapter
|
|||
return new $class();
|
||||
}
|
||||
|
||||
public function apiResponseCallback(callable $callback = null) : callable|null
|
||||
{
|
||||
return $callback ? $this->apiResponseCallback = $callback : $this->apiResponseCallback ?? fn($resp) => $resp->render();
|
||||
}
|
||||
#public function apiResponseCallback(callable $callback = null) : callable|null
|
||||
#{
|
||||
# return $callback ? $this->apiResponseCallback = $callback : $this->apiResponseCallback ?? fn(ResponseInterface $response) => $response;
|
||||
#}
|
||||
|
||||
public function apiRequestCallback(callable $callback = null) : callable|null
|
||||
{
|
||||
return $callback ? $this->apiRequestCallback = $callback : $this->apiRequestCallback ?? fn($resp) => $resp->render();
|
||||
}
|
||||
#public function apiRequestCallback(callable $callback = null) : callable|null
|
||||
#{
|
||||
# return $callback ? $this->apiRequestCallback = $callback : $this->apiRequestCallback ?? fn(RequestInterface $request) => $request;
|
||||
#}
|
||||
}
|
||||
|
|
|
@ -7,52 +7,24 @@ use Psr\Http\Message\ResponseInterface;
|
|||
use Psr\Http\Message\StreamInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use Ulmus\Api\Common\HttpHeaderEnum;
|
||||
use Ulmus\Api\Common\JsonMessageTrait;
|
||||
use Ulmus\Api\Common\MethodEnum;
|
||||
use Ulmus\Api\Stream\JsonStream;
|
||||
use Ulmus\Api\Stream\Stream;
|
||||
use Ulmus\Api\Common\StreamOutputInterface;
|
||||
use Ulmus\Api\Common\Uri;
|
||||
|
||||
class JsonRequest extends Request implements StreamOutputInterface
|
||||
{
|
||||
public const DEFAULT_JSON_ENCODING_FLAGS = JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_TAG | JSON_UNESCAPED_SLASHES;
|
||||
use JsonMessageTrait;
|
||||
|
||||
public const DEFAULT_JSON_DECODING_FLAGS = JSON_THROW_ON_ERROR;
|
||||
|
||||
public int $jsonEncodingOptions;
|
||||
|
||||
public function __construct(string|UriInterface $uri, MethodEnum $method, null|StreamInterface $stream = null, array $headers = [], $jsonEncodingOptions = self::DEFAULT_JSON_ENCODING_FLAGS)
|
||||
public function __construct(string|UriInterface $uri, MethodEnum $method, null|StreamInterface $stream = null, array $headers = [])
|
||||
{
|
||||
parent::__construct($uri, $method, $stream, $headers + [ 'Content-Type' => "application/json", 'Accept' => "application/json" ]);
|
||||
|
||||
$this->jsonEncodingOptions = $jsonEncodingOptions;
|
||||
}
|
||||
|
||||
public static function fromContent(mixed $content, int $encodingOptions = self::DEFAULT_JSON_ENCODING_FLAGS) : StreamInterface
|
||||
{
|
||||
if ( false === $json = json_encode($content, $encodingOptions) ) {
|
||||
throw new \InvalidArgumentException(sprintf('Json encoding failed with message `%s`.', json_last_error_msg()));
|
||||
}
|
||||
|
||||
return static::fromJsonEncoded($json);
|
||||
}
|
||||
|
||||
public static function fromJsonEncoded(string $json) : StreamInterface
|
||||
{
|
||||
$obj = Stream::fromTemp($json);
|
||||
$obj->rewind();
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public static function fromRequest(RequestInterface $request) : static
|
||||
{
|
||||
return new static($request->getUri(), MethodEnum::from($request->getMethod()), $request->getBody(), $request->getHeaders());
|
||||
}
|
||||
|
||||
public function render(): mixed
|
||||
{
|
||||
$content = $this->getBody()->getContents() ?: "[]";
|
||||
|
||||
return json_decode($content, true, 1024, static::DEFAULT_JSON_DECODING_FLAGS);
|
||||
}
|
||||
}
|
|
@ -4,40 +4,21 @@ namespace Ulmus\Api\Response;
|
|||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use Ulmus\Api\Common\HttpHeaderEnum;
|
||||
use Ulmus\Api\Common\JsonMessageTrait;
|
||||
use Ulmus\Api\Common\MethodEnum;
|
||||
use Ulmus\Api\Stream\JsonStream;
|
||||
use Ulmus\Api\Stream\Stream;
|
||||
use Ulmus\Api\Common\StreamOutputInterface;
|
||||
|
||||
class JsonResponse extends Response implements StreamOutputInterface
|
||||
{
|
||||
public const DEFAULT_JSON_ENCODING_FLAGS = JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_TAG | JSON_UNESCAPED_SLASHES;
|
||||
use JsonMessageTrait;
|
||||
|
||||
public const DEFAULT_JSON_DECODING_FLAGS = JSON_THROW_ON_ERROR;
|
||||
|
||||
public int $jsonEncodingOptions;
|
||||
|
||||
public function __construct(null|StreamInterface $stream = null, int $statusCode = 200, array $headers = [], $jsonEncodingOptions = JsonResponse::DEFAULT_JSON_ENCODING_FLAGS)
|
||||
public function __construct(null|StreamInterface $stream = null, int $statusCode = 200, array $headers = [])
|
||||
{
|
||||
parent::__construct($stream, $statusCode, HttpHeaderEnum::normalizeHeaderArray($headers) + [ 'Content-Type' => 'application/json' ]);
|
||||
|
||||
$this->jsonEncodingOptions = $jsonEncodingOptions;
|
||||
}
|
||||
|
||||
public static function fromContent(mixed $content, int $encodingOptions = self::DEFAULT_JSON_ENCODING_FLAGS) : StreamInterface
|
||||
{
|
||||
if ( false === $json = json_encode($content, $encodingOptions) ) {
|
||||
throw new \InvalidArgumentException(sprintf('Json encoding failed with message `%s`.', json_last_error_msg()));
|
||||
}
|
||||
|
||||
return static::fromJsonEncoded($json);
|
||||
}
|
||||
|
||||
public static function fromJsonEncoded(string $json) : StreamInterface
|
||||
{
|
||||
$obj = Stream::fromTemp($json);
|
||||
$obj->rewind();
|
||||
|
||||
return $obj;
|
||||
parent::__construct($stream, $statusCode, $headers + [ 'Content-Type' => "application/json" ]);
|
||||
}
|
||||
|
||||
public static function fromResponse(ResponseInterface $response) : static
|
||||
|
@ -45,8 +26,8 @@ class JsonResponse extends Response implements StreamOutputInterface
|
|||
return new static($response->getBody(), $response->getStatusCode(), $response->getHeaders());
|
||||
}
|
||||
|
||||
public function render(): mixed
|
||||
public static function createFrom(mixed $content, int $encodingOptions = JsonStream::DEFAULT_JSON_ENCODING_FLAGS) : ResponseInterface
|
||||
{
|
||||
return json_decode($this->getBody()->getContents(), true, 1024, static::DEFAULT_JSON_DECODING_FLAGS);
|
||||
return new static(JsonStream::fromContent($content, $encodingOptions));
|
||||
}
|
||||
}
|
|
@ -8,9 +8,9 @@ use Ulmus\Api\Common\HttpHeaderEnum;
|
|||
use Ulmus\Api\Stream\Stream;
|
||||
use Ulmus\Api\Common\StreamOutputInterface;
|
||||
|
||||
class JsonStream extends Stream implements StreamOutputInterface
|
||||
class JsonStream extends Stream
|
||||
{
|
||||
public const DEFAULT_JSON_ENCODING_FLAGS = JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_TAG | JSON_UNESCAPED_SLASHES;
|
||||
public const DEFAULT_JSON_ENCODING_FLAGS = JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_TAG | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR;
|
||||
|
||||
public const DEFAULT_JSON_DECODING_FLAGS = JSON_THROW_ON_ERROR;
|
||||
|
||||
|
@ -37,13 +37,13 @@ class JsonStream extends Stream implements StreamOutputInterface
|
|||
|
||||
public static function fromJsonEncoded(string $json) : StreamInterface
|
||||
{
|
||||
$obj = Stream::fromTemp($json);
|
||||
$obj = static::fromTemp($json);
|
||||
$obj->rewind();
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function render(): mixed
|
||||
public function decode(): mixed
|
||||
{
|
||||
return json_decode($this->getContents(), true, $this->depth, $this->decodingOptions);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace Ulmus\Api\Transport;
|
||||
|
||||
use Ulmus\Api\Common\{ ContentTypeEnum, MethodEnum, HttpHeaderEnum };
|
||||
use Ulmus\Api\Stream\JsonStream;
|
||||
use Ulmus\Api\Stream\Stream;
|
||||
use Psr\Http\Message\{ RequestInterface, ResponseInterface };
|
||||
use Ulmus\Api\Response\{ Response, JsonResponse };
|
||||
|
@ -51,7 +52,7 @@ abstract class CurlTransport {
|
|||
|
||||
public function fromRequest(RequestInterface $request) : ResponseInterface
|
||||
{
|
||||
return $this->request(MethodEnum::from($request->getMethod()), $request->getUri(), $request->getBody()->getContents(), $request->getHeaders());
|
||||
return $this->request(MethodEnum::from($request->getMethod()), $request->getUri(), $request->getBody(), $request->getHeaders());
|
||||
}
|
||||
|
||||
public function request(MethodEnum $method, string $url, mixed $data = null, array $headers = [], array $options = []) : ResponseInterface
|
||||
|
@ -143,7 +144,17 @@ abstract class CurlTransport {
|
|||
|
||||
switch($this->contentType) {
|
||||
case ContentTypeEnum::Json:
|
||||
$options[CURLOPT_POSTFIELDS] = json_encode(is_string($data) && empty($data) ? new \stdClass() : $data, JsonResponse::DEFAULT_JSON_ENCODING_FLAGS);
|
||||
if ($data instanceof JsonStream) {
|
||||
$json = $data->getContents();
|
||||
}
|
||||
elseif (is_string($data) && empty($data)) {
|
||||
$json = $this->contentType->defaultBody();
|
||||
}
|
||||
else {
|
||||
$json = json_encode($data, JsonStream::DEFAULT_JSON_ENCODING_FLAGS);
|
||||
}
|
||||
|
||||
$options[CURLOPT_POSTFIELDS] = $json;
|
||||
$headers['Accept'] ??= $this->contentType->value;
|
||||
break;
|
||||
|
||||
|
|
Loading…
Reference in New Issue