Doctrine – Accessoren & Mutatoren

Also erstens, damit man die Doku versteht: Mutatoren sind natürlich “Setter” (setFirstname(string name)),  Accessoren “Getter” (getFirstname()). Doctrine ermöglicht es auf vielfältige Weise, Attribute eines OR-Objekts programmatisch zu erfragen bzw. zu verändern. Da jede Instanz von Doctrine_Record letztlich die abstrakte Elternklasse Doctrine_Access implementiert, wird der Zugriff und alle Änderungen durch die (magischen) PHP-Methoden __get(), __set() und __call() koordiniert. Zusätzlich bietet Doctrine eine Konfiguration, die es ermöglicht, jede Änderung an einem Objekt einem optionalen, zentralen Methodenaufruf zuzuleiten, der dann als eine Art Interzeptor fungiert.

Doctrine_Access setzt unter anderem die Schnittstelle ArrayAccess voraus, die seit PHP 5 mit SPL existiert. Jedes Objekt, das diese Schnittstelle erbt, kann wie ein Array behandelt werden:


$obj = new MyArray();

$obj['property'] = "Value";

echo $obj['property'];

Was genau beim Zugriff auf das Objekt oder das Setzen seiner Eigenschaften via Array-Syntax + Zuweisungsoperator geschieht, bleibt dabei dem Entwickler überlassen, der das Verhalten durch die Implementierung der Methoden offsetSet(), offsetUnset(), offsetExists() und offsetGet() steuern kann.

Durch die php-internen magischen Methoden plus ArrayAccess ist es möglich, auf Attribute von Doctrine_Record Instanzen so zuzugreifen:

echo $employee['firstname'];

oder so:

echo $employee->firstname;

oder auch so:

echo $employee->getFirstname();

Letztere Möglichkeit existiert nur im Symfony-Kontext, sprich wenn Doctrine als Symfony-Plugin läuft. Dann nämlich wird dem Model-Generator noch eine Symfony-Klasse “dazwischengeschoben”, nämlich sfDoctrineRecord. Diese Klasse kümmert sich um das Mapping von setXY() und getYZ()-Methoden auf die jeweiligen Accessoren und Mutatoren, die Doctrine bereitsstellt.

Möchte man nun eine dieser Methoden implementieren, also die “Magie” umgehen und seine “eigene” Methode namens setFirstname() implementieren, hat man ein Problem. Da die Sichtbarkeit nicht auf Getter beschränkt ist, könnte man auf die Idee kommen, statt $employee->setFirstname(“Kalle”) einfach $employee->firstname = “Kalle” zu schreiben. Um zu erreichen, dass jeder Mutator-Aufruf, egal welcher Syntax, auf die eigene, konkrete Methode setXYZ() umgeleitet wird, muss man nur eine Doctrine-Umgebungsvariable setzen:

$manager = Doctrine_Manager::getInstance();
$manager->setAttribute('auto_accessor_override', true);

Nun ist aber bei der Implementierung ein wenig Vorsicht geboten:

function setFirstname($firstname){
if(empty($firstname))
{
$this->firstname = "Noname";
}
}

Damit hängen wir in einer Endlos-Schleife fest. Die Lösung ist, sofort den “Endpunkt” aller Mutator-Aufrufe anzuspringen, die Methode _set($attr, $value) (nicht zu verwechseln mit der magischen Methode __(set).

function setFirstname($firstname){
if(empty($firstname))
{
$this->_set('firstname', 'Noname');
}
}

So lässt sich jede Änderung am Objekt protokollieren und bei Bedarf triggern.