• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

api-platform / core / 19799301771

30 Nov 2025 01:04PM UTC coverage: 25.229% (-0.03%) from 25.257%
19799301771

push

github

web-flow
fix(metadata): repeatable attribute mutators (#7542)

14557 of 57700 relevant lines covered (25.23%)

28.11 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.18
/src/Validator/Exception/ValidationException.php
1
<?php
2

3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <dunglas@gmail.com>
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\JsonSchema\SchemaFactory;
17
use ApiPlatform\Metadata\ApiProperty;
18
use ApiPlatform\Metadata\Error as ErrorOperation;
19
use ApiPlatform\Metadata\ErrorResource;
20
use ApiPlatform\Metadata\Exception\HttpExceptionInterface;
21
use ApiPlatform\Metadata\Exception\ProblemExceptionInterface;
22
use ApiPlatform\Metadata\Exception\RuntimeException;
23
use ApiPlatform\Metadata\Util\CompositeIdentifierParser;
24
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface as SymfonyHttpExceptionInterface;
25
use Symfony\Component\Serializer\Attribute\Groups;
26
use Symfony\Component\Serializer\Attribute\SerializedName;
27
use Symfony\Component\Validator\ConstraintViolationList;
28
use Symfony\Component\Validator\ConstraintViolationListInterface;
29
use Symfony\Component\WebLink\Link;
30

31
/**
32
 * Thrown when a validation error occurs.
33
 *
34
 * @author Kévin Dunglas <dunglas@gmail.com>
35
 */
36
#[ErrorResource(
37
    uriTemplate: '/validation_errors/{id}',
38
    status: 422,
39
    uriVariables: ['id'],
40
    openapi: false,
41
    outputFormats: [
42
        'jsonapi' => ['application/vnd.api+json'],
43
        'jsonld' => ['application/ld+json'],
44
        'json' => ['application/problem+json', 'application/json'],
45
        'xml' => ['application/xml', 'text/xml'],
46
    ],
47
    provider: 'api_platform.validator.state.error_provider',
48
    shortName: 'ConstraintViolation',
49
    description: 'Unprocessable entity',
50
    operations: [
51
        new ErrorOperation(
52
            name: '_api_validation_errors_problem',
53
            outputFormats: [
54
                'json' => ['application/problem+json'],
55
            ],
56
            normalizationContext: [
57
                SchemaFactory::OPENAPI_DEFINITION_NAME => '',
58
                'groups' => ['json'],
59
                'ignored_attributes' => ['trace', 'file', 'line', 'code', 'message', 'traceAsString', 'previous'],
60
                'skip_null_values' => true,
61
            ]
62
        ),
63
        new ErrorOperation(
64
            name: '_api_validation_errors_hydra',
65
            outputFormats: ['jsonld' => ['application/problem+json', 'application/ld+json']],
66
            links: [new Link(rel: 'http://www.w3.org/ns/json-ld#error', href: 'http://www.w3.org/ns/hydra/error')],
67
            normalizationContext: [
68
                SchemaFactory::OPENAPI_DEFINITION_NAME => '',
69
                'groups' => ['jsonld'],
70
                'ignored_attributes' => ['trace', 'file', 'line', 'code', 'message', 'traceAsString', 'previous'],
71
                'skip_null_values' => true,
72
            ]
73
        ),
74
        new ErrorOperation(
75
            name: '_api_validation_errors_jsonapi',
76
            outputFormats: ['jsonapi' => ['application/vnd.api+json']],
77
            normalizationContext: [
78
                SchemaFactory::OPENAPI_DEFINITION_NAME => '',
79
                'disable_json_schema_serializer_groups' => false,
80
                'groups' => ['jsonapi'],
81
                'skip_null_values' => true,
82
                'ignored_attributes' => ['trace', 'file', 'line', 'code', 'message', 'traceAsString', 'previous'],
83
            ]
84
        ),
85
        new ErrorOperation(
86
            name: '_api_validation_errors_xml',
87
            outputFormats: [
88
                'xml' => ['application/xml', 'text/xml'],
89
            ],
90
            normalizationContext: [
91
                'groups' => ['json'],
92
                'ignored_attributes' => ['trace', 'file', 'line', 'code', 'message', 'traceAsString', 'previous'],
93
                'skip_null_values' => true,
94
            ]
95
        ),
96
    ],
97
    graphQlOperations: []
98
)]
99
#[ApiProperty(property: 'traceAsString', hydra: false)]
100
#[ApiProperty(property: 'string', hydra: false)]
101
class ValidationException extends RuntimeException implements ConstraintViolationListAwareExceptionInterface, \Stringable, ProblemExceptionInterface, HttpExceptionInterface, SymfonyHttpExceptionInterface
102
{
103
    private int $status = 422;
104
    protected ?string $errorTitle = null;
105
    private ConstraintViolationListInterface $constraintViolationList;
106

107
    public function __construct(string|ConstraintViolationListInterface $message = new ConstraintViolationList(), string|int|null $code = null, int|\Throwable|null $previous = null, \Throwable|string|null $errorTitle = null)
108
    {
109
        $this->errorTitle = $errorTitle;
124✔
110

111
        if ($message instanceof ConstraintViolationListInterface) {
124✔
112
            $this->constraintViolationList = $message;
124✔
113
            parent::__construct($this->__toString(), $code ?? 0, $previous);
124✔
114

115
            return;
124✔
116
        }
117

118
        $this->constraintViolationList = new ConstraintViolationList();
×
119

120
        trigger_deprecation('api_platform/core', '5.0', \sprintf('The "%s" exception will have a "%s" first argument in 5.x.', self::class, ConstraintViolationListInterface::class));
×
121
        parent::__construct($message ?: $this->__toString(), $code ?? 0, $previous);
×
122
    }
123

124
    public function getId(): string
125
    {
126
        $ids = [];
68✔
127
        foreach ($this->getConstraintViolationList() as $violation) {
68✔
128
            $ids[] = $violation->getCode();
68✔
129
        }
130

131
        $id = 1 < \count($ids) ? CompositeIdentifierParser::stringify(identifiers: $ids) : ($ids[0] ?? null);
68✔
132

133
        if (!$id) {
68✔
134
            return spl_object_hash($this);
2✔
135
        }
136

137
        return $id;
66✔
138
    }
139

140
    #[Groups(['jsonld'])]
141
    #[ApiProperty(writable: false, initializable: false)]
142
    public function getDescription(): string
143
    {
144
        return $this->detail;
68✔
145
    }
146

147
    #[Groups(['jsonld', 'json', 'jsonapi'])]
148
    #[ApiProperty(writable: false, initializable: false)]
149
    public function getType(): string
150
    {
151
        return '/validation_errors/'.$this->getId();
68✔
152
    }
153

154
    #[Groups(['jsonld', 'json', 'jsonapi'])]
155
    #[ApiProperty(writable: false, initializable: false)]
156
    public function getTitle(): ?string
157
    {
158
        return $this->errorTitle ?? 'An error occurred';
122✔
159
    }
160

161
    #[Groups(['jsonld', 'json', 'jsonapi'])]
162
    #[ApiProperty(writable: false, initializable: false)]
163
    private string $detail;
164

165
    public function getDetail(): ?string
166
    {
167
        return $this->detail;
68✔
168
    }
169

170
    public function setDetail(string $detail): void
171
    {
172
        $this->detail = $detail;
68✔
173
    }
174

175
    #[Groups(['jsonld', 'json', 'jsonapi'])]
176
    public function getStatus(): ?int
177
    {
178
        return $this->status;
122✔
179
    }
180

181
    public function setStatus(int $status): void
182
    {
183
        $this->status = $status;
68✔
184
    }
185

186
    #[Groups(['jsonld', 'json', 'jsonapi'])]
187
    #[ApiProperty(writable: false, initializable: false)]
188
    public function getInstance(): ?string
189
    {
190
        return null;
68✔
191
    }
192

193
    #[SerializedName('violations')]
194
    #[Groups(['json', 'jsonld'])]
195
    #[ApiProperty(
196
        jsonldContext: ['@type' => 'ConstraintViolationList'],
197
        schema: [
198
            'type' => 'array',
199
            'items' => [
200
                'type' => 'object',
201
                'properties' => [
202
                    'propertyPath' => ['type' => 'string', 'description' => 'The property path of the violation'],
203
                    'message' => ['type' => 'string', 'description' => 'The message associated with the violation'],
204
                ],
205
            ],
206
        ]
207
    )]
208
    public function getConstraintViolationList(): ConstraintViolationListInterface
209
    {
210
        return $this->constraintViolationList;
124✔
211
    }
212

213
    public function __toString(): string
214
    {
215
        $message = '';
124✔
216
        foreach ($this->getConstraintViolationList() as $violation) {
124✔
217
            if ('' !== $message) {
70✔
218
                $message .= "\n";
20✔
219
            }
220
            if ($propertyPath = $violation->getPropertyPath()) {
70✔
221
                $message .= "$propertyPath: ";
70✔
222
            }
223

224
            $message .= $violation->getMessage();
70✔
225
        }
226

227
        return $message;
124✔
228
    }
229

230
    public function getStatusCode(): int
231
    {
232
        return $this->status;
68✔
233
    }
234

235
    public function getHeaders(): array
236
    {
237
        return [];
68✔
238
    }
239
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc