Skip to content

Commit c21c6ae

Browse files
committed
Implement object value object
1 parent 64b6567 commit c21c6ae

File tree

7 files changed

+161
-21
lines changed

7 files changed

+161
-21
lines changed

README.md

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,6 @@ This library offer you an easy way to handle **value objects** in a **Doctrine O
1111

1212
It will save you the creation of Doctrine types for every different value object types you have.
1313

14-
**If you are looking for Value Objects with multiple properties to store:
15-
please use [Doctrine Embeddables](https://www.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/embeddables.html).**
16-
1714

1815
## Installation
1916

@@ -93,16 +90,6 @@ The object will be **stored as a string** in the database but **hydrated back to
9390
See [PhoneNumber code](tests/PhoneNumber.php) in tests.
9491

9592

96-
### Collection
97-
98-
Imagine that in this application, you will sometimes have to store multiple phone numbers in a property.
99-
You can centralize this type hinting in a `PhoneNumbers` **collection value object**.
100-
101-
The object will be **stored as a json** in the database but **hydrated back to this object** by Doctrine.
102-
103-
See [PhoneNumbers code](tests/PhoneNumbers.php) in tests.
104-
105-
10693
### DateTime
10794

10895
Imagine you have an application that stores users birthdate.
@@ -125,6 +112,33 @@ The object will be **stored as a integer** in the database but **hydrated back t
125112
See [Status code](tests/Status.php) in tests.
126113

127114

115+
### Object
116+
117+
**If you are looking for value objects with multiple properties each stored in a table column:
118+
please use [Doctrine Embeddables](https://www.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/embeddables.html).**
119+
120+
**If you are looking for polymorphic value objects stored as JSON:
121+
please use [dunglas/doctrine-json-odm](https://github.com/dunglas/doctrine-json-odm).**
122+
123+
Imagine you have an application that stores user notifications preferences.
124+
There is multiple information you want to store, as a JSON object.
125+
126+
You can centralize this logic in a `Notifications` **object value object**.
127+
The object will be **stored as a json object** in the database but **hydrated back to this object** by Doctrine.
128+
129+
See [Notifications code](tests/Notifications.php) in tests.
130+
131+
132+
### Collection
133+
134+
Imagine that you have an application that have to store multiple phone numbers in a property.
135+
You can centralize this type hinting in a `PhoneNumbers` **collection value object**.
136+
137+
The object will be **stored as a json array** in the database but **hydrated back to this object** by Doctrine.
138+
139+
See [PhoneNumbers code](tests/PhoneNumbers.php) in tests.
140+
141+
128142
## Contribution
129143

130144
Please feel free to open an [issue](https://github.com/yokai-php/doctrine-value-object/issues)

src/Doctrine/Types.php

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,15 @@
66

77
use Doctrine\DBAL\Types\Type;
88
use Doctrine\DBAL\Types\TypeRegistry;
9-
use Yokai\DoctrineValueObject\Doctrine\Types\CollectionValueObjectType;
10-
use Yokai\DoctrineValueObject\Doctrine\Types\DateTimeValueObjectType;
11-
use Yokai\DoctrineValueObject\Doctrine\Types\IntegerValueObjectType;
12-
use Yokai\DoctrineValueObject\Doctrine\Types\StringValueObjectType;
139

1410
final class Types
1511
{
1612
private const DOCTRINE_TYPES = [
17-
CollectionValueObjectType::class,
18-
DateTimeValueObjectType::class,
19-
IntegerValueObjectType::class,
20-
StringValueObjectType::class,
13+
Types\CollectionValueObjectType::class,
14+
Types\DateTimeValueObjectType::class,
15+
Types\IntegerValueObjectType::class,
16+
Types\ObjectValueObjectType::class,
17+
Types\StringValueObjectType::class,
2118
];
2219

2320
private array $types;
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yokai\DoctrineValueObject\Doctrine\Types;
6+
7+
use Doctrine\DBAL\Platforms\AbstractPlatform;
8+
use Doctrine\DBAL\Types\JsonType;
9+
use Webmozart\Assert\Assert;
10+
use Yokai\DoctrineValueObject\ObjectValueObject;
11+
12+
final class ObjectValueObjectType extends JsonType
13+
{
14+
use ValueObjectType;
15+
16+
/**
17+
* @inheritdoc
18+
*/
19+
public static function getSupportedValueObjectType(): string
20+
{
21+
return ObjectValueObject::class;
22+
}
23+
24+
/**
25+
* @inheritdoc
26+
*/
27+
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string
28+
{
29+
if ($value === null) {
30+
return null;
31+
}
32+
33+
Assert::isInstanceOf($value, $this->class);
34+
/** @var ObjectValueObject $value */
35+
36+
return parent::convertToDatabaseValue($value->toValue(), $platform);
37+
}
38+
39+
/**
40+
* @inheritdoc
41+
*/
42+
public function convertToPHPValue($value, AbstractPlatform $platform): ?ObjectValueObject
43+
{
44+
$value = parent::convertToPHPValue($value, $platform);
45+
if ($value === null) {
46+
return null;
47+
}
48+
49+
Assert::isArray($value);
50+
51+
/** @var ObjectValueObject $collection */
52+
$collection = \call_user_func([$this->class, 'fromValue'], $value);
53+
Assert::isInstanceOf($collection, $this->class);
54+
55+
return $collection;
56+
}
57+
}

src/ObjectValueObject.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yokai\DoctrineValueObject;
6+
7+
/**
8+
* todo php 8 : fromValue with static return type + impacts on descendants
9+
*/
10+
interface ObjectValueObject
11+
{
12+
public static function fromValue(array $value): self;
13+
14+
public function toValue(): array;
15+
}

tests/EntityTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ final class EntityTest extends TestCase
1919
'birthdate' => Birthdate::class,
2020
'phone_number' => PhoneNumber::class,
2121
'phone_numbers' => PhoneNumbers::class,
22+
'notifications' => Notifications::class,
2223
];
2324

2425
public function testUserWithNoValues(): void
@@ -42,6 +43,7 @@ public function testUserWithNoValues(): void
4243
self::assertNull($row['birthdate']);
4344
self::assertNull($row['contactPhoneNumber']);
4445
self::assertNull($row['phoneNumbers']);
46+
self::assertNull($row['notifications']);
4547

4648
// fetch entity from database
4749
/** @var User $user */
@@ -52,6 +54,7 @@ public function testUserWithNoValues(): void
5254
self::assertNull($user->birthdate);
5355
self::assertNull($user->contactPhoneNumber);
5456
self::assertNull($user->phoneNumbers);
57+
self::assertNull($user->notifications);
5558
}
5659

5760
public function testUserWithAllValues(): void
@@ -66,6 +69,7 @@ public function testUserWithAllValues(): void
6669
$user->phoneNumbers = new PhoneNumbers(
6770
[new PhoneNumber('+33455667788'), new PhoneNumber('+33233445566')]
6871
);
72+
$user->notifications = new Notifications(true, false);
6973
// save changes
7074
$entityManager->persist($user);
7175
$entityManager->flush();
@@ -82,6 +86,7 @@ public function testUserWithAllValues(): void
8286
self::assertSame('1986-11-17 00:00:00', $row['birthdate']);
8387
self::assertSame('+33677889900', $row['contactPhoneNumber']);
8488
self::assertSame('["+33455667788","+33233445566"]', $row['phoneNumbers']);
89+
self::assertSame('{"email":true,"sms":false}', $row['notifications']);
8590

8691
// fetch entity from database
8792
/** @var User $user */
@@ -95,6 +100,8 @@ public function testUserWithAllValues(): void
95100
['+33455667788', '+33233445566'],
96101
\array_map(fn(PhoneNumber $number) => $number->getNumber(), $user->phoneNumbers->getNumbers())
97102
);
103+
self::assertTrue($user->notifications->isEmail());
104+
self::assertFalse($user->notifications->isSms());
98105
}
99106

100107
private function manager(): EntityManager

tests/Notifications.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yokai\DoctrineValueObject\Tests;
6+
7+
use Yokai\DoctrineValueObject\ObjectValueObject;
8+
9+
final class Notifications implements ObjectValueObject
10+
{
11+
private bool $email;
12+
private bool $sms;
13+
14+
public function __construct(bool $email, bool $sms)
15+
{
16+
$this->email = $email;
17+
$this->sms = $sms;
18+
}
19+
20+
public static function fromValue(array $value): ObjectValueObject
21+
{
22+
return new self(
23+
$value['email'] ?? false,
24+
$value['sms'] ?? false
25+
);
26+
}
27+
28+
public function toValue(): array
29+
{
30+
return [
31+
'email' => $this->email,
32+
'sms' => $this->sms,
33+
];
34+
}
35+
36+
public function isEmail(): bool
37+
{
38+
return $this->email;
39+
}
40+
41+
public function isSms(): bool
42+
{
43+
return $this->sms;
44+
}
45+
}

tests/User.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,9 @@ final class User
3737
* @ORM\Column(type="phone_numbers", nullable=true)
3838
*/
3939
public ?PhoneNumbers $phoneNumbers = null;
40+
41+
/**
42+
* @ORM\Column(type="notifications", nullable=true)
43+
*/
44+
public ?Notifications $notifications = null;
4045
}

0 commit comments

Comments
 (0)