Skip to content

Commit b6898f3

Browse files
authored
Merge pull request #2375 from GregoireHebert/auto-schema
feat (symfony-validator-bridge): guess IRI from validation constraints
2 parents e4b8bd3 + 80c349a commit b6898f3

File tree

4 files changed

+185
-10
lines changed

4 files changed

+185
-10
lines changed

features/jsonld/context.feature

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Feature: JSON-LD contexts generation
2828
"dummy": "Dummy/dummy",
2929
"dummyBoolean": "Dummy/dummyBoolean",
3030
"dummyDate": {
31-
"@id": "Dummy/dummyDate",
31+
"@id": "http://schema.org/DateTime",
3232
"@type": "@id"
3333
},
3434
"dummyFloat": "Dummy/dummyFloat",

src/Bridge/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactory.php

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,22 @@
1616
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
1717
use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
1818
use Symfony\Component\Validator\Constraint;
19+
use Symfony\Component\Validator\Constraints\Bic;
20+
use Symfony\Component\Validator\Constraints\CardScheme;
21+
use Symfony\Component\Validator\Constraints\Currency;
22+
use Symfony\Component\Validator\Constraints\Date;
23+
use Symfony\Component\Validator\Constraints\DateTime;
24+
use Symfony\Component\Validator\Constraints\Email;
25+
use Symfony\Component\Validator\Constraints\File;
26+
use Symfony\Component\Validator\Constraints\Iban;
27+
use Symfony\Component\Validator\Constraints\Image;
28+
use Symfony\Component\Validator\Constraints\Isbn;
29+
use Symfony\Component\Validator\Constraints\Issn;
1930
use Symfony\Component\Validator\Constraints\NotBlank;
2031
use Symfony\Component\Validator\Constraints\NotNull;
32+
use Symfony\Component\Validator\Constraints\Time;
33+
use Symfony\Component\Validator\Constraints\Url;
34+
use Symfony\Component\Validator\Constraints\Uuid;
2135
use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface as ValidatorMetadataFactoryInterface;
2236
use Symfony\Component\Validator\Mapping\PropertyMetadataInterface as ValidatorPropertyMetadataInterface;
2337

@@ -33,6 +47,23 @@ final class ValidatorPropertyMetadataFactory implements PropertyMetadataFactoryI
3347
*/
3448
const REQUIRED_CONSTRAINTS = [NotBlank::class, NotNull::class];
3549

50+
const SCHEMA_MAPPED_CONSTRAINTS = [
51+
Url::class => 'http://schema.org/url',
52+
Email::class => 'http://schema.org/email',
53+
Uuid::class => 'http://schema.org/identifier',
54+
CardScheme::class => 'http://schema.org/identifier',
55+
Bic::class => 'http://schema.org/identifier',
56+
Iban::class => 'http://schema.org/identifier',
57+
Date::class => 'http://schema.org/Date',
58+
DateTime::class => 'http://schema.org/DateTime',
59+
Time::class => 'http://schema.org/Time',
60+
Image::class => 'http://schema.org/image',
61+
File::class => 'http://schema.org/MediaObject',
62+
Currency::class => 'http://schema.org/priceCurrency',
63+
Isbn::class => 'http://schema.org/isbn',
64+
Issn::class => 'http://schema.org/issn',
65+
];
66+
3667
private $decorated;
3768
private $validatorMetadataFactory;
3869

@@ -48,26 +79,36 @@ public function __construct(ValidatorMetadataFactoryInterface $validatorMetadata
4879
public function create(string $resourceClass, string $name, array $options = []): PropertyMetadata
4980
{
5081
$propertyMetadata = $this->decorated->create($resourceClass, $name, $options);
51-
if (null !== $propertyMetadata->isRequired()) {
82+
83+
$required = $propertyMetadata->isRequired();
84+
$iri = $propertyMetadata->getIri();
85+
86+
if (null !== $required && null !== $iri) {
5287
return $propertyMetadata;
5388
}
5489

5590
$validatorClassMetadata = $this->validatorMetadataFactory->getMetadataFor($resourceClass);
5691
foreach ($validatorClassMetadata->getPropertyMetadata($name) as $validatorPropertyMetadata) {
57-
if (isset($options['validation_groups'])) {
58-
return $propertyMetadata->withRequired($this->isRequiredByGroups($validatorPropertyMetadata, $options));
92+
if (null === $required && isset($options['validation_groups'])) {
93+
$required = $this->isRequiredByGroups($validatorPropertyMetadata, $options);
5994
}
6095

6196
foreach ($validatorPropertyMetadata->findConstraints($validatorClassMetadata->getDefaultGroup()) as $constraint) {
62-
if ($this->isRequired($constraint)) {
63-
return $propertyMetadata->withRequired(true);
97+
if (null === $required && $this->isRequired($constraint)) {
98+
$required = true;
6499
}
65-
}
66100

67-
return $propertyMetadata->withRequired(false);
101+
if (null === $iri) {
102+
$iri = self::SCHEMA_MAPPED_CONSTRAINTS[\get_class($constraint)] ?? null;
103+
}
104+
105+
if (null !== $required && null !== $iri) {
106+
break 2;
107+
}
108+
}
68109
}
69110

70-
return $propertyMetadata->withRequired(false);
111+
return $propertyMetadata->withIri($iri)->withRequired($required ?? false);
71112
}
72113

73114
/**

tests/Bridge/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactoryTest.php

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use ApiPlatform\Core\Bridge\Symfony\Validator\Metadata\Property\ValidatorPropertyMetadataFactory;
1717
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
1818
use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
19+
use ApiPlatform\Core\Tests\Fixtures\DummyIriWithValidationEntity;
1920
use ApiPlatform\Core\Tests\Fixtures\DummyValidatedEntity;
2021
use Doctrine\Common\Annotations\AnnotationReader;
2122
use PHPUnit\Framework\TestCase;
@@ -58,6 +59,7 @@ public function testCreateWithPropertyWithNotRequiredConstraints()
5859
{
5960
$propertyMetadata = new PropertyMetadata(null, 'A dummy date', true, true, null, null, null, false);
6061
$expectedPropertyMetadata = $propertyMetadata->withRequired(false);
62+
$expectedPropertyMetadata = $expectedPropertyMetadata->withIri('http://schema.org/Date');
6163

6264
$decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class);
6365
$decoratedPropertyMetadataFactory->create(DummyValidatedEntity::class, 'dummyDate', [])->willReturn($propertyMetadata)->shouldBeCalled();
@@ -146,7 +148,7 @@ public function testCreateWithPropertyWithNonStringValidationGroupsAndRequiredCo
146148

147149
public function testCreateWithRequiredByDecorated()
148150
{
149-
$propertyMetadata = new PropertyMetadata(null, 'A dummy date', true, true, null, null, true, false);
151+
$propertyMetadata = new PropertyMetadata(null, 'A dummy date', true, true, null, null, true, false, 'foo:bar');
150152
$expectedPropertyMetadata = clone $propertyMetadata;
151153

152154
$decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class);
@@ -160,4 +162,42 @@ public function testCreateWithRequiredByDecorated()
160162
$this->assertInstanceOf(PropertyMetadata::class, $resultedPropertyMetadata);
161163
$this->assertEquals($expectedPropertyMetadata, $resultedPropertyMetadata);
162164
}
165+
166+
public function testCreateWithPropertyWithValidationConstraints()
167+
{
168+
$validatorClassMetadata = new ClassMetadata(DummyIriWithValidationEntity::class);
169+
(new AnnotationLoader(new AnnotationReader()))->loadClassMetadata($validatorClassMetadata);
170+
171+
$types = [
172+
'dummyUrl' => 'http://schema.org/url',
173+
'dummyEmail' => 'http://schema.org/email',
174+
'dummyUuid' => 'http://schema.org/identifier',
175+
'dummyCardScheme' => 'http://schema.org/identifier',
176+
'dummyBic' => 'http://schema.org/identifier',
177+
'dummyIban' => 'http://schema.org/identifier',
178+
'dummyDate' => 'http://schema.org/Date',
179+
'dummyDateTime' => 'http://schema.org/DateTime',
180+
'dummyTime' => 'http://schema.org/Time',
181+
'dummyImage' => 'http://schema.org/image',
182+
'dummyFile' => 'http://schema.org/MediaObject',
183+
'dummyCurrency' => 'http://schema.org/priceCurrency',
184+
'dummyIsbn' => 'http://schema.org/isbn',
185+
'dummyIssn' => 'http://schema.org/issn',
186+
];
187+
188+
$decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class);
189+
foreach ($types as $property => $iri) {
190+
$decoratedPropertyMetadataFactory->create(DummyIriWithValidationEntity::class, $property, [])->willReturn(new PropertyMetadata())->shouldBeCalled();
191+
}
192+
193+
$validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class);
194+
$validatorMetadataFactory->getMetadataFor(DummyIriWithValidationEntity::class)->willReturn($validatorClassMetadata)->shouldBeCalled();
195+
196+
$validatorPropertyMetadataFactory = new ValidatorPropertyMetadataFactory($validatorMetadataFactory->reveal(), $decoratedPropertyMetadataFactory->reveal());
197+
198+
foreach ($types as $property => $iri) {
199+
$resultedPropertyMetadata = $validatorPropertyMetadataFactory->create(DummyIriWithValidationEntity::class, $property);
200+
$this->assertSame($iri, $resultedPropertyMetadata->getIri());
201+
}
202+
}
163203
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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\Core\Tests\Fixtures;
15+
16+
use Symfony\Component\Validator\Constraints as Assert;
17+
18+
/**
19+
* Dummy Iri filled with validation Entity.
20+
*
21+
* @author Grégoire Hébert <[email protected]>
22+
*/
23+
class DummyIriWithValidationEntity
24+
{
25+
/**
26+
* @Assert\Url
27+
*/
28+
public $dummyUrl;
29+
30+
/**
31+
* @Assert\Email
32+
*/
33+
public $dummyEmail;
34+
35+
/**
36+
* @Assert\Uuid
37+
*/
38+
public $dummyUuid;
39+
40+
/**
41+
* @Assert\CardScheme(schemes="MASTERCARD")
42+
*/
43+
public $dummyCardScheme;
44+
45+
/**
46+
* @Assert\Bic
47+
*/
48+
public $dummyBic;
49+
50+
/**
51+
* @Assert\Iban
52+
*/
53+
public $dummyIban;
54+
55+
/**
56+
* @Assert\Date
57+
*/
58+
public $dummyDate;
59+
60+
/**
61+
* @Assert\DateTime
62+
*/
63+
public $dummyDateTime;
64+
65+
/**
66+
* @Assert\Time
67+
*/
68+
public $dummyTime;
69+
70+
/**
71+
* @Assert\Image
72+
*/
73+
public $dummyImage;
74+
75+
/**
76+
* @Assert\File
77+
*/
78+
public $dummyFile;
79+
80+
/**
81+
* @Assert\Currency
82+
*/
83+
public $dummyCurrency;
84+
85+
/**
86+
* @Assert\Isbn
87+
*/
88+
public $dummyIsbn;
89+
90+
/**
91+
* @Assert\Issn
92+
*/
93+
public $dummyIssn;
94+
}

0 commit comments

Comments
 (0)