guava / laravel-populator
一个用于填充测试和生产数据的laravel包。
Requires (Dev)
- larastan/larastan: ^2.9
- laravel/pint: ^1.16
- orchestra/testbench: ^8.0
- phpstan/phpstan: ^1.11
- spatie/phpunit-watcher: ^1.24
README
laravel-populator的目标是提供一种统一的方式将预定义数据填充到您的数据库中,同时保持您的迁移保持完整。
关于如何做到这一点有许多教程和意见。有些人手动将数据插入到迁移中的数据库中,有些人使用种子器。在两种情况下,许多人倾向于使用Model::create()
,这乍一看似乎是 simplest 方法,它甚至有效。然而,如果您将来更改Model的结构,您的迁移可能会停止工作。例如,当调整fillable
属性时。
laravel-populator解决了这个问题,同时在使用数据库数据时保持了出色的开发者体验。
安装
您可以使用composer安装此包
composer require guava/laravel-populator
如果您打算启用跟踪,则可以可选地发布数据库迁移
php artisan vendor:publish --provider 'Guava\LaravelPopulator\PopulatorServiceProvider' --tag 'migrations' php artisan migrate
或发布配置
php artisan vendor:publish --provider 'Guava\LaravelPopulator\PopulatorServiceProvider' --tag 'config'
目前提供以下选项
config/populator.php
return [ 'tracking' => false, 'population_model' => '\Guava\LaravelPopulator\Models\Population', ];
工作原理
Laravel Populator引入了三个主要术语
填充器:这些基本上是您/database/populators
文件夹内的命名文件夹。它们包含包。
包:包是特定模型的记录集合。包是填充器的一部分,用于定义记录的模型、默认属性或突变。
记录:记录是最小的单元,它代表一个等待填充的数据库记录。记录是一个php文件,它返回一个数组,其中包含key => value
对,描述了您的模型/数据库条目。
示例
2023_01_20_203807_populate_initial_data.php
:
return new class extends Migration { public function up() { Populator::make('initial') // // bundles are located in /database/populators/initial/ ->environments(['local', 'testing']) ->bundles([ Bundle::make(User::class) ->mutate('password', fn($value) => Hash::make($value)) ->records([ 'admin' => [ 'name' => 'Administrator', 'email' => 'admin@example.tld', 'password' => 'my-strong-password', ], ]), Bundle::make(Tag::class, 'my-tags'), // records are located in /database/populators/initial/my-tags/ Bundle::make(Post::class) // records are located in /database/populators/initial/post/ ->generate('slug', fn(array $attributes) => Str::slug($attributes['name'])), Bundle::make(Permission::class) // records are located in /database/populators/initial/permission/ ->default('guard_name', 'web'), Bundle::make(Role::class) // records are located in /database/populators/initial/role/ ->default('guard_name', 'web'), ]); } }
示例记录 /database/populators/initial/post/example-post.php
<?php return [ 'name' => 'Example post', 'content' => 'Lorem ipsum dolor sit amet', 'author' => 'admin', // could also be ID or specific column:value, like email:admin@example.tld 'tags' => ['Technology', 'Design', 'Off-topic'], ];
用法
使用生成器命令
进行中
手动
首先,您需要使用以下命令创建迁移
php artisan make:migration populate_initial_data
在迁移内部,您必须定义您的填充器和它的包
Populator::make('v1') ->bundles([ Bundle::make(User::class), ]) ->call()
现在您需要创建目录结构。由于我们命名为填充器initial
并且只有一个用于User
模型的包,我们的结构将是
/database/
/populators/
/initial/
/user/
现在我们可以向用户包添加尽可能多的记录。要做到这一点,只需在相应的文件夹中创建一个php文件
并命名为您想要的名称(它必须在包内是唯一的)。让我们创建john-doe.php
来创建我们的第一个用户,John Doe
<?php return [ 'name' => 'John Doe', 'email' => 'john.doe@example.tld', 'password' => 'my-strong-password', ];
就这样!当运行迁移时,它将创建填充器的包中的所有记录。
请注意,密码不会被散列,为了散列所有密码或了解更多关于所有定制选项的信息,请参阅下面的文档。
填充器
填充器充当您想要填充的记录包的组。这种分组的原因是在您的应用程序的生命周期中,您可能希望在生产过程中间添加另一批数据到您的应用程序中。众所周知,开发者不喜欢想名字,为了避免出现users1
、users2
、users-new
、yet-another-batch-of-users
等名称的包,我们决定将它们分组到填充器中,这样您只需要想一个名字。 :)
如果您在生产过程中需要中间填充数据,我们建议根据您的版本命名您的填充器,例如v1.0
、v1.1
、v2.0
等等。
调用填充器
填充器是这个包提供一切功能的入口点。您可以从任何想要的位置调用填充器,但我们建议从迁移中调用它们,如下所示
Populator::make('v1') ->bundles([ // Your bundles here]) ->call()
这将调用您的填充器和所有定义的包(更多信息请参阅“包”部分)
反转填充器
如果填充器运行时启用了跟踪,则可以删除填充器插入的记录。
Populator::make('v1') ->bundles([//your bundles to reverse or leave blank for all bundles in the populator]) ->rollback()
回滚将使用以下条件进行筛选
- 填充器名称
- 包模型类
- 包名称
这允许您控制从填充器回滚哪些包。
例如,您可以回滚填充器中的一部分包
Populator::make('initial') ->bundles([ Bundle::make(User::class), Bundle::make(Post::class), ]) ->call(); //User and Post entries now exist Populator::make('initial') ->bundles([Bundle::make(Post::class)]) ->rollback(); //Post entries were removed
环境
填充器可以设置为仅在特定环境中执行。您可能最想为本地环境和生产环境提供不同的数据。
您可以使用environments
方法轻松做到这一点
Populator::make('v1') ->environments(['local']) ... ->call()
此填充器将仅在本地环境中执行。
包
包类似于所有记录的蓝图,它们定义默认属性或常用修饰符,以便您不需要在每条记录中重复它们。这是通过以下描述的附加方法链来实现的。
创建包就像这样简单
Bundle::make(Model::class, 'optional-name'),
传递名称是可选的,并定义填充器目录内的目录名称。如果省略,名称将从模型的类名自动生成。例如,对于模型Foo
,名称将为foo
,对于模型FooBar
,它将是foo-bar
。
环境
与填充器类似,您也可以为每个包分别定义特定环境。为此,在包本身上链式调用environments
方法
Bundle::make(User::class) ->environments(['production'])
修饰符
您可以在模型的任何属性上定义修饰符,以在将其存储到数据库之前修改其值。
例如,您可能想对所有密码进行哈希处理
Bundle::make(User::class) ->mutate('password', fn($value) => Hash::make($value))
默认
如果您未设置,可以为所有记录定义默认属性。如果您有很多具有相同属性的记录,这很有用。
例如,您可能想将guard_name
添加到所有权限(spatie/laravel-permission)
Bundle::make(Permission::class) ->default('guard_name', 'web'),
生成
如果您想为属性设置默认或生成值,您可以在包上链式调用一个generated()
方法。
一个常见的用例可能是,例如,如果您想从其他属性生成一个别名
Bundle::make(Post::class) ->generate('slug', fn($attributes) => Str::slug($attributes['name']))
记录
如果您要创建的记录数量很少,创建整个目录结构可能会很麻烦。在这种情况下,您可以使用record
和records
方法在迁移内部创建它们。
例如,为了快速创建一个管理员帐户,您可以这样做
... Bundle::make(User::class) ->mutate('password', fn($value) => Hash::make($value)) ->records([ 'admin' => [ 'name' => 'Administrator', 'email' => 'admin@example.tld', 'password' => 'admin123', ], ]); ...
覆盖插入行为
如果您需要为记录自定义插入行为,可以在包上调用performInsertUsing()
。
例如,要执行updateOrCreate而不是插入
Bundle::make(User::class) ->performInsertUsing(function (array $data, Bundle $bundle) { return $bundle->model::updateOrCreate( Arr::only($data, ['email']), Arr::except($data, ['email']) )->getKey(); });
关系
记录当然可以与其他记录有关系。目前支持的关系包括
- 一对一及其逆关系
- 一对多及其逆关系
- 多对多(
belongsToMany
) - 多态一对一及其逆关系
- 多态一对多及其逆关系(
morphMany
)
引用其他记录
其他记录可以通过多种方式引用,该包会按照以下顺序尝试这三种方法。
通过它们的标识符(键)
这仅在同一个填充器(跨所有包)内部有效。键是文件名,所以对于前面提到的john-doe.php
示例文件,键将是john-doe
。
如果您直接通过 records()
方法将记录提供给包,则键是您设置的数组键。
根据主键
如果没有找到键,则包假设提供的键是主键,并尝试在数据库中查找记录。如果记录存在,则将使用主键。
通过(最好是唯一的)列
您还可以使用任何喜欢的列引用记录。例如,要引用 John Doe 的其他记录,您可以使用 email:john.doe@example.tld
来引用他们的电子邮件。然后包将尝试查找具有该电子邮件地址的记录。
一对一
假设我们有一个 User
模型,它有一个 Address
关系。您可以在 Address
包中这样定义关系
<?php return [ 'street' => 'Example Street', 'city' => 'Example City', 'state' => 'Example State', 'zip' => '12345', 'user' => 'admin', ];
这将尝试将地址与用户记录 admin
关联。
您也可以使用主键引用用户
return [ ... 'user' => 1, ];
或使用您选择的列
return [ ... 'user' => 'email:john.doe@example.tld', ];
一对一(反向)
假设我们有一个 User
模型,它有一个 Address
关系。您可以从 User
记录创建一个 Address
<?php return [ 'name' => 'Webmaster', 'email' => 'webmaster@example.tld', 'password' => 'my-strong-password', 'address' => [ 'street' => 'Example street', 'city' => 'Example city', 'zip' => '12345', 'state' => 'Example State', ] ];
这将创建一个用户并将其与具有指定属性的全新地址记录相关联。
一对多
想象我们有一个 Posts
包,它有一个到 author
(John Doe)的一对多关系,这是在第一个例子中创建的。我们只需使用记录的键将其与帖子关联。
<?php return [ 'name' => 'Example post', 'slug' => 'example-post', 'author' => 'john-doe', ];
多对多
如果我们想修改上面的例子并添加一个到 tags
的 多对多
关系,就像这样简单
<?php return [ 'name' => 'Example post', 'slug' => 'example-post', 'author' => 1, 'tags' => ['technology', 'design', 'off-topic'] ];
这将把指定的标签附加到帖子。
多态一对多
现在想象我们有一个 Comment
模型,它与 Post
模型具有多态关系。
添加此类关系类似于“属于”关系,但我们需要传递一个包含键/主键和形态类的数组的数组。
<?php return [ 'name' => 'A useful comment', 'author' => 1, 'post' => ['example-post', Post::class], ];
多态一对多(反向)
最后但同样重要的是,假设我们有一个 Like
模型,它与我们的 Post
模型具有多态关系。让我们再次看看我们的 example-post
记录,并通过添加一个反向的 likes
关系来扩展它。
<?php return [ 'name' => 'Example post', 'slug' => 'example-post', 'author' => 1, 'tags' => ['technology', 'design', 'off-topic'], 'likes' => [ [ 'user' => 'john.doe@example.tld', ], [ 'user' => 'jennifer.doe@example.tld', ] ] ];
这将自动创建两个具有定义的属性和与创建它们的帖子相关联关系的 Like。
跟踪插入的模型
可以通过在 Laravel populator 中启用跟踪功能来在数据库中跟踪由填充器插入的模型。
\Guava\LaravelPopulator\Models\Population::first() ->populatable; //provides access to the model that was inserted by Laravel populator.
Laravel populator 提供了一个 [HasPopulation.php](src%2FConcerns%2FHasPopulation.php)
特性,用于从模型中访问 Population。
class User extends Model implements TracksPopulatedEntries { use HasPopulation; }
然后模型可以通过 $model->population
访问 Population 关系。
启用跟踪
可以通过在发布配置文件中启用功能或在服务提供者的 boot 方法中启用它来启用跟踪。
通过配置启用:
config/populator.php
return [ 'tracking' => true, //...other options ];
通过提供者启用:
AppServiceProvider.php
public function boot() { \Guava\LaravelPopulator\Facades\Feature::enableTrackingFeature(); }
标记要跟踪的模型
模型必须实现 TracksPopulatedEntries.php 以选择保存 population。
class User extends Model implements TracksPopulatedEntries { }
交换 Population 模型
可以通过在发布配置文件中设置模型类或在服务提供者的 boot 方法中启用它来更改 population 模型。
通过配置启用:
config/populator.php
return [ 'population_model' => SomeModel::class, //...other options ];
通过提供者启用:
AppServiceProvider.php
public function boot() { \Guava\LaravelPopulator\Facades\Feature::customPopulationModel(SomeModel::class); }