Skip to content

Commit 723fa2f

Browse files
committed
feat(database): support backed enum values
- Allow database escaping to use BackedEnum backing values - Support BackedEnum values in query bindings and Query Builder binds - Add focused tests and user guide examples Signed-off-by: memleakd <121398829+memleakd@users.noreply.github.com>
1 parent 2ef1571 commit 723fa2f

11 files changed

Lines changed: 120 additions & 4 deletions

File tree

system/Database/BaseConnection.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace CodeIgniter\Database;
1515

16+
use BackedEnum;
1617
use Closure;
1718
use CodeIgniter\Database\Exceptions\DatabaseException;
1819
use CodeIgniter\Database\Exceptions\RetryableTransactionException;
@@ -1758,7 +1759,7 @@ abstract public function affectedRows(): int;
17581759
* Escapes data based on type.
17591760
* Sets boolean and null types
17601761
*
1761-
* @param array|bool|float|int|object|string|null $str
1762+
* @param array|BackedEnum|bool|float|int|object|string|null $str
17621763
*
17631764
* @return ($str is array ? array : float|int|string)
17641765
*/
@@ -1768,6 +1769,10 @@ public function escape($str)
17681769
return array_map($this->escape(...), $str);
17691770
}
17701771

1772+
if ($str instanceof BackedEnum) {
1773+
$str = $str->value;
1774+
}
1775+
17711776
if ($str instanceof Stringable) {
17721777
if ($str instanceof RawSql) {
17731778
return $str->__toString();

system/Database/ConnectionInterface.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
namespace CodeIgniter\Database;
1515

16+
use BackedEnum;
17+
1618
/**
1719
* @template TConnection
1820
* @template TResult
@@ -179,7 +181,7 @@ public function getLastQuery();
179181
* Escapes data based on type.
180182
* Sets boolean and null types.
181183
*
182-
* @param array|bool|float|int|object|string|null $str
184+
* @param array|BackedEnum|bool|float|int|object|string|null $str
183185
*
184186
* @return ($str is array ? array : float|int|string)
185187
*/

system/Database/Postgre/Connection.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace CodeIgniter\Database\Postgre;
1515

16+
use BackedEnum;
1617
use CodeIgniter\Database\BaseConnection;
1718
use CodeIgniter\Database\Exceptions\DatabaseException;
1819
use CodeIgniter\Database\RawSql;
@@ -354,7 +355,7 @@ public function affectedRows(): int
354355
*
355356
* Escapes data based on type
356357
*
357-
* @param array|bool|float|int|object|string|null $str
358+
* @param array|BackedEnum|bool|float|int|object|string|null $str
358359
*
359360
* @return ($str is array ? array : float|int|string)
360361
*/
@@ -364,6 +365,10 @@ public function escape($str)
364365
$this->initialize();
365366
}
366367

368+
if ($str instanceof BackedEnum) {
369+
$str = $str->value;
370+
}
371+
367372
if ($str instanceof Stringable) {
368373
if ($str instanceof RawSql) {
369374
return $str->__toString();

tests/system/Database/BaseQueryTest.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
use CodeIgniter\Test\Mock\MockConnection;
1818
use PHPUnit\Framework\Attributes\DataProvider;
1919
use PHPUnit\Framework\Attributes\Group;
20+
use Tests\Support\Enum\RoleEnum;
21+
use Tests\Support\Enum\StatusEnum;
2022

2123
/**
2224
* @internal
@@ -242,6 +244,17 @@ public function testBindingAutoEscapesParameters(): void
242244
$this->assertSame($expected, $query->getQuery());
243245
}
244246

247+
public function testBindingBackedEnum(): void
248+
{
249+
$query = new Query($this->db);
250+
251+
$query->setQuery('SELECT * FROM users WHERE status = ? AND role = ?', [StatusEnum::ACTIVE, RoleEnum::ADMIN]);
252+
253+
$expected = "SELECT * FROM users WHERE status = 'active' AND role = 2";
254+
255+
$this->assertSame($expected, $query->getQuery());
256+
}
257+
245258
/**
246259
* @see https://github.com/codeigniter4/CodeIgniter4/issues/5114
247260
*/

tests/system/Database/Builder/InsertTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use CodeIgniter\Test\CIUnitTestCase;
2121
use CodeIgniter\Test\Mock\MockConnection;
2222
use PHPUnit\Framework\Attributes\Group;
23+
use Tests\Support\Enum\StatusEnum;
2324

2425
/**
2526
* @internal
@@ -65,6 +66,20 @@ public function testInsertArray(): void
6566
$this->assertSame($expectedBinds, $builder->getBinds());
6667
}
6768

69+
public function testInsertWithBackedEnum(): void
70+
{
71+
$builder = $this->db->table('jobs');
72+
73+
$builder->testMode()->insert([
74+
'id' => 1,
75+
'status' => StatusEnum::ACTIVE,
76+
], true);
77+
78+
$expectedSQL = 'INSERT INTO "jobs" ("id", "status") VALUES (1, \'active\')';
79+
80+
$this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledInsert()));
81+
}
82+
6883
public function testInsertObject(): void
6984
{
7085
$builder = $this->db->table('jobs');

tests/system/Database/Builder/WhereTest.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
use PHPUnit\Framework\Attributes\DataProvider;
2626
use PHPUnit\Framework\Attributes\Group;
2727
use stdClass;
28+
use Tests\Support\Enum\RoleEnum;
29+
use Tests\Support\Enum\StatusEnum;
2830

2931
/**
3032
* @internal
@@ -400,6 +402,28 @@ public function testOrWhereSameColumn(): void
400402
$this->assertSame($expectedBinds, $builder->getBinds());
401403
}
402404

405+
public function testWhereWithBackedEnum(): void
406+
{
407+
$builder = $this->db->table('jobs');
408+
409+
$builder->where('status', StatusEnum::ACTIVE);
410+
411+
$expectedSQL = 'SELECT * FROM "jobs" WHERE "status" = \'active\'';
412+
413+
$this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect()));
414+
}
415+
416+
public function testWhereBetweenWithBackedEnums(): void
417+
{
418+
$builder = $this->db->table('jobs');
419+
420+
$builder->whereBetween('role', [RoleEnum::GUEST, RoleEnum::ADMIN]);
421+
422+
$expectedSQL = 'SELECT * FROM "jobs" WHERE "role" BETWEEN 0 AND 2';
423+
424+
$this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect()));
425+
}
426+
403427
#[DataProvider('provideWhereColumnWithOperators')]
404428
public function testWhereColumnWithOperators(string $first, string $operator): void
405429
{
@@ -840,6 +864,17 @@ public function testWhereIn(): void
840864
$this->assertSame($expectedBinds, $builder->getBinds());
841865
}
842866

867+
public function testWhereInWithBackedEnums(): void
868+
{
869+
$builder = $this->db->table('jobs');
870+
871+
$builder->whereIn('status', [StatusEnum::ACTIVE, StatusEnum::INACTIVE]);
872+
873+
$expectedSQL = 'SELECT * FROM "jobs" WHERE "status" IN (\'active\',\'inactive\')';
874+
875+
$this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect()));
876+
}
877+
843878
public function testWhereInSubQuery(): void
844879
{
845880
$expectedSQL = 'SELECT * FROM "jobs" WHERE "id" IN (SELECT "job_id" FROM "users_jobs" WHERE "user_id" = 3)';

tests/system/Database/Live/EscapeTest.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
use CodeIgniter\Test\CIUnitTestCase;
1919
use CodeIgniter\Test\DatabaseTestTrait;
2020
use PHPUnit\Framework\Attributes\Group;
21+
use Tests\Support\Enum\RoleEnum;
22+
use Tests\Support\Enum\StatusEnum;
2123

2224
/**
2325
* @internal
@@ -63,6 +65,16 @@ public function testEscapeStringable(): void
6365
$this->assertSame($expected, $sql);
6466
}
6567

68+
public function testEscapeStringBackedEnum(): void
69+
{
70+
$this->assertSame("'active'", $this->db->escape(StatusEnum::ACTIVE));
71+
}
72+
73+
public function testEscapeIntBackedEnum(): void
74+
{
75+
$this->assertSame(2, $this->db->escape(RoleEnum::ADMIN));
76+
}
77+
6678
public function testEscapeString(): void
6779
{
6880
$expected = "SELECT * FROM brands WHERE name = 'O" . $this->char . "'Doules'";
@@ -111,7 +123,7 @@ public function testEscapeLikeStringDirect(): void
111123

112124
public function testEscapeStringArray(): void
113125
{
114-
$stringArray = [' A simple string ', new RawSql('CURRENT_TIMESTAMP()'), false, null];
126+
$stringArray = [' A simple string ', new RawSql('CURRENT_TIMESTAMP()'), false, null, StatusEnum::ACTIVE, RoleEnum::ADMIN];
115127

116128
$escapedString = $this->db->escape($stringArray);
117129

@@ -125,5 +137,7 @@ public function testEscapeStringArray(): void
125137
}
126138

127139
$this->assertSame('NULL', $escapedString[3]);
140+
$this->assertSame("'active'", $escapedString[4]);
141+
$this->assertSame(2, $escapedString[5]);
128142
}
129143
}

user_guide_src/source/changelogs/v4.8.0.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ Database
210210

211211
- Added ``afterCommit()`` and ``afterRollback()`` transaction callbacks to database connections. These callbacks run after the outermost transaction commits or rolls back. See :ref:`transactions-transaction-callbacks`.
212212
- Added ``inTransaction()`` to database connections to check whether the connection is inside an active CodeIgniter-managed transaction. See :ref:`transactions-checking-transaction-state`.
213+
- Added support for PHP ``BackedEnum`` values in database escaping, query bindings, and Query Builder bound values.
213214
- Prepared query execution failures now throw or store typed database exceptions such as ``UniqueConstraintViolationException`` and ``RetryableTransactionException`` when applicable, matching normal query failures.
214215
- Added ``RetryableTransactionException`` for driver-specific retryable transaction failures such as deadlocks and serialization failures. See :ref:`transactions-retryable-exceptions`.
215216
- Added the ``transaction()`` method to database connections to run a callback inside a transaction, with optional retry attempts for retryable transaction failures and scoped ``transException`` and ``resetTransStatus`` options. See :ref:`transactions-closure`.

user_guide_src/source/database/queries.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@ don't have to:
138138

139139
.. literalinclude:: queries/009.php
140140

141+
.. versionadded:: 4.8.0
142+
``$db->escape()`` accepts PHP ``BackedEnum`` values and escapes their backing
143+
values.
144+
141145
2. $db->escapeString()
142146
======================
143147

@@ -188,6 +192,13 @@ The secondary benefit of using binds is that the values are
188192
automatically escaped producing safer queries.
189193
You don't have to remember to manually escape data - the engine does it automatically for you.
190194

195+
.. versionadded:: 4.8.0
196+
Query bindings and Query Builder bound values accept PHP ``BackedEnum``
197+
values. CodeIgniter uses the enum backing value when escaping the bound
198+
value.
199+
200+
.. literalinclude:: queries/032.php
201+
191202
Named Bindings
192203
==============
193204

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
$db->table('users')->where('status', \UserStatus::Active)->get();
4+
5+
$db->query(
6+
'SELECT * FROM users WHERE status = ?',
7+
[\UserStatus::Active],
8+
);

0 commit comments

Comments
 (0)