Completed
Pull Request — master (#65)
by Alex
02:11
created

OpenWeatherMap   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 443
Duplicated Lines 1.35 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 47.22%

Importance

Changes 20
Bugs 11 Features 2
Metric Value
wmc 48
c 20
b 11
f 2
lcom 1
cbo 7
dl 6
loc 443
ccs 51
cts 108
cp 0.4722
rs 8.4864

16 Methods

Rating   Name   Duplication   Size   Complexity  
C __construct() 0 28 12
A setApiKey() 0 4 1
A getApiKey() 0 4 1
A getWeather() 0 7 1
A getWeatherForecast() 0 13 3
A getWeatherHistory() 3 14 3
A getRawWeatherData() 0 6 1
A getRawHourlyForecastData() 0 6 1
A getRawDailyForecastData() 0 9 2
B getRawWeatherHistory() 3 18 5
A wasCached() 0 4 1
A getRawData() 0 4 1
A cacheOrFetchResult() 0 19 3
A buildUrl() 0 9 2
B buildQueryUrlParameter() 0 13 8
A parseXML() 0 18 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like OpenWeatherMap often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use OpenWeatherMap, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * OpenWeatherMap-PHP-API — A php api to parse weather data from http://www.OpenWeatherMap.org .
4
 *
5
 * @license MIT
6
 *
7
 * Please see the LICENSE file distributed with this source code for further
8
 * information regarding copyright and licensing.
9
 *
10
 * Please visit the following links to read about the usage policies and the license of
11
 * OpenWeatherMap before using this class:
12
 *
13
 * @see http://www.OpenWeatherMap.org
14
 * @see http://www.OpenWeatherMap.org/terms
15
 * @see http://openweathermap.org/appid
16
 */
17
18
namespace Cmfcmf;
19
20
use Cmfcmf\OpenWeatherMap\AbstractCache;
21
use Cmfcmf\OpenWeatherMap\CurrentWeather;
22
use Cmfcmf\OpenWeatherMap\Exception as OWMException;
23
use Cmfcmf\OpenWeatherMap\Fetcher\CurlFetcher;
24
use Cmfcmf\OpenWeatherMap\Fetcher\FetcherInterface;
25
use Cmfcmf\OpenWeatherMap\Fetcher\FileGetContentsFetcher;
26
use Cmfcmf\OpenWeatherMap\WeatherForecast;
27
use Cmfcmf\OpenWeatherMap\WeatherHistory;
28
29
/**
30
 * Main class for the OpenWeatherMap-PHP-API. Only use this class.
31
 *
32
 * @api
33
 */
34
class OpenWeatherMap
35
{
36
    /**
37
     * The copyright notice. This is no official text, it was created by
38
     * following the guidelines at http://openweathermap.org/copyright.
39
     *
40
     * @var string $copyright
41
     */
42
    const COPYRIGHT = "Weather data from <a href=\"http://www.openweathermap.org\">OpenWeatherMap.org</a>";
43
44
    /**
45
     * @var string The basic api url to fetch weather data from.
46
     */
47
    private $weatherUrl = 'http://api.openweathermap.org/data/2.5/weather?';
48
49
    /**
50
     * @var string The basic api url to fetch weekly forecast data from.
51
     */
52
    private $weatherHourlyForecastUrl = 'http://api.openweathermap.org/data/2.5/forecast?';
53
54
    /**
55
     * @var string The basic api url to fetch daily forecast data from.
56
     */
57
    private $weatherDailyForecastUrl = 'http://api.openweathermap.org/data/2.5/forecast/daily?';
58
59
    /**
60
     * @var string The basic api url to fetch history weather data from.
61
     */
62
    private $weatherHistoryUrl = 'http://api.openweathermap.org/data/2.5/history/city?';
63
64
    /**
65
     * @var AbstractCache|bool $cache The cache to use.
66
     */
67
    private $cache = false;
68
69
    /**
70
     * @var int
71
     */
72
    private $seconds;
73
74
    /**
75
     * @var bool
76
     */
77
    private $wasCached = false;
78
79
    /**
80
     * @var FetcherInterface The url fetcher.
81
     */
82
    private $fetcher;
83
84
    /**
85
     * @var string
86
     */
87
    private $apiKey = '';
88
89
    /**
90
     * Constructs the OpenWeatherMap object.
91
     *
92
     * @param string                $apiKey  The OpenWeatherMap API key. Required and only optional for BC.
93
     * @param null|FetcherInterface $fetcher The interface to fetch the data from OpenWeatherMap. Defaults to
94
     *                                       CurlFetcher() if cURL is available. Otherwise defaults to
95
     *                                       FileGetContentsFetcher() using 'file_get_contents()'.
96
     * @param bool|string           $cache   If set to false, caching is disabled. Otherwise this must be a class
97
     *                                       extending AbstractCache. Defaults to false.
98
     * @param int $seconds                   How long weather data shall be cached. Default 10 minutes.
99
     *
100
     * @throws \Exception If $cache is neither false nor a valid callable extending Cmfcmf\OpenWeatherMap\Util\Cache.
101
     *
102
     * @api
103
     */
104 6
    public function __construct($apiKey = '', $fetcher = null, $cache = false, $seconds = 600)
105
    {
106 6
        if (!is_string($apiKey) || empty($apiKey)) {
107
            // BC
108 6
            $seconds = $cache !== false ? $cache : 600;
109 6
            $cache = $fetcher !== null ? $fetcher : false;
110 6
            $fetcher = $apiKey !== '' ? $apiKey : null;
111 6
        } else {
112
            $this->apiKey = $apiKey;
113
        }
114
115 6
        if ($cache !== false && !($cache instanceof AbstractCache)) {
116
            throw new \Exception('The cache class must implement the FetcherInterface!');
117
        }
118 6
        if (!is_numeric($seconds)) {
119
            throw new \Exception('$seconds must be numeric.');
120
        }
121 6
        if (!isset($fetcher)) {
122 6
            $fetcher = (function_exists('curl_version')) ? new CurlFetcher() : new FileGetContentsFetcher();
123 6
        }
124 6
        if ($seconds == 0) {
125
            $cache = false;
126
        }
127
128 6
        $this->cache = $cache;
129 6
        $this->seconds = $seconds;
0 ignored issues
show
Documentation Bug introduced by
It seems like $seconds can also be of type double or string. However, the property $seconds is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
130 6
        $this->fetcher = $fetcher;
0 ignored issues
show
Documentation Bug introduced by
It seems like $fetcher can also be of type string. However, the property $fetcher is declared as type object<Cmfcmf\OpenWeathe...tcher\FetcherInterface>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
131 6
    }
132
133
    /**
134
     * Sets the API Key.
135
     *
136
     * @param string $apiKey API key for the OpenWeatherMap account.
137
     *
138
     * @api
139
     */
140 5
    public function setApiKey($apiKey)
141
    {
142 5
        $this->apiKey = $apiKey;
143 5
    }
144
145
    /**
146
     * Returns the API Key.
147
     *
148
     * @return string
149
     *
150
     * @api
151
     */
152
    public function getApiKey()
153
    {
154
        return $this->apiKey;
155
    }
156
157
    /**
158
     * Returns the current weather at the place you specified.
159
     *
160
     * @param array|int|string $query The place to get weather information for. For possible values see below.
161
     * @param string           $units Can be either 'metric' or 'imperial' (default). This affects almost all units returned.
162
     * @param string           $lang  The language to use for descriptions, default is 'en'. For possible values see http://openweathermap.org/current#multi.
163
     * @param string           $appid Your app id, default ''. See http://openweathermap.org/appid for more details.
164
     *
165
     * @throws OpenWeatherMap\Exception  If OpenWeatherMap returns an error.
166
     * @throws \InvalidArgumentException If an argument error occurs.
167
     *
168
     * @return CurrentWeather The weather object.
169
     *
170
     * There are three ways to specify the place to get weather information for:
171
     * - Use the city name: $query must be a string containing the city name.
172
     * - Use the city id: $query must be an integer containing the city id.
173
     * - Use the coordinates: $query must be an associative array containing the 'lat' and 'lon' values.
174
     *
175
     * @api
176
     */
177 6
    public function getWeather($query, $units = 'imperial', $lang = 'en', $appid = '')
178
    {
179 6
        $answer = $this->getRawWeatherData($query, $units, $lang, $appid, 'xml');
180 6
        $xml = $this->parseXML($answer);
0 ignored issues
show
Bug introduced by
It seems like $answer defined by $this->getRawWeatherData..., $lang, $appid, 'xml') on line 179 can also be of type boolean; however, Cmfcmf\OpenWeatherMap::parseXML() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
181
182 5
        return new CurrentWeather($xml, $units);
183
    }
184
185
    /**
186
     * Returns the forecast for the place you specified. DANGER: Might return
187
     * fewer results than requested due to a bug in the OpenWeatherMap API!
188
     *
189
     * @param array|int|string $query The place to get weather information for. For possible values see ::getWeather.
190
     * @param string           $units Can be either 'metric' or 'imperial' (default). This affects almost all units returned.
191
     * @param string           $lang  The language to use for descriptions, default is 'en'. For possible values see http://openweathermap.org/current#multi.
192
     * @param string           $appid Your app id, default ''. See http://openweathermap.org/appid for more details.
193
     * @param int              $days  For how much days you want to get a forecast. Default 1, maximum: 16.
194
     *
195
     * @throws OpenWeatherMap\Exception If OpenWeatherMap returns an error.
196
     * @throws \InvalidArgumentException If an argument error occurs.
197
     *
198
     * @return WeatherForecast
199
     *
200
     * @api
201
     */
202
    public function getWeatherForecast($query, $units = 'imperial', $lang = 'en', $appid = '', $days = 1)
203
    {
204
        if ($days <= 5) {
205
            $answer = $this->getRawHourlyForecastData($query, $units, $lang, $appid, 'xml');
206
        } elseif ($days <= 16) {
207
            $answer = $this->getRawDailyForecastData($query, $units, $lang, $appid, 'xml', $days);
208
        } else {
209
            throw new \InvalidArgumentException('Error: forecasts are only available for the next 16 days. $days must be 16 or lower.');
210
        }
211
        $xml = $this->parseXML($answer);
0 ignored issues
show
Bug introduced by
It seems like $answer can also be of type boolean; however, Cmfcmf\OpenWeatherMap::parseXML() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
212
213
        return new WeatherForecast($xml, $units, $days);
214
    }
215
216
    /**
217
     * Returns the weather history for the place you specified.
218
     *
219
     * @param array|int|string $query      The place to get weather information for. For possible values see ::getWeather.
220
     * @param \DateTime        $start
221
     * @param int              $endOrCount
222
     * @param string           $type       Can either be 'tick', 'hour' or 'day'.
223
     * @param string           $units      Can be either 'metric' or 'imperial' (default). This affects almost all units returned.
224
     * @param string           $lang       The language to use for descriptions, default is 'en'. For possible values see http://openweathermap.org/current#multi.
225
     * @param string           $appid      Your app id, default ''. See http://openweathermap.org/appid for more details.
226
     *
227
     * @throws OpenWeatherMap\Exception  If OpenWeatherMap returns an error.
228
     * @throws \InvalidArgumentException If an argument error occurs.
229
     *
230
     * @return WeatherHistory
231
     *
232
     * @api
233
     */
234
    public function getWeatherHistory($query, \DateTime $start, $endOrCount = 1, $type = 'hour', $units = 'imperial', $lang = 'en', $appid = '')
235
    {
236 View Code Duplication
        if (!in_array($type, array('tick', 'hour', 'day'))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
237
            throw new \InvalidArgumentException('$type must be either "tick", "hour" or "day"');
238
        }
239
240
        $xml = json_decode($this->getRawWeatherHistory($query, $start, $endOrCount, $type, $units, $lang, $appid), true);
241
242
        if ($xml['cod'] != 200) {
243
            throw new OWMException($xml['message'], $xml['cod']);
244
        }
245
246
        return new WeatherHistory($xml, $query);
247
    }
248
249
    /**
250
     * Directly returns the xml/json/html string returned by OpenWeatherMap for the current weather.
251
     *
252
     * @param array|int|string $query The place to get weather information for. For possible values see ::getWeather.
253
     * @param string           $units Can be either 'metric' or 'imperial' (default). This affects almost all units returned.
254
     * @param string           $lang  The language to use for descriptions, default is 'en'. For possible values see http://openweathermap.org/current#multi.
255
     * @param string           $appid Your app id, default ''. See http://openweathermap.org/appid for more details.
256
     * @param string           $mode  The format of the data fetched. Possible values are 'json', 'html' and 'xml' (default).
257
     *
258
     * @return string Returns false on failure and the fetched data in the format you specified on success.
259
     *
260
     * Warning: If an error occurs, OpenWeatherMap ALWAYS returns json data.
261
     *
262
     * @api
263
     */
264 6
    public function getRawWeatherData($query, $units = 'imperial', $lang = 'en', $appid = '', $mode = 'xml')
265
    {
266 6
        $url = $this->buildUrl($query, $units, $lang, $appid, $mode, $this->weatherUrl);
267
268 6
        return $this->cacheOrFetchResult($url);
269
    }
270
271
    /**
272
     * Directly returns the xml/json/html string returned by OpenWeatherMap for the hourly forecast.
273
     *
274
     * @param array|int|string $query The place to get weather information for. For possible values see ::getWeather.
275
     * @param string           $units Can be either 'metric' or 'imperial' (default). This affects almost all units returned.
276
     * @param string           $lang  The language to use for descriptions, default is 'en'. For possible values see http://openweathermap.org/current#multi.
277
     * @param string           $appid Your app id, default ''. See http://openweathermap.org/appid for more details.
278
     * @param string           $mode  The format of the data fetched. Possible values are 'json', 'html' and 'xml' (default).
279
     *
280
     * @return string Returns false on failure and the fetched data in the format you specified on success.
281
     *
282
     * Warning: If an error occurs, OpenWeatherMap ALWAYS returns json data.
283
     *
284
     * @api
285
     */
286
    public function getRawHourlyForecastData($query, $units = 'imperial', $lang = 'en', $appid = '', $mode = 'xml')
287
    {
288
        $url = $this->buildUrl($query, $units, $lang, $appid, $mode, $this->weatherHourlyForecastUrl);
289
290
        return $this->cacheOrFetchResult($url);
291
    }
292
293
    /**
294
     * Directly returns the xml/json/html string returned by OpenWeatherMap for the daily forecast.
295
     *
296
     * @param array|int|string $query The place to get weather information for. For possible values see ::getWeather.
297
     * @param string           $units Can be either 'metric' or 'imperial' (default). This affects almost all units returned.
298
     * @param string           $lang  The language to use for descriptions, default is 'en'. For possible values see http://openweathermap.org/current#multi.
299
     * @param string           $appid Your app id, default ''. See http://openweathermap.org/appid for more details.
300
     * @param string           $mode  The format of the data fetched. Possible values are 'json', 'html' and 'xml' (default)
301
     * @param int              $cnt   How many days of forecast shall be returned? Maximum (and default): 16
302
     *
303
     * @throws \InvalidArgumentException If $cnt is higher than 16.
304
     *
305
     * @return string Returns false on failure and the fetched data in the format you specified on success.
306
     *
307
     * Warning: If an error occurs, OpenWeatherMap ALWAYS returns json data.
308
     *
309
     * @api
310
     */
311
    public function getRawDailyForecastData($query, $units = 'imperial', $lang = 'en', $appid = '', $mode = 'xml', $cnt = 16)
312
    {
313
        if ($cnt > 16) {
314
            throw new \InvalidArgumentException('$cnt must be 16 or lower!');
315
        }
316
        $url = $this->buildUrl($query, $units, $lang, $appid, $mode, $this->weatherDailyForecastUrl) . "&cnt=$cnt";
317
318
        return $this->cacheOrFetchResult($url);
319
    }
320
321
    /**
322
     * Directly returns the xml/json/html string returned by OpenWeatherMap for the weather history.
323
     *
324
     * @param array|int|string $query      The place to get weather information for. For possible values see ::getWeather.
325
     * @param \DateTime        $start      The \DateTime object of the date to get the first weather information from.
326
     * @param \DateTime|int    $endOrCount Can be either a \DateTime object representing the end of the period to
327
     *                                     receive weather history data for or an integer counting the number of
328
     *                                     reports requested.
329
     * @param string           $type       The period of the weather history requested. Can be either be either "tick",
330
     *                                     "hour" or "day".
331
     * @param string           $units      Can be either 'metric' or 'imperial' (default). This affects almost all units returned.
332
     * @param string           $lang       The language to use for descriptions, default is 'en'. For possible values see http://openweathermap.org/current#multi.
333
     * @param string           $appid      Your app id, default ''. See http://openweathermap.org/appid for more details.
334
     *
335
     * @throws \InvalidArgumentException
336
     *
337
     * @return string Returns false on failure and the fetched data in the format you specified on success.
338
     *
339
     * Warning If an error occurred, OpenWeatherMap ALWAYS returns data in json format.
340
     *
341
     * @api
342
     */
343
    public function getRawWeatherHistory($query, \DateTime $start, $endOrCount = 1, $type = 'hour', $units = 'imperial', $lang = 'en', $appid = '')
344
    {
345 View Code Duplication
        if (!in_array($type, array('tick', 'hour', 'day'))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
346
            throw new \InvalidArgumentException('$type must be either "tick", "hour" or "day"');
347
        }
348
349
        $url = $this->buildUrl($query, $units, $lang, $appid, 'json', $this->weatherHistoryUrl);
350
        $url .= "&type=$type&start={$start->format('U')}";
351
        if ($endOrCount instanceof \DateTime) {
352
            $url .= "&end={$endOrCount->format('U')}";
353
        } elseif (is_numeric($endOrCount) && $endOrCount > 0) {
354
            $url .= "&cnt=$endOrCount";
355
        } else {
356
            throw new \InvalidArgumentException('$endOrCount must be either a \DateTime or a positive integer.');
357
        }
358
359
        return $this->cacheOrFetchResult($url);
360
    }
361
362
    /**
363
     * Returns whether or not the last result was fetched from the cache.
364
     *
365
     * @return bool true if last result was fetched from cache, false otherwise.
366
     */
367
    public function wasCached()
368
    {
369
        return $this->wasCached;
370
    }
371
372
    /**
373
     * @deprecated Use {@link self::getRawWeatherData()} instead.
374
     */
375
    public function getRawData($query, $units = 'imperial', $lang = 'en', $appid = '', $mode = 'xml')
376
    {
377
        return $this->getRawWeatherData($query, $units, $lang, $appid, $mode);
378
    }
379
380
    /**
381
     * Fetches the result or delivers a cached version of the result.
382
     *
383
     * @param string $url
384
     *
385
     * @return string
386
     */
387 6
    private function cacheOrFetchResult($url)
388
    {
389 6
        if ($this->cache !== false) {
390
            /** @var AbstractCache $cache */
391
            $cache = $this->cache;
392
            $cache->setSeconds($this->seconds);
393
            if ($cache->isCached($url)) {
394
                $this->wasCached = true;
395
                return $cache->getCached($url);
396
            }
397
            $result = $this->fetcher->fetch($url);
398
            $cache->setCached($url, $result);
399
        } else {
400 6
            $result = $this->fetcher->fetch($url);
401
        }
402 6
        $this->wasCached = false;
403
404 6
        return $result;
405
    }
406
407
    /**
408
     * Build the url to fetch weather data from.
409
     *
410
     * @param        $query
411
     * @param        $units
412
     * @param        $lang
413
     * @param        $appid
414
     * @param        $mode
415
     * @param string $url   The url to prepend.
416
     *
417
     * @return bool|string The fetched url, false on failure.
418
     */
419 6
    private function buildUrl($query, $units, $lang, $appid, $mode, $url)
420
    {
421 6
        $queryUrl = $this->buildQueryUrlParameter($query);
422
423 6
        $url = $url."$queryUrl&units=$units&lang=$lang&mode=$mode&APPID=";
424 6
        $url .= empty($appid) ? $this->apiKey : $appid;
425
426 6
        return $url;
427
    }
428
429
    /**
430
     * Builds the query string for the url.
431
     *
432
     * @param mixed $query
433
     *
434
     * @return string The built query string for the url.
435
     *
436
     * @throws \InvalidArgumentException If the query parameter is invalid.
437
     */
438 6
    private function buildQueryUrlParameter($query)
439
    {
440
        switch ($query) {
441 6
            case is_array($query) && isset($query['lat']) && isset($query['lon']) && is_numeric($query['lat']) && is_numeric($query['lon']):
442 1
                return "lat={$query['lat']}&lon={$query['lon']}";
443 5
            case is_numeric($query):
444 1
                return "id=$query";
445 4
            case is_string($query):
446 4
                return 'q='.urlencode($query);
447
            default:
448
                throw new \InvalidArgumentException('Error: $query has the wrong format. See the documentation of OpenWeatherMap::getWeather() to read about valid formats.');
449
        }
450
    }
451
452
    /**
453
     * @param string $answer The content returned by OpenWeatherMap.
454
     *
455
     * @return \SimpleXMLElement
456
     * @throws OWMException If the content isn't valid XML.
457
     */
458 6
    private function parseXML($answer)
459
    {
460
        // Disable default error handling of SimpleXML (Do not throw E_WARNINGs).
461 6
        libxml_use_internal_errors(true);
462 6
        libxml_clear_errors();
463
        try {
464 6
            return new \SimpleXMLElement($answer);
465 1
        } catch (\Exception $e) {
466
            // Invalid xml format. This happens in case OpenWeatherMap returns an error.
467
            // OpenWeatherMap always uses json for errors, even if one specifies xml as format.
468 1
            $error = json_decode($answer, true);
469 1
            if (isset($error['message'])) {
470 1
                throw new OWMException($error['message'], $error['cod']);
471
            } else {
472
                throw new OWMException('Unknown fatal error: OpenWeatherMap returned the following json object: ' . $answer);
473
            }
474
        }
475
    }
476
}
477