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

api-platform / core / 10943429050

19 Sep 2024 02:48PM UTC coverage: 7.647% (-0.03%) from 7.675%
10943429050

push

github

web-flow
feat: api-platform/json-hal component (#6621)

* feat: add hal support for laravel

* feat: quick review

* fix: typo & cs-fixer

* fix: typo in composer.json

* fix: cs-fixer & phpstan

* fix: forgot about hal item normalizer, therefore there's no more createbook nor updatebook test as Hal is a readonly format

0 of 94 new or added lines in 2 files covered. (0.0%)

9082 existing lines in 291 files now uncovered.

12629 of 165144 relevant lines covered (7.65%)

22.89 hits per line

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

94.55
/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 Symfony\Component\Validator\Constraint;
21
use Symfony\Component\Validator\Constraints\Bic;
22
use Symfony\Component\Validator\Constraints\CardScheme;
23
use Symfony\Component\Validator\Constraints\Compound;
24
use Symfony\Component\Validator\Constraints\Currency;
25
use Symfony\Component\Validator\Constraints\Date;
26
use Symfony\Component\Validator\Constraints\DateTime;
27
use Symfony\Component\Validator\Constraints\Email;
28
use Symfony\Component\Validator\Constraints\File;
29
use Symfony\Component\Validator\Constraints\Iban;
30
use Symfony\Component\Validator\Constraints\Image;
31
use Symfony\Component\Validator\Constraints\Isbn;
32
use Symfony\Component\Validator\Constraints\Issn;
33
use Symfony\Component\Validator\Constraints\NotBlank;
34
use Symfony\Component\Validator\Constraints\NotNull;
35
use Symfony\Component\Validator\Constraints\Sequentially;
36
use Symfony\Component\Validator\Constraints\Time;
37
use Symfony\Component\Validator\Constraints\Url;
38
use Symfony\Component\Validator\Constraints\Uuid;
39
use Symfony\Component\Validator\Mapping\ClassMetadataInterface as ValidatorClassMetadataInterface;
40
use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface as ValidatorMetadataFactoryInterface;
41
use Symfony\Component\Validator\Mapping\PropertyMetadataInterface as ValidatorPropertyMetadataInterface;
42

43
/**
44
 * Decorates a metadata loader using the validator.
45
 *
46
 * @author Kévin Dunglas <dunglas@gmail.com>
47
 */
48
final class ValidatorPropertyMetadataFactory implements PropertyMetadataFactoryInterface
49
{
50
    /**
51
     * @var string[] A list of constraint classes making the entity required
52
     */
53
    public const REQUIRED_CONSTRAINTS = [NotBlank::class, NotNull::class];
54

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

72
    /**
73
     * @param PropertySchemaRestrictionMetadataInterface[] $restrictionsMetadata
74
     */
75
    public function __construct(private readonly ValidatorMetadataFactoryInterface $validatorMetadataFactory, private readonly PropertyMetadataFactoryInterface $decorated, private readonly iterable $restrictionsMetadata = [])
76
    {
UNCOV
77
    }
2,825✔
78

79
    /**
80
     * {@inheritdoc}
81
     */
82
    public function create(string $resourceClass, string $property, array $options = []): ApiProperty
83
    {
UNCOV
84
        $propertyMetadata = $this->decorated->create($resourceClass, $property, $options);
814✔
85

UNCOV
86
        $extraProperties = $propertyMetadata->getExtraProperties() ?? [];
814✔
87
        // see AttributePropertyMetadataFactory
UNCOV
88
        if (true === ($extraProperties[SchemaPropertyMetadataFactory::JSON_SCHEMA_USER_DEFINED] ?? false)) {
814✔
89
            // schema seems to have been declared by the user: do not override nor complete user value
UNCOV
90
            return $propertyMetadata;
6✔
91
        }
92

UNCOV
93
        $required = $propertyMetadata->isRequired();
814✔
UNCOV
94
        $types = $propertyMetadata->getTypes();
814✔
UNCOV
95
        $schema = $propertyMetadata->getSchema();
814✔
96

UNCOV
97
        if (null !== $required && $types && $schema) {
814✔
98
            return $propertyMetadata;
×
99
        }
100

UNCOV
101
        $validatorClassMetadata = $this->validatorMetadataFactory->getMetadataFor($resourceClass);
814✔
102

UNCOV
103
        if (!$validatorClassMetadata instanceof ValidatorClassMetadataInterface) {
814✔
104
            throw new \UnexpectedValueException(\sprintf('Validator class metadata expected to be of type "%s".', ValidatorClassMetadataInterface::class));
×
105
        }
106

UNCOV
107
        $validationGroups = $this->getValidationGroups($validatorClassMetadata, $options);
814✔
UNCOV
108
        $restrictions = [];
814✔
UNCOV
109
        $types ??= [];
814✔
110

UNCOV
111
        foreach ($validatorClassMetadata->getPropertyMetadata($property) as $validatorPropertyMetadata) {
814✔
UNCOV
112
            foreach ($this->getPropertyConstraints($validatorPropertyMetadata, $validationGroups) as $constraint) {
265✔
UNCOV
113
                if (null === $required && $this->isRequired($constraint)) {
259✔
UNCOV
114
                    $required = true;
112✔
115
                }
116

UNCOV
117
                $type = self::SCHEMA_MAPPED_CONSTRAINTS[$constraint::class] ?? null;
259✔
118

UNCOV
119
                if ($type && !\in_array($type, $types, true)) {
259✔
UNCOV
120
                    $types[] = $type;
65✔
121
                }
122

UNCOV
123
                foreach ($this->restrictionsMetadata as $restrictionMetadata) {
259✔
UNCOV
124
                    if ($restrictionMetadata->supports($constraint, $propertyMetadata)) {
244✔
UNCOV
125
                        $restrictions[] = $restrictionMetadata->create($constraint, $propertyMetadata);
130✔
126
                    }
127
                }
128
            }
129
        }
130

UNCOV
131
        if ($types) {
814✔
UNCOV
132
            $propertyMetadata = $propertyMetadata->withTypes($types);
100✔
133
        }
134

UNCOV
135
        $propertyMetadata = $propertyMetadata->withRequired($required ?? false);
814✔
136

UNCOV
137
        if (!empty($restrictions)) {
814✔
UNCOV
138
            if (null === $schema) {
130✔
UNCOV
139
                $schema = [];
130✔
140
            }
141

UNCOV
142
            $schema += array_merge(...$restrictions);
130✔
UNCOV
143
            $propertyMetadata = $propertyMetadata->withSchema($schema);
130✔
144
        }
145

UNCOV
146
        return $propertyMetadata;
814✔
147
    }
148

149
    /**
150
     * Returns the list of validation groups.
151
     */
152
    private function getValidationGroups(ValidatorClassMetadataInterface $classMetadata, array $options): array
153
    {
UNCOV
154
        if (isset($options['validation_groups'])) {
814✔
UNCOV
155
            return $options['validation_groups'];
9✔
156
        }
157

UNCOV
158
        if (!method_exists($classMetadata, 'getDefaultGroup')) {
805✔
159
            throw new \UnexpectedValueException(\sprintf('Validator class metadata expected to have method "%s".', 'getDefaultGroup'));
×
160
        }
161

UNCOV
162
        return [$classMetadata->getDefaultGroup()];
805✔
163
    }
164

165
    /**
166
     * Tests if the property is required because of its validation groups.
167
     */
168
    private function getPropertyConstraints(
169
        ValidatorPropertyMetadataInterface $validatorPropertyMetadata,
170
        array $groups,
171
    ): array {
UNCOV
172
        $constraints = [];
265✔
173

UNCOV
174
        foreach ($groups as $validationGroup) {
265✔
UNCOV
175
            if (!\is_string($validationGroup)) {
265✔
UNCOV
176
                continue;
3✔
177
            }
178

UNCOV
179
            foreach ($validatorPropertyMetadata->findConstraints($validationGroup) as $propertyConstraint) {
262✔
UNCOV
180
                if ($propertyConstraint instanceof Sequentially || $propertyConstraint instanceof Compound) {
259✔
UNCOV
181
                    $constraints[] = $propertyConstraint->getNestedConstraints();
6✔
182
                } else {
UNCOV
183
                    $constraints[] = [$propertyConstraint];
253✔
184
                }
185
            }
186
        }
187

UNCOV
188
        return array_merge([], ...$constraints);
265✔
189
    }
190

191
    /**
192
     * Is this constraint making the related property required?
193
     */
194
    private function isRequired(Constraint $constraint): bool
195
    {
UNCOV
196
        if ($constraint instanceof NotBlank && $constraint->allowNull) {
256✔
UNCOV
197
            return false;
6✔
198
        }
199

UNCOV
200
        foreach (self::REQUIRED_CONSTRAINTS as $requiredConstraint) {
256✔
UNCOV
201
            if ($constraint instanceof $requiredConstraint) {
256✔
UNCOV
202
                return true;
112✔
203
            }
204
        }
205

UNCOV
206
        return false;
170✔
207
    }
208
}
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