Skip to content

Commit

Permalink
Better enum support when interacting with SQL databases
Browse files Browse the repository at this point in the history
  • Loading branch information
freost committed Apr 19, 2024
1 parent d8fb76b commit 86e820e
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 66 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The major version bump is due to dropped support for PHP `8.1` and a several bre
* Added `Connection::blob()` method that allows you to easily fetch a blob column as a stream.
* Added `Query::blob()` method that allows you to easily fetch a blob column as a stream.
* The `ManyToMany::unlink()` and `ManyToMany::synchronize()` methods now support junction attributes.
* Enums are now also supported when writing "raw" SQL.

#### Improvements

Expand Down
17 changes: 13 additions & 4 deletions src/mako/database/connections/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use PDOStatement;
use SensitiveParameter;
use Throwable;
use UnitEnum;

use function array_shift;
use function array_splice;
Expand Down Expand Up @@ -383,10 +384,18 @@ protected function bindParameter(PDOStatement $statement, int $key, mixed $value
elseif (is_null($value)) {
$type = PDO::PARAM_NULL;
}
elseif (is_object($value) && $value instanceof TypeInterface) {
$value = $value->getValue();

$type = $value->getType();
elseif (is_object($value)) {
if ($value instanceof TypeInterface) {
$value = $value->getValue();
$type = $value->getType();
}
elseif ($value instanceof UnitEnum) {
$value = $value->value ?? $value->name;
$type = is_int($value) ? PDO::PARAM_INT : PDO::PARAM_STR;
}
else {
throw new DatabaseException(vsprintf('Unable to bind object of type [ %s ] to the prepared statement.', [$value::class]));
}
}
else {
$type = PDO::PARAM_STR;
Expand Down
6 changes: 0 additions & 6 deletions src/mako/database/query/compilers/Compiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

use function array_keys;
use function array_shift;
use function enum_exists;
use function explode;
use function implode;
use function is_array;
Expand Down Expand Up @@ -356,11 +355,6 @@ protected function param(mixed $param, bool $enclose = true): string
elseif ($param instanceof DateTimeInterface) {
$this->params[] = $param->format(static::$dateFormat);

return '?';
}
elseif (enum_exists($param::class)) {
$this->params[] = $param->value ?? $param->name;

return '?';
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
use mako\tests\TestCase;

/**
* Builder test case.
* In memory DB test case.
*/
abstract class BuilderTestCase extends TestCase
abstract class InMemoryDbTestCase extends TestCase
{
/**
*
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/ORMTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
/**
* ORM test case.
*/
abstract class ORMTestCase extends BuilderTestCase
abstract class ORMTestCase extends InMemoryDbTestCase
{
/**
*{@inheritDoc}
Expand Down
99 changes: 99 additions & 0 deletions tests/integration/database/EnumTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

/**
* @copyright Frederic G. Østby
* @license http://www.makoframework.com/license
*/

namespace mako\tests\integration\database;

use mako\tests\integration\InMemoryDbTestCase;

// --------------------------------------------------------------------------
// START CLASSES
// --------------------------------------------------------------------------

enum BackedUserEmailEnum: string
{
case FOO = 'foo@example.org';
case BAR = 'bar@example.org';
}

enum BackedUserIdEnum: int
{
case FOO = 1;
case BAR = 2;
}

enum UsernameEnum
{
case foo;
case bar;
}

// --------------------------------------------------------------------------
// END CLASSES
// --------------------------------------------------------------------------

/**
* @group integration
* @group integration:database
* @requires extension PDO
* @requires extension pdo_sqlite
*/
class EnumTest extends InMemoryDbTestCase
{
/**
*
*/
public function testBackedStringEnum(): void
{
$user = $this->connectionManager->getConnection()->first(<<<'SQL'
SELECT * FROM users WHERE email = ?
SQL, [BackedUserEmailEnum::FOO]);

$this->assertSame(BackedUserEmailEnum::FOO->value, $user->email);

$user = $this->connectionManager->getConnection()->first(<<<'SQL'
SELECT * FROM users WHERE email = ?
SQL, [BackedUserEmailEnum::BAR]);

$this->assertSame(BackedUserEmailEnum::BAR->value, $user->email);
}

/**
*
*/
public function testBackedIntEnum(): void
{
$user = $this->connectionManager->getConnection()->first(<<<'SQL'
SELECT * FROM users WHERE id = ?
SQL, [BackedUserIdEnum::FOO]);

$this->assertSame(BackedUserIdEnum::FOO->value, $user->id);

$user = $this->connectionManager->getConnection()->first(<<<'SQL'
SELECT * FROM users WHERE id = ?
SQL, [BackedUserIdEnum::BAR]);

$this->assertSame(BackedUserIdEnum::BAR->value, $user->id);
}

/**
*
*/
public function testEnum(): void
{
$user = $this->connectionManager->getConnection()->first(<<<'SQL'
SELECT * FROM users WHERE username = ?
SQL, [UsernameEnum::foo]);

$this->assertSame(UsernameEnum::foo->name, $user->username);

$user = $this->connectionManager->getConnection()->first(<<<'SQL'
SELECT * FROM users WHERE username = ?
SQL, [UsernameEnum::bar]);

$this->assertSame(UsernameEnum::bar->name, $user->username);
}
}
64 changes: 61 additions & 3 deletions tests/integration/database/query/compilers/BaseCompilerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,39 @@
use mako\database\query\Subquery;
use mako\pagination\PaginationFactoryInterface;
use mako\pagination\PaginationInterface;
use mako\tests\integration\BuilderTestCase;
use mako\tests\integration\InMemoryDbTestCase;
use Mockery;
use PDO;

// --------------------------------------------------------------------------
// START CLASSES
// --------------------------------------------------------------------------

enum BackedUserEmailEnum: string
{
case FOO = 'foo@example.org';
}

enum BackedUserIdEnum: int
{
case FOO = 1;
}

enum UsernameEnum
{
case foo;
}

// --------------------------------------------------------------------------
// END CLASSES
// --------------------------------------------------------------------------

/**
* @group integration
* @group integration:database
* @requires extension PDO
* @requires extension pdo_sqlite
*/
class BaseCompilerTest extends BuilderTestCase
class BaseCompilerTest extends InMemoryDbTestCase
{
public function setUp(): void
{
Expand Down Expand Up @@ -397,4 +419,40 @@ public function testBlob(): void

$this->assertEquals('SELECT "blob" FROM "blobs" LIMIT 1', $this->connectionManager->getConnection()->getLog()[0]['query']);
}

/**
*
*/
public function testBackedStringEnum(): void
{
$query = new Query($this->connectionManager->getConnection());

$user = $query->table('users')->where('email', '=', BackedUserEmailEnum::FOO)->first();

$this->assertSame(BackedUserEmailEnum::FOO->value, $user->email);
}

/**
*
*/
public function testBackedIntEnum(): void
{
$query = new Query($this->connectionManager->getConnection());

$user = $query->table('users')->where('id', '=', BackedUserIdEnum::FOO)->first();

$this->assertSame(BackedUserIdEnum::FOO->value, $user->id);
}

/**
*
*/
public function testEnum(): void
{
$query = new Query($this->connectionManager->getConnection());

$user = $query->table('users')->where('username', '=', UsernameEnum::foo)->first();

$this->assertSame(UsernameEnum::foo->name, $user->username);
}
}
50 changes: 0 additions & 50 deletions tests/unit/database/query/compilers/BaseCompilerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,6 @@
use mako\tests\TestCase;
use Mockery;

// --------------------------------------------------------------------------
// START CLASSES
// --------------------------------------------------------------------------

enum FooEnum
{
case ONE;
case TWO;
}

enum BarEnum: int
{
case ONE = 1;
case TWO = 2;
}

// --------------------------------------------------------------------------
// END CLASSES
// --------------------------------------------------------------------------

/**
* @group unit
*/
Expand Down Expand Up @@ -2059,34 +2039,4 @@ public function testDateTimeParameter(): void
$this->assertEquals('SELECT * FROM "foobar" WHERE "foo" = ?', $query['sql']);
$this->assertEquals(['2021-11-02 13:37:00'], $query['params']);
}

/**
*
*/
public function testEnumParameter(): void
{
$query = $this->getBuilder();

$query->where('foo', '=', FooEnum::ONE);

$query = $query->getCompiler()->select();

$this->assertEquals('SELECT * FROM "foobar" WHERE "foo" = ?', $query['sql']);
$this->assertEquals(['ONE'], $query['params']);
}

/**
*
*/
public function testBackedEnumParameter(): void
{
$query = $this->getBuilder();

$query->where('foo', '=', BarEnum::ONE);

$query = $query->getCompiler()->select();

$this->assertEquals('SELECT * FROM "foobar" WHERE "foo" = ?', $query['sql']);
$this->assertEquals([1], $query['params']);
}
}

0 comments on commit 86e820e

Please sign in to comment.