Completed
Push — master ( c3530d...654211 )
by Christian
02:38
created

OpenWeatherMap::__construct()   C

Complexity

Conditions 12
Paths 72

Size

Total Lines 28
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 156

Importance

Changes 0
Metric Value
dl 0
loc 28
ccs 0
cts 25
cp 0
rs 5.1612
c 0
b 0
f 0
cc 12
eloc 18
nc 72
nop 4
crap 156

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
     * @api
340
     */
341
    public function getRawWeatherGroupData($ids, $units = 'imperial', $lang = 'en', $appid = '')
342
    {
343
        $url = $this->buildUrl($ids, $units, $lang, $appid, 'json', $this->weatherGroupUrl);
344
345
        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 343 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...
346
    }
347
348
    /**
349
     * Directly returns the xml/json/html string returned by OpenWeatherMap for the hourly forecast.
350
     *
351
     * @param array|int|string $query The place to get weather information for. For possible values see ::getWeather.
352
     * @param string           $units Can be either 'metric' or 'imperial' (default). This affects almost all units returned.
353
     * @param string           $lang  The language to use for descriptions, default is 'en'. For possible values see http://openweathermap.org/current#multi.
354
     * @param string           $appid Your app id, default ''. See http://openweathermap.org/appid for more details.
355
     * @param string           $mode  The format of the data fetched. Possible values are 'json', 'html' and 'xml' (default).
356
     *
357
     * @return string Returns false on failure and the fetched data in the format you specified on success.
358
     *
359
     * Warning: If an error occurs, OpenWeatherMap ALWAYS returns json data.
360
     *
361
     * @api
362
     */
363
    public function getRawHourlyForecastData($query, $units = 'imperial', $lang = 'en', $appid = '', $mode = 'xml')
364
    {
365
        $url = $this->buildUrl($query, $units, $lang, $appid, $mode, $this->weatherHourlyForecastUrl);
366
367
        return $this->cacheOrFetchResult($url);
0 ignored issues
show
Bug introduced by
It seems like $url defined by $this->buildUrl($query, ...atherHourlyForecastUrl) on line 365 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...
368
    }
369
370
    /**
371
     * Directly returns the xml/json/html string returned by OpenWeatherMap for the daily forecast.
372
     *
373
     * @param array|int|string $query The place to get weather information for. For possible values see ::getWeather.
374
     * @param string           $units Can be either 'metric' or 'imperial' (default). This affects almost all units returned.
375
     * @param string           $lang  The language to use for descriptions, default is 'en'. For possible values see http://openweathermap.org/current#multi.
376
     * @param string           $appid Your app id, default ''. See http://openweathermap.org/appid for more details.
377
     * @param string           $mode  The format of the data fetched. Possible values are 'json', 'html' and 'xml' (default)
378
     * @param int              $cnt   How many days of forecast shall be returned? Maximum (and default): 16
379
     *
380
     * @throws \InvalidArgumentException If $cnt is higher than 16.
381
     *
382
     * @return string Returns false on failure and the fetched data in the format you specified on success.
383
     *
384
     * Warning: If an error occurs, OpenWeatherMap ALWAYS returns json data.
385
     *
386
     * @api
387
     */
388
    public function getRawDailyForecastData($query, $units = 'imperial', $lang = 'en', $appid = '', $mode = 'xml', $cnt = 16)
389
    {
390
        if ($cnt > 16) {
391
            throw new \InvalidArgumentException('$cnt must be 16 or lower!');
392
        }
393
        $url = $this->buildUrl($query, $units, $lang, $appid, $mode, $this->weatherDailyForecastUrl) . "&cnt=$cnt";
394
395
        return $this->cacheOrFetchResult($url);
396
    }
397
398
    /**
399
     * Directly returns the xml/json/html string returned by OpenWeatherMap for the weather history.
400
     *
401
     * @param array|int|string $query      The place to get weather information for. For possible values see ::getWeather.
402
     * @param \DateTime        $start      The \DateTime object of the date to get the first weather information from.
403
     * @param \DateTime|int    $endOrCount Can be either a \DateTime object representing the end of the period to
404
     *                                     receive weather history data for or an integer counting the number of
405
     *                                     reports requested.
406
     * @param string           $type       The period of the weather history requested. Can be either be either "tick",
407
     *                                     "hour" or "day".
408
     * @param string           $units      Can be either 'metric' or 'imperial' (default). This affects almost all units returned.
409
     * @param string           $lang       The language to use for descriptions, default is 'en'. For possible values see http://openweathermap.org/current#multi.
410
     * @param string           $appid      Your app id, default ''. See http://openweathermap.org/appid for more details.
411
     *
412
     * @throws \InvalidArgumentException
413
     *
414
     * @return string Returns false on failure and the fetched data in the format you specified on success.
415
     *
416
     * Warning If an error occurred, OpenWeatherMap ALWAYS returns data in json format.
417
     *
418
     * @api
419
     */
420
    public function getRawWeatherHistory($query, \DateTime $start, $endOrCount = 1, $type = 'hour', $units = 'imperial', $lang = 'en', $appid = '')
421
    {
422 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...
423
            throw new \InvalidArgumentException('$type must be either "tick", "hour" or "day"');
424
        }
425
426
        $url = $this->buildUrl($query, $units, $lang, $appid, 'json', $this->weatherHistoryUrl);
427
        $url .= "&type=$type&start={$start->format('U')}";
428
        if ($endOrCount instanceof \DateTime) {
429
            $url .= "&end={$endOrCount->format('U')}";
430
        } elseif (is_numeric($endOrCount) && $endOrCount > 0) {
431
            $url .= "&cnt=$endOrCount";
432
        } else {
433
            throw new \InvalidArgumentException('$endOrCount must be either a \DateTime or a positive integer.');
434
        }
435
436
        return $this->cacheOrFetchResult($url);
437
    }
438
439
    /**
440
     * Returns whether or not the last result was fetched from the cache.
441
     *
442
     * @return bool true if last result was fetched from cache, false otherwise.
443
     */
444
    public function wasCached()
445
    {
446
        return $this->wasCached;
447
    }
448
449
    /**
450
     * @deprecated Use {@link self::getRawWeatherData()} instead.
451
     */
452
    public function getRawData($query, $units = 'imperial', $lang = 'en', $appid = '', $mode = 'xml')
453
    {
454
        return $this->getRawWeatherData($query, $units, $lang, $appid, $mode);
455
    }
456
457
    /**
458
     * Fetches the result or delivers a cached version of the result.
459
     *
460
     * @param string $url
461
     *
462
     * @return string
463
     */
464
    private function cacheOrFetchResult($url)
465
    {
466
        if ($this->cache !== false) {
467
            /** @var AbstractCache $cache */
468
            $cache = $this->cache;
469
            $cache->setSeconds($this->seconds);
470
            if ($cache->isCached($url)) {
471
                $this->wasCached = true;
472
                return $cache->getCached($url);
473
            }
474
            $result = $this->fetcher->fetch($url);
475
            $cache->setCached($url, $result);
476
        } else {
477
            $result = $this->fetcher->fetch($url);
478
        }
479
        $this->wasCached = false;
480
481
        return $result;
482
    }
483
484
    /**
485
     * Build the url to fetch weather data from.
486
     *
487
     * @param        $query
488
     * @param        $units
489
     * @param        $lang
490
     * @param        $appid
491
     * @param        $mode
492
     * @param string $url   The url to prepend.
493
     *
494
     * @return bool|string The fetched url, false on failure.
495
     */
496
    private function buildUrl($query, $units, $lang, $appid, $mode, $url)
497
    {
498
        $queryUrl = $this->buildQueryUrlParameter($query);
499
500
        $url = $url."$queryUrl&units=$units&lang=$lang&mode=$mode&APPID=";
501
        $url .= empty($appid) ? $this->apiKey : $appid;
502
503
        return $url;
504
    }
505
506
    /**
507
     * Builds the query string for the url.
508
     *
509
     * @param mixed $query
510
     *
511
     * @return string The built query string for the url.
512
     *
513
     * @throws \InvalidArgumentException If the query parameter is invalid.
514
     */
515
    private function buildQueryUrlParameter($query)
516
    {
517
        switch ($query) {
518
            case is_array($query) && isset($query['lat']) && isset($query['lon']) && is_numeric($query['lat']) && is_numeric($query['lon']):
519
                return "lat={$query['lat']}&lon={$query['lon']}";
520
            case is_array($query) && is_numeric($query[0]):
521
                return 'id='.implode(',', $query);
522
            case is_numeric($query):
523
                return "id=$query";
524
            case is_string($query):
525
                return 'q='.urlencode($query);
526
            default:
527
                throw new \InvalidArgumentException('Error: $query has the wrong format. See the documentation of OpenWeatherMap::getWeather() to read about valid formats.');
528
        }
529
    }
530
531
    /**
532
     * @param string $answer The content returned by OpenWeatherMap.
533
     *
534
     * @return \SimpleXMLElement
535
     * @throws OWMException If the content isn't valid XML.
536
     */
537
    private function parseXML($answer)
538
    {
539
        // Disable default error handling of SimpleXML (Do not throw E_WARNINGs).
540
        libxml_use_internal_errors(true);
541
        libxml_clear_errors();
542
        try {
543
            return new \SimpleXMLElement($answer);
544
        } catch (\Exception $e) {
545
            // Invalid xml format. This happens in case OpenWeatherMap returns an error.
546
            // OpenWeatherMap always uses json for errors, even if one specifies xml as format.
547
            $error = json_decode($answer, true);
548
            if (isset($error['message'])) {
549
                throw new OWMException($error['message'], $error['cod']);
550
            } else {
551
                throw new OWMException('Unknown fatal error: OpenWeatherMap returned the following json object: ' . $answer);
552
            }
553
        }
554
    }
555
556
    /**
557
     * @param string $answer The content returned by OpenWeatherMap.
558
     *
559
     * @return \stdClass
560
     * @throws OWMException If the content isn't valid JSON.
561
     */
562
    private function parseJson($answer)
563
    {
564
        $json = json_decode($answer);
565
        if (json_last_error() !== JSON_ERROR_NONE) {
566
            throw new OWMException('OpenWeatherMap returned an invalid json object: ' . json_last_error_msg());
567
        }
568
569
        return $json;
570
    }
571
572
}
573