Es sind ein paar Tage ins Land gezogen seit dem letzten Artikel. In den letzten Monaten ist viel passiert. Mitunter habe ich für die Jungs und Mädels von zeitsprung GmbH & Co KG ein PHP BiPRO Framework ins Leben gerufen, mit dem wir BiPRO Webservices ganz innovativ und schnell realisieren.
Bei der Programmierung des BiPRO Frameworks ist es für mich selbstverständlich, dass ich Entitäten als Objekte verwende, um Daten aus der Datenbank oder aus XML Requests darzustellen. Irgendwie hat es sich in den letzten Jahren der objektorientierten Programmierung so eingeschlichen und auch etabliert. Ich nutze einfach weitestgehend nur noch Objekte, ohne großartig darüber nachzudenken. Vor Kurzem fragte man mich dann doch mal, welche Vorteile es hat nur noch Objekte zu benutzen. Ich möchte das einfach mal anhand eines Resultsets einer Datenbankabfrage zeigen.
Das Array Resultset
So haben wir es alle einmal gelernt, als wir angefangen haben. Datenbank Resultsets sind einfach Arrays. Folgendes Beispiel kennen wir alle.
$dbh = new PDO('...');
foreach ($dbh->query('SELECT id, name, birthday FROM users') as $user) {
var_dump($user);
/*
array(3) {
["id"]=> int(1)
["name"]=> string(6) "Marcel"
["birthday"]=> string(10) "1979-12-19"
}
*/
}
So kennen wir es alle. Einfach quick and dirty Inhalte aus einer Datenbank auslesen und die Daten in einem Array festhalten. Daran ist absolut nichts verkehrt.
Warum das Array nicht gut ist
Der allergrößte Nachteil an einem PHP Array ist, dass es ein relativ loses Konstrukt ist. Man kann in einem Array nicht festlegen, welche Inhalte es haben soll. Es nimmt einfach fast unbegrenzt Daten auf. So fancy Sachen wie Typsicherheit, Methoden, Validierung oder weitere Techniken wie Immutables kannst Du mit einem Array einfach mal ganz vergessen. Im Grunde genommen wissen wir gar nicht, was in einem Array ist und sind darauf angewiesen andauernd zu prüfen, ob die Werte, die wir erwarten, auch vorhanden sind. Zudem sind die Daten im Array nicht so formatiert, wie ich sie eigentlich erwarte. Das Datum ist zum Beispiel ein String, anstatt ein DateTime Objekt, mit dem ich das Format des Datums einfach selbst festlegen kann. So richtig praktisch ist so ein Array also nicht wirklich.
Warum Entitäten und Value Objects gut sind
Wenn ich von Entitäten und Value Objects spreche, meine ich einfach Klassen, die ein exaktes Mapping zu den zu erwartenden Daten aus einem Rowset darstellen. Ganz simple Modelle also. Wie so ein Model aussieht, habe ich schon bei der Hydrierung und Extrahierung von Daten beschrieben. Zum besseren Verständnis einfach mal unsere Entity Klasse, welches unser Rowset aus dem ersten Codebeispiel darstellt.
declare(strict_types=1);
namespace MMNewmedia\Entity;
use BadMethodCallException;
use DateTimeImmutable;
use JsonSerializable;
class User implements JsonSerializable
{
protected int $id;
protected string $name;
protected DateTimeImmutable $birthday;
protected bool $isImmutable = false;
public function getId() : int
{
return $this->id;
}
public function setId(int $id) : self
{
if ($this->isImmutable) {
throw new BadMethodCallException('The id is immutable.');
}
$this->id = $id;
return $this;
}
public function getName() : string
{
return $this->name;
}
public function setName(string $name) : self
{
if ($this->isImmutable) {
throw new BadMethodCallException('The name is immutable.');
}
$this->name = $name;
return $this;
}
public function getBirthday() : DateTimeImmutable
{
return $this->birthday;
}
public function setBirthday(DateTimeImmutable $birthday) : self
{
if ($this->isImmutable) {
throw new BadMethodCallException('The birthday is immutable.');
}
$this->birthday = $birthday;
return $this;
}
public function setImmutable(bool $immutable) : self
{
$this->isImmutable = $immutable;
return $this;
}
public function jsonSerialize()
{
return get_object_vars($this);
}
}
Das Beispiel beinhaltet schon die Type Hints für Klasseneigenschaften, welche es erst ab PHP 7.4 geben wird. Warum ist eine solche Klasse jetzt eigentlich besser, als ein simples Array? Ganz einfach: strikte Typisierung bringt Typsicherheit, festgelegte und somit bekannte Eigenschaften, Getter und Setter Methoden, mit Immutable gibt es eine Möglichkeit das Objekt nicht mehr ändern zu können, Method Chaining und sogar die Erweiterung zur Ausgabe der Klasseneigenschaften als JSON Objekt gibt es jetzt.
Also dann mit Objekt?
Natürlich bringt die Verwendung von Objekten immer ein wenig mehr Aufwand mit sich, als ein simples Array. Allein durch die strikte Typisierung muss ich einfach die Möglichkeit von Exceptions in Betracht ziehen. Ich muss entsprechende Exceptions abfangen und immer im Hinterkopf behalten, dass mein Objekt sehr viel genauer und strikter funktioniert, als ein Array. Zudem muss ich dafür sorgen, dass die Daten aus dem oben genannten Datenbankbeispiel auch irgendwie in das Objekt kommen. Ich muss es also hydrieren. Entweder mit einer entsprechenden Hydrator Klasse, oder wie im folgendem Beispiel.
$dbh = new PDO('...');
$users = new SplObjectStorage();
foreach ($dbh->query('SELECT id, name, birthday FROM users') as $row) {
$birthday = new DateTimeImmutable($row['birthday']);
$user = (new User())
->setId($row['id'])
->setName($row['name'])
->setBirthday($birthday)
->setImmutable(true);
$users->attach($user);
}
Fazit
Auch wenn es auf den ersten Blick kompliziert aussieht, bringen Objekte sehr viel mehr Vorteile mit sich, als einfache Arrays. In einem objektorientierten Zusammenhang benötige ich einfach ein Objekt, welches mir entsprechende Funktionalitäten im Zusammenhang mit den zu etwartenden Daten zur Verfügung stellt. Ich benötige einfach Typensicherheit, um mir das Leben im Umgang mit den zu erwartenden Daten leichter zu machen. Also bitte tut Euch und irgendwie auch mir einen Gefallen, und benutzt weniger Arrays und mehr Objekte in der objektorientierten Programmierung. Wie ihr gesehen habt, gibt es jede Menge Vorteile, die dafür sprechen.
PS: Noch ein riesiger Vorteil wäre die Handhabung von Objekten in Deiner IDE. Autovervollständigung von Klassennamen und Methoden sowie die direkte Typisierung von Return-Werten. Probiere es aus. Du wirst es lieben!
Ich gehe sogar noch weiter und definiere auch die simplen Typen
z.B.:
Klasse StringType()
Dann kann ich typische stringbasierte Operationen anwenden.
Das Gleiche gilt für
integer
z.B.
Int8
int16 usw…
… statt …
name( ‚Name‘ );
… schreib ich …
name( new StringType( ‚Name‘ ) );
… usw.
Man könnte auch fetchClass machen dann hätte man automatisch ein Objekt. Aber das setImmutable(true) würde ich nicht machen, eher enableImmutable oder so. lässt sich irgendwie schöner lesen, und für das false müsste man eben eine zweite Methode einbauen.
Tolle Artikel!
Mahlzeit!
Ich stimme Dir zu, wenn man mit PDO arbeitet, bietet sich fetchClass an. Arbeitet man aber mit einer anderen Datenbank-Abstraktionsschicht oder einfach nicht mit PDO, wird es schwierig. Das Zend Framework geht in seiner aktuellen Version einen ähnlichen Weg. Die Datenbankverbindung an sich hat nicht wirklich viel mit der Business Logik zu tun. Statt dessen gibt es hydrierende Result Sets, die die aus der Datenbank ausgelesenen Daten in eine Entität hydrieren und direkt als Ergebnis zurück geben. Vollkommen unabhängig von Datenbankverbindung.
Deinen Einwand zur Benennung der Immutable Methode finde ich gut. Mir schwebt da gerade etwas komplett anderes vor. Man könnte daraus ein Value Object machen und somit den Zustand der Entität darstellen. Danke für den Denkanstoß. Das ist auf jeden Fall Stoff für einen neuen Artikel. 😉