How to Enable Enums in Symfony 2/Doctrine

Enum enabling issues in Symfony2/Doctrine2

The causes of the error were comments nested in the columns of the MySQL database. I never would have guessed that Doctrine would read these comments and try to interpret them. Deleting the comments fixed the problem. I could not find further documentation on this issue. If anyone has a good source, please comment (or edit) this answer.

How to use actual ENUM type in Symfony Doctrine?

Start by creating an abstract base class which extends Doctrine\DBAL\Types\Type. This allows it to be used as a type in Entity column declarations.

<?php

namespace App\DBAL;

use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Exception;
use InvalidArgumentException;
use ReflectionClass;

abstract class EnumType extends Type
{

private static ?array $constCacheArray = NULL;

public static function getConstants() : array
{
if (self::$constCacheArray == NULL)
self::$constCacheArray = [];

$calledClass = get_called_class();

if (!array_key_exists($calledClass, self::$constCacheArray)) {
$reflect = new ReflectionClass($calledClass);
self::$constCacheArray[$calledClass] = $reflect->getConstants();
}

return self::$constCacheArray[$calledClass];
}

public static function isValidName($name, $strict = false) : bool
{
$constants = self::getConstants();

if ($strict) {
return array_key_exists($name, $constants);
}

$keys = array_map('strtolower', array_keys($constants));
return in_array(strtolower($name), $keys);
}

public static function isValidValue($value, $strict = true) : bool
{
$values = array_values(self::getConstants());
return in_array($value, $values, $strict);
}

protected static string $name;

public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
{
$values = array_map(function ($val) {
return "'" . $val . "'";
}, self::getConstants());

return "ENUM(" . implode(", ", $values) . ")";
}

/**
* @param $value
* @param AbstractPlatform $platform
* @return mixed
*/
public function convertToPHPValue($value, AbstractPlatform $platform)
{
return $value;
}

/**
* @param $value
* @param AbstractPlatform $platform
* @return mixed
*/
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
$this->checkValue($value);

return $value;
}

/**
* @param $value
* @throws InvalidArgumentException
*/
public function checkValue($value): void
{
if (!self::isValidValue($value)) {
throw new InvalidArgumentException("Invalid '" . static::$name . "' value.");
}
}

public function getName(): string
{
return static::$name;
}

public function requiresSQLCommentHint(AbstractPlatform $platform): bool
{
return true;
}

public static function getValuesArray(): array
{
return self::getConstants();
}

/**
* @throws Exception
*/
public static function getChoicesArray(): array
{
throw new Exception("Not implemented");
}
}

Credit for the base of this goes to @Brian Cline

Whats important is that this class provides some helper functions with Reflection, but it also has inherited functions that allow it to be used as actual DB type. I will show you the usage with an example below.

This is how you define a new ENUM type:

<?php

namespace App\DBAL;

class AdminRoleType extends EnumType
{
public const ADMIN = 'ROLE_ADMIN';
public const SUPER_ADMIN = 'ROLE_SUPER_ADMIN';
public const CSR = 'ROLE_CSR';
public const MANAGER = 'ROLE_MANAGER';
public const ACCOUNTING = 'ROLE_ACCOUNTING';

protected static string $name = 'admin_role';
}

Pretty simple, right? This out of the box allows you to some cool things in PHP such as:

$myRole = AdminRoleType::CSR; // 'ROLE_CSR'
$isValidRole = AdminRoleType::isValidValue('ROLE_ADMIN'); // true
$isValidRole = AdminRoleType::isValidName('ADMIN'); // true

But still we did not achieve actual ENUM type in our DB table. To do this, first add the following to your config/packages/doctrine.yaml:

doctrine:
dbal:
mapping_types:
enum: string
types:
admin_role: App\DBAL\AdminRoleType

This maps DB ENUM type to local string type (sorry, native ENUMs are not in this solution, but for PHP 8.1 could(?) be possible.)

The last step is your Entity class:

    /**
* @ORM\Column(name="admin_role", type="admin_role")
*/
private string $admin_role = AdminRoleType::CSR;

public function getAdminRole(): string
{
return $this->admin_role;
}

/**
* @param string $admin_role
* @return $this
* @throws InvalidArgumentException
*/
public function setAdminRole(string $admin_role): self
{
if(!AdminRoleType::isValidValue($admin_role))
throw new InvalidArgumentException('Invalid Admin Role');

$this->admin_role = $admin_role;

return $this;
}

As you can see the code will throw an exception if you try to set some string that is not allowed value for your ENUM.

And when you do migration, the output should look like:

ALTER TABLE admin CHANGE admin_role admin_role ENUM('ROLE_ADMIN', 'ROLE_SUPER_ADMIN', 'ROLE_CSR', 'ROLE_MANAGER', 'ROLE_ACCOUNTING') NOT NULL COMMENT '(DC2Type:admin_role)'

That's it. When you work in PHP, remember to use AdminRoleType:: class instead of magic strings. If you need to add/remove item in enum, just add/remove public const from the enum class.

How do I set enum data type in doctrine 2

Just use:

fields:
status:
type: string
columnDefinition: ENUM('visible', 'invisible')

Doctrine 2 - How to update schema with new enum values?

You can try adding the enum values list in each field comment option, using the postGenerateSchema event:

class EnumListener
{
public function postGenerateSchema(\Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs $eventArgs)
{
$columns = [];

foreach ($eventArgs->getSchema()->getTables() as $table) {
foreach ($table->getColumns() as $column) {
if ($column->getType() instanceof EnumType) {
$columns[] = $column;
}
}
}

/** @var \Doctrine\DBAL\Schema\Column $column */
foreach ($columns as $column) {
$column->setComment(trim(sprintf('%s (%s)', $column->getComment(), implode(',', $column->getType()->getEnum()::toArray()))));
}
}
}

Works for the orm:schema-tool:update command, I suppose it's the same for migrations:diff

(How) Is it possible to use ENUMs in Doctrine and an SQLite database?

columnDefinition is (or at least can be) platform dependent.

As mentioned in the docs:

columnDefinition: DDL SQL snippet that starts after the column name and specifies the complete (non-portable!) column definition.

Note above (emphasis mine) the "non-portable" bit.

Since ENUM is not supported by SQLite, you cannot use it there.

If you need to enforce an enum-like field and do it in a way that's platform independent, you'll need to do it in code, not on your mapping configuration.

E.g. define the restriction on the constructor/setters, etc, checking that the value for $foo is within certain accepted values, with an assertion or anything like that.

If your integration testing infrastructure does not fully match your real application platform, using anything that platform dependent is going to be risky or even impossible.

This is true of mapping, but also valid for any queries that you perform that leverage certain platform features, but that may not exist in SQLite.

(If you wanted to have enum-like functionality on SQLite, you could do it with something like CHECK constraints in your columnDefinition for the field, but then that wouldn't work with MySQL).

Right way to define and work with ENUM types from Symfony2 and Doctrine2

Regarding this doc you need to add these lines to your config:

# app/config/config.yml
doctrine:
dbal:
connections:
default:
// Other connections parameters
mapping_types:
enum: string

For the forms I'd add a helper like getPossibleEnumValues and use this to fill the choices in the builder:

$builder->add('enumField', 'choice', array(
'choices' => $entity->getPossibleEnumValues(),
));


Related Topics



Leave a reply



Submit