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 mitset-Hook. Negative Werte →InvalidArgumentException, der alte Wert bleibt.public float $euros– virtuell (kein eigener Speicher): liestcents / 100, schreiben setztcentsauf den auf ganze Cent gerundeten Wert (round(), also kaufmännisch: 0.125 € → 13 Cent). Negative Werte scheitern genauso wie beicents.public string $label– virtuell und nur lesbar, deutsches Format:1.234,50 €(Tausenderpunkt, Komma, zwei Nachkommastellen, Leerzeichen, €). Schreiben wirft einenError.public private(set) array $history– jeder erfolgreich gesetzte Cent-Wert wird angehängt, auch der aus dem Konstruktor und der übereuros. 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 anzeigenTipp ausblendenkostet 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->centsschreibt, macht die Eigenschaft backed (mit Speicher). Innerhalb des eigenen Hooks greift$this->centsdirekt auf den Speicher zu, ohne Endlosschleife. - Hooks, die nie auf
$this->eurosselbst zugreifen, machen die Eigenschaft virtuell. Hat eine virtuelle Eigenschaft nurget, ist sie automatisch read-only – Schreiben wirft einenError. public private(set)erlaubt Lesen von außen, Schreiben nur in der Klasse. Achtung: Auch$budget->history[] = 1von 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;
}
}