update to PHP-8.1
[m6w6/seekat] / lib / API / Call / Deferred.php
index 7fc44de1ba607c50c36e22c4ee9c39fae130b887..331cc2f65526aec5943c6f542c0100cc06703593 100644 (file)
 
 namespace seekat\API\Call;
 
-use Exception;
-use http\{
-       Client, Client\Request, Client\Response
-};
+use http\{Client, Client\Request, Client\Response};
+use Psr\Log\LoggerInterface;
 use seekat\API;
-use SplObserver;
-use SplSubject;
 
-final class Deferred extends \React\Promise\Deferred implements SplObserver
-{
-       /**
-        * The response importer
-        *
-        * @var Result
-        */
-       private $result;
-
-       /**
-        * The HTTP client
-        *
-        * @var Client
-        */
-       private $client;
+final class Deferred {
+       private Result $result;
+       private Client $client;
+       private LoggerInterface $logger;
+       private Cache $cache;
 
        /**
-        * Request cache
-        *
-        * @var callable
+        * The promised response
         */
-       private $cache;
+       private ?Response $response = null;
 
        /**
-        * The executed request
-        *
-        * @var Request
+        * @var mixed
         */
-       private $request;
-
-       /**
-        * The promised response
-        *
-        * @var Response
-        */
-       private $response;
+       private object $promise;
+       private \Closure $resolve;
+       private \Closure $reject;
 
        /**
         * Create a deferred promise for the response of $request
         *
         * @param API $api The endpoint of the request
         * @param Request $request The request to execute
-        * @param Cache\Service $cache
         */
-       function __construct(API $api, Request $request, Cache\Service $cache = null) {
-               parent::__construct(function ($resolve, $reject) {
-                       return $this->cancel($resolve, $reject);
-               });
-
-               $this->request = $request;
-               $this->client = $api->getClient();
+       function __construct(API $api, private readonly Request $request) {
                $this->result = new Result($api);
-               $this->cache = new Cache($cache);
-
-               if ($this->cache->load($this->request, $cached)) {
-                       $api->getLogger()->info("deferred -> cached", [
-                               "method" => $request->getRequestMethod(),
-                               "url" => $request->getRequestUrl(),
-                       ]);
+               $this->client = $api->getClient();
+               $this->logger = $api->getLogger();
+               $this->cache  = new Cache($this->logger, $api->getCache());
+
+               $future = $api->getFuture();
+               $context = $future->createContext(function() {
+                       if ($this->response) {
+                               /* we did finish in the meantime */
+                               $this->complete();
+                       } else {
+                               $this->client->dequeue($this->request);
+                               ($this->reject)("Cancelled");
+                       }
+               });
+               $this->promise = $future->getPromise($context);
+               $this->resolve = $future->resolver($context);
+               $this->reject = $future->rejecter($context);
+       }
 
-                       $this->response = $cached;
-                       $this->complete(
-                               [$this, "resolve"],
-                               [$this, "reject"]
-                       );
-               } else {
-                       $this->client->attach($this);
-                       $this->client->enqueue($this->request, function(Response $response) use($cached) {
-                               if ($response->getResponseCode() == 304) {
-                                       $this->response = $cached;
-                               } else {
-                                       $this->response = $response;
-                               }
-                               $this->complete(
-                                       [$this, "resolve"],
-                                       [$this, "reject"]
-                               );
-                               return true;
-                       });
-                       $api->getLogger()->info("deferred -> enqueued", [
-                               "method" => $request->getRequestMethod(),
-                               "url" => $request->getRequestUrl(),
-                       ]);
-                       /* start off */
-                       $this->client->once();
+       function __invoke() {
+               if (!$this->cached($cached)) {
+                       $this->refresh($cached);
                }
+
+               return $this->promise;
        }
 
        /**
-        * Progress observer
+        * Peek into cache
         *
-        * Import the response's data on success and resolve the promise.
-        *
-        * @param SplSubject $client The observed HTTP client
-        * @param Request $request The request which generated the update
-        * @param object $progress The progress information
+        * @param Response $cached
+        * @return bool
         */
-       function update(SplSubject $client, Request $request = null, $progress = null) {
-               if ($request !== $this->request) {
-                       return;
+       private function cached(Response &$cached = null) : bool {
+               $fresh = $this->cache->load($this->request, $cachedResponse);
+
+               if (!$cachedResponse) {
+                       return false;
+               } else {
+                       $cached = $cachedResponse;
+
+                       $this->logger->info("deferred -> cached", [
+                               "method" => $this->request->getRequestMethod(),
+                               "url" => $this->request->getRequestUrl(),
+                       ]);
+
+
+                       if (!$fresh) {
+                               $this->logger->info("cached -> stale", [
+                                       "method" => $this->request->getRequestMethod(),
+                                       "url"    => $this->request->getRequestUrl(),
+                               ]);
+                               return false;
+                       }
                }
 
-               $this->notify((object) compact("client", "request", "progress"));
+               $this->response = $cached;
+               $this->complete("cached");
+               return true;
+       }
+
+       private function refresh(Response $cached = null) : void {
+               $this->client->enqueue($this->request, function(Response $response) use($cached) {
+                       $this->response = $response;
+                       $this->complete();
+                       return true;
+               });
+
+               $this->logger->info(($cached ? "stale" : "deferred") . " -> enqueued", [
+                       "method" => $this->request->getRequestMethod(),
+                       "url" => $this->request->getRequestUrl(),
+               ]);
+
+               /* start off */
+               $this->client->once();
        }
 
        /**
         * Completion callback
-        * @param callable $resolve
-        * @param callable $reject
         */
-       private function complete(callable $resolve, callable $reject) {
-               $this->client->detach($this);
+       private function complete(string $by = "enqueued") : void {
+               $this->logger->info("complete -> $by");
 
                if ($this->response) {
-                       try {
-                               $api = ($this->result)($this->response);
-
-                               $this->cache->save($this->request, $this->response);
+                       $this->logger->info("$by -> response", [
+                               "url"  => $this->request->getRequestUrl(),
+                               "info" => $this->response->getInfo(),
+                       ]);
 
-                               $resolve($api);
-                       } catch (Exception $e) {
-                               $reject($e);
+                       try {
+                               $this->cache->update($this->request, $this->response);
+                               ($this->resolve)(($this->result)($this->response));
+                       } catch (\Throwable $e) {
+                               $this->logger->warning("$by -> cache", ["exception" => $e]);
+                               ($this->reject)($e);
                        }
                } else {
-                       $reject($this->client->getTransferInfo($this->request)->error);
-               }
-       }
+                       $info = $this->client->getTransferInfo($this->request);
 
-       /**
-        * Cancellation callback
-        * @param callable $resolve
-        * @param callable $reject
-        */
-       private function cancel(callable $resolve, callable $reject) {
-               /* did we finish in the meantime? */
-               if ($this->response) {
-                       $this->complete($resolve, $reject);
-               } else {
-                       $this->client->detach($this);
-                       $this->client->dequeue($this->request);
-                       $reject("Cancelled");
+                       $this->logger->warning("$by -> no response", [
+                               "url"  => $this->request->getRequestUrl(),
+                               "info" => $info
+                       ]);
+
+                       ($this->reject)($info->error);
                }
        }
+
 }