Completed
Pull Request — master (#42)
by Chad
01:23
created

GuzzleAdapter   A

Complexity

Total Complexity 10

Size/Duplication

Total Lines 129
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
dl 0
loc 129
c 0
b 0
f 0
wmc 10
lcom 1
cbo 5
rs 10

3 Methods

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