Skip to content

Commit

Permalink
Feature: Migrate efficient UUID support (#130)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
michaeldyrynda authored Mar 19, 2024
1 parent bc0ba23 commit efb9dd3
Show file tree
Hide file tree
Showing 14 changed files with 335 additions and 27 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ If you use the suggested [laravel-efficient-uuid](https://github.com/michaeldyry

namespace App;

use Dyrynda\Database\Casts\EfficientUuid;
use Dyrynda\Database\Support\Casts\EfficientUuid;
use Dyrynda\Database\Support\GeneratesUuid;
use Illuminate\Database\Eloquent\Model;

Expand Down
6 changes: 2 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
],
"require": {
"php": "^8.1",
"illuminate/container": "^10.0 || ^11.0",
"illuminate/contracts": "^10.0 || ^11.0",
"illuminate/database": "^10.0 || ^11.0",
"illuminate/events": "^10.0 || ^11.0",
"illuminate/support": "^10.0 || ^11.0",
Expand All @@ -29,7 +31,6 @@
}
},
"require-dev": {
"dyrynda/laravel-efficient-uuid": "^5.0",
"laravel/legacy-factories": "^1.3",
"laravel/pint": "^1.13",
"orchestra/testbench": "^8.0 || ^9.0",
Expand All @@ -40,9 +41,6 @@
"Tests\\": "tests/"
}
},
"suggest": {
"dyrynda/laravel-efficient-uuid": "Override Laravel's default query grammar to efficiently store UUIDs."
},
"minimum-stability": "dev",
"prefer-stable": true,
"config": {
Expand Down
43 changes: 43 additions & 0 deletions src/Casts/EfficientUuid.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace Dyrynda\Database\Support\Casts;

use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Ramsey\Uuid\Uuid;

class EfficientUuid implements CastsAttributes
{
/**
* Transform the attribute from the underlying model values.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param mixed $value
* @return mixed
*/
public function get($model, string $key, $value, array $attributes)
{
if (blank($value)) {
return;
}

return Uuid::fromBytes($value)->toString();
}

/**
* Transform the attribute to its underlying model values.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param mixed $value
* @return array
*/
public function set($model, string $key, $value, array $attributes)
{
if (blank($value)) {
return;
}

return [
$key => Uuid::fromString(strtolower($value))->getBytes(),
];
}
}
11 changes: 11 additions & 0 deletions src/Exceptions/UnknownGrammarClass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Dyrynda\Database\Support\Exceptions;

use Exception;

class UnknownGrammarClass extends Exception
{
/** @var string */
protected $message = 'Unknown grammar class, unable to define database type.';
}
22 changes: 22 additions & 0 deletions src/LaravelModelUuidServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

namespace Dyrynda\Database\Support;

use Dyrynda\Database\Support\Exceptions\UnknownGrammarClass;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\ColumnDefinition;
use Illuminate\Database\Schema\Grammars\Grammar;
use Illuminate\Support\Fluent;
use Spatie\LaravelPackageTools\Commands\InstallCommand;
use Spatie\LaravelPackageTools\Package;
use Spatie\LaravelPackageTools\PackageServiceProvider;
Expand All @@ -17,4 +22,21 @@ public function configurePackage(Package $package): void
$command->publishConfigFile();
});
}

public function packageRegistered()
{
Grammar::macro('typeEfficientUuid', function (Fluent $column) {
return match (class_basename(static::class)) {
'MySqlGrammar' => sprintf('binary(%d)', $column->length ?? 16),
'PostgresGrammar' => 'bytea',
'SQLiteGrammar' => 'blob(256)',
default => throw new UnknownGrammarClass
};
});

Blueprint::macro('efficientUuid', function ($column): ColumnDefinition {
/** @var \Illuminate\Database\Schema\Blueprint $this */
return $this->addColumn('efficientUuid', $column);
});
}
}
38 changes: 38 additions & 0 deletions src/Rules/EfficientUuidExists.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace Dyrynda\Database\Support\Rules;

use Illuminate\Contracts\Validation\Rule;
use Ramsey\Uuid\Uuid;

class EfficientUuidExists implements Rule
{
/** @var \Dyrynda\Database\Support\GeneratesUuid */
protected $model;

/** @var string */
protected $column;

public function __construct(string $model, string $column = 'uuid')
{
$this->model = new $model;

$this->column = $column;
}

public function passes($attribute, $value): bool
{
if (Uuid::isValid($value ?: '')) {
$binaryUuid = Uuid::fromString(strtolower($value))->getBytes();

return $this->model->where($this->column, $binaryUuid)->exists();
}

return false;
}

public function message(): string
{
return trans('validation.exists');
}
}
32 changes: 32 additions & 0 deletions tests/Feature/DatabaseInvalidSchemaGrammarTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Tests\Feature;

use Dyrynda\Database\Support\Exceptions\UnknownGrammarClass;
use Illuminate\Database\Connection;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Grammars\SqlServerGrammar;
use Mockery as m;
use Tests\TestCase;

class DatabaseInvalidSchemaGrammarTest extends TestCase
{
public function tearDown(): void
{
m::close();
}

public function testAddingUuid()
{
$blueprint = new Blueprint('users', function ($table) {
$table->uuid('foo');
$table->efficientUuid('bar');
});

$connection = m::mock(Connection::class);

$this->expectExceptionObject(new UnknownGrammarClass());

$blueprint->toSql($connection, new SqlServerGrammar);
}
}
32 changes: 32 additions & 0 deletions tests/Feature/DatabaseMySqlSchemaGrammarTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Tests\Feature;

use Illuminate\Database\Connection;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Grammars\MySqlGrammar;
use Mockery as m;
use Tests\TestCase;

class DatabaseMySqlSchemaGrammarTest extends TestCase
{
public function tearDown(): void
{
m::close();
}

public function testAddingUuid()
{
$blueprint = new Blueprint('users', function ($table) {
$table->uuid('foo');
$table->efficientUuid('bar');
});

$connection = m::mock(Connection::class);

$this->assertEquals(
['alter table `users` add `foo` char(36) not null, add `bar` binary(16) not null'],
$blueprint->toSql($connection, new MySqlGrammar)
);
}
}
32 changes: 32 additions & 0 deletions tests/Feature/DatabasePostgresSchemaGrammarTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Tests\Feature;

use Illuminate\Database\Connection;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Grammars\PostgresGrammar;
use Mockery as m;
use Tests\TestCase;

class DatabasePostgresSchemaGrammarTest extends TestCase
{
public function tearDown(): void
{
m::close();
}

public function testAddingUuid()
{
$blueprint = new Blueprint('users', function ($table) {
$table->uuid('foo');
$table->efficientUuid('bar');
});

$connection = m::mock(Connection::class);

$this->assertEquals(
['alter table "users" add column "foo" uuid not null, add column "bar" bytea not null'],
$blueprint->toSql($connection, new PostgresGrammar)
);
}
}
32 changes: 32 additions & 0 deletions tests/Feature/DatabaseSQLiteSchemaGrammarTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Tests\Feature;

use Illuminate\Database\Connection;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Grammars\SQLiteGrammar;
use Mockery as m;
use Tests\TestCase;

class DatabaseSQLiteSchemaGrammarTest extends TestCase
{
public function tearDown(): void
{
m::close();
}

public function testAddingUuid()
{
$blueprint = new Blueprint('users', function ($table) {
$table->uuid('foo');
$table->efficientUuid('bar');
});

$connection = m::mock(Connection::class);

$this->assertEquals(
['alter table "users" add column "foo" varchar not null', 'alter table "users" add column "bar" blob(256) not null'],
$blueprint->toSql($connection, new SQLiteGrammar)
);
}
}
53 changes: 53 additions & 0 deletions tests/Feature/EfficientUuidExistsRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace Tests\Feature;

use Dyrynda\Database\Support\Rules\EfficientUuidExists;
use Ramsey\Uuid\Uuid;
use Tests\Fixtures\EfficientUuidPost;
use Tests\TestCase;

class EfficientUuidExistsRuleTest extends TestCase
{
/** @test */
public function it_passes_valid_existing_uuid()
{
/** @var \Tests\Fixtures\EfficientUuidPost $post */
$post = factory(EfficientUuidPost::class)->create();

$rule = new EfficientUuidExists(EfficientUuidPost::class, 'efficient_uuid');

$this->assertTrue($rule->passes('efficient_uuid', $post->efficient_uuid));
}

/** @test */
public function it_fails_on_non_existing_uuid()
{
$uuid = Uuid::uuid4();

$rule = new EfficientUuidExists(EfficientUuidPost::class);

$this->assertFalse($rule->passes('post_id', $uuid));
}

/** @test */
public function it_fails_on_any_non_uuid_invalid_strings()
{
$uuid = '1235123564354633';

$rule = new EfficientUuidExists(EfficientUuidPost::class, 'uuid');

$this->assertFalse($rule->passes('post_id', $uuid));
}

/** @test */
public function it_works_with_custom_uuid_column_name()
{
/** @var \Tests\Fixtures\EfficientUuidPost $post */
$post = factory(EfficientUuidPost::class)->create();

$rule = new EfficientUuidExists(EfficientUuidPost::class, 'custom_efficient_uuid');

$this->assertTrue($rule->passes('custom_efficient_uuid', $post->custom_efficient_uuid));
}
}
Loading

0 comments on commit efb9dd3

Please sign in to comment.