Problem mit magischen Methoden __set() und __get()

Parantatatam

mag Cookies & Kekse
Guten Tag Tutorianer,

ich habe gerade ein kleines, vielleicht sogar großes, Problem mit den magischen Methoden __set() und __get(). Warum, dass erkläre ich euch jetzt: ich arbeite momentan an einem ActiveRecord-Framework und noch momentaner an der Modelklasse. Dabei gibt es sogenannte Setter und Getter, mit welchen man die Eigenschaften eines Datensatzes abfragen kann und auch verändern kann. Soweit ist das kein Problem, allerdings soll man auch die Möglichkeit haben, dass beispielsweise beim Speichern eines Kennwortes dieses gleich beim Festlegen in einen SHA1-Hash umwandeln. Das ist auch nicht das Problem, denn das erledige ich über Methoden, welche mit "set_" oder "get_" anfangen und dann der Name der Eigenschaft folgt. Jetzt kommt aber das Problem: will man nun gleich die Eigenschaft mit dem gleichen Namen überschreiben, so wird das in den meisten Frameworks darüber gelöst, dass man zwei weitere Methoden, write_attribute() und read_attribute(), hat mit welchen man den Wert überschreiben kann. Das will ich nicht, sondern ich will die magischen Methoden von PHP auch an dieser Stelle verwenden können, nur leider funktioniert dies nur halbherzig und ohne meinerseits erkenntliche Logik. Vielleicht seht ihr wo der Fehler liegt.

So wird es meistens gelöst:
PHP:
function set_password($password) {
    $this->write_attribute('password', $password);
}

So will ich es lösen:
PHP:
function set_password($password) {
    $this->password = $password;
}

Was ich bisher habe:
PHP:
class XtModelStorage {
    private static $storage = array();
    public static function setGetter($model, array $attributes) {
        if(!isset(self::$storage[$model]['getter']))
            self::$storage[$model]['getter'] = $attributes;
        }

    public static function setSetter($model, array $attributes) {
        if(!isset(self::$storage[$model]['setter']))
            self::$storage[$model]['setter'] = $attributes;
    }

    public static function getGetter($model) {
        if(isset(self::$storage[$model]['getter']))
            return self::$storage[$model]['getter'];
        return null;
    }

    public static function getSetter($model) {
        if(isset(self::$storage[$model]['setter']))
            return self::$storage[$model]['setter'];
        return null;
    }

    public static function hasGetter($model, $name) {
        return (isset(self::$storage[$model]['getter']) && in_array($name, self::$storage[$model]['getter']));
    }

    public static function hasSetter($model, $name) {
        return (isset(self::$storage[$model]['setter']) && in_array($name, self::$storage[$model]['setter']));
    }

    public static function getList() {
        return self::$storage;
    }
}
		
class XtModel {		
    private static $attributes = array();
    private static $temp       = false;
				
    final public function __construct() {
        $setter  = array();
        $getter  = array();
        $methods = XtClass::create(get_called_class())->getMethods();
        foreach($methods as $method) {
            if($method->name[0] === '_')
                continue;
            @list($prefix, $attribute) = explode('_', $method->name, 2);
            if($prefix === 'set')
                $setter[] = $attribute;
            elseif($prefix === 'get')
                $getter[] = $attribute;
        }		

        XtModelStorage::setGetter(get_called_class(), $getter);
        XtModelStorage::setSetter(get_called_class(), $setter);
    }

    final public function __set($attribute, $value) {
        $called = get_called_class();
        if(!self::$temp && XtModelStorage::hasSetter($called, $attribute)) {
            self::$temp = true;
            $this->{'set_' . $attribute}($value);
            return;
        }
        self::$attributes[$called][$attribute] = $value;
        self::$temp = false;
    }

    final public function __get($attribute) {
        $called = get_called_class();
        return self::$attributes[$class][$attribute];
    }
}

class Test extends XtModel {		
    function set_password($password) {
        $this->password = sha1($password);
    }

    function set_username($username) {
        $this->username = strlen($username);
    }
}

Was erledigen die einzelnen Klassen?
  • Klasse "XtModelStorage" speichert alle Getter- und Settermethoden von Klassen, welche von der Klasse "XtModel" erben
  • Klasse "XtModel" enthält die gesamte Logik des Models, lädt also die Getter- und Settermethoden, speichert sie. Außerdem enthält es die Methoden __set() und __get()
  • Klasse "Test" ist nur ein Beispiel
Was erledigt meine __set()-Methode?

Wenn ein Attribut aufgerufen wird, zu dem eine Settermethode existiert, so werden die Werte an die entsprechende Settermethode weitergeleitet. Da diese auch die Methode __set() verwendet, wird vorher markiert, dass gerade eine solche Methode aufgerufen wurde. Das steckt in der Eigenschaft self::$temp. Falls self::$temp wahr ist, wird mit der Eigenschaft wie mit einer normalen Variable umgegangen und sie in der Eigenschaft self::$attributes gespeichert. Danach wird die Eigenschaft self::$temp wieder als falsch markiert. Soweit die Theorie - es funktioniert nur nicht.

PS: Auch wenn es um __set() und __get() geht, so möchte ich mich vorerst nur auf die Methode __set() beschränken.
 
Hi,

laut PHP-Doku - "__set() wird aufgerufen, wenn Daten in unzugreifbare Eigenschaften geschrieben werden sollen." Bei dir sind Properties nach dem ersten Aufruf zugreifbar ($this->password, $this->username), sprich die Methode __set wird dann nicht emhr ausgeführt. Du könntest das verhindern, indem du diese Properties als private definierst oder statt dessen in einem Property-Array vorhältst.

Was ich komisch finde, beim ersten Aufruf mittels Setter schreibst du den Wert nicht in self::$attributes[$called][$attribute] weg, liest beim Getter aber immer von dort aus. Ist das so gewollt?

Grüße
BN
 
Ja, dass ist so gewollt, ist aber nur momentan so. Später wird es, wie schon oben genannt, auch Gettermethoden geben, welche dazwischen geschaltet werden können. Außerdem schreibe ich den Wert auch in dieses Array, allerdings erst beim zweiten Mal. Aber mir ist gerade etwas aufgefallen, was ich mal überprüfen sollte: in der Methode __set() verwende ich die Variable $called und bei __get() die Variable $class, obwohl in beiden Fällen nur $called richtig ist.
 
Zurück