PHP, PDO: Objekte aus der Datenbank via fetchObject

Einfache Vererbung und Objekterstellung mit Daten aus MySQL

Diese Sache hat es in sich, für mich der Anlaß für diesen Artikel. Worum es geht: In PHP ist es möglich, Objekte beim Initialisieren der Eigenschaften direkt beim Abholen der Daten aus der Datenbank zu erstellen. Für das Beispiel habe ich eine Tabelle mit Mondphasen, da gibt es die Felder julianday und phase. Ziel der Übung ist es, mit den beiden Werten ein Objekt zu initialisieren, das beispielsweise so aussieht:

Moon Object
(
    [julianday] => 2458889
    [phase] => Full
    [JD] => 2458889
    [DAY] => 9
    [MONTH] => 2
    [YEAR] => 2020
    [EPOCH] => G
    [WDAY] => 7
    [LEAP] => 1
    [ISA] => userobject
    [KW] => 6
    [DAYS_INMONTH] => 29
)

Wie zu sehen, sind da außer julianday und phase noch weitere Eigenschaften erwünscht, die sich aus dem Wert für julianday ergeben. Von daher erbt unsere Klasse Moon von der Klasse Scaliger wo diese Eigenschaften für ein Datumobjekt berechnet werden. Werfen wir nun einen Blick auf den Code:

class Moon extends Scaliger{
    # Zusätzliche Eigenschaften
    # für die Instanz
    public $julianday, $phase;

    # Klassenmethode zur Instanzerstellung
    function load($d,$m,$y, PDO $pdo){
        $sth = $pdo->prepare("
            SELECT julianday, phase FROM moon WHERE
            julianday = 1721060 + to_days(?'-'?'-'?)
        ");
        $sth->execute(array($y,$m,$d));
        if( $m = $sth->fetchObject(__CLASS__, array($d,$m,$y) ) ) {
            return $m;
        }
        else{
            throw new Exception("Can't create Moon-Object!");
        }
    }
}

$pdo = pdo4base();
$m = Moon::load(9,2,2020, $pdo);
# Kontrollausgabe
print_r($m);

Anmerkungen

Beachte: Die Instanz wird hier nicht mit new erstellt wird sondern durch den Aufruf einer Klassenmethode: $m = Moon::load(); Damit wird der Konstruktor umgangen, denn wir wollen ja die Instanz aus der Datenbank heraus erstellen. Die Klasse Moon braucht keinen Konstruktor, um die Instanzerstellung einschließlich Aufruf des Konstruktors der Elternklasse kümmert sich das PDO-Objekt. Die der Instanz hinzuzufügenden Eigenschaften julianday und phase sind in der Subklasse also außerhalb des Konstruktors zu deklarieren, ansonsten werden sie nicht hinzugefügt.

Mit $sth->fetchObject(..) nun, wird das Objekt als eine Instanz der Klasse Moon erstellt, dazu werden der Name der Klasse in __CLASS__ und das Datum (Tag, Monat, Jahr) übergeben. Spontan wird der Konstruktor der Elternklasse aufgerufen womit die dort definierten und geerbten Eigenschaften der frischgebackenen Instanz hinzugefügt werden. Der Aufruf dieser Methode ist also äquivalent zu:

new Scaliger($day, $month, $year);
# Elternklasse

Beachte auch: Wenn die Datenbankabfrage keine Daten liefert, wird kein Objekt erstellt! Das heißt, daß auf einen solchen Fall unbedingt geprüft werden muss.

Diskrete Objekterstellung

Schauen wir nun was man tun muss, um das Objekt ohne $sth->fetchObject(..) zu erstellen. Grundsätzlich muss der Konstruktor der Elternklasse aufgerufen werden damit die Eigenschaften initialisiert und geerbt werden. Vorher jedoch wird geprüft, ob die Abfrage auf die Mond-Tabelle für den betreffenden Tag einen Eintrag liefert. Ist das nicht der Fall, wird eine Exception geworfen. Sofern jedoch Daten vorliegen, werden julianday und phase als weitere Eigenschaften der Instanz hinzugefügt.

class Moon extends Scaliger{
    public $julianday, $phase;
    function __construct($d,$m,$y, PDO $pdo){
        $sth = $pdo->prepare("
            SELECT julianday, phase FROM moon WHERE
            julianday = 1721060 + to_days(?'-'?'-'?)
        ");
        $sth->execute(array($y,$m,$d));
        if( $r = $sth->fetch() ){
            # rufe den Konstruktor der Elternklasse
            parent::__construct($d,$m,$y);
            # füge neue Eingenschaften hinzu
            $this->phase = $r['phase'];
            $this->julianday = $r['julianday'];
        }
        else{
            throw new Exception("Can't create Moon-Object!");
        }
    }
}

$pdo = pdo4base();
$m = new Moon(9,2,2020,$pdo);

Beachte: In diesem Fall also, sieht die API vor, das Datumsobjekt mit new zu erzeugen. Hierzu muss ein Konstruktor definiert sein über den der Konstruktor der Elternklasse aufgerufen wird damit die Eigenschaften der Elternklasse geerbt und auch initialisiert werden. Beachte auch: Es ist unerheblich, ob der Vaterkonstruktor vor oder nach dem Hinzufügen der neuen Eigenschaften aufgerufen wird.

Unsere Klasse Moon in Perl

Der Vollständigkeit halber wollen wir das Ganze nun in Perl machen, also ein Objekt erstellen was in etwa so aussieht:

$VAR1 = bless( {
     'age' => 'G',
     'day' => '9',
     'gregdate' => '9.2.2020',
     'jd' => 2458889,
     'julidate' => '27.1.2020',
     'leap' => 'Y',
     'month' => '2',
     'phase' => 'Full',
     'ultimo' => 29,
     'wd' => 7,
     'wochentag' => 'Sonntag',
     'year' => '2020'
   }, 'Moon' );

Untenstehend die Package. Zunächst wird eine Instanz der Elternklasse erstellt, hierzu wird also der Konstruktor der Elternklasse aufgerufen. Als Nächstes werden für den betreffenden Tag gehörigen Daten aus der DB abgerufen wobei auch geprüft wird, ob es für den betreffenden Tag überhaupt Daten gibt. Sofern das der Fall ist, wird die Phase als eine neue Eigenschaft hinzugefügt und die Instanz zrückgegeben. Andernfalls wird mit die(..) eine Exception geworfen.

package Moon{
    use base qw(Scaliger);
    use dbh;
    sub new{
        my $class = shift;
        # rufe den Konstruktor der Elternklasse
        my $self = $class->SUPER::new(@_);
        return eval{
            my $dbh = $self->dbh();
            my $m = dbh->selectrow_hashref(q(
                select julianday, phase from
                moon where julianday=?
            ), {}, $self->{jd}) || die "Das Objekt kann nicht erstellt werden!\n";
            $self->{phase} = $m->{phase}; # Neue Eingenschaft hinzufügen
            $self; # Instanz zurückgeben
        }
    }
};

my $moon = Moon->new( date => '9.2.2020' ) or die $@;
                 # gemäß Elternklasse

Julianische Tage in MySQL

Wenn ohnehin eine MySQL-Verbindung besteht, kann man die JulianDays auch so berechnen:

select 1721060 + to_days('2020-2-1');

Die Korrektur zu 1721060 Tagen ergibt sich dadurch, daß MySQL durchweg den Gregorianischen Kalender sowie das Jahr 0 annimmt.


Datenschutzerklärung: Diese Seite dient rein privaten Zwecken. Auf den für diese Domäne installierten Seiten werden grundsätzlich keine personenbezogenen Daten erhoben. Das Loggen der Zugriffe mit Ihrer Remote Adresse erfolgt beim Provider soweit das technisch erforderlich ist. s​os­@rolf­rost.de.