<?php
abstract class Number {
abstract public function primitive(): mixed;
abstract public function add(Number $other): Number;
abstract protected function addIntNumber(IntNumber $other): Number;
abstract protected function addFloatNumber(FloatNumber $other): Number;
public static function from(int|float $primitive): Number
{
return match (true) {
is_int($primitive) => new IntNumber($primitive),
is_float($primitive) => new FloatNumber($primitive),
default => throw new InvalidArgumentException('blah blah'),
};
}
}
class IntNumber extends Number {
public function __construct(
private int $value
) {}
public function primitive(): int
{
return $this->value;
}
public function add(Number $other): Number
{
return $other->addIntNumber($this);
}
protected function addIntNumber(IntNumber $other): IntNumber
{
return new IntNumber($this->primitive() + $other->primitive());
}
protected function addFloatNumber(FloatNumber $other): FloatNumber
{
return new FloatNumber((float) $this->primitive() + $other->primitive());
}
}
class FloatNumber extends Number {
public function __construct(
private float $value
) {}
public function primitive(): float
{
return $this->value;
}
public function add(Number $other): Number
{
return $other->addFloatNumber($this);
}
protected function addIntNumber(IntNumber $other): FloatNumber
{
return new FloatNumber($this->primitive() + (float) $other->primitive());
}
protected function addFloatNumber(FloatNumber $other): FloatNumber
{
return new FloatNumber($this->primitive() + $other->primitive());
}
}
$primitives = [1, 2, 7.13, 5.012];
$numbers = array_map([Number::class, 'from'], $primitives);
$carry = new IntNumber(0);
foreach ($numbers as $number) {
$carry = $carry->add($number);
echo '$number: ', get_class($number), ' ', $number->primitive(), PHP_EOL;
echo '$carry: ', get_class($carry), ' ', $carry->primitive(), PHP_EOL;
echo PHP_EOL;
}