├── .github
├── actions
│ └── install
│ │ └── action.yml
└── workflows
│ └── tests.yml
├── composer.json
├── phpcs.xml
├── phpstan.neon
├── phpunit.xml
└── src
├── CollectionValueObject.php
├── DateTimeValueObject.php
├── Doctrine
├── Types.php
└── Types
│ ├── CollectionValueObjectType.php
│ ├── DateTimeValueObjectType.php
│ ├── IntegerValueObjectType.php
│ ├── ObjectValueObjectType.php
│ ├── StringValueObjectType.php
│ └── ValueObjectType.php
├── IntegerValueObject.php
├── ObjectValueObject.php
└── StringValueObject.php
/.github/actions/install/action.yml:
--------------------------------------------------------------------------------
1 | name: Prepare env & install dependencies
2 |
3 | on:
4 | workflow_call:
5 | inputs:
6 | php-version:
7 | required: true
8 | type: string
9 | dbal-version:
10 | required: true
11 | type: string
12 | orm-version:
13 | required: true
14 | type: string
15 | coverage-mode:
16 | required: false
17 | type: string
18 | default: 'none'
19 | secrets:
20 | token:
21 | required: true
22 |
23 | runs:
24 | using: "composite"
25 | steps:
26 | - name: "Setup PHP"
27 | uses: shivammathur/setup-php@v2
28 | with:
29 | coverage: ${{ inputs.coverage-mode }}
30 | php-version: ${{ inputs.php-version }}
31 | - name: "Install dependencies with composer"
32 | shell: bash
33 | run: |
34 | composer require --quiet --no-update "doctrine/orm:${{ inputs.orm-version }}"
35 | composer require --quiet --no-update "doctrine/dbal:${{ inputs.dbal-version }}"
36 | composer update --no-interaction --no-progress --no-suggest
37 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: "Tests"
2 |
3 | on:
4 | pull_request:
5 | push:
6 | branches:
7 | - "main"
8 |
9 | jobs:
10 | phpunit:
11 | name: "PHPUnit"
12 | runs-on: ubuntu-latest
13 | strategy:
14 | matrix:
15 | include:
16 | - php-version: '8.0'
17 | dbal-version: '^3.8'
18 | orm-version: '^2.7'
19 | - php-version: '8.2'
20 | dbal-version: '^4.0'
21 | orm-version: '^3.1'
22 | steps:
23 | - name: "Checkout"
24 | uses: actions/checkout@v3
25 | - name: "Setup env & install dependencies"
26 | uses: ./.github/actions/install
27 | with:
28 | php-version: ${{ matrix.php-version }}
29 | dbal-version: ${{ matrix.dbal-version }}
30 | orm-version: ${{ matrix.orm-version }}
31 | - name: "Run tests with phpunit/phpunit"
32 | run: vendor/bin/phpunit
33 |
34 | phpstan:
35 | name: "PhpStan"
36 | runs-on: ubuntu-latest
37 | steps:
38 | - name: "Checkout"
39 | uses: actions/checkout@v3
40 | - name: "Setup env & install dependencies"
41 | uses: ./.github/actions/install
42 | with:
43 | php-version: '8.2'
44 | dbal-version: '^4.0'
45 | orm-version: '^3.1'
46 | - name: "Run static analyzis with phpstan/phpstan"
47 | run: vendor/bin/phpstan analyze
48 |
49 | checkstyke:
50 | name: "Checkstyle"
51 | runs-on: ubuntu-latest
52 | steps:
53 | - name: "Checkout"
54 | uses: actions/checkout@v3
55 | - name: "Setup env & install dependencies"
56 | uses: ./.github/actions/install
57 | with:
58 | php-version: '8.2'
59 | dbal-version: '^4.0'
60 | orm-version: '^3.1'
61 | - name: "Run checkstyle with squizlabs/php_codesniffer"
62 | run: vendor/bin/phpcs
63 |
64 | codecov:
65 | name: "Code coverage"
66 | runs-on: ubuntu-latest
67 | steps:
68 | - name: "Checkout"
69 | uses: actions/checkout@v3
70 | - name: "Setup env & install dependencies"
71 | uses: ./.github/actions/install
72 | with:
73 | php-version: '8.2'
74 | dbal-version: '^4.0'
75 | orm-version: '^3.1'
76 | - name: "Run tests with phpunit/phpunit"
77 | env:
78 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
79 | run: |
80 | vendor/bin/phpunit --coverage-clover coverage.xml
81 | - name: "Upload coverage to Codecov"
82 | uses: codecov/codecov-action@v1
83 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "yokai/doctrine-value-object",
3 | "description": "Value Objects for Doctrine ORM simplified",
4 | "keywords": ["doctrine", "orm", "value object"],
5 | "license": "MIT",
6 | "authors": [
7 | {
8 | "name": "Yann Eugoné",
9 | "email": "eugone.yann@gmail.com"
10 | }
11 | ],
12 | "require": {
13 | "php": "^8.0",
14 | "doctrine/dbal": "^3.8|^4.0",
15 | "doctrine/orm": "^2.7|^3.1",
16 | "webmozart/assert": "^1.11"
17 | },
18 | "autoload": {
19 | "psr-4": {
20 | "Yokai\\DoctrineValueObject\\": "src/"
21 | }
22 | },
23 | "require-dev": {
24 | "phpunit/phpunit": "^9.4",
25 | "phpstan/phpstan": "^1.11",
26 | "squizlabs/php_codesniffer": "^3.9",
27 | "symfony/cache": "^5.4|^6.0|^7.0"
28 | },
29 | "autoload-dev": {
30 | "psr-4": {
31 | "Yokai\\DoctrineValueObject\\Tests\\": "tests/"
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | src/
14 | tests/
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | level: max
3 | paths:
4 | - src/
5 | - tests/
6 |
7 | ignoreErrors:
8 | - identifier: missingType.iterableValue
9 | - identifier: missingType.generics
10 | # callable with array syntax is not inferred
11 | - '#.*function call_user_func expects callable.*#'
12 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 | ./tests
13 |
14 |
15 |
16 |
17 |
18 | ./src
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/CollectionValueObject.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | interface CollectionValueObject extends Countable, IteratorAggregate
16 | {
17 | public static function fromValue(array $value): static;
18 |
19 | public function toValue(): array;
20 | }
21 |
--------------------------------------------------------------------------------
/src/DateTimeValueObject.php:
--------------------------------------------------------------------------------
1 | $types
24 | */
25 | public function __construct(array $types)
26 | {
27 | $this->types = $types;
28 | }
29 |
30 | public function register(TypeRegistry $types): void
31 | {
32 | foreach ($this->types as $doctrineTypeName => $valueObjectType) {
33 | if (!$types->has($doctrineTypeName)) {
34 | $types->register($doctrineTypeName, $this->type($valueObjectType, $doctrineTypeName));
35 | }
36 | }
37 | }
38 |
39 | private function type(string $valueObjectType, string $doctrineTypeName): Type
40 | {
41 | foreach (self::DOCTRINE_TYPES as $doctrineTypeClass) {
42 | $supportValueObjectType = \call_user_func([$doctrineTypeClass, 'getSupportedValueObjectType']);
43 | if (\is_a($valueObjectType, $supportValueObjectType, true)) {
44 | return \call_user_func([$doctrineTypeClass, 'create'], $valueObjectType, $doctrineTypeName);
45 | }
46 | }
47 |
48 | throw new \InvalidArgumentException(
49 | \sprintf(
50 | 'Value object named "%s" associated to type "%s" has no related doctrine type',
51 | $doctrineTypeName,
52 | $valueObjectType
53 | )
54 | );
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Doctrine/Types/CollectionValueObjectType.php:
--------------------------------------------------------------------------------
1 | getInheritedType()->getSQLDeclaration($column, $platform);
21 | }
22 |
23 | /**
24 | * @inheritdoc
25 | */
26 | public static function getSupportedValueObjectType(): string
27 | {
28 | return CollectionValueObject::class;
29 | }
30 |
31 | /**
32 | * @inheritdoc
33 | */
34 | public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): ?string
35 | {
36 | if ($value === null) {
37 | return null;
38 | }
39 |
40 | Assert::isInstanceOf($value, $this->class);
41 | /** @var CollectionValueObject $value */
42 |
43 | return $this->getInheritedType()->convertToDatabaseValue($value->toValue(), $platform);
44 | }
45 |
46 | /**
47 | * @inheritdoc
48 | */
49 | public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?CollectionValueObject
50 | {
51 | $value = $this->getInheritedType()->convertToPHPValue($value, $platform);
52 |
53 | if ($value === null) {
54 | return null;
55 | }
56 |
57 | Assert::isArray($value);
58 |
59 | /** @var CollectionValueObject $collection */
60 | $collection = \call_user_func([$this->class, 'fromValue'], $value);
61 | Assert::isInstanceOf($collection, $this->class);
62 |
63 | return $collection;
64 | }
65 |
66 | private function getInheritedType(): JsonType
67 | {
68 | /** @var JsonType $type */
69 | $type = $this->getType(Types::JSON);
70 |
71 | return $type;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Doctrine/Types/DateTimeValueObjectType.php:
--------------------------------------------------------------------------------
1 | getInheritedType()->getSQLDeclaration($column, $platform);
22 | }
23 |
24 | /**
25 | * @inheritdoc
26 | */
27 | public static function getSupportedValueObjectType(): string
28 | {
29 | return DateTimeValueObject::class;
30 | }
31 |
32 | /**
33 | * @inheritdoc
34 | */
35 | public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): ?string
36 | {
37 | if ($value === null) {
38 | return null;
39 | }
40 |
41 | Assert::isInstanceOf($value, $this->class);
42 | /** @var DateTimeValueObject $value */
43 |
44 | return $this->getInheritedType()->convertToDatabaseValue($value->toValue(), $platform);
45 | }
46 |
47 | /**
48 | * @inheritdoc
49 | */
50 | public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?DateTimeValueObject
51 | {
52 | $value = $this->getInheritedType()->convertToPHPValue($value, $platform);
53 |
54 | if ($value === null) {
55 | return null;
56 | }
57 |
58 | Assert::isInstanceOf($value, DateTimeImmutable::class);
59 |
60 | /** @var DateTimeValueObject $object */
61 | $object = \call_user_func([$this->class, 'fromValue'], $value);
62 | Assert::isInstanceOf($object, $this->class);
63 |
64 | return $object;
65 | }
66 |
67 | private function getInheritedType(): DateTimeTzImmutableType
68 | {
69 | /** @var DateTimeTzImmutableType $type */
70 | $type = $this->getType(Types::DATETIMETZ_IMMUTABLE);
71 |
72 | return $type;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/Doctrine/Types/IntegerValueObjectType.php:
--------------------------------------------------------------------------------
1 | getInheritedType()->getSQLDeclaration($column, $platform);
21 | }
22 |
23 | /**
24 | * @inheritdoc
25 | */
26 | public static function getSupportedValueObjectType(): string
27 | {
28 | return IntegerValueObject::class;
29 | }
30 |
31 | /**
32 | * @inheritdoc
33 | */
34 | public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): ?int
35 | {
36 | if ($value === null) {
37 | return null;
38 | }
39 |
40 | Assert::isInstanceOf($value, $this->class);
41 | /** @var IntegerValueObject $value */
42 |
43 | return $this->getInheritedType()->convertToPHPValue($value->toValue(), $platform);
44 | }
45 |
46 | /**
47 | * @inheritdoc
48 | */
49 | public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?IntegerValueObject
50 | {
51 | $value = $this->getInheritedType()->convertToPHPValue($value, $platform);
52 |
53 | if ($value === null) {
54 | return null;
55 | }
56 |
57 | Assert::integer($value);
58 |
59 | /** @var IntegerValueObject $object */
60 | $object = \call_user_func([$this->class, 'fromValue'], $value);
61 | Assert::isInstanceOf($object, $this->class);
62 |
63 | return $object;
64 | }
65 |
66 | private function getInheritedType(): IntegerType
67 | {
68 | /** @var IntegerType $type */
69 | $type = $this->getType(Types::INTEGER);
70 |
71 | return $type;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Doctrine/Types/ObjectValueObjectType.php:
--------------------------------------------------------------------------------
1 | getInheritedType()->getSQLDeclaration($column, $platform);
21 | }
22 |
23 | /**
24 | * @inheritdoc
25 | */
26 | public static function getSupportedValueObjectType(): string
27 | {
28 | return ObjectValueObject::class;
29 | }
30 |
31 | /**
32 | * @inheritdoc
33 | */
34 | public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): ?string
35 | {
36 | if ($value === null) {
37 | return null;
38 | }
39 |
40 | Assert::isInstanceOf($value, $this->class);
41 | /** @var ObjectValueObject $value */
42 |
43 | return $this->getInheritedType()->convertToDatabaseValue($value->toValue(), $platform);
44 | }
45 |
46 | /**
47 | * @inheritdoc
48 | */
49 | public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?ObjectValueObject
50 | {
51 | $value = $this->getInheritedType()->convertToPHPValue($value, $platform);
52 |
53 | if ($value === null) {
54 | return null;
55 | }
56 |
57 | Assert::isArray($value);
58 |
59 | /** @var ObjectValueObject $collection */
60 | $collection = \call_user_func([$this->class, 'fromValue'], $value);
61 | Assert::isInstanceOf($collection, $this->class);
62 |
63 | return $collection;
64 | }
65 |
66 | private function getInheritedType(): JsonType
67 | {
68 | /** @var JsonType $type */
69 | $type = $this->getType(Types::JSON);
70 |
71 | return $type;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Doctrine/Types/StringValueObjectType.php:
--------------------------------------------------------------------------------
1 | getInheritedType()->getSQLDeclaration($column, $platform);
21 | }
22 |
23 | /**
24 | * @inheritdoc
25 | */
26 | public static function getSupportedValueObjectType(): string
27 | {
28 | return StringValueObject::class;
29 | }
30 |
31 | /**
32 | * @inheritdoc
33 | */
34 | public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): mixed
35 | {
36 | if ($value === null) {
37 | return null;
38 | }
39 |
40 | Assert::isInstanceOf($value, $this->class);
41 | /** @var StringValueObject $value */
42 |
43 | return $this->getInheritedType()->convertToDatabaseValue($value->toValue(), $platform);
44 | }
45 |
46 | /**
47 | * @inheritdoc
48 | */
49 | public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?StringValueObject
50 | {
51 | $value = $this->getInheritedType()->convertToPHPValue($value, $platform);
52 |
53 | if ($value === null) {
54 | return null;
55 | }
56 |
57 | Assert::string($value);
58 |
59 | /** @var StringValueObject $object */
60 | $object = \call_user_func([$this->class, 'fromValue'], $value);
61 | Assert::isInstanceOf($object, $this->class);
62 |
63 | return $object;
64 | }
65 |
66 | private function getInheritedType(): StringType
67 | {
68 | /** @var StringType $type */
69 | $type = $this->getType(Types::STRING);
70 |
71 | return $type;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Doctrine/Types/ValueObjectType.php:
--------------------------------------------------------------------------------
1 | class = $class;
37 | $type->name = $name;
38 |
39 | return $type;
40 | }
41 |
42 | /**
43 | * @return class-string
44 | */
45 | abstract public static function getSupportedValueObjectType(): string;
46 |
47 | /**
48 | * @inheritdoc
49 | */
50 | public function getName(): string
51 | {
52 | return $this->name;
53 | }
54 |
55 | /**
56 | * {@inheritdoc}
57 | */
58 | public function requiresSQLCommentHint(AbstractPlatform $platform): bool
59 | {
60 | return true;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/IntegerValueObject.php:
--------------------------------------------------------------------------------
1 |