Un des principes de la programmation objet s'appelle l'encapsulation. Il permet de garder la cohérence des données stockées dans un objet en restreignant l'accés aux attributs et méthodes de cet objet.

### Encapsulation

L'encapsulation est réalisée en définissant des droits d'accès sur les données d'un objet. On donne ainsi plus ou moins de visibilité au monde extérieur sur l'intérieur de l'objet. On fait souvent l'analogie avec une boîte noire. Par exemple un moteur de voiture peut être considéré comme une boîte noire : on n'a pas à connaître son fonctionnement détaillé pour pouvoir utiliser la voiture dont il fait partie.

- Les attributs ne doivent pas pouvoir être atteints directement, mais uniquement par l'intermédiaire de méthodes spécialisées pour l'écriture et la lecture de sa valeur.
- Une méthode spécialisée dans l'écriture de la valeur d'un attribut s'appelle un mutateur ou setter (prononcer setteur).
- Une méthode spécialisée dans la lecture de la valeur d'un attribut s'appelle un accesseur ou getter (prononcer gueteur).

Les méthodes ne doivent pouvoir être atteintes directement que si elles concernent la "communication" avec le monde extérieure. Si elles concernent le fonctionnement interne de l'objet elles doivent être cachées.

Le concepteur d'une classe ne doit jamais laisser l'utilisateur d'une classe manipuler les objets instanciés sans restriction (même si le concepteur est également l'utilisateur). L'utilisateur d'une classe doit se contenter d'utiliser les méthodes mises à sa disposition, sans connaître les détails des traitements éventuellement réalisés sur les attributs.

En n'utilisant un objet qu'à travers ses méthodes, on a la garantie de conserver l'objet dans un état cohérent et prévu par le concepteur.

Le fait d'accéder aux attributs d'une classe uniquement par des méthodes offre également l'avantage de pouvoir modifier ou faire évoluer un objet sans changer son comportement extérieur (ie les méthodes à invoquer). Cela permet de séparer la spécification du comportement d'un objet de l'implémentation en langage informatique de cette spécification.

### Visibilité

La visibilité d'un attribut ou d'une méthode définit l'endroit à partir duquel on peut avoir accès à cet attribut ou à cette méthode.
Trois mots-clés permettent de définir cette visibilité.

Endroit | Mot-clé | Remarque
Uniquement la classe | `private` | La visibilité `private` est la plus faible (le droit d'accès le plus restreint). Les éléments `private` ne sont vus que depuis l'intérieur de la classe qui les implémente. On ne pourra pas les atteindre depuis un autre endroit du code (autres classes, autres endroits du script). En UML La visibilité `private` est symbolisée par le signe moins (`-`).
La classe et ses enfants | `protected` | La visibilité `protected` est une extension de `private`. Les éléments `protected` sont vus depuis la classe qui les implémente et depuis les classes qui héritent de cette super-classe. Les autres classes et les autres endroits du script n'auront pas accès à ses éléments. En UML La visibilité `protected` est symbolisée par le signe dièse (`#`).
Partout | `public` | La visibilité `public` est celle qui donne le plus de visibilité aux élements. Ils sont visible et accessibles de tous les endroits du code. Ce type de visibilité brise la règle de l'encapsulation des données. En UML La visibilité `public` est symbolisée par le signe plus (`+`).

Les mots-clés de visibilité se placent au début de la définition des attributs ou des méthodes.

définir la visibilité des attributs et des méthodes

Quand la visibilité d'un attribut ou d'une méthode est définie, cette visibilité ne peut plus être changée dans le code.

La visibilité `protected` sera étudiée quand nous parlerons de l'héritage.

L'exemple suivant reprend la classe Guitare et rend `public` les attributs nombre de cordes et taux de remise. La méthode getPrix() est quant à elle passée en `private`.
Après instanciation d'un objet guitare, on peut donc changer son nombre de cordes de n'importe où dans le script. Cette opération est bien évidemment impossible dans le monde réel où par exemple un modèle 4 cordes ne peut pas être changé en 12 cordes
On peut également modifier la remise sans contrôle. Ici on donne une valeur négative ce qui a pour effet non pas de faire une remise mais de doubler le prix de la guitare.
Finalement, le script s'arrête avec un message d'erreur quand on essaye depuis l'extérieur de la classe d'invoquer la méthode `private` getPrix().

Exemple : visibilité des attributs et des méthodes
<?php ob_start(); require('bib_fonctions.php'); // Définition de la classe Guitare class Guitare { private $modele; private $prix; public $cordes; public $remise = 0; public function __construct($p1, $p2, $p3) { $this->modele = $p1; $this->prix = $p2; $this->cordes = $p3; } private function getPrix() { $rem = ceil($this->prix * $this->remise / 100); return $this->prix - $rem; } public function decrire() { echo '<h4>Instance de la classe ', __CLASS__, '</h4>', $this->modele, '<br>', $this->cordes, ' cordes<br>', $this->getPrix(), ' &euro;'; } } // Fin de la classe Guitare htmlDebut('Visibilité attributs et méthodes'); // création d'un nouvel objet instance de la classe Guitare $guitare = new Guitare('Guild D120', 639, 6); // Etat normale de l'objet $guitare->decrire(); // Ajout d'incohérences dans l'objet $guitare->cordes = 36; $guitare->remise = -100; $guitare->decrire(); // L'appel de cette méthode privée provoque l'arrêt // du script avec l'affichage d'une erreur fatale $guitare->getPrix(); htmlFin(); ?>

### Accesseurs et mutateurs

Pour mettre en place le principe de l'encapsulation (masquer les données), mais néanmoins permettre d'en voir et d'en modifier certaines avec ou sans condition de validité, on va définir des méthodes spécialisées dans ces traitements.

Une méthode spécialisée dans la lecture de la valeur d'un attribut s'appelle un accesseur ou getter (prononcer guetteur). Par convention ces méthodes sont préfixées par `get` suivi du nom de l'attribut dont elles renvoient la valeur.
Exemple : `getModele()`, `getRemise()`, ou `get_modele()`, `get_remise()`, etc. suivant votre style de nommage.

Une méthode spécialisée dans l'écriture de la valeur d'un attribut s'appelle un mutateur ou setter (prononcer setteur). Par convention ces méthodes sont préfixées par `set` suivi du nom de l'attribut dont elles modifient la valeur..
Exemple : `setModele()`, `setRemise()`,ou `set_modele()`, `set_remise()`, etc. suivant votre style de nommage.

Généralement les accesseurs et les mutateurs sont des méthodes `public` puisqu'elles sont sensées permettre à l'extérieur d'accèder à des attributs intérieurs (`private` ou `protected`).

Tous les attributs ne doivent pas forcément avoir des getters et/ou des setters. Seuls les attributs exposés au monde extérieur en ont besoin.

Exemple : la classe guitare avec accesseurs et mutateurs
<?php ob_start(); require('bib_fonctions.php'); class Guitare { private $modele, $prix, $cordes, $remise = 0; // Constructeur public function __construct($p1, $p2, $p3) { $this->modele = $p1; $this->prix = $p2; $this->cordes = $p3; } // Getter de l'attribut remise public function getRemise() { return $this->remise(); } // Le setter de l'attribut remise vérifie que la remise // est une valeur numérique comprise entre 0 et 100 public function setRemise($taux) { if (is_numeric($taux) && estEntre($taux, 0, 100)) { $this->remise = $taux; } } // Il n'y a pas de setter pour le prix. Une fois l'objet // instancié, on ne peut plus modifier son prix. // Le getter du prix ne renvoie pas simplement // le prix de base, mais le prix remise déduite. public function getPrix() { $rem = ceil($this->prix * $this->remise / 100); return $this->prix - $rem; } public function decrire() { echo '<h4>Instance de la classe ', __CLASS__, '</h4>', $this->modele, '<br>', $this->cordes, ' cordes<br>', $this->getPrix(), ' &euro;'; } } // Fin de la classe Guitare htmlDebut('La classe Guitare avec getters et setters'); $guitare = new Guitare('Fender Villager', 444, 12); $guitare->decrire(); // Invocation du setter du taux de remise $guitare->setRemise(30); // Invocation du getter du prix htmlInfo('Prix avec remise : '.$guitare->getPrix()); // Invocation du setter setRemise avec une mauvaise valeur $guitare->setRemise(-100); // La mauvaise remise n'est pas prise en compte htmlInfo('Prix avec remise : '.$guitare->getPrix()); // Essayer de modifier la valeur d'un attribut privé // provoque une erreur fatale et l'arrêt du script $guitare->cordes = 123; htmlFin(); ?>

### Exercice : getter et setter

la classe Livre

Reprenez la classe `Livre` de l'exercice précédent et écrivez les setters pour tous les attributs.
Les attributs `titre` et `auteur` ne doivent pas être vides.
L'attribut `pages` doit être un nombre entre 10 et 1000.
L'attribut `prix` doit être un nombre entre 0 et 1000.
L'attribut `cat` doit être "Méthode", "Partition", "Biographie" ou "Autre".

Si les valeurs passées aux setters ne sont pas valides, l'attribut n'est pas modifié et un message d'erreur doit être affiché et le script continue son éxécution sans s'arrêter.
Le message d'erreur affiché doit être de la forme 'Valeur [valeur passée] invalide pour l'attribut [nom attribut].'

Pour tester, une instances de la classe `Livre` doit être créée de la façon suivante :
`$livre = new Livre('Débutant Guitare Acoustique', 'Rébillard', 70, 17, 'Méthode');`
Voud devez ensuite invoquer chacun des setters une valeur invalide puis une valeur valide pour vérifier que votre traitement est correct.

affichage obtenu

Une solution possible

Exercice : getters et setters
<?php ob_start(); require('bib_fonctions.php'); // Définition de la classe Livre class Livre { private $titre, $auteur, $pages, $prix, $cat; public function __construct($p1, $p2, $p3, $p4, $p5) { $this->titre = $p1; $this->auteur = $p2; $this->pages = $p3; $this->prix = $p4; $this->cat = $p5; } 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) { 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 htmlDebut('Getters et setters'); $livre = new Livre('Débutant Guitare Acoustique', 'Rébillard', 70, 17, 'Méthode'); $livre->decrire(); $livre->setTitre(' '); $livre->setTitre('Guitar Play Along Neil Young'); $livre->setAuteur(''); $livre->setAuteur('Leonard'); $livre->setPages(1); $livre->setPages(50); $livre->setPrix('30 euros'); $livre->setPrix(30); $livre->setCat('Rock'); $livre->setCat('Partition'); $livre->decrire(); htmlFin(); ?>

### Getters et setters magiques

Parfois, quand les attributs d'une classe sont nombreux, il peut être pénible d'écrire des getters et des setters dont la plupart n'auront qu'une ligne de code.

Pour nous faciliter la tâche, PHP met à notre disposition ce qu'on appelle des "méthodes magiques". Deux d'entre elles vont dans notre cas être bien utiles : la méthode magique `__get()` et la méthode magique `__set()`.

Comme leur nom l'indique, `__get()` est un getter et `__set()` est un setter.
`__get()` accepte en paramètre le nom de l'attribut pour lequel on veut connaître la valeur.
`__set()` accepte 2 paramètres : le nom de l'attribut pour lequel on veut modifier la valeur et la nouvelle valeur à affecter à l'attribut.

`__get()` et `__set()`sont invoqué automatiquement par PHP quand le monde extérieur essaye d'accéder à un attribut dont l'accés lui est refusé.
Le refus d'accès peut avoir 2 causes :
- l'attribut est `private` ou `protected`,
- l'attribut n'est pas défini dans la classe.

Dans le code de ces 2 méthodes, nous définissons ce qui est fait dans le cas ou l'attribut n'est pas défini et ce qui est fait si l'attribut est défini : renvoi ou pas d'une valeur, modification ou pas d'une valeur

Les méthodes magiques dans PHP commencent toutes par un double underscore `__` suivi du nom de la méthode.

Tous les attributs ne doivent pas forcément avoir des getters et/ou des setters. Seuls les attributs exposés au monde extérieur en ont besoin.

Exemple : méthode magique __get()
<?php ob_start(); require('bib_fonctions.php'); class Guitare { private $modele, $prix, $cordes, $remise = 0; // Constructeur public function __construct($p1, $p2, $p3) { $this->modele = $p1; $this->prix = $p2; $this->cordes = $p3; } public function getPrix() { $rem = ceil($this->prix * $this->remise / 100); return $this->prix - $rem; } // méthode magique __get() public function __get($nom_attribut) { if (!isset($this->$nom_attribut)) { exit("Erreur : attribut $nom_attribut indéfini."); } // L'attribut prix n'est jamais accessible directement // car la valeur retrounée est toujours le résultat // d'un calcul avec la remise. if ($nom_attribut == 'prix') { return $this->getPrix(); } // Tous les autres attributs renvoient leur valeur // Remarquez ici le $ après -> // On atteint un attribut ou une méthode, soit // directement avec leur nom, soit indirectement // avec une variable. Dans ce cas pour indiquer qu'on // utilise une variable on a la syntaxe avec $ return $this->$nom_attribut; } } // Fin de la classe Guitare htmlDebut('La méthode magique __get()'); $guitare = new Guitare('Fender Villager', 444, 12); htmlInfo('Modèle : '.$guitare->modele); htmlInfo('Nombre de corde : '.$guitare->cordes); htmlInfo('Taux de remise : '.$guitare->remise.'%'); htmlInfo('Prix : '.$guitare->prix.' euros'); // Provoque l'arrêt du script htmlinfo('Attribut indéfini : '.$guitare->stock); htmlFin(); ?>

La méthode magique `__set()` fonctionne comme `__get()` à la différence qu'elle permet au monde extérieur de modifier la valeur d'un attribut. A mon avis l'utilisation de `__set()` est à éviter car il faut toujours vérifier les valeurs des attributs pour que l'état de l'objet reste cohérent. Comme on l'a vu précédemment, permet de modifier la valeur de l'attribut `remise` sans contrôle permet d'introduire des erreurs dans le traitement du prix de vente d'un livre.

L'exemple suivant montre l'utilisation de `__set()`. Encore une fois, je pense qu'il faut mieux ne pas utiliser cette méthode dans les développements car on perd les contrôles que l'on peut faire sur les valeurs des attributs, ou ses contrôles s'ils sont faits dans `__set()` donneront souvent un code difficile à maintenir.

Exemple : méthode magique __set()
<?php ob_start(); require('bib_fonctions.php'); class Guitare { private $modele, $prix, $cordes, $remise = 0; // Constructeur public function __construct($p1, $p2, $p3) { $this->modele = $p1; $this->prix = $p2; $this->cordes = $p3; } // méthode magique __set() public function __set($nom_attribut, $val) { switch ($nom_attribut) { case 'prix': if (!is_numeric($val)) return; if ($val <= 0) return; break; case 'remise': if (!is_numeric($val)) return; if (!estEntre($val, 0, 100)) return; break; case 'cordes': return; // pas modifiable case 'modele': $val = trim($val); if ($val == '') return; break; default: exit("Erreur : attribut $nom_attribut indéfini."); } $this->$nom_attribut = $val; } public function getPrix() { $rem = ceil($this->prix * $this->remise / 100); return $this->prix - $rem; } // méthode magique __get() public function __get($nom_attribut) { if (!isset($this->$nom_attribut)) { exit("Erreur : attribut $nom_attribut indéfini."); } return ($nom_attribut == 'prix') ? $this->getPrix() : $this->$nom_attribut; } } // Fin de la classe Guitare htmlDebut('La méthode magique __set()'); $guitare = new Guitare('Fender Villager', 444, 12); $guitare->modele = 'François Piat'; $guitare->cordes = 1234; $guitare->prix = 10; $guitare->remise = 'cent pour cent'; htmlInfo('Modèle : '.$guitare->modele); htmlInfo('Nombre de corde : '.$guitare->cordes); htmlInfo('Taux de remise : '.$guitare->remise.'%'); htmlInfo('Prix : '.$guitare->prix.' euros'); // Provoque l'arrêt du script $guitare->stock = 2; htmlFin(); ?>