bfe8c27e4da46dacbcb9f558615a21a7a1ee02ad
[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 /**
11 * The response importer
12 *
13 * @var Result
14 */
15 private $result;
16
17 /**
18 * The HTTP client
19 *
20 * @var Client
21 */
22 private $client;
23
24 /**
25 * Request cache
26 *
27 * @var callable
28 */
29 private $cache;
30
31 /**
32 * @var LoggerInterface
33 */
34 private $logger;
35
36 /**
37 * The executed request
38 *
39 * @var Request
40 */
41 private $request;
42
43 /**
44 * The promised response
45 *
46 * @var Response
47 */
48 private $response;
49
50 /**
51 * @var mixed
52 */
53 private $promise;
54
55 /**
56 * @var \Closure
57 */
58 private $resolve;
59
60 /**
61 * @var \Closure
62 */
63 private $reject;
64
65 /**
66 * Create a deferred promise for the response of $request
67 *
68 * @param API $api The endpoint of the request
69 * @param Request $request The request to execute
70 * @param Cache\Service $cache
71 */
72 function __construct(API $api, Request $request, Cache\Service $cache = null) {
73 $this->request = $request;
74 $this->client = $api->getClient();
75 $this->logger = $api->getLogger();
76 $this->result = new Result($api);
77 $this->cache = new Cache($cache);
78
79 $future = $api->getFuture();
80 $context = $future->createContext(function() {
81 if ($this->response) {
82 /* we did finish in the meantime */
83 $this->complete();
84 } else {
85 $this->client->dequeue($this->request);
86 ($this->reject)("Cancelled");
87 }
88 });
89 $this->promise = $future->getPromise($context);
90 $this->resolve = API\Future\resolver($future, $context);
91 $this->reject = API\Future\rejecter($future, $context);
92 }
93
94 function __invoke() {
95 if (!$this->cached($cached)) {
96 $this->refresh($cached);
97 }
98
99 return $this->promise;
100 }
101
102 /**
103 * Peek into cache
104 *
105 * @param Response $cached
106 * @return bool
107 */
108 private function cached(Response &$cached = null) : bool {
109 $fresh = $this->cache->load($this->request, $cachedResponse);
110
111 if (!$cachedResponse) {
112 return false;
113 } else {
114 $cached = $cachedResponse;
115
116 $this->logger->info("deferred -> cached", [
117 "method" => $this->request->getRequestMethod(),
118 "url" => $this->request->getRequestUrl(),
119 ]);
120
121
122 if (!$fresh) {
123 $this->logger->info("cached -> stale", [
124 "method" => $this->request->getRequestMethod(),
125 "url" => $this->request->getRequestUrl(),
126 ]);
127 return false;
128 }
129 }
130
131 $this->response = $cached;
132 $this->complete("cached");
133 return true;
134 }
135
136 /**
137 * Refresh
138 *
139 * @param Response|null $cached
140 */
141 private function refresh(Response $cached = null) {
142 $this->client->enqueue($this->request, function(Response $response) use($cached) {
143 $this->response = $response;
144 $this->complete();
145 return true;
146 });
147
148 $this->logger->info(($cached ? "stale" : "deferred") . " -> enqueued", [
149 "method" => $this->request->getRequestMethod(),
150 "url" => $this->request->getRequestUrl(),
151 ]);
152
153 /* start off */
154 $this->client->once();
155 }
156
157 /**
158 * Completion callback
159 */
160 private function complete(string $by = "enqueued") {
161 if ($this->response) {
162 $this->logger->info("$by -> response", [
163 "url" => $this->request->getRequestUrl(),
164 "info" => $this->response->getInfo(),
165 ]);
166
167 try {
168 $this->cache->update($this->request, $this->response);
169 ($this->resolve)(($this->result)($this->response));
170 } catch (\Throwable $e) {
171 ($this->reject)($e);
172 }
173 } else {
174 $info = $this->client->getTransferInfo($this->request);
175
176 $this->logger->warning("$by -> no response", [
177 "url" => $this->request->getRequestUrl(),
178 "info" => $info
179 ]);
180
181 ($this->reject)($info->error);
182 }
183 }
184
185 }