Skip to content

Commit 76a93c4

Browse files
Copilotsoyuka
andcommitted
fix: add isInitialized check before getValue in PersistProcessor
Co-authored-by: soyuka <[email protected]>
1 parent 22cba22 commit 76a93c4

File tree

4 files changed

+170
-0
lines changed

4 files changed

+170
-0
lines changed

src/Doctrine/Common/State/PersistProcessor.php

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

171+
// Skip uninitialized typed properties (e.g., those initialized in @PrePersist)
172+
if (!$reflectionProperty->isInitialized($data)) {
173+
continue;
174+
}
175+
171176
$value = $reflectionProperty->getValue($data);
172177

173178
if (!\is_object($value)) {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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\Tests\Fixtures\TestBundle\ApiResource\Issue7735;
15+
16+
use ApiPlatform\Doctrine\Orm\State\Options;
17+
use ApiPlatform\Metadata\ApiResource;
18+
use ApiPlatform\Metadata\Post;
19+
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue7735\Issue7735Entity;
20+
use Symfony\Component\ObjectMapper\Attribute\Map;
21+
22+
#[ApiResource(
23+
stateOptions: new Options(entityClass: Issue7735Entity::class),
24+
operations: [
25+
new Post(
26+
uriTemplate: '/issue7735_resources',
27+
),
28+
]
29+
)]
30+
#[Map(target: Issue7735Entity::class)]
31+
class Issue7735Resource
32+
{
33+
public ?string $id = null;
34+
public string $name;
35+
public ?string $generatedValue = null;
36+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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\Tests\Fixtures\TestBundle\Entity\Issue7735;
15+
16+
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue7735\Issue7735Resource;
17+
use Doctrine\ORM\Mapping as ORM;
18+
use Symfony\Component\ObjectMapper\Attribute\Map;
19+
20+
#[ORM\Entity]
21+
#[ORM\HasLifecycleCallbacks]
22+
#[Map(target: Issue7735Resource::class)]
23+
class Issue7735Entity
24+
{
25+
#[ORM\Id, ORM\Column, ORM\GeneratedValue]
26+
#[Map(transform: 'strval')]
27+
private ?int $id = null;
28+
29+
#[ORM\Column]
30+
private string $name;
31+
32+
/**
33+
* This typed property is intentionally not initialized.
34+
* It will be set in the PrePersist callback.
35+
*/
36+
#[ORM\Column]
37+
private string $generatedValue;
38+
39+
#[ORM\PrePersist]
40+
public function prePersist(): void
41+
{
42+
// Initialize the typed property in PrePersist
43+
$this->generatedValue = 'generated_' . uniqid();
44+
}
45+
46+
public function getId(): ?int
47+
{
48+
return $this->id;
49+
}
50+
51+
public function getName(): string
52+
{
53+
return $this->name;
54+
}
55+
56+
public function setName(string $name): self
57+
{
58+
$this->name = $name;
59+
60+
return $this;
61+
}
62+
63+
public function getGeneratedValue(): string
64+
{
65+
return $this->generatedValue;
66+
}
67+
68+
public function setGeneratedValue(string $generatedValue): self
69+
{
70+
$this->generatedValue = $generatedValue;
71+
72+
return $this;
73+
}
74+
}

tests/Functional/Issue7735Test.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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\Tests\Functional;
15+
16+
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
17+
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue7735\Issue7735Resource;
18+
use ApiPlatform\Tests\SetupClassResourcesTrait;
19+
20+
final class Issue7735Test extends ApiTestCase
21+
{
22+
use SetupClassResourcesTrait;
23+
24+
protected static ?bool $alwaysBootKernel = false;
25+
26+
/**
27+
* @return class-string[]
28+
*/
29+
public static function getResources(): array
30+
{
31+
return [Issue7735Resource::class];
32+
}
33+
34+
public function testPostWithUninitializedTypedPropertyInPrePersist(): void
35+
{
36+
$response = self::createClient()->request('POST', '/issue7735_resources', [
37+
'json' => [
38+
'name' => 'Test Resource',
39+
],
40+
'headers' => [
41+
'Content-Type' => 'application/ld+json',
42+
],
43+
]);
44+
45+
$this->assertResponseStatusCodeSame(201);
46+
$this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
47+
48+
$responseData = $response->toArray();
49+
$this->assertArrayHasKey('name', $responseData);
50+
$this->assertSame('Test Resource', $responseData['name']);
51+
$this->assertArrayHasKey('generatedValue', $responseData);
52+
$this->assertNotNull($responseData['generatedValue']);
53+
$this->assertStringStartsWith('generated_', $responseData['generatedValue']);
54+
}
55+
}

0 commit comments

Comments
 (0)