Completed
Pull Request — develop (#22)
by Ben
02:15
created

AbstractBridge::setTimeout()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 5
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
1
<?php
2
3
namespace GroupByInc\API;
4
5
use Exception;
6
use GroupByInc\API\Util\SerializerFactory;
7
use GroupByInc\API\Util\UriBuilder;
8
use Httpful\Mime;
9
use Httpful\Request;
10
use Httpful\Response;
11
use JMS\Serializer\Serializer;
12
use RuntimeException;
13
14
abstract class AbstractBridge
15
{
16
    const SEARCH = '/search';
17
    const CLUSTER = '/cluster';
18
    const REFINEMENTS = '/refinements';
19
    const RESULTS_CLASS = 'GroupByInc\API\Model\Results';
20
    const REFINEMENTS_RESULT_CLASS = 'GroupByInc\API\Model\RefinementsResult';
21
    const HTTP = 'http://';
22
    const HTTPS = 'https://';
23
    const COLON = ':';
24
    const MAX_TRIES = 3;
25
    const RETRY_TIMEOUT = 80000;
26
    const DEFAULT_TIMEOUT = 30;
27
    const DEFAULT_CONNECT_TIMEOUT = 15;
28
29
    /** @var string */
30
    private $clientKey;
31
    /** @var string */
32
    private $bridgeUrl;
33
    /** @var string */
34
    private $bridgeUrlCluster;
35
    /** @var string */
36
    private $bridgeRefinementsUrl;
37
    /** @var Serializer */
38
    private $serializer;
39
    /** @var int */
40
    private $timeout;
41
    /** @var int */
42
    private $connectTimeout;
43
44
    /**
45
     * @param string $clientKey
46
     * @param string $baseUrl
47
     */
48
    function __construct($clientKey, $baseUrl)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
49
    {
50
        $this->clientKey = $clientKey;
51
        $this->bridgeUrl = $baseUrl . self::SEARCH;
52
        $this->bridgeUrlCluster = $baseUrl . self::CLUSTER;
53
        $this->bridgeRefinementsUrl = $baseUrl . self::SEARCH . self::REFINEMENTS;
54
55
        $this->serializer = SerializerFactory::build();
56
    }
57
58
    /**
59
     * @param Query $query
60
     *
61
     * @return mixed
62
     */
63
    public function search($query)
64
    {
65
        $content = $query->getBridgeJson($this->clientKey);
66
67
        return $this->query($this->bridgeUrl, $content, self::RESULTS_CLASS);
68
    }
69
70
    /**
71
     * @param Query  $query
72
     * @param string $navigationName
73
     *
74
     * @return mixed
75
     */
76
    public function refinements($query, $navigationName)
77
    {
78
        $content = $query->getBridgeRefinementsJson($this->clientKey, $navigationName);
79
80
        return $this->query($this->bridgeRefinementsUrl, $content, self::REFINEMENTS_RESULT_CLASS);
81
    }
82
83
    /**
84
     * @param string $url
85
     * @param string $content
86
     * @param string $class
87
     *
88
     * @return mixed
89
     */
90
    private function query($url, $content, $class)
91
    {
92
        $response = null;
93
        $tries = 0;
94
        $lastError = null;
95
96
        while ($tries < self::MAX_TRIES) {
97
            try {
98
                $response = $this->execute($url, $content, $tries);
99
                break;
100
            } catch (Exception $e) {
101
                usleep(self::RETRY_TIMEOUT);
102
                error_log('Connection failed, retrying');
103
                $lastError = $e;
104
                $tries++;
105
            }
106
        }
107
108
        if ($tries == self::MAX_TRIES) {
109
            throw new RuntimeException("Error: call to URL $url failed after " . self::MAX_TRIES . " tries", 0, $lastError);
110
        }
111
112
        if ($response->hasErrors()) {
113
            throw new RuntimeException("Error: call to URL $url failed with status $response->code, response $response");
114
        }
115
116
        if ($response->content_type !== Mime::JSON) {
117
            throw new RuntimeException("Error: bridge at URL $url did not return the expected JSON response, it returned: " . $response->content_type . " instead");
118
        }
119
120
        $responseBody = $response->raw_body;
121
        if (strpos($this->getContentEncoding($response), 'gzip') !== FALSE) {
122
            $responseBody = gzdecode($responseBody);
123
        }
124
125
        return $this->deserialize($responseBody, $class);
126
    }
127
128
    /**
129
     * @param string $url
130
     * @param string $content
131
     * @param int    $tries
132
     *
133
     * @return Response
134
     */
135
    protected function execute($url, $content, $tries)
136
    {
137
        echo "sending request $content to $url";
138
139
        return Request::post($url . "?retry=$tries")
140
            ->body($content)
141
            ->timeout(isset($this->timeout) ? $this->timeout : self::DEFAULT_TIMEOUT)
142
            ->addOnCurlOption(CURLOPT_CONNECTTIMEOUT, isset($this->connectTimeout) ? $this->connectTimeout : self::DEFAULT_CONNECT_TIMEOUT)
143
            ->sendsType(Mime::JSON)
144
            ->send();
145
    }
146
147
    /**
148
     * @param Response $response
149
     *
150
     * @return bool|string
151
     */
152
    private function getContentEncoding($response)
153
    {
154
        $headers = $response->headers;
155
        foreach ($headers as $header) {
0 ignored issues
show
Bug introduced by
The expression $headers of type object<Httpful\Response\Headers> is not traversable.
Loading history...
156
            list($k, $v) = explode(':', $header);
157
            if ('content-encoding' == strtolower($k)) {
158
                return trim($v);
159
            }
160
        }
161
        return false;
162
    }
163
164
    private function deserialize($json, $class)
165
    {
166
        $object = null;
167
        try {
168
            $object = $this->serializer->deserialize($json, $class, 'json');
169
        } catch (RuntimeException $e) {
170
            error_log("deserialization failed with exception $e");
171
        }
172
        return $object;
173
    }
174
175
    /**
176
     * @param int $connectTimeout seconds
177
     * @return $this
178
     */
179
    public function setConnectTimeout($connectTimeout)
180
    {
181
        $this->connectTimeout = $connectTimeout;
182
        return $this;
183
    }
184
185
    /**
186
     * @param int $timeout seconds
187
     * @return $this
188
     */
189
    public function setTimeout($timeout)
190
    {
191
        $this->timeout = $timeout;
192
        return $this;
193
    }
194
195
}
196