/ Gists / 24. Value -> Object
On gists

24. Value -> Object

PHP Patterns

example1.php Raw #

<?php
// https://forum.nette.org/cs/34970-rozne-ceny-za-roznych-okolnosti#p218919
class Product
{
	private Price $price;

	public function getPrice(): Price
	{
		return $this->price;
	}
}

//

	public function getPrice(...$modifiers): Price
	{
		$price = $this->price;
		foreach($modifiers as $modifier) {
			$price = $price->modify($modifier);
		}
		return $price;
	}
	
	//
	
	class PriceResolver
{
	private array $modifiers = [];

	public function setCustomer(IModifier $modifier): void
	{
		$this->modifiers[] = $modifier;
	}

	public function setSeason(IModifier $modifier): void
	{
		$this->modifiers[] = $modifier;
	}

	public function setOtherRule(IModifier $modifier): void
	{
		$this->modifiers[] = $modifier;
	}

	public function resolve(Product $product): Price
	{
		$price = $product->getPrice();
		foreach($modifiers as $modifier) {
			$price = $price->modify($modifier);
		}
		return $price;
	}
}

class Price
{
    private float $amount;
    private string $currency;

    public function __construct(float $amount, string $currency)
    {
        $this->amount = $amount;
        $this->currency = $currency;
    }

    public function getAmount(int $precision = 2): float
    {
        return round($this->amount, $precision);
    }

    public function getCurrency(): string
    {
        return $this->currency;
    }

    public function modify(Modifier $modifier): Price
    {
        if($modifier->isPercentage()) {
            $amount = $this->getAmount(4) * (100 + $modifier->getAmount()) / 100;
        }
        else {
            $amount = $this->getAmount(4) + $modifier->getAmount();
        }
        return new self($amount, $this->currency);
    }

    public function multiply(float $multiplier): Price
    {
        return new self($this->amount * $multiplier, $this->currency);
    }

    public function divide(float $divisor): Price
    {
        return new self($this->amount / $divisor, $this->currency);
    }

    public function add(Price $price): Price
    {
        if($this->currency !== $price->getCurrency()) {
            throw new CurrencyMismatchException("Currency missmatch");
        }
        return new self($this->amount + $price->getAmount(4), $this->currency);
    }

    public function minus(Price $price): Price
    {
        if($this->currency !== $price->getCurrency()) {
            throw new CurrencyMismatchException("Currency missmatch");
        }
        return new self($this->amount - $price->getAmount(4), $this->currency);
    }

    public function equals(Price $other): bool
    {
        return $this->getAmount(4) === $other->getAmount(4) and $this->currency === $other->getCurrency();
    }
}

class Modifier
{
    const TYPE_PERCENTAGE = "P";
    const TYPE_ABSOLUTE = "A";

    private string $type;
    private float $amount;

    public function __construct(string $type, float $amount)
    {
        $this->type = $type;
        $this->amount = $amount;
    }

    public function getType(): string
    {
        return $this->type;
    }

    public function getAmount(int $precision = 2): float
    {
        return round($this->amount, $precision);
    }

    public function cumulate(Modifier $modifier): Modifier
    {
        if($this->type !== $modifier->getType()) {
            throw new TypeMissmatchException("Type missmatch");
        }
        return new self($this->type, $this->amount + $modifier->getAmount(4));
    }

    public function isPercentage(): bool
    {
        return $this->type === self::TYPE_PERCENTAGE;
    }

    public function isAbsolute(): bool
    {
        return $this->type === self::TYPE_ABSOLUTE;
    }
}

example2.php Raw #

<?php
// https://dev.to/ianrodrigues/writing-value-objects-in-php-4acg
<?php declare(strict_types=1);

final class Price
{
    const USD = 'USD';
    const CAD = 'CAD';

    /** @var float */
    private $amount;

    /** @var string */
    private $currency;

    public function __construct(float $amount, string $currency = 'USD')
    {
        if ($amount < 0) {
            throw new \InvalidArgumentException("Amount should be a positive value: {$amount}.");
        }

        if (!in_array($currency, $this->getAvailableCurrencies())) {
            throw new \InvalidArgumentException("Currency should be a valid one: {$currency}.");
        }

        $this->amount = $amount;
        $this->currency = $currency;
    }

    private function getAvailableCurrencies(): array
    {
        return [self::USD, self::CAD];
    }

    public function getAmount(): float
    {
        return $this->amount;
    }

    public function getCurrency(): string
    {
        return $this->currency;
    }
}

final class Price
{
    // ...

    private function hasSameCurrency(Price $price): bool
    {
        return $this->currency === $price->currency;
    }

    public function sum(Price $price): self
    {
        if (!$this->hasSameCurrency($price)) {
            throw \InvalidArgumentException(
                "You can only sum values with the same currency: {$this->currency} !== {$price->currency}."
            );
        }

        return new self($this->amount + $price->amount, $this->currency);
    }
    
    
    final class Price
{
    // ...

    public function isEqualsTo(Price $price): bool
    {
        return $this->amount === $price->amount && $this->currency === $price->currency;
    }
}


final class Price
{
    // ...

    private function hash(): string
    {
        return md5("{$this->amount}{$this->currency}");
    }

    public function isEqualsTo(Price $price): bool
    {
        return $this->hash() === $price->hash();
    }
}


final class Price
{
    // ...

    public function __construct(float $amount, string $currency = 'USD')
    {
        if ($amount < 0) {
            throw new \InvalidArgumentException("Amount should be a positive value: {$amount}.");
        }

        if (!in_array($currency, $this->getAvailableCurrencies())) {
            throw new \InvalidArgumentException("Currency should be a valid one: {$currency}.");
        }

        $this->amount = $amount;
        $this->currency = $currency;
    }
}