attogram/weatherbit-api-wrapper

View on GitHub
src/Weatherbit.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php
/**
 * Weatherbit API Wrapper
 *
 * @see https://github.com/attogram/weatherbit-api-wrapper
 */
declare(strict_types=1);

namespace Attogram\Weatherbit;

use function curl_close;
use function curl_exec;
use function curl_getinfo;
use function curl_init;
use function curl_setopt;
use function is_array;
use function is_string;
use function json_decode;
use function strlen;

class Weatherbit
{
    const VERSION = '2.1.1';

    /**
     * @var string - user agent for API requests
     */
    const USER_AGENT = 'WeatherbitApiWrapper/' . self::VERSION;

    /**
     * @var string - Weatherbit api endpoint prefix
     * @see https://www.weatherbit.io/api
     * @see https://www.weatherbit.io/api/swaggerui/weather-api-v2
     */
    const PREFIX_API = 'https://api.weatherbit.io/v2.0';

    /**
     * @var string - api postfix for 16 day / daily Forecast
     *             - Returns a daily forecast, where each point represents one day (24hr) period.
     *             - Every point has a datetime string in the format "YYYY-MM-DD".
     *             - One day begins at 00:00 UTC, and ends at 23:59 UTC.
     * @see https://www.weatherbit.io/api/weather-forecast-16-day
     * @see https://www.weatherbit.io/api/swaggerui/weather-api-v2#/1632day324732daily32Forecast
     */
    const POSTFIX_FORECAST_DAILY = '/forecast/daily';

    /**
     * @var string - api postfix for current weather
     *             - Returns a Current Observation
     *             - Given a city in the format of "City" or "City, State".
     *             - The state, and country parameters can be provided to make the search more accurate.
     * @see https://www.weatherbit.io/api/weather-current
     * @see https://www.weatherbit.io/api/swaggerui/weather-api-v2#/Current32Weather32Data
     */
    const POSTFIX_CURRENT = '/current';

    /**
     * @var string - api postfix for current usage
     *             - Returns the current Weather API usage summary for your API key subscription.
     * @see https://www.weatherbit.io/api/subscription-usage
     */
    const POSTFIX_USAGE = '/subscription/usage';

    /**
     * @var string - Weatherbit API access key
     */
    private $key = '';

    /**
     * @var string - Language for API response - default 'en' for English
     * @see https://www.weatherbit.io/api/requests
     */
    private $language = '';

    /**
     * @var string - Units for API Response
     *               M = [DEFAULT] Metric (Celcius, m/s, mm)
     *               S = Scientific (Kelvin, m/s, mm)
     *               I = Imperial (Fahrenheit, mph, in)
     * @see https://www.weatherbit.io/api/requests
     */
    private $units = '';

    /**
     * @var string - array of location values for API call
     */
    private $location = [];

    /**
     * @var string - URL for API call
     */
    private $url = '';

    /**
     * Set Weatherbit API access key
     *
     * @param string $key
     * @throws WeatherbitException
     * @return void
     */
    public function setKey(string $key)
    {
        if (empty($key)) {
            throw new WeatherbitException('Missing API Key');
        }
        $this->key = $key;
    }

    /**
     * Set Language
     * @see https://www.weatherbit.io/api/requests
     *
     * @param string $languageCode - 2 letter language code
     * @throws WeatherbitException
     * @return void
     */
    public function setLanguage(string $languageCode)
    {
        if (empty($languageCode) || strlen($languageCode) != 2) {
            throw new WeatherbitException('Invalid Language Code');
        }
        $this->language = $languageCode;
    }

    /**
     * Set Units
     * @see https://www.weatherbit.io/api/requests
     *
     * @param string $unitsCode - 1 letter units code
     * @throws WeatherbitException
     * @return void
     */
    public function setUnits(string $unitsCode)
    {
        if (empty($unitsCode) || !in_array($unitsCode, ['M', 'S', 'I'])) {
            throw new WeatherbitException('Invalid Units value.  Please use: M, S, or I');
        }
        $this->units = $unitsCode;
    }

    /**
     * Set Location by Latitude/Longitude
     *
     * @param string $latitude
     * @param string $longitude
     * @throws WeatherbitException
     * @return void
     */
    public function setLocationByLatitudeLongitude(string $latitude, string $longitude)
    {
        if (empty($latitude) || empty($longitude)) {
            throw new WeatherbitException('Missing latitude and/or longitude');
        }

        $this->location = [
            'lat' => $latitude,
            'lon' => $longitude,
        ];
    }

    /**
     * Set Location by City Name
     *
     * @param string $city
     * @param string $country (optional) 2 letter country code
     * @throws WeatherbitException
     * @return void
     */
    public function setLocationByCity(string $city, string $country = '')
    {
        if (empty($city)) {
            throw new WeatherbitException('Invalid City');
        }

        if (!empty($country) && strlen($country) != 2) {
            throw new WeatherbitException('Invalid Country Code');
        }

        $this->location['city'] = $city;
        if (!empty($country)) {
            $this->location['country'] = $country;
        }
    }

    /**
     * Set Location by City ID
     *
     * @param string $cityId
     */
    public function setLocationByCityId(string $cityId)
    {
        $this->location = [
            'city_id' => $cityId,
        ];
    }

    /**
     * Set Location to a List of Cities IDs
     *
     * @param array $cityIds
     */
    public function setLocationByCityIds(array $cityIds)
    {
        // Comma separated list of City IDs
        $this->location = [
            'cities' => implode(',', $cityIds),
        ];
    }

    /**
     * Set Location by Postal Code
     *
     * @param string $postalCode
     */
    public function setLocationByPostalCode(string $postalCode)
    {
        $this->location = [
            'postal_code' => $postalCode,
        ];
    }

    /**
     * Set Location by IP Address
     *
     * @param string $ipAddress - Ip Address, or 'auto'
     */
    public function setLocationByIp(string $ipAddress = 'auto')
    {
        $this->location = [
            'ip' => $ipAddress,
        ];
    }

    /**
     * Set Location by Weather Station
     *
     * @param string $weatherStations
     */
    public function setLocationByStation(string $weatherStation)
    {
        $this->location = [
            'station' => $weatherStation,
        ];
    }

    /**
     * Set Location to List of Weather Stations
     *
     * @param array $weatherStations
     */
    public function setLocationByStations(array $weatherStations)
    {
        $this->location = [
            'stations' => implode(',', $weatherStations),
        ];
    }

    /**
     * Get Daily Weather Forecast for 1-16 days in future
     *
     * @param int $days - Number of days to forecast (optional, default 10)
     * @throws WeatherbitException
     * @return array - array of weather forecast data
     */
    public function getDailyForecast($days = 10): array
    {
        if ($days < 1 || $days > 16) {
            throw new WeatherbitException('Forecast Days must between 1 and 16');
        }

        $this->setUrl(
            self::POSTFIX_FORECAST_DAILY,
            ['days' => $days]
        );

        return $this->get();
    }

    /**
     * Get Current Weather
     *
     * @return array - array of current weather data
     */
    public function getCurrent(): array
    {
        $this->setUrl(self::POSTFIX_CURRENT);

        return $this->get();
    }

    /**
     * Get current API usage stats
     *
     * @return array - array of API subscription usage
     */
    public function getUsage(): array
    {
        $this->location = []; // erase any location settings, not needed
        $this->setUrl(self::POSTFIX_USAGE);

        return $this->get();
    }

    /**
     * Get current API Call URL
     *
     * @return string - The Current URL
     */
    public function getUrl(): string
    {
        return $this->url;
    }

    /**
     * Set the URL string for the API Call
     *
     * @param string $prefix - URL Prefix
     * @param array $additional - array of name/value pairs for additional URL values
     * @throws WeatherbitException
     * @return void
     */
    private function setUrl($prefix, $additional = [])
    {
        if (empty($this->key)) {
            throw new WeatherbitException('Missing API Key');
        }

        $this->url = self::PREFIX_API . $prefix . '?key=' . urlencode($this->key);

        if (!empty($this->language)) {
            $this->url .= '&lang=' . urlencode($this->language);
        }
        if (!empty($this->units)) {
            $this->url .= '&units=' . urlencode($this->units);
        }
        $this->setUrlLocation();
        $this->setUrlAdditional($additional);
    }

    /**
     * Set location values to url
     *
     * @return void
     */
    private function setUrlLocation()
    {
        if (empty($this->location)) {
            return;
        }
        foreach ($this->location as $name => $value) {
            if (!empty($value)) {
                $this->url .= '&' . $name . '=' . urlencode((string) $value);
            }
        }
    }

    /**
     * Set additional values to url
     *
     * @param array $additional
     * @return void
     */
    private function setUrlAdditional(array $additional = [])
    {
        if (empty($additional)) {
            return;
        }
        foreach ($additional as $name => $value) {
            if (!empty($value)) {
                $this->url .= '&' . $name . '=' . urlencode((string) $value);
            }
        }
    }

    /**
     * Get Weather Data from the API
     *
     * @throws WeatherbitException
     * @return array - array of weather data
     */
    private function get()
    {
        if (empty($this->url)) {
            throw new WeatherbitException('Missing URL for API Call');
        }

        $curl = curl_init($this->url);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_USERAGENT, self::USER_AGENT);

        $jsonData = curl_exec($curl);
        $status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
        curl_close($curl);

        if ($status != '200') {
            throw new WeatherbitException(
                'API Failure - status code: ' . $status . ' - data: ' . print_r($jsonData, true)
            );
        }

        if (empty($jsonData)) {
            throw new WeatherbitException('No data from API');
        }

        $data = @json_decode($jsonData, true); // @silently ignore decode errors
        if (!is_array($data)) {
            throw new WeatherbitException('Unable to decode response from API');
        }

        return $data;
    }
}