Encapsular la API de Deepl en una fuente de datos


1Introducción

En el tutorial anterior, vimos cómo crear una fuente de datos que encapsula la API del servicio OpenWeatherMap. Ahora vamos a crear otra fuente de datos, una que da acceso a la API del servicio de traducción Deepl, que se puede usar para traducir textos con facilidad.


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 deepl://API_KEY@DEFAULT_LANG
siendo API_KEY la clave de API proporcionada por Deepl, y DEFAULT_LANG el idioma predeterminado al que se traducirán los textos.

Ejemplo de configuración:

<?php

return [
    "application" => [
        "dataSources" => [
            "transl" => '[\Deepl\Translate]deepl://0123456789abcdef0123456789abcdef@EN'
        ]
    ]
];

3Código fuente

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

<?php

namespace Deepl;

class Translate extends \Temma\Base\Datasource {
    /** Constante: prefijo del DSN. */
    const DSN_SCHEME = 'deepl://';
    /** Constante: URL de la API de Deepl. */
    const API_URL = 'https://api-free.deepl.com/v2/translate';
    /** Clave de acceso a la API. */
    private string $_key;
    /** Idioma de traducción predeterminado. */
    private string $_lang;

    /**
     * Fábrica. Crea una instancia del objeto a partir de un DSN.
     * @param   string   $dsn   Cadena de configuración.
     * @return  \Deepl\Translate    El objeto instanciado.
     * @throws  \Exception   Si el DSN es incorrecto.
     */
    static public function factory(string $dsn) : \Deepl\Translate {
        // interpretación del DSN
        $parts = parse_url($dsn);
        $scheme = $parts['scheme'] ?? null;
        $key = $parts['user'] ?? null;
        $lang = $parts['host'] ?? null;
        // comprobaciones
        if ($scheme != self::DSN_SCHEME || !$key || !$lang) {
            throw new \Exception("Invalid Deepl DSN '$dsn'.");
        }
        return (new self($key, $lang));
    }
    /**
     * Constructor.
     * @param   string   $key    Clave de acceso a la API.
     * @param   string   $lang   Idioma de traducción predeterminado.
     */
    public function __construct(string $key, string $lang) {
        $this->_key = $key;
        $this->_lang = $lang;
    }
    /**
     * Traducción de un texto al idioma predeterminado.
     * @param   string   $text     Texto a traducir.
     * @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    El texto traducido.
     * @throws  \Exception   Si se ha producido un error.
     */
    public function get(string $text, mixed $default=null, mixed $options=null) : mixed {
        $this->translate($text, $this->_lang);
    }
    /**
     * Traduce un texto a un idioma específico.
     * @param   string   $text         Texto a traducir
     * @param   string   $lang         Idioma de traducción.
     * @param   ?string  $sourceLang   (opcional) Idioma del texto a traducir.
     *                                 Deepl lo detecta automáticamente por defecto.
     * @return  string   La traducción del texto, o una cadena vacía si el texto no pudo traducirse.
     * @throws  \Exception       Si se ha producido un error.
    */
    public function translate(string $text, string $lang, ?string $sourceLang=null) : string {
        // preparación de los datos a enviar
        $data = [
            'text'        => [$text],
            'target_lang' => $lang,
        ];
        if ($sourceLang) {
            $data['source_lang'] = $sourceLang;
        }
        $data = json_encode($data);

        // llamada a la API
        $curl = curl_init(self::API_URL);
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST');
        curl_setopt($curl, CURLOPT_POST, true);
        curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 3);
        curl_setopt($curl, CURLOPT_HTTPHEADER, [
            'Authorization: DeepL-Auth-Key ' . $this->_key,
            'Content-Type: application/json',
        ]);
        $result = curl_exec($curl);
        if ($result === false)
            throw new \Exception(curl_error($curl));
        curl_close($curl);

        // recuperación del resultado
        $result = json_decode($result, true);
        return ($result['translations'][0]['text'] ?? '');
    }
}
  • Líneas 21 a 32: 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 38 a 41: constructor, usado para crear una instancia del objeto directamente.
  • Líneas 50 a 52: método get(), que sobrescribe el del objeto padre \Temma\Base\Datasource, y llama al método translate() del objeto. Este método se puede usar directamente, pero también se llama automáticamente cuando el objeto se lee como un array.
  • Líneas 62 a 93: método translate(), que se conecta a la API de Deepl para realizar una traducción. Este método recibe como parámetros el texto a traducir y el idioma de traducción. También puede recibir el idioma de origen, si quieres traducir un texto en un idioma distinto del idioma predeterminado definido en la configuración.
  • Líneas 64 a 71: creación de la tabla de datos que se enviará a la API de Deepl, tras ser serializada en JSON.
  • Líneas 74 a 88: conexión con la API de Deepl, envío de los datos y recuperación de la respuesta.
  • Líneas 91 y 92: deserialización de los datos recuperados y retorno del método.

4Uso

En un controlador, la fuente de datos llamada "transl" en el archivo de configuración está disponible en los controladores escribiendo $this->transl. Un texto se puede traducir al idioma predeterminado definido en la configuración llamando al método get() o usándola como un array:

// usando el método get()
$englishText = $this->transl->get('Bonjour, comment allez-vous ?');
// escritura como array
$englishText = $this->transl['Bonjour, comment allez-vous ?'];

Para definir un idioma de traducción distinto, o para definir el idioma de origen, usa el método translate():

$germanText = $this->transl->translate('Hi, how are you?', 'DE', 'EN');

Fuera de los controladores, debes usar el componente de inyección de dependencias:

$englishText = $this->_loader->dataSources->transl->get('Bonjour, comment allez-vous ?');
$englishText = $this->_loader->dataSources->transl['Bonjour, comment allez-vous ?'];
$germanText = $this->_loader->dataSources->transl->translate('Hi, how are you?', 'DE', 'EN');