Completed
Pull Request — master (#84)
by
unknown
02:56
created

OpenWeatherMap::getDailyWeatherForecast()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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