Encapsulando a API do OpenWeatherMap em uma Fonte de Dados


1Introdução

Neste tutorial, vamos ver como criar um objeto que pode ser usado como fonte de dados. Como exemplo, vamos criar um objeto que se conecta à API do serviço OpenWeatherMap, permitindo obter a previsão do tempo atual para qualquer localidade (para ser preciso, a API do OpenWeatherMap recebe uma latitude e uma longitude como parâmetros; por isso, vamos usar a API de Geocodificação para obter as coordenadas geográficas da cidade solicitada).


2Configuração

No arquivo de configuração etc/temma.php, vamos adicionar a definição de uma fonte de dados um pouco especial. Como não é uma fonte de dados gerenciada nativamente pelo Temma, vamos prefixar o DSN com o nome do objeto a ser usado (entre colchetes).

O DSN terá a forma openweather://API_KEY
sendo API_KEY a chave de API fornecida pelo OpenWeatherMap.

Exemplo de configuração:

<?php

return [
    "application" => [
        "dataSources" => [
            "meteo" => '[\OpenWeatherMap\Weather]openweather://0123456789abcdef0123456789abcdef'
        ]
    ]
];

3Código-fonte

Aqui está o código da nossa fonte de dados, que vamos salvar no arquivo lib/OpenWeatherMap/Weather.php:

<?php

namespace OpenWeatherMap;

class Weather extends \Temma\Base\Datasource {
    /** Constante: prefixo do DSN. */
    const DSN_SCHEME = 'weather://';
    /** Constante: URL da API de dados do OpenWeatherMap. */
    const DATA_API_URL = 'https://api.openweathermap.org/data/2.5/weather';
    /** Constante: URL da API de geocodificação do OpenWeatherMap. */
    const GEO_API_URL = 'http://api.openweathermap.org/geo/1.0/direct';
    /** Chave de acesso à API. */
    private $_key = null;

    /**
     * Fábrica. Cria uma instância do objeto a partir de um DSN.
     * @param   string   $dsn   String de configuração.
     * @return  \OpenWeatherMap\Weather    O objeto instanciado.
     * @throws  \Exception   Se o DSN estiver incorreto.
     */
    static public function factory(string $dsn) : \OpenWeatherMap\Weather {
        if (!str_starts_with($dsn, self::DSN_SCHEME)) {
            throw new \Exception("Invalid OpenWeatherMap DSN '$dsn'.");
        }
        $key = mb_substr($dsn, mb_strlen(self::DSN_SCHEME));
        return (new self($key));
    }
    /**
     * Construtor.
     * @param   string   $key   Chave de acesso à API.
     */
    public function __construct(string $key) {
        $this->_key = $key;
    }
    /**
     * Obtém a previsão do tempo de uma localidade.
     * @param   string   $place    Localidade, no formato "Cidade" ou "Cidade, País" ou "Cidade, Região, País".
     * @param   mixed    $default  Parâmetro não utilizado (necessário por motivos de herança).
     * @param   mixed    $options  Parâmetro não utilizado (necessário por motivos de herança).
     * @return  mixed    Array associativo contendo os dados meteorológicos.
     * @throws  \Exception      Se ocorreu um erro.
     */
    public function get(string $place, mixed $default=null, mixed $options=null) : mixed {
        // obtenção dos dados geográficos
        $query = http_build_query([
            'appid' => $this->_key,
            'limit' => 1,
            'q'     => $place,
        ]);
        $result = file_get_contents(self::GEO_API_URL . '?' . $query);
        $geo = json_decode($result, true);
        if (!isset($geo[0]['lat']) || !isset($geo[0]['lon'])) {
            throw new \Exception("Unable to get geographic data for '$place'.");
        }
        // obtenção dos dados meteorológicos
        $query = http_build_query([
            'appid' => $this->_key,
            'lat'   => $geo[0]['lat'],
            'lon'   => $geo[0]['lon'],
            'units' => 'metric',
        ]);
        $result = file_get_contents(self::DATA_API_URL . '?' . $query);
        $weather = json_decode($result, true);
        if (!isset($weather['main']['temp']))
            throw new \Exception("Unable to get weather data for '$place'.");
        return [
            'temperature' => $weather['main']['temp'],
            'perceived'   => $weather['main']['feels_like'],
            'pressure'    => $weather['main']['pressure'],
            'humidity'    => $weather['main']['humidity'],
        ];
    }
}
  • Linhas 21 a 27: método factory(), chamado pelo Temma quando a fonte de dados é criada. Esse método interpreta o DSN e chama o construtor.
  • Linhas 32 a 34: construtor, usado para criar uma instância do objeto diretamente.
  • Linhas 43 a 72: método get(), que sobrescreve o método get() do objeto pai \Temma\Base\Datasource. Esse método pode ser usado diretamente, mas também é chamado automaticamente quando lido como um array.
  • Linhas 45 a 54: conexão com a API de Geocodificação, para obter a latitude e a longitude da cidade solicitada.
  • Linhas 56 a 65: conexão com a API do OpenWeatherMap, para obter as informações meteorológicas a partir da latitude e da longitude obtidas anteriormente.

4Uso

Em um controlador, a fonte de dados chamada "meteo" no arquivo de configuração está disponível escrevendo $this->meteo. As informações meteorológicas podem ser obtidas chamando o método get() ou usando-a como um array:

// usando o método get()
$data = $this->meteo->get('Paris, FR');
// escrita como array
$data = $this->meteo['Paris, FR'];

Em outro objeto, você precisa usar o componente de injeção de dependências:

$data = $this->_loader->dataSources->meteo->get('Paris, FR');
$data = $this->_loader->dataSources->meteo['Paris, FR'];

Os dados obtidos são armazenados em um array associativo:


printf(" Temperatura medida: %f °C\n", $data['temperature']);
printf("   Sensação térmica: %f °C\n", $data['perceived']);
printf(" Pressão atmosférica: %f hPa\n", $data['pressure']);
printf("             Umidade: %f %%\n", $data['humidity']);