Skip to content

Commit dc16780

Browse files
committed
convert database value straight into Enum without using postLoad event (typed properties compatibility)
1 parent 1d3a303 commit dc16780

16 files changed

Lines changed: 802 additions & 158 deletions

build/composer-dependency-analyser.config.php

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,6 @@
1010
$config = $config->enableAnalysisOfUnusedDevDependencies();
1111
$config = $config->addPathToScan(__DIR__, true);
1212

13-
// dependency from YAML configuration only
14-
$config = $config->ignoreErrorsOnPackages([
15-
'doctrine/doctrine-bundle', // e.g. dependency on @annotation_reader, postLoad event etc.
16-
], [ErrorType::PROD_DEPENDENCY_ONLY_IN_DEV]);
17-
18-
// opt-in Symfony functionality
19-
$config = $config->ignoreErrorsOnPackages([
20-
'symfony/yaml',
21-
], [ErrorType::UNUSED_DEPENDENCY]);
22-
2313
// tools
2414
$config = $config->ignoreErrorsOnPackages([
2515
'consistence/coding-standard',

composer.json

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,11 @@
1313
"require": {
1414
"php": "^7.2",
1515
"consistence/consistence": "^1.0|^2.0",
16-
"consistence/consistence-doctrine": "^1.2|^2.0",
17-
"doctrine/doctrine-bundle": "^1.3",
18-
"symfony/config": "^3.0|^4.0",
16+
"consistence/consistence-doctrine": "^3.0",
17+
"doctrine/dbal": "^2.13.1",
18+
"symfony/config": "^3.0|~4.0.0|^4.1.4",
1919
"symfony/dependency-injection": "^3.0|^4.0",
20-
"symfony/http-kernel": "^3.0|^4.0",
21-
"symfony/yaml": "^3.0|^4.0"
20+
"symfony/http-kernel": "^3.0|^4.0"
2221
},
2322
"require-dev": {
2423
"consistence/coding-standard": "3.10.1",

readme.md

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -30,26 +30,13 @@ class Sex extends \Consistence\Enum\Enum
3030
}
3131
```
3232

33-
Now you can use the `Sex` enum in your `User` entity. There are two important things to notice:
34-
35-
1) `type="string_enum"` in `ORM\Column` - this will be used for mapping the value to your database, that means if you have a string based enum (see values in `Sex`), use `string_enum`
36-
37-
You can specify any other parameters for `ORM\Column` as you would usually (nullability, length...).
38-
39-
There is also `integer_enum`, `float_enum` and `boolean_enum` which can be used respectively for their types.
40-
41-
2) `@Enum(class=Sex::class)` - this will be used for reconstructing the `Sex`
42-
enum object when loading the value back from database
43-
44-
The `class` annotation parameter uses the same namespace resolution process as other Doctrine annotations, so it is practically the same as when you specify a `targetEntity` in associations mapping.
33+
To use the `Sex` enum in your `User` entity notice the `type="enum<Consistence\Doctrine\Example\User\Sex>"` in `ORM\Column` - this will be used for mapping the value to and from your database. You can specify any other parameters for `ORM\Column` as you would usually (nullability, length...).
4534

4635
```php
4736
<?php
4837

4938
namespace Consistence\Doctrine\Example\User;
5039

51-
use Consistence\Doctrine\Enum\EnumAnnotation as Enum;
52-
5340
use Doctrine\ORM\Mapping as ORM;
5441

5542
/**
@@ -61,8 +48,7 @@ class User extends \Consistence\ObjectPrototype
6148
// ...
6249

6350
/**
64-
* @Enum(class=Sex::class)
65-
* @ORM\Column(type="string_enum", nullable=true)
51+
* @ORM\Column(type="enum<Consistence\Doctrine\Example\User\Sex>", nullable=true)
6652
* @var \Consistence\Doctrine\Example\User\Sex|null
6753
*/
6854
private $sex;
@@ -85,6 +71,21 @@ class User extends \Consistence\ObjectPrototype
8571
}
8672
```
8773

74+
Before this will work with database, we need to register [Doctrine DBAL type](https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/types.html#custom-mapping-types) for each enum. This can be done using the configuration:
75+
76+
```yaml
77+
# config/packages/consistence_doctrine.yaml
78+
consistence_doctrine:
79+
enum:
80+
dbal_types:
81+
string:
82+
- 'Consistence\Doctrine\Example\User'
83+
```
84+
85+
We used `string` because the `Sex` enum uses strings for its values. There are also `integer`, `float` and `boolean` - depending on what scalar values are used in the enum.
86+
87+
If you use the same enum in multiple places configure it only once.
88+
8889
Now everything is ready to be used, when you call `flush`, only `female` will be saved:
8990

9091
```php
@@ -130,16 +131,17 @@ This means that the objects API is symmetrical (you get the same type as you set
130131
Configuration
131132
-------------
132133

133-
You can override services used internally, for example if you want to use a more effective cache in production (which is recommended), you can provide custom instance with an [alias](http://symfony.com/doc/current/components/dependency_injection/advanced.html#aliasing):
134+
Configuration structure with listed default values:
134135

135136
```yaml
136-
services:
137-
mycache:
138-
class: 'Doctrine\Common\Cache\FilesystemCache'
139-
arguments:
140-
$directory: '%kernel.cache_dir%/mycache'
141-
142-
consistence.doctrine.enum.enum_fields_cache: '@mycache'
137+
# config/packages/consistence_doctrine.yaml
138+
consistence_doctrine:
139+
enum:
140+
dbal_types:
141+
boolean: []
142+
float: []
143+
integer: []
144+
string: []
143145
```
144146
145147
Installation
@@ -161,4 +163,6 @@ return [
161163
];
162164
```
163165

166+
3) Configure needed DBAL types (see `Usage` section above).
167+
164168
That's all, you are good to go!

src/ConsistenceDoctrineBundle.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,42 @@
44

55
namespace Consistence\Doctrine\SymfonyBundle;
66

7+
use Consistence\Doctrine\Enum\Type\BooleanEnumType;
8+
use Consistence\Doctrine\Enum\Type\FloatEnumType;
9+
use Consistence\Doctrine\Enum\Type\IntegerEnumType;
10+
use Consistence\Doctrine\Enum\Type\StringEnumType;
11+
use Doctrine\DBAL\Types\Type as DbalType;
12+
713
class ConsistenceDoctrineBundle extends \Symfony\Component\HttpKernel\Bundle\Bundle
814
{
915

16+
public function boot(): void
17+
{
18+
parent::boot();
19+
20+
$this->registerClassesForEnumDbalType(BooleanEnumType::class, $this->container->getParameter('consistence.doctrine.enum.dbal_types.boolean'));
21+
$this->registerClassesForEnumDbalType(FloatEnumType::class, $this->container->getParameter('consistence.doctrine.enum.dbal_types.float'));
22+
$this->registerClassesForEnumDbalType(IntegerEnumType::class, $this->container->getParameter('consistence.doctrine.enum.dbal_types.integer'));
23+
$this->registerClassesForEnumDbalType(StringEnumType::class, $this->container->getParameter('consistence.doctrine.enum.dbal_types.string'));
24+
}
25+
26+
/**
27+
* @param string $dbalTypeClass
28+
* @param string[] $enumClasses
29+
*/
30+
private function registerClassesForEnumDbalType(
31+
string $dbalTypeClass,
32+
array $enumClasses
33+
): void
34+
{
35+
foreach ($enumClasses as $enumClass) {
36+
$dbalType = $dbalTypeClass::create($enumClass);
37+
if (DbalType::getTypeRegistry()->has($dbalType->getName())) {
38+
DbalType::getTypeRegistry()->override($dbalType->getName(), $dbalType);
39+
} else {
40+
DbalType::getTypeRegistry()->register($dbalType->getName(), $dbalType);
41+
}
42+
}
43+
}
44+
1045
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace Consistence\Doctrine\SymfonyBundle\DependencyInjection;
6+
7+
use Consistence\Enum\Enum;
8+
use Consistence\Type\ArrayType\ArrayType;
9+
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
10+
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
11+
12+
class Configuration
13+
extends \Consistence\ObjectPrototype
14+
implements \Symfony\Component\Config\Definition\ConfigurationInterface
15+
{
16+
17+
/** @var string */
18+
private $rootNode;
19+
20+
public function __construct(
21+
string $rootNode
22+
)
23+
{
24+
$this->rootNode = $rootNode;
25+
}
26+
27+
public function getConfigTreeBuilder(): TreeBuilder
28+
{
29+
$treeBuilder = new TreeBuilder();
30+
$rootNode = $treeBuilder->root($this->rootNode);
31+
32+
$rootNode->children()->append($this->createEnumNode('enum'));
33+
34+
return $treeBuilder;
35+
}
36+
37+
private function createEnumNode(string $nodeName): ArrayNodeDefinition
38+
{
39+
$node = new ArrayNodeDefinition($nodeName);
40+
$node->children()->append($this->createEnumDbalTypesNode('dbal_types'));
41+
$node->addDefaultsIfNotSet();
42+
43+
return $node;
44+
}
45+
46+
private function createEnumDbalTypesNode(string $nodeName): ArrayNodeDefinition
47+
{
48+
$node = new ArrayNodeDefinition($nodeName);
49+
$node->children()->append($this->createEnumClassesNode('boolean'));
50+
$node->children()->append($this->createEnumClassesNode('float'));
51+
$node->children()->append($this->createEnumClassesNode('integer'));
52+
$node->children()->append($this->createEnumClassesNode('string'));
53+
$node->addDefaultsIfNotSet();
54+
55+
return $node;
56+
}
57+
58+
private function createEnumClassesNode(string $nodeName): ArrayNodeDefinition
59+
{
60+
$node = new ArrayNodeDefinition($nodeName);
61+
$node->prototype('scalar');
62+
$node
63+
->validate()
64+
->ifTrue(function (array $enumClasses): bool {
65+
return count($enumClasses) !== count(ArrayType::uniqueValues($enumClasses))
66+
|| ArrayType::containsValueByValueCallback($enumClasses, function ($enumClass): bool {
67+
return !is_string($enumClass)
68+
|| !$this->isEnumClass($enumClass);
69+
});
70+
})
71+
->then(function (array $enumClasses): void {
72+
$frequency = array_count_values($enumClasses);
73+
$duplicateClasses = array_keys(ArrayType::filterValuesByCallback($frequency, static function (int $count): bool {
74+
return $count > 1;
75+
}));
76+
if (count($duplicateClasses) > 0) {
77+
throw new \Consistence\Doctrine\SymfonyBundle\DependencyInjection\EnumDbalTypesClassListCannotContainDuplicateClassesException($duplicateClasses);
78+
}
79+
80+
$notEnumClasses = ArrayType::filterValuesByCallback($enumClasses, function ($enumClass): bool {
81+
return !is_string($enumClass)
82+
|| !$this->isEnumClass($enumClass);
83+
});
84+
if (count($notEnumClasses) > 0) {
85+
throw new \Consistence\Doctrine\SymfonyBundle\DependencyInjection\EnumDbalTypesClassListCannotContainNonEnumClassesException($notEnumClasses);
86+
}
87+
88+
// @codeCoverageIgnoreStart
89+
// should be unreachable code
90+
throw new \Exception('Unexpected case');
91+
// @codeCoverageIgnoreEnd
92+
});
93+
94+
return $node;
95+
}
96+
97+
private function isEnumClass(string $class): bool
98+
{
99+
return is_a($class, Enum::class, true);
100+
}
101+
102+
}

src/DependencyInjection/ConsistenceDoctrineExtension.php

Lines changed: 41 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,54 +4,59 @@
44

55
namespace Consistence\Doctrine\SymfonyBundle\DependencyInjection;
66

7-
use Consistence\Doctrine\Enum\Type\BooleanEnumType;
8-
use Consistence\Doctrine\Enum\Type\FloatEnumType;
9-
use Consistence\Doctrine\Enum\Type\IntegerEnumType;
10-
use Consistence\Doctrine\Enum\Type\StringEnumType;
11-
use Symfony\Component\Config\FileLocator;
127
use Symfony\Component\DependencyInjection\ContainerBuilder;
13-
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
148

15-
class ConsistenceDoctrineExtension
16-
extends \Symfony\Component\HttpKernel\DependencyInjection\Extension
17-
implements \Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface
9+
class ConsistenceDoctrineExtension extends \Symfony\Component\HttpKernel\DependencyInjection\ConfigurableExtension
1810
{
1911

20-
public const ALIAS = 'consistence_doctrine';
21-
22-
public const DOCTRINE_BUNDLE_ALIAS = 'doctrine';
23-
24-
public function prepend(ContainerBuilder $container): void
25-
{
26-
if (!$container->hasExtension(self::DOCTRINE_BUNDLE_ALIAS)) {
27-
throw new \Consistence\Doctrine\SymfonyBundle\DependencyInjection\DoctrineBundleRequiredException();
28-
}
29-
30-
$container->loadFromExtension(self::DOCTRINE_BUNDLE_ALIAS, [
31-
'dbal' => [
32-
'types' => [
33-
BooleanEnumType::NAME => BooleanEnumType::class,
34-
FloatEnumType::NAME => FloatEnumType::class,
35-
IntegerEnumType::NAME => IntegerEnumType::class,
36-
StringEnumType::NAME => StringEnumType::class,
37-
],
38-
],
39-
]);
40-
}
12+
use \Consistence\Type\ObjectMixinTrait;
4113

4214
/**
43-
* @param mixed[][] $configs
15+
* @param mixed[] $mergedConfig
4416
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
4517
*/
46-
public function load(array $configs, ContainerBuilder $container): void
18+
public function loadInternal(array $mergedConfig, ContainerBuilder $container): void
4719
{
48-
$loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/config'));
49-
$loader->load('services.yaml');
20+
$container->setParameter(
21+
'consistence.doctrine.enum.dbal_types.boolean',
22+
$mergedConfig
23+
['enum']
24+
['dbal_types']
25+
['boolean']
26+
);
27+
$container->setParameter(
28+
'consistence.doctrine.enum.dbal_types.float',
29+
$mergedConfig
30+
['enum']
31+
['dbal_types']
32+
['float']
33+
);
34+
$container->setParameter(
35+
'consistence.doctrine.enum.dbal_types.integer',
36+
$mergedConfig
37+
['enum']
38+
['dbal_types']
39+
['integer']
40+
);
41+
$container->setParameter(
42+
'consistence.doctrine.enum.dbal_types.string',
43+
$mergedConfig
44+
['enum']
45+
['dbal_types']
46+
['string']
47+
);
5048
}
5149

52-
public function getAlias(): string
50+
/**
51+
* @param mixed[] $config
52+
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
53+
* @return \Consistence\Doctrine\SymfonyBundle\DependencyInjection\Configuration
54+
*/
55+
public function getConfiguration(array $config, ContainerBuilder $container): Configuration
5356
{
54-
return self::ALIAS;
57+
return new Configuration(
58+
$this->getAlias()
59+
);
5560
}
5661

5762
}

src/DependencyInjection/config/services.yaml

Lines changed: 0 additions & 15 deletions
This file was deleted.

0 commit comments

Comments
 (0)