Die Magic Methoden in PHP werden oft diskutiert. Legen wir mal die Ideologie zur Seite und betrachten die Vorteile in Verbindung mit den in PHP 5.4 eingeführten Traits. Dies kann man besonders gut durch eine allgemeine Lösung für das Handling von Getter und Setter darstellen.

 

Bis dato sahen Getter und Setter ungefähr so aus:

class OldSchoolPHP
{
    private $name;
    private $date;
    function __get($name)
    {
        if ($name == 'name') {
            return $this->name;
        } elseif ($name == 'date') {
            return $this->date;
        }
    }
    // ...
}

Dies ist unüberschaubar und schwer zu pflegen. Oftmals ist man dazu übergegangen eine Basisklasse zu schreiben, war dann aber in der objektorientierten Vererbung behindert. Mit Traits kann man das Problem elegant lösen.

Traits sind einfach erklärt Fragmente von Klassen, die alleine nicht funktional sind. Fügt man diese in eine Klasse ein wird diese ohne Vererbung erweitert. Dies ist mehrfach möglich.

Ein Getter Trait kann wie folgt aussehen:

trait Getters
{
    /**
     * calls Class::$name()
     *
     * @param string $name the name of a requested property
     * @return mixed the result
     */
    public function __get($name)
    {
        return $this->__call($name);
    }
    /**
     * checks wether a get method get<$Name>() exists and calls it
     *
     * @param string $name
     * @param array $args optional
     * @return mixed
     */
    public function __call($name, $args = [])
    {
        if (method_exists($this, $method = 'get' . ucfirst($name))) {
            return $this->{$method}($args);
        } else {
            throw new \Exception('Method or property does not exists');
        }
    }
}

Dieser Trait kann in einer beliebigen Klasse verwendet werden. Was kann er? Existiert in der Klasse eine Getter-Methode in der Konvention get so wird bei dem Zugriff auf eine Member-Variable die Methode ausgeführt. Zusätzlich kann auch der Methodenaufruf genutzt werden.

Ein Beispiel:

class ModernPHPClass
{
    use Getters;
    private $name;
    public function getName()
    {
        return $this->name;
    }
}

Die Properties der Objekte lassen sich nun wie folgt lesen:

echo $obj->name; // calls Class::<$name>()
echo $obj->name(); // calls Class::get<$Name>()
echo $obj->getName(); // calls itself

Mit Setter funktioniert dies ebenso. Ein Setter-Trait kann wie folgt aussehen:

trait Setters
{
    /**
     * @param string $name property name
     * @param mixed $value the value
     */
    public function __set($name, $value)
    {
        if (method_exists($this, $method = 'set' . ucfirst($name))) {
            $this->{$method}($value);
        } else {
            throw new \Exception('Private or protected properties are not accessible');
        }
    }
}

Die Funktionsweise ist analog. Existiert eine Methode set in der jeweiligen Klasse kann diese als Setter verwendet werden.

Hier ein Beispiel der Nutzung beider PHP Traits zur ordentlichen Typensicherheit:

class MyTest
{
    use Getters;
    use Setters;
    /**
     * @var \DateTime
     */
    private $date;
    /**
     * @return \DateTime
     */
    public function getDate()
    {
        return $this->date;
    }
    /**
     * @param mixed $value
     */
    public function setDate($value)
    {
        if ($value instanceof \DateTime) {
            $this->date = $value;
        } elseif (is_string($value)) {
            $this->date = new \DateTime($value);
        } elseif (is_int($value)) {
            $this->date = new \DateTime(date(DATE_ATOM, $value));
        }
    }
}

Der Getter ist sehr einfach. Der Setter setDate() hat es in sich. Er kann DateTime Objekte, Strings und UNIX timestamps aufnehmen und konvertiert diese on the fly. So kann sichergestellt werden, dass die Property-Variable immer ein DateTime Objekt ist.

PHP 5.4 hat viele Verbesserungen und ich bin verwundert, wie langsam die Version eingeführt wird. Traits sind nur ein Beispiel von vielen, die kluge und effiziente Softwareentwicklung ermöglichen.

Alle Traits und Beispiele habe ich in einem Gist zusammengefasst.

https://gist.github.com/chriskoch/4943794