LE MEILLEUR DU
TYPAGE FORT
La loi du plus fort
AGENDA
• Revue du typage en PHP
• Vue de l'intérieur des
méthodes
• Approche systémique
INTERVENANT
• Damien Seguy
• Directeur technique
@exakat
• Analyse statique
• Maison de retraite pour
elePHPants
TYPAGE EN PHP
RÉVISIONS
• Types scalaires
• Classes et interfaces
<?php
use FrameworkAccountsSociete as A;
class Foo {
   function bar(string $s, 
                Usager $u) : A {
     // Code
  }
}
?>
RÉVISIONS
• PHP 7.4 : propriétés
• Variables
• Annotations
<?php 
class Foo { 
   private User $user;
   function bar(){ 
     /** @var Account $a */
     $a = new Account();
  } 
} 
?>
RÉVISIONS
• bool, int, float, string, object
• iterable, callable, stringable (php 8.0)
• Interfaces et classes
• self, parent; static (php 8.0)
• array, void, ? - null, false
• mixed, resource, numeric (réservés)
CAS DE RIEN DUTOUT
• Void pour les valeurs de retour
• Attention, void est rien, mais pas le même rien que null…
• Pas de typeVOID pour les arguments
• Laisser vide
• PHP 8.0 : type null, (synonyme de ?)
• Les classes NullPattern sont de retour!
CLASSE NULL PATTERN
• Remplacement de NULL
• Avec la syntaxe objet
• Utilise la même interface
• Implemente toutes les méthodes
• Ne fait rien, retourne un élément neutre ('', 0, [], …)
• Mute en Exception Pattern à des fins de developement
SITUATION ACTUELLE
7 %
93 %
Avec typehint Sans Typehint
COUVERTURE DES PROJETS
0
25
50
75
100
QUELSTYPES?
0 %
17,5 %
35 %
52,5 %
70 %
object iterable callable int bool void string array scalar Classes
VUE DE LA MÉTHODE
• Meilleure validation
• Aux frontières de l'empire
• Cas limite
fonction ($args) : string
MEILLEUREVALIDATION
• is_array()
• is_int()
• === null
• (int), (string)…
<?php 
function bar($integer) { 
if (!is_int($integer)) {
     throw new TypeError();
   }
return $integer * 2;
}
function bar(int $integer) { 
return $integer * 2;
}
?>
MOINS DEVALIDATION
• is_a()
• is_subclass_of()
• instanceof
• === null
<?php  
function bar($user) {  
  if ($user === null) {
     throw new TypeError(); 
  }
  
  if (!$user instanceof User)) { 
     throw new TypeError(); 
  } 
   return $user->validate(); 
} 
?>
MOINS DEVALIDATION
• is_a()
• is_subclass_of()
• instanceof
• === null
<?php  
function bar(Usager $user) {  
   return $user->validate(); 
} 
?>
COHÉRENCE
• Syndrome strpos()
• Retours mono-
types
• Circuit de gestion
des erreurs
<?php  
function bar($user) {  
  if ($user === null) {
     return false;
  } else {
     return $user->age();
}
} 
?>
COHÉRENCE
• Syndrome strpos()
• Retours mono-
types
• Circuit de gestion
des erreurs
<?php  
function bar($user) : int {  
  if ($user === null) {
     throw new TypeError();
  } else {
     return $user->age();
}
} 
?>
COHÉRENCE
• Ne pas
repousser les
vérifications
<?php  
function bar($cmd) {  
     return shell_exec($cmd);
} 
?>
SELF CONSISTENCE
• Ne pas
repousser les
vérifications
<?php  
function bar($cmd) : string {  
     return shell_exec($cmd) ?? '';
} 
?>
DÉBUGAGE
• Emet une Fatal Error
• Assure le type de
données à un point du
code
• A retirer en production
• strict_types = 1
<?php 
foo("a");
foo(1);
foo(new x);
foo(array());
function foo(string $s) {
    echo $s;
}
class x {
    function __toString() 
{ return "a"; }
}
?>
Fatal error: Uncaught TypeError: Argument 1 passed to foo()
must be of the type string, array given
ADOPTION DESTYPES FORTS
• Défi : passer de quelques uns à presque tous
• Grosse tâche : 11000 cas à traiter en moyenne
• L'analyse statique aide considérablement
• Rapport de typage Exakat
• Psalm detecte automatiquement les types
DETECTION AUTOMATIQUE
• Cas d'usage
<?php 
  function bar($b) {
    return array_fill(0, $b, 'c');
  }
foo(array());
function foo($a) {
    return 1 + CONSTANT;
  }
?>
DETECTION AUTOMATIQUE
• Cas ambigus
<?php 
  function foo($b) {
    return array_fill(0, 10, $b);
  }
  function bar($a) {
    return shell_exec($a);
  }
?>
EVOLUTIONS D'UNTYPE
aucun
type
types
scalaires
types
classes
types
interfaces
non-null
PLUS DIFFICILE
• Utilisation de func_get_args()
• Cas des variadic
<?php    
declare(strict_types = 1);
function foo(string ...$a) { }
foo('a', 'b', 'c', 'd');
foo('a', 'b', 'c', 'd', 2);
?>
PLUS DIFFICILE
• array()
• Pas de génériques
• Attributs et annotations
<?php    
declare(strict_types = 1);
function foo(array $a) { }
foo(['a', 'b', 'c', 'd' ]);
foo(['a', 'b', 'c', null, 2]);
?>
ENCORE PLUS DUR
• __get()
• __set()
• Possible
• une seule
fois
<?php    
declare(strict_types = 1);
class x {
function __set($name,  $a) : array {
    return array();
}
}
$x = (new x);
$x->c = 3;
?>
TYPES MULTIPLES
• catch(
Exception |
TypeError $e)
<?php    
function foo($arg) {
  if (is_int($arg)) {
    return $arg + 1;
  } else {
    return $arg->getId();
  }
}
?>
UNIONS DETYPES (PHP 8.0)
<?php     
function foo(int|User|null $arg) { 
  if (is_int($arg)) { 
    return $arg + 1; 
  } elseif ($arg instanceof User) { 
    return $arg->getId(); 
  } else { 
    return NULL; 
  } 
} 
?>
INTERFACES, PAS CLASSES
<?php    
class C {} 
function foo(C $arg) { }
?>
INTERFACES, PAS CLASSES
<?php    
interface I {}
class C  extends I {} 
class C2  extends I {} 
interface I2 extends I {}
function foo(I $arg) { }
?>
INTERFACES INSUFFISANTES
<?php    
interface I {
   function foo() ;
}
class C extends I { /**/ } 
function foo(I $arg) { 
   $arg->foo($arg->p);
}
?>
CLASSES, PAS INTERFACES
<?php    
abstract class C { /**/ } 
function foo(C $arg) { 
   $arg->foo($arg->p);
}
?>
APPROCHE SYSTÉMIQUE
• Structuration du code
• Délégation de responsabilités
f ($a) : s
f ($a) : s
f ($a) : s
f ($a) : s
f ($a) : s
f ($a) : s
INTERACTIONS DE
FONCTIONS
<?php    
function M2($a) { 
$r = quelquechose();
    return $r;
}
?>
M1() M2() M3()
$a
$r
INTERACTIONS DE
FONCTIONS
<?php    
$m1 = M1();
$m2 = M2($m1);
$m3 = M3($m2);
echo $m3;
?>
M1() M2() M3()
INTERACTIONS DE
FONCTIONS
• Enchainement de fonctions
Relation 'dépend de'
M1() M2() M3()
INTERACTIONS DE
FONCTIONS (2)
M1() M2() M3()
INTERACTIONS DE
FONCTIONS (2)
• Conteneur, classe de configuration, bootstrap
M1()
M2() M3()
INTERACTIONS DE
FONCTIONS (3)
M1() M2() M3()
INTERACTIONS DE
FONCTIONS (3)
• Null Pattern
• Attentions aux cycles
de typages
M3()
M1()
M2()
INTERACTIONS DE
FONCTIONS (4)
• Typage impossible
M1() M2() M3()
INTERACTIONS DE
FONCTIONS (4)
M1() M2() M3()
INTERACTIONS DE
FONCTIONS (4)
• Boucles de typage
M1() M2() M3()
CAS RÉELS
• Avec des classes
• Grands nombres de classes
TYPAGE ET CLASSES
C1
M1()__construct()
FOSSILISATION
C2 extends C1
C1
M1()
M1()
• Signature de méthodes
• Nombre d'arguments
• Type
• Valeur par défaut
• Visibilité
FOSSILISATION
• Modifier C3
• Modifier C1
• Modifier C2, C4
• Modifier C5
• Modifier C6
• …
C2
C1
C3 C4
C5
C6
extends
extends
ORDRE DE CLASSES
C1
M1()__construct()
ORDRE DE CLASSES
C1
M1()
ORDRE DE CLASSES
• Tri topologique
des classes
C5C1
C3
C2
C1
M1()__construct()
ORDRE DE CLASSES
ORDRE DE CLASSES
UNE MÉTHODE :
UN GROUPE DETYPES
}
C1
MC1()__construct()
C2
MC2()__construct()
C3
MC3()__construct() MC4()
TYPAGE FORT EN PHP
• Meilleure maitrise du code
• Pratique pour les grandes applications, et les
applications historiques
• Impact significatif sur l'architecture du code
• Encore des innovations à venir (PHP 8.x)
OUTILS D'ANALYSE STATIQUE
• Static analysis
• Exakat
• Suggestions et vérifications de types
• Psalm
• PHPStan
SE PRÉPARER
• Faites vous aider par exakat ou un outil d'analyse statique
• Éviter func_get_args() et compagnie
• Types multiples dans une méthode (is_*)
• Attention à __get()/__set()
• Surveillez les méthodes souvent surchargées
• Eviter nullable, passer aux objets NullPattern
• Eviter les cycles de types
MERCI!
https://www.exakat.io/ - @exakat -
https://joind.in/talk/094e8

Meilleur du typage fort (AFUP Day, 2020)