在Laravel中实现UUID主要密钥及其利益
#php #laravel #database #uuid

嘿,dev.to to Community!

自从我上次写在这里以来已经很长时间了。

在我缺席的那段时间里,我一直在研究一些大规模项目,在这些项目中,我看到了UUID如何以多种方式使您的应用程序受益。

现在,我一直在使用UUID作为我的主要钥匙结构,但实际上是在完成项目后表现出的真正优势,我很高兴我决定这样做。

我主要将Laravel用作后端,并喜欢使用MySQL(我知道那里还有很多其他RDBMS/DBMS,但我喜欢MySQL,并且对其感到满意,并且对此感到满意)。尽管使用MySQL或许多其他数据库时,您的主要密钥是将AI设置为AI(自动启动),并且数据类型是整数,但是您可以使用一些调整来使用UUID,并相信我值得这很麻烦。

>

Angry type

编码时me。

好处

枚举利用修复

枚举利用是可以预测的。假设您例如,您将视频上传到YouTube上,该视频隐藏在公众中,只能由您共享链接的人查看。好吧,让我们看一下YouTube链接:

https://www.youtube.com/watch?v=3wVTmlD86a
我最喜欢的歌曲btw

您可以看到地址在v=3wVTmlD86a中结束,该地址指示了视频的(假设)ID。因此,请想象YouTube使用了数值索引,并为上传的每个视频逐一递增。那你可能会问什么。在这种情况下,某人可以从数字1开始,并使用自定义程序来尝试每个链接,以在YouTube上提取所有可用的视频,并且您的仅链接视频也将暴露。

这称为枚举利用,可能会导致严重的数据泄漏,具体取决于您的程序应该做什么。

YouTube算法用来为其视频制作ID,但我的描述的目的对于UUID是相同的。

给出了诸如700234f5-0e45-452e-ae3a-70b4b3d024e1之类的uuid,因为没有订单,您将不知道在此之前或之后的uuid是什么。如您所见,这可以解决应用程序中的枚举利用。

水平可扩展的数据库

让我们想象,您有两个数据库实例,这些数据库实例是您要扩展的同一系统,或者出于某种原因仅制作一个新集群,然后合并两个实例。鉴于数据库结构是相同的,并且数据库中的关系复杂,您可能会发现很难在不手动或使用自动任务调整一些值的情况下合并数据。

Database diagram

您可以在抽象数据库的上面的简单图中看到,您可能有一个与产品和用户相关的订单表。

现在让我们考虑一下:在每个数据库实例中,您只有10个用户和10个产品和10个订单,这些订单在1到10上的数字索引。让我们在我们的第一个数据库实例订单2中与用户3有关名称为Adnan Babakan。在另一种情况下,订单2也与使用3有关,但它们的名称为Arian Amini。即使我们正确导入订单且未从其他表中引用其ID,并且它们的索引从1重新分配至20,因为现在我们的订单也被重新索引了,我们的订单也引用了错误的用户!

我想您现在看到问题和解决方案。如果我们使用UUID,则由于它们在全球范围内是唯一的,因此我们的数据之间不会发生冲突,我们可以随心所欲地合并而没有任何问题。

除了合并两个数据库之外,UUID还可以通过同时运行的多个数据库实例来更轻松地管理基于群集的数据库体系结构。

缺点

在数据库中使用UUID有两个主要缺点。首先是它在数据库中的大小,其次是插入问题。

由于未订购执行插入操作的UUID会变得昂贵,因为您的记录会在使用数值索引系统时随机插入到桌子的末端。

即使存在这些缺点,也可以原谅,因为空间并不昂贵,在大多数情况下,插入问题并不是什么大的。

有一种方法可以避免使用与以前相同的数值索引系统的插入问题,但也为每个记录分配一个单独的UUID,因此您从上述点中受益。


在Laravel中使用UUID

在本文的这一部分中,我将讨论您如何在Laravel应用程序中实现UUID,以及我面临的问题以及如何解决这些问题。请记住,UUID不仅用于Laravel/MySQL,您可以在您喜欢的任何其他编程语言,框架和数据库中使用它们。

请记住,这是我实施这种方法的首选方法,您可能会找到一种更有效的方法,如果您这样做,请在评论中告诉我。

迁移

您需要做的第一件事就是知道如何定义迁移,因此您的桌子使用UUID。

幸运的是,Laravel配备了许多好方法和东西来帮助您实现目标。

首先,让我们更改如何在迁移中定义PK(主要键)。这就是您通常在Laravel迁移中定义PK的方式:

$table->id();

上面的代码等效于:

$table->integer('id')->primary();

不幸的是,没有用于在迁移中创建基于UUID的PK的速写方法。但这也不是那么多:

$table->uuid('id')->primary();

tada!现在,您的表将有一个名为id的列,该列容纳UUID。

参考和关系

现在让我们谈谈关系。关系是当您想从另一表或同一表引用不同的记录时。引用是使用目标记录的ID完成的,因为它是您确保永远不会重复的唯一键。

想象您要在表中引用用户,这就是您在迁移中定义正常关系的方式:

$table->foreignId('user_id')->constrained();

请记住,constrained()方法是:

$table->foreignId('user_id')->references('id')->on('users');

laravel以遵循命名惯例为代价赋予您速记的力量。因此,当您的外国ID列命名为user_id,然后称为constrained()时,Laravel知道您的意思是该user_id将在users表上引用id列。如果不是这种情况,则必须使用references()on()方法来定义它,以专门确定您的意思。

要引用uuid的外id ID,您可以使用foreignUuid()方法而不是foreignId()方法。其余的是相同的,您也可以将constrained()应用于外国UUID参考。这就是您给定users表将uuid用作其PK结构的用户记录的方式。

$table->foreignUuid('user_id')->constrained();

或更详细:

$table->foreignUuid('user_id')->references('id')->on('users');

楷模

既然您已经成功定义了迁移,则需要对模型进行一些小调整。

模型的默认值告诉它使用基于整数的ID,我们应该告诉模型并非如此。

首先,将一个名为$keyType的私人属性添加到您的模型中,并将其值设置为'string'

protected $keyType = 'string';

这告诉模型,您的密钥是字符串,而不是整数(UUID是字符串)。

第二件事是告诉模型不要将增量系统用于此类键,这是通过将$incrementing属性设置为false
来完成的。

public $incrementing = false;

执行这些模型后,您的模型知道您的PK的工作原理。

但是等等!创建新唱片时,您是否会遇到问题,告诉您id不能无效并且没有默认值?

要解决此问题,您要么每次创建一个新记录时都必须定义一个UUID:

$new_user = new User();
$new_user->id = Str::uuid();
$new_user->username = 'Adnan';
$new_user->password = Hash::make('helloWorld');
$new_user->save();

Str类是从Illuminate\Support\Str导入的。 Str::uuid()助手为您创建了一个新的UUID。

或者您可以使用模型事件来告诉Laravel如何在数据库上创建记录时为模型创建ID。

这可以简单地使用booted方法中的封闭事件来完成。在您的模型中添加称为booted的静态方法:

public static function booted() {

}

然后,您可以在其中使用称为creating的事件,并告诉它制作uuid并将其分配到模型的ID:

public static function booted() {
    static::creating(function ($model) {
        $model->id = Str::uuid();
    });
}

您可以在https://laravel.com/docs/10.x/eloquent#events-using-closures

上阅读有关模型事件的更多信息

如果您使用的是旧的boot方法,请记住调用父级的boot方法,以免被覆盖:

public static function boot() {
    parent::boot();

    static::creating(function ($model) {
        $model->id = Str::uuid();
    });
}

如果您已经遵循到目前为止,那么您的User模型或任何其他模型应该看起来像这样:

class User extends Model {
    use HasFactory;

    ...

    protected $keyType = 'string';

    public $incrementing = false;

    ...

    public static function boot() {
        parent::boot();

        static::creating(function ($model) {
            $model->id = Str::uuid();
        });
    }
}

解决圣所问题

如果您尝试使用Sanctum发布您的令牌,您可能会意识到您会遇到一个错误,告诉您您的约束失败。这是因为您的personal_access_tokens表旨在引用基于整数的外国ID(该表使用了变形,因为它应该能够参考多种类型的模型)。

如果您已经运行了一个项目,则应使用新迁移在personal_access_tokens表中更改tokenable_id列的类型。

首先,创建一个新的迁移:

php artisan make:migration change_tokenable_id_type_in_personal_access_tokens_table

然后在您的迁移中使用change()方法在tokenable_id列上:

$table->foreignUuid('tokentable_id')->change();

这将tokenable_id的数据类型更改为引用UUID而不是整数。

要使我们的数据库结构统一,我想使我的令牌记录也使用UUID作为其ID,因此也添加下面的说明:

$table->uuid('id')->primary()->change();

如果您收到运行此迁移的错误,请使用作曲家安装doctrine/dbal软件包。

composer require doctrine/dbal

尽管上述方法是更改​​tokenable_id列的数据类型的正确方法,但是如果您只是启动新项目或您不关心当前数据并且是,则有一种更简单的方法愿意刷新您的迁移(将删除您的数据)。

只需打开2019_12_14_000001_create_personal_access_tokens_table.php文件(开头的日期可能有所不同),然后更改:

$table->morphs('tokenable');

to:

$table->uuidMorphs('tokenable');

和更改:

$table->id();

to:

$table->uuid('id')->primary();

然后运行:

php artisan migrate

或:

php artisan migrate:fresh

如果您已经运行了迁移。

现在,我们的personal_access_tokens可以引用UUID及其ID也是一个UUID,我们需要使Laravel知道如何对待我们的令牌,因为Sanctum使用默认模型来处理这些记录。

首先,让我们创建一个新模型,该模型将是Sanctum使用的模型,因为我们已经更改了结构:

php artisan make:model PersonalAccessToken

现在打开创建模型的文件,而不是扩展Model类,而是通过Laravel\Sanctum\PersonalAccessToken扩展类:

use Laravel\Sanctum\PersonalAccessToken as SanctumPersonalAccessToken;

class PersonalAccessToken extends SanctumPersonalAccessToken
{

}

由于我们的班级的名称与Sanctum的默认个人AccessToken类所使用的类相同,因此您在导入时应该定义别名,或在extend关键字之后使用绝对命名,例如:\Laravel\Sanctum\PersonalAccessToken

我们必须确保Laravel像以前一样知道该模型的工作方式。因此,我们应该设置$keyType$incrementing并定义一个事件,以为每个记录的ID创建一个UUID。

这就是您的最终PersonalAccessToken型号的样子:

<?php

namespace App\Models;

...
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Support\Str;
use Laravel\Sanctum\PersonalAccessToken as SanctumPersonalAccessToken;
...

class PersonalAccessToken extends SanctumPersonalAccessToken
{
    use HasFactory;

    public $keyType = 'string';

    public $incrementing = false;

    ....

    public static function boot()
    {
        parent::boot();

        static::creating(function ($model) {
            $model->id = (string) Str::uuid();
        });
    }

    ...
}

最后一步是将该模型定义为Sanctum使用的模型。可以使用Sanctum::usePersonalAccessTokenModel()方法完成此步骤。该方法应在提供商文件中调用。我更喜欢位于app/Providers/AppServiceProvider.phpAppServiceProvider

打开提供商文件,并在boot方法中添加下面的代码:

Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);

记住正确导入Sanctum和您的自定义PersonalAccessToken课程。 Sanctum类是从Laravel\Sanctum\Sanctum导入的,您的自定义PersonalAccessToken是从App\Models\PersonalAccessToken导入的。

现在您已经完成了Laravel应用程序中实现UUID!


我希望您喜欢这篇文章,它可以帮助您完成想做的事情。请让我知道本文中是否有任何错误,或者您知道一种更好的方法。

顺便说一句!在此处查看我的免费node.js Essentials电子书: