|
|
# Conception
|
|
|
|
|
|
## Organisation
|
|
|
|
|
|
Notre projet est divisé en plusieurs parties:
|
|
|
|
|
|
- `src/API`, qui définit les classes qui implémentent les actions de l’api
|
|
|
- `src/App`, qui définit les contrôleurs et les vues de l’application web
|
|
|
- `src/Core`, définit les modèles, les classes métiers, les librairies internes (validation, http), les gateways, en somme, les élements logiques de l’application et les actions que l’ont peut faire avec.
|
|
|
- `sql`, définit la base de donnée utilisée, et éxécute les fichiers sql lorsque la base de donnée n’est pas initialisée.
|
|
|
- `profiles`, définit les profiles d’execution, voir `Documentation/how-to-dev.md` pour plus d’info
|
|
|
- `front` contient le code front-end react/typescript
|
|
|
- `ci` contient les scripts de déploiement et la définition du workflow d’intégration continue et de déploiement constant vers notre staging server ([maxou.dev/<branch>/public/](https://maxou.dev/IQBall/master/public)).
|
|
|
- `public` point d’entrée, avec :
|
|
|
- `public/index.php` point d’entrée pour la webapp
|
|
|
- `public/api/index.php` point d’entrée pour l’api.
|
|
|
|
|
|
## Backend
|
|
|
|
|
|
### Validation et résilience des erreurs
|
|
|
|
|
|
#### Motivation
|
|
|
|
|
|
Un controlleur a pour but de valider les données d'une requête avant de les manipuler.
|
|
|
|
|
|
Nous nous sommes rendu compte que la vérification des données d'une requête était redondante, même en factorisant les différents
|
|
|
types de validation, nous devions quand même explicitement vérifier la présence des champs utilisés dans la requête.
|
|
|
|
|
|
```php
|
|
|
public function doPostAction(array $form) {
|
|
|
$failures = [];
|
|
|
$req = new HttpRequest($form);
|
|
|
$email = $req['email'] ?? null;
|
|
|
if ($email == null) {
|
|
|
$failures[] = "Vous n'avez pas entré d'adresse email.";
|
|
|
return;
|
|
|
}
|
|
|
if (Validation::isEmail($email)) {
|
|
|
$failures[] = "Format d'adresse email invalide.";
|
|
|
}
|
|
|
if (Validation::isLenBetween($email, 6, 64))) {
|
|
|
$failures[] = "L'adresse email doit être d'une longueur comprise entre 6 et 64 charactères.";
|
|
|
}
|
|
|
|
|
|
if (!empty($failures)) {
|
|
|
return ViewHttpResponse::twig('error.html.twig', ['failures' => $failures]);
|
|
|
}
|
|
|
|
|
|
// traitement ...
|
|
|
}
|
|
|
```
|
|
|
|
|
|
Nous sommes obligés de tester à la main la présence des champs dans la requête, et nous avons une paire condition/erreur par type de validation,
|
|
|
ce qui, pour des requêtes avec plusieurs champs, peut vite devenir illisible si nous voulons être précis sur les erreurs.
|
|
|
|
|
|
Ici, une validation est une règle, un prédicat qui permet de valider une donnée sur un critère bien précis (injection html, adresse mail, longueur, etc.).
|
|
|
Bien souvent, lorsque le prédicat échoue, un message est ajouté à la liste des erreurs rencontrés, mais ce message est souvent le même, ce qui rajoute en plus
|
|
|
de la redondance sur les messages d'erreurs choisis lorsqu'une validation échoue.
|
|
|
|
|
|
#### Schéma
|
|
|
|
|
|
Toutes ces lignes de code pour définir que notre requête doit contenir un champ nommé `email`, dont la valeur est une adresse mail, d'une longueur comprise entre 6 et 64.
|
|
|
Nous avons donc trouvé l'idée de définir un système nous permettant de déclarer le schéma d'une requête,
|
|
|
et de reporter le plus d'erreurs possibles lorsqu'une requête ne valide pas le schéma :
|
|
|
|
|
|
```php
|
|
|
public function doPostAction(array $form): HttpResponse {
|
|
|
$failures = [];
|
|
|
$req = HttpRequest::from($form, $failures, [
|
|
|
'email' => [DefaultValidators::email(), DefaultValidators::isLenBetween(6, 64)]
|
|
|
]);
|
|
|
|
|
|
if (!empty($failures)) { //ou $req == null
|
|
|
return ViewHttpResponse::twig('error.html.twig', ['failures' => $failures])
|
|
|
}
|
|
|
|
|
|
// traitement ...
|
|
|
}
|
|
|
```
|
|
|
|
|
|
Ce système nous permet de faire de la programmation _déclarative_, nous disons à _quoi_ ressemble la forme de la requête que l'on souhaite,
|
|
|
plustot que de définir _comment_ réagir face à notre requête.
|
|
|
Ce système permet d'être beaucoup plus clair sur la forme attendue d'une requête, et de garder une lisibilité satisfaisante peu importe le nombre de
|
|
|
champs que celle-ci contient.
|
|
|
|
|
|
Nous pouvons ensuite emballer les erreurs de validation dans des `ValidationFail` et `FieldValidationFail`, ce qui permet ensuite d'obtenir
|
|
|
plus de précision sur une erreur, comme le nom du champ qui est invalidé, et qui permet ensuite à nos vues de pouvoir manipuler plus facilement
|
|
|
les erreurs et facilement entourer les champs invalides en rouge, ainsi que d'afficher toutes les erreurs que l'utilisateur a fait, d'un coup.
|
|
|
|
|
|
### HttpRequest, HttpResponse
|
|
|
|
|
|
Nous avons choisi d'encapsuler les requêtes et les réponses afin de faciliter leur manipulation.
|
|
|
Cela permet de mieux repérer les endroits du code qui manipulent une requête d'un simple tableau,
|
|
|
et de garder à un seul endroit la responsabilitée d'écrire le contenu de la requête vers client.
|
|
|
|
|
|
`src/App` définit une `ViewHttpResponse`, qui permet aux controlleurs de retourner la vue qu'ils ont choisit.
|
|
|
C'est ensuite à la classe `src/App/App` d'afficher la réponse.
|
|
|
|
|
|
### index.php
|
|
|
|
|
|
Il y a deux points d'entrés, celui de la WebApp (`public/index.php`), et celui de l'API (`public/api/index.php`).
|
|
|
Ces fichiers ont pour but de définir ce qui va être utilisé dans l'application, comme les routes, la base de donnée, les classes métiers utilisés,
|
|
|
comment gérer l'authentification dans le cadre de l'API, et une session dans le cadre de la web app.
|
|
|
|
|
|
L'index définit aussi quoi faire lorsque l'application retourne une réponse. Dans les implémentations actuelles, elle délègue simplement
|
|
|
l'affichage dans une classe (`IQBall\App\App` et `IQBall\API\API`).
|
|
|
|
|
|
### API
|
|
|
|
|
|
Nous avons définit une petite API (`src/Api`) pour nous permettre de faire des actions en arrière plan depuis le front end.
|
|
|
Par exemple, l'API permet de changer le nom d'une tactique, ou de sauvegarder son contenu.
|
|
|
C'est plus pratique de faire des requêtes en arrière-plan plustot que faire une requête directement à la partie WebApp, ce qui
|
|
|
aurait eu pour conséquences de recharger la page
|
|
|
|
|
|
## Frontend
|
|
|
|
|
|
### Utilisation de React
|
|
|
|
|
|
Notre application est une application de création et de visualisation de stratégies pour des match de basket.
|
|
|
L’éditeur devra comporter un terrain sur lequel il est possible de placer et bouger des pions, représentant les joueurs.
|
|
|
Une stratégie est un arbre composé de plusieurs étapes, une étape étant constituée d’un ensemble de joueurs et d’adversaires sur le terrain,
|
|
|
aillant leur position de départ, et leur position cible, avec des flèches représentant le type de mouvement (dribble, écran, etc) effectué.
|
|
|
les enfants d’une étape, sont d’autres étapes en fonction des cas de figures (si tel joueur fait tel mouvement, ou si tel joueur fait telle passe etc).
|
|
|
Pour rendre le tout agréable à utiliser, il faut que l’interface soit réactive : si l’on bouge un joueur,
|
|
|
il faut que les flèches qui y sont liés bougent aussi, il faut que les joueurs puissent bouger le long des flèches en mode visualiseur etc…
|
|
|
|
|
|
Le front-end de l’éditeur et du visualiseur étant assez ambitieux, et occupant une place importante du projet, nous avons décidés de l’effectuer en utilisant
|
|
|
le framework React qui rend simple le développement d’interfaces dynamiques, et d’utiliser typescript parce qu’ici on code bien et qu’on impose une type safety a notre code.
|