Compare commits
15
Commits
aba0ec3458
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ddd8a399a | ||
|
|
042ff5cc2f | ||
|
|
fe56a1727b | ||
|
|
b1adf5a086 | ||
|
|
0550036fd9 | ||
|
|
2d6f2c665b | ||
|
|
6d7701cfe5 | ||
|
|
98c608dfba | ||
|
|
e945a7a9b2 | ||
|
|
fa1d4783d2 | ||
|
|
7a31baa586 | ||
|
|
4e120cea6c | ||
|
|
ce4d6cdf7c | ||
|
|
f035fdf0a2 | ||
|
|
56ca1e2071 |
No files matched your search
+40
-19
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Ulmus\Api\Adapter;
|
||||
|
||||
use Negundo\Client\Transport\Curl;
|
||||
use Ulmus\Adapter\AdapterInterface;
|
||||
use Ulmus\Api\ApiRepository;
|
||||
use Ulmus\Api\Common\AuthenticationEnum;
|
||||
@@ -13,23 +14,35 @@ use Ulmus\Ulmus;
|
||||
|
||||
class Rest implements AdapterInterface
|
||||
{
|
||||
protected AuthenticationEnum $auth;
|
||||
protected CurlAuthorization $authorization;
|
||||
|
||||
protected string $username;
|
||||
|
||||
protected string $password;
|
||||
|
||||
protected string $token;
|
||||
protected AuthenticationEnum $authorizationMethod;
|
||||
|
||||
protected array $headers = [];
|
||||
|
||||
protected array $options = [];
|
||||
|
||||
protected array $parameters = [];
|
||||
|
||||
public readonly string $url;
|
||||
|
||||
public function setup(array $configuration): void
|
||||
{
|
||||
$this->url = rtrim($configuration['url'], '/');
|
||||
|
||||
$this->authorizationMethod = AuthenticationEnum::from($configuration['auth'] ?: AuthenticationEnum::Basic->value);
|
||||
$this->authorization = new CurlAuthorization($configuration);
|
||||
|
||||
$this->headers = $configuration['headers'] ?? [];
|
||||
$this->options = $configuration['options'] ?? [];
|
||||
$this->parameters = $configuration['parameters'] ?? [];
|
||||
}
|
||||
|
||||
public function connect(): object
|
||||
{
|
||||
return new CurlClient($this->headers, $this->options, ( new CurlAuthorization() )->fromAuthenticationEnum($this->auth, $this->username ?? false, $this->password ?? false, $this->token ?? false) );
|
||||
$this->authorization = $this->authorization->fromAuthenticationEnum($this->authorizationMethod);
|
||||
|
||||
return new CurlClient([], $this->options, $this->authorization);
|
||||
}
|
||||
|
||||
public function buildDataSourceName(): string
|
||||
@@ -37,18 +50,6 @@ class Rest implements AdapterInterface
|
||||
return "https";
|
||||
}
|
||||
|
||||
public function setup(array $configuration): void
|
||||
{
|
||||
$this->auth = AuthenticationEnum::from($configuration['auth'] ?: AuthenticationEnum::Basic->value);
|
||||
$this->url = rtrim($configuration['url'], '/');
|
||||
|
||||
foreach([ 'username', 'password', 'token', 'headers', 'options' ] as $conf) {
|
||||
if ($configuration[$conf] ?? false) {
|
||||
$this->$conf = $configuration[$conf];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function writableValue(mixed $value) : mixed
|
||||
{
|
||||
switch (true) {
|
||||
@@ -77,4 +78,24 @@ class Rest implements AdapterInterface
|
||||
{
|
||||
return RequestBuilder::class;
|
||||
}
|
||||
|
||||
public function getCurlOptions() : array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
public function getHeaders() : array
|
||||
{
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
public function getParameters() : array
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
public function escapeIdentifier(string $segment, int $type): string
|
||||
{
|
||||
return $segment;
|
||||
}
|
||||
}
|
||||
+163
-34
@@ -4,17 +4,21 @@ namespace Ulmus\Api;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use Ulmus\Api\Attribute\Obj\Api\ApiAction;
|
||||
use Ulmus\Api\Common\MethodEnum;
|
||||
use Ulmus\Api\RequestBuilder\Filter;
|
||||
use Ulmus\Api\RequestBuilder\UrlParameter;
|
||||
use Ulmus\Api\SearchRequest\ApiSearchRequest;
|
||||
use Ulmus\Api\Stream\JsonStream;
|
||||
use Ulmus\Api\Stream\Stream;
|
||||
use Ulmus\Api\Request\JsonRequest;
|
||||
use Ulmus\Api\Request\Request;
|
||||
use Ulmus\Api\Transport\CurlClient;
|
||||
use Ulmus\Api\Transport\CurlTransport;
|
||||
use Ulmus\Entity\EntityInterface;
|
||||
use Ulmus\EntityCollection;
|
||||
use Ulmus\SearchRequest\SearchRequestInterface;
|
||||
use Ulmus\Ulmus;
|
||||
|
||||
class ApiRepository extends \Ulmus\Repository
|
||||
{
|
||||
@@ -22,34 +26,62 @@ class ApiRepository extends \Ulmus\Repository
|
||||
|
||||
public CurlClient $client;
|
||||
|
||||
protected array $urlParameters = [];
|
||||
|
||||
public ResponseInterface $lastResponse;
|
||||
|
||||
public function __construct(string $entity, string $alias = self::DEFAULT_ALIAS, ConnectionAdapter $adapter = null)
|
||||
public function __construct(string $entity, string $alias = self::DEFAULT_ALIAS, null|ConnectionAdapter $adapter = null)
|
||||
{
|
||||
parent::__construct($entity, $alias, $adapter);
|
||||
}
|
||||
|
||||
public function loadOne(): ? object
|
||||
{
|
||||
$response = $this->executeRequest(Attribute\Obj\Api\Read::class);
|
||||
return $this->loadOneFromAttribute(Attribute\Obj\Api\Read::class);
|
||||
}
|
||||
|
||||
return $this->instanciateEntity()->fromArray($response->getParsedBody());
|
||||
public function loadOneFromAttribute(string $attributeClass): ? object
|
||||
{
|
||||
return $this->instanciateEntity(null, $this->executeRequest($attributeClass)->getParsedBody());
|
||||
}
|
||||
|
||||
public function loadAll() : EntityCollection
|
||||
{
|
||||
$response = $this->executeRequest(Attribute\Obj\Api\Collection::class);
|
||||
return $this->loadAllFromAttribute(Attribute\Obj\Api\Collection::class);
|
||||
}
|
||||
|
||||
public function loadAllFromAttribute(string $attributeClass) : EntityCollection
|
||||
{
|
||||
$response = $this->executeRequest($attributeClass);
|
||||
|
||||
return $this->instanciateEntityCollection()->fromArray($response->getParsedBody());
|
||||
}
|
||||
|
||||
public function search() : EntityCollection
|
||||
{
|
||||
return $this->loadAllFromAttribute(Attribute\Obj\Api\Search::class);
|
||||
}
|
||||
|
||||
public function save(object|array $entity, ?array $fieldsAndValue = null, bool $replace = false): bool
|
||||
{
|
||||
$response = $this->executeRequest(Attribute\Obj\Api\Create::class, $entity->entityGetDataset());
|
||||
|
||||
if ($replace && $entity instanceof EntityInterface) {
|
||||
$entity->fromArray($response->getParsedBody());
|
||||
}
|
||||
|
||||
return $response->getStatusCode() === 200;
|
||||
}
|
||||
|
||||
public function destroy(object $entity) : bool
|
||||
{
|
||||
if ( ! $this->matchEntity($entity) ) {
|
||||
throw new \Exception("Your entity class `" . get_class($entity) . "` cannot match entity type of repository `{$this->entityClass}`");
|
||||
}
|
||||
|
||||
$this->wherePrimaryKeyValue($entity);
|
||||
|
||||
$this->eventExecute(\Ulmus\Event\Query\Delete::class, $this, $entity);
|
||||
|
||||
$response = $this->executeRequest(Attribute\Obj\Api\Delete::class);
|
||||
|
||||
return $response->getStatusCode() === 200;
|
||||
}
|
||||
@@ -62,33 +94,39 @@ class ApiRepository extends \Ulmus\Repository
|
||||
throw new \RuntimeException(sprintf("Could not find attribute class '%s' for class '%s'", $attributeClass, $this->entityClass));
|
||||
}
|
||||
|
||||
$request = $this->prepareRequest($this->buildRequestUrl($attribute->url), $attribute->method, $data);
|
||||
if (method_exists($this->adapter->apiHandler, 'executingRequest')) {
|
||||
$this->adapter->apiHandler->executingRequest($this, $attribute);
|
||||
}
|
||||
|
||||
$request = $this->prepareRequest($this->buildRequestUrl($attribute->url), $attribute->method, $data, $this->adapter->adapter()->getHeaders(), $this->adapter->adapter()->getParameters());
|
||||
|
||||
$request = $this->compileRequestParameters($request, $attribute);
|
||||
|
||||
$request = $this->callApiRequestCallback($request, $attribute);
|
||||
|
||||
$rest = $this->adapter->adapter()->connect();
|
||||
|
||||
$rest->timeout = $attribute->timeout;
|
||||
|
||||
$this->lastResponse = $response = $rest->fromRequest($request);
|
||||
$response = $this->launchRequest($request, $attribute);
|
||||
|
||||
$response = $this->callApiResponseCallback($response, $attribute);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function bindUrl(string $parameter, mixed $value) : self
|
||||
protected function buildRequestUrl(string $uri) : string
|
||||
{
|
||||
$this->urlParameters[$parameter] = $value;
|
||||
|
||||
return $this;
|
||||
return $this->adapter->adapter()->url . '/' . ltrim($uri, '/');
|
||||
}
|
||||
|
||||
protected function buildRequestUrl(string $uri) :string
|
||||
protected function launchRequest(RequestInterface $request, object $attribute) : ResponseInterface
|
||||
{
|
||||
$uri = $this->prepareUri($uri, $this->urlParameters);
|
||||
$transport = $this->adapter->adapter()->connect();
|
||||
|
||||
return $this->adapter->adapter()->url . '/' . ltrim($uri, '/');
|
||||
$transport->timeout = $attribute->timeout;
|
||||
|
||||
$this->lastResponse = $transport->fromRequest($request);
|
||||
|
||||
$this->callApiDebugCallback($transport);
|
||||
|
||||
return $this->lastResponse;
|
||||
}
|
||||
|
||||
protected function prepareUri(string $route, array $arguments) : string
|
||||
@@ -129,9 +167,47 @@ class ApiRepository extends \Ulmus\Repository
|
||||
return $route;
|
||||
}
|
||||
|
||||
protected function prepareRequest(string|UriInterface $uri, MethodEnum $method, mixed $body = null, array $headers = []) : RequestInterface
|
||||
protected function prepareRequest(string|UriInterface $uri, MethodEnum $method, mixed $body = null, array $headers = [], array $queryParameters = []) : ServerRequestInterface
|
||||
{
|
||||
return new JsonRequest($uri, $method, $body === null ? Stream::fromTemp() : JsonStream::fromContent($body), $headers);
|
||||
$request = new JsonRequest($uri, $method, $body === null ? Stream::fromTemp() : JsonStream::fromContent($body), $headers);
|
||||
|
||||
# Adding parameters
|
||||
$request = $request->withQueryParams($queryParameters)
|
||||
->withAttribute('repository', $this);
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
protected function compileRequestParameters(ServerRequestInterface $request, object $attribute) : ServerRequestInterface
|
||||
{
|
||||
$requestOptions = $this->queryBuilder->render();
|
||||
|
||||
$request = $this->applyFiltering($request, $attribute, $requestOptions[Filter::KEY] ?? []);
|
||||
|
||||
$request = $this->applyUrlBinding($request, $attribute, $requestOptions[UrlParameter::KEY] ?? []);
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
protected function applyUrlBinding(ServerRequestInterface $request, object $attribute, array $bindings) : ServerRequestInterface
|
||||
{
|
||||
$uriObj = $request->getUri();
|
||||
|
||||
$uri = $this->prepareUri($request->getUri()->render(), $bindings);
|
||||
|
||||
return $request->withUri($uriObj->from($uri));
|
||||
}
|
||||
|
||||
protected function applyFiltering(ServerRequestInterface $request, object $attribute, array $filters) : ServerRequestInterface
|
||||
{
|
||||
if ($attribute->searchMethod === MethodEnum::Get) {
|
||||
$request = $request->withQueryParams($filters);
|
||||
}
|
||||
elseif ($attribute->searchMethod === MethodEnum::Post) {
|
||||
$request = $request->withParsedBody($filters);
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
public function getApiAttribute(string $type) : ? object
|
||||
@@ -139,29 +215,82 @@ class ApiRepository extends \Ulmus\Repository
|
||||
return $this->entityClass::resolveEntity()->getAttributeImplementing($type);
|
||||
}
|
||||
|
||||
protected function callApiRequestCallback(RequestInterface $request, ApiAction $attribute) : RequestInterface
|
||||
protected function callApiRequestCallback(RequestInterface $request, Attribute\Obj\Api\ApiAction $attribute) : ServerRequestInterface
|
||||
{
|
||||
return $this->adapter->apiHandler->handleRequest($request, $attribute);
|
||||
}
|
||||
|
||||
protected function callApiResponseCallback(ResponseInterface $response, ApiAction $attribute) : ResponseInterface
|
||||
protected function callApiResponseCallback(ResponseInterface $response, Attribute\Obj\Api\ApiAction $attribute) : ResponseInterface
|
||||
{
|
||||
return $this->adapter->apiHandler->handleResponse($response, $attribute);
|
||||
}
|
||||
|
||||
public function filterServerRequest(SearchRequestInterface $searchRequest, bool $count = true) : self
|
||||
protected function callApiDebugCallback(CurlTransport $transport) : void
|
||||
{
|
||||
return $searchRequest->filter($this)
|
||||
->wheres($searchRequest->wheres(), \Ulmus\Query\Where::OPERATOR_EQUAL, \Ulmus\Query\Where::CONDITION_AND)
|
||||
->likes($searchRequest->likes(), \Ulmus\Query\Where::CONDITION_OR)
|
||||
->orders($searchRequest->orders())
|
||||
->groups($searchRequest->groups())
|
||||
->offset($searchRequest->offset())
|
||||
->limit($searchRequest->limit());
|
||||
if (method_exists($this->adapter->apiHandler, 'debugResponse')) {
|
||||
$this->adapter->apiHandler->debugResponse($transport, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function filterServerRequest(SearchRequestInterface $searchRequest, bool $count = true) : \Ulmus\Repository
|
||||
{
|
||||
if ($searchRequest instanceof ApiSearchRequest) {
|
||||
$this->bindings($searchRequest->bindings());
|
||||
}
|
||||
|
||||
return parent::filterServerRequest($searchRequest, false);
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function bindUrl(string|\Stringable $field, mixed $value) : self
|
||||
{
|
||||
$this->queryBuilder->bindUrl($field, $value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function bindings(array $fieldValues) : self
|
||||
{
|
||||
foreach($fieldValues as $field => $value) {
|
||||
$this->bindUrl($field, $value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function wherePrimaryKey(mixed $value, null|string|bool $alias = self::DEFAULT_ALIAS) : self
|
||||
{
|
||||
$primaryKeyField = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField();
|
||||
|
||||
if ( $primaryKeyField === null ) {
|
||||
throw new \Ulmus\Exception\EntityPrimaryKeyUnknown(sprintf("No primary key found for entity %s", $this->entityClass));
|
||||
}
|
||||
|
||||
$pkField = key($primaryKeyField);
|
||||
|
||||
return $this->where($pkField, $value);
|
||||
}
|
||||
|
||||
public function wherePrimaryKeyValue(EntityInterface $entity) : self
|
||||
{
|
||||
$primaryKeyField = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField();
|
||||
|
||||
if ( $primaryKeyField === null ) {
|
||||
throw new \Ulmus\Exception\EntityPrimaryKeyUnknown(sprintf("No primary key found for entity %s", $this->entityClass));
|
||||
}
|
||||
|
||||
$pkField = key($primaryKeyField);
|
||||
|
||||
$value = $entity->$pkField;
|
||||
|
||||
if ( $value !== 0 && empty($value) ) {
|
||||
throw new \Ulmus\Exception\EntityPrimaryKeyUnknown(sprintf("A primary key value has to be defined to delete an item of entity %s.", $entity::class));
|
||||
}
|
||||
|
||||
return $this->where($pkField, $value);
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,13 @@ namespace Ulmus\Api\Attribute\Obj\Api;
|
||||
use Ulmus\Api\Common\MethodEnum;
|
||||
|
||||
#[\Attribute]
|
||||
abstract class ApiAction
|
||||
class ApiAction
|
||||
{
|
||||
public function __construct(
|
||||
public string $url,
|
||||
public MethodEnum $method,
|
||||
public int $timeout = 5,
|
||||
public int $timeout = 30,
|
||||
# Define how the search string will be build, passing params through the URL or into request's body
|
||||
public MethodEnum $searchMethod = MethodEnum::Get,
|
||||
) {}
|
||||
}
|
||||
@@ -5,9 +5,12 @@ namespace Ulmus\Api\Attribute\Obj\Api;
|
||||
use Ulmus\Api\Common\MethodEnum;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_CLASS)]
|
||||
class Collection extends ApiAction {
|
||||
class Collection extends ApiAction
|
||||
{
|
||||
public function __construct(
|
||||
public string $url,
|
||||
public MethodEnum $method = MethodEnum::Get,
|
||||
public int $timeout = 30,
|
||||
public MethodEnum $searchMethod = MethodEnum::Get,
|
||||
) {}
|
||||
}
|
||||
@@ -5,9 +5,12 @@ namespace Ulmus\Api\Attribute\Obj\Api;
|
||||
use Ulmus\Api\Common\MethodEnum;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_CLASS)]
|
||||
class Create extends ApiAction {
|
||||
class Create extends ApiAction
|
||||
{
|
||||
public function __construct(
|
||||
public string $url,
|
||||
public MethodEnum $method = MethodEnum::Post,
|
||||
public int $timeout = 30,
|
||||
public MethodEnum $searchMethod = MethodEnum::Get,
|
||||
) {}
|
||||
}
|
||||
@@ -5,9 +5,12 @@ namespace Ulmus\Api\Attribute\Obj\Api;
|
||||
use Ulmus\Api\Common\MethodEnum;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_CLASS)]
|
||||
class Delete extends ApiAction {
|
||||
class Delete extends ApiAction
|
||||
{
|
||||
public function __construct(
|
||||
public string $url,
|
||||
public MethodEnum $method = MethodEnum::Delete,
|
||||
public int $timeout = 30,
|
||||
public MethodEnum $searchMethod = MethodEnum::Get,
|
||||
) {}
|
||||
}
|
||||
@@ -5,9 +5,12 @@ namespace Ulmus\Api\Attribute\Obj\Api;
|
||||
use Ulmus\Api\Common\MethodEnum;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_CLASS)]
|
||||
class Read extends ApiAction {
|
||||
class Read extends ApiAction
|
||||
{
|
||||
public function __construct(
|
||||
public string $url,
|
||||
public MethodEnum $method = MethodEnum::Get,
|
||||
public int $timeout = 30,
|
||||
public MethodEnum $searchMethod = MethodEnum::Get,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Ulmus\Api\Attribute\Obj\Api;
|
||||
|
||||
use Ulmus\Api\Common\MethodEnum;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_CLASS)]
|
||||
class Search extends ApiAction
|
||||
{
|
||||
public function __construct(
|
||||
public string $url,
|
||||
public MethodEnum $method = MethodEnum::Get,
|
||||
public int $timeout = 30,
|
||||
public MethodEnum $searchMethod = MethodEnum::Get,
|
||||
) {}
|
||||
}
|
||||
@@ -5,9 +5,12 @@ namespace Ulmus\Api\Attribute\Obj\Api;
|
||||
use Ulmus\Api\Common\MethodEnum;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_CLASS)]
|
||||
class Update extends ApiAction {
|
||||
class Update extends ApiAction
|
||||
{
|
||||
public function __construct(
|
||||
public string $url,
|
||||
public MethodEnum $method = MethodEnum::Patch,
|
||||
public int $timeout = 30,
|
||||
public MethodEnum $searchMethod = MethodEnum::Get,
|
||||
) {}
|
||||
}
|
||||
@@ -5,11 +5,12 @@ namespace Ulmus\Api\Common;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Ulmus\Api\ApiHandlerInterface;
|
||||
use Ulmus\Api\ApiRepository;
|
||||
use Ulmus\Api\Attribute\Obj\Api\ApiAction;
|
||||
use Ulmus\Api\Transport\CurlTransport;
|
||||
|
||||
class ApiHandlerPassThrough implements ApiHandlerInterface
|
||||
{
|
||||
|
||||
public function handleRequest(RequestInterface $request, ApiAction $attribute): RequestInterface
|
||||
{
|
||||
return $request;
|
||||
@@ -19,4 +20,6 @@ class ApiHandlerPassThrough implements ApiHandlerInterface
|
||||
{
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function debugResponse(CurlTransport $transport, ApiRepository $repository) : void {}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ enum AuthenticationEnum : string
|
||||
case Key = "key";
|
||||
case Ntlm = "ntlm";
|
||||
case Negotiate = "negotiate";
|
||||
# case OAuth = "oauth";
|
||||
case OAuth2 = "oauth2";
|
||||
# case OpenID = "openid";
|
||||
# case OpenAPI = "openapi";
|
||||
case Token = "token";
|
||||
|
||||
@@ -5,8 +5,10 @@ namespace Ulmus\Api\Common;
|
||||
enum ContentTypeEnum : string
|
||||
{
|
||||
case Json = "application/json";
|
||||
case Multipart = "multipart/form-data";
|
||||
case Xml = "application/xml";
|
||||
case RssXml = "application/rss+xml";
|
||||
case UrlEncoded = "application/x-www-form-urlencoded";
|
||||
case Multipart = "multipart/form-data";
|
||||
|
||||
public function defaultBody() : string
|
||||
{
|
||||
|
||||
@@ -7,12 +7,12 @@ use Ulmus\Api\Stream\JsonStream;
|
||||
|
||||
trait JsonMessageTrait
|
||||
{
|
||||
public function getParsedBody(): mixed
|
||||
public function getParsedBody() : null|array|object
|
||||
{
|
||||
return JsonStream::fromJsonEncoded( $this->getBody()->getContents() )->decode() ;
|
||||
}
|
||||
|
||||
public function withParsedBody(mixed $data) : MessageInterface
|
||||
public function withParsedBody(mixed $data) : static
|
||||
{
|
||||
return (clone $this)->setBody(
|
||||
JsonStream::fromContent($data)
|
||||
|
||||
@@ -13,4 +13,11 @@ enum MethodEnum : string
|
||||
case Post = "POST"; # https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST )
|
||||
case Put = "PUT"; # https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT )
|
||||
case Trace = "TRACE"; # https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/TRACE )
|
||||
|
||||
public static function generateArray() : array
|
||||
{
|
||||
$values = array_map(fn($e) => $e->value, MethodEnum::cases());
|
||||
|
||||
return array_combine($values, $values);
|
||||
}
|
||||
}
|
||||
@@ -7,5 +7,5 @@ use Psr\Http\Message\MessageInterface;
|
||||
interface StreamOutputInterface
|
||||
{
|
||||
public function getParsedBody() : mixed;
|
||||
public function withParsedBody(mixed $data) : MessageInterface;
|
||||
public function withParsedBody(mixed $data) : static;
|
||||
}
|
||||
+1
-1
@@ -24,7 +24,7 @@ class Uri implements UriInterface
|
||||
|
||||
protected null|string $fragment;
|
||||
|
||||
public function __construct(string $uri = null)
|
||||
public function __construct(?string $uri = null)
|
||||
{
|
||||
$this->parse($uri);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Ulmus\Api\Common;
|
||||
|
||||
use Psr\Http\Message\MessageInterface;
|
||||
use Ulmus\Api\Stream\JsonStream;
|
||||
use Ulmus\Api\Stream\XmlStream;
|
||||
|
||||
# @TODO !
|
||||
trait XmlMessageTrait
|
||||
{
|
||||
public function getParsedBody() : null|array|object
|
||||
{
|
||||
return XmlStream::fromXmlEncoded( $this->getBody()->getContents() )->decode() ;
|
||||
}
|
||||
|
||||
public function withParsedBody(mixed $data) : static
|
||||
{
|
||||
return (clone $this)->setBody(
|
||||
XmlStream::fromContent($data)
|
||||
);
|
||||
}
|
||||
}
|
||||
+13
-22
@@ -4,6 +4,7 @@ namespace Ulmus\Api;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use Ulmus\Adapter\AdapterInterface;
|
||||
use Ulmus\Api\Request\JsonRequest;
|
||||
use Ulmus\Api\Response\JsonResponse;
|
||||
@@ -12,24 +13,20 @@ use Ulmus\Ulmus;
|
||||
|
||||
class ConnectionAdapter extends \Ulmus\ConnectionAdapter
|
||||
{
|
||||
public string $name;
|
||||
|
||||
public array $configuration;
|
||||
|
||||
protected AdapterInterface $adapter;
|
||||
|
||||
private array $connection;
|
||||
|
||||
public readonly ApiHandlerInterface $apiHandler;
|
||||
|
||||
public function __construct(ApiHandlerInterface $apiHandler, $name = "default", array $configuration = [], bool $default = false)
|
||||
{
|
||||
public function __construct(
|
||||
public readonly ApiHandlerInterface $apiHandler,
|
||||
public string $name = "default",
|
||||
protected array $configuration = [],
|
||||
public bool $default = false,
|
||||
public ? CacheInterface $cacheObject = null
|
||||
) {
|
||||
parent::__construct($name, $configuration, $default);
|
||||
|
||||
$this->apiHandler = $apiHandler;
|
||||
}
|
||||
|
||||
|
||||
public function resolveConfiguration(): void
|
||||
{
|
||||
$connection = $this->configuration['connections'][$this->name] ?? [];
|
||||
@@ -71,18 +68,12 @@ class ConnectionAdapter extends \Ulmus\ConnectionAdapter
|
||||
|
||||
protected function instanciateAdapter($name): AdapterInterface
|
||||
{
|
||||
$class = substr($name, 0, 2) === "\\" ? $name : "\\Ulmus\\Api\\Adapter\\$name";
|
||||
if (class_exists($name)) {
|
||||
return new $name();
|
||||
}
|
||||
|
||||
$class = sprintf("\\%s\\Adapter\\%s", __NAMESPACE__, $name);
|
||||
|
||||
return new $class();
|
||||
}
|
||||
|
||||
#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(RequestInterface $request) => $request;
|
||||
#}
|
||||
}
|
||||
+127
-8
@@ -2,13 +2,11 @@
|
||||
|
||||
namespace Ulmus\Api\Request;
|
||||
|
||||
use League\CommonMark\Block\Element\StringContainerInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use Ulmus\Api\Common\{MethodEnum, Message, Stream, Uri};
|
||||
use Ulmus\Api\Common\{MethodEnum, Message, Uri, };
|
||||
use Ulmus\Api\Stream\{JsonStream, Stream};
|
||||
use Psr\Http\Message\{MessageInterface, ServerRequestInterface, UriInterface, RequestInterface, StreamInterface};
|
||||
|
||||
class Request extends Message implements RequestInterface
|
||||
class Request extends Message implements ServerRequestInterface
|
||||
{
|
||||
protected int $code;
|
||||
|
||||
@@ -20,6 +18,12 @@ class Request extends Message implements RequestInterface
|
||||
|
||||
protected MethodEnum $method;
|
||||
|
||||
protected array $cookies = [];
|
||||
|
||||
protected array $files = [];
|
||||
|
||||
protected array $attributes = [];
|
||||
|
||||
public function __construct(string|UriInterface $uri, MethodEnum $method, null|StreamInterface $stream = null, array $headers = [])
|
||||
{
|
||||
$this->method = $method;
|
||||
@@ -70,9 +74,9 @@ class Request extends Message implements RequestInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUri()
|
||||
public function getUri() : UriInterface
|
||||
{
|
||||
return (string) $this->uri;
|
||||
return $this->uri;
|
||||
}
|
||||
|
||||
public function withUri(UriInterface $uri, $preserveHost = false)
|
||||
@@ -101,4 +105,119 @@ class Request extends Message implements RequestInterface
|
||||
$fromUri->getHost(), $port && ! in_array($port, Uri::IGNORE_HTTP_PORT) ? ":{$port}" : ""
|
||||
]);
|
||||
}
|
||||
|
||||
public function withQueryParams(array $query) : static
|
||||
{
|
||||
return (clone $this)->addQueryParams($query);
|
||||
}
|
||||
|
||||
protected function addQueryParams(array $query) : static
|
||||
{
|
||||
$rebuilt = http_build_query(array_merge($this->getQueryParams(), $query), "", null, PHP_QUERY_RFC3986);
|
||||
|
||||
$this->getUri()->withQuery($rebuilt);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getServerParams() : array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getCookieParams() : array
|
||||
{
|
||||
return $this->cookies;
|
||||
}
|
||||
|
||||
public function withCookieParams(array $cookies) : static
|
||||
{
|
||||
return (clone $this)->setCookies($cookies);
|
||||
}
|
||||
|
||||
protected function setCookies(array $cookies) : static
|
||||
{
|
||||
$this->cookies = $cookies;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getQueryParams() : array
|
||||
{
|
||||
parse_str($this->getUri()->getQuery(), $query);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function getUploadedFiles() : array
|
||||
{
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
public function withUploadedFiles(array $uploadedFiles) : static
|
||||
{
|
||||
return (clone $this)->setUploadedFiles($uploadedFiles);
|
||||
}
|
||||
|
||||
protected function setUploadedFiles(array $uploadedFiles) : static
|
||||
{
|
||||
$this->files = $uploadedFiles;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getParsedBody() : null|array|object
|
||||
{
|
||||
$raw = $this->getBody()->getContents();
|
||||
|
||||
$contentType = $this->getHeader('Content-Type')[0] ?? "application/x-www-form-urlencoded";
|
||||
|
||||
switch($contentType) {
|
||||
case "application/x-www-form-urlencoded":
|
||||
parse_str($raw, $result);
|
||||
|
||||
return $result;
|
||||
|
||||
case "multipart/form-data":
|
||||
|
||||
return [];
|
||||
|
||||
case "application/json":
|
||||
return JsonStream::fromJsonEncoded( $raw )->decode();
|
||||
|
||||
}
|
||||
|
||||
return [ sprintf("Unsupported content type: %s", $contentType) ];
|
||||
}
|
||||
|
||||
public function withParsedBody(mixed $data) : static
|
||||
{
|
||||
return (clone $this)->setBody(
|
||||
JsonStream::fromContent($data)
|
||||
);
|
||||
}
|
||||
|
||||
public function getAttributes() : array
|
||||
{
|
||||
return $this->attributes;
|
||||
}
|
||||
|
||||
public function getAttribute($name, $default = null) : mixed
|
||||
{
|
||||
return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default;
|
||||
}
|
||||
|
||||
public function withAttribute($name, $value) : static
|
||||
{
|
||||
$this->attributes[$name] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withoutAttribute($name): static
|
||||
{
|
||||
unset($this->attributes[$name]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
+51
-74
@@ -2,23 +2,25 @@
|
||||
|
||||
namespace Ulmus\Api;
|
||||
|
||||
use Ulmus;
|
||||
use Ulmus, Ulmus\Query\QueryFragmentInterface;
|
||||
|
||||
class RequestBuilder implements Ulmus\Query\QueryBuilderInterface
|
||||
class RequestBuilder implements Ulmus\QueryBuilder\QueryBuilderInterface
|
||||
{
|
||||
public Ulmus\Query\QueryBuilderInterface $parent;
|
||||
public Ulmus\QueryBuilder\QueryBuilderInterface $parent;
|
||||
|
||||
public array $parameters = [];
|
||||
public RequestBuilder\Filter $filters;
|
||||
|
||||
public array $values = [];
|
||||
|
||||
public string $whereConditionOperator = Ulmus\Query\Where::CONDITION_AND;
|
||||
|
||||
public string $havingConditionOperator = Ulmus\Query\Where::CONDITION_AND;
|
||||
public RequestBuilder\UrlParameter $urlParameters;
|
||||
|
||||
protected int $parameterIndex = 0;
|
||||
|
||||
protected array $queryStack = [];
|
||||
protected array $queryParamStack = [];
|
||||
|
||||
protected ? string $limitQueryField = "limit";
|
||||
|
||||
protected ? string $offsetQueryField = "offset";
|
||||
|
||||
protected ? string $pageQueryField = "page";
|
||||
|
||||
public function values(array $dataset) : self
|
||||
{
|
||||
@@ -27,79 +29,73 @@ class RequestBuilder implements Ulmus\Query\QueryBuilderInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function where(/* stringable*/ $field, $value, string $operator = Ulmus\Query\Where::OPERATOR_EQUAL, string $condition = Ulmus\Query\Where::CONDITION_AND, bool $not = false) : self
|
||||
public function filter(\Stringable|string $field, mixed $value,) : self
|
||||
{
|
||||
# Empty IN case
|
||||
if ( [] === $value ) {
|
||||
return $this;
|
||||
if ( empty($this->filters) && ! $this->getFragment(RequestBuilder\Filter::class) ) {
|
||||
$this->filters = new RequestBuilder\Filter();
|
||||
$this->push($this->filters);
|
||||
}
|
||||
|
||||
if ( $this->where ?? false ) {
|
||||
$where = $this->where;
|
||||
}
|
||||
elseif ( null === ( $where = $this->getFragment(RequestBuilder\Filter::class) ) ) {
|
||||
$this->where = $where = new RequestBuilder\Filter($this);
|
||||
$this->push($where);
|
||||
}
|
||||
|
||||
$this->whereConditionOperator = $operator;
|
||||
|
||||
$where->add($field, $value, $operator, $condition, $not);
|
||||
$this->filters->add($field, $value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function notWhere($field, $value, string $operator = Ulmus\Query\Where::CONDITION_AND) : self
|
||||
public function where(\Stringable|string $field, mixed $value,) : self
|
||||
{
|
||||
return $this->where($field, $value, $operator, true);
|
||||
return $this->filter($field, $value);
|
||||
}
|
||||
|
||||
public function bindUrl(\Stringable|string $field, mixed $value,) : self
|
||||
{
|
||||
if ( empty($this->urlParameters) && ! $this->getFragment(RequestBuilder\UrlParameter::class) ) {
|
||||
$this->urlParameters = new RequestBuilder\UrlParameter();
|
||||
$this->push($this->urlParameters);
|
||||
}
|
||||
|
||||
$this->urlParameters->add($field, $value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function limit(int $value) : self
|
||||
{
|
||||
/*if ( null === $limit = $this->getFragment(Ulmus\Query\Limit::class) ) {
|
||||
$limit = new Query\Limit();
|
||||
$this->push($limit);
|
||||
}
|
||||
|
||||
$limit->set($value);
|
||||
*/
|
||||
return $this;
|
||||
return is_null($this->limitQueryField) ? $this : $this->filter($this->limitQueryField, $value);
|
||||
}
|
||||
|
||||
public function offset(int $value) : self
|
||||
{
|
||||
if ( null === $offset = $this->getFragment(Ulmus\Query\Offset::class) ) {
|
||||
$offset = new Ulmus\Query\Offset();
|
||||
$this->push($offset);
|
||||
return is_null($this->offsetQueryField) ? $this : $this->filter($this->offsetQueryField, $value);
|
||||
}
|
||||
|
||||
$offset->set($value);
|
||||
|
||||
return $this;
|
||||
public function page(int $value) : self
|
||||
{
|
||||
return is_null($this->pageQueryField) ? $this : $this->filter($this->pageQueryField, $value);
|
||||
}
|
||||
|
||||
|
||||
public function orderBy(string $field, ? string $direction = null) : self
|
||||
{
|
||||
return $this;
|
||||
return $this->where($field, $direction);
|
||||
}
|
||||
|
||||
public function push(Ulmus\Query\Fragment $queryFragment) : self
|
||||
public function push(QueryFragmentInterface $queryFragment) : self
|
||||
{
|
||||
$this->queryStack[] = $queryFragment;
|
||||
$this->queryParamStack[] = $queryFragment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function pull(Ulmus\Query\Fragment $queryFragment) : self
|
||||
public function pull(QueryFragmentInterface $queryFragment) : self
|
||||
{
|
||||
return array_shift($this->queryStack);
|
||||
return array_shift($this->queryParamStack);
|
||||
}
|
||||
|
||||
public function render(bool $skipToken = false) : array
|
||||
{
|
||||
$stack = [];
|
||||
|
||||
foreach($this->queryStack as $fragment) {
|
||||
foreach($this->queryParamStack as $fragment) {
|
||||
$stack = array_merge($stack, (array) $fragment->render($skipToken));
|
||||
}
|
||||
|
||||
@@ -108,16 +104,12 @@ class RequestBuilder implements Ulmus\Query\QueryBuilderInterface
|
||||
|
||||
public function reset() : void
|
||||
{
|
||||
$this->parameters = $this->values = $this->queryStack = [];
|
||||
$this->whereConditionOperator = Ulmus\Query\Where::CONDITION_AND;
|
||||
$this->parameterIndex = 0;
|
||||
|
||||
unset($this->where);
|
||||
unset($this->filters);
|
||||
}
|
||||
|
||||
public function getFragment(string $class, int $index = 0) : ? Ulmus\Query\Fragment
|
||||
public function getFragment(string $class, int $index = 0) : ? QueryFragmentInterface
|
||||
{
|
||||
foreach($this->queryStack as $item) {
|
||||
foreach($this->queryParamStack as $item) {
|
||||
if ( get_class($item) === $class ) {
|
||||
if ( $index-- === 0 ) {
|
||||
return $item;
|
||||
@@ -128,11 +120,11 @@ class RequestBuilder implements Ulmus\Query\QueryBuilderInterface
|
||||
return null;
|
||||
}
|
||||
|
||||
public function removeFragment(\Ulmus\Query\Fragment|array|\Stringable|string $fragment) : void
|
||||
public function removeFragment(QueryFragmentInterface|array|\Stringable|string $fragment) : void
|
||||
{
|
||||
foreach($this->queryStack as $key => $item) {
|
||||
foreach($this->queryParamStack as $key => $item) {
|
||||
if ( $item === $fragment ) {
|
||||
unset($this->queryStack[$key]);
|
||||
unset($this->queryParamStack[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,23 +134,8 @@ class RequestBuilder implements Ulmus\Query\QueryBuilderInterface
|
||||
return $this->render();
|
||||
}
|
||||
|
||||
public function addParameter($value, string $key = null) : string
|
||||
public function hashSerializedQuery(): string
|
||||
{
|
||||
if ( $this->parent ?? false ) {
|
||||
return $this->parent->addParameter($value, $key);
|
||||
}
|
||||
|
||||
if ( $key === null ) {
|
||||
$key = ":p" . $this->parameterIndex++;
|
||||
}
|
||||
|
||||
$this->parameters[$key] = $value;
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
public function addValues(array $values) : void
|
||||
{
|
||||
$this->values = $values;
|
||||
return "UNSUPPORTED";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Ulmus\Api\RequestBuilder;
|
||||
|
||||
class Filter extends Fragment
|
||||
{
|
||||
public const KEY = "filters";
|
||||
|
||||
public array $conditionList;
|
||||
|
||||
public function add($field, mixed $value) : self
|
||||
{
|
||||
$this->conditionList[(string) $field] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function render() : array
|
||||
{
|
||||
return [
|
||||
static::KEY => $this->conditionList
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Ulmus\Api\RequestBuilder;
|
||||
|
||||
use Ulmus\Query\QueryFragmentInterface;
|
||||
|
||||
abstract class Fragment implements QueryFragmentInterface {
|
||||
|
||||
protected int $encodingType = \PHP_QUERY_RFC3986;
|
||||
|
||||
protected ? string $querySeparator = null;
|
||||
|
||||
public abstract function render() : array;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Ulmus\Api\RequestBuilder;
|
||||
|
||||
class Pagination extends Fragment {
|
||||
|
||||
public array $conditionList;
|
||||
|
||||
public function add($field, mixed $value) : self
|
||||
{
|
||||
$this->conditionList[$field] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function render(bool $skipToken = false) : string
|
||||
{
|
||||
return http_build_query($this->conditionList);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Ulmus\Api\RequestBuilder;
|
||||
|
||||
class UrlParameter extends Fragment
|
||||
{
|
||||
public const KEY = "bindings";
|
||||
|
||||
public array $conditionList;
|
||||
|
||||
public function add($field, mixed $value) : self
|
||||
{
|
||||
$this->conditionList[$field] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function render() : array
|
||||
{
|
||||
return [
|
||||
static::KEY => $this->conditionList
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Ulmus\Api\Response;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Ulmus\Api\Common\XmlMessageTrait;
|
||||
use Ulmus\Api\Stream\XmlStream;
|
||||
use Ulmus\Api\Common\StreamOutputInterface;
|
||||
|
||||
class XmlResponse extends Response implements StreamOutputInterface
|
||||
{
|
||||
use XmlMessageTrait;
|
||||
|
||||
public function __construct(null|StreamInterface $stream = null, int $statusCode = 200, array $headers = [])
|
||||
{
|
||||
parent::__construct($stream, $statusCode, $headers + [ 'Content-Type' => "application/xml" ]);
|
||||
}
|
||||
|
||||
public static function fromResponse(ResponseInterface $response) : static
|
||||
{
|
||||
return new static($response->getBody(), $response->getStatusCode(), $response->getHeaders());
|
||||
}
|
||||
|
||||
public static function createFrom(mixed $content) : ResponseInterface
|
||||
{
|
||||
return new static(XmlStream::fromContent($content));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Ulmus\Api\SearchRequest;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Ulmus\{Api\SearchRequest\Attribute\BindParameter,
|
||||
Repository,
|
||||
SearchRequest\SearchMethodEnum,
|
||||
SearchRequest\SearchRequest,
|
||||
SearchRequest\SearchRequestFromRequestTrait,
|
||||
SearchRequest\SearchRequestInterface,
|
||||
SearchRequest\SearchRequestPaginationTrait};
|
||||
|
||||
class ApiSearchRequest extends SearchRequest implements SearchRequestInterface
|
||||
{
|
||||
use /*SearchRequestPaginationTrait,*/ SearchRequestFromRequestTrait {
|
||||
parseAttributeMethod as parseAttributeMethodParent;
|
||||
}
|
||||
|
||||
protected array $bindings = [];
|
||||
|
||||
public function bindings(): iterable
|
||||
{
|
||||
return array_filter($this->bindings + [
|
||||
|
||||
], fn($i) => !is_null($i)) + [];
|
||||
}
|
||||
|
||||
protected function parseAttributeMethod(object $attribute, string|\Stringable $field, string $propertyName) : void
|
||||
{
|
||||
switch ($attribute->method) {
|
||||
case SearchMethodEnum::Where:
|
||||
case SearchMethodEnum::Manual:
|
||||
if ($attribute instanceof BindParameter) {
|
||||
if (isset($this->$propertyName)) {
|
||||
$this->bindings[(string)$field] = $this->$propertyName;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
$this->parseAttributeMethodParent($attribute, $field, $propertyName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Ulmus\Api\SearchRequest\Attribute;
|
||||
|
||||
use Ulmus\SearchRequest\{Attribute\PropertyValueSource,
|
||||
Attribute\SearchManual,
|
||||
SearchMethodEnum,
|
||||
Attribute\SearchParameter};
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY)]
|
||||
class BindParameter extends SearchManual
|
||||
{
|
||||
public function __construct(
|
||||
public null|string|array $parameter = null,
|
||||
public null|string|\Stringable|array $field = null,
|
||||
public bool $toggle = false,
|
||||
public SearchMethodEnum $method = SearchMethodEnum::Manual,
|
||||
public string $description = "",
|
||||
public PropertyValueSource|array $source = PropertyValueSource::RequestAttribute,
|
||||
) {}
|
||||
}
|
||||
@@ -107,7 +107,7 @@ class Stream implements StreamInterface
|
||||
return $this->hasMode('acwx+');
|
||||
}
|
||||
|
||||
public function write($string) : false|int
|
||||
public function write($string) : int
|
||||
{
|
||||
if (! $this->isWritable()) {
|
||||
throw new \UnexpectedValueException("An error occured while trying to seek an unseekable stream.");
|
||||
@@ -166,7 +166,7 @@ class Stream implements StreamInterface
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getMetadata(string $key = null) : mixed
|
||||
public function getMetadata(?string $key = null) : mixed
|
||||
{
|
||||
$metadata = stream_get_meta_data($this->stream);
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Ulmus\Api\Stream;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Ulmus\Api\Common\HttpHeaderEnum;
|
||||
use Ulmus\Api\Stream\Stream;
|
||||
use Ulmus\Api\Common\StreamOutputInterface;
|
||||
|
||||
# @TODO
|
||||
class XmlStream extends Stream
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public static function fromContent(mixed $content) : StreamInterface
|
||||
{
|
||||
#if ( false === $json = json_encode($content) ) {
|
||||
# throw new \InvalidArgumentException(sprintf('Json encoding failed with message `%s`.', json_last_error_msg()));
|
||||
# }
|
||||
|
||||
# @TODO !
|
||||
|
||||
#return static::fromJsonEncoded($json);
|
||||
return static::fromXmlEncoded($content);
|
||||
}
|
||||
|
||||
public static function fromXmlEncoded(string $xml) : StreamInterface
|
||||
{
|
||||
$obj = static::fromTemp($xml);
|
||||
$obj->rewind();
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function decode(): mixed
|
||||
{
|
||||
$content = $this->getContents();
|
||||
|
||||
if ($content === "") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return json_decode(json_encode((array) \simplexml_load_string($content)), true);
|
||||
}
|
||||
}
|
||||
@@ -2,39 +2,52 @@
|
||||
|
||||
namespace Ulmus\Api\Transport;
|
||||
|
||||
use League\OAuth2\Client\Provider\GenericProvider;
|
||||
use Ulmus\Api\Common\AuthenticationEnum;
|
||||
|
||||
class CurlAuthorization
|
||||
{
|
||||
public array $headers = [];
|
||||
public function __construct(
|
||||
#[\SensitiveParameter]
|
||||
public array $configuration,
|
||||
#[\SensitiveParameter]
|
||||
public array $headers = [],
|
||||
public array $options = [],
|
||||
) { }
|
||||
|
||||
public array $options = [];
|
||||
|
||||
public function fromAuthenticationEnum(AuthenticationEnum $auth, string|false $username, #[\SensitiveParameter] string|false $password, #[\SensitiveParameter] string|false $token,) : static
|
||||
public function fromAuthenticationEnum(AuthenticationEnum $auth,) : static
|
||||
{
|
||||
switch($auth) {
|
||||
case AuthenticationEnum::Custom:
|
||||
$this->customHeader($this->configuration['header'], $this->configuration['secret']);
|
||||
break;
|
||||
|
||||
case AuthenticationEnum::Bearer:
|
||||
$this->bearer($token);
|
||||
$this->bearer($this->configuration['token']);
|
||||
break;
|
||||
|
||||
case AuthenticationEnum::Key:
|
||||
$this->key($token);
|
||||
$this->key($this->configuration['token']);
|
||||
break;
|
||||
|
||||
case AuthenticationEnum::Token:
|
||||
$this->token($token);
|
||||
$this->token($this->configuration['token']);
|
||||
break;
|
||||
|
||||
case AuthenticationEnum::Basic:
|
||||
$this->basic($username, $password);
|
||||
$this->basic($this->configuration['username'], $this->configuration['password']);
|
||||
break;
|
||||
|
||||
case AuthenticationEnum::Ntlm:
|
||||
$this->ntlm($username, $password);
|
||||
$this->ntlm($this->configuration['username'], $this->configuration['password']);
|
||||
break;
|
||||
|
||||
case AuthenticationEnum::Negotiate:
|
||||
$this->negotiate($username, $password);
|
||||
$this->negotiate($this->configuration['username'], $this->configuration['password']);
|
||||
break;
|
||||
|
||||
case AuthenticationEnum::OAuth2:
|
||||
$this->oauth2($this->configuration['grant'], $this->configuration['oauth_token_url'], $this->configuration['redirect_uri'], $this->configuration['client_id'], $this->configuration['client_secret'], $this->configuration['scope']);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -43,6 +56,7 @@ class CurlAuthorization
|
||||
|
||||
public function bearer(#[\SensitiveParameter] string $token) : void
|
||||
{
|
||||
$this->options[CURLOPT_HTTPAUTH] = CURLAUTH_BEARER;
|
||||
$this->options[CURLOPT_XOAUTH2_BEARER] = $token;
|
||||
}
|
||||
|
||||
@@ -86,6 +100,53 @@ class CurlAuthorization
|
||||
$this->userpass(CURLAUTH_NEGOTIATE, $username, $password);
|
||||
}
|
||||
|
||||
public function customHeader(string $header, #[\SensitiveParameter] string $token) : void
|
||||
{
|
||||
$this->headers[$header] = $token;
|
||||
}
|
||||
|
||||
public function oauth2(Oauth2GrantTypeEnum $grant, string $oauthTokenUrl, string $redirectUri, string $clientId, string $clientSecret, string $scope) : void
|
||||
{
|
||||
$unid = sprintf("oauth_token_%s", md5(serialize($this->configuration)));
|
||||
|
||||
$token = apcu_fetch($unid, $exists);
|
||||
|
||||
if ( ! $exists ) {
|
||||
$provider = new GenericProvider([
|
||||
'clientId' => $clientId,
|
||||
'clientSecret' => $clientSecret,
|
||||
'redirectUri' => $redirectUri,
|
||||
'urlAccessToken' => $oauthTokenUrl,
|
||||
'urlAuthorize' => 'https://service.example.com/authorize',
|
||||
'urlResourceOwnerDetails' => 'https://service.example.com/resource',
|
||||
'scopes' => $scope,
|
||||
]);
|
||||
|
||||
try {
|
||||
$accessToken = $provider->getAccessToken($grant->value);
|
||||
$token = json_encode($accessToken);
|
||||
|
||||
apcu_store($unid, $token, $accessToken->getExpires() - time() - 10);
|
||||
}
|
||||
catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$decodedToken = json_decode($token, true, 512, JSON_THROW_ON_ERROR);
|
||||
}
|
||||
catch(\Exception $ex) {
|
||||
throw new \InvalidArgumentException("Given OAuth2 token is not a parsable JSON stream.");
|
||||
}
|
||||
|
||||
if ( empty($decodedToken) ) {
|
||||
throw new \InvalidArgumentException("Given OAuth Token is empty");
|
||||
}
|
||||
|
||||
$this->bearer($decodedToken['access_token']);
|
||||
}
|
||||
|
||||
protected function userpass(int $authType, string $username, #[\SensitiveParameter] $password) : void
|
||||
{
|
||||
$this->options[CURLOPT_HTTPAUTH] = $authType;
|
||||
|
||||
@@ -6,7 +6,7 @@ use Ulmus\Api\Common\{ ContentTypeEnum, MethodEnum, HttpHeaderEnum };
|
||||
use Ulmus\Api\Stream\JsonStream;
|
||||
use Ulmus\Api\Stream\Stream;
|
||||
use Psr\Http\Message\{RequestInterface, ResponseInterface, StreamInterface};
|
||||
use Ulmus\Api\Response\{ Response, JsonResponse };
|
||||
use Ulmus\Api\Response\{Response, JsonResponse, XmlResponse};
|
||||
|
||||
abstract class CurlTransport {
|
||||
|
||||
@@ -27,7 +27,7 @@ abstract class CurlTransport {
|
||||
public ContentTypeEnum $contentType = ContentTypeEnum::Json,
|
||||
# Matching Guzzle's great user-agent syntax
|
||||
public string $userAgent = "ulmus-api/1.0 curl/{curl} php/{php}",
|
||||
public bool $debug = false,
|
||||
public bool $debug = true,
|
||||
) {
|
||||
$this->userAgent = str_replace([ '{curl}', '{php}', ], [ curl_version()['version'] ?? "???", phpversion() ], $this->userAgent);
|
||||
}
|
||||
@@ -37,9 +37,9 @@ abstract class CurlTransport {
|
||||
return $this->request(MethodEnum::Post, $url, $data, $headers, $curlOptions);
|
||||
}
|
||||
|
||||
public function get(string $url, mixed $data, array $headers = [], array $curlOptions = []) : mixed
|
||||
public function get(string $url, array $headers = [], array $curlOptions = []) : mixed
|
||||
{
|
||||
return $this->request(MethodEnum::Get, $url, $data, $headers, $curlOptions);
|
||||
return $this->request(MethodEnum::Get, $url, null, $headers, $curlOptions);
|
||||
}
|
||||
|
||||
public function delete(string $url, mixed $data, array $headers = [], array $curlOptions = []) : mixed
|
||||
@@ -77,9 +77,10 @@ abstract class CurlTransport {
|
||||
|
||||
$this->applyMethod($method, $data, $headers, $options);
|
||||
|
||||
$headers += $this->authorization->headers;
|
||||
|
||||
if ($this->authorization) {
|
||||
$headers += HttpHeaderEnum::normalizeHeaderArray($this->authorization->headers);
|
||||
$options += $this->authorization->options;
|
||||
}
|
||||
|
||||
$options += [
|
||||
CURLOPT_URL => $url,
|
||||
@@ -129,13 +130,26 @@ abstract class CurlTransport {
|
||||
|
||||
$response = $response->withBody( Stream::fromMemory($result) );
|
||||
|
||||
if ( $response->hasHeader('Content-Type') && false !== strpos($response->getHeader('Content-Type')[0], 'json') ) {
|
||||
$response = JsonResponse::fromResponse($response);
|
||||
if ($response->hasHeader('Content-Type') ) {
|
||||
list($mime) = explode(';', $response->getHeader('Content-Type')[0]);
|
||||
|
||||
return match(ContentTypeEnum::tryFrom($mime)) {
|
||||
ContentTypeEnum::Json => JsonResponse::fromResponse($response),
|
||||
ContentTypeEnum::RssXml, ContentTypeEnum::Xml => XmlResponse::fromResponse($response),
|
||||
default => $response,
|
||||
};
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function getDebugErrorLines() : array
|
||||
{
|
||||
$this->stderr->rewind();
|
||||
|
||||
return explode(PHP_EOL, $this->stderr->getContents());
|
||||
}
|
||||
|
||||
protected function applyMethod(MethodEnum $method, mixed &$data, array &$headers, array &$options) : void
|
||||
{
|
||||
switch ($method) {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Ulmus\Api\Transport;
|
||||
|
||||
enum Oauth2GrantTypeEnum : string
|
||||
{
|
||||
case AuthorizationCode = "authorization_code";
|
||||
case PKCE = "pkce";
|
||||
case ClientCredentials = "client_credentials";
|
||||
case DeviceCode = "device_code";
|
||||
case RefreshToken = "refresh_token";
|
||||
}
|
||||
Reference in new issue
Block a user