Wundersame Geldvermehrung
Wir können hier ein lustiges Problem mit wundersamer Geldvermehrung schaffen.
Dazu erzeuge ich mir erst mal 20 EUR ($myMoney
).
Dann schenke ich Dir diese 20 EUR ($yourMoney
).
<?php declare(strict_types=1);
namespace spriebsch\tfd\designPatterns\valueObject\money\mutable;
require __DIR__ . '/autoload.php';
$myMoney = new Money(200, Currency::EUR);
$yourMoney = $myMoney;
Stellen wir uns vor, dass Du erfolgreich an der Börse spekulierst hast. Weil Du jetzt mehr Geld hast, möchtest Du das auch in unserer Software abbilden:
<?php declare(strict_types=1);
namespace spriebsch\tfd\designPatterns\valueObject\money\mutable;
class Money
{
public function __construct(
private int $amount,
private Currency $currency
) {}
public function increase(int $amount): void
{
$this->amount += $amount;
}
}
Also erhöhst Du Dein Vermögen:
<?php declare(strict_types=1);
namespace spriebsch\tfd\designPatterns\valueObject\money\mutable;
require __DIR__ . '/autoload.php';
$myMoney = new Money(200, Currency::EUR);
$yourMoney = $myMoney;
// ...
$yourMoney->increase(100);
var_dump($myMoney, $yourMoney);
increase.php:14: class spriebsch\tfd\designPatterns\valueObject\money\mutable\Money#2 (2) { private int $amount => int(300) private spriebsch\tfd\designPatterns\valueObject\money\mutable\Currency $currency => enum spriebsch\tfd\designPatterns\valueObject\money\mutable\Currency::EUR; } increase.php:14: class spriebsch\tfd\designPatterns\valueObject\money\mutable\Money#2 (2) { private int $amount => int(300) private spriebsch\tfd\designPatterns\valueObject\money\mutable\Currency $currency => enum spriebsch\tfd\designPatterns\valueObject\money\mutable\Currency::EUR; }
Aber was ist jetzt da passiert? Ich bin reich geworden, und das ganz von selbst! Wundersame Geldvermehrung! Eine tolle Sache, oder?
Wenn wir das mal aus technischer Sicht betrachten, dann halten nach meiner Schenkung noch immer wir beide den gleichen 20 EUR-Schein in der Hand.
Das sehen wir daran, dass var_dump()
uns bei beiden Objekten jeweils #2
anzeigt.
Das bedeutet, dass sowohl $myMoney
als auch $yourMoney
das identische Objekt referenzieren.
Wir können das auch mit einer expliziten Prüfung nachweisen:
<?php declare(strict_types=1);
namespace spriebsch\tfd\designPatterns\valueObject\money\mutable;
require __DIR__ . '/autoload.php';
$myMoney = new Money(200, Currency::EUR);
$yourMoney = $myMoney;
var_dump($myMoney === $yourMoney);
same.php:10: bool(true)
Wenn Du also Dein Geld vermehrst, indem Du die Methode increase()
aufrufst, verändert sich auch "mein" Betrag.
Ich könnte mich jetzt darüber freuen, aber letztlich sind wir uns denke ich einig, dass Geld nicht einfach so auf den Bäumen wachsen sollte.
Hätten wir statt eines Objektes skalare Werte verwendet, gäbe es dieses Problem nicht, weil PHP Kopien der Werte erzeugt und beim Verändern der Kopie das Original unverändert bleibt:
<?php declare(strict_types=1);
namespace spriebsch\tfd\designPatterns\valueObject\money\mutable;
$myMoney = 200;
$yourMoney = $myMoney;
// ...
$yourMoney += 100;
var_dump($myMoney, $yourMoney);
copy.php:12: int(200) copy.php:12: int(300)
Jetzt sind wir aber wieder zurück bei der Tatsache, dass Betrag und Währung eng zusammengehören, aber als getrennte Variablen verwaltet werden. Das ist fehlerträchtig, weil es sehr leicht passieren kann, dass wir einen Betrag mit der falschen Währung zusammenmischen, oder, weil globale Variablen in PHP ja ger nicht typsicher sind, völlig unsinnige Werte hineinschreiben.
Bedeutet das, dass wir keine Wertobjekte verwenden können? Nein, aber wir müssen das Beste aus zwei Welten kombinieren: die Call by Value-Semantik und die Arbeit mit Objekten.