Completed
Push — master ( d75a47...7295a1 )
by Chad
10s
created

GuzzleAdapter::fulfillPromises()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
c 0
b 0
f 0
rs 9.4285
cc 2
eloc 12
nc 2
nop 2
1
<?php
2
3
namespace TraderInteractive\Api;
4
5
use ArrayObject;
6
use TraderInteractive\Util;
7
use GuzzleHttp\Client as GuzzleClient;
8
use GuzzleHttp\ClientInterface as GuzzleClientInterface;
9
use GuzzleHttp\Exception\RequestException;
10
use GuzzleHttp\Promise;
11
use Psr\Http\Message\RequestInterface;
12
use Psr\Http\Message\ResponseInterface;
13
14
/**
15
 * Concrete implentation of Adapter interface
16
 */
17
final class GuzzleAdapter implements AdapterInterface
18
{
19
    /**
20
     * Collection of Promise\PromiseInterface instances with keys matching what was given from start().
21
     *
22
     * @var array
23
     */
24
    private $promises = [];
25
26
    /**
27
     * Collection of Api\Response with keys matching what was given from start().
28
     *
29
     * @var array
30
     */
31
    private $responses = [];
32
33
    /**
34
     * Collection of \Exception with keys matching what was given from start().
35
     *
36
     * @var ArrayObject
37
     */
38
    private $exceptions;
39
40
    /**
41
     * @var GuzzleClientInterface
42
     */
43
    private $client;
44
45
    public function __construct(GuzzleClientInterface $client = null)
46
    {
47
        $this->exceptions = new ArrayObject();
48
        $this->client = $client ?? new GuzzleClient(
49
            [
50
                'allow_redirects' => false, //stop guzzle from following redirects
51
                'http_errors' => false, //only for 400/500 error codes, actual exceptions can still happen
52
            ]
53
        );
54
    }
55
56
    /**
57
     * @see AdapterInterface::start()
58
     */
59
    public function start(RequestInterface $request) : string
60
    {
61
        $handle = uniqid();
62
        $this->promises[$handle] = $this->client->sendAsync($request);
63
        return $handle;
64
    }
65
66
    /**
67
     * @see Adapter::end()
68
     *
69
     * @throws \InvalidArgumentException
70
     */
71
    public function end(string $endHandle) : ResponseInterface
72
    {
73
        $results = $this->fulfillPromises($this->promises, $this->exceptions);
74
        foreach ($results as $handle => $response) {
75
            try {
76
                $contents = (string)$response->getBody();
77
                if (trim($contents) !== '') {
78
                    json_decode($contents, true);
79
                    Util::ensure(
80
                        JSON_ERROR_NONE,
81
                        json_last_error(),
82
                        '\UnexpectedValueException',
83
                        [json_last_error_msg()]
84
                    );
85
                }
86
87
                $this->responses[$handle] = $response;
88
            } catch (\Exception $e) {
89
                $this->exceptions[$handle] = $e;
90
            }
91
        }
92
93
        $this->promises = [];
94
95
        if (array_key_exists($endHandle, $this->exceptions)) {
96
            $exception = $this->exceptions[$endHandle];
97
            unset($this->exceptions[$endHandle]);
98
            throw $exception;
99
        }
100
101
        if (array_key_exists($endHandle, $this->responses)) {
102
            $response = $this->responses[$endHandle];
103
            unset($this->responses[$endHandle]);
104
            return $response;
105
        }
106
107
        throw new \InvalidArgumentException('$endHandle not found');
108
    }
109
110
    /**
111
     * Helper method to execute all guzzle promises.
112
     *
113
     * @param array $promises
114
     * @param array $exceptions
115
     *
116
     * @return array Array of fulfilled PSR7 responses.
117
     */
118
    private function fulfillPromises(array $promises, ArrayObject $exceptions) : array
119
    {
120
        if (empty($promises)) {
121
            return [];
122
        }
123
124
        $results = new ArrayObject();
125
        Promise\each(
126
            $this->promises,
127
            function (ResponseInterface $response, $index) use ($results) {
128
                $results[$index] = $response;
129
            },
130
            function (RequestException $e, $index) use ($exceptions) {
131
                $exceptions[$index] = $e;
132
            }
133
        )->wait();
134
135
        return $results->getArrayCopy();
136
    }
137
}
138