Méthodes magiques PHP __get et __set

85

À moins que je ne me trompe complètement, les méthodes __getet __setsont censées permettre de surcharger les → getet set.

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

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

Et ce qui suit devrait utiliser la __setméthode:

$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]

Mettre quelques die()appels là-dedans montre que cela ne touche pas du tout.

Pour l'instant, je viens de dire visser, et j'utilise manuellement __getlà où c'est nécessaire pour l'instant, 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 spécifiquement appelé. J'aimerais savoir si cela n'est pas censé fonctionner comme je l'ai compris ou pourquoi cela ne fonctionne pas.

Cela continue php 5.3.3.

airbear
la source

Réponses:

164

__get, __set, __callEt __callStaticsont invoquées lorsque la méthode ou la propriété est inaccessible. Votre $barest public et donc pas inaccessible.

Voir la 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 remplacent pas les getters et les setters. Ils vous permettent simplement de gérer les appels de méthode ou l'accès aux propriétés qui entraîneraient autrement 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 d'utiliser un getter et un setter appropriés ou des appels de méthode directs.

Gordon
la source
5
Pour développer cela, supprimez simplement "public $ bar" pour que la propriété n'existe plus et qu'elle fonctionnera comme un charme.
Steffen Müller
Merci. C'est ce que j'obtiens en ne lisant pas complètement le manuel et en regardant à la place un exemple de blog de gars;) Toujours en attente d'une véritable surcharge d'opérateurs en PHP, cependant.
airbear
@airbear il existe un ancien package PECL de Sara Golemon qui vous permettrait de surcharger les opérateurs . Je ne sais pas à quel point il est compatible avec PHP.current.
Gordon
1
@Pooya C'est parce que ce noden'est pas une propriété de $ foo mais une propriété de doesNotExist. Donc, à moins que 'doesNotExist' soit un objet (qui implémente __set ou possède une propriété publique appelée node), cela ne fonctionnera pas.
Tivie
1
@Allen oui, c'est vrai.
Gordon
34

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 $valuesc'est protégé), pour éviter les collisions.

Fidi
la source
19

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 n'est appelé que lors de la lecture / écriture de propriétés inaccessibles . Votre propriété est cependant publique, ce qui signifie qu'elle est accessible. La modification du modificateur d'accès en protégé résout le problème.

Berry Langerak
la source
7

Pour développer la réponse de Berry, le fait de définir le niveau d'accès sur protected permet d'utiliser __get et __set avec des propriétés explicitement déclarées (au moins en cas d'accès en dehors de la classe) et la vitesse étant considérablement plus lente, je vais citer un commentaire d'une autre question sur ce sujet et faites un cas pour l'utiliser quand même:

Je suis d'accord que __get est plus lent à une fonction get personnalisée (faisant les mêmes choses), c'est 0,0124455 le temps pour __get () et ce 0,0024445 est pour custom get () après 10000 boucles. - Melsi 23 novembre 12 à 22:32 Meilleures pratiques: méthodes PHP Magic __set et __get

Selon les tests de Melsi, considérablement plus lent est environ 5 fois plus lent. C'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 10000 fois, en comptant le temps pour 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, et c'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 vaut pas la peine d'être pris en compte dans 99% des applications du monde réel. Le seul moment où cela devrait vraiment être évité, c'est lorsque vous allez réellement accéder aux propriétés plus de 10000 fois en une seule demande. Les sites à fort trafic font quelque chose de vraiment mal s'ils ne peuvent pas se permettre de lancer quelques serveurs supplémentaires pour maintenir leurs applications en marche. Une annonce textuelle sur une seule ligne au 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 se demandera jamais ce qui prend si longtemps à charger 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'une expérience dans .NET, mais les méthodes get and set invisibles pour le consommateur ne sont pas l'invention de .NET. Ce ne sont tout simplement pas des propriétés sans elles, et ces méthodes magiques sont la grâce salvatrice du développeur PHP pour même appeler leur version de propriétés "propriétés". De plus, l'extension Visual Studio pour PHP prend en charge intellisense avec des propriétés protégées, avec cette astuce à l'esprit, je pense. Je pense qu'avec suffisamment de développeurs utilisant les méthodes magiques __get et __set de cette façon, les développeurs PHP ajusteraient le temps d'exécution pour répondre à la communauté des développeurs.

Edit: En théorie, les propriétés protégées semblaient fonctionner dans la plupart des situations. En pratique, il s'avère que vous voudrez souvent utiliser vos getters et setters lors de l'accès aux 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ée non testée avec la propriété magique obtenant et définissant en 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 bogues dans le code.

Jason Ensinger
la source
J'ai trouvé un problème sur le 'access' => $ property-> getModifier ()
macki
5

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.

La suppression public $bar;de votre classe devrait corriger cela.

Matt Lowden
la source
1

Utilisez au mieux 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 conviens 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 paramètres prédéfinis.

«Les méthodes magiques ne remplacent pas les getters et les setters. Elles vous permettent simplement de gérer les appels de méthode ou l'accès aux propriétés qui, autrement, 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

PRODUCTION

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
DevWL
la source
0

Supprimez la public $bar;déclaration et cela devrait fonctionner comme prévu.

Alix Axel
la source
-6

Intenta con:

__GET($k){
 return $this->$k;
}

_SET($k,$v){
 return $this->$k = $v;
}
charly
la source