2 : Répondre aux incidents et aux demandes d'assistance et d'évolution

  • 1.2.1 Traiter des demandes concernant les services réseau et système, applicatifs

3 : Développer la présence en ligne de l'organisation

  • 1.3.1 Participation à l'évolution d'un site web

1.5 : Mise à disposition des utilisateurs d'un service informatique

  • 1.5.2 Déployer un service

Contexte

Le back-office est une interface permettant de gérer un fichier JSON contenant des corrections de prononciation pour certains mots. Il permet d'ajouter, de modifier et de supprimer des prononciations selon la langue et l'API utilisée ainsi que la possibilité d'écouter la prononciation selon la voix choisie


Service PatternService

Afin d'améliorer la maintenabilité du code, un service Symfony centralise toute la logique métier concernant les traitement du json . Il contient par exemple :

  • getJson() : récupère le contenu du JSON sous forme de tableau associatif
  • writeJson() : écrit les modifications dans le fichier JSON
  • getWordBySlug() : retrouve un mot à partir de son slug

Système de slug

Un slug unique est généré pour chaque mot grâce à la bibliothèque cocur/slugify. Cette évolution a résolu un problème de routage : certains mots contenaient des caractères spéciaux (. ou /) interprétés incorrectement par Symfony dans les paramètres d'URL.


Routes du contrôleur

Le contrôleur expose les routes suivantes, toutes basées sur le service :

  • deleteWord : suppression d'un mot
  • deletePronunciation : suppression d'une prononciation
  • addWord : ajout d'un mot
  • editPronunciation : modification d'une prononciation

Gestion des erreurs

Des blocs try/catch ont été ajoutés dans les contrôleurs. Les exceptions sont interceptées et affichées à l'utilisateur sous forme de messages flash, évitant l'affichage de la page d'erreur par défaut de Symfony et améliorant l'expérience utilisateur.


Interface

Formulaire de modification

Formulaire d'ajout de mot

Formulaire pour ecouter l'audio

Route getAudio — lecture audio dans le backoffice :

#[Route(
    "/{lang}/{api}/{word}/{voice}",
    name: "getAudio",
    requirements: ['word' => ".+"]
)]
public function getAudio(
    string $lang,
    string $api,
    string $word,
    string $voice,
): Response {
    $json          = $this->patternService->getJson();
    $pronunciation = $json[$word][$api] ?? "";

    try {
        $apiClass    = $this->ttsSelectApi->selectApi($api);
        $configVoice = $apiClass->getVoice($voice, $lang);

        $audioContent = $apiClass->getOutputAudio(
            $pronunciation,
            $configVoice
        );
    } catch (\Exception $e) {
        return new Response($e->getMessage());
    }

    return new Response($audioContent, Response::HTTP_OK, [
        'Content-Type' => "audio/wav",
    ]);
}

Route playAudio — page de lecture avec sélection de voix :

#[Route(
    "/playaudio/{lang}/{api}/{slug}",
    name: "playAudio",
    requirements: ['slug' => ".+"]
)]
public function playAudio(
    Request $request,
    string $lang,
    string $api,
    string $slug,
): Response {
    try {
        $word          = $this->patternService->getWordBySlug($slug);
        $json          = $this->patternService->getJson();
        $pronunciation = $json[$word][$lang][$api] ?? "";

        $apiClass = $this->ttsSelectApi->selectApi($api);
        $voices   = $apiClass->getVoices();
    } catch (
        \InvalidArgumentException
        | \RuntimeException
        | \Exception $e
    ) {
        $this->addFlash('danger', $e->getMessage());
    }

    $form = $this->createForm(PlayAudioType::class, null, [
        'voices' => $voices,
    ]);
    $form->handleRequest($request);

    $selectedVoice = null;

    if ($form->isSubmitted()) {
        if ($form->isValid()) {
            $selectedVoice = $form->get('voice')->getData();
        }
    }

    return $this->render("backoffice/playAudio.html.twig", [
        'word'          => $word,
        'lang'          => $lang,
        'api'           => $api,
        'pronunciation' => $pronunciation,
        'form'          => $form->createView(),
        'selectedVoice' => $selectedVoice,
    ]);
}
{% extends "base.html.twig" %}

{% block body %}
<div class="container-fluid mt-5 bg-secondary p-4 rounded">
    <div class="card mb-4 p-3 w-100 border border-secondary rounded">
        <h1 class="mb-4">Mot : <strong>{{ word }}</strong></h1>
        <h2><strong>Prononciation : {{ pronunciation|raw }}</strong></h2>

        {{ form_start(form) }}
            {{ form_row(form.voice) }}
            {% if selectedVoice %}
            <button type="submit" class="btn btn-primary mt-2">Recharger l'audio</button>
                <div class="col-md-6 mt-4">
                    <div class="card border-success mb-3">
                        <div class="card-body">
                            <h5 class="card-title text-success">Audio</h5>
                            <audio class="w-100" controls>
                                <source src="{{ path('getAudio', {
                                    'word': word,
                                    'lang': lang,
                                    'api': api,
                                    'voice': selectedVoice
                                }) }}" type="audio/wav">
                            </audio>
                        </div>
                    </div>
                </div>
            {% else %}
            <button type="submit" class="btn btn-primary mt-2">Charger l'audio</button>
            {% endif %}
        {{ form_end(form) }}
    </div>
</div>
{% endblock %}