Quand on instancie une classe avec l'opérateur `new` un espace mémoire est attribué à l'instance de l'objet créé. Cet espace mémoire est construit par une méthode particulière qui s'appelle un constructeur.

Dans PHP, il existe également un destructeur qui est une méthode particulière appelée juste avant que l'objet ne soit supprimé de la mémoire.

### Constructeur

Un constructeur sert essentiellement à initialiser les valeurs des attributs d'un nouvel objet lors de l'instanciation de sa classe.

Un constructeur peut être implicite ou explicite. Bien que dans 99% des classes le constructeur soit explicite, nous commencerons par rapidement voir ce qu'est un constructeur implicite.

#### Constructeur implicite

Un constructeur implicite est un constructeur qui n'est pas défini par le concepteur de la classe. Dans le code de la classe on ne trouvera aucune méthode particuliére qui serait invoquée lors de l'instanciation.

Dans le cas d'un constructeur implicite, si les attributs n'ont pas de valeur assignée lors de leur définition, ils ont par défaut la valeur `NULL`.

Si la classe a un constructeur implicite, les parenthèses qui suivent son nom lors de son instanciation sont facultatives : `$objet = new Classe` est équivalent à `$objet = new Classe()`.

Exemple : constructeur implicite
<?php ob_start(); require('bib_fonctions.php'); class Guitare { private $modele, $prix, $cordes = 6; public function getModele() { return $this->modele; } public function getCordes() { return $this->cordes; } public function setModele($val) { $this->modele = $val; } } // Fin de la classe Guitare htmlDebut('Constructeur implicite'); $guitare = new Guitare; $x = $guitare->getModele(); htmlInfo('L\'attribut modèle est du type : '.gettype($x)); htmlInfo("La valeur de modèle est : $x"); $guitare->setModele('Gibson Hummingbird'); $x = $guitare->getModele(); htmlInfo('L\'attribut modèle est du type : '.gettype($x)); htmlInfo("La valeur de modèle est : $x"); $y = $guitare->getCordes(); htmlInfo('L\'attribut nb cordes est du type : '.gettype($y)); htmlInfo("La valeur de nb cordes est : $y"); htmlFin(); ?>

#### Constructeur explicite

Un constructeur explicite est une méthode magique qui s'appelle `__construct()`. Si cette méthode `__construct` est définie explicitement dans le code de la classe, elle sera invoquée automatiquement par PHP lors de l'instanciation de la classe avec l'opérateur `new`.

La méthode constrcuteur `__construct()` accepte généralement un ou plusieurs paramètres qui vont servir à initialiser la valeur d'attributs de la classe pour caractériser l'instance du nouvel objet.

Si le constructeur explicite n'a pas de paramètre, les parenthèses qui suivent le nom de la classe lors de son instanciation sont facultatives. On peut écrire `$objet = new Classe`.

Si le constructeur explicite accepte des paramètres, les parenthèses qui suivent le nom de la classe lors de son instanciation sont obligatoires. Il FAUT écrire `$objet = new Classe()`.

Avec PHP, il n'est pas possible (comme en Java par exemple) de surcharger un constructeur.

Exemple : constructeur explicite
<?php ob_start(); require('bib_fonctions.php'); class Guitare { private $modele, $prix, $cordes, $remise; // Constructeur public function __construct($p1, $p2, $p3) { $this->modele = $p1; $this->prix = $p2; $this->cordes = $p3; } public function getModele() { return $this->modele; } public function getCordes() { return $this->cordes; } public function getRemise() { return $this->remise; } } // Fin de la classe Guitare htmlDebut('Constructeur explicite'); $guitare = new Guitare('Gibson Hummingbird', 2033, 6); htmlInfo('Modèle : '.$guitare->getModele()); htmlInfo('Nombre de cordes : '.$guitare->getCordes()); // L'attribut remise n'est pas initialisé // Il est donc de type NULL $x = $guitare->getRemise(); htmlInfo('L\'attribut remise est du type : '.gettype($x)); htmlFin(); ?>

#### Validation des paramètres

Si on passe des paramètres au constructeur, ceux-ci doivent être validés avant d'être affectés comme valeur à des attributs.

Si des setters sont implémentés pour les attributs, ils doivent être appelés depuis le constructeur pour effectuer la mise à jour de la valeur de l'attribut.

S'il n'y a pas de setter pour les attributs, la validation doit être faite dans le constructeur.

L'exemple suivant est une classe Livre avec des attributs titre, auteur, nombre de pages, prix et catégorie. Les valeurs de ces attributs sont validées dans le constructeur, sauf pour le prix qui a un setter.

Exemple : validation des paramètres du constructeur
<?php ob_start(); require('bib_fonctions.php'); class Guitare { private $modele, $prix, $cordes, $remise; public function __construct($p1, $p2, $p3){ $p1 = trim($p1); if ($p1 == '') exit('Paramètre modèle invalide'); $this->modele = $p1; $ok = array(4, 6, 12); if (!estEntier($p3) || !in_array($p3, $ok)) { exit ('Paramètre nombre de cordes invalide'); } $this->cordes = $p3; $this->setPrix($p2); $this->remise = 0; } public function setPrix($val) { if (!is_numeric($val) || $val < 0) { exit ('Valeur de prix invalide'); } $this->prix = $val; } public function decrire() { echo '<h4>Instance de la classe ', __CLASS__, '</h4>', $this->modele, '<br>', $this->cordes, ' cordes<br>', $this->prix, ' &euro;'; } } // Fin de la classe Guitare htmlDebut('Constructeur explicite'); htmlInfo('Constructeur avec paramètres valides'); $ibanez = new Guitare('Ibanez PCBE12-OPN', 229, 4); $ibanez->decrire(); htmlInfo('Constructeur avec paramètre invalide'); $epiphone = new Guitare('Epiphone EJ-200CE', 334, 24); $epiphone->decrire(); htmlFin(); ?>

#### Paramètres dans un tableau

ASTUCE : quand un constructeur (ou une méthode ou une fonction) acceptent un grand nombre de paramètres, il est pratique d'utiliser un tableau pour passer ces paramètres.

Le plus pratique est d'utiliser un tableau associatif. On peut ainsi nommer les paramètres, les passer dans n'importe quel ordre et facilement en ajouter de nouveaux.

Exemple : paramètres dans un tableau associatif
<?php ob_start(); require('bib_fonctions.php'); class Guitare { private $modele, $prix, $cordes, $remise; public function __construct($p){ $p['modele'] = trim($p['modele']); if ($p['modele'] == '') exit('Modèle invalide'); $this->modele = $p['modele']; $ok = array(4, 6, 12); if (!estEntier($p['cordes']) || !in_array($p['cordes'], $ok)) { exit ('Nombre de cordes invalide'); } $this->cordes = $p['cordes']; $this->setPrix($p['prix']); $this->remise = 0; } public function setPrix($val) { if (!is_numeric($val) || $val < 0) { exit ('Valeur de prix invalide'); } $this->prix = $val; } public function decrire() { echo '<h4>Instance de la classe ', __CLASS__, '</h4>', $this->modele, '<br>', $this->cordes, ' cordes<br>', $this->prix, ' &euro;'; } } // Fin de la classe Guitare htmlDebut('Paramètres dans un tableau associatif'); $p = array('modele' => 'Ibanez PCBE12-OPN', 'cordes' => 4, 'prix' => 229); $ibanez = new Guitare($p); $ibanez->decrire(); $p = array('prix' => 334, 'modele' => 'Epiphone EJ-200CE', 'cordes' => 6); $epiphone = new Guitare($p); $epiphone->decrire(); htmlFin(); ?>

Bien que d'un usage moins pratique, on peut aussi utiliser un tableau à index numérique.
L'exemple suivant instancie des guitares avec les informations trouvées dans un fichier texte référençant nos guitares en vente. Le fichier s'appelle `guitares.txt` et se trouve dans le dossier `stock`.
Le contenu du fichier est le suivant :

    Guild D120|639|6
    Fender Villager|444|12
    Gibson Hummingbird|2033|6
    Ibanez PCBE12-OPN|229|4
    Epiphone EJ-200CE|334|6
    Gretsch Falcon|599|12

On trouve le même genre de formattage que celui utilisé dans la page sur la lecture de fichiers.

On considère que les informations trouvées dans le fichier sont valides et on ne fait donc pas de vérifications dans le constructeur de la classe Guitare.

Exemple : paramètres dans un tableau index numérique
<?php ob_start(); require('bib_fonctions.php'); class Guitare { private $modele, $prix, $cordes, $remise; public function __construct($p){ $this->modele = $p[0]; $this->prix = $p[1]; $this->cordes = $p[2]; $this->remise = 0; } public function decrire() { echo '<h4>Instance de la classe ', __CLASS__, '</h4>', $this->modele, '<br>', $this->cordes, ' cordes<br>', $this->prix, ' &euro;'; } } // Fin de la classe Guitare htmlDebut('Paramètres dans un tableau a index numérique'); $stock = array(); // tableau contenant les guitares $fichier = 'stock/guitares.txt'; if (! is_file($fichier)) { exit("$fichier n'est pas un fichier"); } // Lecture du fichier et instanciation des guitares foreach (file($fichier) as $enreg) { $stock[] = new Guitare(explode('|', $enreg)); } // Affichage des guitares foreach ($stock as $guitare) { $guitare->decrire(); } htmlFin(); ?>

### Destructeur

De la même façon qu'il y a une méthode magique `__construct()` invoquée automatiquement par PHP lors de l'instanciation d'une classe, il existe une autre méthode magique `__destruct()` qui, si elle est définie, sera invoquée automatiquement par PHP lors qu'un objet sera supprimé de la mémoire car il n'existe plus aucune référence à l'objet.

La méthode `__destruct()` de PHP est l'équivalent de la méthode `finalize()` en Java.

La méthode `__destruct()` n'accepte pas de paramètre. Si on la définit avec un ou des arguments, le script n'est pas exécuté et s'arrête avec une erreur fatale.

L'exemple suivant montre une classe Stock qui contient les objets Guitare de notre stock.
Quand on détruit l'instance de Stock (`$stock = null;`) observez les appels de destructeurs : le destructeur de l'objet Stock est d'abord invoqué, puis comme il n'existe plus de référence aux objets Guitare leur destructeur est à leur tour invoqué.

Exemple : destructeur
<?php ob_start(); require('bib_fonctions.php'); class Stock { private $guitares = array(); public function __construct($fichier) { foreach (file($fichier) as $e) { $this->guitares[] = new Guitare(explode('|', $e)); } } public function afficher() { htmlInfo('Modèles de guitare en stock'); foreach ($this->guitares as $guitare) { echo $guitare->getModele(), '<hr>'; } } public function __destruct() { htmlInfo('Destruction du stock'); } } // Fin de la classe Stock class Guitare { private $modele, $prix, $cordes, $remise; public function __construct($p){ $this->modele = $p[0]; $this->prix = $p[1]; $this->cordes = $p[2]; $this->remise = 0; } public function getModele() { return $this->modele; } public function __destruct() { htmlInfo('Destruction de '.$this->modele); } } // Fin de la classe Guitare htmlDebut('Méthode __destruct'); $stock = new Stock('stock/guitares.txt'); $stock->afficher(); $stock = null; htmlFin(); ?>

### Exercice : constructeur explicite

la classe Livre

Reprenez la classe `Livre` de l'exercice précédent et faites les tests de validité en appelant les setters dans le constructeur.

Les modifications suivantes doivent être apportées :
- si une ou des valeurs passées au constructeur ne sont pas valides, un message d'erreur doit être affiché sans que le script soit arrêté,
- si une des valeurs passées au constructeur n'est pas valide, le nouvel objet est supprimé.
Le message d'erreur affiché doit être de la forme 'Valeur [valeur passée] invalide pour l'attribut [nom attribut].'

Vous devez tester votre code en instanciant des `Livre` avec les informations suivantes qui provoqueront toutes des erreurs :

Titre | Auteur | Nb Pages | Prix | Cat.
| Pas de titre | 70 | 17 | Méthode
Pas d'auteur |  | 160 | 4.90 | Méthode
Mauvais nombre de pages | Mauvais prix | 0 | -21.90 | Partition
Seulement le titre OK |  | inconnu | 30 euros | Magazine

affichage obtenu

Une solution possible

Exercice : constructeur implicite
<?php ob_start(); require('bib_fonctions.php'); // Définition de la classe Livre class Livre { private $titre, $auteur, $pages, $prix, $cat; private $ok = true; public function __construct($p1, $p2, $p3, $p4, $p5) { $this->setTitre($p1); $this->setAuteur($p2); $this->setPages($p3); $this->setPrix($p4); $this->setCat($p5); } public function getOk() { return $this->ok; } public function setTitre($val) { $val = $this->verifChaine($val, 'titre'); ($val != '') && $this->titre = $val; } public function setAuteur($val) { $val = $this->verifChaine($val, 'auteur'); ($val != '') && $this->auteur = $val; } public function setPages($val) { if (is_numeric($val) && estEntre($val, 10, 1000)) { $this->pages = $val; } else { $this->afficheErreur($val, 'pages'); } } public function setPrix($val) { if (is_numeric($val) && estEntre($val, 0, 1000)) { $this->prix = $val; } else { $this->afficheErreur($val, 'prix'); } } public function setCat($val) { $oks = array('Méthode', 'Partition', 'Biographie', 'Autre'); if (in_array($val, $oks)) { $this->cat = $val; } else { $this->afficheErreur($val, 'cat'); } } private function verifChaine($val, $attr) { $val = trim($val); ($val == '') && $this->afficheErreur($val, $attr); return $val; } private function afficheErreur($val, $attr) { $this->ok = false; echo "<hr>Valeur '<b>$val</b>' invalide pour l'attribut <b>$attr</b>.<hr>"; } public function decrire() { echo '<h4>Instance de la classe Livre</h4>', $this->titre, ' - ', $this->auteur, ' - ', $this->pages, ' pages - ', $this->prix, ' &euro; - ', $this->cat, '<hr>'; } } // Fin de la classe Livre // On passe une référence à l'objet avec & pour que // la suppression de l'objet se fasse correctement function verifLivre(&$livre) { if (! $livre->getOk()) { $livre = null; htmlInfo('Livre supprimé'); } } htmlDebut('Constructeur implicite'); $l1 = new Livre('', 'Pas de titre', 70, 17, 'Méthode'); verifLivre($l1); $l2 = new Livre('Pas d\'auteur', '', 160, 4.90, 'Méthode'); verifLivre($l2); $l3 = new Livre('Mauvais nombre de pages', 'Mauvais prix', 0, -21.90, 'Partition'); verifLivre($l3); $l4 = new Livre('Seulement le titre OK', '', 'inconnu', '30 euros', 'Magazine'); verifLivre($l4); htmlFin(); ?>

### Exercice : tableau de paramètres

L'exercice consiste à instancier des `Livre` suivant la classe simplifiée ci-contre avec les informations trouvées dans notre base de données exemple pour MySQL. On a supprimé les setters de la classe car on considère que les informations provenant de la base de données sont correctes et n'ont pas besoin d'être validées. | la classe Livre
schéma de la base de données php_tuto | Pour que l'exemple fonctionne, il faut bien sûr que la base de donnée de test `php_tuto` soit toujours installée sur votre serveur MySQL. N'oubliez pas d'inclure la bibliothéque `bib_params.php` pour les paramètres de connexion. Les informations à passer au constructeur se trouvent dans la table `livres` et dans la table `auteurs`. Tous les livres de la table `livres` seront affichés triés par titre. Le tableau retourné par la fonction mysqli_fetch_assoc() sera passé tel quel au constructeur.

affichage obtenu

Une solution possible

Exercice : tableau de paramètres
<?php ob_start(); require('bib_params.php'); require('bib_fonctions.php'); // Définition de la classe Livre class Livre { private $titre, $auteur, $pages, $prix, $cat; public function __construct($p) { $this->titre = $p['liTitre']; $this->auteur = $p['auNom']; $this->pages = $p['liPages']; $this->prix = $p['liPrix']; $this->cat = $p['liCat']; } public function decrire() { echo '<h4>Instance de la classe Livre</h4>', $this->titre, ' - ', $this->auteur, ' - ', $this->pages, ' pages - ', $this->prix, ' &euro; - ', $this->cat, '<hr>'; } } // Fin de la classe Livre htmlDebut('Tableau de paramètres'); $stock = array(); // tableau contenant les livres instanciés $bd = bdConnecter(); $sql = 'SELECT liTitre, liPages, liPrix, liCat, (SELECT auNom FROM auteurs, aut_livre WHERE al_IDLivre = liID AND auID = al_IDAuteur LIMIT 1) as auNom FROM livres ORDER BY liTitre'; $r = mysqli_query($bd, $sql) or bdErreur($bd, $sql);; while ($enr = mysqli_fetch_assoc($r)) { $stock[] = new Livre($enr); } foreach ($stock as $livre) { $livre->decrire(); } htmlFin(); ?>

### Exercice : destructeur

Réalisez une classe `BDD` (base de données, très simpliée, voire simpliste) avec une méthode destucteur qui ferme la base de données quand l'objet base de donnée est supprimé.
Comme pour l'exercice précédent, on utilisera notre base de données exemple pour MySQL.

schéma de la base de données php_tuto | Pour que l'exemple fonctionne, il faut bien sûr que la base de donnée de test `php_tuto` soit toujours installée sur votre serveur MySQL. N'oubliez pas d'inclure la bibliothéque `bib_params.php` pour les paramètres de connexion.

Le constructeur n'a pas de paramètre. Il réalise la connexion avec les constantes BD_SERVEUR, BD_USER, BD_PASS, BD_NOM. Si la connexion n'est pas possible, la méthode `stopper()` est appelée avec le message "Pas de connexion". La méthode `lire()` a un seul paramètre : la requête `SELECT` à envoyer à la base. Pour simplifier, on ne fera pas de test de validité pour s'assurer qu'on a bien une requête `SELECT`. Si cette requête provoque une erreur, la méthode `stopper()` est appelée avec le message "Erreur requête". Les enregistrements sélectionnés par la requête sont renvoyés par la méthode sous la forme d'un tableau d'enregistrements (ie un tableau de tableaux). La méthode `stopper()` a un seul paramètre : le texte d'un message d'erreur. Le message d'erreur est affiché puis le script est arrêté. Le destructeur ferme la base de données avec la fonction mysqli_close(). Pour contrôler que le destructeur est bien appelé, il affichera "Fin du destructeur BDD". Le script doit afficher la liste des auteurs (nom et prénom) contenus dans la table `auteurs`. | la classe BDD affichage obtenu

Une solution possible

Exercice : destructeur
<?php ob_start(); require('bib_params.php'); require('bib_fonctions.php'); class BDD { private $base; public function __construct() { $this->base = mysqli_connect(BD_SERVEUR, BD_USER, BD_PASS, BD_NOM); if ($this->base === FALSE) { $this->erreur('Pas de connexion'); } } public function lire($sql) { $enrs = array(); $resultat = mysqli_query($this->base, $sql); if ($resultat === false) { $this->erreur('Erreur requête'); } while ($enr = mysqli_fetch_assoc($resultat)) { $enrs[] = $enr; } mysqli_free_result($resultat); return $enrs; } private function stopper($msg) { exit($msg); } public function __destruct() { if ($this->base !== null) { mysqli_close($this->base); } echo '<h4>Fin du destructeur</h4>'; } } // Fin de la classe BDD htmlDebut('Destructeur'); $base = new BDD; $sql = 'SELECT auNom, auPrenom FROM auteurs ORDER BY auNom'; $enrs = $base->lire($sql); $base = null; // objet BDD supprimé htmlInfo('Liste des auteurs'); foreach ($enrs as $enr) { echo $enr['auNom'], ' ', $enr['auPrenom'], '<br>'; } htmlFin(); ?>