web-dev-qa-db-fra.com

Meilleure pratique: PHP Méthodes magiques __set et __get

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.

120
rfc1484

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:

  • c'est plus lent (que les getters/setters)
  • il y a pas de complétion automatique (et c'est en fait un problème majeur), et gestion du type par le IDE pour le refactoring et le code- parcourir (sous Zend Studio/PhpStorm cela peut être géré avec l'annotation phpdoc @property mais cela nécessite de les maintenir: assez pénible)
  • le documentation (phpdoc) ne correspond pas à la façon dont votre code est censé être utilisé, et regarder votre classe n'apporte pas beaucoup de réponses. Ceci est déroutant.
  • ajouté après édition: avoir des getters pour les propriétés est plus cohérent avec les méthodes "réelles"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.

151
Matthieu Napoli

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é.

113
vbence

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 #.

82
user187291

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

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.

0
Adam Arold