PHP code example of phphd / exceptional-validation

1. Go to this page and download the library: Download phphd/exceptional-validation library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

phphd / exceptional-validation example snippets


    PhPhD\ExceptionalValidation\Bundle\PhdExceptionalValidationBundle::class => ['all' => true],
    PhPhD\ExceptionToolkit\Bundle\PhdExceptionToolkitBundle::class => ['all' => true],
    

use PhPhD\ExceptionalValidation;
use PhPhD\ExceptionalValidation\Capture;

#[ExceptionalValidation]
final class RegisterUserCommand
{
    #[Capture(LoginAlreadyTakenException::class, 'auth.login.already_taken')]
    private string $login;

    #[Capture(WeakPasswordException::class, 'auth.password.weak')]
    private string $password;
}

$command = new RegisterUserCommand($login, $password);

try {
    $this->commandBus->dispatch($command);
} catch (ExceptionalValidationFailedException $exception) {
    $violationList = $exception->getViolationList();

    return $this->render('registrationForm.html.twig', ['errors' => $violationList]);
} 

use PhPhD\ExceptionalValidation;
use PhPhD\ExceptionalValidation\Capture;
use Symfony\Component\Validator\Constraints as Assert;

#[ExceptionalValidation]
final class OrderProductCommand
{
    #[Assert\Valid]
    private ProductDetails $product;
}

#[ExceptionalValidation]
final class ProductDetails
{
    private int $id;

    #[Capture(InsufficientStockException::class, 'order.insufficient_stock')]
    private string $quantity;

    // ...
}

use PhPhD\ExceptionalValidation;
use PhPhD\ExceptionalValidation\Capture;

#[ExceptionalValidation]
final class TransferMoneyCommand
{
    #[Capture(
        BlockedCardException::class,
        'wallet.blocked_card',
        when: [self::class, 'isWithdrawalCardBlocked'],
    )]
    private int $withdrawalCardId;

    #[Capture(
        BlockedCardException::class,
        'wallet.blocked_card',
        when: [self::class, 'isDepositCardBlocked'],
    )]
    private int $depositCardId;

    public function isWithdrawalCardBlocked(BlockedCardException $exception): bool
    {
        return $exception->getCardId() === $this->withdrawalCardId;
    }

    public function isDepositCardBlocked(BlockedCardException $exception): bool
    {
        return $exception->getCardId() === $this->depositCardId;
    }
}

use PhPhD\ExceptionalValidation\Model\Condition\ValueExceptionMatchCondition;

#[ExceptionalValidation]
final class TransferMoneyCommand
{
    #[Capture(BlockedCardException::class, 'wallet.blocked_card', condition: ValueExceptionMatchCondition::class)]
    private int $withdrawalCardId;

    #[Capture(BlockedCardException::class, 'wallet.blocked_card', condition: ValueExceptionMatchCondition::class)]
    private int $depositCardId;
}

use DomainException;
use PhPhD\ExceptionalValidation\Model\Condition\Exception\ValueException;

final class BlockedCardException extends DomainException implements ValueException
{
    public function __construct(
        private Card $card,
    ) {
        parent::__construct();
    }

    public function getValue(): int
    {
        return $this->card->getId();    
    }
}

use PhPhD\ExceptionalValidation;
use PhPhD\ExceptionalValidation\Capture;
use Symfony\Component\Validator\Constraints as Assert;

#[ExceptionalValidation]
final class CreateOrderCommand
{
    /** @var ProductDetails[] */
    #[Assert\Valid]
    private array $products;
}

#[ExceptionalValidation]
final class ProductDetails
{
    private int $id;

    #[Capture(
        InsufficientStockException::class, 
        'order.insufficient_stock', 
        when: [self::class, 'isStockExceptionForThisProduct'],
    )]
    private string $quantity;

    public function isStockExceptionForThisProduct(InsufficientStockException $exception): bool
    {
        return $exception->getProductId() === $this->id;
    }
}

/**
 * @var Login $login 
 * @var Password $password 
 */
[$login, $password] = awaitAnyN([
    // validate and create Login instance
    async(fn (): Login => $this->createLogin($command->getLogin())),
    // validate and create Password instance
    async(fn (): Password => $this->createPassword($command->getPassword())),
]);

use DomainException;
use PhPhD\ExceptionalValidation\Formatter\ViolationListException;
use Symfony\Component\Validator\ConstraintViolationListInterface;

final class CardNumberValidationFailedException extends DomainException implements ViolationListException
{
    public function __construct(
        private readonly string $cardNumber,
        private readonly ConstraintViolationListInterface $violationList,
    ) {
        parent::__construct((string)$this->violationList);
    }

    public function getViolationList(): ConstraintViolationListInterface
    {
        return $this->violationList;
    }
}

use PhPhD\ExceptionalValidation;
use PhPhD\ExceptionalValidation\Capture;
use PhPhD\ExceptionalValidation\Formatter\ViolationListExceptionFormatter;

#[ExceptionalValidation]
final class IssueCreditCardCommand
{
    #[Capture(
        exception: CardNumberValidationFailedException::class, 
        formatter: ViolationListExceptionFormatter::class,
    )]
    private string $cardNumber;
}

use PhPhD\ExceptionalValidation\Formatter\ExceptionViolationFormatter;
use PhPhD\ExceptionalValidation\Model\Exception\CapturedException;
use Symfony\Component\Validator\ConstraintViolationInterface;

final class RegistrationViolationsFormatter implements ExceptionViolationFormatter
{
    public function __construct(
        #[Autowire('@phd_exceptional_validation.violation_formatter.default')]
        private ExceptionViolationFormatter $defaultFormatter,
    ) {
    }

    /** @return array{ConstraintViolationInterface} */
    public function format(CapturedException $capturedException): ConstraintViolationInterface
    {
        // you can format violations with the default formatter
        // and then slightly adjust only necessary parts
        [$violation] = $this->defaultFormatter->format($capturedException);

        $exception = $capturedException->getException();

        if ($exception instanceof LoginAlreadyTakenException) {
            $violation = new ConstraintViolation(
                $violation->getMessage(),
                $violation->getMessageTemplate(),
                ['loginHolder' => $exception->getLoginHolder()],
                // ...
            );
        }

        if ($exception instanceof WeakPasswordException) {
            // ...
        }

        return [$violation];
    }
}

use PhPhD\ExceptionalValidation;
use PhPhD\ExceptionalValidation\Capture;

#[ExceptionalValidation]
final class RegisterUserCommand
{
    #[Capture(
        LoginAlreadyTakenException::class, 
        'auth.login.already_taken', 
        formatter: RegistrationViolationsFormatter::class,
    )]
    private string $login;

    #[Capture(
        WeakPasswordException::class, 
        'auth.password.weak', 
        formatter: RegistrationViolationsFormatter::class,
    )]
    private string $password;
}