orbitale / array-fixture
基于 PHP 数组的 Doctrine 数据固定服务。
Requires
- php: ^7.3|^8.0
- doctrine/data-fixtures: ^1.4
- doctrine/instantiator: ^1.3|^2.0
- doctrine/persistence: ^1.3|^2.0|^3.0
Requires (Dev)
- doctrine/mongodb-odm: ^2.2
- doctrine/orm: ^2.7|^3.0
- phpstan/phpstan: ^1.11
- phpstan/phpstan-deprecation-rules: ^1.2
- phpstan/phpstan-doctrine: ^1.4
- phpstan/phpstan-phpunit: ^1.4
- phpstan/phpstan-symfony: ^1.4
- phpunit/phpunit: ^9.5
Suggests
- doctrine/doctrine-fixtures-bundle: To use with ORMFixtureInterface and register fixtures automatically in the Dependency Injection container.
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
,因此它可以是array
或Generator
,因为固定将只遍历它。
在getObjects()
中的每个数组都将填充到实体类的实例中,无需设置器。再见贫血实体!
然后,新实体被持久化。
在所有实体被填充和持久化后,实体管理器将被刷新,实体将被保存到您的数据库中。
引用
有时,一个实体依赖于另一个实体来填充。
使用此固定类,这变得简单得多,因为可以通过覆盖一些方法来简化对实体如何持久化的理解。
注意:为此,您必须确定这些固定必须执行的顺序,通过实现 Doctrine\Common\DataFixtures\OrderedFixtureInterface
或 Doctrine\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'], ]; } }
通过覆盖 getReferencePrefix
和 getMethodNameForReference
,在每次 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文件。