Duplicate possible:
Les méthodes magiques sont-elles les meilleures pratiques en PHP?
Ce sont des exemples simples, mais imaginez que vous ayez plus de propriétés que deux dans votre classe.
Quelle serait la meilleure pratique?
a) Utiliser __get et __set
class MyClass {
private $firstField;
private $secondField;
public function __get($property) {
if (property_exists($this, $property)) {
return $this->$property;
}
}
public function __set($property, $value) {
if (property_exists($this, $property)) {
$this->$property = $value;
}
}
}
$myClass = new MyClass();
$myClass->firstField = "This is a foo line";
$myClass->secondField = "This is a bar line";
echo $myClass->firstField;
echo $myClass->secondField;
/* Output:
This is a foo line
This is a bar line
*/
b) Utilisation des setters et getters traditionnels
class MyClass {
private $firstField;
private $secondField;
public function getFirstField() {
return $this->firstField;
}
public function setFirstField($firstField) {
$this->firstField = $firstField;
}
public function getSecondField() {
return $this->secondField;
}
public function setSecondField($secondField) {
$this->secondField = $secondField;
}
}
$myClass = new MyClass();
$myClass->setFirstField("This is a foo line");
$myClass->setSecondField("This is a bar line");
echo $myClass->getFirstField();
echo $myClass->getSecondField();
/* Output:
This is a foo line
This is a bar line
*/
Dans cet article: http://blog.webspecies.co.uk/2011-05-23/the-new-era-of-php-frameworks.html
L'auteur affirme que l'utilisation de méthodes magiques n'est pas une bonne idée:
Tout d’abord, il était très courant d’utiliser les fonctions magiques de PHP (__get, __call, etc.). Il n’ya rien de mal avec eux dès le premier regard, mais ils sont en réalité très dangereux. Ils rendent les API peu claires, l'auto-complétion est impossible et, surtout, ils sont lents. Le cas d’utilisation pour eux était de bidouiller PHP) pour faire des choses qu’il ne voulait pas. Et cela a fonctionné.
Mais j'aimerais entendre plus d'opinions à ce sujet.
J'ai été exactement dans votre cas par le passé. Et je suis allé pour les méthodes magiques.
C'était une erreur, la dernière partie de votre question dit tout:
@property
mais cela nécessite de les maintenir: assez pénible)getXXX()
ne retourne pas seulement une propriété privée, mais fait de la logique réelle. Vous avez le même nom. Par exemple, vous avez $user->getName()
(retourne une propriété privée) et $user->getToken($key)
(calculée). Le jour où votre getter devient plus qu'un getter et doit faire preuve de logique, tout est toujours cohérent.Enfin, et c’est le plus gros problème IMO: c’est magique. Et la magie est très très mauvaise, car vous devez savoir comment la magie fonctionne pour l'utiliser correctement. C'est un problème que j'ai rencontré dans une équipe: tout le monde doit comprendre la magie, pas seulement vous.
Les Getters et les setters sont pénibles à écrire (je les déteste) mais ils en valent la peine.
Vous n'avez besoin d'utiliser la magie que si l'objet est vraiment "magique". Si vous avez un objet classique avec des propriétés fixes, utilisez des séparateurs et des getters, ils fonctionnent bien.
Si votre objet a propriétés dynamiques par exemple, il fait partie d'une couche d'abstraction de base de données et que ses paramètres sont définis à l'exécution, vous avez donc besoin des méthodes magiques pour plus de commodité.
J'utilise __get
(et les propriétés publiques) autant que possible, car ils rendent le code beaucoup plus lisible. Comparer:
ce code dit sans équivoque ce que je fais:
echo $user->name;
ce code me fait me sentir stupide, ce que je n'aime pas:
function getName() { return $this->_name; }
....
echo $user->getName();
La différence entre les deux est particulièrement évidente lorsque vous accédez à plusieurs propriétés à la fois.
echo "
Dear $user->firstName $user->lastName!
Your purchase:
$product->name $product->count x $product->price
"
et
echo "
Dear " . $user->getFirstName() . " " . $user->getLastName() . "
Your purchase:
" . $product->getName() . " " . $product->getCount() . " x " . $product->getPrice() . " ";
Qu'il s'agisse $a->b
devrait vraiment faire quelque chose ou simplement renvoyer une valeur est la responsabilité de l'appelé. Pour l'appelant, $user->name
et $user->accountBalance
devrait être identique, bien que ce dernier puisse impliquer des calculs compliqués. J'utilise la petite méthode suivante dans mes classes de données:
function __get($p) {
$m = "get_$p";
if(method_exists($this, $m)) return $this->$m();
user_error("undefined property $p");
}
quand quelqu'un appelle $obj->xxx
et la classe a get_xxx
définie, cette méthode sera appelée implicitement. Vous pouvez donc définir un getter si vous en avez besoin, tout en maintenant votre interface uniforme et transparente. En prime, cela constitue un moyen élégant de mémoriser des calculs:
function get_accountBalance() {
$result = <...complex stuff...>
// since we cache the result in a public property, the getter will be called only once
$this->accountBalance = $result;
}
....
echo $user->accountBalance; // calculate the value
....
echo $user->accountBalance; // use the cached value
Conclusion: php est un langage de script dynamique, utilisez-le ainsi, ne prétendez pas que vous faites Java ou C #.
Je fais un mélange de réponse d'Edem et de votre deuxième code. De cette façon, je bénéficie des avantages du getter/setters commun (complétion du code dans votre IDE), de la facilité de codage si je le souhaite, des exceptions dues à des propriétés inexistantes (idéal pour la découverte de fautes de frappe: $foo->naem
au lieu de $foo->name
), ne lit que les propriétés et les propriétés composées.
class Foo
{
private $_bar;
private $_baz;
public function getBar()
{
return $this->_bar;
}
public function setBar($value)
{
$this->_bar = $value;
}
public function getBaz()
{
return $this->_baz;
}
public function getBarBaz()
{
return $this->_bar . ' ' . $this->_baz;
}
public function __get($var)
{
$func = 'get'.$var;
if (method_exists($this, $func))
{
return $this->$func();
} else {
throw new InexistentPropertyException("Inexistent property: $var");
}
}
public function __set($var, $value)
{
$func = 'set'.$var;
if (method_exists($this, $func))
{
$this->$func($value);
} else {
if (method_exists($this, 'get'.$var))
{
throw new ReadOnlyException("property $var is read-only");
} else {
throw new InexistentPropertyException("Inexistent property: $var");
}
}
}
}
Je vote pour une troisième solution. J'utilise ceci dans mes projets et Symfony utilise quelque chose comme ceci aussi:
public function __call($val, $x) {
if(substr($val, 0, 3) == 'get') {
$varname = strtolower(substr($val, 3));
}
else {
throw new Exception('Bad method.', 500);
}
if(property_exists('Yourclass', $varname)) {
return $this->$varname;
} else {
throw new Exception('Property does not exist: '.$varname, 500);
}
}
De cette façon, vous avez des getters automatisés (vous pouvez aussi écrire des setters), et vous n'avez qu'à écrire de nouvelles méthodes s'il existe un cas spécial pour une variable membre.