From 8f8cca52bf9143edef5e55eb35b9ff804dff335b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Pimpa=CC=83o?= Date: Tue, 30 Apr 2024 18:30:14 +0100 Subject: [PATCH 01/32] feat: added ddev config --- .ddev/config.yaml | 282 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 282 insertions(+) create mode 100644 .ddev/config.yaml 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: From 0abba168172376d68325e6a125a03f2ce92224d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Pimpa=CC=83o?= Date: Tue, 30 Apr 2024 18:45:21 +0100 Subject: [PATCH 02/32] chore: updated composer required libs --- composer.json | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/composer.json b/composer.json index c654533..02e4b04 100644 --- a/composer.json +++ b/composer.json @@ -2,36 +2,28 @@ "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" + "programmatordev/php-api-sdk": "^0.1.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", From 7e651556c43e4f748d84adff28860b5612798e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Pimpa=CC=83o?= Date: Thu, 2 May 2024 13:08:17 +0100 Subject: [PATCH 03/32] chore: base revamp configuration --- src/Config.php | 144 ------------------ src/Endpoint/AbstractEndpoint.php | 130 ---------------- src/Entity/Error.php | 34 ----- src/Exception/ApiErrorException.php | 8 +- src/HttpClient/HttpClientBuilder.php | 66 -------- .../Listener/LoggerCacheListener.php | 56 ------- src/HttpClient/ResponseMediator.php | 13 -- src/OpenWeatherMap.php | 108 ++++++++++--- ...ointTest.php => AbstractEndpointTest_.php} | 0 ...Test.php => AirPollutionEndpointTest_.php} | 0 tests/{ApiErrorTest.php => ApiErrorTest_.php} | 0 ...tlTraitTest.php => CacheTtlTraitTest_.php} | 0 tests/{ConfigTest.php => ConfigTest_.php} | 0 ...intTest.php => GeocodingEndpointTest_.php} | 0 ...derTest.php => HttpClientBuilderTest_.php} | 0 ...geTraitTest.php => LanguageTraitTest_.php} | 0 ...pointTest.php => OneCallEndpointTest_.php} | 0 ...herMapTest.php => OpenWeatherMapTest_.php} | 0 ...TraitTest.php => UnitSystemTraitTest_.php} | 0 ...pointTest.php => WeatherEndpointTest_.php} | 0 20 files changed, 91 insertions(+), 468 deletions(-) delete mode 100644 src/Config.php delete mode 100644 src/Endpoint/AbstractEndpoint.php delete mode 100644 src/Entity/Error.php delete mode 100644 src/HttpClient/HttpClientBuilder.php delete mode 100644 src/HttpClient/Listener/LoggerCacheListener.php delete mode 100644 src/HttpClient/ResponseMediator.php rename tests/{AbstractEndpointTest.php => AbstractEndpointTest_.php} (100%) rename tests/{AirPollutionEndpointTest.php => AirPollutionEndpointTest_.php} (100%) rename tests/{ApiErrorTest.php => ApiErrorTest_.php} (100%) rename tests/{CacheTtlTraitTest.php => CacheTtlTraitTest_.php} (100%) rename tests/{ConfigTest.php => ConfigTest_.php} (100%) rename tests/{GeocodingEndpointTest.php => GeocodingEndpointTest_.php} (100%) rename tests/{HttpClientBuilderTest.php => HttpClientBuilderTest_.php} (100%) rename tests/{LanguageTraitTest.php => LanguageTraitTest_.php} (100%) rename tests/{OneCallEndpointTest.php => OneCallEndpointTest_.php} (100%) rename tests/{OpenWeatherMapTest.php => OpenWeatherMapTest_.php} (100%) rename tests/{UnitSystemTraitTest.php => UnitSystemTraitTest_.php} (100%) rename tests/{WeatherEndpointTest.php => WeatherEndpointTest_.php} (100%) 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/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/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/OpenWeatherMap.php b/src/OpenWeatherMap.php index 1934b15..402a5d5 100644 --- a/src/OpenWeatherMap.php +++ b/src/OpenWeatherMap.php @@ -2,37 +2,105 @@ 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\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 readonly string $apiKey, + array $options = [] + ) { - return $this->config; - } + parent::__construct(); - public function oneCall(): OneCallEndpoint - { - return new OneCallEndpoint($this); + $this->options = $this->configureOptions($options); + $this->configureApi(); } - public function weather(): WeatherEndpoint - { - return new WeatherEndpoint($this); - } +// public function config(): Config +// { +// return $this->config; +// } - public function airPollution(): AirPollutionEndpoint +// public function oneCall(): OneCallEndpoint +// { +// return new OneCallEndpoint($this); +// } +// +// public function weather(): WeatherEndpoint +// { +// return new WeatherEndpoint($this); +// } +// +// public function airPollution(): AirPollutionEndpoint +// { +// return new AirPollutionEndpoint($this); +// } +// +// public function geocoding(): GeocodingEndpoint +// { +// return new GeocodingEndpoint($this); +// } + + private function configureOptions(array $options): array { - return new AirPollutionEndpoint($this); + $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::getList()); + $this->optionsResolver->setAllowedValues('language', Language::getList()); + + return $this->optionsResolver->resolve($options); } - public function geocoding(): GeocodingEndpoint + private function configureApi(): void { - return new GeocodingEndpoint($this); + $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/tests/AbstractEndpointTest.php b/tests/AbstractEndpointTest_.php similarity index 100% rename from tests/AbstractEndpointTest.php rename to tests/AbstractEndpointTest_.php diff --git a/tests/AirPollutionEndpointTest.php b/tests/AirPollutionEndpointTest_.php similarity index 100% rename from tests/AirPollutionEndpointTest.php rename to tests/AirPollutionEndpointTest_.php diff --git a/tests/ApiErrorTest.php b/tests/ApiErrorTest_.php similarity index 100% rename from tests/ApiErrorTest.php rename to tests/ApiErrorTest_.php diff --git a/tests/CacheTtlTraitTest.php b/tests/CacheTtlTraitTest_.php similarity index 100% rename from tests/CacheTtlTraitTest.php rename to tests/CacheTtlTraitTest_.php diff --git a/tests/ConfigTest.php b/tests/ConfigTest_.php similarity index 100% rename from tests/ConfigTest.php rename to tests/ConfigTest_.php diff --git a/tests/GeocodingEndpointTest.php b/tests/GeocodingEndpointTest_.php similarity index 100% rename from tests/GeocodingEndpointTest.php rename to tests/GeocodingEndpointTest_.php diff --git a/tests/HttpClientBuilderTest.php b/tests/HttpClientBuilderTest_.php similarity index 100% rename from tests/HttpClientBuilderTest.php rename to tests/HttpClientBuilderTest_.php diff --git a/tests/LanguageTraitTest.php b/tests/LanguageTraitTest_.php similarity index 100% rename from tests/LanguageTraitTest.php rename to tests/LanguageTraitTest_.php diff --git a/tests/OneCallEndpointTest.php b/tests/OneCallEndpointTest_.php similarity index 100% rename from tests/OneCallEndpointTest.php rename to tests/OneCallEndpointTest_.php diff --git a/tests/OpenWeatherMapTest.php b/tests/OpenWeatherMapTest_.php similarity index 100% rename from tests/OpenWeatherMapTest.php rename to tests/OpenWeatherMapTest_.php diff --git a/tests/UnitSystemTraitTest.php b/tests/UnitSystemTraitTest_.php similarity index 100% rename from tests/UnitSystemTraitTest.php rename to tests/UnitSystemTraitTest_.php diff --git a/tests/WeatherEndpointTest.php b/tests/WeatherEndpointTest_.php similarity index 100% rename from tests/WeatherEndpointTest.php rename to tests/WeatherEndpointTest_.php From 17d315301fa1e52573c753ee5b43d0d8725b8d6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Pimpa=CC=83o?= Date: Fri, 3 May 2024 16:29:57 +0100 Subject: [PATCH 04/32] feat: added geocoding resource and lots of cleanup --- composer.json | 2 +- src/Entity/Geocoding/ZipCodeLocation.php | 47 ------ src/Entity/Location.php | 54 ++----- src/Language/Language.php | 4 +- src/OpenWeatherMap.php | 12 +- .../GeocodingResource.php} | 53 +++---- src/Resource/Util/ValidationTrait.php | 42 +++++ src/Test/AbstractTest.php | 22 +-- src/UnitSystem/UnitSystem.php | 4 +- src/Util/EntityListTrait.php | 13 -- src/Util/EntityTrait.php | 13 ++ ...ConstantsTrait.php => ReflectionTrait.php} | 6 +- tests/AbstractEndpointTest_.php | 57 ------- tests/CacheTtlTraitTest_.php | 14 -- tests/ConfigTest_.php | 149 ------------------ .../DataProvider/InvalidParamDataProvider.php | 18 --- tests/GeocodingEndpointTest_.php | 101 ------------ tests/HttpClientBuilderTest_.php | 61 ------- tests/Integration/OpenWeatherMapTest.php | 14 ++ tests/LanguageTraitTest_.php | 24 --- tests/OpenWeatherMapTest_.php | 28 ---- tests/UnitSystemTraitTest_.php | 24 --- .../Util/TestEndpointInvalidResponseTrait.php | 22 --- .../Util/TestEndpointSuccessResponseTrait.php | 29 ---- 24 files changed, 127 insertions(+), 686 deletions(-) delete mode 100644 src/Entity/Geocoding/ZipCodeLocation.php rename src/{Endpoint/GeocodingEndpoint.php => Resource/GeocodingResource.php} (53%) create mode 100644 src/Resource/Util/ValidationTrait.php delete mode 100644 src/Util/EntityListTrait.php create mode 100644 src/Util/EntityTrait.php rename src/Util/{ClassConstantsTrait.php => ReflectionTrait.php} (69%) delete mode 100644 tests/AbstractEndpointTest_.php delete mode 100644 tests/CacheTtlTraitTest_.php delete mode 100644 tests/ConfigTest_.php delete mode 100644 tests/DataProvider/InvalidParamDataProvider.php delete mode 100644 tests/GeocodingEndpointTest_.php delete mode 100644 tests/HttpClientBuilderTest_.php create mode 100644 tests/Integration/OpenWeatherMapTest.php delete mode 100644 tests/LanguageTraitTest_.php delete mode 100644 tests/OpenWeatherMapTest_.php delete mode 100644 tests/UnitSystemTraitTest_.php delete mode 100644 tests/Util/TestEndpointInvalidResponseTrait.php delete mode 100644 tests/Util/TestEndpointSuccessResponseTrait.php diff --git a/composer.json b/composer.json index 02e4b04..84406e7 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ ], "require": { "php": ">=8.1", - "programmatordev/php-api-sdk": "^0.1.0", + "programmatordev/php-api-sdk": "^0.2.0", "programmatordev/yet-another-php-validator": "^1.1" }, "require-dev": { diff --git a/src/Entity/Geocoding/ZipCodeLocation.php b/src/Entity/Geocoding/ZipCodeLocation.php deleted file mode 100644 index 1225763..0000000 --- a/src/Entity/Geocoding/ZipCodeLocation.php +++ /dev/null @@ -1,47 +0,0 @@ -zipCode = $data['zip']; - $this->name = $data['name']; - $this->countryCode = $data['country']; - $this->coordinate = new Coordinate([ - 'lat' => $data['lat'], - 'lon' => $data['lon'] - ]); - } - - public function getZipCode(): string - { - return $this->zipCode; - } - - public function getName(): string - { - return $this->name; - } - - public function getCoordinate(): Coordinate - { - return $this->coordinate; - } - - public function getCountryCode(): string - { - return $this->countryCode; - } -} \ No newline at end of file diff --git a/src/Entity/Location.php b/src/Entity/Location.php index 31b7080..795474b 100644 --- a/src/Entity/Location.php +++ b/src/Entity/Location.php @@ -4,8 +4,6 @@ class Location { - private ?int $id; - private ?string $name; private ?string $state; @@ -14,36 +12,18 @@ class Location private ?array $localNames; - private ?int $population; + private ?string $zipCode; private Coordinate $coordinate; - private ?Timezone $timezone; - - private ?\DateTimeImmutable $sunriseAt; - - private ?\DateTimeImmutable $sunsetAt; - public function __construct(array $data) { - $this->coordinate = new Coordinate([ - '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; - } - - public function getId(): ?int - { - return $this->id; + $this->name = $data['name'] ?? null; + $this->state = $data['state'] ?? null; + $this->countryCode = $data['country'] ?? null; + $this->localNames = $data['local_names'] ?? null; + $this->zipCode = $data['zip'] ?? null; + $this->coordinate = new Coordinate(['lat' => $data['lat'], 'lon' => $data['lon']]); } public function getName(): ?string @@ -69,31 +49,17 @@ public function getLocalNames(): ?array public function getLocalName(string $countryCode): ?string { $countryCode = strtolower($countryCode); + return $this->localNames[$countryCode] ?? null; } - public function getPopulation(): ?int + public function getZipCode(): ?string { - return $this->population; + return $this->zipCode; } public function getCoordinate(): Coordinate { return $this->coordinate; } - - public function getTimezone(): ?Timezone - { - return $this->timezone; - } - - public function getSunriseAt(): ?\DateTimeImmutable - { - return $this->sunriseAt; - } - - public function getSunsetAt(): ?\DateTimeImmutable - { - return $this->sunsetAt; - } } \ No newline at end of file diff --git a/src/Language/Language.php b/src/Language/Language.php index 58ca1dc..08bb8ca 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'; diff --git a/src/OpenWeatherMap.php b/src/OpenWeatherMap.php index 402a5d5..c40c4c1 100644 --- a/src/OpenWeatherMap.php +++ b/src/OpenWeatherMap.php @@ -12,6 +12,7 @@ use ProgrammatorDev\OpenWeatherMap\Exception\UnauthorizedException; use ProgrammatorDev\OpenWeatherMap\Exception\UnexpectedErrorException; use ProgrammatorDev\OpenWeatherMap\Language\Language; +use ProgrammatorDev\OpenWeatherMap\Resource\GeocodingResource; use ProgrammatorDev\OpenWeatherMap\UnitSystem\UnitSystem; class OpenWeatherMap extends Api @@ -27,6 +28,12 @@ public function __construct( $this->options = $this->configureOptions($options); $this->configureApi(); + + } + + public function geocoding(): GeocodingResource + { + return new GeocodingResource($this); } // public function config(): Config @@ -47,11 +54,6 @@ public function __construct( // public function airPollution(): AirPollutionEndpoint // { // return new AirPollutionEndpoint($this); -// } -// -// public function geocoding(): GeocodingEndpoint -// { -// return new GeocodingEndpoint($this); // } private function configureOptions(array $options): array diff --git a/src/Endpoint/GeocodingEndpoint.php b/src/Resource/GeocodingResource.php similarity index 53% rename from src/Endpoint/GeocodingEndpoint.php rename to src/Resource/GeocodingResource.php index ec90d96..7a134a9 100644 --- a/src/Endpoint/GeocodingEndpoint.php +++ b/src/Resource/GeocodingResource.php @@ -1,37 +1,36 @@ 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 +42,15 @@ public function getByLocationName(string $locationName, int $numResults = self:: } /** - * @throws Exception - * @throws ApiErrorException + * @throws ClientExceptionInterface * @throws ValidationException */ - public function getByZipCode(string $zipCode, string $countryCode): ZipCodeLocation + public function getByZipCode(string $zipCode, string $countryCode): Location { - $this->validateSearchQuery($zipCode, 'zipCode'); - $this->validateCountryCode($countryCode); + $this->validateQuery($zipCode, 'zipCode'); + $this->validateCountry($countryCode, 'countryCode'); - $data = $this->sendRequest( + $data = $this->api->request( method: 'GET', path: '/geo/1.0/zip', query: [ @@ -60,22 +58,21 @@ public function getByZipCode(string $zipCode, string $countryCode): ZipCodeLocat ] ); - return new ZipCodeLocation($data); + return new Location($data); } /** * @return Location[] - * @throws Exception - * @throws ApiErrorException + * @throws ClientExceptionInterface * @throws ValidationException */ 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/Util/ValidationTrait.php b/src/Resource/Util/ValidationTrait.php new file mode 100644 index 0000000..50034b5 --- /dev/null +++ b/src/Resource/Util/ValidationTrait.php @@ -0,0 +1,42 @@ +assert($query, $name); + } + + /** + * @throws ValidationException + */ + private function validatePositive(int $number, string $name): void + { + Validator::greaterThan(0)->assert($number, $name); + } + + /** + * @throws ValidationException + */ + private function validateCoordinate(float $latitude, float $longitude): void + { + Validator::range(-90, 90)->assert($latitude, 'latitude'); + Validator::range(-180, 180)->assert($longitude, 'longitude'); + } + + /** + * @throws ValidationException + */ + private function validateCountry(string $countryCode, string $name): void + { + Validator::country()->assert($countryCode, $name); + } +} \ 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/UnitSystem/UnitSystem.php b/src/UnitSystem/UnitSystem.php index f7f576e..e140e31 100644 --- a/src/UnitSystem/UnitSystem.php +++ b/src/UnitSystem/UnitSystem.php @@ -2,11 +2,11 @@ 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'; 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/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/OpenWeatherMapTest.php b/tests/Integration/OpenWeatherMapTest.php new file mode 100644 index 0000000..a78bad5 --- /dev/null +++ b/tests/Integration/OpenWeatherMapTest.php @@ -0,0 +1,14 @@ +assertInstanceOf(GeocodingResource::class, $this->api->geocoding()); + } +} \ 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/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/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 From c88e365dfb317d948df5b50bd94beba63537ea13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Pimpa=CC=83o?= Date: Mon, 6 May 2024 18:25:41 +0100 Subject: [PATCH 05/32] tests: added geocoding resource tests --- src/Test/Util/TestExceptionsTrait.php | 16 ++++++ src/Test/Util/TestResponsesTrait.php | 45 ++++++++++++++++ tests/Integration/GeocodingResourceTest.php | 58 +++++++++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 src/Test/Util/TestExceptionsTrait.php create mode 100644 src/Test/Util/TestResponsesTrait.php create mode 100644 tests/Integration/GeocodingResourceTest.php diff --git a/src/Test/Util/TestExceptionsTrait.php b/src/Test/Util/TestExceptionsTrait.php new file mode 100644 index 0000000..3a0990c --- /dev/null +++ b/src/Test/Util/TestExceptionsTrait.php @@ -0,0 +1,16 @@ +expectException(ValidationException::class); + $this->api->$resource()->$method(...$args); + } +} \ No newline at end of file diff --git a/src/Test/Util/TestResponsesTrait.php b/src/Test/Util/TestResponsesTrait.php new file mode 100644 index 0000000..c192614 --- /dev/null +++ b/src/Test/Util/TestResponsesTrait.php @@ -0,0 +1,45 @@ +mockClient->addResponse(new Response( + status: 200, + body: $responseBody + )); + + $response = $this->api->$resource()->$method(...$args); + $this->assertContainsOnlyInstancesOf($responseClass, $response); + } + + #[DataProvider(methodName: 'provideItemResponseData')] + public function testItemResponse( + string $responseClass, + string $responseBody, + string $resource, + string $method, + ?array $args = null + ): void + { + $this->mockClient->addResponse(new Response( + status: 200, + body: $responseBody + )); + + $response = $this->api->$resource()->$method(...$args); + $this->assertInstanceOf($responseClass, $response); + } +} \ No newline at end of file diff --git a/tests/Integration/GeocodingResourceTest.php b/tests/Integration/GeocodingResourceTest.php new file mode 100644 index 0000000..d706e70 --- /dev/null +++ b/tests/Integration/GeocodingResourceTest.php @@ -0,0 +1,58 @@ + [ + Location::class, + MockResponse::GEOCODING_DIRECT, + 'geocoding', + 'getByLocationName', + ['test'] + ]; + yield 'get by coordinate' => [ + Location::class, + MockResponse::GEOCODING_REVERSE, + 'geocoding', + 'getByCoordinate', + [50, 50] + ]; + } + + public static function provideItemResponseData(): \Generator + { + yield 'get by zip code' => [ + Location::class, + MockResponse::GEOCODING_ZIP, + 'geocoding', + 'getByZipCode', + ['1000-001', 'pt'] + ]; + } + + 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 From df731d36e9c68ad59cad4c85e78266330c356894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Pimpa=CC=83o?= Date: Mon, 6 May 2024 19:02:07 +0100 Subject: [PATCH 06/32] tests: added entities tests --- tests/Unit/CoordinateTest.php | 20 ++++++++++++++++++++ tests/Unit/LocationTest.php | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 tests/Unit/CoordinateTest.php create mode 100644 tests/Unit/LocationTest.php 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/LocationTest.php b/tests/Unit/LocationTest.php new file mode 100644 index 0000000..77ba688 --- /dev/null +++ b/tests/Unit/LocationTest.php @@ -0,0 +1,34 @@ + 'Name', + 'state' => 'State', + 'country' => 'CO', + 'local_names' => [ + 'en' => 'Local Name' + ], + 'zip' => 'ZIP123', + 'lat' => 50, + 'lon' => 50 + ]); + + $this->assertSame('Name', $entity->getName()); + $this->assertSame('State', $entity->getState()); + $this->assertSame('CO', $entity->getCountryCode()); + $this->assertSame(['en' => 'Local Name'], $entity->getLocalNames()); + $this->assertSame('Local Name', $entity->getLocalName('en')); + $this->assertSame(null, $entity->getLocalName('pt')); + $this->assertSame('ZIP123', $entity->getZipCode()); + $this->assertInstanceOf(Coordinate::class, $entity->getCoordinate()); + } +} \ No newline at end of file From 514fb63738fa9c638ac9ec06a586fc62db7ec5ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Pimpa=CC=83o?= Date: Mon, 6 May 2024 19:49:01 +0100 Subject: [PATCH 07/32] docs: updated config and geocoding resource --- README.md | 29 +++------ docs/01-usage.md | 27 +++----- docs/02-configuration.md | 85 ++++---------------------- docs/03-supported-apis.md | 66 ++++++++------------ docs/{05-objects.md => 05-entities.md} | 16 +---- 5 files changed, 53 insertions(+), 170 deletions(-) rename docs/{05-objects.md => 05-entities.md} (94%) diff --git a/README.md b/README.md index e69e930..0dfbe75 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) ## Contributing 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..893c589 100644 --- a/docs/02-configuration.md +++ b/docs/02-configuration.md @@ -2,62 +2,38 @@ - [Default Configuration](#default-configuration) - [Options](#options) - - [applicationKey](#applicationkey) - [unitSystem](#unitsystem) - [language](#language) - - [httpClientBuilder](#httpclientbuilder) - - [cache](#cache) - - [logger](#logger) -- [Config Object](#config-object) ## Default Configuration -Only the `applicationKey` option is required: - ```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. 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,21 +42,16 @@ 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` @@ -226,38 +197,4 @@ $openWeatherMap = new OpenWeatherMap( ``` > **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' - ]) -); - -// 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 +> If a `cache` implementation is configured, cache events will also be logged. \ No newline at end of file diff --git a/docs/03-supported-apis.md b/docs/03-supported-apis.md index 7fc2c82..633dd99 100644 --- a/docs/03-supported-apis.md +++ b/docs/03-supported-apis.md @@ -14,8 +14,8 @@ - [getHistory](#gethistory) - [Geocoding](#geocoding) - [getByLocationName](#getbylocationname) - - [getByZipCode](#getbyzipcode) - [getByCoordinate](#getbycoordinate) + - [getByZipCode](#getbyzipcode) - [Common Methods](#common-methods) - [withUnitSystem](#withunitsystem) - [withLanguage](#withlanguage) @@ -33,7 +33,7 @@ getWeather(float $latitude, float $longitude): OneCall Get current and forecast (minutely, hourly and daily) weather data. -Returns a [`OneCall`](05-objects.md#onecall) object: +Returns a [`OneCall`](05-entities#onecall) object: ```php $weather = $openWeatherMap->oneCall()->getWeather(50, 50); @@ -49,7 +49,7 @@ getHistoryMoment(float $latitude, float $longitude, \DateTimeInterface $dateTime Get weather data from a single moment in the past. -Returns a [`WeatherLocation`](05-objects.md#weatherlocation) object: +Returns a [`WeatherLocation`](05-entities#weatherlocation) object: ```php $weather = $openWeatherMap->oneCall()->getHistoryMoment(50, 50, new \DateTime('2023-01-01 12:00:00')); @@ -65,7 +65,7 @@ getHistoryAggregate(float $latitude, float $longitude, \DateTimeInterface $date) Get aggregated weather data from a single day in the past. -Returns a [`WeatherAggregate`](05-objects.md#weatheraggregate) object: +Returns a [`WeatherAggregate`](05-entities#weatheraggregate) object: ```php $weather = $openWeatherMap->oneCall()->getHistoryAggregate(50, 50, new \DateTime('1985-07-19')); @@ -83,7 +83,7 @@ getCurrent(float $latitude, float $longitude): WeatherLocation Get current weather data. -Returns a [`WeatherLocation`](05-objects.md#weatherlocation-1) object: +Returns a [`WeatherLocation`](05-entities#weatherlocation-1) object: ```php $weather = $openWeatherMap->weather()->getCurrent(50, 50); @@ -99,7 +99,7 @@ getForecast(float $latitude, float $longitude, int $numResults = 40): WeatherLoc Get weather forecast data per 3-hour steps for the next 5 days. -Returns a [`WeatherLocationList`](05-objects.md#weatherlocationlist) object: +Returns a [`WeatherLocationList`](05-entities#weatherlocationlist) object: ```php // Since it returns 3-hour steps, @@ -122,7 +122,7 @@ getCurrent(float $latitude, float $longitude): AirPollutionLocation Get current air pollution data. -Returns a [`AirPollutionLocation`](05-objects.md#airpollutionlocation) object: +Returns a [`AirPollutionLocation`](05-entities#airpollutionlocation) object: ```php $airPollution = $openWeatherMap->airPollution()->getCurrent(50, 50); @@ -139,7 +139,7 @@ getForecast(float $latitude, float $longitude): AirPollutionLocationList Get air pollution forecast data per 1-hour for the next 24 hours. -Returns a [`AirPollutionLocationList`](05-objects.md#airpollutionlocationlist) object: +Returns a [`AirPollutionLocationList`](05-entities#airpollutionlocationlist) object: ```php $airPollutionForecast = $openWeatherMap->airPollution()->getForecast(50, 50); @@ -159,7 +159,7 @@ getHistory(float $latitude, float $longitude, \DateTimeInterface $startDate, \Da Get air pollution history data between two dates. -Returns a [`AirPollutionLocationList`](05-objects.md#airpollutionlocationlist) object: +Returns a [`AirPollutionLocationList`](05-entities#airpollutionlocationlist) object: ```php $startDate = new \DateTime('-7 days'); // 7 days ago @@ -177,6 +177,8 @@ foreach ($airPollutionHistory->getList() as $airPollution) { #### `getByLocationName` +Get locations by location name. Returns an array of [`Location`](05-entities#location) entities: + ```php /** * @return Location[] @@ -184,37 +186,14 @@ foreach ($airPollutionHistory->getList() as $airPollution) { getByLocationName(string $locationName, int $numResults = 5): array ``` -Get locations data by location name. - -Returns an array of [`Location`](05-objects.md#location) objects: - -```php -$locations = $openWeatherMap->geocoding()->getByLocationName('lisbon'); - -foreach ($locations as $location) { - echo $location->getName(); - echo $location->getCountryCode(); -} -``` - -#### `getByZipCode` - -```php -getByZipCode(string $zipCode, string $countryCode): ZipCodeLocation -``` - -Get location data by zip/post code. - -Returns a [`ZipCodeLocation`](05-objects.md#zipcodelocation) object: - ```php -$location = $openWeatherMap->geocoding()->getByZipCode('1000-001', 'pt'); - -echo $location->getName(); +$api->geocoding()->getByLocationName('lisbon'); ``` #### `getByCoordinate` +Get locations by coordinate. Returns an array of [`Location`](05-entities#location) entities: + ```php /** * @return Location[] @@ -222,17 +201,20 @@ echo $location->getName(); getByCoordinate(float $latitude, float $longitude, int $numResults = 5): array ``` -Get locations data by coordinate. +```php +$api->geocoding()->getByCoordinate(50, 50); +``` + +#### `getByZipCode` -Returns an array of [`Location`](05-objects.md#location) objects: +Get location by zip code. Returns a [`Location`](05-entities#location) entity: ```php -$locations = $openWeatherMap->geocoding()->getByCoordinate(50, 50); +getByZipCode(string $zipCode, string $countryCode): Location +``` -foreach ($locations as $location) { - echo $location->getName(); - echo $location->getCountryCode(); -} +```php +$api->geocoding()->getByZipCode('1000-001', 'pt'); ``` ## Common Methods diff --git a/docs/05-objects.md b/docs/05-entities.md similarity index 94% rename from docs/05-objects.md rename to docs/05-entities.md index b2e13c3..79e9487 100644 --- a/docs/05-objects.md +++ b/docs/05-entities.md @@ -16,8 +16,6 @@ - [AirPollutionLocation](#airpollutionlocation) - [AirPollutionLocationList](#airpollutionlocationlist) - [AirQuality](#airquality) -- [Geocoding](#geocoding) - - [ZipCodeLocation](#zipcodelocation) - [Common](#common) - [AtmosphericPressure](#atmosphericpressure) - [Coordinate](#coordinate) @@ -199,15 +197,6 @@ - `getIndex()`: `int` - `getQualitativeName()`: `string` -## Geocoding - -### ZipCodeLocation - -- `getZipCode()`: `string` -- `getName()`: `string` -- `getCoordinate()`: [`Coordinate`](#coordinate) -- `getCountryCode()`: `string` - ## Common ### AtmosphericPressure @@ -233,11 +222,8 @@ - `getState()`: `?string` - `getCountryCode()`: `?string` - `getLocalNames()`: `?array` -- `getPopulation()`: `?int` +- `getLocalName(string $countryCode)`: `?string` - `getCoordinate()`: [`Coordinate`](#coordinate) -- `getTimezone()`: [`?Timezone`](#timezone) -- `getSunriseAt()`: `?\DateTimeImmutable` -- `getSunsetAt()`: `?\DateTimeImmutable` ### MoonPhase From 8f4532308ed93a21763e15fc0532d8cefd3408f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Pimpa=CC=83o?= Date: Mon, 6 May 2024 20:03:00 +0100 Subject: [PATCH 08/32] docs: fixed typos and some improvements --- README.md | 2 +- docs/02-configuration.md | 4 ++-- docs/03-supported-apis.md | 22 ++++++++++----------- docs/04-error-handling.md | 41 ++++++++++++++++++++------------------- docs/05-entities.md | 2 +- 5 files changed, 36 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 0dfbe75..d54fbd1 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ echo $weather->getTemperature(); - [Configuration](docs/02-configuration.md) - [Supported APIs](docs/03-supported-apis.md) - [Error Handling](docs/04-error-handling.md) -- [Entities](docs/05-entities) +- [Entities](docs/05-entities.md) ## Contributing diff --git a/docs/02-configuration.md b/docs/02-configuration.md index 893c589..42ef512 100644 --- a/docs/02-configuration.md +++ b/docs/02-configuration.md @@ -31,7 +31,7 @@ Example: use ProgrammatorDev\OpenWeatherMap\UnitSystem\UnitSystem; use ProgrammatorDev\OpenWeatherMap\OpenWeatherMap; -$api = new OpenWeatherMap('yourapikey', +$api = new OpenWeatherMap('yourapikey', [ 'unitSystem' => UnitSystem::IMPERIAL ]); ``` @@ -49,7 +49,7 @@ Example: use ProgrammatorDev\OpenWeatherMap\Language\Language; use ProgrammatorDev\OpenWeatherMap\OpenWeatherMap; -$api = new OpenWeatherMap('yourapikey', +$api = new OpenWeatherMap('yourapikey', [ 'language' => Language::PORTUGUESE ]); ``` diff --git a/docs/03-supported-apis.md b/docs/03-supported-apis.md index 633dd99..afd06a1 100644 --- a/docs/03-supported-apis.md +++ b/docs/03-supported-apis.md @@ -33,7 +33,7 @@ getWeather(float $latitude, float $longitude): OneCall Get current and forecast (minutely, hourly and daily) weather data. -Returns a [`OneCall`](05-entities#onecall) object: +Returns a [`OneCall`](05-entities.md#onecall) object: ```php $weather = $openWeatherMap->oneCall()->getWeather(50, 50); @@ -49,7 +49,7 @@ getHistoryMoment(float $latitude, float $longitude, \DateTimeInterface $dateTime Get weather data from a single moment in the past. -Returns a [`WeatherLocation`](05-entities#weatherlocation) object: +Returns a [`WeatherLocation`](05-entities.md#weatherlocation) object: ```php $weather = $openWeatherMap->oneCall()->getHistoryMoment(50, 50, new \DateTime('2023-01-01 12:00:00')); @@ -65,7 +65,7 @@ getHistoryAggregate(float $latitude, float $longitude, \DateTimeInterface $date) Get aggregated weather data from a single day in the past. -Returns a [`WeatherAggregate`](05-entities#weatheraggregate) object: +Returns a [`WeatherAggregate`](05-entities.md#weatheraggregate) object: ```php $weather = $openWeatherMap->oneCall()->getHistoryAggregate(50, 50, new \DateTime('1985-07-19')); @@ -83,7 +83,7 @@ getCurrent(float $latitude, float $longitude): WeatherLocation Get current weather data. -Returns a [`WeatherLocation`](05-entities#weatherlocation-1) object: +Returns a [`WeatherLocation`](05-entities.md#weatherlocation-1) object: ```php $weather = $openWeatherMap->weather()->getCurrent(50, 50); @@ -99,7 +99,7 @@ getForecast(float $latitude, float $longitude, int $numResults = 40): WeatherLoc Get weather forecast data per 3-hour steps for the next 5 days. -Returns a [`WeatherLocationList`](05-entities#weatherlocationlist) object: +Returns a [`WeatherLocationList`](05-entities.md#weatherlocationlist) object: ```php // Since it returns 3-hour steps, @@ -122,7 +122,7 @@ getCurrent(float $latitude, float $longitude): AirPollutionLocation Get current air pollution data. -Returns a [`AirPollutionLocation`](05-entities#airpollutionlocation) object: +Returns a [`AirPollutionLocation`](05-entities.md#airpollutionlocation) object: ```php $airPollution = $openWeatherMap->airPollution()->getCurrent(50, 50); @@ -139,7 +139,7 @@ getForecast(float $latitude, float $longitude): AirPollutionLocationList Get air pollution forecast data per 1-hour for the next 24 hours. -Returns a [`AirPollutionLocationList`](05-entities#airpollutionlocationlist) object: +Returns a [`AirPollutionLocationList`](05-entities.md#airpollutionlocationlist) object: ```php $airPollutionForecast = $openWeatherMap->airPollution()->getForecast(50, 50); @@ -159,7 +159,7 @@ getHistory(float $latitude, float $longitude, \DateTimeInterface $startDate, \Da Get air pollution history data between two dates. -Returns a [`AirPollutionLocationList`](05-entities#airpollutionlocationlist) object: +Returns a [`AirPollutionLocationList`](05-entities.md#airpollutionlocationlist) object: ```php $startDate = new \DateTime('-7 days'); // 7 days ago @@ -177,7 +177,7 @@ foreach ($airPollutionHistory->getList() as $airPollution) { #### `getByLocationName` -Get locations by location name. Returns an array of [`Location`](05-entities#location) entities: +Get locations by location name. Returns an array of [`Location`](05-entities.md#location) entities: ```php /** @@ -192,7 +192,7 @@ $api->geocoding()->getByLocationName('lisbon'); #### `getByCoordinate` -Get locations by coordinate. Returns an array of [`Location`](05-entities#location) entities: +Get locations by coordinate. Returns an array of [`Location`](05-entities.md#location) entities: ```php /** @@ -207,7 +207,7 @@ $api->geocoding()->getByCoordinate(50, 50); #### `getByZipCode` -Get location by zip code. Returns a [`Location`](05-entities#location) entity: +Get location by zip code. Returns a [`Location`](05-entities.md#location) entity: ```php getByZipCode(string $zipCode, string $countryCode): Location diff --git a/docs/04-error-handling.md b/docs/04-error-handling.md index 27327a7..5121d6f 100644 --- a/docs/04-error-handling.md +++ b/docs/04-error-handling.md @@ -15,36 +15,36 @@ 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(); } -// API key requests quota exceeded +// api key requests quota exceeded 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-entities.md b/docs/05-entities.md index 79e9487..739e8ab 100644 --- a/docs/05-entities.md +++ b/docs/05-entities.md @@ -217,12 +217,12 @@ ### Location -- `getId()`: `?int` - `getName()`: `?string` - `getState()`: `?string` - `getCountryCode()`: `?string` - `getLocalNames()`: `?array` - `getLocalName(string $countryCode)`: `?string` +- `getZipCode()`: `?string` - `getCoordinate()`: [`Coordinate`](#coordinate) ### MoonPhase From ba10e247823cb38a9cf237885adb6e62f66e7a51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Pimpa=CC=83o?= Date: Tue, 7 May 2024 15:57:17 +0100 Subject: [PATCH 09/32] feat: added cache trait --- composer.json | 1 + docs/04-error-handling.md | 6 +++--- src/Endpoint/Util/CacheTtlTrait.php | 19 ------------------- src/OpenWeatherMap.php | 2 +- src/Resource/GeocodingResource.php | 4 +++- src/Resource/Util/CacheTrait.php | 16 ++++++++++++++++ 6 files changed, 24 insertions(+), 24 deletions(-) delete mode 100644 src/Endpoint/Util/CacheTtlTrait.php create mode 100644 src/Resource/Util/CacheTrait.php diff --git a/composer.json b/composer.json index 84406e7..fe8dff3 100644 --- a/composer.json +++ b/composer.json @@ -13,6 +13,7 @@ ], "require": { "php": ">=8.1", + "myclabs/deep-copy": "^1.11", "programmatordev/php-api-sdk": "^0.2.0", "programmatordev/yet-another-php-validator": "^1.1" }, diff --git a/docs/04-error-handling.md b/docs/04-error-handling.md index 5121d6f..aee7575 100644 --- a/docs/04-error-handling.md +++ b/docs/04-error-handling.md @@ -23,7 +23,7 @@ try { $coordinate->getLongitude() ); } -// bad request to the api +// bad request to the API catch (BadRequestException $exception) { echo $exception->getCode(); // 400 echo $exception->getMessage(); @@ -39,7 +39,7 @@ catch (NotFoundException $exception) { echo $exception->getCode(); // 404 echo $exception->getMessage(); } -// api key requests quota exceeded +// API key requests quota exceeded catch (TooManyRequestsException $exception) { echo $exception->getCode(); // 429 echo $exception->getMessage(); @@ -65,7 +65,7 @@ try { $coordinate->getLongitude() ); } -// catches all api response errors +// catches all API response errors catch (ApiErrorException $exception) { echo $exception->getCode(); echo $exception->getMessage(); 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/OpenWeatherMap.php b/src/OpenWeatherMap.php index c40c4c1..42dfb24 100644 --- a/src/OpenWeatherMap.php +++ b/src/OpenWeatherMap.php @@ -20,7 +20,7 @@ class OpenWeatherMap extends Api private array $options; public function __construct( - #[\SensitiveParameter] private readonly string $apiKey, + #[\SensitiveParameter] private string $apiKey, array $options = [] ) { diff --git a/src/Resource/GeocodingResource.php b/src/Resource/GeocodingResource.php index 7a134a9..baa2d76 100644 --- a/src/Resource/GeocodingResource.php +++ b/src/Resource/GeocodingResource.php @@ -5,6 +5,7 @@ use ProgrammatorDev\Api\Method; use ProgrammatorDev\OpenWeatherMap\Entity\Location; use ProgrammatorDev\OpenWeatherMap\OpenWeatherMap; +use ProgrammatorDev\OpenWeatherMap\Resource\Util\CacheTrait; use ProgrammatorDev\OpenWeatherMap\Resource\Util\ValidationTrait; use ProgrammatorDev\OpenWeatherMap\Util\EntityTrait; use ProgrammatorDev\Validator\Exception\ValidationException; @@ -12,12 +13,13 @@ class GeocodingResource { + use CacheTrait; use EntityTrait; use ValidationTrait; private const NUM_RESULTS = 5; - public function __construct(private readonly OpenWeatherMap $api) {} + public function __construct(private OpenWeatherMap $api) {} /** * @return Location[] diff --git a/src/Resource/Util/CacheTrait.php b/src/Resource/Util/CacheTrait.php new file mode 100644 index 0000000..b5c6c76 --- /dev/null +++ b/src/Resource/Util/CacheTrait.php @@ -0,0 +1,16 @@ +api->getCacheBuilder()?->setTtl($ttl); + + return $clone; + } +} \ No newline at end of file From c279e6edbf9080389beec5a25aede7d5bdde525c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Pimpa=CC=83o?= Date: Tue, 7 May 2024 17:16:07 +0100 Subject: [PATCH 10/32] chore: added base Resource class to remove redundancy --- src/Resource/GeocodingResource.php | 7 +------ src/Resource/Resource.php | 13 +++++++++++++ 2 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 src/Resource/Resource.php diff --git a/src/Resource/GeocodingResource.php b/src/Resource/GeocodingResource.php index baa2d76..d9d9894 100644 --- a/src/Resource/GeocodingResource.php +++ b/src/Resource/GeocodingResource.php @@ -4,23 +4,18 @@ use ProgrammatorDev\Api\Method; use ProgrammatorDev\OpenWeatherMap\Entity\Location; -use ProgrammatorDev\OpenWeatherMap\OpenWeatherMap; -use ProgrammatorDev\OpenWeatherMap\Resource\Util\CacheTrait; use ProgrammatorDev\OpenWeatherMap\Resource\Util\ValidationTrait; use ProgrammatorDev\OpenWeatherMap\Util\EntityTrait; use ProgrammatorDev\Validator\Exception\ValidationException; use Psr\Http\Client\ClientExceptionInterface; -class GeocodingResource +class GeocodingResource extends Resource { - use CacheTrait; use EntityTrait; use ValidationTrait; private const NUM_RESULTS = 5; - public function __construct(private OpenWeatherMap $api) {} - /** * @return Location[] * @throws ClientExceptionInterface diff --git a/src/Resource/Resource.php b/src/Resource/Resource.php new file mode 100644 index 0000000..d17aef8 --- /dev/null +++ b/src/Resource/Resource.php @@ -0,0 +1,13 @@ + Date: Wed, 8 May 2024 12:00:49 +0100 Subject: [PATCH 11/32] chore: updated location and added related entities --- docs/03-supported-apis.md | 46 ++++++++++-------- docs/05-entities.md | 20 ++++++-- src/Entity/Geocoding/ZipLocation.php | 48 +++++++++++++++++++ src/Entity/Location.php | 53 +++++++++++++++++---- src/Entity/Timezone.php | 14 +++--- src/Resource/GeocodingResource.php | 5 +- tests/Integration/GeocodingResourceTest.php | 3 +- tests/Unit/LocationTest.php | 34 +++++++------ tests/Unit/TimezoneTest.php | 20 ++++++++ tests/Unit/ZipLocationTest.php | 26 ++++++++++ 10 files changed, 214 insertions(+), 55 deletions(-) create mode 100644 src/Entity/Geocoding/ZipLocation.php create mode 100644 tests/Unit/TimezoneTest.php create mode 100644 tests/Unit/ZipLocationTest.php diff --git a/docs/03-supported-apis.md b/docs/03-supported-apis.md index afd06a1..ba99395 100644 --- a/docs/03-supported-apis.md +++ b/docs/03-supported-apis.md @@ -177,8 +177,6 @@ foreach ($airPollutionHistory->getList() as $airPollution) { #### `getByLocationName` -Get locations by location name. Returns an array of [`Location`](05-entities.md#location) entities: - ```php /** * @return Location[] @@ -186,14 +184,16 @@ Get locations by location name. Returns an array of [`Location`](05-entities.md# getByLocationName(string $locationName, int $numResults = 5): array ``` +Get locations by location name. + +Returns an array of [`Location`](05-entities.md#location) entities. + ```php -$api->geocoding()->getByLocationName('lisbon'); +$locations = $api->geocoding()->getByLocationName('lisbon'); ``` #### `getByCoordinate` -Get locations by coordinate. Returns an array of [`Location`](05-entities.md#location) entities: - ```php /** * @return Location[] @@ -201,20 +201,26 @@ Get locations by coordinate. Returns an array of [`Location`](05-entities.md#loc getByCoordinate(float $latitude, float $longitude, int $numResults = 5): array ``` +Get locations by coordinate. + +Returns an array of [`Location`](05-entities.md#location) entities. + ```php -$api->geocoding()->getByCoordinate(50, 50); +$locations = $api->geocoding()->getByCoordinate(50, 50); ``` #### `getByZipCode` -Get location by zip code. Returns a [`Location`](05-entities.md#location) entity: - ```php -getByZipCode(string $zipCode, string $countryCode): Location +getByZipCode(string $zipCode, string $countryCode): ZipLocation ``` +Get location by zip code. + +Returns a [`ZipLocation`](05-entities.md#ziplocation) entity. + ```php -$api->geocoding()->getByZipCode('1000-001', 'pt'); +$location = $api->geocoding()->getByZipCode('1000-001', 'pt'); ``` ## Common Methods @@ -260,25 +266,25 @@ $openWeatherMap->weather() #### `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. +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). -> **Note** -> Setting cache to `0` seconds will **not** invalidate any existing cache. +> [!NOTE] +> Setting cache to `null` or `0` seconds will **not** invalidate any existing cache. -Check the [Cache TTL](02-configuration.md#cache-ttl) section for more information regarding default values. +[//]: # (Check the [Cache TTL](02-configuration.md#cache-ttl) section for more information regarding default values.) -Available for all APIs if `cache` is enabled in the [configuration](02-configuration.md#cache). +[//]: # (Available for all APIs if `cache` is enabled in the [configuration](02-configuration.md#cache).) ```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/05-entities.md b/docs/05-entities.md index 739e8ab..606a3ab 100644 --- a/docs/05-entities.md +++ b/docs/05-entities.md @@ -16,6 +16,8 @@ - [AirPollutionLocation](#airpollutionlocation) - [AirPollutionLocationList](#airpollutionlocationlist) - [AirQuality](#airquality) +- [Geocoding](#geocoding) + - [ZipLocation](#ziplocation) - [Common](#common) - [AtmosphericPressure](#atmosphericpressure) - [Coordinate](#coordinate) @@ -197,6 +199,15 @@ - `getIndex()`: `int` - `getQualitativeName()`: `string` +## Geocoding + +### ZipLocation + +- `getZipCode()`: `string` +- `getName()`: `string` +- `getCountryCode()`: `string` +- `getCoordinate()`: [`Coordinate`](#coordinate) + ## Common ### AtmosphericPressure @@ -217,13 +228,16 @@ ### Location +- `getCoordinate()`: [`Coordinate`](#coordinate) +- `getId()`: `?int` - `getName()`: `?string` - `getState()`: `?string` - `getCountryCode()`: `?string` - `getLocalNames()`: `?array` - `getLocalName(string $countryCode)`: `?string` -- `getZipCode()`: `?string` -- `getCoordinate()`: [`Coordinate`](#coordinate) +- `getTimezone()`: [`?Timezone`](#timezone) +- `getSunriseAt()`: `?\DateTimeImmutable` +- `getSunsetAt()`: `?\DateTimeImmutable` ### MoonPhase @@ -252,8 +266,8 @@ ### Timezone -- `getIdentifier()`: `?string` - `getOffset()`: `int` +- `getIdentifier()`: `?string` ### WeatherCondition diff --git a/src/Entity/Geocoding/ZipLocation.php b/src/Entity/Geocoding/ZipLocation.php new file mode 100644 index 0000000..5bbe563 --- /dev/null +++ b/src/Entity/Geocoding/ZipLocation.php @@ -0,0 +1,48 @@ +zipCode = $data['zip']; + $this->name = $data['name']; + $this->countryCode = $data['country']; + + $this->coordinate = new Coordinate([ + 'lat' => $data['lat'], + 'lon' => $data['lon'] + ]); + } + + public function getZipCode(): string + { + return $this->zipCode; + } + + public function getName(): string + { + return $this->name; + } + + public function getCountryCode(): string + { + return $this->countryCode; + } + + public function getCoordinate(): Coordinate + { + return $this->coordinate; + } +} \ No newline at end of file diff --git a/src/Entity/Location.php b/src/Entity/Location.php index 795474b..3c5d154 100644 --- a/src/Entity/Location.php +++ b/src/Entity/Location.php @@ -4,6 +4,10 @@ class Location { + private Coordinate $coordinate; + + private ?int $id; + private ?string $name; private ?string $state; @@ -12,18 +16,46 @@ class Location private ?array $localNames; - private ?string $zipCode; + private ?Timezone $timezone; - private Coordinate $coordinate; + private ?\DateTimeImmutable $sunriseAt; + + private ?\DateTimeImmutable $sunsetAt; public function __construct(array $data) { + $this->coordinate = new Coordinate([ + 'lat' => $data['lat'], + 'lon' => $data['lon'] + ]); + + $this->id = $data['id'] ?? null; $this->name = $data['name'] ?? null; $this->state = $data['state'] ?? null; $this->countryCode = $data['country'] ?? null; $this->localNames = $data['local_names'] ?? null; - $this->zipCode = $data['zip'] ?? null; - $this->coordinate = new Coordinate(['lat' => $data['lat'], 'lon' => $data['lon']]); + + $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 + { + return $this->id; } public function getName(): ?string @@ -53,13 +85,18 @@ public function getLocalName(string $countryCode): ?string return $this->localNames[$countryCode] ?? null; } - public function getZipCode(): ?string + public function getTimezone(): ?Timezone { - return $this->zipCode; + return $this->timezone; } - public function getCoordinate(): Coordinate + public function getSunriseAt(): ?\DateTimeImmutable { - return $this->coordinate; + return $this->sunriseAt; + } + + public function getSunsetAt(): ?\DateTimeImmutable + { + return $this->sunsetAt; } } \ No newline at end of file diff --git a/src/Entity/Timezone.php b/src/Entity/Timezone.php index c49391b..95c0485 100644 --- a/src/Entity/Timezone.php +++ b/src/Entity/Timezone.php @@ -4,23 +4,23 @@ class Timezone { - private ?string $identifier; - private int $offset; + private ?string $identifier; + public function __construct(array $data) { - $this->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/Resource/GeocodingResource.php b/src/Resource/GeocodingResource.php index d9d9894..22d9a0d 100644 --- a/src/Resource/GeocodingResource.php +++ b/src/Resource/GeocodingResource.php @@ -3,6 +3,7 @@ namespace ProgrammatorDev\OpenWeatherMap\Resource; use ProgrammatorDev\Api\Method; +use ProgrammatorDev\OpenWeatherMap\Entity\Geocoding\ZipLocation; use ProgrammatorDev\OpenWeatherMap\Entity\Location; use ProgrammatorDev\OpenWeatherMap\Resource\Util\ValidationTrait; use ProgrammatorDev\OpenWeatherMap\Util\EntityTrait; @@ -42,7 +43,7 @@ public function getByLocationName(string $locationName, int $numResults = self:: * @throws ClientExceptionInterface * @throws ValidationException */ - public function getByZipCode(string $zipCode, string $countryCode): Location + public function getByZipCode(string $zipCode, string $countryCode): ZipLocation { $this->validateQuery($zipCode, 'zipCode'); $this->validateCountry($countryCode, 'countryCode'); @@ -55,7 +56,7 @@ public function getByZipCode(string $zipCode, string $countryCode): Location ] ); - return new Location($data); + return new ZipLocation($data); } /** diff --git a/tests/Integration/GeocodingResourceTest.php b/tests/Integration/GeocodingResourceTest.php index d706e70..fde076d 100644 --- a/tests/Integration/GeocodingResourceTest.php +++ b/tests/Integration/GeocodingResourceTest.php @@ -2,6 +2,7 @@ namespace ProgrammatorDev\OpenWeatherMap\Test\Integration; +use ProgrammatorDev\OpenWeatherMap\Entity\Geocoding\ZipLocation; use ProgrammatorDev\OpenWeatherMap\Entity\Location; use ProgrammatorDev\OpenWeatherMap\Test\AbstractTest; use ProgrammatorDev\OpenWeatherMap\Test\MockResponse; @@ -34,7 +35,7 @@ public static function provideCollectionResponseData(): \Generator public static function provideItemResponseData(): \Generator { yield 'get by zip code' => [ - Location::class, + ZipLocation::class, MockResponse::GEOCODING_ZIP, 'geocoding', 'getByZipCode', diff --git a/tests/Unit/LocationTest.php b/tests/Unit/LocationTest.php index 77ba688..226e963 100644 --- a/tests/Unit/LocationTest.php +++ b/tests/Unit/LocationTest.php @@ -4,6 +4,7 @@ use ProgrammatorDev\OpenWeatherMap\Entity\Coordinate; use ProgrammatorDev\OpenWeatherMap\Entity\Location; +use ProgrammatorDev\OpenWeatherMap\Entity\Timezone; use ProgrammatorDev\OpenWeatherMap\Test\AbstractTest; class LocationTest extends AbstractTest @@ -11,24 +12,29 @@ class LocationTest extends AbstractTest public function testMethods() { $entity = new Location([ - 'name' => 'Name', - 'state' => 'State', - 'country' => 'CO', + 'lat' => 50, + 'lon' => 50, + 'id' => 123, + 'name' => 'name', + 'state' => 'state', + 'country' => 'co', 'local_names' => [ - 'en' => 'Local Name' + 'en' => 'local name' ], - 'zip' => 'ZIP123', - 'lat' => 50, - 'lon' => 50 + 'timezone_offset' => 0, + 'sunrise' => 1661834187, + 'sunset' => 1661882248 ]); - $this->assertSame('Name', $entity->getName()); - $this->assertSame('State', $entity->getState()); - $this->assertSame('CO', $entity->getCountryCode()); - $this->assertSame(['en' => 'Local Name'], $entity->getLocalNames()); - $this->assertSame('Local Name', $entity->getLocalName('en')); - $this->assertSame(null, $entity->getLocalName('pt')); - $this->assertSame('ZIP123', $entity->getZipCode()); $this->assertInstanceOf(Coordinate::class, $entity->getCoordinate()); + $this->assertSame('name', $entity->getName()); + $this->assertSame('state', $entity->getState()); + $this->assertSame('co', $entity->getCountryCode()); + $this->assertSame(['en' => 'local name'], $entity->getLocalNames()); + $this->assertSame('local name', $entity->getLocalName('en')); + $this->assertSame(null, $entity->getLocalName('pt')); + $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/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/ZipLocationTest.php b/tests/Unit/ZipLocationTest.php new file mode 100644 index 0000000..494ba37 --- /dev/null +++ b/tests/Unit/ZipLocationTest.php @@ -0,0 +1,26 @@ + '1234-567', + 'name' => 'name', + 'country' => 'co', + 'lat' => 50, + 'lon' => 50 + ]); + + $this->assertSame('1234-567', $entity->getZipCode()); + $this->assertSame('name', $entity->getName()); + $this->assertSame('co', $entity->getCountryCode()); + $this->assertInstanceOf(Coordinate::class, $entity->getCoordinate()); + } +} \ No newline at end of file From 8538f4b8eb15c0823937f2161500e75fc8cdbd18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Pimpa=CC=83o?= Date: Wed, 8 May 2024 17:54:14 +0100 Subject: [PATCH 12/32] feat: added WeatherResource --- src/Entity/AtmosphericPressure.php | 34 ---- src/Entity/Condition.php | 92 +++++++++++ src/Entity/Icon.php | 10 +- src/Entity/Location.php | 31 +++- src/Entity/Rain.php | 5 - src/Entity/Snow.php | 5 - src/Entity/Volume.php | 32 ---- src/Entity/Weather/Weather.php | 136 ++-------------- ...LocationList.php => WeatherCollection.php} | 26 ++-- src/Entity/Weather/WeatherData.php | 146 ++++++++++++++++++ src/Entity/Weather/WeatherLocation.php | 31 ---- src/Entity/Wind.php | 2 +- src/OpenWeatherMap.php | 10 +- src/Resource/GeocodingResource.php | 6 +- src/Resource/WeatherResource.php | 59 +++++++ 15 files changed, 368 insertions(+), 257 deletions(-) delete mode 100644 src/Entity/AtmosphericPressure.php create mode 100644 src/Entity/Condition.php delete mode 100644 src/Entity/Rain.php delete mode 100644 src/Entity/Snow.php delete mode 100644 src/Entity/Volume.php rename src/Entity/Weather/{WeatherLocationList.php => WeatherCollection.php} (59%) create mode 100644 src/Entity/Weather/WeatherData.php delete mode 100644 src/Entity/Weather/WeatherLocation.php create mode 100644 src/Resource/WeatherResource.php 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/Condition.php b/src/Entity/Condition.php new file mode 100644 index 0000000..8f4e9d5 --- /dev/null +++ b/src/Entity/Condition.php @@ -0,0 +1,92 @@ +id = $data['id']; + $this->name = $data['main']; + $this->description = $data['description']; + $this->icon = new Icon($data); + $this->systemName = $this->findSystemName($this->id); + } + + public function getId(): int + { + return $this->id; + } + + public function getName(): string + { + return $this->name; + } + + public function getDescription(): string + { + return $this->description; + } + + public function getIcon(): Icon + { + return $this->icon; + } + + public function getSystemName(): string + { + return $this->systemName; + } + + /** + * Find group based on this table https://openweathermap.org/weather-conditions + */ + private function findSystemName(int $id): string + { + return match ($id) { + 200, 201, 202, 210, 211, 212, 221, 230, 231, 232 => self::THUNDERSTORM, + 300, 301, 302, 310, 311, 312, 313, 314, 321 => self::DRIZZLE, + 500, 501, 502, 503, 504, 511, 520, 521, 522, 531 => self::RAIN, + 600, 601, 602, 611, 612, 613, 615, 616, 620, 621, 622 => self::SNOW, + 701 => self::MIST, + 711 => self::SMOKE, + 721 => self::HAZE, + 731, 761 => self::DUST, + 741 => self::FOG, + 751 => self::SAND, + 762 => self::ASH, + 771 => self::SQUALL, + 781 => self::TORNADO, + 800 => self::CLEAR, + 801, 802, 803, 804 => self::CLOUDS, + default => self::UNDEFINED + }; + } +} \ 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 3c5d154..d5ba534 100644 --- a/src/Entity/Location.php +++ b/src/Entity/Location.php @@ -16,6 +16,8 @@ class Location private ?array $localNames; + private ?int $population; + private ?Timezone $timezone; private ?\DateTimeImmutable $sunriseAt; @@ -29,12 +31,30 @@ public function __construct(array $data) 'lon' => $data['lon'] ]); - $this->id = $data['id'] ?? null; - $this->name = $data['name'] ?? 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; - $this->countryCode = $data['country'] ?? 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; @@ -85,6 +105,11 @@ public function getLocalName(string $countryCode): ?string return $this->localNames[$countryCode] ?? null; } + public function getPopulation(): ?int + { + return $this->population; + } + public function getTimezone(): ?Timezone { return $this->timezone; 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 @@ -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..caf71b0 --- /dev/null +++ b/src/Entity/Weather/WeatherData.php @@ -0,0 +1,146 @@ +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; + } + + 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/OpenWeatherMap.php b/src/OpenWeatherMap.php index 42dfb24..63ff52d 100644 --- a/src/OpenWeatherMap.php +++ b/src/OpenWeatherMap.php @@ -13,6 +13,7 @@ use ProgrammatorDev\OpenWeatherMap\Exception\UnexpectedErrorException; use ProgrammatorDev\OpenWeatherMap\Language\Language; use ProgrammatorDev\OpenWeatherMap\Resource\GeocodingResource; +use ProgrammatorDev\OpenWeatherMap\Resource\WeatherResource; use ProgrammatorDev\OpenWeatherMap\UnitSystem\UnitSystem; class OpenWeatherMap extends Api @@ -28,7 +29,11 @@ public function __construct( $this->options = $this->configureOptions($options); $this->configureApi(); + } + public function weather(): WeatherResource + { + return new WeatherResource($this); } public function geocoding(): GeocodingResource @@ -46,11 +51,6 @@ public function geocoding(): GeocodingResource // return new OneCallEndpoint($this); // } // -// public function weather(): WeatherEndpoint -// { -// return new WeatherEndpoint($this); -// } -// // public function airPollution(): AirPollutionEndpoint // { // return new AirPollutionEndpoint($this); diff --git a/src/Resource/GeocodingResource.php b/src/Resource/GeocodingResource.php index 22d9a0d..63f6b38 100644 --- a/src/Resource/GeocodingResource.php +++ b/src/Resource/GeocodingResource.php @@ -19,8 +19,8 @@ class GeocodingResource extends Resource /** * @return Location[] - * @throws ClientExceptionInterface * @throws ValidationException + * @throws ClientExceptionInterface */ public function getByLocationName(string $locationName, int $numResults = self::NUM_RESULTS): array { @@ -40,8 +40,8 @@ public function getByLocationName(string $locationName, int $numResults = self:: } /** - * @throws ClientExceptionInterface * @throws ValidationException + * @throws ClientExceptionInterface */ public function getByZipCode(string $zipCode, string $countryCode): ZipLocation { @@ -61,8 +61,8 @@ public function getByZipCode(string $zipCode, string $countryCode): ZipLocation /** * @return Location[] - * @throws ClientExceptionInterface * @throws ValidationException + * @throws ClientExceptionInterface */ public function getByCoordinate(float $latitude, float $longitude, int $numResults = self::NUM_RESULTS): array { diff --git a/src/Resource/WeatherResource.php b/src/Resource/WeatherResource.php new file mode 100644 index 0000000..3fcfb8a --- /dev/null +++ b/src/Resource/WeatherResource.php @@ -0,0 +1,59 @@ +validateCoordinate($latitude, $longitude); + + $data = $this->api->request( + method: Method::GET, + path: '/data/2.5/weather', + query: [ + 'lat' => $latitude, + 'lon' => $longitude, + ] + ); + + return new Weather($data); + } + + /** + * @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 From 84a586224f6f29b919d685615a745f5b243d157c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Pimpa=CC=83o?= Date: Wed, 8 May 2024 19:29:03 +0100 Subject: [PATCH 13/32] tests: added Weather tests --- src/Endpoint/WeatherEndpoint.php | 69 ------ src/Entity/WeatherCondition.php | 92 -------- src/Test/Util/TestCollectionResponseTrait.php | 27 +++ src/Test/Util/TestItemResponseTrait.php | 27 +++ .../Util/TestValidationExceptionTrait.php | 16 ++ tests/Integration/GeocodingResourceTest.php | 32 +-- tests/Integration/OpenWeatherMapTest.php | 2 + tests/Integration/WeatherResourceTest.php | 47 ++++ tests/Unit/ConditionTest.php | 26 +++ tests/Unit/Geocoding/ZipLocationTest.php | 26 +++ tests/Unit/IconTest.php | 19 ++ tests/Unit/LocationTest.php | 11 +- tests/Unit/Weather/WeatherCollectionTest.php | 72 +++++++ tests/Unit/Weather/WeatherDataTest.php | 65 ++++++ tests/Unit/Weather/WeatherTest.php | 79 +++++++ tests/Unit/WindTest.php | 22 ++ tests/WeatherEndpointTest_.php | 201 ------------------ 17 files changed, 453 insertions(+), 380 deletions(-) delete mode 100644 src/Endpoint/WeatherEndpoint.php delete mode 100644 src/Entity/WeatherCondition.php create mode 100644 src/Test/Util/TestCollectionResponseTrait.php create mode 100644 src/Test/Util/TestItemResponseTrait.php create mode 100644 src/Test/Util/TestValidationExceptionTrait.php create mode 100644 tests/Integration/WeatherResourceTest.php create mode 100644 tests/Unit/ConditionTest.php create mode 100644 tests/Unit/Geocoding/ZipLocationTest.php create mode 100644 tests/Unit/IconTest.php create mode 100644 tests/Unit/Weather/WeatherCollectionTest.php create mode 100644 tests/Unit/Weather/WeatherDataTest.php create mode 100644 tests/Unit/Weather/WeatherTest.php create mode 100644 tests/Unit/WindTest.php delete mode 100644 tests/WeatherEndpointTest_.php 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/WeatherCondition.php b/src/Entity/WeatherCondition.php deleted file mode 100644 index 608efde..0000000 --- a/src/Entity/WeatherCondition.php +++ /dev/null @@ -1,92 +0,0 @@ -id = $data['id']; - $this->name = $data['main']; - $this->description = $data['description']; - $this->icon = new Icon($data); - $this->sysName = $this->findSysName($this->id); - } - - public function getId(): int - { - return $this->id; - } - - public function getName(): string - { - return $this->name; - } - - public function getDescription(): string - { - return $this->description; - } - - public function getIcon(): Icon - { - return $this->icon; - } - - public function getSysName(): string - { - return $this->sysName; - } - - /** - * Find group based on this table https://openweathermap.org/weather-conditions - */ - private function findSysName(int $id): string - { - return match ($id) { - 200, 201, 202, 210, 211, 212, 221, 230, 231, 232 => self::THUNDERSTORM, - 300, 301, 302, 310, 311, 312, 313, 314, 321 => self::DRIZZLE, - 500, 501, 502, 503, 504, 511, 520, 521, 522, 531 => self::RAIN, - 600, 601, 602, 611, 612, 613, 615, 616, 620, 621, 622 => self::SNOW, - 701 => self::MIST, - 711 => self::SMOKE, - 721 => self::HAZE, - 731, 761 => self::DUST, - 741 => self::FOG, - 751 => self::SAND, - 762 => self::ASH, - 771 => self::SQUALL, - 781 => self::TORNADO, - 800 => self::CLEAR, - 801, 802, 803, 804 => self::CLOUDS, - default => self::UNDEFINED - }; - } -} \ No newline at end of file diff --git a/src/Test/Util/TestCollectionResponseTrait.php b/src/Test/Util/TestCollectionResponseTrait.php new file mode 100644 index 0000000..fae81e5 --- /dev/null +++ b/src/Test/Util/TestCollectionResponseTrait.php @@ -0,0 +1,27 @@ +mockClient->addResponse(new Response( + status: 200, + body: $responseBody + )); + + $response = $this->api->$resource()->$method(...$args); + $this->assertContainsOnlyInstancesOf($responseClass, $response); + } +} \ 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..4cf721e --- /dev/null +++ b/src/Test/Util/TestItemResponseTrait.php @@ -0,0 +1,27 @@ +mockClient->addResponse(new Response( + status: 200, + body: $responseBody + )); + + $response = $this->api->$resource()->$method(...$args); + $this->assertInstanceOf($responseClass, $response); + } +} \ 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..ae4813a --- /dev/null +++ b/src/Test/Util/TestValidationExceptionTrait.php @@ -0,0 +1,16 @@ +expectException(ValidationException::class); + $this->api->$resource()->$method(...$args); + } +} \ No newline at end of file diff --git a/tests/Integration/GeocodingResourceTest.php b/tests/Integration/GeocodingResourceTest.php index fde076d..269372f 100644 --- a/tests/Integration/GeocodingResourceTest.php +++ b/tests/Integration/GeocodingResourceTest.php @@ -6,13 +6,26 @@ use ProgrammatorDev\OpenWeatherMap\Entity\Location; use ProgrammatorDev\OpenWeatherMap\Test\AbstractTest; use ProgrammatorDev\OpenWeatherMap\Test\MockResponse; -use ProgrammatorDev\OpenWeatherMap\Test\Util\TestExceptionsTrait; -use ProgrammatorDev\OpenWeatherMap\Test\Util\TestResponsesTrait; +use ProgrammatorDev\OpenWeatherMap\Test\Util\TestCollectionResponseTrait; +use ProgrammatorDev\OpenWeatherMap\Test\Util\TestValidationExceptionTrait; +use ProgrammatorDev\OpenWeatherMap\Test\Util\TestItemResponseTrait; class GeocodingResourceTest extends AbstractTest { - use TestResponsesTrait; - use TestExceptionsTrait; + use TestItemResponseTrait; + use TestCollectionResponseTrait; + use TestValidationExceptionTrait; + + public static function provideItemResponseData(): \Generator + { + yield 'get by zip code' => [ + ZipLocation::class, + MockResponse::GEOCODING_ZIP, + 'geocoding', + 'getByZipCode', + ['1000-001', 'pt'] + ]; + } public static function provideCollectionResponseData(): \Generator { @@ -32,17 +45,6 @@ public static function provideCollectionResponseData(): \Generator ]; } - public static function provideItemResponseData(): \Generator - { - yield 'get by zip code' => [ - ZipLocation::class, - MockResponse::GEOCODING_ZIP, - 'geocoding', - 'getByZipCode', - ['1000-001', 'pt'] - ]; - } - public static function provideValidationExceptionData(): \Generator { yield 'get by location name, blank value' => ['geocoding', 'getByLocationName', ['']]; diff --git a/tests/Integration/OpenWeatherMapTest.php b/tests/Integration/OpenWeatherMapTest.php index a78bad5..544e8ad 100644 --- a/tests/Integration/OpenWeatherMapTest.php +++ b/tests/Integration/OpenWeatherMapTest.php @@ -3,6 +3,7 @@ namespace ProgrammatorDev\OpenWeatherMap\Test\Integration; use ProgrammatorDev\OpenWeatherMap\Resource\GeocodingResource; +use ProgrammatorDev\OpenWeatherMap\Resource\WeatherResource; use ProgrammatorDev\OpenWeatherMap\Test\AbstractTest; class OpenWeatherMapTest extends AbstractTest @@ -10,5 +11,6 @@ class OpenWeatherMapTest extends AbstractTest public function testMethods() { $this->assertInstanceOf(GeocodingResource::class, $this->api->geocoding()); + $this->assertInstanceOf(WeatherResource::class, $this->api->weather()); } } \ No newline at end of file diff --git a/tests/Integration/WeatherResourceTest.php b/tests/Integration/WeatherResourceTest.php new file mode 100644 index 0000000..a0600ac --- /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/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/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 index 226e963..fd1bae2 100644 --- a/tests/Unit/LocationTest.php +++ b/tests/Unit/LocationTest.php @@ -14,25 +14,30 @@ public function testMethods() $entity = new Location([ 'lat' => 50, 'lon' => 50, - 'id' => 123, + 'id' => 100, 'name' => 'name', 'state' => 'state', - 'country' => 'co', + '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('co', $entity->getCountryCode()); + $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()); 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/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 From b39dadb99e90b4c16f7467dbfe3d7ade13a5e2e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Pimpa=CC=83o?= Date: Wed, 8 May 2024 19:43:20 +0100 Subject: [PATCH 14/32] docs: updated Weather related info --- docs/03-supported-apis.md | 23 +++----- docs/05-entities.md | 78 +++++++++++---------------- src/Test/Util/TestExceptionsTrait.php | 16 ------ src/Test/Util/TestResponsesTrait.php | 45 ---------------- tests/Unit/ZipLocationTest.php | 26 --------- 5 files changed, 38 insertions(+), 150 deletions(-) delete mode 100644 src/Test/Util/TestExceptionsTrait.php delete mode 100644 src/Test/Util/TestResponsesTrait.php delete mode 100644 tests/Unit/ZipLocationTest.php diff --git a/docs/03-supported-apis.md b/docs/03-supported-apis.md index ba99395..bcb151b 100644 --- a/docs/03-supported-apis.md +++ b/docs/03-supported-apis.md @@ -78,38 +78,31 @@ echo $weather->getTemperature(); #### `getCurrent` ```php -getCurrent(float $latitude, float $longitude): WeatherLocation +getCurrent(float $latitude, float $longitude): Weather ``` Get current weather data. -Returns a [`WeatherLocation`](05-entities.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 weather forecast for the next 5 days in 3-hour steps. -Returns a [`WeatherLocationList`](05-entities.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 diff --git a/docs/05-entities.md b/docs/05-entities.md index 606a3ab..d7b2b61 100644 --- a/docs/05-entities.md +++ b/docs/05-entities.md @@ -9,8 +9,8 @@ - [WeatherLocation](#weatherlocation) - [Weather](#weather-1) - [Weather](#weather-2) - - [WeatherLocation](#weatherlocation-1) - - [WeatherLocationList](#weatherlocationlist) + - [WeatherCollection](#weathercollection) + - [WeatherData](#weatherdata) - [Air Pollution](#air-pollution) - [AirPollution](#airpollution) - [AirPollutionLocation](#airpollutionlocation) @@ -19,17 +19,14 @@ - [Geocoding](#geocoding) - [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 @@ -121,6 +118,8 @@ ### Weather +- `getLocation()`: [`Location`](#location) +- `getDateTime()`: `\DateTimeImmutable` - `getTemperature()`: `float` - `getTemperatureFeelsLike()`: `float` - `getMinTemperature()`: `float` @@ -128,17 +127,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) + +### WeatherData + +- `getDateTime()`: `\DateTimeImmutable` - `getTemperature()`: `float` - `getTemperatureFeelsLike()`: `float` - `getMinTemperature()`: `float` @@ -146,19 +150,12 @@ - `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 @@ -210,21 +207,23 @@ ## 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 @@ -235,6 +234,7 @@ - `getCountryCode()`: `?string` - `getLocalNames()`: `?array` - `getLocalName(string $countryCode)`: `?string` +- `getPopulation()`: `?int` - `getTimezone()`: [`?Timezone`](#timezone) - `getSunriseAt()`: `?\DateTimeImmutable` - `getSunsetAt()`: `?\DateTimeImmutable` @@ -245,16 +245,6 @@ - `getName()`: `string` - `getSysName()`: `string` -### Rain - -- `getLastOneHourVolume()`: `?float` -- `getLastThreeHoursVolume()`: `?float` - -### Snow - -- `getLastOneHourVolume()`: `?float` -- `getLastThreeHoursVolume()`: `?float` - ### Temperature - `getMorning()`: `float` @@ -269,14 +259,6 @@ - `getOffset()`: `int` - `getIdentifier()`: `?string` -### WeatherCondition - -- `getId()`: `int` -- `getName()`: `string` -- `getDescription()`: `string` -- `getIcon()`: [`Icon`](#icon) -- `getSysName()`: `string` - ### Wind - `getSpeed()`: `float` diff --git a/src/Test/Util/TestExceptionsTrait.php b/src/Test/Util/TestExceptionsTrait.php deleted file mode 100644 index 3a0990c..0000000 --- a/src/Test/Util/TestExceptionsTrait.php +++ /dev/null @@ -1,16 +0,0 @@ -expectException(ValidationException::class); - $this->api->$resource()->$method(...$args); - } -} \ No newline at end of file diff --git a/src/Test/Util/TestResponsesTrait.php b/src/Test/Util/TestResponsesTrait.php deleted file mode 100644 index c192614..0000000 --- a/src/Test/Util/TestResponsesTrait.php +++ /dev/null @@ -1,45 +0,0 @@ -mockClient->addResponse(new Response( - status: 200, - body: $responseBody - )); - - $response = $this->api->$resource()->$method(...$args); - $this->assertContainsOnlyInstancesOf($responseClass, $response); - } - - #[DataProvider(methodName: 'provideItemResponseData')] - public function testItemResponse( - string $responseClass, - string $responseBody, - string $resource, - string $method, - ?array $args = null - ): void - { - $this->mockClient->addResponse(new Response( - status: 200, - body: $responseBody - )); - - $response = $this->api->$resource()->$method(...$args); - $this->assertInstanceOf($responseClass, $response); - } -} \ No newline at end of file diff --git a/tests/Unit/ZipLocationTest.php b/tests/Unit/ZipLocationTest.php deleted file mode 100644 index 494ba37..0000000 --- a/tests/Unit/ZipLocationTest.php +++ /dev/null @@ -1,26 +0,0 @@ - '1234-567', - 'name' => 'name', - 'country' => 'co', - 'lat' => 50, - 'lon' => 50 - ]); - - $this->assertSame('1234-567', $entity->getZipCode()); - $this->assertSame('name', $entity->getName()); - $this->assertSame('co', $entity->getCountryCode()); - $this->assertInstanceOf(Coordinate::class, $entity->getCoordinate()); - } -} \ No newline at end of file From b3e5f68fe1518646f4d6fcf7a84978e7937f56ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Pimpa=CC=83o?= Date: Thu, 9 May 2024 12:10:07 +0100 Subject: [PATCH 15/32] feat: added Language and UnitSystem traits --- src/Endpoint/Util/LanguageTrait.php | 28 --------------------------- src/Endpoint/Util/UnitSystemTrait.php | 28 --------------------------- src/Language/Language.php | 2 +- src/OpenWeatherMap.php | 4 ++-- src/Resource/GeocodingResource.php | 2 +- src/Resource/Util/LanguageTrait.php | 24 +++++++++++++++++++++++ src/Resource/Util/UnitSystemTrait.php | 24 +++++++++++++++++++++++ src/Resource/Util/ValidationTrait.php | 22 +++++++++++++++++++-- src/Resource/WeatherResource.php | 4 ++++ src/UnitSystem/UnitSystem.php | 2 +- 10 files changed, 77 insertions(+), 63 deletions(-) delete mode 100644 src/Endpoint/Util/LanguageTrait.php delete mode 100644 src/Endpoint/Util/UnitSystemTrait.php create mode 100644 src/Resource/Util/LanguageTrait.php create mode 100644 src/Resource/Util/UnitSystemTrait.php 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/Language/Language.php b/src/Language/Language.php index 08bb8ca..a92c795 100644 --- a/src/Language/Language.php +++ b/src/Language/Language.php @@ -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 63ff52d..49ad077 100644 --- a/src/OpenWeatherMap.php +++ b/src/OpenWeatherMap.php @@ -64,8 +64,8 @@ private function configureOptions(array $options): array $this->optionsResolver->setAllowedTypes('unitSystem', 'string'); $this->optionsResolver->setAllowedTypes('language', 'string'); - $this->optionsResolver->setAllowedValues('unitSystem', UnitSystem::getList()); - $this->optionsResolver->setAllowedValues('language', Language::getList()); + $this->optionsResolver->setAllowedValues('unitSystem', UnitSystem::getOptions()); + $this->optionsResolver->setAllowedValues('language', Language::getOptions()); return $this->optionsResolver->resolve($options); } diff --git a/src/Resource/GeocodingResource.php b/src/Resource/GeocodingResource.php index 63f6b38..fa8ef48 100644 --- a/src/Resource/GeocodingResource.php +++ b/src/Resource/GeocodingResource.php @@ -46,7 +46,7 @@ public function getByLocationName(string $locationName, int $numResults = self:: public function getByZipCode(string $zipCode, string $countryCode): ZipLocation { $this->validateQuery($zipCode, 'zipCode'); - $this->validateCountry($countryCode, 'countryCode'); + $this->validateCountryCode($countryCode); $data = $this->api->request( method: 'GET', diff --git a/src/Resource/Util/LanguageTrait.php b/src/Resource/Util/LanguageTrait.php new file mode 100644 index 0000000..36e3444 --- /dev/null +++ b/src/Resource/Util/LanguageTrait.php @@ -0,0 +1,24 @@ +validateLanguage($language); + + $clone = deep_copy($this); + $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..ed48aee --- /dev/null +++ b/src/Resource/Util/UnitSystemTrait.php @@ -0,0 +1,24 @@ +validateUnitSystem($unitSystem); + + $clone = deep_copy($this); + $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 index 50034b5..cf8eb62 100644 --- a/src/Resource/Util/ValidationTrait.php +++ b/src/Resource/Util/ValidationTrait.php @@ -2,6 +2,8 @@ namespace ProgrammatorDev\OpenWeatherMap\Resource\Util; +use ProgrammatorDev\OpenWeatherMap\Language\Language; +use ProgrammatorDev\OpenWeatherMap\UnitSystem\UnitSystem; use ProgrammatorDev\Validator\Exception\ValidationException; use ProgrammatorDev\Validator\Validator; @@ -35,8 +37,24 @@ private function validateCoordinate(float $latitude, float $longitude): void /** * @throws ValidationException */ - private function validateCountry(string $countryCode, string $name): void + private function validateCountryCode(string $countryCode): void { - Validator::country()->assert($countryCode, $name); + Validator::country()->assert($countryCode, 'countryCode'); + } + + /** + * @throws ValidationException + */ + private function validateLanguage(string $language): void + { + Validator::choice(Language::getOptions())->assert($language, 'language'); + } + + /** + * @throws ValidationException + */ + private function validateUnitSystem(string $unitSystem): void + { + Validator::choice(UnitSystem::getOptions())->assert($unitSystem, 'unitSystem'); } } \ No newline at end of file diff --git a/src/Resource/WeatherResource.php b/src/Resource/WeatherResource.php index 3fcfb8a..66803a4 100644 --- a/src/Resource/WeatherResource.php +++ b/src/Resource/WeatherResource.php @@ -5,12 +5,16 @@ use ProgrammatorDev\Api\Method; use ProgrammatorDev\OpenWeatherMap\Entity\Weather\Weather; use ProgrammatorDev\OpenWeatherMap\Entity\Weather\WeatherCollection; +use ProgrammatorDev\OpenWeatherMap\Resource\Util\LanguageTrait; +use ProgrammatorDev\OpenWeatherMap\Resource\Util\UnitSystemTrait; use ProgrammatorDev\OpenWeatherMap\Resource\Util\ValidationTrait; use ProgrammatorDev\Validator\Exception\ValidationException; use Psr\Http\Client\ClientExceptionInterface; class WeatherResource extends Resource { + use LanguageTrait; + use UnitSystemTrait; use ValidationTrait; private const NUM_RESULTS = 40; diff --git a/src/UnitSystem/UnitSystem.php b/src/UnitSystem/UnitSystem.php index e140e31..b7239d3 100644 --- a/src/UnitSystem/UnitSystem.php +++ b/src/UnitSystem/UnitSystem.php @@ -12,7 +12,7 @@ class UnitSystem public const IMPERIAL = 'imperial'; public const STANDARD = 'standard'; - public static function getList(): array + public static function getOptions(): array { return (new UnitSystem)->getClassConstants(self::class); } From 67d559866a85123b15de12752124962a67cb44e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Pimpa=CC=83o?= Date: Thu, 9 May 2024 12:59:25 +0100 Subject: [PATCH 16/32] tests: added language and unit system trait tests --- src/Test/Util/TestCollectionResponseTrait.php | 2 + src/Test/Util/TestItemResponseTrait.php | 2 + .../Util/TestValidationExceptionTrait.php | 2 + tests/Integration/LanguageTraitTest.php | 39 +++++++++++++++++++ tests/Integration/UnitSystemTraitTest.php | 39 +++++++++++++++++++ 5 files changed, 84 insertions(+) create mode 100644 tests/Integration/LanguageTraitTest.php create mode 100644 tests/Integration/UnitSystemTraitTest.php diff --git a/src/Test/Util/TestCollectionResponseTrait.php b/src/Test/Util/TestCollectionResponseTrait.php index fae81e5..1eb2727 100644 --- a/src/Test/Util/TestCollectionResponseTrait.php +++ b/src/Test/Util/TestCollectionResponseTrait.php @@ -24,4 +24,6 @@ public function testCollectionResponse( $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 index 4cf721e..c12d7e0 100644 --- a/src/Test/Util/TestItemResponseTrait.php +++ b/src/Test/Util/TestItemResponseTrait.php @@ -24,4 +24,6 @@ public function testItemResponse( $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 index ae4813a..073cd15 100644 --- a/src/Test/Util/TestValidationExceptionTrait.php +++ b/src/Test/Util/TestValidationExceptionTrait.php @@ -13,4 +13,6 @@ public function testValidationException(string $resource, string $method, ?array $this->expectException(ValidationException::class); $this->api->$resource()->$method(...$args); } + + abstract public static function provideValidationExceptionData(): \Generator; } \ No newline at end of file diff --git a/tests/Integration/LanguageTraitTest.php b/tests/Integration/LanguageTraitTest.php new file mode 100644 index 0000000..8803691 --- /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/UnitSystemTraitTest.php b/tests/Integration/UnitSystemTraitTest.php new file mode 100644 index 0000000..f3f8fee --- /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 From 23906c41906827603f0696318b7808813cb13b8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Pimpa=CC=83o?= Date: Thu, 9 May 2024 13:09:11 +0100 Subject: [PATCH 17/32] docs: updated withLanguage and withUnitSystem sections --- docs/03-supported-apis.md | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/docs/03-supported-apis.md b/docs/03-supported-apis.md index bcb151b..111b8bf 100644 --- a/docs/03-supported-apis.md +++ b/docs/03-supported-apis.md @@ -218,41 +218,39 @@ $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); ``` From 3ec554446a5523e6d5e80b74e3d8533fa1b2981d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Pimpa=CC=83o?= Date: Thu, 9 May 2024 17:44:50 +0100 Subject: [PATCH 18/32] feat: added air pollution resource --- src/Entity/AirPollution/AirPollution.php | 84 ++--------------- ...ionList.php => AirPollutionCollection.php} | 16 ++-- src/Entity/AirPollution/AirPollutionData.php | 90 +++++++++++++++++++ .../AirPollution/AirPollutionLocation.php | 22 ----- src/Entity/AirPollution/AirQuality.php | 19 ++-- .../Util/AirQualityQualitativeNameTrait.php | 21 ----- src/OpenWeatherMap.php | 11 +-- .../AirPollutionResource.php} | 61 ++++++------- src/Resource/Util/ValidationTrait.php | 11 +++ 9 files changed, 166 insertions(+), 169 deletions(-) rename src/Entity/AirPollution/{AirPollutionLocationList.php => AirPollutionCollection.php} (55%) create mode 100644 src/Entity/AirPollution/AirPollutionData.php delete mode 100644 src/Entity/AirPollution/AirPollutionLocation.php delete mode 100644 src/Entity/AirPollution/Util/AirQualityQualitativeNameTrait.php rename src/{Endpoint/AirPollutionEndpoint.php => Resource/AirPollutionResource.php} (50%) 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/AirPollutionLocationList.php b/src/Entity/AirPollution/AirPollutionCollection.php similarity index 55% rename from src/Entity/AirPollution/AirPollutionLocationList.php rename to src/Entity/AirPollution/AirPollutionCollection.php index fa3ecb1..2223cf5 100644 --- a/src/Entity/AirPollution/AirPollutionLocationList.php +++ b/src/Entity/AirPollution/AirPollutionCollection.php @@ -3,21 +3,21 @@ namespace ProgrammatorDev\OpenWeatherMap\Entity\AirPollution; use ProgrammatorDev\OpenWeatherMap\Entity\Coordinate; -use ProgrammatorDev\OpenWeatherMap\Util\EntityListTrait; +use ProgrammatorDev\OpenWeatherMap\Util\EntityTrait; -class AirPollutionLocationList +class AirPollutionCollection { - use EntityListTrait; + use EntityTrait; private Coordinate $coordinate; - /** @var AirPollution[] */ - private array $list; + /** @var AirPollutionData[] */ + private array $data; public function __construct(array $data) { $this->coordinate = new Coordinate($data['coord']); - $this->list = $this->createEntityList(AirPollution::class, $data['list']); + $this->data = $this->createEntityList(AirPollutionData::class, $data['list']); } public function getCoordinate(): Coordinate @@ -25,8 +25,8 @@ public function getCoordinate(): Coordinate return $this->coordinate; } - 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/AirPollution/AirPollutionData.php b/src/Entity/AirPollution/AirPollutionData.php new file mode 100644 index 0000000..bc7be47 --- /dev/null +++ b/src/Entity/AirPollution/AirPollutionData.php @@ -0,0 +1,90 @@ +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']; + } + + 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/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/OpenWeatherMap.php b/src/OpenWeatherMap.php index 49ad077..40b00ba 100644 --- a/src/OpenWeatherMap.php +++ b/src/OpenWeatherMap.php @@ -12,6 +12,7 @@ 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\WeatherResource; use ProgrammatorDev\OpenWeatherMap\UnitSystem\UnitSystem; @@ -36,6 +37,11 @@ public function weather(): WeatherResource return new WeatherResource($this); } + public function airPollution(): AirPollutionResource + { + return new AirPollutionResource($this); + } + public function geocoding(): GeocodingResource { return new GeocodingResource($this); @@ -49,11 +55,6 @@ public function geocoding(): GeocodingResource // public function oneCall(): OneCallEndpoint // { // return new OneCallEndpoint($this); -// } -// -// public function airPollution(): AirPollutionEndpoint -// { -// return new AirPollutionEndpoint($this); // } private function configureOptions(array $options): array diff --git a/src/Endpoint/AirPollutionEndpoint.php b/src/Resource/AirPollutionResource.php similarity index 50% rename from src/Endpoint/AirPollutionEndpoint.php rename to src/Resource/AirPollutionResource.php index 574a9db..7400468 100644 --- a/src/Endpoint/AirPollutionEndpoint.php +++ b/src/Resource/AirPollutionResource.php @@ -1,88 +1,85 @@ validateCoordinate($latitude, $longitude); - $data = $this->sendRequest( - method: 'GET', + $data = $this->api->request( + method: Method::GET, path: '/data/2.5/air_pollution', query: [ 'lat' => $latitude, - 'lon' => $longitude + 'lon' => $longitude, ] ); - return new AirPollutionLocation($data); + return new AirPollution($data); } /** - * @throws Exception - * @throws ApiErrorException * @throws ValidationException + * @throws ClientExceptionInterface */ - public function getForecast(float $latitude, float $longitude): AirPollutionLocationList + public function getForecast(float $latitude, float $longitude): AirPollutionCollection { $this->validateCoordinate($latitude, $longitude); - $data = $this->sendRequest( - method: 'GET', + $data = $this->api->request( + method: Method::GET, path: '/data/2.5/air_pollution/forecast', query: [ 'lat' => $latitude, - 'lon' => $longitude + 'lon' => $longitude, ] ); - return new AirPollutionLocationList($data); + return new AirPollutionCollection($data); } /** - * @throws Exception - * @throws ApiErrorException * @throws ValidationException + * @throws ClientExceptionInterface */ public function getHistory( float $latitude, float $longitude, \DateTimeInterface $startDate, \DateTimeInterface $endDate - ): AirPollutionLocationList + ): AirPollutionCollection { $this->validateCoordinate($latitude, $longitude); - $this->validateDateRange($startDate, $endDate); + $this->validateDateOrder($startDate, $endDate); - $timezoneUtc = new \DateTimeZone('UTC'); + $utcTimezone = new \DateTimeZone('UTC'); - $data = $this->sendRequest( - method: 'GET', + $data = $this->api->request( + method: Method::GET, path: '/data/2.5/air_pollution/history', query: [ 'lat' => $latitude, 'lon' => $longitude, - 'start' => $startDate->setTimezone($timezoneUtc)->getTimestamp(), - 'end' => $endDate->setTimezone($timezoneUtc)->getTimestamp() + 'start' => $startDate->setTimezone($utcTimezone)->getTimestamp(), + 'end' => $endDate->setTimezone($utcTimezone)->getTimestamp() ] ); - return new AirPollutionLocationList($data); + return new AirPollutionCollection($data); } } \ No newline at end of file diff --git a/src/Resource/Util/ValidationTrait.php b/src/Resource/Util/ValidationTrait.php index cf8eb62..382ce7c 100644 --- a/src/Resource/Util/ValidationTrait.php +++ b/src/Resource/Util/ValidationTrait.php @@ -57,4 +57,15 @@ private function validateUnitSystem(string $unitSystem): void { Validator::choice(UnitSystem::getOptions())->assert($unitSystem, 'unitSystem'); } + + /** + * @throws ValidationException + */ + private 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 From e8a6665c2af6a3240ab257ce6ff69a25ef0d06e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Pimpa=CC=83o?= Date: Thu, 9 May 2024 17:54:12 +0100 Subject: [PATCH 19/32] tests: added air pollution resource tests --- src/Endpoint/Util/ValidationTrait.php | 68 -------- tests/AirPollutionEndpointTest_.php | 155 ------------------ .../Integration/AirPollutionResourceTest.php | 58 +++++++ tests/Integration/OpenWeatherMapTest.php | 4 +- 4 files changed, 61 insertions(+), 224 deletions(-) delete mode 100644 src/Endpoint/Util/ValidationTrait.php delete mode 100644 tests/AirPollutionEndpointTest_.php create mode 100644 tests/Integration/AirPollutionResourceTest.php 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/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/Integration/AirPollutionResourceTest.php b/tests/Integration/AirPollutionResourceTest.php new file mode 100644 index 0000000..b10e108 --- /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/OpenWeatherMapTest.php b/tests/Integration/OpenWeatherMapTest.php index 544e8ad..93fd9ce 100644 --- a/tests/Integration/OpenWeatherMapTest.php +++ b/tests/Integration/OpenWeatherMapTest.php @@ -2,6 +2,7 @@ namespace ProgrammatorDev\OpenWeatherMap\Test\Integration; +use ProgrammatorDev\OpenWeatherMap\Resource\AirPollutionResource; use ProgrammatorDev\OpenWeatherMap\Resource\GeocodingResource; use ProgrammatorDev\OpenWeatherMap\Resource\WeatherResource; use ProgrammatorDev\OpenWeatherMap\Test\AbstractTest; @@ -10,7 +11,8 @@ class OpenWeatherMapTest extends AbstractTest { public function testMethods() { - $this->assertInstanceOf(GeocodingResource::class, $this->api->geocoding()); $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 From e35e1e2c04962daf8ac8944af041660ad154e904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Pimpa=CC=83o?= Date: Thu, 9 May 2024 19:50:26 +0100 Subject: [PATCH 20/32] tests: added air pollution entities tests --- src/Resource/AirPollutionResource.php | 3 -- src/Resource/GeocodingResource.php | 2 - src/Resource/Resource.php | 2 + src/Resource/Util/ValidationTrait.php | 14 ++--- src/Resource/WeatherResource.php | 2 - .../AirPollutionCollectionTest.php | 42 +++++++++++++++ .../AirPollution/AirPollutionDataTest.php | 41 +++++++++++++++ tests/Unit/AirPollution/AirPollutionTest.php | 51 +++++++++++++++++++ tests/Unit/AirPollution/AirQualityTest.php | 19 +++++++ 9 files changed, 162 insertions(+), 14 deletions(-) create mode 100644 tests/Unit/AirPollution/AirPollutionCollectionTest.php create mode 100644 tests/Unit/AirPollution/AirPollutionDataTest.php create mode 100644 tests/Unit/AirPollution/AirPollutionTest.php create mode 100644 tests/Unit/AirPollution/AirQualityTest.php diff --git a/src/Resource/AirPollutionResource.php b/src/Resource/AirPollutionResource.php index 7400468..7a1ff14 100644 --- a/src/Resource/AirPollutionResource.php +++ b/src/Resource/AirPollutionResource.php @@ -5,14 +5,11 @@ use ProgrammatorDev\Api\Method; use ProgrammatorDev\OpenWeatherMap\Entity\AirPollution\AirPollution; use ProgrammatorDev\OpenWeatherMap\Entity\AirPollution\AirPollutionCollection; -use ProgrammatorDev\OpenWeatherMap\Resource\Util\ValidationTrait; use ProgrammatorDev\Validator\Exception\ValidationException; use Psr\Http\Client\ClientExceptionInterface; class AirPollutionResource extends Resource { - use ValidationTrait; - /** * @throws ValidationException * @throws ClientExceptionInterface diff --git a/src/Resource/GeocodingResource.php b/src/Resource/GeocodingResource.php index fa8ef48..c0539ba 100644 --- a/src/Resource/GeocodingResource.php +++ b/src/Resource/GeocodingResource.php @@ -5,7 +5,6 @@ use ProgrammatorDev\Api\Method; use ProgrammatorDev\OpenWeatherMap\Entity\Geocoding\ZipLocation; use ProgrammatorDev\OpenWeatherMap\Entity\Location; -use ProgrammatorDev\OpenWeatherMap\Resource\Util\ValidationTrait; use ProgrammatorDev\OpenWeatherMap\Util\EntityTrait; use ProgrammatorDev\Validator\Exception\ValidationException; use Psr\Http\Client\ClientExceptionInterface; @@ -13,7 +12,6 @@ class GeocodingResource extends Resource { use EntityTrait; - use ValidationTrait; private const NUM_RESULTS = 5; diff --git a/src/Resource/Resource.php b/src/Resource/Resource.php index d17aef8..b9f59e7 100644 --- a/src/Resource/Resource.php +++ b/src/Resource/Resource.php @@ -4,10 +4,12 @@ use ProgrammatorDev\OpenWeatherMap\OpenWeatherMap; use ProgrammatorDev\OpenWeatherMap\Resource\Util\CacheTrait; +use ProgrammatorDev\OpenWeatherMap\Resource\Util\ValidationTrait; class Resource { use CacheTrait; + use ValidationTrait; public function __construct(protected OpenWeatherMap $api) {} } \ No newline at end of file diff --git a/src/Resource/Util/ValidationTrait.php b/src/Resource/Util/ValidationTrait.php index 382ce7c..33dd7d6 100644 --- a/src/Resource/Util/ValidationTrait.php +++ b/src/Resource/Util/ValidationTrait.php @@ -12,7 +12,7 @@ trait ValidationTrait /** * @throws ValidationException */ - private function validateQuery(string $query, string $name): void + protected function validateQuery(string $query, string $name): void { Validator::notBlank()->assert($query, $name); } @@ -20,7 +20,7 @@ private function validateQuery(string $query, string $name): void /** * @throws ValidationException */ - private function validatePositive(int $number, string $name): void + protected function validatePositive(int $number, string $name): void { Validator::greaterThan(0)->assert($number, $name); } @@ -28,7 +28,7 @@ private function validatePositive(int $number, string $name): void /** * @throws ValidationException */ - private function validateCoordinate(float $latitude, float $longitude): void + protected function validateCoordinate(float $latitude, float $longitude): void { Validator::range(-90, 90)->assert($latitude, 'latitude'); Validator::range(-180, 180)->assert($longitude, 'longitude'); @@ -37,7 +37,7 @@ private function validateCoordinate(float $latitude, float $longitude): void /** * @throws ValidationException */ - private function validateCountryCode(string $countryCode): void + protected function validateCountryCode(string $countryCode): void { Validator::country()->assert($countryCode, 'countryCode'); } @@ -45,7 +45,7 @@ private function validateCountryCode(string $countryCode): void /** * @throws ValidationException */ - private function validateLanguage(string $language): void + protected function validateLanguage(string $language): void { Validator::choice(Language::getOptions())->assert($language, 'language'); } @@ -53,7 +53,7 @@ private function validateLanguage(string $language): void /** * @throws ValidationException */ - private function validateUnitSystem(string $unitSystem): void + protected function validateUnitSystem(string $unitSystem): void { Validator::choice(UnitSystem::getOptions())->assert($unitSystem, 'unitSystem'); } @@ -61,7 +61,7 @@ private function validateUnitSystem(string $unitSystem): void /** * @throws ValidationException */ - private function validateDateOrder(\DateTimeInterface $startDate, \DateTimeInterface $endDate): void + protected function validateDateOrder(\DateTimeInterface $startDate, \DateTimeInterface $endDate): void { Validator::greaterThan( constraint: $startDate, diff --git a/src/Resource/WeatherResource.php b/src/Resource/WeatherResource.php index 66803a4..73ba121 100644 --- a/src/Resource/WeatherResource.php +++ b/src/Resource/WeatherResource.php @@ -7,7 +7,6 @@ use ProgrammatorDev\OpenWeatherMap\Entity\Weather\WeatherCollection; use ProgrammatorDev\OpenWeatherMap\Resource\Util\LanguageTrait; use ProgrammatorDev\OpenWeatherMap\Resource\Util\UnitSystemTrait; -use ProgrammatorDev\OpenWeatherMap\Resource\Util\ValidationTrait; use ProgrammatorDev\Validator\Exception\ValidationException; use Psr\Http\Client\ClientExceptionInterface; @@ -15,7 +14,6 @@ class WeatherResource extends Resource { use LanguageTrait; use UnitSystemTrait; - use ValidationTrait; private const NUM_RESULTS = 40; diff --git a/tests/Unit/AirPollution/AirPollutionCollectionTest.php b/tests/Unit/AirPollution/AirPollutionCollectionTest.php new file mode 100644 index 0000000..f7757a9 --- /dev/null +++ b/tests/Unit/AirPollution/AirPollutionCollectionTest.php @@ -0,0 +1,42 @@ + [ + '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->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 From 70b8f5942aca0be7b535783ca17520c5612ee9af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Pimpa=CC=83o?= Date: Thu, 9 May 2024 19:57:59 +0100 Subject: [PATCH 21/32] feat: added numResults to AirPollutionCollection entity --- src/Entity/AirPollution/AirPollutionCollection.php | 8 ++++++++ tests/Unit/AirPollution/AirPollutionCollectionTest.php | 1 + 2 files changed, 9 insertions(+) diff --git a/src/Entity/AirPollution/AirPollutionCollection.php b/src/Entity/AirPollution/AirPollutionCollection.php index 2223cf5..6dcbf46 100644 --- a/src/Entity/AirPollution/AirPollutionCollection.php +++ b/src/Entity/AirPollution/AirPollutionCollection.php @@ -9,6 +9,8 @@ class AirPollutionCollection { use EntityTrait; + private int $numResults; + private Coordinate $coordinate; /** @var AirPollutionData[] */ @@ -16,10 +18,16 @@ class AirPollutionCollection public function __construct(array $data) { + $this->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; diff --git a/tests/Unit/AirPollution/AirPollutionCollectionTest.php b/tests/Unit/AirPollution/AirPollutionCollectionTest.php index f7757a9..e8ee297 100644 --- a/tests/Unit/AirPollution/AirPollutionCollectionTest.php +++ b/tests/Unit/AirPollution/AirPollutionCollectionTest.php @@ -36,6 +36,7 @@ public function testMethods() ] ]); + $this->assertSame(1, $entity->getNumResults()); $this->assertInstanceOf(Coordinate::class, $entity->getCoordinate()); $this->assertContainsOnlyInstancesOf(AirPollutionData::class, $entity->getData()); } From fd3b159f52bceae469ac8b7eec82194567a07190 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Pimpa=CC=83o?= Date: Thu, 9 May 2024 20:10:27 +0100 Subject: [PATCH 22/32] docs: updated air pollution related info --- docs/03-supported-apis.md | 45 ++++++++++++++------------------------- docs/05-entities.md | 17 ++++++++------- 2 files changed, 25 insertions(+), 37 deletions(-) diff --git a/docs/03-supported-apis.md b/docs/03-supported-apis.md index 111b8bf..7994fab 100644 --- a/docs/03-supported-apis.md +++ b/docs/03-supported-apis.md @@ -110,60 +110,47 @@ $weatherForecast = $api->weather()->getForecast(50, 50, 8); #### `getCurrent` ```php -getCurrent(float $latitude, float $longitude): AirPollutionLocation +getCurrent(float $latitude, float $longitude): AirPollution ``` Get current air pollution data. -Returns a [`AirPollutionLocation`](05-entities.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 air pollution forecast data per hour. -Returns a [`AirPollutionLocationList`](05-entities.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 air pollution history data per hour between two dates. -Returns a [`AirPollutionLocationList`](05-entities.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 @@ -210,7 +197,7 @@ getByZipCode(string $zipCode, string $countryCode): ZipLocation Get location by zip code. -Returns a [`ZipLocation`](05-entities.md#ziplocation) entity. +Returns a [`ZipLocation`](05-entities.md#ziplocation) object. ```php $location = $api->geocoding()->getByZipCode('1000-001', 'pt'); diff --git a/docs/05-entities.md b/docs/05-entities.md index d7b2b61..6402c74 100644 --- a/docs/05-entities.md +++ b/docs/05-entities.md @@ -13,8 +13,8 @@ - [WeatherData](#weatherdata) - [Air Pollution](#air-pollution) - [AirPollution](#airpollution) - - [AirPollutionLocation](#airpollutionlocation) - - [AirPollutionLocationList](#airpollutionlocationlist) + - [AirPollutionCollection](#airpollutioncollection) + - [AirPollutionData](#airpollutiondata) - [AirQuality](#airquality) - [Geocoding](#geocoding) - [ZipLocation](#ziplocation) @@ -161,6 +161,7 @@ ### AirPollution +- `getCoordinate()`: [`Coordinate`](#coordinate) - `getDateTime()`: `\DateTimeImmutable` - `getAirQuality`: [`AirQuality`](#airquality) - `getCarbonMonoxide()`: `float` @@ -172,9 +173,14 @@ - `getCoarseParticulateMatter()`: `float` - `getAmmonia()`: `float` -### AirPollutionLocation +### AirPollutionCollection +- `getNumResults()`: `int` - `getCoordinate()`: [`Coordinate`](#coordinate) +- `getData()`: [`AirPollutionData[]`](#airpollutiondata) + +### AirPollutionData + - `getDateTime()`: `\DateTimeImmutable` - `getAirQuality`: [`AirQuality`](#airquality) - `getCarbonMonoxide()`: `float` @@ -186,11 +192,6 @@ - `getCoarseParticulateMatter()`: `float` - `getAmmonia()`: `float` -### AirPollutionLocationList - -- `getCoordinate()`: [`Coordinate`](#coordinate) -- `getList()`: [`AirPollution[]`](#airpollution) - ### AirQuality - `getIndex()`: `int` From 898b88b7cf1379e976ba3fc3f44823ed992f5ba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Pimpa=CC=83o?= Date: Fri, 10 May 2024 18:18:08 +0100 Subject: [PATCH 23/32] feat: added one call resource --- docs/05-entities.md | 2 +- src/Endpoint/OneCallEndpoint.php | 93 ----- src/Entity/OneCall/Alert.php | 6 +- src/Entity/OneCall/BaseWeather.php | 103 ++++++ src/Entity/OneCall/DayData.php | 84 +++++ src/Entity/OneCall/HourData.php | 44 +++ .../{MinuteForecast.php => MinuteData.php} | 4 +- src/Entity/{ => OneCall}/MoonPhase.php | 14 +- src/Entity/OneCall/OneCall.php | 76 ---- src/Entity/{ => OneCall}/Temperature.php | 2 +- src/Entity/OneCall/Weather.php | 190 +++------- src/Entity/OneCall/WeatherData.php | 58 +++ ...{WeatherLocation.php => WeatherMoment.php} | 13 +- ...eatherAggregate.php => WeatherSummary.php} | 22 +- src/Entity/Weather/WeatherData.php | 2 +- src/OpenWeatherMap.php | 16 +- src/Resource/OneCallResource.php | 84 +++++ tests/OneCallEndpointTest_.php | 338 ------------------ 18 files changed, 463 insertions(+), 688 deletions(-) delete mode 100644 src/Endpoint/OneCallEndpoint.php create mode 100644 src/Entity/OneCall/BaseWeather.php create mode 100644 src/Entity/OneCall/DayData.php create mode 100644 src/Entity/OneCall/HourData.php rename src/Entity/OneCall/{MinuteForecast.php => MinuteData.php} (88%) rename src/Entity/{ => OneCall}/MoonPhase.php (77%) delete mode 100644 src/Entity/OneCall/OneCall.php rename src/Entity/{ => OneCall}/Temperature.php (94%) create mode 100644 src/Entity/OneCall/WeatherData.php rename src/Entity/OneCall/{WeatherLocation.php => WeatherMoment.php} (62%) rename src/Entity/OneCall/{WeatherAggregate.php => WeatherSummary.php} (81%) create mode 100644 src/Resource/OneCallResource.php delete mode 100644 tests/OneCallEndpointTest_.php diff --git a/docs/05-entities.md b/docs/05-entities.md index 6402c74..4512a4b 100644 --- a/docs/05-entities.md +++ b/docs/05-entities.md @@ -1,4 +1,4 @@ -# Objects +# Entities - [One Call](#one-call) - [Alert](#alert) 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/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..2bb6072 --- /dev/null +++ b/src/Entity/OneCall/BaseWeather.php @@ -0,0 +1,103 @@ +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; + } + + 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..1a7d0cf --- /dev/null +++ b/src/Entity/OneCall/DayData.php @@ -0,0 +1,84 @@ +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; + } + + public function getMoonriseAt(): \DateTimeImmutable + { + return $this->moonriseAt; + } + + public function getMoonsetAt(): \DateTimeImmutable + { + return $this->moonsetAt; + } + + public function getSunriseAt(): \DateTimeImmutable + { + return $this->sunriseAt; + } + + 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 88% rename from src/Entity/OneCall/MinuteForecast.php rename to src/Entity/OneCall/MinuteData.php index 5f980f4..a301b7d 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,7 +10,7 @@ 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']; } 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..f401a16 --- /dev/null +++ b/src/Entity/OneCall/WeatherData.php @@ -0,0 +1,58 @@ +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; + } + + public function getSunriseAt(): ?\DateTimeImmutable + { + return $this->sunriseAt; + } + + 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 81% rename from src/Entity/OneCall/WeatherAggregate.php rename to src/Entity/OneCall/WeatherSummary.php index 41f60ba..1dc1937 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']) ]); } diff --git a/src/Entity/Weather/WeatherData.php b/src/Entity/Weather/WeatherData.php index caf71b0..4fdf2ac 100644 --- a/src/Entity/Weather/WeatherData.php +++ b/src/Entity/Weather/WeatherData.php @@ -54,7 +54,7 @@ public function __construct(array $data) $this->wind = new Wind($data['wind']); $this->precipitationProbability = isset($data['pop']) - ? round($data['pop'] * 100) + ? \round($data['pop'] * 100) : null; $this->rainVolume = $data['rain']['1h'] ?? $data['rain']['3h'] ?? null; diff --git a/src/OpenWeatherMap.php b/src/OpenWeatherMap.php index 40b00ba..427f5b0 100644 --- a/src/OpenWeatherMap.php +++ b/src/OpenWeatherMap.php @@ -14,6 +14,7 @@ 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; @@ -32,6 +33,11 @@ public function __construct( $this->configureApi(); } + public function oneCall(): OneCallResource + { + return new OneCallResource($this); + } + public function weather(): WeatherResource { return new WeatherResource($this); @@ -47,16 +53,6 @@ public function geocoding(): GeocodingResource return new GeocodingResource($this); } -// public function config(): Config -// { -// return $this->config; -// } - -// public function oneCall(): OneCallEndpoint -// { -// return new OneCallEndpoint($this); -// } - private function configureOptions(array $options): array { $this->optionsResolver->setDefault('unitSystem', UnitSystem::METRIC); diff --git a/src/Resource/OneCallResource.php b/src/Resource/OneCallResource.php new file mode 100644 index 0000000..17c09ce --- /dev/null +++ b/src/Resource/OneCallResource.php @@ -0,0 +1,84 @@ +validateCoordinate($latitude, $longitude); + + $data = $this->api->request( + method: Method::GET, + path: '/data/3.0/onecall', + query: [ + 'lat' => $latitude, + 'lon' => $longitude, + ] + ); + + return new Weather($data); + } + + /** + * @throws ValidationException + * @throws ClientExceptionInterface + */ + public function getWeatherByDateTime(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); + } + + /** + * @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/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 From 724c5c0745b0ff6b33cca7659133aa950d16b53d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Pimpa=CC=83o?= Date: Mon, 13 May 2024 12:17:12 +0100 Subject: [PATCH 24/32] tests: added one call resource tests --- src/Entity/AirPollution/AirPollutionData.php | 3 + src/Entity/Location.php | 6 ++ src/Entity/OneCall/BaseWeather.php | 3 + src/Entity/OneCall/DayData.php | 12 ++++ src/Entity/OneCall/MinuteData.php | 3 + src/Entity/OneCall/WeatherData.php | 6 ++ src/Entity/OneCall/WeatherSummary.php | 3 + src/Entity/Weather/WeatherData.php | 3 + src/Resource/AirPollutionResource.php | 6 ++ src/Resource/GeocodingResource.php | 6 ++ src/Resource/OneCallResource.php | 10 +++- src/Resource/WeatherResource.php | 4 ++ src/Test/MockResponse.php | 4 +- tests/Integration/OneCallResourceTest.php | 58 ++++++++++++++++++++ tests/Integration/OpenWeatherMapTest.php | 2 + 15 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 tests/Integration/OneCallResourceTest.php diff --git a/src/Entity/AirPollution/AirPollutionData.php b/src/Entity/AirPollution/AirPollutionData.php index bc7be47..4b85346 100644 --- a/src/Entity/AirPollution/AirPollutionData.php +++ b/src/Entity/AirPollution/AirPollutionData.php @@ -38,6 +38,9 @@ public function __construct(array $data) $this->ammonia = $data['components']['nh3']; } + /** + * DateTime in UTC + */ public function getDateTime(): \DateTimeImmutable { return $this->dateTime; diff --git a/src/Entity/Location.php b/src/Entity/Location.php index d5ba534..1d5c171 100644 --- a/src/Entity/Location.php +++ b/src/Entity/Location.php @@ -115,11 +115,17 @@ 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/BaseWeather.php b/src/Entity/OneCall/BaseWeather.php index 2bb6072..dfa9f69 100644 --- a/src/Entity/OneCall/BaseWeather.php +++ b/src/Entity/OneCall/BaseWeather.php @@ -51,6 +51,9 @@ public function __construct(array $data) $this->snowVolume = $data['snow']['1h'] ?? $data['snow']['3h'] ?? $data['snow'] ?? null; } + /** + * DateTime in UTC + */ public function getDateTime(): \DateTimeImmutable { return $this->dateTime; diff --git a/src/Entity/OneCall/DayData.php b/src/Entity/OneCall/DayData.php index 1a7d0cf..11cd28e 100644 --- a/src/Entity/OneCall/DayData.php +++ b/src/Entity/OneCall/DayData.php @@ -62,21 +62,33 @@ 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; diff --git a/src/Entity/OneCall/MinuteData.php b/src/Entity/OneCall/MinuteData.php index a301b7d..3273731 100644 --- a/src/Entity/OneCall/MinuteData.php +++ b/src/Entity/OneCall/MinuteData.php @@ -14,6 +14,9 @@ public function __construct(array $data) $this->precipitation = $data['precipitation']; } + /** + * DateTime in UTC + */ public function getDateTime(): \DateTimeImmutable { return $this->dateTime; diff --git a/src/Entity/OneCall/WeatherData.php b/src/Entity/OneCall/WeatherData.php index f401a16..2811290 100644 --- a/src/Entity/OneCall/WeatherData.php +++ b/src/Entity/OneCall/WeatherData.php @@ -46,11 +46,17 @@ 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; diff --git a/src/Entity/OneCall/WeatherSummary.php b/src/Entity/OneCall/WeatherSummary.php index 1dc1937..4396839 100644 --- a/src/Entity/OneCall/WeatherSummary.php +++ b/src/Entity/OneCall/WeatherSummary.php @@ -70,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/Weather/WeatherData.php b/src/Entity/Weather/WeatherData.php index 4fdf2ac..fae2605 100644 --- a/src/Entity/Weather/WeatherData.php +++ b/src/Entity/Weather/WeatherData.php @@ -61,6 +61,9 @@ public function __construct(array $data) $this->snowVolume = $data['snow']['1h'] ?? $data['snow']['3h'] ?? null; } + /** + * DateTime in UTC + */ public function getDateTime(): \DateTimeImmutable { return $this->dateTime; diff --git a/src/Resource/AirPollutionResource.php b/src/Resource/AirPollutionResource.php index 7a1ff14..2c05279 100644 --- a/src/Resource/AirPollutionResource.php +++ b/src/Resource/AirPollutionResource.php @@ -11,6 +11,8 @@ class AirPollutionResource extends Resource { /** + * Get access to current air pollution data + * * @throws ValidationException * @throws ClientExceptionInterface */ @@ -31,6 +33,8 @@ public function getCurrent(float $latitude, float $longitude): AirPollution } /** + * Get access to air pollution forecast data + * * @throws ValidationException * @throws ClientExceptionInterface */ @@ -51,6 +55,8 @@ public function getForecast(float $latitude, float $longitude): AirPollutionColl } /** + * Get access to historical air pollution data + * * @throws ValidationException * @throws ClientExceptionInterface */ diff --git a/src/Resource/GeocodingResource.php b/src/Resource/GeocodingResource.php index c0539ba..263c597 100644 --- a/src/Resource/GeocodingResource.php +++ b/src/Resource/GeocodingResource.php @@ -16,6 +16,8 @@ class GeocodingResource extends Resource private const NUM_RESULTS = 5; /** + * Get geographical coordinates (latitude, longitude) by using the name of the location (city name or area name) + * * @return Location[] * @throws ValidationException * @throws ClientExceptionInterface @@ -38,6 +40,8 @@ public function getByLocationName(string $locationName, int $numResults = self:: } /** + * Get geographical coordinates (latitude, longitude) by using the zip/postal code + * * @throws ValidationException * @throws ClientExceptionInterface */ @@ -58,6 +62,8 @@ public function getByZipCode(string $zipCode, string $countryCode): ZipLocation } /** + * Get name of the location (city name or area name) by using geographical coordinates (latitude, longitude) + * * @return Location[] * @throws ValidationException * @throws ClientExceptionInterface diff --git a/src/Resource/OneCallResource.php b/src/Resource/OneCallResource.php index 17c09ce..427f2ce 100644 --- a/src/Resource/OneCallResource.php +++ b/src/Resource/OneCallResource.php @@ -4,7 +4,6 @@ use ProgrammatorDev\Api\Method; use ProgrammatorDev\OpenWeatherMap\Entity\OneCall\Weather; -use ProgrammatorDev\OpenWeatherMap\Entity\OneCall\WeatherData; use ProgrammatorDev\OpenWeatherMap\Entity\OneCall\WeatherMoment; use ProgrammatorDev\OpenWeatherMap\Entity\OneCall\WeatherSummary; use ProgrammatorDev\OpenWeatherMap\Resource\Util\LanguageTrait; @@ -18,6 +17,9 @@ class OneCallResource extends Resource use UnitSystemTrait; /** + * Get access to current weather, minute forecast for 1 hour, hourly forecast for 48 hours, + * daily forecast for 8 days and government weather alerts + * * @throws ValidationException * @throws ClientExceptionInterface */ @@ -38,10 +40,12 @@ public function getWeather(float $latitude, float $longitude): Weather } /** + * Get access to weather data for any datetime + * * @throws ValidationException * @throws ClientExceptionInterface */ - public function getWeatherByDateTime(float $latitude, float $longitude, \DateTimeInterface $dateTime): WeatherMoment + public function getWeatherByDate(float $latitude, float $longitude, \DateTimeInterface $dateTime): WeatherMoment { $this->validateCoordinate($latitude, $longitude); @@ -61,6 +65,8 @@ public function getWeatherByDateTime(float $latitude, float $longitude, \DateTim } /** + * Get access to aggregated weather data for a particular date + * * @throws ValidationException * @throws ClientExceptionInterface */ diff --git a/src/Resource/WeatherResource.php b/src/Resource/WeatherResource.php index 73ba121..c4cd357 100644 --- a/src/Resource/WeatherResource.php +++ b/src/Resource/WeatherResource.php @@ -18,6 +18,8 @@ class WeatherResource extends Resource private const NUM_RESULTS = 40; /** + * Get access to current weather data + * * @throws ValidationException * @throws ClientExceptionInterface */ @@ -38,6 +40,8 @@ public function getCurrent(float $latitude, float $longitude): Weather } /** + * Get access to 5-day weather forecast data with 3-hour steps + * * @throws ValidationException * @throws ClientExceptionInterface */ 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/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 index 93fd9ce..76f4bca 100644 --- a/tests/Integration/OpenWeatherMapTest.php +++ b/tests/Integration/OpenWeatherMapTest.php @@ -4,6 +4,7 @@ use ProgrammatorDev\OpenWeatherMap\Resource\AirPollutionResource; use ProgrammatorDev\OpenWeatherMap\Resource\GeocodingResource; +use ProgrammatorDev\OpenWeatherMap\Resource\OneCallResource; use ProgrammatorDev\OpenWeatherMap\Resource\WeatherResource; use ProgrammatorDev\OpenWeatherMap\Test\AbstractTest; @@ -11,6 +12,7 @@ class OpenWeatherMapTest extends AbstractTest { public function testMethods() { + $this->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()); From 5e8d2325e1c14d51921ba379c27a909ccfe63f36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Pimpa=CC=83o?= Date: Mon, 13 May 2024 13:09:42 +0100 Subject: [PATCH 25/32] tests: added one call entity tests --- tests/Unit/OneCall/AlertTest.php | 28 +++++ tests/Unit/OneCall/DayDataTest.php | 79 ++++++++++++ tests/Unit/OneCall/HourDataTest.php | 55 ++++++++ tests/Unit/OneCall/MinuteDataTest.php | 20 +++ tests/Unit/OneCall/MoonPhaseTest.php | 20 +++ tests/Unit/OneCall/TemperatureTest.php | 28 +++++ tests/Unit/OneCall/WeatherDataTest.php | 57 +++++++++ tests/Unit/OneCall/WeatherMomentTest.php | 69 ++++++++++ tests/Unit/OneCall/WeatherSummaryTest.php | 61 +++++++++ tests/Unit/OneCall/WeatherTest.php | 147 ++++++++++++++++++++++ 10 files changed, 564 insertions(+) create mode 100644 tests/Unit/OneCall/AlertTest.php create mode 100644 tests/Unit/OneCall/DayDataTest.php create mode 100644 tests/Unit/OneCall/HourDataTest.php create mode 100644 tests/Unit/OneCall/MinuteDataTest.php create mode 100644 tests/Unit/OneCall/MoonPhaseTest.php create mode 100644 tests/Unit/OneCall/TemperatureTest.php create mode 100644 tests/Unit/OneCall/WeatherDataTest.php create mode 100644 tests/Unit/OneCall/WeatherMomentTest.php create mode 100644 tests/Unit/OneCall/WeatherSummaryTest.php create mode 100644 tests/Unit/OneCall/WeatherTest.php 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 From 383cd3ccf329aa4ce809a325efb95531568a1ef1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Pimpa=CC=83o?= Date: Mon, 13 May 2024 16:39:45 +0100 Subject: [PATCH 26/32] docs: updated one call resource related info --- docs/03-supported-apis.md | 49 ++++--- docs/05-entities.md | 177 ++++++++++++++++---------- src/Resource/AirPollutionResource.php | 4 +- 3 files changed, 131 insertions(+), 99 deletions(-) diff --git a/docs/03-supported-apis.md b/docs/03-supported-apis.md index 7994fab..21db682 100644 --- a/docs/03-supported-apis.md +++ b/docs/03-supported-apis.md @@ -31,46 +31,41 @@ getWeather(float $latitude, float $longitude): OneCall ``` -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-entities.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-entities.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-entities.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 @@ -81,7 +76,7 @@ echo $weather->getTemperature(); getCurrent(float $latitude, float $longitude): Weather ``` -Get current weather data. +Get access to current weather data. Returns a [`Weather`](05-entities.md#weather-2) object: @@ -95,7 +90,7 @@ $currentWeather = $api->weather()->getCurrent(50, 50); getForecast(float $latitude, float $longitude, int $numResults = 40): WeatherCollection ``` -Get weather forecast for the next 5 days in 3-hour steps. +Get access to 5-day weather forecast data with 3-hour steps. Returns a [`WeatherCollection`](05-entities.md#weathercollection) object: @@ -113,7 +108,7 @@ $weatherForecast = $api->weather()->getForecast(50, 50, 8); getCurrent(float $latitude, float $longitude): AirPollution ``` -Get current air pollution data. +Get access to current air pollution data. Returns a [`AirPollution`](05-entities.md#airpollution) object: @@ -127,7 +122,7 @@ $currentAirPollution = $api->airPollution()->getCurrent(50, 50); getForecast(float $latitude, float $longitude): AirPollutionCollection ``` -Get air pollution forecast data per hour. +Get access to air pollution forecast data per hour. Returns a [`AirPollutionCollection`](05-entities.md#airpollutioncollection) object: @@ -141,7 +136,7 @@ $airPollutionForecast = $api->airPollution()->getForecast(50, 50); getHistory(float $latitude, float $longitude, \DateTimeInterface $startDate, \DateTimeInterface $endDate): AirPollutionCollection ``` -Get air pollution history data per hour between two dates. +Get access to historical air pollution data per hour between two dates. Returns a [`AirPollutionCollection`](05-entities.md#airpollutioncollection) object: @@ -164,7 +159,7 @@ $airPollutionHistory = $api->airPollution()->getHistory(50, 50, $startDate, $end getByLocationName(string $locationName, int $numResults = 5): array ``` -Get locations 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-entities.md#location) entities. @@ -181,7 +176,7 @@ $locations = $api->geocoding()->getByLocationName('lisbon'); getByCoordinate(float $latitude, float $longitude, int $numResults = 5): array ``` -Get locations by coordinate. +Get name of the location (city name or area name) by using geographical coordinates (latitude, longitude). Returns an array of [`Location`](05-entities.md#location) entities. @@ -195,7 +190,7 @@ $locations = $api->geocoding()->getByCoordinate(50, 50); getByZipCode(string $zipCode, string $countryCode): ZipLocation ``` -Get location by zip code. +Get geographical coordinates (latitude, longitude) by using the zip/postal code. Returns a [`ZipLocation`](05-entities.md#ziplocation) object. diff --git a/docs/05-entities.md b/docs/05-entities.md index 4512a4b..412e901 100644 --- a/docs/05-entities.md +++ b/docs/05-entities.md @@ -1,12 +1,16 @@ # 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) - [WeatherCollection](#weathercollection) @@ -30,54 +34,41 @@ ## 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) @@ -89,30 +80,91 @@ - `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 @@ -138,7 +190,7 @@ - `getNumResults()`: `int` - `getLocation()`: [`Location`](#location) -- `getData()`: [`WeatherData[]`](#weatherdata) +- `getData()`: [`WeatherData[]`](#weatherdata-1) ### WeatherData @@ -240,21 +292,6 @@ - `getSunriseAt()`: `?\DateTimeImmutable` - `getSunsetAt()`: `?\DateTimeImmutable` -### MoonPhase - -- `getValue()`: `float` -- `getName()`: `string` -- `getSysName()`: `string` - -### Temperature - -- `getMorning()`: `float` -- `getDay()`: `float` -- `getEvening()`: `float` -- `getNight()`: `float` -- `getMin()`: `?float` -- `getMax()`: `?float` - ### Timezone - `getOffset()`: `int` diff --git a/src/Resource/AirPollutionResource.php b/src/Resource/AirPollutionResource.php index 2c05279..4f40ae1 100644 --- a/src/Resource/AirPollutionResource.php +++ b/src/Resource/AirPollutionResource.php @@ -33,7 +33,7 @@ public function getCurrent(float $latitude, float $longitude): AirPollution } /** - * Get access to air pollution forecast data + * Get access to air pollution forecast data per hour * * @throws ValidationException * @throws ClientExceptionInterface @@ -55,7 +55,7 @@ public function getForecast(float $latitude, float $longitude): AirPollutionColl } /** - * Get access to historical air pollution data + * Get access to historical air pollution data per hour between two dates * * @throws ValidationException * @throws ClientExceptionInterface From 696e0cb2354f07fff2aa5b3104bffb05f52a3dc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Pimpa=CC=83o?= Date: Mon, 13 May 2024 16:44:20 +0100 Subject: [PATCH 27/32] docs: added library methods documentation --- docs/02-configuration.md | 180 +++++++++++++++----------------------- docs/03-supported-apis.md | 5 +- 2 files changed, 72 insertions(+), 113 deletions(-) diff --git a/docs/02-configuration.md b/docs/02-configuration.md index 42ef512..bcee3ed 100644 --- a/docs/02-configuration.md +++ b/docs/02-configuration.md @@ -4,9 +4,17 @@ - [Options](#options) - [unitSystem](#unitsystem) - [language](#language) +- [Methods](#methods) + - [setClientBuilder](#setclientbuilder) + - [setCacheBuilder](#setcachebuilder) + - [setLoggerBuilder](#setloggerbuilder) ## Default Configuration +```php +OpenWeatherMap(string $apiKey, array $options => []); +``` + ```php use ProgrammatorDev\OpenWeatherMap\OpenWeatherMap; @@ -23,7 +31,10 @@ $api = new OpenWeatherMap('yourapikey', [ Unit system used when retrieving data. Affects temperature and speed values. -Available options are `metric`, `imperial` and `standard`. +Available options: +- `metric` +- `imperial` +- `standard` Example: @@ -54,147 +65,96 @@ $api = new OpenWeatherMap('yourapikey', [ ]); ``` -### `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 available methods, make sure to check the documentation [here](https://github.com/programmatordev/php-api-sdk?tab=readme-ov-file#php-api-sdk). -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'); -$openWeatherMap = new OpenWeatherMap( - new Config([ - 'applicationKey' => 'yourappkey', - 'httpClientBuilder' => $httpClientBuilder - ]) +$client = new Psr18Client(); +$requestFactory = $streamFactory = new Psr17Factory(); + +$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. - -### `cache` +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). -Configure a PSR-6 cache adapter. +### `setCacheBuilder` -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) +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) -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') -); +$api = new OpenWeatherMap('yourapikey'); -$openWeatherMap = new OpenWeatherMap( - new Config([ - 'applicationKey' => 'yourappkey', - 'logger' => $logger - ]) -); -``` +$logger = new Logger('openweathermap'); +$logger->pushHandler(new StreamHandler('/logs/api.log')); -...and will provide logs similar to this: - -``` -[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"} +$api->setLoggerBuilder( + new LoggerBuilder( + logger: $logger + ) +); ``` -> **Note** -> If a `cache` implementation is configured, cache events will also be logged. \ 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 21db682..3e124d6 100644 --- a/docs/03-supported-apis.md +++ b/docs/03-supported-apis.md @@ -251,9 +251,8 @@ Semantics of values: > [!NOTE] > Setting cache to `null` or `0` seconds will **not** invalidate any existing cache. -[//]: # (Check the [Cache TTL](02-configuration.md#cache-ttl) section for more information regarding default values.) - -[//]: # (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#cache) for more information. ```php // cache will be saved for 1 hour for this request alone From 71aa3a36e6b159eba919de4a85e763a522d7f6c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Pimpa=CC=83o?= Date: Mon, 13 May 2024 17:07:55 +0100 Subject: [PATCH 28/32] docs: fixes --- docs/02-configuration.md | 4 ++-- docs/03-supported-apis.md | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/02-configuration.md b/docs/02-configuration.md index bcee3ed..6cf9509 100644 --- a/docs/02-configuration.md +++ b/docs/02-configuration.md @@ -69,7 +69,7 @@ $api = new OpenWeatherMap('yourapikey', [ > [!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 available methods, make sure to check the documentation [here](https://github.com/programmatordev/php-api-sdk?tab=readme-ov-file#php-api-sdk). +> 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#php-api-sdk). The following sections have examples of some of the most important methods, particularly related with the configuration of the client, cache and logger. @@ -147,7 +147,7 @@ use ProgrammatorDev\OpenWeatherMap\OpenWeatherMap; $api = new OpenWeatherMap('yourapikey'); -$logger = new Logger('openweathermap'); +$logger = new Logger('api'); $logger->pushHandler(new StreamHandler('/logs/api.log')); $api->setLoggerBuilder( diff --git a/docs/03-supported-apis.md b/docs/03-supported-apis.md index 3e124d6..fe411fb 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) @@ -28,7 +28,7 @@ #### `getWeather` ```php -getWeather(float $latitude, float $longitude): OneCall +getWeather(float $latitude, float $longitude): Weather ``` Get access to current weather, minute forecast for 1 hour, hourly forecast for 48 hours, @@ -161,7 +161,7 @@ getByLocationName(string $locationName, int $numResults = 5): array Get geographical coordinates (latitude, longitude) by using the name of the location (city name or area name). -Returns an array of [`Location`](05-entities.md#location) entities. +Returns an array of [`Location`](05-entities.md#location) objects. ```php $locations = $api->geocoding()->getByLocationName('lisbon'); @@ -178,7 +178,7 @@ getByCoordinate(float $latitude, float $longitude, int $numResults = 5): array Get name of the location (city name or area name) by using geographical coordinates (latitude, longitude). -Returns an array of [`Location`](05-entities.md#location) entities. +Returns an array of [`Location`](05-entities.md#location) objects. ```php $locations = $api->geocoding()->getByCoordinate(50, 50); From e52d3df879a0a3db6c3282259c0b7852e90b95fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Pimpa=CC=83o?= Date: Mon, 13 May 2024 18:51:21 +0100 Subject: [PATCH 29/32] tests: added api error tests --- tests/ApiErrorTest_.php | 38 -------------------- tests/Integration/ResourceTest.php | 56 ++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 38 deletions(-) delete mode 100644 tests/ApiErrorTest_.php create mode 100644 tests/Integration/ResourceTest.php 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/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 From ca79612fc1006ea0abef9911be3c3f400a81ae13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Pimpa=CC=83o?= Date: Mon, 13 May 2024 18:51:31 +0100 Subject: [PATCH 30/32] docs: fixes --- docs/02-configuration.md | 2 +- docs/03-supported-apis.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/02-configuration.md b/docs/02-configuration.md index 6cf9509..7416324 100644 --- a/docs/02-configuration.md +++ b/docs/02-configuration.md @@ -69,7 +69,7 @@ $api = new OpenWeatherMap('yourapikey', [ > [!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#php-api-sdk). +> 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). The following sections have examples of some of the most important methods, particularly related with the configuration of the client, cache and logger. diff --git a/docs/03-supported-apis.md b/docs/03-supported-apis.md index fe411fb..de77325 100644 --- a/docs/03-supported-apis.md +++ b/docs/03-supported-apis.md @@ -252,7 +252,7 @@ Semantics of values: > Setting cache to `null` or `0` seconds will **not** invalidate any existing cache. Available for all APIs if a cache adapter is set. -Check the following [documentation](02-configuration.md#cache) for more information. +Check the following [documentation](02-configuration.md#setcachebuilder) for more information. ```php // cache will be saved for 1 hour for this request alone From fa8bcdf65c82fa803f4f746ed3c82658ffc12425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Pimpa=CC=83o?= Date: Mon, 13 May 2024 19:17:22 +0100 Subject: [PATCH 31/32] tests: fix integration tests namespace --- tests/Integration/AirPollutionResourceTest.php | 2 +- tests/Integration/LanguageTraitTest.php | 2 +- tests/Integration/UnitSystemTraitTest.php | 2 +- tests/Integration/WeatherResourceTest.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Integration/AirPollutionResourceTest.php b/tests/Integration/AirPollutionResourceTest.php index b10e108..753fe19 100644 --- a/tests/Integration/AirPollutionResourceTest.php +++ b/tests/Integration/AirPollutionResourceTest.php @@ -1,6 +1,6 @@ Date: Mon, 13 May 2024 19:49:28 +0100 Subject: [PATCH 32/32] tests: added cache trait tests --- src/Resource/Util/CacheTrait.php | 2 +- src/Resource/Util/LanguageTrait.php | 2 +- src/Resource/Util/UnitSystemTrait.php | 2 +- tests/Integration/CacheTraitTest.php | 39 +++++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 tests/Integration/CacheTraitTest.php diff --git a/src/Resource/Util/CacheTrait.php b/src/Resource/Util/CacheTrait.php index b5c6c76..eadfd25 100644 --- a/src/Resource/Util/CacheTrait.php +++ b/src/Resource/Util/CacheTrait.php @@ -8,7 +8,7 @@ trait CacheTrait { public function withCacheTtl(?int $ttl): static { - $clone = deep_copy($this); + $clone = deep_copy($this, true); $clone->api->getCacheBuilder()?->setTtl($ttl); return $clone; diff --git a/src/Resource/Util/LanguageTrait.php b/src/Resource/Util/LanguageTrait.php index 36e3444..76e6493 100644 --- a/src/Resource/Util/LanguageTrait.php +++ b/src/Resource/Util/LanguageTrait.php @@ -16,7 +16,7 @@ public function withLanguage(string $language): static { $this->validateLanguage($language); - $clone = deep_copy($this); + $clone = deep_copy($this, true); $clone->api->addQueryDefault('lang', $language); return $clone; diff --git a/src/Resource/Util/UnitSystemTrait.php b/src/Resource/Util/UnitSystemTrait.php index ed48aee..166ac87 100644 --- a/src/Resource/Util/UnitSystemTrait.php +++ b/src/Resource/Util/UnitSystemTrait.php @@ -16,7 +16,7 @@ public function withUnitSystem(string $unitSystem): static { $this->validateUnitSystem($unitSystem); - $clone = deep_copy($this); + $clone = deep_copy($this, true); $clone->api->addQueryDefault('units', $unitSystem); return $clone; 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