124 votes

Meilleure pratique: méthodes magiques PHP __set et __get

Double Possible:
La Magie des Méthodes les Meilleures pratiques en PHP?

Ce sont des exemples simples, mais imaginez que vous avez plus de propriétés que deux dans votre classe.

Quelle serait la meilleure pratique?

a) à l'Aide de __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) à l'Aide du traditionnel setters et getters

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, à l'époque, il était très populaire à l'utilisation de PHP fonctions magiques (__get, __call, etc.). Il n'y a rien de mal avec eux à partir d'un premier abord, mais en réalité, ils sont vraiment dangereux. Ils font Api incertaine, l'auto-complétion impossible et surtout, ils sont lents. Le cas d'utilisation a été de hack PHP pour faire des choses dont il n'a pas voulu. Et cela a fonctionné. Mais pris de mauvaises choses se produisent.

Mais j'aimerais entendre d'autres opinions à ce sujet.

165voto

Matthieu Napoli Points 9453

J'ai été exactement dans votre cas dans le passé. Et je suis pour les méthodes magiques.

C'était une erreur, la dernière partie de votre question, dit :

  • c'est plus lent (de getters/setters)
  • il n'y a pas d'auto-complétion (et c'est un problème important en fait), et le type de gestion par l'IDE pour la refactorisation de code et de navigation (sous Zend Studio/PhpStorm cela peut être manipulé avec le @property phpdoc annotation, mais qui nécessite de maintenir entre eux: pas de douleur)
  • la documentation (phpdoc) ne correspond pas à la façon dont votre code est censé être utilisé, et en regardant votre classe n'apporte pas beaucoup de réponses. Ceci est source de confusion.
  • ajouté après edit: avoir des accesseurs pour les propriétés est plus compatible avec le "réel" les méthodes d' où l' getXXX() n'est pas seulement le retour d'une propriété privée, mais en le faisant logique réelle. Vous avez le même nom. Par exemple, vous avez $user->getName() (revenus de la propriété privée) et $user->getToken($key) (calculée). Le jour de votre getter devient plus qu'un getter et a besoin de faire un peu de logique, tout est cohérent.

Enfin, et c'est le plus gros problème de l'OMI : c'est de la magie. Et la magie est très très mauvais, parce que vous avez de savoir comment la magie fonctionne à 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 accesseurs et mutateurs sont une douleur à écrire (je les déteste), mais ils en valent la peine.

125voto

vbence Points 10528

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 possède des 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 au moment de l'exécution, vous avez donc besoin des méthodes magiques pour plus de commodité.

90voto

user187291 Points 28951

J'utilise __get (et les propriétés publiques) autant que possible, car ils rendent le code plus lisible. Comparer:

ce code sans équivoque dit ce que je fais:

echo $user->name;

ce code permet de me sentir stupide, je n'aime pas:

function getName() { return $this->_name; }
....

echo $user->getName();

La différence entre les deux est particulièrement évident 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() . " ";

Si "$a->b" devrait vraiment faire quelque chose ou tout simplement retourner une valeur est de la responsabilité du destinataire de l'appel. Pour l'appelant, "$user->name" et "$utilisateur->accountBalance" doivent se ressemblent, bien que celle-ci peut impliquer des calculs compliqués. Dans mes classes de données j'utilise la suite des petites méthode:

 function __get($p) { 
      $m = "get_$p";
      if(method_exists($this, $m)) return $this->$m();
      user_error("undefined property $p");
 }

quand quelqu'un vous appelle "$obj->xxx" et la classe "get_xxx" défini, cette méthode sera implicitement appelé. Ainsi, vous pouvez définir un getter si vous en avez besoin, tout en gardant votre interface uniforme et transparente. Comme un bonus supplémentaire cela fournit un moyen élégant pour memoize 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

Bottom line: php est un langage de script dynamique, l'utiliser de cette façon, ne pas faire semblant que vous êtes en train de faire du Java ou du C#.

3voto

Carlos Campderrós Points 6055

Je fais un mélange de réponse d'Edem et de votre deuxième code. De cette façon, j'ai les 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 découvrir des fautes de frappe: $foo->naem au lieu de $foo->name ), propriétés en lecture seule et propriétés du composé.

 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");
            }
        }
    }
}
 

2voto

Adam Arold Points 11130

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 ne devez écrire de nouvelles méthodes que s'il existe un cas particulier pour une variable membre.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X