7fc44de1ba607c50c36e22c4ee9c39fae130b887
[m6w6/seekat] / lib / API / Call / Deferred.php
1 <?php
2
3 namespace seekat\API\Call;
4
5 use Exception;
6 use http\{
7 Client, Client\Request, Client\Response
8 };
9 use seekat\API;
10 use SplObserver;
11 use SplSubject;
12
13 final class Deferred extends \React\Promise\Deferred implements SplObserver
14 {
15 /**
16 * The response importer
17 *
18 * @var Result
19 */
20 private $result;
21
22 /**
23 * The HTTP client
24 *
25 * @var Client
26 */
27 private $client;
28
29 /**
30 * Request cache
31 *
32 * @var callable
33 */
34 private $cache;
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 * Create a deferred promise for the response of $request
52 *
53 * @param API $api The endpoint of the request
54 * @param Request $request The request to execute
55 * @param Cache\Service $cache
56 */
57 function __construct(API $api, Request $request, Cache\Service $cache = null) {
58 parent::__construct(function ($resolve, $reject) {
59 return $this->cancel($resolve, $reject);
60 });
61
62 $this->request = $request;
63 $this->client = $api->getClient();
64 $this->result = new Result($api);
65 $this->cache = new Cache($cache);
66
67 if ($this->cache->load($this->request, $cached)) {
68 $api->getLogger()->info("deferred -> cached", [
69 "method" => $request->getRequestMethod(),
70 "url" => $request->getRequestUrl(),
71 ]);
72
73 $this->response = $cached;
74 $this->complete(
75 [$this, "resolve"],
76 [$this, "reject"]
77 );
78 } else {
79 $this->client->attach($this);
80 $this->client->enqueue($this->request, function(Response $response) use($cached) {
81 if ($response->getResponseCode() == 304) {
82 $this->response = $cached;
83 } else {
84 $this->response = $response;
85 }
86 $this->complete(
87 [$this, "resolve"],
88 [$this, "reject"]
89 );
90 return true;
91 });
92 $api->getLogger()->info("deferred -> enqueued", [
93 "method" => $request->getRequestMethod(),
94 "url" => $request->getRequestUrl(),
95 ]);
96 /* start off */
97 $this->client->once();
98 }
99 }
100
101 /**
102 * Progress observer
103 *
104 * Import the response's data on success and resolve the promise.
105 *
106 * @param SplSubject $client The observed HTTP client
107 * @param Request $request The request which generated the update
108 * @param object $progress The progress information
109 */
110 function update(SplSubject $client, Request $request = null, $progress = null) {
111 if ($request !== $this->request) {
112 return;
113 }
114
115 $this->notify((object) compact("client", "request", "progress"));
116 }
117
118 /**
119 * Completion callback
120 * @param callable $resolve
121 * @param callable $reject
122 */
123 private function complete(callable $resolve, callable $reject) {
124 $this->client->detach($this);
125
126 if ($this->response) {
127 try {
128 $api = ($this->result)($this->response);
129
130 $this->cache->save($this->request, $this->response);
131
132 $resolve($api);
133 } catch (Exception $e) {
134 $reject($e);
135 }
136 } else {
137 $reject($this->client->getTransferInfo($this->request)->error);
138 }
139 }
140
141 /**
142 * Cancellation callback
143 * @param callable $resolve
144 * @param callable $reject
145 */
146 private function cancel(callable $resolve, callable $reject) {
147 /* did we finish in the meantime? */
148 if ($this->response) {
149 $this->complete($resolve, $reject);
150 } else {
151 $this->client->detach($this);
152 $this->client->dequeue($this->request);
153 $reject("Cancelled");
154 }
155 }
156 }