web-dev-qa-db-fra.com

PHP __get et __set méthodes magiques

À moins que je ne me trompe complètement, les méthodes __get Et __set Sont supposées permettre la surcharge des → get et set.

Par exemple, les instructions suivantes doivent appeler la méthode __get:

echo $foo->bar;
$var = $foo->bar;

Et ce qui suit devrait utiliser la méthode __set:

$foo->bar = 'test';

Cela ne fonctionnait pas dans mon code et est reproductible avec cet exemple simple:

class foo {

    public $bar;
    public function __get($name) {

        echo "Get:$name";
        return $this->$name;
    }

    public function __set($name, $value) {

        echo "Set:$name to $value";
        $this->$name = $value;
    }
}


$foo = new foo();

echo $foo->bar;
$foo->bar = 'test';

echo "[$foo->bar]";

Cela se traduit uniquement par:

[test]

Le fait de placer quelques appels die() montre que cela ne le frappe pas du tout.

Pour l'instant, je viens de dire de le viser, et j'utilise manuellement __get Là où c'est nécessaire, mais ce n'est pas très dynamique et nécessite de savoir que le code "surchargé" n'est en fait pas appelé à moins d'être appelé spécifiquement. J'aimerais savoir si cela n'est pas censé fonctionner comme j'ai compris qu'il le devrait ou si cela ne fonctionne pas.

Ceci fonctionne sur php 5.3.3.

81
airbear

__get, __set, __call Et __callStatic Sont appelés lorsque la méthode ou la propriété est inaccessible. Votre $bar Est public et donc non inaccessible.

Voir le section sur la surcharge de propriété dans le manuel:

  • __set() est exécuté lors de l'écriture de données dans des propriétés inaccessibles.
  • __get() est utilisé pour lire des données à partir de propriétés inaccessibles.

Les méthodes magiques ne se substituent pas aux accesseurs et aux setters. Ils vous permettent simplement de gérer les appels de méthode ou l'accès aux propriétés qui, sinon, entraîneraient une erreur. En tant que tel, il y a beaucoup plus lié à la gestion des erreurs. Notez également qu’ils sont considérablement plus lents que les appels de méthode getter et setter ou de méthode directe appropriés.

150
Gordon

Je recommanderais d'utiliser un tableau pour stocker toutes les valeurs via __set().

class foo {

    protected $values = array();

    public function __get( $key )
    {
        return $this->values[ $key ];
    }

    public function __set( $key, $value )
    {
        $this->values[ $key ] = $value;
    }

}

De cette façon, vous vous assurez que vous ne pouvez pas accéder aux variables d’une autre manière (notez que $values _ est protégé), pour éviter les collisions.

31
Alex Sawallich

Depuis le manuel PHP :

  • __set () est exécuté lors de l'écriture de données dans des propriétés inaccessibles.
  • __get () est utilisé pour lire des données à partir de propriétés inaccessibles.

Ceci est appelé uniquement en lecture/écriture des propriétés inaccessibles . Cependant, votre propriété est publique, ce qui signifie qu’elle est accessible. Le fait de modifier le modificateur d'accès sur protégé résout le problème.

18
Berry Langerak

Pour développer la réponse de Berry, qui définit le niveau d'accès sur protected permet à __get et __set d'être utilisés avec des propriétés explicitement déclarées (au moins en dehors de la classe) et que la vitesse est considérablement plus lente, je citerai un commentaire tiré d'une autre question. sur ce sujet et justifier de l’utiliser quand même:

Je conviens que __get est plus lent pour une fonction get personnalisée (en faisant la même chose), il s'agit de 0.0124455 du temps pour __get () et ce 0.0024445 est pour un get personnalisé après 10000 boucles. - Melsi le 23 novembre 12 à 22:32 bonne pratique: PHP Méthodes magiques __set et __get

Selon les tests de Melsi, une vitesse considérablement plus lente est environ cinq fois plus lente. Cela est certainement beaucoup plus lent, mais notez également que les tests montrent que vous pouvez toujours accéder à une propriété avec cette méthode 10 000 fois, en comptant le temps de l'itération de la boucle, en environ 1/100 de seconde. Il est considérablement plus lent en comparaison avec les méthodes get et set définies, ce qui est un euphémisme, mais dans le grand schéma des choses, même 5 fois plus lent n'est jamais vraiment lent.

Le temps de calcul de l'opération est encore négligeable et ne mérite pas d'être considéré dans 99% des applications du monde réel. La seule fois que cela devrait vraiment être évité, c'est lorsque vous allez accéder aux propriétés plus de 10 000 fois en une seule demande. Les sites à fort trafic font vraiment quelque chose de mal s'ils ne peuvent pas se permettre de lancer quelques serveurs de plus pour que leurs applications continuent de fonctionner. Une annonce textuelle d'une seule ligne dans le pied d'un site à fort trafic où le taux d'accès devient un problème pourrait probablement payer pour une batterie de 1 000 serveurs avec cette ligne de texte. L'utilisateur final ne va jamais taper du doigt sur le temps nécessaire au chargement de la page, car l'accès aux propriétés de votre application prend un millionième de seconde.

Je dis cela en tant que développeur issu d'un contexte .NET, mais les méthodes invisible et définie pour le consommateur ne sont pas l'invention de .NET. Elles ne sont tout simplement pas des propriétés sans elles, et ces méthodes magiques sont une bénédiction pour les développeurs de PHP, qui peuvent même appeler leur version de propriétés "propriétés". En outre, l'extension Visual Studio pour PHP prend en charge intellisense avec des propriétés protégées, avec cette astuce en tête, je pense. Je penserais avec suffisamment de développeurs utilisant les méthodes magiques __get et __set dans ce Ainsi, les développeurs PHP ajusteraient le temps d’exécution pour répondre aux besoins de la communauté des développeurs.

Edit: En théorie, les propriétés protégées semblaient fonctionner dans la plupart des cas. En pratique, il s'avère que très souvent, vous souhaiterez utiliser vos accesseurs et vos traceurs lors de l'accès à des propriétés dans la définition de classe et les classes étendues. Une meilleure solution est une classe de base et une interface pour l'extension d'autres classes. Vous pouvez donc simplement copier les quelques lignes de code de la classe de base dans la classe d'implémentation. Je fais un peu plus avec la classe de base de mon projet, donc je n'ai pas d'interface à fournir pour le moment, mais voici la définition de classe dépouillé non testée avec la propriété magique get et setting utilisant la réflexion pour supprimer et déplacer les propriétés vers un tableau protégé:

/** Base class with magic property __get() and __set() support for defined properties. */
class Component {
    /** Gets the properties of the class stored after removing the original
     * definitions to trigger magic __get() and __set() methods when accessed. */
    protected $properties = array();

    /** Provides property get support. Add a case for the property name to
     * expand (no break;) or replace (break;) the default get method. When
     * overriding, call parent::__get($name) first and return if not null,
     * then be sure to check that the property is in the overriding class
     * before doing anything, and to implement the default get routine. */
    public function __get($name) {
        $caller = array_shift(debug_backtrace());
        $max_access = ReflectionProperty::IS_PUBLIC;
        if (is_subclass_of($caller['class'], get_class($this)))
            $max_access = ReflectionProperty::IS_PROTECTED;
        if ($caller['class'] == get_class($this))
            $max_access = ReflectionProperty::IS_PRIVATE;
        if (!empty($this->properties[$name])
            && $this->properties[$name]->class == get_class()
            && $this->properties[$name]->access <= $max_access)
            switch ($name) {
                default:
                    return $this->properties[$name]->value;
            }
    }

    /** Provides property set support. Add a case for the property name to
     * expand (no break;) or replace (break;) the default set method. When
     * overriding, call parent::__set($name, $value) first, then be sure to
     * check that the property is in the overriding class before doing anything,
     * and to implement the default set routine. */
    public function __set($name, $value) {
        $caller = array_shift(debug_backtrace());
        $max_access = ReflectionProperty::IS_PUBLIC;
        if (is_subclass_of($caller['class'], get_class($this)))
            $max_access = ReflectionProperty::IS_PROTECTED;
        if ($caller['class'] == get_class($this))
            $max_access = ReflectionProperty::IS_PRIVATE;
        if (!empty($this->properties[$name])
            && $this->properties[$name]->class == get_class()
            && $this->properties[$name]->access <= $max_access)
            switch ($name) {
                default:
                    $this->properties[$name]->value = $value;
            }
    }

    /** Constructor for the Component. Call first when overriding. */
    function __construct() {
        // Removing and moving properties to $properties property for magic
        // __get() and __set() support.
        $reflected_class = new ReflectionClass($this);
        $properties = array();
        foreach ($reflected_class->getProperties() as $property) {
            if ($property->isStatic()) { continue; }
            $properties[$property->name] = (object)array(
                'name' => $property->name, 'value' => $property->value
                , 'access' => $property->getModifier(), 'class' => get_class($this));
            unset($this->{$property->name}); }
        $this->properties = $properties;
    }
}

Mes excuses s'il y a des bugs dans le code.

6
Jason Ensinger

C'est parce que $ bar est une propriété publique.

$foo->bar = 'test';

Il n’est pas nécessaire d’appeler la méthode magique lors de l’exécution de ce qui précède.

Supprimer public $bar; de votre classe devrait corriger cela.

5
Matt Lowden

Utilisez de préférence les méthodes magic set/get avec des méthodes set/get personnalisées prédéfinies, comme dans l'exemple ci-dessous. De cette façon, vous pouvez combiner le meilleur de deux mondes. En termes de vitesse, je suis d’accord pour dire qu’ils sont un peu plus lents, mais pouvez-vous même sentir la différence? L'exemple ci-dessous valide également le tableau de données par rapport à des setters prédéfinis.

"Les méthodes magiques ne se substituent pas aux accesseurs ni aux setters. Elles vous permettent simplement de gérer les appels de méthodes ou l'accès aux propriétés qui, sinon, entraîneraient une erreur."

C'est pourquoi nous devrions utiliser les deux.

Exemple d'article de classe

    /*
    * Item class
    */
class Item{
    private $data = array();

    function __construct($options=""){ //set default to none
        $this->setNewDataClass($options); //calling function
    }

    private function setNewDataClass($options){
        foreach ($options as $key => $value) {
            $method = 'set'.ucfirst($key); //capitalize first letter of the key to preserve camel case convention naming
            if(is_callable(array($this, $method))){  //use seters setMethod() to set value for this data[key];      
                $this->$method($value); //execute the setters function
            }else{
                $this->data[$key] = $value; //create new set data[key] = value without seeters;
            }   
        }
    }

    private function setNameOfTheItem($value){ // no filter
        $this->data['name'] = strtoupper($value); //assign the value
        return $this->data['name']; // return the value - optional
    }

    private function setWeight($value){ //use some kind of filter
        if($value >= "100"){ 
            $value = "this item is too heavy - sorry - exceeded weight of maximum 99 kg [setters filter]";
        }
        $this->data['weight'] = strtoupper($value); //asign the value
        return $this->data['weight']; // return the value - optional
    }

    function __set($key, $value){
        $method = 'set'.ucfirst($key); //capitalize first letter of the key to preserv camell case convention naming
        if(is_callable(array($this, $method))){  //use seters setMethod() to set value for this data[key];      
            $this->$method($value); //execute the seeter function
        }else{
            $this->data[$key] = $value; //create new set data[key] = value without seeters;
        }
    }

    function __get($key){
        return $this->data[$key];
    }

    function dump(){
        var_dump($this);
    }
}

INDEX.PHP

$data = array(
    'nameOfTheItem' => 'tv',
    'weight' => '1000',
    'size' => '10x20x30'
);

$item = new Item($data);
$item->dump();

$item->somethingThatDoNotExists = 0; // this key (key, value) will trigger magic function __set() without any control or check of the input,
$item->weight = 99; // this key will trigger predefined setter function of a class - setWeight($value) - value is valid,
$item->dump();

$item->weight = 111; // this key will trigger predefined setter function of a class - setWeight($value) - value invalid - will generate warning.
$item->dump(); // display object info

SORTIE

object(Item)[1]
  private 'data' => 
    array (size=3)
      'name' => string 'TV' (length=2)
      'weight' => string 'THIS ITEM IS TOO HEAVY - SORRY - EXIDED WEIGHT OF MAXIMUM 99 KG [SETTERS FILTER]' (length=80)
      'size' => string '10x20x30' (length=8)
object(Item)[1]
  private 'data' => 
    array (size=4)
      'name' => string 'TV' (length=2)
      'weight' => string '99' (length=2)
      'size' => string '10x20x30' (length=8)
      'somethingThatDoNotExists' => int 0
object(Item)[1]
  private 'data' => 
    array (size=4)
      'name' => string 'TV' (length=2)
      'weight' => string 'THIS ITEM IS TOO HEAVY - SORRY - EXIDED WEIGHT OF MAXIMUM 99 KG [SETTERS FILTER]' (length=80)
      'size' => string '10x20x30' (length=8)
      'somethingThatDoNotExists' => int 0
1
DevWL

Jeter le public $bar; déclaration et cela devrait fonctionner comme prévu.

0
Alix Axel