Skip to content

Commit c747e6a

Browse files
Add functional regression test for EntityValueResolver on primary-key routes (#2230)
1 parent bf7ba3f commit c747e6a

5 files changed

Lines changed: 203 additions & 1 deletion

File tree

composer.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"symfony/http-kernel": "^6.4 || ^7.0",
6060
"symfony/messenger": "^6.4 || ^7.0",
6161
"symfony/property-info": "^6.4 || ^7.0",
62+
"symfony/runtime": "^6.4 || ^7.0",
6263
"symfony/security-bundle": "^6.4 || ^7.0",
6364
"symfony/stopwatch": "^6.4 || ^7.0",
6465
"symfony/string": "^6.4 || ^7.0",
@@ -97,7 +98,8 @@
9798
"allow-plugins": {
9899
"composer/package-versions-deprecated": true,
99100
"dealerdirect/phpcodesniffer-composer-installer": true,
100-
"symfony/flex": true
101+
"symfony/flex": true,
102+
"symfony/runtime": false
101103
},
102104
"sort-packages": true
103105
},
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Bundle\DoctrineBundle\Tests\ArgumentResolver;
6+
7+
use Doctrine\Bundle\DoctrineBundle\Tests\ArgumentResolver\Fixtures\EntityValueResolverFunctionalKernel;
8+
use Doctrine\Bundle\DoctrineBundle\Tests\ArgumentResolver\Fixtures\Post;
9+
use Doctrine\ORM\EntityManagerInterface;
10+
use PHPUnit\Framework\TestCase;
11+
use Symfony\Component\HttpFoundation\Request;
12+
13+
use function assert;
14+
use function interface_exists;
15+
use function json_decode;
16+
17+
/**
18+
* Regression coverage for the /posts/{post} + Post $post happy path resolved by
19+
* Symfony\Bridge\Doctrine\ArgumentResolver\EntityValueResolver. The tag priority
20+
* configured in config/orm.php (and the priorities of FrameworkBundle's own
21+
* value resolvers) must stay compatible so that the entity argument is still
22+
* populated from the route placeholder.
23+
*/
24+
class EntityValueResolverFunctionalTest extends TestCase
25+
{
26+
public function testEntityArgumentResolvedFromRoutePlaceholder(): void
27+
{
28+
if (! interface_exists(EntityManagerInterface::class)) {
29+
self::markTestSkipped('This test requires ORM');
30+
}
31+
32+
$kernel = new EntityValueResolverFunctionalKernel();
33+
$kernel->boot();
34+
35+
try {
36+
$container = $kernel->getContainer();
37+
$em = $container->get('doctrine.orm.default_entity_manager');
38+
assert($em instanceof EntityManagerInterface);
39+
40+
// SchemaTool::createSchema triggers Schema::createTable, deprecated in
41+
// DBAL 4 (https://github.com/doctrine/dbal/pull/7373); the DBAL 4 fluent
42+
// replacement (Table::editor() / Column::editor()) is unavailable on
43+
// DBAL 3 which this branch must still support, so a raw CREATE TABLE is
44+
// emitted directly through the connection. The test kernel is SQLite-
45+
// only, so a SQLite literal is safe here.
46+
$em->getConnection()->executeStatement(
47+
'CREATE TABLE posts (id INTEGER NOT NULL, title VARCHAR(255) NOT NULL, PRIMARY KEY(id))',
48+
);
49+
50+
$post = new Post('Hello world');
51+
$em->persist($post);
52+
$em->flush();
53+
$em->clear();
54+
55+
$response = $kernel->handle(Request::create('/posts/' . $post->id, 'GET'));
56+
57+
self::assertSame(200, $response->getStatusCode());
58+
self::assertSame(
59+
['id' => $post->id, 'title' => 'Hello world'],
60+
json_decode((string) $response->getContent(), true),
61+
);
62+
} finally {
63+
$kernel->shutdown();
64+
}
65+
}
66+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Bundle\DoctrineBundle\Tests\ArgumentResolver\Fixtures;
6+
7+
use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
8+
use Doctrine\ORM\Configuration;
9+
use Psr\Log\NullLogger;
10+
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
11+
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
12+
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
13+
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
14+
use Symfony\Component\HttpKernel\Kernel;
15+
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
16+
17+
use function md5;
18+
use function method_exists;
19+
use function mt_rand;
20+
use function sys_get_temp_dir;
21+
22+
use const PHP_VERSION_ID;
23+
24+
class EntityValueResolverFunctionalKernel extends Kernel
25+
{
26+
use MicroKernelTrait;
27+
28+
private string|null $projectDir = null;
29+
30+
public function __construct()
31+
{
32+
parent::__construct('test', true);
33+
}
34+
35+
/** @return iterable<BundleInterface> */
36+
public function registerBundles(): iterable
37+
{
38+
return [
39+
new FrameworkBundle(),
40+
new DoctrineBundle(),
41+
];
42+
}
43+
44+
protected function configureContainer(ContainerConfigurator $container): void
45+
{
46+
$container->extension('framework', [
47+
'secret' => 'F00',
48+
'http_method_override' => false,
49+
'annotations' => false,
50+
'php_errors' => ['log' => true],
51+
'handle_all_throwables' => true,
52+
'router' => ['utf8' => true],
53+
'test' => true,
54+
]);
55+
56+
$container->extension('doctrine', [
57+
'dbal' => [
58+
'driver' => 'pdo_sqlite',
59+
'memory' => true,
60+
'schema_manager_factory' => 'doctrine.dbal.default_schema_manager_factory',
61+
],
62+
'orm' => [
63+
'controller_resolver' => ['auto_mapping' => false],
64+
'enable_lazy_ghost_objects' => true,
65+
/** @phpstan-ignore function.alreadyNarrowedType */
66+
'enable_native_lazy_objects' => PHP_VERSION_ID >= 80400 && method_exists(Configuration::class, 'enableNativeLazyObjects'),
67+
'mappings' => [
68+
'PostFixtures' => [
69+
'type' => 'attribute',
70+
'dir' => __DIR__,
71+
'prefix' => 'Doctrine\Bundle\DoctrineBundle\Tests\ArgumentResolver\Fixtures',
72+
],
73+
],
74+
],
75+
]);
76+
77+
$services = $container->services();
78+
$services->set('logger', NullLogger::class);
79+
$services->set(PostController::class)
80+
->autowire()
81+
->autoconfigure()
82+
->public()
83+
->tag('controller.service_arguments');
84+
}
85+
86+
protected function configureRoutes(RoutingConfigurator $routes): void
87+
{
88+
$routes->add('post_show', '/posts/{post}')->controller(PostController::class);
89+
}
90+
91+
public function getProjectDir(): string
92+
{
93+
return $this->projectDir ??= sys_get_temp_dir() . '/sf_evr_kernel_' . md5((string) mt_rand());
94+
}
95+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Bundle\DoctrineBundle\Tests\ArgumentResolver\Fixtures;
6+
7+
use Doctrine\DBAL\Types\Types;
8+
use Doctrine\ORM\Mapping as ORM;
9+
10+
#[ORM\Entity]
11+
#[ORM\Table(name: 'posts')]
12+
class Post
13+
{
14+
#[ORM\Id]
15+
#[ORM\GeneratedValue(strategy: 'AUTO')]
16+
#[ORM\Column(type: Types::INTEGER)]
17+
public int|null $id = null;
18+
19+
public function __construct(
20+
#[ORM\Column(type: Types::STRING)]
21+
public string $title,
22+
) {
23+
}
24+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Bundle\DoctrineBundle\Tests\ArgumentResolver\Fixtures;
6+
7+
use Symfony\Component\HttpFoundation\JsonResponse;
8+
9+
class PostController
10+
{
11+
public function __invoke(Post $post): JsonResponse
12+
{
13+
return new JsonResponse(['id' => $post->id, 'title' => $post->title]);
14+
}
15+
}

0 commit comments

Comments
 (0)