Bing::extractTotalMatches()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 1
crap 2
1
<?php
2
3
namespace Radowoj\Searcher\SearchProvider;
4
5
use stdClass;
6
7
use GuzzleHttp\Client as GuzzleClient;
8
use Psr\Http\Message\ResponseInterface as Psr7Response;
9
10
use Radowoj\Searcher\SearchResult\Collection;
11
use Radowoj\Searcher\SearchResult\ICollection;
12
use Radowoj\Searcher\SearchResult\Item;
13
use Radowoj\Searcher\SearchResult\IItem;
14
15
use Radowoj\Searcher\Exceptions\Exception;
16
use Radowoj\Searcher\Exceptions\QuotaExceededException;
17
use Radowoj\Searcher\Exceptions\RateLimitExceededException;
18
19
class Bing extends SearchProvider implements ISearchProvider
20
{
21
    const URI = 'https://api.cognitive.microsoft.com/bing/v5.0/search?';
22
    const API_KEY_HEADER = 'Ocp-Apim-Subscription-Key';
23
24
    protected $apiKey = null;
25
26
    protected $guzzle = null;
27
28 10
    public function __construct(GuzzleClient $guzzle, string $apiKey)
29
    {
30 10
        $this->apiKey = $apiKey;
31 10
        $this->guzzle = $guzzle;
32 10
    }
33
34
35 9
    protected function searchRequest(string $query, int $limit, int $offset) : stdClass
36
    {
37
        $params = [
38 9
            'q' => $query,
39 9
            'count' => $limit,
40 9
            'offset' => $offset
41
        ];
42
43 9
        $queryString = http_build_query($params);
44
45 9
        $uri = self::URI . $queryString;
46
47 9
        $result = $this->guzzle->request(
48 9
            'GET',
49
            $uri, [
50
                'headers' => [
51 9
                    self::API_KEY_HEADER => $this->apiKey,
52 9
                ],
53
                'http_errors' => false,
54
            ]
55
        );
56
57 9
        return $this->decodeResponse($result);
58
    }
59
60
61
    /**
62
     * Handle response based on HTTP status code (catches 400s - usually quota or rate limit,
63
     * so authorisation errors and other stuff will be thrown as generic Searcher exception)
64
     *
65
     * On status == 200 it simply returns json-decoded response.
66
     *
67
     * @param  Psr7Response $result result from Guzzle
68
     * @return array
69
     */
70 9
    protected function decodeResponse(Psr7Response $result) : stdClass
71
    {
72 9
        switch($result->getStatusCode()) {
73 9
            case 200:
74 6
                return json_decode($result->getBody());
75 3
            case 403:   //Out of call volume quota
76 1
                throw new QuotaExceededException($result->getReasonPhrase());
77 2
            case 429:   //Rate limit is exceeded
78 1
                throw new RateLimitExceededException($result->getReasonPhrase());
79
            default:
80 1
                throw new Exception("Bing API responded with HTTP status {$result->getStatusCode()} - {$result->getReasonPhrase()}");
81
        }
82
    }
83
84
85 6
    protected function validateRequestResult(stdClass $result)
86
    {
87 6
        if (!isset($result->_type) || $result->_type !== 'SearchResponse') {
88 1
            throw new Exception("Invalid Bing API response: " . print_r($result, 1));
89
        }
90 5
    }
91
92
93 5
    protected function enforceLimit(stdClass $result, int $limit) : stdClass
94
    {
95 5
        if (!isset($result->webPages->value)) {
96 1
            return $result;
97
        }
98 4
        $result->webPages->value = array_slice($result->webPages->value, 0, $limit);
99 4
        return $result;
100
    }
101
102
103 5
    protected function extractResults(stdClass $result) : array
104
    {
105 5
        return isset($result->webPages->value)
106 4
            ? $result->webPages->value
107 5
            : [];
108
    }
109
110
111 5
    protected function extractTotalMatches(stdClass $result) : int
112
    {
113 5
        return isset($result->webPages->totalEstimatedMatches)
114 4
            ? $result->webPages->totalEstimatedMatches
115 5
            : 0;
116
    }
117
118
119 1
    protected function populateItem(stdClass $item) : IItem
120
    {
121 1
        return new Item([
122 1
            'url' => preg_match('/^https?:\/\//', $item->url)
123 1
                ? $item->url
124 1
                : "http://{$item->url}",
125 1
            'title' => $item->name,
126 1
            'description' => $item->snippet,
127
        ]);
128
    }
129
130
}
131