Encapsular la API de OpenWeatherMap en una fuente de datos


1Introducción

En este tutorial, veremos cómo crear un objeto que se pueda usar como fuente de datos. Como ejemplo, vamos a crear un objeto que se conecta a la API del servicio OpenWeatherMap, lo que permite obtener el tiempo actual de cualquier ubicación (para ser precisos, la API de OpenWeatherMap recibe una latitud y una longitud como parámetros; por eso usaremos la API de Geocodificación para obtener las coordenadas geográficas de la ciudad solicitada).


2Configuración

En el archivo de configuración etc/temma.php, vamos a añadir la definición de una fuente de datos un tanto especial. Como no es una fuente de datos gestionada de forma nativa por Temma, vamos a anteponer al DSN el nombre del objeto que se va a usar (entre corchetes).

El DSN tendrá la forma openweather://API_KEY
siendo API_KEY la clave de API proporcionada por OpenWeatherMap.

Ejemplo de configuración:

<?php

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

3Código fuente

Aquí está el código de nuestra fuente de datos, que guardaremos en el archivo lib/OpenWeatherMap/Weather.php:

<?php

namespace OpenWeatherMap;

class Weather extends \Temma\Base\Datasource {
    /** Constante: prefijo del DSN. */
    const DSN_SCHEME = 'weather://';
    /** Constante: URL de la API de datos de OpenWeatherMap. */
    const DATA_API_URL = 'https://api.openweathermap.org/data/2.5/weather';
    /** Constante: URL de la API de geocodificación de OpenWeatherMap. */
    const GEO_API_URL = 'http://api.openweathermap.org/geo/1.0/direct';
    /** Clave de acceso a la API. */
    private $_key = null;

    /**
     * Fábrica. Crea una instancia del objeto a partir de un DSN.
     * @param   string   $dsn   Cadena de configuración.
     * @return  \OpenWeatherMap\Weather    El objeto instanciado.
     * @throws  \Exception   Si el DSN es incorrecto.
     */
    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));
    }
    /**
     * Constructor.
     * @param   string   $key   Clave de acceso a la API.
     */
    public function __construct(string $key) {
        $this->_key = $key;
    }
    /**
     * Obtiene el tiempo de una ubicación.
     * @param   string   $place    Ubicación, en el formato "Ciudad" o "Ciudad, País" o "Ciudad, Región, País".
     * @param   mixed    $default  Parámetro no utilizado (necesario por razones de herencia).
     * @param   mixed    $options  Parámetro no utilizado (necesario por razones de herencia).
     * @return  mixed    Array asociativo con los datos meteorológicos.
     * @throws  \Exception      Si se ha producido un error.
     */
    public function get(string $place, mixed $default=null, mixed $options=null) : mixed {
        // obtención de los datos 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'.");
        }
        // obtención de los datos 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'],
        ];
    }
}
  • Líneas 21 a 27: método factory(), llamado por Temma cuando se crea la fuente de datos. Este método interpreta el DSN y llama al constructor.
  • Líneas 32 a 34: constructor, usado para crear una instancia del objeto directamente.
  • Líneas 43 a 72: método get(), que sobrescribe el método get() del objeto padre \Temma\Base\Datasource. Este método se puede usar directamente, pero también se llama automáticamente cuando se lee como un array.
  • Líneas 45 a 54: conexión con la API de Geocodificación, para obtener la latitud y la longitud de la ciudad solicitada.
  • Líneas 56 a 65: conexión con la API de OpenWeatherMap, para obtener la información meteorológica a partir de la latitud y la longitud obtenidas previamente.

4Uso

En un controlador, la fuente de datos llamada "meteo" en el archivo de configuración está disponible escribiendo $this->meteo. La información meteorológica se puede obtener llamando al método get() o usándola como un array:

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

En otro objeto, debes usar el componente de inyección de dependencias:

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

Los datos obtenidos se almacenan en un array asociativo:


printf(" Temperatura medida: %f °C\n", $data['temperature']);
printf("  Sensación térmica: %f °C\n", $data['perceived']);
printf("Presión atmosférica: %f hPa\n", $data['pressure']);
printf("            Humedad: %f %%\n", $data['humidity']);