Utiliser les traits en orienté objet PHP

Télécharger le PDF du cours


Livret PDF du cours PHP et MySQL
Dans cette nouvelle leçon, nous allons découvrir une fonctionnalité PHP qui va nous permettre de réutiliser du code dans des classes indépendantes et qu’on appelle « traits ».

 

Définition et intérêt des traits

Les traits sont apparus avec la version 5.4 du PHP. Très simplement, les traits correspondent à un mécanisme nous permettant de réutiliser des méthodes dans des classes indépendantes, repoussant ainsi les limites de l’héritage traditionnel.

En effet, rappelez-vous qu’en PHP une classe ne peut hériter que d’une seule classe mère.

Or, imaginons que nous devions définir la même opération au sein de plusieurs classes indépendantes, c’est-à-dire des classes qui ne partagent pas de fonctionnalité commune et pour lesquelles il n’est donc pas pertinent de créer une classe mère et de les faire étendre cette classe.

Dans ce cas-là, nous allons être obligé de réécrire le code correspondant à la méthode que ces classes ont en commun dans chacune des classes à moins justement d’utiliser les traits qui permettent à plusieurs classes d’utiliser une même méthode.

Par exemple, on peut imaginer qu’un site marchand possède deux classes Utilisateur et Produit qui vont être indépendantes mais qui vont posséder certaines méthodes en commun comme une méthode de comptage plusUn() par exemple.

Comme ces classes sont indépendantes et qu’on ne veut donc pas les faire hériter d’une même classe mère, on va être obligé de réécrire le code de notre méthode dans les deux classes si on n’utilise pas les traits :

<?php
    class Utilisateur{
        protected $user_name;
        protected $user_region;
        protected $prix_abo;
        protected $user_pass;
        protected $nombre;
        public const ABONNEMENT = 15;
        
        public function __construct($n, $p, $r, $nb){
            $this->user_name = $n;
            $this->user_pass = $p;
            $this->user_region = $r;
            $this->nombre = $nb;
        }
        public function __destruct(){
            //Du code à exécuter
        }
        
        public function getNom(){
            echo $this->user_name;
        }
 
        public function plusUn(){
            $this->nombre++;
            echo $this->nombre. '<br>';
            return $this;
        }
        
        //D'autres méthodes...
    }
?>

 

<?php
    class Produit{
        protected $nom;
        protected $nombre;
        
        public function __construct($n, $nb){
            $this->nom = $n;
            $this->nombre = $nb;
        }
        
        public function getNom(){
            echo $this->nom;
        }
 
        public function plusUn(){
            $this->nombre++;
            echo $this->nombre. '<br>';
            return $this;
        }
        
        //D'autres méthodes...
    }
?>

 

Si on tente ensuite d’utiliser notre méthode, cela va bien évidemment fonctionner mais il ne sera pas optimisé puisqu’on a dû réécrire notre méthode deux fois.

<!DOCTYPE html>
<html>
    <head>
        <title>Cours PHP & MySQL</title>
        <meta charset="utf-8">
        <link rel="stylesheet" href="cours.css">
    </head>
    
    <body>
        <h1>Titre principal</h1>
        <?php
            spl_autoload_register(function($classe){
                require 'classes/' .$classe. '.class.php';
            });
            
            $pierre = new Utilisateur('Pierre', 'abcdef', 'Sud', 5);
            $yeti = new Produit('Yeti', 10);
            
            $pierre->plusUn();
            $yeti->plusUn()->plusUn();
        ?>
        <p>Un paragraphe</p>
    </body>
</html>

 

Les problèmes d'héritage levés par l'héritage simple des classes en PHP objet

Dans le cas présent, ce n’est pas trop grave mais imaginez maintenant que nous ayons des dizaines de classes utilisant certaines mêmes méthodes… … Cela va faire beaucoup de code écrit pour rien et en cas de modification d’une méthode il faudra modifier chaque classe, ce qui est loin d’être optimal !

Pour optimiser notre code, il va être intéressant dans ce cas d’utiliser les traits. Un trait est semblable dans l’idée à une classe mère en ce sens qu’il sert à grouper des fonctionnalités qui vont être partagées par plusieurs classes mais, à la différence des classes, on ne va pas pouvoir instancier un trait.

De plus, vous devez bien comprendre que le mécanisme des traits est un ajout à l’héritage « traditionnel » en PHP et que les méthodes contenues dans les traits ne vont pas être « héritées » dans le même sens que ce qu’on a pu voir jusqu’à présent par les différentes classes.

 

Utiliser les traits en pratique

On va définir un trait de façon similaire à une classe, à la différence que nous allons utiliser le mot clef trait suivi du nom de notre trait.

Une bonne pratique consiste à utiliser un nouveau fichier pour chaque nouveau trait (on inclura ensuite les traits dans les classes qui en ont besoin). Ici, on peut déjà créer un trait qu’on va appeler Inventaire.

Dans ce trait, nous allons définir une propriété $nombre et une méthode plusUn().

Notez qu’on peut tout à fait inclure des propriétés dans nos traits. Il faut cependant faire bien attention à la visibilité de celles-ci et ne pas abuser de cela au risque d’avoir un code au final moins clair et plus faillible.

Notez également que si on définit une propriété dans un trait, alors on ne peut pas à nouveau définir une propriété de même nom dans une classe utilisant notre trait à moins que la propriété possède la même visibilité et la même valeur initiale.

<?php
    trait Inventaire{
        protected $nombre;
    
        public function plusUn(){
            $this->nombre++;
            echo $this->nombre. '<br>';
            return $this;
        }
    }
?>

 

Une fois notre trait défini, nous devons préciser une instruction use pour pouvoir l’utiliser dans les différentes classes qui vont en avoir besoin. On peut également en profiter pour supprimer la propriété $nombre et la méthode plusUn() de ces classes.

<?php
    class Utilisateur{
        use Inventaire;
        protected $user_name;
        protected $user_region;
        protected $prix_abo;
        protected $user_pass;
        public const ABONNEMENT = 15;
        
        public function __construct($n, $p, $r, $nb){
            $this->user_name = $n;
            $this->user_pass = $p;
            $this->user_region = $r;
            $this->nombre = $nb;
        }
        public function __destruct(){
            //Du code à exécuter
        }
        
        public function getNom(){
            echo $this->user_name;
        }
        
        //D'autres méthodes...
    }
?>

 

<?php
    class Produit{
        use Inventaire;
        protected $nom;
        
        public function __construct($n, $nb){
            $this->nom = $n;
            $this->nombre = $nb;
        }
        
        public function getNom(){
            echo $this->nom;
        }

        //D'autres méthodes...
    }
?>

 

Dans notre script principal, nous allons également devoir inclure notre trait pour l’utiliser. On va faire cela de manière « classique », c’est-à-dire en dehors de la fonction spl_autoload_register() ici car il faudrait la modifier pour qu’elle accepte un fichier en .trait.php.

Les classes vont maintenant pouvoir utiliser les propriétés et les méthodes définies dans le trait et notre code va à nouveau fonctionner.

Définition et utilisation des traits en PHP objet

Les traits nous permettent de dépasser les limites de l'héritage simple des classes et de réutiliser du code en PHP objet

Utiliser un trait ici nous a permis de pouvoir réutiliser notre méthode plusUn() et notre propriété $nombre dans des classes indépendantes.

 

Ordre de précédence (ordre de priorité)

Dans le cas où une classe hérite d’une méthode d’une classe mère, celle-ci va être écrasée par une méthode (du même nom, bien évidemment) provenant d’un trait.

En revanche, dans le cas où une classe définit elle-même une méthode, celle-ci sera prédominante par rapport à celle du trait.

Ainsi, une méthode issue de la classe elle-même sera prioritaire sur celle venant d’un trait qui sera elle-même prédominante par rapport à une méthode héritée d’une classe mère.

Pour illustrer cela, nous allons commencer par définir une méthode precedence() dans notre trait qui va renvoyer un texte.

<?php
    trait Inventaire{
        protected $nombre;
    
        public function plusUn(){
            $this->nombre++;
            echo $this->nombre. '<br>';
            return $this;
        }
        
        public function precedence(){
            echo 'Méthode issue du trait<br>';
        }
    }
?>

 

Ensuite, nous allons récupérer notre classe Utilisateur et notre classes étendue Admin créée précédemment et allons utiliser notre trait dans chacune d’entre elles.

Nous allons également redéfinir notre méthode precedence() dans la classe Utilisateur mais pas dans Admin.

Utilisation des traits et ordre de précédence en cas de surcharge d'une méthode en PHP objet

En cas de surcharge d'une méthode en PHP objet, la méthode du trait est prioritaire

Finalement, on instancie nos deux classes et on appelle notre méthode via nos deux objets créés pour observer le résultat.

<!DOCTYPE html>
<html>
    <head>
        <title>Cours PHP & MySQL</title>
        <meta charset="utf-8">
        <link rel="stylesheet" href="cours.css">
    </head>
    
    <body>
        <h1>Titre principal</h1>
        <?php
        require 'classes/inventaire.trait.php';
            spl_autoload_register(function($classe){
                require 'classes/' .$classe. '.class.php';
            });
            
            $pierre = new Utilisateur('Pierre', 'abcdef', 'Sud', 5);
            $mathilde = new Admin('Math', 123456, 'Nord', 2);
            
            $pierre->precedence();
            $mathilde->precedence();
        ?>
        <p>Un paragraphe</p>
    </body>
</html>

 

Exemple illustrant l'ordre de précédence entre trait et classe fille en cas de surcharge de méthode dans une classe en PHP objet

Comme on peut le constater, la méthode définie dans Utilisateur est celle utilisée par-dessus celle définie dans le trait pour l’objet issu de cette classe.

En revanche, pour l’objet issu de Admin, c’est la méthode du trait qui va être utilisée par-dessus celle de la classe mère.

 

Inclusion de plusieurs traits et gestion des conflits

L’un des intérêts principaux des traits est qu’on va pouvoir utiliser plusieurs traits différents dans une même classe.

Cependant, cela nous laisse à priori avec le même problème d’héritage multiple vu précédemment et qui interdisait à une classe d’hériter de plusieurs classes parents.

En effet, imaginez le cas où une classe utilise plusieurs traits qui définissent une méthode de même nom mais de façon différente. Quelle définition doit alors être choisie par la classe ?

Dans ce genre de cas, il va falloir utiliser l’opérateur insteadof (« plutôt que » ou « à la place de » en français) pour choisir explicitement quelle définition de la méthode doit être choisie.

Utiliser l’opérateur insteadof nous permet en fait d’exclure les définitions d’une méthode qui ne devront pas être utilisées.

Pour illustrer cela, on peut par exemple créer un nouveau trait qu’on appellera multiple et qui va redéfinir la méthode precedence().

<?php
    trait Multiple{
        public function precedence(){
            echo 'Méthode issue du trait multiple<br>';
        }
    }
?>

 

<?php
    trait Inventaire{
        protected $nombre;
    
        public function plusUn(){
            $this->nombre++;
            echo $this->nombre. '<br>';
            return $this;
        }
        
        public function precedence(){
            echo 'Méthode issue du trait inventaire<br>';
        }
    }
?>

 

On va ensuite inclure nos deux traits dans notre classe Produit et utiliser l’opérateur insteadof pour définir laquelle des eux définitions de notre méthode doit être utilisée avec la syntaxe suivante :

<?php
    class Produit{
        use Inventaire, Multiple{
            Multiple::precedence insteadof Inventaire;
        }
        protected $nom;
        
        public function __construct($n, $nb){
            $this->nom = $n;
            $this->nombre = $nb;
        }
        
        public function getNom(){
            echo $this->nom;
        }

        //D'autres méthodes...
    }
?>

 

Ici, on déclare qu’on souhaite utiliser la définition de notre méthode precedence() du trait Multiple plutôt que celle du trait Inventaire.

<!DOCTYPE html>
<html>
    <head>
        <title>Cours PHP & MySQL</title>
        <meta charset="utf-8">
        <link rel="stylesheet" href="cours.css">
    </head>
    
    <body>
        <h1>Titre principal</h1>
        <?php
            //On pense bien à inclure nos traits
            require 'classes/inventaire.trait.php';
            require 'classes/multiple.trait.php';
            spl_autoload_register(function($classe){
                require 'classes/' .$classe. '.class.php';
            });
            
            $yeti = new Produit('Yeti', 10);
            $yeti->precedence();
            
        ?>
        <p>Un paragraphe</p>
    </body>
</html>

 

Exemple d'utilisation de insteadof pour gérer les conflits entre traits en PHP objet

Notez que dans le cas (rare) où on souhaite utiliser les différentes définitions de méthodes portant le même nom définies dans différents traits, on peut également utiliser l’opérateur as qui permet d’utiliser une autre version d’une méthode de même nom en lui choisissant un nouveau nom temporaire.

Retenez ici bien que l’opérateur as ne renomme pas une méthode en soi mais permet simplement d’utiliser un autre nom pour une méthode juste pour le temps d’une inclusion et d’une exécution : le nom d’origine de la méthode n’est pas modifié.

<?php
    class Produit{
        use Inventaire, Multiple{
            Inventaire::precedence insteadof Multiple;
            Multiple::precedence as prece;
        }
        protected $nom;
        
        public function __construct($n, $nb){
            $this->nom = $n;
            $this->nombre = $nb;
        }
        
        public function getNom(){
            echo $this->nom;
        }

        //D'autres méthodes...
    }
?>

 

On utilise insteadof pour définir la précédence et as pour renommer une méthode d'un trait en PHP objet

Exemple d'utilisation des opérateurs as et insteadof pour gérer les conflits entre traits en PHP objet

Ici, on commence par définir quelle définition de precedence() doit être utilisée grâce à l’opérateur insteadof. Ensuite, on demande à également utiliser la méthode precedence() du trait Multiple en l’utilisant sous le nom prece() pour éviter les conflits.

Une nouvelle fois, il est très rare d’avoir à effectuer ce genre d’opérations mais il reste tout de même bon de les connaitre, ne serait-ce que pour les reconnaitre dans les codes d’autres développeurs.

Par ailleurs, vous devez savoir qu’on peut également définir une nouvelle visibilité pour une méthode lorsqu’on utilise l’opérateur as. Pour cela, il suffit de préciser la nouvelle visibilité juste avant le nom d’emprunt de la méthode.

 

Les traits composés (héritage de traits)

Vous devez finalement savoir que des traits peuvent eux-mêmes utiliser d’autres traits et hériter de tout ou d’une partie de ceux-ci.

Pour cela, on va à nouveau utiliser le mot clef use pour utiliser un trait dans un autre.

On pourrait ainsi par exemple utiliser notre trait Multiple dans notre trait Inventaire (même si cela ne fait pas beaucoup de sens dans le cas présent) en utilisant la syntaxe suivante :

Héritage des traits et traits composée en orienté objet PHP

Notez que selon le système utilisé et l’emplacement de vos différents traits, vous devrez peut être spécifier le chemin complet du trait ou utiliser une instruction require pour inclure le trait directement dans l’autre avant d’utiliser use.

Laisser un commentaire

© Pierre Giraud - Toute reproduction interdite - Mentions légales