Skip to content

Instantly share code, notes, and snippets.

@dereckmartin
Last active December 6, 2023 07:55
Show Gist options
  • Save dereckmartin/06eaf720c50bb5ba1c81d83106cb7bc8 to your computer and use it in GitHub Desktop.
Save dereckmartin/06eaf720c50bb5ba1c81d83106cb7bc8 to your computer and use it in GitHub Desktop.
Laravel Trait to support TSID (string) primary key support that generates IDs automatically on insert.
<?php
/**
* Thank you to https://github.com/odan/tsid for this package.
*
* This isn't perfect, and I don't know any further method to test for
* for the validity of the TSID, but this is a start.
*
* I couldn't get Laravel 10 to play nice with bigIntegers as primary key.
* Laravel kept trying to add auto_increment on table create during migration.
* I had to follow the docs to use the following:
*
* Migration:
* $table->string('id', 13)
* ->nullable(false)
* ->unique();
*
* $table->primary('id')
*
* In your Models where this will be implemented:
*
* use App\Traits\Database\HasTsids;
*
* class PromptStyle extends Model
* {
* ...
* use HasTsids;
* public $incrementing = false;
* protected $keyType = 'string';
* ...
* }
*
* POST Request Response:
*
* * Connection #0 to host xxxxx left intact
* {"prompt":"test","id":"0EDZ6E1648XS6"}
*
* Database:
*
* MariaDB [xxxxxxx]> select * from prompt_styles;
* +---------------+--------+------------+
* | id | prompt | created |
* +---------------+--------+------------+
* | 0EDZ6E1648XS6 | test | 1701847279 |
* +---------------+--------+------------+
* 1 row in set (0.000 sec)
*
*
* Update the namespace to where you plan on installing this...
*/
namespace App\Traits\Database;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Odan\Tsid\TsidFactory;
trait HasTsids
{
/**
* Initialize the trait.
*
* @return void
*/
public function initializeHasTsids()
{
$this->usesUniqueIds = true;
}
/**
* Get the columns that should receive a unique identifier.
*
* @return array
*/
public function uniqueIds()
{
return [$this->getKeyName()];
}
/**
* Generate a new UUID for the model.
*
* @return string
*/
public function newUniqueId()
{
$tsid = new TsidFactory();
return (string) $tsid->generate()->toString();
}
/**
* 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 ($field && in_array($field, $this->uniqueIds()) && ! $this->isValidTsidStr($value)) {
throw (new ModelNotFoundException)->setModel(get_class($this), $value);
}
if (! $field && in_array($this->getRouteKeyName(), $this->uniqueIds()) && ! $this->isValidTsidStr($value)) {
throw (new ModelNotFoundException)->setModel(get_class($this), $value);
}
return parent::resolveRouteBindingQuery($query, $value, $field);
}
/**
* Get the auto-incrementing key type.
*
* @return string
*/
public function getKeyType()
{
if (in_array($this->getKeyName(), $this->uniqueIds())) {
return 'string';
}
return $this->keyType;
}
/**
* Get the value indicating whether the IDs are incrementing.
*
* @return bool
*/
public function getIncrementing()
{
if (in_array($this->getKeyName(), $this->uniqueIds())) {
return false;
}
return $this->incrementing;
}
public function isValidTsidStr(string $tsid_str): bool
{
if (13 !== \strlen($tsid_str)) {
return false;
}
if (13 !== strspn($tsid_str, '0123456789ABCDEFGHJKMNPQRSTVWXYZ')) {
return false;
}
return true;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment