Félicitations ! Vous commencez à structurer votre code avec de plus en plus d'expertise. 😎
Souvent, lorsque vous allez obtenir une technique donnant des possibilités d'extension d'un code, vous allez aussi croiser des situations où vous auriez aimé, au contraire, restreindre les accès à certaines propriétés ou méthodes. C'est ce que nous allons voir dans ce chapitre.
Empêchez l'accès aux propriétés et aux méthodes
Nous avons déjà croisé 2 formes de visibilité, publique et privée, dans la première partie. Mais jusqu'ici, c'était uniquement via une seule classe. Que se passe-t-il lorsque je définis une propriété ou une méthode comme privée, puis que j'hérite de cette classe ?
Changeons la classe User
en passant tout en private
:
<?php
declare(strict_types=1);
class User
{
private const STATUS_ACTIVE = 'active';
private const STATUS_INACTIVE = 'inactive';
public function __construct(private string $username, private string $status = self::STATUS_ACTIVE)
{
}
private function setStatus(string $status): void
{
assert(
in_array($status, [self::STATUS_ACTIVE, self::STATUS_INACTIVE]),
sprintf('Le status %s n\'est pas valide. Les status possibles sont : %s', $status, implode(', ', [self::STATUS_ACTIVE, self::STATUS_INACTIVE]))
);
$this->status = $status;
}
private function getStatus(): string
{
return $this->status;
}
}
class Admin extends User
{
public const STATUS_LOCKED = 'locked';
// la méthode est entièrement ré-écrite ici :) seule la signature reste inchangée
public function setStatus(string $status): void
{
assert(
in_array($status, [self::STATUS_ACTIVE, self::STATUS_INACTIVE, self::STATUS_LOCKED]),
sprintf('Le status %s n\'est pas valide. Les status possibles sont : %s', $status, implode(', ', [self::STATUS_ACTIVE, self::STATUS_INACTIVE, self::STATUS_LOCKED]))
);
$this->status = $status;
}
// la méthode utilise celle de la classe parente, et ajoute un comportement :)
public function getStatus(): string
{
return strtoupper(parent::getStatus());
}
}
$admin = new Admin('Paddington');
$admin->setStatus(Admin::STATUS_LOCKED);
echo $admin->getStatus();
C'est bien malin ! Plus aucune des méthodes surchargées de la classe Admin
ne fonctionne ! Ce code me renvoie une erreur. PHP va m'indiquer que j'ai tenté d'accéder à une méthode privée, ou encore que les propriétés auxquelles nous tentons d’accéder n’existent pas...
Un élément privé devient uniquement accessible pour la classe dans laquelle il se trouve. L'héritage est interrompu pour cet élément, qu'il s'agisse d'une propriété ou d'une méthode.
Heureusement, nous avons accès à une troisième visibilité nous apportant un peu de finesse.
Autorisez l'accès uniquement aux enfants
Cette visibilité s'applique par le biais du mot clé protected
. Donc pour synthétiser, nous avons 3 moyens d'exposer nos propriétés et nos méthodes :
public
ouvre l'accès à tous ;private
ferme à tous ;et
protected
permet de fermer à l'extérieur, mais d'ouvrir à l'héritage.
protected
est un entre-deux idéal pour définir une méthode dont le but n'est que d'être appelée en interne, dont un développeur utilisant la classe n'a pas vraiment à connaître l'existence, mais en autorisant qu'elle soit manipulée depuis une classe enfant.
Changeons à nouveau notre classe User
pour lui appliquer la visibilité protected
.
<?php
declare(strict_types=1);
class User
{
protected const STATUS_ACTIVE = 'active';
protected const STATUS_INACTIVE = 'inactive';
public function __construct(protected string $username, protected string $status = self::STATUS_ACTIVE)
{
}
protected function setStatus(string $status): void
{
assert(
in_array($status, [self::STATUS_ACTIVE, self::STATUS_INACTIVE]),
sprintf('Le status %s n\'est pas valide. Les status possibles sont : %s', $status, implode(', ', [self::STATUS_ACTIVE, self::STATUS_INACTIVE]))
);
$this->status = $status;
}
protected function getStatus(): string
{
return $this->status;
}
}
class Admin extends User
{
public const STATUS_LOCKED = 'locked';
// la méthode est entièrement ré-écrite ici :) seule la signature reste inchangée
public function setStatus(string $status): void
{
assert(
in_array($status, [self::STATUS_ACTIVE, self::STATUS_INACTIVE, self::STATUS_LOCKED]),
sprintf('Le status %s n\'est pas valide. Les status possibles sont : %s', $status, implode(', ', [self::STATUS_ACTIVE, self::STATUS_INACTIVE, self::STATUS_LOCKED]))
);
$this->status = $status;
}
// la méthode utilise celle de la classe parente, et ajoute un comportement :)
public function getStatus(): string
{
return strtoupper(parent::getStatus());
}
}
$admin = new Admin('Paddington');
$admin->setStatus(Admin::STATUS_LOCKED);
echo $admin->getStatus();
Cette fois-ci, le code fonctionne ! 😀
Nous avons accès à la méthode depuis les classes enfants.
Bon, j’y suis allé un peu fort dans l’exemple. En réalité, ici, les constantes seraient probablement déclarées publiques, les propriétés en protégé, les accesseurs et les mutateurs en public, et les méthodes “internes” (qui ne devraient être utilisées que par la classe elle-même) en privé, ou protégées, selon l’usage. :)
En résumé
En utilisant le mot clé
private
, vous empêchez quiconque autre que la classe courante de manipuler les propriétés et méthodes.En utilisant le mot clé
protected
, vous donnez l'autorisation à la classe courante ainsi qu'à ses enfants de manipuler les propriétés et méthodes :) ; mais pas aux éléments extérieurs à la classe, ou extérieurs aux objets de cette classe.Et comme nous l’avons vu précédemment, avec le mot-clé
public
, il est possible de manipuler la méthode ou la propriété depuis la classe, ses classes enfants et depuis l’extérieur.Autrement dit, pour modifier une propriété depuis un enfant, il faut qu’elle possède la visibilité
public
ouprotected
, ou qu’elle possède un mutateur.
Restreindre la visibilité et la portée des actions d’une classe permet de réduire le champ des possibles pour un utilisateur de cette classe. Moins d’erreurs seront à venir. C’est top ! Si des mécanismes sont prévus pour la restriction, eh bien il existe aussi des mécanismes inverses ! Des mécanismes contraignants à l’ouverture ; plus précisément, à l’extension. Voyons cela dans le prochain chapitre.