mitmario.dev

Klasse und Objekt

PHP Sandbox 4 Min Lesezeit 4 BeispieleLektion 7 von 8

Du hast in Abschnitt 5 gelernt, wie man mit Arrays Daten zusammenhält, und in diesem Abschnitt, wie man Funktionen schreibt. Beides zusammen reicht erstaunlich weit. Es hat nur eine Schwachstelle, und die zeigt sich erst, wenn ein Programm größer wird.

Das Problem: die Funktionen wissen zu viel und PHP weiß zu wenig

Eine Notiz als Array, Funktionen daneben
<?php

// So haettest du es bis hierher gebaut: die Daten in einem Array,
// die Funktionen daneben.

function notizZeile(array $notiz): string
{
    return $notiz["titel"] . " (" . ($notiz["erledigt"] ? "erledigt" : "offen") . ")";
}

function notizErledigen(array $notiz): array
{
    $notiz["erledigt"] = true;

    return $notiz;
}

$milch = ["titel" => "Milch", "erledigt" => false];
$brot = ["titel" => "Brot", "erledigt" => true];

echo notizZeile($milch), "\n";
echo notizZeile($brot), "\n";

$milch = notizErledigen($milch);

echo notizZeile($milch), "\n";

Sieh dir an, was notizZeile() alles voraussetzt. Sie verlangt ein Array, in dem ein Schlüssel titel steht und ein Schlüssel erledigt. Steht dort etwas anderes, merkt PHP das erst in dem Moment, in dem die Funktion darauf zugreift, und dann ist es eine Warnung mitten im Lauf.

Die Typangabe array hilft dabei kein bisschen. Sie sagt „hier kommt ein Array”, nicht „hier kommt ein Array mit genau diesen zwei Schlüsseln”. Aus PHPs Sicht ist ein Einkaufszettel mit dreißig Einträgen genauso ein Array wie deine Notiz.

Dazu kommt die Sache mit notizErledigen(). Ein Array geht als Kopie in eine Funktion, wie du in Lektion 6.4 gesehen hast. Die Funktion kann die Notiz draußen also gar nicht ändern, sie muss eine neue zurückgeben, und der Aufrufer muss daran denken, sie wieder zuzuweisen. Vergisst er es, passiert nichts Schlimmes, nur eben auch nichts.

Die Klasse ist der Bauplan, das Objekt ist das Gebaute

Dieselbe Notiz als Klasse
<?php

class Notiz
{
    public string $titel;
    public bool $erledigt;

    public function __construct(string $titel, bool $erledigt)
    {
        $this->titel = $titel;
        $this->erledigt = $erledigt;
    }

    public function zeile(): string
    {
        return $this->titel . " (" . ($this->erledigt ? "erledigt" : "offen") . ")";
    }

    public function erledigen(): void
    {
        $this->erledigt = true;
    }
}

$milch = new Notiz("Milch", false);
$brot = new Notiz("Brot", true);

echo $milch->zeile(), "\n";
echo $brot->zeile(), "\n";

$milch->erledigen();

echo $milch->zeile(), "\n";

Eine Klasse beschreibt, wie etwas aussieht und was es kann. Sie ist ein Bauplan, kein Ding: Vom Bauplan eines Hauses kann niemand einziehen. Das Ding entsteht mit new, und es heißt Objekt.

In der Klasse stehen zwei Sorten Zeilen:

  • Eigenschaften sind die Daten. public string $titel; sagt: Jede Notiz hat einen Titel, und der ist eine Zeichenkette. Das ist genau die Zusage, die dem Array gefehlt hat.
  • Methoden sind die Funktionen, die zu diesen Daten gehören. Sie werden mit dem Pfeil aufgerufen: $milch->zeile().

Der Pfeil -> ist dabei kein neues Konzept, sondern nur eine andere Schreibweise für „da drin”. Wo beim Array $notiz["titel"] stand, steht jetzt $notiz->titel, ohne Anführungszeichen und ohne Klammern.

Und $this? Das ist das Objekt selbst, von innen gesehen. In zeile() weiß PHP nicht von sich aus, welche Notiz gemeint ist, es könnten ja tausend geben. $this->titel heißt deshalb: der Titel von genau der Notiz, an der diese Methode gerade aufgerufen wurde.

__construct ist eine Methode mit besonderem Namen. PHP ruft sie beim new von selbst auf, und sie ist der Ort, an dem ein Objekt vollständig wird. Mehr dazu in Lektion 13.1, wenn es mit den Objekten richtig losgeht.

Jedes Objekt hat seinen eigenen Zustand

Ein Bauplan, drei Objekte
<?php

class Zaehler
{
    public int $stand = 0;

    public function hoch(): void
    {
        $this->stand = $this->stand + 1;
    }
}

$links = new Zaehler();
$mitte = new Zaehler();
$rechts = new Zaehler();

$links->hoch();
$links->hoch();
$mitte->hoch();

echo "links:  ", $links->stand, "\n";
echo "mitte:  ", $mitte->stand, "\n";
echo "rechts: ", $rechts->stand, "\n";

Drei Objekte aus derselben Klasse, und jedes hat seinen eigenen $stand. Das ist der Unterschied zwischen einer Klasse und einer Funktion mit static aus Lektion 6.4: Dort gibt es einen Behälter für alle Aufrufe, hier bekommt jedes Objekt seinen eigenen.

Beachte auch, dass hoch() nichts zurückgibt. Sie muss nicht: Das Objekt geht nicht als Kopie irgendwohin, $links ist ein Verweis auf das Objekt. Wer die Methode aufruft, ändert das Original. Genau die Umständlichkeit von notizErledigen() fällt damit weg.

Was ein Tippfehler kostet

Ein Tippfehler, zweimal verglichen
<?php

// Derselbe Tippfehler zweimal. Sieh dir im Terminal beide Kanaele an:
// die Ausgabe und den Fehlerkanal.

$notiz = ["titel" => "Milch", "erledigt" => false];
$notiz["erldigt"] = true;

echo "Schluessel im Array: ", implode(", ", array_keys($notiz)), "\n";
echo "Und erledigt ist:    ";
var_dump($notiz["erledigt"]);

class Notiz
{
    public string $titel = "Milch";
    public bool $erledigt = false;
}

$objekt = new Notiz();
$objekt->erldigt = true;

echo "Und erledigt ist:    ";
var_dump($objekt->erledigt);

Beide Male steht erldigt statt erledigt, beide Male bleibt der richtige Wert unverändert. Der Unterschied steht auf dem Fehlerkanal.

Beim Array passiert gar nichts. $notiz["erldigt"] = true; legt einen neuen Schlüssel an, das ist eine völlig normale Sache, und PHP hat keine Möglichkeit zu wissen, dass du das nicht wolltest. Der Array hat danach drei Schlüssel, und der Fehler wandert weiter, bis irgendwo eine Zahl falsch ist. Im Reiter Debug kannst du ihn dabei beobachten: Nach Aufzeichnen steht ab der Zeile mit dem Tippfehler erldigt: true als dritter Eintrag neben dem unveränderten erledigt: false, beim Array wie beim Objekt.

Beim Objekt meldet sich PHP: Deprecated: Creation of dynamic property Notiz::$erldigt is deprecated. Seit PHP 8.2 ist das Anlegen einer Eigenschaft, die in der Klasse nicht steht, verwarnt, und in PHP 9 soll ein Fehler daraus werden. Die Meldung nennt dabei beides beim Namen: die Klasse und die Eigenschaft.

Sei fair zu dir selbst: Das ist eine Warnung und kein Absturz, und das Programm läuft weiter. Aber eine Warnung ist unendlich viel mehr als gar nichts. Der nächste Abschnitt handelt von nichts anderem, und sein Kernsatz gilt schon hier: Ein Fehler, der sich meldet, ist der billigste Fehler, den es gibt.

Wann sich eine Klasse lohnt

Nicht immer. Ein Array mit drei Werten, das an einer einzigen Stelle benutzt wird, bleibt ein Array, und eine Funktion, die aus zwei Zahlen eine dritte macht, bleibt eine Funktion.

Die Klasse lohnt sich ab dem Moment, in dem dieselben Daten an mehreren Stellen auftauchen und mehrere Funktionen sich darauf verlassen, wie sie aussehen. Dann ist die Klasse der Ort, an dem diese Absprache einmal aufgeschrieben steht, statt in fünf Funktionsköpfen zu stecken.

Zum Mitnehmen

Bis hierher waren deine Daten Arrays, und die Funktionen darauf standen daneben und hofften das Beste. Eine Klasse legt beides zusammen. Der Gewinn zeigt sich nicht am schönen Code, sondern am ersten Tippfehler.

Jetzt du

Basis Konto, kostenlos

Zu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.

Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.

In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.

Was in dieser Lektion steckt

  • Artikel mit 4 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe, dein Code läuft auf einem Server

    Öffnet sich mit dem Basis Konto.