Completed
Push — master ( 27bfd4...cb929f )
by Tobias
11:02
created

AlgoliaPlaces::reverseQuery()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Geocoder package.
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 *
10
 * @license    MIT License
11
 */
12
13
namespace Geocoder\Provider\AlgoliaPlaces;
14
15
use Geocoder\Collection;
16
use Geocoder\Exception\InvalidArgument;
17
use Geocoder\Exception\UnsupportedOperation;
18
use Geocoder\Http\Provider\AbstractHttpProvider;
19
use Geocoder\Model\Address;
20
use Geocoder\Model\AddressBuilder;
21
use Geocoder\Model\AddressCollection;
22
use Geocoder\Provider\Provider;
23
use Geocoder\Query\GeocodeQuery;
24
use Geocoder\Query\ReverseQuery;
25
use Http\Client\HttpClient;
26
use Psr\Http\Message\RequestInterface;
27
28
class AlgoliaPlaces extends AbstractHttpProvider implements Provider
29
{
30
    const TYPE_CITY = 'city';
31
32
    const TYPE_COUNTRY = 'country';
33
34
    const TYPE_ADDRESS = 'address';
35
36
    const TYPE_BUS_STOP = 'busStop';
37
38
    const TYPE_TRAIN_STATION = 'trainStation';
39
40
    const TYPE_TOWN_HALL = 'townhall';
41
42
    const TYPE_AIRPORT = 'airport';
43
44
    /** @var string */
45
    const ENDPOINT_URL_SSL = 'https://places-dsn.algolia.net/1/places/query';
46
47
    /** @var string */
48
    private $apiKey;
49
50
    /** @var string */
51
    private $appId;
52
53
    /** @var GeocodeQuery */
54
    private $query;
55
56
    /** @var HttpClient */
57
    private $client;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
58
59 13
    public function __construct(HttpClient $client, string $apiKey, string $appId)
60
    {
61 13
        parent::__construct($client);
62
63 13
        $this->apiKey = $apiKey;
64 13
        $this->client = $client;
65 13
        $this->appId = $appId;
66 13
    }
67
68 3
    public function getName(): string
69
    {
70 3
        return 'algolia_places';
71
    }
72
73 12
    public function geocodeQuery(GeocodeQuery $query): Collection
74
    {
75
        // This API doesn't handle IPs
76 12
        if (filter_var($query->getText(), FILTER_VALIDATE_IP)) {
77 3
            throw new UnsupportedOperation('The AlgoliaPlaces provider does not support IP addresses, only street addresses.');
78
        }
79
80 9
        $this->query = $query;
81
82 9
        $request = $this->getRequest(self::ENDPOINT_URL_SSL);
83 9
        $jsonParsed = AbstractHttpProvider::getParsedResponse($request);
84 3
        $jsonResponse = json_decode($jsonParsed, true);
85
86 3
        if (is_null($jsonResponse)) {
87
            return new AddressCollection([]);
88
        }
89
90 3
        if ($jsonResponse['degradedQuery']) {
91 1
            return new AddressCollection([]);
92
        }
93 2
        if (0 == $jsonResponse['nbHits']) {
94
            return new AddressCollection([]);
95
        }
96
97 2
        return $this->buildResult($jsonResponse);
98
    }
99
100
    public function reverseQuery(ReverseQuery $query): Collection
101
    {
102
        throw new UnsupportedOperation('The AlgoliaPlaces provided does not support reverse geocoding.');
103
    }
104
105
    public function getTypes(): array
106
    {
107
        return [
108
            self::TYPE_CITY,
109
            self::TYPE_COUNTRY,
110
            self::TYPE_ADDRESS,
111
            self::TYPE_BUS_STOP,
112
            self::TYPE_TRAIN_STATION,
113
            self::TYPE_TOWN_HALL,
114
            self::TYPE_AIRPORT,
115
        ];
116
    }
117
118 9
    protected function getRequest(string $url): RequestInterface
119
    {
120 9
        return $this->getMessageFactory()->createRequest(
121 9
            'POST',
122 9
            $url,
123 9
            $this->buildHeaders(),
124 9
            $this->buildData()
125
        );
126
    }
127
128 9
    private function buildData(): string
129
    {
130 9
        $query = $this->query;
131
        $params = [
132 9
            'query' => $query->getText(),
133
            'aroundLatLngViaIP' => false,
134 9
            'language' => $query->getLocale(),
135 9
            'type' => $this->buildType($query),
136 9
            'countries' => $this->buildCountries($query),
137
        ];
138
139 9
        return json_encode(array_filter($params));
140
    }
141
142 9
    private function buildType(GeocodeQuery $query): string
143
    {
144 9
        $type = $query->getData('type', '');
145 9
        if (!empty($type) && !in_array($type, $this->getTypes())) {
146
            throw new InvalidArgument('The type provided to AlgoliaPlace provider must be one those "'.implode('", "', $this->getTypes()).'"".');
147
        }
148
149 9
        return $type;
150
    }
151
152 9
    private function buildCountries(GeocodeQuery $query)
153
    {
154 9
        return array_map(
155
            function ($country) {
156
                if (2 != strlen($country)) {
157
                    throw new InvalidArgument('The country provided to AlgoliaPlace provider must be an ISO 639-1 code.');
158
                }
159
160
                return $country;
161 9
            },
162 9
            $query->getData('countries') ?? []
163
        );
164
    }
165
166
    /**
167
     * @return array
168
     */
169 9
    private function buildHeaders(): array
170
    {
171 9
        if (empty($this->appId) || empty($this->apiKey)) {
172 8
            return [];
173
        }
174
175
        return [
176 1
            'X-Algolia-Application-Id' => $this->appId,
177 1
            'X-Algolia-API-Key' => $this->apiKey,
178
        ];
179
    }
180
181
    /**
182
     * @param $jsonResponse
183
     *
184
     * @return AddressCollection
185
     */
186 2
    private function buildResult($jsonResponse): AddressCollection
187
    {
188 2
        $results = [];
189
190
        //error_log(\json_encode($jsonResponse));
191
        // 1. degradedQuery: checkfor if(degradedQuery) and set results accordingly?
192
        // 2. setStreetNumber($result->locale_name) AlgoliaPlaces does not offer streetnumber
193
        // precision for the geocoding (with the exception to addresses situated in France)
194
195
        // error_log(json_encode($jsonResponse));
196 2
        foreach ($jsonResponse['hits'] as $result) {
197 2
            $builder = new AddressBuilder($this->getName());
198 2
            $builder->setCoordinates($result['_geoloc']['lat'], $result['_geoloc']['lng']);
199 2
            if (isset($result['country'])) {
200 2
                $builder->setCountry($result['country']);
201
            }
202 2
            $builder->setCountryCode($result['country_code']);
203 2
            if (isset($result['city'])) {
204 2
                $builder->setLocality($result['city'][0]);
205
            }
206 2
            if (isset($result['postcode'])) {
207 2
                $builder->setPostalCode($result['postcode'][0]);
208
            }
209 2
            if (isset($result['locale_name'])) {
210
                $builder->setStreetNumber($result['locale_name']);
211
            }
212 2
            if (isset($result['locale_names']) && isset($result['locale_names'][0])) {
213 2
                $builder->setStreetName($result['locale_names'][0]);
214
            }
215 2
            foreach ($result['administrative'] ?? [] as $i => $adminLevel) {
216 2
                $builder->addAdminLevel($i + 1, $adminLevel[0]);
217
            }
218 2
            $results[] = $builder->build(Address::class);
219
        }
220
221 2
        return new AddressCollection($results);
222
    }
223
}
224