普通的PHP对象

1.1.1 2024-04-15 13:40 UTC

This package is auto-updated.

Last update: 2024-09-15 14:32:41 UTC


README

A Popo(Plain old PHP object)是我们对Pojo(Plain old Java object)的实现。这些对象的目的是用严格类型化的PHP对象替换所有原本应该是键值数组的变量。

此包仅包含一个类(BasePopo),该类旨在由我们创建的每个Popo扩展。BasePopo类确保您可以从前端返回Popo到后端,并且它们将自动转换为JSON,而无需编写代码。除此之外,BasePopo还为所有Popo提供了toArray()函数。

安装

composer require scrumble-nl/popo

使用

创建新的Popo对象时,只需扩展BasePopo。在将对象转换为数组或将Popo返回到前端时,只会包含公共属性,因此需要将可见属性标记为公共。

基本使用

class ProductPopo extends BasePopo
{    
    /**
     * @var string
     */
    public string $name;

    /**
     * @var float
     */
    public float $price;

    /**
     * @param string $name
     * @param float  $price
     */
    public function __construct(string $name, float $price)
    {
        $this->name = $name;
        $this->price = $price;
    }
}

初始化

要实例化Popo,您可以使用构造函数,就像您已经习惯的那样,或者在前端本身中开发一个可重用的辅助函数。此函数应考虑输入数据来自何处以及数据的格式。

例如,典型的API响应将解码为JSON对象或数组。实例化Popo的函数如下所示

public static function fromApiResponse(array $apiResponse): ProductPopo
{
    return new self(
        $apiResponse['name'],
        (float) $apiResponse['price'],
    );
}

您可以从项目的任何位置调用此函数来实例化Popo: ProductPopo::fromApiResponse($array);

您可以自由实现其他方法,如fromObjectfromArray,以适应您的最佳情况。

严格类型和子Popo

您可能还有其他情况,其中Popo有自己的子Popo。例如,一个ProductPopo可能有一个属性public Collection $categories,这将是一个CategoryPopo的集合。

为了确保在使用子Popo的数组或集合时最佳使用严格类型,请确保在PHPDoc中类型提示这些内容。

/**
* @var Collection<int, CategoryPopo>
*/
public Collection $categories;

测试

在编写应用程序测试时,您可能会发现创建Popo对象的实例很有用。一种方法是使用工厂。

工厂是一个负责创建另一个类实例的类。在Popo对象的上下文中,工厂可以用来创建具有随机或特定值的Popo对象实例,这对于测试非常有用。

测试使用

首先,为您的Popo对象创建一个工厂类。工厂类应有一个方法,该方法返回具有随机或特定值的Popo对象实例。以下是为ProductPopo对象创建的示例工厂类。

use Tests\Popo\ProductPopo;
use Scrumble\Popo\PopoFactory;

class ProductPopoFactory extends PopoFactory
{
    /**
     * @var null|string
     */
    public ?string $popoClass = ProductPopo::class;

    /**
     * {@inheritDoc}
     */
    public function definition(): array
    {
        return [
            'name' => $this->faker->name,
            'price' => $this->faker->randomNumber(1, 10),
        ];
    }
}

然后,确保将工厂包含在Popo的定义中。

use Scrumble\Popo\HasPopoFactory;

class ProductPopo extends BasePopo
{
    use HasPopoFactory;
    
    /**
     * @var string
     */
    public string $name;

    /**
     * @var float
     */
    public float $price;

    /**
     * @param string $name
     * @param float  $price
     */
    public function __construct(string $name, float $price)
    {
        $this->name = $name;
        $this->price = $price;
    }
    
    /**
     * @return string
     */
    public static function popoFactory(): string
    {
        return ProductPopoFactory::class;
    }
}

工厂创建

假设您有一个服务函数create,它接收一个ProductPopo对象并根据其属性创建一个新的产品。您可以使用Popo工厂轻松地为此函数创建测试数据。

/** @test */
public function can_create_product(): void
{
    $productPopo = ProductPopo::factory()->create();

    $product = $this->productService->create($productPopo);

    $this->assertDatabaseHas('products', [
        'name' => $productPopo->name,
        'price' => $productPopo->price,
    ]);
}

创建多个

有时,您可能希望在单元测试中创建具有略微不同属性的Popo对象的多个实例。您可以使用此包提供的count()sequence()方法来实现这一点。

count(int $count) 方法接收一个整数作为参数,告诉工厂要创建多少个实例。sequence(array|closure $sequence) 方法接收一个包含每个要创建的实例的属性值的数组。您还可以向 sequence() 方法提供一个闭包,该闭包返回一个属性值数组。

以下是一个如何使用 count()sequence() 创建具有稍微不同属性的多个 Popo 实例的示例

ProductPopo::factory()
    ->count(3)
    ->sequence(
        ['name' => 'Apples'],
        ['name' => 'Cookies'],
        ['name' => 'Bread'],
    )
    ->create();

请注意,向 create() 方法传递属性将覆盖 sequence() 中传递的值。

原始数据

raw(array $attributes = []) 函数返回一个数组,包含用于创建具有工厂值的数组的属性。

$productData = ProductPopo::factory()->raw();

覆盖数据

state() 函数是 Laravel 工厂的一个强大功能,允许您定义一组属性,这些属性将覆盖工厂的默认值。当您需要创建一个 Popo,它具有不能通过默认工厂生成的特定属性集时,这非常有用。

以下是一个如何使用 state() 函数设置 Popo 状态的示例

class ProductPopoFactory extends Factory
{
    ...

    public function published(): array
    {
        return $this->state(function (array $attributes) {
            return [
                'published' => true,
            ];
        });
    }
}

在这个例子中,我们为 ProductPopo 类定义了一个工厂,其中包含一个默认属性集,包括将 published 属性设置为 false。我们还定义了一个 published() 函数,该函数使用 state() 函数将 published 属性设置为 true。

现在,假设我们想要创建一个已发布的 Popo。我们可以通过在工厂上调用 published() 函数来实现这一点

$popo = ProductPopo::factory()->published()->create();

您可以通过向 create() 函数传递一个包含属性值的数组来覆盖特定数据。该数组应包含属性名称作为键,所需值作为对应值。数组中的值将覆盖任何默认值以及使用 state() 函数设置的任何值。

$popo = ProductPopo::factory()->create([
    'name' => 'New product',
]);

在上面的示例中,我们正在创建一个新的 ProductPopo 实例,并使用我们自己的值覆盖默认名称值。

同样,您也可以使用 raw() 函数覆盖特定数据。通过向 raw() 函数传递一个包含属性值的数组,这些值将覆盖任何默认值以及使用 state() 函数设置的任何值。

$productData = ProductPopo::factory()->raw([
    'name' => 'New product',
]);

在上面的示例中,我们正在使用 raw() 函数创建一个新的 ProductPopo 实例属性数组,并使用我们自己的值覆盖默认名称值。

贡献

如果您想看到对这个包的添加/更改,您始终欢迎添加一些代码或改进它。

Scrumble

该产品最初由 Scrumble 为内部使用开发。因为我们使用了大量的开源包,我们想要回馈社区。我们希望这能帮助您像其他人帮助我们的那样取得进步!