266 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			266 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
 | 
						|
namespace Ulmus\Api;
 | 
						|
 | 
						|
use Psr\Http\Message\RequestInterface;
 | 
						|
use Psr\Http\Message\ResponseInterface;
 | 
						|
use Psr\Http\Message\ServerRequestInterface;
 | 
						|
use Psr\Http\Message\StreamInterface;
 | 
						|
use Psr\Http\Message\UriInterface;
 | 
						|
use Ulmus\Api\Attribute\Obj\Api\ApiAction;
 | 
						|
use Ulmus\Api\Common\MethodEnum;
 | 
						|
use Ulmus\Api\RequestBuilder\Filter;
 | 
						|
use Ulmus\Api\RequestBuilder\UrlParameter;
 | 
						|
use Ulmus\Api\SearchRequest\ApiSearchRequest;
 | 
						|
use Ulmus\Api\Stream\JsonStream;
 | 
						|
use Ulmus\Api\Stream\Stream;
 | 
						|
use Ulmus\Api\Request\JsonRequest;
 | 
						|
use Ulmus\Api\Request\Request;
 | 
						|
use Ulmus\Api\Transport\CurlClient;
 | 
						|
use Ulmus\Api\Transport\CurlTransport;
 | 
						|
use Ulmus\EntityCollection;
 | 
						|
use Ulmus\SearchRequest\SearchRequestInterface;
 | 
						|
use Ulmus\Ulmus;
 | 
						|
 | 
						|
class ApiRepository extends \Ulmus\Repository
 | 
						|
{
 | 
						|
    const DEFAULT_ALIAS = "";
 | 
						|
 | 
						|
    public CurlClient $client;
 | 
						|
 | 
						|
    public ResponseInterface $lastResponse;
 | 
						|
 | 
						|
    public function __construct(string $entity, string $alias = self::DEFAULT_ALIAS, ConnectionAdapter $adapter = null)
 | 
						|
    {
 | 
						|
        parent::__construct($entity, $alias, $adapter);
 | 
						|
    }
 | 
						|
 | 
						|
    public function loadOne(): ? object
 | 
						|
    {
 | 
						|
        return $this->loadOneFromAttribute(Attribute\Obj\Api\Read::class);
 | 
						|
    }
 | 
						|
 | 
						|
    public function loadOneFromAttribute(string $attributeClass): ? object
 | 
						|
    {
 | 
						|
        $response = $this->executeRequest($attributeClass);
 | 
						|
 | 
						|
        return $this->instanciateEntity()->fromArray($response->getParsedBody());
 | 
						|
    }
 | 
						|
 | 
						|
    public function loadAll() : EntityCollection
 | 
						|
    {
 | 
						|
        return $this->loadAllFromAttribute(Attribute\Obj\Api\Collection::class);
 | 
						|
    }
 | 
						|
 | 
						|
    public function loadAllFromAttribute(string $attributeClass) : EntityCollection
 | 
						|
    {
 | 
						|
        $response = $this->executeRequest($attributeClass);
 | 
						|
 | 
						|
        return $this->instanciateEntityCollection()->fromArray($response->getParsedBody());
 | 
						|
    }
 | 
						|
 | 
						|
    public function search() : EntityCollection
 | 
						|
    {
 | 
						|
        return $this->loadAllFromAttribute(Attribute\Obj\Api\Search::class);
 | 
						|
    }
 | 
						|
 | 
						|
    public function save(object|array $entity, ?array $fieldsAndValue = null, bool $replace = false): bool
 | 
						|
    {
 | 
						|
        $response = $this->executeRequest(Attribute\Obj\Api\Create::class, $entity->entityGetDataset());
 | 
						|
 | 
						|
        $entity->fromArray($response->getParsedBody());
 | 
						|
 | 
						|
        return $response->getStatusCode() === 200;
 | 
						|
    }
 | 
						|
 | 
						|
    public function executeRequest(string $attributeClass, mixed $data = null) : ResponseInterface
 | 
						|
    {
 | 
						|
        $attribute = $this->getApiAttribute($attributeClass);
 | 
						|
 | 
						|
        if ($attribute === null) {
 | 
						|
            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, $this->adapter->adapter()->getHeaders(), $this->adapter->adapter()->getParameters());
 | 
						|
 | 
						|
        $request = $this->compileRequestParameters($request, $attribute);
 | 
						|
 | 
						|
        $request = $this->callApiRequestCallback($request, $attribute);
 | 
						|
 | 
						|
        $response = $this->launchRequest($request, $attribute);
 | 
						|
 | 
						|
        $response = $this->callApiResponseCallback($response, $attribute);
 | 
						|
 | 
						|
        return $response;
 | 
						|
    }
 | 
						|
 | 
						|
    protected function buildRequestUrl(string $uri) : string
 | 
						|
    {
 | 
						|
        return $this->adapter->adapter()->url . '/' . ltrim($uri, '/');
 | 
						|
    }
 | 
						|
 | 
						|
    protected function launchRequest(RequestInterface $request, object $attribute) : ResponseInterface
 | 
						|
    {
 | 
						|
        $transport = $this->adapter->adapter()->connect();
 | 
						|
 | 
						|
        $transport->timeout = $attribute->timeout;
 | 
						|
 | 
						|
        $this->lastResponse = $transport->fromRequest($request);
 | 
						|
 | 
						|
        $this->callApiDebugCallback($transport);
 | 
						|
 | 
						|
        return $this->lastResponse;
 | 
						|
    }
 | 
						|
 | 
						|
    protected function prepareUri(string $route, array $arguments) : string
 | 
						|
    {
 | 
						|
        if ( preg_match_all('~{(.*?)}~si', $route, $matches, PREG_SET_ORDER) ) {
 | 
						|
            $search = [];
 | 
						|
 | 
						|
            foreach($matches as $item) {
 | 
						|
                $default = null;
 | 
						|
 | 
						|
                $variable = $item[1];
 | 
						|
 | 
						|
                # Handles default
 | 
						|
                if (strpos($variable, "=") !== false) {
 | 
						|
                    list($variable, $default) = explode('=', $item[1]);
 | 
						|
                }
 | 
						|
 | 
						|
                if ( array_key_exists($variable, $arguments) ) {
 | 
						|
                    $value = $arguments[$variable];
 | 
						|
 | 
						|
                    unset($arguments[$variable]);
 | 
						|
                }
 | 
						|
                else {
 | 
						|
                    if ($default ?? false) {
 | 
						|
                        $value = $default;
 | 
						|
                    }
 | 
						|
                    elseif ( strpos($route, "[{$matches[0][0]}]") !== false && $this->enforceExistingArguments) {
 | 
						|
                        throw new \RuntimeException(sprintf("Error while preparing route %s : could not match variable '%s' into given arguments ( %s ) from %s::%s", $route, $variable, json_encode($arguments), $routeParam['class'], $routeParam['classMethod']));
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
                $search[$item[0]] = rawurlencode($value ?? "");
 | 
						|
            }
 | 
						|
 | 
						|
            $route = str_replace(array_keys($search), array_values($search), $route);
 | 
						|
        }
 | 
						|
 | 
						|
        return $route;
 | 
						|
    }
 | 
						|
 | 
						|
    protected function prepareRequest(string|UriInterface $uri, MethodEnum $method, mixed $body = null, array $headers = [], array $queryParameters = []) : ServerRequestInterface
 | 
						|
    {
 | 
						|
        $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
 | 
						|
    {
 | 
						|
        return $this->entityClass::resolveEntity()->getAttributeImplementing($type);
 | 
						|
    }
 | 
						|
 | 
						|
    protected function callApiRequestCallback(RequestInterface $request, ApiAction $attribute) : ServerRequestInterface
 | 
						|
    {
 | 
						|
        return $this->adapter->apiHandler->handleRequest($request, $attribute);
 | 
						|
    }
 | 
						|
 | 
						|
    protected function callApiResponseCallback(ResponseInterface $response, ApiAction $attribute) : ResponseInterface
 | 
						|
    {
 | 
						|
        return $this->adapter->apiHandler->handleResponse($response, $attribute);
 | 
						|
    }
 | 
						|
 | 
						|
    protected function callApiDebugCallback(CurlTransport $transport) : void
 | 
						|
    {
 | 
						|
        if (method_exists($this->adapter->apiHandler, 'debugResponse')) {
 | 
						|
            $this->adapter->apiHandler->debugResponse($transport, $this);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    public function collectionFromQuery(? string $entityClass = null) : EntityCollection
 | 
						|
    {
 | 
						|
        $entityClass ??= $this->entityClass;
 | 
						|
 | 
						|
        $entityCollection = $entityClass::entityCollection();
 | 
						|
 | 
						|
        $this->finalizeQuery();
 | 
						|
 | 
						|
        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
 | 
						|
    {
 | 
						|
        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;
 | 
						|
    }
 | 
						|
} |