Silverstripe Complex Fields

Silverstripe ist ja ein Superding. Problematisch nur hin und wieder, dass die Dokumentation nicht allzuviel hergibt und das Ding intern teils etwas eigenwillig geschrieben ist. So ist der ORMapper eher für Routineaufgaben geeignet, komplexere Anforderungen erfüllt man besser “zu Fuß”.

Und die DBFields … tjaja. Nirgends (?) steht geschrieben, wie man denn die magisch überladenen Getter und Setter dazu bringt, hier den primitiven Typ, dort die Objektrepräsentation zu liefern. Gesetzt den Fall, man hat ein Feld “Price” vom Typ “Currency”:


$myObject->Price; // liefert Float.
$myObject->dbObject('Price'); // liefert Currency

Der direkte Zugriff auf die Eigenschaft (intern aber magisch via __get() auf die entsprechenden Attribute gemappt) liefert also den “Rohwert”, die Methode dbObject(‘FieldName’) die komplexe Objektrepräsentation.

Manchmal möchte man diese Werte im eigenen Controller auch mal setzen:


$myObject->Price = 9999.9932; // float gesetzt
echo $myObject->dbObject('Price')->Nice() // string, "$ 9,999.90"

$myObject->Price = 124553412312512514214521412414..... // Eine wirklich lange Zahl, die mit ziemlicher Sicherheit überläuft.
echo $myObject->Price // 1.24553412313E+35, was zu erwarten war

$myObject->Price = "12455341231251251421452......" // Eine wirklich lange Zahl, die mit ziemlicher Sicherheit überläuft.

echo $myObject->Price
// (string) 124553412312512514214521412414551324.123152134
// 152141231212536324123132, aber wie sieht es in der Datenbank aus?

$myObject->dbObject('Price')
// Peng! €124,553,412,312,512,528,384,343,341,833,125,888.00

Man nutzt also ausschließlich den direkten Zugriff auf Instanzeigenschaften (intern auch wieder gemappt  via magic __set()), um komplexe Werte zu setzen. Nebenbei haben wir auch bemerkt, dass das Ganze nicht wirklich sicher im Umgang mit Zahlen ist. Man ist also selbst gegenüber der Datenbank in der Verantwortung, die Felder ggf. durch PHP-Funktionen, die Präzision außerhalb der Grenzen “normaler” Fließkommaberechnungen erlauben, zu schützen.

Manchmal möchte ich die Vorteile eines DBField-Objekts auch mal außerhalb des Datenbankkontextes benutzen. Ein simples Beispiel sind Ergebnisse von Berechnungen im Warenkorb, die ich im Template ebenfalls wie “normale” DBFields via Nice()-Methode formatieren möchte.

Vorstellbar ist eine (vereinfachte) Methode getTotalPrice():


class Cart_Controller extends Page_Controller
{
public function getTotalPrice()
{
$total = 0;
foreach($this->getItems() AS $item)
{
$total += $item->Price();
}
return $total;
}
}

Um den Weg über


$v = new myDBField('NameDenDasFeldNichtBraucht');
$v->setValue();
return $v;

abzukürzen, bietet DbField eine statische Methode create():


public function getTotalPrice()
{
$total = 0;
foreach($this->getItems() AS $item)
{
$total += $item->Price();
}
return DbField::create('Currency',  $total);
}

Dann kann man im Template schreiben:


<p>Preis: $getTotalPrice.Nice</p>

An dieser Stelle ist aber Vorsicht geboten, denn


$myDataObject->Price = new DbField('Currency', 13.421);

Wird natürlich nicht funktionieren (siehe auch weiter oben, Setter sind ausschließlich die via __set() erreichbaren Attribute eines DbFields). Ganz im Gegenteil, hier wird sogar eine Warnung via Exception geworfen, weil jedes DbField nach Instanziierung  bereits benamt ist und eine (magische) Neu-Zuordnung zu einem bereits existierendem DataObject eine neue Benamung voraussetzt. Das impliziert, dass DbField-Instanzen ausschließlich via


$myDataObject->dbObject('FieldName');

erreicht werden können.

Möchte man auf den internen Wert eines DbFields zurückgreifen, nutzt man einfach die Methode RAW() oder auch einfach die Methode forTemplate() (die jedes Objekt, das ViewableData implementiert, besitzt), die in diesem Falle einfach ein Proxy auf RAW() ist.