From 4e8cbc0e8a9479e7ea6f5c23ae60a07851101fee Mon Sep 17 00:00:00 2001 From: Eric Hocking Date: Sun, 26 Jan 2025 17:47:02 -0500 Subject: [PATCH 01/19] Add cuid2 support --- composer.json | 3 +- config/cuid.php | 13 ++++ .../Database/Eloquent/Concerns/HasCuid.php | 32 +++++++++ src/Illuminate/Database/Schema/Blueprint.php | 71 +++++++++++++++++++ src/Illuminate/Support/Str.php | 27 +++++++ 5 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 config/cuid.php create mode 100644 src/Illuminate/Database/Eloquent/Concerns/HasCuid.php diff --git a/composer.json b/composer.json index 39ea6589a7f7..6f82e0dbb8f8 100644 --- a/composer.json +++ b/composer.json @@ -18,6 +18,7 @@ "php": "^8.2", "ext-ctype": "*", "ext-filter": "*", + "ext-gmp": "*", "ext-hash": "*", "ext-mbstring": "*", "ext-openssl": "*", @@ -57,6 +58,7 @@ "symfony/uid": "^7.0.3", "symfony/var-dumper": "^7.0.3", "tijsverkoyen/css-to-inline-styles": "^2.2.5", + "visus/cuid2": "^5.0.0", "vlucas/phpdotenv": "^5.6.1", "voku/portable-ascii": "^2.0.2" }, @@ -98,7 +100,6 @@ "spatie/once": "*" }, "require-dev": { - "ext-gmp": "*", "ably/ably-php": "^1.0", "aws/aws-sdk-php": "^3.322.9", "fakerphp/faker": "^1.24", diff --git a/config/cuid.php b/config/cuid.php new file mode 100644 index 000000000000..a298863bdab0 --- /dev/null +++ b/config/cuid.php @@ -0,0 +1,13 @@ + env('CUID_LENGTH', 24), +]; diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasCuid.php b/src/Illuminate/Database/Eloquent/Concerns/HasCuid.php new file mode 100644 index 000000000000..098ff5426912 --- /dev/null +++ b/src/Illuminate/Database/Eloquent/Concerns/HasCuid.php @@ -0,0 +1,32 @@ + 32) ? 24 : $size); + + return $cuid->toString(); + } + + /** + * Determine if given key is valid. + * + * @param mixed $value + */ + protected function isValidUniqueId($value): bool + { + return Str::isCuid($value); + } +} diff --git a/src/Illuminate/Database/Schema/Blueprint.php b/src/Illuminate/Database/Schema/Blueprint.php index ca2eed4eb55b..8026efff8ac9 100755 --- a/src/Illuminate/Database/Schema/Blueprint.php +++ b/src/Illuminate/Database/Schema/Blueprint.php @@ -4,6 +4,7 @@ use Closure; use Illuminate\Database\Connection; +use Illuminate\Database\Eloquent\Concerns\HasCuid; use Illuminate\Database\Eloquent\Concerns\HasUlids; use Illuminate\Database\Query\Expression; use Illuminate\Database\Schema\Grammars\Grammar; @@ -1053,6 +1054,12 @@ public function foreignIdFor($model, $column = null) ->referencesModelColumn($model->getKeyName()); } + if (in_array(HasCuid::class, $modelTraits, true)) { + return $this->foreignCuid($column, 32) + ->table($model->getTable()) + ->referencesModelColumn($model->getKeyName()); + } + return $this->foreignUuid($column) ->table($model->getTable()) ->referencesModelColumn($model->getKeyName()); @@ -1399,6 +1406,34 @@ public function foreignUlid($column, $length = 26) ])); } + /** + * Create a new CUID column on the table. + * + * @param string $column + * @param int|null $length + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function cuid($column = 'cuid', $length = 32) { + return $this->string($column, $length); + } + + + /** + * Create a new CUID column on the table with a foreign key constraint. + * + * @param string $column + * @param int|null $length + * @return \Illuminate\Database\Schema\ForeignIdColumnDefinition + */ + public function foreignCuid($column, $length = 32) + { + return $this->addColumnDefinition(new ForeignIdColumnDefinition($this, [ + 'type' => 'string', + 'name' => $column, + 'length' => $length, + ])); + } + /** * Create a new IP address column on the table. * @@ -1486,6 +1521,8 @@ public function morphs($name, $indexName = null) $this->uuidMorphs($name, $indexName); } elseif (Builder::$defaultMorphKeyType === 'ulid') { $this->ulidMorphs($name, $indexName); + } elseif (Builder::$defaultMorphKeyType === 'cuid') { + $this->cuidMorphs($name, $indexName); } else { $this->numericMorphs($name, $indexName); } @@ -1504,6 +1541,8 @@ public function nullableMorphs($name, $indexName = null) $this->nullableUuidMorphs($name, $indexName); } elseif (Builder::$defaultMorphKeyType === 'ulid') { $this->nullableUlidMorphs($name, $indexName); + } elseif (Builder::$defaultMorphKeyType === 'cuid') { + $this->nullableCuidMorphs($name, $indexName); } else { $this->nullableNumericMorphs($name, $indexName); } @@ -1605,6 +1644,38 @@ public function nullableUlidMorphs($name, $indexName = null) $this->index(["{$name}_type", "{$name}_id"], $indexName); } + /** + * Add the proper columns for a polymorphic table using CUIDs. + * + * @param string $name + * @param string|null $indexName + * @return void + */ + public function cuidMorphs($name, $indexName = null) + { + $this->string("{$name}_type"); + + $this->string("{$name}_id", 32); + + $this->index(["{$name}_type", "{$name}_id"], $indexName); + } + + /** + * Add nullable columns for a polymorphic table using CUIDs. + * + * @param string $name + * @param string|null $indexName + * @return void + */ + public function nullableCuidMorphs($name, $indexName = null) + { + $this->string("{$name}_type")->nullable(); + + $this->string("{$name}_id", 32)->nullable(); + + $this->index(["{$name}_type", "{$name}_id"], $indexName); + } + /** * Add the `remember_token` column to the table. * diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index af8f0ea6568b..e5b07cb90189 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -626,6 +626,33 @@ public static function isUlid($value) return Ulid::isValid($value); } + /** + * Determine if a given value is a valid CUID. + * + * @param mixed $value + * @return bool + */ + public static function isCuid($value) + { + if (!is_string($value)) { + return false; + } + + // validate length + $minLength = 2; + $maxLength = 32; + + $length = strlen($value); + if ($length >= $minLength + && $length <= $maxLength + && preg_match('/^[a-z][0-9a-z]+$/', $value) + ) { + return true; + } + + return false; + } + /** * Convert a string to kebab case. * From d732778e660a8a18e4325790eb91ff5f6c4f7106 Mon Sep 17 00:00:00 2001 From: Eric Hocking Date: Sun, 26 Jan 2025 18:11:32 -0500 Subject: [PATCH 02/19] fix style issues. --- src/Illuminate/Database/Schema/Blueprint.php | 3 ++- src/Illuminate/Support/Str.php | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Database/Schema/Blueprint.php b/src/Illuminate/Database/Schema/Blueprint.php index 8026efff8ac9..be443fe0ced2 100755 --- a/src/Illuminate/Database/Schema/Blueprint.php +++ b/src/Illuminate/Database/Schema/Blueprint.php @@ -1413,7 +1413,8 @@ public function foreignUlid($column, $length = 26) * @param int|null $length * @return \Illuminate\Database\Schema\ColumnDefinition */ - public function cuid($column = 'cuid', $length = 32) { + public function cuid($column = 'cuid', $length = 32) + { return $this->string($column, $length); } diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index e5b07cb90189..f07a08b1fa48 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -634,7 +634,7 @@ public static function isUlid($value) */ public static function isCuid($value) { - if (!is_string($value)) { + if (! is_string($value)) { return false; } From 21f09c635db9e3dc66ab0a00e81ed58763a5b31c Mon Sep 17 00:00:00 2001 From: Eric Hocking Date: Sun, 26 Jan 2025 18:15:35 -0500 Subject: [PATCH 03/19] fix blueprint style issues --- src/Illuminate/Database/Schema/Blueprint.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Illuminate/Database/Schema/Blueprint.php b/src/Illuminate/Database/Schema/Blueprint.php index be443fe0ced2..59d3e10f9234 100755 --- a/src/Illuminate/Database/Schema/Blueprint.php +++ b/src/Illuminate/Database/Schema/Blueprint.php @@ -1417,8 +1417,7 @@ public function cuid($column = 'cuid', $length = 32) { return $this->string($column, $length); } - - + /** * Create a new CUID column on the table with a foreign key constraint. * From cf0c69b7f21c0bc6651c1f31d78e0b7d39834bda Mon Sep 17 00:00:00 2001 From: Eric Hocking Date: Sun, 26 Jan 2025 18:17:56 -0500 Subject: [PATCH 04/19] fix stylci errors --- src/Illuminate/Database/Schema/Blueprint.php | 2 +- src/Illuminate/Support/Str.php | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Database/Schema/Blueprint.php b/src/Illuminate/Database/Schema/Blueprint.php index 59d3e10f9234..245b242411ad 100755 --- a/src/Illuminate/Database/Schema/Blueprint.php +++ b/src/Illuminate/Database/Schema/Blueprint.php @@ -1417,7 +1417,7 @@ public function cuid($column = 'cuid', $length = 32) { return $this->string($column, $length); } - + /** * Create a new CUID column on the table with a foreign key constraint. * diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index f07a08b1fa48..b05b9bcc37b8 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -643,9 +643,7 @@ public static function isCuid($value) $maxLength = 32; $length = strlen($value); - if ($length >= $minLength - && $length <= $maxLength - && preg_match('/^[a-z][0-9a-z]+$/', $value) + if ($length >= $minLength && $length <= $maxLength && preg_match('/^[a-z][0-9a-z]+$/', $value) ) { return true; } From cb496091bd7bcdb9df33149f1969c9d09a52c681 Mon Sep 17 00:00:00 2001 From: Eric Hocking Date: Sun, 26 Jan 2025 18:19:41 -0500 Subject: [PATCH 05/19] fix format in str class --- src/Illuminate/Support/Str.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index b05b9bcc37b8..c3c9b97062f7 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -643,8 +643,7 @@ public static function isCuid($value) $maxLength = 32; $length = strlen($value); - if ($length >= $minLength && $length <= $maxLength && preg_match('/^[a-z][0-9a-z]+$/', $value) - ) { + if ($length >= $minLength && $length <= $maxLength && preg_match('/^[a-z][0-9a-z]+$/', $value)) { return true; } From ad5a262a2be8307a7cfdc7976a2f60dc7610ed79 Mon Sep 17 00:00:00 2001 From: Eric Hocking Date: Sun, 26 Jan 2025 18:21:09 -0500 Subject: [PATCH 06/19] fix styleci spacing issue --- src/Illuminate/Support/Str.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index c3c9b97062f7..70ad62a5e28e 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -643,6 +643,7 @@ public static function isCuid($value) $maxLength = 32; $length = strlen($value); + if ($length >= $minLength && $length <= $maxLength && preg_match('/^[a-z][0-9a-z]+$/', $value)) { return true; } From d1d05af8401dd0794c5ebf734c82e7eb51d956f9 Mon Sep 17 00:00:00 2001 From: Eric Hocking Date: Sun, 26 Jan 2025 18:29:12 -0500 Subject: [PATCH 07/19] fix line issue --- src/Illuminate/Support/Str.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index 70ad62a5e28e..c3c9b97062f7 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -643,7 +643,6 @@ public static function isCuid($value) $maxLength = 32; $length = strlen($value); - if ($length >= $minLength && $length <= $maxLength && preg_match('/^[a-z][0-9a-z]+$/', $value)) { return true; } From c708ddcb2d19abdbd0dc81f1c8fd9410189a6117 Mon Sep 17 00:00:00 2001 From: Eric Hocking Date: Sun, 26 Jan 2025 18:29:39 -0500 Subject: [PATCH 08/19] add space back to fix styleci. --- src/Illuminate/Support/Str.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index c3c9b97062f7..70ad62a5e28e 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -643,6 +643,7 @@ public static function isCuid($value) $maxLength = 32; $length = strlen($value); + if ($length >= $minLength && $length <= $maxLength && preg_match('/^[a-z][0-9a-z]+$/', $value)) { return true; } From c96fbb53b3ccf54076943dd40aea83f18b90483c Mon Sep 17 00:00:00 2001 From: Eric Hocking Date: Sun, 26 Jan 2025 18:30:29 -0500 Subject: [PATCH 09/19] make styleci happy with LF formatting. --- src/Illuminate/Support/Str.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index 70ad62a5e28e..c3c9b97062f7 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -643,7 +643,6 @@ public static function isCuid($value) $maxLength = 32; $length = strlen($value); - if ($length >= $minLength && $length <= $maxLength && preg_match('/^[a-z][0-9a-z]+$/', $value)) { return true; } From c0aa78c69bff760309508ee9f273c4cc535db783 Mon Sep 17 00:00:00 2001 From: Eric Hocking Date: Sun, 26 Jan 2025 18:30:51 -0500 Subject: [PATCH 10/19] add LF space back for styleci formatting --- src/Illuminate/Support/Str.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index c3c9b97062f7..70ad62a5e28e 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -643,6 +643,7 @@ public static function isCuid($value) $maxLength = 32; $length = strlen($value); + if ($length >= $minLength && $length <= $maxLength && preg_match('/^[a-z][0-9a-z]+$/', $value)) { return true; } From 9e43f41bdbe00c2137347619edaa5fd6510605ec Mon Sep 17 00:00:00 2001 From: Eric Hocking Date: Sun, 26 Jan 2025 18:40:54 -0500 Subject: [PATCH 11/19] make if statement readable --- src/Illuminate/Support/Str.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index 70ad62a5e28e..507981b0f0d8 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -643,8 +643,9 @@ public static function isCuid($value) $maxLength = 32; $length = strlen($value); - - if ($length >= $minLength && $length <= $maxLength && preg_match('/^[a-z][0-9a-z]+$/', $value)) { + + if ($length >= $minLength && $length <= $maxLength + && preg_match('/^[a-z][0-9a-z]+$/', $value)) { return true; } From 71d9b2f211552ebb340be615d7c7c77e2694d4ad Mon Sep 17 00:00:00 2001 From: Eric Hocking Date: Sun, 26 Jan 2025 18:41:57 -0500 Subject: [PATCH 12/19] fix styleci issues --- src/Illuminate/Support/Str.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index 507981b0f0d8..518fea17c111 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -644,8 +644,7 @@ public static function isCuid($value) $length = strlen($value); - if ($length >= $minLength && $length <= $maxLength - && preg_match('/^[a-z][0-9a-z]+$/', $value)) { + if ($length >= $minLength && $length <= $maxLength && preg_match('/^[a-z][0-9a-z]+$/', $value)) { return true; } From 3cca8d0798ffd1822ecfa20547bfa8a827900aef Mon Sep 17 00:00:00 2001 From: Eric Hocking Date: Sun, 26 Jan 2025 18:44:07 -0500 Subject: [PATCH 13/19] fix spacing in if statement --- src/Illuminate/Support/Str.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index 518fea17c111..e09f3b84a1bb 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -644,7 +644,7 @@ public static function isCuid($value) $length = strlen($value); - if ($length >= $minLength && $length <= $maxLength && preg_match('/^[a-z][0-9a-z]+$/', $value)) { + if ($length >= $minLength && $length <= $maxLength && preg_match('/^[a-z][0-9a-z]+$/', $value)) { return true; } From 73a98e48783523eb99ec04c7c4364171e9f20064 Mon Sep 17 00:00:00 2001 From: Eric Hocking Date: Mon, 27 Jan 2025 12:50:37 -0500 Subject: [PATCH 14/19] add tests for Cuid validation --- tests/Database/DatabaseEloquentModelTest.php | 75 ++++++++++++++++++++ tests/Support/SupportStrTest.php | 48 +++++++++++-- 2 files changed, 116 insertions(+), 7 deletions(-) diff --git a/tests/Database/DatabaseEloquentModelTest.php b/tests/Database/DatabaseEloquentModelTest.php index 3271749ddb61..ec83ef2bd5ab 100755 --- a/tests/Database/DatabaseEloquentModelTest.php +++ b/tests/Database/DatabaseEloquentModelTest.php @@ -31,6 +31,7 @@ use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Concerns\HasUlids; use Illuminate\Database\Eloquent\Concerns\HasUuids; +use Illuminate\Database\Eloquent\Concerns\HasCuid; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\JsonEncodingException; @@ -2058,6 +2059,52 @@ public function testCloneModelMakesAFreshCopyOfTheModelWhenModelHasUlid() $this->assertEquals(['bar'], $clone->foo); } + public function testCloneModelMakesAFreshCopyOfTheModelWhenModelHasCuidPrimaryKey() + { + $class = new EloquentPrimaryCuidModelStub(); + $class->cuid = 'uzmbdw1jp9yvi2nm3s8zx27p'; + $class->exists = true; + $class->first = 'taylor'; + $class->last = 'otwell'; + $class->created_at = $class->freshTimestamp(); + $class->updated_at = $class->freshTimestamp(); + $class->setRelation('foo', ['bar']); + + $clone = $class->replicate(); + + $this->assertNull($clone->cuid); + $this->assertFalse($clone->exists); + $this->assertSame('taylor', $clone->first); + $this->assertSame('otwell', $clone->last); + $this->assertArrayNotHasKey('created_at', $clone->getAttributes()); + $this->assertArrayNotHasKey('updated_at', $clone->getAttributes()); + $this->assertEquals(['bar'], $clone->foo); + } + + public function testCloneModelMakesAFreshCopyOfTheModelWhenModelHasCuid() + { + $class = new EloquentNonPrimaryCuidModelStub(); + $class->id = 1; + $class->cuid = 'uzmbdw1jp9yvi2nm3s8zx27p'; + $class->exists = true; + $class->first = 'taylor'; + $class->last = 'otwell'; + $class->created_at = $class->freshTimestamp(); + $class->updated_at = $class->freshTimestamp(); + $class->setRelation('foo', ['bar']); + + $clone = $class->replicate(); + + $this->assertNull($clone->id); + $this->assertNull($clone->cuid); + $this->assertFalse($clone->exists); + $this->assertSame('taylor', $clone->first); + $this->assertSame('otwell', $clone->last); + $this->assertArrayNotHasKey('created_at', $clone->getAttributes()); + $this->assertArrayNotHasKey('updated_at', $clone->getAttributes()); + $this->assertEquals(['bar'], $clone->foo); + } + public function testModelObserversCanBeAttachedToModels() { EloquentModelStub::setEventDispatcher($events = m::mock(Dispatcher::class)); @@ -3728,6 +3775,34 @@ public function uniqueIds() } } +class EloquentPrimaryCuidModelStub extends EloquentModelStub +{ + use HasUuids; + + public $incrementing = false; + protected $keyType = 'string'; + + public function getKeyName() + { + return 'cuid'; + } +} + +class EloquentNonPrimaryCuidModelStub extends EloquentModelStub +{ + use HasUuids; + + public function getKeyName() + { + return 'id'; + } + + public function uniqueIds() + { + return ['cuid']; + } +} + #[ObservedBy(EloquentTestObserverStub::class)] class EloquentModelWithObserveAttributeStub extends EloquentModelStub { diff --git a/tests/Support/SupportStrTest.php b/tests/Support/SupportStrTest.php index f00c5e3d3dcf..361b45181d9b 100755 --- a/tests/Support/SupportStrTest.php +++ b/tests/Support/SupportStrTest.php @@ -47,8 +47,8 @@ public function testStringTitle() $this->assertSame('Laravel123', Str::title('laravel123')); $this->assertSame('Laravel123', Str::title('Laravel123')); - $longString = 'lorem ipsum '.str_repeat('dolor sit amet ', 1000); - $expectedResult = 'Lorem Ipsum Dolor Sit Amet '.str_repeat('Dolor Sit Amet ', 999); + $longString = 'lorem ipsum ' . str_repeat('dolor sit amet ', 1000); + $expectedResult = 'Lorem Ipsum Dolor Sit Amet ' . str_repeat('Dolor Sit Amet ', 999); $this->assertSame($expectedResult, Str::title($longString)); } @@ -130,7 +130,7 @@ public function testStringApa() public function testStringWithoutWordsDoesntProduceError(): void { - $nbsp = chr(0xC2).chr(0xA0); + $nbsp = chr(0xC2) . chr(0xA0); $this->assertSame(' ', Str::words(' ')); $this->assertEquals($nbsp, Str::words($nbsp)); $this->assertSame(' ', Str::words(' ')); @@ -560,6 +560,18 @@ public function testIsUuidWithInvalidUuid($uuid) $this->assertFalse(Str::isUuid($uuid)); } + #[DataProvider('validCuidList')] + public function testIsCuidWithValidCuid($cuid) + { + $this->assertTrue(Str::isCuid($cuid)); + } + + #[DataProvider('invalidCuidList')] + public function testIsCuidWithInvalidCuid($cuid) + { + $this->assertFalse(Str::isCuid($cuid)); + } + public function testIsJson() { $this->assertTrue(Str::isJson('1')); @@ -680,7 +692,7 @@ public function testWhetherTheNumberOfGeneratedCharactersIsEquallyDistributed() public function testRandomStringFactoryCanBeSet() { - Str::createRandomStringsUsing(fn ($length) => 'length:'.$length); + Str::createRandomStringsUsing(fn($length) => 'length:' . $length); $this->assertSame('length:7', Str::random(7)); $this->assertSame('length:7', Str::random(7)); @@ -712,7 +724,7 @@ public function testItCanSpecifyASequenceOfRandomStringsToUtilise() public function testItCanSpecifyAFallbackForARandomStringSequence() { - Str::createRandomStringsUsingSequence([Str::random(), Str::random()], fn () => throw new Exception('Out of random strings.')); + Str::createRandomStringsUsingSequence([Str::random(), Str::random()], fn() => throw new Exception('Out of random strings.')); Str::random(); Str::random(); @@ -748,7 +760,7 @@ public function testReplaceArray() $this->assertSame('foo/bar', Str::replaceArray('?', [1 => 'foo', 2 => 'bar'], '?/?')); $this->assertSame('foo/bar', Str::replaceArray('?', ['x' => 'foo', 'y' => 'bar'], '?/?')); // Test does not crash on bad input - $this->assertSame('?', Str::replaceArray('?', [(object) ['foo' => 'bar']], '?')); + $this->assertSame('?', Str::replaceArray('?', [(object)['foo' => 'bar']], '?')); } public function testReplaceFirst() @@ -1258,7 +1270,7 @@ public static function invalidUuidList() return [ ['not a valid uuid so we can test this'], ['zf6f8cb0-c57d-11e1-9b21-0800200c9a66'], - ['145a1e72-d11d-11e8-a8d5-f2801f1b9fd1'.PHP_EOL], + ['145a1e72-d11d-11e8-a8d5-f2801f1b9fd1' . PHP_EOL], ['145a1e72-d11d-11e8-a8d5-f2801f1b9fd1 '], [' 145a1e72-d11d-11e8-a8d5-f2801f1b9fd1'], ['145a1e72-d11d-11e8-a8d5-f2z01f1b9fd1'], @@ -1269,6 +1281,28 @@ public static function invalidUuidList() ]; } + public static function validCuidList() + { + return [ + ['zm2x9igk6ian853ux1iikr93'], + ['c0qapxb62ghwol2l8vpxryv8'], + ['zcodvpsaeznjd5ygsedu1il7'], + ['eb4nv3psuv9xqks4kca7ut74'], + ['xss09xjvzvulul04na8kkll0'], + ['xss09xjvzvulul04na8kkll0eb4v9xqk'], + ]; + } + + public static function invalidCuidList() + { + return [ + ['zm2x9igk6iAn853Ux1iikr93'], + ['c0qApXb62ghw@l2l8vpxryv8'], + ['not a valid cuid so we can test this'], + ['eb4nv3psuv9xqVSs4kZDvedZca7ut743'], + ]; + } + public static function strContainsProvider() { return [ From d1e6c9a35f6738ec57b2280232be5767f9f84eab Mon Sep 17 00:00:00 2001 From: Eric Hocking Date: Mon, 27 Jan 2025 13:17:47 -0500 Subject: [PATCH 15/19] add tests for Cuid database fields --- src/Illuminate/Database/Schema/Blueprint.php | 2 +- src/Illuminate/Database/Schema/Builder.php | 14 ++- .../Database/DatabaseSchemaBlueprintTest.php | 90 +++++++++++++++++++ .../Models/EloquentModelUsingCuid.php | 39 ++++++++ 4 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 tests/Database/Fixtures/Models/EloquentModelUsingCuid.php diff --git a/src/Illuminate/Database/Schema/Blueprint.php b/src/Illuminate/Database/Schema/Blueprint.php index 245b242411ad..71d84857e99d 100755 --- a/src/Illuminate/Database/Schema/Blueprint.php +++ b/src/Illuminate/Database/Schema/Blueprint.php @@ -1428,7 +1428,7 @@ public function cuid($column = 'cuid', $length = 32) public function foreignCuid($column, $length = 32) { return $this->addColumnDefinition(new ForeignIdColumnDefinition($this, [ - 'type' => 'string', + 'type' => 'char', 'name' => $column, 'length' => $length, ])); diff --git a/src/Illuminate/Database/Schema/Builder.php b/src/Illuminate/Database/Schema/Builder.php index 9af11e2e0836..63eb04c239c1 100755 --- a/src/Illuminate/Database/Schema/Builder.php +++ b/src/Illuminate/Database/Schema/Builder.php @@ -81,8 +81,8 @@ public static function defaultStringLength($length) */ public static function defaultMorphKeyType(string $type) { - if (! in_array($type, ['int', 'uuid', 'ulid'])) { - throw new InvalidArgumentException("Morph key type must be 'int', 'uuid', or 'ulid'."); + if (! in_array($type, ['int', 'uuid', 'ulid', 'cuid'])) { + throw new InvalidArgumentException("Morph key type must be 'int', 'uuid', 'ulid', or 'cuid'."); } static::$defaultMorphKeyType = $type; @@ -108,6 +108,16 @@ public static function morphUsingUlids() static::defaultMorphKeyType('ulid'); } + /** + * Set the default morph key type for migrations to CUIDs. + * + * @return void + */ + public static function morphUsingCuids() + { + static::defaultMorphKeyType('cuid'); + } + /** * Create a database in the schema. * diff --git a/tests/Database/DatabaseSchemaBlueprintTest.php b/tests/Database/DatabaseSchemaBlueprintTest.php index f92672b2f63e..d7bb9814e0fd 100755 --- a/tests/Database/DatabaseSchemaBlueprintTest.php +++ b/tests/Database/DatabaseSchemaBlueprintTest.php @@ -390,6 +390,44 @@ public function testDefaultUsingNullableUlidMorph() ], $blueprint->toSql($connection, new MySqlGrammar)); } + public function testDefaultUsingCuidMorph() + { + Builder::defaultMorphKeyType('cuid'); + + $base = new Blueprint('comments', function ($table) { + $table->morphs('commentable'); + }); + + $connection = m::mock(Connection::class); + + $blueprint = clone $base; + + $this->assertEquals([ + 'alter table `comments` add `commentable_type` varchar(255) not null', + 'alter table `comments` add `commentable_id` char(32) not null', + 'alter table `comments` add index `comments_commentable_type_commentable_id_index`(`commentable_type`, `commentable_id`)', + ], $blueprint->toSql($connection, new MySqlGrammar)); + } + + public function testDefaultUsingNullableCuidMorph() + { + Builder::defaultMorphKeyType('cuid'); + + $base = new Blueprint('comments', function ($table) { + $table->nullableMorphs('commentable'); + }); + + $connection = m::mock(Connection::class); + + $blueprint = clone $base; + + $this->assertEquals([ + 'alter table `comments` add `commentable_type` varchar(255) null', + 'alter table `comments` add `commentable_id` char(32) null', + 'alter table `comments` add index `comments_commentable_type_commentable_id_index`(`commentable_type`, `commentable_id`)', + ], $blueprint->toSql($connection, new MySqlGrammar)); + } + public function testGenerateRelationshipColumnWithIncrementalModel() { $base = new Blueprint('posts', function ($table) { @@ -435,6 +473,27 @@ public function testGenerateRelationshipColumnWithUuidModel() ], $blueprint->toSql($connection, new MySqlGrammar)); } + public function testGenerateRelationshipColumnWithCuidModel() + { + $base = new Blueprint('posts', function ($table) { + $table->foreignIdFor(Fixtures\Models\EloquentModelUsingCuid::class); + }); + + $connection = m::mock(Connection::class); + + $blueprint = clone $base; + + $this->assertEquals([ + 'alter table "posts" add column "model_using_cuid_id" char(32) not null', + ], $blueprint->toSql($connection, new PostgresGrammar)); + + $blueprint = clone $base; + + $this->assertEquals([ + 'alter table `posts` add `model_using_cuid_id` char(32) not null', + ], $blueprint->toSql($connection, new MySqlGrammar)); + } + public function testGenerateRelationshipColumnWithUlidModel() { $base = new Blueprint('posts', function (Blueprint $table) { @@ -534,6 +593,21 @@ public function testDropConstrainedRelationshipColumnWithIncrementalModel() ], $blueprint->toSql($connection, new MySqlGrammar)); } + public function testDropRelationshipColumnWithCuidModel() + { + $base = new Blueprint('posts', function ($table) { + $table->dropForeignIdFor(Fixtures\Models\EloquentModelUsingCuid::class); + }); + + $connection = m::mock(Connection::class); + + $blueprint = clone $base; + + $this->assertEquals([ + 'alter table `posts` drop foreign key `posts_model_using_cuid_id_foreign`', + ], $blueprint->toSql($connection, new MySqlGrammar)); + } + public function testDropConstrainedRelationshipColumnWithUuidModel() { $base = new Blueprint('posts', function ($table) { @@ -550,6 +624,22 @@ public function testDropConstrainedRelationshipColumnWithUuidModel() ], $blueprint->toSql($connection, new MySqlGrammar)); } + public function testDropConstrainedRelationshipColumnWithCuidModel() + { + $base = new Blueprint('posts', function ($table) { + $table->dropConstrainedForeignIdFor(Fixtures\Models\EloquentModelUsingCuid::class); + }); + + $connection = m::mock(Connection::class); + + $blueprint = clone $base; + + $this->assertEquals([ + 'alter table `posts` drop foreign key `posts_model_using_cuid_id_foreign`', + 'alter table `posts` drop `model_using_cuid_id`', + ], $blueprint->toSql($connection, new MySqlGrammar)); + } + public function testTinyTextColumn() { $base = new Blueprint('posts', function ($table) { diff --git a/tests/Database/Fixtures/Models/EloquentModelUsingCuid.php b/tests/Database/Fixtures/Models/EloquentModelUsingCuid.php new file mode 100644 index 000000000000..39e16cc317f0 --- /dev/null +++ b/tests/Database/Fixtures/Models/EloquentModelUsingCuid.php @@ -0,0 +1,39 @@ + Date: Mon, 27 Jan 2025 13:31:02 -0500 Subject: [PATCH 16/19] modify blueprint to use char instead --- src/Illuminate/Database/Schema/Blueprint.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Database/Schema/Blueprint.php b/src/Illuminate/Database/Schema/Blueprint.php index 71d84857e99d..d58e25e8dd06 100755 --- a/src/Illuminate/Database/Schema/Blueprint.php +++ b/src/Illuminate/Database/Schema/Blueprint.php @@ -1415,7 +1415,7 @@ public function foreignUlid($column, $length = 26) */ public function cuid($column = 'cuid', $length = 32) { - return $this->string($column, $length); + return $this->char($column, $length); } /** @@ -1655,7 +1655,7 @@ public function cuidMorphs($name, $indexName = null) { $this->string("{$name}_type"); - $this->string("{$name}_id", 32); + $this->cuid("{$name}_id", 32); $this->index(["{$name}_type", "{$name}_id"], $indexName); } @@ -1671,7 +1671,7 @@ public function nullableCuidMorphs($name, $indexName = null) { $this->string("{$name}_type")->nullable(); - $this->string("{$name}_id", 32)->nullable(); + $this->cuid("{$name}_id", 32)->nullable(); $this->index(["{$name}_type", "{$name}_id"], $indexName); } From 1b411672da629ab2936cf3b682adb644952261b4 Mon Sep 17 00:00:00 2001 From: Eric Hocking Date: Mon, 27 Jan 2025 13:45:52 -0500 Subject: [PATCH 17/19] fix cuid classes to use HasCuid --- tests/Database/DatabaseEloquentModelTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Database/DatabaseEloquentModelTest.php b/tests/Database/DatabaseEloquentModelTest.php index ec83ef2bd5ab..678d50987da5 100755 --- a/tests/Database/DatabaseEloquentModelTest.php +++ b/tests/Database/DatabaseEloquentModelTest.php @@ -3777,7 +3777,7 @@ public function uniqueIds() class EloquentPrimaryCuidModelStub extends EloquentModelStub { - use HasUuids; + use HasCuid; public $incrementing = false; protected $keyType = 'string'; @@ -3790,7 +3790,7 @@ public function getKeyName() class EloquentNonPrimaryCuidModelStub extends EloquentModelStub { - use HasUuids; + use HasCuid; public function getKeyName() { From f49f41480626d26537b1a3e0e7c55e8f85ef42c1 Mon Sep 17 00:00:00 2001 From: Eric Hocking Date: Mon, 27 Jan 2025 14:01:44 -0500 Subject: [PATCH 18/19] fix EloquentModelUsingCuid to import HasCuid --- tests/Database/Fixtures/Models/EloquentModelUsingCuid.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/Database/Fixtures/Models/EloquentModelUsingCuid.php b/tests/Database/Fixtures/Models/EloquentModelUsingCuid.php index 39e16cc317f0..60abf15d16a3 100644 --- a/tests/Database/Fixtures/Models/EloquentModelUsingCuid.php +++ b/tests/Database/Fixtures/Models/EloquentModelUsingCuid.php @@ -2,10 +2,13 @@ namespace Illuminate\Tests\Database\Fixtures\Models; +use Illuminate\Database\Eloquent\Concerns\HasCuid; use Illuminate\Database\Eloquent\Model; class EloquentModelUsingCuid extends Model { + use HasCuid; + /** * The table associated with the model. * From 53229ac57b8bbc58d873f140ee8b0ff7699452ae Mon Sep 17 00:00:00 2001 From: Eric Hocking Date: Mon, 27 Jan 2025 14:12:31 -0500 Subject: [PATCH 19/19] fix import order and styleci issues. --- tests/Database/DatabaseEloquentModelTest.php | 2 +- tests/Support/SupportStrTest.php | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/Database/DatabaseEloquentModelTest.php b/tests/Database/DatabaseEloquentModelTest.php index 678d50987da5..4bae13a7b9c6 100755 --- a/tests/Database/DatabaseEloquentModelTest.php +++ b/tests/Database/DatabaseEloquentModelTest.php @@ -29,9 +29,9 @@ use Illuminate\Database\Eloquent\Casts\AsStringable; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Database\Eloquent\Concerns\HasCuid; use Illuminate\Database\Eloquent\Concerns\HasUlids; use Illuminate\Database\Eloquent\Concerns\HasUuids; -use Illuminate\Database\Eloquent\Concerns\HasCuid; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\JsonEncodingException; diff --git a/tests/Support/SupportStrTest.php b/tests/Support/SupportStrTest.php index 361b45181d9b..7b268c329d4b 100755 --- a/tests/Support/SupportStrTest.php +++ b/tests/Support/SupportStrTest.php @@ -47,8 +47,8 @@ public function testStringTitle() $this->assertSame('Laravel123', Str::title('laravel123')); $this->assertSame('Laravel123', Str::title('Laravel123')); - $longString = 'lorem ipsum ' . str_repeat('dolor sit amet ', 1000); - $expectedResult = 'Lorem Ipsum Dolor Sit Amet ' . str_repeat('Dolor Sit Amet ', 999); + $longString = 'lorem ipsum '.str_repeat('dolor sit amet ', 1000); + $expectedResult = 'Lorem Ipsum Dolor Sit Amet '.str_repeat('Dolor Sit Amet ', 999); $this->assertSame($expectedResult, Str::title($longString)); } @@ -130,7 +130,7 @@ public function testStringApa() public function testStringWithoutWordsDoesntProduceError(): void { - $nbsp = chr(0xC2) . chr(0xA0); + $nbsp = chr(0xC2).chr(0xA0); $this->assertSame(' ', Str::words(' ')); $this->assertEquals($nbsp, Str::words($nbsp)); $this->assertSame(' ', Str::words(' ')); @@ -692,7 +692,7 @@ public function testWhetherTheNumberOfGeneratedCharactersIsEquallyDistributed() public function testRandomStringFactoryCanBeSet() { - Str::createRandomStringsUsing(fn($length) => 'length:' . $length); + Str::createRandomStringsUsing(fn ($length) => 'length:'.$length); $this->assertSame('length:7', Str::random(7)); $this->assertSame('length:7', Str::random(7)); @@ -724,7 +724,7 @@ public function testItCanSpecifyASequenceOfRandomStringsToUtilise() public function testItCanSpecifyAFallbackForARandomStringSequence() { - Str::createRandomStringsUsingSequence([Str::random(), Str::random()], fn() => throw new Exception('Out of random strings.')); + Str::createRandomStringsUsingSequence([Str::random(), Str::random()], fn () => throw new Exception('Out of random strings.')); Str::random(); Str::random(); @@ -760,7 +760,7 @@ public function testReplaceArray() $this->assertSame('foo/bar', Str::replaceArray('?', [1 => 'foo', 2 => 'bar'], '?/?')); $this->assertSame('foo/bar', Str::replaceArray('?', ['x' => 'foo', 'y' => 'bar'], '?/?')); // Test does not crash on bad input - $this->assertSame('?', Str::replaceArray('?', [(object)['foo' => 'bar']], '?')); + $this->assertSame('?', Str::replaceArray('?', [(object) ['foo' => 'bar']], '?')); } public function testReplaceFirst() @@ -1270,7 +1270,7 @@ public static function invalidUuidList() return [ ['not a valid uuid so we can test this'], ['zf6f8cb0-c57d-11e1-9b21-0800200c9a66'], - ['145a1e72-d11d-11e8-a8d5-f2801f1b9fd1' . PHP_EOL], + ['145a1e72-d11d-11e8-a8d5-f2801f1b9fd1'.PHP_EOL], ['145a1e72-d11d-11e8-a8d5-f2801f1b9fd1 '], [' 145a1e72-d11d-11e8-a8d5-f2801f1b9fd1'], ['145a1e72-d11d-11e8-a8d5-f2z01f1b9fd1'],