Completed
Pull Request — master (#58)
by
unknown
02:04
created

OpenWeatherMap   C

Complexity

Total Complexity 54

Size/Duplication

Total Lines 611
Duplicated Lines 2.62 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 0%

Importance

Changes 12
Bugs 6 Features 2
Metric Value
wmc 54
c 12
b 6
f 2
lcom 1
cbo 7
dl 16
loc 611
ccs 0
cts 160
cp 0
rs 6.9245

15 Methods

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

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
     * @var string $weatherUrl The basic api url to fetch weather data from.
38
     */
39
    private $weatherUrl = "http://api.openweathermap.org/data/2.5/weather?";
40
41
    /**
42
     * @var string $url The basic api url to fetch weekly forecast data from.
43
     */
44
    private $weatherHourlyForecastUrl = "http://api.openweathermap.org/data/2.5/forecast?";
45
46
    /**
47
     * @var string $url The basic api url to fetch daily forecast data from.
48
     */
49
    private $weatherDailyForecastUrl = "http://api.openweathermap.org/data/2.5/forecast/daily?";
50
51
    /**
52
     * @var string $url The basic api url to fetch history weather data from.
53
     */
54
    private $weatherHistoryUrl = "http://api.openweathermap.org/data/2.5/history/city?";
55
56
    /**
57
     * The copyright notice. This is no official text, this hint was created regarding to http://openweathermap.org/copyright.
58
     *
59
     * @var string $copyright
60
     */
61
    const COPYRIGHT = "Weather data from <a href=\"http://www.openweathermap.org\">OpenWeatherMap.org</a>";
62
63
    /**
64
     * @var \Cmfcmf\OpenWeatherMap\AbstractCache|bool $cacheClass The cache class.
65
     */
66
    private $cacheClass = false;
67
68
    /**
69
     * @var int
70
     */
71
    private $seconds;
72
73
    /**
74
     * @var bool
75
     */
76
    private $wasCached = false;
77
    
78
    /**
79
     * @var FetcherInterface The url fetcher.
80
     */
81
    
82
    private $fetcher;
83
84
    /**
85
     * @var $apiKey.
86
     */
87
88
    private $apiKey = '';
89
90
    /**
91
     * Constructs the OpenWeatherMap object.
92
     *
93
     * @param null|string           $appid      The API key. Defaults to null.
94
     *
95
     * @param null|FetcherInterface $fetcher    The interface to fetch the data from OpenWeatherMap. Defaults to
96
     *                                          CurlFetcher() if cURL is available. Otherwise defaults to
97
     *                                          FileGetContentsFetcher() using 'file_get_contents()'.
98
     * @param bool|string           $cacheClass If set to false, caching is disabled. Otherwise this must be a class
99
     *                                          extending AbstractCache. Defaults to false.
100
     * @param int                   $seconds    How long weather data shall be cached. Default 10 minutes.
101
     *
102
     * @throws \Exception If $cache is neither false nor a valid callable extending Cmfcmf\OpenWeatherMap\Util\Cache.
103
     * @api
104
     */
105
    public function __construct($appid = null, $fetcher = null, $cacheClass = false, $seconds = 600)
106
    {
107
        if ($cacheClass !== false && !($cacheClass instanceof AbstractCache)) {
108
            throw new \Exception("The cache class must implement the FetcherInterface!");
109
        }
110
        if (!is_numeric($seconds)) {
111
            throw new \Exception("\$seconds must be numeric.");
112
        }
113
        if (!isset($fetcher)) {
114
            $fetcher = (function_exists('curl_version')) ? new CurlFetcher() : new FileGetContentsFetcher();
115
        }
116
        if ($seconds == 0) {
117
            $cacheClass = false;
118
        }
119
120
        if (!is_null($appid)) {
121
            $this->apiKey = $appid;
122
        }
123
        $this->cacheClass = $cacheClass;
124
        $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...
125
        $this->fetcher = $fetcher;
126
    }
127
128
    /**
129
     * Sets the API Key
130
     * @param	string	API key for the OpenWeatherMap account making the connection.
131
     *
132
     * @api
133
     */
134
     public function setApiKey($appid)
135
     {
136
         $this->apiKey = $appid;
137
     }
138
139
    /**
140
     * Returns the API Key
141
     *
142
     * @api
143
     */
144
     public function getApiKey()
145
     {
146
         return $this->apiKey;
147
     }
148
149
    /**
150
     * Returns the current weather at the place you specified as an object.
151
     *
152
     * @param array|int|string $query The place to get weather information for. For possible values see below.
153
     * @param string           $units Can be either 'metric' or 'imperial' (default). This affects almost all units returned.
154
     * @param string           $lang  The language to use for descriptions, default is 'en'. For possible values see below.
155
     * @param string           $appid Your app id, default ''. See http://openweathermap.org/appid for more details.
156
     *
157
     * @throws OpenWeatherMap\Exception If OpenWeatherMap returns an error.
158
     * @throws \InvalidArgumentException If an argument error occurs.
159
     *
160
     * @return CurrentWeather The weather object.
161
     *
162
     * There are three ways to specify the place to get weather information for:
163
     * - Use the city name: $query must be a string containing the city name.
164
     * - Use the city id: $query must be an integer containing the city id.
165
     * - Use the coordinates: $query must be an associative array containing the 'lat' and 'lon' values.
166
     *
167
     * Available languages are (as of 17. July 2013):
168
     * - English - en
169
     * - Russian - ru
170
     * - Italian - it
171
     * - Spanish - sp
172
     * - Ukrainian - ua
173
     * - German - de
174
     * - Portuguese - pt
175
     * - Romanian - ro
176
     * - Polish - pl
177
     * - Finnish - fi
178
     * - Dutch - nl
179
     * - French - fr
180
     * - Bulgarian - bg
181
     * - Swedish - se
182
     * - Chinese Traditional - zh_tw
183
     * - Chinese Simplified - zh_cn
184
     * - Turkish - tr
185
     *
186
     * @api
187
     */
188
    public function getWeather($query, $units = 'imperial', $lang = 'en', $appid = '')
189
    {
190
        // Disable default error handling of SimpleXML (Do not throw E_WARNINGs).
191
        libxml_use_internal_errors(true);
192
        libxml_clear_errors();
193
194
        $answer = $this->getRawWeatherData($query, $units, $lang, empty($appid) ? $this->apiKey : $appid, 'xml');
195
196
        try {
197
            $xml = new \SimpleXMLElement($answer);
198
        } catch (\Exception $e) {
199
            // Invalid xml format. This happens in case OpenWeatherMap returns an error.
200
            // OpenWeatherMap always uses json for errors, even if one specifies xml as format.
201
            $error = json_decode($answer, true);
202 View Code Duplication
            if (isset($error['message'])) {
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...
203
                throw new OWMException($error['message'], $error['cod']);
204
            } else {
205
                throw new OWMException('Unknown fatal error: OpenWeatherMap returned the following json object: ' . $answer);
206
            }
207
        }
208
209
        return new CurrentWeather($xml, $units);
210
    }
211
212
    /**
213
     * Returns the current weather at the place you specified as an object.
214
     *
215
     * @param array|int|string $query The place to get weather information for. For possible values see below.
216
     * @param string           $units Can be either 'metric' or 'imperial' (default). This affects almost all units returned.
217
     * @param string           $lang  The language to use for descriptions, default is 'en'. For possible values see below.
218
     * @param string           $appid Your app id, default ''. See http://openweathermap.org/appid for more details.
219
     * @param int              $days  For how much days you want to get a forecast. Default 1, maximum: 16.
220
     *
221
     * @throws OpenWeatherMap\Exception If OpenWeatherMap returns an error.
222
     * @throws \InvalidArgumentException If an argument error occurs.
223
     *
224
     * @return WeatherForecast The WeatherForecast object.
225
     *
226
     * There are three ways to specify the place to get weather information for:
227
     * - Use the city name: $query must be a string containing the city name.
228
     * - Use the city id: $query must be an integer containing the city id.
229
     * - Use the coordinates: $query must be an associative array containing the 'lat' and 'lon' values.
230
     *
231
     * Available languages are (as of 17. July 2013):
232
     * - English - en
233
     * - Russian - ru
234
     * - Italian - it
235
     * - Spanish - sp
236
     * - Ukrainian - ua
237
     * - German - de
238
     * - Portuguese - pt
239
     * - Romanian - ro
240
     * - Polish - pl
241
     * - Finnish - fi
242
     * - Dutch - nl
243
     * - French - fr
244
     * - Bulgarian - bg
245
     * - Swedish - se
246
     * - Chinese Traditional - zh_tw
247
     * - Chinese Simplified - zh_cn
248
     * - Turkish - tr
249
     *
250
     * @api
251
     */
252
    public function getWeatherForecast($query, $units = 'imperial', $lang = 'en', $appid = '', $days = 1)
253
    {
254
        // Disable default error handling of SimpleXML (Do not throw E_WARNINGs).
255
        libxml_use_internal_errors(true);
256
        libxml_clear_errors();
257
258
        if ($days <= 5) {
259
            $answer = $this->getRawHourlyForecastData($query, $units, $lang, empty($appid) ? $this->apiKey : $appid, 'xml');
260
        } elseif ($days <= 16) {
261
            $answer = $this->getRawDailyForecastData($query, $units, $lang, empty($appid) ? $this->apiKey : $appid, 'xml', $days);
262
        } else {
263
            throw new \InvalidArgumentException('Error: forecasts are only available for the next 16 days. $days must be lower than 17.');
264
        }
265
266
        try {
267
            $xml = new \SimpleXMLElement($answer);
268
        } catch (\Exception $e) {
269
            // Invalid xml format. This happens in case OpenWeatherMap returns an error.
270
            // OpenWeatherMap always uses json for errors, even if one specifies xml as format.
271
            $error = json_decode($answer, true);
272 View Code Duplication
            if (isset($error['message'])) {
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...
273
                throw new OWMException($error['message'], $error['cod']);
274
            } else {
275
                throw new OWMException('Unknown fatal error: OpenWeatherMap returned the following json object: ' . $answer);
276
            }
277
        }
278
279
        return new WeatherForecast($xml, $units, $days);
280
    }
281
282
    /**
283
     * Returns the weather history for the place you specified as an object.
284
     *
285
     * @param array|int|string $query The place to get weather information for. For possible values see below.
286
     * @param \DateTime        $start
287
     * @param int              $endOrCount
288
     * @param string           $type
289
     * @param string           $units Can be either 'metric' or 'imperial' (default). This affects almost all units returned.
290
     * @param string           $lang  The language to use for descriptions, default is 'en'. For possible values see below.
291
     * @param string           $appid Your app id, default ''. See http://openweathermap.org/appid for more details.
292
     *
293
     * @throws OpenWeatherMap\Exception If OpenWeatherMap returns an error.
294
     * @throws \InvalidArgumentException If an argument error occurs.
295
     *
296
     * @return WeatherHistory The WeatherHistory object.
297
     *
298
     * There are three ways to specify the place to get weather information for:
299
     * - Use the city name: $query must be a string containing the city name.
300
     * - Use the city id: $query must be an integer containing the city id.
301
     * - Use the coordinates: $query must be an associative array containing the 'lat' and 'lon' values.
302
     *
303
     * Available languages are (as of 17. July 2013):
304
     * - English - en
305
     * - Russian - ru
306
     * - Italian - it
307
     * - Spanish - sp
308
     * - Ukrainian - ua
309
     * - German - de
310
     * - Portuguese - pt
311
     * - Romanian - ro
312
     * - Polish - pl
313
     * - Finnish - fi
314
     * - Dutch - nl
315
     * - French - fr
316
     * - Bulgarian - bg
317
     * - Swedish - se
318
     * - Chinese Traditional - zh_tw
319
     * - Chinese Simplified - zh_cn
320
     * - Turkish - tr
321
     *
322
     * @api
323
     */
324
    public function getWeatherHistory($query, \DateTime $start, $endOrCount = 1, $type = 'hour', $units = 'imperial', $lang = 'en', $appid = '')
325
    {
326 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...
327
            throw new \InvalidArgumentException('$type must be either "tick", "hour" or "day"');
328
        }
329
330
        $xml = json_decode($this->getRawWeatherHistory($query, $start, $endOrCount, $type, $units, $lang, empty($appid) ? $this->apiKey : $appid), true);
331
332
        if ($xml['cod'] != 200) {
333
            throw new OWMException($xml['message'], $xml['cod']);
334
        }
335
336
        return new WeatherHistory($xml, $query);
337
    }
338
339
    /**
340
     * @deprecated Use {@link self::getRawWeatherData()} instead.
341
     */
342
    public function getRawData($query, $units = 'imperial', $lang = 'en', $appid = '', $mode = 'xml')
343
    {
344
        return $this->getRawWeatherData($query, $units, $lang, empty($appid) ? $this->apiKey : $appid, $mode);
345
    }
346
347
    /**
348
     * Directly returns the xml/json/html string returned by OpenWeatherMap for the current weather.
349
     *
350
     * @param array|int|string $query The place to get weather information for. For possible values see below.
351
     * @param string           $units Can be either 'metric' or 'imperial' (default). This affects almost all units returned.
352
     * @param string           $lang  The language to use for descriptions, default is 'en'. For possible values see below.
353
     * @param string           $appid Your app id, default ''. See http://openweathermap.org/appid for more details.
354
     * @param string           $mode  The format of the data fetched. Possible values are 'json', 'html' and 'xml' (default).
355
     *
356
     * @return string Returns false on failure and the fetched data in the format you specified on success.
357
     *
358
     * Warning If an error occurred, OpenWeatherMap returns data in json format ALWAYS
359
     *
360
     * There are three ways to specify the place to get weather information for:
361
     * - Use the city name: $query must be a string containing the city name.
362
     * - Use the city id: $query must be an integer containing the city id.
363
     * - Use the coordinates: $query must be an associative array containing the 'lat' and 'lon' values.
364
     *
365
     * Available languages are (as of 17. July 2013):
366
     * - English - en
367
     * - Russian - ru
368
     * - Italian - it
369
     * - Spanish - sp
370
     * - Ukrainian - ua
371
     * - German - de
372
     * - Portuguese - pt
373
     * - Romanian - ro
374
     * - Polish - pl
375
     * - Finnish - fi
376
     * - Dutch - nl
377
     * - French - fr
378
     * - Bulgarian - bg
379
     * - Swedish - se
380
     * - Chinese Traditional - zh_tw
381
     * - Chinese Simplified - zh_cn
382
     * - Turkish - tr
383
     *
384
     * @api
385
     */
386
    public function getRawWeatherData($query, $units = 'imperial', $lang = 'en', $appid = '', $mode = 'xml')
387
    {
388
        $url = $this->buildUrl($query, $units, $lang, empty($appid) ? $this->apiKey : $appid, $mode, $this->weatherUrl);
389
390
        return $this->cacheOrFetchResult($url);
391
    }
392
393
    /**
394
     * Directly returns the xml/json/html string returned by OpenWeatherMap for the hourly forecast.
395
     *
396
     * @param array|int|string $query The place to get weather information for. For possible values see below.
397
     * @param string           $units Can be either 'metric' or 'imperial' (default). This affects almost all units returned.
398
     * @param string           $lang  The language to use for descriptions, default is 'en'. For possible values see below.
399
     * @param string           $appid Your app id, default ''. See http://openweathermap.org/appid for more details.
400
     * @param string           $mode  The format of the data fetched. Possible values are 'json', 'html' and 'xml' (default).
401
     *
402
     * @return string Returns false on failure and the fetched data in the format you specified on success.
403
     *
404
     * Warning If an error occurred, OpenWeatherMap returns data in json format ALWAYS
405
     *
406
     * There are three ways to specify the place to get weather information for:
407
     * - Use the city name: $query must be a string containing the city name.
408
     * - Use the city id: $query must be an integer containing the city id.
409
     * - Use the coordinates: $query must be an associative array containing the 'lat' and 'lon' values.
410
     *
411
     * Available languages are (as of 17. July 2013):
412
     * - English - en
413
     * - Russian - ru
414
     * - Italian - it
415
     * - Spanish - sp
416
     * - Ukrainian - ua
417
     * - German - de
418
     * - Portuguese - pt
419
     * - Romanian - ro
420
     * - Polish - pl
421
     * - Finnish - fi
422
     * - Dutch - nl
423
     * - French - fr
424
     * - Bulgarian - bg
425
     * - Swedish - se
426
     * - Chinese Traditional - zh_tw
427
     * - Chinese Simplified - zh_cn
428
     * - Turkish - tr
429
     *
430
     * @api
431
     */
432
    public function getRawHourlyForecastData($query, $units = 'imperial', $lang = 'en', $appid = '', $mode = 'xml')
433
    {
434
        $url = $this->buildUrl($query, $units, $lang, empty($appid) ? $this->apiKey : $appid, $mode, $this->weatherHourlyForecastUrl);
435
436
        return $this->cacheOrFetchResult($url);
437
    }
438
439
    /**
440
     * Directly returns the xml/json/html string returned by OpenWeatherMap for the daily forecast.
441
     *
442
     * @param array|int|string $query The place to get weather information for. For possible values see below.
443
     * @param string           $units Can be either 'metric' or 'imperial' (default). This affects almost all units returned.
444
     * @param string           $lang  The language to use for descriptions, default is 'en'. For possible values see below.
445
     * @param string           $appid Your app id, default ''. See http://openweathermap.org/appid for more details.
446
     * @param string           $mode  The format of the data fetched. Possible values are 'json', 'html' and 'xml' (default)
447
     * @param int              $cnt   How many days of forecast shall be returned? Maximum (and default): 16
448
     *
449
     * @throws \InvalidArgumentException If $cnt is higher than 16.
450
     * @return string Returns false on failure and the fetched data in the format you specified on success.
451
     *
452
     * Warning If an error occurred, OpenWeatherMap returns data in json format ALWAYS
453
     *
454
     * There are three ways to specify the place to get weather information for:
455
     * - Use the city name: $query must be a string containing the city name.
456
     * - Use the city id: $query must be an integer containing the city id.
457
     * - Use the coordinates: $query must be an associative array containing the 'lat' and 'lon' values.
458
     *
459
     * Available languages are (as of 17. July 2013):
460
     * - English - en
461
     * - Russian - ru
462
     * - Italian - it
463
     * - Spanish - sp
464
     * - Ukrainian - ua
465
     * - German - de
466
     * - Portuguese - pt
467
     * - Romanian - ro
468
     * - Polish - pl
469
     * - Finnish - fi
470
     * - Dutch - nl
471
     * - French - fr
472
     * - Bulgarian - bg
473
     * - Swedish - se
474
     * - Chinese Traditional - zh_tw
475
     * - Chinese Simplified - zh_cn
476
     * - Turkish - tr
477
     *
478
     * @api
479
     */
480
    public function getRawDailyForecastData($query, $units = 'imperial', $lang = 'en', $appid = '', $mode = 'xml', $cnt = 16)
481
    {
482
        if ($cnt > 16) {
483
            throw new \InvalidArgumentException('$cnt must be 16 or below!');
484
        }
485
        $url = $this->buildUrl($query, $units, $lang, empty($appid) ? $this->apiKey : $appid, $mode, $this->weatherDailyForecastUrl) . "&cnt=$cnt";
486
487
        return $this->cacheOrFetchResult($url);
488
    }
489
490
    /**
491
     * Directly returns the xml/json/html string returned by OpenWeatherMap for the daily forecast.
492
     *
493
     * @param array|int|string $query           The place to get weather information for. For possible values see below.
494
     * @param \DateTime        $start           The \DateTime object of the date to get the first weather information from.
495
     * @param \DateTime|int    $endOrCount      Can be either a \DateTime object representing the end of the period to
496
     *                                          receive weather history data for or an integer counting the number of
497
     *                                          reports requested.
498
     * @param string           $type            The period of the weather history requested. Can be either be either "tick",
499
     *                                          "hour" or "day".
500
     * @param string           $units           Can be either 'metric' or 'imperial' (default). This affects almost all units returned.
501
     * @param string           $lang            The language to use for descriptions, default is 'en'. For possible values see below.
502
     * @param string           $appid           Your app id, default ''. See http://openweathermap.org/appid for more details.
503
     *
504
     * @throws \InvalidArgumentException
505
     *
506
     * @return string Returns false on failure and the fetched data in the format you specified on success.
507
     *
508
     * Warning If an error occurred, OpenWeatherMap returns data in json format ALWAYS
509
     *
510
     * There are three ways to specify the place to get weather information for:
511
     * - Use the city name: $query must be a string containing the city name.
512
     * - Use the city id: $query must be an integer containing the city id.
513
     * - Use the coordinates: $query must be an associative array containing the 'lat' and 'lon' values.
514
     *
515
     * Available languages are (as of 17. July 2013):
516
     * - English - en
517
     * - Russian - ru
518
     * - Italian - it
519
     * - Spanish - sp
520
     * - Ukrainian - ua
521
     * - German - de
522
     * - Portuguese - pt
523
     * - Romanian - ro
524
     * - Polish - pl
525
     * - Finnish - fi
526
     * - Dutch - nl
527
     * - French - fr
528
     * - Bulgarian - bg
529
     * - Swedish - se
530
     * - Chinese Traditional - zh_tw
531
     * - Chinese Simplified - zh_cn
532
     * - Turkish - tr
533
     *
534
     * @api
535
     */
536
    public function getRawWeatherHistory($query, \DateTime $start, $endOrCount = 1, $type = 'hour', $units = 'imperial', $lang = 'en', $appid = '')
537
    {
538 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...
539
            throw new \InvalidArgumentException('$type must be either "tick", "hour" or "day"');
540
        }
541
542
        $queryUrl = $this->weatherHistoryUrl . $this->buildQueryUrlParameter($query) . "&start={$start->format('U')}";
543
544
        if ($endOrCount instanceof \DateTime) {
545
            $queryUrl .= "&end={$endOrCount->format('U')}";
546
        } elseif (is_numeric($endOrCount) && $endOrCount > 0) {
547
            $queryUrl .= "&cnt=$endOrCount";
548
        } else {
549
            throw new \InvalidArgumentException('$endOrCount must be either a \DateTime or a positive integer.');
550
        }
551
        $queryUrl .= "&type=$type&units=$units&lang=$lang&APPID=";
552
553
        $queryUrl .= empty($appid) ? $this->apiKey : $appid;
554
555
        return $this->cacheOrFetchResult($queryUrl);
556
    }
557
558
    /**
559
     * Fetches the result or delivers a cached version of the result.
560
     *
561
     * @param $url
562
     *
563
     * @return string
564
     *
565
     * @internal
566
     */
567
    private function cacheOrFetchResult($url)
568
    {
569
        if ($this->cacheClass !== false) {
570
            /** @var \Cmfcmf\OpenWeatherMap\AbstractCache $cache */
571
            $cache = $this->cacheClass;
572
            $cache->setSeconds($this->seconds);
573
            $this->wasCached=false;
574
            if ($cache->isCached($url)) {
575
                $this->wasCached=true;
576
                return $cache->getCached($url);
577
            }
578
            $result = $this->fetcher->fetch($url);
579
            $cache->setCached($url, $result);
580
        } else {
581
            $result = $this->fetcher->fetch($url);
582
        }
583
584
        return $result;
585
    }
586
587
    /**
588
     * Build the url to fetch weather data from.
589
     *
590
     * @param        $query
591
     * @param        $units
592
     * @param        $lang
593
     * @param        $appid
594
     * @param        $mode
595
     * @param string $url The url to prepend.
596
     *
597
     * @return bool|string The fetched url, false on failure.
598
     *
599
     * @internal
600
     */
601
    private function buildUrl($query, $units, $lang, $appid, $mode, $url)
602
    {
603
        $queryUrl = $this->buildQueryUrlParameter($query);
604
605
        $url = $url . "$queryUrl&units=$units&lang=$lang&mode=$mode&APPID=";
606
        $url .= empty($appid) ? $this->apiKey : $appid;
607
608
        return $url;
609
    }
610
611
    /**
612
     * Builds the query string for the url.
613
     *
614
     * @param $query
615
     *
616
     * @return string The built query string for the url.
617
     * @throws \InvalidArgumentException If the query parameter is invalid.
618
     *
619
     * @internal
620
     */
621
    private function buildQueryUrlParameter($query)
622
    {
623
        switch ($query) {
624
            case (is_array($query) && isset($query['lat']) && isset($query['lon']) && is_numeric($query['lat']) && is_numeric($query['lon'])):
625
                return "lat={$query['lat']}&lon={$query['lon']}";
626
            case (is_numeric($query)):
627
                return "id=$query";
628
            case (is_string($query)):
629
                return "q=" . urlencode($query);
630
            default:
631
                throw new \InvalidArgumentException('Error: $query has the wrong format. See the documentation of OpenWeatherMap::getRawData() to read about valid formats.');
632
        }
633
    }
634
    
635
    /**
636
     * Returns whether or not the last result was fetched from the cache.
637
     * 
638
     * @return bool true if last result was fetched from cache, otherwise false.
639
     */
640
    public function wasCached()
641
    {
642
        return $this->wasCached;
643
    }
644
}
645