Symfony LiveComponent表单测试出现There is currently no session available错误
测试Symfony LiveComponent表单时出现SessionNotFoundException的问题
问题概述
测试带表单的Symfony LiveComponent时触发报错:There is currently no session available,错误发生在Symfony为表单生成CSRF令牌的环节。该组件在网页端运行正常,但测试环境下无法正常初始化Session导致失败。
相关代码
测试文件(/tests/SomeTest.php)
class SomeTest extends WebTestCase { use InteractsWithLiveComponents; public function testSomething(): void { $client = static::createClient(); $productRepository = static::getContainer()->get(ProductRepository::class); $userRepository = static::getContainer()->get(UserRepository::class); $product = $productRepository->findOneBy(['name' => 'Chino Beige']); $user = $userRepository->findOneBy(['username' => 'admin']); $component = $this->createLiveComponent(AddToCart::class, [ 'product' => $product, ])->actingAs($user); $this->assertInstanceOf(TestLiveComponent::class, $component); $this->assertEquals(200, $component->response()->getStatusCode()); } }
LiveComponent表单实例化方法(/src/Twig/Components/Form/AddToCart.php)
protected function instantiateForm(): FormInterface { $cartItem = new CartItem(); return $this->createForm(AddToCartType::class, $cartItem, [ 'product' => $this->product, 'cart' => $this->cartRepository->findOneBy(['customer' => $this->getUser(), 'catalog' => $this->product->getCatalog()]), 'view_mode' => $this->product->getCatalog()->getViewMode(), ]); }
AddToCartType表单代码
<?php namespace App\Form; // use [...] class AddToCartType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('product', EntityType::class, [ 'class' => Product::class, 'data' => $options['product'], 'required' => true, 'constraints' => [ new NotBlank(), ], ]) ->add('cart', EntityType::class, [ 'class' => Cart::class, 'data' => $options['cart'], 'choice_label' => 'id', 'required' => true, 'constraints' => [ new NotBlank(), ], ]) ; if ($options['view_mode'] === 'grid') { foreach ($options['product']->getMeasures() as $measure) { $builder->add('quantity_' . $measure->getId(), IntegerType::class, [ 'mapped' => false, 'label' => $measure->getName(), 'data' => 0, 'required' => false, 'attr' => [ 'min' => 0, ], 'constraints' => [ new PositiveOrZero(), ], ]); } $builder->add('total_quantity', HiddenType::class, [ 'mapped' => false, 'label' => false, 'required' => true, 'data' => 0, 'constraints' => [ new TotalQuantity(), ], ]); } else { $builder ->add('measure', EntityType::class, [ 'label' => 'app.add_to_cart.measures', 'class' => Measure::class, 'query_builder' => function (MeasureRepository $measureRepository) use ($options): QueryBuilder { $qb = $measureRepository->createQueryBuilder('m'); return $qb->andWhere($qb->expr()->in('m.id', ':ids')) ->setParameter('ids', $options['product']->getMeasures()->map(fn($measure) => $measure->getId())); }, 'expanded' => true, 'required' => true, 'placeholder' => false, 'constraints' => [ new NotBlank(), ], ]) ->add('quantity', IntegerType::class, [ 'label' => 'app.add_to_cart.quantity', 'data' => 0, 'attr' => [ 'min' => 0, ], 'required' => true, 'constraints' => [ new Positive(), ], ]) ->add('receiver', TextType::class, [ 'label' => 'app.add_to_cart.receiver', 'attr' => [ 'placeholder' => 'app.add_to_cart.receiver', ], 'required' => true, 'constraints' => [ new NotBlank(), ], ]) ; } } public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'data_class' => CartItem::class, 'product' => null, 'cart' => null, 'view_mode' => 'classic', ]); $resolver->setAllowedTypes('product', Product::class); $resolver->setAllowedTypes('cart', Cart::class); $resolver->setAllowedTypes('view_mode', 'string'); } }
报错堆栈跟踪
Symfony\Component\HttpFoundation\Exception\SessionNotFoundException : There is currently no session available. .../vendor/symfony/http-foundation/RequestStack.php:105 ...:vendor/symfony/security-csrf/TokenStorage/SessionTokenStorage.php:104 .../vendor/symfony/security-csrf/TokenStorage/SessionTokenStorage.php:71 .../vendor/symfony/security-csrf/CsrfTokenManager.php:69 .../vendor/symfony/form/Extension/Csrf/Type/FormTypeCsrfExtension.php:78 .../vendor/symfony/form/ResolvedFormType.php:125 .../vendor/symfony/form/Extension/DataCollector/Proxy/ResolvedTypeDataCollectorProxy.php:86 .../vendor/symfony/form/ResolvedFormType.php:119 .../vendor/symfony/form/Extension/DataCollector/Proxy/ResolvedTypeDataCollectorProxy.php:86 .../vendor/symfony/form/Form.php:904 .../vendor/symfony/ux-live-component/src/ComponentWithFormTrait.php:118 .../vendor/symfony/ux-live-component/src/ComponentWithFormTrait.php:93 .../vendor/symfony/ux-twig-component/src/ComponentFactory.php:205 .../vendor/symfony/ux-twig-component/src/ComponentFactory.php:91 .../vendor/symfony/ux-twig-component/src/ComponentFactory.php:65 .../vendor/symfony/ux-live-component/src/Test/TestLiveComponent.php:174 .../vendor/symfony/ux-live-component/src/Test/TestLiveComponent.php:123 .../vendor/symfony/ux-live-component/src/Test/TestLiveComponent.php:51 .../tests/SomeTest.php:31
环境信息
- Symfony 7.0.6
- PHP 8.3
- PHPUnit 9.6.19
- Symfony UX Live Component 2.17
原因分析与解决方法
核心原因
测试中通过createLiveComponent直接创建组件时,上下文未初始化Session;而表单生成CSRF令牌依赖Session存储令牌数据。虽然调用了actingAs($user),但该方法仅设置认证用户,不会自动创建Session。
解决方案
方案1:手动在测试中启动Session
修改测试代码,先通过客户端触发请求初始化Session:
public function testSomething(): void { $client = static::createClient(); // 发起请求以初始化Session $client->request('GET', '/'); $session = $client->getContainer()->get('session'); $session->start(); $productRepository = static::getContainer()->get(ProductRepository::class); $userRepository = static::getContainer()->get(UserRepository::class); $product = $productRepository->findOneBy(['name' => 'Chino Beige']); $user = $userRepository->findOneBy(['username' => 'admin']); $component = $this->createLiveComponent(AddToCart::class, [ 'product' => $product, ])->actingAs($user); $this->assertInstanceOf(TestLiveComponent::class, $component); $this->assertEquals(200, $component->response()->getStatusCode()); }
方案2:测试环境下禁用表单CSRF保护
在LiveComponent的表单实例化逻辑中,判断环境并临时关闭CSRF:
protected function instantiateForm(): FormInterface { $cartItem = new CartItem(); $formOptions = [ 'product' => $this->product, 'cart' => $this->cartRepository->findOneBy(['customer' => $this->getUser(), 'catalog' => $this->product->getCatalog()]), 'view_mode' => $this->product->getCatalog()->getViewMode(), ]; // 测试环境禁用CSRF if ($this->container->get('kernel')->getEnvironment() === 'test') { $formOptions['csrf_protection'] = false; } return $this->createForm(AddToCartType::class, $cartItem, $formOptions); }
方案3:通过客户端请求渲染组件
放弃直接创建组件,改为通过客户端请求包含组件的页面,自动初始化Session:
public function testSomething(): void { $client = static::createClient(); $productRepository = static::getContainer()->get(ProductRepository::class); $userRepository = static::getContainer()->get(UserRepository::class); $product = $productRepository->findOneBy(['name' => 'Chino Beige']); $user = $userRepository->findOneBy(['username' => 'admin']); // 模拟用户登录 $client->loginUser($user); // 请求包含AddToCart组件的页面(替换为实际路由) $client->request('GET', '/product/' . $product->getId()); $this->assertEquals(200, $client->getResponse()->getStatusCode()); // 可选:断言组件存在 $this->assertSelectorExists('[data-controller="live-component"]'); }
内容的提问来源于stack exchange,提问作者Dylan Adam
相关产品推荐
相关产品推荐

