- Added SearchRequest compatibilty and began working on QueryBuilder processing

This commit is contained in:
Dave Mc Nicoll 2024-04-17 15:57:51 -04:00
parent ce4d6cdf7c
commit 4e120cea6c
20 changed files with 510 additions and 141 deletions

View File

@ -22,6 +22,8 @@ class Rest implements AdapterInterface
protected array $options = []; protected array $options = [];
protected array $parameters = [];
public readonly string $url; public readonly string $url;
public function setup(array $configuration): void public function setup(array $configuration): void
@ -29,15 +31,18 @@ class Rest implements AdapterInterface
$this->url = rtrim($configuration['url'], '/'); $this->url = rtrim($configuration['url'], '/');
$this->authorizationMethod = AuthenticationEnum::from($configuration['auth'] ?: AuthenticationEnum::Basic->value); $this->authorizationMethod = AuthenticationEnum::from($configuration['auth'] ?: AuthenticationEnum::Basic->value);
$this->authorization = ( new CurlAuthorization() )->fromAuthenticationEnum($this->authorizationMethod, $configuration); $this->authorization = new CurlAuthorization($configuration);
$this->headers = $configuration['headers'] ?? []; $this->headers = $configuration['headers'] ?? [];
$this->options = $configuration['options'] ?? []; $this->options = $configuration['options'] ?? [];
$this->parameters = $configuration['parameters'] ?? [];
} }
public function connect(): object public function connect(): object
{ {
return new CurlClient($this->headers, $this->options, $this->authorization); $this->authorization = $this->authorization->fromAuthenticationEnum($this->authorizationMethod);
return new CurlClient([], $this->options, $this->authorization);
} }
public function buildDataSourceName(): string public function buildDataSourceName(): string
@ -73,4 +78,19 @@ class Rest implements AdapterInterface
{ {
return RequestBuilder::class; return RequestBuilder::class;
} }
public function getCurlOptions() : array
{
return $this->options;
}
public function getHeaders() : array
{
return $this->headers;
}
public function getParameters() : array
{
return $this->parameters;
}
} }

View File

@ -4,10 +4,14 @@ namespace Ulmus\Api;
use Psr\Http\Message\RequestInterface; use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface; use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface; use Psr\Http\Message\UriInterface;
use Ulmus\Api\Attribute\Obj\Api\ApiAction; use Ulmus\Api\Attribute\Obj\Api\ApiAction;
use Ulmus\Api\Common\MethodEnum; 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\JsonStream;
use Ulmus\Api\Stream\Stream; use Ulmus\Api\Stream\Stream;
use Ulmus\Api\Request\JsonRequest; use Ulmus\Api\Request\JsonRequest;
@ -16,6 +20,7 @@ use Ulmus\Api\Transport\CurlClient;
use Ulmus\Api\Transport\CurlTransport; use Ulmus\Api\Transport\CurlTransport;
use Ulmus\EntityCollection; use Ulmus\EntityCollection;
use Ulmus\SearchRequest\SearchRequestInterface; use Ulmus\SearchRequest\SearchRequestInterface;
use Ulmus\Ulmus;
class ApiRepository extends \Ulmus\Repository class ApiRepository extends \Ulmus\Repository
{ {
@ -23,8 +28,6 @@ class ApiRepository extends \Ulmus\Repository
public CurlClient $client; public CurlClient $client;
protected array $urlParameters = [];
public ResponseInterface $lastResponse; 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, ConnectionAdapter $adapter = null)
@ -34,18 +37,33 @@ class ApiRepository extends \Ulmus\Repository
public function loadOne(): ? object public function loadOne(): ? object
{ {
$response = $this->executeRequest(Attribute\Obj\Api\Read::class); return $this->loadOneFromAttribute(Attribute\Obj\Api\Read::class);
}
public function loadOneFromAttribute(string $attributeClass): ? object
{
$response = $this->executeRequest($attributeClass);
return $this->instanciateEntity()->fromArray($response->getParsedBody()); return $this->instanciateEntity()->fromArray($response->getParsedBody());
} }
public function loadAll() : EntityCollection 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()); 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 public function save(object|array $entity, ?array $fieldsAndValue = null, bool $replace = false): bool
{ {
$response = $this->executeRequest(Attribute\Obj\Api\Create::class, $entity->entityGetDataset()); $response = $this->executeRequest(Attribute\Obj\Api\Create::class, $entity->entityGetDataset());
@ -63,38 +81,35 @@ class ApiRepository extends \Ulmus\Repository
throw new \RuntimeException(sprintf("Could not find attribute class '%s' for class '%s'", $attributeClass, $this->entityClass)); 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); $request = $this->prepareRequest($this->buildRequestUrl($attribute->url), $attribute->method, $data, $this->adapter->adapter()->getHeaders(), $this->adapter->adapter()->getParameters());
# @TODO !! SET HEADERS FROM CONFIG IN REQUEST HERE INSTEAD OF DIRECTLY IN TRANSPORT ! $request = $this->compileRequestParameters($request, $attribute);
# $request->withAddedHeader()
$request = $this->callApiRequestCallback($request, $attribute); $this->callApiRequestCallback($request, $attribute);
$transport = $this->adapter->adapter()->connect(); $response = $this->launchRequest($request, $attribute);
$transport->timeout = $attribute->timeout;
$this->lastResponse = $response = $transport->fromRequest($request);
$response = $this->callApiResponseCallback($response, $attribute); $response = $this->callApiResponseCallback($response, $attribute);
$this->callApiDebugCallback($transport);
return $response; return $response;
} }
public function bindUrl(string $parameter, mixed $value) : self protected function buildRequestUrl(string $uri) : string
{ {
$this->urlParameters[$parameter] = $value; return $this->adapter->adapter()->url . '/' . ltrim($uri, '/');
return $this;
} }
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 protected function prepareUri(string $route, array $arguments) : string
@ -135,9 +150,46 @@ class ApiRepository extends \Ulmus\Repository
return $route; 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);
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 public function getApiAttribute(string $type) : ? object
@ -145,7 +197,7 @@ class ApiRepository extends \Ulmus\Repository
return $this->entityClass::resolveEntity()->getAttributeImplementing($type); return $this->entityClass::resolveEntity()->getAttributeImplementing($type);
} }
protected function callApiRequestCallback(RequestInterface $request, ApiAction $attribute) : RequestInterface protected function callApiRequestCallback(RequestInterface $request, ApiAction $attribute) : ServerRequestInterface
{ {
return $this->adapter->apiHandler->handleRequest($request, $attribute); return $this->adapter->apiHandler->handleRequest($request, $attribute);
} }
@ -160,19 +212,53 @@ class ApiRepository extends \Ulmus\Repository
$this->adapter->apiHandler->debugResponse($transport, $this); $this->adapter->apiHandler->debugResponse($transport, $this);
} }
public function filterServerRequest(SearchRequestInterface $searchRequest, bool $count = true) : self public function collectionFromQuery(? string $entityClass = null) : EntityCollection
{ {
return $searchRequest->filter($this) $entityClass ??= $this->entityClass;
->wheres($searchRequest->wheres(), \Ulmus\Query\Where::OPERATOR_EQUAL, \Ulmus\Query\Where::CONDITION_AND)
->likes($searchRequest->likes(), \Ulmus\Query\Where::CONDITION_OR) $entityCollection = $entityClass::entityCollection();
->orders($searchRequest->orders())
->groups($searchRequest->groups()) $this->finalizeQuery();
->offset($searchRequest->offset())
->limit($searchRequest->limit()); foreach(Ulmus::iterateQueryBuilder($this->queryBuilder, $this->adapter) as $entityData) {
$entity = $this->instanciateEntity($entityClass);
$entity->loadedFromAdapter = $this->adapter->name;
$entityCollection->append( $entity->resetVirtualProperties()->entityFillFromDataset($entityData) );
}
$this->eventExecute(\Ulmus\Event\Repository\CollectionFromQueryInterface::class, $entityCollection);
return $entityCollection;
}
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 public function count(): int
{ {
return 0; 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;
}
} }

View File

@ -11,5 +11,7 @@ abstract class ApiAction
public string $url, public string $url,
public MethodEnum $method, public MethodEnum $method,
public int $timeout = 30, 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,
) {} ) {}
} }

View File

@ -5,10 +5,12 @@ namespace Ulmus\Api\Attribute\Obj\Api;
use Ulmus\Api\Common\MethodEnum; use Ulmus\Api\Common\MethodEnum;
#[\Attribute(\Attribute::TARGET_CLASS)] #[\Attribute(\Attribute::TARGET_CLASS)]
class Collection extends ApiAction { class Collection extends ApiAction
{
public function __construct( public function __construct(
public string $url, public string $url,
public MethodEnum $method = MethodEnum::Get, public MethodEnum $method = MethodEnum::Get,
public int $timeout = 30, public int $timeout = 30,
public MethodEnum $searchMethod = MethodEnum::Get,
) {} ) {}
} }

View File

@ -5,7 +5,8 @@ namespace Ulmus\Api\Attribute\Obj\Api;
use Ulmus\Api\Common\MethodEnum; use Ulmus\Api\Common\MethodEnum;
#[\Attribute(\Attribute::TARGET_CLASS)] #[\Attribute(\Attribute::TARGET_CLASS)]
class Create extends ApiAction { class Create extends ApiAction
{
public function __construct( public function __construct(
public string $url, public string $url,
public MethodEnum $method = MethodEnum::Post, public MethodEnum $method = MethodEnum::Post,

View File

@ -5,7 +5,8 @@ namespace Ulmus\Api\Attribute\Obj\Api;
use Ulmus\Api\Common\MethodEnum; use Ulmus\Api\Common\MethodEnum;
#[\Attribute(\Attribute::TARGET_CLASS)] #[\Attribute(\Attribute::TARGET_CLASS)]
class Delete extends ApiAction { class Delete extends ApiAction
{
public function __construct( public function __construct(
public string $url, public string $url,
public MethodEnum $method = MethodEnum::Delete, public MethodEnum $method = MethodEnum::Delete,

View File

@ -5,10 +5,12 @@ namespace Ulmus\Api\Attribute\Obj\Api;
use Ulmus\Api\Common\MethodEnum; use Ulmus\Api\Common\MethodEnum;
#[\Attribute(\Attribute::TARGET_CLASS)] #[\Attribute(\Attribute::TARGET_CLASS)]
class Read extends ApiAction { class Read extends ApiAction
{
public function __construct( public function __construct(
public string $url, public string $url,
public MethodEnum $method = MethodEnum::Get, public MethodEnum $method = MethodEnum::Get,
public int $timeout = 30, public int $timeout = 30,
public MethodEnum $searchMethod = MethodEnum::Get,
) {} ) {}
} }

View File

@ -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,
) {}
}

View File

@ -5,7 +5,8 @@ namespace Ulmus\Api\Attribute\Obj\Api;
use Ulmus\Api\Common\MethodEnum; use Ulmus\Api\Common\MethodEnum;
#[\Attribute(\Attribute::TARGET_CLASS)] #[\Attribute(\Attribute::TARGET_CLASS)]
class Update extends ApiAction { class Update extends ApiAction
{
public function __construct( public function __construct(
public string $url, public string $url,
public MethodEnum $method = MethodEnum::Patch, public MethodEnum $method = MethodEnum::Patch,

View File

@ -7,12 +7,12 @@ use Ulmus\Api\Stream\JsonStream;
trait JsonMessageTrait trait JsonMessageTrait
{ {
public function getParsedBody(): mixed public function getParsedBody() : null|array|object
{ {
return JsonStream::fromJsonEncoded( $this->getBody()->getContents() )->decode() ; return JsonStream::fromJsonEncoded( $this->getBody()->getContents() )->decode() ;
} }
public function withParsedBody(mixed $data) : MessageInterface public function withParsedBody(mixed $data) : static
{ {
return (clone $this)->setBody( return (clone $this)->setBody(
JsonStream::fromContent($data) JsonStream::fromContent($data)

View File

@ -7,5 +7,5 @@ use Psr\Http\Message\MessageInterface;
interface StreamOutputInterface interface StreamOutputInterface
{ {
public function getParsedBody() : mixed; public function getParsedBody() : mixed;
public function withParsedBody(mixed $data) : MessageInterface; public function withParsedBody(mixed $data) : static;
} }

View File

@ -2,13 +2,11 @@
namespace Ulmus\Api\Request; namespace Ulmus\Api\Request;
use League\CommonMark\Block\Element\StringContainerInterface; use Ulmus\Api\Common\{MethodEnum, Message, Uri, };
use Psr\Http\Message\RequestInterface; use Ulmus\Api\Stream\{JsonStream, Stream};
use Psr\Http\Message\StreamInterface; use Psr\Http\Message\{MessageInterface, ServerRequestInterface, UriInterface, RequestInterface, StreamInterface};
use Psr\Http\Message\UriInterface;
use Ulmus\Api\Common\{MethodEnum, Message, Stream, Uri};
class Request extends Message implements RequestInterface class Request extends Message implements ServerRequestInterface
{ {
protected int $code; protected int $code;
@ -20,6 +18,10 @@ class Request extends Message implements RequestInterface
protected MethodEnum $method; protected MethodEnum $method;
protected array $cookies = [];
protected array $files = [];
public function __construct(string|UriInterface $uri, MethodEnum $method, null|StreamInterface $stream = null, array $headers = []) public function __construct(string|UriInterface $uri, MethodEnum $method, null|StreamInterface $stream = null, array $headers = [])
{ {
$this->method = $method; $this->method = $method;
@ -70,9 +72,9 @@ class Request extends Message implements RequestInterface
return $this; return $this;
} }
public function getUri() public function getUri() : UriInterface
{ {
return (string) $this->uri; return $this->uri;
} }
public function withUri(UriInterface $uri, $preserveHost = false) public function withUri(UriInterface $uri, $preserveHost = false)
@ -101,4 +103,115 @@ class Request extends Message implements RequestInterface
$fromUri->getHost(), $port && ! in_array($port, Uri::IGNORE_HTTP_PORT) ? ":{$port}" : "" $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
{
// TODO: Implement getAttributes() method.
}
public function getAttribute($name, $default = null) : mixed
{
// TODO: Implement getAttribute() method.
}
public function withAttribute($name, $value) : static
{
// TODO: Implement withAttribute() method.
}
public function withoutAttribute($name): static
{
// TODO: Implement withoutAttribute() method.
}
}

View File

@ -2,23 +2,19 @@
namespace Ulmus\Api; 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 RequestBuilder\UrlParameter $urlParameters;
public string $whereConditionOperator = Ulmus\Query\Where::CONDITION_AND;
public string $havingConditionOperator = Ulmus\Query\Where::CONDITION_AND;
protected int $parameterIndex = 0; protected int $parameterIndex = 0;
protected array $queryStack = []; protected array $queryParamStack = [];
public function values(array $dataset) : self public function values(array $dataset) : self
{ {
@ -27,33 +23,36 @@ class RequestBuilder implements Ulmus\Query\QueryBuilderInterface
return $this; 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 where(\Stringable|string $field, mixed $value,) : self
{ {
# Empty IN case if ( empty($this->filters) && ! $this->getFragment(RequestBuilder\Filter::class) ) {
if ( [] === $value ) { $this->filters = new RequestBuilder\Filter();
return $this; $this->push($this->filters);
} }
if ( $this->where ?? false ) { $this->filters->add($field, $value);
$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);
return $this; return $this;
} }
public function notWhere($field, $value, string $operator = Ulmus\Query\Where::CONDITION_AND) : self public function bindUrl(\Stringable|string $field, mixed $value,) : self
{ {
return $this->where($field, $value, $operator, true); 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 notWhere($field, mixed $value) : self
{
return $this->filters($field, $value);
}*/
public function limit(int $value) : self public function limit(int $value) : self
{ {
/*if ( null === $limit = $this->getFragment(Ulmus\Query\Limit::class) ) { /*if ( null === $limit = $this->getFragment(Ulmus\Query\Limit::class) ) {
@ -68,12 +67,12 @@ class RequestBuilder implements Ulmus\Query\QueryBuilderInterface
public function offset(int $value) : self public function offset(int $value) : self
{ {
if ( null === $offset = $this->getFragment(Ulmus\Query\Offset::class) ) { /*if ( null === $offset = $this->getFragment(Ulmus\Query\Offset::class) ) {
$offset = new Ulmus\Query\Offset(); $offset = new Ulmus\Query\Offset();
$this->push($offset); $this->push($offset);
} }
$offset->set($value); $offset->set($value);*/
return $this; return $this;
} }
@ -83,23 +82,23 @@ class RequestBuilder implements Ulmus\Query\QueryBuilderInterface
return $this; return $this;
} }
public function push(Ulmus\Query\Fragment $queryFragment) : self public function push(QueryFragmentInterface $queryFragment) : self
{ {
$this->queryStack[] = $queryFragment; $this->queryParamStack[] = $queryFragment;
return $this; 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 public function render(bool $skipToken = false) : array
{ {
$stack = []; $stack = [];
foreach($this->queryStack as $fragment) { foreach($this->queryParamStack as $fragment) {
$stack = array_merge($stack, (array) $fragment->render($skipToken)); $stack = array_merge($stack, (array) $fragment->render($skipToken));
} }
@ -108,16 +107,12 @@ class RequestBuilder implements Ulmus\Query\QueryBuilderInterface
public function reset() : void public function reset() : void
{ {
$this->parameters = $this->values = $this->queryStack = []; unset($this->filters);
$this->whereConditionOperator = Ulmus\Query\Where::CONDITION_AND;
$this->parameterIndex = 0;
unset($this->where);
} }
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 ( get_class($item) === $class ) {
if ( $index-- === 0 ) { if ( $index-- === 0 ) {
return $item; return $item;
@ -128,11 +123,11 @@ class RequestBuilder implements Ulmus\Query\QueryBuilderInterface
return null; 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 ) { if ( $item === $fragment ) {
unset($this->queryStack[$key]); unset($this->queryParamStack[$key]);
} }
} }
} }
@ -141,24 +136,4 @@ class RequestBuilder implements Ulmus\Query\QueryBuilderInterface
{ {
return $this->render(); return $this->render();
} }
public function addParameter($value, string $key = null) : 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;
}
} }

View File

@ -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[$field] = $value;
return $this;
}
public function render() : array
{
return [
static::KEY => $this->conditionList
];
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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
];
}
}

View File

@ -0,0 +1,43 @@
<?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 $field, string $propertyName,): void
{
switch ($attribute->method) {
case SearchMethodEnum::Manual:
if ($attribute instanceof BindParameter) {
$this->bindings[$field] = $this->$propertyName;
}
break;
default:
$this->parseAttributeMethodParent($attribute, $field, $propertyName);
break;
}
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace Ulmus\Api\SearchRequest\Attribute;
use Ulmus\SearchRequest\{ SearchMethodEnum, Attribute\SearchParameter };
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class BindParameter extends SearchParameter
{
public function __construct(
public ? string $parameter = null,
public ? string $field = null,
public bool $toggle = false,
public SearchMethodEnum $method = SearchMethodEnum::Manual,
) {}
}

View File

@ -7,39 +7,43 @@ use Ulmus\Api\Common\AuthenticationEnum;
class CurlAuthorization 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,) : static
public function fromAuthenticationEnum(AuthenticationEnum $auth, #[\SensitiveParameter] array $configuration,) : static
{ {
switch($auth) { switch($auth) {
case AuthenticationEnum::Bearer: case AuthenticationEnum::Bearer:
$this->bearer($configuration['token']); $this->bearer($this->configuration['token']);
break; break;
case AuthenticationEnum::Key: case AuthenticationEnum::Key:
$this->key($configuration['token']); $this->key($this->configuration['token']);
break; break;
case AuthenticationEnum::Token: case AuthenticationEnum::Token:
$this->token($configuration['token']); $this->token($this->configuration['token']);
break; break;
case AuthenticationEnum::Basic: case AuthenticationEnum::Basic:
$this->basic($configuration['username'], $configuration['password']); $this->basic($this->configuration['username'], $this->configuration['password']);
break; break;
case AuthenticationEnum::Ntlm: case AuthenticationEnum::Ntlm:
$this->ntlm($configuration['username'], $configuration['password']); $this->ntlm($this->configuration['username'], $this->configuration['password']);
break; break;
case AuthenticationEnum::Negotiate: case AuthenticationEnum::Negotiate:
$this->negotiate($configuration['username'], $configuration['password']); $this->negotiate($this->configuration['username'], $this->configuration['password']);
break; break;
case AuthenticationEnum::OAuth2: case AuthenticationEnum::OAuth2:
$this->oauth2($configuration['grant'], $configuration['oauth_token_url'], $configuration['redirect_uri'], $configuration['client_id'], $configuration['client_secret'], $configuration['scope']); $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; break;
} }
@ -94,11 +98,11 @@ class CurlAuthorization
public function oauth2(Oauth2GrantTypeEnum $grant, string $oauthTokenUrl, string $redirectUri, string $clientId, string $clientSecret, string $scope) : void public function oauth2(Oauth2GrantTypeEnum $grant, string $oauthTokenUrl, string $redirectUri, string $clientId, string $clientSecret, string $scope) : void
{ {
#return; $unid = sprintf("oauth_token_%s", md5(serialize($this->configuration)));
# TMP ! $token = apcu_fetch($unid, $exists);
if (false === ($_SESSION['grics_token'] ?? false)) {
# IF NO TOKEN OR TIMEOUT if ( ! $exists ) {
$provider = new GenericProvider([ $provider = new GenericProvider([
'clientId' => $clientId, 'clientId' => $clientId,
'clientSecret' => $clientSecret, 'clientSecret' => $clientSecret,
@ -111,22 +115,27 @@ class CurlAuthorization
try { try {
$accessToken = $provider->getAccessToken($grant->value); $accessToken = $provider->getAccessToken($grant->value);
$_SESSION['grics_token'] = $accessToken->getToken(); $token = json_encode($accessToken);
# var_dump($accessToken); die(); apcu_store($unid, $token, $accessToken->getExpires() - time() - 10);
}
# KEEP TOKEN IN APCU ! catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) {
} catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) { throw $e;
// Failed to get the access token
exit($e->getMessage());
} }
} }
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.");
}
$this->bearer($_SESSION['grics_token']); if ( empty($decodedToken) ) {
throw new \InvalidArgumentException("Given OAuth Token is empty");
}
#var_dump($_SESSION['grics_token']);die(); $this->bearer($decodedToken['access_token']);
# SWITCH TO BEARER AUTH TYPE, SET TOKEN FROM APCU IF STILL VALID
} }
protected function userpass(int $authType, string $username, #[\SensitiveParameter] $password) : void protected function userpass(int $authType, string $username, #[\SensitiveParameter] $password) : void