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 associatifwriteJson(): écrit les modifications dans le fichier JSONgetWordBySlug(): 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 motdeletePronunciation: suppression d'une prononciationaddWord: ajout d'un moteditPronunciation: 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 %}