← PHPStan et ingénierie logicielle sécurisée

Module 3 / 8 · 90 min

03. Types riches et validation des frontières

Utiliser les formes de données sans prétendre valider le réseau par PHPDoc.

Votre progression apparaîtra ici lorsque JavaScript est actif.

La progression et le meilleur score sont enregistrés uniquement dans ce navigateur. Aucun compte n’est nécessaire ; effacer les données du navigateur les supprime. Les durées de modules sont indicatives et incluent les exercices.

Comprendre le mécanisme

Les PHPDocs peuvent exprimer unions, listes, tableaux de forme précise et contraintes utiles à l’analyse. Ces annotations décrivent un contrat ; elles ne transforment pas un corps HTTP en donnée valide. Écrire qu’un tableau contient amount positif ne rend pas vraie une entrée négative. Le code exécuté doit refuser les types, clés et valeurs non conformes avant de produire l’objet du domaine.

Raisonner et vérifier

Séparez parseur, validateur et modèle métier. Une garde valide d’abord la structure puis les invariants : entier n’implique pas montant autorisé, chaîne n’implique pas destination sûre. Préférez des DTO et objets de valeur pour éviter de disperser des tableaux aux clés approximatives. Les erreurs d’entrée doivent être testées sans convertir silencieusement toute valeur en type attendu.

Décrire puis établir

Un PHPDoc décrit un contrat. Une annotation n’exécute pas une garde d’entrée. Le commentaire array{amount: positive-int} n’exécute aucune vérification du JSON. Le code doit contrôler structure, type natif et bornes avant de transmettre la valeur à une fonction qui suppose ce contrat.

Erreur fréquente à comprendre

Un PHPDoc valide automatiquement le JSON réseau.

Une annotation n’exécute pas une garde d’entrée.

Formes de tableau

Une forme de tableau précise ses clés attendues. Les données externes doivent établir ce contrat. Une array shape nomme les clés obligatoires et optionnelles. Testez clé manquante, clé présente avec null et valeur de mauvais type : ces situations ont des contrats distincts qu’un simple accès au tableau masque.

Erreur fréquente à comprendre

Toute array shape garantit la présence réelle de ses clés sans contrôle.

Les données externes doivent établir ce contrat.

Unions

Une union indique plusieurs types possibles. Une opération doit être compatible avec les possibilités restantes. Un retour User|null demande de gérer l’absence avant d’appeler une méthode utilisateur. Les unions documentent plusieurs chemins ; elles ne signifient pas que l’appelant peut choisir arbitrairement celui qui l’arrange.

Erreur fréquente à comprendre

Une union autorise toute méthode de l’un de ses membres.

Une opération doit être compatible avec les possibilités restantes.

Type et domaine

Une garde peut vérifier type et valeur. La positivité est un invariant supplémentaire. is_int puis une comparaison positive rejettent la chaîne '12'. Ajoutez un plafond et une règle de devise pour un montant ; les types raffinés ne doivent pas dépasser ce que la garde établit.

Erreur fréquente à comprendre

is_int garantit que tout montant est positif.

La positivité est un invariant supplémentaire.

Conversions destructrices

Un cast peut masquer une entrée incorrecte. La conversion ne remplace pas une politique d’acceptation. Caster 'inconnu' en entier peut produire zéro et perdre l’erreur initiale. Définissez les représentations admises puis convertissez après validation, en vérifiant dépassement et conservation de la précision.

Erreur fréquente à comprendre

Caster toute entrée en int vérifie la grammaire métier.

La conversion ne remplace pas une politique d’acceptation.

DTO validé

Un DTO peut structurer des données validées. La permission demeure une décision distincte. Construisez un objet d’entrée seulement après validation, ou faites refuser son constructeur en cas d’invariant violé. Un nom comme ValidPayment ne rend pas sûr un constructeur qui accepte toute donnée sans contrôle.

Erreur fréquente à comprendre

Construire un DTO prouve automatiquement l’autorisation.

La permission demeure une décision distincte.

Plafond métier

Un entier positif peut dépasser un plafond métier. Les limites du domaine peuvent être plus étroites. Un positive-int de plusieurs milliards peut être incompatible avec un remboursement de dix euros. Vérifiez solde, autorisation et limites en contexte ; la propriété arithmétique positive reste une seule condition parmi plusieurs.

Erreur fréquente à comprendre

positive-int suffit à autoriser tout montant.

Les limites du domaine peuvent être plus étroites.

Matrice de refus

Les erreurs de frontière doivent être testées. Les absences et mauvais types doivent être refusés selon le contrat. Envoyez tableaux, flottants, chaînes numériques, négatifs et valeurs extrêmes. Vérifiez l’erreur publique et l’absence d’écriture, pas seulement que PHPStan continue d’inférer int à la sortie nominale.

Erreur fréquente à comprendre

Seul un exemple nominal prouve toute validation.

Les absences et mauvais types doivent être refusés selon le contrat.

Parsing distinct

Le parsing et la validation sont des étapes différentes. Structure et invariants peuvent encore être invalides. json_decode peut produire un tableau valide syntaxiquement qui ne contient pas les champs requis. Une exception de parsing et une violation de schéma doivent être traitées comme deux causes différentes.

Erreur fréquente à comprendre

Un JSON syntaxiquement correct est toujours conforme au domaine.

Structure et invariants peuvent encore être invalides.

Politique de conversion

Un contrat doit définir les conversions acceptées. L’API choisit explicitement sa grammaire d’entrée. Une API peut accepter une chaîne décimale par contrat explicite ; une autre exige un entier JSON. Documentez ce choix et évitez qu’un cast opportuniste élargisse silencieusement les entrées acceptées.

Erreur fréquente à comprendre

La chaîne numérique doit toujours être acceptée comme entier dans toute API.

L’API choisit explicitement sa grammaire d’entrée.

Atelier guidé : lire, expliquer et vérifier le code

<?php
declare(strict_types=1);
function accept(mixed $value): bool {
    return is_int($value) && $value > 0;
}
echo accept('12') ? 'accepted' : 'rejected';

Évaluez is_int avant la comparaison numérique : la valeur fournie est une chaîne et échoue donc à la première condition. Comparez ensuite avec un entier positif, zéro et un tableau. Pour transformer cet exemple en frontière de paiement, ajoutez un plafond métier et les contrôles d’autorisation nécessaires.

Le résultat affiché est un libellé. La fonction est syntaxiquement valide. La garde demande un int ; la valeur reçue est une string. Le contrat ne prévoit pas ici de conversion de chaîne.

Travail pratique : Envoyez 12, "12", 0, -1 et null à la frontière de laboratoire. Comparez un cast aveugle avec la garde.

Critères de correction : Seul l’entier positif respecte ce contrat strict. Vérifiez ensuite la limite métier et la permission séparément. Une API acceptant aussi une chaîne doit définir explicitement sa grammaire de conversion.

Exemple commenté

<?php
declare(strict_types=1);
function positiveAmount(mixed $value): int
{
    if (!is_int($value) || $value <= 0) {
        throw new InvalidArgumentException('Montant invalide');
    }
    return $value;
}

La garde vérifie type et valeur ; elle ne décide pas si un utilisateur peut payer ce montant.

Exercice de mise en pratique

Envoyez 12, "12", 0, -1 et null à la frontière de laboratoire. Comparez un cast aveugle avec la garde.

Voir une proposition de correction

Seul l’entier positif respecte ce contrat strict. Vérifiez ensuite la limite métier et la permission séparément. Une API acceptant aussi une chaîne doit définir explicitement sa grammaire de conversion.

Points de vérification

  • Annotations honnêtes.
  • Gardes exécutées.
  • Invariants distincts des types.

Contrôle de connaissances

Cours associé : 03. Types riches et validation des frontières ↗
Un PHPDoc array{amount: positive-int} valide-t-il un JSON reçu ?

Documentation de référence ↗

Validation du module

101 questions, objectif pédagogique de 75 %. Durée conseillée de 150 minutes, sans limite imposée. Les questions et les choix sont mélangés ; le corrigé apparaît après validation. Le contrôle rapide de la leçon et l’examen final du cours restent des exercices distincts.

Commencer ou reprendre les 101 questions →