- Still WIP on Api's wrapper

This commit is contained in:
Dave Mc Nicoll 2023-10-27 18:33:33 -04:00
parent 7e69a09e7f
commit 5d9c0f0dbf
16 changed files with 548 additions and 42 deletions

View File

@ -10,6 +10,7 @@
} }
], ],
"require": { "require": {
"ext-curl": "*",
"mcnd/ulmus": "dev-master" "mcnd/ulmus": "dev-master"
}, },
"repositories": [ "repositories": [

View File

@ -47,7 +47,6 @@ class Rest implements AdapterInterface
$this->$conf = $configuration[$conf]; $this->$conf = $configuration[$conf];
} }
} }
} }
public function writableValue(mixed $value) : mixed public function writableValue(mixed $value) : mixed

View File

@ -2,10 +2,18 @@
namespace Ulmus\Api; namespace Ulmus\Api;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
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\Stream\Stream;
use Ulmus\Api\Request\JsonRequest;
use Ulmus\Api\Request\Request;
use Ulmus\Api\Transport\CurlClient; use Ulmus\Api\Transport\CurlClient;
use Ulmus\EntityCollection; use Ulmus\EntityCollection;
use Ulmus\SearchRequest\SearchRequestInterface;
class ApiRepository extends \Ulmus\Repository class ApiRepository extends \Ulmus\Repository
{ {
@ -29,29 +37,41 @@ class ApiRepository extends \Ulmus\Repository
# Must manager URL here ! # Must manager URL here !
$attribute = $this->getApiAttribute(Attribute\Obj\Api\Read::class); $attribute = $this->getApiAttribute(Attribute\Obj\Api\Read::class);
$this->lastResponse = $response = $this->client->request($attribute->method, $this->buildRequestUrl($attribute->url)); $request = $this->prepareRequest($this->buildRequestUrl($attribute->url), $attribute->method);
$dataset = $this->callApiResponseCallback($response, $attribute); $request = $this->callApiRequestCallback($request, $attribute);
return $this->instanciateEntity()->fromArray($dataset); $this->lastResponse = $response = $this->client->fromRequest($request);
$response = $this->callApiResponseCallback($response, $attribute);
return $this->instanciateEntity()->fromArray($response->render());
} }
public function loadAll() : EntityCollection public function loadAll() : EntityCollection
{ {
$attribute = $this->getApiAttribute(Attribute\Obj\Api\Collection::class); $attribute = $this->getApiAttribute(Attribute\Obj\Api\Collection::class);
$this->lastResponse = $response = $this->client->request($attribute->method, $this->buildRequestUrl($attribute->url)); $request = $this->prepareRequest($this->buildRequestUrl($attribute->url), $attribute->method);
$collection = $this->callApiResponseCallback($response, $attribute); $request = $this->callApiRequestCallback($request, $attribute);
return $this->instanciateEntityCollection($collection); $this->lastResponse = $response = $this->client->fromRequest($request);
$response = $this->callApiResponseCallback($response, $attribute);
return $this->instanciateEntityCollection()->fromArray($response->render());
} }
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
{ {
$attribute = $this->getApiAttribute(Attribute\Obj\Api\Create::class); $attribute = $this->getApiAttribute(Attribute\Obj\Api\Create::class);
$this->lastResponse = $response = $this->client->request($attribute->method, $this->buildRequestUrl($attribute->url), $entity->entityLoadedDataset); $request = $this->prepareRequest($this->buildRequestUrl($attribute->url), $attribute->method);
$this->callApiRequestCallback($request, $attribute);
$this->lastResponse = $response = $this->client->fromRequest($request);
$ret = $this->callApiResponseCallback($response, $attribute); $ret = $this->callApiResponseCallback($response, $attribute);
@ -112,13 +132,40 @@ class ApiRepository extends \Ulmus\Repository
return $route; return $route;
} }
protected function prepareRequest(string|UriInterface $uri, MethodEnum $method, null|string $body = null, array $headers = []) : RequestInterface
{
return new JsonRequest($uri, $method, $body === null ? Stream::fromTemp() : Stream::fromMemory($body), $headers);
}
public function getApiAttribute(string $type) : object public function getApiAttribute(string $type) : object
{ {
return $this->entityClass::resolveEntity()->getAttributeImplementing($type); return $this->entityClass::resolveEntity()->getAttributeImplementing($type);
} }
protected function callApiRequestCallback(RequestInterface $request, ApiAction $attribute) : mixed
{
return call_user_func_array($this->adapter->apiRequestCallback(), func_get_args());
}
protected function callApiResponseCallback(ResponseInterface $response, ApiAction $attribute) : mixed protected function callApiResponseCallback(ResponseInterface $response, ApiAction $attribute) : mixed
{ {
return call_user_func_array($this->adapter->apiResponseCallback(), func_get_args()); return call_user_func_array($this->adapter->apiResponseCallback(), func_get_args());
} }
public function count(): int
{
return 0;
}
public function filterServerRequest(SearchRequestInterface $searchRequest, bool $count = true) : self
{
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());
}
} }

View File

@ -1,6 +1,6 @@
<?php <?php
namespace Ulmus\Api\Response; namespace Ulmus\Api\Common;
use Psr\Http\Message\MessageInterface; use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\StreamInterface; use Psr\Http\Message\StreamInterface;
@ -19,7 +19,7 @@ class Message implements MessageInterface
return $this->protocolVersion; return $this->protocolVersion;
} }
public function withProtocolVersion(string $version) : MessageInterface public function withProtocolVersion($version) : MessageInterface
{ {
return (clone $this)->setProtocolVersion($version); return (clone $this)->setProtocolVersion($version);
} }
@ -47,12 +47,12 @@ class Message implements MessageInterface
return $output; return $output;
} }
public function hasHeader(string $name) : bool public function hasHeader($name) : bool
{ {
return isset($this->headers[HttpHeaderEnum::normalizeHeaderKey($name)]); return isset($this->headers[HttpHeaderEnum::normalizeHeaderKey($name)]);
} }
public function getHeader(string $name) : array public function getHeader($name) : array
{ {
$name = HttpHeaderEnum::normalizeHeaderKey($name); $name = HttpHeaderEnum::normalizeHeaderKey($name);
@ -63,7 +63,7 @@ class Message implements MessageInterface
return $this->headers[$name]; return $this->headers[$name];
} }
public function getHeaderLine(string $name) : string public function getHeaderLine($name) : string
{ {
$name = HttpHeaderEnum::normalizeHeaderKey($name); $name = HttpHeaderEnum::normalizeHeaderKey($name);
@ -74,17 +74,17 @@ class Message implements MessageInterface
return sprintf("%s: %s", $name, implode(', ', $this->getHeader($name))); return sprintf("%s: %s", $name, implode(', ', $this->getHeader($name)));
} }
public function withHeader(string $name, mixed $value): MessageInterface public function withHeader($name, mixed $value): MessageInterface
{ {
return (clone $this)->setHeaders(array_merge_recursive($this->headers, [ HttpHeaderEnum::normalizeHeaderKey($name) => $value ])); return (clone $this)->setHeaders(array_merge_recursive($this->headers, [ HttpHeaderEnum::normalizeHeaderKey($name) => $value ]));
} }
public function withAddedHeader(string $name, mixed $value) : MessageInterface public function withAddedHeader($name, mixed $value) : MessageInterface
{ {
return (clone $this)->setHeaders([ HttpHeaderEnum::normalizeHeaderKey($name) => $value ] + $this->headers); return (clone $this)->setHeaders([ HttpHeaderEnum::normalizeHeaderKey($name) => $value ] + $this->headers);
} }
public function withoutHeader(string $name) : MessageInterface public function withoutHeader($name) : MessageInterface
{ {
return (clone $this)->unsetHeader($name); return (clone $this)->unsetHeader($name);
} }

View File

@ -1,6 +1,6 @@
<?php <?php
namespace Ulmus\Api\Request; namespace Ulmus\Api\Common;
interface StreamOutputInterface interface StreamOutputInterface
{ {

241
src/Common/Uri.php Normal file
View File

@ -0,0 +1,241 @@
<?php
namespace Ulmus\Api\Common;
use Psr\Http\Message\UriInterface;
class Uri implements UriInterface
{
public const IGNORE_HTTP_PORT = [ 80, 443 ];
protected string $scheme;
protected null|string $user;
protected null|string $password;
protected string $host;
protected null|int $port;
protected null|string $path;
protected null|array $query;
protected null|string $fragment;
public function __construct(string $uri = null)
{
$this->parse($uri);
}
public static function from(string|UriInterface $uri = "") : UriInterface
{
if ($uri instanceof UriInterface) {
return $uri;
}
return new static($uri);
}
protected function parse(string $uri): void
{
if (false === $parts = parse_url($uri)) {
throw new \InvalidArgumentException(sprintf("Malformed URIs was provided (%s)", $uri));
}
$this->setScheme($parts['scheme'] ?? "");
$this->setUserInfo($parts['user'] ?? null, $parts['pass'] ?? null);
$this->setHost($parts['host'] ?? "");
$this->setPort($parts['port'] ?? null);
$this->setPath($parts['path'] ?? null);
$this->setQuery($parts['query'] ?? []);
$this->setFragment($parts['fragment'] ?? null);
}
public function getScheme()
{
return $this->scheme;
}
public function renderScheme() : string
{
return $this->scheme ? "{$this->scheme}:" : "";
}
protected function setScheme(string $scheme) : self
{
$this->scheme = $scheme;
return $this;
}
public function getAuthority() : string
{
return implode('', array_filter([
$this->getHost(), $this->renderUserInfo(), $this->renderPort(), $this->renderFragment()
]));
}
public function renderAuthority() : string
{
$authority = $this->getAuthority();
return $authority ? "//{$authority}" : $authority;
}
public function getUserInfo() : string
{
return $this->user . ( $this->password ? ":{$this->password}" : "" );
}
public function renderUserInfo() : string
{
return ( $info = $this->getUserInfo() ) ? "{$info}@" : "";
}
public function getHost()
{
return $this->host;
}
protected function setHost(string $host) : static
{
$this->host = $host;
return $this;
}
public function getPort()
{
return $this->port;
}
public function renderPort(array $ignore = self::IGNORE_HTTP_PORT) : string
{
$port = $this->getPort();
return $port && ! in_array($port, $ignore) ? ":{$port}" : "";
}
public function getPath()
{
$path = implode('/', array_map('rawurlencode', explode('/', $this->path)));
return str_starts_with($this->path, '/') ? '/' . ltrim($this->path, '/.') : $this->path;
}
public function renderPath() : string
{
return $this->getPath();
}
public function getQuery()
{
return http_build_query($this->query, "", null, PHP_QUERY_RFC3986);
}
public function renderQuery() : string
{
$query = $this->getQuery();
return $query ? "?{$query}" : "";
}
public function getFragment()
{
return rawurlencode($this->fragment ? "#{$this->fragment}" : "");
}
public function renderFragment() : string
{
return $this->getFragment();
}
public function withScheme($scheme)
{
return (clone $this)->setScheme($scheme);
}
public function withUserInfo($user, $password = null)
{
return (clone $this)->setUserInfo($user, $password);
}
protected function setUserInfo(null|string $user, null|string $password) : static
{
$this->user = $user;
$this->password = $password;
return $this;
}
public function withHost($host)
{
return (clone $this)->setHost($host);
}
public function withPort($port)
{
return (clone $this)->setPort($port);
}
protected function setPort(null|int $port) : static
{
$this->port = $port;
return $this;
}
public function withPath($path)
{
return (clone $this)->setPath($path);
}
protected function setPath(null|string $path) : static
{
$this->path = $path;
return $this;
}
public function withQuery($query)
{
return $this->setQuery($query);
}
protected function setQuery(null|array|string $query) : static
{
if (is_string($query)) {
parse_str($query, $parsed);
}
$this->query = $parsed ?? $query;
return $this;
}
public function withFragment($fragment)
{
return (clone $this)->setFragment($fragment);
}
protected function setFragment(null|string $fragment) : static
{
$this->fragment = $fragment;
return $this;
}
public function __toString()
{
return $this->render();
}
public function render() : string
{
return implode('', array_filter([
$this->renderScheme(), $this->renderAuthority(), $this->renderPath(), $this->renderQuery(), $this->renderFragment()
]));
}
}

View File

@ -16,6 +16,8 @@ class ConnectionAdapter extends \Ulmus\ConnectionAdapter
protected mixed $apiResponseCallback; protected mixed $apiResponseCallback;
protected mixed $apiRequestCallback;
public function resolveConfiguration(): void public function resolveConfiguration(): void
{ {
$connection = $this->configuration['connections'][$this->name] ?? []; $connection = $this->configuration['connections'][$this->name] ?? [];
@ -66,4 +68,9 @@ class ConnectionAdapter extends \Ulmus\ConnectionAdapter
{ {
return $callback ? $this->apiResponseCallback = $callback : $this->apiResponseCallback ?? fn($resp) => $resp->render(); return $callback ? $this->apiResponseCallback = $callback : $this->apiResponseCallback ?? fn($resp) => $resp->render();
} }
public function apiRequestCallback(callable $callback = null) : callable|null
{
return $callback ? $this->apiRequestCallback = $callback : $this->apiRequestCallback ?? fn($resp) => $resp->render();
}
} }

View File

@ -0,0 +1,58 @@
<?php
namespace Ulmus\Api\Request;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;
use Ulmus\Api\Common\HttpHeaderEnum;
use Ulmus\Api\Common\MethodEnum;
use Ulmus\Api\Stream\Stream;
use Ulmus\Api\Common\StreamOutputInterface;
use Ulmus\Api\Common\Uri;
class JsonRequest extends Request implements StreamOutputInterface
{
public const DEFAULT_JSON_ENCODING_FLAGS = JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_TAG | JSON_UNESCAPED_SLASHES;
public const DEFAULT_JSON_DECODING_FLAGS = JSON_THROW_ON_ERROR;
public int $jsonEncodingOptions;
public function __construct(string|UriInterface $uri, MethodEnum $method, null|StreamInterface $stream = null, array $headers = [], $jsonEncodingOptions = self::DEFAULT_JSON_ENCODING_FLAGS)
{
parent::__construct($uri, $method, $stream, $headers + [ 'Content-Type' => "application/json", 'Accept' => "application/json" ]);
$this->jsonEncodingOptions = $jsonEncodingOptions;
}
public static function fromContent(mixed $content, int $encodingOptions = self::DEFAULT_JSON_ENCODING_FLAGS) : StreamInterface
{
if ( false === $json = json_encode($content, $encodingOptions) ) {
throw new \InvalidArgumentException(sprintf('Json encoding failed with message `%s`.', json_last_error_msg()));
}
return static::fromJsonEncoded($json);
}
public static function fromJsonEncoded(string $json) : StreamInterface
{
$obj = Stream::fromTemp($json);
$obj->rewind();
return $obj;
}
public static function fromRequest(RequestInterface $request) : static
{
return new static($request->getUri(), MethodEnum::from($request->getMethod()), $request->getBody(), $request->getHeaders());
}
public function render(): mixed
{
$content = $this->getBody()->getContents() ?: "[]";
return json_decode($content, true, 1024, static::DEFAULT_JSON_DECODING_FLAGS);
}
}

104
src/Request/Request.php Normal file
View File

@ -0,0 +1,104 @@
<?php
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};
class Request extends Message implements RequestInterface
{
protected int $code;
protected string $message;
protected string $target;
protected UriInterface $uri;
protected MethodEnum $method;
public function __construct(string|UriInterface $uri, MethodEnum $method, null|StreamInterface $stream = null, array $headers = [])
{
$this->method = $method;
$this->uri = Uri::from($uri);
$this->stream = $stream ?? Stream::fromMemory();
$this->setHeaders($headers );
}
public function getRequestTarget(): string
{
if ( isset($this->target) ) {
return $this->target;
}
$query = $this->uri->getQuery() ?: null;
$target = $this->uri->getPath() . ( $query ? "?{$query}" : "" );
return $target ?: '/';
}
public function withRequestTarget($requestTarget)
{
return (clone $this)->setRequestTarget($requestTarget);
}
public function setRequestTarget(string $target) : static
{
$this->target = $target;
return $this;
}
public function getMethod()
{
return $this->method->value;
}
public function withMethod($method)
{
return (clone $this)->setMethod($method);
}
protected function setMethod(string $method) : static
{
$this->method = MethodEnum::from($method);
return $this;
}
public function getUri()
{
return (string) $this->uri;
}
public function withUri(UriInterface $uri, $preserveHost = false)
{
return (clone $this)->setUri($uri, $preserveHost);
}
protected function setUri(UriInterface|string $uri, bool $preserveHost = false) : static
{
$this->uri = Uri::from($uri);
if ( ! $preserveHost || ! $this->hasHeader('Host') ) {
$this->headers['Host'] = [ $this->renderHostHeader() ];
}
return $this;
}
protected function renderHostHeader(UriInterface $fromUri = null) : string
{
$fromUri ??= $this->uri;
$port = $fromUri->getPort();
return implode('', [
$fromUri->getHost(), $port && ! in_array($port, Uri::IGNORE_HTTP_PORT) ? ":{$port}" : ""
]);
}
}

View File

@ -56,13 +56,13 @@ class RequestBuilder implements Ulmus\Query\QueryBuilderInterface
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) ) {
$limit = new Query\Limit(); $limit = new Query\Limit();
$this->push($limit); $this->push($limit);
} }
$limit->set($value); $limit->set($value);
*/
return $this; return $this;
} }
@ -128,7 +128,7 @@ class RequestBuilder implements Ulmus\Query\QueryBuilderInterface
return null; return null;
} }
public function removeFragment(Ulmus\Query\Fragment $fragment) : void public function removeFragment(\Ulmus\Query\Fragment|array|\Stringable|string $fragment) : void
{ {
foreach($this->queryStack as $key => $item) { foreach($this->queryStack as $key => $item) {
if ( $item === $fragment ) { if ( $item === $fragment ) {

View File

@ -5,8 +5,8 @@ namespace Ulmus\Api\Response;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface; use Psr\Http\Message\StreamInterface;
use Ulmus\Api\Common\HttpHeaderEnum; use Ulmus\Api\Common\HttpHeaderEnum;
use Ulmus\Api\Request\Stream; use Ulmus\Api\Stream\Stream;
use Ulmus\Api\Request\StreamOutputInterface; use Ulmus\Api\Common\StreamOutputInterface;
class JsonResponse extends Response implements StreamOutputInterface class JsonResponse extends Response implements StreamOutputInterface
{ {
@ -34,7 +34,7 @@ class JsonResponse extends Response implements StreamOutputInterface
public static function fromJsonEncoded(string $json) : StreamInterface public static function fromJsonEncoded(string $json) : StreamInterface
{ {
$obj = static::fromTemp($json); $obj = Stream::fromTemp($json);
$obj->rewind(); $obj->rewind();
return $obj; return $obj;

View File

@ -4,7 +4,8 @@ namespace Ulmus\Api\Response;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface; use Psr\Http\Message\StreamInterface;
use Ulmus\Api\Request\Stream; use Ulmus\Api\Common\{ Message };
use Ulmus\Api\Stream\Stream;
class Response extends Message implements ResponseInterface class Response extends Message implements ResponseInterface
{ {
@ -15,7 +16,7 @@ class Response extends Message implements ResponseInterface
public function __construct(null|StreamInterface $stream = null, int $statusCode = 200, array $headers = []) public function __construct(null|StreamInterface $stream = null, int $statusCode = 200, array $headers = [])
{ {
$this->setStatusCode($statusCode); $this->setStatusCode($statusCode);
$this->stream = $stream ?? new Stream("php://memory", "wb+"); $this->stream = $stream ?? Stream::fromMemory();
$this->setHeaders($headers); $this->setHeaders($headers);
} }
@ -24,7 +25,7 @@ class Response extends Message implements ResponseInterface
return $this->code; return $this->code;
} }
public function withStatus(int $code, null|string $reasonPhrase = null)/* : ResponseInterface*/ public function withStatus($code, $reasonPhrase = null)/* : ResponseInterface*/
{ {
return (clone $this)->setStatusCode($code, $reasonPhrase); return (clone $this)->setStatusCode($code, $reasonPhrase);
} }

50
src/Stream/JsonStream.php Normal file
View File

@ -0,0 +1,50 @@
<?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;
class JsonStream extends Stream implements StreamOutputInterface
{
public const DEFAULT_JSON_ENCODING_FLAGS = JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_TAG | JSON_UNESCAPED_SLASHES;
public const DEFAULT_JSON_DECODING_FLAGS = JSON_THROW_ON_ERROR;
public int $depth = 1024;
public int $encodingOptions;
public int $decodingOptions;
public function __construct($encodingOptions = self::DEFAULT_JSON_ENCODING_FLAGS, $decodingOptions = self::DEFAULT_JSON_DECODING_FLAGS)
{
$this->encodingOptions = $encodingOptions;
$this->decodingOptions = $decodingOptions;
}
public static function fromContent(mixed $content, int $encodingOptions = self::DEFAULT_JSON_ENCODING_FLAGS) : StreamInterface
{
if ( false === $json = json_encode($content, $encodingOptions) ) {
throw new \InvalidArgumentException(sprintf('Json encoding failed with message `%s`.', json_last_error_msg()));
}
return static::fromJsonEncoded($json);
}
public static function fromJsonEncoded(string $json) : StreamInterface
{
$obj = Stream::fromTemp($json);
$obj->rewind();
return $obj;
}
public function render(): mixed
{
return json_decode($this->getContents(), true, $this->depth, $this->decodingOptions);
}
}

View File

@ -1,6 +1,6 @@
<?php <?php
namespace Ulmus\Api\Request; namespace Ulmus\Api\Stream;
use Psr\Http\Message\StreamInterface; use Psr\Http\Message\StreamInterface;
@ -104,7 +104,7 @@ class Stream implements StreamInterface
public function isWritable() : bool public function isWritable() : bool
{ {
return $this->hasMode('wxca+'); return $this->hasMode('acwx+');
} }
public function write($string) : false|int public function write($string) : false|int
@ -137,7 +137,7 @@ class Stream implements StreamInterface
return $this->hasMode('r+'); return $this->hasMode('r+');
} }
public function read(int $length): string public function read($length): string
{ {
if (! $this->isReadable()) { if (! $this->isReadable()) {
throw new \UnexpectedValueException("An error occured while trying to read a stream which was not readable."); throw new \UnexpectedValueException("An error occured while trying to read a stream which was not readable.");
@ -178,6 +178,6 @@ class Stream implements StreamInterface
$meta = stream_get_meta_data($this->stream); $meta = stream_get_meta_data($this->stream);
$mode = $meta['mode']; $mode = $meta['mode'];
return strpbrk($mode, 'acwx+') !== false; return strpbrk($mode, $search) !== false;
} }
} }

View File

@ -29,12 +29,12 @@ class CurlAuthorization
$this->basic($username, $password); $this->basic($username, $password);
break; break;
case AuthenticationEnum::NTLM: case AuthenticationEnum::Ntlm:
$this->ntlm($username, $password); $this->ntlm($username, $password);
break; break;
case AuthenticationEnum::Negotiate: case AuthenticationEnum::Negotiate:
$this->ntlm($username, $password); $this->negotiate($username, $password);
break; break;
} }

View File

@ -3,12 +3,9 @@
namespace Ulmus\Api\Transport; namespace Ulmus\Api\Transport;
use Ulmus\Api\Common\{ ContentTypeEnum, MethodEnum, HttpHeaderEnum }; use Ulmus\Api\Common\{ ContentTypeEnum, MethodEnum, HttpHeaderEnum };
use Psr\Http\Message\ResponseInterface; use Ulmus\Api\Stream\Stream;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\{ RequestInterface, ResponseInterface };
use Ulmus\Api\Response\JsonResponse; use Ulmus\Api\Response\{ Response, JsonResponse };
use Ulmus\Api\Response\Response;
use Ulmus\Api\Request\Stream;
use Ulmus\Attribute\Obj\Method;
abstract class CurlTransport { abstract class CurlTransport {
@ -52,16 +49,16 @@ abstract class CurlTransport {
return $this->request(MethodEnum::Put, $url, $data, $headers, $curlOptions); return $this->request(MethodEnum::Put, $url, $data, $headers, $curlOptions);
} }
public function fromRequest(ServerRequestInterface $request) : ResponseInterface public function fromRequest(RequestInterface $request) : ResponseInterface
{ {
return $this->request($request->getMethod(), $request->getUri(), $request->getBody()->getContents(), $request->getHeaders()); return $this->request(MethodEnum::from($request->getMethod()), $request->getUri(), $request->getBody()->getContents(), $request->getHeaders());
} }
public function request(MethodEnum $method, string $url, mixed $data = null, array $headers = [], array $options = []) : ResponseInterface public function request(MethodEnum $method, string $url, mixed $data = null, array $headers = [], array $options = []) : ResponseInterface
{ {
$response = new Response(); $response = new Response();
$headers = HttpHeaderEnum::normalizeHeaderArray($headers + $this->headers); $headers = array_merge_recursive(HttpHeaderEnum::normalizeHeaderArray($headers), HttpHeaderEnum::normalizeHeaderArray($this->headers));
$options += $this->curlOptions; $options += $this->curlOptions;
@ -83,6 +80,7 @@ abstract class CurlTransport {
CURLOPT_HEADERFUNCTION => function($curl, $headerLine) use (& $response) { CURLOPT_HEADERFUNCTION => function($curl, $headerLine) use (& $response) {
if ( strpos($headerLine, ':') ) { if ( strpos($headerLine, ':') ) {
list($key, $value) = array_map('trim', explode(':', $headerLine)); list($key, $value) = array_map('trim', explode(':', $headerLine));
$response = $response->withHeader($key, $value); $response = $response->withHeader($key, $value);
} }
elseif ( strtoupper(substr($headerLine, 0, 4)) === 'HTTP' ) { elseif ( strtoupper(substr($headerLine, 0, 4)) === 'HTTP' ) {
@ -145,7 +143,7 @@ abstract class CurlTransport {
switch($this->contentType) { switch($this->contentType) {
case ContentTypeEnum::Json: case ContentTypeEnum::Json:
$options[CURLOPT_POSTFIELDS] = json_encode($data, JsonResponse::DEFAULT_JSON_ENCODING_FLAGS); $options[CURLOPT_POSTFIELDS] = json_encode(is_string($data) && empty($data) ? new \stdClass() : $data, JsonResponse::DEFAULT_JSON_ENCODING_FLAGS);
$headers['Accept'] ??= $this->contentType->value; $headers['Accept'] ??= $this->contentType->value;
break; break;