Programmation objet
Aperçu des sections
-
-
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'); -
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.
-
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
-
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.
-
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.
-
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.
-
Voir cours générique
-
Voir cours générique
-
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 :
- créer des méthodes classique : getId() et setId($id). Obligation de passer par une méthode "spécialisée" depuis les objets.
- 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...
- 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. -
Voir cours générique
-
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.Doc vers setFetchMode()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. -
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 ! -
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. -
- 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 permet d'interdire l'héritage de classes ou de méthodes.
Lien vers la doc officielle. -
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.
Doc API Reflection PHP.Exemple avec la méthode getName().