Skip to content

Commit 3a2cf74

Browse files
committed
Add binary UUID relationship support
Implements BinaryUuidBuilder to enable Eloquent relationships when using EfficientUuid cast with BINARY(16) storage. Addresses issue #65. Features: - Custom query builder that converts UUID strings to binary automatically - Opt-in via UsesBinaryUuidBuilder trait - Full support for belongsTo, hasMany, hasOne relationships - Eager loading support (with, whereIn queries) - whereNotIn support for exclusion queries Testing: - 22 new comprehensive tests (78 total, 144 assertions) - 100% backward compatible - all existing tests pass - Edge cases covered (invalid UUIDs, empty arrays, qualified columns) - Complex query scenarios tested Implementation: - Zero performance penalty for non-UUID queries - Strict type checking (PHPStan ready) - No breaking changes to existing functionality
1 parent 9e6ae37 commit 3a2cf74

File tree

6 files changed

+720
-0
lines changed

6 files changed

+720
-0
lines changed

src/BinaryUuidBuilder.php

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Dyrynda\Database\Support;
6+
7+
use Illuminate\Database\Eloquent\Builder;
8+
use Ramsey\Uuid\Uuid;
9+
10+
/**
11+
* Custom Eloquent Builder for models with binary UUID columns.
12+
*
13+
* This builder automatically converts UUID strings to binary format
14+
* when querying columns that use the EfficientUuid cast, ensuring
15+
* Eloquent relationships work correctly with binary UUID storage.
16+
*
17+
* @template TModel of \Illuminate\Database\Eloquent\Model
18+
*
19+
* @extends Builder<TModel>
20+
*/
21+
class BinaryUuidBuilder extends Builder
22+
{
23+
/**
24+
* Add a basic where clause to the query.
25+
*
26+
* Automatically converts UUID strings to binary when querying
27+
* columns that use the EfficientUuid cast.
28+
*
29+
* @param \Closure|string|array<mixed>|\Illuminate\Contracts\Database\Query\Expression $column
30+
* @param mixed $operator
31+
* @param mixed $value
32+
* @param string $boolean
33+
* @return $this
34+
*/
35+
public function where($column, $operator = null, $value = null, $boolean = 'and')
36+
{
37+
// Check if this is a simple where clause on a UUID column
38+
if (is_string($column) && $this->shouldConvertToUuidBinary($column, $value ?? $operator)) {
39+
$actualValue = func_num_args() === 2 ? $operator : $value;
40+
41+
if (is_string($actualValue) && Uuid::isValid($actualValue)) {
42+
// Convert UUID string to binary
43+
$binaryValue = Uuid::fromString($actualValue)->getBytes();
44+
45+
if (func_num_args() === 2) {
46+
return parent::where($column, '=', $binaryValue, $boolean);
47+
}
48+
49+
return parent::where($column, $operator, $binaryValue, $boolean);
50+
}
51+
}
52+
53+
return parent::where($column, $operator, $value, $boolean);
54+
}
55+
56+
/**
57+
* Add a "where in" clause to the query.
58+
*
59+
* Automatically converts arrays of UUID strings to binary format.
60+
* This is crucial for eager loading (with()) and other bulk queries.
61+
*
62+
* @param \Illuminate\Contracts\Database\Query\Expression|string $column
63+
* @param mixed $values
64+
* @param string $boolean
65+
* @param bool $not
66+
* @return $this
67+
*/
68+
public function whereIn($column, $values, $boolean = 'and', $not = false)
69+
{
70+
if (is_string($column) && $this->isUuidColumn($column)) {
71+
// Convert array of UUID strings to binary
72+
$values = collect($values)->map(function ($value) {
73+
if (is_string($value) && Uuid::isValid($value)) {
74+
return Uuid::fromString($value)->getBytes();
75+
}
76+
77+
return $value;
78+
})->all();
79+
}
80+
81+
return parent::whereIn($column, $values, $boolean, $not);
82+
}
83+
84+
/**
85+
* Add a "where not in" clause to the query.
86+
*
87+
* Automatically converts arrays of UUID strings to binary format.
88+
*
89+
* @param \Illuminate\Contracts\Database\Query\Expression|string $column
90+
* @param mixed $values
91+
* @param string $boolean
92+
* @return $this
93+
*/
94+
public function whereNotIn($column, $values, $boolean = 'and')
95+
{
96+
return $this->whereIn($column, $values, $boolean, true);
97+
}
98+
99+
/**
100+
* Check if column should convert UUID string to binary.
101+
*/
102+
protected function shouldConvertToUuidBinary(string $column, mixed $value): bool
103+
{
104+
return $this->isUuidColumn($column) && is_string($value) && Uuid::isValid($value);
105+
}
106+
107+
/**
108+
* Check if a column uses UUID binary cast (EfficientUuid).
109+
*/
110+
protected function isUuidColumn(string $column): bool
111+
{
112+
$model = $this->getModel();
113+
114+
// Remove table prefix if present (e.g., 'users.id' -> 'id')
115+
$columnName = str_contains($column, '.')
116+
? substr($column, strrpos($column, '.') + 1)
117+
: $column;
118+
119+
// Get casts from the model
120+
$casts = $model->getCasts();
121+
122+
if (! isset($casts[$columnName])) {
123+
return false;
124+
}
125+
126+
$castType = $casts[$columnName];
127+
128+
// Handle string class names
129+
if (is_string($castType)) {
130+
return $castType === Casts\EfficientUuid::class
131+
|| $castType === 'Dyrynda\Database\Support\Casts\EfficientUuid';
132+
}
133+
134+
// Handle object instances (Laravel 12+)
135+
return $castType instanceof Casts\EfficientUuid;
136+
}
137+
}

src/UsesBinaryUuidBuilder.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Dyrynda\Database\Support;
6+
7+
/**
8+
* Enables binary UUID relationship support.
9+
*
10+
* Use this trait on models that store UUIDs as BINARY(16) using
11+
* the EfficientUuid cast to enable automatic conversion in
12+
* Eloquent relationships.
13+
*
14+
* When to use this trait:
15+
* - Your model uses the EfficientUuid cast on UUID columns
16+
* - You need Eloquent relationships to work with binary UUID storage
17+
* - You're experiencing issues with belongsTo, hasMany, or other relationships
18+
*
19+
* Example usage:
20+
* ```php
21+
* use Dyrynda\Database\Support\{GeneratesUuid, UsesBinaryUuidBuilder};
22+
* use Dyrynda\Database\Support\Casts\EfficientUuid;
23+
*
24+
* class User extends Model
25+
* {
26+
* use GeneratesUuid, UsesBinaryUuidBuilder;
27+
*
28+
* public $incrementing = false;
29+
* protected $keyType = 'string';
30+
*
31+
* protected function casts(): array
32+
* {
33+
* return ['id' => EfficientUuid::class];
34+
* }
35+
*
36+
* public function posts()
37+
* {
38+
* return $this->hasMany(Post::class);
39+
* }
40+
* }
41+
* ```
42+
*/
43+
trait UsesBinaryUuidBuilder
44+
{
45+
/**
46+
* Create a new Eloquent query builder for the model.
47+
*
48+
* This method is called automatically by Eloquent when building queries.
49+
* It returns our custom BinaryUuidBuilder to handle UUID binary conversions.
50+
*
51+
* @param \Illuminate\Database\Query\Builder $query
52+
* @return BinaryUuidBuilder<static>
53+
*/
54+
public function newEloquentBuilder($query): BinaryUuidBuilder
55+
{
56+
return new BinaryUuidBuilder($query);
57+
}
58+
}

0 commit comments

Comments
 (0)