Skip to content

[9.x] UUID and ULID support for Eloquent #44074

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Sep 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,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 @@ -163,7 +164,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).",
Copy link
Contributor

@tanthammar tanthammar Sep 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there something wrong with the requirement?
I had to manually composer require symfony/uid to get Str::ulid() to work.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is in the suggests session. So to use its functionality you should manually install this dependency.

"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 = [];
}