Compare commits

..
16 Commits
Author SHA1 Message Date
mcndave 042ff5cc2f - WIP on PHP 8.5 support 2026-07-14 15:53:21 +00:00
mcndave fe56a1727b - Added some more features to attributes and various Curl wrappers 2026-07-01 13:24:04 -04:00
Dave Mc Nicoll b1adf5a086 - Fixed some bugs regarding parameter binding 2026-06-15 19:36:09 +00:00
Dave Mc Nicoll 0550036fd9 - Added generateArray method for Enums 2025-05-21 18:36:46 +00:00
Dave Mc Nicoll 2d6f2c665b - WIP on destroy() method for an API DELET call 2025-02-04 08:46:27 -05:00
Dave Mc Nicoll 6d7701cfe5 - Added some more functionnality to API's connector 2024-12-02 15:24:26 -05:00
Dave Mc Nicoll 98c608dfba - Added missing escapeIdentifier() from the Adapter 2024-11-13 14:07:14 -05:00
Dave Mc Nicoll e945a7a9b2 - Fixed a bug within ApiRepository with MethodENum 2024-06-03 21:51:43 -04:00
mcndave fa1d4783d2 Fixed an important bug where request callback was never applied to request ! 2024-06-04 01:49:20 +00:00
Dave Mc Nicoll 7a31baa586 - Quickfix on missing debugResponse method of Apihandler 2024-04-24 11:23:43 -04:00
Dave Mc Nicoll 4e120cea6c - Added SearchRequest compatibilty and began working on QueryBuilder processing 2024-04-17 15:57:51 -04:00
Dave Mc Nicoll ce4d6cdf7c - Added Oauth2 authentication. Fixed non-working bearer token also. 2024-04-09 19:13:53 -04:00
mcndave f035fdf0a2 - Timeout now set at 30 sec like CURL is. 2024-02-26 11:14:04 -05:00
Dave Mc Nicoll 56ca1e2071 - Fixed a bug occuring within Timeout 2024-02-16 14:38:18 -05:00
Dave Mc Nicoll aba0ec3458 Merge branch 'master' of https://git.mcnd.ca/mcndave/ulmus-api 2023-11-17 16:02:05 -05:00
Dave Mc Nicoll c026762fdb - Fixed ternary where a false value was provided whenever the env var was empty 2023-11-17 16:01:54 -05:00
32 changed files with 815 additions and 195 deletions

No files matched your search

+41 -20
View File
@@ -2,6 +2,7 @@
namespace Ulmus\Api\Adapter; namespace Ulmus\Api\Adapter;
use Negundo\Client\Transport\Curl;
use Ulmus\Adapter\AdapterInterface; use Ulmus\Adapter\AdapterInterface;
use Ulmus\Api\ApiRepository; use Ulmus\Api\ApiRepository;
use Ulmus\Api\Common\AuthenticationEnum; use Ulmus\Api\Common\AuthenticationEnum;
@@ -13,23 +14,35 @@ use Ulmus\Ulmus;
class Rest implements AdapterInterface class Rest implements AdapterInterface
{ {
protected AuthenticationEnum $auth; protected CurlAuthorization $authorization;
protected string $username; protected AuthenticationEnum $authorizationMethod;
protected string $password;
protected string $token;
protected array $headers = []; protected array $headers = [];
protected array $options = []; protected array $options = [];
protected array $parameters = [];
public readonly string $url; 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 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 public function buildDataSourceName(): string
@@ -37,19 +50,7 @@ class Rest implements AdapterInterface
return "https"; return "https";
} }
public function setup(array $configuration): void public function writableValue(mixed $value) : mixed
{
$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) { switch (true) {
case $value instanceof \UnitEnum: case $value instanceof \UnitEnum:
@@ -77,4 +78,24 @@ 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;
}
public function escapeIdentifier(string $segment, int $type): string
{
return $segment;
}
} }
+164 -35
View File
@@ -4,17 +4,21 @@ 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\StreamInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UriInterface; use Psr\Http\Message\UriInterface;
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;
use Ulmus\Api\Request\Request;
use Ulmus\Api\Transport\CurlClient; use Ulmus\Api\Transport\CurlClient;
use Ulmus\Api\Transport\CurlTransport;
use Ulmus\Entity\EntityInterface;
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
{ {
@@ -22,34 +26,62 @@ 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, null|ConnectionAdapter $adapter = null)
{ {
parent::__construct($entity, $alias, $adapter); parent::__construct($entity, $alias, $adapter);
} }
public function loadOne(): ? object 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 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());
$entity->fromArray($response->getParsedBody()); 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; 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)); 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); $request = $this->callApiRequestCallback($request, $attribute);
$rest = $this->adapter->adapter()->connect(); $response = $this->launchRequest($request, $attribute);
$rest->timeout = $attribute->timeout;
$this->lastResponse = $response = $rest->fromRequest($request);
$response = $this->callApiResponseCallback($response, $attribute); $response = $this->callApiResponseCallback($response, $attribute);
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
@@ -129,9 +167,47 @@ 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)
->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 public function getApiAttribute(string $type) : ? object
@@ -139,29 +215,82 @@ 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, Attribute\Obj\Api\ApiAction $attribute) : ServerRequestInterface
{ {
return $this->adapter->apiHandler->handleRequest($request, $attribute); 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); 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) if (method_exists($this->adapter->apiHandler, 'debugResponse')) {
->wheres($searchRequest->wheres(), \Ulmus\Query\Where::OPERATOR_EQUAL, \Ulmus\Query\Where::CONDITION_AND) $this->adapter->apiHandler->debugResponse($transport, $this);
->likes($searchRequest->likes(), \Ulmus\Query\Where::CONDITION_OR) }
->orders($searchRequest->orders()) }
->groups($searchRequest->groups())
->offset($searchRequest->offset()) public function filterServerRequest(SearchRequestInterface $searchRequest, bool $count = true) : \Ulmus\Repository
->limit($searchRequest->limit()); {
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;
}
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);
}
} }
+3 -1
View File
@@ -10,6 +10,8 @@ abstract class ApiAction
public function __construct( public function __construct(
public string $url, public string $url,
public MethodEnum $method, 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,
) {} ) {}
} }
+4 -1
View File
@@ -5,9 +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 MethodEnum $searchMethod = MethodEnum::Get,
) {} ) {}
} }
+4 -1
View File
@@ -5,9 +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 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,
public int $timeout = 30,
public MethodEnum $searchMethod = MethodEnum::Get,
) {} ) {}
} }
+4 -1
View File
@@ -5,9 +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 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,
public int $timeout = 30,
public MethodEnum $searchMethod = MethodEnum::Get,
) {} ) {}
} }
+4 -1
View File
@@ -5,9 +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 MethodEnum $searchMethod = MethodEnum::Get,
) {} ) {}
} }
+16
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,
) {}
}
+4 -1
View File
@@ -5,9 +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 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,
public int $timeout = 30,
public MethodEnum $searchMethod = MethodEnum::Get,
) {} ) {}
} }
+4 -1
View File
@@ -5,11 +5,12 @@ namespace Ulmus\Api\Common;
use Psr\Http\Message\RequestInterface; use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Ulmus\Api\ApiHandlerInterface; use Ulmus\Api\ApiHandlerInterface;
use Ulmus\Api\ApiRepository;
use Ulmus\Api\Attribute\Obj\Api\ApiAction; use Ulmus\Api\Attribute\Obj\Api\ApiAction;
use Ulmus\Api\Transport\CurlTransport;
class ApiHandlerPassThrough implements ApiHandlerInterface class ApiHandlerPassThrough implements ApiHandlerInterface
{ {
public function handleRequest(RequestInterface $request, ApiAction $attribute): RequestInterface public function handleRequest(RequestInterface $request, ApiAction $attribute): RequestInterface
{ {
return $request; return $request;
@@ -19,4 +20,6 @@ class ApiHandlerPassThrough implements ApiHandlerInterface
{ {
return $response; return $response;
} }
public function debugResponse(CurlTransport $transport, ApiRepository $repository) : void {}
} }
+1 -1
View File
@@ -11,7 +11,7 @@ enum AuthenticationEnum : string
case Key = "key"; case Key = "key";
case Ntlm = "ntlm"; case Ntlm = "ntlm";
case Negotiate = "negotiate"; case Negotiate = "negotiate";
# case OAuth = "oauth"; case OAuth2 = "oauth2";
# case OpenID = "openid"; # case OpenID = "openid";
# case OpenAPI = "openapi"; # case OpenAPI = "openapi";
case Token = "token"; case Token = "token";
+3 -1
View File
@@ -5,8 +5,10 @@ namespace Ulmus\Api\Common;
enum ContentTypeEnum : string enum ContentTypeEnum : string
{ {
case Json = "application/json"; 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 UrlEncoded = "application/x-www-form-urlencoded";
case Multipart = "multipart/form-data";
public function defaultBody() : string public function defaultBody() : string
{ {
+2 -2
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)
+7
View File
@@ -13,4 +13,11 @@ enum MethodEnum : string
case Post = "POST"; # https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST ) 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 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 ) 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);
}
} }
+1 -1
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;
} }
+1 -1
View File
@@ -24,7 +24,7 @@ class Uri implements UriInterface
protected null|string $fragment; protected null|string $fragment;
public function __construct(string $uri = null) public function __construct(?string $uri = null)
{ {
$this->parse($uri); $this->parse($uri);
} }
+23
View File
@@ -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
View File
@@ -4,6 +4,7 @@ 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\SimpleCache\CacheInterface;
use Ulmus\Adapter\AdapterInterface; use Ulmus\Adapter\AdapterInterface;
use Ulmus\Api\Request\JsonRequest; use Ulmus\Api\Request\JsonRequest;
use Ulmus\Api\Response\JsonResponse; use Ulmus\Api\Response\JsonResponse;
@@ -12,24 +13,20 @@ use Ulmus\Ulmus;
class ConnectionAdapter extends \Ulmus\ConnectionAdapter class ConnectionAdapter extends \Ulmus\ConnectionAdapter
{ {
public string $name;
public array $configuration;
protected AdapterInterface $adapter; protected AdapterInterface $adapter;
private array $connection; private array $connection;
public readonly ApiHandlerInterface $apiHandler; public function __construct(
public readonly ApiHandlerInterface $apiHandler,
public function __construct(ApiHandlerInterface $apiHandler, $name = "default", array $configuration = [], bool $default = false) public string $name = "default",
{ protected array $configuration = [],
public bool $default = false,
public ? CacheInterface $cacheObject = null
) {
parent::__construct($name, $configuration, $default); parent::__construct($name, $configuration, $default);
$this->apiHandler = $apiHandler;
} }
public function resolveConfiguration(): void public function resolveConfiguration(): void
{ {
$connection = $this->configuration['connections'][$this->name] ?? []; $connection = $this->configuration['connections'][$this->name] ?? [];
@@ -71,18 +68,12 @@ class ConnectionAdapter extends \Ulmus\ConnectionAdapter
protected function instanciateAdapter($name): AdapterInterface 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(); 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
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,12 @@ class Request extends Message implements RequestInterface
protected MethodEnum $method; 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 = []) public function __construct(string|UriInterface $uri, MethodEnum $method, null|StreamInterface $stream = null, array $headers = [])
{ {
$this->method = $method; $this->method = $method;
@@ -70,9 +74,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 +105,119 @@ 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
{
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;
}
} }
+53 -76
View File
@@ -2,23 +2,25 @@
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 = [];
protected ? string $limitQueryField = "limit";
protected ? string $offsetQueryField = "offset";
protected ? string $pageQueryField = "page";
public function values(array $dataset) : self public function values(array $dataset) : self
{ {
@@ -27,79 +29,73 @@ 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 filter(\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 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 public function limit(int $value) : self
{ {
/*if ( null === $limit = $this->getFragment(Ulmus\Query\Limit::class) ) { return is_null($this->limitQueryField) ? $this : $this->filter($this->limitQueryField, $value);
$limit = new Query\Limit();
$this->push($limit);
}
$limit->set($value);
*/
return $this;
} }
public function offset(int $value) : self public function offset(int $value) : self
{ {
if ( null === $offset = $this->getFragment(Ulmus\Query\Offset::class) ) { return is_null($this->offsetQueryField) ? $this : $this->filter($this->offsetQueryField, $value);
$offset = new Ulmus\Query\Offset();
$this->push($offset);
}
$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 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; 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 +104,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 +120,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]);
} }
} }
} }
@@ -142,23 +134,8 @@ class RequestBuilder implements Ulmus\Query\QueryBuilderInterface
return $this->render(); return $this->render();
} }
public function addParameter($value, string $key = null) : string public function hashSerializedQuery(): string
{ {
if ( $this->parent ?? false ) { return "UNSUPPORTED";
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;
} }
} }
+24
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[(string) $field] = $value;
return $this;
}
public function render() : array
{
return [
static::KEY => $this->conditionList
];
}
}
+14
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;
}
+20
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);
}
}
+24
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
];
}
}
+29
View File
@@ -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));
}
}
+47
View File
@@ -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,
) {}
}
+2 -2
View File
@@ -107,7 +107,7 @@ class Stream implements StreamInterface
return $this->hasMode('acwx+'); return $this->hasMode('acwx+');
} }
public function write($string) : false|int public function write($string) : int
{ {
if (! $this->isWritable()) { if (! $this->isWritable()) {
throw new \UnexpectedValueException("An error occured while trying to seek an unseekable stream."); throw new \UnexpectedValueException("An error occured while trying to seek an unseekable stream.");
@@ -166,7 +166,7 @@ class Stream implements StreamInterface
return $result; return $result;
} }
public function getMetadata(string $key = null) : mixed public function getMetadata(?string $key = null) : mixed
{ {
$metadata = stream_get_meta_data($this->stream); $metadata = stream_get_meta_data($this->stream);
+49
View File
@@ -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);
}
}
+71 -10
View File
@@ -2,39 +2,52 @@
namespace Ulmus\Api\Transport; namespace Ulmus\Api\Transport;
use League\OAuth2\Client\Provider\GenericProvider;
use Ulmus\Api\Common\AuthenticationEnum; 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, string|false $username, #[\SensitiveParameter] string|false $password, #[\SensitiveParameter] string|false $token,) : static
{ {
switch($auth) { switch($auth) {
case AuthenticationEnum::Custom:
$this->customHeader($this->configuration['header'], $this->configuration['secret']);
break;
case AuthenticationEnum::Bearer: case AuthenticationEnum::Bearer:
$this->bearer($token); $this->bearer($this->configuration['token']);
break; break;
case AuthenticationEnum::Key: case AuthenticationEnum::Key:
$this->key($token); $this->key($this->configuration['token']);
break; break;
case AuthenticationEnum::Token: case AuthenticationEnum::Token:
$this->token($token); $this->token($this->configuration['token']);
break; break;
case AuthenticationEnum::Basic: case AuthenticationEnum::Basic:
$this->basic($username, $password); $this->basic($this->configuration['username'], $this->configuration['password']);
break; break;
case AuthenticationEnum::Ntlm: case AuthenticationEnum::Ntlm:
$this->ntlm($username, $password); $this->ntlm($this->configuration['username'], $this->configuration['password']);
break; break;
case AuthenticationEnum::Negotiate: 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; break;
} }
@@ -43,6 +56,7 @@ class CurlAuthorization
public function bearer(#[\SensitiveParameter] string $token) : void public function bearer(#[\SensitiveParameter] string $token) : void
{ {
$this->options[CURLOPT_HTTPAUTH] = CURLAUTH_BEARER;
$this->options[CURLOPT_XOAUTH2_BEARER] = $token; $this->options[CURLOPT_XOAUTH2_BEARER] = $token;
} }
@@ -86,6 +100,53 @@ class CurlAuthorization
$this->userpass(CURLAUTH_NEGOTIATE, $username, $password); $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 protected function userpass(int $authType, string $username, #[\SensitiveParameter] $password) : void
{ {
$this->options[CURLOPT_HTTPAUTH] = $authType; $this->options[CURLOPT_HTTPAUTH] = $authType;
+23 -9
View File
@@ -6,7 +6,7 @@ use Ulmus\Api\Common\{ ContentTypeEnum, MethodEnum, HttpHeaderEnum };
use Ulmus\Api\Stream\JsonStream; use Ulmus\Api\Stream\JsonStream;
use Ulmus\Api\Stream\Stream; use Ulmus\Api\Stream\Stream;
use Psr\Http\Message\{RequestInterface, ResponseInterface, StreamInterface}; use Psr\Http\Message\{RequestInterface, ResponseInterface, StreamInterface};
use Ulmus\Api\Response\{ Response, JsonResponse }; use Ulmus\Api\Response\{Response, JsonResponse, XmlResponse};
abstract class CurlTransport { abstract class CurlTransport {
@@ -27,7 +27,7 @@ abstract class CurlTransport {
public ContentTypeEnum $contentType = ContentTypeEnum::Json, public ContentTypeEnum $contentType = ContentTypeEnum::Json,
# Matching Guzzle's great user-agent syntax # Matching Guzzle's great user-agent syntax
public string $userAgent = "ulmus-api/1.0 curl/{curl} php/{php}", 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); $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); 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 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); $this->applyMethod($method, $data, $headers, $options);
$headers += $this->authorization->headers; if ($this->authorization) {
$headers += HttpHeaderEnum::normalizeHeaderArray($this->authorization->headers);
$options += $this->authorization->options; $options += $this->authorization->options;
}
$options += [ $options += [
CURLOPT_URL => $url, CURLOPT_URL => $url,
@@ -129,13 +130,26 @@ abstract class CurlTransport {
$response = $response->withBody( Stream::fromMemory($result) ); $response = $response->withBody( Stream::fromMemory($result) );
if ( $response->hasHeader('Content-Type') && false !== strpos($response->getHeader('Content-Type')[0], 'json') ) { if ($response->hasHeader('Content-Type') ) {
$response = JsonResponse::fromResponse($response); 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; 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 protected function applyMethod(MethodEnum $method, mixed &$data, array &$headers, array &$options) : void
{ {
switch ($method) { switch ($method) {
+12
View File
@@ -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";
}