Skip to content

Commit efb9dd3

Browse files
Feature: Migrate efficient UUID support (#130)
* Merge dyrynda/laravel-efficient-uuid into this package It has long-since been a maintenance problem to have the two packages separately and dependant on each other. This will combine them into one package, simplifying maintenance and testing. dyrynda/laravel-efficient-uuid has been marked abandoned, suggesting use of this package. * fix namespaces * typo * tweaks to make sure we're using the right fields types in right places in testing * don't cast uuid on efficient uuid post * don't cast custom_uuid on efficient uuid post
1 parent bc0ba23 commit efb9dd3

14 files changed

+335
-27
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ If you use the suggested [laravel-efficient-uuid](https://github.com/michaeldyry
135135

136136
namespace App;
137137

138-
use Dyrynda\Database\Casts\EfficientUuid;
138+
use Dyrynda\Database\Support\Casts\EfficientUuid;
139139
use Dyrynda\Database\Support\GeneratesUuid;
140140
use Illuminate\Database\Eloquent\Model;
141141

composer.json

+2-4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
],
1818
"require": {
1919
"php": "^8.1",
20+
"illuminate/container": "^10.0 || ^11.0",
21+
"illuminate/contracts": "^10.0 || ^11.0",
2022
"illuminate/database": "^10.0 || ^11.0",
2123
"illuminate/events": "^10.0 || ^11.0",
2224
"illuminate/support": "^10.0 || ^11.0",
@@ -29,7 +31,6 @@
2931
}
3032
},
3133
"require-dev": {
32-
"dyrynda/laravel-efficient-uuid": "^5.0",
3334
"laravel/legacy-factories": "^1.3",
3435
"laravel/pint": "^1.13",
3536
"orchestra/testbench": "^8.0 || ^9.0",
@@ -40,9 +41,6 @@
4041
"Tests\\": "tests/"
4142
}
4243
},
43-
"suggest": {
44-
"dyrynda/laravel-efficient-uuid": "Override Laravel's default query grammar to efficiently store UUIDs."
45-
},
4644
"minimum-stability": "dev",
4745
"prefer-stable": true,
4846
"config": {

src/Casts/EfficientUuid.php

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
namespace Dyrynda\Database\Support\Casts;
4+
5+
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
6+
use Ramsey\Uuid\Uuid;
7+
8+
class EfficientUuid implements CastsAttributes
9+
{
10+
/**
11+
* Transform the attribute from the underlying model values.
12+
*
13+
* @param \Illuminate\Database\Eloquent\Model $model
14+
* @param mixed $value
15+
* @return mixed
16+
*/
17+
public function get($model, string $key, $value, array $attributes)
18+
{
19+
if (blank($value)) {
20+
return;
21+
}
22+
23+
return Uuid::fromBytes($value)->toString();
24+
}
25+
26+
/**
27+
* Transform the attribute to its underlying model values.
28+
*
29+
* @param \Illuminate\Database\Eloquent\Model $model
30+
* @param mixed $value
31+
* @return array
32+
*/
33+
public function set($model, string $key, $value, array $attributes)
34+
{
35+
if (blank($value)) {
36+
return;
37+
}
38+
39+
return [
40+
$key => Uuid::fromString(strtolower($value))->getBytes(),
41+
];
42+
}
43+
}
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Dyrynda\Database\Support\Exceptions;
4+
5+
use Exception;
6+
7+
class UnknownGrammarClass extends Exception
8+
{
9+
/** @var string */
10+
protected $message = 'Unknown grammar class, unable to define database type.';
11+
}

src/LaravelModelUuidServiceProvider.php

+22
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
namespace Dyrynda\Database\Support;
44

5+
use Dyrynda\Database\Support\Exceptions\UnknownGrammarClass;
6+
use Illuminate\Database\Schema\Blueprint;
7+
use Illuminate\Database\Schema\ColumnDefinition;
8+
use Illuminate\Database\Schema\Grammars\Grammar;
9+
use Illuminate\Support\Fluent;
510
use Spatie\LaravelPackageTools\Commands\InstallCommand;
611
use Spatie\LaravelPackageTools\Package;
712
use Spatie\LaravelPackageTools\PackageServiceProvider;
@@ -17,4 +22,21 @@ public function configurePackage(Package $package): void
1722
$command->publishConfigFile();
1823
});
1924
}
25+
26+
public function packageRegistered()
27+
{
28+
Grammar::macro('typeEfficientUuid', function (Fluent $column) {
29+
return match (class_basename(static::class)) {
30+
'MySqlGrammar' => sprintf('binary(%d)', $column->length ?? 16),
31+
'PostgresGrammar' => 'bytea',
32+
'SQLiteGrammar' => 'blob(256)',
33+
default => throw new UnknownGrammarClass
34+
};
35+
});
36+
37+
Blueprint::macro('efficientUuid', function ($column): ColumnDefinition {
38+
/** @var \Illuminate\Database\Schema\Blueprint $this */
39+
return $this->addColumn('efficientUuid', $column);
40+
});
41+
}
2042
}

src/Rules/EfficientUuidExists.php

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
namespace Dyrynda\Database\Support\Rules;
4+
5+
use Illuminate\Contracts\Validation\Rule;
6+
use Ramsey\Uuid\Uuid;
7+
8+
class EfficientUuidExists implements Rule
9+
{
10+
/** @var \Dyrynda\Database\Support\GeneratesUuid */
11+
protected $model;
12+
13+
/** @var string */
14+
protected $column;
15+
16+
public function __construct(string $model, string $column = 'uuid')
17+
{
18+
$this->model = new $model;
19+
20+
$this->column = $column;
21+
}
22+
23+
public function passes($attribute, $value): bool
24+
{
25+
if (Uuid::isValid($value ?: '')) {
26+
$binaryUuid = Uuid::fromString(strtolower($value))->getBytes();
27+
28+
return $this->model->where($this->column, $binaryUuid)->exists();
29+
}
30+
31+
return false;
32+
}
33+
34+
public function message(): string
35+
{
36+
return trans('validation.exists');
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace Tests\Feature;
4+
5+
use Dyrynda\Database\Support\Exceptions\UnknownGrammarClass;
6+
use Illuminate\Database\Connection;
7+
use Illuminate\Database\Schema\Blueprint;
8+
use Illuminate\Database\Schema\Grammars\SqlServerGrammar;
9+
use Mockery as m;
10+
use Tests\TestCase;
11+
12+
class DatabaseInvalidSchemaGrammarTest extends TestCase
13+
{
14+
public function tearDown(): void
15+
{
16+
m::close();
17+
}
18+
19+
public function testAddingUuid()
20+
{
21+
$blueprint = new Blueprint('users', function ($table) {
22+
$table->uuid('foo');
23+
$table->efficientUuid('bar');
24+
});
25+
26+
$connection = m::mock(Connection::class);
27+
28+
$this->expectExceptionObject(new UnknownGrammarClass());
29+
30+
$blueprint->toSql($connection, new SqlServerGrammar);
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace Tests\Feature;
4+
5+
use Illuminate\Database\Connection;
6+
use Illuminate\Database\Schema\Blueprint;
7+
use Illuminate\Database\Schema\Grammars\MySqlGrammar;
8+
use Mockery as m;
9+
use Tests\TestCase;
10+
11+
class DatabaseMySqlSchemaGrammarTest extends TestCase
12+
{
13+
public function tearDown(): void
14+
{
15+
m::close();
16+
}
17+
18+
public function testAddingUuid()
19+
{
20+
$blueprint = new Blueprint('users', function ($table) {
21+
$table->uuid('foo');
22+
$table->efficientUuid('bar');
23+
});
24+
25+
$connection = m::mock(Connection::class);
26+
27+
$this->assertEquals(
28+
['alter table `users` add `foo` char(36) not null, add `bar` binary(16) not null'],
29+
$blueprint->toSql($connection, new MySqlGrammar)
30+
);
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace Tests\Feature;
4+
5+
use Illuminate\Database\Connection;
6+
use Illuminate\Database\Schema\Blueprint;
7+
use Illuminate\Database\Schema\Grammars\PostgresGrammar;
8+
use Mockery as m;
9+
use Tests\TestCase;
10+
11+
class DatabasePostgresSchemaGrammarTest extends TestCase
12+
{
13+
public function tearDown(): void
14+
{
15+
m::close();
16+
}
17+
18+
public function testAddingUuid()
19+
{
20+
$blueprint = new Blueprint('users', function ($table) {
21+
$table->uuid('foo');
22+
$table->efficientUuid('bar');
23+
});
24+
25+
$connection = m::mock(Connection::class);
26+
27+
$this->assertEquals(
28+
['alter table "users" add column "foo" uuid not null, add column "bar" bytea not null'],
29+
$blueprint->toSql($connection, new PostgresGrammar)
30+
);
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace Tests\Feature;
4+
5+
use Illuminate\Database\Connection;
6+
use Illuminate\Database\Schema\Blueprint;
7+
use Illuminate\Database\Schema\Grammars\SQLiteGrammar;
8+
use Mockery as m;
9+
use Tests\TestCase;
10+
11+
class DatabaseSQLiteSchemaGrammarTest extends TestCase
12+
{
13+
public function tearDown(): void
14+
{
15+
m::close();
16+
}
17+
18+
public function testAddingUuid()
19+
{
20+
$blueprint = new Blueprint('users', function ($table) {
21+
$table->uuid('foo');
22+
$table->efficientUuid('bar');
23+
});
24+
25+
$connection = m::mock(Connection::class);
26+
27+
$this->assertEquals(
28+
['alter table "users" add column "foo" varchar not null', 'alter table "users" add column "bar" blob(256) not null'],
29+
$blueprint->toSql($connection, new SQLiteGrammar)
30+
);
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
namespace Tests\Feature;
4+
5+
use Dyrynda\Database\Support\Rules\EfficientUuidExists;
6+
use Ramsey\Uuid\Uuid;
7+
use Tests\Fixtures\EfficientUuidPost;
8+
use Tests\TestCase;
9+
10+
class EfficientUuidExistsRuleTest extends TestCase
11+
{
12+
/** @test */
13+
public function it_passes_valid_existing_uuid()
14+
{
15+
/** @var \Tests\Fixtures\EfficientUuidPost $post */
16+
$post = factory(EfficientUuidPost::class)->create();
17+
18+
$rule = new EfficientUuidExists(EfficientUuidPost::class, 'efficient_uuid');
19+
20+
$this->assertTrue($rule->passes('efficient_uuid', $post->efficient_uuid));
21+
}
22+
23+
/** @test */
24+
public function it_fails_on_non_existing_uuid()
25+
{
26+
$uuid = Uuid::uuid4();
27+
28+
$rule = new EfficientUuidExists(EfficientUuidPost::class);
29+
30+
$this->assertFalse($rule->passes('post_id', $uuid));
31+
}
32+
33+
/** @test */
34+
public function it_fails_on_any_non_uuid_invalid_strings()
35+
{
36+
$uuid = '1235123564354633';
37+
38+
$rule = new EfficientUuidExists(EfficientUuidPost::class, 'uuid');
39+
40+
$this->assertFalse($rule->passes('post_id', $uuid));
41+
}
42+
43+
/** @test */
44+
public function it_works_with_custom_uuid_column_name()
45+
{
46+
/** @var \Tests\Fixtures\EfficientUuidPost $post */
47+
$post = factory(EfficientUuidPost::class)->create();
48+
49+
$rule = new EfficientUuidExists(EfficientUuidPost::class, 'custom_efficient_uuid');
50+
51+
$this->assertTrue($rule->passes('custom_efficient_uuid', $post->custom_efficient_uuid));
52+
}
53+
}

0 commit comments

Comments
 (0)