Aperçu des sections

  • Présentation

    PHP permet de faire de la POO depuis longtemps. A la base, PHP n'intégrait pas d'aspect POO, mais les concepteurs du langage l'ont introduit il y un bon moment déjà (un petit peu avant la version 5, si ma mémoire est bonne).

    Ce cours contient un exercice réparti sur l'ensemble des différents chapitres (un mini site de e-commerce). On va illustrer ce cours en faisant ensemble la première partie de cet exercice, vous devrez réaliser la suite par vos propres moyens.

    Les concepts de la POO peuvent s'appréhender plus ou moins "seuls". Mais pour concrétiser leurs utilisation dans ce cours, il vous faudra être familiariser avec les autres concepts de PHP.

    Vous devez donc avoir vu ces cours avant de commencer la POO :


    Voir le cours générique sur la présentation de la programmation orientée objet.

    Créez un fichier sandbox pour tester les extraits de code présent dans ce cours.

    Sandbox :

    $d = new DateTime();
    echo $d->format('d/m/Y H:i:s');


  • Classes et instances de classes

    Voir le cours générique sur les classes et instances de classe.


    Voici comment définir les classes et les instances de classes en PHP (copiez le dans votre sandbox):

    class User {    //Création de la classe
    
     //contenu de la classe
    
    }
    
    $user = new User();  //Instanciation d'un objet


    C'est aussi simple que ça !

    Note importante : la plupart du temps, les classes sont utilisées par de nombreux fichiers dans votre programme. Pensez bien à créer des fichiers à part pour écrire vos classes, du type : class-test.inc.php. Vous n'aurez plus qu'a les appeler dans les fichiers ou elles seront nécessaires.


  • Attributs et propriétés

    Voir cours générique sur les attributs et propriétés.

    Sandbox :

    class User {    //Création de la classe
    
     public $id;
     public $pseudo;
     public $password;
     public $type;
    
    }
    
    $user = new User();  //Instanciation d'un objet
    
    var_dump($user); //Affichage de l'objet user
  • Constructeur

    Voir cours générique sur le constructeur.


    Sanbox :

    class User {    //Création de la classe
    
     public $id;
     public $pseudo;
     public $password;
     public $type;
    
     public function __construct($id, $pseudo, $password, $type){   
       //Contenu du constructeur
     }
     
    }
    
    $user = new User(1, 'pierro82', '1234', 'client');  //Instanciation d'un objet avec les bons paramètres
    var_dump($user);

    Un constructeur est forcément publique, étant donné qu'il est appelé depuis l'extérieur de la classe. Le double tiret avant "construct" signifie que c'est une méthode magique de PHP, qui encapsule une certaine logique.

    Dans cet exemple, on peut voir que l'affichage de l'objet affiche toujours des valeurs nulles pour les propriétés de l'objet user, alors que nous lui avons envoyées dans le constructeur. C'est normal, car on n'a pas encore défini la valeur des propriétés de l'objet courant. A ce stade, le constructeur reçoit des valeurs, mais on en fait rien pour le moment.

  • Le mot-clé this

    Voir cours générique sur le mot-clé this.

    Rien de particulier à ajouter en PHP sur le mot clé this, il fonctionne de la même manière que JS. On pourra donc accéder aux propriétés et méthodes d'un objet dans la classe via la variable $this (ne pas oublier le $ tout de même).

    Sandbox :

    class User {    //Création de la classe
    
     public $id;
     public $pseudo;
     public $password;
     public $type;
    
     public function __construct($id, $pseudo, $password, $type){
       //Contenu du constructeur
       $this->id = $id;
       $this->pseudo = $pseudo;
       $this->password = $password;
       $this->type = $type;
     }
    
    }
    
    $user = new User(1, 'pierro82', '1234', 'client');  //Instanciation d'un objet avec les bons paramètres
    print_r($user);


    Désormais, on peux voir que la variable $user (qui est un objet user), contient bien les bonnes informations (id, pseudo, password, type). Grâce à cet organisation du code, tous lnos utilisateurs auront ces propriétés la.

    Instanciez un nouvel utilisateur ($user2) à la suite du code :

    $user2 = new User(2, 'nico_las', '4567', 'admin');
    print_r($user2);

    Grâce à cet exemple, vous voyez bien que les deux utilisateurs utilisent la même classe, et ont les mêmes propriétés, mais ne partagent pas les même valeurs.

  • Méthodes

    Voir le cours générique sur les méthodes en POO.


    Sandbox :

    class User {    //Création de la classe
    
     public $id;
     public $pseudo;
     public $password;
     public $type;
    
     public function __construct($id, $pseudo, $password, $type){
       //Contenu du constructeur
       $this->id = $id;
       $this->pseudo = $pseudo;
       $this->password = $password;
       $this->type = $type;
     }
    
     public function affiche_info(){
       echo "<hr />";
       echo "id = $this->id<br />";
       echo "pseudo = $this->pseudo<br />";
       echo "password = $this->password<br />";
       echo "type = $this->type<br />";
       echo "<hr />";
     }
    
    }
    
    $user = new User(1, 'pierro82', '1234', 'client');  //Instanciation d'un objet avec les bons paramètres
    $user->affiche_info();
    
    $user2 = new User(2, 'nico_las', '4567', 'admin');  //Instanciation d'un objet avec les bons paramètres
    $user2->affiche_info();


    Avec cet exemple, on peux voir comment créer une méthode simple, puis l'appelez depuis un objet à l'extérieur de la classe.

  • Opérateurs de visibilité

    Voir cours générique

  • Attributs et méthodes statiques

    Voir cours générique

  • Accesseurs et mutateurs

    Voir cours générique pour les généralités sur les getters/setters.
    Important : pas de mots-clés get et set en PHP, mais possibilité d'utiliser les méthodes magiques pour faire quelque chose de pratique.


    Voici les trois possibilités pour mettre en place des getters et setters en PHP :

    1. créer des méthodes classique : getId() et setId($id). Obligation de passer par une méthode "spécialisée" depuis les objets.
    2. faire une seule méthode pour tous les attributs : set($attr, $value) et get($attr). Pas top car pas de logique métier par setters, à moins de mettre en place d'autres mécanismes...
    3. utiliser les méthodes magiques __get et __set, puis appeler automatiquement les méthodes getId() et setId(). Au top, car on a une utilisation normalisée des getters et setters.


    Voir les méthodes magiques __get() et __set() 
    Plugin : avec Visual Studio, vous pouvez installer ce plugin qui construit automatiquement les getters et setters d'un attribut.


    Voici un exemple avec les méthodes magiques :

    <?
    class Client
    {
    /*Attributs privés, donc protégés*/
    private $id;
    private $prenom;
    private $nom;


    /*Getters et setters*/
    public function getId():int
    {
    return $this->id;
    }

    public function setId(int $id): void
    {
    $this->id = $id;
    }

    public function getPrenom(): string
    {
    return $this->prenom;
    }

    public function setPrenom(string $prenom): void
    {
    $this->prenom = $prenom;
    }

    public function getNom(): string
    {
    return $this->nom;
    }

    public function setNom(string $nom): void
    {
    $this->nom = $nom;
    }


    /* Méthode magiques*/
    public function __get($attr){
    $method = 'get'.ucFirst($attr);
    return $this->$method();
    }
    public function __set($attr, $value){
    $method = 'set'.ucFirst($attr);
    $this->$method($value);
    }
    }


    $client = new Client();

    /* Ecriture facilité des setters*/
    $client->id = 3; /* exécutera setId(3)*/
    $client->prenom = 'pierre';
    $client->nom = 'durand';

    /* Ecriture facilité des getters*/
    echo "Le client $client->id s'appelle $client->prenom $client->nom.";

    Affichera : "Le client 3 s'appelle pierre durand."


    Note : les attributs de la classe doivent être privés ou protected, sinon aucun intérêt de faire des getters et des setters. C'est le principe même de l'encapsulation, très important en POO.
  • Héritage

    Voir cours générique

  • Méthodes magiques

  • Autoloading

  • Fetch_class et Fetch_into

    FETCH_CLASS et FETCH_INTO sont des fetch mode de PDO qui sont très utiles lorsque l'on travaille avec des classes. Ils permettent d'instancier une classe avec les valeurs reçue par PDO, ou bien d'hydrater un objet déjà existant.



    FETCH_CLASS

    <?

    try {
    $db = new PDO("mysql:host=localhost; dbname=agenceimmo;", 'root', '');
    } catch (PDOException $e) {
    echo "Erreur!: " . $e->getMessage() . "<br/>";
    die();
    }


    class Agent {

    private $id;
    private $prenom;
    private $nom;
    private $date_embauche;
    private $created_at;
    private $updated_at;

    public function __construct(){
    echo "Agent $this->id créé !<br>";
    }

    }

    $query = $db->query('SELECT * FROM agent');
    $agents = $query->fetchAll(PDO::FETCH_CLASS, 'Agent');

    echo "<pre>";
    print_r($agents);
    echo "</pre>";

    Ce code renverra ceci :


    Les données proviennent d'une table contenant des agents commerciaux.

    Remarque : le tableau agents contient bien des instances de classe Agent, et le FETCH_CLASS passe bien par le constructeur (affichage "agent xx créé !").



    FETCH_INTO

    try {
    $db = new PDO("mysql:host=localhost; dbname=agenceimmo;", 'root', '');
    } catch (PDOException $e) {
    echo "Erreur!: " . $e->getMessage() . "<br/>";
    die();
    }


    class Agent {

    public $id;
    public $prenom;
    public $nom;
    public $date_embauche;
    public $created_at;
    public $updated_at;

    public function __construct(){
    echo "Agent $this->id créé !<br>";
    }

    }

    $agent = new Agent();

    $query = $db->query('SELECT * FROM agent WHERE id = 3');
    $query->setFetchMode(PDO::FETCH_INTO, $agent);
    $agent = $query->fetch();

    echo "<pre>";
    print_r($agent);
    echo "</pre>";

    Ce code affichera :


    Remarque : le constructeur est appelé avant l'hydratation de l'objet avec les valeurs de la DB, et les attributs doivent être publics car ils sont modifiés "à la volée" depuis l'extérieur de classe, contrairement à FETCH_CLASS.


    Et voici une autre manière d'exploiter FETCH_INTO, en chargeant la données dans le constructeur, via $this :

    Note : dans cet exemple, j'utilise l'héritage afin de dissocier les problématiques de connexion à la DB de ma classe Agent (qui est censée s'occuper seulement des problématiques "agents"). L'attribut $db a une visibilité protected afin d'être accessible dans la classe Agent.
    <?

    class Database {

    protected $db;

    public function __construct(){
    try {
    $this->db = new PDO("mysql:host=localhost; dbname=agenceimmo;", 'root', '');
    } catch (PDOException $e) {
    echo "Erreur!: " . $e->getMessage() . "<br/>";
    die();
    }
    }
    }


    class Agent extends Database {

    public $id;
    public $prenom;
    public $nom;
    public $date_embauche;
    public $created_at;
    public $updated_at;

    public function __construct(int $id){
    if($id > 0){
    parent::__construct();
    $query = $this->db->query("SELECT * FROM agent WHERE id = $id");
    $query->setFetchMode(PDO::FETCH_INTO, $this);
    $query->fetch();
    echo "Agent $this->id créé !<br>";
    }
    }

    }

    $agent = new Agent(3);

    echo "<pre>";
    print_r($agent);
    echo "</pre>";

    Ce qui nous donne :


    Remarque : on envoie l'id au constructeur, et celui-ci génère automatiquement la requête pour charger les données depuis la DB.


  • Chaîner des méthodes entre elles

    Vous avez très certainement vu que l'on pouvait (pas toujours) appeler plusieurs méthodes les unes à la suite des autres lorsque l'on code en POO.



    En JavaScript cela se fait beaucoup, par exemple :


    document.getElementById('xxx').style.display = 'none';


    Ici, chaque point représente un nouvel appel de méthode, sans qu'on ait eu à écrire le code "ligne par ligne" (et heureusement).


    Le mécanisme est simple, il suffit que chaque méthode retourne l'objet courant (this) quand c'est possible. 

    Voici un exemple en PHP :

    <?
    try {
    $db = new PDO("mysql:host=localhost; dbname=agenceimmo;", 'root', '');
    } catch (PDOException $e) {
    echo "Erreur!: " . $e->getMessage() . "<br/>";
    die();
    }

    class Agent {

    private $id;
    private $prenom;
    private $nom;
    private $date_embauche;
    private $created_at;
    private $updated_at;

    public function __construct(){
    echo "Agent $this->id créé !<br>";
    }

    public function formatDate(){
    $this->date_embauche = date('d-m-Y', strtotime($this->date_embauche));
    return $this; /*la méthode retourne l'objet, ce qui permet de chainer avec la méthod d'après*/
    }

    public function afficheInfo(){
    echo "<pre>";
    print_r($this);
    echo "</pre>";
    }
    }

    $query = $db->query('SELECT * FROM agent WHERE id = 3');
    $query->setFetchMode(PDO::FETCH_CLASS, 'Agent');
    $agent = $query->fetch();

    /*Utilisation des méthodes par chaînage*/
    $agent->formatDate()->afficheInfo(); /*formatDate() retourne this, donc on peux appeler une autre émthode derrière*/


    Ce code affichera :


    La méthode formatDate() a bien été appelée !
  • Classes abstraites

    Définition

    • Une méthode abstraite ne comporte que sa signature.
    • Une classe contenant une méthode abstraite DOIT être abstraite.
    • Une classe abstraite ne peut pas être instanciée.


    Une classe enfant d'une classe abstraite doit :

    En définir les méthodes abstraites (Accessibilité identique ou supérieure. Signature identique.)

    ou bien :

    Se déclarer abstraite (et reporter l'implémentation aux enfants).


    Exemple
    abstract class User {
    abstract public function login(): void; /* Signée, mais pas définie */
    }


    class Client extends User {

    public function login(): void { /* définition */
    echo "connexion CLIENT<br>";
    }

    }

    class Agent extends User {

    public function login(): void { /* définition */
    echo "connexion AGENT<br>";
    }

    }

    $client = new Client();
    $agent = new Agent();

    $client->login();
    $agent->login();

    Ce code affichera :



    Note : si j'omets de définir la méthode login() dans une des classes enfant de User, alors une erreur fatale sera levée.

    Cas d'utilisation : quand je ne peux pas définir certaines méthodes, mais que je veux que ces méthodes soient définies chez les enfants d'une classe.
    Lien vers la doc sur les classes abstraites en PHP.
  • Interfaces

    • Une interface ne comporte que des méthodes abstraites publiques ou des constantes de classe publiques.
    • Une classe qui implémente une interface DOIT en implémenter la totalité des méthodes. Elle offre donc une garantie d'implémentation.
    • Une classe peut implémenter plusieurs interfaces.


    Exemple :

    /*Définition de ce que l'on attend*/
    interface userInterface
    {
    public function getId():int;
    public function getName():string;
    }

    interface agentInterface extends userInterface
    {
    public function getSalaire():int;
    const TYPE = 'A';
    }

    interface clientInterface extends userInterface
    {
    public function getBudget():int;
    const TYPE = 'C';
    }


    /*Implémentation*/
    class Agent implements agentInterface
    {
    public function getId():int
    {
    return $this->id;
    }
    public function getName():string
    {
    return $this->name;
    }
    public function getSalaire():int
    {
    return $this->salaire;
    }

    }

    class Client implements clientInterface
    {
    public function getId():int
    {
    return $this->id;
    }
    public function getName():string
    {
    return $this->name;
    }
    public function getBudget():int
    {
    return $this->name;
    }
    }

    $agent = new Agent();
    $agent->id = 3;
    $agent->name='jean';
    $agent->salaire = 2000;
    print_r($agent);
    echo "type : ".Agent::TYPE;


    echo "<br>---------<br>";


    $client = new Client();
    $client->id = 4;
    $client->name='pierre';
    $client->budget = 15000;
    print_r($client);
    echo "type : ".Client::TYPE;

    Lien vers la doc officielle sur les interfaces en PHP.

  • Le mot clé final

    Le mot clé final permet d'interdire l'héritage de classes ou de méthodes.


  • API de réflection

    L'API de réflection permet de faire une analyse d'une classe, fonction ou autres, sur elle-même (d'où le terme introspection). Cela veut dire que vous pouvez retirer des informations technique sur une classe, au lieu de l'utiliser de façon classique.


    Exemple avec la méthode getName().