Completed
Branch master (eed020)
by Jeroen
08:38 queued 01:52
created

Geolocation::doCall()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 44
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
c 5
b 0
f 0
dl 0
loc 44
rs 8.439
cc 6
eloc 19
nc 7
nop 1
1
<?php
2
3
namespace JeroenDesloovere\Geolocation;
4
5
use JeroenDesloovere\Geolocation\Result\Address;
6
use JeroenDesloovere\Geolocation\Result\Coordinates;
7
8
/**
9
 * Geolocation
10
 *
11
 * Get latitude/longitude or address using Google Maps API
12
 *
13
 * @author Jeroen Desloovere <[email protected]>
14
 */
15
class Geolocation
16
{
17
    // API URL
18
    const API_URL = 'maps.googleapis.com/maps/api/geocode/json';
19
20
    /** @var string */
21
    private $api_key;
22
23
    /** @var bool */
24
    private $https;
25
26
    public function __construct(string $api_key = null, bool $https = false)
27
    {
28
        $this->https = $https;
29
30
        if ($api_key) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $api_key of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
31
            $this->api_key = $api_key;
32
            $this->https = true;
33
        }
34
    }
35
36
    private function createUrl(array $parameters): string
37
    {
38
        // define url
39
        $url = ($this->https ? 'https://' : 'http://') . self::API_URL . '?';
40
41
        // add every parameter to the url
42
        foreach ($parameters as $key => $value) {
43
            $url .= $key . '=' . urlencode($value) . '&';
44
        }
45
46
        // trim last &
47
        $url = trim($url, '&');
48
49
        if ($this->api_key) {
50
            $url .= '&key=' . $this->api_key;
51
        }
52
53
        return $url;
54
    }
55
56
    /**
57
     * Do call
58
     *
59
     * @param  array $parameters
60
     * @return mixed
61
     * @throws Exception
62
     */
63
    protected function doCall(array $parameters = array())
64
    {
65
        // check if curl is available
66
        if (!function_exists('curl_init')) {
67
            throw Exception::CurlNotInstalled();
68
        }
69
70
        // init curl
71
        $curl = curl_init();
72
73
        // set options
74
        curl_setopt($curl, CURLOPT_URL, $this->createUrl($parameters));
75
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
76
        curl_setopt($curl, CURLOPT_TIMEOUT, 10);
77
        if (ini_get('open_basedir') == '' && ini_get('safe_mode' == 'Off')) {
78
            curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
79
        }
80
81
        // execute
82
        $response = curl_exec($curl);
83
84
        // fetch errors
85
        $errorNumber = curl_errno($curl);
86
        $errorMessage = curl_error($curl);
87
88
        // close curl
89
        curl_close($curl);
90
91
        // we have errors
92
        if ($errorNumber != '') {
93
            throw new Exception($errorMessage);
94
        }
95
96
        // redefine response as json decoded
97
        $response = json_decode($response);
98
99
        // API returns with an error
100
        if (isset($response->error_message)) {
101
            throw new Exception($response->error_message);
102
        }
103
104
        // return the content
105
        return $response->results;
106
    }
107
108
    /**
109
     * Get address using latitude/longitude
110
     *
111
     * @param  float $latitude
112
     * @param  float $longitude
113
     * @return Address
114
     * @throws Exception
115
     */
116
    public function getAddress(float $latitude, float $longitude): Address
117
    {
118
        $addressSuggestions = $this->getAddresses($latitude, $longitude);
119
120
        if (count($addressSuggestions) == 0) {
121
            throw Exception::noAddressFoundForCoordinates($latitude, $longitude);
122
        }
123
124
        return $addressSuggestions[0];
125
    }
126
127
    /**
128
     * Get possible addresses using latitude/longitude
129
     *
130
     * @param  float $latitude
131
     * @param  float $longitude
132
     * @return array
133
     * @throws Exception
134
     */
135
    public function getAddresses(float $latitude, float $longitude): ?array
136
    {
137
        // init results
138
        $addresses = [];
139
140
        // define result
141
        $addressSuggestions = $this->doCall(array(
142
            'latlng' => $latitude . ',' . $longitude,
143
            'sensor' => 'false'
144
        ));
145
146
        // loop addresses
147
        foreach ($addressSuggestions as $key => $addressSuggestion) {
148
            $addresses[$key] = Address::createFromGoogleResult($addressSuggestion);
149
        }
150
151
        return $addresses;
152
    }
153
154
    /**
155
     * Get coordinates latitude/longitude
156
     *
157
     * @param  null|string $street
158
     * @param  null|string $streetNumber
159
     * @param  null|string $city
160
     * @param  null|string $zip
161
     * @param  null|string $country
162
     * @return Coordinates
163
     * @throws Exception
164
     */
165
    public function getCoordinates(
166
        ?string $street = null,
167
        ?string $streetNumber = null,
168
        ?string $city = null,
169
        ?string $zip = null,
170
        ?string $country = null
171
    ): Coordinates {
172
        $items = [];
173
        $variables = [$street, $streetNumber, $city, $zip, $country];
174
        for ($i = 0; $i < count($variables); $i ++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
175
            if (empty($variables[$i])) {
176
                continue;
177
            }
178
179
            $items[] = $variables[$i];
180
        }
181
182
        $results = $this->doCall(array(
183
            'address' => implode(' ', $items),
184
            'sensor' => 'false'
185
        ));
186
187
        if (!array_key_exists(0, $results)) {
188
            throw Exception::noCoordinatesFoundforAddress($variables);
189
        }
190
191
        return new Coordinates(
192
            $results[0]->geometry->location->lat,
193
            $results[0]->geometry->location->lng
194
        );
195
    }
196
}
197