Validation in a PHP Application (Part 1 - Domain Layer Validation)

As for me, it is quite an important, albeit holivar question. This article accumulates those practices that are close to me and which I adhere to in development.





The article is not intended for beginners, therefore it is normal if some concepts are unknown to you in the course of reading, I tried to briefly disclose them here, and also indicated links to posts in my Beer :: PHP telegram channel , which can reveal a little more or another concept.





In the next part, we will look at user validation and talk about restrictions in the database, but we will start right away with the domain layer of our application, that is, with the very business logic.





Entity validation

Sooner or later, user data passed to our application gets inside the Entity .





Entities are objects that store the state of your application.





But not "just store". An entity must always protect its domain invariants and make sure that it is in a consistent state.





Invariants are some conditions that remain true throughout the life of an object. Typically, invariants convey the internal state of an object.





Entity .





?





, , , . - , .





, , , , .. - - .





class Order
{
    private Product $product;
    private int $quantity;
    
    public function __construct(Product $product, int $quantity)
    {
        if ($quantity <= 0) {
            throw new MinQuantityException();
        }
    }
}
      
      



?





, . Exceptions, . - .





, , . , , , .





. , . - :





$order->getStatus();  
// isn't delivered 
$order->setCancel();
      
      



cancel(), β€” .





class Order
{
    // ...
    public function cancel(): void
    {
        if ($this->status === STATUS::DELIVERED) {
            throw new LogicException(
                sprintf(
                    'Order %s has already been delivered', 
                    $this->id->asString()
                )
            );
        }     
        
        $this->status = STATUS::CANCEL;
    }
}
      
      



Value Objects

, . Account, :





class Account
{
    private string $accountNumber;
    private float $amount = 0.00;
    private string $currency = 'USD';
    
    const NUMBER_OF_CHARACTERS = 16;
    
    public function __construct(string $accountNumber)
    {
        if (strlen($accountNumber) !== self::NUMBER_OF_CHARACTERS) {
            // throw exception 
        }
        
        $this->accountNumber = $accountNumber;
    }
    
    public function putMoney(float $amount, string $currency)
    {
        if ($amount <= 0) {
            // throw exception 
        }
        
        if ($currency !== $this->currency) {
            //thow exception
        }
        
        $this->amount += $amount;
    }
}
      
      



AccountNumber, .





AccountNumber
class AccountNumer
{
    const NUMBER_OF_CHARACTERS = 16;

    private string $accountNumber;

    public function __construct(string $accountNumber)
    {
        if (strlen($accountNumber) !== self::NUMBER_OF_CHARACTERS) {
            // throw exception
        }
        // another rules

        $this->accountNumber = $accountNumber;
    }

    public function __toString(): string
    {
        return $this->accountNumber;
    }
}
      
      



Value Object Money .





Money
class Currency extends SplEnum //   8.1
{
    const __default = self::USD;

    const USD = 'USD';
    const EUR = 'EUR';

    // etc.
}

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

    public function __construct(float $amount, Currency $currency)
    {
        if ($amount <= 0) {
            // throw exception
        }

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

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

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

    public function append(Money $money)
    {
        if ($money->currency() !== $this->currency) {
            // throw exception
        }
        $amount = $this->amount + $money->amount();

        return new self($amount, $this->currency);
    }
}
      
      



Entity :





class Account
{
    private AccountNumer $accountNumber;
    private Money $money;

    public function __construct(AccountNumer $accountNumber)
    {
        $this->accountNumber = $accountNumber;
        $this->money = new Money(0.00, 'USD');
    }

    public function putMoney(Money $money)
    {
        $this->money = $this->money->append($money);
    }
}
      
      



Value Objects, - , .





Whole value concept (Quantity pattern)

, Value Objects, .





, .





. , . . "" "" , , . VO, , .





(Money). , . amount, currency. currency , -, amount.





, , , , , , ..





E - . -, () ( / ).





- .





, VO (, ..). , .





Entity

. Entity ( - ), .





, . (Low Coupling), , .





ID, , Entity ID , , ID ( UUID).





. , .





.





Beer::PHP. , .








All Articles