Skip to content

Commit b29bb27

Browse files
committed
first commit
0 parents  commit b29bb27

77 files changed

Lines changed: 3248 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.editorconfig

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
root=true
2+
[*]
3+
charset=utf-8
4+
indent_style=space
5+
indent_size=4
6+
tab_width=4
7+
end_of_line=lf
8+
trim_trailing_whitespace=true
9+
insert_final_newline=true
10+
11+
[*.{yaml,yml}]
12+
indent_size=2
13+
tab_width=2
14+
15+
[*.{js,ts,jsx,tsx}]
16+
indent_size=2
17+
tab_width=2
18+
19+
[*.hcl]
20+
indent_size=2
21+
tab_width=2
22+
23+
[Makefile]
24+
indent_style=tab
25+
26+
[Jenkinsfile]
27+
indent_size=2
28+
tab_width=2
29+
30+
[*.sh]
31+
end_of_line=lf
32+
33+
[*.md]
34+
trim_trailing_whitespace=false
35+
36+
[*.{xml,xsd}]
37+
max_line_length=off
38+
indent_size=2
39+
tab_width=2
40+
41+
[*.json]
42+
indent_size=2
43+
tab_width=2

.env.test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
KERNEL_CLASS='PixelFederation\DoctrineGenericTypesBundle\Tests\TestApplication\Kernel'

.gitconfig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[safe]
2+
directory = /srv/www

.github/dependabot.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# To get started with Dependabot version updates, you'll need to specify which
2+
# package ecosystems to update and where the package manifests are located.
3+
# Please see the documentation for all configuration options:
4+
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5+
6+
version: 2
7+
updates:
8+
- package-ecosystem: "github-actions"
9+
directory: "/"
10+
schedule:
11+
interval: "monthly"

.github/workflows/grumphp.yaml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: Grumphp
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
branches:
7+
- main
8+
pull_request:
9+
branches:
10+
- main
11+
12+
jobs:
13+
composer-grumphp:
14+
runs-on: ubuntu-latest
15+
timeout-minutes: 30
16+
strategy:
17+
matrix:
18+
php-version: [ '8.3', '8.4' ]
19+
# symfony-version: [ '7.3.*' ]
20+
21+
steps:
22+
- name: Checkout current repo
23+
uses: actions/checkout@v5
24+
25+
- name: Setup .env file
26+
run: echo "php-container=generics-php$(echo "${{ matrix.php-version }}" | tr -d '.')" >> $GITHUB_ENV
27+
28+
- name: Start containers
29+
run: docker compose -f "compose.yaml" up -d ${{ env.php-container }}
30+
31+
- name: Check PHP Version
32+
run: docker exec ${{ env.php-container }} php -v
33+
34+
- name: Validate composer.json
35+
run: docker exec ${{ env.php-container }} composer validate --strict --no-check-lock
36+
37+
# - name: Symfony version
38+
# run: docker exec ${{ env.php-container }} composer config extra.symfony.require ${{ matrix.symfony-version }}
39+
40+
- name: Composer update
41+
run: docker exec ${{ env.php-container }} composer update --prefer-dist --no-interaction
42+
43+
- name: Run static analysis (GrumPHP)
44+
run: docker exec ${{ env.php-container }} php vendor/bin/grumphp run --testsuite=php${{ matrix.php-version }}
45+
46+
- name: Stop containers
47+
if: always()
48+
run: docker compose -f "docker-compose-base.yaml" down -v

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
composer.lock
2+
.idea
3+
vendor/
4+
.phpunit.cache
5+
tests/TestApplication/var/cache
6+
!/tests/TestApplication/var/cache/.gitkeep
7+
tests/TestApplication/var/sqlite
8+
!/tests/TestApplication/var/sqlite/.gitkeep
9+
.php-cs-fixer.cache

.php-cs-fixer.dist.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use PhpCsFixer\Config;
6+
use PhpCsFixer\Finder;
7+
8+
return (new Config())
9+
->setRiskyAllowed(true)
10+
->setRules([
11+
'array_syntax' => [
12+
'syntax' => 'short',
13+
],
14+
'no_unused_imports' => true,
15+
'no_useless_else' => true,
16+
'no_useless_return' => true,
17+
'strict_comparison' => true,
18+
'strict_param' => true,
19+
])
20+
->setFinder(
21+
Finder::create()
22+
->exclude('tests/TestApplication/var')
23+
->in(__DIR__),
24+
);

README.md

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
[//]: # ( [![CI Status]&#40;https://github.com/pixelfederation/doctrine-generic-types-bundle/workflows/CI/badge.svg&#41;]&#40;https://github.com/pixelfederation/doctrine-generic-types-bundle/actions?query=workflow%3ACI&#41;)
2+
[//]: # ( [![Code Coverage]&#40;https://codecov.io/gh/pixelfederation/doctrine-generic-types-bundle/branch/master/graph/badge.svg?token=77JIFYSUC5&#41;]&#40;https://codecov.io/gh/pixelfederation/doctrine-generic-types-bundle&#41;)
3+
[//]: # ([![Latest Version]&#40;https://img.shields.io/packagist/v/pixelfederation/pixelfederation/doctrine-generic-types-bundle.svg&#41;]&#40;https://packagist.org/packages/pixelfederation/doctrine-generic-types-bundle&#41;)
4+
[//]: # ([![Downloads]&#40;https://img.shields.io/packagist/dm/pixelfederation/doctrine-generic-types-bundley&#41;]&#40;https://packagist.org/packages/pixelfederation/doctrine-generic-types-bundle&#41;)
5+
6+
# PixelFederation DoctrineGenericTypesBundle
7+
8+
## Installation
9+
10+
install via Composer:
11+
12+
```bash
13+
composer require pixelfederation/doctrine-generic-types-bundle
14+
```
15+
16+
register bundle in `config/bundles.php` (if you don't use Symfony Flex)
17+
18+
```php
19+
return [
20+
// ...
21+
PixelFederation\DoctrineGenericTypesBundle\PixelFederationDoctrineGenericTypesBundle::class => ['all' => true],
22+
];
23+
```
24+
25+
bundle configuration:
26+
27+
```yaml
28+
# config/packages/pixel_federation_doctrine_generic_types.yaml
29+
pixel_federation_doctrine_generic_types:
30+
generic_types:
31+
PixelFederation\DoctrineGenericTypesBundle\Value\BooleanValue: PixelFederation\DoctrineGenericTypesBundle\Doctrine\Type\BooleanValueType
32+
PixelFederation\DoctrineGenericTypesBundle\Value\FloatValue: PixelFederation\DoctrineGenericTypesBundle\Doctrine\Type\FloatValueType
33+
PixelFederation\DoctrineGenericTypesBundle\Value\IntegerValue: PixelFederation\DoctrineGenericTypesBundle\Doctrine\Type\IntegerValueType
34+
PixelFederation\DoctrineGenericTypesBundle\Value\StringValue: PixelFederation\DoctrineGenericTypesBundle\Doctrine\Type\StringValueType
35+
# https://github.com/ramsey/uuid integration
36+
PixelFederation\DoctrineGenericTypesBundle\Bridge\RamseyUuid\Value\UuidValue: PixelFederation\DoctrineGenericTypesBundle\Bridge\RamseyUuid\Doctrine\Type\UuidValueType
37+
# Directories where to find your Value Objects
38+
directories:
39+
- ./src/App/Value
40+
- ./src/App/OtherValue
41+
```
42+
43+
## Usage
44+
45+
Crete your Value Object:
46+
47+
```php
48+
<?php
49+
50+
declare(strict_types=1);
51+
52+
namespace App\Value;
53+
54+
use PixelFederation\DoctrineGenericTypesBundle\Value\StringValue;
55+
56+
final class FirstName extends StringValue
57+
{
58+
}
59+
```
60+
61+
Use it in your Doctrine entity:
62+
63+
```php
64+
<?php
65+
66+
declare(strict_types=1);
67+
68+
namespace App\Entity;
69+
70+
use Doctrine\ORM\Mapping as ORM;
71+
use App\Value\FirstName;
72+
73+
#[ORM\Entity]
74+
#[ORM\Table(name: 'person')]
75+
class Person
76+
{
77+
public function __construct(
78+
#[ORM\Column(type: FirstName::class)]
79+
public FirstName $firstName,
80+
) {
81+
}
82+
}
83+
```
84+
85+
doctrine will handle persisting and retrieving your Value Object automatically.
86+
87+
## How to create custom Generic Types
88+
89+
Create abstract Value class extending `PixelFederation\DoctrineGenericTypesBundle\Value\Value` or `PixelFederation\DoctrineGenericTypesBundle\Value\BaseValue`:
90+
91+
```php
92+
<?php
93+
94+
declare(strict_types=1);
95+
96+
namespace App\CustomValue;
97+
98+
use PixelFederation\DoctrineGenericTypesBundle\Value\Value;
99+
100+
abstract class MoneyValue implements Value
101+
{
102+
public function __construct(
103+
public readonly float $value,
104+
public readonly string $currency,
105+
) {
106+
// value object validation logic
107+
}
108+
}
109+
```
110+
111+
Create Doctrine Type class extending `PixelFederation\DoctrineGenericTypesBundle\Doctrine\Type\GenericType`:
112+
113+
```php
114+
<?php
115+
116+
declare(strict_types=1);
117+
118+
namespace App\Doctrine\Type;
119+
120+
use App\CustomValue\MoneyValue;
121+
use Doctrine\DBAL\Platforms\AbstractPlatform;
122+
use Doctrine\DBAL\Types\ConversionException;
123+
use Doctrine\DBAL\Types\JsonType;
124+
use Doctrine\DBAL\Types\Type;
125+
use InvalidArgumentException;
126+
use JsonException;
127+
use Override;
128+
use PixelFederation\DoctrineGenericTypesBundle\Doctrine\Type\GenericType;
129+
130+
final class MoneyValueType extends JsonType implements GenericType
131+
{
132+
/**
133+
* @var class-string<MoneyValue>
134+
*/
135+
protected string $class;
136+
137+
public static function createForValue(string $class): Type
138+
{
139+
if (!is_a($class, MoneyValue::class, true)) {
140+
throw new InvalidArgumentException(sprintf(
141+
'Doctrine Type %s must handle class %s. Got %s',
142+
self::class,
143+
MoneyValue::class,
144+
$class,
145+
));
146+
}
147+
148+
$self = new self();
149+
$self->class = $class;
150+
151+
return $self;
152+
}
153+
154+
#[Override]
155+
public function getName(): string
156+
{
157+
return $this->class;
158+
}
159+
160+
#[Override]
161+
public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): ?string
162+
{
163+
if ($value === null) {
164+
return null;
165+
}
166+
167+
$class = $this->class;
168+
if (!$value instanceof $class) {
169+
throw ConversionException::conversionFailedInvalidType(
170+
$value,
171+
$this->getName(),
172+
['null', $class],
173+
);
174+
}
175+
176+
try {
177+
return json_encode(['value' => $value->value, 'currency' => $value->currency], JSON_THROW_ON_ERROR);
178+
} catch (JsonException $e) {
179+
throw ConversionException::conversionFailedSerialization($value, 'json', $e->getMessage());
180+
}
181+
}
182+
183+
/**
184+
* @return object<MoneyValue>|null
185+
*/
186+
#[Override]
187+
public function convertToPHPValue(mixed $value, AbstractPlatform $platform): mixed
188+
{
189+
if ($value === null) {
190+
return null;
191+
}
192+
193+
if (!is_string($value)) {
194+
throw ConversionException::conversionFailedFormat($value, $this->getName(), 'json');
195+
}
196+
197+
try {
198+
$data = json_decode($value, true, 512, JSON_THROW_ON_ERROR);
199+
assert(is_array($data));
200+
} catch (JsonException $e) {
201+
throw ConversionException::conversionFailedUnserialization($value, $e->getMessage());
202+
}
203+
204+
$dataValue = $data['value'] ?? null;
205+
$dataCurrency = $data['currency'] ?? null;
206+
if (!is_float($dataValue) || !is_string($dataCurrency)) {
207+
throw ConversionException::conversionFailedFormat(
208+
$value,
209+
$this->getName(),
210+
'{"value": float, "currency": string}',
211+
);
212+
}
213+
214+
return new ($this->class)($dataValue, $dataCurrency);
215+
}
216+
217+
#[Override]
218+
public function requiresSQLCommentHint(AbstractPlatform $platform): bool
219+
{
220+
return false;
221+
}
222+
}
223+
```
224+
225+
Register your custom Generic Type in the bundle configuration:
226+
227+
```yaml
228+
# config/packages/pixel_federation_doctrine_generic_types.yaml
229+
pixel_federation_doctrine_generic_types:
230+
generic_types:
231+
# ...
232+
App\CustomValue\MoneyValue: App\Doctrine\Type\MoneyValueType
233+
directories:
234+
# ...
235+
- ./src/App/CustomValue
236+
```
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/bin/bash
2+
3+
export PATH="/usr/local/bin:$PATH"
4+
5+
#
6+
# Run the hook command.
7+
# Note: this will be replaced by the real command during copy.
8+
#
9+
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
10+
11+
if [[ -z `docker compose ps -q generics-php-min` ]] || [[ -z `docker ps -q --no-trunc | grep $(docker compose ps -q generics-php-min)` ]]; then
12+
docker compose up -d
13+
fi

0 commit comments

Comments
 (0)