orbitale/array-fixture

基于 PHP 数组的 Doctrine 数据固定服务。

v1.3.5 2024-06-10 19:59 UTC

This package is auto-updated.

Last update: 2024-09-10 20:30:15 UTC


README

一个 Doctrine 数据固定服务,允许您将固定数据作为 PHP 数组编写。

安装

使用 Composer 在您的项目中安装库

composer require orbitale/array-fixture

为什么?

网络上的 Doctrine 固定示例大多如下所示

<?php

namespace AppBundle\DataFixtures\ORM;

use App\Entity\Post;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;

class PostFixtures extends Fixture
{
    public function load(ObjectManager $manager)
    {
        $post = new Post();
        $post->setTitle('First post');
        $post->setDescription('Lorem ipsum');

        $manager->persist($post);
        $manager->flush();
    }
}

此示例包含两个主要问题

  • 它非常冗长:您必须手动完成所有操作。
  • 您的实体 必须 有设置器,因此暴露了一个 贫血模型

此包提供了一个可以使用的固定类(带有或没有 DoctrineBundle),允许您编写 PHP 数组 而不是对象和手动指令。

最大的优势是您可以轻松地将数据库的原始 PHP 导出(例如,使用 PhpMyAdmin)放入固定中(这需要检查字段名称,但搜索/替换将方便地帮助快速完成此操作),或者您还可以从任何来源(当在 Symfony 中使用固定时,固定作为服务注册)获取数据并作为 PHP 数组返回(例如从 API、CSV 文件、Yaml 文件等获取)。

有一些特定功能仅在纯 PHP 模式下可用(例如获取引用和使用 Closure 来填充字段)。

用法

这是一个标准固定类的示例

<?php

namespace AppBundle\DataFixtures\ORM;

use App\Entity\Post;
use Orbitale\Component\ArrayFixture\ArrayFixture;
use Doctrine\Bundle\FixturesBundle\ORMFixtureInterface;

class PostFixtures extends ArrayFixture implements ORMFixtureInterface
{
    public function getEntityClass(): string
    {
        return Post::class;
    }

    public function getObjects(): iterable
    {
        yield ['title' => 'First post', 'description' => 'Lorem ipsum'];
        yield ['title' => 'Second post', 'description' => 'muspi meroL'];
    }
}

让我们来解释一下

  • 我们从这个包中扩展了 ArrayFixture
  • 您必须自己实现 ORMFixtureInterface 以允许 Symfony 自动配置此固定为服务。这在 使用 Symfony 和 DoctrineBundle 时是强制性的
  • getEntityClass() 方法需要知道哪个实体将由此固定类管理。
  • getObjects() 方法必须返回一个 iterable,因此它可以是 arrayGenerator,因为固定将只遍历它。
    getObjects() 中的每个数组都将填充到实体类的实例中,无需设置器。再见贫血实体!
    然后,新实体被持久化。

在所有实体被填充和持久化后,实体管理器将被刷新,实体将被保存到您的数据库中。

引用

有时,一个实体依赖于另一个实体来填充。

使用此固定类,这变得简单得多,因为可以通过覆盖一些方法来简化对实体如何持久化的理解。

注意:为此,您必须确定这些固定必须执行的顺序,通过实现 Doctrine\Common\DataFixtures\OrderedFixtureInterfaceDoctrine\Common\DataFixtures\DependentFixtureInterface 接口(但不能同时实现两者!)。

查看示例

<?php

namespace App\DataFixtures\ORM;

use App\Entity\Tag;
use Orbitale\Component\ArrayFixture\ArrayFixture;
use Doctrine\Bundle\FixturesBundle\ORMFixtureInterface;

class TagFixtures extends ArrayFixture implements ORMFixtureInterface
{
    public function getEntityClass(): string
    {
        return Tag::class;
    }

    public function getReferencePrefix(): ?string
    {
        return 'tags-';
    }

    public function getMethodNameForReference(): string
    {
        return 'getName';
    }

    public function getObjects(): array
    {
        return [
            ['name' => 'Some tag'],
        ];
    }
}

通过覆盖 getReferencePrefixgetMethodNameForReference,在每次 Tag 持久化后,固定将调用此方法在内存中添加对标签的引用

$this->addReference($this->getReferencePrefix().$tag->getName(), $tag);

然后,我们可以创建 PostFixtures 并像使用任何常见的固定一样获取引用

<?php

namespace App\DataFixtures\ORM;

use App\Entity\Post;
use Orbitale\Component\ArrayFixture\ArrayFixture;
use Doctrine\Bundle\FixturesBundle\ORMFixtureInterface;

class PostFixtures extends ArrayFixture implements ORMFixtureInterface
{
    public function getEntityClass(): string
    {
        return Post::class;
    }

    public function getObjects(): array
    {
        return [
            [
                'title' => 'First post',
                'tags' => [
                    $this->getReference('tags-Some tag'),
                ],
            ],
        ];
    }
}

在这里,我们重用了 Some tag 标签名和 tags- 前缀,这些都是在 TagFixtures 中指定的!

🚀小技巧:您还可以在TagFixtures(或任何其他类)中使用类常量来设置标签名(此处为"Some tag"),然后在测试中重复使用它,这样您就可以在不破坏测试的情况下将Some Tag更改为其他内容!

使用可调用函数获取自引用实体

当您有自引用关系时,您可能需要一个可能已经持久化的对象的引用。

为此,首先,您应将flushEveryXIterations选项设置为1(见下文),以允许每次迭代都刷新每个实体。请注意,每次刷新都会消耗更多时间。如果您在 fixtures 中要处理大量实体(如数百个),则不要这样做。

接下来,您可以将callable元素设置为对象的值,以便您可以手动与注入的对象(作为第一个参数)和AbstractFixture对象(作为第二个参数)交互。

如果需要,EntityManagerInterface也作为第三个参数注入,以便您可以进行一些特定的请求或查询。

示例在此

<?php

namespace App\DataFixtures\ORM;

use App\Entity\Post;
use Orbitale\Component\ArrayFixture\ArrayFixture;
use Doctrine\Bundle\FixturesBundle\ORMFixtureInterface;
use Doctrine\ORM\EntityManagerInterface;

class PostFixtures extends ArrayFixture implements ORMFixtureInterface
{
    public function getEntityClass(): string
    {
        return Post::class;
    }

    /**
     * With this, we can retrieve a Post reference with this method:
     *   $this->getReference('posts-1');
     * where '1' is the post id.
     */
    public function getReferencePrefix(): ?string
    {
        return 'posts-';
    }

    protected function flushEveryXIterations(): int
    {
        return 1;
    }

    public function getObjects(): array
    {
        return [
            ['id' => 1, 'title' => 'First post', 'parent' => null],
            [
                'title' => 'Second post',
                'parent' => function(Post $object, ArrayFixture $fixture, EntityManagerInterface $manager) {
                    return $fixture->getReference('posts-1');
                },
            ],
        ];
    }
}

这在使用自引用关系时可以实现完美的同步性。

闭包的优点是,您还可以通过在闭包定义中使用use (...)指令来使用外部引用,或者您还可以调用实体本身、fixture或实体管理器,因为它们作为参数(如示例所示)在运行时传递给闭包。

插入主键

如果您想,您可以指定实体的主键字段,如下所示

public function getObjects(): array
{
    return [
        ['id' => 1, 'title' => 'Post 1'],
        ['id' => 2, 'title' => 'Post 2'],
    ];
}

如果您的键名与id不同,没关系:fixture 类使用 Doctrine 的Metadata来检测您的键。

当使用uuid作为实体字段类型时,这特别有效,因为 UUID 可以从数据库或您的代码本身生成,并且 Doctrine 会将其考虑在内,而与需要从数据库获取反馈以生成 ID 的自增整数字段不同。

请注意,此功能目前尚不支持复合主键(目前,如果您了解一些相关信息,请随时提供帮助!)。

ArrayFixture类参考

ArrayFixture类包含几个您可以覆盖以满足您需求的protected方法

  • getReferencePrefix()(默认值为null
    用于使 fixture 类在每保存一个实体后调用$this->addReference()
    引用存储为{referencePrefix}-{id|__toString()|特定方法名称}
  • getMethodNameForReference()(默认值为getId
    用于指定用于指定引用的对象上的哪个方法。
    默认为getId,如果存在,则始终回退到__toString(),如果不存在方法,则抛出异常。
  • flushEveryXIterations()(默认值为0
    用于批量刷新,而不是在所有 fixtures 持久化结束后只刷新一次。
  • disableLogger()(默认值为true
    用于禁用 SQL 查询记录,在您需要保存数百个实体时,可以在运行时节省内存。
  • clearEntityManagerOnFlush()(默认值为true
    用于确保在flush()后完全清除实体管理器,这可以防止 Doctrine 出现潜在的内存泄漏或高内存消耗,但代价是需要进行更多查询,如果您需要从数据库中获取对象,尽管这应该是一个非常罕见且边缘的情况。
  • createNewInstance(array $data)
    此方法用于自动实例化您的对象(无构造函数)并填充其属性(无设置器)。
    如果您有非常定制化的对象创建处理方式,则可以覆盖它,尽管这个库应该足够使用了。

许可证和版权

本项目采用LGPL-2.1许可证。有关详细信息,请查看LICENSE文件。