PHP __get e __set metodi magici


85

A meno che non mi sbagli completamente, i metodi __gete __setdovrebbero consentire il sovraccarico di → gete set.

Ad esempio, le seguenti istruzioni dovrebbero richiamare il __getmetodo:

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

E quanto segue dovrebbe utilizzare il __setmetodo:

$foo->bar = 'test';

Questo non funzionava nel mio codice ed è riproducibile con questo semplice esempio:

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]";

Questo si traduce solo in:

[test]

Mettere alcune die()chiamate lì mostra che non sta arrivando affatto.

Per ora, ho appena detto di fottere e sto usando manualmente __getdove è necessario per ora, ma non è molto dinamico e richiede la conoscenza che il codice "sovraccarico" in realtà non viene chiamato se non specificamente chiamato. Mi piacerebbe sapere se questo non dovrebbe funzionare nel modo in cui ho capito che dovrebbe o perché non funziona.

Questo sta funzionando php 5.3.3.

Risposte:


164

__get, __set, __callE __callStaticvengono richiamati quando il metodo o proprietà non è accessibile. Il tuo $barè pubblico e quindi non inaccessibile.

Vedere la sezione sul sovraccarico delle proprietà nel manuale:

  • __set() viene eseguito durante la scrittura di dati su proprietà inaccessibili.
  • __get() viene utilizzato per leggere dati da proprietà inaccessibili.

I metodi magici non sono sostituti di getter e setter. Consentono solo di gestire le chiamate ai metodi o l'accesso alle proprietà che altrimenti risulterebbero in un errore. In quanto tali, ci sono molto di più legati alla gestione degli errori. Si noti inoltre che sono notevolmente più lenti rispetto all'utilizzo di getter e setter appropriati o chiamate al metodo diretto.


5
Per approfondire questo, basta rimuovere "public $ bar" in modo che la proprietà non esista più e funzionerà come un incantesimo.
Steffen Müller

Grazie. Questo è quello che ottengo per non aver letto a fondo il manuale e invece guardando alcuni esempi del blog di ragazzi;) Tuttavia, sto ancora aspettando un vero sovraccarico dell'operatore in PHP.
Airbear

@airbear c'è un vecchio pacchetto PECL di Sara Golemon che ti permetterebbe di sovraccaricare gli operatori . Non so quanto sia compatibile con PHP. Attuale però.
Gordon

1
@Pooya Questo perché nodenon è una proprietà di $ foo ma una proprietà di doesNotExist. Quindi, a meno che 'doesNotExist' non sia un oggetto (che implementa __set o ha una proprietà pubblica chiamata node) non funzionerà.
Tivie

1
@Allen sì, lo fa.
Gordon

34

Consiglierei di utilizzare un array per memorizzare tutti i valori tramite __set().

class foo {

    protected $values = array();

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

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

}

In questo modo ti assicuri di non poter accedere alle variabili in un altro modo (nota che $valuesè protetto), per evitare collisioni.


19

Dal manuale PHP :

  • __set () viene eseguito durante la scrittura di dati in proprietà inaccessibili.
  • __get () viene utilizzato per leggere dati da proprietà inaccessibili.

Viene chiamato solo in lettura / scrittura di proprietà inaccessibili . La tua proprietà tuttavia è pubblica, il che significa che è accessibile. La modifica del modificatore di accesso in protected risolve il problema.


7

Per espandere la risposta di Berry, l'impostazione del livello di accesso su protected consente a __get e __set di essere utilizzati con proprietà dichiarate esplicitamente (quando si accede al di fuori della classe, almeno) e la velocità è notevolmente più lenta, citerò un commento da un'altra domanda su questo argomento e proponi comunque di utilizzarlo:

Sono d'accordo che __get è più lento per una funzione get personalizzata (facendo le stesse cose), questo è 0,0124455 il tempo per __get () e questo 0,0024445 è per get () personalizzato dopo 10000 cicli. - Melsi 23 novembre 12 alle 22:32 Best practice: PHP Magic Methods __set e __get

Secondo i test di Melsi, considerevolmente più lento è circa 5 volte più lento. Questo è decisamente molto più lento, ma si noti anche che i test mostrano che è ancora possibile accedere a una proprietà con questo metodo 10.000 volte, contando il tempo per l'iterazione del ciclo, in circa 1/100 di secondo. È considerevolmente più lento rispetto agli effettivi metodi get e set definiti, e questo è un eufemismo, ma nel grande schema delle cose, anche 5 volte più lento non è mai effettivamente lento.

Il tempo di elaborazione dell'operazione è ancora trascurabile e non vale la pena considerarlo nel 99% delle applicazioni del mondo reale. L'unico momento in cui dovrebbe essere davvero evitato è quando accederai effettivamente alle proprietà più di 10.000 volte in una singola richiesta. I siti ad alto traffico stanno facendo qualcosa di veramente sbagliato se non possono permettersi di lanciare alcuni server in più per mantenere in esecuzione le loro applicazioni. Un annuncio di testo di una sola riga nel piè di pagina di un sito ad alto traffico in cui la velocità di accesso diventa un problema potrebbe probabilmente pagare per una farm di 1.000 server con quella riga di testo. L'utente finale non si batterà mai le dita chiedendosi perché la pagina impiega così tanto tempo a caricarsi perché l'accesso alle proprietà dell'applicazione richiede un milionesimo di secondo.

Dico questo parlando come sviluppatore proveniente da un background in .NET, ma i metodi di acquisizione e impostazione invisibili per il consumatore non sono un'invenzione di .NET. Semplicemente non sono proprietà senza di loro, e questi metodi magici sono la grazia salvifica degli sviluppatori di PHP anche per chiamare la loro versione delle proprietà "proprietà". Inoltre, l'estensione Visual Studio per PHP supporta intellisense con proprietà protette, con quel trucco in mente, penso. Penso che con un numero sufficiente di sviluppatori che utilizzano i metodi magic __get e __set in questo modo, gli sviluppatori PHP regolerebbero il tempo di esecuzione per soddisfare la comunità degli sviluppatori.

Modifica: in teoria, le proprietà protette sembravano funzionare nella maggior parte delle situazioni. In pratica, risulta che molte volte vorrai usare i tuoi getter e setter quando accedi alle proprietà all'interno della definizione di classe e delle classi estese. Una soluzione migliore è una classe di base e un'interfaccia per l'estensione di altre classi, quindi puoi semplicemente copiare le poche righe di codice dalla classe di base nella classe di implementazione. Sto facendo un po 'di più con la classe base del mio progetto, quindi non ho un'interfaccia da fornire in questo momento, ma ecco la definizione di classe ridotta non testata con proprietà magiche che ottengono e impostano usando la riflessione per rimuovere e spostare le proprietà in un array protetto:

/** 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;
    }
}

Mi scuso se ci sono bug nel codice.


Ho trovato un problema su 'access' => $ property-> getModifier ()
macki

5

È perché $ bar è una proprietà pubblica.

$foo->bar = 'test';

Non è necessario chiamare il metodo magico quando si esegue quanto sopra.

L'eliminazione public $bar;dalla classe dovrebbe correggere questo problema.


1

Utilizzare al meglio i metodi magic set / get con metodi set / get personalizzati predefiniti come nell'esempio seguente. In questo modo puoi combinare il meglio di due mondi. In termini di velocità, sono d'accordo sul fatto che siano un po 'più lenti, ma puoi anche sentire la differenza. L'esempio seguente convalida anche l'array di dati rispetto a setter predefiniti.

"I metodi magici non sostituiscono getter e setter. Consentono solo di gestire le chiamate ai metodi o l'accesso alle proprietà che altrimenti risulterebbero in un errore."

Questo è il motivo per cui dovremmo usarli entrambi.

ESEMPIO DI ARTICOLO DI 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

PRODUZIONE

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

0

Elimina la public $bar;dichiarazione e dovrebbe funzionare come previsto.


-6

Intenta con:

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

_SET($k,$v){
 return $this->$k = $v;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.