Visibilité des propriétés et des méthodes

Rechercher

Visibilité des propriétés et des méthodes

  • Par Emacs
  • 21 commentaires
  • 35838 lectures
  • RSS -  Atom

La visibilité des propriétés et des méthodes d'un objet constitue une des particularités élémentaires de la programmation orientée objet. Ce tutoriel a pour objectif de présenter les différents niveaux de visibilité que propose le modèle objet de PHP 5. Nous les passerons en revue un par un au travers d'exemples pratiques et nous apporterons quelques bonnes pratiques à adopter lorsqu'on les utilise.

Qu'est-ce que la visibilité des propriétés et méthodes d'un objet ?

La visibilité permet de définir de quelle manière un attribut ou une méthode d'un objet sera accessible dans le programme. Comme Java, C++ ou bien ActionScript 3, PHP introduit 3 niveaux différents de visibilité applicables aux propriétés ou méthodes de l'objet.

Il s'agit des visibilités publiques, privées ou protégées qui sont respectivement définies dans la classe au moyen des mots-clés public, private et protected.

L'exemple suivant illustre la syntaxe de déclaration de données membres (attributs) et de méthodes ayant des visibilités différentes. Nous expliquerons juste après chaque particularité des niveaux de visibilité.

Utilisation des mots-clés public, private et protected
<?php
class Vehicule
{
// Attributs
public $marque;
private $_volumeCarburant;
protected $_estRepare;
// Méthodes
public function __construct()
{
$this->_volumeCarburant = 40;
$this->_estRepare = false;
}
// Démarre la voiture si le réservoir
// n'est pas vide
public function demarrer()
{
if ($this->_controlerVolumeCarburant())
{
echo 'Le véhicule démarre';
return true;
}
echo 'Le réservoir est vide...';
return false;
}
// Vérifie s'il y'a du carburant dans le réservoir
private function _controlerVolumeCarburant()
{
return ($this->_volumeCarburant > 0); // renvoi true ou false
}
// Met le véhicule en maintenance
protected function reparer()
{
$this->_estRepare = true;
echo 'Le véhicule est en réparation';
}
}

Nous remarquons ici que nous utilisions déjà les attributs et méthodes publiques dans les exemples du premier tutoriel sur la programmation orientée objet. Présentons à présent chacun des niveaux de visibilité.

L'accès public

C'est l'accès par défaut de PHP si l'on ne précise aucune visibilité. Tous les attributs et méthodes qui sont déclarés sans l'un de ces trois mots-clés sera considéré automatiquement par l'interprêteur comme publique.

Le mot-clé public indique que les propriétés et méthodes d'un objet seront accessibles depuis n'importe où dans le programme principal ou bien dans les classes mères héritées ou classes filles dérivées. Retenez ces termes dans un coin de votre mémoire car nous présenterons les notions d'héritage au prochain tutoriel.

Concrètement, c'est ce que nous faisions dans le tutoriel précédent. Ce qui donne avec notre exemple :

Utilisation de la visibilité publique
<?php
// Instanciation de l'objet : appel implicite à la méthode __construct()
$monVehicule = new Vehicule();
// Mise à jour de la marque du véhicule
$monVehicule->marque = 'Peugeot';
// Affichage de la marque du véhicule
echo $monVehicule->marque;
?>

Dans cet exemple, nous remarquons que nous pouvons lire et modifier directement la valeur de l'attribut publique marque en l'appellant directement de cette manière : $monVehicule->marque (sans le dollars).

Nous verrons juste après que cette même utilisation sera impossible avec les attributs privés et protégés.

Note : lorsque l'on instancie la classe pour créer un nouvel objet, le mot-clé "new" se charge d'appeler la méthode constructeur de la classe. Cette dernière doit donc obligatoirement être déclarée publique car elle est appellée depuis l'extérieur de la classe.

L'accès private

Le mot-clé private permet de déclarer des attributs et des méthodes qui ne seront visibles et accessibles directement que depuis l'intérieur même de la classe. C'est à dire qu'il devient impossible de lire ou d'écrire la valeur d'un attribut privé directement en faisant $monVehicule->_volumeCarburant. De même pour utiliser la méthode _controlerVolumeCarburant() via $monVehicule->_controlerVolumeCarburant(). Vous remarquerez en testant, qu'une erreur fatale est générée à l'exécution.

La valeur de l'attribut privé $_volumeCarburant est initialisée dans le constructeur de la classe. C'est à dire quand on construit l'objet.

Par exemple, lorsque notre véhicule est enfin assemblé et prêt à être utilisé, il sera vendu au client avec le réservoir d'essence rempli,soit 40L.

Une méthode est déclarée private lorsqu'elle n'a pas vocation à être utilisée en dehors de la classe. Les méthodes privées servent notamment à valider des données dans un objet ou bien à effectuer différents traitements internes. C'est par exemple la tâche que remplit la méthode privée _controlerVolumeCarburant() lorsqu'elle est appellée par la méthode publique demarrer(). Cette méthode privée vérifie qu'il y'a bien du carburant dans le véhicule en testant la valeur de l'attribut privé $_volumeCarburant. Si tel est le cas, la méthode retourne TRUE et donc la voiture démarre. Dans le cas contraire, la valeur retournée est FALSE et la voiture ne peut démarrer.

Appel de la méthode demarrer() sur l'objet $monVehicule
<?php
// Affiche : "Le véhicule démarre"
$monVehicule->demarrer();
?>

Note : par convention, on déclare les attributs privés et protégés avec un underscore afin de les identifier plus facilement lorsque l'on relit le code. Cette règle d'usage n'est pas suivie par tous les développeurs mais nous vous recommandons vivement de l'adopter dans vos développements.

L'accès protected

L'accès protégé ( protected ) est un intermédiaire entre l'accès publique et l'accès privé. Il permet d'utiliser des attributs et méthodes communs dans une classe parente et ses classes dérivées (héritantes).

Le chapitre concernant l'héritage n'est pas au programme de ce tutoriel. Nous l'étudierons dans le prochain cours. Toutefois, nous allons devoir l'anticiper afin de comprendre comment fonctionne l'accès protégé. Pour cela, nous allons ajouter une classe Voiture qui héritera de notre classe actuelle Véhicule. Comme une voiture est une sorte de véhicule, il nous est donc possible de dériver la classe Véhicule avec une classe Voiture.

La classe Vehicule contiendra les attributs et méthodes communs à tout type de véhicule tandis que la classe Voiture encapsulera les propriétés et méthodes propres aux voitures. Par exemple, la marque ou le booléen estRepare peuvent très bien être des attributs partagés. Par contre le volume de carburant dans le réservoir n'est pas commun à tous les véhicules.

En effet, un vélo, une trottinette ou bien encore un aviron peuvent être considérés comme des véhicules non motorisés. Ils n'ont donc pas de réservoir et ne fonctionnent pas avec du carburant.

Utilisation des accès protégés
<?php
class Vehicule
{
// Attributs
protected $_marque;
protected $_estRepare;
// Méthodes
public function __construct($marque)
{
$this->_marque = $marque;
$this->_estRepare = false;
}
// Met le véhicule en maintenance
public function reparer()
{
$this->_estRepare = true;
echo 'Le véhicule est en réparation';
}
}
class Voiture extends Vehicule
{
// Attributs
private $_volumeCarburant;
// Constructeur
public function __construct($marque)
{
// Appel du constructeur de la classe parente
parent::__construct($marque);
$this->_volumeCarburant = 40;
}
// Démarre la voiture si le réservoir
// n'est pas vide
public function demarrer()
{
if ($this->_controlerVolumeCarburant())
{
echo 'Le véhicule démarre';
return true;
}
echo 'Le réservoir est vide...';
return false;
}
// Vérifie qu'il y'a du carburant dans le réservoir
private function _controlerVolumeCarburant()
{
return ($this->_volumeCarburant > 0);
}
}

Nous venons de déclarer deux attributs protégés dans la classe parente Vehicule et nous avons redescendu l'attribut _volumeCarburant dans la classe Voiture. De même nous avons redescendu les méthodes demarrer() et _controlerVolumeCarburant() dans la classe Voiture. Ainsi, nous pouvons désormais instancier un nouvel objet de type Voiture et profiter des attributs et des méthodes de la classe Vehicule en plus. Enfin, notez que la méthode reparer() est passée du mode protected à public afin de pouvoir l'appeler depuis l'extérieur de la classe. Ce qui donne :

Utilisation des attributs et méthodes protégés
<?php
$monVehicule = new Voiture('Peugeot');
$monVehicule->demarrer();
$monVehicule->reparer();
?>

Mise à jour d'un attribut privé ou protégé : rôle du mutator

Nous avons évoqué plus haut qu'il était impossible d'accéder à la valeur d'un attribut privé ou protégé en utilisant la syntaxe suivante : $objet->attribut. Une erreur fatale est générée. Comment faire pour mettre à jour la valeur de l'attribut dans ce cas ? C'est là qu'intervient le mutator.

Le mutator n'est ni plus ni mois qu'une méthode publique à laquelle on passe en paramètre la nouvelle valeur à affecter à l'attribut. Par convention, on nomme ces méthodes avec le préfixe set. Par exemple : setMarque(), setVolumeCarburant()... C'est pourquoi on entendra plus souvent parler de setter que de mutator.

Ajoutons un mutator à notre classe Voiture qui permet de modifier la valeur du volume de carburant. Cela donne :

Ajout du mutator (setter) setVolumeCarburant à la classe Voiture
<?php
class Voiture extends Vehicule
{
// ...
public function setVolumeCarburant($dVolume)
{
$this->_volumeCarburant = $dVolume;
}
}

Pour respecter les conventions de développement informatique, nous utilisons dans notre exemple l'écriture au moyen du préfixe set. Mais ce nom est complètement arbitraire et notre méthode aurait très bien pu s'appeller faireLePlein() ou remplirLeReservoir(). Il ne tient qu' à vous de donner des noms explicites à vos méthodes.

Quant à l'utilisation de cette méthode, il s'agit tout simplement d'un appel de méthode traditionnel.

Mise à jour de la valeur de l'attribut privé $_volumeCarburant
<?php
// Je remplis mon réservoir de 50 L d'essence
$monVehicule->setVolumeCarburant(50);
?>

Obtenir la valeur d'un attribut privé ou protégé : rôle de l'accessor

Nous venons d'étudier comment mettre à jour (écriture) la valeur d'un attribut privé ou protégé. Il ne nous reste plus qu' à aborder le cas de la lecture de cette valeur. Comment restitue-t-on cette valeur dans un programme sachant que l'accès direct à l'attribut est interdit ? De la même manière que pour le mutator, nous devons déclarer une méthode qui va se charger de retourner la valeur de l'attribut. Ce n'est donc ni plus ni moins qu'une fonction avec une instruction return.

On appelle ce type de méthode un accessor ou bien plus communément un getter. En effet, par convention, ces méthodes sont préfixées du mot get qui se traduit en français par « obtenir, récupérer ». Au même titre que les mutators, il est possible d'appliquer n'importe quel nom à un accessor.

Reprenons notre exemple précédent. Le getter ci-après explique le principe de retour de la valeur d'un attribut privé. Nous renvoyons ici la valeur de la variable $_volumeCarburant.

Déclaration d'un accessor pour l'attribut privé $_volumeCarburant de la classe Voiture
<?php
class Voiture extends Vehicule
{
// ...
public function getVolumeCarburant()
{
return $this->_volumeCarburant;
}
}

Voici un exemple d'utilisation de la méthode getVolumeCarburant :

Utilisation de l'accessor getVolumeCarburant() sur l'objet $monVehicule
<?php
echo sprintf("Il me reste %u L d'essence", $monVehicule->getVolumeCarburant());
?>

C'est tout ce qu'il faut savoir d'essentiel sur la visibilité des méthodes et des attributs ainsi que sur les accès aux valeurs de ces derniers. Avant de conclure ce tutoriel, faisons un court rappel de bonnes pratiques de développement orienté objet histoire que vous puissiez démarrer immédiatement avec des bases solides.

Quelques bonnes pratiques...

Voici une petite série récapitulative de conseils et de réflexes à mettre en oeuvre en développement orienté objet afin d'uniformiser et de standardiser le code. Ces astuces syntaxiques ne sont pas des techniques personnelles puisque ce sont des conventions éprouvées par des spécialistes des langages orientés objet. Malgré tout, ne le prenez pas comme des paroles d'évangile. Vous êtes libres d'adopter les conventions et règles syntaxiques de votre choix.

  • Préfixer les noms de méthodes et d'attributs privés avec un underscore afin de les distinguer plus rapidement à la relectue du code.
  • Placer les attributs et méthodes en accès privé ou bien en accès protégé si l'on souhaite dériver la classe dans le futur.
  • Utiliser autant que possible les conventions de nommage pour les accessors et les mutators ( getNomAttribut() et setNomAttribut() ).

Conclusion

Nous voilà arrivés à l'issue de ce tutoriel sur la programmation orientée objet. Nous avons étudié chacune des visibilités relatives aux attributs et aux méthodes d'un objet. Puis nous nous sommes intéressés aux concepts de mutators (setters) et accessors (getters) qui permettent respectivement d'affecter une nouvelle valeur à un attribut ou bien d'en récupérer cette dernière via l'utilisation de méthodes. Enfin, nous avons donné une petite série de bonnes pratiques à adopter pour produire du code standard et maintenable.

Les tutoriels suivants de ce chapitre s'intéresseront particulièrement aux concepts d'héritage, de classes abstraites, d'interfaces, de propriétés ou méthodes statiques, ou bien encore aux fameuses méthodes magiques dont on entend souvent parler.



Les commentaires

1. Par Talus le 26/12/2007 13:00

Yo,

Tuto pas mal dans l'ensemble, mais pour les méthodes magiques de PHP5 (les mutator et autres accessor), il existe justement 2 méthodes magiques : __set($nom, $valeur) et __get($nom), qui respectivement donnent la valeur $valeur à l'attributs $nom, et récupère la valeur de l'attribut $nom.

Certes, c'est plutot une surcharge d'opérateur, mais je pense tout de même que tu devrais en parler dans ce tuto. Elles n'ont pas non plus "vraiment" la même utilité que les deux facons que tu utilises (elles nécessitent, par exemple, un array attribut $mes_attributs, qui contient en clé les nom des attributs surchargés, et comme valeurs leur valeurs respectives), mais ca vaut tout de même le détour, je pense.

Et puis, si on veut faire les mutator et autres accessors, pourquoi s'emmerder à foutre en private les attributs concernés ?

Talus

2. Par Emacs le 26/12/2007 14:39

Bonjour Talus,

Oui j'aurais pu en parler effectivement. Si je ne l'ai pas fait c'est parceque j'envisageai de faire un tutoriel sur les méthodes magiques plus tard. Je vais y réfléchir

++

Hugo.

3. Par Xinox le 18/02/2008 21:18

Bonjour Emacs,

Une petite faute, ligne 6 : "public $marque", oubli d'un point virgule.

4. Par Emacs le 19/02/2008 00:44

Merci Xinox, c'est corrigé

5. Par Warthog le 02/05/2008 17:39

Salut Emacs,

Tout d'abord merci pour ces tutos
Tu as mélangé les noms de tes attributs/méthodes (carburant, essence...) dans ton premier code
Pour plus de lisibilité, ça serait cool de les corriger

6. Par Emacs le 02/05/2008 18:31

@Warthog : merci pour tes compliments et ton retour d'erratum. C'est corrigé

7. Par BLANCHON Vincent le 17/06/2008 10:51

Il reste toujours une erreur dans le tout premier code

8. Par Emacs le 17/06/2008 13:38

Merci Vincent, c'est corrigé

9. Par Michael le 24/06/2008 19:05

Merci pour ce tuto très instructif.

Une coquille :
private function _controllerVolumeCarburant()

Doit devenir :
private function _controlerVolumeCarburant()

Un seul l à controler, sinon ca fonctionne pas

10. Par Emacs le 24/06/2008 21:42

Arf honte à moi, c'est corrigé. Merci

11. Par Jérémy_B le 11/08/2008 20:50

Encore une très bonne explication, je comprend tout ce que tu nous décris, avec de bons exemples. Par contre, j'ai une question : ces niveaux de visibilités améliorent-elles le niveaux de sécurité de notre programme ? (je ne pense pas, mais je préfère poser la question)

PS : Dans " Obtenir la valeur d'un attribut privé ou protégé : rôle de l'accessor " --> 2èm paragraphe : " il est possible appliquer n'importe quel nom à un accessor. ", il manque " d' " devant " appliquer " !

Autre question qui n'a pas de rapport avec ce tutoriel : tous ces tutoriels que tu proposes dans la catégories POO sont des tutoriels théoriques munis d'exemples, mais il y aura-t-il dans un avenir des sortes de " TP " ou autre sur un cas concret d'une application web (livre d'or, news ou autre..) ?

Il est bien évident qu'on peut en apprendre sur ce sujet en lisant le code source des frameworks tel que Kohana ou Prado, mais il serait intéressant d'avoir un petit TP pour éviter d'être noyer dans des codes.. (je débute en POO, je lis tes tutos pour comprendre l'aspect théorique, mais en lisant Kohana, je suis encore un peu perdu tellement il y a de classe..)

Bref, ce n'est qu'une proposition.

Merci encore pour le tutoriel.

12. Par Emacs le 12/08/2008 00:41

Merci encore pour tes compliments et tes retours de correction Jérémy. Je vais prendre le temps de répondre à chacune de tes interrogations.

>> ces niveaux de visibilités améliorent-elles le niveaux de sécurité de notre programme ?

La sécurité du programme, pas vraiment ! En revanche la sûreté de programmation oui. En effet, grâce aux 3 niveaux de visibilité, cela permet tout d'abord d'interdire l'accès à certaines propriétés et méthodes depuis l'extérieur de la classe. Ainsi, on oblige le développeur à n'utiliser que certaines fonctionnalités bien définies dans un périmètre bien délimité. C'est l'accès publique. De l'autre côté, tout ce qui est déclaré privé ou protégé ne peut être vu que depuis l'intérieur de l'objet lui même, et seul lui même est capable de manipuler ces propriétés et actions. Le seul moyen d'accéder à une propriété privée (ou protégée) est de passer par une méthode (accessor) qui se charge de réaliser un contrôle d'accès. C'est-à-dire que cette méthode sera le seul moyen possible d'accéder à une donnée en lecture ou en écriture selon le type d'accessor (getter ou setter) et d'appliquer par exemple une validation. Par exemple, nous pouvons modéliser un compte bancaire au moyen d'un objet de type CompteBancaire :

<?php

class CompteBancaire
{
private $_solde;

public function __construct($montant_initial) {
if(is_numeric($montant_initial)) {
$this->_solde = $montant_initial;
} else {
throw new Exception("Le paramètre n'est pas un nombre";
}
}

public function virer(CompteBancaire $destination, $montant) {
if((double) $montant > 0) {
// On décrémente le montant
$this->_solde -= $montant;
// On incrémente le montant à l'autre compte
$destination->crediter($montant);
} else {
throw new Exception('Montant inférieur ou égal à 0';
}
}

public function crediter($montant) {
if((double) $montant > 0) {
// On incrémente le montant
$this->_solde += $montant;
} else {
throw new Exception('Montant inférieur ou égal à 0';
}
}
}

Ainsi, si je fais :

$compteHugo = new CompteBancaire(350);
$compteRomain = new CompteBancaire(200);
// Je vire 180 euros à Romain
$compteHugo->virer($compteRomain, 180);

Pendant les phases de débit et de crédit, le montant a été contrôlé avant d'être crédité / débité aux deux comptes. C'est le contrôle d'accès et l'intérêt de la visibilité. Je t'invite à lire le tutoriel sur les méthodes magiques __set() et __get() de Palleas qui rappelle en partie tout cela.

Concernant les TP, pour le moment ils ne sont pas prévus pour ces deux raisons :

1/ Nous avons encore beaucoup de tutoriels "théoriques" à écrire avant de nous consacrer aux tutoriels pratiques. Le but des tutoriels "théoriques" est de permettre d'acquérir et de comprendre des bases saines de programmation avec PHP pour pouvoir développer soi même des applications.
2/ Ce genre d'exemples (livre d'or, news...) pullulent sur Internet, bien que finalement on en trouve plus de mauvais que de bons... Peut-être qu'un jour nous prendrons le temps d'en écrire un mais ce n'est pas encore planifié faute de temps. Et puis trouver un exemple suffisamment générique, original et simple qui soit capable de couvrir la plupart des chapitres de POO de PHP, c'est difficile à trouver

Je t'invite à expérimenter tous les codes que l'on présente dans ces tutoriels et écrire quelques codes personnels reposant sur de la POO pour en comprendre les grands principes. Apprendre en autodidacte après avoir suivi de bons exemples théoriques est l'un des meilleurs moyens de se former, de comprendre et de pouvoir développer soi même en POO.

++

Hugo.

13. Par Jérémy_B le 12/08/2008 17:33

Bonjour !

Merci pour cette longue réponse, et très détaillée.

Pour ma question sur ce tutoriel, j'ai maintenant bien compris la distinction entre code sécurisé et sureté du code.

Pour ma proposition de TP, je comprend tout à fait qu'il est plus important d'avoir de bon tutoriel théorique, que pratique. Et comme tu le dis, je vais m'entrainer avec des classes toutes simples pour voir comme elles réagissent.

Et dans un future proche, j'attaquerai l'analyse du code de Kohana, qui m'a l'air très intéressant.

Bref, il y a encore du boulot sur la planche.

Merci encore !

14. Par axool le 13/12/2008 19:37

Excellent tutoriel que je recommanderai aux autres .

Merci =)

15. Par moi le 19/12/2008 17:16

Voilà la réponse que j'obtiens pour $monVehicule->reparer : Fatal error: Call to protected method Vehicule::reparer() from context '' in ******** on line *.
Ce qui semble logique puisqu'on fait appel à une fonction "protected" ??

16. Par Kijer le 29/12/2008 12:52

J'adore ton sens pédagogique !
Tu arrives à simplifier la compréhension d'un procédé pas forcément accueillant.

Sinon j'aurai une petite question/remarque !

Que ce soit $_estRepare ou $volumeCarburant, je ne vois à aucun endroit l'utilisation de ces variables !

car ce le premier, tu fais avec this est une affectation false, et sur l'autre this est une affectation de 40.
Du coup, aucun n'est lié ?

17. Par Emacs le 01/01/2009 15:05

C'est normal Kijer. En POO, quand tu déclares une variable $toto dans une classe comme attribut de cette dernière, tu y accèdes en utilisant $this->toto.

18. Par niamor le 04/05/2009 18:19

bravo pour ces tutos.

j'ai relevé 2 fautes dans le texte:
§ mutator: "une erreur est généréE"
§ private access: "Une méthode est déclarée private lorsqu'elle n'A pas vocation à être utilisée en dehors de la classe."

19. Par Emacs le 04/05/2009 19:51

Merci niamor, c'est corrigé

20. Par thierry le 20/05/2009 14:19

J'ai une question qui concerne le paramètre d'accessibilité des méthodes magiques.

En faisant mes petits tests dans mon coin. J'ai remarqué que certaines méthodes magiques pouvaient êtres appelées "magiquement" depuis l'extérieur (de la classe) même en les déclarant "private" (comme __toString, __set, __get, __call), alors que d'autres non (__construct, __destruct, __clone, __sleep, __wakeup).

Je ne trouve pas d'explication logique à cette différence de comportement.

En aurais-tu une ?

(j'espère avoir été clair dans mes explications)

21. Par Tadaa9 le 25/06/2009 13:59

Salut,
Merci pour tes cours sur la POO ! Pour le moment, je trouve ça très instructif et assez clair.
J'ai bien compris l'histoire des public, private et protected. Par contre sa devient flou quand tu parle de setter et de getter. En effet, ce que je ne comprend pas : c'est pourquoi on met des attributs en private ou protected si c'est pour quand même permettre ensuite de les modifier ou de les lire... lol Autant les mettre directement en public ! Non ?