update to PHP-8.1
[m6w6/seekat] / lib / API / Call / Deferred.php
1 <?php
2
3 namespace seekat\API\Call;
4
5 use http\{Client, Client\Request, Client\Response};
6 use Psr\Log\LoggerInterface;
7 use seekat\API;
8
9 final class Deferred {
10 private Result $result;
11 private Client $client;
12 private LoggerInterface $logger;
13 private Cache $cache;
14
15 /**
16 * The promised response
17 */
18 private ?Response $response = null;
19
20 /**
21 * @var mixed
22 */
23 private object $promise;
24 private \Closure $resolve;
25 private \Closure $reject;
26
27 /**
28 * Create a deferred promise for the response of $request
29 *
30 * @param API $api The endpoint of the request
31 * @param Request $request The request to execute
32 */
33 function __construct(API $api, private readonly Request $request) {
34 $this->result = new Result($api);
35 $this->client = $api->getClient();
36 $this->logger = $api->getLogger();
37 $this->cache = new Cache($this->logger, $api->getCache());
38
39 $future = $api->getFuture();
40 $context = $future->createContext(function() {
41 if ($this->response) {
42 /* we did finish in the meantime */
43 $this->complete();
44 } else {
45 $this->client->dequeue($this->request);
46 ($this->reject)("Cancelled");
47 }
48 });
49 $this->promise = $future->getPromise($context);
50 $this->resolve = $future->resolver($context);
51 $this->reject = $future->rejecter($context);
52 }
53
54 function __invoke() {
55 if (!$this->cached($cached)) {
56 $this->refresh($cached);
57 }
58
59 return $this->promise;
60 }
61
62 /**
63 * Peek into cache
64 *
65 * @param Response $cached
66 * @return bool
67 */
68 private function cached(Response &$cached = null) : bool {
69 $fresh = $this->cache->load($this->request, $cachedResponse);
70
71 if (!$cachedResponse) {
72 return false;
73 } else {
74 $cached = $cachedResponse;
75
76 $this->logger->info("deferred -> cached", [
77 "method" => $this->request->getRequestMethod(),
78 "url" => $this->request->getRequestUrl(),
79 ]);
80
81
82 if (!$fresh) {
83 $this->logger->info("cached -> stale", [
84 "method" => $this->request->getRequestMethod(),
85 "url" => $this->request->getRequestUrl(),
86 ]);
87 return false;
88 }
89 }
90
91 $this->response = $cached;
92 $this->complete("cached");
93 return true;
94 }
95
96 private function refresh(Response $cached = null) : void {
97 $this->client->enqueue($this->request, function(Response $response) use($cached) {
98 $this->response = $response;
99 $this->complete();
100 return true;
101 });
102
103 $this->logger->info(($cached ? "stale" : "deferred") . " -> enqueued", [
104 "method" => $this->request->getRequestMethod(),
105 "url" => $this->request->getRequestUrl(),
106 ]);
107
108 /* start off */
109 $this->client->once();
110 }
111
112 /**
113 * Completion callback
114 */
115 private function complete(string $by = "enqueued") : void {
116 $this->logger->info("complete -> $by");
117
118 if ($this->response) {
119 $this->logger->info("$by -> response", [
120 "url" => $this->request->getRequestUrl(),
121 "info" => $this->response->getInfo(),
122 ]);
123
124 try {
125 $this->cache->update($this->request, $this->response);
126 ($this->resolve)(($this->result)($this->response));
127 } catch (\Throwable $e) {
128 $this->logger->warning("$by -> cache", ["exception" => $e]);
129 ($this->reject)($e);
130 }
131 } else {
132 $info = $this->client->getTransferInfo($this->request);
133
134 $this->logger->warning("$by -> no response", [
135 "url" => $this->request->getRequestUrl(),
136 "info" => $info
137 ]);
138
139 ($this->reject)($info->error);
140 }
141 }
142
143 }