diff --git a/Makefile b/Makefile
index 8615151..3fc5ecd 100644
--- a/Makefile
+++ b/Makefile
@@ -1,3 +1,12 @@
+composer-install:
+ PHP_VERSION=7.4 docker-compose run --rm composer composer install
+composer-update:
+ PHP_VERSION=7.4 docker-compose run --rm composer composer update
+composer-require:
+ PHP_VERSION=7.4 docker-compose run --rm composer composer require ${PACKAGE}
+composer-require-dev:
+ PHP_VERSION=7.4 docker-compose run --rm composer composer require --dev ${PACKAGE}
+
test: test-phpunit
test-phpunit:
PHP_VERSION=7.4 docker-compose run --rm php php -v
@@ -22,3 +31,6 @@ travis-job:
qa-psalm:
PHP_VERSION=7.4 docker-compose run --rm php php vendor/bin/psalm --no-cache
+
+run-php:
+ PHP_VERSION=7.4 docker-compose run --rm php php ${FILE}
diff --git a/README.md b/README.md
index b0ae814..755e36b 100644
--- a/README.md
+++ b/README.md
@@ -238,6 +238,39 @@ final class AccountStatus
}
```
+## Persistence
+
+Enumerations are frequently used in entities and mapped in ORMs. Register your custom Doctrine enum type by calling dedicated `PlatenumDoctrineType` static method:
+
+```php
+PlatenumDoctrineType::registerString('currency', Currency::class);
+PlatenumDoctrineType::registerInteger('accountStatus', AccountStatus::class);
+```
+
+The alias provided as a first argument can be then used as a Doctrine type, as shown in the listings below (equivalent XML and PHP mapping):
+
+```xml
+
+
+
+
+
+```
+
+```php
+final class Entity
+{
+ public static function loadMetadata(ClassMetadata $m): void
+ {
+ $m->setPrimaryTable(['name' => 'doctrine_entity']);
+
+ $m->mapField(['fieldName' => 'id', 'type' => 'bigint', 'id' => true]);
+ $m->mapField(['fieldName' => 'code', 'type' => 'currency', 'columnName' => 'code']);
+ $m->mapField(['fieldName' => 'status', 'type' => 'accountStatus', 'columnName' => 'status']);
+ }
+}
+```
+
## Reasons
There are already a few `enum` libraries in the PHP ecosystem. Why another one? There are several reasons to do so:
diff --git a/composer.json b/composer.json
index e5e120d..dd4b778 100644
--- a/composer.json
+++ b/composer.json
@@ -17,7 +17,9 @@
"ext-json": "*",
"phpunit/phpunit": ">=6.0",
"hirak/prestissimo": "^0.3.8",
- "vimeo/psalm": "^3.10"
+ "vimeo/psalm": "^3.10",
+ "doctrine/dbal": "^2.9",
+ "doctrine/orm": "^2.7"
},
"autoload": {
"psr-4": {
diff --git a/src/Doctrine/PlatenumDoctrineType.php b/src/Doctrine/PlatenumDoctrineType.php
new file mode 100644
index 0000000..5eb8247
--- /dev/null
+++ b/src/Doctrine/PlatenumDoctrineType.php
@@ -0,0 +1,125 @@
+getIntegerTypeDeclarationSQL([]);
+ };
+
+ static::registerCallback($alias, $class, $toInteger, $sql);
+ }
+
+ /**
+ * @param string $alias
+ * @param class-string $class
+ */
+ public static function registerString(string $alias, string $class): void
+ {
+ /** @psalm-suppress MissingClosureParamType */
+ $toString = function($value): string {
+ return (string)$value;
+ };
+ $sql = function(array $declaration, AbstractPlatform $platform): string {
+ return $platform->getVarcharTypeDeclarationSQL([]);
+ };
+
+ static::registerCallback($alias, $class, $toString, $sql);
+ }
+
+ /**
+ * @param string $alias
+ * @param class-string $class
+ * @param callable $callback
+ * @param callable(array,AbstractPlatform):string $sql
+ */
+ private static function registerCallback(string $alias, string $class, callable $callback, callable $sql): void
+ {
+ if(static::hasType($alias)) {
+ throw new \LogicException(sprintf('Alias `%s` was already registered in PlatenumDoctrineType.', $class));
+ }
+ if(false === in_array(EnumTrait::class, static::allTraitsOf($class))) {
+ throw new \LogicException(sprintf('PlatenumDoctrineType allows only Platenum enumerations, `%s` given.', $class));
+ }
+
+ static::addType($alias, static::class);
+
+ /** @var static $type */
+ $type = static::getType($alias);
+ $type->platenumAlias = $alias;
+ $type->platenumClass = $class;
+ $type->platenumCallback = $callback;
+ $type->platenumSql = $sql;
+ }
+
+ /**
+ * @param class-string $class
+ * @psalm-return list
+ */
+ private static function allTraitsOf(string $class): array
+ {
+ $traits = [];
+
+ do {
+ $traits = array_merge(class_uses($class, true), $traits);
+ } while($class = get_parent_class($class));
+
+ foreach ($traits as $trait => $same) {
+ $traits = array_merge(class_uses($trait, true), $traits);
+ }
+
+ return array_values(array_unique($traits));
+ }
+
+ public function getName(): string
+ {
+ return $this->platenumAlias;
+ }
+
+ public function getSQLDeclaration(array $declaration, AbstractPlatform $platform): string
+ {
+ return ($this->platenumSql)($declaration, $platform);
+ }
+
+ public function convertToDatabaseValue($value, AbstractPlatform $platform)
+ {
+ /** @psalm-suppress MixedMethodCall */
+ return ($this->platenumCallback)($value->getValue());
+ }
+
+ public function convertToPHPValue($value, AbstractPlatform $platform)
+ {
+ /** @psalm-suppress MixedMethodCall */
+ return ($this->platenumClass)::fromValue(($this->platenumCallback)($value));
+ }
+
+ public function requiresSQLCommentHint(AbstractPlatform $platform): bool
+ {
+ return true;
+ }
+}
diff --git a/tests/DoctrineTest.php b/tests/DoctrineTest.php
new file mode 100644
index 0000000..b7493c0
--- /dev/null
+++ b/tests/DoctrineTest.php
@@ -0,0 +1,47 @@
+
+ */
+final class DoctrineTest extends AbstractTestCase
+{
+ public function testCreateFromMember(): void
+ {
+ $connection = DriverManager::getConnection(['driver' => 'pdo_sqlite', 'dbname' => ':memory:']);
+ $connection->exec('CREATE TABLE doctrine_entity (
+ id INTEGER NOT NULL PRIMARY KEY,
+ int_value INTEGER NOT NULL,
+ string_value VARCHAR(20) NOT NULL
+ )');
+ $configuration = new Configuration();
+ $configuration->setMetadataDriverImpl(new StaticPHPDriver([__DIR__.'/Fake']));
+ $configuration->setProxyDir(__DIR__.'/../var/doctrine');
+ $configuration->setProxyNamespace('Platenum\\Doctrine');
+
+ PlatenumDoctrineType::registerInteger('intEnum', DoctrineIntEnum::class);
+ PlatenumDoctrineType::registerString('stringEnum', DoctrineStringEnum::class);
+
+ $entity = new DoctrineEntity(1337, DoctrineIntEnum::FIRST(), DoctrineStringEnum::TWO());
+ $em = EntityManager::create($connection, $configuration);
+ $em->persist($entity);
+ $em->flush();
+ $em->clear();
+
+ $foundEntity = $em->find(DoctrineEntity::class, 1337);
+ $this->assertInstanceOf(DoctrineEntity::class, $foundEntity);
+ $this->assertSame($entity->getId(), $foundEntity->getId());
+ $this->assertSame($entity->getIntValue(), $foundEntity->getIntValue());
+ $this->assertSame($entity->getStringValue(), $foundEntity->getStringValue());
+ }
+}
diff --git a/tests/Fake/DoctrineEntity.php b/tests/Fake/DoctrineEntity.php
new file mode 100644
index 0000000..5cce7ff
--- /dev/null
+++ b/tests/Fake/DoctrineEntity.php
@@ -0,0 +1,44 @@
+id = $id;
+ $this->intValue = $int;
+ $this->stringValue = $string;
+ }
+
+ public static function loadMetadata(ClassMetadata $metadata)
+ {
+ $metadata->setPrimaryTable(['name' => 'doctrine_entity']);
+
+ $metadata->mapField(['id' => true, 'fieldName' => 'id', 'type' => 'integer']);
+ $metadata->mapField(['fieldName' => 'intValue', 'columnName' => 'int_value', 'type' => 'intEnum']);
+ $metadata->mapField(['fieldName' => 'stringValue', 'columnName' => 'string_value', 'type' => 'stringEnum']);
+ }
+
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ public function getIntValue(): DoctrineIntEnum
+ {
+ return $this->intValue;
+ }
+
+ public function getStringValue(): DoctrineStringEnum
+ {
+ return $this->stringValue;
+ }
+}
diff --git a/tests/Fake/DoctrineIntEnum.php b/tests/Fake/DoctrineIntEnum.php
new file mode 100644
index 0000000..940dec1
--- /dev/null
+++ b/tests/Fake/DoctrineIntEnum.php
@@ -0,0 +1,17 @@
+