Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 37 additions & 3 deletions src/Service/UuidResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

namespace Dmstr\ApiPlatformUtils\Service;

use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Uid\Uuid;

Expand Down Expand Up @@ -59,17 +61,20 @@ public function findByPartialUuid(string $entityClass, string $partialId): ?obje
}

/**
* Find entity with binary UUID storage using native SQL
* Find entity with UUID storage using native SQL.
*
* Uses HEX(id) to convert binary UUID to string for LIKE matching
* The Symfony `uuid` type stores the value differently per platform — a
* BINARY(16) on MySQL/MariaDB, a native `uuid` column on PostgreSQL — so the
* partial-prefix match must be built per platform (see {@see buildPartialUuidSql}).
*/
private function findByPartialBinaryUuid(string $entityClass, string $partialId): ?object
{
$metadata = $this->entityManager->getClassMetadata($entityClass);
$tableName = $metadata->getTableName();
$idColumn = $metadata->getColumnName('id');

$conn = $this->entityManager->getConnection();
$sql = "SELECT BIN_TO_UUID(id) as uuid_str FROM {$tableName} WHERE LOWER(HEX(id)) LIKE :partialId LIMIT 2";
$sql = self::buildPartialUuidSql($conn->getDatabasePlatform(), $tableName, $idColumn);
$stmt = $conn->prepare($sql);
$stmt->bindValue('partialId', strtolower(str_replace('-', '', $partialId)) . '%');
$resultSet = $stmt->executeQuery();
Expand All @@ -89,6 +94,35 @@ private function findByPartialBinaryUuid(string $entityClass, string $partialId)
return $this->entityManager->getRepository($entityClass)->find($fullUuid);
}

/**
* Build the native partial-UUID lookup SQL for the given platform.
*
* Both branches select the canonical hyphenated UUID string as `uuid_str`
* and match the caller's hyphen-stripped, lower-cased hex prefix:
* - PostgreSQL: the column is a native `uuid`; cast to text and strip the
* hyphens (`REPLACE(LOWER(id::text), '-', '')`).
* - MySQL/MariaDB: the column is BINARY(16); `HEX()` yields the 32-char hex
* and `BIN_TO_UUID()` reads it back as a canonical string.
*
* Public + static so it can be unit-tested per platform without a database.
*/
public static function buildPartialUuidSql(AbstractPlatform $platform, string $tableName, string $idColumn): string
{
if ($platform instanceof PostgreSQLPlatform) {
return sprintf(
"SELECT %2\$s::text AS uuid_str FROM %1\$s WHERE REPLACE(LOWER(%2\$s::text), '-', '') LIKE :partialId LIMIT 2",
$tableName,
$idColumn
);
}

return sprintf(
'SELECT BIN_TO_UUID(%2$s) AS uuid_str FROM %1$s WHERE LOWER(HEX(%2$s)) LIKE :partialId LIMIT 2',
$tableName,
$idColumn
);
}

/**
* Find entity with string UUID storage using DQL
*
Expand Down
38 changes: 38 additions & 0 deletions tests/Service/UuidResolverTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php
// file generated with AI assistance: Claude Code - 2026-07-01 15:25:00 UTC

declare(strict_types=1);

namespace Dmstr\ApiPlatformUtils\Tests\Service;

use Dmstr\ApiPlatformUtils\Service\UuidResolver;
use Doctrine\DBAL\Platforms\MySQL80Platform;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use PHPUnit\Framework\TestCase;

/**
* The partial-UUID lookup SQL must match the platform's UUID storage:
* BINARY(16) + HEX()/BIN_TO_UUID() on MySQL, native `uuid` + `::text` on
* PostgreSQL. A MySQL-only query threw `function bin_to_uuid(uuid) does not
* exist` on PostgreSQL; these tests guard against a regression.
*/
final class UuidResolverTest extends TestCase
{
public function testPostgresSqlCastsToTextAndAvoidsMysqlFunctions(): void
{
$sql = UuidResolver::buildPartialUuidSql(new PostgreSQLPlatform(), 'za7_api_configuration', 'id');

self::assertStringContainsString('id::text', $sql);
self::assertStringContainsString("REPLACE(LOWER(id::text), '-', '')", $sql);
self::assertStringNotContainsString('BIN_TO_UUID', $sql);
self::assertStringNotContainsString('HEX(', $sql);
}

public function testMysqlSqlUsesHexFunctions(): void
{
$sql = UuidResolver::buildPartialUuidSql(new MySQL80Platform(), 'za7_api_configuration', 'id');

self::assertStringContainsString('BIN_TO_UUID(id)', $sql);
self::assertStringContainsString('HEX(id)', $sql);
}
}
Loading