php : propriété publique en lecture seule

Il est assez courant de rencontrer des propriétés publiques en lecture seule quand on manipule des objets DOM en javascript par exemple. Voici une piste, sans doute pas toute neuve, pour disposer de ce type d'accès à une donnée en PHP.

On peut utiliser le modèle classique de getter / setter, en évitant de définir le setter évidemment : nous obtenons la même protection de la donnée mais pas la même interface d'utilisation ; et quand on a pris certaines habitudes, on aimerait ne pas en changer quand on passe d'un langage à l'autre. Pour simuler une propriété publique en lecture seule, on peut dés lors passer par les « méthodes magiques » des classes PHP :

class ReadOnly {
    // propriété "publique" en lecture seule
    private $readOnlyProp = 'foo';   
    // setter « magique »
    public function __set($name, $value) {
        if ($name == 'readOnlyProp') {
            trigger_error($name.' is readonly property', E_USER_ERROR);
        }
    }
    // getter « magique »
    public function __get($name) {
        if ($name == 'readOnlyProp') {
            return $this->readOnlyProp;
        }
    }
}
Définition d'une propriété « readOnlyProp » en lecture seule dans une classe ReadOnly.

Nous obtenons alors le comportement suivant :

$instance = new ReadOnly();
echo $instance->readOnlyProp; // affiche 'foo'
$instance->readOnlyProp = 'bar'; // Fatal error
Utilisation de la propriété en lecture seule.

Imaginons maintenant que cette propriété doit pouvoir être assignée de l'extérieur de la classe, mais uniquement dans certaines conditions. Nous prendrons pour analogie la propriété parentNode de l'interface DOM node : quand on insère un noeud enfant, il faut bien pourvoir mettre à jour sa propriété parentNode depuis « l'extérieur ». Comment faire ?

Nous pouvons y arriver avec le niveau d'accès protégé, qui permet de définir des membres de classe accessibles non seulement au sein de la classe (et des classes filles) mais aussi depuis l'extérieur si l'appel se fait à partir d'une classe de même type. Ce sera le cas pour tout objet pouvant avoir des « enfants » du même type que lui :

class Node {
    // propriété publique en lecture seule
    private $parentNode;
    // Liste des enfants
    private $childNodes = array();
    // setter magique
    public function __set($name, $value) {
        if ($name == 'parentNode') {
            trigger_error($name.' is readonly property', E_USER_ERROR);
        }
    }
    // getter magique
    public function __get($name) {
        if ($name == 'parentNode') {
            return $this->parentNode;
        }
    }
    // Ajouter un enfant et renseigné la propriété parentNode
    public function appendChild($node) {
        if ($this->instanceof Node) {
            $this->childNodes[] = $node;
            $node->setParentNode($this);
        } else {
            trigger_error('Wrong type', E_USER_ERROR);
        }
    }
    // Permettre la définition de parentNode
    protected function setParentNode($node) {
        $this->parentNode = $node;
    }
}
Exemple de propriété en lecture seule mais éditable sous certaines conditions.

La surcharge de la méthode magique __set n'est pas absolument indispensable (mais elle est préférable) : en son absence, une tentative d'assignation d'une propriété privée génèrera une erreur fatale « Cannot access private property ». L'utilisation de ces méthodes magiques semble par ailleurs plus consommatrice de ressources que des méthodes plus « directes » comme un couple getter/setter par exemple ; ce genre de technique est donc à utiliser avec parcimonie.

ACHOUR, Mehdi ; BETZ, Friedhelm ; DOVGAL, Antony, et al.. Manuel PHP. PHP.net, . Surcharge magique