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;
example.php

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;
    }
}
Money.php

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
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;
}
Ausgabe von increase.php
increase.php ausführen

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
same.php:10:
bool(true)
Ausgabe von same.php
same.php ausführen

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
copy.php:12:
int(200)
copy.php:12:
int(300)
Ausgabe von copy.php
copy.php ausführen

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.

Dazu machen wir unsere Wertobjekte unveränderlich.