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