diff --git a/.ddev/config.yaml b/.ddev/config.yaml new file mode 100644 index 0000000..c399138 --- /dev/null +++ b/.ddev/config.yaml @@ -0,0 +1,282 @@ +name: openweathermap-php-api +type: php +docroot: "" +php_version: "8.1" +webserver_type: nginx-fpm +xdebug_enabled: false +additional_hostnames: [] +additional_fqdns: [] +database: + type: mariadb + version: "10.11" +omit_containers: [db] +use_dns_when_possible: true +composer_version: "2" +web_environment: [] +corepack_enable: false +disable_upload_dirs_warning: true + +# Key features of DDEV's config.yaml: + +# name: # Name of the project, automatically provides +# http://projectname.ddev.site and https://projectname.ddev.site + +# type: # backdrop, craftcms, django4, drupal, drupal6, drupal7, laravel, magento, magento2, php, python, shopware6, silverstripe, typo3, wordpress +# See https://ddev.readthedocs.io/en/stable/users/quickstart/ for more +# information on the different project types +# "drupal" covers recent Drupal 8+ + +# docroot: # Relative path to the directory containing index.php. + +# php_version: "8.2" # PHP version to use, "5.6", "7.0", "7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3" + +# You can explicitly specify the webimage but this +# is not recommended, as the images are often closely tied to DDEV's' behavior, +# so this can break upgrades. + +# webimage: # nginx/php docker image. + +# database: +# type: # mysql, mariadb, postgres +# version: # database version, like "10.11" or "8.0" +# MariaDB versions can be 5.5-10.8 and 10.11, MySQL versions can be 5.5-8.0 +# PostgreSQL versions can be 9-16. + +# router_http_port: # Port to be used for http (defaults to global configuration, usually 80) +# router_https_port: # Port for https (defaults to global configuration, usually 443) + +# xdebug_enabled: false # Set to true to enable Xdebug and "ddev start" or "ddev restart" +# Note that for most people the commands +# "ddev xdebug" to enable Xdebug and "ddev xdebug off" to disable it work better, +# as leaving Xdebug enabled all the time is a big performance hit. + +# xhprof_enabled: false # Set to true to enable Xhprof and "ddev start" or "ddev restart" +# Note that for most people the commands +# "ddev xhprof" to enable Xhprof and "ddev xhprof off" to disable it work better, +# as leaving Xhprof enabled all the time is a big performance hit. + +# webserver_type: nginx-fpm, apache-fpm, or nginx-gunicorn + +# timezone: Europe/Berlin +# This is the timezone used in the containers and by PHP; +# it can be set to any valid timezone, +# see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones +# For example Europe/Dublin or MST7MDT + +# composer_root: +# Relative path to the Composer root directory from the project root. This is +# the directory which contains the composer.json and where all Composer related +# commands are executed. + +# composer_version: "2" +# You can set it to "" or "2" (default) for Composer v2 or "1" for Composer v1 +# to use the latest major version available at the time your container is built. +# It is also possible to use each other Composer version channel. This includes: +# - 2.2 (latest Composer LTS version) +# - stable +# - preview +# - snapshot +# Alternatively, an explicit Composer version may be specified, for example "2.2.18". +# To reinstall Composer after the image was built, run "ddev debug refresh". + +# nodejs_version: "20" +# change from the default system Node.js version to any other version. +# Numeric version numbers can be complete (i.e. 18.15.0) or +# incomplete (18, 17.2, 16). 'lts' and 'latest' can be used as well along with +# other named releases. +# see https://www.npmjs.com/package/n#specifying-nodejs-versions +# Note that you can continue using 'ddev nvm' or nvm inside the web container +# to change the project's installed node version if you need to. + +# corepack_enable: false +# Change to 'true' to 'corepack enable' and gain access to latest versions of yarn/pnpm + +# additional_hostnames: +# - somename +# - someothername +# would provide http and https URLs for "somename.ddev.site" +# and "someothername.ddev.site". + +# additional_fqdns: +# - example.com +# - sub1.example.com +# would provide http and https URLs for "example.com" and "sub1.example.com" +# Please take care with this because it can cause great confusion. + +# upload_dirs: "custom/upload/dir" +# +# upload_dirs: +# - custom/upload/dir +# - ../private +# +# would set the destination paths for ddev import-files to /custom/upload/dir +# When Mutagen is enabled this path is bind-mounted so that all the files +# in the upload_dirs don't have to be synced into Mutagen. + +# disable_upload_dirs_warning: false +# If true, turns off the normal warning that says +# "You have Mutagen enabled and your 'php' project type doesn't have upload_dirs set" + +# ddev_version_constraint: "" +# Example: +# ddev_version_constraint: ">= 1.22.4" +# This will enforce that the running ddev version is within this constraint. +# See https://github.com/Masterminds/semver#checking-version-constraints for +# supported constraint formats + +# working_dir: +# web: /var/www/html +# db: /home +# would set the default working directory for the web and db services. +# These values specify the destination directory for ddev ssh and the +# directory in which commands passed into ddev exec are run. + +# omit_containers: [db, ddev-ssh-agent] +# Currently only these containers are supported. Some containers can also be +# omitted globally in the ~/.ddev/global_config.yaml. Note that if you omit +# the "db" container, several standard features of DDEV that access the +# database container will be unusable. In the global configuration it is also +# possible to omit ddev-router, but not here. + +# performance_mode: "global" +# DDEV offers performance optimization strategies to improve the filesystem +# performance depending on your host system. Should be configured globally. +# +# If set, will override the global config. Possible values are: +# - "global": uses the value from the global config. +# - "none": disables performance optimization for this project. +# - "mutagen": enables Mutagen for this project. +# - "nfs": enables NFS for this project. +# +# See https://ddev.readthedocs.io/en/stable/users/install/performance/#nfs +# See https://ddev.readthedocs.io/en/stable/users/install/performance/#mutagen + +# fail_on_hook_fail: False +# Decide whether 'ddev start' should be interrupted by a failing hook + +# host_https_port: "59002" +# The host port binding for https can be explicitly specified. It is +# dynamic unless otherwise specified. +# This is not used by most people, most people use the *router* instead +# of the localhost port. + +# host_webserver_port: "59001" +# The host port binding for the ddev-webserver can be explicitly specified. It is +# dynamic unless otherwise specified. +# This is not used by most people, most people use the *router* instead +# of the localhost port. + +# host_db_port: "59002" +# The host port binding for the ddev-dbserver can be explicitly specified. It is dynamic +# unless explicitly specified. + +# mailpit_http_port: "8025" +# mailpit_https_port: "8026" +# The Mailpit ports can be changed from the default 8025 and 8026 + +# host_mailpit_port: "8025" +# The mailpit port is not normally bound on the host at all, instead being routed +# through ddev-router, but it can be bound directly to localhost if specified here. + +# webimage_extra_packages: [php7.4-tidy, php-bcmath] +# Extra Debian packages that are needed in the webimage can be added here + +# dbimage_extra_packages: [telnet,netcat] +# Extra Debian packages that are needed in the dbimage can be added here + +# use_dns_when_possible: true +# If the host has internet access and the domain configured can +# successfully be looked up, DNS will be used for hostname resolution +# instead of editing /etc/hosts +# Defaults to true + +# project_tld: ddev.site +# The top-level domain used for project URLs +# The default "ddev.site" allows DNS lookup via a wildcard +# If you prefer you can change this to "ddev.local" to preserve +# pre-v1.9 behavior. + +# ngrok_args: --basic-auth username:pass1234 +# Provide extra flags to the "ngrok http" command, see +# https://ngrok.com/docs/ngrok-agent/config or run "ngrok http -h" + +# disable_settings_management: false +# If true, DDEV will not create CMS-specific settings files like +# Drupal's settings.php/settings.ddev.php or TYPO3's AdditionalConfiguration.php +# In this case the user must provide all such settings. + +# You can inject environment variables into the web container with: +# web_environment: +# - SOMEENV=somevalue +# - SOMEOTHERENV=someothervalue + +# no_project_mount: false +# (Experimental) If true, DDEV will not mount the project into the web container; +# the user is responsible for mounting it manually or via a script. +# This is to enable experimentation with alternate file mounting strategies. +# For advanced users only! + +# bind_all_interfaces: false +# If true, host ports will be bound on all network interfaces, +# not the localhost interface only. This means that ports +# will be available on the local network if the host firewall +# allows it. + +# default_container_timeout: 120 +# The default time that DDEV waits for all containers to become ready can be increased from +# the default 120. This helps in importing huge databases, for example. + +#web_extra_exposed_ports: +#- name: nodejs +# container_port: 3000 +# http_port: 2999 +# https_port: 3000 +#- name: something +# container_port: 4000 +# https_port: 4000 +# http_port: 3999 +# Allows a set of extra ports to be exposed via ddev-router +# Fill in all three fields even if you don’t intend to use the https_port! +# If you don’t add https_port, then it defaults to 0 and ddev-router will fail to start. +# +# The port behavior on the ddev-webserver must be arranged separately, for example +# using web_extra_daemons. +# For example, with a web app on port 3000 inside the container, this config would +# expose that web app on https://.ddev.site:9999 and http://.ddev.site:9998 +# web_extra_exposed_ports: +# - name: myapp +# container_port: 3000 +# http_port: 9998 +# https_port: 9999 + +#web_extra_daemons: +#- name: "http-1" +# command: "/var/www/html/node_modules/.bin/http-server -p 3000" +# directory: /var/www/html +#- name: "http-2" +# command: "/var/www/html/node_modules/.bin/http-server /var/www/html/sub -p 3000" +# directory: /var/www/html + +# override_config: false +# By default, config.*.yaml files are *merged* into the configuration +# But this means that some things can't be overridden +# For example, if you have 'use_dns_when_possible: true'' you can't override it with a merge +# and you can't erase existing hooks or all environment variables. +# However, with "override_config: true" in a particular config.*.yaml file, +# 'use_dns_when_possible: false' can override the existing values, and +# hooks: +# post-start: [] +# or +# web_environment: [] +# or +# additional_hostnames: [] +# can have their intended affect. 'override_config' affects only behavior of the +# config.*.yaml file it exists in. + +# Many DDEV commands can be extended to run tasks before or after the +# DDEV command is executed, for example "post-start", "post-import-db", +# "pre-composer", "post-composer" +# See https://ddev.readthedocs.io/en/stable/users/extend/custom-commands/ for more +# information on the commands that can be extended and the tasks you can define +# for them. Example: +#hooks: diff --git a/README.md b/README.md index e69e930..d54fbd1 100644 --- a/README.md +++ b/README.md @@ -19,37 +19,26 @@ You must sign up for an [OpenWeatherMap account](https://openweathermap.org/appi ## Installation -You can install the library via [Composer](https://getcomposer.org/): +Install the library via [Composer](https://getcomposer.org/): ```bash composer require programmatordev/openweathermap-php-api ``` -To use the library, use Composer's [autoload](https://getcomposer.org/doc/01-basic-usage.md#autoloading): - -```php -require_once 'vendor/autoload.php'; -``` - ## Basic Usage Simple usage looks like: ```php -use ProgrammatorDev\OpenWeatherMap\Config; use ProgrammatorDev\OpenWeatherMap\OpenWeatherMap; -// Initialize -$openWeatherMap = new OpenWeatherMap( - new Config([ - 'applicationKey' => 'yourappkey' - ]) -); - -// Get current weather by coordinate (latitude, longitude) -$currentWeather = $openWeatherMap->weather()->getCurrent(50, 50); -// Show current temperature -echo $currentWeather->getTemperature(); +// initialize +$api = new OpenWeatherMap('yourapikey'); + +// get current weather by coordinate (latitude, longitude) +$weather = $api->weather()->getCurrent(50, 50); +// show current temperature +echo $weather->getTemperature(); ``` ## Documentation @@ -58,7 +47,7 @@ echo $currentWeather->getTemperature(); - [Configuration](docs/02-configuration.md) - [Supported APIs](docs/03-supported-apis.md) - [Error Handling](docs/04-error-handling.md) -- [Objects](docs/05-objects.md) +- [Entities](docs/05-entities.md) ## Contributing diff --git a/composer.json b/composer.json index c654533..fe8dff3 100644 --- a/composer.json +++ b/composer.json @@ -2,36 +2,29 @@ "name": "programmatordev/openweathermap-php-api", "description": "OpenWeatherMap PHP library that provides convenient access to the OpenWeatherMap API", "type": "library", - "keywords": ["OpenWeatherMap", "API", "PHP", "PHP8", "SDK", "PSR-18", "PSR-17", "PSR-6", "PSR-3"], + "keywords": ["openweathermap", "api", "php", "php8", "sdk", "psr-18", "psr-17", "psr-6", "psr-3"], "license": "MIT", "authors": [ { "name": "André Pimpão", "email": "a.pimpao@programmator.dev", - "homepage": "https://programmator.dev/" + "homepage": "https://programmator.dev" } ], "require": { "php": ">=8.1", - "php-http/cache-plugin": "^1.8", - "php-http/client-common": "^2.7", - "php-http/discovery": "^1.18", - "php-http/logger-plugin": "^1.3", - "programmatordev/yet-another-php-validator": "^0.5", - "psr/cache": "^2.0 || ^3.0", - "psr/http-client": "^1.0", - "psr/http-factory": "^1.0", - "psr/log": "^2.0 || ^3.0", - "symfony/options-resolver": "^6.3" + "myclabs/deep-copy": "^1.11", + "programmatordev/php-api-sdk": "^0.2.0", + "programmatordev/yet-another-php-validator": "^1.1" }, "require-dev": { - "monolog/monolog": "^3.4", + "monolog/monolog": "^3.6", "nyholm/psr7": "^1.8", "php-http/mock-client": "^1.6", - "phpunit/phpunit": "^10.0", - "symfony/cache": "^6.3", - "symfony/http-client": "^6.3", - "symfony/var-dumper": "^6.3" + "phpunit/phpunit": "^10.5", + "symfony/cache": "^6.4", + "symfony/http-client": "^6.4", + "symfony/var-dumper": "^6.4" }, "provide": { "psr/http-client-implementation": "1.0", diff --git a/docs/01-usage.md b/docs/01-usage.md index b2ab51d..513bacb 100644 --- a/docs/01-usage.md +++ b/docs/01-usage.md @@ -16,35 +16,24 @@ You must sign up for an [OpenWeatherMap account](https://openweathermap.org/appi ## Installation -You can install the library via [Composer](https://getcomposer.org/): +Install the library via [Composer](https://getcomposer.org/): ```bash composer require programmatordev/openweathermap-php-api ``` -To use the library, use Composer's [autoload](https://getcomposer.org/doc/01-basic-usage.md#autoloading): - -```php -require_once 'vendor/autoload.php'; -``` - ## Basic Usage Simple usage looks like: ```php -use ProgrammatorDev\OpenWeatherMap\Config; use ProgrammatorDev\OpenWeatherMap\OpenWeatherMap; -// Initialize -$openWeatherMap = new OpenWeatherMap( - new Config([ - 'applicationKey' => 'yourappkey' - ]) -); - -// Get current weather by coordinate (latitude, longitude) -$currentWeather = $openWeatherMap->weather()->getCurrent(50, 50); -// Show current temperature -echo $currentWeather->getTemperature(); +// initialize +$api = new OpenWeatherMap('yourapikey'); + +// get current weather by coordinate (latitude, longitude) +$weather = $api->weather()->getCurrent(50, 50); +// show current temperature +echo $weather->getTemperature(); ``` \ No newline at end of file diff --git a/docs/02-configuration.md b/docs/02-configuration.md index 3bde134..7416324 100644 --- a/docs/02-configuration.md +++ b/docs/02-configuration.md @@ -2,62 +2,49 @@ - [Default Configuration](#default-configuration) - [Options](#options) - - [applicationKey](#applicationkey) - [unitSystem](#unitsystem) - [language](#language) - - [httpClientBuilder](#httpclientbuilder) - - [cache](#cache) - - [logger](#logger) -- [Config Object](#config-object) +- [Methods](#methods) + - [setClientBuilder](#setclientbuilder) + - [setCacheBuilder](#setcachebuilder) + - [setLoggerBuilder](#setloggerbuilder) ## Default Configuration -Only the `applicationKey` option is required: +```php +OpenWeatherMap(string $apiKey, array $options => []); +``` ```php -use ProgrammatorDev\OpenWeatherMap\Config; -use ProgrammatorDev\OpenWeatherMap\HttpClient\HttpClientBuilder; use ProgrammatorDev\OpenWeatherMap\OpenWeatherMap; -$openWeatherMap = new OpenWeatherMap( - new Config([ - 'applicationKey' => 'yourappkey', // required - 'unitSystem' => 'metric', - 'language' => 'en', - 'httpClientBuilder' => new HttpClientBuilder(), - 'cache' => null, - 'logger' => null - ]) -); +$api = new OpenWeatherMap('yourapikey', [ + 'unitSystem' => 'metric', + 'language' => 'en' +]); ``` ## Options -### `applicationKey` - -Required for all requests. Check the [API Key](01-usage.md#api-key) section for more information. - ### `unitSystem` Unit system used when retrieving data. Affects temperature and speed values. -Available options are `metric`, `imperial` and `standard`. -Pre-defined [constants](../src/UnitSystem/UnitSystem.php) are also available. +Available options: +- `metric` +- `imperial` +- `standard` Example: ```php -use ProgrammatorDev\OpenWeatherMap\Config; use ProgrammatorDev\OpenWeatherMap\UnitSystem\UnitSystem; use ProgrammatorDev\OpenWeatherMap\OpenWeatherMap; -$openWeatherMap = new OpenWeatherMap( - new Config([ - 'applicationKey' => 'yourappkey', - 'unitSystem' => UnitSystem::IMPERIAL - ]) -); +$api = new OpenWeatherMap('yourapikey', [ + 'unitSystem' => UnitSystem::IMPERIAL +]); ``` ### `language` @@ -66,198 +53,108 @@ Language used when retrieving data. It seems to only affect weather conditions descriptions. List of all available languages can be found [here](https://openweathermap.org/api/one-call-3#multi). -Pre-defined [constants](../src/Language/Language.php) are also available. Example: ```php -use ProgrammatorDev\OpenWeatherMap\Config; use ProgrammatorDev\OpenWeatherMap\Language\Language; use ProgrammatorDev\OpenWeatherMap\OpenWeatherMap; -$openWeatherMap = new OpenWeatherMap( - new Config([ - 'applicationKey' => 'yourappkey', - 'language' => Language::PORTUGUESE - ]) -); +$api = new OpenWeatherMap('yourapikey', [ + 'language' => Language::PORTUGUESE +]); ``` -### `httpClientBuilder` - -Configure a PSR-18 HTTP client and PSR-17 HTTP factory adapters. +## Methods -By default, and for convenience, this library makes use of the [HTTPlug's Discovery](https://github.com/php-http/discovery) library. -This means that it will automatically find and install a well-known PSR-18 and PSR-17 implementation for you (if one was not found on your project): -- [List of PSR-18 compatible implementations](https://packagist.org/providers/psr/http-client-implementation) -- [List of PSR-17 compatible implementations](https://packagist.org/providers/psr/http-factory-implementation) +> [!IMPORTANT] +> The [PHP API SDK](https://github.com/programmatordev/php-api-sdk) library was used to create the OpenWeatherMap PHP API. +> To get to know about all the available methods, make sure to check the documentation [here](https://github.com/programmatordev/php-api-sdk?tab=readme-ov-file#documentation). -If you want to manually provide one, it should look like this: +The following sections have examples of some of the most important methods, +particularly related with the configuration of the client, cache and logger. -```php -use ProgrammatorDev\OpenWeatherMap\Config; -use ProgrammatorDev\OpenWeatherMap\HttpClient\HttpClientBuilder; -use ProgrammatorDev\OpenWeatherMap\OpenWeatherMap; +### `setClientBuilder` -$httpClient = new Symfony\Component\HttpClient\Psr18Client(); -$httpFactory = new Nyholm\Psr7\Factory\Psr17Factory(); - -// HttpClientBuilder( -// ?ClientInterface $client = null, -// ?RequestFactoryInterface $requestFactory = null, -// ?StreamFactoryInterface $streamFactory = null -// ); -$httpClientBuilder = new HttpClientBuilder($httpClient, $httpFactory, $httpFactory); - -$openWeatherMap = new OpenWeatherMap( - new Config([ - 'applicationKey' => 'yourappkey', - 'httpClientBuilder' => $httpClientBuilder - ]) -); -``` +By default, this library makes use of the [HTTPlug's Discovery](https://github.com/php-http/discovery) library. +This means that it will automatically find and install a well-known PSR-18 client and PSR-17 factory implementation for you +(if they were not found on your project): +- [PSR-18 compatible implementations](https://packagist.org/providers/psr/http-client-implementation) +- [PSR-17 compatible implementations](https://packagist.org/providers/psr/http-factory-implementation) -> **Note** -> All `HttpClientBuilder` parameters are optional. -> If you only pass an HTTP client, an HTTP factory will still be discovered for you. - -#### Plugin System - -[HTTPlug's plugin system](https://docs.php-http.org/en/latest/plugins/index.html) is also implemented to give you full control of what happens during the request/response workflow. - -For example, to attempt to re-send a request in case of failure (service temporarily down because of unreliable connections/servers, etc.), -the [RetryPlugin](https://docs.php-http.org/en/latest/plugins/retry.html) can be added: +If you don't want to rely on the discovery of implementations, you can set the ones you want: ```php -use ProgrammatorDev\OpenWeatherMap\Config; -use ProgrammatorDev\OpenWeatherMap\HttpClient\HttpClientBuilder; +use Nyholm\Psr7\Factory\Psr17Factory; use ProgrammatorDev\OpenWeatherMap\OpenWeatherMap; +use Symfony\Component\HttpClient\Psr18Client; -$httpClientBuilder = new HttpClientBuilder(); -$httpClientBuilder->addPlugin( - new \Http\Client\Common\Plugin\RetryPlugin([ - 'retries' => 3 - ]) -); +$api = new OpenWeatherMap('yourapikey'); + +$client = new Psr18Client(); +$requestFactory = $streamFactory = new Psr17Factory(); -$openWeatherMap = new OpenWeatherMap( - new Config([ - 'applicationKey' => 'yourappkey', - 'httpClientBuilder' => $httpClientBuilder - ]) +$api->setClientBuilder( + new ClientBuilder( + client: $client, + requestFactory: $requestFactory, + streamFactory: $streamFactory + ) ); ``` -You can check their [plugin list](https://docs.php-http.org/en/latest/plugins/index.html) or [create your own](https://docs.php-http.org/en/latest/plugins/build-your-own.html). - -> **Note** -> This library already uses HTTPlug's `CachePlugin` and `LoggerPlugin`. -> Re-adding those may lead to an unexpected behaviour. +Check the full documentation [here](https://github.com/programmatordev/php-api-sdk?tab=readme-ov-file#http-client-psr-18-and-http-factories-psr-17). -### `cache` +### `setCacheBuilder` -Configure a PSR-6 cache adapter. +This library allows configuring the cache layer of the client for making API requests. +It uses a standard PSR-6 implementation and provides methods to fine-tune how HTTP caching behaves: +- [PSR-6 compatible implementations](https://packagist.org/providers/psr/cache-implementation) -By default, no responses are cached. -To enable cache, you must provide a PSR-6 implementation: -- [List of PSR-6 compatible implementations](https://packagist.org/providers/psr/cache-implementation) - -In the example below, a filesystem-based cache is used: +Example: ```php -use ProgrammatorDev\OpenWeatherMap\Config; use ProgrammatorDev\OpenWeatherMap\OpenWeatherMap; +use Symfony\Component\Cache\Adapter\FilesystemAdapter; -$cache = new \Symfony\Component\Cache\Adapter\FilesystemAdapter(); - -$openWeatherMap = new OpenWeatherMap( - new Config([ - 'applicationKey' => 'yourappkey', - 'cache' => $cache - ]) -); -``` - -#### Cache TTL - -By default, all responses are cached for `10 minutes`, with the exception to `Geocoding` requests -where responses are cached for `30 days` (due to the low update frequency, since location data doesn't change that often). +$api = new OpenWeatherMap('yourapikey'); -It is possible to change the cache duration per request: +$pool = new FilesystemAdapter(); -```php -// Response will be cached for 1 hour -$currentWeather = $openWeatherMap->weather() - ->withCacheTtl(3600) - ->getCurrent(50, 50); +// set a file-based cache adapter with a 1-hour default cache lifetime +$api->setCacheBuilder( + new CacheBuilder( + pool: $pool, + ttl: 3600 + ) +); ``` -### `logger` +Check the full documentation [here](https://github.com/programmatordev/php-api-sdk?tab=readme-ov-file#cache-psr-6). -Configure a PSR-3 logger adapter. +### `setLoggerBuilder` -By default, no logs are saved. To enable logs, you must provide a PSR-3 implementation: -- [List of PSR-3 compatible implementations](https://packagist.org/providers/psr/log-implementation) +This library allows configuring a logger to save data for making API requests. +It uses a standard PSR-3 implementation and provides methods to fine-tune how logging behaves: +- [PSR-3 compatible implementations](https://packagist.org/providers/psr/log-implementation) -In the example below, a file-based logger is used... +Example: ```php -use ProgrammatorDev\OpenWeatherMap\Config; +use Monolog\Logger; +use Monolog\Handler\StreamHandler; use ProgrammatorDev\OpenWeatherMap\OpenWeatherMap; -$logger = new \Monolog\Logger('openweathermap'); -$logger->pushHandler( - new \Monolog\Handler\StreamHandler(__DIR__ . '/logs/openweathermap.log') -); - -$openWeatherMap = new OpenWeatherMap( - new Config([ - 'applicationKey' => 'yourappkey', - 'logger' => $logger - ]) -); -``` - -...and will provide logs similar to this: +$api = new OpenWeatherMap('yourapikey'); -``` -[2023-07-12T12:25:02.235721+00:00] openweathermap.INFO: Sending request: GET https://api.openweathermap.org/data/3.0/onecall?lat=50&lon=50&units=metric&lang=en&appid=[REDACTED] 1.1 {"request":{},"uid":"64ae9b9e394ff6.24668056"} -[2023-07-12T12:25:02.682278+00:00] openweathermap.INFO: Received response: 200 OK 1.1 {"milliseconds":447,"uid":"64ae9b9e394ff6.24668056"} -``` +$logger = new Logger('api'); +$logger->pushHandler(new StreamHandler('/logs/api.log')); -> **Note** -> If a `cache` implementation is configured, cache events will also be logged. - -## Config Object - -Configuration getters and setters for all options are available to access and change after initialization: - -```php -use ProgrammatorDev\OpenWeatherMap\Config; -use ProgrammatorDev\OpenWeatherMap\OpenWeatherMap; - -$openWeatherMap = new OpenWeatherMap( - new Config([ - 'applicationKey' => 'yourappkey' - ]) +$api->setLoggerBuilder( + new LoggerBuilder( + logger: $logger + ) ); - -// Using applicationKey as an example, -// but getters and setters are available for all options -$openWeatherMap->config()->getApplicationKey(); -$openWeatherMap->config()->setApplicationKey('newappkey'); ``` -Just take into account that any change will affect any subsequent request globally: - -```php -// Using default 'metric' unit system -$openWeatherMap->weather()->getCurrent(50, 50); - -// Set new unit system -$openWeatherMap->config()->setUnitSystem(UnitSystem::IMPERIAL); - -// Using 'imperial' unit system -$openWeatherMap->weather()->getCurrent(50, 50); -$openWeatherMap->weather()->getForecast(50, 50); -``` \ No newline at end of file +Check the full documentation [here](https://github.com/programmatordev/php-api-sdk?tab=readme-ov-file#logger-psr-3). \ No newline at end of file diff --git a/docs/03-supported-apis.md b/docs/03-supported-apis.md index 7fc2c82..de77325 100644 --- a/docs/03-supported-apis.md +++ b/docs/03-supported-apis.md @@ -3,8 +3,8 @@ - [APIs](#apis) - [One Call](#one-call) - [getWeather](#getweather) - - [getHistoryMoment](#gethistorymoment) - - [getHistoryAggregate](#gethistoryaggregate) + - [getWeatherByDate](#getweatherbydate) + - [getWeatherSummaryByDate](#getweathersummarybydate) - [Weather](#weather) - [getCurrent](#getcurrent) - [getForecast](#getforecast) @@ -14,8 +14,8 @@ - [getHistory](#gethistory) - [Geocoding](#geocoding) - [getByLocationName](#getbylocationname) - - [getByZipCode](#getbyzipcode) - [getByCoordinate](#getbycoordinate) + - [getByZipCode](#getbyzipcode) - [Common Methods](#common-methods) - [withUnitSystem](#withunitsystem) - [withLanguage](#withlanguage) @@ -28,49 +28,44 @@ #### `getWeather` ```php -getWeather(float $latitude, float $longitude): OneCall +getWeather(float $latitude, float $longitude): Weather ``` -Get current and forecast (minutely, hourly and daily) weather data. +Get access to current weather, minute forecast for 1 hour, hourly forecast for 48 hours, +daily forecast for 8 days and government weather alerts. -Returns a [`OneCall`](05-objects.md#onecall) object: +Returns a [`Weather`](05-entities.md#weather) object: ```php -$weather = $openWeatherMap->oneCall()->getWeather(50, 50); - -echo $weather->getCurrent()->getTemperature(); +$weather = $api->oneCall()->getWeather(50, 50); ``` -#### `getHistoryMoment` +#### `getWeatherByDate` ```php -getHistoryMoment(float $latitude, float $longitude, \DateTimeInterface $dateTime): WeatherLocation +getWeatherByDate(float $latitude, float $longitude, \DateTimeInterface $dateTime): WeatherMoment ``` -Get weather data from a single moment in the past. +Get access to weather data for any datetime. -Returns a [`WeatherLocation`](05-objects.md#weatherlocation) object: +Returns a [`WeatherMoment`](05-entities.md#weathermoment) object: ```php -$weather = $openWeatherMap->oneCall()->getHistoryMoment(50, 50, new \DateTime('2023-01-01 12:00:00')); - -echo $weather->getTemperature(); +$weather = $api->oneCall()->getWeatherByDate(50, 50, new \DateTime('2023-05-13 16:32:00')); ``` -#### `getHistoryAggregate` +#### `getWeatherSummaryByDate` ```php -getHistoryAggregate(float $latitude, float $longitude, \DateTimeInterface $date): WeatherAggregate +getWeatherSummaryByDate(float $latitude, float $longitude, \DateTimeInterface $date): WeatherSummary ``` -Get aggregated weather data from a single day in the past. +Get access to aggregated weather data for a particular date. -Returns a [`WeatherAggregate`](05-objects.md#weatheraggregate) object: +Returns a [`WeatherSummary`](05-entities.md#weathersummary) object: ```php -$weather = $openWeatherMap->oneCall()->getHistoryAggregate(50, 50, new \DateTime('1985-07-19')); - -echo $weather->getTemperature(); +$weatherSummary = $api->oneCall()->getWeatherSummaryByDate(50, 50, new \DateTime('1985-07-19')); ``` ### Weather @@ -78,38 +73,31 @@ echo $weather->getTemperature(); #### `getCurrent` ```php -getCurrent(float $latitude, float $longitude): WeatherLocation +getCurrent(float $latitude, float $longitude): Weather ``` -Get current weather data. +Get access to current weather data. -Returns a [`WeatherLocation`](05-objects.md#weatherlocation-1) object: +Returns a [`Weather`](05-entities.md#weather-2) object: ```php -$weather = $openWeatherMap->weather()->getCurrent(50, 50); - -echo $weather->getTemperature(); +$currentWeather = $api->weather()->getCurrent(50, 50); ``` #### `getForecast` ```php -getForecast(float $latitude, float $longitude, int $numResults = 40): WeatherLocationList +getForecast(float $latitude, float $longitude, int $numResults = 40): WeatherCollection ``` -Get weather forecast data per 3-hour steps for the next 5 days. +Get access to 5-day weather forecast data with 3-hour steps. -Returns a [`WeatherLocationList`](05-objects.md#weatherlocationlist) object: +Returns a [`WeatherCollection`](05-entities.md#weathercollection) object: ```php -// Since it returns 3-hour steps, +// Since it returns data in 3-hour steps, // passing 8 as the numResults means it will return results for the next 24 hours -$weatherForecast = $openWeatherMap->weather()->getForecast(50, 50, 8); - -foreach ($weatherForecast->getList() as $weather) { - echo $weather->getDateTime()->format('Y-m-d H:i:s'); - echo $weather->getTemperature(); -} +$weatherForecast = $api->weather()->getForecast(50, 50, 8); ``` ### Air Pollution @@ -117,60 +105,47 @@ foreach ($weatherForecast->getList() as $weather) { #### `getCurrent` ```php -getCurrent(float $latitude, float $longitude): AirPollutionLocation +getCurrent(float $latitude, float $longitude): AirPollution ``` -Get current air pollution data. +Get access to current air pollution data. -Returns a [`AirPollutionLocation`](05-objects.md#airpollutionlocation) object: +Returns a [`AirPollution`](05-entities.md#airpollution) object: ```php -$airPollution = $openWeatherMap->airPollution()->getCurrent(50, 50); - -echo $airPollution->getAirQuality()->getQualitativeName(); -echo $airPollution->getCarbonMonoxide(); +$currentAirPollution = $api->airPollution()->getCurrent(50, 50); ``` #### `getForecast` ```php -getForecast(float $latitude, float $longitude): AirPollutionLocationList +getForecast(float $latitude, float $longitude): AirPollutionCollection ``` -Get air pollution forecast data per 1-hour for the next 24 hours. +Get access to air pollution forecast data per hour. -Returns a [`AirPollutionLocationList`](05-objects.md#airpollutionlocationlist) object: +Returns a [`AirPollutionCollection`](05-entities.md#airpollutioncollection) object: ```php -$airPollutionForecast = $openWeatherMap->airPollution()->getForecast(50, 50); - -foreach ($airPollutionForecast->getList() as $airPollution) { - echo $airPollution->getDateTime()->format('Y-m-d H:i:s'); - echo $airPollution->getAirQuality()->getQualitativeName(); - echo $airPollution->getCarbonMonoxide(); -} +$airPollutionForecast = $api->airPollution()->getForecast(50, 50); ``` #### `getHistory` ```php -getHistory(float $latitude, float $longitude, \DateTimeInterface $startDate, \DateTimeInterface $endDate): AirPollutionLocationList +getHistory(float $latitude, float $longitude, \DateTimeInterface $startDate, \DateTimeInterface $endDate): AirPollutionCollection ``` -Get air pollution history data between two dates. +Get access to historical air pollution data per hour between two dates. -Returns a [`AirPollutionLocationList`](05-objects.md#airpollutionlocationlist) object: +Returns a [`AirPollutionCollection`](05-entities.md#airpollutioncollection) object: ```php -$startDate = new \DateTime('-7 days'); // 7 days ago -$endDate = new \DateTime('-6 days'); // 6 days ago -$airPollutionHistory = $openWeatherMap->airPollution()->getHistory(50, 50, $startDate, $endDate); - -foreach ($airPollutionHistory->getList() as $airPollution) { - echo $airPollution->getDateTime()->format('Y-m-d H:i:s'); - echo $airPollution->getAirQuality()->getQualitativeName(); - echo $airPollution->getCarbonMonoxide(); -} +$startDate = new \DateTime('-1 day'); +$endDate = new \DateTime('now'); + +// returns air pollution data for the last 24 hours +$airPollutionHistory = $api->airPollution()->getHistory(50, 50, $startDate, $endDate); ``` ### Geocoding @@ -184,119 +159,104 @@ foreach ($airPollutionHistory->getList() as $airPollution) { getByLocationName(string $locationName, int $numResults = 5): array ``` -Get locations data by location name. +Get geographical coordinates (latitude, longitude) by using the name of the location (city name or area name). -Returns an array of [`Location`](05-objects.md#location) objects: +Returns an array of [`Location`](05-entities.md#location) objects. ```php -$locations = $openWeatherMap->geocoding()->getByLocationName('lisbon'); - -foreach ($locations as $location) { - echo $location->getName(); - echo $location->getCountryCode(); -} +$locations = $api->geocoding()->getByLocationName('lisbon'); ``` -#### `getByZipCode` +#### `getByCoordinate` ```php -getByZipCode(string $zipCode, string $countryCode): ZipCodeLocation +/** + * @return Location[] + */ +getByCoordinate(float $latitude, float $longitude, int $numResults = 5): array ``` -Get location data by zip/post code. +Get name of the location (city name or area name) by using geographical coordinates (latitude, longitude). -Returns a [`ZipCodeLocation`](05-objects.md#zipcodelocation) object: +Returns an array of [`Location`](05-entities.md#location) objects. ```php -$location = $openWeatherMap->geocoding()->getByZipCode('1000-001', 'pt'); - -echo $location->getName(); +$locations = $api->geocoding()->getByCoordinate(50, 50); ``` -#### `getByCoordinate` +#### `getByZipCode` ```php -/** - * @return Location[] - */ -getByCoordinate(float $latitude, float $longitude, int $numResults = 5): array +getByZipCode(string $zipCode, string $countryCode): ZipLocation ``` -Get locations data by coordinate. +Get geographical coordinates (latitude, longitude) by using the zip/postal code. -Returns an array of [`Location`](05-objects.md#location) objects: +Returns a [`ZipLocation`](05-entities.md#ziplocation) object. ```php -$locations = $openWeatherMap->geocoding()->getByCoordinate(50, 50); - -foreach ($locations as $location) { - echo $location->getName(); - echo $location->getCountryCode(); -} +$location = $api->geocoding()->getByZipCode('1000-001', 'pt'); ``` ## Common Methods -#### `withUnitSystem` +#### `withLanguage` ```php -withUnitSystem(string $unitSystem): self +withLanguage(string $language): self ``` -Makes a request with a different unit system from the one globally defined in the [configuration](02-configuration.md#unitsystem). - -Only available for [`OneCall`](#one-call) and [`Weather`](#weather) APIs. +Set the language per request. +Only available for [`OneCall`](#one-call) and [`Weather`](#weather) API requests. ```php -use ProgrammatorDev\OpenWeatherMap\UnitSystem\UnitSystem; +use ProgrammatorDev\OpenWeatherMap\Language\Language -// Uses 'imperial' unit system for this request alone -$openWeatherMap->weather() - ->withUnitSystem(UnitSystem::IMPERIAL) +// uses the "pt" language for this request alone +$api->weather() + ->withLanguage(Language::PORTUGUESE) ->getCurrent(50, 50); ``` -#### `withLanguage` +#### `withUnitSystem` ```php -withLanguage(string $language): self +withUnitSystem(string $unitSystem): self ``` -Makes a request with a different language from the one globally defined in the [configuration](02-configuration.md#language). - -Only available for [`OneCall`](#one-call) and [`Weather`](#weather) APIs. +Set the unit system per request. +Only available for [`OneCall`](#one-call) and [`Weather`](#weather) API requests. ```php -use ProgrammatorDev\OpenWeatherMap\Language\Language +use ProgrammatorDev\OpenWeatherMap\UnitSystem\UnitSystem; -// Uses 'pt' language for this request alone -$openWeatherMap->weather() - ->withLanguage(Language::PORTUGUESE) +// uses the "imperial" unit system for this request alone +$api->weather() + ->withUnitSystem(UnitSystem::IMPERIAL) ->getCurrent(50, 50); ``` #### `withCacheTtl` ```php -withCacheTtl(int $seconds): self +withCacheTtl(?int $ttl): self ``` Makes a request and saves into cache for the provided duration in seconds. -If `0` seconds is provided, the request will not be cached. - -> **Note** -> Setting cache to `0` seconds will **not** invalidate any existing cache. +Semantics of values: +- `0`, the response will not be cached (if the servers specifies no `max-age`). +- `null`, the response will be cached for as long as it can (forever). -Check the [Cache TTL](02-configuration.md#cache-ttl) section for more information regarding default values. +> [!NOTE] +> Setting cache to `null` or `0` seconds will **not** invalidate any existing cache. -Available for all APIs if `cache` is enabled in the [configuration](02-configuration.md#cache). +Available for all APIs if a cache adapter is set. +Check the following [documentation](02-configuration.md#setcachebuilder) for more information. ```php -use ProgrammatorDev\OpenWeatherMap\Language\Language - -// Cache will be saved for 1 hour for this request alone -$openWeatherMap->weather() +// cache will be saved for 1 hour for this request alone +$api->weather() ->withCacheTtl(3600) ->getCurrent(50, 50); ``` \ No newline at end of file diff --git a/docs/04-error-handling.md b/docs/04-error-handling.md index 27327a7..aee7575 100644 --- a/docs/04-error-handling.md +++ b/docs/04-error-handling.md @@ -15,26 +15,26 @@ use ProgrammatorDev\OpenWeatherMap\Exception\UnauthorizedException; use ProgrammatorDev\OpenWeatherMap\Exception\UnexpectedErrorException; try { - $location = $openWeatherMap->geocoding()->getByZipCode('1000-001', 'pt'); + $location = $api->geocoding()->getByZipCode('1000-001', 'pt'); + $coordinate = $location->getCoordinate(); - $weather = $openWeatherMap->oneCall()->getWeather( - $location->getCoordinate()->getLatitude(), - $location->getCoordinate()->getLongitude() + $weather = $api->oneCall()->getWeather( + $coordinate->getLatitude(), + $coordinate->getLongitude() ); } -// Bad requests to the API -// If this library is making a good job validating input data, this should not happen +// bad request to the API catch (BadRequestException $exception) { echo $exception->getCode(); // 400 echo $exception->getMessage(); } -// Invalid API key or trying to request an endpoint with no granted access +// invalid API key or trying to request an endpoint with no granted access catch (UnauthorizedException $exception) { echo $exception->getCode(); // 401 echo $exception->getMessage(); } -// Resource not found -// For example, when trying to get a location with a zip/post code that does not exist +// resource not found +// for example, when trying to get a location with a zip code that does not exist catch (NotFoundException $exception) { echo $exception->getCode(); // 404 echo $exception->getMessage(); @@ -44,7 +44,7 @@ catch (TooManyRequestsException $exception) { echo $exception->getCode(); // 429 echo $exception->getMessage(); } -// Any other error, probably an internal error +// any other error, probably an internal error catch (UnexpectedErrorException $exception) { echo $exception->getCode(); // 5xx echo $exception->getMessage(); @@ -57,14 +57,15 @@ To catch all API errors with a single exception, `ApiErrorException` is availabl use ProgrammatorDev\OpenWeatherMap\Exception\ApiErrorException; try { - $location = $openWeatherMap->geocoding()->getByZipCode('1000-001', 'pt'); + $location = $api->geocoding()->getByZipCode('1000-001', 'pt'); + $coordinate = $location->getCoordinate(); - $weather = $openWeatherMap->oneCall()->getWeather( - $location->getCoordinate()->getLatitude(), - $location->getCoordinate()->getLongitude() + $weather = $api->oneCall()->getWeather( + $coordinate->getLatitude(), + $coordinate->getLongitude() ); } -// Catches all API response errors +// catches all API response errors catch (ApiErrorException $exception) { echo $exception->getCode(); echo $exception->getMessage(); @@ -76,14 +77,14 @@ catch (ApiErrorException $exception) { To catch invalid input data (like an out of range coordinate, blank location name, etc.), the `ValidationException` is available: ```php -use ProgrammatorDev\YetAnotherPhpValidator\Exception\ValidationException; +use ProgrammatorDev\Validator\Exception\ValidationException; try { - // An invalid latitude value is given - $weather = $openWeatherMap->weather()->getCurrent(999, 50); + // an invalid latitude value is given + $weather = $api->weather()->getCurrent(999, 50); } catch (ValidationException $exception) { - // Should print: + // should print: // The latitude value should be between -90 and 90, 999 given. echo $exception->getMessage(); } diff --git a/docs/05-objects.md b/docs/05-entities.md similarity index 61% rename from docs/05-objects.md rename to docs/05-entities.md index b2e13c3..412e901 100644 --- a/docs/05-objects.md +++ b/docs/05-entities.md @@ -1,86 +1,74 @@ -# Objects +# Entities - [One Call](#one-call) - - [Alert](#alert) - - [MinuteForecast](#minuteforecast) - - [OneCall](#onecall) - [Weather](#weather) - - [WeatherAggregate](#weatheraggregate) - - [WeatherLocation](#weatherlocation) + - [WeatherMoment](#weathermoment) + - [WeatherSummary](#weathersummary) + - [WeatherData](#weatherdata) + - [MinuteData](#minutedata) + - [HourData](#hourdata) + - [DayData](#daydata) + - [Alert](#alert) + - [MoonPhase](#moonphase) + - [Temperature](#temperature) - [Weather](#weather-1) - [Weather](#weather-2) - - [WeatherLocation](#weatherlocation-1) - - [WeatherLocationList](#weatherlocationlist) + - [WeatherCollection](#weathercollection) + - [WeatherData](#weatherdata) - [Air Pollution](#air-pollution) - [AirPollution](#airpollution) - - [AirPollutionLocation](#airpollutionlocation) - - [AirPollutionLocationList](#airpollutionlocationlist) + - [AirPollutionCollection](#airpollutioncollection) + - [AirPollutionData](#airpollutiondata) - [AirQuality](#airquality) - [Geocoding](#geocoding) - - [ZipCodeLocation](#zipcodelocation) + - [ZipLocation](#ziplocation) - [Common](#common) - - [AtmosphericPressure](#atmosphericpressure) - [Coordinate](#coordinate) + - [Condition](#condition) - [Icon](#icon) - [Location](#location) - [MoonPhase](#moonphase) - - [Rain](#rain) - - [Snow](#snow) - [Temperature](#temperature) - [Timezone](#timezone) - [Wind](#wind) - - [WeatherCondition](#weathercondition) ## One Call -### Alert - -- `getSenderName()`: `string` -- `getEventName()`: `string` -- `getStartsAt()`: `\DateTimeImmutable` -- `getEndsAt()`: `\DateTimeImmutable` -- `getDescription()`: `string` -- `getTags()`: `array` - -### MinuteForecast - -- `getDateTime()`: `\DateTimeImmutable` -- `getPrecipitation()`: `float` - -### OneCall +### Weather - `getCoordinate()`: [`Coordinate`](#coordinate) - `getTimezone()`: [`Timezone`](#timezone) -- `getCurrent()`: [`Weather`](#weather) -- `getMinutelyForecast()`: [`?MinuteForecast[]`](#minuteforecast) -- `getHourlyForecast()`: [`Weather[]`](#weather) -- `getDailyForecast()`: [`Weather[]`](#weather) +- `getCurrent()`: [`WeatherData`](#weatherdata) +- `getMinutelyForecast()`: [`?MinuteData[]`](#minutedata) +- `getHourlyForecast()`: [`HourData[]`](#hourdata) +- `getDailyForecast()`: [`DayData[]`](#daydata) - `getAlerts()`: [`?Alert[]`](#alert) -### Weather +### WeatherMoment +- `getCoordinate()`: [`Coordinate`](#coordinate) +- `getTimezone()`: [`Timezone`](#timezone) - `getDateTime()`: `\DateTimeImmutable` -- `getSunriseAt()`: `?\DateTimeImmutable` -- `getSunsetAt()`: `?\DateTimeImmutable` -- `getMoonriseAt()`: `?\DateTimeImmutable` -- `getMoonsetAt()`: `?\DateTimeImmutable` -- `getMoonPhase()`: [`?MoonPhase`](#moonphase) -- `getTemperature()`: `float`|[`Temperature`](#temperature) -- `getTemperatureFeelsLike()`: `float`|[`Temperature`](#temperature) -- `getDescription()`: `?string` +- `getTemperature()`: `float` +- `getTemperatureFeelsLike()`: `float` - `getAtmosphericPressure()`: `int` - `getHumidity()`: `int` -- `getDewPoint()`: `?float` +- `getDewPoint()`: `float` - `getUltraVioletIndex()`: `?float` - `getCloudiness()`: `int` - `getVisibility()`: `?int` - `getWind()`: [`Wind`](#wind) -- `getPrecipitationProbability()`: `?int` -- `getRain()`: `null`|`float`|[`Rain`](#rain) -- `getSnow()`: `null`|`float`|[`Snow`](#snow) -- `getWeatherConditions()`: [`WeatherCondition[]`](#weathercondition) +- `getConditions()`: [`Condition[]`](#condition) +- `getSummary()`: `?string` +- `getRainVolume()`: `?float` +- `getSnowVolume()`: `?float` +- `getMoonPhase()`: [`?MoonPhase`](#moonphase) +- `getSunriseAt()`: `?\DateTimeImmutable` +- `getSunsetAt()`: `?\DateTimeImmutable` +- `getMoonriseAt()`: `?\DateTimeImmutable` +- `getMoonsetAt()`: `?\DateTimeImmutable` -### WeatherAggregate +### WeatherSummary - `getCoordinate()`: [`Coordinate`](#coordinate) - `getTimezone()`: [`Timezone`](#timezone) @@ -92,35 +80,98 @@ - `getAtmosphericPressure()`: `int` - `getWind()`: [`Wind`](#wind) -### WeatherLocation +### WeatherData -- `getCoordinate()`: [`Coordinate`](#coordinate) -- `getTimezone()`: [`Timezone`](#timezone) - `getDateTime()`: `\DateTimeImmutable` +- `getTemperature()`: `float` +- `getTemperatureFeelsLike()`: `float` +- `getAtmosphericPressure()`: `int` +- `getVisibility()`: `?int` +- `getHumidity()`: `int` +- `getDewPoint()`: `float` +- `getUltraVioletIndex()`: `?float` +- `getCloudiness()`: `int` +- `getWind()`: [`Wind`](#wind) +- `getConditions()`: [`Condition[]`](#condition) +- `getRainVolume()`: `?float` +- `getSnowVolume()`: `?float` - `getSunriseAt()`: `?\DateTimeImmutable` - `getSunsetAt()`: `?\DateTimeImmutable` -- `getMoonriseAt()`: `?\DateTimeImmutable` -- `getMoonsetAt()`: `?\DateTimeImmutable` -- `getMoonPhase()`: [`?MoonPhase`](#moonphase) -- `getTemperature()`: `float`|[`Temperature`](#temperature) -- `getTemperatureFeelsLike()`: `float`|[`Temperature`](#temperature) -- `getDescription()`: `?string` + +### MinuteData + +- `getDateTime()`: `\DateTimeImmutable` +- `getPrecipitation()`: `float` + +### HourData + +- `getDateTime()`: `\DateTimeImmutable` +- `getTemperature()`: `float` +- `getTemperatureFeelsLike()`: `float` +- `getVisibility()`: `int` +- `getPrecipitationProbability()`: `int` - `getAtmosphericPressure()`: `int` - `getHumidity()`: `int` -- `getDewPoint()`: `?float` +- `getDewPoint()`: `float` - `getUltraVioletIndex()`: `?float` - `getCloudiness()`: `int` -- `getVisibility()`: `?int` - `getWind()`: [`Wind`](#wind) -- `getPrecipitationProbability()`: `?int` -- `getRain()`: `null`|`float`|[`Rain`](#rain) -- `getSnow()`: `null`|`float`|[`Snow`](#snow) -- `getWeatherConditions()`: [`WeatherCondition[]`](#weathercondition) +- `getConditions()`: [`Condition[]`](#condition) +- `getRainVolume()`: `?float` +- `getSnowVolume()`: `?float` + +### DayData + +- `getDateTime()`: `\DateTimeImmutable` +- `getTemperature()`: [`Temperature`](#temperature) +- `getTemperatureFeelsLike()`: [`Temperature`](#temperature) +- `getPrecipitationProbability()`: `int` +- `getAtmosphericPressure()`: `int` +- `getHumidity()`: `int` +- `getDewPoint()`: `float` +- `getUltraVioletIndex()`: `?float` +- `getCloudiness()`: `int` +- `getWind()`: [`Wind`](#wind) +- `getConditions()`: [`Condition[]`](#condition) +- `getRainVolume()`: `?float` +- `getSnowVolume()`: `?float` +- `getSummary()`: `string` +- `getMoonPhase()`: [`MoonPhase`](#moonphase) +- `getSunriseAt()`: `\DateTimeImmutable` +- `getSunsetAt()`: `\DateTimeImmutable` +- `getMoonriseAt()`: `\DateTimeImmutable` +- `getMoonsetAt()`: `\DateTimeImmutable` + +### Alert + +- `getSenderName()`: `string` +- `getEventName()`: `string` +- `getStartsAt()`: `\DateTimeImmutable` +- `getEndsAt()`: `\DateTimeImmutable` +- `getDescription()`: `string` +- `getTags()`: `array` + +### MoonPhase + +- `getValue()`: `float` +- `getName()`: `string` +- `getSystemName()`: `string` + +### Temperature + +- `getMorning()`: `float` +- `getDay()`: `float` +- `getEvening()`: `float` +- `getNight()`: `float` +- `getMin()`: `?float` +- `getMax()`: `?float` ## Weather ### Weather +- `getLocation()`: [`Location`](#location) +- `getDateTime()`: `\DateTimeImmutable` - `getTemperature()`: `float` - `getTemperatureFeelsLike()`: `float` - `getMinTemperature()`: `float` @@ -128,17 +179,22 @@ - `getHumidity()`: `int` - `getCloudiness()`: `int` - `getVisibility()`: `int` -- `getWeatherConditions()`: [`WeatherCondition[]`](#weathercondition) +- `getAtmosphericPressure()`: `int` +- `getConditions()`: [`Condition[]`](#condition) - `getWind()`: [`Wind`](#wind) - `getPrecipitationProbability()`: `?int` -- `getRain()`: [`?Rain`](#rain) -- `getSnow()`: [`?Snow`](#snow) -- `getAtmosphericPressure()`: [`AtmosphericPressure`](#atmosphericpressure) -- `getDateTime()`: `\DateTimeImmutable` +- `getRainVolume()`: `?float` +- `getSnowVolume()`: `?float` -### WeatherLocation +### WeatherCollection +- `getNumResults()`: `int` - `getLocation()`: [`Location`](#location) +- `getData()`: [`WeatherData[]`](#weatherdata-1) + +### WeatherData + +- `getDateTime()`: `\DateTimeImmutable` - `getTemperature()`: `float` - `getTemperatureFeelsLike()`: `float` - `getMinTemperature()`: `float` @@ -146,24 +202,18 @@ - `getHumidity()`: `int` - `getCloudiness()`: `int` - `getVisibility()`: `int` -- `getWeatherConditions()`: [`WeatherCondition[]`](#weathercondition) +- `getAtmosphericPressure()`: `int` +- `getConditions()`: [`Condition[]`](#condition) - `getWind()`: [`Wind`](#wind) - `getPrecipitationProbability()`: `?int` -- `getRain()`: [`?Rain`](#rain) -- `getSnow()`: [`?Snow`](#snow) -- `getAtmosphericPressure()`: [`AtmosphericPressure`](#atmosphericpressure) -- `getDateTime()`: `\DateTimeImmutable` - -### WeatherLocationList - -- `getNumResults()`: `int` -- `getLocation()`: [`Location`](#location) -- `getList()`: [`Weather[]`](#weather-2) +- `getRainVolume()`: `?float` +- `getSnowVolume()`: `?float` ## Air Pollution ### AirPollution +- `getCoordinate()`: [`Coordinate`](#coordinate) - `getDateTime()`: `\DateTimeImmutable` - `getAirQuality`: [`AirQuality`](#airquality) - `getCarbonMonoxide()`: `float` @@ -175,9 +225,14 @@ - `getCoarseParticulateMatter()`: `float` - `getAmmonia()`: `float` -### AirPollutionLocation +### AirPollutionCollection +- `getNumResults()`: `int` - `getCoordinate()`: [`Coordinate`](#coordinate) +- `getData()`: [`AirPollutionData[]`](#airpollutiondata) + +### AirPollutionData + - `getDateTime()`: `\DateTimeImmutable` - `getAirQuality`: [`AirQuality`](#airquality) - `getCarbonMonoxide()`: `float` @@ -189,11 +244,6 @@ - `getCoarseParticulateMatter()`: `float` - `getAmmonia()`: `float` -### AirPollutionLocationList - -- `getCoordinate()`: [`Coordinate`](#coordinate) -- `getList()`: [`AirPollution[]`](#airpollution) - ### AirQuality - `getIndex()`: `int` @@ -201,81 +251,51 @@ ## Geocoding -### ZipCodeLocation +### ZipLocation - `getZipCode()`: `string` - `getName()`: `string` -- `getCoordinate()`: [`Coordinate`](#coordinate) - `getCountryCode()`: `string` +- `getCoordinate()`: [`Coordinate`](#coordinate) ## Common -### AtmosphericPressure - -- `getPressure()`: `int` -- `getSeaLevelPressure()`: `?int` -- `getGroundLevelPressure()`: `?int` - ### Coordinate - `getLatitude()`: `float` - `getLongitude()`: `float` +### Condition + +- `getId()`: `int` +- `getName()`: `string` +- `getDescription()`: `string` +- `getIcon()`: [`Icon`](#icon) +- `getSystemName()`: `string` + ### Icon - `getId()`: `string` -- `getImageUrl()`: `string` +- `getUrl()`: `string` ### Location +- `getCoordinate()`: [`Coordinate`](#coordinate) - `getId()`: `?int` - `getName()`: `?string` - `getState()`: `?string` - `getCountryCode()`: `?string` - `getLocalNames()`: `?array` +- `getLocalName(string $countryCode)`: `?string` - `getPopulation()`: `?int` -- `getCoordinate()`: [`Coordinate`](#coordinate) - `getTimezone()`: [`?Timezone`](#timezone) - `getSunriseAt()`: `?\DateTimeImmutable` - `getSunsetAt()`: `?\DateTimeImmutable` -### MoonPhase - -- `getValue()`: `float` -- `getName()`: `string` -- `getSysName()`: `string` - -### Rain - -- `getLastOneHourVolume()`: `?float` -- `getLastThreeHoursVolume()`: `?float` - -### Snow - -- `getLastOneHourVolume()`: `?float` -- `getLastThreeHoursVolume()`: `?float` - -### Temperature - -- `getMorning()`: `float` -- `getDay()`: `float` -- `getEvening()`: `float` -- `getNight()`: `float` -- `getMin()`: `?float` -- `getMax()`: `?float` - ### Timezone -- `getIdentifier()`: `?string` - `getOffset()`: `int` - -### WeatherCondition - -- `getId()`: `int` -- `getName()`: `string` -- `getDescription()`: `string` -- `getIcon()`: [`Icon`](#icon) -- `getSysName()`: `string` +- `getIdentifier()`: `?string` ### Wind diff --git a/src/Config.php b/src/Config.php deleted file mode 100644 index 1b5170e..0000000 --- a/src/Config.php +++ /dev/null @@ -1,144 +0,0 @@ -options = $this->resolveOptions($options); - } - - private function resolveOptions(array $options): array - { - $resolver = new OptionsResolver(); - - $resolver->setDefaults([ - 'unitSystem' => UnitSystem::METRIC, - 'language' => Language::ENGLISH, - 'httpClientBuilder' => new HttpClientBuilder(), - 'cache' => null, - 'logger' => null - ]); - - $resolver->setRequired('applicationKey'); - - $resolver->setAllowedTypes('applicationKey', 'string'); - $resolver->setAllowedTypes('unitSystem', 'string'); - $resolver->setAllowedTypes('language', 'string'); - $resolver->setAllowedTypes('httpClientBuilder', HttpClientBuilder::class); - $resolver->setAllowedTypes('cache', ['null', CacheItemPoolInterface::class]); - $resolver->setAllowedTypes('logger', ['null', LoggerInterface::class]); - - $resolver->setAllowedValues('applicationKey', fn($value) => !empty($value)); - $resolver->setAllowedValues('unitSystem', UnitSystem::getList()); - $resolver->setAllowedValues('language', Language::getList()); - - return $resolver->resolve($options); - } - - public function getBaseUrl(): string - { - return $this->baseUrl; - } - - public function getApplicationKey(): string - { - return $this->options['applicationKey']; - } - - /** - * @throws ValidationException - */ - public function setApplicationKey(string $applicationKey): self - { - Validator::notBlank()->assert($applicationKey, 'applicationKey'); - - $this->options['applicationKey'] = $applicationKey; - - return $this; - } - - public function getUnitSystem(): string - { - return $this->options['unitSystem']; - } - - /** - * @throws ValidationException - */ - public function setUnitSystem(string $unitSystem): self - { - Validator::choice(UnitSystem::getList())->assert($unitSystem, 'unitSystem'); - - $this->options['unitSystem'] = $unitSystem; - - return $this; - } - - public function getLanguage(): string - { - return $this->options['language']; - } - - /** - * @throws ValidationException - */ - public function setLanguage(string $language): self - { - Validator::choice(Language::getList())->assert($language, 'language'); - - $this->options['language'] = $language; - - return $this; - } - - public function getHttpClientBuilder(): HttpClientBuilder - { - return $this->options['httpClientBuilder']; - } - - public function setHttpClientBuilder(HttpClientBuilder $httpClientBuilder): self - { - $this->options['httpClientBuilder'] = $httpClientBuilder; - - return $this; - } - - public function getCache(): ?CacheItemPoolInterface - { - return $this->options['cache']; - } - - public function setCache(?CacheItemPoolInterface $cache): self - { - $this->options['cache'] = $cache; - - return $this; - } - - public function getLogger(): ?LoggerInterface - { - return $this->options['logger']; - } - - public function setLogger(?LoggerInterface $logger): self - { - $this->options['logger'] = $logger; - - return $this; - } -} \ No newline at end of file diff --git a/src/Endpoint/AbstractEndpoint.php b/src/Endpoint/AbstractEndpoint.php deleted file mode 100644 index 4972fd8..0000000 --- a/src/Endpoint/AbstractEndpoint.php +++ /dev/null @@ -1,130 +0,0 @@ -config = $this->api->config(); - - $this->httpClientBuilder = $this->config->getHttpClientBuilder(); - $this->cache = $this->config->getCache(); - $this->logger = $this->config->getLogger(); - $this->unitSystem = $this->config->getUnitSystem(); - $this->language = $this->config->getLanguage(); - } - - /** - * @throws Exception - * @throws ApiErrorException - */ - protected function sendRequest( - string $method, - string $path, - array $query = [], - array $headers = [], - StreamInterface|string $body = null - ): array - { - $this->configurePlugins(); - - $response = $this->httpClientBuilder->getHttpClient()->send( - $method, - $this->buildUrl($path, $query), - $headers, - $body - ); - - if ($response->getStatusCode() >= 400) { - $this->handleResponseErrors($response); - } - - return ResponseMediator::toArray($response); - } - - private function configurePlugins(): void - { - // Plugin order is important - // CachePlugin should come first, otherwise the LoggerPlugin will log requests even if they are cached - - if ($this->cache !== null) { - $this->httpClientBuilder->addPlugin( - new CachePlugin($this->cache, $this->httpClientBuilder->getStreamFactory(), [ - 'default_ttl' => $this->cacheTtl, - 'cache_lifetime' => 0, - 'cache_listeners' => ($this->logger !== null) ? [new LoggerCacheListener($this->logger)] : [] - ]) - ); - } - - if ($this->logger !== null) { - $this->httpClientBuilder->addPlugin( - new LoggerPlugin($this->logger) - ); - } - } - - /** - * @throws ApiErrorException - */ - private function handleResponseErrors(ResponseInterface $response): void - { - $error = new Error( - ResponseMediator::toArray($response) - ); - - match ($response->getStatusCode()) { - 400 => throw new BadRequestException($error), - 401 => throw new UnauthorizedException($error), - 404 => throw new NotFoundException($error), - 429 => throw new TooManyRequestsException($error), - default => throw new UnexpectedErrorException($error) - }; - } - - private function buildUrl(string $path, array $query): string - { - // Add application key to all requests - $query['appid'] = $this->config->getApplicationKey(); - - return \sprintf('%s%s?%s', $this->config->getBaseUrl(), $path, http_build_query($query)); - } -} \ No newline at end of file diff --git a/src/Endpoint/AirPollutionEndpoint.php b/src/Endpoint/AirPollutionEndpoint.php deleted file mode 100644 index 574a9db..0000000 --- a/src/Endpoint/AirPollutionEndpoint.php +++ /dev/null @@ -1,88 +0,0 @@ -validateCoordinate($latitude, $longitude); - - $data = $this->sendRequest( - method: 'GET', - path: '/data/2.5/air_pollution', - query: [ - 'lat' => $latitude, - 'lon' => $longitude - ] - ); - - return new AirPollutionLocation($data); - } - - /** - * @throws Exception - * @throws ApiErrorException - * @throws ValidationException - */ - public function getForecast(float $latitude, float $longitude): AirPollutionLocationList - { - $this->validateCoordinate($latitude, $longitude); - - $data = $this->sendRequest( - method: 'GET', - path: '/data/2.5/air_pollution/forecast', - query: [ - 'lat' => $latitude, - 'lon' => $longitude - ] - ); - - return new AirPollutionLocationList($data); - } - - /** - * @throws Exception - * @throws ApiErrorException - * @throws ValidationException - */ - public function getHistory( - float $latitude, - float $longitude, - \DateTimeInterface $startDate, - \DateTimeInterface $endDate - ): AirPollutionLocationList - { - $this->validateCoordinate($latitude, $longitude); - $this->validateDateRange($startDate, $endDate); - - $timezoneUtc = new \DateTimeZone('UTC'); - - $data = $this->sendRequest( - method: 'GET', - path: '/data/2.5/air_pollution/history', - query: [ - 'lat' => $latitude, - 'lon' => $longitude, - 'start' => $startDate->setTimezone($timezoneUtc)->getTimestamp(), - 'end' => $endDate->setTimezone($timezoneUtc)->getTimestamp() - ] - ); - - return new AirPollutionLocationList($data); - } -} \ No newline at end of file diff --git a/src/Endpoint/OneCallEndpoint.php b/src/Endpoint/OneCallEndpoint.php deleted file mode 100644 index 10f00e1..0000000 --- a/src/Endpoint/OneCallEndpoint.php +++ /dev/null @@ -1,93 +0,0 @@ -validateCoordinate($latitude, $longitude); - - $data = $this->sendRequest( - method: 'GET', - path: '/data/3.0/onecall', - query: [ - 'lat' => $latitude, - 'lon' => $longitude, - 'units' => $this->getUnitSystem(), - 'lang' => $this->getLanguage() - ] - ); - - return new OneCall($data); - } - - /** - * @throws Exception - * @throws ApiErrorException - * @throws ValidationException - */ - public function getHistoryMoment(float $latitude, float $longitude, \DateTimeInterface $dateTime): WeatherLocation - { - $this->validateCoordinate($latitude, $longitude); - $this->validatePastDate($dateTime, 'dateTime'); - - $data = $this->sendRequest( - method: 'GET', - path: '/data/3.0/onecall/timemachine', - query: [ - 'lat' => $latitude, - 'lon' => $longitude, - 'dt' => $dateTime->setTimezone(new \DateTimeZone('UTC'))->getTimestamp(), - 'units' => $this->getUnitSystem(), - 'lang' => $this->getLanguage() - ] - ); - - return new WeatherLocation($data); - } - - /** - * @throws Exception - * @throws ApiErrorException - * @throws ValidationException - */ - public function getHistoryAggregate(float $latitude, float $longitude, \DateTimeInterface $date): WeatherAggregate - { - $this->validateCoordinate($latitude, $longitude); - $this->validatePastDate($date, 'date'); - - $data = $this->sendRequest( - method: 'GET', - path: '/data/3.0/onecall/day_summary', - query: [ - 'lat' => $latitude, - 'lon' => $longitude, - 'date' => $date->format('Y-m-d'), - 'units' => $this->getUnitSystem(), - 'lang' => $this->getLanguage() - ] - ); - - return new WeatherAggregate($data); - } -} \ No newline at end of file diff --git a/src/Endpoint/Util/CacheTtlTrait.php b/src/Endpoint/Util/CacheTtlTrait.php deleted file mode 100644 index e4cb69c..0000000 --- a/src/Endpoint/Util/CacheTtlTrait.php +++ /dev/null @@ -1,19 +0,0 @@ -cacheTtl = $seconds; - - return $clone; - } - - public function getCacheTtl(): int - { - return $this->cacheTtl; - } -} \ No newline at end of file diff --git a/src/Endpoint/Util/LanguageTrait.php b/src/Endpoint/Util/LanguageTrait.php deleted file mode 100644 index 63e6cec..0000000 --- a/src/Endpoint/Util/LanguageTrait.php +++ /dev/null @@ -1,28 +0,0 @@ -assert($language, 'language'); - - $clone = clone $this; - $clone->language = $language; - - return $clone; - } - - public function getLanguage(): string - { - return $this->language; - } -} \ No newline at end of file diff --git a/src/Endpoint/Util/UnitSystemTrait.php b/src/Endpoint/Util/UnitSystemTrait.php deleted file mode 100644 index 194249f..0000000 --- a/src/Endpoint/Util/UnitSystemTrait.php +++ /dev/null @@ -1,28 +0,0 @@ -assert($unitSystem, 'unitSystem'); - - $clone = clone $this; - $clone->unitSystem = $unitSystem; - - return $clone; - } - - public function getUnitSystem(): string - { - return $this->unitSystem; - } -} \ No newline at end of file diff --git a/src/Endpoint/Util/ValidationTrait.php b/src/Endpoint/Util/ValidationTrait.php deleted file mode 100644 index 4c8f0d6..0000000 --- a/src/Endpoint/Util/ValidationTrait.php +++ /dev/null @@ -1,68 +0,0 @@ -assert($latitude, 'latitude'); - Validator::range(-180, 180)->assert($longitude, 'longitude'); - } - - /** - * @throws ValidationException - */ - private function validateDateRange(\DateTimeInterface $startDate, \DateTimeInterface $endDate): void - { - $nowDate = new \DateTime('now'); - $nowDate->setTime($nowDate->format('H'), 0); - - // Start date must be less or equal to end date - Validator::lessThanOrEqual( - constraint: $endDate, - message: 'The startDate value should be less than or equal to the endDate.' - )->assert($startDate); - - // End date must be less or equal to today date - Validator::lessThanOrEqual($nowDate)->assert($endDate, 'endDate'); - } - - /** - * @throws ValidationException - */ - private function validatePastDate(\DateTimeInterface $date, string $name): void - { - Validator::lessThan(new \DateTime('now'))->assert($date, $name); - } - - /** - * @throws ValidationException - */ - private function validateSearchQuery(string $searchQuery, string $name): void - { - Validator::notBlank()->assert($searchQuery, $name); - } - - /** - * @throws ValidationException - */ - private function validateNumResults(int $numResults): void - { - Validator::greaterThan(0)->assert($numResults, 'numResults'); - } - - /** - * @throws ValidationException - */ - private function validateCountryCode(string $countryCode): void - { - Validator::country()->assert($countryCode, 'countryCode'); - } -} \ No newline at end of file diff --git a/src/Endpoint/WeatherEndpoint.php b/src/Endpoint/WeatherEndpoint.php deleted file mode 100644 index 2209715..0000000 --- a/src/Endpoint/WeatherEndpoint.php +++ /dev/null @@ -1,69 +0,0 @@ -validateCoordinate($latitude, $longitude); - - $data = $this->sendRequest( - method: 'GET', - path: '/data/2.5/weather', - query: [ - 'lat' => $latitude, - 'lon' => $longitude, - 'units' => $this->getUnitSystem(), - 'lang' => $this->getLanguage() - ] - ); - - return new WeatherLocation($data); - } - - /** - * @throws Exception - * @throws ApiErrorException - * @throws ValidationException - */ - public function getForecast(float $latitude, float $longitude, int $numResults = self::NUM_RESULTS): WeatherLocationList - { - $this->validateCoordinate($latitude, $longitude); - $this->validateNumResults($numResults); - - $data = $this->sendRequest( - method: 'GET', - path: '/data/2.5/forecast', - query: [ - 'lat' => $latitude, - 'lon' => $longitude, - 'cnt' => $numResults, - 'units' => $this->getUnitSystem(), - 'lang' => $this->getLanguage() - ] - ); - - return new WeatherLocationList($data); - } -} \ No newline at end of file diff --git a/src/Entity/AirPollution/AirPollution.php b/src/Entity/AirPollution/AirPollution.php index 5992b18..9e90556 100644 --- a/src/Entity/AirPollution/AirPollution.php +++ b/src/Entity/AirPollution/AirPollution.php @@ -2,89 +2,21 @@ namespace ProgrammatorDev\OpenWeatherMap\Entity\AirPollution; -class AirPollution -{ - private \DateTimeImmutable $dateTime; - - private AirQuality $airQuality; - - private float $carbonMonoxide; - - private float $nitrogenMonoxide; - - private float $nitrogenDioxide; - - private float $ozone; - - private float $sulphurDioxide; - - private float $fineParticulateMatter; - - private float $coarseParticulateMatter; +use ProgrammatorDev\OpenWeatherMap\Entity\Coordinate; - private float $ammonia; +class AirPollution extends AirPollutionData +{ + private Coordinate $coordinate; public function __construct(array $data) { - $this->dateTime = \DateTimeImmutable::createFromFormat('U', $data['dt'], new \DateTimeZone('UTC')); - $this->airQuality = new AirQuality($data['main']); - $this->carbonMonoxide = $data['components']['co']; - $this->nitrogenMonoxide = $data['components']['no']; - $this->nitrogenDioxide = $data['components']['no2']; - $this->ozone = $data['components']['o3']; - $this->sulphurDioxide = $data['components']['so2']; - $this->fineParticulateMatter = $data['components']['pm2_5']; - $this->coarseParticulateMatter = $data['components']['pm10']; - $this->ammonia = $data['components']['nh3']; - } - - public function getDateTime(): \DateTimeImmutable - { - return $this->dateTime; - } - - public function getAirQuality(): AirQuality - { - return $this->airQuality; - } - - public function getCarbonMonoxide(): float - { - return $this->carbonMonoxide; - } - - public function getNitrogenMonoxide(): float - { - return $this->nitrogenMonoxide; - } - - public function getNitrogenDioxide(): float - { - return $this->nitrogenDioxide; - } - - public function getOzone(): float - { - return $this->ozone; - } - - public function getSulphurDioxide(): float - { - return $this->sulphurDioxide; - } + parent::__construct($data['list'][0]); - public function getFineParticulateMatter(): float - { - return $this->fineParticulateMatter; - } - - public function getCoarseParticulateMatter(): float - { - return $this->coarseParticulateMatter; + $this->coordinate = new Coordinate($data['coord']); } - public function getAmmonia(): float + public function getCoordinate(): Coordinate { - return $this->ammonia; + return $this->coordinate; } } \ No newline at end of file diff --git a/src/Entity/AirPollution/AirPollutionCollection.php b/src/Entity/AirPollution/AirPollutionCollection.php new file mode 100644 index 0000000..6dcbf46 --- /dev/null +++ b/src/Entity/AirPollution/AirPollutionCollection.php @@ -0,0 +1,40 @@ +numResults = \count($data['list']); + $this->coordinate = new Coordinate($data['coord']); + $this->data = $this->createEntityList(AirPollutionData::class, $data['list']); + } + + public function getNumResults(): int + { + return $this->numResults; + } + + public function getCoordinate(): Coordinate + { + return $this->coordinate; + } + + public function getData(): array + { + return $this->data; + } +} \ No newline at end of file diff --git a/src/Entity/AirPollution/AirPollutionData.php b/src/Entity/AirPollution/AirPollutionData.php new file mode 100644 index 0000000..4b85346 --- /dev/null +++ b/src/Entity/AirPollution/AirPollutionData.php @@ -0,0 +1,93 @@ +dateTime = \DateTimeImmutable::createFromFormat('U', $data['dt']); + $this->airQuality = new AirQuality($data['main']); + $this->carbonMonoxide = $data['components']['co']; + $this->nitrogenMonoxide = $data['components']['no']; + $this->nitrogenDioxide = $data['components']['no2']; + $this->ozone = $data['components']['o3']; + $this->sulphurDioxide = $data['components']['so2']; + $this->fineParticulateMatter = $data['components']['pm2_5']; + $this->coarseParticulateMatter = $data['components']['pm10']; + $this->ammonia = $data['components']['nh3']; + } + + /** + * DateTime in UTC + */ + public function getDateTime(): \DateTimeImmutable + { + return $this->dateTime; + } + + public function getAirQuality(): AirQuality + { + return $this->airQuality; + } + + public function getCarbonMonoxide(): float + { + return $this->carbonMonoxide; + } + + public function getNitrogenMonoxide(): float + { + return $this->nitrogenMonoxide; + } + + public function getNitrogenDioxide(): float + { + return $this->nitrogenDioxide; + } + + public function getOzone(): float + { + return $this->ozone; + } + + public function getSulphurDioxide(): float + { + return $this->sulphurDioxide; + } + + public function getFineParticulateMatter(): float + { + return $this->fineParticulateMatter; + } + + public function getCoarseParticulateMatter(): float + { + return $this->coarseParticulateMatter; + } + + public function getAmmonia(): float + { + return $this->ammonia; + } +} \ No newline at end of file diff --git a/src/Entity/AirPollution/AirPollutionLocation.php b/src/Entity/AirPollution/AirPollutionLocation.php deleted file mode 100644 index 2cb924a..0000000 --- a/src/Entity/AirPollution/AirPollutionLocation.php +++ /dev/null @@ -1,22 +0,0 @@ -coordinate = new Coordinate($data['coord']); - } - - public function getCoordinate(): Coordinate - { - return $this->coordinate; - } -} \ No newline at end of file diff --git a/src/Entity/AirPollution/AirPollutionLocationList.php b/src/Entity/AirPollution/AirPollutionLocationList.php deleted file mode 100644 index fa3ecb1..0000000 --- a/src/Entity/AirPollution/AirPollutionLocationList.php +++ /dev/null @@ -1,32 +0,0 @@ -coordinate = new Coordinate($data['coord']); - $this->list = $this->createEntityList(AirPollution::class, $data['list']); - } - - public function getCoordinate(): Coordinate - { - return $this->coordinate; - } - - public function getList(): array - { - return $this->list; - } -} \ No newline at end of file diff --git a/src/Entity/AirPollution/AirQuality.php b/src/Entity/AirPollution/AirQuality.php index 8e4c929..ddc2d6c 100644 --- a/src/Entity/AirPollution/AirQuality.php +++ b/src/Entity/AirPollution/AirQuality.php @@ -2,12 +2,8 @@ namespace ProgrammatorDev\OpenWeatherMap\Entity\AirPollution; -use ProgrammatorDev\OpenWeatherMap\Entity\AirPollution\Util\AirQualityQualitativeNameTrait; - class AirQuality { - use AirQualityQualitativeNameTrait; - private int $index; private string $qualitativeName; @@ -15,7 +11,7 @@ class AirQuality public function __construct(array $data) { $this->index = $data['aqi']; - $this->qualitativeName = $this->getAirQualityQualitativeName($this->index); + $this->qualitativeName = $this->findQualitativeName($this->index); } public function getIndex(): int @@ -27,4 +23,17 @@ public function getQualitativeName(): string { return $this->qualitativeName; } + + private function findQualitativeName(int $index): string + { + // levels based on https://openweathermap.org/api/air-pollution + return match ($index) { + 0 => 'Undefined', + 1 => 'Good', + 2 => 'Fair', + 3 => 'Moderate', + 4 => 'Poor', + 5 => 'Very Poor' + }; + } } \ No newline at end of file diff --git a/src/Entity/AirPollution/Util/AirQualityQualitativeNameTrait.php b/src/Entity/AirPollution/Util/AirQualityQualitativeNameTrait.php deleted file mode 100644 index c6cdcdb..0000000 --- a/src/Entity/AirPollution/Util/AirQualityQualitativeNameTrait.php +++ /dev/null @@ -1,21 +0,0 @@ - 'Undefined', - 1 => 'Good', - 2 => 'Fair', - 3 => 'Moderate', - 4 => 'Poor', - 5 => 'Very poor' - ]; - - private function getAirQualityQualitativeName(int $index): string - { - return $this->airQualityIndex[$index]; - } -} \ No newline at end of file diff --git a/src/Entity/AtmosphericPressure.php b/src/Entity/AtmosphericPressure.php deleted file mode 100644 index 0c97e4e..0000000 --- a/src/Entity/AtmosphericPressure.php +++ /dev/null @@ -1,34 +0,0 @@ -pressure = $data['pressure']; - $this->seaLevelPressure = $data['sea_level'] ?? null; - $this->groundLevelPressure = $data['grnd_level'] ?? null; - } - - public function getPressure(): int - { - return $this->pressure; - } - - public function getSeaLevelPressure(): ?int - { - return $this->seaLevelPressure; - } - - public function getGroundLevelPressure(): ?int - { - return $this->groundLevelPressure; - } -} \ No newline at end of file diff --git a/src/Entity/WeatherCondition.php b/src/Entity/Condition.php similarity index 89% rename from src/Entity/WeatherCondition.php rename to src/Entity/Condition.php index 608efde..8f4e9d5 100644 --- a/src/Entity/WeatherCondition.php +++ b/src/Entity/Condition.php @@ -2,7 +2,7 @@ namespace ProgrammatorDev\OpenWeatherMap\Entity; -class WeatherCondition +class Condition { public const THUNDERSTORM = 'THUNDERSTORM'; public const DRIZZLE = 'DRIZZLE'; @@ -29,7 +29,7 @@ class WeatherCondition private Icon $icon; - private string $sysName; + private string $systemName; public function __construct(array $data) { @@ -37,7 +37,7 @@ public function __construct(array $data) $this->name = $data['main']; $this->description = $data['description']; $this->icon = new Icon($data); - $this->sysName = $this->findSysName($this->id); + $this->systemName = $this->findSystemName($this->id); } public function getId(): int @@ -60,15 +60,15 @@ public function getIcon(): Icon return $this->icon; } - public function getSysName(): string + public function getSystemName(): string { - return $this->sysName; + return $this->systemName; } /** * Find group based on this table https://openweathermap.org/weather-conditions */ - private function findSysName(int $id): string + private function findSystemName(int $id): string { return match ($id) { 200, 201, 202, 210, 211, 212, 221, 230, 231, 232 => self::THUNDERSTORM, diff --git a/src/Entity/Error.php b/src/Entity/Error.php deleted file mode 100644 index 7ea1b62..0000000 --- a/src/Entity/Error.php +++ /dev/null @@ -1,34 +0,0 @@ -code = $data['cod']; - $this->message = $data['message']; - $this->parameters = $data['parameters'] ?? null; - } - - public function getCode(): int - { - return $this->code; - } - - public function getMessage(): string - { - return $this->message; - } - - public function getParameters(): ?array - { - return $this->parameters; - } -} \ No newline at end of file diff --git a/src/Entity/Geocoding/ZipCodeLocation.php b/src/Entity/Geocoding/ZipLocation.php similarity index 97% rename from src/Entity/Geocoding/ZipCodeLocation.php rename to src/Entity/Geocoding/ZipLocation.php index 1225763..5bbe563 100644 --- a/src/Entity/Geocoding/ZipCodeLocation.php +++ b/src/Entity/Geocoding/ZipLocation.php @@ -4,21 +4,22 @@ use ProgrammatorDev\OpenWeatherMap\Entity\Coordinate; -class ZipCodeLocation +class ZipLocation { private string $zipCode; private string $name; - private Coordinate $coordinate; - private string $countryCode; + private Coordinate $coordinate; + public function __construct(array $data) { $this->zipCode = $data['zip']; $this->name = $data['name']; $this->countryCode = $data['country']; + $this->coordinate = new Coordinate([ 'lat' => $data['lat'], 'lon' => $data['lon'] @@ -35,13 +36,13 @@ public function getName(): string return $this->name; } - public function getCoordinate(): Coordinate + public function getCountryCode(): string { - return $this->coordinate; + return $this->countryCode; } - public function getCountryCode(): string + public function getCoordinate(): Coordinate { - return $this->countryCode; + return $this->coordinate; } } \ No newline at end of file diff --git a/src/Entity/Icon.php b/src/Entity/Icon.php index 7ae0949..7b179ba 100644 --- a/src/Entity/Icon.php +++ b/src/Entity/Icon.php @@ -4,16 +4,14 @@ class Icon { - private string $url = 'https://openweathermap.org/img/wn/%s@4x.png'; - private string $id; - private string $imageUrl; + private string $url; public function __construct(array $data) { $this->id = $data['icon']; - $this->imageUrl = \sprintf($this->url, $this->id); + $this->url = \sprintf('https://openweathermap.org/img/wn/%s@4x.png', $this->id); } public function getId(): string @@ -21,8 +19,8 @@ public function getId(): string return $this->id; } - public function getImageUrl(): string + public function getUrl(): string { - return $this->imageUrl; + return $this->url; } } \ No newline at end of file diff --git a/src/Entity/Location.php b/src/Entity/Location.php index 31b7080..1d5c171 100644 --- a/src/Entity/Location.php +++ b/src/Entity/Location.php @@ -4,6 +4,8 @@ class Location { + private Coordinate $coordinate; + private ?int $id; private ?string $name; @@ -16,8 +18,6 @@ class Location private ?int $population; - private Coordinate $coordinate; - private ?Timezone $timezone; private ?\DateTimeImmutable $sunriseAt; @@ -30,15 +30,47 @@ public function __construct(array $data) 'lat' => $data['lat'], 'lon' => $data['lon'] ]); - $this->id = !empty($data['id']) ? $data['id'] : null; - $this->name = !empty($data['name']) ? $data['name'] : null; - $this->state = !empty($data['state']) ? $data['state'] : null; - $this->countryCode = !empty($data['country']) ? $data['country'] : null; - $this->localNames = !empty($data['local_names']) ? $data['local_names'] : null; - $this->population = !empty($data['population']) ? $data['population'] : null; - $this->sunriseAt = !empty($data['sunrise']) ? \DateTimeImmutable::createFromFormat('U', $data['sunrise'], new \DateTimeZone('UTC')) : null; - $this->sunsetAt = !empty($data['sunset']) ? \DateTimeImmutable::createFromFormat('U', $data['sunset'], new \DateTimeZone('UTC')) : null; - $this->timezone = isset($data['timezone_offset']) ? new Timezone(['timezone_offset' => $data['timezone_offset']]) : null; + + // set no null if it is 0 + $this->id = !empty($data['id']) + ? $data['id'] + : null; + + // set to null if it is an empty string + $this->name = !empty($data['name']) + ? $data['name'] + : null; + + $this->state = $data['state'] ?? null; + + // set to null if it is an empty string + $this->countryCode = !empty($data['country']) + ? $data['country'] + : null; + + $this->localNames = $data['local_names'] ?? null; + + // set to null if it is 0 + $this->population = !empty($data['population']) + ? $data['population'] + : null; + + $this->timezone = isset($data['timezone_offset']) + ? new Timezone(['timezone_offset' => $data['timezone_offset']]) + : null; + + $this->sunriseAt = isset($data['sunrise']) + ? \DateTimeImmutable::createFromFormat('U', $data['sunrise']) + : null; + + $this->sunsetAt = isset($data['sunset']) + ? \DateTimeImmutable::createFromFormat('U', $data['sunset']) + : null; + } + + public function getCoordinate(): Coordinate + { + return $this->coordinate; } public function getId(): ?int @@ -69,6 +101,7 @@ public function getLocalNames(): ?array public function getLocalName(string $countryCode): ?string { $countryCode = strtolower($countryCode); + return $this->localNames[$countryCode] ?? null; } @@ -77,21 +110,22 @@ public function getPopulation(): ?int return $this->population; } - public function getCoordinate(): Coordinate - { - return $this->coordinate; - } - public function getTimezone(): ?Timezone { return $this->timezone; } + /** + * Sunrise date in UTC + */ public function getSunriseAt(): ?\DateTimeImmutable { return $this->sunriseAt; } + /** + * Sunset date in UTC + */ public function getSunsetAt(): ?\DateTimeImmutable { return $this->sunsetAt; diff --git a/src/Entity/OneCall/Alert.php b/src/Entity/OneCall/Alert.php index 1931d0c..ff15e44 100644 --- a/src/Entity/OneCall/Alert.php +++ b/src/Entity/OneCall/Alert.php @@ -18,12 +18,10 @@ class Alert public function __construct(array $data) { - $timezoneUtc = new \DateTimeZone('UTC'); - $this->senderName = $data['sender_name']; $this->eventName = $data['event']; - $this->startsAt = \DateTimeImmutable::createFromFormat('U', $data['start'], $timezoneUtc); - $this->endsAt = \DateTimeImmutable::createFromFormat('U', $data['end'], $timezoneUtc); + $this->startsAt = \DateTimeImmutable::createFromFormat('U', $data['start']); + $this->endsAt = \DateTimeImmutable::createFromFormat('U', $data['end']); $this->description = $data['description']; $this->tags = $data['tags']; } diff --git a/src/Entity/OneCall/BaseWeather.php b/src/Entity/OneCall/BaseWeather.php new file mode 100644 index 0000000..dfa9f69 --- /dev/null +++ b/src/Entity/OneCall/BaseWeather.php @@ -0,0 +1,106 @@ +dateTime = \DateTimeImmutable::createFromFormat('U', $data['dt']); + $this->atmosphericPressure = $data['pressure']; + $this->humidity = $data['humidity']; + $this->dewPoint = $data['dew_point']; + $this->ultraVioletIndex = $data['uvi'] ?? null; + $this->cloudiness = $data['clouds']; + + $this->wind = new Wind([ + 'speed' => $data['wind_speed'], + 'deg' => $data['wind_deg'], + 'gust' => $data['wind_gust'] ?? null + ]); + + $this->conditions = $this->createEntityList(Condition::class, $data['weather']); + $this->rainVolume = $data['rain']['1h'] ?? $data['rain']['3h'] ?? $data['rain'] ?? null; + $this->snowVolume = $data['snow']['1h'] ?? $data['snow']['3h'] ?? $data['snow'] ?? null; + } + + /** + * DateTime in UTC + */ + public function getDateTime(): \DateTimeImmutable + { + return $this->dateTime; + } + + public function getAtmosphericPressure(): int + { + return $this->atmosphericPressure; + } + + public function getHumidity(): int + { + return $this->humidity; + } + + public function getDewPoint(): float + { + return $this->dewPoint; + } + + public function getUltraVioletIndex(): ?float + { + return $this->ultraVioletIndex; + } + + public function getCloudiness(): int + { + return $this->cloudiness; + } + + public function getWind(): Wind + { + return $this->wind; + } + + public function getConditions(): array + { + return $this->conditions; + } + + public function getRainVolume(): ?float + { + return $this->rainVolume; + } + + public function getSnowVolume(): ?float + { + return $this->snowVolume; + } +} \ No newline at end of file diff --git a/src/Entity/OneCall/DayData.php b/src/Entity/OneCall/DayData.php new file mode 100644 index 0000000..11cd28e --- /dev/null +++ b/src/Entity/OneCall/DayData.php @@ -0,0 +1,96 @@ +temperature = new Temperature($data['temp']); + $this->temperatureFeelsLike = new Temperature($data['feels_like']); + $this->precipitationProbability = \round($data['pop'] * 100); + $this->summary = $data['summary']; + $this->moonPhase = new MoonPhase($data); + $this->moonriseAt = \DateTimeImmutable::createFromFormat('U', $data['moonrise']); + $this->moonsetAt = \DateTimeImmutable::createFromFormat('U', $data['moonset']); + $this->sunriseAt = \DateTimeImmutable::createFromFormat('U', $data['sunrise']); + $this->sunsetAt = \DateTimeImmutable::createFromFormat('U', $data['sunset']); + } + + public function getTemperature(): Temperature + { + return $this->temperature; + } + + public function getTemperatureFeelsLike(): Temperature + { + return $this->temperatureFeelsLike; + } + + public function getPrecipitationProbability(): int + { + return $this->precipitationProbability; + } + + public function getSummary(): string + { + return $this->summary; + } + + public function getMoonPhase(): MoonPhase + { + return $this->moonPhase; + } + + /** + * Moonrise date in UTC + */ + public function getMoonriseAt(): \DateTimeImmutable + { + return $this->moonriseAt; + } + + /** + * Moonset date in UTC + */ + public function getMoonsetAt(): \DateTimeImmutable + { + return $this->moonsetAt; + } + + /** + * Sunrise date in UTC + */ + public function getSunriseAt(): \DateTimeImmutable + { + return $this->sunriseAt; + } + + /** + * Sunset date in UTC + */ + public function getSunsetAt(): \DateTimeImmutable + { + return $this->sunsetAt; + } +} \ No newline at end of file diff --git a/src/Entity/OneCall/HourData.php b/src/Entity/OneCall/HourData.php new file mode 100644 index 0000000..e417dc2 --- /dev/null +++ b/src/Entity/OneCall/HourData.php @@ -0,0 +1,44 @@ +temperature = $data['temp']; + $this->temperatureFeelsLike = $data['feels_like']; + $this->visibility = $data['visibility']; + $this->precipitationProbability = \round($data['pop'] * 100); + } + + public function getTemperature(): float + { + return $this->temperature; + } + + public function getTemperatureFeelsLike(): float + { + return $this->temperatureFeelsLike; + } + + public function getVisibility(): int + { + return $this->visibility; + } + + public function getPrecipitationProbability(): int + { + return $this->precipitationProbability; + } +} \ No newline at end of file diff --git a/src/Entity/OneCall/MinuteForecast.php b/src/Entity/OneCall/MinuteData.php similarity index 87% rename from src/Entity/OneCall/MinuteForecast.php rename to src/Entity/OneCall/MinuteData.php index 5f980f4..3273731 100644 --- a/src/Entity/OneCall/MinuteForecast.php +++ b/src/Entity/OneCall/MinuteData.php @@ -2,7 +2,7 @@ namespace ProgrammatorDev\OpenWeatherMap\Entity\OneCall; -class MinuteForecast +class MinuteData { private \DateTimeImmutable $dateTime; @@ -10,10 +10,13 @@ class MinuteForecast public function __construct(array $data) { - $this->dateTime = \DateTimeImmutable::createFromFormat('U', $data['dt'], new \DateTimeZone('UTC')); + $this->dateTime = \DateTimeImmutable::createFromFormat('U', $data['dt']); $this->precipitation = $data['precipitation']; } + /** + * DateTime in UTC + */ public function getDateTime(): \DateTimeImmutable { return $this->dateTime; diff --git a/src/Entity/MoonPhase.php b/src/Entity/OneCall/MoonPhase.php similarity index 77% rename from src/Entity/MoonPhase.php rename to src/Entity/OneCall/MoonPhase.php index 6047b81..3d036f8 100644 --- a/src/Entity/MoonPhase.php +++ b/src/Entity/OneCall/MoonPhase.php @@ -1,6 +1,6 @@ value = $data['moon_phase']; - $this->sysName = $this->findSysName($this->value); - $this->name = ucfirst(strtolower(str_replace('_', ' ', $this->sysName))); + $this->systemName = $this->findSystemName($this->value); + $this->name = \ucwords(\strtolower(\str_replace('_', ' ', $this->systemName))); } public function getValue(): float @@ -36,12 +36,12 @@ public function getName(): string return $this->name; } - public function getSysName(): string + public function getSystemName(): string { - return $this->sysName; + return $this->systemName; } - private function findSysName(float $value): string + private function findSystemName(float $value): string { return match (true) { $value > 0 && $value < 0.25 => self::WAXING_CRESCENT, diff --git a/src/Entity/OneCall/OneCall.php b/src/Entity/OneCall/OneCall.php deleted file mode 100644 index 58f4561..0000000 --- a/src/Entity/OneCall/OneCall.php +++ /dev/null @@ -1,76 +0,0 @@ -coordinate = new Coordinate($data); - $this->timezone = new Timezone($data); - $this->current = new Weather($data['current']); - $this->minutelyForecast = !empty($data['minutely']) ? $this->createEntityList(MinuteForecast::class, $data['minutely']) : null; - $this->hourlyForecast = $this->createEntityList(Weather::class, $data['hourly']); - $this->dailyForecast = $this->createEntityList(Weather::class, $data['daily']); - $this->alerts = !empty($data['alerts']) ? $this->createEntityList(Alert::class, $data['alerts']) : null; - } - - public function getCoordinate(): Coordinate - { - return $this->coordinate; - } - - public function getTimezone(): Timezone - { - return $this->timezone; - } - - public function getCurrent(): Weather - { - return $this->current; - } - - public function getMinutelyForecast(): ?array - { - return $this->minutelyForecast; - } - - public function getHourlyForecast(): array - { - return $this->hourlyForecast; - } - - public function getDailyForecast(): array - { - return $this->dailyForecast; - } - - public function getAlerts(): ?array - { - return $this->alerts; - } -} \ No newline at end of file diff --git a/src/Entity/Temperature.php b/src/Entity/OneCall/Temperature.php similarity index 94% rename from src/Entity/Temperature.php rename to src/Entity/OneCall/Temperature.php index f5c52f6..94d13f7 100644 --- a/src/Entity/Temperature.php +++ b/src/Entity/OneCall/Temperature.php @@ -1,6 +1,6 @@ dateTime = \DateTimeImmutable::createFromFormat('U', $data['dt'], $timezoneUtc); - $this->sunriseAt = !empty($data['sunrise']) ? \DateTimeImmutable::createFromFormat('U', $data['sunrise'], $timezoneUtc) : null; - $this->sunsetAt = !empty($data['sunset']) ? \DateTimeImmutable::createFromFormat('U', $data['sunset'], $timezoneUtc) : null; - $this->moonriseAt = !empty($data['moonrise']) ? \DateTimeImmutable::createFromFormat('U', $data['moonrise'], $timezoneUtc) : null; - $this->moonsetAt = !empty($data['moonset']) ? \DateTimeImmutable::createFromFormat('U', $data['moonset'], $timezoneUtc) : null; - $this->moonPhase = !empty($data['moon_phase']) ? new MoonPhase($data) : null; - $this->temperature = is_array($data['temp']) ? new Temperature($data['temp']) : $data['temp']; - $this->temperatureFeelsLike = is_array($data['feels_like']) ? new Temperature($data['feels_like']) : $data['feels_like']; - $this->description = $data['summary'] ?? null; - $this->atmosphericPressure = $data['pressure']; - $this->humidity = $data['humidity']; - $this->dewPoint = $data['dew_point']; - $this->ultraVioletIndex = $data['uvi'] ?? null; - $this->cloudiness = $data['clouds']; - $this->visibility = $data['visibility'] ?? null; - $this->wind = new Wind([ - 'speed' => $data['wind_speed'], - 'deg' => $data['wind_deg'], - 'gust' => $data['wind_gust'] ?? null + $this->coordinate = new Coordinate([ + 'lat' => $data['lat'], + 'lon' => $data['lon'] ]); - $this->precipitationProbability = isset($data['pop']) ? round($data['pop'] * 100) : null; - $this->rain = !empty($data['rain']) - ? is_array($data['rain']) ? new Rain($data['rain']) : $data['rain'] - : null; - $this->snow = !empty($data['snow']) - ? is_array($data['snow']) ? new Snow($data['snow']) : $data['snow'] - : null; - $this->weatherConditions = $this->createEntityList(WeatherCondition::class, $data['weather']); - } - - public function getDateTime(): \DateTimeImmutable - { - return $this->dateTime; - } - - public function getSunriseAt(): ?\DateTimeImmutable - { - return $this->sunriseAt; - } - - public function getSunsetAt(): ?\DateTimeImmutable - { - return $this->sunsetAt; - } - - public function getMoonriseAt(): ?\DateTimeImmutable - { - return $this->moonriseAt; - } - - public function getMoonsetAt(): ?\DateTimeImmutable - { - return $this->moonsetAt; - } - - public function getMoonPhase(): ?MoonPhase - { - return $this->moonPhase; - } - - public function getTemperature(): Temperature|float - { - return $this->temperature; - } - - public function getTemperatureFeelsLike(): Temperature|float - { - return $this->temperatureFeelsLike; - } - public function getDescription(): ?string - { - return $this->description; - } + $this->timezone = new Timezone([ + 'timezone' => $data['timezone'], + 'timezone_offset' => $data['timezone_offset'] + ]); - public function getAtmosphericPressure(): int - { - return $this->atmosphericPressure; - } + $this->current = new WeatherData($data['current']); - public function getHumidity(): int - { - return $this->humidity; - } + $this->minutelyForecast = isset($data['minutely']) + ? $this->createEntityList(MinuteData::class, $data['minutely']) + : null; - public function getDewPoint(): float - { - return $this->dewPoint; - } + $this->hourlyForecast = $this->createEntityList(HourData::class, $data['hourly']); + $this->dailyForecast = $this->createEntityList(DayData::class, $data['daily']); - public function getUltraVioletIndex(): ?float - { - return $this->ultraVioletIndex; + $this->alerts = isset($data['alerts']) + ? $this->createEntityList(Alert::class, $data['alerts']) + : null; } - public function getCloudiness(): int + public function getCoordinate(): Coordinate { - return $this->cloudiness; + return $this->coordinate; } - public function getVisibility(): ?int + public function getTimezone(): Timezone { - return $this->visibility; + return $this->timezone; } - public function getWind(): Wind + public function getCurrent(): WeatherData { - return $this->wind; + return $this->current; } - public function getPrecipitationProbability(): ?int + public function getMinutelyForecast(): ?array { - return $this->precipitationProbability; + return $this->minutelyForecast; } - public function getRain(): Rain|float|null + public function getHourlyForecast(): array { - return $this->rain; + return $this->hourlyForecast; } - public function getSnow(): Snow|float|null + public function getDailyForecast(): array { - return $this->snow; + return $this->dailyForecast; } - public function getWeatherConditions(): array + public function getAlerts(): ?array { - return $this->weatherConditions; + return $this->alerts; } } \ No newline at end of file diff --git a/src/Entity/OneCall/WeatherData.php b/src/Entity/OneCall/WeatherData.php new file mode 100644 index 0000000..2811290 --- /dev/null +++ b/src/Entity/OneCall/WeatherData.php @@ -0,0 +1,64 @@ +temperature = $data['temp']; + $this->temperatureFeelsLike = $data['feels_like']; + $this->visibility = $data['visibility'] ?? null; + + $this->sunriseAt = isset($data['sunrise']) + ? \DateTimeImmutable::createFromFormat('U', $data['sunrise']) + : null; + + $this->sunsetAt = isset($data['sunset']) + ? \DateTimeImmutable::createFromFormat('U', $data['sunset']) + : null; + } + + public function getTemperature(): float + { + return $this->temperature; + } + + public function getTemperatureFeelsLike(): float + { + return $this->temperatureFeelsLike; + } + + public function getVisibility(): ?int + { + return $this->visibility; + } + + /** + * Sunrise date in UTC + */ + public function getSunriseAt(): ?\DateTimeImmutable + { + return $this->sunriseAt; + } + + /** + * Sunset date in UTC + */ + public function getSunsetAt(): ?\DateTimeImmutable + { + return $this->sunsetAt; + } +} \ No newline at end of file diff --git a/src/Entity/OneCall/WeatherLocation.php b/src/Entity/OneCall/WeatherMoment.php similarity index 62% rename from src/Entity/OneCall/WeatherLocation.php rename to src/Entity/OneCall/WeatherMoment.php index c5101bd..dc3665e 100644 --- a/src/Entity/OneCall/WeatherLocation.php +++ b/src/Entity/OneCall/WeatherMoment.php @@ -5,7 +5,7 @@ use ProgrammatorDev\OpenWeatherMap\Entity\Coordinate; use ProgrammatorDev\OpenWeatherMap\Entity\Timezone; -class WeatherLocation extends Weather +class WeatherMoment extends WeatherData { private Coordinate $coordinate; @@ -15,8 +15,15 @@ public function __construct(array $data) { parent::__construct($data['data'][0]); - $this->coordinate = new Coordinate($data); - $this->timezone = new Timezone($data); + $this->coordinate = new Coordinate([ + 'lat' => $data['lat'], + 'lon' => $data['lon'] + ]); + + $this->timezone = new Timezone([ + 'timezone' => $data['timezone'], + 'timezone_offset' => $data['timezone_offset'] + ]); } public function getCoordinate(): Coordinate diff --git a/src/Entity/OneCall/WeatherAggregate.php b/src/Entity/OneCall/WeatherSummary.php similarity index 80% rename from src/Entity/OneCall/WeatherAggregate.php rename to src/Entity/OneCall/WeatherSummary.php index 41f60ba..4396839 100644 --- a/src/Entity/OneCall/WeatherAggregate.php +++ b/src/Entity/OneCall/WeatherSummary.php @@ -3,11 +3,10 @@ namespace ProgrammatorDev\OpenWeatherMap\Entity\OneCall; use ProgrammatorDev\OpenWeatherMap\Entity\Coordinate; -use ProgrammatorDev\OpenWeatherMap\Entity\Temperature; use ProgrammatorDev\OpenWeatherMap\Entity\Timezone; use ProgrammatorDev\OpenWeatherMap\Entity\Wind; -class WeatherAggregate +class WeatherSummary { private Coordinate $coordinate; @@ -30,13 +29,20 @@ class WeatherAggregate public function __construct(array $data) { $this->coordinate = new Coordinate($data); + $this->timezone = new Timezone([ 'timezone_offset' => \DateTimeImmutable::createFromFormat('P', $data['tz'])->getOffset() ]); - $this->dateTime = \DateTimeImmutable::createFromFormat('Y-m-d', $data['date'], new \DateTimeZone('UTC'))->setTime(0, 0); - $this->cloudiness = round($data['cloud_cover']['afternoon']); - $this->humidity = round($data['humidity']['afternoon']); + + $this->dateTime = \DateTimeImmutable::createFromFormat( + 'Y-m-d H:i:s P', + \sprintf('%s 00:00:00 %s', $data['date'], $data['tz']) + ); + + $this->cloudiness = \round($data['cloud_cover']['afternoon']); + $this->humidity = \round($data['humidity']['afternoon']); $this->precipitation = $data['precipitation']['total']; + $this->temperature = new Temperature([ 'morn' => $data['temperature']['morning'], 'day' => $data['temperature']['afternoon'], @@ -45,10 +51,12 @@ public function __construct(array $data) 'min' => $data['temperature']['min'], 'max' => $data['temperature']['max'] ]); - $this->atmosphericPressure = round($data['pressure']['afternoon']); + + $this->atmosphericPressure = \round($data['pressure']['afternoon']); + $this->wind = new Wind([ 'speed' => $data['wind']['max']['speed'], - 'deg' => round($data['wind']['max']['direction']) + 'deg' => \round($data['wind']['max']['direction']) ]); } @@ -62,6 +70,9 @@ public function getTimezone(): Timezone return $this->timezone; } + /** + * DateTime in UTC + */ public function getDateTime(): \DateTimeImmutable { return $this->dateTime; diff --git a/src/Entity/Rain.php b/src/Entity/Rain.php deleted file mode 100644 index 70e43e0..0000000 --- a/src/Entity/Rain.php +++ /dev/null @@ -1,5 +0,0 @@ -identifier = $data['timezone'] ?? null; $this->offset = $data['timezone_offset']; + $this->identifier = $data['timezone'] ?? null; } - public function getIdentifier(): ?string + public function getOffset(): int { - return $this->identifier; + return $this->offset; } - public function getOffset(): int + public function getIdentifier(): ?string { - return $this->offset; + return $this->identifier; } } \ No newline at end of file diff --git a/src/Entity/Volume.php b/src/Entity/Volume.php deleted file mode 100644 index a594e06..0000000 --- a/src/Entity/Volume.php +++ /dev/null @@ -1,32 +0,0 @@ -lastOneHourVolume = $data['1h'] ?? null; - $this->lastThreeHoursVolume = $data['3h'] ?? null; - } - - /** - * Volume for the last 1 hour, in millimetres (mm) - */ - public function getLastOneHourVolume(): ?float - { - return $this->lastOneHourVolume; - } - - /** - * Volume for the last 3 hours, in millimetres (mm) - */ - public function getLastThreeHoursVolume(): ?float - { - return $this->lastThreeHoursVolume; - } -} \ No newline at end of file diff --git a/src/Entity/Weather/Weather.php b/src/Entity/Weather/Weather.php index adadb79..5e43e1b 100644 --- a/src/Entity/Weather/Weather.php +++ b/src/Entity/Weather/Weather.php @@ -2,134 +2,30 @@ namespace ProgrammatorDev\OpenWeatherMap\Entity\Weather; -use ProgrammatorDev\OpenWeatherMap\Entity\AtmosphericPressure; -use ProgrammatorDev\OpenWeatherMap\Entity\Rain; -use ProgrammatorDev\OpenWeatherMap\Entity\Snow; -use ProgrammatorDev\OpenWeatherMap\Entity\WeatherCondition; -use ProgrammatorDev\OpenWeatherMap\Entity\Wind; -use ProgrammatorDev\OpenWeatherMap\Util\EntityListTrait; +use ProgrammatorDev\OpenWeatherMap\Entity\Location; -class Weather +class Weather extends WeatherData { - use EntityListTrait; - - private float $temperature; - - private float $temperatureFeelsLike; - - private float $minTemperature; - - private float $maxTemperature; - - private int $humidity; - - private int $cloudiness; - - private int $visibility; - - /** @var WeatherCondition[] */ - private array $weatherConditions; - - private Wind $wind; - - private ?int $precipitationProbability; - - private ?Rain $rain; - - private ?Snow $snow; - - private AtmosphericPressure $atmosphericPressure; - - private \DateTimeImmutable $dateTime; + private Location $location; public function __construct(array $data) { - $this->dateTime = \DateTimeImmutable::createFromFormat('U', $data['dt'], new \DateTimeZone('UTC')); - $this->temperature = $data['main']['temp']; - $this->temperatureFeelsLike = $data['main']['feels_like']; - $this->minTemperature = $data['main']['temp_min']; - $this->maxTemperature = $data['main']['temp_max']; - $this->humidity = $data['main']['humidity']; - $this->cloudiness = $data['clouds']['all']; - $this->visibility = $data['visibility']; - $this->weatherConditions = $this->createEntityList(WeatherCondition::class, $data['weather']); - $this->atmosphericPressure = new AtmosphericPressure($data['main']); - $this->wind = new Wind($data['wind']); - $this->precipitationProbability = isset($data['pop']) ? round($data['pop'] * 100) : null; - $this->rain = !empty($data['rain']) ? new Rain($data['rain']) : null; - $this->snow = !empty($data['snow']) ? new Snow($data['snow']) : null; - } - - public function getTemperature(): float - { - return $this->temperature; - } - - public function getTemperatureFeelsLike(): float - { - return $this->temperatureFeelsLike; - } - - public function getMinTemperature(): float - { - return $this->minTemperature; - } - - public function getMaxTemperature(): float - { - return $this->maxTemperature; - } - - public function getHumidity(): int - { - return $this->humidity; - } - - public function getCloudiness(): int - { - return $this->cloudiness; - } - - public function getVisibility(): int - { - return $this->visibility; - } - - public function getWeatherConditions(): array - { - return $this->weatherConditions; - } + parent::__construct($data); - public function getWind(): Wind - { - return $this->wind; - } - - /** - * Probability of precipitation, in percentage (%) - */ - public function getPrecipitationProbability(): ?int - { - return $this->precipitationProbability; - } - - public function getRain(): ?Rain - { - return $this->rain; - } - - public function getSnow(): ?Snow - { - return $this->snow; - } - - public function getAtmosphericPressure(): AtmosphericPressure - { - return $this->atmosphericPressure; + $this->location = new Location([ + 'lat' => $data['coord']['lat'], + 'lon' => $data['coord']['lon'], + 'id' => $data['id'] ?? null, + 'name' => $data['name'] ?? null, + 'country' => $data['sys']['country'] ?? null, + 'sunrise' => $data['sys']['sunrise'], + 'sunset' => $data['sys']['sunset'], + 'timezone_offset' => $data['timezone'] + ]); } - public function getDateTime(): \DateTimeImmutable + public function getLocation(): Location { - return $this->dateTime; + return $this->location; } } \ No newline at end of file diff --git a/src/Entity/Weather/WeatherLocationList.php b/src/Entity/Weather/WeatherCollection.php similarity index 59% rename from src/Entity/Weather/WeatherLocationList.php rename to src/Entity/Weather/WeatherCollection.php index 9f7fca0..ae7486d 100644 --- a/src/Entity/Weather/WeatherLocationList.php +++ b/src/Entity/Weather/WeatherCollection.php @@ -3,34 +3,36 @@ namespace ProgrammatorDev\OpenWeatherMap\Entity\Weather; use ProgrammatorDev\OpenWeatherMap\Entity\Location; -use ProgrammatorDev\OpenWeatherMap\Util\EntityListTrait; +use ProgrammatorDev\OpenWeatherMap\Util\EntityTrait; -class WeatherLocationList +class WeatherCollection { - use EntityListTrait; + use EntityTrait; private int $numResults; private Location $location; - /** @var Weather[] */ - private array $list; + /** @var WeatherData[] */ + private array $data; public function __construct(array $data) { $this->numResults = $data['cnt']; + $this->location = new Location([ - 'id' => $data['city']['id'], - 'name' => $data['city']['name'], - 'country' => $data['city']['country'], - 'population' => $data['city']['population'], 'lat' => $data['city']['coord']['lat'], 'lon' => $data['city']['coord']['lon'], + 'id' => $data['city']['id'] ?? null, + 'name' => $data['city']['name'] ?? null, + 'country' => $data['city']['country'] ?? null, + 'population' => $data['city']['population'] ?? null, 'sunrise' => $data['city']['sunrise'], 'sunset' => $data['city']['sunset'], 'timezone_offset' => $data['city']['timezone'] ]); - $this->list = $this->createEntityList(Weather::class, $data['list']); + + $this->data = $this->createEntityList(WeatherData::class, $data['list']); } public function getNumResults(): int @@ -43,8 +45,8 @@ public function getLocation(): Location return $this->location; } - public function getList(): array + public function getData(): array { - return $this->list; + return $this->data; } } \ No newline at end of file diff --git a/src/Entity/Weather/WeatherData.php b/src/Entity/Weather/WeatherData.php new file mode 100644 index 0000000..fae2605 --- /dev/null +++ b/src/Entity/Weather/WeatherData.php @@ -0,0 +1,149 @@ +dateTime = \DateTimeImmutable::createFromFormat('U', $data['dt']); + $this->temperature = $data['main']['temp']; + $this->temperatureFeelsLike = $data['main']['feels_like']; + $this->minTemperature = $data['main']['temp_min']; + $this->maxTemperature = $data['main']['temp_max']; + $this->humidity = $data['main']['humidity']; + $this->cloudiness = $data['clouds']['all']; + $this->visibility = $data['visibility']; + $this->atmosphericPressure = $data['main']['pressure']; + $this->conditions = $this->createEntityList(Condition::class, $data['weather']); + $this->wind = new Wind($data['wind']); + + $this->precipitationProbability = isset($data['pop']) + ? \round($data['pop'] * 100) + : null; + + $this->rainVolume = $data['rain']['1h'] ?? $data['rain']['3h'] ?? null; + $this->snowVolume = $data['snow']['1h'] ?? $data['snow']['3h'] ?? null; + } + + /** + * DateTime in UTC + */ + public function getDateTime(): \DateTimeImmutable + { + return $this->dateTime; + } + + public function getTemperature(): float + { + return $this->temperature; + } + + public function getTemperatureFeelsLike(): float + { + return $this->temperatureFeelsLike; + } + + public function getMinTemperature(): float + { + return $this->minTemperature; + } + + public function getMaxTemperature(): float + { + return $this->maxTemperature; + } + + public function getHumidity(): int + { + return $this->humidity; + } + + public function getCloudiness(): int + { + return $this->cloudiness; + } + + /** + * Visibility, meters + * Maximum value is 10000 + */ + public function getVisibility(): int + { + return $this->visibility; + } + + /** + * Atmospheric pressure on the sea level, hPa + */ + public function getAtmosphericPressure(): int + { + return $this->atmosphericPressure; + } + + public function getConditions(): array + { + return $this->conditions; + } + + public function getWind(): Wind + { + return $this->wind; + } + + public function getPrecipitationProbability(): ?int + { + return $this->precipitationProbability; + } + + /** + * Rain volume, mm + */ + public function getRainVolume(): ?float + { + return $this->rainVolume; + } + + /** + * Snow volume, mm + */ + public function getSnowVolume(): ?float + { + return $this->snowVolume; + } +} \ No newline at end of file diff --git a/src/Entity/Weather/WeatherLocation.php b/src/Entity/Weather/WeatherLocation.php deleted file mode 100644 index c7aa42f..0000000 --- a/src/Entity/Weather/WeatherLocation.php +++ /dev/null @@ -1,31 +0,0 @@ -location = new Location([ - 'id' => $data['id'], - 'name' => $data['name'], - 'country' => $data['sys']['country'], - 'lat' => $data['coord']['lat'], - 'lon' => $data['coord']['lon'], - 'sunrise' => $data['sys']['sunrise'], - 'sunset' => $data['sys']['sunset'], - 'timezone_offset' => $data['timezone'] - ]); - } - - public function getLocation(): Location - { - return $this->location; - } -} \ No newline at end of file diff --git a/src/Entity/Wind.php b/src/Entity/Wind.php index 6c75bde..77ae282 100644 --- a/src/Entity/Wind.php +++ b/src/Entity/Wind.php @@ -23,7 +23,7 @@ public function getSpeed(): float } /** - * Wind direction, in degrees + * Wind direction, degrees */ public function getDirection(): int { diff --git a/src/Exception/ApiErrorException.php b/src/Exception/ApiErrorException.php index ec01111..d10f5f3 100644 --- a/src/Exception/ApiErrorException.php +++ b/src/Exception/ApiErrorException.php @@ -2,17 +2,15 @@ namespace ProgrammatorDev\OpenWeatherMap\Exception; -use ProgrammatorDev\OpenWeatherMap\Entity\Error; - class ApiErrorException extends \Exception { private ?array $parameters; - public function __construct(Error $error, \Throwable $previous = null) + public function __construct(array $error) { - parent::__construct($error->getMessage(), $error->getCode(), $previous); + parent::__construct($error['message'], $error['cod']); - $this->parameters = $error->getParameters(); + $this->parameters = $error['parameters'] ?? null; } public function getParameters(): ?array diff --git a/src/HttpClient/HttpClientBuilder.php b/src/HttpClient/HttpClientBuilder.php deleted file mode 100644 index cd2f22b..0000000 --- a/src/HttpClient/HttpClientBuilder.php +++ /dev/null @@ -1,66 +0,0 @@ -client ??= Psr18ClientDiscovery::find(); - $this->requestFactory ??= Psr17FactoryDiscovery::findRequestFactory(); - $this->streamFactory ??= Psr17FactoryDiscovery::findStreamFactory(); - } - - public function addPlugin(Plugin $plugin): void - { - $this->plugins[$plugin::class] = $plugin; - } - - public function getPlugin(string $className): Plugin - { - return $this->plugins[$className]; - } - - public function getHttpClient(): HttpMethodsClient - { - $this->addPlugin(new HeaderDefaultsPlugin([ - 'Content-Type' => 'application/json', - 'Accept' => 'application/json' - ])); - - $pluginClientFactory = new PluginClientFactory(); - $client = $pluginClientFactory->createClient($this->client, $this->plugins); - - return new HttpMethodsClient( - $client, - $this->requestFactory, - $this->streamFactory - ); - } - - public function getRequestFactory(): ?RequestFactoryInterface - { - return $this->requestFactory; - } - - public function getStreamFactory(): ?StreamFactoryInterface - { - return $this->streamFactory; - } -} \ No newline at end of file diff --git a/src/HttpClient/Listener/LoggerCacheListener.php b/src/HttpClient/Listener/LoggerCacheListener.php deleted file mode 100644 index ce3838d..0000000 --- a/src/HttpClient/Listener/LoggerCacheListener.php +++ /dev/null @@ -1,56 +0,0 @@ -logger->info( - $this->formatMessage($request, 'Cache hit'), [ - 'expires' => $cacheItem->get()['expiresAt'], - 'key' => $cacheItem->getKey() - ] - ); - } - // If response was cached - else if ($cacheItem !== null) { - $this->logger->info( - $this->formatMessage($request, 'Response was cached'), [ - 'expires' => $cacheItem->get()['expiresAt'], - 'key' => $cacheItem->getKey() - ] - ); - } - // If request is not cachable (invalid method, etc.) - else { - $this->logger->info( - $this->formatMessage($request, 'Request not cachable') - ); - } - - return $response; - } - - private function formatMessage(RequestInterface $request, string $message): string - { - return \sprintf( - '%s: %s %s %s', - $message, - $request->getMethod(), - $request->getUri(), - $request->getProtocolVersion() - ); - } -} \ No newline at end of file diff --git a/src/HttpClient/ResponseMediator.php b/src/HttpClient/ResponseMediator.php deleted file mode 100644 index dca65a1..0000000 --- a/src/HttpClient/ResponseMediator.php +++ /dev/null @@ -1,13 +0,0 @@ -getBody()->getContents(), true); - } -} \ No newline at end of file diff --git a/src/Language/Language.php b/src/Language/Language.php index 58ca1dc..a92c795 100644 --- a/src/Language/Language.php +++ b/src/Language/Language.php @@ -2,11 +2,11 @@ namespace ProgrammatorDev\OpenWeatherMap\Language; -use ProgrammatorDev\OpenWeatherMap\Util\ClassConstantsTrait; +use ProgrammatorDev\OpenWeatherMap\Util\ReflectionTrait; class Language { - use ClassConstantsTrait; + use ReflectionTrait; public const AFRIKAANS = 'af'; public const ALBANIAN = 'al'; @@ -55,7 +55,7 @@ class Language public const VIETNAMESE = 'vi'; public const ZULU = 'zu'; - public static function getList(): array + public static function getOptions(): array { return (new Language)->getClassConstants(self::class); } diff --git a/src/OpenWeatherMap.php b/src/OpenWeatherMap.php index 1934b15..427f5b0 100644 --- a/src/OpenWeatherMap.php +++ b/src/OpenWeatherMap.php @@ -2,37 +2,104 @@ namespace ProgrammatorDev\OpenWeatherMap; -use ProgrammatorDev\OpenWeatherMap\Endpoint\AirPollutionEndpoint; -use ProgrammatorDev\OpenWeatherMap\Endpoint\GeocodingEndpoint; -use ProgrammatorDev\OpenWeatherMap\Endpoint\OneCallEndpoint; -use ProgrammatorDev\OpenWeatherMap\Endpoint\WeatherEndpoint; +use Http\Message\Authentication\QueryParam; +use ProgrammatorDev\Api\Api; +use ProgrammatorDev\Api\Event\PostRequestEvent; +use ProgrammatorDev\Api\Event\ResponseContentsEvent; +use ProgrammatorDev\OpenWeatherMap\Exception\BadRequestException; +use ProgrammatorDev\OpenWeatherMap\Exception\NotFoundException; +use ProgrammatorDev\OpenWeatherMap\Exception\TooManyRequestsException; +use ProgrammatorDev\OpenWeatherMap\Exception\UnauthorizedException; +use ProgrammatorDev\OpenWeatherMap\Exception\UnexpectedErrorException; +use ProgrammatorDev\OpenWeatherMap\Language\Language; +use ProgrammatorDev\OpenWeatherMap\Resource\AirPollutionResource; +use ProgrammatorDev\OpenWeatherMap\Resource\GeocodingResource; +use ProgrammatorDev\OpenWeatherMap\Resource\OneCallResource; +use ProgrammatorDev\OpenWeatherMap\Resource\WeatherResource; +use ProgrammatorDev\OpenWeatherMap\UnitSystem\UnitSystem; -class OpenWeatherMap +class OpenWeatherMap extends Api { - public function __construct(private readonly Config $config) {} + private array $options; - public function config(): Config + public function __construct( + #[\SensitiveParameter] private string $apiKey, + array $options = [] + ) { - return $this->config; + parent::__construct(); + + $this->options = $this->configureOptions($options); + $this->configureApi(); } - public function oneCall(): OneCallEndpoint + public function oneCall(): OneCallResource { - return new OneCallEndpoint($this); + return new OneCallResource($this); } - public function weather(): WeatherEndpoint + public function weather(): WeatherResource { - return new WeatherEndpoint($this); + return new WeatherResource($this); } - public function airPollution(): AirPollutionEndpoint + public function airPollution(): AirPollutionResource { - return new AirPollutionEndpoint($this); + return new AirPollutionResource($this); } - public function geocoding(): GeocodingEndpoint + public function geocoding(): GeocodingResource { - return new GeocodingEndpoint($this); + return new GeocodingResource($this); + } + + private function configureOptions(array $options): array + { + $this->optionsResolver->setDefault('unitSystem', UnitSystem::METRIC); + $this->optionsResolver->setDefault('language', Language::ENGLISH); + + $this->optionsResolver->setAllowedTypes('unitSystem', 'string'); + $this->optionsResolver->setAllowedTypes('language', 'string'); + + $this->optionsResolver->setAllowedValues('unitSystem', UnitSystem::getOptions()); + $this->optionsResolver->setAllowedValues('language', Language::getOptions()); + + return $this->optionsResolver->resolve($options); + } + + private function configureApi(): void + { + $this->setBaseUrl('https://api.openweathermap.org'); + + $this->setAuthentication(new QueryParam(['appid' => $this->apiKey])); + + $this->addQueryDefault('units', $this->options['unitSystem']); + $this->addQueryDefault('lang', $this->options['language']); + + $this->addPostRequestHandler(function(PostRequestEvent $event) { + $response = $event->getResponse(); + $statusCode = $response->getStatusCode(); + + // if there was a response with an error status code + if ($statusCode >= 400) { + $error = \json_decode($response->getBody()->getContents(), true); + + match ($statusCode) { + 400 => throw new BadRequestException($error), + 401 => throw new UnauthorizedException($error), + 404 => throw new NotFoundException($error), + 429 => throw new TooManyRequestsException($error), + default => throw new UnexpectedErrorException($error) + }; + } + }); + + $this->addResponseContentsHandler(function(ResponseContentsEvent $event) { + // decode json string response into an array + $contents = $event->getContents(); + $contents = \json_decode($contents, true); + + $event->setContents($contents); + }); } } \ No newline at end of file diff --git a/src/Resource/AirPollutionResource.php b/src/Resource/AirPollutionResource.php new file mode 100644 index 0000000..4f40ae1 --- /dev/null +++ b/src/Resource/AirPollutionResource.php @@ -0,0 +1,88 @@ +validateCoordinate($latitude, $longitude); + + $data = $this->api->request( + method: Method::GET, + path: '/data/2.5/air_pollution', + query: [ + 'lat' => $latitude, + 'lon' => $longitude, + ] + ); + + return new AirPollution($data); + } + + /** + * Get access to air pollution forecast data per hour + * + * @throws ValidationException + * @throws ClientExceptionInterface + */ + public function getForecast(float $latitude, float $longitude): AirPollutionCollection + { + $this->validateCoordinate($latitude, $longitude); + + $data = $this->api->request( + method: Method::GET, + path: '/data/2.5/air_pollution/forecast', + query: [ + 'lat' => $latitude, + 'lon' => $longitude, + ] + ); + + return new AirPollutionCollection($data); + } + + /** + * Get access to historical air pollution data per hour between two dates + * + * @throws ValidationException + * @throws ClientExceptionInterface + */ + public function getHistory( + float $latitude, + float $longitude, + \DateTimeInterface $startDate, + \DateTimeInterface $endDate + ): AirPollutionCollection + { + $this->validateCoordinate($latitude, $longitude); + $this->validateDateOrder($startDate, $endDate); + + $utcTimezone = new \DateTimeZone('UTC'); + + $data = $this->api->request( + method: Method::GET, + path: '/data/2.5/air_pollution/history', + query: [ + 'lat' => $latitude, + 'lon' => $longitude, + 'start' => $startDate->setTimezone($utcTimezone)->getTimestamp(), + 'end' => $endDate->setTimezone($utcTimezone)->getTimestamp() + ] + ); + + return new AirPollutionCollection($data); + } +} \ No newline at end of file diff --git a/src/Endpoint/GeocodingEndpoint.php b/src/Resource/GeocodingResource.php similarity index 51% rename from src/Endpoint/GeocodingEndpoint.php rename to src/Resource/GeocodingResource.php index ec90d96..263c597 100644 --- a/src/Endpoint/GeocodingEndpoint.php +++ b/src/Resource/GeocodingResource.php @@ -1,37 +1,34 @@ validateSearchQuery($locationName, 'locationName'); - $this->validateNumResults($numResults); + $this->validateQuery($locationName, 'locationName'); + $this->validatePositive($numResults, 'numResults'); - $data = $this->sendRequest( - method: 'GET', + $data = $this->api->request( + method: Method::GET, path: '/geo/1.0/direct', query: [ 'q' => $locationName, @@ -43,16 +40,17 @@ public function getByLocationName(string $locationName, int $numResults = self:: } /** - * @throws Exception - * @throws ApiErrorException + * Get geographical coordinates (latitude, longitude) by using the zip/postal code + * * @throws ValidationException + * @throws ClientExceptionInterface */ - public function getByZipCode(string $zipCode, string $countryCode): ZipCodeLocation + public function getByZipCode(string $zipCode, string $countryCode): ZipLocation { - $this->validateSearchQuery($zipCode, 'zipCode'); + $this->validateQuery($zipCode, 'zipCode'); $this->validateCountryCode($countryCode); - $data = $this->sendRequest( + $data = $this->api->request( method: 'GET', path: '/geo/1.0/zip', query: [ @@ -60,22 +58,23 @@ public function getByZipCode(string $zipCode, string $countryCode): ZipCodeLocat ] ); - return new ZipCodeLocation($data); + return new ZipLocation($data); } /** + * Get name of the location (city name or area name) by using geographical coordinates (latitude, longitude) + * * @return Location[] - * @throws Exception - * @throws ApiErrorException * @throws ValidationException + * @throws ClientExceptionInterface */ public function getByCoordinate(float $latitude, float $longitude, int $numResults = self::NUM_RESULTS): array { $this->validateCoordinate($latitude, $longitude); - $this->validateNumResults($numResults); + $this->validatePositive($numResults, 'numResults'); - $data = $this->sendRequest( - method: 'GET', + $data = $this->api->request( + method: Method::GET, path: '/geo/1.0/reverse', query: [ 'lat' => $latitude, diff --git a/src/Resource/OneCallResource.php b/src/Resource/OneCallResource.php new file mode 100644 index 0000000..427f2ce --- /dev/null +++ b/src/Resource/OneCallResource.php @@ -0,0 +1,90 @@ +validateCoordinate($latitude, $longitude); + + $data = $this->api->request( + method: Method::GET, + path: '/data/3.0/onecall', + query: [ + 'lat' => $latitude, + 'lon' => $longitude, + ] + ); + + return new Weather($data); + } + + /** + * Get access to weather data for any datetime + * + * @throws ValidationException + * @throws ClientExceptionInterface + */ + public function getWeatherByDate(float $latitude, float $longitude, \DateTimeInterface $dateTime): WeatherMoment + { + $this->validateCoordinate($latitude, $longitude); + + $utcTimezone = new \DateTimeZone('UTC'); + + $data = $this->api->request( + method: Method::GET, + path: '/data/3.0/onecall/timemachine', + query: [ + 'lat' => $latitude, + 'lon' => $longitude, + 'dt' => $dateTime->setTimezone($utcTimezone)->getTimestamp() + ] + ); + + return new WeatherMoment($data); + } + + /** + * Get access to aggregated weather data for a particular date + * + * @throws ValidationException + * @throws ClientExceptionInterface + */ + public function getWeatherSummaryByDate(float $latitude, float $longitude, \DateTimeInterface $date): WeatherSummary + { + $this->validateCoordinate($latitude, $longitude); + + $data = $this->api->request( + method: Method::GET, + path: '/data/3.0/onecall/day_summary', + query: [ + 'lat' => $latitude, + 'lon' => $longitude, + 'date' => $date->format('Y-m-d'), + 'tz' => $date->format('P') + ] + ); + + return new WeatherSummary($data); + } +} \ No newline at end of file diff --git a/src/Resource/Resource.php b/src/Resource/Resource.php new file mode 100644 index 0000000..b9f59e7 --- /dev/null +++ b/src/Resource/Resource.php @@ -0,0 +1,15 @@ +api->getCacheBuilder()?->setTtl($ttl); + + return $clone; + } +} \ No newline at end of file diff --git a/src/Resource/Util/LanguageTrait.php b/src/Resource/Util/LanguageTrait.php new file mode 100644 index 0000000..76e6493 --- /dev/null +++ b/src/Resource/Util/LanguageTrait.php @@ -0,0 +1,24 @@ +validateLanguage($language); + + $clone = deep_copy($this, true); + $clone->api->addQueryDefault('lang', $language); + + return $clone; + } +} \ No newline at end of file diff --git a/src/Resource/Util/UnitSystemTrait.php b/src/Resource/Util/UnitSystemTrait.php new file mode 100644 index 0000000..166ac87 --- /dev/null +++ b/src/Resource/Util/UnitSystemTrait.php @@ -0,0 +1,24 @@ +validateUnitSystem($unitSystem); + + $clone = deep_copy($this, true); + $clone->api->addQueryDefault('units', $unitSystem); + + return $clone; + } +} \ No newline at end of file diff --git a/src/Resource/Util/ValidationTrait.php b/src/Resource/Util/ValidationTrait.php new file mode 100644 index 0000000..33dd7d6 --- /dev/null +++ b/src/Resource/Util/ValidationTrait.php @@ -0,0 +1,71 @@ +assert($query, $name); + } + + /** + * @throws ValidationException + */ + protected function validatePositive(int $number, string $name): void + { + Validator::greaterThan(0)->assert($number, $name); + } + + /** + * @throws ValidationException + */ + protected function validateCoordinate(float $latitude, float $longitude): void + { + Validator::range(-90, 90)->assert($latitude, 'latitude'); + Validator::range(-180, 180)->assert($longitude, 'longitude'); + } + + /** + * @throws ValidationException + */ + protected function validateCountryCode(string $countryCode): void + { + Validator::country()->assert($countryCode, 'countryCode'); + } + + /** + * @throws ValidationException + */ + protected function validateLanguage(string $language): void + { + Validator::choice(Language::getOptions())->assert($language, 'language'); + } + + /** + * @throws ValidationException + */ + protected function validateUnitSystem(string $unitSystem): void + { + Validator::choice(UnitSystem::getOptions())->assert($unitSystem, 'unitSystem'); + } + + /** + * @throws ValidationException + */ + protected function validateDateOrder(\DateTimeInterface $startDate, \DateTimeInterface $endDate): void + { + Validator::greaterThan( + constraint: $startDate, + message: 'The endDate must be after the startDate.' + )->assert($endDate); + } +} \ No newline at end of file diff --git a/src/Resource/WeatherResource.php b/src/Resource/WeatherResource.php new file mode 100644 index 0000000..c4cd357 --- /dev/null +++ b/src/Resource/WeatherResource.php @@ -0,0 +1,65 @@ +validateCoordinate($latitude, $longitude); + + $data = $this->api->request( + method: Method::GET, + path: '/data/2.5/weather', + query: [ + 'lat' => $latitude, + 'lon' => $longitude, + ] + ); + + return new Weather($data); + } + + /** + * Get access to 5-day weather forecast data with 3-hour steps + * + * @throws ValidationException + * @throws ClientExceptionInterface + */ + public function getForecast(float $latitude, float $longitude, int $numResults = self::NUM_RESULTS): WeatherCollection + { + $this->validateCoordinate($latitude, $longitude); + $this->validatePositive($numResults, 'numResults'); + + $data = $this->api->request( + method: Method::GET, + path: '/data/2.5/forecast', + query: [ + 'lat' => $latitude, + 'lon' => $longitude, + 'cnt' => $numResults + ] + ); + + return new WeatherCollection($data); + } +} \ No newline at end of file diff --git a/src/Test/AbstractTest.php b/src/Test/AbstractTest.php index f778fc7..66c6a8b 100644 --- a/src/Test/AbstractTest.php +++ b/src/Test/AbstractTest.php @@ -4,30 +4,24 @@ use Http\Mock\Client; use PHPUnit\Framework\TestCase; -use ProgrammatorDev\OpenWeatherMap\Config; -use ProgrammatorDev\OpenWeatherMap\HttpClient\HttpClientBuilder; +use ProgrammatorDev\Api\Builder\ClientBuilder; use ProgrammatorDev\OpenWeatherMap\OpenWeatherMap; class AbstractTest extends TestCase { - protected const APPLICATION_KEY = 'testappkey'; + protected const API_KEY = 'testapikey'; - protected Client $mockHttpClient; + protected OpenWeatherMap $api; + + protected Client $mockClient; protected function setUp(): void { parent::setUp(); - $this->mockHttpClient = new Client(); - } + $this->mockClient = new Client(); - protected function givenApi(): OpenWeatherMap - { - return new OpenWeatherMap( - new Config([ - 'applicationKey' => self::APPLICATION_KEY, - 'httpClientBuilder' => new HttpClientBuilder($this->mockHttpClient) - ]) - ); + $this->api = new OpenWeatherMap(self::API_KEY); + $this->api->setClientBuilder(new ClientBuilder($this->mockClient)); } } \ No newline at end of file diff --git a/src/Test/MockResponse.php b/src/Test/MockResponse.php index 0609f9a..1f9ce44 100644 --- a/src/Test/MockResponse.php +++ b/src/Test/MockResponse.php @@ -5,8 +5,8 @@ class MockResponse { public const ONE_CALL_WEATHER = '{"lat":38.7078,"lon":-9.1366,"timezone":"Europe/Lisbon","timezone_offset":3600,"current":{"dt":1688384139,"sunrise":1688361368,"sunset":1688414697,"temp":25.1,"feels_like":25.21,"pressure":1017,"humidity":59,"dew_point":16.53,"uvi":9.78,"clouds":20,"visibility":10000,"wind_speed":7.2,"wind_deg":10,"weather":[{"id":801,"main":"Clouds","description":"few clouds","icon":"02d"}]},"minutely":[{"dt":1688384160,"precipitation":0},{"dt":1688384220,"precipitation":0},{"dt":1688384280,"precipitation":0},{"dt":1688384340,"precipitation":0},{"dt":1688384400,"precipitation":0},{"dt":1688384460,"precipitation":0},{"dt":1688384520,"precipitation":0},{"dt":1688384580,"precipitation":0},{"dt":1688384640,"precipitation":0},{"dt":1688384700,"precipitation":0},{"dt":1688384760,"precipitation":0},{"dt":1688384820,"precipitation":0},{"dt":1688384880,"precipitation":0},{"dt":1688384940,"precipitation":0},{"dt":1688385000,"precipitation":0},{"dt":1688385060,"precipitation":0},{"dt":1688385120,"precipitation":0},{"dt":1688385180,"precipitation":0},{"dt":1688385240,"precipitation":0},{"dt":1688385300,"precipitation":0},{"dt":1688385360,"precipitation":0},{"dt":1688385420,"precipitation":0},{"dt":1688385480,"precipitation":0},{"dt":1688385540,"precipitation":0},{"dt":1688385600,"precipitation":0},{"dt":1688385660,"precipitation":0},{"dt":1688385720,"precipitation":0},{"dt":1688385780,"precipitation":0},{"dt":1688385840,"precipitation":0.1032},{"dt":1688385900,"precipitation":0.115},{"dt":1688385960,"precipitation":0.1276},{"dt":1688386020,"precipitation":0.1402},{"dt":1688386080,"precipitation":0.1528},{"dt":1688386140,"precipitation":0.1654},{"dt":1688386200,"precipitation":0.178},{"dt":1688386260,"precipitation":0.1654},{"dt":1688386320,"precipitation":0.1528},{"dt":1688386380,"precipitation":0.1402},{"dt":1688386440,"precipitation":0.1276},{"dt":1688386500,"precipitation":0.115},{"dt":1688386560,"precipitation":0.1032},{"dt":1688386620,"precipitation":0},{"dt":1688386680,"precipitation":0},{"dt":1688386740,"precipitation":0},{"dt":1688386800,"precipitation":0},{"dt":1688386860,"precipitation":0},{"dt":1688386920,"precipitation":0},{"dt":1688386980,"precipitation":0},{"dt":1688387040,"precipitation":0},{"dt":1688387100,"precipitation":0},{"dt":1688387160,"precipitation":0},{"dt":1688387220,"precipitation":0},{"dt":1688387280,"precipitation":0},{"dt":1688387340,"precipitation":0},{"dt":1688387400,"precipitation":0},{"dt":1688387460,"precipitation":0},{"dt":1688387520,"precipitation":0},{"dt":1688387580,"precipitation":0},{"dt":1688387640,"precipitation":0},{"dt":1688387700,"precipitation":0},{"dt":1688387760,"precipitation":0}],"hourly":[{"dt":1688382000,"temp":25.38,"feels_like":25.44,"pressure":1017,"humidity":56,"dew_point":15.97,"uvi":8.42,"clouds":16,"visibility":10000,"wind_speed":4.94,"wind_deg":327,"wind_gust":6.14,"weather":[{"id":801,"main":"Clouds","description":"few clouds","icon":"02d"}],"pop":0},{"dt":1688385600,"temp":25.1,"feels_like":25.21,"pressure":1017,"humidity":59,"dew_point":16.53,"uvi":9.78,"clouds":20,"visibility":10000,"wind_speed":5.92,"wind_deg":325,"wind_gust":7.02,"weather":[{"id":801,"main":"Clouds","description":"few clouds","icon":"02d"}],"pop":0},{"dt":1688389200,"temp":25.69,"feels_like":25.75,"pressure":1017,"humidity":55,"dew_point":15.98,"uvi":9.92,"clouds":16,"visibility":10000,"wind_speed":6.69,"wind_deg":328,"wind_gust":7.82,"weather":[{"id":801,"main":"Clouds","description":"few clouds","icon":"02d"}],"pop":0},{"dt":1688392800,"temp":26.43,"feels_like":26.43,"pressure":1017,"humidity":50,"dew_point":15.18,"uvi":8.86,"clouds":12,"visibility":10000,"wind_speed":7.32,"wind_deg":331,"wind_gust":8.69,"weather":[{"id":801,"main":"Clouds","description":"few clouds","icon":"02d"}],"pop":0},{"dt":1688396400,"temp":26.82,"feels_like":27.09,"pressure":1017,"humidity":47,"dew_point":14.57,"uvi":6.86,"clouds":8,"visibility":10000,"wind_speed":7.95,"wind_deg":337,"wind_gust":9.66,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"pop":0},{"dt":1688400000,"temp":26.88,"feels_like":26.92,"pressure":1017,"humidity":43,"dew_point":13.26,"uvi":4.61,"clouds":4,"visibility":10000,"wind_speed":8.11,"wind_deg":341,"wind_gust":10.42,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"pop":0},{"dt":1688403600,"temp":26.09,"feels_like":26.09,"pressure":1017,"humidity":45,"dew_point":12.76,"uvi":2.47,"clouds":0,"visibility":10000,"wind_speed":8.65,"wind_deg":344,"wind_gust":11.66,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"pop":0},{"dt":1688407200,"temp":24.67,"feels_like":24.52,"pressure":1017,"humidity":51,"dew_point":13.62,"uvi":0.99,"clouds":0,"visibility":10000,"wind_speed":8.8,"wind_deg":347,"wind_gust":12.69,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"pop":0},{"dt":1688410800,"temp":22.93,"feels_like":22.85,"pressure":1018,"humidity":60,"dew_point":14.39,"uvi":0.26,"clouds":0,"visibility":10000,"wind_speed":8.19,"wind_deg":348,"wind_gust":13.04,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"pop":0},{"dt":1688414400,"temp":21.16,"feels_like":21.13,"pressure":1018,"humidity":69,"dew_point":14.95,"uvi":0,"clouds":0,"visibility":10000,"wind_speed":6.88,"wind_deg":347,"wind_gust":12.91,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"pop":0},{"dt":1688418000,"temp":20,"feels_like":20.04,"pressure":1019,"humidity":76,"dew_point":15.45,"uvi":0,"clouds":0,"visibility":10000,"wind_speed":6.37,"wind_deg":346,"wind_gust":12.91,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"pop":0},{"dt":1688421600,"temp":19.41,"feels_like":19.5,"pressure":1019,"humidity":80,"dew_point":15.55,"uvi":0,"clouds":0,"visibility":10000,"wind_speed":5.94,"wind_deg":348,"wind_gust":12.71,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"pop":0},{"dt":1688425200,"temp":19.04,"feels_like":19.11,"pressure":1020,"humidity":81,"dew_point":15.48,"uvi":0,"clouds":0,"visibility":10000,"wind_speed":5.74,"wind_deg":348,"wind_gust":12.82,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"pop":0},{"dt":1688428800,"temp":18.7,"feels_like":18.77,"pressure":1020,"humidity":82,"dew_point":15.32,"uvi":0,"clouds":0,"visibility":10000,"wind_speed":5.63,"wind_deg":346,"wind_gust":12.84,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"pop":0},{"dt":1688432400,"temp":18.45,"feels_like":18.52,"pressure":1020,"humidity":83,"dew_point":15.27,"uvi":0,"clouds":0,"visibility":10000,"wind_speed":5.38,"wind_deg":344,"wind_gust":12.56,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"pop":0},{"dt":1688436000,"temp":18.15,"feels_like":18.24,"pressure":1019,"humidity":85,"dew_point":15.27,"uvi":0,"clouds":0,"visibility":10000,"wind_speed":5.28,"wind_deg":344,"wind_gust":12.34,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"pop":0},{"dt":1688439600,"temp":17.93,"feels_like":18.02,"pressure":1019,"humidity":86,"dew_point":15.38,"uvi":0,"clouds":0,"visibility":10000,"wind_speed":5.14,"wind_deg":342,"wind_gust":11.93,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"pop":0},{"dt":1688443200,"temp":17.73,"feels_like":17.83,"pressure":1019,"humidity":87,"dew_point":15.36,"uvi":0,"clouds":0,"visibility":10000,"wind_speed":5.1,"wind_deg":340,"wind_gust":11.84,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"pop":0},{"dt":1688446800,"temp":17.58,"feels_like":17.67,"pressure":1019,"humidity":87,"dew_point":15.24,"uvi":0,"clouds":0,"visibility":10000,"wind_speed":4.88,"wind_deg":339,"wind_gust":11.23,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"pop":0},{"dt":1688450400,"temp":17.79,"feels_like":17.87,"pressure":1019,"humidity":86,"dew_point":15.25,"uvi":0.17,"clouds":0,"visibility":10000,"wind_speed":5.07,"wind_deg":339,"wind_gust":11.53,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"pop":0},{"dt":1688454000,"temp":18.96,"feels_like":18.97,"pressure":1019,"humidity":79,"dew_point":14.99,"uvi":0.75,"clouds":0,"visibility":10000,"wind_speed":5.6,"wind_deg":344,"wind_gust":10.56,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"pop":0},{"dt":1688457600,"temp":20.57,"feels_like":20.48,"pressure":1020,"humidity":69,"dew_point":14.39,"uvi":2.05,"clouds":0,"visibility":10000,"wind_speed":6.35,"wind_deg":344,"wind_gust":9.7,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"pop":0},{"dt":1688461200,"temp":22.34,"feels_like":22.14,"pressure":1020,"humidity":58,"dew_point":13.35,"uvi":4.13,"clouds":0,"visibility":10000,"wind_speed":6.29,"wind_deg":345,"wind_gust":8.74,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"pop":0},{"dt":1688464800,"temp":23.94,"feels_like":23.7,"pressure":1020,"humidity":50,"dew_point":12.37,"uvi":6.49,"clouds":0,"visibility":10000,"wind_speed":6.28,"wind_deg":341,"wind_gust":8.01,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"pop":0},{"dt":1688468400,"temp":25.12,"feels_like":24.86,"pressure":1020,"humidity":45,"dew_point":11.69,"uvi":8.74,"clouds":0,"visibility":10000,"wind_speed":6.67,"wind_deg":336,"wind_gust":7.78,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"pop":0},{"dt":1688472000,"temp":25.82,"feels_like":25.55,"pressure":1019,"humidity":42,"dew_point":11.33,"uvi":10.16,"clouds":0,"visibility":10000,"wind_speed":6.93,"wind_deg":333,"wind_gust":7.78,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"pop":0},{"dt":1688475600,"temp":26.12,"feels_like":26.12,"pressure":1019,"humidity":41,"dew_point":11.21,"uvi":10.23,"clouds":0,"visibility":10000,"wind_speed":7.39,"wind_deg":331,"wind_gust":8.04,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"pop":0},{"dt":1688479200,"temp":26.13,"feels_like":26.13,"pressure":1019,"humidity":41,"dew_point":11.01,"uvi":9.13,"clouds":0,"visibility":10000,"wind_speed":7.75,"wind_deg":332,"wind_gust":8.6,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"pop":0},{"dt":1688482800,"temp":25.79,"feels_like":25.5,"pressure":1019,"humidity":41,"dew_point":10.99,"uvi":7.07,"clouds":0,"visibility":10000,"wind_speed":7.86,"wind_deg":333,"wind_gust":9,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"pop":0},{"dt":1688486400,"temp":25.14,"feels_like":24.83,"pressure":1018,"humidity":43,"dew_point":11.02,"uvi":4.68,"clouds":0,"visibility":10000,"wind_speed":7.9,"wind_deg":334,"wind_gust":9.28,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"pop":0},{"dt":1688490000,"temp":24.33,"feels_like":23.97,"pressure":1018,"humidity":44,"dew_point":10.98,"uvi":2.5,"clouds":0,"visibility":10000,"wind_speed":7.8,"wind_deg":336,"wind_gust":9.64,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"pop":0},{"dt":1688493600,"temp":23.02,"feels_like":22.63,"pressure":1018,"humidity":48,"dew_point":11.18,"uvi":1,"clouds":0,"visibility":10000,"wind_speed":7.77,"wind_deg":339,"wind_gust":10.17,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"pop":0},{"dt":1688497200,"temp":21.59,"feels_like":21.21,"pressure":1019,"humidity":54,"dew_point":11.6,"uvi":0.26,"clouds":0,"visibility":10000,"wind_speed":7.21,"wind_deg":342,"wind_gust":10.42,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"pop":0},{"dt":1688500800,"temp":19.87,"feels_like":19.58,"pressure":1019,"humidity":64,"dew_point":12.66,"uvi":0,"clouds":0,"visibility":10000,"wind_speed":6.25,"wind_deg":340,"wind_gust":11.21,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"pop":0},{"dt":1688504400,"temp":18.72,"feels_like":18.66,"pressure":1019,"humidity":77,"dew_point":14.27,"uvi":0,"clouds":0,"visibility":10000,"wind_speed":5.96,"wind_deg":341,"wind_gust":12.03,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"pop":0},{"dt":1688508000,"temp":18.26,"feels_like":18.31,"pressure":1020,"humidity":83,"dew_point":15.08,"uvi":0,"clouds":0,"visibility":10000,"wind_speed":5.73,"wind_deg":340,"wind_gust":12.21,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"pop":0},{"dt":1688511600,"temp":18.1,"feels_like":18.21,"pressure":1019,"humidity":86,"dew_point":15.47,"uvi":0,"clouds":0,"visibility":10000,"wind_speed":5.61,"wind_deg":344,"wind_gust":12.34,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"pop":0},{"dt":1688515200,"temp":18.1,"feels_like":18.24,"pressure":1019,"humidity":87,"dew_point":15.75,"uvi":0,"clouds":1,"visibility":10000,"wind_speed":5.75,"wind_deg":344,"wind_gust":12.19,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"pop":0},{"dt":1688518800,"temp":18.25,"feels_like":18.38,"pressure":1019,"humidity":86,"dew_point":15.67,"uvi":0,"clouds":16,"visibility":10000,"wind_speed":5.71,"wind_deg":346,"wind_gust":11.73,"weather":[{"id":801,"main":"Clouds","description":"few clouds","icon":"02n"}],"pop":0},{"dt":1688522400,"temp":18.21,"feels_like":18.31,"pressure":1018,"humidity":85,"dew_point":15.32,"uvi":0,"clouds":14,"visibility":10000,"wind_speed":5.25,"wind_deg":345,"wind_gust":11.6,"weather":[{"id":801,"main":"Clouds","description":"few clouds","icon":"02n"}],"pop":0},{"dt":1688526000,"temp":17.82,"feels_like":17.9,"pressure":1018,"humidity":86,"dew_point":15.12,"uvi":0,"clouds":10,"visibility":10000,"wind_speed":4.67,"wind_deg":337,"wind_gust":11.06,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"pop":0},{"dt":1688529600,"temp":17.66,"feels_like":17.73,"pressure":1018,"humidity":86,"dew_point":15.1,"uvi":0,"clouds":9,"visibility":10000,"wind_speed":4.77,"wind_deg":339,"wind_gust":11.79,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"pop":0},{"dt":1688533200,"temp":17.54,"feels_like":17.6,"pressure":1018,"humidity":86,"dew_point":14.95,"uvi":0,"clouds":8,"visibility":10000,"wind_speed":4.65,"wind_deg":336,"wind_gust":10.53,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"pop":0},{"dt":1688536800,"temp":17.83,"feels_like":17.86,"pressure":1018,"humidity":84,"dew_point":14.82,"uvi":0.17,"clouds":11,"visibility":10000,"wind_speed":5.08,"wind_deg":337,"wind_gust":11.85,"weather":[{"id":801,"main":"Clouds","description":"few clouds","icon":"02d"}],"pop":0},{"dt":1688540400,"temp":18.98,"feels_like":18.94,"pressure":1019,"humidity":77,"dew_point":14.66,"uvi":0.73,"clouds":71,"visibility":10000,"wind_speed":6.05,"wind_deg":327,"wind_gust":10.65,"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04d"}],"pop":0},{"dt":1688544000,"temp":20.08,"feels_like":19.95,"pressure":1018,"humidity":69,"dew_point":13.96,"uvi":2,"clouds":85,"visibility":10000,"wind_speed":6.22,"wind_deg":352,"wind_gust":10.17,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04d"}],"pop":0},{"dt":1688547600,"temp":20.87,"feels_like":20.68,"pressure":1018,"humidity":64,"dew_point":13.5,"uvi":4.03,"clouds":90,"visibility":10000,"wind_speed":6.36,"wind_deg":346,"wind_gust":9.8,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04d"}],"pop":0},{"dt":1688551200,"temp":21.11,"feels_like":20.87,"pressure":1018,"humidity":61,"dew_point":13.04,"uvi":5.58,"clouds":92,"visibility":10000,"wind_speed":5.05,"wind_deg":348,"wind_gust":7.7,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04d"}],"pop":0}],"daily":[{"dt":1688385600,"sunrise":1688361368,"sunset":1688414697,"moonrise":1688417100,"moonset":1688359440,"moon_phase":0.5,"summary":"Expect a day of partly cloudy with clear spells","temp":{"day":25.1,"min":18.28,"max":26.88,"night":19.41,"eve":24.67,"morn":18.39},"feels_like":{"day":25.21,"night":19.5,"eve":24.52,"morn":18.5},"pressure":1017,"humidity":59,"dew_point":16.53,"wind_speed":8.8,"wind_deg":347,"wind_gust":13.04,"weather":[{"id":801,"main":"Clouds","description":"few clouds","icon":"02d"}],"clouds":20,"pop":0,"uvi":9.92},{"dt":1688472000,"sunrise":1688447798,"sunset":1688501087,"moonrise":1688506560,"moonset":1688450100,"moon_phase":0.54,"summary":"There will be clear sky today","temp":{"day":25.82,"min":17.58,"max":26.13,"night":18.26,"eve":23.02,"morn":17.79},"feels_like":{"day":25.55,"night":18.31,"eve":22.63,"morn":17.87},"pressure":1019,"humidity":42,"dew_point":11.33,"wind_speed":7.9,"wind_deg":334,"wind_gust":12.84,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"clouds":0,"pop":0,"uvi":10.23},{"dt":1688558400,"sunrise":1688534230,"sunset":1688587476,"moonrise":1688595540,"moonset":1688541240,"moon_phase":0.58,"summary":"Expect a day of partly cloudy with clear spells","temp":{"day":25.2,"min":17.54,"max":25.28,"night":18.64,"eve":22.5,"morn":17.83},"feels_like":{"day":24.98,"night":18.7,"eve":22.32,"morn":17.86},"pressure":1018,"humidity":46,"dew_point":12.27,"wind_speed":7.86,"wind_deg":324,"wind_gust":12.34,"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04d"}],"clouds":71,"pop":0,"uvi":9.93},{"dt":1688644800,"sunrise":1688620663,"sunset":1688673862,"moonrise":1688683980,"moonset":1688632380,"moon_phase":0.62,"summary":"The day will start with partly cloudy through the late morning hours, transitioning to clearing","temp":{"day":25.05,"min":17.57,"max":25.05,"night":19.01,"eve":22.56,"morn":17.59},"feels_like":{"day":24.86,"night":19.06,"eve":22.39,"morn":17.75},"pressure":1016,"humidity":48,"dew_point":12.69,"wind_speed":7.09,"wind_deg":312,"wind_gust":9.69,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"clouds":2,"pop":0,"uvi":8.84},{"dt":1688731200,"sunrise":1688707097,"sunset":1688760247,"moonrise":0,"moonset":1688723400,"moon_phase":0.65,"summary":"There will be clear sky until morning, then partly cloudy","temp":{"day":24.48,"min":17.64,"max":24.48,"night":19.8,"eve":22.83,"morn":17.64},"feels_like":{"day":24.32,"night":20,"eve":22.81,"morn":17.81},"pressure":1017,"humidity":51,"dew_point":13,"wind_speed":5.56,"wind_deg":305,"wind_gust":7.26,"weather":[{"id":801,"main":"Clouds","description":"few clouds","icon":"02d"}],"clouds":15,"pop":0,"uvi":8.73},{"dt":1688817600,"sunrise":1688793533,"sunset":1688846630,"moonrise":1688772060,"moonset":1688814180,"moon_phase":0.69,"summary":"The day will start with partly cloudy through the late morning hours, transitioning to clearing","temp":{"day":24.93,"min":18.43,"max":25.64,"night":20.37,"eve":23.97,"morn":18.43},"feels_like":{"day":24.99,"night":20.47,"eve":23.96,"morn":18.65},"pressure":1019,"humidity":58,"dew_point":15.5,"wind_speed":5.99,"wind_deg":276,"wind_gust":6.46,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"clouds":5,"pop":0,"uvi":9},{"dt":1688904000,"sunrise":1688879970,"sunset":1688933011,"moonrise":1688860020,"moonset":1688904780,"moon_phase":0.73,"summary":"Expect a day of partly cloudy with clear spells","temp":{"day":24.48,"min":18.35,"max":25.38,"night":18.35,"eve":22.97,"morn":19.77},"feels_like":{"day":24.42,"night":18.07,"eve":22.55,"morn":20.15},"pressure":1021,"humidity":55,"dew_point":14.3,"wind_speed":7.17,"wind_deg":330,"wind_gust":10.71,"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04d"}],"clouds":70,"pop":0,"uvi":9},{"dt":1688990400,"sunrise":1688966408,"sunset":1689019390,"moonrise":1688947980,"moonset":1688995260,"moon_phase":0.75,"summary":"There will be clear sky today","temp":{"day":25.17,"min":16.95,"max":25.65,"night":20.15,"eve":24,"morn":17.24},"feels_like":{"day":25.18,"night":20.1,"eve":23.97,"morn":17.08},"pressure":1021,"humidity":55,"dew_point":15.07,"wind_speed":6.84,"wind_deg":315,"wind_gust":8.77,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"clouds":0,"pop":0,"uvi":9}],"alerts":[{"sender_name":"NWS Portland (Northwest Oregon and Southwest Washington)","event":"Heat Advisory","start":1688490000,"end":1688623200,"description":"...HEAT ADVISORY REMAINS IN EFFECT FROM 10 AM TUESDAY TO 11 PM\nPDT WEDNESDAY...\n* WHAT...Maximum temperatures of 92 to 102 degrees expected\nTuesday and Wednesday. Hottest temperatures will be across\ninner portions of the Portland, Vancouver, Hillsboro, and\nSalem metropolitan areas.\n* WHERE...In Oregon, Greater Portland Metro Area and Central\nWillamette Valley. In Washington, Greater Vancouver Area.\n* WHEN...From 10 AM Tuesday to 11 PM PDT Wednesday.\n* IMPACTS...Hot temperatures may cause heat illnesses to occur.\n* ADDITIONAL DETAILS...Temperatures will struggle to fall much\nbelow 70 degrees Tuesday night for the inner urban core of\nPortland.","tags":["Extreme temperature value"]}]}'; - public const ONE_CALL_HISTORY_MOMENT = '{"lat":38.7078,"lon":-9.1366,"timezone":"Europe/Lisbon","timezone_offset":0,"data":[{"dt":1672531200,"sunrise":1672559671,"sunset":1672593902,"temp":17.48,"feels_like":17.16,"pressure":1019,"humidity":72,"dew_point":12.38,"clouds":20,"visibility":9999,"wind_speed":16.54,"wind_deg":337,"wind_gust":16.54,"weather":[{"id":801,"main":"Clouds","description":"few clouds","icon":"02n"}]}]}'; - public const ONE_CALL_HISTORY_AGGREGATE = '{"lat":38.7077507,"lon":-9.1365919,"tz":"+00:00","date":"2023-01-01","units":"metric","cloud_cover":{"afternoon":75.0},"humidity":{"afternoon":71.0},"precipitation":{"total":2.53},"temperature":{"min":12.52,"max":18.29,"afternoon":18.26,"night":17.39,"evening":13.9,"morning":17.23},"pressure":{"afternoon":1017.0},"wind":{"max":{"speed":26.38,"direction":225.0}}}'; + public const ONE_CALL_TIMEMACHINE = '{"lat":38.7078,"lon":-9.1366,"timezone":"Europe/Lisbon","timezone_offset":0,"data":[{"dt":1672531200,"sunrise":1672559671,"sunset":1672593902,"temp":17.48,"feels_like":17.16,"pressure":1019,"humidity":72,"dew_point":12.38,"clouds":20,"visibility":9999,"wind_speed":16.54,"wind_deg":337,"wind_gust":16.54,"weather":[{"id":801,"main":"Clouds","description":"few clouds","icon":"02n"}]}]}'; + public const ONE_CALL_DAY_SUMMARY = '{"lat":38.7077507,"lon":-9.1365919,"tz":"+00:00","date":"2023-01-01","units":"metric","cloud_cover":{"afternoon":75.0},"humidity":{"afternoon":71.0},"precipitation":{"total":2.53},"temperature":{"min":12.52,"max":18.29,"afternoon":18.26,"night":17.39,"evening":13.9,"morning":17.23},"pressure":{"afternoon":1017.0},"wind":{"max":{"speed":26.38,"direction":225.0}}}'; public const WEATHER_CURRENT = '{"coord":{"lon":-9.1366,"lat":38.7078},"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"base":"stations","main":{"temp":27.12,"feels_like":28.16,"temp_min":22.76,"temp_max":29.9,"pressure":1013,"humidity":59,"sea_level":1013,"grnd_level":997},"visibility":10000,"wind":{"speed":9.26,"deg":360,"gust":2.34},"clouds":{"all":0},"rain":{"1h":0.17,"3h":0.81},"snow":{"1h":0.14,"3h":0.46},"dt":1687949133,"sys":{"type":1,"id":6901,"country":"PT","sunrise":1687929236,"sunset":1687982718},"timezone":3600,"id":6930126,"name":"Chiado","cod":200}'; public const WEATHER_FORECAST = '{"cod":"200","message":0,"cnt":1,"list":[{"dt":1687975200,"main":{"temp":26.2,"feels_like":26.2,"temp_min":25.64,"temp_max":26.2,"pressure":1013,"sea_level":1013,"grnd_level":1013,"humidity":56,"temp_kf":0.56},"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"clouds":{"all":0},"wind":{"speed":8.88,"deg":340,"gust":13.77},"visibility":10000,"pop":0,"sys":{"pod":"d"},"dt_txt":"2023-06-28 18:00:00"}],"city":{"id":6930126,"name":"Chiado","coord":{"lat":38.7078,"lon":-9.1366},"country":"PT","population":500000,"timezone":3600,"sunrise":1687929236,"sunset":1687982718}}'; diff --git a/src/Test/Util/TestCollectionResponseTrait.php b/src/Test/Util/TestCollectionResponseTrait.php new file mode 100644 index 0000000..1eb2727 --- /dev/null +++ b/src/Test/Util/TestCollectionResponseTrait.php @@ -0,0 +1,29 @@ +mockClient->addResponse(new Response( + status: 200, + body: $responseBody + )); + + $response = $this->api->$resource()->$method(...$args); + $this->assertContainsOnlyInstancesOf($responseClass, $response); + } + + abstract public static function provideCollectionResponseData(): \Generator; +} \ No newline at end of file diff --git a/src/Test/Util/TestItemResponseTrait.php b/src/Test/Util/TestItemResponseTrait.php new file mode 100644 index 0000000..c12d7e0 --- /dev/null +++ b/src/Test/Util/TestItemResponseTrait.php @@ -0,0 +1,29 @@ +mockClient->addResponse(new Response( + status: 200, + body: $responseBody + )); + + $response = $this->api->$resource()->$method(...$args); + $this->assertInstanceOf($responseClass, $response); + } + + abstract public static function provideItemResponseData(): \Generator; +} \ No newline at end of file diff --git a/src/Test/Util/TestValidationExceptionTrait.php b/src/Test/Util/TestValidationExceptionTrait.php new file mode 100644 index 0000000..073cd15 --- /dev/null +++ b/src/Test/Util/TestValidationExceptionTrait.php @@ -0,0 +1,18 @@ +expectException(ValidationException::class); + $this->api->$resource()->$method(...$args); + } + + abstract public static function provideValidationExceptionData(): \Generator; +} \ No newline at end of file diff --git a/src/UnitSystem/UnitSystem.php b/src/UnitSystem/UnitSystem.php index f7f576e..b7239d3 100644 --- a/src/UnitSystem/UnitSystem.php +++ b/src/UnitSystem/UnitSystem.php @@ -2,17 +2,17 @@ namespace ProgrammatorDev\OpenWeatherMap\UnitSystem; -use ProgrammatorDev\OpenWeatherMap\Util\ClassConstantsTrait; +use ProgrammatorDev\OpenWeatherMap\Util\ReflectionTrait; class UnitSystem { - use ClassConstantsTrait; + use ReflectionTrait; public const METRIC = 'metric'; public const IMPERIAL = 'imperial'; public const STANDARD = 'standard'; - public static function getList(): array + public static function getOptions(): array { return (new UnitSystem)->getClassConstants(self::class); } diff --git a/src/Util/EntityListTrait.php b/src/Util/EntityListTrait.php deleted file mode 100644 index f7fb6bf..0000000 --- a/src/Util/EntityListTrait.php +++ /dev/null @@ -1,13 +0,0 @@ -getConstants(); + $class = new \ReflectionClass($className); + $constants = $class->getConstants(); // Sort by alphabetical order // to be more intuitive when listing values for error messages diff --git a/tests/AbstractEndpointTest.php b/tests/AbstractEndpointTest.php deleted file mode 100644 index 137af31..0000000 --- a/tests/AbstractEndpointTest.php +++ /dev/null @@ -1,57 +0,0 @@ -mockHttpClient->addResponse( - new Response(body: '[]') - ); - - $cache = $this->createMock(CacheItemPoolInterface::class); - $cache->method('getItem')->willReturn( - $this->createMock(CacheItemInterface::class) - ); - - $cache->expects($this->once())->method('save'); - - $api = $this->givenApi(); - $api->config()->setCache($cache); - - $this->mockSendRequest($api); - } - - public function testAbstractEndpointWithLogger() - { - $this->mockHttpClient->addResponse( - new Response(body: '[]') - ); - - $logger = $this->createMock(LoggerInterface::class); - $logger->expects($this->atLeastOnce())->method('info'); - - $api = $this->givenApi(); - $api->config()->setLogger($logger); - - $this->mockSendRequest($api); - } - - private function mockSendRequest(OpenWeatherMap $api): void - { - // Using ReflectionClass to be able to call the *protected* sendRequest method - // (otherwise it would not be possible) - $endpoint = new AbstractEndpoint($api); - $reflectionClass = new \ReflectionClass($endpoint); - $sendRequest = $reflectionClass->getMethod('sendRequest'); - $sendRequest->invokeArgs($endpoint, ['GET', '/test']); - } -} \ No newline at end of file diff --git a/tests/AirPollutionEndpointTest.php b/tests/AirPollutionEndpointTest.php deleted file mode 100644 index f66bb77..0000000 --- a/tests/AirPollutionEndpointTest.php +++ /dev/null @@ -1,155 +0,0 @@ - [ - MockResponse::AIR_POLLUTION_CURRENT, - 'airPollution', - 'getCurrent', - [50, 50], - 'assertGetCurrentResponse' - ]; - yield 'get forecast' => [ - MockResponse::AIR_POLLUTION_FORECAST, - 'airPollution', - 'getForecast', - [50, 50], - 'assertGetForecastResponse' - ]; - yield 'get history' => [ - MockResponse::AIR_POLLUTION_HISTORY, - 'airPollution', - 'getHistory', - [50, 50, new \DateTime('yesterday'), new \DateTime('today')], - 'assertGetHistoryResponse' - ]; - } - - public static function provideEndpointInvalidResponseData(): \Generator - { - yield 'get current, latitude lower than -90' => ['airPollution', 'getCurrent', [-91, 50]]; - yield 'get current, latitude greater than 90' => ['airPollution', 'getCurrent', [91, 50]]; - yield 'get current, longitude lower than -180' => ['airPollution', 'getCurrent', [50, -181]]; - yield 'get current, longitude greater than 180' => ['airPollution', 'getCurrent', [50, 181]]; - - yield 'get forecast, latitude lower than -90' => ['airPollution', 'getForecast', [-91, 50]]; - yield 'get forecast, latitude greater than 90' => ['airPollution', 'getForecast', [91, 50]]; - yield 'get forecast, longitude lower than -180' => ['airPollution', 'getForecast', [50, -181]]; - yield 'get forecast, longitude greater than 180' => ['airPollution', 'getForecast', [50, 181]]; - - yield 'get history, latitude lower than -90' => ['airPollution', 'getHistory', - [-91, 50, new \DateTime('yesterday'), new \DateTime('today')] - ]; - yield 'get history, latitude greater than 90' => ['airPollution', 'getHistory', - [91, 50, new \DateTime('yesterday'), new \DateTime('today')] - ]; - yield 'get history, longitude lower than -180' => ['airPollution', 'getHistory', - [50, -181, new \DateTime('yesterday'), new \DateTime('today')] - ]; - yield 'get history, longitude greater than 180' => ['airPollution', 'getHistory', - [50, 181, new \DateTime('yesterday'), new \DateTime('today')] - ]; - yield 'get history, invalid past end date' => ['airPollution', 'getHistory', - [50, 50, new \DateTime('yesterday'), new \DateTime('tomorrow')] - ]; - yield 'get history, end date before start date' => ['airPollution', 'getHistory', - [50, 50, new \DateTime('yesterday'), new \DateTime('-2 days')] - ]; - } - - public function testAirPollutionMethodsExist() - { - $this->assertSame(false, method_exists(AirPollutionEndpoint::class, 'withUnitSystem')); - $this->assertSame(false, method_exists(AirPollutionEndpoint::class, 'withLanguage')); - $this->assertSame(true, method_exists(AirPollutionEndpoint::class, 'withCacheTtl')); - } - - private function assertGetCurrentResponse(AirPollutionLocation $airPollutionLocation): void - { - $this->assertSame(196.93, $airPollutionLocation->getCarbonMonoxide()); - $this->assertSame(0.65, $airPollutionLocation->getNitrogenMonoxide()); - $this->assertSame(3.98, $airPollutionLocation->getNitrogenDioxide()); - $this->assertSame(107.29, $airPollutionLocation->getOzone()); - $this->assertSame(1.46, $airPollutionLocation->getSulphurDioxide()); - $this->assertSame(8.58, $airPollutionLocation->getFineParticulateMatter()); - $this->assertSame(13.5, $airPollutionLocation->getCoarseParticulateMatter()); - $this->assertSame(2.03, $airPollutionLocation->getAmmonia()); - $this->assertSame('2023-06-23 17:21:57', $airPollutionLocation->getDateTime()->format('Y-m-d H:i:s')); - - $coordinate = $airPollutionLocation->getCoordinate(); - $this->assertInstanceOf(Coordinate::class, $coordinate); - $this->assertSame(38.7078, $coordinate->getLatitude()); - $this->assertSame(-9.1366, $coordinate->getLongitude()); - - $airQuality = $airPollutionLocation->getAirQuality(); - $this->assertInstanceOf(AirQuality::class, $airQuality); - $this->assertSame(3, $airQuality->getIndex()); - $this->assertSame('Moderate', $airQuality->getQualitativeName()); - } - - private function assertGetForecastResponse(AirPollutionLocationList $airPollutionLocationList): void - { - $coordinate = $airPollutionLocationList->getCoordinate(); - $this->assertInstanceOf(Coordinate::class, $coordinate); - $this->assertSame(38.7078, $coordinate->getLatitude()); - $this->assertSame(-9.1366, $coordinate->getLongitude()); - - $list = $airPollutionLocationList->getList(); - $this->assertContainsOnlyInstancesOf(AirPollution::class, $list); - $this->assertSame(196.93, $list[0]->getCarbonMonoxide()); - $this->assertSame(0.65, $list[0]->getNitrogenMonoxide()); - $this->assertSame(3.98, $list[0]->getNitrogenDioxide()); - $this->assertSame(107.29, $list[0]->getOzone()); - $this->assertSame(1.46, $list[0]->getSulphurDioxide()); - $this->assertSame(8.58, $list[0]->getFineParticulateMatter()); - $this->assertSame(13.5, $list[0]->getCoarseParticulateMatter()); - $this->assertSame(2.03, $list[0]->getAmmonia()); - $this->assertSame('2023-06-23 17:00:00', $list[0]->getDateTime()->format('Y-m-d H:i:s')); - - $airQuality = $list[0]->getAirQuality(); - $this->assertInstanceOf(AirQuality::class, $airQuality); - $this->assertSame(3, $airQuality->getIndex()); - $this->assertSame('Moderate', $airQuality->getQualitativeName()); - } - - private function assertGetHistoryResponse(AirPollutionLocationList $airPollutionLocationList): void - { - $coordinate = $airPollutionLocationList->getCoordinate(); - $this->assertInstanceOf(Coordinate::class, $coordinate); - $this->assertSame(38.7078, $coordinate->getLatitude()); - $this->assertSame(-9.1366, $coordinate->getLongitude()); - - $list = $airPollutionLocationList->getList(); - $this->assertContainsOnlyInstancesOf(AirPollution::class, $list); - $this->assertSame(220.3, $list[0]->getCarbonMonoxide()); - $this->assertSame(0.12, $list[0]->getNitrogenMonoxide()); - $this->assertSame(3.3, $list[0]->getNitrogenDioxide()); - $this->assertSame(87.26, $list[0]->getOzone()); - $this->assertSame(1.25, $list[0]->getSulphurDioxide()); - $this->assertSame(1.62, $list[0]->getFineParticulateMatter()); - $this->assertSame(2.94, $list[0]->getCoarseParticulateMatter()); - $this->assertSame(0.38, $list[0]->getAmmonia()); - $this->assertSame('2023-06-18 18:00:00', $list[0]->getDateTime()->format('Y-m-d H:i:s')); - - $airQuality = $list[0]->getAirQuality(); - $this->assertInstanceOf(AirQuality::class, $airQuality); - $this->assertSame(2, $airQuality->getIndex()); - $this->assertSame('Fair', $airQuality->getQualitativeName()); - } -} \ No newline at end of file diff --git a/tests/ApiErrorTest.php b/tests/ApiErrorTest.php deleted file mode 100644 index 25f0e4f..0000000 --- a/tests/ApiErrorTest.php +++ /dev/null @@ -1,38 +0,0 @@ -mockHttpClient->addResponse( - new Response( - status: $statusCode, - body: MockResponse::API_ERROR - ) - ); - - $this->expectException($expectedException); - $this->givenApi()->weather()->getCurrent(38.7077507, -9.1365919); - } - - public static function provideApiErrorData(): \Generator - { - yield 'bad request exception' => [400, BadRequestException::class]; - yield 'unauthorized exception' => [401, UnauthorizedException::class]; - yield 'not found exception' => [404, NotFoundException::class]; - yield 'too many requests exception' => [429, TooManyRequestsException::class]; - yield 'unexpected error exception' => [500, UnexpectedErrorException::class]; - } -} \ No newline at end of file diff --git a/tests/CacheTtlTraitTest.php b/tests/CacheTtlTraitTest.php deleted file mode 100644 index a056116..0000000 --- a/tests/CacheTtlTraitTest.php +++ /dev/null @@ -1,14 +0,0 @@ -assertSame( - 60 * 60, - $this->givenApi()->weather()->withCacheTtl(60 * 60)->getCacheTtl() - ); - } -} \ No newline at end of file diff --git a/tests/ConfigTest.php b/tests/ConfigTest.php deleted file mode 100644 index b8a95e1..0000000 --- a/tests/ConfigTest.php +++ /dev/null @@ -1,149 +0,0 @@ -config = new Config([ - 'applicationKey' => self::APPLICATION_KEY - ]); - } - - public function testConfigDefaultOptions() - { - $this->assertSame(self::APPLICATION_KEY, $this->config->getApplicationKey()); - $this->assertSame('metric', $this->config->getUnitSystem()); - $this->assertSame('en', $this->config->getLanguage()); - $this->assertInstanceOf(HttpClientBuilder::class, $this->config->getHttpClientBuilder()); - $this->assertSame(null, $this->config->getCache()); - $this->assertSame(null, $this->config->getLogger()); - } - - public function testConfigWithOptions() - { - $config = new Config([ - 'applicationKey' => 'newtestappkey', - 'unitSystem' => 'imperial', - 'language' => 'pt', - 'httpClientBuilder' => new HttpClientBuilder(), - 'cache' => $this->createMock(CacheItemPoolInterface::class), - 'logger' => $this->createMock(LoggerInterface::class) - ]); - - $this->assertSame('newtestappkey', $config->getApplicationKey()); - $this->assertSame('imperial', $config->getUnitSystem()); - $this->assertSame('pt', $config->getLanguage()); - $this->assertInstanceOf(HttpClientBuilder::class, $config->getHttpClientBuilder()); - $this->assertInstanceOf(CacheItemPoolInterface::class, $config->getCache()); - $this->assertInstanceOf(LoggerInterface::class, $config->getLogger()); - } - - #[DataProvider('provideInvalidConfigOptionsData')] - public function testConfigWithInvalidOptions(array $options, string $expectedException) - { - $this->expectException($expectedException); - - new Config($options); - } - - public static function provideInvalidConfigOptionsData(): \Generator - { - yield 'missing application key' => [ - [], - MissingOptionsException::class - ]; - yield 'empty application key' => [ - [ - 'applicationKey' => '' - ], - InvalidOptionsException::class - ]; - yield 'invalid unit system' => [ - [ - 'applicationKey' => self::APPLICATION_KEY, - 'unitSystem' => 'invalid' - ], - InvalidOptionsException::class - ]; - yield 'invalid language' => [ - [ - 'applicationKey' => self::APPLICATION_KEY, - 'language' => 'invalid' - ], - InvalidOptionsException::class - ]; - } - - public function testConfigSetApplicationKey() - { - $this->config->setApplicationKey('newtestappkey'); - $this->assertSame('newtestappkey', $this->config->getApplicationKey()); - } - - public function testConfigSetApplicationKeyWithBlankValue() - { - $this->expectException(ValidationException::class); - $this->config->setApplicationKey(''); - } - - public function testConfigSetUnitSystem() - { - $this->config->setUnitSystem('imperial'); - $this->assertSame('imperial', $this->config->getUnitSystem()); - } - - #[DataProviderExternal(InvalidParamDataProvider::class, 'provideInvalidUnitSystemData')] - public function testConfigSetUnitSystemWithInvalidValue(string $unitSystem, string $expectedException) - { - $this->expectException($expectedException); - $this->config->setUnitSystem($unitSystem); - } - - public function testConfigSetLanguage() - { - $this->config->setLanguage('pt'); - $this->assertSame('pt', $this->config->getLanguage()); - } - - #[DataProviderExternal(InvalidParamDataProvider::class, 'provideInvalidLanguageData')] - public function testConfigSetLanguageWithInvalidValue(string $language, string $expectedException) - { - $this->expectException($expectedException); - $this->config->setLanguage($language); - } - - public function testConfigSetHttpClientBuilder() - { - $this->config->setHttpClientBuilder(new HttpClientBuilder()); - $this->assertInstanceOf(HttpClientBuilder::class, $this->config->getHttpClientBuilder()); - } - - public function testConfigSetCache() - { - $this->config->setCache($this->createMock(CacheItemPoolInterface::class)); - $this->assertInstanceOf(CacheItemPoolInterface::class, $this->config->getCache()); - } - - public function testConfigSetLogger() - { - $this->config->setLogger($this->createMock(LoggerInterface::class)); - $this->assertInstanceOf(LoggerInterface::class, $this->config->getLogger()); - } -} \ No newline at end of file diff --git a/tests/DataProvider/InvalidParamDataProvider.php b/tests/DataProvider/InvalidParamDataProvider.php deleted file mode 100644 index 474e874..0000000 --- a/tests/DataProvider/InvalidParamDataProvider.php +++ /dev/null @@ -1,18 +0,0 @@ - ['invalid', ValidationException::class]; - } - - public static function provideInvalidLanguageData(): \Generator - { - yield 'not allowed language' => ['invalid', ValidationException::class]; - } -} \ No newline at end of file diff --git a/tests/GeocodingEndpointTest.php b/tests/GeocodingEndpointTest.php deleted file mode 100644 index 8041a47..0000000 --- a/tests/GeocodingEndpointTest.php +++ /dev/null @@ -1,101 +0,0 @@ - [ - MockResponse::GEOCODING_DIRECT, - 'geocoding', - 'getByLocationName', - ['test'], - 'assertGetByLocationResponse' - ]; - yield 'get by coordinate' => [ - MockResponse::GEOCODING_REVERSE, - 'geocoding', - 'getByCoordinate', - [50, 50], - 'assertGetByLocationResponse' - ]; - yield 'get by zip code' => [ - MockResponse::GEOCODING_ZIP, - 'geocoding', - 'getByZipCode', - ['1234-567', 'pt'], - 'assertGetByZipCodeResponse' - ]; - } - - public static function provideEndpointInvalidResponseData(): \Generator - { - yield 'get by location name, blank value' => ['geocoding', 'getByLocationName', ['']]; - yield 'get by location name, zero num results' => ['geocoding', 'getByLocationName', ['test', 0]]; - yield 'get by location name, negative num results' => ['geocoding', 'getByLocationName', ['test', -1]]; - - yield 'get by zip code, blank zip code' => ['geocoding', 'getByZipCode', ['', 'pt']]; - yield 'get by zip code, blank country code' => ['geocoding', 'getByZipCode', ['1234-567', '']]; - yield 'get by zip code, invalid country code' => ['geocoding', 'getByZipCode', ['1234-567', 'invalid']]; - - yield 'get by coordinate, latitude lower than -90' => ['geocoding', 'getByCoordinate', [-91, 50]]; - yield 'get by coordinate, latitude greater than 90' => ['geocoding', 'getByCoordinate', [91, 50]]; - yield 'get by coordinate, longitude lower than -180' => ['geocoding', 'getByCoordinate', [50, -181]]; - yield 'get by coordinate, longitude greater than 180' => ['geocoding', 'getByCoordinate', [50, 181]]; - yield 'get by coordinate, zero num results' => ['geocoding', 'getByCoordinate', [50, 50, 0]]; - yield 'get by coordinate, negative num results' => ['geocoding', 'getByCoordinate', [50, 50, -1]]; - } - - public function testGeocodingMethodsExist() - { - $this->assertSame(false, method_exists(GeocodingEndpoint::class, 'withUnitSystem')); - $this->assertSame(false, method_exists(GeocodingEndpoint::class, 'withLanguage')); - $this->assertSame(true, method_exists(GeocodingEndpoint::class, 'withCacheTtl')); - } - - /** @param Location[] $locations */ - private function assertGetByLocationResponse(array $locations): void - { - $this->assertContainsOnlyInstancesOf(Location::class, $locations); - - $location = $locations[0]; - $this->assertSame(null, $location->getId()); - $this->assertSame('Lisbon', $location->getName()); - $this->assertSame(null, $location->getState()); - $this->assertSame('PT', $location->getCountryCode()); - $this->assertIsArray($location->getLocalNames()); - $this->assertSame('Lisboa', $location->getLocalName('pt')); - $this->assertSame(null, $location->getPopulation()); - $this->assertSame(null, $location->getTimezone()); - $this->assertSame(null, $location->getSunriseAt()); - $this->assertSame(null, $location->getSunsetAt()); - - $coordinate = $locations[0]->getCoordinate(); - $this->assertInstanceOf(Coordinate::class, $coordinate); - $this->assertSame(38.7077507, $coordinate->getLatitude()); - $this->assertSame(-9.1365919, $coordinate->getLongitude()); - } - - private function assertGetByZipCodeResponse(ZipCodeLocation $zipCodeLocation): void - { - $this->assertSame('1000-001', $zipCodeLocation->getZipCode()); - $this->assertSame('Lisbon', $zipCodeLocation->getName()); - $this->assertSame('PT', $zipCodeLocation->getCountryCode()); - - $coordinate = $zipCodeLocation->getCoordinate(); - $this->assertInstanceOf(Coordinate::class, $coordinate); - $this->assertSame(38.7167, $coordinate->getLatitude()); - $this->assertSame(-9.1333, $coordinate->getLongitude()); - } -} \ No newline at end of file diff --git a/tests/HttpClientBuilderTest.php b/tests/HttpClientBuilderTest.php deleted file mode 100644 index ed3586d..0000000 --- a/tests/HttpClientBuilderTest.php +++ /dev/null @@ -1,61 +0,0 @@ -httpClientBuilder = new HttpClientBuilder(); - } - - public function testHttpClientBuilderGetPlugin() - { - $this->httpClientBuilder->addPlugin( - new HeaderDefaultsPlugin([ - 'Content-Type' => 'application/json', - 'Accept' => 'application/json' - ]) - ); - $this->httpClientBuilder->addPlugin( - new RetryPlugin([ - 'retries' => 3 - ]) - ); - - $this->assertInstanceOf( - HeaderDefaultsPlugin::class, - $this->httpClientBuilder->getPlugin(HeaderDefaultsPlugin::class) - ); - $this->assertInstanceOf( - RetryPlugin::class, - $this->httpClientBuilder->getPlugin(RetryPlugin::class) - ); - } - - public function testHttpClientBuilderGetHttpClient() - { - $this->assertInstanceOf(HttpMethodsClient::class, $this->httpClientBuilder->getHttpClient()); - } - - public function testHttpClientBuilderGetRequestFactory() - { - $this->assertInstanceOf(RequestFactoryInterface::class, $this->httpClientBuilder->getRequestFactory()); - } - - public function testHttpClientBuilderGetStreamFactory() - { - $this->assertInstanceOf(StreamFactoryInterface::class, $this->httpClientBuilder->getStreamFactory()); - } -} \ No newline at end of file diff --git a/tests/Integration/AirPollutionResourceTest.php b/tests/Integration/AirPollutionResourceTest.php new file mode 100644 index 0000000..753fe19 --- /dev/null +++ b/tests/Integration/AirPollutionResourceTest.php @@ -0,0 +1,58 @@ + [ + AirPollution::class, + MockResponse::AIR_POLLUTION_CURRENT, + 'airPollution', + 'getCurrent', + [50, 50] + ]; + yield 'get forecast' => [ + AirPollutionCollection::class, + MockResponse::AIR_POLLUTION_FORECAST, + 'airPollution', + 'getForecast', + [50, 50] + ]; + yield 'get history' => [ + AirPollutionCollection::class, + MockResponse::AIR_POLLUTION_HISTORY, + 'airPollution', + 'getHistory', + [50, 50, new \DateTime('-1 day'), new \DateTime('now')] + ]; + } + + public static function provideValidationExceptionData(): \Generator + { + yield 'get current, latitude lower than -90' => ['airPollution', 'getCurrent', [-91, 50]]; + yield 'get current, latitude greater than 90' => ['airPollution', 'getCurrent', [91, 50]]; + yield 'get current, longitude lower than -180' => ['airPollution', 'getCurrent', [50, -181]]; + yield 'get current, longitude greater than 180' => ['airPollution', 'getCurrent', [50, 181]]; + yield 'get forecast, latitude lower than -90' => ['airPollution', 'getForecast', [-91, 50]]; + yield 'get forecast, latitude greater than 90' => ['airPollution', 'getForecast', [91, 50]]; + yield 'get forecast, longitude lower than -180' => ['airPollution', 'getForecast', [50, -181]]; + yield 'get forecast, longitude greater than 180' => ['airPollution', 'getForecast', [50, 181]]; + yield 'get history, latitude lower than -90' => ['airPollution', 'getHistory', [-91, 50, new \DateTime('-1 day'), new \DateTime('now')]]; + yield 'get history, latitude greater than 90' => ['airPollution', 'getHistory', [91, 50, new \DateTime('-1 day'), new \DateTime('now')]]; + yield 'get history, longitude lower than -180' => ['airPollution', 'getHistory', [50, -181, new \DateTime('-1 day'), new \DateTime('now')]]; + yield 'get history, longitude greater than 180' => ['airPollution', 'getHistory', [50, 181, new \DateTime('-1 day'), new \DateTime('now')]]; + yield 'get history, end date before start date' => ['airPollution', 'getHistory', [50, 50, new \DateTime('now'), new \DateTime('-1 day')]]; + } +} \ No newline at end of file diff --git a/tests/Integration/CacheTraitTest.php b/tests/Integration/CacheTraitTest.php new file mode 100644 index 0000000..ec6395d --- /dev/null +++ b/tests/Integration/CacheTraitTest.php @@ -0,0 +1,39 @@ +createMock(CacheItemPoolInterface::class); + $cacheBuilder = new CacheBuilder($pool); + + $this->api->setCacheBuilder($cacheBuilder); + + $this->resource = new class($this->api) extends Resource { + use CacheTrait; + + public function getCacheTtl(): ?int + { + return $this->api->getCacheBuilder()?->getTtl(); + } + }; + } + + public function testMethods(): void + { + $this->assertSame(600, $this->resource->withCacheTtl(600)->getCacheTtl()); + $this->assertSame(60, $this->resource->getCacheTtl()); // back to default value + } +} \ No newline at end of file diff --git a/tests/Integration/GeocodingResourceTest.php b/tests/Integration/GeocodingResourceTest.php new file mode 100644 index 0000000..269372f --- /dev/null +++ b/tests/Integration/GeocodingResourceTest.php @@ -0,0 +1,61 @@ + [ + ZipLocation::class, + MockResponse::GEOCODING_ZIP, + 'geocoding', + 'getByZipCode', + ['1000-001', 'pt'] + ]; + } + + public static function provideCollectionResponseData(): \Generator + { + yield 'get by location name' => [ + Location::class, + MockResponse::GEOCODING_DIRECT, + 'geocoding', + 'getByLocationName', + ['test'] + ]; + yield 'get by coordinate' => [ + Location::class, + MockResponse::GEOCODING_REVERSE, + 'geocoding', + 'getByCoordinate', + [50, 50] + ]; + } + + public static function provideValidationExceptionData(): \Generator + { + yield 'get by location name, blank value' => ['geocoding', 'getByLocationName', ['']]; + yield 'get by location name, zero num results' => ['geocoding', 'getByLocationName', ['test', 0]]; + yield 'get by coordinate, latitude lower than -90' => ['geocoding', 'getByCoordinate', [-91, 50]]; + yield 'get by coordinate, latitude greater than 90' => ['geocoding', 'getByCoordinate', [91, 50]]; + yield 'get by coordinate, longitude lower than -180' => ['geocoding', 'getByCoordinate', [50, -181]]; + yield 'get by coordinate, longitude greater than 180' => ['geocoding', 'getByCoordinate', [50, 181]]; + yield 'get by coordinate, zero num results' => ['geocoding', 'getByCoordinate', [50, 50, 0]]; + yield 'get by zip code, blank zip code' => ['geocoding', 'getByZipCode', ['', 'pt']]; + yield 'get by zip code, blank country code' => ['geocoding', 'getByZipCode', ['1000-001', '']]; + yield 'get by zip code, invalid country code' => ['geocoding', 'getByZipCode', ['1000-001', 'invalid']]; + } +} \ No newline at end of file diff --git a/tests/Integration/LanguageTraitTest.php b/tests/Integration/LanguageTraitTest.php new file mode 100644 index 0000000..2419017 --- /dev/null +++ b/tests/Integration/LanguageTraitTest.php @@ -0,0 +1,39 @@ +resource = new class($this->api) extends Resource { + use LanguageTrait; + + public function getLanguage(): string + { + return $this->api->getQueryDefault('lang'); + } + }; + } + + public function testMethods(): void + { + $this->assertSame('pt', $this->resource->withLanguage('pt')->getLanguage()); + $this->assertSame('en', $this->resource->getLanguage()); // back to default value + } + + public function testValidationException(): void + { + $this->expectException(ValidationException::class); + $this->resource->withLanguage('invalid'); + } +} \ No newline at end of file diff --git a/tests/Integration/OneCallResourceTest.php b/tests/Integration/OneCallResourceTest.php new file mode 100644 index 0000000..b9f2c29 --- /dev/null +++ b/tests/Integration/OneCallResourceTest.php @@ -0,0 +1,58 @@ + [ + Weather::class, + MockResponse::ONE_CALL_WEATHER, + 'oneCall', + 'getWeather', + [50, 50] + ]; + yield 'get weather by date' => [ + WeatherMoment::class, + MockResponse::ONE_CALL_TIMEMACHINE, + 'oneCall', + 'getWeatherByDate', + [50, 50, new \DateTime()] + ]; + yield 'get weather summary by date' => [ + WeatherSummary::class, + MockResponse::ONE_CALL_DAY_SUMMARY, + 'oneCall', + 'getWeatherSummaryByDate', + [50, 50, new \DateTime()] + ]; + } + + public static function provideValidationExceptionData(): \Generator + { + yield 'get weather, latitude lower than -90' => ['oneCall', 'getWeather', [-91, 50]]; + yield 'get weather, latitude greater than 90' => ['oneCall', 'getWeather', [91, 50]]; + yield 'get weather, longitude lower than -180' => ['oneCall', 'getWeather', [50, -181]]; + yield 'get weather, longitude greater than 180' => ['oneCall', 'getWeather', [50, 181]]; + yield 'get weather by date, latitude lower than -90' => ['oneCall', 'getWeatherByDate', [-91, 50, new \DateTime()]]; + yield 'get weather by date, latitude greater than 90' => ['oneCall', 'getWeatherByDate', [91, 50, new \DateTime()]]; + yield 'get weather by date, longitude lower than -180' => ['oneCall', 'getWeatherByDate', [50, -181, new \DateTime()]]; + yield 'get weather by date, longitude greater than 180' => ['oneCall', 'getWeatherByDate', [50, 181, new \DateTime()]]; + yield 'get weather summary by date, latitude lower than -90' => ['oneCall', 'getWeatherSummaryByDate', [-91, 50, new \DateTime()]]; + yield 'get weather summary by date, latitude greater than 90' => ['oneCall', 'getWeatherSummaryByDate', [91, 50, new \DateTime()]]; + yield 'get weather summary by date, longitude lower than -180' => ['oneCall', 'getWeatherSummaryByDate', [50, -181, new \DateTime()]]; + yield 'get weather summary by date, longitude greater than 180' => ['oneCall', 'getWeatherSummaryByDate', [50, 181, new \DateTime()]]; + } +} \ No newline at end of file diff --git a/tests/Integration/OpenWeatherMapTest.php b/tests/Integration/OpenWeatherMapTest.php new file mode 100644 index 0000000..76f4bca --- /dev/null +++ b/tests/Integration/OpenWeatherMapTest.php @@ -0,0 +1,20 @@ +assertInstanceOf(OneCallResource::class, $this->api->oneCall()); + $this->assertInstanceOf(WeatherResource::class, $this->api->weather()); + $this->assertInstanceOf(AirPollutionResource::class, $this->api->airPollution()); + $this->assertInstanceOf(GeocodingResource::class, $this->api->geocoding()); + } +} \ No newline at end of file diff --git a/tests/Integration/ResourceTest.php b/tests/Integration/ResourceTest.php new file mode 100644 index 0000000..7638911 --- /dev/null +++ b/tests/Integration/ResourceTest.php @@ -0,0 +1,56 @@ +resource = new class($this->api) extends Resource { + public function request(): void + { + $this->api->request( + method: Method::GET, + path: '/test' + ); + } + }; + } + + #[DataProvider(methodName: 'provideApiErrorData')] + public function testApiError(int $statusCode, string $exception): void + { + $this->mockClient->addResponse(new Response( + status: $statusCode, + body: MockResponse::API_ERROR + )); + + $this->expectException($exception); + $this->resource->request(); + } + + public static function provideApiErrorData(): \Generator + { + yield 'bad request' => [400, BadRequestException::class]; + yield 'unauthorized' => [401, UnauthorizedException::class]; + yield 'not found' => [404, NotFoundException::class]; + yield 'too many requests' => [429, TooManyRequestsException::class]; + yield 'unexpected error' => [500, UnexpectedErrorException::class]; + } +} \ No newline at end of file diff --git a/tests/Integration/UnitSystemTraitTest.php b/tests/Integration/UnitSystemTraitTest.php new file mode 100644 index 0000000..629c3b6 --- /dev/null +++ b/tests/Integration/UnitSystemTraitTest.php @@ -0,0 +1,39 @@ +resource = new class($this->api) extends Resource { + use UnitSystemTrait; + + public function getUnitSystem(): string + { + return $this->api->getQueryDefault('units'); + } + }; + } + + public function testMethods(): void + { + $this->assertSame('imperial', $this->resource->withUnitSystem('imperial')->getUnitSystem()); + $this->assertSame('metric', $this->resource->getUnitSystem()); // back to default value + } + + public function testValidationException(): void + { + $this->expectException(ValidationException::class); + $this->resource->withUnitSystem('invalid'); + } +} \ No newline at end of file diff --git a/tests/Integration/WeatherResourceTest.php b/tests/Integration/WeatherResourceTest.php new file mode 100644 index 0000000..de960b8 --- /dev/null +++ b/tests/Integration/WeatherResourceTest.php @@ -0,0 +1,47 @@ + [ + Weather::class, + MockResponse::WEATHER_CURRENT, + 'weather', + 'getCurrent', + [50, 50] + ]; + yield 'get forecast' => [ + WeatherCollection::class, + MockResponse::WEATHER_FORECAST, + 'weather', + 'getForecast', + [50, 50] + ]; + } + + public static function provideValidationExceptionData(): \Generator + { + yield 'get current, latitude lower than -90' => ['weather', 'getCurrent', [-91, 50]]; + yield 'get current, latitude greater than 90' => ['weather', 'getCurrent', [91, 50]]; + yield 'get current, longitude lower than -180' => ['weather', 'getCurrent', [50, -181]]; + yield 'get current, longitude greater than 180' => ['weather', 'getCurrent', [50, 181]]; + yield 'get forecast, latitude lower than -90' => ['weather', 'getForecast', [-91, 50]]; + yield 'get forecast, latitude greater than 90' => ['weather', 'getForecast', [91, 50]]; + yield 'get forecast, longitude lower than -180' => ['weather', 'getForecast', [50, -181]]; + yield 'get forecast, longitude greater than 180' => ['weather', 'getForecast', [50, 181]]; + yield 'get forecast, zero num results' => ['weather', 'getForecast', [50, 50, 0]]; + } +} \ No newline at end of file diff --git a/tests/LanguageTraitTest.php b/tests/LanguageTraitTest.php deleted file mode 100644 index b85a06e..0000000 --- a/tests/LanguageTraitTest.php +++ /dev/null @@ -1,24 +0,0 @@ -assertSame( - 'pt', - $this->givenApi()->weather()->withLanguage('pt')->getLanguage() - ); - } - - #[DataProviderExternal(InvalidParamDataProvider::class, 'provideInvalidLanguageData')] - public function testLanguageTraitWithLanguageWithInvalidValue(string $language, string $expectedException) - { - $this->expectException($expectedException); - $this->givenApi()->weather()->withLanguage($language); - } -} \ No newline at end of file diff --git a/tests/OneCallEndpointTest.php b/tests/OneCallEndpointTest.php deleted file mode 100644 index 81e44d5..0000000 --- a/tests/OneCallEndpointTest.php +++ /dev/null @@ -1,338 +0,0 @@ - [ - MockResponse::ONE_CALL_WEATHER, - 'oneCall', - 'getWeather', - [50, 50], - 'assertGetWeatherResponse' - ]; - yield 'get history moment' => [ - MockResponse::ONE_CALL_HISTORY_MOMENT, - 'oneCall', - 'getHistoryMoment', - [50, 50, new \DateTime('yesterday')], - 'assertGetHistoryMomentResponse' - ]; - yield 'get history aggregate' => [ - MockResponse::ONE_CALL_HISTORY_AGGREGATE, - 'oneCall', - 'getHistoryAggregate', - [50, 50, new \DateTime('yesterday')], - 'assertGetHistoryAggregateResponse' - ]; - } - - public static function provideEndpointInvalidResponseData(): \Generator - { - yield 'get weather, latitude lower than -90' => ['oneCall', 'getWeather', [-91, 50]]; - yield 'get weather, latitude greater than 90' => ['oneCall', 'getWeather', [91, 50]]; - yield 'get weather, longitude lower than -180' => ['oneCall', 'getWeather', [50, -181]]; - yield 'get weather, longitude greater than 180' => ['oneCall', 'getWeather', [50, 181]]; - - yield 'get history moment, latitude lower than -90' => ['oneCall', 'getHistoryMoment', [-91, 50, new \DateTime('yesterday')]]; - yield 'get history moment, latitude greater than 90' => ['oneCall', 'getHistoryMoment', [91, 50, new \DateTime('yesterday')]]; - yield 'get history moment, longitude lower than -180' => ['oneCall', 'getHistoryMoment', [50, -181, new \DateTime('yesterday')]]; - yield 'get history moment, longitude greater than 180' => ['oneCall', 'getHistoryMoment', [50, 181, new \DateTime('yesterday')]]; - yield 'get history moment, invalid past date' => ['oneCall', 'getHistoryMoment', [50, 50, new \DateTime('tomorrow')]]; - - yield 'get history aggregate, latitude lower than -90' => ['oneCall', 'getHistoryAggregate', [-91, 50, new \DateTime('yesterday')]]; - yield 'get history aggregate, latitude greater than 90' => ['oneCall', 'getHistoryAggregate', [91, 50, new \DateTime('yesterday')]]; - yield 'get history aggregate, longitude lower than -180' => ['oneCall', 'getHistoryAggregate', [50, -181, new \DateTime('yesterday')]]; - yield 'get history aggregate, longitude greater than 180' => ['oneCall', 'getHistoryAggregate', [50, 181, new \DateTime('yesterday')]]; - yield 'get history aggregate, invalid past date' => ['oneCall', 'getHistoryAggregate', [50, 50, new \DateTime('tomorrow')]]; - } - - public function testOneCallMethodsExist() - { - $this->assertSame(true, method_exists(OneCallEndpoint::class, 'withUnitSystem')); - $this->assertSame(true, method_exists(OneCallEndpoint::class, 'withLanguage')); - $this->assertSame(true, method_exists(OneCallEndpoint::class, 'withCacheTtl')); - } - - private function assertGetWeatherResponse(OneCall $oneCall): void - { - // Coordinate - $coordinate = $oneCall->getCoordinate(); - $this->assertInstanceOf(Coordinate::class, $coordinate); - $this->assertSame(38.7078, $coordinate->getLatitude()); - $this->assertSame(-9.1366, $coordinate->getLongitude()); - - // Timezone - $timezone = $oneCall->getTimezone(); - $this->assertInstanceOf(Timezone::class, $timezone); - $this->assertSame('Europe/Lisbon', $timezone->getIdentifier()); - $this->assertSame(3600, $timezone->getOffset()); - - // Current - $current = $oneCall->getCurrent(); - $this->assertInstanceOf(Weather::class, $current); - $this->assertSame(null, $current->getMoonriseAt()); - $this->assertSame(null, $current->getMoonsetAt()); - $this->assertSame(null, $current->getMoonPhase()); - $this->assertSame(25.1, $current->getTemperature()); - $this->assertSame(25.21, $current->getTemperatureFeelsLike()); - $this->assertSame(null, $current->getDescription()); - $this->assertSame(1017, $current->getAtmosphericPressure()); - $this->assertSame(59, $current->getHumidity()); - $this->assertSame(16.53, $current->getDewPoint()); - $this->assertSame(9.78, $current->getUltraVioletIndex()); - $this->assertSame(20, $current->getCloudiness()); - $this->assertSame(10000, $current->getVisibility()); - $this->assertSame(null, $current->getPrecipitationProbability()); - $this->assertSame(null, $current->getRain()); - $this->assertSame(null, $current->getSnow()); - $this->assertSame('2023-07-03 11:35:39', $current->getDateTime()->format('Y-m-d H:i:s')); - $this->assertSame('2023-07-03 05:16:08', $current->getSunriseAt()->format('Y-m-d H:i:s')); - $this->assertSame('2023-07-03 20:04:57', $current->getSunsetAt()->format('Y-m-d H:i:s')); - - $currentWind = $current->getWind(); - $this->assertInstanceOf(Wind::class, $currentWind); - $this->assertSame(7.2, $currentWind->getSpeed()); - $this->assertSame(10, $currentWind->getDirection()); - $this->assertSame(null, $currentWind->getGust()); - - $currentWeatherConditions = $current->getWeatherConditions(); - $this->assertContainsOnlyInstancesOf(WeatherCondition::class, $currentWeatherConditions); - $this->assertSame(801, $currentWeatherConditions[0]->getId()); - $this->assertSame('Clouds', $currentWeatherConditions[0]->getName()); - $this->assertSame('few clouds', $currentWeatherConditions[0]->getDescription()); - $this->assertSame('CLOUDS', $currentWeatherConditions[0]->getSysName()); - - $currentWeatherConditionsIcon = $currentWeatherConditions[0]->getIcon(); - $this->assertInstanceOf(Icon::class, $currentWeatherConditionsIcon); - $this->assertSame('02d', $currentWeatherConditionsIcon->getId()); - $this->assertSame('https://openweathermap.org/img/wn/02d@4x.png', $currentWeatherConditionsIcon->getImageUrl()); - - // MinutelyForecast - $minutelyForecast = $oneCall->getMinutelyForecast(); - $this->assertContainsOnlyInstancesOf(MinuteForecast::class, $minutelyForecast); - $this->assertSame(0.0, $minutelyForecast[0]->getPrecipitation()); - $this->assertSame('2023-07-03 11:36:00', $minutelyForecast[0]->getDateTime()->format('Y-m-d H:i:s')); - - // HourlyForecast - $hourlyForecast = $oneCall->getHourlyForecast(); - $this->assertContainsOnlyInstancesOf(Weather::class, $hourlyForecast); - $this->assertSame(null, $hourlyForecast[0]->getSunriseAt()); - $this->assertSame(null, $hourlyForecast[0]->getSunsetAt()); - $this->assertSame(null, $hourlyForecast[0]->getMoonriseAt()); - $this->assertSame(null, $hourlyForecast[0]->getMoonsetAt()); - $this->assertSame(null, $hourlyForecast[0]->getMoonPhase()); - $this->assertSame(25.38, $hourlyForecast[0]->getTemperature()); - $this->assertSame(25.44, $hourlyForecast[0]->getTemperatureFeelsLike()); - $this->assertSame(null, $hourlyForecast[0]->getDescription()); - $this->assertSame(1017, $hourlyForecast[0]->getAtmosphericPressure()); - $this->assertSame(56, $hourlyForecast[0]->getHumidity()); - $this->assertSame(15.97, $hourlyForecast[0]->getDewPoint()); - $this->assertSame(8.42, $hourlyForecast[0]->getUltraVioletIndex()); - $this->assertSame(16, $hourlyForecast[0]->getCloudiness()); - $this->assertSame(10000, $hourlyForecast[0]->getVisibility()); - $this->assertSame(0, $hourlyForecast[0]->getPrecipitationProbability()); - $this->assertSame(null, $hourlyForecast[0]->getRain()); - $this->assertSame(null, $hourlyForecast[0]->getSnow()); - $this->assertSame('2023-07-03 11:00:00', $hourlyForecast[0]->getDateTime()->format('Y-m-d H:i:s')); - - $hourlyForecastWind = $hourlyForecast[0]->getWind(); - $this->assertInstanceOf(Wind::class, $hourlyForecastWind); - $this->assertSame(4.94, $hourlyForecastWind->getSpeed()); - $this->assertSame(327, $hourlyForecastWind->getDirection()); - $this->assertSame(6.14, $hourlyForecastWind->getGust()); - - $hourlyForecastWeatherConditions = $hourlyForecast[0]->getWeatherConditions(); - $this->assertContainsOnlyInstancesOf(WeatherCondition::class, $hourlyForecastWeatherConditions); - $this->assertSame(801, $hourlyForecastWeatherConditions[0]->getId()); - $this->assertSame('Clouds', $hourlyForecastWeatherConditions[0]->getName()); - $this->assertSame('few clouds', $hourlyForecastWeatherConditions[0]->getDescription()); - $this->assertSame('CLOUDS', $hourlyForecastWeatherConditions[0]->getSysName()); - - $hourlyForecastWeatherConditionsIcon = $hourlyForecastWeatherConditions[0]->getIcon(); - $this->assertInstanceOf(Icon::class, $hourlyForecastWeatherConditionsIcon); - $this->assertSame('02d', $hourlyForecastWeatherConditionsIcon->getId()); - $this->assertSame('https://openweathermap.org/img/wn/02d@4x.png', $hourlyForecastWeatherConditionsIcon->getImageUrl()); - - // DailyForecast - $dailyForecast = $oneCall->getDailyForecast(); - $this->assertContainsOnlyInstancesOf(Weather::class, $dailyForecast); - $this->assertSame('Expect a day of partly cloudy with clear spells', $dailyForecast[0]->getDescription()); - $this->assertSame(1017, $dailyForecast[0]->getAtmosphericPressure()); - $this->assertSame(59, $dailyForecast[0]->getHumidity()); - $this->assertSame(16.53, $dailyForecast[0]->getDewPoint()); - $this->assertSame(9.92, $dailyForecast[0]->getUltraVioletIndex()); - $this->assertSame(20, $dailyForecast[0]->getCloudiness()); - $this->assertSame(null, $dailyForecast[0]->getVisibility()); - $this->assertSame(0, $dailyForecast[0]->getPrecipitationProbability()); - $this->assertSame(null, $dailyForecast[0]->getRain()); - $this->assertSame(null, $dailyForecast[0]->getSnow()); - $this->assertSame('2023-07-03 12:00:00', $dailyForecast[0]->getDateTime()->format('Y-m-d H:i:s')); - $this->assertSame('2023-07-03 05:16:08', $dailyForecast[0]->getSunriseAt()->format('Y-m-d H:i:s')); - $this->assertSame('2023-07-03 20:04:57', $dailyForecast[0]->getSunsetAt()->format('Y-m-d H:i:s')); - $this->assertSame('2023-07-03 20:45:00', $dailyForecast[0]->getMoonriseAt()->format('Y-m-d H:i:s')); - $this->assertSame('2023-07-03 04:44:00', $dailyForecast[0]->getMoonsetAt()->format('Y-m-d H:i:s')); - - $dailyForecastTemperature = $dailyForecast[0]->getTemperature(); - $this->assertInstanceOf(Temperature::class, $dailyForecastTemperature); - $this->assertSame(18.39, $dailyForecastTemperature->getMorning()); - $this->assertSame(25.1, $dailyForecastTemperature->getDay()); - $this->assertSame(24.67, $dailyForecastTemperature->getEvening()); - $this->assertSame(19.41, $dailyForecastTemperature->getNight()); - $this->assertSame(18.28, $dailyForecastTemperature->getMin()); - $this->assertSame(26.88, $dailyForecastTemperature->getMax()); - - $dailyForecastTemperatureFeelsLike = $dailyForecast[0]->getTemperatureFeelsLike(); - $this->assertInstanceOf(Temperature::class, $dailyForecastTemperature); - $this->assertSame(18.5, $dailyForecastTemperatureFeelsLike->getMorning()); - $this->assertSame(25.21, $dailyForecastTemperatureFeelsLike->getDay()); - $this->assertSame(24.52, $dailyForecastTemperatureFeelsLike->getEvening()); - $this->assertSame(19.5, $dailyForecastTemperatureFeelsLike->getNight()); - $this->assertSame(null, $dailyForecastTemperatureFeelsLike->getMin()); - $this->assertSame(null, $dailyForecastTemperatureFeelsLike->getMax()); - - $dailyForecastWind = $dailyForecast[0]->getWind(); - $this->assertInstanceOf(Wind::class, $dailyForecastWind); - $this->assertSame(8.8, $dailyForecastWind->getSpeed()); - $this->assertSame(347, $dailyForecastWind->getDirection()); - $this->assertSame(13.04, $dailyForecastWind->getGust()); - - $dailyForecastWeatherConditions = $dailyForecast[0]->getWeatherConditions(); - $this->assertContainsOnlyInstancesOf(WeatherCondition::class, $dailyForecastWeatherConditions); - $this->assertSame(801, $dailyForecastWeatherConditions[0]->getId()); - $this->assertSame('Clouds', $dailyForecastWeatherConditions[0]->getName()); - $this->assertSame('few clouds', $dailyForecastWeatherConditions[0]->getDescription()); - $this->assertSame('CLOUDS', $dailyForecastWeatherConditions[0]->getSysName()); - - $dailyForecastWeatherConditionsIcon = $dailyForecastWeatherConditions[0]->getIcon(); - $this->assertInstanceOf(Icon::class, $dailyForecastWeatherConditionsIcon); - $this->assertSame('02d', $dailyForecastWeatherConditionsIcon->getId()); - $this->assertSame('https://openweathermap.org/img/wn/02d@4x.png', $dailyForecastWeatherConditionsIcon->getImageUrl()); - - $dailyForecastMoonPhase = $dailyForecast[0]->getMoonPhase(); - $this->assertInstanceOf(MoonPhase::class, $dailyForecastMoonPhase); - $this->assertSame(0.5, $dailyForecastMoonPhase->getValue()); - $this->assertSame('Full moon', $dailyForecastMoonPhase->getName()); - $this->assertSame('FULL_MOON', $dailyForecastMoonPhase->getSysName()); - - // Alerts - $alerts = $oneCall->getAlerts(); - $this->assertContainsOnlyInstancesOf(Alert::class, $alerts); - $this->assertSame('NWS Portland (Northwest Oregon and Southwest Washington)', $alerts[0]->getSenderName()); - $this->assertSame('Heat Advisory', $alerts[0]->getEventName()); - $this->assertStringStartsWith('...HEAT ADVISORY REMAINS', $alerts[0]->getDescription()); - $this->assertIsArray($alerts[0]->getTags()); - $this->assertSame('2023-07-04 17:00:00', $alerts[0]->getStartsAt()->format('Y-m-d H:i:s')); - $this->assertSame('2023-07-06 06:00:00', $alerts[0]->getEndsAt()->format('Y-m-d H:i:s')); - } - - private function assertGetHistoryMomentResponse(WeatherLocation $weatherLocation): void - { - $this->assertInstanceOf(WeatherLocation::class, $weatherLocation); - - $this->assertSame(null, $weatherLocation->getMoonriseAt()); - $this->assertSame(null, $weatherLocation->getMoonsetAt()); - $this->assertSame(null, $weatherLocation->getMoonPhase()); - $this->assertSame(17.48, $weatherLocation->getTemperature()); - $this->assertSame(17.16, $weatherLocation->getTemperatureFeelsLike()); - $this->assertSame(null, $weatherLocation->getDescription()); - $this->assertSame(1019, $weatherLocation->getAtmosphericPressure()); - $this->assertSame(72, $weatherLocation->getHumidity()); - $this->assertSame(12.38, $weatherLocation->getDewPoint()); - $this->assertSame(null, $weatherLocation->getUltraVioletIndex()); - $this->assertSame(20, $weatherLocation->getCloudiness()); - $this->assertSame(9999, $weatherLocation->getVisibility()); - $this->assertSame(null, $weatherLocation->getPrecipitationProbability()); - $this->assertSame(null, $weatherLocation->getRain()); - $this->assertSame(null, $weatherLocation->getSnow()); - $this->assertSame('2023-01-01 00:00:00', $weatherLocation->getDateTime()->format('Y-m-d H:i:s')); - $this->assertSame('2023-01-01 07:54:31', $weatherLocation->getSunriseAt()->format('Y-m-d H:i:s')); - $this->assertSame('2023-01-01 17:25:02', $weatherLocation->getSunsetAt()->format('Y-m-d H:i:s')); - - $wind = $weatherLocation->getWind(); - $this->assertInstanceOf(Wind::class, $wind); - $this->assertSame(16.54, $wind->getSpeed()); - $this->assertSame(337, $wind->getDirection()); - $this->assertSame(16.54, $wind->getGust()); - - $weatherConditions = $weatherLocation->getWeatherConditions(); - $this->assertContainsOnlyInstancesOf(WeatherCondition::class, $weatherConditions); - $this->assertSame(801, $weatherConditions[0]->getId()); - $this->assertSame('Clouds', $weatherConditions[0]->getName()); - $this->assertSame('few clouds', $weatherConditions[0]->getDescription()); - $this->assertSame('CLOUDS', $weatherConditions[0]->getSysName()); - - $weatherConditionsIcon = $weatherConditions[0]->getIcon(); - $this->assertInstanceOf(Icon::class, $weatherConditionsIcon); - $this->assertSame('02n', $weatherConditionsIcon->getId()); - $this->assertSame('https://openweathermap.org/img/wn/02n@4x.png', $weatherConditionsIcon->getImageUrl()); - - $coordinate = $weatherLocation->getCoordinate(); - $this->assertInstanceOf(Coordinate::class, $coordinate); - $this->assertSame(38.7078, $coordinate->getLatitude()); - $this->assertSame(-9.1366, $coordinate->getLongitude()); - - $timezone = $weatherLocation->getTimezone(); - $this->assertInstanceOf(Timezone::class, $timezone); - $this->assertSame('Europe/Lisbon', $timezone->getIdentifier()); - $this->assertSame(0, $timezone->getOffset()); - } - - private function assertGetHistoryAggregateResponse(WeatherAggregate $weatherAggregate): void - { - $this->assertInstanceOf(WeatherAggregate::class, $weatherAggregate); - - $this->assertSame(75, $weatherAggregate->getCloudiness()); - $this->assertSame(71, $weatherAggregate->getHumidity()); - $this->assertSame(2.53, $weatherAggregate->getPrecipitation()); - $this->assertSame(1017, $weatherAggregate->getAtmosphericPressure()); - $this->assertSame('2023-01-01 00:00:00', $weatherAggregate->getDateTime()->format('Y-m-d H:i:s')); - - $coordinate = $weatherAggregate->getCoordinate(); - $this->assertInstanceOf(Coordinate::class, $coordinate); - $this->assertSame(38.7077507, $coordinate->getLatitude()); - $this->assertSame(-9.1365919, $coordinate->getLongitude()); - - $timezone = $weatherAggregate->getTimezone(); - $this->assertInstanceOf(Timezone::class, $timezone); - $this->assertSame(null, $timezone->getIdentifier()); - $this->assertSame(0, $timezone->getOffset()); - - $temperature = $weatherAggregate->getTemperature(); - $this->assertInstanceOf(Temperature::class, $temperature); - $this->assertSame(17.23, $temperature->getMorning()); - $this->assertSame(18.26, $temperature->getDay()); - $this->assertSame(13.9, $temperature->getEvening()); - $this->assertSame(17.39, $temperature->getNight()); - $this->assertSame(12.52, $temperature->getMin()); - $this->assertSame(18.29, $temperature->getMax()); - - $wind = $weatherAggregate->getWind(); - $this->assertInstanceOf(Wind::class, $wind); - $this->assertSame(26.38, $wind->getSpeed()); - $this->assertSame(225, $wind->getDirection()); - $this->assertSame(null, $wind->getGust()); - } -} \ No newline at end of file diff --git a/tests/OpenWeatherMapTest.php b/tests/OpenWeatherMapTest.php deleted file mode 100644 index 91b53bf..0000000 --- a/tests/OpenWeatherMapTest.php +++ /dev/null @@ -1,28 +0,0 @@ -assertInstanceOf($instance, $this->givenApi()->$methodName()); - } - - public static function provideMethodsData(): \Generator - { - yield 'config' => [Config::class, 'config']; - yield 'air pollution' => [AirPollutionEndpoint::class, 'airPollution']; - yield 'geocoding' => [GeocodingEndpoint::class, 'geocoding']; - yield 'one call' => [OneCallEndpoint::class, 'oneCall']; - yield 'weather' => [WeatherEndpoint::class, 'weather']; - } -} \ No newline at end of file diff --git a/tests/Unit/AirPollution/AirPollutionCollectionTest.php b/tests/Unit/AirPollution/AirPollutionCollectionTest.php new file mode 100644 index 0000000..e8ee297 --- /dev/null +++ b/tests/Unit/AirPollution/AirPollutionCollectionTest.php @@ -0,0 +1,43 @@ + [ + 'lat' => 50, + 'lon' => 50 + ], + 'list' => [ + [ + 'dt' => 1715279409, + 'main' => [ + 'aqi' => 1 + ], + 'components' => [ + 'co' => 100, + 'no' => 0, + 'no2' => 1, + 'o3' => 100, + 'so2' => 1, + 'pm2_5' => 1, + 'pm10' => 1, + 'nh3' => 1 + ] + ] + ] + ]); + + $this->assertSame(1, $entity->getNumResults()); + $this->assertInstanceOf(Coordinate::class, $entity->getCoordinate()); + $this->assertContainsOnlyInstancesOf(AirPollutionData::class, $entity->getData()); + } +} \ No newline at end of file diff --git a/tests/Unit/AirPollution/AirPollutionDataTest.php b/tests/Unit/AirPollution/AirPollutionDataTest.php new file mode 100644 index 0000000..68da780 --- /dev/null +++ b/tests/Unit/AirPollution/AirPollutionDataTest.php @@ -0,0 +1,41 @@ + 1715279409, + 'main' => [ + 'aqi' => 1 + ], + 'components' => [ + 'co' => 100, + 'no' => 0, + 'no2' => 1, + 'o3' => 100, + 'so2' => 1, + 'pm2_5' => 1, + 'pm10' => 1, + 'nh3' => 1 + ] + ]); + + $this->assertInstanceOf(\DateTimeImmutable::class, $entity->getDateTime()); + $this->assertInstanceOf(AirQuality::class, $entity->getAirQuality()); + $this->assertSame(100.0, $entity->getCarbonMonoxide()); + $this->assertSame(0.0, $entity->getNitrogenMonoxide()); + $this->assertSame(1.0, $entity->getNitrogenDioxide()); + $this->assertSame(100.0, $entity->getOzone()); + $this->assertSame(1.0, $entity->getSulphurDioxide()); + $this->assertSame(1.0, $entity->getFineParticulateMatter()); + $this->assertSame(1.0, $entity->getCoarseParticulateMatter()); + $this->assertSame(1.0, $entity->getAmmonia()); + } +} \ No newline at end of file diff --git a/tests/Unit/AirPollution/AirPollutionTest.php b/tests/Unit/AirPollution/AirPollutionTest.php new file mode 100644 index 0000000..76902f7 --- /dev/null +++ b/tests/Unit/AirPollution/AirPollutionTest.php @@ -0,0 +1,51 @@ + [ + 'lat' => 50, + 'lon' => 50 + ], + 'list' => [ + [ + 'dt' => 1715279409, + 'main' => [ + 'aqi' => 1 + ], + 'components' => [ + 'co' => 100, + 'no' => 0, + 'no2' => 1, + 'o3' => 100, + 'so2' => 1, + 'pm2_5' => 1, + 'pm10' => 1, + 'nh3' => 1 + ] + ] + ] + ]); + + $this->assertInstanceOf(Coordinate::class, $entity->getCoordinate()); + $this->assertInstanceOf(\DateTimeImmutable::class, $entity->getDateTime()); + $this->assertInstanceOf(AirQuality::class, $entity->getAirQuality()); + $this->assertSame(100.0, $entity->getCarbonMonoxide()); + $this->assertSame(0.0, $entity->getNitrogenMonoxide()); + $this->assertSame(1.0, $entity->getNitrogenDioxide()); + $this->assertSame(100.0, $entity->getOzone()); + $this->assertSame(1.0, $entity->getSulphurDioxide()); + $this->assertSame(1.0, $entity->getFineParticulateMatter()); + $this->assertSame(1.0, $entity->getCoarseParticulateMatter()); + $this->assertSame(1.0, $entity->getAmmonia()); + } +} \ No newline at end of file diff --git a/tests/Unit/AirPollution/AirQualityTest.php b/tests/Unit/AirPollution/AirQualityTest.php new file mode 100644 index 0000000..cae7f29 --- /dev/null +++ b/tests/Unit/AirPollution/AirQualityTest.php @@ -0,0 +1,19 @@ + 1 + ]); + + $this->assertSame(1, $entity->getIndex()); + $this->assertSame('Good', $entity->getQualitativeName()); + } +} \ No newline at end of file diff --git a/tests/Unit/ConditionTest.php b/tests/Unit/ConditionTest.php new file mode 100644 index 0000000..2aa2c7f --- /dev/null +++ b/tests/Unit/ConditionTest.php @@ -0,0 +1,26 @@ + 200, + 'main' => 'name', + 'description' => 'description', + 'icon' => '01d' + ]); + + $this->assertSame(200, $entity->getId()); + $this->assertSame('name', $entity->getName()); + $this->assertSame('description', $entity->getDescription()); + $this->assertInstanceOf(Icon::class, $entity->getIcon()); + $this->assertSame('THUNDERSTORM', $entity->getSystemName()); + } +} \ No newline at end of file diff --git a/tests/Unit/CoordinateTest.php b/tests/Unit/CoordinateTest.php new file mode 100644 index 0000000..8ef62b9 --- /dev/null +++ b/tests/Unit/CoordinateTest.php @@ -0,0 +1,20 @@ + 50, + 'lon' => 50 + ]); + + $this->assertSame(50.0, $entity->getLatitude()); + $this->assertSame(50.0, $entity->getLongitude()); + } +} \ No newline at end of file diff --git a/tests/Unit/Geocoding/ZipLocationTest.php b/tests/Unit/Geocoding/ZipLocationTest.php new file mode 100644 index 0000000..f335f7f --- /dev/null +++ b/tests/Unit/Geocoding/ZipLocationTest.php @@ -0,0 +1,26 @@ + '1234-567', + 'name' => 'name', + 'country' => 'PT', + 'lat' => 50, + 'lon' => 50 + ]); + + $this->assertSame('1234-567', $entity->getZipCode()); + $this->assertSame('name', $entity->getName()); + $this->assertSame('PT', $entity->getCountryCode()); + $this->assertInstanceOf(Coordinate::class, $entity->getCoordinate()); + } +} \ No newline at end of file diff --git a/tests/Unit/IconTest.php b/tests/Unit/IconTest.php new file mode 100644 index 0000000..7d0d984 --- /dev/null +++ b/tests/Unit/IconTest.php @@ -0,0 +1,19 @@ + '01d' + ]); + + $this->assertSame('01d', $entity->getId()); + $this->assertSame('https://openweathermap.org/img/wn/01d@4x.png', $entity->getUrl()); + } +} \ No newline at end of file diff --git a/tests/Unit/LocationTest.php b/tests/Unit/LocationTest.php new file mode 100644 index 0000000..fd1bae2 --- /dev/null +++ b/tests/Unit/LocationTest.php @@ -0,0 +1,45 @@ + 50, + 'lon' => 50, + 'id' => 100, + 'name' => 'name', + 'state' => 'state', + 'country' => 'PT', + 'local_names' => [ + 'en' => 'local name' + ], + 'population' => 100, + 'timezone_offset' => 0, + 'sunrise' => 1661834187, + 'sunset' => 1661882248 + ]); + + $this->assertInstanceOf(Coordinate::class, $entity->getCoordinate()); + $this->assertSame(100, $entity->getId()); + $this->assertSame('name', $entity->getName()); + $this->assertSame('state', $entity->getState()); + $this->assertSame('PT', $entity->getCountryCode()); + + $this->assertSame(['en' => 'local name'], $entity->getLocalNames()); + $this->assertSame('local name', $entity->getLocalName('en')); + $this->assertSame(null, $entity->getLocalName('pt')); + + $this->assertSame(100, $entity->getPopulation()); + $this->assertInstanceOf(Timezone::class, $entity->getTimezone()); + $this->assertInstanceOf(\DateTimeImmutable::class, $entity->getSunriseAt()); + $this->assertInstanceOf(\DateTimeImmutable::class, $entity->getSunsetAt()); + } +} \ No newline at end of file diff --git a/tests/Unit/OneCall/AlertTest.php b/tests/Unit/OneCall/AlertTest.php new file mode 100644 index 0000000..d143863 --- /dev/null +++ b/tests/Unit/OneCall/AlertTest.php @@ -0,0 +1,28 @@ + 'sender name', + 'event' => 'event name', + 'start' => 1715561801, + 'end' => 1715616968, + 'description' => 'description', + 'tags' => ['tag1', 'tag2'] + ]); + + $this->assertSame('sender name', $entity->getSenderName()); + $this->assertSame('event name', $entity->getEventName()); + $this->assertInstanceOf(\DateTimeImmutable::class, $entity->getStartsAt()); + $this->assertInstanceOf(\DateTimeImmutable::class, $entity->getEndsAt()); + $this->assertSame('description', $entity->getDescription()); + $this->assertSame(['tag1', 'tag2'], $entity->getTags()); + } +} \ No newline at end of file diff --git a/tests/Unit/OneCall/DayDataTest.php b/tests/Unit/OneCall/DayDataTest.php new file mode 100644 index 0000000..9b381b4 --- /dev/null +++ b/tests/Unit/OneCall/DayDataTest.php @@ -0,0 +1,79 @@ + 1715561801, + 'pressure' => 1000, + 'humidity' => 10, + 'dew_point' => 10, + 'uvi' => 1, + 'clouds' => 10, + 'wind_speed' => 10, + 'wind_deg' => 10, + 'wind_gust' => 10, + 'weather' => [ + [ + 'id' => 200, + 'main' => 'name', + 'description' => 'description', + 'icon' => '01d' + ] + ], + 'rain' => 1, + 'snow' => 1, + 'temp' => [ + 'morn' => 10, + 'day' => 15, + 'eve' => 15, + 'night' => 10, + 'min' => 10, + 'max' => 15, + ], + 'feels_like' => [ + 'morn' => 10, + 'day' => 15, + 'eve' => 15, + 'night' => 10 + ], + 'pop' => 1, + 'summary' => 'summary', + 'moon_phase' => 1, + 'moonrise' => 1715561801, + 'moonset' => 1715616968, + 'sunrise' => 1715561801, + 'sunset' => 1715616968 + ]); + + $this->assertInstanceOf(\DateTimeImmutable::class, $entity->getDateTime()); + $this->assertSame(1000, $entity->getAtmosphericPressure()); + $this->assertSame(10, $entity->getHumidity()); + $this->assertSame(10.0, $entity->getDewPoint()); + $this->assertSame(1.0, $entity->getUltraVioletIndex()); + $this->assertSame(10, $entity->getCloudiness()); + $this->assertInstanceOf(Wind::class, $entity->getWind()); + $this->assertContainsOnlyInstancesOf(Condition::class, $entity->getConditions()); + $this->assertSame(1.0, $entity->getRainVolume()); + $this->assertSame(1.0, $entity->getSnowVolume()); + $this->assertInstanceOf(Temperature::class, $entity->getTemperature()); + $this->assertInstanceOf(Temperature::class, $entity->getTemperatureFeelsLike()); + $this->assertSame(100, $entity->getPrecipitationProbability()); + $this->assertSame('summary', $entity->getSummary()); + $this->assertInstanceOf(MoonPhase::class, $entity->getMoonPhase()); + $this->assertInstanceOf(\DateTimeImmutable::class, $entity->getMoonriseAt()); + $this->assertInstanceOf(\DateTimeImmutable::class, $entity->getMoonsetAt()); + $this->assertInstanceOf(\DateTimeImmutable::class, $entity->getSunriseAt()); + $this->assertInstanceOf(\DateTimeImmutable::class, $entity->getSunsetAt()); + } +} \ No newline at end of file diff --git a/tests/Unit/OneCall/HourDataTest.php b/tests/Unit/OneCall/HourDataTest.php new file mode 100644 index 0000000..aaef7dc --- /dev/null +++ b/tests/Unit/OneCall/HourDataTest.php @@ -0,0 +1,55 @@ + 1715561801, + 'pressure' => 1000, + 'humidity' => 10, + 'dew_point' => 10, + 'uvi' => 1, + 'clouds' => 10, + 'wind_speed' => 10, + 'wind_deg' => 10, + 'wind_gust' => 10, + 'weather' => [ + [ + 'id' => 200, + 'main' => 'name', + 'description' => 'description', + 'icon' => '01d' + ] + ], + 'rain' => 1, + 'snow' => 1, + 'temp' => 10, + 'feels_like' => 10, + 'visibility' => 10000, + 'pop' => 1 + ]); + + $this->assertInstanceOf(\DateTimeImmutable::class, $entity->getDateTime()); + $this->assertSame(1000, $entity->getAtmosphericPressure()); + $this->assertSame(10, $entity->getHumidity()); + $this->assertSame(10.0, $entity->getDewPoint()); + $this->assertSame(1.0, $entity->getUltraVioletIndex()); + $this->assertSame(10, $entity->getCloudiness()); + $this->assertInstanceOf(Wind::class, $entity->getWind()); + $this->assertContainsOnlyInstancesOf(Condition::class, $entity->getConditions()); + $this->assertSame(1.0, $entity->getRainVolume()); + $this->assertSame(1.0, $entity->getSnowVolume()); + $this->assertSame(10.0, $entity->getTemperature()); + $this->assertSame(10.0, $entity->getTemperatureFeelsLike()); + $this->assertSame(10000, $entity->getVisibility()); + $this->assertSame(100, $entity->getPrecipitationProbability()); + } +} \ No newline at end of file diff --git a/tests/Unit/OneCall/MinuteDataTest.php b/tests/Unit/OneCall/MinuteDataTest.php new file mode 100644 index 0000000..81f1e85 --- /dev/null +++ b/tests/Unit/OneCall/MinuteDataTest.php @@ -0,0 +1,20 @@ + 1715561801, + 'precipitation' => 1, + ]); + + $this->assertInstanceOf(\DateTimeImmutable::class, $entity->getDateTime()); + $this->assertSame(1.0, $entity->getPrecipitation()); + } +} \ No newline at end of file diff --git a/tests/Unit/OneCall/MoonPhaseTest.php b/tests/Unit/OneCall/MoonPhaseTest.php new file mode 100644 index 0000000..e4b1094 --- /dev/null +++ b/tests/Unit/OneCall/MoonPhaseTest.php @@ -0,0 +1,20 @@ + 1 + ]); + + $this->assertSame(1.0, $entity->getValue()); + $this->assertSame('New Moon', $entity->getName()); + $this->assertSame('NEW_MOON', $entity->getSystemName()); + } +} \ No newline at end of file diff --git a/tests/Unit/OneCall/TemperatureTest.php b/tests/Unit/OneCall/TemperatureTest.php new file mode 100644 index 0000000..5e5bdd5 --- /dev/null +++ b/tests/Unit/OneCall/TemperatureTest.php @@ -0,0 +1,28 @@ + 10, + 'day' => 15, + 'eve' => 15, + 'night' => 10, + 'min' => 10, + 'max' => 15, + ]); + + $this->assertSame(10.0, $entity->getMorning()); + $this->assertSame(15.0, $entity->getDay()); + $this->assertSame(15.0, $entity->getEvening()); + $this->assertSame(10.0, $entity->getNight()); + $this->assertSame(10.0, $entity->getMin()); + $this->assertSame(15.0, $entity->getMax()); + } +} \ No newline at end of file diff --git a/tests/Unit/OneCall/WeatherDataTest.php b/tests/Unit/OneCall/WeatherDataTest.php new file mode 100644 index 0000000..9d040f2 --- /dev/null +++ b/tests/Unit/OneCall/WeatherDataTest.php @@ -0,0 +1,57 @@ + 1715561801, + 'pressure' => 1000, + 'humidity' => 10, + 'dew_point' => 10, + 'uvi' => 1, + 'clouds' => 10, + 'wind_speed' => 10, + 'wind_deg' => 10, + 'wind_gust' => 10, + 'weather' => [ + [ + 'id' => 200, + 'main' => 'name', + 'description' => 'description', + 'icon' => '01d' + ] + ], + 'rain' => 1, + 'snow' => 1, + 'temp' => 10, + 'feels_like' => 10, + 'visibility' => 10000, + 'sunrise' => 1715561801, + 'sunset' => 1715616968 + ]); + + $this->assertInstanceOf(\DateTimeImmutable::class, $entity->getDateTime()); + $this->assertSame(1000, $entity->getAtmosphericPressure()); + $this->assertSame(10, $entity->getHumidity()); + $this->assertSame(10.0, $entity->getDewPoint()); + $this->assertSame(1.0, $entity->getUltraVioletIndex()); + $this->assertSame(10, $entity->getCloudiness()); + $this->assertInstanceOf(Wind::class, $entity->getWind()); + $this->assertContainsOnlyInstancesOf(Condition::class, $entity->getConditions()); + $this->assertSame(1.0, $entity->getRainVolume()); + $this->assertSame(1.0, $entity->getSnowVolume()); + $this->assertSame(10.0, $entity->getTemperature()); + $this->assertSame(10.0, $entity->getTemperatureFeelsLike()); + $this->assertSame(10000, $entity->getVisibility()); + $this->assertInstanceOf(\DateTimeImmutable::class, $entity->getSunriseAt()); + $this->assertInstanceOf(\DateTimeImmutable::class, $entity->getSunsetAt()); + } +} \ No newline at end of file diff --git a/tests/Unit/OneCall/WeatherMomentTest.php b/tests/Unit/OneCall/WeatherMomentTest.php new file mode 100644 index 0000000..19e6f27 --- /dev/null +++ b/tests/Unit/OneCall/WeatherMomentTest.php @@ -0,0 +1,69 @@ + 50, + 'lon' => 50, + 'timezone' => 'UTC', + 'timezone_offset' => 0, + 'data' => [ + [ + 'dt' => 1715561801, + 'pressure' => 1000, + 'humidity' => 10, + 'dew_point' => 10, + 'uvi' => 1, + 'clouds' => 10, + 'wind_speed' => 10, + 'wind_deg' => 10, + 'wind_gust' => 10, + 'weather' => [ + [ + 'id' => 200, + 'main' => 'name', + 'description' => 'description', + 'icon' => '01d' + ] + ], + 'rain' => 1, + 'snow' => 1, + 'temp' => 10, + 'feels_like' => 10, + 'visibility' => 10000, + 'sunrise' => 1715561801, + 'sunset' => 1715616968 + ] + ] + ]); + + $this->assertInstanceOf(Coordinate::class, $entity->getCoordinate()); + $this->assertInstanceOf(Timezone::class, $entity->getTimezone()); + $this->assertInstanceOf(\DateTimeImmutable::class, $entity->getDateTime()); + $this->assertSame(1000, $entity->getAtmosphericPressure()); + $this->assertSame(10, $entity->getHumidity()); + $this->assertSame(10.0, $entity->getDewPoint()); + $this->assertSame(1.0, $entity->getUltraVioletIndex()); + $this->assertSame(10, $entity->getCloudiness()); + $this->assertInstanceOf(Wind::class, $entity->getWind()); + $this->assertContainsOnlyInstancesOf(Condition::class, $entity->getConditions()); + $this->assertSame(1.0, $entity->getRainVolume()); + $this->assertSame(1.0, $entity->getSnowVolume()); + $this->assertSame(10.0, $entity->getTemperature()); + $this->assertSame(10.0, $entity->getTemperatureFeelsLike()); + $this->assertSame(10000, $entity->getVisibility()); + $this->assertInstanceOf(\DateTimeImmutable::class, $entity->getSunriseAt()); + $this->assertInstanceOf(\DateTimeImmutable::class, $entity->getSunsetAt()); + } +} \ No newline at end of file diff --git a/tests/Unit/OneCall/WeatherSummaryTest.php b/tests/Unit/OneCall/WeatherSummaryTest.php new file mode 100644 index 0000000..7a438bb --- /dev/null +++ b/tests/Unit/OneCall/WeatherSummaryTest.php @@ -0,0 +1,61 @@ + 50, + 'lon' => 50, + 'tz' => '+00:00', + 'date' => '2024-01-01', + 'cloud_cover' => [ + 'afternoon' => 10 + ], + 'humidity' => [ + 'afternoon' => 10 + ], + 'precipitation' => [ + 'total' => 1 + ], + 'temperature' => [ + 'morning' => 10, + 'afternoon' => 15, + 'evening' => 15, + 'night' => 10, + 'min' => 10, + 'max' => 15 + ], + 'pressure' => [ + 'afternoon' => 1000 + ], + 'wind' => [ + 'max' => [ + 'speed' => 10, + 'direction' => 10 + ] + ] + ]); + + $this->assertInstanceOf(Coordinate::class, $entity->getCoordinate()); + $this->assertInstanceOf(Timezone::class, $entity->getTimezone()); + $this->assertInstanceOf(\DateTimeImmutable::class, $entity->getDateTime()); + $this->assertSame(10, $entity->getCloudiness()); + $this->assertSame(10, $entity->getHumidity()); + $this->assertSame(1.0, $entity->getPrecipitation()); + $this->assertInstanceOf(Temperature::class, $entity->getTemperature()); + $this->assertSame(1000, $entity->getAtmosphericPressure()); + $this->assertInstanceOf(Wind::class, $entity->getWind()); + } +} \ No newline at end of file diff --git a/tests/Unit/OneCall/WeatherTest.php b/tests/Unit/OneCall/WeatherTest.php new file mode 100644 index 0000000..27174ae --- /dev/null +++ b/tests/Unit/OneCall/WeatherTest.php @@ -0,0 +1,147 @@ + 50, + 'lon' => 50, + 'timezone' => 'UTC', + 'timezone_offset' => 0, + 'current' => [ + 'dt' => 1715561801, + 'pressure' => 1000, + 'humidity' => 10, + 'dew_point' => 10, + 'uvi' => 1, + 'clouds' => 10, + 'wind_speed' => 10, + 'wind_deg' => 10, + 'wind_gust' => 10, + 'weather' => [ + [ + 'id' => 200, + 'main' => 'name', + 'description' => 'description', + 'icon' => '01d' + ] + ], + 'rain' => 1, + 'snow' => 1, + 'temp' => 10, + 'feels_like' => 10, + 'visibility' => 10000, + 'sunrise' => 1715561801, + 'sunset' => 1715616968 + ], + 'minutely' => [ + [ + 'dt' => 1715561801, + 'precipitation' => 1 + ] + ], + 'hourly' => [ + [ + 'dt' => 1715561801, + 'pressure' => 1000, + 'humidity' => 10, + 'dew_point' => 10, + 'uvi' => 1, + 'clouds' => 10, + 'wind_speed' => 10, + 'wind_deg' => 10, + 'wind_gust' => 10, + 'weather' => [ + [ + 'id' => 200, + 'main' => 'name', + 'description' => 'description', + 'icon' => '01d' + ] + ], + 'rain' => 1, + 'snow' => 1, + 'temp' => 10, + 'feels_like' => 10, + 'visibility' => 10000, + 'pop' => 1 + ] + ], + 'daily' => [ + [ + 'dt' => 1715561801, + 'pressure' => 1000, + 'humidity' => 10, + 'dew_point' => 10, + 'uvi' => 1, + 'clouds' => 10, + 'wind_speed' => 10, + 'wind_deg' => 10, + 'wind_gust' => 10, + 'weather' => [ + [ + 'id' => 200, + 'main' => 'name', + 'description' => 'description', + 'icon' => '01d' + ] + ], + 'rain' => 1, + 'snow' => 1, + 'temp' => [ + 'morn' => 10, + 'day' => 15, + 'eve' => 15, + 'night' => 10, + 'min' => 10, + 'max' => 15, + ], + 'feels_like' => [ + 'morn' => 10, + 'day' => 15, + 'eve' => 15, + 'night' => 10 + ], + 'pop' => 1, + 'summary' => 'summary', + 'moon_phase' => 1, + 'moonrise' => 1715561801, + 'moonset' => 1715616968, + 'sunrise' => 1715561801, + 'sunset' => 1715616968 + ] + ], + 'alerts' => [ + [ + 'sender_name' => 'sender name', + 'event' => 'event name', + 'start' => 1715561801, + 'end' => 1715616968, + 'description' => 'description', + 'tags' => ['tag1', 'tag2'] + ] + ] + ]); + + $this->assertInstanceOf(Coordinate::class, $entity->getCoordinate()); + $this->assertInstanceOf(Timezone::class, $entity->getTimezone()); + $this->assertInstanceOf(WeatherData::class, $entity->getCurrent()); + $this->assertContainsOnlyInstancesOf(MinuteData::class, $entity->getMinutelyForecast()); + $this->assertContainsOnlyInstancesOf(HourData::class, $entity->getHourlyForecast()); + $this->assertContainsOnlyInstancesOf(DayData::class, $entity->getDailyForecast()); + $this->assertContainsOnlyInstancesOf(Alert::class, $entity->getAlerts()); + } +} \ No newline at end of file diff --git a/tests/Unit/TimezoneTest.php b/tests/Unit/TimezoneTest.php new file mode 100644 index 0000000..84bc444 --- /dev/null +++ b/tests/Unit/TimezoneTest.php @@ -0,0 +1,20 @@ + 'UTC', + 'timezone_offset' => 0 + ]); + + $this->assertSame('UTC', $entity->getIdentifier()); + $this->assertSame(0, $entity->getOffset()); + } +} \ No newline at end of file diff --git a/tests/Unit/Weather/WeatherCollectionTest.php b/tests/Unit/Weather/WeatherCollectionTest.php new file mode 100644 index 0000000..0aefbe5 --- /dev/null +++ b/tests/Unit/Weather/WeatherCollectionTest.php @@ -0,0 +1,72 @@ + 1, + 'city' => [ + 'id' => 100, + 'name' => 'name', + 'coord' => [ + 'lat' => 50, + 'lon' => 50 + ], + 'country' => 'PT', + 'population' => 100, + 'timezone' => 0, + 'sunrise' => 1715130253, + 'sunset' => 1715184525 + ], + 'list' => [ + [ + 'dt' => 1715187406, + 'weather' => [ + [ + 'id' => 200, + 'main' => 'main', + 'description' => 'description', + 'icon' => '01d' + ] + ], + 'main' => [ + 'temp' => 20, + 'feels_like' => 20, + 'temp_min' => 15, + 'temp_max' => 25, + 'pressure' => 1010, + 'humidity' => 50 + ], + 'visibility' => 10000, + 'wind' => [ + 'speed' => 100, + 'deg' => 100, + 'gust' => 100 + ], + 'clouds' => [ + 'all' => 100 + ], + 'pop' => 1, + 'rain' => [ + '3h' => 10 + ], + 'snow' => [ + '3h' => 10 + ] + ] + ] + ]); + + $this->assertSame(1, $entity->getNumResults()); + $this->assertInstanceOf(Location::class, $entity->getLocation()); + $this->assertContainsOnlyInstancesOf(WeatherData::class, $entity->getData()); + } +} \ No newline at end of file diff --git a/tests/Unit/Weather/WeatherDataTest.php b/tests/Unit/Weather/WeatherDataTest.php new file mode 100644 index 0000000..5bedf1f --- /dev/null +++ b/tests/Unit/Weather/WeatherDataTest.php @@ -0,0 +1,65 @@ + [ + [ + 'id' => 200, + 'main' => 'main', + 'description' => 'description', + 'icon' => '01d' + ] + ], + 'main' => [ + 'temp' => 20, + 'feels_like' => 20, + 'temp_min' => 15, + 'temp_max' => 25, + 'pressure' => 1010, + 'humidity' => 50 + ], + 'visibility' => 10000, + 'wind' => [ + 'speed' => 100, + 'deg' => 100, + 'gust' => 100 + ], + 'clouds' => [ + 'all' => 100 + ], + 'pop' => 1, + 'rain' => [ + '1h' => 10 + ], + 'snow' => [ + '1h' => 10 + ], + 'dt' => 1715187406 + ]); + + $this->assertInstanceOf(\DateTimeImmutable::class, $entity->getDateTime()); + $this->assertSame(20.0, $entity->getTemperature()); + $this->assertSame(20.0, $entity->getTemperatureFeelsLike()); + $this->assertSame(15.0, $entity->getMinTemperature()); + $this->assertSame(25.0, $entity->getMaxTemperature()); + $this->assertSame(50, $entity->getHumidity()); + $this->assertSame(100, $entity->getCloudiness()); + $this->assertSame(10000, $entity->getVisibility()); + $this->assertSame(1010, $entity->getAtmosphericPressure()); + $this->assertContainsOnlyInstancesOf(Condition::class, $entity->getConditions()); + $this->assertInstanceOf(Wind::class, $entity->getWind()); + $this->assertSame(100, $entity->getPrecipitationProbability()); + $this->assertSame(10.0, $entity->getRainVolume()); + $this->assertSame(10.0, $entity->getSnowVolume()); + } +} \ No newline at end of file diff --git a/tests/Unit/Weather/WeatherTest.php b/tests/Unit/Weather/WeatherTest.php new file mode 100644 index 0000000..b7ebec9 --- /dev/null +++ b/tests/Unit/Weather/WeatherTest.php @@ -0,0 +1,79 @@ + [ + 'lat' => 50, + 'lon' => 50 + ], + 'id' => 100, + 'name' => 'name', + 'sys' => [ + 'country' => 'PT', + 'sunrise' => 1715130253, + 'sunset' => 1715184525 + ], + 'timezone' => 0, + 'weather' => [ + [ + 'id' => 200, + 'main' => 'main', + 'description' => 'description', + 'icon' => '01d' + ] + ], + 'main' => [ + 'temp' => 20, + 'feels_like' => 20, + 'temp_min' => 15, + 'temp_max' => 25, + 'pressure' => 1010, + 'humidity' => 50 + ], + 'visibility' => 10000, + 'wind' => [ + 'speed' => 100, + 'deg' => 100, + 'gust' => 100 + ], + 'clouds' => [ + 'all' => 100 + ], + 'pop' => 1, + 'rain' => [ + '1h' => 10 + ], + 'snow' => [ + '1h' => 10 + ], + 'dt' => 1715187406 + ]); + + $this->assertInstanceOf(Location::class, $entity->getLocation()); + $this->assertInstanceOf(\DateTimeImmutable::class, $entity->getDateTime()); + $this->assertSame(20.0, $entity->getTemperature()); + $this->assertSame(20.0, $entity->getTemperatureFeelsLike()); + $this->assertSame(15.0, $entity->getMinTemperature()); + $this->assertSame(25.0, $entity->getMaxTemperature()); + $this->assertSame(50, $entity->getHumidity()); + $this->assertSame(100, $entity->getCloudiness()); + $this->assertSame(10000, $entity->getVisibility()); + $this->assertSame(1010, $entity->getAtmosphericPressure()); + $this->assertContainsOnlyInstancesOf(Condition::class, $entity->getConditions()); + $this->assertInstanceOf(Wind::class, $entity->getWind()); + $this->assertSame(100, $entity->getPrecipitationProbability()); + $this->assertSame(10.0, $entity->getRainVolume()); + $this->assertSame(10.0, $entity->getSnowVolume()); + } +} \ No newline at end of file diff --git a/tests/Unit/WindTest.php b/tests/Unit/WindTest.php new file mode 100644 index 0000000..6a487ec --- /dev/null +++ b/tests/Unit/WindTest.php @@ -0,0 +1,22 @@ + 100, + 'deg' => 100, + 'gust' => 100 + ]); + + $this->assertSame(100.0, $entity->getSpeed()); + $this->assertSame(100, $entity->getDirection()); + $this->assertSame(100.0, $entity->getGust()); + } +} \ No newline at end of file diff --git a/tests/UnitSystemTraitTest.php b/tests/UnitSystemTraitTest.php deleted file mode 100644 index add86d7..0000000 --- a/tests/UnitSystemTraitTest.php +++ /dev/null @@ -1,24 +0,0 @@ -assertSame( - 'imperial', - $this->givenApi()->weather()->withUnitSystem('imperial')->getUnitSystem() - ); - } - - #[DataProviderExternal(InvalidParamDataProvider::class, 'provideInvalidUnitSystemData')] - public function testUnitSystemTraitWithUnitSystemWithInvalidValue(string $unitSystem, string $expectedException) - { - $this->expectException($expectedException); - $this->givenApi()->weather()->withUnitSystem($unitSystem); - } -} \ No newline at end of file diff --git a/tests/Util/TestEndpointInvalidResponseTrait.php b/tests/Util/TestEndpointInvalidResponseTrait.php deleted file mode 100644 index 2eb13f7..0000000 --- a/tests/Util/TestEndpointInvalidResponseTrait.php +++ /dev/null @@ -1,22 +0,0 @@ -expectException(ValidationException::class); - $this->givenApi()->$endpointName()->$methodName(...$methodParams); - } - - public abstract static function provideEndpointInvalidResponseData(): \Generator; -} \ No newline at end of file diff --git a/tests/Util/TestEndpointSuccessResponseTrait.php b/tests/Util/TestEndpointSuccessResponseTrait.php deleted file mode 100644 index 9e3de44..0000000 --- a/tests/Util/TestEndpointSuccessResponseTrait.php +++ /dev/null @@ -1,29 +0,0 @@ -mockHttpClient->addResponse(new Response( - body: $mockData - )); - - $response = $this->givenApi()->$endpointName()->$methodName(...$methodParams); - - $this->$callback($response); - } - - public abstract static function provideEndpointSuccessResponseData(): \Generator; -} \ No newline at end of file diff --git a/tests/WeatherEndpointTest.php b/tests/WeatherEndpointTest.php deleted file mode 100644 index 13b3635..0000000 --- a/tests/WeatherEndpointTest.php +++ /dev/null @@ -1,201 +0,0 @@ - [ - MockResponse::WEATHER_CURRENT, - 'weather', - 'getCurrent', - [50, 50], - 'assertGetCurrentResponse' - ]; - yield 'get forecast' => [ - MockResponse::WEATHER_FORECAST, - 'weather', - 'getForecast', - [50, 50], - 'assertGetForecastResponse' - ]; - } - - public static function provideEndpointInvalidResponseData(): \Generator - { - yield 'get current, latitude lower than -90' => ['weather', 'getCurrent', [-91, 50]]; - yield 'get current, latitude greater than 90' => ['weather', 'getCurrent', [91, 50]]; - yield 'get current, longitude lower than -180' => ['weather', 'getCurrent', [50, -181]]; - yield 'get current, longitude greater than 180' => ['weather', 'getCurrent', [50, 181]]; - - yield 'get forecast, latitude lower than -90' => ['weather', 'getForecast', [-91, 50]]; - yield 'get forecast, latitude greater than 90' => ['weather', 'getForecast', [91, 50]]; - yield 'get forecast, longitude lower than -180' => ['weather', 'getForecast', [50, -181]]; - yield 'get forecast, longitude greater than 180' => ['weather', 'getForecast', [50, 181]]; - yield 'get forecast, zero num results' => ['weather', 'getForecast', [50, 50, 0]]; - yield 'get forecast, negative num results' => ['weather', 'getForecast', [50, 50, -1]]; - } - - public function testWeatherMethodsExist() - { - $this->assertSame(true, method_exists(WeatherEndpoint::class, 'withUnitSystem')); - $this->assertSame(true, method_exists(WeatherEndpoint::class, 'withLanguage')); - $this->assertSame(true, method_exists(WeatherEndpoint::class, 'withCacheTtl')); - } - - private function assertGetCurrentResponse(WeatherLocation $weatherLocation): void - { - $this->assertSame(27.12, $weatherLocation->getTemperature()); - $this->assertSame(28.16, $weatherLocation->getTemperatureFeelsLike()); - $this->assertSame(22.76, $weatherLocation->getMinTemperature()); - $this->assertSame(29.9, $weatherLocation->getMaxTemperature()); - $this->assertSame(59, $weatherLocation->getHumidity()); - $this->assertSame(0, $weatherLocation->getCloudiness()); - $this->assertSame(10000, $weatherLocation->getVisibility()); - $this->assertSame(null, $weatherLocation->getPrecipitationProbability()); - $this->assertSame('2023-06-28 10:45:33', $weatherLocation->getDateTime()->format('Y-m-d H:i:s')); - - $weatherConditions = $weatherLocation->getWeatherConditions(); - $this->assertContainsOnlyInstancesOf(WeatherCondition::class, $weatherConditions); - $this->assertSame(800, $weatherConditions[0]->getId()); - $this->assertSame('Clear', $weatherConditions[0]->getName()); - $this->assertSame('clear sky', $weatherConditions[0]->getDescription()); - $this->assertSame('CLEAR', $weatherConditions[0]->getSysName()); - - $weatherConditionsIcon = $weatherConditions[0]->getIcon(); - $this->assertInstanceOf(Icon::class, $weatherConditionsIcon); - $this->assertSame('01d', $weatherConditionsIcon->getId()); - $this->assertSame('https://openweathermap.org/img/wn/01d@4x.png', $weatherConditionsIcon->getImageUrl()); - - $wind = $weatherLocation->getWind(); - $this->assertInstanceOf(Wind::class, $wind); - $this->assertSame(9.26, $wind->getSpeed()); - $this->assertSame(360, $wind->getDirection()); - $this->assertSame(2.34, $wind->getGust()); - - $rain = $weatherLocation->getRain(); - $this->assertInstanceOf(Rain::class, $rain); - $this->assertSame(0.17, $rain->getLastOneHourVolume()); - $this->assertSame(0.81, $rain->getLastThreeHoursVolume()); - - $snow = $weatherLocation->getSnow(); - $this->assertInstanceOf(Snow::class, $snow); - $this->assertSame(0.14, $snow->getLastOneHourVolume()); - $this->assertSame(0.46, $snow->getLastThreeHoursVolume()); - - $atmosphericPressure = $weatherLocation->getAtmosphericPressure(); - $this->assertInstanceOf(AtmosphericPressure::class, $atmosphericPressure); - $this->assertSame(1013, $atmosphericPressure->getPressure()); - $this->assertSame(1013, $atmosphericPressure->getSeaLevelPressure()); - $this->assertSame(997, $atmosphericPressure->getGroundLevelPressure()); - - $location = $weatherLocation->getLocation(); - $this->assertInstanceOf(Location::class, $location); - $this->assertSame(6930126, $location->getId()); - $this->assertSame('Chiado', $location->getName()); - $this->assertSame('PT', $location->getCountryCode()); - $this->assertSame(null, $location->getLocalNames()); - $this->assertSame(null, $location->getPopulation()); - $this->assertSame('2023-06-28 05:13:56', $location->getSunriseAt()->format('Y-m-d H:i:s')); - $this->assertSame('2023-06-28 20:05:18', $location->getSunsetAt()->format('Y-m-d H:i:s')); - - $coordinate = $location->getCoordinate(); - $this->assertInstanceOf(Coordinate::class, $coordinate); - $this->assertSame(38.7078, $coordinate->getLatitude()); - $this->assertSame(-9.1366, $coordinate->getLongitude()); - - $timezone = $location->getTimezone(); - $this->assertInstanceOf(Timezone::class, $timezone); - $this->assertSame(null, $timezone->getIdentifier()); - $this->assertSame(3600, $timezone->getOffset()); - } - - private function assertGetForecastResponse(WeatherLocationList $weatherLocationList): void - { - $this->assertSame(1, $weatherLocationList->getNumResults()); - - $list = $weatherLocationList->getList(); - $this->assertContainsOnlyInstancesOf(Weather::class, $list); - - $weather = $list[0]; - $this->assertSame(26.2, $weather->getTemperature()); - $this->assertSame(26.2, $weather->getTemperatureFeelsLike()); - $this->assertSame(25.64, $weather->getMinTemperature()); - $this->assertSame(26.2, $weather->getMaxTemperature()); - $this->assertSame(56, $weather->getHumidity()); - $this->assertSame(0, $weather->getCloudiness()); - $this->assertSame(10000, $weather->getVisibility()); - $this->assertSame(0, $weather->getPrecipitationProbability()); - $this->assertSame('2023-06-28 18:00:00', $weather->getDateTime()->format('Y-m-d H:i:s')); - - $weatherConditions = $weather->getWeatherConditions(); - $this->assertContainsOnlyInstancesOf(WeatherCondition::class, $weatherConditions); - $this->assertSame(800, $weatherConditions[0]->getId()); - $this->assertSame('Clear', $weatherConditions[0]->getName()); - $this->assertSame('clear sky', $weatherConditions[0]->getDescription()); - $this->assertSame('CLEAR', $weatherConditions[0]->getSysName()); - - $weatherConditionsIcon = $weatherConditions[0]->getIcon(); - $this->assertInstanceOf(Icon::class, $weatherConditionsIcon); - $this->assertSame('01d', $weatherConditionsIcon->getId()); - $this->assertSame('https://openweathermap.org/img/wn/01d@4x.png', $weatherConditionsIcon->getImageUrl()); - - $wind = $weather->getWind(); - $this->assertInstanceOf(Wind::class, $wind); - $this->assertSame(8.88, $wind->getSpeed()); - $this->assertSame(340, $wind->getDirection()); - $this->assertSame(13.77, $wind->getGust()); - - $rain = $weather->getRain(); - $this->assertSame(null, $rain); - - $snow = $weather->getSnow(); - $this->assertSame(null, $snow); - - $atmosphericPressure = $weather->getAtmosphericPressure(); - $this->assertInstanceOf(AtmosphericPressure::class, $atmosphericPressure); - $this->assertSame(1013, $atmosphericPressure->getPressure()); - $this->assertSame(1013, $atmosphericPressure->getSeaLevelPressure()); - $this->assertSame(1013, $atmosphericPressure->getGroundLevelPressure()); - - $location = $weatherLocationList->getLocation(); - $this->assertInstanceOf(Location::class, $location); - $this->assertSame(6930126, $location->getId()); - $this->assertSame('Chiado', $location->getName()); - $this->assertSame('PT', $location->getCountryCode()); - $this->assertSame(null, $location->getLocalNames()); - $this->assertSame(500000, $location->getPopulation()); - $this->assertSame('2023-06-28 05:13:56', $location->getSunriseAt()->format('Y-m-d H:i:s')); - $this->assertSame('2023-06-28 20:05:18', $location->getSunsetAt()->format('Y-m-d H:i:s')); - - $coordinate = $location->getCoordinate(); - $this->assertInstanceOf(Coordinate::class, $coordinate); - $this->assertSame(38.7078, $coordinate->getLatitude()); - $this->assertSame(-9.1366, $coordinate->getLongitude()); - - $timezone = $location->getTimezone(); - $this->assertInstanceOf(Timezone::class, $timezone); - $this->assertSame(null, $timezone->getIdentifier()); - $this->assertSame(3600, $timezone->getOffset()); - } -} \ No newline at end of file