vehikl / faktory
将 Ruby 的 FactoryGirl 的奇妙之处引入 PHP 的尝试
Requires
- php: >=5.4.0
Requires (Dev)
- illuminate/database: 4.*
- mockery/mockery: 0.9.*
This package is not auto-updated.
Last update: 2024-09-10 07:34:58 UTC
README
Faktory 是一个易于构建测试对象的工具,类似于 FactoryGirl,但适用于 PHP。它仍处于早期阶段,但如果您对此感兴趣,不妨试一试,并为您认为非常重要的缺失功能提出问题。
使用 Composer 安装
您可以通过在 composer.json
中包含以下内容来通过 Composer 安装此软件包:
{ "require": { "vehikl/faktory": "dev-master" } }
注意:由于此软件包仍处于早期阶段,我尚未标记任何稳定版本。如果您想在我标记版本之前尝试此软件包,请确保将您的
minimum-stability
设置为dev
。
Laravel 4
如果您正在使用 Laravel 4,可以通过注册包含的服务提供程序快速开始。
修改 app/config/app.php
中的 providers
数组以包含 FaktoryServiceProvider
'providers' => array( //... 'Vehikl\Faktory\FaktoryServiceProvider' ),
将 Faktory
外观添加到 app/config/app.php
中的 aliases
数组
'aliases' => array( //... 'Faktory' => 'Vehikl\Faktory\Facades\Faktory' ),
现在您可以通过直接调用 Faktory
外观的函数来开始使用 Faktory
Faktory::define('User', function($f) { $f->first_name = 'John'; $f->last_name = 'Doe'; });
Laravel 4 之外
要在外部使用 Laravel 4,只需实例化一个新的 Faktory
。请确保在您最喜欢的依赖注入容器中将其注册为单例,因为您可能希望在整个应用程序中使用相同的实例。
$faktory = new Vehikl\Faktory\Faktory;
注意:当在 Laravel 4 之外使用且无法访问
Faktory
外观时,您需要确保在任何需要生成其他对象的嵌套闭包中use
您的$faktory
实例。这在 PHP 中很痛苦,但这就是 PHP。
使用 Faktory
定义工厂
您可以在任何地方定义工厂。
在 Laravel 4 中,我在测试目录中创建了一个 factories.php
文件,并将其添加到 app/bootstrap/testing.php
中,如下所示:
// app/bootstrap/testing.php require app_path().'/tests/factories.php';
基本定义
最基本的工厂定义需要一个类名和一个闭包,该闭包定义了工厂的默认属性。这将定义一个以该类名命名的工厂,用于生成该类的实例。
Faktory::define('Album', function($f) { $f->name = 'Diary of a madman'; $f->release_date = new DateTime('1981-11-07'); }); Faktory::define('Song', function($f) { $f->name = 'Over the mountain'; $f->length = 271; });
使用工厂
一旦您定义了工厂,就可以非常容易地为您测试生成对象。
对象可以使用两种不同的构建策略之一生成。
build
只在内存中创建对象,而不将其持久化。create
创建对象并将它们持久化到您在测试环境中设置的数据库。
注意:
create
策略是为 Laravel 4 的 Eloquent ORM 设计的,但只要您的对象实现了save()
方法和getKey()
方法以获取对象的 ID,它应该可以在 Eloquent 之外使用。
要生成一个对象,只需调用 build
或 create
并传入您想要从中生成对象的工厂名称。
// Returns an Album object with the default attribute values $album = Faktory::build('Album'); $album->name; // 'Diary of a madman' $album->release_date; // '1981-11-07' // Create a basic instance and persist it to // the database $album = Faktory::create('Album'); $album->id // 1
覆盖属性
使用这些工厂的真正好处在于,当您编写一个需要对象满足某些前提条件,但您并不关心其余属性时。
您可以指定您需要的属性值,并让工厂处理填充您不关心的属性,以便对象处于有效状态。
如果您只需将一些简单属性更改为静态值,您可以简单地作为第二个参数传递一个属性覆盖数组。
// Create an instance and override some properties $album = Faktory::build('Album', ['name' => 'Bark at the moon']), ]); $album->name; // 'Bark at the moon' $album->release_date; // '1981-11-07'
如果您需要做更复杂的事情,您可以传递一个闭包,该闭包提供与实际定义工厂时相同的所有功能。这在处理关系时最有用。
// Create an instance and override some properties $album = Faktory::build('Album', function($album) { $album->name => 'Bark at the moon'; $album->songs->quantity(4)->attributes(['length' => 267]); }); $album->name; // 'Bark at the moon' $album->songs->count(); // 4 $album->songs[0]->length; // 267
命名工厂
工厂也可以被赋予一个名字,这样您就可以为同一个类定义多个工厂,这些工厂生成处于不同预定义状态的对象。
要定义一个命名工厂,请将一个形式为 [factory_name, class_name]
的数组作为第一个参数传递,而不是只传递一个类名。
Faktory::define(['album_with_copies_sold', 'Album'], function($f) { $f->name = 'Diary of a madman'; $f->release_date = '1981-11-07'; $f->copies_sold = 3200000; }); $album = Faktory::build('album_with_copies_sold'); get_class($album); // 'Album' $album->name; // 'Diary of a madman' $album->release_date; // '1981-11-07' $album->copies_sold; // 3200000
工厂继承
您可以通过嵌套定义来创建继承现有工厂属性的工厂。这允许您定义一个基本工厂,以及在其下定义更具体的工厂来生成特定状态的对象,而无需重新声明不需要更改的属性。
Faktory::define(['basic_user', 'User'], function($f) { $f->first_name = 'John'; $f->last_name = 'Doe'; $f->is_admin = false; $f->define('admin', function($f) { $f->is_admin = true; }); }); $user = Faktory::build('admin'); $user->first_name; // 'John' $user->last_name; // 'Doe' $user->is_admin; // true
延迟属性
如果您不希望属性在尝试构建对象时才进行评估,您可以将该属性定义为闭包。
Faktory::define('User', function($f) { $f->username = 'john.doe'; $f->created_at = function() { return new DateTime; }; }); $user1 = Faktory::build('User'); $user1->created_at; // '2014-04-22 14:10:05' sleep(7); $user2 = Faktory::build('User'); $user2->created_at; // '2014-04-22 14:10:12'
依赖属性
您还可以使用延迟属性来定义依赖于工厂中其他属性的属性。
Faktory::define('User', function($f) { $f->first_name = 'John'; $f->last_name = 'Doe'; $f->email = function($f) { return "{$f->first_name}.{$f->last_name}@example.com"; }; }); $user = Faktory::build('User'); $user->first_name; // 'John' $user->last_name; // 'Doe' $user->email; // 'John.Doe@example.com' $user = Faktory::build('User', ['first_name' => 'Bob']); $user->first_name; // 'Bob' $user->last_name; // 'Doe' $user->email; // 'Bob.Doe@example.com'
唯一属性
延迟属性再次救急。闭包还接受一个自增整数作为其第二个参数,这对于确保字段值唯一非常有用。
Faktory::define('User', function($f) { $f->first_name = 'John'; $f->last_name = 'Doe'; $f->email = function($f, $i) { return "example{$i}@example.com"; }; }); $user1 = Faktory::build('User'); $user1->email; // 'example1@example.com' $user2 = Faktory::build('User'); $user2->email; // 'example2@example.com'
定义关系
Faktory 允许您轻松地定义对象之间的关系。
目前,支持 belongsTo
、hasOne
和 hasMany
关系。
属于
通过将 belongsTo
调用分配给一个属性来定义 belongsTo
关系。
belongsTo()
接受应用于生成相关对象的工厂的名称作为第一个参数,外键列的名称作为第二个参数,以及一个可选的覆盖属性数组作为第三个参数。
$faktory->define(['song_with_album', 'Song'], function($f) { $f->name = 'Concatenation'; $f->length = 257; $f->album = $f->belongsTo('album', 'album_id', [ 'name' => 'Chaosphere', ]); }); $faktory->define(['album', 'Album'], function($f) { $f->name = 'Destroy Erase Improve'; }); // Build the objects in memory without persisting to the database $song = Faktory::build('song_with_album'); $song->album; // object(Album)( // 'name' => 'Destroy Erase Improve' // ) $song->album_id; // NULL // Save the objects to the database and set up the correct // foreign key associations $song = Faktory::create('song_with_album'); $song->album_id; // 1 Album::find(1); // object(Album)( // 'name' => 'Destroy Erase Improve' // )
有一个
通过将 hasOne
调用分配给一个属性来定义 hasOne
关系。
hasOne()
接受应用于生成相关对象的工厂的名称作为第一个参数,相关对象上的外键列名称作为第二个参数,以及一个可选的覆盖属性数组作为第三个参数。
$faktory->define(['user_with_profile', 'User'], function($f) { $f->username = 'johndoe'; $f->password = 'top-secret'; $f->profile = $f->hasOne('profile', 'user_id'); }); $faktory->define(['profile', 'Profile'], function($f) { $f->email = 'johndoe@example.com'; }); // Build the objects in memory without persisting to the database $user = Faktory::build('user_with_profile'); $user->profile; // object(Profile)( // 'email' => 'johndoe@example.com' // ) // Save the objects to the database and set up the correct // foreign key associations $user = Faktory::create('user_with_profile'); $user->id; // 1 Profile::first(); // object(Album)( // 'user_id' => 1, // 'email' => 'johndoe@example.com' // )
有多个
通过将 hasMany
调用分配给一个属性来定义 hasMany
关系。
hasMany()
接受用于生成相关对象的工厂的名称作为第一个参数,相关对象上的外键列名称作为第二个参数,要生成的对象数量作为第三个参数,以及一个可选的覆盖属性数组作为最后一个参数。
$faktory->define(['album_with_songs', 'Album'], function($f) { $f->name = 'Master of Puppets'; $f->release_date = new DateTime('1986-02-24'); $f->songs = $f->hasMany('song', 'album_id', 8); }); $faktory->define(['song', 'Song'], function($f) { $f->title = 'The Thing That Should Not Be'; $f->length = 397; });
关系和构建策略
每个构建策略以不同的方式处理关系。
当使用 build
策略时,相关对象将直接作为基对象的属性可用。
当使用 create
策略时,相关对象将被持久化到数据库中,外键属性设置为与基对象的 ID 匹配,实际属性本身实际上没有任何设置,允许您通过在基对象类中实际定义的方法检索相关对象。
覆盖关系属性
如果您在构建或创建对象时需要覆盖关系属性,可以通过操作实际的关系属性本身来实现。
// Define the factories $faktory->define(['song_with_album', 'Song'], function($f) { $f->name = 'Concatenation'; $f->length = 257; $f->album = $f->belongsTo('album', 'album_id'); }); $faktory->define(['album', 'Album'], function($f) { $f->name = 'Destroy Erase Improve'; $f->release_date = new DateTime('1995-07-25'); }); // Build a song but override the album name $song = Faktory::build('song_with_album', function($song) { $song->album->name = 'Chaosphere'; }); $song->album; // object(Album)( // 'name' => 'Chaosphere' // ) // Build a song but override a couple attributes at once $song = Faktory::build('song_with_album', function($song) { $song->album->attributes([ 'name' => 'Chaosphere', 'release_date' => new DateTime('1998-11-10'), ]); }); $song->album; // object(Album)( // 'name' => 'Chaosphere' // ) $song->album->release_date; // '1998-11-10'
一次性构建多个实例
您可以使用 buildList
和 createList
一次性生成多个对象。
// Create multiple instances $albums = Faktory::buildList('Album', 5); // Create multiple instances with some overridden properties $songs = Faktory::buildList('Song', 5, [ 'length' => 100 ]) $songs[0]->length; // 100 // ... $songs[4]->length; // 100 // Add a nested relationship where each item is different $album = Faktory::build('Album', [ 'name' => 'Bark at the moon', 'songs' => [ Faktory::build('Song', [ 'length' => 143 ]), Faktory::build('Song', [ 'length' => 251 ]), Faktory::build('Song', [ 'length' => 167 ]), Faktory::build('Song', [ 'length' => 229 ]), ], ]); // Add a nested relationship where each item shares the same // properties $album = Faktory::build('Album', [ 'name' => 'Bark at the moon', 'songs' => Faktory::buildList('Song', 5, [ 'length' => 100 ] ), ]); // Add a nested relationship where each item is different, // but using buildList $album = Faktory::build('Album', [ 'name' => 'Bark at the moon', 'songs' => Faktory::buildList('Song', 4, [ 'length' => [143, 251, 167, 229] ]), ]); // Add a nested relationship using buildList, but wrap // it in a collection $album = Faktory::build('Album', [ 'name' => 'Bark at the moon', 'songs' => function() { return new Collection(Faktory::buildList('Song', 4, [ 'length' => [143, 251, 167, 229] ])); } ]);