首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用主键型UUID创建Laravel/雄辩关系的相关模型

问用主键型UUID创建Laravel/雄辩关系的相关模型
EN

Stack Overflow用户
提问于 2021-07-09 13:34:53
回答 1查看 843关注 0票数 0

Laravel: 7;PHP: 7.4/8.0

在我的项目中,我有两个相关的模型:User和TimeAccount。两者都使用UUID作为主键,密钥名仍然是id。

迁移:

代码语言:javascript
复制
class CreateUsersTable extends Migration
{
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->uuid('id')->primary();
            // snip...
        });
    }
    
    // snip...
}
代码语言:javascript
复制
class CreateTimeAccountsTable extends Migration
{
    public function up()
    {
        Schema::create('time_accounts', function (Blueprint $table) {
            $table->uuid('id')->primary();
            $table->foreignUuid('user_id')->nullable()->constrained();
            // snip...
        });
    }
    
    // snip...
}

模型:

代码语言:javascript
复制
class User
{
    use IsIdentifiedByUuid;

    protected $keyType = 'string';

    public function timeAccount()
    {
        return $this->hasOne(TimeAccount::class);
    }

    // snip...
}
代码语言:javascript
复制
class TimeAccount
{
    use IsIdentifiedByUuid;

    protected $keyType = 'string';

    public function user()
    {
        return $this->belongsTo(User::class);
    }

    // snip...
}

IsIdentifiedByUuid性状:

代码语言:javascript
复制
trait IsIdentifiedByUuid
{
    protected static function bootIsIdentifiedByUuid()
    {
        static::creating(fn ($model) => static::isIdentifiedByUuidCreatingHandler($model));
        static::saving(fn ($model) => static::isIdentifiedByUuidSavingHandler($model));
    }

    public function initializeIsIdentifiedByUuid()
    {
        $this->keyType = 'string';
    }

    protected function getUuidColumn(): string
    {
        return property_exists($this, 'uuid_column') ? $this->uuid_column : 'id';
    }

    protected static function getNewUuid(): UuidInterface
    {
        $columnName = app(static::class)->getUuidColumn();
        $tableName = app(static::class)->getTable();
        $columnDescriptor = "$tableName.$columnName";
        $uuid = "";
        $query = \DB::table($tableName)
            ->select($columnDescriptor)
            ->where($columnDescriptor, $uuid);
        $attempts = 0;
        do {
            if ($attempts >= \App\Constants\Uuid::MAX_ATTEMPTS) {
                throw new UuidMaxGeneratorAttemptsExceededException();
            }
            $uuid = Str::uuid();
            $attempts++;
        } while ($query->setBindings([ $uuid->toString() ])->count() > 0);

        return $uuid;
    }

    /**
     * Handles the creation of a model.
     * - Generates new UUID if the UUID column is empty and auto-increment is enabled
     *
     * @param Model $model
     * @throws \App\Exceptions\UuidMaxGeneratorAttemptsExceededException
     */
    protected static function isIdentifiedByUuidCreatingHandler(Model $model)
    {
        $columnName = $model->getUuidColumn();
        if ($model->getIncrementing() && !$model->{$columnName}) {
            $uuid = static::getNewUuid();
            \Log::debug(
                "IsIdentifiedByUuid [CREATING]:" .
                " Generating new UUID for `" . get_class($model) . ": $uuid`"
            );
            $model->{$columnName} = $uuid->toString();
        } else {
            \Log::debug(
                "IsIdentifiedByUuid [CREATING]:" .
                " Using existing UUID for `" . get_class($model) . ": $model->{$columnName}`"
            );
        }
    }

    /**
     * Handles the saving of a Model.
     * - Prevents changes to the UUID column
     * - Rolls back changed value to old value
     *
     * @param Model $model
     */
    protected static function isIdentifiedByUuidSavingHandler(Model $model)
    {
        $columnName = $model->getUuidColumn();
        $originalUuid = $model->getOriginal($columnName);
        if (!is_null($originalUuid) &&
            $originalUuid !== $model->{$columnName}
        ) {
            \Log::debug(
                "IsIdentifiedByUuid [SAVING]:" .
                " Prevented change of UUID for `" . get_class($model) . ":$originalUuid`"
            );
            $model->{$columnName} = $originalUuid;
        }
    }
}

现在的问题:

在创建的钩子上的用户观察者中,我正在这样做:

代码语言:javascript
复制
$userTimeAccount = $user->timeAccount()->create([
    // snip...
]);

到目前为止,在切换到UUID而不是整数键之前,一切都很好!据我所知,在用户模型被保存之前(显然是在auto被触发之前)创建相关模型是雄辩/Laravel明确允许的(尽管我找不到文档的部分)。

切换到UUID之后,它不再工作了,我得到了一个SQL错误:

SQLSTATE23000:完整性约束违反: 1452不能添加或更新子行:外键约束失败(laravel_torus.time_accounts,约束time_accounts_user_id_foreign外键(user_id)引用users (id)) (SQL: insert insert into time_accounts (time_account_type_id,balance,borrow,user_id,id,updated_at,created_at)值(3,0,0,0,0,a 995807b-b7e1-4af3-96d-7c48f97f943d,2021-07-09 :54:25):2021-07-09 12:54:25)

SQL美化:

代码语言:javascript
复制
insert into `time_accounts` 
    (`time_account_type_id`, `balance`, `borrow`, `user_id`, `id`, `updated_at`, `created_at`)
    values 
    (3, 0, 0, 0, 'bad86e70-7496-4b42-b2a1-5cc3c5a4d06c', '2021-07-09 11:19:17', '2021-07-09 11:19:17');

不知何故,Laravel试图为外键插入0(整数),这显然是行不通的。我甚至尝试手动将keyType设置为'string',但没有效果。

我哪里错了?

编辑:,似乎存在种族条件。观察者的创建钩子将在User模型的创建钩子之前触发。这将导致缺少ID --在使用整数和自动增量时,ID是存在的,就像它应该的那样。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-07-16 07:46:31

TL;DR

I无法用MySQL解决这个问题,于是切换到Postgres。

不要在MYSQL中使用 UUID ,除非您确信: 1.您需要使用,2.性能影响可以忽略不计!

我实在无法解释这个错误是如何发生的。该ID已经存在(与调试器一起查看模型的$attribnutes数组),在调用$user->id时,Laravel仍将返回整数$user->id。

在研究这个问题时,我注意到Laravel正在为UUID创建字符串列,而不是二进制(wich正是我所期望的)。由于与整数/二进制比较相比,字符串比较非常昂贵,所以我决定放弃MySQL。UUID在这个项目中是必需的,将来转向另一个数据库管理系统,而不是MySQL,已经是意料之中的了。

如果定义了UUID列,uuid将使用Postgres支持特殊的列类型。这不仅可以将ID作为二进制进行存储和比较,还可以检查格式和唯一值,这意味着您不能意外地输入无效的UUID,比如在MySQL常规sting列中,除了not null和unique之外,似乎根本没有检查。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/68317720

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档