Lorsque l’on programme en orienté objet sous php, il est courant d’utiliser des objets comme simple “conteneur” d’information, ce qu’en java on appel les POJO (Plain Old Java Object) mais que je me refuse à appeler POPO en PHP pour des raisons plus qu’évidentes 😀 !
Bref, ces objets ont donc une tripotée d’attributs (souvent privés) auxquels nous avons besoin d’accéder
via des méthodes comme getAttribut1(), setAttribut1(), getAttribut2…
Créer ces méthodes peut être très contraignants et alourdir le code de la classe pour pas grand chose.
Prenons pour exemple une classe utilisateur avec une tripotée d’attribut :
Class User{ private $name,$firstname,$town,$phone,$sex,$hobbies,$avatar,$login,$password,$rank,$state,$fax,$cellphone,$mail; }
Afin d’accéder à ces attributs privés, il vas falloir créer les méthodes get et set pour chacune d’elles soit 2 méthodes par attribut
d’environ 3 lignes pour 14 attributs vous vous retrouvez déjà avec 84 lignes dans la vue simplement pour accéder à vos attributs.
Aussi voiçi un petit snippet permettant de générer dynamiquement ces méthodes (souvent appelées “accesseurs” ou “getter et setter“)
avec une seule méthode de 3 lignes utilisant la fonction magique de php : __call()
Class User{ private $name,$firstname,$town,$phone,$sex,$hobbies,$avatar,$login,$password,$rank,$state,$fax,$cellphone,$mail; function __call($m,$p) { $v = strtolower(substr($m,3)); if (!strncasecmp($m,'get',3))return $this->$v; if (!strncasecmp($m,'set',3)) $this->$v = $p[0]; } }
Vous pourrez alors appeler et définir vos attributs comme si vous aviez tapé les 84 lignes d’accesseurs :
$user = new User(); $user->setName('CARRUESCO'); // Définis $name = CARRUESCO echo $user->getName(); // Affiche $name donc affiche CARRUESCO
L’avantage étant que ce snippet ne s’active que si la méthode n’existe pas déjà, aussi
si vous souhaitez créer une méthode getName dans votre classe qui effectue des opérations sur le nom
avant de le retourner :
public function getName(){ return strtoupper($this->name); }
Cette fonction sera prise en priorité par la classe.
Le revers de la médaille
Et oui, le petit soucis avec ces snippet, c’est que vos attributs ne sont plus vraiment privés,
puisqu’il sont automatiquement accessibles depuis le snippet, dans certains cas
ça peut être chiant, aussi je vous conseille d’ajouter une petite clause pour définir
les attributs qui ne pourront pas être appelés de cette façon:
Class User{ private $name,$firstname,$town,$phone,$sex,$hobbies,$avatar,$login,$password,$rank,$state,$fax,$cellphone,$mail; private $private = array('private','rank','avatar'); function __call($m,$p) { $v = strtolower(substr($m,3)); if (!strncasecmp($m,'get',3) && !in_array($v,$this->private))return $this->$v; if (!strncasecmp($m,'set',3) && !in_array($v,$this->private)) $this->$v = $p[0]; } }
Dans ce code, tous les attributs seront accessibles en get/set, exceptés $private,$rank et $avatar
Si vous aussi vous avez des ptites techniques pour réduire la taille des classes, lâchez vous dans les commentaires 🙂
Edit: Je profite des débats lancés dans les commentaires pour donner mon point de vue 🙂 (c’est qu’ils
ont l’esprit critiques mes idlenautes :p)
Pourquoi ne pas utiliser les fonction __get et __set ?
Pourquoi utiliser deux fonctions d’accession de 4 lignes chacune quand une seule fonction __call suffit ? 🙂
Mais sans même tenir compte de la taille, __get et __set ont, selon mon opinion, un gros inconvénients : il n’est pas possible
de définir la forme d’appel. En effet __get et __set appelleront les attributs uniquement sous la forme
$object->attribut = 'toto'
Quand la fonction __call permettra (entre autres) de créer les appels sous forme de méthode :
$object->setAttribut('toto');
Outre le fait que certains développeurs (dont je fait partie) puissent trouver ça plus harmonieux, c’est aussi plus utile,
l’appel au format méthode permet :
De dissocier un appel d’attribut potentiellement traité d’un appel d’attribut “brut”
De ne pas croire que l’attribut est public (la forme $object->attribut = ‘toto’ pouvant porter à confusion).
Et plus important : de changer l’accessibilité ou le mode de traitement de l’attribut du jour au lendemain sans voir à faire de modifications sur le reste du code
Pourquoi ne pas tous simplement placer tous les attributs en public ?
Cela revient à demander “quel est l’intérêt de mettre les attributs en privés ?”, effectivement il est tout à fait possible de mettre
les attributs en public, cela peut même paraître plus judicieux de prime abord, mais peut causer de gros problèmes lors de la maintenance et
de l’évolutivité du code.
Prenons comme exemple notre classe utilisateur, à l’origine l’attribut
$ville est un string, on effectue aucun traitement dessus, donc on décide de le mettre en public pour y accéder de la manière suivante:
echo $object->ville; //Affiche 'Gradignan';
Cela fonctionne parfaitement sans avoir besoin d’accesseur jusqu’au jour ou l’on décide que $ville sera plutot un id faisant référence à une table de ville.
echo $object->ville; //Affiche 8;
On souhaite donc effectuer un traitement sur la donnée $ville avant affichage pour obtenir son nom et non son id ce qui permettrait de ne pas avoir à modifier
tous les appels sous la forme $object->ville de l’application, et zut ! Ce n’est plus possible ! Car l’attribut est appelé
directement, aucun traitement n’est possible. On est donc obligé de modifier toutes les formes d’appel de l’application pour passer par une méthode, ce qui revient à taper tout les
accesseurs, ou à utiliser des fonctions magiques comme __call, __get et __set.
En résumé, l’utilisation d’accesseurs en temps que “wrapper” sur des attributs privés devrait à mon sens TOUJOURS être utilisée même dans
le cas ou ce wrapper ne fait à l’origine du projet que retourner l’attribut ou le setter sans traitement préalable, wrapper les attributs
permet une meilleure maintenance évolutive sur ces derniers car il est possible d’effectuer un traitement sur la donnée (ce qui n’est pas
le cas sur un appel d’attribut public) sans avoir à modifier l’ensemble du script, d’ou l’utilité des accesseurs en lieu et place des accès direct d’attributs.
Et alors du coup, quel est l’apport par rapport à des attributs publics ?
OK ça fait un peu troll gratuit, mais d’un point de vue conception, ce truc ne présente pas vraiment d’intérêt. Si on souhaite que les attributs se comportent *exactement* comme des données publiques, bah on les déclare en public. Et encore pire : tu es obligé de mettre en place un tableau d’exceptions pour les attributs qui doivent réellement être private. C’est pas un peu un grosse usine à gaz ?
Je crois que __call, __set et __get ont deux utilités :
* faciliter les migrations de PHP 4 vers PHP 5, dans les cas où une méthode/donnée private aurait malencontreusement été utilisée à l’extérieur de sa classe (puisque PHP 4 ne gérait pas la portée). Sur de très gros projets, ce genre de truc peut sauver des vies ;
* intercepter les appels foireux sur des sections de code qui ne doivent absolument pas produire d’erreur de compilation (genre des webservices).
A part ça, je suis convaincu que l’utilisation de ces méthodes magiques révèle une erreur de conception.
Je me suis permis de répondre à cette question directement en bas de l’article pour éviter les post multiples 🙂
Merci beaucoup Idleman pour ce snippet, mais m’abusé-je si je crois que, du coup, les appels aux méthodes getname(), getnAMe(), getNamE(), etc. seront tous valides ?
Tout à fait, et c’est un choix personnel, il est possible de modifier le snippet pour ne prendre en compte que l’exacte nommage
getName(), ce qui, je te l’accorde est plus rigoureux.
Je comprends le choix personnel, à visée “performance” je suppose ?
Mais du coup, personnellement, je redoute l’appel “erroné” à getNAme() qui ne se comporte pas comme la méthode getName() créée ensuite, et sans erreur à l’exécution en plus…
Je suis totalement d’accord avec toi sur ce coup :), c’est juste que comme je bosse seul la plupart du temps, je sais que je ne ferais pas ce genre d’erreur, mais autant ne pas prendre de risque et empecher tout appel possible via autre chose que getName 🙂
Pourquoi s’embêter avec __call() alors qu’il y a __set() et __get() ?
http://php.net/manual/fr/language.oop5.overloading.php
Je me suis permis de répondre à cette question directement en bas de l’article pour éviter les post multiples 🙂
Une solution plus standard est de préfixé les noms de variables privés par un underscore “_”, c’est une convention utilisée par PHP lui-même, PEAR, et aussi Zend :
Oui, c’est juste, personnellement, je trouve ça très laid les “_”, je préfère conserver l’intégrité du nom de mes attributs mais en terme de convention de nommage ta variante est la meilleure 🙂
Question idiote mais obligatoire après un article sur ce sujet : quel est l’intéret d’utiliser des attributs privés pour faire ça ?
J’ai du mal à voir l’intéret par rapport à des attributs publics (pas de contrôle à l’assignation des attributs notamment). Le seul avantage que je vois à cette méthode, c’est que ça permet de gérer facilement des attributs read-only, mais c’est suffisamment rare d’en avoir besoin pour pouvoir ce permettre de le déclarer en private avec seulement le getter associé.
Je me suis permis de répondre à cette question directement en bas de l’article pour éviter les post multiples 🙂
Merci pour ce snippet fort utile!
Pour la dernière partie, ce qui me gêne c’est d’avoir à gérer un tableau supplémentaire pour définir les variables privés, c’est fastidieux et faillible de manière transparente si on fait une faute de frappe.
Du coup je verrais plus de nommer ces variables avec un _ devant, comme $_private par exemple, et de vérifier la présence de ce caractère en début de nom dans _call.
ça c’est assez génial ! Je connaissais l’existence de
__call
, sans jamais avoir cherché à l’utiliser. Merci pour ce snippet !J’ai quelques questions qui me taraudent…
As-tu regardé ce que ça donnait niveau performance par rapport à des getter/setter classiques ?
D’autres part, as-tu regardé
__get
et__set
? (http://php.net/manual/fr/language.oop5.overloading.php) ça couperait ta méthode en 2 (ce qui n’est pas utile), mais peut-être que cela peut jouer sur les performances (si besoin).Pour le get et set je me suis permis de répondre à cette question directement en bas de l’article pour éviter les post multiples 🙂
Pour ce qui est des performances, je me pose la même question que toi :), je suppose que les méthodes magiques ralentissent légèrement les performances, mais je doute que ce soit suffisamment significatif pour contrebalancer l’aspect pratique du developpement.
En règle général plus c’est performant, plus c’est chiant à maintenir, et réciproquement, il faut donc un juste milieu : in medio stat virtus comme dirait l’autre.
Merci de ta réponse ! Ton argumentaire au sujet de
__get
et__set
est particulièrement juste. J’apprécie aussi ton hymne à ne pas utiliser que des variables publiques 😉Pour les performances, c’est aussi mon intuition, si j’ai l’occasion de tester ça sur une classe un peu conséquente un de ces jours, je tâcherai de faire un retour.
Au passage, je fais remarquer que les plugins PHP récents pour Eclipse permettent de générer automatiquement getter et setter, du coup dans ce cadre il est moins coûteux de tout coder.
Ca implique de se remettre sous eclipse, or après avoir goûté a sublim text, il faudra m’arracher un œil avant que je me remettre sous Eclipse ^^, cela dit il doit bien exister un plugin sous sublim pour le faire. Mais la encore l’auto génération ne me satisfait pas totalement dans la durée, ça ajoute du code a la page, gène la lisibilité, implique qu’il faut modifier chaque génération en cas d’un souhait de refactoring des accesseurs, limite les possibilités de personnalisation du format de génération des get/set etc…
Je serai curieux de savoir pourquoi ne pas utiliser directement les fonctions __get et __set ?
L’avantage que j’y vois c’est que le test – if (!strncasecmp($m,’get’,3)) – ne serait plus nécessaire.
L’inconvénient est probablement de ne pas y avoir les mots clés “get” et “set” justement mais accéder directement comme un attribut (obj->attr et plus obj->getAttr()), Mais est-ce que c’est vraiment un problème ?
Doc: http://php.net/manual/fr/language.oop5.overloading.php
Je viens de voir les commentaires et l’edit 🙂
Pourquoi utiliser deux fonctions d’accession de 4 lignes chacune quand une seule fonction __call suffit ? 🙂
Ma réponse à cette question serait: Pour éviter une branche conditionnelle dans ton code.
En effet, deux fonction distinct est (généralement) moins groumant qu’un if 😉
Dans le cas d’une page web où le temps de chargement est très long et donc masque les quelques millisecondes d’exécution il n’y a pas de soucis, mais c’est dommage de prendre de “mauvaise habitude” pour le jour où on est amené à de plus forte contrainte (embarqué, forte charge, etc.) ^^
Dans la plupart des cas, l’utilisation de méthodes magiques N’EST PAS UNE BONNE SOLUTION.
Lorsqu’une classe possède un trop grand nombre d’attributs, il faut revoir la conception. Il est certainement possible de découper cette grosse classe en plusieurs classes, plus petites, chacune ayant une responsabilité plus précise, et faire de la composition.
Par ailleurs, concernant l’aspect contraignant d’écrire les Xetters, tout bon IDE proposera de les générer automatiquement en un clin d’œil.
La présence de vrais Xetters fiabilise le développement et facilite la maintenance future car :
– l’IDE pourra proposer de l’autocomplétion
– On pourra déclarer le type du paramètre passé au Setter directement dans la signature de celui-ci
– On pourra avoir de la PhpDoc indiquant notamment les types attendus/retournés
– En étendant une classe par héritage, on bénéficiera des Xetters déjà présents dans celle-ci. On peut les overrider si besoin.
– Le débug est grandement facilité (breakpoint sur une méthode précise…)
J’insiste sur ce dernier point ! Derrière la magie se cachent souvent des heures de débug à s’arracher les cheveux pour comprendre ce qui n’est pas explicite…
Et pour finir, en termes de performance, les méthodes magiques sont plus coûteuses !
Super astuce, son seul défaut à mon sens : Je ne suis pas tombé dessus avant !!! (je viens de me tapper les getters et setters de 14 membres d’une classe…)
Désolé :), je sais ce que c’est ^^
Petit souci que je viens de découvrir : Si comme moi tu utilises une fonction pour hydrater automatiquement tes objets, la vérification de l’existence de la méthode pose problème…
Ma fonction hydrate :
public function hydrate($param) {
foreach ($param as $key => $value){
echo $key.’ ‘.$value.”;
$method = ‘set’.ucfirst($key);
echo $method.”;
//if (method_exists($this, $method)){
echo $method.’ ‘.$value;
$this->$method($value);
//}
}
Je n’hydrate pas, je ne trouve pas le principe très élégant quand il s’agit de reneigner un objet je préfere passer
par les accesseurs que par un tableau un peu batard.
Cela dit si tu souhaite hydrater tu peux contourner le problème en faisant un test sur l’attribut (qui lui est bien réel) et non sur la méthode :
public function hydrate($param) {
foreach ($param as $key => $value){
if (property_exists($this, $key)){
$method = ‘set’.ucfirst($key);
$this->$method($value);
}
}
}
Tu devrais, hydrater c’est bon pour la peau…
Pas faux, bien vu pour le test sur l’attribut…
Hello,
Je suis dans le même cas, j’avais fait tous mes getter et setter.
J’ai remplacé tout ça par la fonction magique et ça marche à la perfection.
Le seul soucis que j’ai obtenu est que l’on peut faire des get et des set sur tout et n’importe quoi (même sur des variable qui ne sont pas définies)… J’ai alors ajouté une condition basée sur la fonction get_class_vars afin de vérifier que ma variable existe bien (permet d’identifier les fautes de frappe ou de construire le code plus proprement).
En tout cas, merci Idle, encore un fois 🙂 !!!
En fait, tu proposes cette solution car tu ne veux pas créer tous les setter/getter. C’est juste une question flemmardise 🙂
Personnellement, j’y vois plusieurs problèmes :
– Pas d’auto-complétion de code dans mon IDE à moins d’ajouter des doc tags @method
– Très mauvaises performances si l’objet est utilisé intensivement
– Complexité d’analyse en cas d’anomalie
Pour ma part, comme j’utilise Zend Studio, ben … heu … clic droit sur la propriété > Source > Generate Getters and Setters 🙂
Mais quand même, j’utilise parfois __set en regardant s’il existe une méthode “setXxxx” (juste un “ucfirst”), et si elle n’existe pas, j’accède à la propriété. Ainsi, lorsque je veux surcharger l’écriture d’une propriété, je définie un setter pour cette propriété, et je fais mes traitements. La plupart du temps, ce n’est pas pour accéder directement à une propriété nommée, mais à un tableau qui regroupe toutes les données que je traite et dont le nombre n’est pas défini. Avec un nombre fini d’élément, je fais :
– propriété “public” : BEAUCOUP plus performant
– en cas de surcharge, je passe en propriété en “protected”, je crée un setter et j’exploite le “__set”
Voilà, my 50 cents ….
Si je peux me permettre dans le __call sur le setXxxx tu peux faire un return $this.
Du coup tu pourras chainer les setXxxx
$obj->setTitle(‘coucou’)->setContent(‘Je suis le contenu’);
On peux aussi utiliser un tableau ce qui est potentiellement interessent sur une méthode __toString().
On peut du coup facilement renvoyer une serialisation JSON (json_encode) ou hydrater le tableau avec un méthode.
Même si un tableau consomme plus de ram.
Pareil avec __invoke dans certain cas ca peut être super interessent de s’en servir de raccourci.
$obj(‘title’, ‘coucou’);
Ou encore pour renvoyer un object qui est lui même embarqué dans la classe.
Ajout interessant merci pour la remarque 🙂
Je sais que le sujet date… Mais la question reste d’actualité 😉
Que pensez-vous de :
public function getProperty($property)
{
return isset($this->$property) ? $this->$property : false;
}
public function setProperty($property, $value)
{
isset($this->$property) ? $this->$property = $value : false;
}
ce qui permet de définir les get/set impliquant un traitement des données.
C’est une solution technique qui se tient, après c’est plus une question de nomination dans le code, personnellement ça m’ennuierais de passer un attribut en chaine ne serait ce que parce que la coloration syntaxique (jaune pour les chaines chez moi) ne me permettrais plus de distinguer rapidement les attributs.
Après je chipotte ^^