Skip to content

Commit

Permalink
Initial testing
Browse files Browse the repository at this point in the history
  • Loading branch information
bigfoot90 committed Mar 24, 2024
1 parent ccdb800 commit ee3c5b7
Show file tree
Hide file tree
Showing 6 changed files with 227 additions and 39 deletions.
3 changes: 3 additions & 0 deletions src/Schema/Column.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class Column extends AbstractAsset

protected ?int $_length = null;

/** @var string[] */
protected array $_members = [];

protected ?int $_precision = null;
Expand Down Expand Up @@ -221,11 +222,13 @@ public function setAutoincrement(bool $flag): self
return $this;
}

/** @return string[] */
public function getMembers(): array
{
return $this->_members;
}

/** @param string[] $members */
public function setMembers(array $members): void
{
$this->_members = $members;
Expand Down
1 change: 1 addition & 0 deletions src/Schema/MySQLSchemaManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use function implode;
use function is_string;
use function preg_match;
use function preg_match_all;
use function str_contains;
use function strtok;
use function strtolower;
Expand Down
98 changes: 59 additions & 39 deletions src/Types/EnumType.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,106 +4,126 @@

namespace Doctrine\DBAL\Types;

use BackedEnum;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
use Doctrine\DBAL\Platforms\SqlitePlatform;
use Doctrine\DBAL\Platforms\SQLServerPlatform;
use Doctrine\DBAL\Types\Exception\InvalidType;
use Doctrine\DBAL\Types\Exception\ValueNotConvertible;
use ReflectionClass;
use Throwable;
use UnitEnum;
use ValueError;

use function array_map;
use function class_exists;
use function enum_exists;
use function implode;
use function is_string;
use function sprintf;

final class EnumType extends Type
{
public string $name = 'enum';

public ?string $enumClassname = null;

/** @var array<int, string> */
public array $members = [];

/**
* Gets an array of database types that map to this Doctrine type.
*
* @return string[]
* {@inheritDoc}
*/
public function getMappedDatabaseTypes(AbstractPlatform $platform): array
{
return [$this->name];
}

/**
* Gets the SQL declaration snippet for a field of this type.
*
* @param mixed[] $column The field declaration
* @param AbstractPlatform $platform The currently used database platform
* {@inheritDoc}
*/
public function getSqlDeclaration(array $column, AbstractPlatform $platform): string
{
assert($column['type'] instanceof self::class);

$values = implode(
', ',
array_map(
static fn (string $value) => "'{$value}'",
$column['members'] ?: $column['type']->members
)
static fn (string $value) => sprintf('\'%s\'', $value),
$column['members'] ?: $column['type']->members,
),
);

$sqlDeclaration = match (true) {
return match (true) {
$platform instanceof SqlitePlatform => sprintf('TEXT CHECK(%s IN (%s))', $column['name'], $values),
$platform instanceof PostgreSqlPlatform, $platform instanceof SQLServerPlatform => sprintf('VARCHAR(255) CHECK(%s IN (%s))', $column['name'], $values),
default => sprintf('ENUM(%s)', $values),
};

return $sqlDeclaration;
}

/**
* {@inheritdoc}
*
* @return mixed the database representation of the value
*
* @throws \InvalidArgumentException
* {@inheritDoc}
*/
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string
{
if (null === $value) {
if ($value === null) {
return null;
}

if ($value instanceof UnitEnum) {
if ($value instanceof BackedEnum) {
return $value->value;
}

return $value->name;
}

return (string) $value;
}

/**
* {@inheritdoc}
*
* @return mixed the PHP representation of the value
*/
public function convertToPHPValue(mixed $value, AbstractPlatform $platform): mixed
{
if (null === $value) {
if ($value === null) {
return null;
}

if (!\is_string($value)) {
if (! is_string($value)) {
throw InvalidType::new($value, $this->name, ['null', 'string']);

Check warning on line 90 in src/Types/EnumType.php

View check run for this annotation

Codecov / codecov/patch

src/Types/EnumType.php#L90

Added line #L90 was not covered by tests
}

$refl = new \ReflectionClass($this->enumClassname);

try {
return $refl->newInstance($value);
} catch (\Throwable $e) {
throw ValueNotConvertible::new($value, $this->name, $e->getMessage(), $e);
if ($this->enumClassname) {
if (enum_exists($this->enumClassname)) {
try {
foreach ($this->enumClassname::cases() as $case) {
if (($case instanceof BackedEnum && $value === $case->value) or $value === $case->name) {
return $case;
}
}

throw new ValueError(sprintf("'%s' is not a valid backing value for enum %s", $value, $this->enumClassname));
} catch (Throwable $e) {
throw ValueNotConvertible::new($value, $this->name, $e->getMessage(), $e);

Check warning on line 104 in src/Types/EnumType.php

View check run for this annotation

Codecov / codecov/patch

src/Types/EnumType.php#L102-L104

Added lines #L102 - L104 were not covered by tests
}
}

if (class_exists($this->enumClassname)) {
$refl = new ReflectionClass($this->enumClassname);

try {
return $refl->newInstance($value);
} catch (Throwable $e) {
throw ValueNotConvertible::new($value, $this->name, $e->getMessage(), $e);

Check warning on line 114 in src/Types/EnumType.php

View check run for this annotation

Codecov / codecov/patch

src/Types/EnumType.php#L113-L114

Added lines #L113 - L114 were not covered by tests
}
}
}

return $value;
}

/**
* {@inheritdoc}
*/
public static function addType(string $name, string $enumClassname): void

Check warning on line 122 in src/Types/EnumType.php

View check run for this annotation

Codecov / codecov/patch

src/Types/EnumType.php#L122

Added line #L122 was not covered by tests
{
self::getTypeRegistry()->register($name, $me = new self());
$me->name = $name;
$me->name = $name;
$me->enumClassname = $enumClassname;
$me->members = $enumClassname::getAllowedValues();
$me->members = $enumClassname::getAllowedValues();

Check warning on line 127 in src/Types/EnumType.php

View check run for this annotation

Codecov / codecov/patch

src/Types/EnumType.php#L124-L127

Added lines #L124 - L127 were not covered by tests
}
}
32 changes: 32 additions & 0 deletions tests/Functional/Types/EnumTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Tests\Functional\Types;

use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Tests\FunctionalTestCase;
use Doctrine\DBAL\Types\Types;

class EnumTest extends FunctionalTestCase
{
protected function setUp(): void
{
$table = new Table('enum_table');
$table->addColumn('val', Types::ENUM, ['members' => ['a', 'b']]);

$this->dropAndCreateTable($table);
}

public function testInsertAndSelect(): void
{
$val = 'b';

$result = $this->connection->insert('enum_table', ['val' => $val]);
self::assertSame(1, $result);

$value = $this->connection->fetchOne('SELECT val FROM enum_table');

self::assertEquals($val, $value);
}
}
14 changes: 14 additions & 0 deletions tests/Schema/ColumnTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public function testToArray(): void
'default' => 'baz',
'notnull' => false,
'length' => 200,
'members' => [],
'precision' => 5,
'scale' => 2,
'fixed' => true,
Expand Down Expand Up @@ -148,4 +149,17 @@ public function testColumnComment(): void
self::assertArrayHasKey('comment', $columnArray);
self::assertEquals('foo', $columnArray['comment']);
}

public function testEnumMembers(): void
{
$column = new Column('bar', Type::getType(Types::ENUM));
self::assertSame([], $column->getMembers());

$column->setMembers(['a', 'b']);
self::assertEquals(['a', 'b'], $column->getMembers());

$columnArray = $column->toArray();
self::assertArrayHasKey('members', $columnArray);
self::assertEquals(['a', 'b'], $columnArray['members']);
}
}
118 changes: 118 additions & 0 deletions tests/Types/EnumTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Tests\Types;

use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\EnumType;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

use function anonymous_class;

class EnumTest extends TestCase
{
private AbstractPlatform&MockObject $platform;
private EnumType $type;

protected function setUp(): void
{
$this->platform = $this->createMock(AbstractPlatform::class);
$this->type = new EnumType();
}

public function testReturnsSQLDeclaration(): void
{
self::assertSame('ENUM(\'a\', \'b\')', $this->type->getSQLDeclaration(['members' => ['a', 'b']], $this->platform));
}

public function testConvertToPHPValue(): void
{
self::assertIsString($this->type->convertToPHPValue('foo', $this->platform));
self::assertIsString($this->type->convertToPHPValue('', $this->platform));
self::assertNull($this->type->convertToPHPValue(null, $this->platform));
}

public function testConvertToPHPEnum(): void
{
$this->type->enumClassname = EnumPhp::class;

self::assertInstanceOf($this->type->enumClassname, $this->type->convertToPHPValue('A', $this->platform));
self::assertSame(EnumPhp::A, $this->type->convertToPHPValue('A', $this->platform));
self::assertNull($this->type->convertToPHPValue(null, $this->platform));
}

public function testConvertToPHPEnumBacked(): void
{
$this->type->enumClassname = EnumPhpBacked::class;

self::assertInstanceOf($this->type->enumClassname, $this->type->convertToPHPValue('a', $this->platform));
self::assertSame(EnumPhpBacked::A, $this->type->convertToPHPValue('a', $this->platform));
self::assertNull($this->type->convertToPHPValue(null, $this->platform));
}

public function testConvertToPHPObject(): void
{
$this->type->enumClassname = EnumClass::class;

self::assertInstanceOf($this->type->enumClassname, $this->type->convertToPHPValue('a', $this->platform));
self::assertEquals(new EnumClass('a'), $this->type->convertToPHPValue('a', $this->platform));
self::assertNull($this->type->convertToPHPValue(null, $this->platform));
}

public function testConvertStringToDatabaseValue(): void
{
self::assertSame('a', $this->type->convertToDatabaseValue('a', $this->platform));
self::assertNull($this->type->convertToDatabaseValue(null, $this->platform));
}

public function testConvertEnumToDatabaseValue(): void
{
$this->type->enumClassname = EnumPhp::class;

self::assertSame('A', $this->type->convertToDatabaseValue(EnumPhp::A, $this->platform));
self::assertNull($this->type->convertToDatabaseValue(null, $this->platform));
}

public function testConvertEnumBackedToDatabaseValue(): void
{
$this->type->enumClassname = EnumPhpBacked::class;

self::assertSame('a', $this->type->convertToDatabaseValue(EnumPhpBacked::A, $this->platform));
self::assertNull($this->type->convertToDatabaseValue(null, $this->platform));
}

public function testConvertObjectToDatabaseValue(): void
{
$this->type->enumClassname = EnumClass::class;

self::assertSame('a', $this->type->convertToDatabaseValue(new EnumClass('a'), $this->platform));
self::assertNull($this->type->convertToDatabaseValue(null, $this->platform));
}
}

enum EnumPhp
{
case A;
case B;
}


enum EnumPhpBacked: string
{
case A = 'a';
case B = 'b';
}

final class EnumClass implements \Stringable {
function __construct(
private ?string $value
) {
}

function __tostring(): string
{
return $this->value;
}
};

0 comments on commit ee3c5b7

Please sign in to comment.