PHP __get und __set magische Methoden

85

Es sei denn , ich völlig täuscht, die __getund __setwerden Verfahren soll ermöglichen , von der Überlastung → getund set.

Die folgenden Anweisungen sollten beispielsweise die __getMethode aufrufen :

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

Und die folgenden sollten die __setMethode verwenden:

$foo->bar = 'test';

Dies funktionierte in meinem Code nicht und ist mit diesem einfachen Beispiel reproduzierbar:

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

Dies führt nur zu:

[test]

Wenn Sie dort einige die()Anrufe tätigen, wird angezeigt, dass es überhaupt nicht funktioniert.

Im Moment habe ich nur gesagt, scheiß drauf und verwende manuell, __getwo es jetzt gebraucht wird, aber das ist nicht sehr dynamisch und erfordert das Wissen, dass der "überladene" Code tatsächlich nicht aufgerufen wird, es sei denn, er wird speziell aufgerufen. Ich würde gerne wissen, ob dies entweder nicht so funktionieren soll, wie ich es verstanden habe, oder warum dies nicht funktioniert.

Das läuft weiter php 5.3.3.

Luftbär
quelle

Antworten:

162

__get, __set, __callUnd __callStaticwerden aufgerufen , wenn die Methode oder Eigenschaft nicht zugänglich ist. Ihr $barist öffentlich und daher nicht unzugänglich.

Siehe den Abschnitt über das Überladen von Eigenschaften im Handbuch:

  • __set() wird ausgeführt, wenn Daten in unzugängliche Eigenschaften geschrieben werden.
  • __get() wird zum Lesen von Daten aus unzugänglichen Eigenschaften verwendet.

Die magischen Methoden sind kein Ersatz für Getter und Setter. Sie ermöglichen Ihnen lediglich die Verarbeitung von Methodenaufrufen oder den Zugriff auf Eigenschaften, die sonst zu einem Fehler führen würden. Daher gibt es viel mehr mit der Fehlerbehandlung zu tun. Beachten Sie auch, dass sie erheblich langsamer sind als die Verwendung geeigneter Getter- und Setter- oder direkter Methodenaufrufe.

Gordon
quelle
5
Um dies näher zu erläutern, entfernen Sie einfach "public $ bar", damit die Eigenschaft nicht mehr existiert und wie ein Zauber wirkt.
Steffen Müller
Vielen Dank. Das ist es, was ich bekomme, wenn ich das Handbuch nicht gründlich lese und mir stattdessen ein Blog-Beispiel davon anschaue;) Ich warte immer noch auf eine echte Überlastung der Operatoren in PHP.
Airbear
@airbear gibt es ein altes PECL-Paket von Sara Golemon, mit dem Sie Operatoren überladen können . Ich weiß nicht, wie kompatibel es mit PHP.current ist.
Gordon
1
@Pooya Das liegt daran, dass nodees keine Eigenschaft von $ foo ist, sondern eine Eigenschaft von doesNotExist. Solange 'doesNotExist' kein Objekt ist (das __set implementiert oder eine öffentliche Eigenschaft namens node hat), funktioniert es nicht.
Tivie
1
@ Allen ja, das tut es.
Gordon
34

Ich würde empfehlen, ein Array zum Speichern aller Werte über zu verwenden __set().

class foo {

    protected $values = array();

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

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

}

Auf diese Weise stellen Sie sicher, dass Sie nicht auf andere Weise auf die Variablen zugreifen können (Hinweis, der $valuesgeschützt ist), um Kollisionen zu vermeiden.

Fidi
quelle
19

Aus dem PHP-Handbuch :

  • __set () wird ausgeführt, wenn Daten in unzugängliche Eigenschaften geschrieben werden.
  • __get () wird zum Lesen von Daten aus unzugänglichen Eigenschaften verwendet.

Dies wird nur beim Lesen / Schreiben unzugänglicher Eigenschaften aufgerufen . Ihr Eigentum ist jedoch öffentlich, was bedeutet, dass es zugänglich ist. Durch Ändern des Zugriffsmodifikators in "Geschützt" wird das Problem behoben.

Berry Langerak
quelle
7

Um Berrys Antwort zu erweitern: Wenn Sie die Zugriffsebene auf "Geschützt" setzen, können __get und __set mit explizit deklarierten Eigenschaften verwendet werden (zumindest beim Zugriff außerhalb der Klasse). Da die Geschwindigkeit erheblich langsamer ist, zitiere ich einen Kommentar aus einer anderen Frage zu diesem Thema und begründen Sie es trotzdem:

Ich bin damit einverstanden, dass __get für eine benutzerdefinierte Get-Funktion langsamer ist (die gleichen Aktionen ausführt). Dies ist 0,0124455 für __get () und 0,0024445 für benutzerdefinierte get () nach 10000 Schleifen. - Melsi 23. November 12 um 22:32 Best Practice: PHP Magic Methoden __set und __get

Nach Melsis Tests ist erheblich langsamer etwa fünfmal langsamer. Das ist definitiv erheblich langsamer, aber beachten Sie auch, dass die Tests zeigen, dass Sie mit dieser Methode immer noch 10.000 Mal auf eine Eigenschaft zugreifen können, wobei die Zeit für die Schleifeniteration in ungefähr 1/100 Sekunde gezählt wird. Es ist im Vergleich zu den tatsächlich definierten Get- und Set-Methoden erheblich langsamer, und das ist eine Untertreibung, aber im großen Schema der Dinge ist sogar fünfmal langsamer nie wirklich langsam.

Die Rechenzeit des Vorgangs ist immer noch vernachlässigbar und in 99% der realen Anwendungen nicht zu berücksichtigen. Das einzige Mal, dass es wirklich vermieden werden sollte, ist, wenn Sie in einer einzigen Anfrage mehr als 10.000 Mal auf die Eigenschaften zugreifen. Websites mit hohem Datenverkehr machen etwas wirklich Falsches, wenn sie es sich nicht leisten können, ein paar weitere Server hochzuwerfen, um ihre Anwendungen am Laufen zu halten. Eine einzeilige Textanzeige in der Fußzeile einer stark frequentierten Site, bei der die Zugriffsrate zum Problem wird, könnte wahrscheinlich für eine Farm mit 1.000 Servern mit dieser Textzeile bezahlt werden. Der Endbenutzer wird niemals mit den Fingern tippen und sich fragen, was das Laden der Seite so lange dauert, da der Zugriff auf die Eigenschaften Ihrer Anwendung eine Millionstel Sekunde dauert.

Ich sage dies als Entwickler mit einem Hintergrund in .NET, aber unsichtbare Methoden zum Abrufen und Festlegen für den Verbraucher sind keine Erfindung von .NET. Sie sind einfach keine Eigenschaften ohne sie, und diese magischen Methoden sind die Rettung des PHP-Entwicklers, wenn es darum geht, ihre Version von Eigenschaften überhaupt als "Eigenschaften" zu bezeichnen. Außerdem unterstützt die Visual Studio-Erweiterung für PHP Intellisense mit geschützten Eigenschaften, mit diesem Trick, denke ich. Ich würde denken, wenn genügend Entwickler die magischen Methoden __get und __set auf diese Weise verwenden, würden die PHP-Entwickler die Ausführungszeit optimieren, um der Entwicklergemeinschaft gerecht zu werden.

Bearbeiten: Theoretisch schienen geschützte Eigenschaften in den meisten Situationen zu funktionieren. In der Praxis stellt sich heraus, dass Sie häufig Ihre Getter und Setter verwenden möchten, wenn Sie auf Eigenschaften innerhalb der Klassendefinition und der erweiterten Klassen zugreifen. Eine bessere Lösung ist eine Basisklasse und eine Schnittstelle für die Erweiterung anderer Klassen, sodass Sie nur die wenigen Codezeilen aus der Basisklasse in die implementierende Klasse kopieren können. Ich mache ein bisschen mehr mit der Basisklasse meines Projekts, daher muss ich momentan keine Schnittstelle bereitstellen, aber hier ist die ungetestete, reduzierte Klassendefinition mit dem Abrufen und Festlegen magischer Eigenschaften mithilfe von Reflektion zum Entfernen und Verschieben der Eigenschaften ein geschütztes Array:

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

Ich entschuldige mich, wenn der Code Fehler enthält.

Jason Ensinger
quelle
Ich habe ein Problem mit 'access' => $ property-> getModifier ()
macki
5

Es ist, weil $ bar ein öffentliches Eigentum ist.

$foo->bar = 'test';

Es ist nicht erforderlich, die magische Methode aufzurufen, wenn Sie die oben genannten Schritte ausführen.

Das Löschen public $bar;aus Ihrer Klasse sollte dies korrigieren.

Matt Lowden
quelle
1

Verwenden Sie am besten magische Set / Get-Methoden mit vordefinierten benutzerdefinierten Set / Get-Methoden wie im folgenden Beispiel. Auf diese Weise können Sie das Beste aus zwei Welten kombinieren. In Bezug auf die Geschwindigkeit stimme ich zu, dass sie etwas langsamer sind, aber spürt man sogar den Unterschied. Das folgende Beispiel überprüft das Datenarray auch anhand vordefinierter Setter.

"Die magischen Methoden sind kein Ersatz für Getter und Setter. Sie ermöglichen lediglich die Verarbeitung von Methodenaufrufen oder den Zugriff auf Eigenschaften, die sonst zu einem Fehler führen würden."

Deshalb sollten wir beide verwenden.

BEISPIEL EINER KLASSE

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

AUSGABE

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
quelle
0

Löschen Sie die public $bar;Deklaration und es sollte wie erwartet funktionieren.

Alix Axel
quelle
-4

Intenta con:

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

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