Skip to content

Commit d2e542d

Browse files
committed
TEMPORARY COMMIT
1 parent 9d5a6b1 commit d2e542d

File tree

20 files changed

+822
-48
lines changed

20 files changed

+822
-48
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
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\Bridge\Doctrine\Orm\Metadata\Property;
15+
16+
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
17+
use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
18+
use Doctrine\ORM\Mapping\ClassMetadataInfo;
19+
use Doctrine\Persistence\ManagerRegistry;
20+
21+
/**
22+
* Use Doctrine metadata to populate the identifier property.
23+
*
24+
* @author Kévin Dunglas <[email protected]>
25+
*/
26+
final class DoctrineOrmPropertyMetadataFactory implements PropertyMetadataFactoryInterface
27+
{
28+
private $decorated;
29+
private $managerRegistry;
30+
31+
public function __construct(ManagerRegistry $managerRegistry, PropertyMetadataFactoryInterface $decorated)
32+
{
33+
$this->managerRegistry = $managerRegistry;
34+
$this->decorated = $decorated;
35+
}
36+
37+
/**
38+
* {@inheritdoc}
39+
*/
40+
public function create(string $resourceClass, string $property, array $options = []): PropertyMetadata
41+
{
42+
$propertyMetadata = $this->decorated->create($resourceClass, $property, $options);
43+
44+
if (null !== $propertyMetadata->isIdentifier()) {
45+
return $propertyMetadata;
46+
}
47+
48+
$manager = $this->managerRegistry->getManagerForClass($resourceClass);
49+
if (!$manager) {
50+
return $propertyMetadata;
51+
}
52+
$doctrineClassMetadata = $manager->getClassMetadata($resourceClass);
53+
54+
$identifiers = $doctrineClassMetadata->getIdentifier();
55+
foreach ($identifiers as $identifier) {
56+
if ($identifier === $property) {
57+
$propertyMetadata = $propertyMetadata->withIdentifier(true);
58+
59+
if (null !== $propertyMetadata->isWritable()) {
60+
break;
61+
}
62+
63+
if ($doctrineClassMetadata instanceof ClassMetadataInfo) {
64+
$writable = $doctrineClassMetadata->isIdentifierNatural();
65+
} else {
66+
$writable = false;
67+
}
68+
69+
$propertyMetadata = $propertyMetadata->withWritable($writable);
70+
71+
break;
72+
}
73+
}
74+
75+
if (null === $propertyMetadata->isIdentifier()) {
76+
$propertyMetadata = $propertyMetadata->withIdentifier(false);
77+
}
78+
79+
if ($doctrineClassMetadata instanceof ClassMetadataInfo && \in_array($property, $doctrineClassMetadata->getFieldNames(), true)) {
80+
/** @var mixed[] */
81+
$fieldMapping = $doctrineClassMetadata->getFieldMapping($property);
82+
$propertyMetadata = $propertyMetadata->withDefault($fieldMapping['options']['default'] ?? $propertyMetadata->getDefault());
83+
}
84+
85+
return $propertyMetadata;
86+
}
87+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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\Bridge\Symfony\PropertyInfo\Metadata\Property;
15+
16+
use ApiPlatform\Core\Exception\PropertyNotFoundException;
17+
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
18+
use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
19+
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
20+
21+
/**
22+
* PropertyInfo metadata loader decorator.
23+
*
24+
* @author Kévin Dunglas <[email protected]>
25+
*/
26+
final class PropertyInfoPropertyMetadataFactory implements PropertyMetadataFactoryInterface
27+
{
28+
private $propertyInfo;
29+
private $decorated;
30+
31+
public function __construct(PropertyInfoExtractorInterface $propertyInfo, PropertyMetadataFactoryInterface $decorated = null)
32+
{
33+
$this->propertyInfo = $propertyInfo;
34+
$this->decorated = $decorated;
35+
}
36+
37+
/**
38+
* {@inheritdoc}
39+
*/
40+
public function create(string $resourceClass, string $property, array $options = []): PropertyMetadata
41+
{
42+
if (null === $this->decorated) {
43+
$propertyMetadata = new PropertyMetadata();
44+
} else {
45+
try {
46+
$propertyMetadata = $this->decorated->create($resourceClass, $property, $options);
47+
} catch (PropertyNotFoundException $propertyNotFoundException) {
48+
$propertyMetadata = new PropertyMetadata();
49+
}
50+
}
51+
52+
if (null === $propertyMetadata->getType()) {
53+
$types = $this->propertyInfo->getTypes($resourceClass, $property, $options);
54+
if (isset($types[0])) {
55+
$propertyMetadata = $propertyMetadata->withType($types[0]);
56+
}
57+
}
58+
59+
if (null === $propertyMetadata->getDescription() && null !== $description = $this->propertyInfo->getShortDescription($resourceClass, $property, $options)) {
60+
$propertyMetadata = $propertyMetadata->withDescription($description);
61+
}
62+
63+
if (null === $propertyMetadata->isReadable() && null !== $readable = $this->propertyInfo->isReadable($resourceClass, $property, $options)) {
64+
$propertyMetadata = $propertyMetadata->withReadable($readable);
65+
}
66+
67+
if (null === $propertyMetadata->isWritable() && null !== $writable = $this->propertyInfo->isWritable($resourceClass, $property, $options)) {
68+
$propertyMetadata = $propertyMetadata->withWritable($writable);
69+
}
70+
71+
if (method_exists($this->propertyInfo, 'isInitializable')) {
72+
if (null === $propertyMetadata->isInitializable() && null !== $initializable = $this->propertyInfo->isInitializable($resourceClass, $property, $options)) {
73+
$propertyMetadata = $propertyMetadata->withInitializable($initializable);
74+
}
75+
} else {
76+
// BC layer for Symfony < 4.2
77+
$ref = new \ReflectionClass($resourceClass);
78+
if ($ref->isInstantiable() && $constructor = $ref->getConstructor()) {
79+
foreach ($constructor->getParameters() as $constructorParameter) {
80+
if ($constructorParameter->name === $property && null === $propertyMetadata->isInitializable()) {
81+
$propertyMetadata = $propertyMetadata->withInitializable(true);
82+
}
83+
}
84+
}
85+
}
86+
87+
return $propertyMetadata;
88+
}
89+
}
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
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\Bridge\Symfony\Validator\Metadata\Property;
15+
16+
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
17+
use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
18+
use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaRestrictionMetadataInterface;
19+
use Symfony\Component\Validator\Constraint;
20+
use Symfony\Component\Validator\Constraints\Bic;
21+
use Symfony\Component\Validator\Constraints\CardScheme;
22+
use Symfony\Component\Validator\Constraints\Compound;
23+
use Symfony\Component\Validator\Constraints\Currency;
24+
use Symfony\Component\Validator\Constraints\Date;
25+
use Symfony\Component\Validator\Constraints\DateTime;
26+
use Symfony\Component\Validator\Constraints\Email;
27+
use Symfony\Component\Validator\Constraints\File;
28+
use Symfony\Component\Validator\Constraints\Iban;
29+
use Symfony\Component\Validator\Constraints\Image;
30+
use Symfony\Component\Validator\Constraints\Isbn;
31+
use Symfony\Component\Validator\Constraints\Issn;
32+
use Symfony\Component\Validator\Constraints\NotBlank;
33+
use Symfony\Component\Validator\Constraints\NotNull;
34+
use Symfony\Component\Validator\Constraints\Sequentially;
35+
use Symfony\Component\Validator\Constraints\Time;
36+
use Symfony\Component\Validator\Constraints\Url;
37+
use Symfony\Component\Validator\Constraints\Uuid;
38+
use Symfony\Component\Validator\Mapping\ClassMetadataInterface as ValidatorClassMetadataInterface;
39+
use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface as ValidatorMetadataFactoryInterface;
40+
use Symfony\Component\Validator\Mapping\PropertyMetadataInterface as ValidatorPropertyMetadataInterface;
41+
42+
/**
43+
* Decorates a metadata loader using the validator.
44+
*
45+
* @author Kévin Dunglas <[email protected]>
46+
*/
47+
final class ValidatorPropertyMetadataFactory implements PropertyMetadataFactoryInterface
48+
{
49+
/**
50+
* @var string[] A list of constraint classes making the entity required
51+
*/
52+
public const REQUIRED_CONSTRAINTS = [NotBlank::class, NotNull::class];
53+
54+
public const SCHEMA_MAPPED_CONSTRAINTS = [
55+
Url::class => 'http://schema.org/url',
56+
Email::class => 'http://schema.org/email',
57+
Uuid::class => 'http://schema.org/identifier',
58+
CardScheme::class => 'http://schema.org/identifier',
59+
Bic::class => 'http://schema.org/identifier',
60+
Iban::class => 'http://schema.org/identifier',
61+
Date::class => 'http://schema.org/Date',
62+
DateTime::class => 'http://schema.org/DateTime',
63+
Time::class => 'http://schema.org/Time',
64+
Image::class => 'http://schema.org/image',
65+
File::class => 'http://schema.org/MediaObject',
66+
Currency::class => 'http://schema.org/priceCurrency',
67+
Isbn::class => 'http://schema.org/isbn',
68+
Issn::class => 'http://schema.org/issn',
69+
];
70+
71+
private $decorated;
72+
private $validatorMetadataFactory;
73+
/**
74+
* @var iterable<PropertySchemaRestrictionMetadataInterface>
75+
*/
76+
private $restrictionsMetadata;
77+
78+
/**
79+
* @param PropertySchemaRestrictionMetadataInterface[] $restrictionsMetadata
80+
*/
81+
public function __construct(ValidatorMetadataFactoryInterface $validatorMetadataFactory, PropertyMetadataFactoryInterface $decorated, iterable $restrictionsMetadata = [])
82+
{
83+
$this->validatorMetadataFactory = $validatorMetadataFactory;
84+
$this->decorated = $decorated;
85+
$this->restrictionsMetadata = $restrictionsMetadata;
86+
}
87+
88+
/**
89+
* {@inheritdoc}
90+
*/
91+
public function create(string $resourceClass, string $property, array $options = []): PropertyMetadata
92+
{
93+
$propertyMetadata = $this->decorated->create($resourceClass, $property, $options);
94+
95+
$required = $propertyMetadata->isRequired();
96+
$iri = $propertyMetadata->getIri();
97+
$schema = $propertyMetadata->getSchema();
98+
99+
if (null !== $required && $iri && $schema) {
100+
return $propertyMetadata;
101+
}
102+
103+
$validatorClassMetadata = $this->validatorMetadataFactory->getMetadataFor($resourceClass);
104+
105+
if (!$validatorClassMetadata instanceof ValidatorClassMetadataInterface) {
106+
throw new \UnexpectedValueException(sprintf('Validator class metadata expected to be of type "%s".', ValidatorClassMetadataInterface::class));
107+
}
108+
109+
$validationGroups = $this->getValidationGroups($validatorClassMetadata, $options);
110+
$restrictions = [];
111+
112+
foreach ($validatorClassMetadata->getPropertyMetadata($property) as $validatorPropertyMetadata) {
113+
foreach ($this->getPropertyConstraints($validatorPropertyMetadata, $validationGroups) as $constraint) {
114+
if (null === $required && $this->isRequired($constraint)) {
115+
$required = true;
116+
}
117+
118+
if (!$iri) {
119+
$iri = self::SCHEMA_MAPPED_CONSTRAINTS[\get_class($constraint)] ?? null;
120+
}
121+
122+
foreach ($this->restrictionsMetadata as $restrictionMetadata) {
123+
if ($restrictionMetadata->supports($constraint, $propertyMetadata)) {
124+
$restrictions[] = $restrictionMetadata->create($constraint, $propertyMetadata);
125+
}
126+
}
127+
}
128+
}
129+
130+
if ($iri) {
131+
$propertyMetadata = $propertyMetadata->withIri($iri);
132+
}
133+
134+
$propertyMetadata = $propertyMetadata->withRequired($required ?? false);
135+
136+
if (!empty($restrictions)) {
137+
if (null === $schema) {
138+
$schema = [];
139+
}
140+
141+
$schema += array_merge(...$restrictions);
142+
$propertyMetadata = $propertyMetadata->withSchema($schema);
143+
}
144+
145+
return $propertyMetadata;
146+
}
147+
148+
/**
149+
* Returns the list of validation groups.
150+
*/
151+
private function getValidationGroups(ValidatorClassMetadataInterface $classMetadata, array $options): array
152+
{
153+
if (isset($options['validation_groups'])) {
154+
return $options['validation_groups'];
155+
}
156+
157+
if (!method_exists($classMetadata, 'getDefaultGroup')) {
158+
throw new \UnexpectedValueException(sprintf('Validator class metadata expected to have method "%s".', 'getDefaultGroup'));
159+
}
160+
161+
return [$classMetadata->getDefaultGroup()];
162+
}
163+
164+
/**
165+
* Tests if the property is required because of its validation groups.
166+
*/
167+
private function getPropertyConstraints(
168+
ValidatorPropertyMetadataInterface $validatorPropertyMetadata,
169+
array $groups
170+
): array {
171+
$constraints = [];
172+
173+
foreach ($groups as $validationGroup) {
174+
if (!\is_string($validationGroup)) {
175+
continue;
176+
}
177+
178+
foreach ($validatorPropertyMetadata->findConstraints($validationGroup) as $propertyConstraint) {
179+
if ($propertyConstraint instanceof Sequentially || $propertyConstraint instanceof Compound) {
180+
$constraints[] = method_exists($propertyConstraint, 'getNestedContraints') ? $propertyConstraint->getNestedContraints() : $propertyConstraint->getNestedConstraints();
181+
} else {
182+
$constraints[] = [$propertyConstraint];
183+
}
184+
}
185+
}
186+
187+
return array_merge([], ...$constraints);
188+
}
189+
190+
/**
191+
* Is this constraint making the related property required?
192+
*/
193+
private function isRequired(Constraint $constraint): bool
194+
{
195+
foreach (self::REQUIRED_CONSTRAINTS as $requiredConstraint) {
196+
if ($constraint instanceof $requiredConstraint) {
197+
return true;
198+
}
199+
}
200+
201+
return false;
202+
}
203+
}

src/Core/Metadata/Property/Factory/AnnotationPropertyMetadataFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public function __construct(Reader $reader = null, PropertyMetadataFactoryInterf
4343
/**
4444
* {@inheritdoc}
4545
*/
46-
public function create(string $resourceClass, string $property, array $options = [])
46+
public function create(string $resourceClass, string $property, array $options = []): PropertyMetadata
4747
{
4848
$parentPropertyMetadata = null;
4949
if ($this->decorated) {

src/Core/Metadata/Property/Factory/AnnotationSubresourceMetadataFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public function __construct(Reader $reader, PropertyMetadataFactoryInterface $de
4343
/**
4444
* {@inheritdoc}
4545
*/
46-
public function create(string $resourceClass, string $property, array $options = [])
46+
public function create(string $resourceClass, string $property, array $options = []): PropertyMetadata
4747
{
4848
/** @var ApiPropertyMetadata|PropertyMetadata */
4949
$propertyMetadata = $this->decorated->create($resourceClass, $property, $options);

0 commit comments

Comments
 (0)