|Cas d'usage | faciliter et normaliser la gestion des données transmises à une API| |Niveau| Avancé|
Dans le cas d'un appel API récurrent (gestion d'un compte utilisateur sur un webservice client), il a été nécessaire de gérer la transmission d'un json légèrement différent selon les différents cas (mise à jour, création, suppression, statut du compte).
Le principal problème rencontré est que, selon la situation, les données à transmettre étaient issues soit d'un array, soit d'un objet User. D'autre part, certaines infos du json ne devaient être transmises que dans le cas d'une mise à jour (le mot de passe, par exemple, ne devait être transmis que s'il s'agissait d'une mise à jour, puisque les process de création de compte et de création de mot de passe sont distincts sur ce webservice).
En partant sur la solution la plus classique de stocker l'array de base dans une constante, il y aurait eu beaucoup de manipulation de l'array :
const BASE_ARRAY = [...];
private function prepareArray($user, $operation='create') {
$body = self::BASE_ARRAY;
$body['user']['userName'] = is_array($user) ? $user['email'] : $user->getEmail();
// à faire pour la plupart des cellules
switch ($operation) {
case 'create':
unset($body['user']['password'];
break;
}
// etc etc
}
Afin d'éviter les problèmes de lisibilité, en se basant sur l'article sourcé en bas de page, le json a été stocké dans un template api-response.json.twig
, le contenu lui étant transmis par des variables.
Le principe est simple :
Le template .json.twig est littéralement juste la structure json vide :
{# mon_module/templates/base-user-array.json.twig #}
{% set name = is_array(user) ? user.name : user.getName() %}
{
"user": {
"email":{ is_array(user) ? user.username : user.getEmail() }}",
"name": {
"familyName": "{{ is_array(user) ? user.family_name : user.getFamilyName() }}",
"givenName": "{{ givenName }}",
}
},
"title": "{{ is_array(user) ? user.title : user.getTitle }}",
"active": {{ status }},
{% if password is not empty%}
"password": "{{ password }}",
{% endif %}
"contact": [
{
"value": "{{ is_array(user) ? user.contact : user.getContact() }}",
"type": "{{ is_array(user) ? user.contactType : user.getContactType()'}}"
}
],
}
L'avantage est que l'on peut y traiter les données (user.name, user.getEmail) au lieu de le faire dans le php (les opérations sont strictement les même, mais ça allège la lecture de la classe), et que l'on peut lui passer des variables préparées utilisables telles qu'elles. On pourrait aussi préparer les données en amont via {% set %}, mais ça n'aurait pas forcément été un gros gain de lisibilité (à noter que le in_array() est une fonction twig custom rajoutée sur le projet).
Il s'agit la de php tout à fait classique, la partie intéressante étant le rendu du template :
private function prepareBody($user, $password = NULL) {
$options = [
'user' => $user,
'password' => $password,
'status' => $active ?? 'false',
'theme_hook_original' => 'not-applicable',
];
return twig_render_template(drupal_get_path('module', 'mon_module') . '/templates/idps-user.json.twig', $options);
}
Le principe est typique Drupal : on prépare un renderable array (avec le theme_hook "not-applicable"), puis à l'aide du moteur twig appelé via la fonction twig_render_engine()
, on génère le rendu en y passant :
public function createIdpsUser(User $user, $password) {
$options = [
'headers' => $this->getRequestHeaders(),
'body' => $this->prepareUser($user, $password),
];
try {
$response = $this->client->post($this->getUri(), $options);
}
catch (ClientException $e) {
// Do some magic here
}
}
Ceci ne fonctionnera que tant Twig est chargé (dés lors que le rendu est assuré par un thème), mais si vous deviez utiliser cette mécanique dans un contexte sans thème (envoi de mail via drush, tâche cron, tâche exécutée en front sur un site headless), il faut inclure manuellement le moteur Twig :
include_once \Drupal::root() . '/core/themes/engines/twig/twig.engine';
Source :