Encapsuler l'API OpenWeatherMap dans une source de données


1Présentation

Dans ce tutoriel, nous verrons comment créer un objet utilisable comme source de données. Pour l'exemple, nous allons faire un objet qui s'interface avec l'API du service OpenWeatherMap, permettant de récupérer la météo actuelle de n'importe quel lieu (pour être précis, l'API OpenWeatherMap prend en paramètre une latitude et une longitude ; on utilisera donc l'API Geocoding pour obtenir les coordonnées géographiques de la ville demandée).


2Configuration

Dans le fichier de configuration etc/temma.php, on va ajouter la définition d'une source de données un peu spéciale. Comme il ne s'agit pas d'une source de données gérée nativement par Temma, on va préfixer le DSN du nom de l'objet à utiliser (entre crochets).

Le DSN sera de la forme openweather://API_KEY
API_KEY étant la clé d'API fournie par OpenWeatherMap.

Exemple de configuration :

<?php

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

3Code source

Voici le code de notre source de données, que l'on enregistrera dans le fichier lib/OpenWeatherMap/Weather.php :

<?php

namespace OpenWeatherMap;

class Weather extends \Temma\Base\Datasource {
    /** Constante : préfixe du DSN. */
    const DSN_SCHEME = 'weather://';
    /** Constante : URL de l'API OpenWeatherMap data. */
    const DATA_API_URL = 'https://api.openweathermap.org/data/2.5/weather';
    /** Constante : URL de l'API OpenWeatherMap geocoding. */
    const GEO_API_URL = 'http://api.openweathermap.org/geo/1.0/direct';
    /** Clé d'accès à l'API. */
    private $_key = null;

    /**
     * Factory. Crée une instance de l'objet à partir d'un DSN.
     * @param   string   $dsn   Chaîne de configuration.
     * @return  \OpenWeatherMap\Weather    L'objet instancié.
     * @throws  \Exception   Si le DSN est incorrect.
     */
    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));
    }
    /**
     * Constructeur.
     * @param   string   $key   Clé d'API.
     */
    public function __construct(string $key) {
        $this->_key = $key;
    }
    /**
     * Recupère la météo d'un lieu.
     * @param   string   $place    Lieu, sous la forme "Ville" ou "Ville, Pays" ou "Ville, Région, Pays".
     * @param   mixed    $default  Paramètre inutilisé (nécessaire pour des raisons d'héritage).
     * @param   mixed    $options  Paramètre inutilisé (nécessaire pour des raisons d'héritage).
     * @return  mixed    Tableau associatif contenant les données météorologiques.
     * @throws  \Exception      Si une erreur est survenue.
     */
    public function get(string $place, mixed $default=null, mixed $options=null) : mixed {
        // récupération des données géographiques
        $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($get[0]['lon']) {
            throw new \Exception("Unable to get geographic data for '$place'.");
        }
        // récupération des données météorologiques
        $query = http_build_query([
            'appid' => $this->_key,
            'lat'   => $geo[0]['lat'],
            'lon'   => $get[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'],
            'ressentie'   => $weather['main']['feels_like'],
            'pression'    => $weather['main']['pressure'],
            'humidite'    => $weather['main']['humidity'],
        ];
    }
}
  • Lignes 21 à 27 : Méthode factory(), appelée par Temma lors de la création de la source de données. Cette méthode interprète le DSN et appelle le constructeur.
  • Lignes 32 à 34 : Constructeur, utilisable pour créer une instance de l'objet directement.
  • Lignes 43 à 72 : Méthode get(), qui surcharge celle de l'objet parent \Temma\Base\Datasource. Cette méthode est utilisable directement, mais elle est aussi appelée automatiquement lors d'une lecture de type tableau.
  • Lignes 45 à 54 : Connexion à l'API Geocoding, pour obtenir la latitude et la longitude de la ville demandée.
  • Lignes 56 à 65 : Connexion à l'API OpenWeatherMap, pour obtenir les informations météorologiques à partir de la latitude et la longitude récupérées précédemment.

4Utilisation

Dans un contrôleur, la source de données qui a été nommée "meteo" dans le fichier de configuration est disponible en écrivant $this->meteo. La récupération des informations météorologiques peut s'écrire en appelant la méthode get() ou avec l'écriture de type tableau :

// utilisation de la méthode get()
$data = $this->meteo->get('Paris, FR');
// écriture de type tableau
$data = $this->meteo['Paris, FR'];

Dans un autre objet, il faut passer par le composant d'injection de dépendances :

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

Les données récupérées sont stockées dans un tableau associatif :

printf("   Température mesurée : %f °C\n", $data['temperature']);
printf(" Température ressentie : %f °C\n", $data['ressentie']);
printf("Pression atmosphérique : %f hPa\n", $data['pression']);
printf("              Humidité : %f %%\n", $data['humidite']);