Typage générique en PHP : définition, conseils et exemples
Découvrez comment réaliser du typage générique en PHP : introduction et définition du concept, conseils et explications pas-à-pas de cas pratique.
Sommaire
L’ExpressionLanguage, ça vous parle ? C’est un composant de Symfony qui existe depuis la version 2.4, autant dire qu’il a de la bouteille. Il est très utile pour quelques cas d’utilisation bien précis : je vais vous montrer quand et comment l’utiliser.
L’ExpressionLanguage permet d’évaluer des expressions dynamiques en fonction d’un contexte. C'est particulièrement utile quand vous avez besoin d’interpréter des règles ou des instructions en utilisant un langage algorithmique léger. Ces dernières peuvent contenir des opérations mathématiques, des comparaisons, des conditions, des fonctions personnalisées, etc. Cela permet d’avoir une application plus flexible et configurable sans avoir à modifier le code source en cas de modification des règles.
L’ExpressionLanguage peut être utilisé dans plusieurs cas :
La liste n’est pas exhaustive, mais dans tous les cas il s’agit de la définition de règles dont l’algorithme doit être simple.
Prenons l’exemple du mapping de données qui va nous suivre pour la suite de l’article.
Pour votre projet, vous avez besoin de mapper les données récupérées dans une API pour les transformer pour remplir un document PDF.
Petite précision sur les deux fonctions permettant d’interpréter les règles :
evaluate()
permet d’évaluer sans mettre en cachecompile()
permet de compiler l’expression, et donc de la mettre en cacheOn utilisera que
evaluate()
pour notre besoin.
Personnellement, j’aime utiliser l’ExpressionLanguage avec des règles écrites en YAML.
Dans le constructeur de ma classe DataConverter
, je vais récupérer le mapping YAML que j’ai créé dans /config
et initialiser mon service ExpressionLanguage.
private readonly array $mapping;
private readonly ExpressionLanguage $expressionLanguage;
public function __construct()
{
$this->mapping = Yaml::parseFile('config/mapping/data.yaml');
$this->expressionLanguage = new ExpressionLanguage();
}
Dans ma fonction convert
, je vais parcourir l’ensemble de mon mapping, et pour chaque ligne je vais utiliser le composant pour convertir l’expression indiquée dans le YAML.
public function convert(array $data): array
{
$pdfData = [];
foreach ($this->mapping as $exportKey => $expression) {
$pdfData[$exportKey] = $this->expressionLanguage->evaluate($expression, ['data' => $data]);
}
return $pdfData;
}
Voici les données récupérées sur mon utilisateur :
$data = [ 'firstname' => 'Jean-Michel', 'lastname' => 'MAPPER', 'title' => 'mr', 'address' => [ 'number' => '5', 'street' => 'rue des données', 'country' => 'France', ], 'birthday' => '22/07/1987' ];
Voyons plus en détail le mapping pour un cas simple :
person: 'data["firstname"] ~ " " ~ data["lastname"]'
Ici, pour le champ “person”, nous voulons la concaténation du firstname et du lastname. Comme vous avez pu le remarquer dans la fonction evaluate()
, j’ai passé en second paramètre un tableau avec la clé data
contenant les données de mon tableau avec les informations de l'utilisateur. Il est donc admis dans mon mapping que le nom du tableau des données à exploiter est data
.
Vous pouvez remarquer que l’ensemble de l’expression est entre quotes et qu’on utilise les doubles quotes pour définir les clés du tableau data
, vous noterez également que pour indiquer l’espace, on utilise ~ “ “ ~
.
On pourrait avoir le cas de cases à cocher pour indiquer la civilité avec une case pour “M” et une pour “Mme”.
# Cache à cocher pour Monsieur/Madame /!\ ça dépendant de votre librairie pour remplir le PDF title_mr: '"mr" === data["title"] ? 1 : 0' title_ms: '"ms" === data["title"] ? 1 : 0'
On peut donc utiliser des opérations booléennes dans son expression : on part du principe que dans les données d’entrées, le title
est un string qu’il faut comparer pour indiquer 0 ou 1 pour le PDF.
On peut avoir besoin d’une expression un peu plus compliquée que les opérateurs proposés : on a besoin d’une “vraie” fonction. Pas de panique, cela s’ajoute ! Dans la documentation de Symfony, il y a un cas simple pour convertir le texte en minuscule. On va essayer d’aller un peu plus loin : j’ai la date d’anniversaire et je dois la séparer en plusieurs champs pour le PDF.
birthdate_day: 'getPartOfDate(data["birthday"], "day")' birthdate_month: 'getPartOfDate(data["birthday"], "month")' birthdate_year: 'getPartOfDate(data["birthday"], "year")'
Il faut rajouter la fonction dans votre __construct()
$this->expressionLanguage->register('getPartOfDate',
function ($date, $part) {
throw new \Exception('Not use this function for compile()');
},
function ($arguments, $date, $part) {
[$day, $month, $year] = explode('/', $date);
return $$part;
}
);
La fonction register()
permet d’insérer une nouvelle fonction utilisable dans les expressions. Elle est composée :
compile()
que je n’ai pas faite parce qu’elle n’a pas d’utilité dans mon cas)evaluate()
)Il est possible de rajouter autant de paramètres que l’on veut pour le besoin, mais sachez que $arguments
contient l’ensemble des données du context (contenant data
).
J’espère que vous appréhendez un peu mieux ce composant et que vous allez pouvoir imaginer les cas d’utilisation qui seront utiles pour vos projets ! Mais n’oubliez pas qu’il faut utiliser un composant pour de bonnes raisons, sinon vous risquez de générer de la dette technique inutilement.
Auteur(s)
Marianne Joseph-Géhannin
Architecte applicatif et lead développeuse PHP/Symfony 🦝
Vous souhaitez en savoir plus sur le sujet ?
Organisons un échange !
Notre équipe d'experts répond à toutes vos questions.
Nous contacterDécouvrez nos autres contenus dans le même thème
Découvrez comment réaliser du typage générique en PHP : introduction et définition du concept, conseils et explications pas-à-pas de cas pratique.
Découvrez un cas d'usage d'intégration d'un CRM avec une application e-commerce, en asynchrone, avec Hubspot et RabbitMQ
Pourquoi, quand et comment mettre en place une event driven architecture ? Exemples et conseils