Skip to content

Commit 2ad839d

Browse files
committed
Merge 4.2
2 parents 4cd3468 + 6f85f2a commit 2ad839d

File tree

22 files changed

+440
-82
lines changed

22 files changed

+440
-82
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,19 @@
1111

1212
* When using `output` with `itemUriTemplate` on a collection operation, the JSON-LD `@type` will now use the resource class name instead of the output DTO class name for semantic consistency with `itemUriTemplate` behavior.
1313

14+
## v4.2.15
15+
16+
### Bug fixes
17+
18+
* [2de06db1d](https://github.com/api-platform/core/commit/2de06db1d0ee5f3c83972381420597ae73dae141) fix(jsonapi): output null on a to-one relationship (#7686)
19+
* [5577f07bf](https://github.com/api-platform/core/commit/5577f07bf243f2e24e7454a9931fe77c36137804) fix(openapi): change payload type from array to free-form object (#7694)
20+
* [5d860bdec](https://github.com/api-platform/core/commit/5d860bdec2fb91d700cb329dc15e23dfbf8e3dbb) fix(hydra): memory persistent cache during schema generation (#7718)
21+
* [696d31597](https://github.com/api-platform/core/commit/696d31597814c114219e8045ddf4fe9813e004ee) fix(metadata): issues with extending xml/yaml resources (#5956)
22+
* [773289658](https://github.com/api-platform/core/commit/773289658748d732b30fdd12fd7046d5fdb42b1e) fix(laravel): properly transform invokable service to route action (#7720)
23+
* [881812926](https://github.com/api-platform/core/commit/8818129269498d1257ac41736f7e760549241a3d) fix(symfony): declare api_platform.normalizer.object fixes #7705 (#7717)
24+
* [cc2f88558](https://github.com/api-platform/core/commit/cc2f88558d4a92eeb3240a074e2184815762ffd2) fix(doctrine): post with mapped relation
25+
* [f3c2b1a56](https://github.com/api-platform/core/commit/f3c2b1a569cb7838d276a440dd233a6a31896bb4) fix(symfony): prevent symfony name converter service pollution (#7691)
26+
1427
## v4.2.14
1528

1629
### Bug fixes

features/hydra/docs.feature

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,6 @@ Feature: Documentation support
1515
# Context
1616
And the Hydra context matches the online resource "http://www.w3.org/ns/hydra/context.jsonld"
1717
And the JSON node "@context[1].@vocab" should be equal to "http://example.com/docs.jsonld#"
18-
And the JSON node "@context[1].hydra" should be equal to "http://www.w3.org/ns/hydra/core#"
19-
And the JSON node "@context[1].rdf" should be equal to "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
20-
And the JSON node "@context[1].rdfs" should be equal to "http://www.w3.org/2000/01/rdf-schema#"
21-
And the JSON node "@context[1].xmls" should be equal to "http://www.w3.org/2001/XMLSchema#"
22-
And the JSON node "@context[1].owl" should be equal to "http://www.w3.org/2002/07/owl#"
2318
And the JSON node "@context[1].domain.@id" should be equal to "rdfs:domain"
2419
And the JSON node "@context[1].domain.@type" should be equal to "@id"
2520
And the JSON node "@context[1].range.@id" should be equal to "rdfs:range"
@@ -63,7 +58,7 @@ Feature: Documentation support
6358
And the value of the node "hydra:property.@type" of the property "name" of the Hydra class "Dummy" is "rdf:Property"
6459
And the value of the node "hydra:property.label" of the property "name" of the Hydra class "Dummy" is "name"
6560
And the value of the node "hydra:property.domain" of the property "name" of the Hydra class "Dummy" is "#Dummy"
66-
And the value of the node "hydra:property.range" of the property "name" of the Hydra class "Dummy" is "xmls:string"
61+
And the value of the node "hydra:property.range" of the property "name" of the Hydra class "Dummy" is "xsd:string"
6762
And the value of the node "hydra:property.range" of the property "relatedDummy" of the Hydra class "Dummy" is "https://schema.org/Product"
6863
And the value of the node "hydra:property.owl:maxCardinality" of the property "relatedDummy" of the Hydra class "Dummy" is "1"
6964
And the value of the node "hydra:property.range" of the property "relatedDummies" of the Hydra class "Dummy" is "https://schema.org/Product"

src/Doctrine/Common/State/PersistProcessor.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,10 @@ private function handleLazyObjectRelations(object $data, DoctrineObjectManager $
168168
continue;
169169
}
170170

171+
if (!$reflectionProperty->isInitialized($data)) {
172+
continue;
173+
}
174+
171175
$value = $reflectionProperty->getValue($data);
172176

173177
if (!\is_object($value)) {
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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\Doctrine\Common\Tests\Fixtures\TestBundle\Entity;
15+
16+
use Doctrine\ORM\Mapping as ORM;
17+
18+
/**
19+
* Entity with typed properties that are not initialized.
20+
* Simulates entities using PrePersist lifecycle callbacks.
21+
*/
22+
#[ORM\Entity]
23+
class DummyWithUninitializedProperties
24+
{
25+
#[ORM\Id]
26+
#[ORM\GeneratedValue]
27+
#[ORM\Column]
28+
public int $id;
29+
30+
#[ORM\Column(length: 255)]
31+
public string $title;
32+
33+
#[ORM\Column(length: 50)]
34+
public string $status;
35+
36+
#[ORM\Column]
37+
public int $version;
38+
}

src/Doctrine/Common/Tests/State/PersistProcessorTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515

1616
use ApiPlatform\Doctrine\Common\State\PersistProcessor;
1717
use ApiPlatform\Doctrine\Common\Tests\Fixtures\TestBundle\Entity\Dummy;
18+
use ApiPlatform\Doctrine\Common\Tests\Fixtures\TestBundle\Entity\DummyWithUninitializedProperties;
1819
use ApiPlatform\Metadata\Get;
20+
use ApiPlatform\Metadata\Post;
1921
use ApiPlatform\State\ProcessorInterface;
2022
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
2123
use Doctrine\ORM\Mapping\ClassMetadata as ORMClassMetadata;
@@ -116,4 +118,26 @@ public function testTrackingPolicy(string $metadataClass, bool $deferredExplicit
116118
$result = (new PersistProcessor($managerRegistryProphecy->reveal()))->process($dummy, new Get());
117119
$this->assertSame($dummy, $result);
118120
}
121+
122+
public function testHandleLazyObjectRelationsSkipsUninitializedProperties(): void
123+
{
124+
$dummy = new DummyWithUninitializedProperties();
125+
$dummy->title = 'My Book';
126+
127+
$classMetadata = new ORMClassMetadata(DummyWithUninitializedProperties::class);
128+
$classMetadata->identifier = ['id'];
129+
130+
$objectManagerProphecy = $this->prophesize(ObjectManager::class);
131+
$objectManagerProphecy->getClassMetadata(DummyWithUninitializedProperties::class)->willReturn($classMetadata);
132+
$objectManagerProphecy->contains($dummy)->willReturn(false);
133+
$objectManagerProphecy->persist($dummy)->shouldBeCalled();
134+
$objectManagerProphecy->flush()->shouldBeCalled();
135+
$objectManagerProphecy->refresh($dummy)->shouldBeCalled();
136+
137+
$managerRegistryProphecy = $this->prophesize(ManagerRegistry::class);
138+
$managerRegistryProphecy->getManagerForClass(DummyWithUninitializedProperties::class)->willReturn($objectManagerProphecy->reveal());
139+
140+
$result = (new PersistProcessor($managerRegistryProphecy->reveal()))->process($dummy, new Post(map: true));
141+
$this->assertSame($dummy, $result);
142+
}
119143
}

src/Doctrine/Odm/Metadata/Resource/DoctrineMongoDbOdmResourceCollectionMetadataFactory.php

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ public function create(string $resourceClass): ResourceMetadataCollection
5050
continue;
5151
}
5252

53-
$operations->add($operationName, $this->addDefaults($operation));
53+
$operation = $this->addDefaults($operation);
54+
$operation = $this->setParametersFilterClass($operation, $documentClass);
55+
$operations->add($operationName, $operation);
5456
}
5557

5658
$resourceMetadata = $resourceMetadata->withOperations($operations);
@@ -60,11 +62,14 @@ public function create(string $resourceClass): ResourceMetadataCollection
6062

6163
if ($graphQlOperations) {
6264
foreach ($graphQlOperations as $operationName => $graphQlOperation) {
63-
if (!$this->managerRegistry->getManagerForClass($graphQlOperation->getClass()) instanceof DocumentManager) {
65+
$documentClass = $this->getStateOptionsClass($graphQlOperation, $graphQlOperation->getClass(), Options::class);
66+
if (!$this->managerRegistry->getManagerForClass($documentClass) instanceof DocumentManager) {
6467
continue;
6568
}
6669

67-
$graphQlOperations[$operationName] = $this->addDefaults($graphQlOperation);
70+
$graphQlOperation = $this->addDefaults($graphQlOperation);
71+
$graphQlOperation = $this->setParametersFilterClass($graphQlOperation, $documentClass);
72+
$graphQlOperations[$operationName] = $graphQlOperation;
6873
}
6974

7075
$resourceMetadata = $resourceMetadata->withGraphQlOperations($graphQlOperations);
@@ -112,4 +117,20 @@ private function getProcessor(Operation $operation): string
112117

113118
return 'api_platform.doctrine_mongodb.odm.state.persist_processor';
114119
}
120+
121+
private function setParametersFilterClass(Operation $operation, string $documentClass): Operation
122+
{
123+
$parameters = $operation->getParameters();
124+
if (!$parameters) {
125+
return $operation;
126+
}
127+
128+
foreach ($parameters as $key => $parameter) {
129+
if (null === $parameter->getFilterClass()) {
130+
$parameters->add($key, $parameter->withFilterClass($documentClass));
131+
}
132+
}
133+
134+
return $operation->withParameters($parameters);
135+
}
115136
}

src/Doctrine/Orm/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactory.php

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,9 @@ public function create(string $resourceClass): ResourceMetadataCollection
6262
continue;
6363
}
6464

65-
$operations->add($operationName, $this->addDefaults($operation));
65+
$operation = $this->addDefaults($operation);
66+
$operation = $this->setParametersFilterClass($operation, $entityClass);
67+
$operations->add($operationName, $operation);
6668
}
6769

6870
$resourceMetadata = $resourceMetadata->withOperations($operations);
@@ -78,7 +80,9 @@ public function create(string $resourceClass): ResourceMetadataCollection
7880
continue;
7981
}
8082

81-
$graphQlOperations[$operationName] = $this->addDefaults($graphQlOperation);
83+
$graphQlOperation = $this->addDefaults($graphQlOperation);
84+
$graphQlOperation = $this->setParametersFilterClass($graphQlOperation, $entityClass);
85+
$graphQlOperations[$operationName] = $graphQlOperation;
8286
}
8387

8488
$resourceMetadata = $resourceMetadata->withGraphQlOperations($graphQlOperations);
@@ -126,4 +130,20 @@ private function getProcessor(Operation $operation): string
126130

127131
return 'api_platform.doctrine.orm.state.persist_processor';
128132
}
133+
134+
private function setParametersFilterClass(Operation $operation, string $entityClass): Operation
135+
{
136+
$parameters = $operation->getParameters();
137+
if (!$parameters) {
138+
return $operation;
139+
}
140+
141+
foreach ($parameters as $key => $parameter) {
142+
if (null === $parameter->getFilterClass()) {
143+
$parameters->add($key, $parameter->withFilterClass($entityClass));
144+
}
145+
}
146+
147+
return $operation->withParameters($parameters);
148+
}
129149
}

src/Hydra/Serializer/DocumentationNormalizer.php

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -378,23 +378,23 @@ private function getRange(ApiProperty $propertyMetadata): array|string|null
378378
}
379379

380380
if ($nativeType->isIdentifiedBy(TypeIdentifier::STRING)) {
381-
$types[] = 'xmls:string';
381+
$types[] = 'xsd:string';
382382
}
383383

384384
if ($nativeType->isIdentifiedBy(TypeIdentifier::INT)) {
385-
$types[] = 'xmls:integer';
385+
$types[] = 'xsd:integer';
386386
}
387387

388388
if ($nativeType->isIdentifiedBy(TypeIdentifier::FLOAT)) {
389-
$types[] = 'xmls:decimal';
389+
$types[] = 'xsd:decimal';
390390
}
391391

392392
if ($nativeType->isIdentifiedBy(TypeIdentifier::BOOL)) {
393-
$types[] = 'xmls:boolean';
393+
$types[] = 'xsd:boolean';
394394
}
395395

396396
if ($nativeType->isIdentifiedBy(\DateTimeInterface::class)) {
397-
$types[] = 'xmls:dateTime';
397+
$types[] = 'xsd:dateTime';
398398
}
399399

400400
/** @var class-string|null $className */
@@ -427,23 +427,23 @@ private function getRange(ApiProperty $propertyMetadata): array|string|null
427427

428428
switch ($type->getBuiltinType()) {
429429
case LegacyType::BUILTIN_TYPE_STRING:
430-
if (!\in_array('xmls:string', $types, true)) {
431-
$types[] = 'xmls:string';
430+
if (!\in_array('xsd:string', $types, true)) {
431+
$types[] = 'xsd:string';
432432
}
433433
break;
434434
case LegacyType::BUILTIN_TYPE_INT:
435-
if (!\in_array('xmls:integer', $types, true)) {
436-
$types[] = 'xmls:integer';
435+
if (!\in_array('xsd:integer', $types, true)) {
436+
$types[] = 'xsd:integer';
437437
}
438438
break;
439439
case LegacyType::BUILTIN_TYPE_FLOAT:
440-
if (!\in_array('xmls:decimal', $types, true)) {
441-
$types[] = 'xmls:decimal';
440+
if (!\in_array('xsd:decimal', $types, true)) {
441+
$types[] = 'xsd:decimal';
442442
}
443443
break;
444444
case LegacyType::BUILTIN_TYPE_BOOL:
445-
if (!\in_array('xmls:boolean', $types, true)) {
446-
$types[] = 'xmls:boolean';
445+
if (!\in_array('xsd:boolean', $types, true)) {
446+
$types[] = 'xsd:boolean';
447447
}
448448
break;
449449
case LegacyType::BUILTIN_TYPE_OBJECT:
@@ -452,8 +452,8 @@ private function getRange(ApiProperty $propertyMetadata): array|string|null
452452
}
453453

454454
if (is_a($className, \DateTimeInterface::class, true)) {
455-
if (!\in_array('xmls:dateTime', $types, true)) {
456-
$types[] = 'xmls:dateTime';
455+
if (!\in_array('xsd:dateTime', $types, true)) {
456+
$types[] = 'xsd:dateTime';
457457
}
458458
break;
459459
}
@@ -555,7 +555,7 @@ private function getClasses(array $entrypointProperties, array $classes, string
555555
'@type' => 'rdf:Property',
556556
'rdfs:label' => 'propertyPath',
557557
'domain' => '#ConstraintViolationList',
558-
'range' => 'xmls:string',
558+
'range' => 'xsd:string',
559559
],
560560
$hydraPrefix.'title' => 'propertyPath',
561561
$hydraPrefix.'description' => 'The property path of the violation',
@@ -569,7 +569,7 @@ private function getClasses(array $entrypointProperties, array $classes, string
569569
'@type' => 'rdf:Property',
570570
'rdfs:label' => 'message',
571571
'domain' => '#ConstraintViolationList',
572-
'range' => 'xmls:string',
572+
'range' => 'xsd:string',
573573
],
574574
$hydraPrefix.'title' => 'message',
575575
$hydraPrefix.'description' => 'The message associated with the violation',
@@ -662,11 +662,6 @@ private function getContext(string $hydraPrefix = ContextBuilder::HYDRA_PREFIX):
662662
HYDRA_CONTEXT,
663663
[
664664
'@vocab' => $this->urlGenerator->generate('api_doc', ['_format' => self::FORMAT], UrlGeneratorInterface::ABS_URL).'#',
665-
'hydra' => ContextBuilderInterface::HYDRA_NS,
666-
'rdf' => ContextBuilderInterface::RDF_NS,
667-
'rdfs' => ContextBuilderInterface::RDFS_NS,
668-
'xmls' => ContextBuilderInterface::XML_NS,
669-
'owl' => ContextBuilderInterface::OWL_NS,
670665
'schema' => ContextBuilderInterface::SCHEMA_ORG_NS,
671666
'domain' => ['@id' => 'rdfs:domain', '@type' => '@id'],
672667
'range' => ['@id' => 'rdfs:range', '@type' => '@id'],

0 commit comments

Comments
 (0)