Skip to content

Commit f7e3cca

Browse files
committed
Add broken tests for issue #1610
1 parent 27f8951 commit f7e3cca

File tree

4 files changed

+170
-0
lines changed

4 files changed

+170
-0
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace JMS\Serializer\Tests\Fixtures\Doctrine\Entity;
6+
7+
interface UserInterface
8+
{
9+
public function getId(): int;
10+
11+
public function getUsername(): string;
12+
13+
public function isAdmin(): bool;
14+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace JMS\Serializer\Tests\Fixtures\Doctrine\Entity;
6+
7+
use Doctrine\DBAL\Types\Types;
8+
use Doctrine\ORM\Mapping as ORM;
9+
10+
/** @ORM\Entity */
11+
#[ORM\Entity]
12+
class UserWithConstructorDefault implements UserInterface
13+
{
14+
/**
15+
* @ORM\Id
16+
* @ORM\Column(type="integer")
17+
*/
18+
#[ORM\Id]
19+
#[ORM\Column('id', Types::INTEGER)]
20+
private int $id;
21+
22+
/**
23+
* @ORM\Column(type="string")
24+
*/
25+
#[ORM\Column('username', Types::STRING)]
26+
private string $username;
27+
28+
/**
29+
* @ORM\Column(type="boolean")
30+
*/
31+
#[ORM\Column('admin', Types::BOOLEAN)]
32+
private bool $admin;
33+
34+
public function __construct(int $id, string $username, bool $admin = false)
35+
{
36+
$this->id = $id;
37+
$this->username = $username;
38+
$this->admin = $admin;
39+
}
40+
41+
public function getId(): int
42+
{
43+
return $this->id;
44+
}
45+
46+
public function getUsername(): string
47+
{
48+
return $this->username;
49+
}
50+
51+
public function isAdmin(): bool
52+
{
53+
return $this->admin;
54+
}
55+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace JMS\Serializer\Tests\Fixtures\Doctrine\Entity;
6+
7+
use Doctrine\DBAL\Types\Types;
8+
use Doctrine\ORM\Mapping as ORM;
9+
10+
/** @ORM\Entity */
11+
#[ORM\Entity]
12+
class UserWithPropertyDefault implements UserInterface
13+
{
14+
/**
15+
* @ORM\Id
16+
* @ORM\Column(type="integer")
17+
*/
18+
#[ORM\Id]
19+
#[ORM\Column('id', Types::INTEGER)]
20+
private int $id;
21+
22+
/**
23+
* @ORM\Column(type="string")
24+
*/
25+
#[ORM\Column('username', Types::STRING)]
26+
private string $username;
27+
28+
/**
29+
* @ORM\Column(type="boolean")
30+
*/
31+
#[ORM\Column('admin', Types::BOOLEAN)]
32+
private bool $admin = false;
33+
34+
public function __construct(int $id, string $username)
35+
{
36+
$this->id = $id;
37+
$this->username = $username;
38+
}
39+
40+
public function getId(): int
41+
{
42+
return $this->id;
43+
}
44+
45+
public function getUsername(): string
46+
{
47+
return $this->username;
48+
}
49+
50+
public function isAdmin(): bool
51+
{
52+
return $this->admin;
53+
}
54+
55+
public function setAdmin(bool $admin): self
56+
{
57+
$this->admin = $admin;
58+
59+
return $this;
60+
}
61+
}

tests/Serializer/Doctrine/ObjectConstructorTest.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@
4545
use JMS\Serializer\Tests\Fixtures\Doctrine\Embeddable\BlogPostSeo;
4646
use JMS\Serializer\Tests\Fixtures\Doctrine\Entity\Author;
4747
use JMS\Serializer\Tests\Fixtures\Doctrine\Entity\AuthorExcludedId;
48+
use JMS\Serializer\Tests\Fixtures\Doctrine\Entity\UserWithPropertyDefault;
49+
use JMS\Serializer\Tests\Fixtures\Doctrine\Entity\UserInterface;
50+
use JMS\Serializer\Tests\Fixtures\Doctrine\Entity\UserWithConstructorDefault;
4851
use JMS\Serializer\Tests\Fixtures\Doctrine\IdentityFields\Server;
4952
use JMS\Serializer\Tests\Fixtures\Doctrine\PersistentCollection\App;
5053
use JMS\Serializer\Tests\Fixtures\Doctrine\PersistentCollection\SmartPhone;
@@ -468,6 +471,43 @@ public function testArrayKeyExistsOnObject(): void
468471
$constructor->construct($this->visitor, $classMetadata, $data, $type, $this->context);
469472
}
470473

474+
/**
475+
* Test for issue #1610: default values should not overwrite existing entity properties
476+
* when deserializing partial payloads with DoctrineObjectConstructor
477+
*/
478+
#[DataProvider('partialDeserializationDoesNotOverwriteExistingEntityPropertiesWithDefaults')]
479+
public function testPartialDeserializationDoesNotOverwriteExistingEntityPropertiesWithDefaults(UserInterface $user): void
480+
{
481+
$serializer = $this->createSerializerWithDoctrineObjectConstructor();
482+
483+
$em = $this->registry->getManager();
484+
assert($em instanceof EntityManager);
485+
486+
// Create and persist a user with admin=true
487+
$em->persist($user);
488+
$em->flush();
489+
$em->clear();
490+
491+
// Deserialize a partial payload containing only the id
492+
// This simulates receiving a nested object reference like {"author": {"id": 1}}
493+
$jsonData = json_encode(['id' => $user->getId()]);
494+
$userDeserialized = $serializer->deserialize($jsonData, get_class($user), 'json');
495+
assert($userDeserialized instanceof UserWithPropertyDefault);
496+
497+
// The user should be fetched from the database and its properties should remain unchanged
498+
// The admin property should still be true, not reset to the default value of false
499+
self::assertEquals($user->getUsername(), $userDeserialized->getUsername(), 'Username should be preserved from database');
500+
self::assertTrue($userDeserialized->isAdmin(), 'Admin property should not be reset to default value when deserializing partial payload');
501+
}
502+
503+
public static function partialDeserializationDoesNotOverwriteExistingEntityPropertiesWithDefaults(): array
504+
{
505+
return [
506+
[new UserWithConstructorDefault(1, 'john', true)],
507+
[(new UserWithPropertyDefault(2, 'jane'))->setAdmin(true)],
508+
];
509+
}
510+
471511
protected function setUp(): void
472512
{
473513
$this->visitor = $this->getMockBuilder(DeserializationVisitorInterface::class)->getMock();

0 commit comments

Comments
 (0)