-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: add support for `uuid` and `ulid` primary keys resolves #26 * ci: reduce tests run by morph type * fix: add support for uuid and ulid in Laravel 9 * docs: update section about uuid and ulid support * Fix styling [skip-ci] * fix: make sure string was passed * ci: reduce tests run by morph type --------- Co-authored-by: marijoo <[email protected]>
- Loading branch information
Showing
8 changed files
with
269 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
<?php | ||
|
||
namespace Kolossal\Multiplex; | ||
|
||
use Illuminate\Database\Eloquent\ModelNotFoundException; | ||
use Illuminate\Support\Str; | ||
|
||
trait HasConfigurableMorphType | ||
{ | ||
/** | ||
* Initialize the trait. | ||
*/ | ||
public function initializeHasConfigurableMorphType(): void | ||
{ | ||
if (!$this->usesUniqueIdsInMorphType()) { | ||
return; | ||
} | ||
|
||
if (property_exists($this, 'usesUniqueIds')) { | ||
$this->usesUniqueIds = true; | ||
|
||
return; | ||
} | ||
|
||
static::creating(function (self $model) { | ||
foreach ($model->uniqueIds() as $column) { | ||
if (empty($model->{$column})) { | ||
$model->{$column} = $model->newUniqueId(); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
/** | ||
* Get the morph key type. | ||
*/ | ||
protected function morphType(): string | ||
{ | ||
if (!is_string(config('multiplex.morph_type'))) { | ||
return 'integer'; | ||
} | ||
|
||
if (in_array(config('multiplex.morph_type'), ['uuid', 'ulid'])) { | ||
return config('multiplex.morph_type'); | ||
} | ||
|
||
return 'integer'; | ||
} | ||
|
||
/** | ||
* Determine if unique ids are used in morphTo relation. | ||
*/ | ||
protected function usesUniqueIdsInMorphType(): bool | ||
{ | ||
return $this->morphType() !== 'integer'; | ||
} | ||
|
||
/** | ||
* Determine if the given value is a valid unique id. | ||
*/ | ||
protected function isValidUniqueMorphId(mixed $value): bool | ||
{ | ||
if (!is_string($value)) { | ||
return false; | ||
} | ||
|
||
if ($this->morphType() === 'ulid') { | ||
return Str::isUlid($value); | ||
} | ||
|
||
if ($this->morphType() === 'uuid') { | ||
return Str::isUuid($value); | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* Get the columns that should receive a unique identifier. | ||
*/ | ||
public function uniqueIds(): array | ||
{ | ||
if (!$this->usesUniqueIdsInMorphType()) { | ||
return []; | ||
} | ||
|
||
return [$this->getKeyName()]; | ||
} | ||
|
||
/** | ||
* Generate a new UUID for the model. | ||
*/ | ||
public function newUniqueId(): ?string | ||
{ | ||
if (!$this->usesUniqueIdsInMorphType()) { | ||
return null; | ||
} | ||
|
||
if ($this->morphType() === 'ulid') { | ||
return strtolower((string) Str::ulid()); | ||
} | ||
|
||
return (string) Str::orderedUuid(); | ||
} | ||
|
||
/** | ||
* Retrieve the model for a bound value. | ||
* | ||
* @param \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Relations\Relation $query | ||
* @param mixed $value | ||
* @param string|null $field | ||
* @return \Illuminate\Database\Eloquent\Relations\Relation | ||
* | ||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException | ||
*/ | ||
public function resolveRouteBindingQuery($query, $value, $field = null) | ||
{ | ||
if (!$this->usesUniqueIdsInMorphType()) { | ||
return parent::resolveRouteBindingQuery($query, $value, $field); | ||
} | ||
|
||
if ($field && is_string($value) && in_array($field, $this->uniqueIds()) && !$this->isValidUniqueMorphId($value)) { | ||
/** @var \Illuminate\Database\Eloquent\Model $this */ | ||
throw (new ModelNotFoundException)->setModel(get_class($this), $value); | ||
} | ||
|
||
if (!$field && is_string($value) && in_array($this->getRouteKeyName(), $this->uniqueIds()) && !$this->isValidUniqueMorphId($value)) { | ||
/** @var \Illuminate\Database\Eloquent\Model $this */ | ||
throw (new ModelNotFoundException)->setModel(get_class($this), $value); | ||
} | ||
|
||
return parent::resolveRouteBindingQuery($query, $value, $field); | ||
} | ||
|
||
/** | ||
* Get the auto-incrementing key type. | ||
*/ | ||
public function getKeyType(): string | ||
{ | ||
if (in_array($this->getKeyName(), $this->uniqueIds())) { | ||
return 'string'; | ||
} | ||
|
||
return $this->keyType; | ||
} | ||
|
||
/** | ||
* Get the value indicating whether the IDs are incrementing. | ||
*/ | ||
public function getIncrementing(): bool | ||
{ | ||
if (in_array($this->getKeyName(), $this->uniqueIds())) { | ||
return false; | ||
} | ||
|
||
return $this->incrementing; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -56,6 +56,7 @@ | |
*/ | ||
class Meta extends Model | ||
{ | ||
use HasConfigurableMorphType; | ||
use HasFactory; | ||
use HasTimestamps; | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
<?php | ||
|
||
namespace Kolossal\Multiplex\Tests; | ||
|
||
use Illuminate\Foundation\Testing\RefreshDatabase; | ||
use Illuminate\Support\Facades\Schema; | ||
use Illuminate\Support\Str; | ||
use Kolossal\Multiplex\Tests\Mocks\Post; | ||
|
||
class PrimaryKeyTypesTest extends TestCase | ||
{ | ||
use RefreshDatabase; | ||
|
||
protected function refreshDatabaseWithType(string $type): void | ||
{ | ||
config()->set('multiplex.morph_type', $type); | ||
|
||
$this->artisan('migrate:fresh', $this->migrateFreshUsing()); | ||
$this->useDatabase(); | ||
} | ||
|
||
/** | ||
* @test | ||
* | ||
* @dataProvider morphTypes | ||
* */ | ||
public function it_uses_the_configured_column_type(string $type, string $column_type) | ||
{ | ||
$this->refreshDatabaseWithType($type); | ||
|
||
if (version_compare(app()->version(), '10.0.0', '>')) { | ||
$this->assertSame($column_type, Schema::getColumnType('meta', 'id')); | ||
} | ||
|
||
$this->assertSame($type, config('multiplex.morph_type')); | ||
|
||
$meta = Post::factory()->create()->saveMeta('foo', 'bar'); | ||
|
||
if (config('multiplex.morph_type') === 'uuid') { | ||
$this->assertTrue(Str::isUuid($meta->id)); | ||
} elseif (config('multiplex.morph_type') === 'ulid') { | ||
$this->assertTrue(Str::isUlid($meta->id)); | ||
} else { | ||
$this->assertIsInt($meta->id); | ||
} | ||
} | ||
|
||
public static function morphTypes(): array | ||
{ | ||
return [ | ||
'integer' => ['integer', 'integer'], | ||
'uuid' => ['uuid', 'varchar'], | ||
'ulid' => ['ulid', 'varchar'], | ||
]; | ||
} | ||
} |