Skip to content

Commit b68e33f

Browse files
committed
chore: move resource access checker to metadata
GraphQl and Serializer have no dependency on our symfony component anymore
1 parent 4f59677 commit b68e33f

16 files changed

+138
-36
lines changed

src/GraphQl/Resolver/Stage/SecurityPostDenormalizeStage.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
namespace ApiPlatform\GraphQl\Resolver\Stage;
1515

1616
use ApiPlatform\Metadata\GraphQl\Operation;
17-
use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface;
17+
use ApiPlatform\Metadata\ResourceAccessCheckerInterface;
18+
use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface as LegacyResourceAccessCheckerInterface;
1819
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
1920

2021
/**
@@ -24,8 +25,17 @@
2425
*/
2526
final class SecurityPostDenormalizeStage implements SecurityPostDenormalizeStageInterface
2627
{
27-
public function __construct(private readonly ?ResourceAccessCheckerInterface $resourceAccessChecker)
28+
/**
29+
* @var LegacyResourceAccessCheckerInterface|ResourceAccessCheckerInterface
30+
*/
31+
private $resourceAccessChecker;
32+
33+
/**
34+
* @param LegacyResourceAccessCheckerInterface|ResourceAccessCheckerInterface|null $resourceAccessChecker
35+
*/
36+
public function __construct($resourceAccessChecker)
2837
{
38+
$this->resourceAccessChecker = $resourceAccessChecker;
2939
}
3040

3141
/**

src/GraphQl/Resolver/Stage/SecurityPostValidationStage.php

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,31 @@
1414
namespace ApiPlatform\GraphQl\Resolver\Stage;
1515

1616
use ApiPlatform\Metadata\GraphQl\Operation;
17-
use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface;
17+
use ApiPlatform\Metadata\ResourceAccessCheckerInterface;
18+
use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface as LegacyResourceAccessCheckerInterface;
1819
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
1920

2021
/**
2122
* Security post validation stage of GraphQL resolvers.
2223
*
24+
* @deprecated use providers instead of stages
25+
*
2326
* @author Vincent Chalamon <[email protected]>
2427
* @author Grégoire Pineau <[email protected]>
2528
*/
2629
final class SecurityPostValidationStage implements SecurityPostValidationStageInterface
2730
{
28-
public function __construct(private readonly ?ResourceAccessCheckerInterface $resourceAccessChecker)
31+
/**
32+
* @var LegacyResourceAccessCheckerInterface|ResourceAccessCheckerInterface
33+
*/
34+
private $resourceAccessChecker;
35+
36+
/**
37+
* @param LegacyResourceAccessCheckerInterface|ResourceAccessCheckerInterface|null $resourceAccessChecker
38+
*/
39+
public function __construct($resourceAccessChecker)
2940
{
41+
$this->resourceAccessChecker = $resourceAccessChecker;
3042
}
3143

3244
/**

src/GraphQl/Resolver/Stage/SecurityStage.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
namespace ApiPlatform\GraphQl\Resolver\Stage;
1515

1616
use ApiPlatform\Metadata\GraphQl\Operation;
17-
use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface;
17+
use ApiPlatform\Metadata\ResourceAccessCheckerInterface;
18+
use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface as LegacyResourceAccessCheckerInterface;
1819
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
1920

2021
/**
@@ -24,8 +25,17 @@
2425
*/
2526
final class SecurityStage implements SecurityStageInterface
2627
{
27-
public function __construct(private readonly ?ResourceAccessCheckerInterface $resourceAccessChecker)
28+
/**
29+
* @var LegacyResourceAccessCheckerInterface|ResourceAccessCheckerInterface
30+
*/
31+
private $resourceAccessChecker;
32+
33+
/**
34+
* @param LegacyResourceAccessCheckerInterface|ResourceAccessCheckerInterface|null $resourceAccessChecker
35+
*/
36+
public function __construct($resourceAccessChecker)
2837
{
38+
$this->resourceAccessChecker = $resourceAccessChecker;
2939
}
3040

3141
/**

src/GraphQl/Serializer/Exception/ValidationExceptionNormalizer.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace ApiPlatform\GraphQl\Serializer\Exception;
1515

1616
use ApiPlatform\Symfony\Validator\Exception\ValidationException;
17+
use ApiPlatform\Validator\Exception\ConstraintViolationListAwareExceptionInterface;
1718
use GraphQL\Error\Error;
1819
use GraphQL\Error\FormattedError;
1920
use Symfony\Component\HttpFoundation\Response;
@@ -37,7 +38,7 @@ public function __construct(private readonly array $exceptionToStatus = [])
3738
*/
3839
public function normalize(mixed $object, string $format = null, array $context = []): array
3940
{
40-
/** @var ValidationException */
41+
/** @var ConstraintViolationListAwareExceptionInterface */
4142
$validationException = $object->getPrevious();
4243
$error = FormattedError::createFromException($object);
4344
$error['message'] = $validationException->getMessage();
@@ -75,7 +76,10 @@ public function normalize(mixed $object, string $format = null, array $context =
7576
*/
7677
public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool
7778
{
78-
return $data instanceof Error && $data->getPrevious() instanceof ValidationException;
79+
return $data instanceof Error && (
80+
$data->getPrevious() instanceof ConstraintViolationListAwareExceptionInterface
81+
|| $data->getPrevious() instanceof ValidationException
82+
);
7983
}
8084

8185
public function getSupportedTypes($format): array

src/GraphQl/Serializer/ItemNormalizer.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@
2020
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
2121
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
2222
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
23+
use ApiPlatform\Metadata\ResourceAccessCheckerInterface;
2324
use ApiPlatform\Metadata\ResourceClassResolverInterface;
2425
use ApiPlatform\Metadata\Util\ClassInfoTrait;
2526
use ApiPlatform\Serializer\CacheKeyTrait;
2627
use ApiPlatform\Serializer\ItemNormalizer as BaseItemNormalizer;
27-
use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface;
28+
use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface as LegacyResourceAccessCheckerInterface;
2829
use Psr\Log\LoggerInterface;
2930
use Psr\Log\NullLogger;
3031
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
@@ -48,7 +49,10 @@ final class ItemNormalizer extends BaseItemNormalizer
4849

4950
private array $safeCacheKeysCache = [];
5051

51-
public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, private readonly IdentifiersExtractorInterface $identifiersExtractor, ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, LoggerInterface $logger = null, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, ResourceAccessCheckerInterface $resourceAccessChecker = null)
52+
/**
53+
* @param LegacyResourceAccessCheckerInterface|ResourceAccessCheckerInterface $resourceAccessChecker
54+
*/
55+
public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, private readonly IdentifiersExtractorInterface $identifiersExtractor, ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, LoggerInterface $logger = null, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, $resourceAccessChecker = null)
5256
{
5357
parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter, $classMetadataFactory, $logger ?: new NullLogger(), $resourceMetadataCollectionFactory, $resourceAccessChecker);
5458
}

src/GraphQl/Tests/Resolver/Stage/SecurityPostDenormalizeStageTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
use ApiPlatform\GraphQl\Resolver\Stage\SecurityPostDenormalizeStage;
1717
use ApiPlatform\Metadata\GraphQl\Operation;
1818
use ApiPlatform\Metadata\GraphQl\Query;
19-
use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface;
19+
use ApiPlatform\Metadata\ResourceAccessCheckerInterface;
2020
use GraphQL\Type\Definition\ResolveInfo;
2121
use PHPUnit\Framework\TestCase;
2222
use Prophecy\Argument;

src/GraphQl/Tests/Resolver/Stage/SecurityPostValidationStageTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
use ApiPlatform\GraphQl\Resolver\Stage\SecurityPostValidationStage;
1717
use ApiPlatform\Metadata\GraphQl\Operation;
1818
use ApiPlatform\Metadata\GraphQl\Query;
19-
use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface;
19+
use ApiPlatform\Metadata\ResourceAccessCheckerInterface;
2020
use GraphQL\Type\Definition\ResolveInfo;
2121
use PHPUnit\Framework\TestCase;
2222
use Prophecy\Argument;

src/GraphQl/Tests/Resolver/Stage/SecurityStageTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
use ApiPlatform\GraphQl\Resolver\Stage\SecurityStage;
1717
use ApiPlatform\Metadata\GraphQl\Operation;
1818
use ApiPlatform\Metadata\GraphQl\Query;
19-
use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface;
19+
use ApiPlatform\Metadata\ResourceAccessCheckerInterface;
2020
use GraphQL\Type\Definition\ResolveInfo;
2121
use PHPUnit\Framework\TestCase;
2222
use Prophecy\Argument;

src/GraphQl/Tests/Serializer/Exception/ValidationExceptionNormalizerTest.php

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@
1414
namespace ApiPlatform\GraphQl\Tests\Serializer\Exception;
1515

1616
use ApiPlatform\GraphQl\Serializer\Exception\ValidationExceptionNormalizer;
17-
use ApiPlatform\Symfony\Validator\Exception\ValidationException;
17+
use ApiPlatform\Validator\Exception\ConstraintViolationListAwareExceptionInterface;
1818
use GraphQL\Error\Error;
1919
use PHPUnit\Framework\TestCase;
2020
use Symfony\Component\Validator\ConstraintViolation;
2121
use Symfony\Component\Validator\ConstraintViolationList;
22+
use Symfony\Component\Validator\ConstraintViolationListInterface;
2223

2324
/**
2425
* @author Mahmood Bazdar<[email protected]>
@@ -38,10 +39,15 @@ protected function setUp(): void
3839
public function testNormalize(): void
3940
{
4041
$exceptionMessage = 'exception message';
41-
$exception = new ValidationException(new ConstraintViolationList([
42-
new ConstraintViolation('message 1', '', [], '', 'field 1', 'invalid'),
43-
new ConstraintViolation('message 2', '', [], '', 'field 2', 'invalid'),
44-
]), $exceptionMessage);
42+
$exception = new class($exceptionMessage) extends \Exception implements ConstraintViolationListAwareExceptionInterface {
43+
public function getConstraintViolationList(): ConstraintViolationListInterface
44+
{
45+
return new ConstraintViolationList([
46+
new ConstraintViolation('message 1', '', [], '', 'field 1', 'invalid'),
47+
new ConstraintViolation('message 2', '', [], '', 'field 2', 'invalid'),
48+
]);
49+
}
50+
};
4551
$error = new Error('test message', null, null, [], null, $exception);
4652

4753
$normalizedError = $this->validationExceptionNormalizer->normalize($error);
@@ -66,7 +72,8 @@ public function testNormalize(): void
6672

6773
public function testSupportsNormalization(): void
6874
{
69-
$exception = new ValidationException(new ConstraintViolationList([]));
75+
$exception = $this->createStub(ConstraintViolationListAwareExceptionInterface::class);
76+
$exception->method('getConstraintViolationList')->willReturn(new ConstraintViolationList([]));
7077
$error = new Error('test message', null, null, [], null, $exception);
7178

7279
$this->assertTrue($this->validationExceptionNormalizer->supportsNormalization($error));

src/GraphQl/Tests/Serializer/ItemNormalizerTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@
2222
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
2323
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
2424
use ApiPlatform\Metadata\Property\PropertyNameCollection;
25+
use ApiPlatform\Metadata\ResourceAccessCheckerInterface;
2526
use ApiPlatform\Metadata\ResourceClassResolverInterface;
2627
use ApiPlatform\Metadata\UrlGeneratorInterface;
27-
use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface;
2828
use PHPUnit\Framework\TestCase;
2929
use Prophecy\Argument;
3030
use Prophecy\PhpUnit\ProphecyTrait;

src/GraphQl/composer.json

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
},
3232
"require-dev": {
3333
"phpspec/prophecy-phpunit": "^2.0",
34-
"api-platform/symfony": "*@dev || ^3.1",
3534
"api-platform/validator": "*@dev || ^3.1",
3635
"twig/twig": "^3.7",
3736
"symfony/mercure-bundle": "*",
@@ -55,7 +54,8 @@
5554
"sort-packages": true,
5655
"allow-plugins": {
5756
"composer/package-versions-deprecated": true,
58-
"phpstan/extension-installer": true
57+
"phpstan/extension-installer": true,
58+
"php-http/discovery": false
5959
}
6060
},
6161
"extra": {
@@ -79,10 +79,6 @@
7979
"type": "path",
8080
"url": "../Serializer"
8181
},
82-
{
83-
"type": "path",
84-
"url": "../Symfony"
85-
},
8682
{
8783
"type": "path",
8884
"url": "../Validator"
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Metadata;
15+
16+
interface ResourceAccessCheckerInterface
17+
{
18+
/**
19+
* Checks if the given item can be accessed by the current user.
20+
*
21+
* @param array{object?: mixed, previous_object?: mixed, request?: \Symfony\Component\HttpFoundation\Request} $extraVariables
22+
*/
23+
public function isGranted(string $resourceClass, string $expression, array $extraVariables = []): bool;
24+
}

src/Serializer/AbstractItemNormalizer.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,20 @@ abstract class AbstractItemNormalizer extends AbstractObjectNormalizer
6262
protected PropertyAccessorInterface $propertyAccessor;
6363
protected array $localCache = [];
6464
protected array $localFactoryOptionsCache = [];
65+
protected $resourceAccessChecker;
6566

66-
public function __construct(protected PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, protected PropertyMetadataFactoryInterface $propertyMetadataFactory, protected LegacyIriConverterInterface|IriConverterInterface $iriConverter, protected LegacyResourceClassResolverInterface|ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, array $defaultContext = [], ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, protected ?ResourceAccessCheckerInterface $resourceAccessChecker = null, protected ?TagCollectorInterface $tagCollector = null)
67+
/**
68+
* @param LegacyResourceAccessCheckerInterface|ResourceAccessCheckerInterface $resourceAccessChecker
69+
*/
70+
public function __construct(protected PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, protected PropertyMetadataFactoryInterface $propertyMetadataFactory, protected LegacyIriConverterInterface|IriConverterInterface $iriConverter, protected LegacyResourceClassResolverInterface|ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, array $defaultContext = [], ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, $resourceAccessChecker = null, protected ?TagCollectorInterface $tagCollector = null)
6771
{
6872
if (!isset($defaultContext['circular_reference_handler'])) {
6973
$defaultContext['circular_reference_handler'] = fn ($object): ?string => $this->iriConverter->getIriFromResource($object);
7074
}
7175

7276
parent::__construct($classMetadataFactory, $nameConverter, null, null, \Closure::fromCallable($this->getObjectClass(...)), $defaultContext);
7377
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
78+
$this->resourceAccessChecker = $resourceAccessChecker;
7479
$this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory;
7580
}
7681

src/Serializer/ItemNormalizer.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@
2020
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
2121
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
2222
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
23+
use ApiPlatform\Metadata\ResourceAccessCheckerInterface;
2324
use ApiPlatform\Metadata\ResourceClassResolverInterface;
2425
use ApiPlatform\Metadata\UrlGeneratorInterface;
25-
use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface;
26+
use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface as LegacyResourceAccessCheckerInterface;
2627
use Psr\Log\LoggerInterface;
2728
use Psr\Log\NullLogger;
2829
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
@@ -39,7 +40,10 @@ class ItemNormalizer extends AbstractItemNormalizer
3940
{
4041
private readonly LoggerInterface $logger;
4142

42-
public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, LoggerInterface $logger = null, ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory = null, ResourceAccessCheckerInterface $resourceAccessChecker = null, array $defaultContext = [], protected ?TagCollectorInterface $tagCollector = null)
43+
/**
44+
* @param LegacyResourceAccessCheckerInterface|ResourceAccessCheckerInterface $resourceAccessChecker
45+
*/
46+
public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, LoggerInterface $logger = null, ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory = null, $resourceAccessChecker = null, array $defaultContext = [], protected ?TagCollectorInterface $tagCollector = null)
4347
{
4448
parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter, $classMetadataFactory, $defaultContext, $resourceMetadataFactory, $resourceAccessChecker, $tagCollector);
4549

src/Symfony/Security/ResourceAccessCheckerInterface.php

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,15 @@
1313

1414
namespace ApiPlatform\Symfony\Security;
1515

16+
use ApiPlatform\Metadata\ResourceAccessCheckerInterface as MetadataResourceAccessCheckerInterface;
17+
1618
/**
1719
* Checks if the logged user has sufficient permissions to access the given resource.
1820
*
21+
* @deprecated use \ApiPlatform\Metadata\ResourceAccessCheckerInterface instead
22+
*
1923
* @author Kévin Dunglas <[email protected]>
2024
*/
21-
interface ResourceAccessCheckerInterface
25+
interface ResourceAccessCheckerInterface extends MetadataResourceAccessCheckerInterface
2226
{
23-
/**
24-
* Checks if the given item can be accessed by the current user.
25-
*
26-
* @param array{object?: mixed, previous_object?: mixed, request?: \Symfony\Component\HttpFoundation\Request} $extraVariables
27-
*/
28-
public function isGranted(string $resourceClass, string $expression, array $extraVariables = []): bool;
2927
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Validator\Exception;
15+
16+
use ApiPlatform\Metadata\Exception\ExceptionInterface;
17+
use Symfony\Component\Validator\ConstraintViolationListInterface;
18+
19+
/**
20+
* An exception which has a constraint violation list.
21+
*/
22+
interface ConstraintViolationListAwareExceptionInterface extends ExceptionInterface
23+
{
24+
/**
25+
* Gets constraint violations related to this exception.
26+
*/
27+
public function getConstraintViolationList(): ConstraintViolationListInterface;
28+
}

0 commit comments

Comments
 (0)