UUID Support
MicroOrm provides comprehensive support for working with UUID fields across different database systems. This guide explains how to set up and use UUIDs as primary keys or regular fields in your models.
Overview
UUIDs (Universally Unique Identifiers) are 128-bit values typically represented as 32 hexadecimal characters. When used as primary keys, they offer advantages like:
- Globally unique identifiers without coordination between database servers
- No need for a centralized sequence or auto-increment field
- Improved security by avoiding sequential predictable IDs
- Distributed system-friendly identification
MicroOrm handles the complexities of working with UUIDs, including:
- Converting between string UUID representations and binary database storage
- Generating new UUIDs for primary keys automatically
- Database-specific UUID handling (MySQL, PostgreSQL, SQLite)
- Automatic formatting when reading from and writing to the database
- Support for UUID fields beyond just primary keys
Setting Up UUID Primary Keys
1. Database Schema
First, set up your database table with a binary field to store the UUID:
-- MySQL example
CREATE TABLE my_table (
id BINARY(16) PRIMARY KEY, -- Binary format is more efficient than VARCHAR
name VARCHAR(100),
-- other fields
);
-- SQLite example
CREATE TABLE my_table (
id BINARY(16) PRIMARY KEY,
name VARCHAR(100),
-- other fields
);
-- PostgreSQL example (can use native UUID type)
CREATE TABLE my_table (
id UUID PRIMARY KEY,
name VARCHAR(100),
-- other fields
);
2. Model Definition
Then define your model using the appropriate UUID attributes:
<?php
namespace MyApp\Model;
use ByJG\MicroOrm\Attributes\FieldUuidAttribute;
use ByJG\MicroOrm\Attributes\FieldAttribute;
// Use the database-specific UUID table attribute
use ByJG\MicroOrm\Attributes\TableMySqlUuidPKAttribute; // For MySQL
// OR
use ByJG\MicroOrm\Attributes\TableSqliteUuidPKAttribute; // For SQLite
// OR
use ByJG\MicroOrm\Attributes\TableUuidPKAttribute; // Generic implementation
#[TableMySqlUuidPKAttribute(tableName: 'my_table')]
class MyModel
{
#[FieldUuidAttribute(primaryKey: true)]
protected ?string $id = null;
#[FieldAttribute()]
protected ?string $name = null;
// Getters and setters
public function getId(): ?string
{
return $this->id;
}
public function setId(?string $id): void
{
$this->id = $id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(?string $name): void
{
$this->name = $name;
}
}
Available UUID Attributes
Table Attributes
MicroOrm provides several table attributes for UUID primary keys:
TableUuidPKAttribute- Base class for UUID tables (uses generic implementation)TableMySqlUuidPKAttribute- MySQL-specific implementation (uses MySQL's UUID functions)TableSqliteUuidPKAttribute- SQLite-specific implementation
Each attribute automatically configures a primary key seed function that generates appropriate UUID values for the specific database.
Field Attribute
The FieldUuidAttribute is used to mark UUID fields (both primary keys and regular fields). It internally configures:
FormatUpdateUuidMapper- Handles converting string UUIDs toHexUuidLiteral(binary format) when saving to databaseFormatSelectUuidMapper- Handles converting binary UUIDs from database to formatted string (with dashes) when reading
These mapper functions ensure that:
- Your application code works with human-readable UUID strings (e.g.,
'550e8400-e29b-41d4-a716-446655440000') - The database stores UUIDs efficiently as 16-byte binary values
- Conversions happen automatically during save and retrieve operations
Using UUID Fields Beyond Primary Keys
You can use FieldUuidAttribute on any UUID field, not just primary keys:
use ByJG\MicroOrm\Attributes\FieldAttribute;
use ByJG\MicroOrm\Attributes\FieldUuidAttribute;
use ByJG\MicroOrm\Attributes\TableAttribute;
#[TableAttribute(tableName: 'users')]
class User
{
#[FieldAttribute(primaryKey: true)]
protected int $id;
#[FieldUuidAttribute(fieldName: 'external_id')]
protected ?string $externalId = null;
#[FieldUuidAttribute(fieldName: 'tenant_id')]
protected ?string $tenantId = null;
#[FieldAttribute]
protected ?string $name = null;
// Getters and setters work with string UUIDs
public function getExternalId(): ?string
{
return $this->externalId;
}
public function setExternalId(?string $externalId): void
{
$this->externalId = $externalId;
}
}
// Usage
$user = new User();
$user->setExternalId('550e8400-e29b-41d4-a716-446655440000'); // String format
$repository->save($user); // Automatically converted to binary for storage
// When retrieved, automatically converted back to string
$retrieved = $repository->get($user->getId());
echo $retrieved->getExternalId(); // '550E8400-E29B-41D4-A716-446655440000'
Querying by UUID
Primary Key Queries
When querying by UUID primary keys using get() or delete(), the conversion is automatic:
use ByJG\MicroOrm\Repository;
$repository = new Repository($dbDriver, MyModel::class);
$uuid = '550e8400-e29b-41d4-a716-446655440000';
// For UUID primary keys, just pass the string directly - NO need to wrap in HexUuidLiteral
$entity = $repository->get($uuid); // ✓ Automatically converted via updateFunction
// Delete also works automatically
$repository->delete($uuid); // ✓ Automatically converted via updateFunction
The repository automatically applies the updateFunction (in this case FormatUpdateUuidMapper) to convert the string
UUID to a HexUuidLiteral before querying.
Manual Queries with WHERE Clauses
When using manual queries with where() clauses, you must explicitly use UUID literals:
use ByJG\MicroOrm\Literal\HexUuidLiteral;
$uuid = '550e8400-e29b-41d4-a716-446655440000';
// ✓ CORRECT - Wrap in HexUuidLiteral for WHERE clauses
$query = $repository->getMapper()->getQuery();
$query->where('id = :id', ['id' => new HexUuidLiteral($uuid)]);
$results = $repository->getByQuery($query);
// ✓ Also correct for non-primary key UUID fields
$query->where('user_id = :user_id', ['user_id' => new HexUuidLiteral($userId)]);
// ✗ INCORRECT - This won't work correctly
$query->where('id = :id', ['id' => $uuid]); // String won't match binary in DB
Why the Difference?
get()/delete(): These methods know they're working with primary keys, so they automatically apply theupdateFunctionfrom the field mappingwhere()clauses: These are generic and don't know which fields are UUIDs, so you must explicitly wrap UUID values in the appropriate literal class
Database-Specific Literals
You can use database-specific literal classes if needed:
use ByJG\MicroOrm\Literal\MySqlUuidLiteral;
use ByJG\MicroOrm\Literal\SqliteUuidLiteral;
use ByJG\MicroOrm\Literal\PostgresUuidLiteral;
// MySQL - uses UUID_TO_BIN()
$query->where('id = :id', ['id' => new MySqlUuidLiteral($uuid)]);
// PostgreSQL - uses ::uuid cast
$query->where('id = :id', ['id' => new PostgresUuidLiteral($uuid)]);
// SQLite/Generic - uses hex format
$query->where('id = :id', ['id' => new HexUuidLiteral($uuid)]);
Working with UUID Literals
UUID Literal classes help you work with UUIDs in raw SQL queries. Each database has its own literal format:
HexUuidLiteral- Generic hexadecimal format:X'550E8400E29B41D4A716446655440000'MySqlUuidLiteral- MySQL-specific:UUID_TO_BIN('550e8400-e29b-41d4-a716-446655440000')PostgresUuidLiteral- PostgreSQL:'550e8400-e29b-41d4-a716-446655440000'::uuidSqliteUuidLiteral- SQLite hexadecimal format
Example Usage
use ByJG\MicroOrm\Literal\HexUuidLiteral;
// Create a literal from a UUID string
$uuid = '550e8400-e29b-41d4-a716-446655440000';
$literal = new HexUuidLiteral($uuid);
// Use in queries
$query->where('id = :id', ['id' => $literal]);
// The literal will be converted to the appropriate format for your database
// e.g., X'550E8400E29B41D4A716446655440000' for direct SQL
Format Helpers
The HexUuidLiteral class provides helper methods for formatting and validating UUIDs:
use ByJG\MicroOrm\Literal\HexUuidLiteral;
// Format a binary UUID (16 bytes) to a formatted string
$binaryUuid = hex2bin('550E8400E29B41D4A716446655440000');
$formatted = HexUuidLiteral::getFormattedUuid($binaryUuid);
// Returns: '550E8400-E29B-41D4-A716-446655440000'
// Format a hex string (32 chars) to formatted UUID
$hexString = '550E8400E29B41D4A716446655440000';
$formatted = HexUuidLiteral::getFormattedUuid($hexString);
// Returns: '550E8400-E29B-41D4-A716-446655440000'
// Already formatted UUIDs are returned as-is
$alreadyFormatted = '550E8400-E29B-41D4-A716-446655440000';
$result = HexUuidLiteral::getFormattedUuid($alreadyFormatted);
// Returns: '550E8400-E29B-41D4-A716-446655440000'
// Handle invalid UUIDs gracefully
$invalid = 'not-a-uuid';
$result = HexUuidLiteral::getFormattedUuid($invalid, throwErrorIfInvalid: false);
// Returns: null
// Or with a default value
$result = HexUuidLiteral::getFormattedUuid($invalid, throwErrorIfInvalid: false, default: 'N/A');
// Returns: 'N/A'
// Extract UUID from a literal object
$literal = new HexUuidLiteral('550E8400-E29B-41D4-A716-446655440000');
$uuid = HexUuidLiteral::getUuidFromLiteral($literal);
// Returns: '550E8400-E29B-41D4-A716-446655440000'
Supported Input Formats
HexUuidLiteral::getFormattedUuid() can handle various UUID formats:
- Binary (16 bytes):
hex2bin('550E8400E29B41D4A716446655440000') - Hex string (32 chars):
'550E8400E29B41D4A716446655440000' - Formatted (36 chars with dashes):
'550E8400-E29B-41D4-A716-446655440000' - Literal format:
X'550E8400E29B41D4A716446655440000' - Prefixed hex:
0x550E8400E29B41D4A716446655440000
All formats are automatically detected and converted to the standard formatted string with dashes.
Database-Specific Notes
MySQL
MySQL doesn't have a native UUID type, so UUIDs are stored as BINARY(16). The TableMySqlUuidPKAttribute uses MySQL's
UUID_TO_BIN() and BIN_TO_UUID() functions.
SQLite
SQLite also doesn't have a native UUID type. The TableSqliteUuidPKAttribute uses SQLite's randomblob() function to
generate UUIDs.
PostgreSQL
PostgreSQL has a native UUID type. The library provides support through the PostgresUuidLiteral class.
Performance Considerations
Storing UUIDs as BINARY(16) is more efficient than VARCHAR(36) both in terms of storage and indexing performance:
- Storage: BINARY(16) uses 16 bytes vs VARCHAR(36) which uses 36 bytes
- Indexing: Binary indexes are smaller and faster to traverse
- Comparison: Binary comparisons are faster than string comparisons
MicroOrm handles all conversions between string representations and binary storage automatically through the mapper functions.
Advanced: Custom UUID Mapper Functions
If you need custom UUID handling, you can create your own mapper functions:
use ByJG\MicroOrm\Interface\MapperFunctionInterface;
use ByJG\AnyDataset\Db\DatabaseExecutor;
use ByJG\MicroOrm\Literal\HexUuidLiteral;
class CustomUuidUpdateMapper implements MapperFunctionInterface
{
public function processedValue(mixed $value, mixed $instance, ?DatabaseExecutor $executor = null): mixed
{
if (empty($value)) {
return null;
}
// Your custom logic here
// Convert string UUID to your preferred format
return new HexUuidLiteral($value);
}
}
class CustomUuidSelectMapper implements MapperFunctionInterface
{
public function processedValue(mixed $value, mixed $instance, ?DatabaseExecutor $executor = null): mixed
{
if (empty($value)) {
return $value;
}
// Your custom logic here
// Convert binary UUID from database to your preferred format
return HexUuidLiteral::getFormattedUuid($value, throwErrorIfInvalid: false);
}
}
// Use in your model
#[FieldAttribute(
fieldName: 'custom_uuid',
updateFunction: CustomUuidUpdateMapper::class,
selectFunction: CustomUuidSelectMapper::class
)]
protected ?string $customUuid = null;
Complete Example
Here's a complete example showing a table with multiple UUID fields:
use ByJG\MicroOrm\Attributes\FieldAttribute;
use ByJG\MicroOrm\Attributes\FieldUuidAttribute;
use ByJG\MicroOrm\Attributes\TableUuidPKAttribute;
use ByJG\MicroOrm\Repository;
#[TableUuidPKAttribute(tableName: 'orders')]
class Order
{
#[FieldUuidAttribute(primaryKey: true)]
protected ?string $id = null;
#[FieldUuidAttribute(fieldName: 'user_id')]
protected ?string $userId = null;
#[FieldUuidAttribute(fieldName: 'session_id')]
protected ?string $sessionId = null;
#[FieldAttribute]
protected ?string $status = null;
#[FieldAttribute(fieldName: 'created_at')]
protected ?string $createdAt = null;
// Getters and setters...
}
// Database schema
/*
CREATE TABLE orders (
id BINARY(16) PRIMARY KEY,
user_id BINARY(16) NOT NULL,
session_id BINARY(16),
status VARCHAR(50),
created_at DATETIME
);
*/
// Usage
$repository = new Repository($dbDriver, Order::class);
// Create new order
$order = new Order();
$order->setUserId('a1b2c3d4-e5f6-7890-abcd-ef1234567890');
$order->setSessionId('11111111-2222-3333-4444-555555555555');
$order->setStatus('pending');
$repository->save($order);
// ID is automatically generated
echo $order->getId(); // e.g., '3B4E18A0-C719-4ED0-C9B2-026A91889DC0'
// Query by UUID
$found = $repository->get($order->getId());
echo $found->getUserId(); // 'A1B2C3D4-E5F6-7890-ABCD-EF1234567890'
// All UUIDs are automatically formatted when retrieved