Completed
Push — GB-2387 ( 556cb5 )
by Ben
02:17
created

AbstractBridge   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 185
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 9
Bugs 1 Features 5
Metric Value
wmc 18
c 9
b 1
f 5
lcom 1
cbo 6
dl 0
loc 185
rs 10

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
A search() 0 6 1
A refinements() 0 6 1
C query() 0 37 7
A execute() 0 12 1
A getContentEncoding() 0 11 3
A deserialize() 0 10 2
A setRetryTimeout() 0 5 1
A setMaxTries() 0 5 1
1
<?php
2
3
namespace GroupByInc\API;
4
5
use Exception;
6
use GroupByInc\API\Config\ConnectionConfiguration;
7
use GroupByInc\API\Util\SerializerFactory;
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 DEFAULT_MAX_TRIES = 3;
25
  const DEFAULT_RETRY_TIMEOUT = 80000;
26
27
  /** @var string */
28
  private $clientKey;
29
  /** @var string */
30
  private $bridgeUrl;
31
  /** @var string */
32
  private $bridgeUrlCluster;
33
  /** @var string */
34
  private $bridgeRefinementsUrl;
35
  /** @var Serializer */
36
  private $serializer;
37
  /** @var int */
38
  private $maxTries = self::DEFAULT_MAX_TRIES;
39
  /** @var float */
40
  private $retryTimeout = self::DEFAULT_RETRY_TIMEOUT;
41
  /** @var ConnectionConfiguration */
42
  private $config;
43
44
  /**
45
   * @param string                  $clientKey
46
   * @param string                  $baseUrl
47
   * @param ConnectionConfiguration $config
48
   */
49
  function __construct($clientKey, $baseUrl, $config)
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...
50
  {
51
    $this->clientKey = $clientKey;
52
    $this->bridgeUrl = $baseUrl . self::SEARCH;
53
    $this->bridgeUrlCluster = $baseUrl . self::CLUSTER;
54
    $this->bridgeRefinementsUrl = $baseUrl . self::SEARCH . self::REFINEMENTS;
55
    $this->config = $config;
56
57
    $this->serializer = SerializerFactory::build();
58
  }
59
60
  /**
61
   * @param Query $query
62
   *
63
   * @return mixed
64
   */
65
  public function search($query)
66
  {
67
    $content = $query->getBridgeJson($this->clientKey);
68
69
    return $this->query($this->bridgeUrl, $content, self::RESULTS_CLASS);
70
  }
71
72
  /**
73
   * @param Query  $query
74
   * @param string $navigationName
75
   *
76
   * @return mixed
77
   */
78
  public function refinements($query, $navigationName)
79
  {
80
    $content = $query->getBridgeRefinementsJson($this->clientKey, $navigationName);
81
82
    return $this->query($this->bridgeRefinementsUrl, $content, self::REFINEMENTS_RESULT_CLASS);
83
  }
84
85
  /**
86
   * @param string $url
87
   * @param string $content
88
   * @param string $class
89
   *
90
   * @return mixed
91
   */
92
  private function query($url, $content, $class)
93
  {
94
    $response = null;
95
    $tries = 0;
96
    $lastError = null;
97
98
    while ($tries < $this->maxTries) {
99
      try {
100
        $response = $this->execute($url, $content, $tries);
101
        break;
102
      } catch (Exception $e) {
103
        usleep($this->retryTimeout);
104
        error_log('Connection failed, retrying');
105
        $lastError = $e;
106
        $tries++;
107
      }
108
    }
109
110
    if ($tries == $this->maxTries) {
111
      throw new RuntimeException("Error: call to URL $url failed after " . $this->maxTries . " tries", 0, $lastError);
112
    }
113
114
    if ($response->hasErrors()) {
115
      throw new RuntimeException("Error: call to URL $url failed with status $response->code, response $response");
116
    }
117
118
    if ($response->content_type !== Mime::JSON) {
119
      throw new RuntimeException("Error: bridge at URL $url did not return the expected JSON response, it returned: " . $response->content_type . " instead");
120
    }
121
122
    $responseBody = $response->raw_body;
123
    if (strpos($this->getContentEncoding($response), 'gzip') !== FALSE) {
124
      $responseBody = gzdecode($responseBody);
125
    }
126
127
    return $this->deserialize($responseBody, $class);
128
  }
129
130
  /**
131
   * @param string $url
132
   * @param string $content
133
   * @param int    $tries
134
   *
135
   * @return Response
136
   */
137
  protected function execute($url, $content, $tries)
138
  {
139
    echo "sending request $content to $url";
140
141
    return Request::post($url . "?retry=$tries")
142
        ->body($content)
143
        ->timeout($this->config->getTimeout())
144
        ->addOnCurlOption(CURLOPT_CONNECTTIMEOUT_MS, $this->config->getConnectTimeout() * 1000)
145
        ->addOnCurlOption(CURLOPT_MAXCONNECTS, $this->config->getMaxConnections())
146
        ->sendsType(Mime::JSON)
147
        ->send();
148
  }
149
150
  /**
151
   * @param Response $response
152
   *
153
   * @return bool|string
154
   */
155
  private function getContentEncoding($response)
156
  {
157
    $headers = $response->headers;
158
    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...
159
      list($k, $v) = explode(':', $header);
160
      if ('content-encoding' == strtolower($k)) {
161
        return trim($v);
162
      }
163
    }
164
    return false;
165
  }
166
167
  private function deserialize($json, $class)
168
  {
169
    $object = null;
170
    try {
171
      $object = $this->serializer->deserialize($json, $class, 'json');
172
    } catch (RuntimeException $e) {
173
      error_log("deserialization failed with exception $e");
174
    }
175
    return $object;
176
  }
177
178
  /**
179
   * @param float $retryTimeout Sets the retry timeout for a failed request.
180
   * @return $this
181
   */
182
  public function setRetryTimeout($retryTimeout)
183
  {
184
    $this->retryTimeout = $retryTimeout;
185
    return $this;
186
  }
187
188
  /**
189
   * @param int $maxTries Sets the maximum number of times to try a request before returning an error.
190
   * @return $this
191
   */
192
  public function setMaxTries($maxTries)
193
  {
194
    $this->maxTries = $maxTries;
195
    return $this;
196
  }
197
198
}
199