1unitedpower: Klasse aus Datenbank füllen, danach die Klasse verarbeiten

Beitrag lesen

Ich wollte schon das gleiche erzählen, aber du bist mir zuvor gekommen. Stattdessen nütze ich den Thread, um ein wenig über Design-Entscheidungen zu plaudern und hoffe, dass man mich dafür kritisiert.

Es ist häufig so, dass man Fachlogik aus den Modellklassen ganz heraushält und sie nur zur Datenhaltung einsetzt. Man spricht dann bei Java-Programmen von POJOs (Plain Old Java Objects), in C++ oder .net sind es POCOs (Plain Old C++/CLR Objects) und dementsprechend heißen sie in PHP POPOs :-D.

Die Modellklasse für einen Proof könnte in PHP z.B. so aussehen:

<?php

namespace Demo\Domain;

final class Proof
{
    private int $id;

    private string $title;

    private int $price;

    public function __construct(int $id, string $title, int $price)
    {
        $this->id = $id;
        $this->title = $title;
        $this->price = $price;
    }

    public function getId() : int
    {
        return $this->id;
    }

    public function getTitle() : string
    {
        return $this->title;
    }

    public function getPrice() : int
    {
        return $this->price;
    }
}

Das ist natürlich ne Menge Schreibarbeit für eine reine Datenklasse, die keine Geschäftslogik enthält. Man hätte sich die getter-Methoden sparen können und dafür die Attribute public machen können. Das ist eine freie Design-Entscheidung, ich persönlich bevorzuge unveränderliche Objekte, und habe mich daher für dieses Design entschieden und die Tipparbeit in Kauf genommen. Einige PHP-Frameworks bieten auch die Möglichkeit an, solche Datenklassen, automatisch erzeugen zu lassen.

Eine andere Design-Entscheidung von mir ist die Klasse mit final auszuzeichnen. Das ist in diesem Fall auch rein meinen persönliche Vorlieben geschuldet: ich bevorzuge Komposition gegenüber Vererbung, deshalb öffne ich meine Klassen nur für Vererbung, wenn ich sie wirklich mal brauche.

Die Objekte, die sich um den Transport POPO <-> DB kümmern, heißen Repository. Repositories werden nach fachlichen Kritierien erstellt, d.h. du schreibst nicht für jede Table ein Repository, sondern für "Business Domains" - fachliche Komplexe.

Ein passendes Proof-Repostiory zum obigen Domain-Model könnte etwa so aussehen:

<?php

namespace Demo\Persistence;

use \PDO;
use \Demo\Domain\Proof;

final class ProofRepository
{
    private PDO $connection;

    public function __construct(PDO $connection)
    {
        $this->connection = $connection;
    }

    public function proofsWithIdGreaterThan(int $id) : array
    {
        $statement = $this
            ->connection
            ->prepare("SELECT id, title, price FROM proofs WHERE id > :id");
        $statement->bindValue(':id', $id, PDO::PARAM_INT);
        $statement->execute();
        return $stament->fetchAll(
            PDO::FETCH_FUNC,
            function (string $id, string $title, string $price) {
                return new Proof((int)$id, $title, (int)$price);
            }
        );
    }
}

Hier habe ich dieselbe Entscheidung über Vererbung wie oben getroffen. Außerdem habe ich mich anders als Rolf entschieden, und die (im Moment einzige) Methode des Repositorys nicht statisch deklariert. Nach Rolfs Vorschlag müsste die Datenbank-Verbindung jeder statischen Methode als zusätzlicher Parameter mitgegeben werden, das fände ich müßig beim Aufrufen. Oder man hätte die Datenbank-Verbindung innerhalb der Methoden selbst herstellen können, das wäre aber wieder eine Verletzung von SOLID. In anderen Programmiersprachen, die partielle Funktions- bzw. Methoden-Aufrufe erzeugen, hätte ich mich auch für das statische Design entschieden.

Der aufrufende Code müsste dann übrigens etwa so aussehen:

use \Demo\Domain\Proof;
use \Demo\Persistence\ProofRepository;

$connection = new Db();
$repository = new ProofRepository($connection);
$proofs = $repository->proofsWithIdGreaterThan(100);