Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 18 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -544,23 +544,20 @@ class CardBlockedException extends DomainException implements ValueException
This one is very similar to `ValueException` condition \
with the difference that it integrates Symfony's native `ValidationFailedException`.

Specify `ValidationFailedExceptionMatchCondition` to correlate validation exception's value with a property value:
Specify `validated_value` match condition to compare property's value against exception's validated value:

```php
use PhPhD\ExceptionalMatcher\Rule\Object\Try_;
use PhPhD\ExceptionalMatcher\Rule\Object\Property\Catch_;
use Symfony\Component\Validator\Exception\ValidationFailedException;

use const PhPhD\ExceptionalMatcher\Rule\Object\Property\Match\Condition\Validator\validated_value;
use const PhPhD\ExceptionalMatcher\Validator\Formatter\Validator\validator_violations;

#[Try_]
class RegisterUserCommand
{
#[Catch_(
exception: ValidationFailedException::class,
from: Password::class,
match: validated_value,
)]
#[Catch_(ValidationFailedException::class, from: Password::class, match: validated_value, format: validator_violations)]
public string $password;
}
```
Expand Down Expand Up @@ -611,15 +608,13 @@ Then, specify `ViolationListExceptionFormatter` as a `format:` for the `#[Catch_
```php
use PhPhD\ExceptionalMatcher\Rule\Object\Try_;
use PhPhD\ExceptionalMatcher\Rule\Object\Property\Catch_;
use PhPhD\ExceptionalMatcher\Validator\Formatter\ViolationList\ViolationListExceptionFormatter;

use const PhPhD\ExceptionalMatcher\Validator\Formatter\ViolationList\included_violations;

#[Try_]
class IssueCreditCardCommand
{
#[Catch_(
exception: CardNumberValidationFailedException::class,
format: ViolationListExceptionFormatter::class,
)]
#[Catch_(CardNumberValidationFailedException::class, format: included_violations)]
private string $cardNumber;
}
```
Expand All @@ -631,7 +626,7 @@ formatter makes sure that a proper representation of this exception in a `Constr
> it would've been ignored in favour of `ConstraintViolationList` messages.


> Besides that, it's also possible to use `ValidationFailedExceptionFormatter`, \
> Besides that, it's also possible to use `validator_violations` formatter, \
> which can format Symfony's native `ValidationFailedException`.

#### Custom Violation Formatters 🎨🖌️
Expand All @@ -646,8 +641,8 @@ use PhPhD\ExceptionalMatcher\Exception\MatchedException;
use PhPhD\ExceptionalMatcher\Validator\Formatter\ExceptionViolationFormatter;
use Symfony\Component\Validator\ConstraintViolationInterface;

/** @implements ExceptionViolationFormatter<LoginAlreadyTakenException|WeakPasswordException> */
final class RegistrationViolationsFormatter implements ExceptionViolationFormatter
/** @implements ExceptionViolationFormatter<LoginAlreadyTakenException> */
final class LoginAlreadyTakenViolationFormatter implements ExceptionViolationFormatter
{
public function __construct(
#[Autowire(service: ExceptionViolationFormatter::class.'<Throwable>')]
Expand All @@ -662,20 +657,15 @@ final class RegistrationViolationsFormatter implements ExceptionViolationFormatt
// and then adjust only the necessary parts
[$violation] = $this->formatter->format($matchedException);

/** @var LoginAlreadyTakenException $exception */
$exception = $matchedException->getException();

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

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

return [$violation];
}
Expand All @@ -686,7 +676,7 @@ Then, register it as a service:

```yaml
services:
App\Auth\User\Features\Registration\Validation\RegistrationViolationsFormatter:
App\Auth\User\Support\Validation\LoginAlreadyTakenViolationFormatter:
autoconfigure: true
```

Expand Down Expand Up @@ -714,11 +704,8 @@ final class RegisterUserCommand
}
```

In this example, `LoginAlreadyTakenViolationFormatter` is used to format constraint violation for
`LoginAlreadyTakenException`, \
and `WeakPasswordViolationFormatter` formats `WeakPasswordException`.

Though not recommended, you might use a single formatter for the two.
In this example, `LoginAlreadyTakenViolationFormatter` formats constraint violation for `LoginAlreadyTakenException`, \
while `WeakPasswordViolationFormatter` formats `WeakPasswordException`.

### In-depth analysis

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace PhPhD\ExceptionalMatcher\Rule\Object\Assembler\Autoload;

use PhPhD\ExceptionalMatcher\Exception\Formatter\MatchedExceptionFormatter;
use PhPhD\ExceptionalMatcher\Rule\Assembler\MatchingRuleSetAssemblerService;
use PhPhD\ExceptionalMatcher\Rule\Object\Assembler\ObjectMatchingRuleSetAssembler;
use PhPhD\ExceptionalMatcher\Rule\Object\Property\Match\Condition\MatchConditionFactory;
Expand All @@ -22,6 +23,7 @@ final class ConstantsAutoloadingCompilerPass implements CompilerPassInterface
public function process(ContainerBuilder $container): void
{
$classNamesSet = $this->getMatchConditionFactoryIds($container);
$classNamesSet += $this->getExceptionFormatterIds($container);

$definition = $container->getDefinition(MatchingRuleSetAssemblerService::class.'<'.ObjectMatchingRuleSetAssembler::class.'>');

Expand Down Expand Up @@ -52,4 +54,22 @@ private function getMatchConditionFactoryIds(ContainerBuilder $container): array

return $classNames;
}

/** @return array<class-string,true> */
private function getExceptionFormatterIds(ContainerBuilder $container): array
{
$classNames = [];
$taggedServiceIds = array_keys($container->findTaggedServiceIds(MatchedExceptionFormatter::class));

foreach ($taggedServiceIds as $taggedServiceId) {
$def = $container->getDefinition($taggedServiceId);

/** @var class-string $className */
$className = $def->getClass();

$classNames[$className] = true;
}

return $classNames;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use PhPhD\ExceptionalMatcher\Rule\Object\Assembler\ObjectMatchingRuleSetAssemblerService;
use PhPhD\ExceptionalMatcher\Rule\Object\Property\Match\Condition\Value\ExceptionValueMatchConditionFactory;
use PhPhD\ExceptionalMatcher\Tests\Unit\Stub\HandleableMessageStub;
use PhPhD\ExceptionalMatcher\Validator\Formatter\ViolationList\ViolationListExceptionFormatter;

use function class_exists;

Expand Down Expand Up @@ -43,5 +44,6 @@ public function testClassesThatDefineConstantsAreAutoloaded(): void

self::assertNotNull($rule);
self::assertTrue(class_exists(ExceptionValueMatchConditionFactory::class, false));
self::assertTrue(class_exists(ViolationListExceptionFormatter::class, false));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

use function array_map;
use function glob;
use function implode;
use function range;
use function sprintf;
use function str_repeat;

Expand All @@ -21,11 +23,16 @@ public static function loadClassNames(array $classNames): void
/** @codeCoverageIgnore */
public static function loadFiles(string $basePath, int $depth = 7): void
{
$nestingPattern = str_repeat('{,*/}', $depth);
$prefixes = array_map(
static fn (int $level): string => str_repeat('*/', $level),
range(1, $depth),
);

$nestingPattern = '{'.implode(',', $prefixes).'}';

/** @var list<string> $glob */
$glob = glob(
$basePath.sprintf('/%s*MatchConditionFactory.php', $nestingPattern),
$basePath.sprintf('/%s*{MatchConditionFactory,ExceptionFormatter}.php', $nestingPattern),
GLOB_BRACE | GLOB_NOSORT,
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
use Throwable;

/**
* @api - use uid_value constant for a class name instead
* @api - use {@see uid_value} constant for a class reference instead
*
* @implements MatchCondition<InvalidUidException>
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
use Throwable;

/**
* @api - use validated_value constant for a class name instead
* @api - use {@see validated_value} constant for a class reference instead
*
* @implements MatchCondition<ValidationFailedException>
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use Throwable;

/**
* @api - use exception_value constant for a class name instead
* @api - use {@see exception_value} constant for a class reference instead
*
* @implements MatchCondition<ValueException>
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@
use PhPhD\ExceptionalMatcher\Validator\Formatter\ViolationList\ViolationListException;
use Symfony\Component\Validator\Exception\ValidationFailedException;

/** @api */
const validator_violations = ValidationFailedExceptionFormatter::class;

/**
* @api
* @api - use {@see validator_violations} constant for a class reference instead
*
* @implements ExceptionViolationFormatter<ValidationFailedException>
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@
use function array_map;
use function iterator_to_array;

/** @api */
const included_violations = ViolationListExceptionFormatter::class;

/**
* @api
* @api - use {@see included_violations} constant for a class reference instead
*
* @implements ExceptionViolationFormatter<ViolationListException>
*/
Expand Down
6 changes: 3 additions & 3 deletions tests/Unit/Stub/HandleableMessageStub.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@
use PhPhD\ExceptionalMatcher\Tests\Unit\Stub\Exception\StaticPropertyMatchedException;
use PhPhD\ExceptionalMatcher\Validator\Formatter\Main\Tests\Stub\MessageContainingException;
use PhPhD\ExceptionalMatcher\Validator\Formatter\Main\Tests\Stub\ObjectPropertyMatchedException;
use PhPhD\ExceptionalMatcher\Validator\Formatter\Validator\ValidationFailedExceptionFormatter;
use Symfony\Component\Uid\Uuid;
use Symfony\Component\Validator\Exception\ValidationFailedException;

use const PhPhD\ExceptionalMatcher\Rule\Object\Property\Match\Condition\Validator\validated_value;
use const PhPhD\ExceptionalMatcher\Rule\Object\Property\Match\Condition\Value\exception_value;
use const PhPhD\ExceptionalMatcher\Validator\Formatter\Validator\validator_violations;

/**
* @psalm-suppress InvalidAttribute ("Attribute Catch_ is not repeatable")
Expand Down Expand Up @@ -59,11 +59,11 @@ final class HandleableMessageStub
private string $messageText;

#[Catch_(SomeValueException::class, match: exception_value, message: 'oops')]
#[Catch_(ValidationFailedException::class, match: validated_value, format: ValidationFailedExceptionFormatter::class)]
#[Catch_(ValidationFailedException::class, match: validated_value, format: validator_violations)]
private string $notMatchedProperty = 'not matched';

#[Catch_(SomeValueException::class, match: exception_value, message: 'oops')]
#[Catch_(ValidationFailedException::class, match: validated_value, format: ValidationFailedExceptionFormatter::class)]
#[Catch_(ValidationFailedException::class, match: validated_value, format: validator_violations)]
private string $matchedProperty = 'matched!';

#[Catch_(SomeValueException::class, message: 'oops')]
Expand Down
6 changes: 4 additions & 2 deletions tests/Unit/Stub/NestedHandleableMessage.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
use PhPhD\ExceptionalMatcher\Rule\Object\Try_;
use PhPhD\ExceptionalMatcher\Tests\Unit\Stub\Exception\NestedPropertyMatchedException;
use PhPhD\ExceptionalMatcher\Validator\Formatter\ViolationList\Tests\Stub\ViolationListExampleException;
use PhPhD\ExceptionalMatcher\Validator\Formatter\ViolationList\ViolationListExceptionFormatter;
use Symfony\Component\Validator\Constraints\Valid;

use const PhPhD\ExceptionalMatcher\Validator\Formatter\ViolationList\included_violations;

/** @psalm-suppress ArgumentTypeCoercion */
#[Try_]
final class NestedHandleableMessage
{
Expand All @@ -21,7 +23,7 @@ final class NestedHandleableMessage
#[Valid]
private ConditionalMessage $conditionalMessage;

#[Catch_(ViolationListExampleException::class, format: ViolationListExceptionFormatter::class)]
#[Catch_(ViolationListExampleException::class, format: included_violations)]
private int $violationListCapturedProperty;

public static function createWithConditionalMessage(ConditionalMessage $conditionalMessage): self
Expand Down
Loading