adamwathan/facktory

此软件包已被废弃且不再维护。作者建议使用 adamwathan/faktory 软件包。

尝试将 Ruby 的 FactoryGirl 的奇妙之处带给 PHP

0.2 2014-04-27 20:11 UTC

This package is auto-updated.

Last update: 2022-02-01 12:34:24 UTC


README

Facktory 是一个用于轻松构建测试对象的工具,类似于 FactoryGirl,但适用于 PHP。它仍处于早期阶段,但如果您对此感兴趣,不妨试试看,并为它缺少的您认为非常重要的功能提出问题。

使用 Composer 安装

您可以通过在您的 composer.json 中包含以下内容来通过 Composer 安装此软件包

{
    "require": {
        "adamwathan/facktory": "dev-master"
    }
}

注意:由于此软件包仍处于早期阶段,我尚未标记任何稳定版本。如果您想在标记发布之前尝试此软件包,请确保将 minimum-stability 设置为 dev

Laravel 4

如果您正在使用 Laravel 4,您可以通过注册包含的服务提供者快速开始。

修改 app/config/app.php 中的 providers 数组以包含 FacktoryServiceProvider

'providers' => array(
        //...
        'AdamWathan\Facktory\FacktoryServiceProvider'
    ),

app/config/app.php 中的 aliases 数组中添加 Facktory 门面

'aliases' => array(
        //...
        'Facktory' => 'AdamWathan\Facktory\Facades\Facktory'
    ),

现在您可以直接在 Facktory 门面上调用方法来使用 Facktory

Facktory::define('User', function($f) {
    $f->first_name = 'John';
    $f->last_name = 'Doe';
});

Laravel 4 之外

要在 Laravel 4 之外使用,只需实例化一个新的 Facktory。确保在您喜欢的依赖注入容器中将它注册为单例,因为您可能希望在所有地方都使用相同的实例。

$facktory = new AdamWathan\Facktory\Facktory;

注意:当在 Laravel 4 之外使用且无法访问 Facktory 门面时,您需要确保在需要生成其他对象的嵌套闭包中使用您的 $facktory 实例。这很糟糕,但这就是 PHP。

使用 Facktory

定义工厂

您可以在任何您想要的地方定义工厂。

在 Laravel 4 中,我一直在测试目录中创建一个 factories.php 文件,并将其添加到 app/bootstrap/testing.php 中。

// app/bootstrap/testing.php
require app_path().'/tests/factories.php';

基本定义

最基本的工厂定义需要一个类名和一个闭包,该闭包定义了工厂的默认属性。这将定义一个以该类名命名的工厂,该工厂生成该类的实例。

Facktory::define('Album', function($f) {
    $f->name = 'Diary of a madman';
    $f->release_date = new DateTime('1981-11-07');
});

Facktory::define('Song', function($f) {
    $f->name = 'Over the mountain';
    $f->length = 271;
});

使用工厂

一旦定义了工厂,您就可以非常容易地为您的测试生成对象。

可以使用两种不同的构建策略来生成对象。

  • build 仅在内存中创建对象,而不持久化它们。
  • create 创建对象并将它们持久化到您在测试环境中设置的数据库。

注意:创建(create)策略是为了Laravel 4的Eloquent ORM设计的,但只要你的对象实现了save()方法和getKey()方法来获取对象的ID,它就应在Eloquent之外工作。

要生成一个对象,只需调用buildcreate,并传入你想要从中生成对象的工厂名称。

// Returns an Album object with the default attribute values
$album = Facktory::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 = Facktory::create('Album');
$album->id
// 1

重写属性

使用这些工厂的实际好处在于当你编写需要对象满足某些预置条件,但你并不关心其余属性时。

你可以指定对测试中关心的属性的所需值,并让工厂用默认数据填充你不关心的属性,从而使对象处于有效状态。

如果你只需要更改一些简单的属性为静态值,你只需将属性覆盖数组作为第二个参数传递即可。

// Create an instance and override some properties
$album = Facktory::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 = Facktory::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]作为第一个参数传递,而不是仅仅一个类名。

Facktory::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 = Facktory::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

工厂继承

你可以通过嵌套定义来创建继承现有工厂属性的新工厂。这允许你定义一个基本的工厂,以及在其下定义更具体的工厂,以生成特定状态的对象,而无需重新声明不需要更改的属性。

Facktory::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 = Facktory::build('admin');

$user->first_name;
// 'John'
$user->last_name;
// 'Doe'
$user->is_admin;
// true

延迟属性

如果你不希望在尝试构建对象之前评估一个属性,你可以将该属性定义为闭包。

Facktory::define('User', function($f) {
    $f->username = 'john.doe';

    $f->created_at = function() {
        return new DateTime;
    };
});


$user1 = Facktory::build('User');
$user1->created_at;
// '2014-04-22 14:10:05'

sleep(7);

$user2 = Facktory::build('User');
$user2->created_at;
// '2014-04-22 14:10:12'

依赖属性

你还可以使用延迟属性来定义依赖于工厂中其他属性的属性。

Facktory::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 = Facktory::build('User');
$user->first_name;
// 'John'
$user->last_name;
// 'Doe'
$user->email;
// 'John.Doe@example.com'

$user = Facktory::build('User', ['first_name' => 'Bob']);
$user->first_name;
// 'Bob'
$user->last_name;
// 'Doe'
$user->email;
// 'Bob.Doe@example.com'

唯一属性

再次使用延迟属性来拯救。闭包还接受一个自增整数作为它的第二个参数,这对于确保字段值是唯一的非常有用。

Facktory::define('User', function($f) {
    $f->first_name = 'John';
    $f->last_name = 'Doe';
    $f->email = function($f, $i) {
        return "example{$i}@example.com";
    };
});


$user1 = Facktory::build('User');
$user1->email;
// 'example1@example.com'

$user2 = Facktory::build('User');
$user2->email;
// 'example2@example.com'

定义关系

Facktory让你能够轻松定义对象之间的关系。

目前,支持belongsTohasOnehasMany关系。

属于

通过将belongsTo调用分配给属性来定义一个belongsTo关系。

belongsTo()接受作为第一个参数的用于生成相关对象的工厂名称,作为第二个参数的外键列名称,以及作为第三个参数的可选的覆盖属性数组。

$facktory->define(['song_with_album', 'Song'], function($f) {
    $f->name = 'Concatenation';
    $f->length = 257;
    $f->album = $f->belongsTo('album', 'album_id', [
        'name' => 'Chaosphere',
    ]);
});

$facktory->define(['album', 'Album'], function($f) {
    $f->name = 'Destroy Erase Improve';
});


// Build the objects in memory without persisting to the database
$song = Facktory::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 = Facktory::create('song_with_album');
$song->album_id;
// 1

Album::find(1);
// object(Album)(
//    'name' => 'Destroy Erase Improve'
// )

有一个

通过将hasOne调用分配给属性来定义一个hasOne关系。

hasOne()接受作为第一个参数的用于生成相关对象的工厂名称,作为第二个参数的外键列名称(在相关对象上),以及作为第三个参数的可选的覆盖属性数组。

$facktory->define(['user_with_profile', 'User'], function($f) {
    $f->username = 'johndoe';
    $f->password = 'top-secret';
    $f->profile = $f->hasOne('profile', 'user_id');
});

$facktory->define(['profile', 'Profile'], function($f) {
    $f->email = 'johndoe@example.com';
});


// Build the objects in memory without persisting to the database
$user = Facktory::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 = Facktory::create('user_with_profile');
$user->id;
// 1

Profile::first();
// object(Album)(
//    'user_id' => 1,
//    'email' => 'johndoe@example.com'
// )

多对一关系

通过将一个hasMany调用分配给一个属性来定义一个hasMany关系。

hasMany()函数接受四个参数:第一个参数是用于生成相关对象的工厂名称,第二个参数是相关对象的外键列名称,第三个参数是要生成的对象数量,最后一个参数是一个可选的覆盖属性数组。

$facktory->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);
});

$facktory->define(['song', 'Song'], function($f) {
    $f->title = 'The Thing That Should Not Be';
    $f->length = 397;
});

关系和构建策略

不同的构建策略以不同的方式处理关系。

使用build策略时,相关对象将直接作为基对象的一个属性可用。

使用create策略时,相关对象将被持久化到数据库中,外键属性被设置为与基对象ID匹配,实际上不会在属性本身上设置任何内容,这样您就可以通过在基对象类中实际定义的方法检索相关对象。

覆盖关系属性

如果需要在构建或创建对象时覆盖关系属性,可以通过操作实际的关系属性本身来实现。

// Define the factories
$facktory->define(['song_with_album', 'Song'], function($f) {
    $f->name = 'Concatenation';
    $f->length = 257;
    $f->album = $f->belongsTo('album', 'album_id');
});
$facktory->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 = Facktory::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 = Facktory::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'

一次性构建多个实例

您可以使用buildListcreateList一次性生成多个对象。

// Create multiple instances
$albums = Facktory::buildList('Album', 5);

// Create multiple instances with some overridden properties
$songs = Facktory::buildList('Song', 5, [ 'length' => 100 ])
$songs[0]->length;
// 100
// ...
$songs[4]->length;
// 100

// Add a nested relationship where each item is different
$album = Facktory::build('Album', [
    'name' => 'Bark at the moon',
    'songs' => [
        Facktory::build('Song', [ 'length' => 143 ]),
        Facktory::build('Song', [ 'length' => 251 ]),
        Facktory::build('Song', [ 'length' => 167 ]),
        Facktory::build('Song', [ 'length' => 229 ]),
    ],
]);

// Add a nested relationship where each item shares the same
// properties
$album = Facktory::build('Album', [
    'name' => 'Bark at the moon',
    'songs' => Facktory::buildList('Song', 5, [ 'length' => 100 ]
    ),
]);

// Add a nested relationship where each item is different,
// but using buildList
$album = Facktory::build('Album', [
    'name' => 'Bark at the moon',
    'songs' => Facktory::buildList('Song', 4, [
        'length' => [143, 251, 167, 229]
    ]),
]);

// Add a nested relationship using buildList, but wrap
// it in a collection
$album = Facktory::build('Album', [
    'name' => 'Bark at the moon',
    'songs' => function() {
        return new Collection(Facktory::buildList('Song', 4, [
            'length' => [143, 251, 167, 229]
        ]));
    }
]);