thettler / laravel-factory-classes
一个通过流畅的API和自动补全轻松创建测试用例Factory类的包
Requires
- php: ^7.4
- christophrumpel/laravel-command-file-picker: ^0.0.7
- fzaninotto/faker: ^1.4
- illuminate/support: ^6.0|^7.0
- laravel/framework: ^6.0|^7.0
Requires (Dev)
- orchestra/testbench: ^4.0
- phpunit/phpunit: ^9.0
- squizlabs/php_codesniffer: ^3.5
This package is auto-updated.
Last update: 2021-08-06 15:23:03 UTC
README
Laravel Factory Classes是一个帮助您通过Factory类使用流畅的API和自动补全创建带有数据的Model的包,用于测试、seeder或任何您可能需要的地方。
💡 为什么想要使用这个包?
为所有Model创建Factory有时会变得相当混乱。它缺乏自动补全,您通常需要了解Model的业务逻辑才能将其置于特定状态。Factory类解决了这些问题。每个Model都有自己的Factory,它包含创建和填充数据所需的一切。您可以使用Factory的API简单地实例化、保存或修改Model。
要了解更多关于这个主题的信息,我建议阅读来自sticher.io的这篇博客文章。
示例
考虑一个场景,我们有一个具有地点(belongsTo)、支持艺人(belongsToMany)、压轴艺人(belongsToMany)以及其他我们在此示例中不关心的属性的音乐会Model。使用常规Factory,您会这样做来创建一个音乐会:
$concert = factory(Concert::class)->create([ 'date' => now() ]); $venue = factory(Venue::class)->create(); $headliner = factory(Artist::class)->create(); $support = factory(Artist::class)->create(); $concert->venue()->associate($venue); $concert->lineup()->attach($headliner->id, ['headliner'=> true]); $concert->lineup()->attach($support->id, ['headliner'=> false]); $this->assert($concert, /*Do something with your Concert*/);
使用此包,它看起来会是这样:
$concert = ConcertFatory::new() ->date(now()) ->withVenue() ->withHeadliner() ->withSupport() ->create(); $this->assert($concert, /*Do something with your Concert*/);
Factory类的样子如下
<?php namespace App\Factories; use App\Concert; use Faker\Generator; use Illuminate\Support\Carbon; use Thettler\LaravelFactoryClasses\FactoryClass; class ConcertFactory extends FactoryClass { protected string $model = Concert::class; protected function fakeData(Generator $faker): array { return [ 'date' => $faker->dateTimeBetween('-5 years', '+5 years'), /* More attributes*/ ]; } public function create(array $extra = []): Concert { return $this->createModel($extra); } public function make(array $extra = []): Concert { return $this->makeModel($extra); } public function date(Carbon $data): self { return $this->addData('date', $data); } public function withVenue($venueFactory = null): self { return $this->belongsTo('venue', $venueFactory ?? VenueFactory::new()); } public function withHeadliner(...$artistFactories): self { return $this->belongsToMany( 'lineups', empty($artistFactories) ? [ArtistFactory::new()] : $artistFactories, fn(BelongsToManyFactoryRelation $relation) => $relation->pivot(['headliner' => true]) ); } public function withSupport(...$artistFactories): self { return $this->belongsToMany( 'lineups', empty($artistFactories) ? [ArtistFactory::new()] : $artistFactories, fn(BelongsToManyFactoryRelation $relation) => $relation->pivot(['headliner' => true]) ); } }
📜 功能
- 创建模型并将其保存到数据库
- 创建模型但不保存到数据库
- 创建具有关系的模型
- 通过一个命令(
--recursive
)创建所有所需的Factory - 自动为您的模型生成模拟数据
- 无需生成模拟数据也可在测试之外使用
- 将模型创建逻辑提取到自己的类中
- 在任何时候都可以完全控制关系和属性
💻 安装
您可以通过composer安装此包
composer require thettler/laravel-factory-classes
要发布配置文件,请运行
php artisan vendor:publish --provider="Thettler\LaravelFactoryClasses\FactoryClassServiceProvider"
它将提供包的配置文件,您可以在其中定义模型的路径、生成的Factory的路径以及生成的Factory的命名空间
<?php /* * You can place your custom package configuration in here. */ return [ 'models_path' => base_path('app'), 'factories_path' => base_path('app/Factories'), 'factories_namespace' => 'App\Factories', ];
🚀 入门
快速入门
要创建新的Factory,可以使用以下命令:
php artisan make:factory-class
这将提示您一个列表,其中包含模型,它知道您的模型位于您的配置中。您可以选择要创建的模型。这将为您在App\Factories
命名空间下创建一个新的Factory
,并自动添加所有关系,如果您在Model内部使用它们的返回值进行类型提示。有关关系的更多信息,请参阅手动指南。
附加参数和选项
如果您不想从列表中选择模型,您可以将模型路径中模型的类名作为参数传递,您的工厂将立即为您创建。
php artisan make:factory-class User
默认情况下,如果尝试创建的工厂已存在,此命令将停止并报错。您可以使用强制选项覆盖现有工厂。
php artisan make:factory-class User --force
使用--recursive
标志可以快速构建大量新的工厂。然后它将遍历所选类的所有关系,如果还没有,还会为它们创建工厂。
php artisan make:factory-class User --recursive
如果您不希望自动创建关系方法,可以使用此标志。
php artisan make:factory-class User --without-relations
您还可以使用以下命令覆盖配置:--models_path=app/models
--factories_path=你的工厂路径
--factories_namespace=你的命名空间\Your\Factories
手动方式
要创建第一个FactoryClass
,您只需创建一个普通的PHP类,并扩展抽象类Thettler\LaravelFactoryClasses\FactoryClass
。此类要求您定义3个方法和一个属性。
- 首先,您必须向工厂添加一个
protected string $model
属性。它包含要创建的模型的引用。例如,Laravel提供的标准User
类。
<?php namespace App\Factories; use Thettler\LaravelFactoryClasses\FactoryClass; use App\User; class UserFactory extends FactoryClass { protected string $model = User::class; }
- 告诉工厂它应该创建哪个模型后,您必须定义一个
public create()
函数。它期望一个参数$extra
,它是一个默认值为空数组的数组。关于$extra
的更多内容稍后讨论。在这里,您应该将模型类类型化为返回值。这将使大多数编辑器提供自动完成功能。最后,调用并返回$this->createModel($extra)
方法。此方法将负责为您创建模型。您仍然可以在模型创建后对其进行修改,或完全自行创建它。所以我们的User
类看起来像这样
<?php namespace App\Factories; use Thettler\LaravelFactoryClasses\FactoryClass; class UserFactory extends FactoryClass { /*...*/ public function create(array $extra = []): User { return $this->createModel($extra); } }
- 现在您必须添加一个
public make()
函数。它与create()
几乎相同,但在这里您调用的是$this->makeModel($extra)
方法而不是$this->createModel($extra)
。我们稍后将讨论create()
和make()
之间的区别。所以我们的User
类看起来像这样
<?php namespace App\Factories; use Thettler\LaravelFactoryClasses\FactoryClass; class UserFactory extends FactoryClass { /*...*/ public function make(array $extra = []): User { return $this->makeModel($extra); } }
- 您必须添加的最后一种方法是
protected fakeData()
。此方法接收一个Faker
实例作为参数,并期望您返回一个数组。fakeData()
方法用于为您的模型生成默认数据。它返回一个关联数组,其中属性名称作为键,您要设置的值作为值。
所以我们的User
类看起来像这样
<?php namespace App\Factories; use Thettler\LaravelFactoryClasses\FactoryClass; class UserFactory extends FactoryClass { /*...*/ protected function fakeData(\Faker\Generator $faker): array { return [ 'name' => $faker->name, 'email' => $faker->unique()->safeEmail, 'email_verified_at' => now(), 'password' => Hash::make('secret'), 'remember_token' => Str::random(10), ]; } }
现在您可以开始了,并已创建了第一个FactoryClass。完整的类现在看起来像这样
<?php namespace App\Factories; use Thettler\LaravelFactoryClasses\FactoryClass; use App\User; class UserFactory extends FactoryClass { protected string $model = User::class; public function create(array $extra = []): User { return $this->createModel($extra); } public function make(array $extra = []): User { return $this->makeModel($extra); } protected function fakeData(\Faker\Generator $faker): array { return [ 'name' => $faker->name, 'email' => $faker->unique()->safeEmail, 'email_verified_at' => now(), 'password' => Hash::make('secret'), 'remember_token' => Str::random(10), ]; } }
使用工厂
创建/制作模型
现在您已创建您的FactoryClass
,您将想使用它。要实例化您的FactoryClass
,请使用static new()
方法。
$userFactory = UserFactory::new();
现在要创建并保存模型到数据库,请调用create()
方法。这将创建一个模型,其数据定义在fakeData()
中。
$userFactory = UserFactory::new(); $userModel = $userFactory->create();
如果您不想将模型存储在数据库中,请使用make()
方法。
$userFactory = UserFactory::new(); $userModel = $userFactory->make();
如果您需要多个模型,可以使用createMany()
和makeMany()
方法。两个方法都接受一个整数参数,表示应创建多少个模型,并返回包含模型的Collection
。
$threeSavedUserCollection = UserFactory::new()->createMany(3); $threeNotSavedUserCollection = UserFactory::new()->makeMany(3);
更改数据
在大多数情况下,您可能希望根据情况更改某些属性,而不希望使用所有的 fakeData
。本包为您提供了3种更改用于创建模型所使用的数据的选项。
- 使用
addData()
方法。该方法以属性名称作为第一个参数,以应设置的值作为第二个参数。
$user = UserFactory::new() ->addData('name', 'myName') ->create(); echo $user->name; // myName
- 第二种选项是使用
data()
方法。该方法接受一个键值对数组,表示属性及其值。此方法还将覆盖之前设置的所有数据
$user = UserFactory::new() ->data(['name' => 'myName']) ->create(); echo $user->name; // myName
⚠️
data()
和addData()
将自动为您提供一个新实例的工厂,以防止副作用
- 最后一个选项是在
create()
、createMany()
、make()
或makeMany()
方法的$extra
参数上使用。这将使用相同的键覆盖以前的数据。
$user = UserFactory::new()->create(['name' => 'extraName']); echo $user->name; // extraName $user = UserFactory::new()->make(['name' => 'extraName']); echo $user->name; // extraName $userCollection = UserFactory::new()->createMany(2, ['name' => 'extraName']); $userCollection->pluck('name')->all(); // ['extraName', 'extraName'] $user = UserFactory::new()->makeMany(2, ['name' => 'extraName']); $userCollection->pluck('name')->all(); // ['extraName', 'extraName']
但 createMany()
和 makeMany()
的 $extra
与 create()
和 make()
略有不同。在这里,您也可以传递一个嵌套数组,为集合中的模型提供不同的数据。
$userCollection = UserFactory::new()->createMany(2, [['name' => 'firstName'], ['name' => 'secondName']]); $userCollection->pluck('name')->all(); // ['firstName', 'secondName']
$userCollection = UserFactory::new()->createMany(3, [['name' => 'firstName'], ['name' => 'secondName']]); $userCollection->pluck('name')->all(); // ['firstName', 'secondName', '<value-from-fakeData>']
✨ 小贴士
如果您想获得更易于阅读的API,可以在工厂类中添加一些小辅助设置器,如下所示
use Thettler\LaravelFactoryClasses\FactoryClass; class UserFactory extends FactoryClass { /*...*/ public function name(string $name): self { return $this->addData('name', $name); } }
$user = UserFactory::new() ->name('myName') ->create(); echo $user->name; // myName
关系
创建具有关系的模型通常有些繁琐。本包提供了简单而强大的关系系统,该系统使用模型上已定义的关系。
所有关系基本上工作方式相同,它们接受关系名称作为第一个参数,接受 FactoryClass
或 Model
作为第二个参数。它们还有一个第三个参数,允许您挂钩到关系并修改其行为,但我们将保留这部分内容以供高级部分。
如果您提供 FactoryClass
作为第二个参数,则关系将在每次调用 create()
、createMany()
、make()
或 makeMany()
时从 FactoryClass
创建一个新的模型,并将其附加到模型关系。
⚠️ 与
data()
和addData()
一样,所有关系方法都会为您提供工厂的新实例以防止副作用。
对于所有示例,我们假设模型有一个名为 company
的公司关系。
HasOne
class User extends Model { public function company(): HasOne { return $this->hasOne(Company::class); } }
$user = UserFactory::new()->hasOne('company', CompanyFactory::new())->create(); $user->company; // Some new created Company // Or $company = Company::find('xy'); $user = UserFactory::new()->hasOne('company', $company)->create(); $user->company; // Company XY
HasMany
hasMany()
方法接受一个包含 FactoryClasses
或 Models
的数组作为第二个参数。
class User extends Model { public function companies(): HasMany { return $this->hasMany(Company::class); } }
$user = UserFactory::new()->belongsTo('companies',[CompanyFactory::new()])->create(); $user->companies[0]; // Some new created Company // Or $company = Company::find('xy'); $user = UserFactory::new()->belongsTo('companies', [$company])->create(); $user->companies[0]; // Company XY
BelongsTo
class User extends Model { public function company(): BelongsTo { return $this->belongsTo(Company::class); } }
$user = UserFactory::new()->belongsTo('company', CompanyFactory::new())->create(); $user->company; // Some new created Company // Or $company = Company::find('xy'); $user = UserFactory::new()->belongsTo('company', $company)->create(); $user->company; // Company XY
BelongsToMany
与 hasMany()
一样,belongsToMany()
也接受一个包含 FactoryClasses
或 Models
的数组
class User extends Model { public function companies(): BelongsToMany { return $this->belongsToMany(Company::class); } }
$user = UserFactory::new()->belongsToMany('companies', CompanyFactory::new())->create(); $user->companies[0]; // Some new created Company // Or $company = Company::find('xy'); $user = UserFactory::new()->belongsToMany('companies', $company)->create(); $user->companies[0]; // Company XY
如果您想为 belongsToMany()
关系添加枢轴数据,可以使用第三个参数。它是一个回调,为您提供 BelongsToManyFactoryRelation
实例。在这个类中,您可以调用 pivot()
方法并返回它以添加您的枢轴数据。有关第三个参数的更多信息,请参阅高级部分。
class User extends Model { public function companies(): BelongsToMany { return $this->belongsToMany(Company::class)->withPivot('role'); } }
$user = UserFactory::new() ->belongsToMany( 'companies', CompanyFactory::new(), fn(BelongsToManyFactoryRelation $relation) => $relation->pivot(['role' => 'manager']) ) ->create(); $user->companies[0]->role; // manager
MorphTo
class User extends Model { public function company(): MorphTo { return $this->morphTo(Company::class); } }
$user = UserFactory::new()->morphTo('company', CompanyFactory::new())->create(); $user->company; // Some new created Company // Or $company = Company::find('xy'); $user = UserFactory::new()->morphTo('company', $company)->create(); $user->company; // Company XY
MorphOne
class User extends Model { public function company(): MorphOne { return $this->morphOne(Company::class); } }
$user = UserFactory::new()->morphOne('company', CompanyFactory::new())->create(); $user->company; // Some new created Company // Or $company = Company::find('xy'); $user = UserFactory::new()->morphOne('company', $company)->create(); $user->company; // Company XY
✨ 小贴士
在您的FactoryClass
中创建一些带有默认值的辅助函数,以便您可以无需明确提供FactoryClass
或模型即可调用它们。
use Thettler\LaravelFactoryClasses\FactoryClass; class UserFactory extends FactoryClass { /*...*/ public function withCompany($company = null): self { return $this->belongsTo('company', $company ?? CompanyFactory::new()); } }
$user = UserFactory::new() ->withCompany() ->create(); echo $user->company; // Company XY
或者对于多个关系
use Thettler\LaravelFactoryClasses\FactoryClass; class UserFactory extends FactoryClass { /*...*/ public function withCompanies(...$companies): self { return $this->belongsToMany('companies', empty($companies) ? [CompanyFactory::new()] : $companies); } }
$user = UserFactory::new() ->withCompanies() ->create(); echo $user->companies[0]; // Company
如果您在模型的关联上定义了返回类型,命令将自动为您生成这些方法。
class User extends Model { // Would generate a withCompany() method on your factory public function company(): BelongsTo { return $this->belongsto(Company::class); } // Would not generate a withCompany() method on your factory public function company() { return $this->belongsto(Company::class); } }
⚠️ 自动方法创建不适用于
MorphTo
关系
禁用假数据生成
如果您不希望为模型生成假数据,可以在工厂上使用withoutFakeData()
方法。
$user = UserFactory::new() ->withoutFakeData() ->make();
🔨 高级
自定义关系
每个关系都有其专门的类,该类负责创建关系。您可以通过使用关系函数的第三个参数来挂钩到该类并修改它。这是一个可调用对象,它接收一个FactoryRelation
实例并返回一个FactoryRelation
实例。
use Thettler\LaravelFactoryClasses\FactoryClass; use App\User; class UserFactory extends FactoryClass { /* ... */ public function withCompany ($company) { return $this->belongsTo( 'company', $company, fn(\Thettler\LaravelFactoryClasses\Relations\BelongsToFactoryRelation $relation) => $relation ->type('before') // after|before this indicates if the relation creation should take place before or after the main Model has ben created and saved to the DB ->relation('diffrentCompany') // change the name of the relation that gets used so here it changes from 'company' to 'differentCompany' ->factories(CompanyFactory::new()->withSpecialState()) // Lets you add an one or more Factories to the Relation !! only the many relations using mor then one factory ->models(Company::find('xy')) // Same as factories() only for Models ); } }
使用自定义关系
如果您需要更复杂的关系,您可以编写自己的FactoryRelations
并在工厂中使用它们。为此,在您的FactoryClass
上使用with()
方法,并通过第一个参数传递您的关联。
use Thettler\LaravelFactoryClasses\FactoryClass; use App\User; class UserFactory extends FactoryClass { /* ... */ public function withCompany ($company) { return $this->with(MyFactoryRelation::new('company', $company)); } }
要创建您的FactoryRelation
,创建一个新的PHP类并扩展FactoryRelation
类。定义抽象方法,然后您就可以使用了。有关更多示例,请查看src/Relations
。
namespace Thettler\LaravelFactoryClasses\Relations; use Illuminate\Database\Eloquent\Model; use Thettler\LaravelFactoryClasses\FactoryRelation; class BelongsToFactoryRelation extends FactoryRelation { protected string $type = self::BEFORE_TYPE; // after|before this indicates if the relation creation should take place before or after the main Model has ben created and saved to the DB /** * This Method will be used if the make() or makeMany() method gets used it gets the current Model (in our case User) and returns this Model again * * @param Model $model * @return Model * @throws \Thettler\LaravelFactoryClasses\Exceptions\FactoryException */ public function make(Model $model): Model { $relative = $this->convertRelative($this->relatives[0], 'make'); return $model->setRelation($this->relation, $relative); } /** * This Method will be used if the create() or createMany() method gets used it gets the current Model (in our case User) and returns this Model again * * @param Model $model * @return Model * @throws \Thettler\LaravelFactoryClasses\Exceptions\FactoryException */ public function create(Model $model): Model { $relation = $this->relation; $relative = $this->convertRelative($this->relatives[0]); $model->$relation()->associate($relative)->save(); return $model; } }
运行测试
./vendor/bin/phpunit
作者
👤 托比亚斯·赫特勒
- 网站: stageslice.com
- Twitter: @TobiSlice
- Github: @thettler
🤝 贡献
欢迎贡献、问题和功能请求!
请随意查看问题页面。
展示您的支持
如果这个项目对您有帮助,请点一个⭐️!
📝 许可证
版权所有 © 2020 托比亚斯·赫特勒。
本项目遵循MIT许可证。
此README是用❤️由readme-md-generator生成的