Zum Inhalt springen

tuerchen 04 · 4. Dezember

Haken dran ans Geschenkbudget

Code-AufgabeMittelPHP 8.4

Aufgabe

PHP 8.4 bringt Property Hooks und asymmetrische Sichtbarkeit. Getter und Setter ade! Baue damit die Klasse GiftBudget für dein Wichtel-Budget:

  • public int $cents – das Budget in Cent, echte (gespeicherte) Eigenschaft mit set-Hook. Negative Werte → InvalidArgumentException, der alte Wert bleibt.
  • public float $euros – virtuell (kein eigener Speicher): liest cents / 100, schreiben setzt cents auf den auf ganze Cent gerundeten Wert (round(), also kaufmännisch: 0.125 € → 13 Cent). Negative Werte scheitern genauso wie bei cents.
  • public string $label – virtuell und nur lesbar, deutsches Format: 1.234,50 € (Tausenderpunkt, Komma, zwei Nachkommastellen, Leerzeichen, €). Schreiben wirft einen Error.
  • public private(set) array $history – jeder erfolgreich gesetzte Cent-Wert wird angehängt, auch der aus dem Konstruktor und der über euros. Von außen nur lesbar.
  • __construct(int $cents = 0) setzt das Startbudget über denselben Weg (also mit Prüfung).

Die Tests prüfen per Reflection, dass du wirklich Hooks nutzt (z. B. isVirtual()), __get/__set-Magie zählt nicht.

Sichtbare Tests

  • Startbudget und Anzeige

    $b = new GiftBudget(4250);
    echo $b->cents . '|' . $b->euros . '|' . $b->label;

    Erwartet: 4250|42.5|42,50 €

  • Budget in Euro setzen

    $b = new GiftBudget();
    $b->euros = 19.99;
    echo $b->cents . '|' . $b->label . '|' . json_encode($b->history);

    Erwartet: 1999|19,99 €|[0,1999]

  • Negatives Budget ist ungültig

    $b = new GiftBudget(500);
    try {
        $b->cents = -1;
        echo 'keine Ausnahme';
    } catch (InvalidArgumentException $e) {
        echo 'InvalidArgumentException|' . $b->cents;
    }

    Erwartet: InvalidArgumentException|500

Dazu kommen 8 versteckte Tests, die nur beim Abgeben laufen.

Tipp anzeigenkostet keine Punkte

Ein set-Hook bekommt den neuen Wert als Parameter und schreibt ihn selbst mit $this->cents = …. Eine Eigenschaft nur mit get-Hook speichert nichts.

Extra-Stufe (+5 Punkte, freiwillig)

Extra-Stufe (+5): Eine Obergrenze public ?int $max = null (in Cent, null = keine), ebenfalls mit set-Hook. Negativ → InvalidArgumentException. Liegt das aktuelle Budget über einer neuen Obergrenze → OverflowException, max bleibt unverändert. Ist eine Obergrenze gesetzt, wirft jeder Versuch, cents (auch über euros) darüber zu setzen, eine OverflowException; der alte Wert bleibt, und history bekommt keinen Eintrag.

Kein zweiter Editor: Dein Code oben zählt für beides. Die Extra-Tests laufen mit jeder Abgabe (4 Tests).

Mitraten geht mit deinem kostenlosen Entwicklerkonto

Lesen darf jeder. Punkte, Bestenliste und Preise gibt es nur mit Konto.

Auflösung

Mit Hooks hängt die Logik direkt an der Eigenschaft:

  • Ein set-Hook, der $this->cents schreibt, macht die Eigenschaft backed (mit Speicher). Innerhalb des eigenen Hooks greift $this->cents direkt auf den Speicher zu, ohne Endlosschleife.
  • Hooks, die nie auf $this->euros selbst zugreifen, machen die Eigenschaft virtuell. Hat eine virtuelle Eigenschaft nur get, ist sie automatisch read-only – Schreiben wirft einen Error.
  • public private(set) erlaubt Lesen von außen, Schreiben nur in der Klasse. Achtung: Auch $budget->history[] = 1 von außen ist ein Schreibzugriff und scheitert.

Typische Stolperfalle: $euros als normale Eigenschaft speichern – dann läuft sie mit cents auseinander. Siehe Property Hooks.

Musterlösung

<?php

final class GiftBudget
{
    public private(set) array $history = [];

    public ?int $max = null {
        set(?int $value) {
            if ($value !== null && $value < 0) {
                throw new InvalidArgumentException('Die Obergrenze darf nicht negativ sein.');
            }
            if ($value !== null && isset($this->cents) && $this->cents > $value) {
                throw new OverflowException('Das Budget liegt schon über der neuen Obergrenze.');
            }
            $this->max = $value;
        }
    }

    public int $cents {
        set(int $value) {
            if ($value < 0) {
                throw new InvalidArgumentException('Das Budget darf nicht negativ sein.');
            }
            if ($this->max !== null && $value > $this->max) {
                throw new OverflowException('Das Budget überschreitet die Obergrenze.');
            }
            $this->cents = $value;
            $this->history[] = $value;
        }
    }

    public float $euros {
        get => $this->cents / 100;
        set(float $value) {
            $this->cents = (int) round($value * 100);
        }
    }

    public string $label {
        get => number_format($this->cents / 100, 2, ',', '.') . ' €';
    }

    public function __construct(int $cents = 0)
    {
        $this->cents = $cents;
    }
}

Zurück zum Kalender