Skip to content

Commit 4abec44

Browse files
authored
fix(metadata): compute isWritable during updates (#7383)
fixes #7382
1 parent 949c3c9 commit 4abec44

32 files changed

+1048
-639
lines changed

src/Doctrine/Odm/Metadata/Property/DoctrineMongoDbOdmPropertyMetadataFactory.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ public function create(string $resourceClass, string $property, array $options =
5757
break;
5858
}
5959

60+
if ($options['api_allow_update'] ?? false) {
61+
break;
62+
}
63+
6064
$propertyMetadata = $propertyMetadata->withWritable(false);
6165

6266
break;

src/Doctrine/Orm/Metadata/Property/DoctrineOrmPropertyMetadataFactory.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ public function create(string $resourceClass, string $property, array $options =
5656
break;
5757
}
5858

59+
if ($options['api_allow_update'] ?? false) {
60+
break;
61+
}
62+
5963
if ($doctrineClassMetadata instanceof ClassMetadata) {
6064
$writable = $doctrineClassMetadata->isIdentifierNatural();
6165
} else {

src/GraphQl/Tests/Serializer/ItemNormalizerTest.php

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,11 @@ public function testNormalize(): void
7878

7979
$propertyNameCollection = new PropertyNameCollection(['name']);
8080
$propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
81-
$propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn($propertyNameCollection);
81+
$propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn($propertyNameCollection);
8282

8383
$propertyMetadata = (new ApiProperty())->withReadable(true);
8484
$propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
85-
$propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn($propertyMetadata);
85+
$propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn($propertyMetadata);
8686

8787
$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
8888
$iriConverterProphecy->getIriFromResource($dummy, UrlGeneratorInterface::ABS_URL, Argument::any(), Argument::type('array'))->willReturn('/dummies/1');
@@ -131,13 +131,13 @@ public function testNormalizeWithUnsafeCacheProperty(): void
131131

132132
$propertyNameCollection = new PropertyNameCollection(['title', 'ownerOnlyProperty']);
133133
$propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
134-
$propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, [])->willReturn($propertyNameCollection);
134+
$propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, Argument::type('array'))->willReturn($propertyNameCollection);
135135

136136
$unsecuredPropertyMetadata = (new ApiProperty())->withReadable(true);
137137
$securedPropertyMetadata = (new ApiProperty())->withReadable(true)->withSecurity('object == null or object.getOwner() == user');
138138
$propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
139-
$propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', [])->willReturn($unsecuredPropertyMetadata);
140-
$propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', [])->willReturn($securedPropertyMetadata);
139+
$propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn($unsecuredPropertyMetadata);
140+
$propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', Argument::type('array'))->willReturn($securedPropertyMetadata);
141141

142142
$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
143143
$iriConverterProphecy->getIriFromResource($securedDummyWithOwnerOnlyPropertyAllowed, UrlGeneratorInterface::ABS_URL, Argument::any(), Argument::type('array'))->willReturn('/dummies/1');
@@ -216,11 +216,11 @@ public function testNormalizeNoResolverData(): void
216216

217217
$propertyNameCollection = new PropertyNameCollection(['name']);
218218
$propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
219-
$propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn($propertyNameCollection);
219+
$propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn($propertyNameCollection);
220220

221221
$propertyMetadata = (new ApiProperty())->withWritable(true)->withReadable(true);
222222
$propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
223-
$propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn($propertyMetadata);
223+
$propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn($propertyMetadata);
224224

225225
$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
226226
$iriConverterProphecy->getIriFromResource($dummy, UrlGeneratorInterface::ABS_URL, Argument::any(), Argument::type('array'))->willReturn('/dummies/1');
@@ -260,11 +260,11 @@ public function testDenormalize(): void
260260

261261
$propertyNameCollection = new PropertyNameCollection(['name']);
262262
$propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
263-
$propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn($propertyNameCollection)->shouldBeCalled();
263+
$propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn($propertyNameCollection)->shouldBeCalled();
264264

265265
$propertyMetadata = (new ApiProperty())->withWritable(true)->withReadable(true);
266266
$propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
267-
$propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn($propertyMetadata)->shouldBeCalled();
267+
$propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn($propertyMetadata)->shouldBeCalled();
268268

269269
$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
270270

src/Hal/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/composer.lock
2+
/vendor
3+
/.phpunit.result.cache
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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\Hal\Tests\Fixtures\ApiResource\Issue5452;
15+
16+
interface ActivableInterface
17+
{
18+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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\Hal\Tests\Fixtures\ApiResource\Issue5452;
15+
16+
use ApiPlatform\Metadata\ApiProperty;
17+
use ApiPlatform\Metadata\ApiResource;
18+
use ApiPlatform\Metadata\Get;
19+
use ApiPlatform\Tests\Fixtures\TestBundle\State\Issue5452\AuthorItemProvider;
20+
21+
#[ApiResource(
22+
operations: [
23+
new Get(uriTemplate: '/issue-5452/authors/{id}{._format}', provider: AuthorItemProvider::class),
24+
]
25+
)]
26+
class Author implements ActivableInterface, TimestampableInterface
27+
{
28+
public function __construct(
29+
#[ApiProperty(identifier: true)]
30+
public readonly string|int $id,
31+
public readonly string $name,
32+
) {
33+
}
34+
}
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 <[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\Hal\Tests\Fixtures\ApiResource\Issue5452;
15+
16+
use ApiPlatform\Metadata\GetCollection;
17+
use ApiPlatform\Metadata\Post;
18+
use ApiPlatform\Tests\Fixtures\TestBundle\State\Issue5452\BookCollectionProvider;
19+
20+
#[GetCollection(uriTemplate: '/issue-5452/books{._format}', provider: BookCollectionProvider::class)]
21+
#[Post(uriTemplate: '/issue-5452/books{._format}')]
22+
class Book
23+
{
24+
// union types
25+
public string|int|null $number = null;
26+
27+
// simple types
28+
public ?string $isbn = null;
29+
30+
// intersect types without specific typehint (throw an error: AbstractItemNormalizer line 872)
31+
public ActivableInterface&TimestampableInterface $library;
32+
33+
/**
34+
* @var Author
35+
*/
36+
// intersect types with PHPDoc
37+
public ActivableInterface&TimestampableInterface $author;
38+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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\Hal\Tests\Fixtures\ApiResource\Issue5452;
15+
16+
use ApiPlatform\Metadata\ApiProperty;
17+
use ApiPlatform\Metadata\ApiResource;
18+
use ApiPlatform\Metadata\Get;
19+
use ApiPlatform\Tests\Fixtures\TestBundle\State\Issue5452\LibraryItemProvider;
20+
21+
#[ApiResource(
22+
operations: [
23+
new Get(uriTemplate: '/issue-5452/libraries/{id}{._format}', provider: LibraryItemProvider::class),
24+
]
25+
)]
26+
class Library implements ActivableInterface, TimestampableInterface
27+
{
28+
public function __construct(
29+
#[ApiProperty(identifier: true)]
30+
public readonly string|int $id,
31+
public readonly string $name,
32+
) {
33+
}
34+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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\Hal\Tests\Fixtures\ApiResource\Issue5452;
15+
16+
interface TimestampableInterface
17+
{
18+
}

src/Hal/Tests/Fixtures/Dummy.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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\Hal\Tests\Fixtures;
15+
16+
class Dummy
17+
{
18+
public int $id;
19+
private $relatedDummy;
20+
private $name;
21+
22+
public function getName(): string
23+
{
24+
return $this->name;
25+
}
26+
27+
public function setName(string $name): void
28+
{
29+
$this->name = $name;
30+
}
31+
32+
public function getRelatedDummy(): RelatedDummy
33+
{
34+
return $this->relatedDummy;
35+
}
36+
37+
public function setRelatedDummy(RelatedDummy $relatedDummy): void
38+
{
39+
$this->relatedDummy = $relatedDummy;
40+
}
41+
}

0 commit comments

Comments
 (0)