Skip to content

Commit

Permalink
[9.x] UUID and ULID support for Eloquent (#44074)
Browse files Browse the repository at this point in the history
* Eloquen UUID & ULID

* wip

* wip

* Update tests/Integration/Database/EloquentPrimaryUlidTest.php

* wip

* wip

* wip

* wip

* tweaking

Co-authored-by: Taylor Otwell <[email protected]>
  • Loading branch information
driesvints and taylorotwell authored Sep 13, 2022
1 parent 65693c0 commit 93ff1bc
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 2 deletions.
6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@
"phpstan/phpstan": "^1.4.7",
"phpunit/phpunit": "^9.5.8",
"predis/predis": "^1.1.9|^2.0",
"symfony/cache": "^6.0"
"symfony/cache": "^6.0",
"symfony/uid": "^6.0"
},
"provide": {
"psr/container-implementation": "1.1|2.0",
Expand Down Expand Up @@ -168,7 +169,8 @@
"symfony/http-client": "Required to enable support for the Symfony API mail transports (^6.0).",
"symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^6.0).",
"symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^6.0).",
"symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0)."
"symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0).",
"symfony/uid": "Required to generate ULIDs for Eloquent (^6.0)."
},
"config": {
"sort-packages": true,
Expand Down
64 changes: 64 additions & 0 deletions src/Illuminate/Database/Eloquent/Concerns/HasUlids.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

namespace Illuminate\Database\Eloquent\Concerns;

use Illuminate\Support\Str;

trait HasUlids
{
/**
* Boot the trait.
*
* @return void
*/
public static function bootHasUlids()
{
static::creating(function (self $model) {
foreach ($model->uniqueIds() as $column) {
if (empty($model->{$column})) {
$model->{$column} = $model->newUniqueId();
}
}
});
}

/**
* Generate a new ULID for the model.
*
* @return string
*/
public function newUniqueId()
{
return strtolower((string) Str::ulid());
}

/**
* Get the columns that should receive a unique identifier.
*
* @return array
*/
public function uniqueIds()
{
return [$this->getKeyName()];
}

/**
* Get the auto-incrementing key type.
*
* @return string
*/
public function getKeyType()
{
return 'string';
}

/**
* Get the value indicating whether the IDs are incrementing.
*
* @return bool
*/
public function getIncrementing()
{
return false;
}
}
64 changes: 64 additions & 0 deletions src/Illuminate/Database/Eloquent/Concerns/HasUuids.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

namespace Illuminate\Database\Eloquent\Concerns;

use Illuminate\Support\Str;

trait HasUuids
{
/**
* Generate a primary UUID for the model.
*
* @return void
*/
public static function bootHasUuids()
{
static::creating(function (self $model) {
foreach ($model->uniqueIds() as $column) {
if (empty($model->{$column})) {
$model->{$column} = $model->newUniqueId();
}
}
});
}

/**
* Generate a new UUID for the model.
*
* @return string
*/
public function newUniqueId()
{
return (string) Str::orderedUuid();
}

/**
* Get the columns that should receive a unique identifier.
*
* @return array
*/
public function uniqueIds()
{
return [$this->getKeyName()];
}

/**
* Get the auto-incrementing key type.
*
* @return string
*/
public function getKeyType()
{
return 'string';
}

/**
* Get the value indicating whether the IDs are incrementing.
*
* @return bool
*/
public function getIncrementing()
{
return false;
}
}
26 changes: 26 additions & 0 deletions src/Illuminate/Support/Str.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Ramsey\Uuid\Generator\CombGenerator;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidFactory;
use Symfony\Component\Uid\Ulid;
use Traversable;
use voku\helper\ASCII;

Expand Down Expand Up @@ -424,6 +425,21 @@ public static function isUuid($value)
return preg_match('/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iD', $value) > 0;
}

/**
* Determine if a given string is a valid ULID.
*
* @param string $value
* @return bool
*/
public static function isUlid($value)
{
if (! is_string($value)) {
return false;
}

return Ulid::isValid($value);
}

/**
* Convert a string to kebab case.
*
Expand Down Expand Up @@ -1306,6 +1322,16 @@ public static function createUuidsNormally()
static::$uuidFactory = null;
}

/**
* Generate a ULID.
*
* @return \Symfony\Component\Uid\Ulid
*/
public static function ulid()
{
return new Ulid();
}

/**
* Remove all strings from the casing caches.
*
Expand Down
1 change: 1 addition & 0 deletions src/Illuminate/Support/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"league/commonmark": "Required to use Str::markdown() and Stringable::markdown() (^2.0.2).",
"ramsey/uuid": "Required to use Str::uuid() (^4.2.2).",
"symfony/process": "Required to use the composer class (^6.0).",
"symfony/uid": "Required to generate ULIDs for Eloquent (^6.0).",
"symfony/var-dumper": "Required to use the dd function (^6.0).",
"vlucas/phpdotenv": "Required to use the Env class and env helper (^5.4.1)."
},
Expand Down
36 changes: 36 additions & 0 deletions tests/Integration/Database/EloquentUlidPrimaryKeyTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace Illuminate\Tests\Integration\Database;

use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Model as Eloquent;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;

class EloquentUlidPrimaryKeyTest extends DatabaseTestCase
{
protected function defineDatabaseMigrationsAfterDatabaseRefreshed()
{
Schema::create('users', function (Blueprint $table) {
$table->char('id', 26)->primary();
$table->timestamps();
});
}

public function testUserWithUlidPrimaryKeyCanBeCreated()
{
$user = UserWithUlidPrimaryKey::create();

$this->assertTrue(Str::isUlid($user->id));
}
}

class UserWithUlidPrimaryKey extends Eloquent
{
use HasUlids;

protected $table = 'users';

protected $guarded = [];
}
36 changes: 36 additions & 0 deletions tests/Integration/Database/EloquentUuidPrimaryKeyTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace Illuminate\Tests\Integration\Database;

use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Model as Eloquent;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;

class EloquentUuidPrimaryKeyTest extends DatabaseTestCase
{
protected function defineDatabaseMigrationsAfterDatabaseRefreshed()
{
Schema::create('users', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->timestamps();
});
}

public function testUserWithUuidPrimaryKeyCanBeCreated()
{
$user = UserWithUuidPrimaryKey::create();

$this->assertTrue(Str::isUuid($user->id));
}
}

class UserWithUuidPrimaryKey extends Eloquent
{
use HasUuids;

protected $table = 'users';

protected $guarded = [];
}

0 comments on commit 93ff1bc

Please sign in to comment.