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

api-platform / core / 15775135891

20 Jun 2025 08:42AM UTC coverage: 22.065% (+0.2%) from 21.876%
15775135891

push

github

soyuka
Merge 4.1

13 of 103 new or added lines in 10 files covered. (12.62%)

868 existing lines in 35 files now uncovered.

11487 of 52060 relevant lines covered (22.06%)

21.72 hits per line

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

87.27
/src/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactory.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\Symfony\Validator\Metadata\Property;
15

16
use ApiPlatform\JsonSchema\Metadata\Property\Factory\SchemaPropertyMetadataFactory;
17
use ApiPlatform\Metadata\ApiProperty;
18
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
19
use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaRestrictionMetadataInterface;
20
use ApiPlatform\Symfony\Validator\ValidationGroupsExtractorTrait;
21
use Psr\Container\ContainerInterface;
22
use Symfony\Component\Validator\Constraint;
23
use Symfony\Component\Validator\Constraints\Bic;
24
use Symfony\Component\Validator\Constraints\CardScheme;
25
use Symfony\Component\Validator\Constraints\Compound;
26
use Symfony\Component\Validator\Constraints\Currency;
27
use Symfony\Component\Validator\Constraints\Date;
28
use Symfony\Component\Validator\Constraints\DateTime;
29
use Symfony\Component\Validator\Constraints\Email;
30
use Symfony\Component\Validator\Constraints\File;
31
use Symfony\Component\Validator\Constraints\Iban;
32
use Symfony\Component\Validator\Constraints\Image;
33
use Symfony\Component\Validator\Constraints\Isbn;
34
use Symfony\Component\Validator\Constraints\Issn;
35
use Symfony\Component\Validator\Constraints\NotBlank;
36
use Symfony\Component\Validator\Constraints\NotNull;
37
use Symfony\Component\Validator\Constraints\Sequentially;
38
use Symfony\Component\Validator\Constraints\Time;
39
use Symfony\Component\Validator\Constraints\Url;
40
use Symfony\Component\Validator\Constraints\Uuid;
41
use Symfony\Component\Validator\Mapping\ClassMetadataInterface as ValidatorClassMetadataInterface;
42
use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface as ValidatorMetadataFactoryInterface;
43
use Symfony\Component\Validator\Mapping\PropertyMetadataInterface as ValidatorPropertyMetadataInterface;
44

45
/**
46
 * Decorates a metadata loader using the validator.
47
 *
48
 * @author Kévin Dunglas <dunglas@gmail.com>
49
 */
50
final class ValidatorPropertyMetadataFactory implements PropertyMetadataFactoryInterface
51
{
52
    use ValidationGroupsExtractorTrait {
53
        getValidationGroups as extractValidationGroups;
54
    }
55

56
    /**
57
     * @var string[] A list of constraint classes making the entity required
58
     */
59
    public const REQUIRED_CONSTRAINTS = [NotBlank::class, NotNull::class];
60

61
    public const SCHEMA_MAPPED_CONSTRAINTS = [
62
        Url::class => 'https://schema.org/url',
63
        Email::class => 'https://schema.org/email',
64
        Uuid::class => 'https://schema.org/identifier',
65
        CardScheme::class => 'https://schema.org/identifier',
66
        Bic::class => 'https://schema.org/identifier',
67
        Iban::class => 'https://schema.org/identifier',
68
        Date::class => 'https://schema.org/Date',
69
        DateTime::class => 'https://schema.org/DateTime',
70
        Time::class => 'https://schema.org/Time',
71
        Image::class => 'https://schema.org/image',
72
        File::class => 'https://schema.org/MediaObject',
73
        Currency::class => 'https://schema.org/priceCurrency',
74
        Isbn::class => 'https://schema.org/isbn',
75
        Issn::class => 'https://schema.org/issn',
76
    ];
77

78
    /**
79
     * @param PropertySchemaRestrictionMetadataInterface[] $restrictionsMetadata
80
     */
81
    public function __construct(
82
        private readonly ValidatorMetadataFactoryInterface $validatorMetadataFactory,
83
        private readonly PropertyMetadataFactoryInterface $decorated,
84
        private readonly iterable $restrictionsMetadata = [],
85
        ?ContainerInterface $container = null,
86
    ) {
87
        $this->container = $container;
582✔
88
    }
89

90
    /**
91
     * {@inheritdoc}
92
     */
93
    public function create(string $resourceClass, string $property, array $options = []): ApiProperty
94
    {
95
        $propertyMetadata = $this->decorated->create($resourceClass, $property, $options);
168✔
96

97
        $extraProperties = $propertyMetadata->getExtraProperties() ?? [];
168✔
98
        // see AttributePropertyMetadataFactory
99
        if (true === ($extraProperties[SchemaPropertyMetadataFactory::JSON_SCHEMA_USER_DEFINED] ?? false)) {
168✔
100
            // schema seems to have been declared by the user: do not override nor complete user value
101
            return $propertyMetadata;
20✔
102
        }
103

104
        $required = $propertyMetadata->isRequired();
168✔
105
        $types = $propertyMetadata->getTypes();
168✔
106
        $schema = $propertyMetadata->getSchema();
168✔
107

108
        if (null !== $required && $types && $schema) {
168✔
UNCOV
109
            return $propertyMetadata;
×
110
        }
111

112
        $validatorClassMetadata = $this->validatorMetadataFactory->getMetadataFor($resourceClass);
168✔
113

114
        if (!$validatorClassMetadata instanceof ValidatorClassMetadataInterface) {
168✔
UNCOV
115
            throw new \UnexpectedValueException(\sprintf('Validator class metadata expected to be of type "%s".', ValidatorClassMetadataInterface::class));
×
116
        }
117

118
        $validationGroups = $this->getValidationGroups($validatorClassMetadata, $options);
168✔
119
        $restrictions = [];
168✔
120
        $types ??= [];
168✔
121

122
        foreach ($validatorClassMetadata->getPropertyMetadata($property) as $validatorPropertyMetadata) {
168✔
123
            foreach ($this->getPropertyConstraints($validatorPropertyMetadata, $validationGroups) as $constraint) {
20✔
124
                if (null === $required && $this->isRequired($constraint)) {
20✔
125
                    $required = true;
16✔
126
                }
127

128
                $type = self::SCHEMA_MAPPED_CONSTRAINTS[$constraint::class] ?? null;
20✔
129

130
                if ($type && !\in_array($type, $types, true)) {
20✔
131
                    $types[] = $type;
8✔
132
                }
133

134
                foreach ($this->restrictionsMetadata as $restrictionMetadata) {
20✔
135
                    if ($restrictionMetadata->supports($constraint, $propertyMetadata)) {
20✔
136
                        $restrictions[] = $restrictionMetadata->create($constraint, $propertyMetadata);
4✔
137
                    }
138
                }
139
            }
140
        }
141

142
        if ($types) {
168✔
143
            $propertyMetadata = $propertyMetadata->withTypes($types);
8✔
144
        }
145

146
        $propertyMetadata = $propertyMetadata->withRequired($required ?? false);
168✔
147

148
        if (!empty($restrictions)) {
168✔
149
            if (null === $schema) {
4✔
150
                $schema = [];
4✔
151
            }
152

153
            $schema += array_merge(...$restrictions);
4✔
154
            $propertyMetadata = $propertyMetadata->withSchema($schema);
4✔
155
        }
156

157
        return $propertyMetadata;
168✔
158
    }
159

160
    /**
161
     * Returns the list of validation groups.
162
     */
163
    private function getValidationGroups(ValidatorClassMetadataInterface $classMetadata, array $options): array
164
    {
165
        if (null !== ($groups = $this->extractValidationGroups($options['validation_groups'] ?? null))) {
168✔
UNCOV
166
            return $groups;
×
167
        }
168

169
        if (!method_exists($classMetadata, 'getDefaultGroup')) {
168✔
UNCOV
170
            throw new \UnexpectedValueException(\sprintf('Validator class metadata expected to have method "%s".', 'getDefaultGroup'));
×
171
        }
172

173
        return [$classMetadata->getDefaultGroup()];
168✔
174
    }
175

176
    /**
177
     * Tests if the property is required because of its validation groups.
178
     */
179
    private function getPropertyConstraints(
180
        ValidatorPropertyMetadataInterface $validatorPropertyMetadata,
181
        array $groups,
182
    ): array {
183
        $constraints = [];
20✔
184

185
        foreach ($groups as $validationGroup) {
20✔
186
            if (!\is_string($validationGroup)) {
20✔
UNCOV
187
                continue;
×
188
            }
189

190
            foreach ($validatorPropertyMetadata->findConstraints($validationGroup) as $propertyConstraint) {
20✔
191
                if ($propertyConstraint instanceof Sequentially || $propertyConstraint instanceof Compound) {
20✔
UNCOV
192
                    $constraints[] = $propertyConstraint->getNestedConstraints();
×
193
                } else {
194
                    $constraints[] = [$propertyConstraint];
20✔
195
                }
196
            }
197
        }
198

199
        return array_merge([], ...$constraints);
20✔
200
    }
201

202
    /**
203
     * Is this constraint making the related property required?
204
     */
205
    private function isRequired(Constraint $constraint): bool
206
    {
207
        if ($constraint instanceof NotBlank && $constraint->allowNull) {
20✔
UNCOV
208
            return false;
×
209
        }
210

211
        foreach (self::REQUIRED_CONSTRAINTS as $requiredConstraint) {
20✔
212
            if ($constraint instanceof $requiredConstraint) {
20✔
213
                return true;
16✔
214
            }
215
        }
216

217
        return false;
12✔
218
    }
219
}
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

© 2025 Coveralls, Inc