承接 breerly/factory-girl-php 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

breerly/factory-girl-php

Composer 安装命令:

composer require --dev breerly/factory-girl-php

包简介

Fixture replacement for focused and readable tests - A PHP port of Thoughtbot's Ruby Factory Girl

README 文档

README

Continuous Integration

A PHP port of Thoughtbot's Ruby Factory Girl. Based on a fork of xi-doctrine.

FactoryGirl FixtureFactory

FactoryGirl\Provider\Doctrine\FixtureFactory provides convenient creation of Doctrine entities in tests. If you're familiar with FactoryGirl for Ruby, then this is essentially the same thing for Doctrine/PHP.

Motivation

Many web applications have non-trivial database structures with lots of dependencies between tables. A component of such an application might deal with entities from only one or two tables, but the entities may depend on a complex entity graph to be useful or pass validation.

For instance, a User may be a member of a Group, which is part of an Organization, which in turn depends on five different tables describing who-knows-what about the organization. You are writing a component that change's the user's password and are currently uninterested in groups, organizations and their dependencies. How do you set up your test?

  1. Do you create all dependencies for Organization and Group to get a valid User in your setUp()? No, that would be horribly tedious and verbose.
  2. Do you make a shared fixture for all your tests that includes an example organization with satisifed dependencies? No, that would make the fixture extremely fragile.
  3. Do you use mock objects? Sure, where practical. In many cases, however, the code you're testing interacts with the entities in such a complex way that mocking them sufficiently is impractical.

FixtureFactory is a middle ground between (1) and (2). You specify how to generate your entities and their dependencies in one central place but explicitly create them in your tests, overriding only the fields you want.

Tutorial

We'll assume you have a base class for your tests that arranges a fresh EntityManager connected to a minimally initialized blank test database. A simple factory setup looks like this.

<?php

use FactoryGirl\Provider\Doctrine\FieldDef;
use FactoryGirl\Provider\Doctrine\FixtureFactory;
use PHPUnit\Framework;

abstract class TestCase extends Framework\TestCase
{
    protected $factory;

    protected function setUp(): void
    {
        // ... (set up a blank database and $this->entityManager) ...

        $this->factory = new FixtureFactory($this->entityManager);
        $this->factory->setEntityNamespace('What\Ever'); // If applicable

        // Define that users have names like user_1, user_2, etc.,
        // that they are not administrators by default and
        // that they point to a Group entity.
        $this->factory->defineEntity('User', [
            'username' => FieldDef::sequence("user_%d"),
            'administrator' => false,
            'group' => FieldDef::reference('Group')
        ]);

        // Define a Group to just have a unique name as above.
        // The order of the definitions does not matter.
        $this->factory->defineEntity('Group', [
            'name' => FieldDef::sequence("group_%d")
        ]);


        // If you want your created entities to be saved by default
        // then do the following. You can selectively re-enable or disable
        // this behavior in each test as well.
        // It's recommended to only enable this in tests that need it.
        // In any case, you'll need to call flush() yourself.
        //$this->factory->persistOnGet();
    }
}

Now you can easily get entities and override fields relevant to your test case like this.

<?php

class UserServiceTest extends TestCase
{
    // ...

    public function testChangingPasswords(): void
    {
        $user = $this->factory->get('User', [
            'name' => 'John'
        ]);
        $this->service->changePassword($user, 'xoo');
        $this->assertSame($user, $this->service->authenticateUser('john', 'xoo'));
    }
}

Singletons

Sometimes your entity has a dependency graph with several references to some entity type. For instance, the application may have a concept of a "current organization" with users, groups, products, categories etc. belonging to an organization. By default FixtureFactory would create a new Organization each time one is needed, which is not always what you want. Sometimes you'd like each new entity to point to one shared Organization.

Your first reaction should be to avoid situations like this and specify the shared entity explicitly when you can't. If that isn't feasible for whatever reason, FixtureFactory allows you to make an entity a singleton. If a singleton exists for a type of entity then get() will return that instead of creating a new instance.

<?php

class SomeTest extends TestCase
{
    protected function setUp(): void
    {
        parent::setUp();
        $this->org = $this->factory->getAsSingleton('Organization');
    }

    public function testSomething(): void
    {
        $user1 = $this->factory->get('User');
        $user2 = $this->factory->get('User');

        // now $user1->getOrganization() === $user2->getOrganization() ...
    }
}

It's highly recommended to create singletons only in the setups of individual test classes and NOT in the base class of your tests.

Advanced

You can give an 'afterCreate' callback to be called after an entity is created and its fields are set. Here you can, for instance, invoke the entity's constructor, since FixtureFactory doesn't do that by default.

<?php

$factory->defineEntity(
    'User', 
    [
        'username' => FieldDef::sequence("user_%d"),
    ], 
    [
    'afterCreate' => function(User $user, array $fieldValues) {
        $user->__construct($fieldValues['username']);
    }
]);

API reference

<?php

// Defining entities
$factory->defineEntity(
    'EntityName', 
    [
        'simpleField' => 'constantValue',
    
        'generatedField' => function($factory) { return ...; },
    
        'sequenceField1' => FieldDef::sequence('name-%d'), // name-1, name-2, ...
        'sequenceField2' => FieldDef::sequence('name-'),   // the same
        'sequenceField3' => FieldDef::sequence(function($n) { return "name-$n"; }),
    
        'referenceField' => FieldDef::reference('OtherEntity')
    ], 
    [
        'afterCreate' => function($entity, $fieldValues) {
            // ...
        }
    ]
);

// Getting an entity (new or singleton)
$factory->get('EntityName', ['field' => 'value']);

// Getting an array of entities
$numberOfEntities = 15;
$factory->getList('EntityName', ['field' => 'value'], $numberOfEntities);

// Singletons
$factory->getAsSingleton('EntityName', ['field' => 'value']);
$factory->setSingleton('EntityName', $entity);
$factory->unsetSingleton('EntityName');

// Configuration
$this->factory->setEntityNamespace('What\Ever');  // Default: empty
$this->factory->persistOnGet();                   // Default: don't persist
$this->factory->persistOnGet(false);

Miscellaneous

  • FixtureFactory and FieldDef are designed to be subclassable.
  • With bidirectional one-to-many associations, the collection on the 'one' side will get updated as long as you've remembered to specify the inversedBy attribute in your mapping.

Development

Tests

The composer packages must be installed with

composer install --prefer-source

breerly/factory-girl-php 适用场景与选型建议

breerly/factory-girl-php 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 242.97k 次下载、GitHub Stars 达 111, 最近一次更新时间为 2012 年 08 月 02 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「testing」 「TDD」 「Fixture」 「factory girl」 「fixture factory」 「fixture replacement」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 breerly/factory-girl-php 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 breerly/factory-girl-php 我们能提供哪些服务?
定制开发 / 二次开发

基于 breerly/factory-girl-php 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 242.97k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 111
  • 点击次数: 23
  • 依赖项目数: 2
  • 推荐数: 2

GitHub 信息

  • Stars: 111
  • Watchers: 4
  • Forks: 23
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2012-08-02