You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHPUnit中Zenstruck Foundry Factory密码未哈希问题求助

在PHPUnit中使用Zenstruck Foundry时服务注入失效的问题

我在PHPUnit测试中使用Zenstruck Foundry Factory,只要涉及服务注入就完全失效。已按照文档要求启用扩展,但配置未生效,例如用户密码始终未被哈希。

PHPUnit配置

<phpunit>
    <extensions>
        <bootstrap class="Zenstruck\Foundry\PHPUnit\FoundryExtension"/>
    </extensions>
</phpunit>

UserFactory代码

<?php

namespace App\DataFixtures\Factory;

use App\Entity\User;
use Symfony\Component\PasswordHasher\PasswordHasherInterface;
use Zenstruck\Foundry\Persistence\PersistentObjectFactory;

/**
 * @extends PersistentObjectFactory<User>
 */
final class UserFactory extends PersistentObjectFactory
{
    private static ?User $admin = null;

    public function __construct(
        private readonly ?PasswordHasherInterface $passwordHasher = null
    ) {
        parent::__construct();
    }

    #[\Override]
    public static function class(): string
    {
        return User::class;
    }

    #[\Override]
    protected function defaults(): array|callable
    {
        return [
            'email' => self::faker()->unique()->email(),
            'username' => self::faker()->userName(),
            'password' => self::faker()->password(),
            'color' => ColorFactory::randomOrCreate(),
            'roles' => [User::ROLE_USER],
        ];
    }

    #[\Override]
    protected function initialize(): static
    {
        return $this
            ->afterInstantiate(function(User $user): void {
                if (!$this->passwordHasher instanceof PasswordHasherInterface) {
                    return;
                }

                $user->setPassword($this->passwordHasher->hash($user->getPassword()));
            })
        ;
    }
}

测试代码

#[DataProvider(methodName: 'getCollectionUrls')]
public function testGetCollection(?UserFactory $userFactory, int $expectedCount, int $expectedPageCount): void
{
    ForumsStory::load();
    ForumFactory::createOne(['deletedAt' => new \DateTimeImmutable()]);

    if ($userFactory) {
        $user = $userFactory->create();
        dump($user->getPassword()); // 未哈希
        dump(UserFactory::createOne()->getPassword()); // 同样未哈希

        $response = $this->createClientWithUserCredentials($user->getEmail())->request('GET', '/api/forums');

        dump($response->toArray());
    } else {
        $response = $this->client->request('GET', '/api/forums');
    }

    $this->assertResponseIsSuccessful();
    $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');

    $this->assertJsonContains([
        '@context' => '/api/contexts/Forum',
        '@id' => '/api/forums',
        '@type' => 'Collection',
        'totalItems' => $expectedCount,
        'view' => [
            '@id' => '/api/forums?page=1',
            '@type' => 'PartialCollectionView',
            'first' => '/api/forums?page=1',
            'next' => '/api/forums?page=2',
            'last' => '/api/forums?page=' . $expectedPageCount,
        ],
    ]);

    $this->assertCount(30, $response->toArray()['member']);

    $this->assertMatchesResourceCollectionJsonSchema(Forum::class);
}

public static function getCollectionUrls(): iterable
{
    yield 'all anonymous forums' => [
        null,
        35,
        2,
    ];
    yield 'all normal user forums' => [
        UserFactory::new([
            'password' => self::DEFAULT_PASSWORD,
        ]),
        35,
        2,
    ];
    yield 'all coach forums' => [
        UserFactory::new([
            'password' => self::DEFAULT_PASSWORD,
            'roles' => [User::ROLE_COACH],
        ]),
        70,
        3,
    ];
    yield 'all admin forums' => [
        UserFactory::new([
            'password' => self::DEFAULT_PASSWORD,
            'roles' => [User::ROLE_ADMIN],
        ]),
        70,
        3,
    ];
}

解决方案

1. 修正PHPUnit扩展配置

原配置使用了bootstrap标签,这是旧的加载方式,正确配置应使用extension标签:

<phpunit>
    <extensions>
        <extension class="Zenstruck\Foundry\PHPUnit\FoundryExtension"/>
    </extensions>
</phpunit>

2. 替换正确的密码哈希服务接口

Symfony中通常使用UserPasswordHasherInterface而非底层的PasswordHasherInterface,修改UserFactory:

use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;

// ...

public function __construct(
    private readonly ?UserPasswordHasherInterface $passwordHasher = null
) {
    parent::__construct();
}

// ...

->afterInstantiate(function(User $user): void {
    if (!$this->passwordHasher instanceof UserPasswordHasherInterface) {
        return;
    }

    $user->setPassword($this->passwordHasher->hashPassword($user, $user->getPassword()));
})

3. 避免在DataProvider中直接创建Factory实例

DataProvider静态执行时服务容器尚未初始化,传递配置数组而非Factory实例:

public static function getCollectionUrls(): iterable
{
    yield 'all anonymous forums' => [
        null,
        35,
        2,
    ];
    yield 'all normal user forums' => [
        ['password' => self::DEFAULT_PASSWORD],
        35,
        2,
    ];
    yield 'all coach forums' => [
        ['password' => self::DEFAULT_PASSWORD, 'roles' => [User::ROLE_COACH]],
        70,
        3,
    ];
    yield 'all admin forums' => [
        ['password' => self::DEFAULT_PASSWORD, 'roles' => [User::ROLE_ADMIN]],
        70,
        3,
    ];
}

修改测试方法逻辑:

#[DataProvider(methodName: 'getCollectionUrls')]
public function testGetCollection(?array $userConfig, int $expectedCount, int $expectedPageCount): void
{
    ForumsStory::load();
    ForumFactory::createOne(['deletedAt' => new \DateTimeImmutable()]);

    if ($userConfig) {
        $user = UserFactory::createOne($userConfig);
        dump($user->getPassword()); // 此时密码已哈希
        $response = $this->createClientWithUserCredentials($user->getEmail())->request('GET', '/api/forums');
        dump($response->toArray());
    } else {
        $response = $this->client->request('GET', '/api/forums');
    }

    // ... 其余断言逻辑不变
}

4. 确保Factory被注册为服务

检查config/services_test.yaml,确保Factory被自动扫描或手动注册:

services:
    App\DataFixtures\Factory\UserFactory:
        autowire: true
        tags: ['zenstruck_foundry.factory']

内容的提问来源于stack exchange,提问作者Gaylord.P

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.11 18:05:53