Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit5675aa8

Browse files
committed
feature#43854 [DoctrineBridge] Add an Entity Argument Resolver (jderusse, nicolas-grekas)
This PR was merged into the 6.2 branch.Discussion----------[DoctrineBridge] Add an Entity Argument Resolver| Q | A| ------------- | ---| Branch? | 6.1| Bug fix? | no| New feature? | yes| Deprecations? | no| Tickets | part of#40333| License | MIT| Doc PR | todoThis PR provides an Argument Resolver for Doctrine entities.This would replace the SensioFramework's DoctrineParamConverter (in fact most of the code is copy/pasted from here) and helps users to disable the paramConverter and fix the related issue.usage:```yamlsensio_framework_extra: request: converters: falseservices: Symfony\Bridge\Doctrine\ArgumentResolver\EntityValueResolver: ~``````php#[Route('/blog/{slug}')]public function show(Post $post){}```or with custom options```php#[Route('/blog/{id}')]public function show( #[MapEntity(entityManager: 'foo', expr: 'repository.findNotDeletedById(id)')] Post $post){}```Commits-------5a3df5e Improve EntityValueResolver (#3)4524083 Add an Entity Argument Resolver
2 parents397abb6 +5a3df5e commit5675aa8

File tree

4 files changed

+909
-0
lines changed

4 files changed

+909
-0
lines changed
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespaceSymfony\Bridge\Doctrine\ArgumentResolver;
13+
14+
useDoctrine\DBAL\Types\ConversionException;
15+
useDoctrine\ORM\EntityManagerInterface;
16+
useDoctrine\ORM\NoResultException;
17+
useDoctrine\Persistence\ManagerRegistry;
18+
useDoctrine\Persistence\ObjectManager;
19+
useSymfony\Bridge\Doctrine\Attribute\MapEntity;
20+
useSymfony\Component\ExpressionLanguage\ExpressionLanguage;
21+
useSymfony\Component\HttpFoundation\Request;
22+
useSymfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
23+
useSymfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
24+
useSymfony\Component\HttpKernel\Exception\NotFoundHttpException;
25+
26+
/**
27+
* Yields the entity matching the criteria provided in the route.
28+
*
29+
* @author Fabien Potencier <fabien@symfony.com>
30+
* @author Jérémy Derussé <jeremy@derusse.com>
31+
*/
32+
finalclass EntityValueResolverimplements ArgumentValueResolverInterface
33+
{
34+
publicfunction__construct(
35+
privateManagerRegistry$registry,
36+
private ?ExpressionLanguage$expressionLanguage =null,
37+
privateMapEntity$defaults =newMapEntity(),
38+
) {
39+
}
40+
41+
/**
42+
* {@inheritdoc}
43+
*/
44+
publicfunctionsupports(Request$request,ArgumentMetadata$argument):bool
45+
{
46+
if (!$this->registry->getManagerNames()) {
47+
returnfalse;
48+
}
49+
50+
$options =$this->getOptions($argument);
51+
if (!$options->class ||$options->disabled) {
52+
returnfalse;
53+
}
54+
55+
// Doctrine Entity?
56+
if (!$objectManager =$this->getManager($options->objectManager,$options->class)) {
57+
returnfalse;
58+
}
59+
60+
return !$objectManager->getMetadataFactory()->isTransient($options->class);
61+
}
62+
63+
/**
64+
* {@inheritdoc}
65+
*/
66+
publicfunctionresolve(Request$request,ArgumentMetadata$argument):iterable
67+
{
68+
$options =$this->getOptions($argument);
69+
$name =$argument->getName();
70+
$class =$options->class;
71+
72+
$errorMessage =null;
73+
if (null !==$options->expr) {
74+
if (null ===$object =$this->findViaExpression($class,$request,$options->expr,$options)) {
75+
$errorMessage =sprintf('The expression "%s" returned null',$options->expr);
76+
}
77+
// find by identifier?
78+
}elseif (false ===$object =$this->find($class,$request,$options,$name)) {
79+
// find by criteria
80+
if (false ===$object =$this->findOneBy($class,$request,$options)) {
81+
if (!$argument->isNullable()) {
82+
thrownew \LogicException(sprintf('Unable to guess how to get a Doctrine instance from the request information for parameter "%s".',$name));
83+
}
84+
85+
$object =null;
86+
}
87+
}
88+
89+
if (null ===$object && !$argument->isNullable()) {
90+
$message =sprintf('"%s" object not found by the "%s" Argument Resolver.',$class,self::class);
91+
if ($errorMessage) {
92+
$message .=''.$errorMessage;
93+
}
94+
95+
thrownewNotFoundHttpException($message);
96+
}
97+
98+
return [$object];
99+
}
100+
101+
privatefunctiongetManager(?string$name,string$class): ?ObjectManager
102+
{
103+
if (null ===$name) {
104+
return$this->registry->getManagerForClass($class);
105+
}
106+
107+
if (!isset($this->registry->getManagerNames()[$name])) {
108+
returnnull;
109+
}
110+
111+
try {
112+
return$this->registry->getManager($name);
113+
}catch (\InvalidArgumentException) {
114+
returnnull;
115+
}
116+
}
117+
118+
privatefunctionfind(string$class,Request$request,MapEntity$options,string$name):false|object|null
119+
{
120+
if ($options->mapping ||$options->exclude) {
121+
returnfalse;
122+
}
123+
124+
$id =$this->getIdentifier($request,$options,$name);
125+
if (false ===$id ||null ===$id) {
126+
returnfalse;
127+
}
128+
129+
$objectManager =$this->getManager($options->objectManager,$class);
130+
if ($options->evictCache &&$objectManagerinstanceof EntityManagerInterface) {
131+
$cacheProvider =$objectManager->getCache();
132+
if ($cacheProvider &&$cacheProvider->containsEntity($class,$id)) {
133+
$cacheProvider->evictEntity($class,$id);
134+
}
135+
}
136+
137+
try {
138+
return$objectManager->getRepository($class)->find($id);
139+
}catch (NoResultException|ConversionException) {
140+
returnnull;
141+
}
142+
}
143+
144+
privatefunctiongetIdentifier(Request$request,MapEntity$options,string$name):mixed
145+
{
146+
if (\is_array($options->id)) {
147+
$id = [];
148+
foreach ($options->idas$field) {
149+
// Convert "%s_uuid" to "foobar_uuid"
150+
if (str_contains($field,'%s')) {
151+
$field =sprintf($field,$name);
152+
}
153+
154+
$id[$field] =$request->attributes->get($field);
155+
}
156+
157+
return$id;
158+
}
159+
160+
if (null !==$options->id) {
161+
$name =$options->id;
162+
}
163+
164+
if ($request->attributes->has($name)) {
165+
return$request->attributes->get($name);
166+
}
167+
168+
if (!$options->id &&$request->attributes->has('id')) {
169+
return$request->attributes->get('id');
170+
}
171+
172+
returnfalse;
173+
}
174+
175+
privatefunctionfindOneBy(string$class,Request$request,MapEntity$options):false|object|null
176+
{
177+
if (null ===$mapping =$options->mapping) {
178+
$keys =$request->attributes->keys();
179+
$mapping =$keys ?array_combine($keys,$keys) : [];
180+
}
181+
182+
foreach ($options->excludeas$exclude) {
183+
unset($mapping[$exclude]);
184+
}
185+
186+
if (!$mapping) {
187+
returnfalse;
188+
}
189+
190+
// if a specific id has been defined in the options and there is no corresponding attribute
191+
// return false in order to avoid a fallback to the id which might be of another object
192+
if (\is_string($options->id) &&null ===$request->attributes->get($options->id)) {
193+
returnfalse;
194+
}
195+
196+
$criteria = [];
197+
$objectManager =$this->getManager($options->objectManager,$class);
198+
$metadata =$objectManager->getClassMetadata($class);
199+
200+
foreach ($mappingas$attribute =>$field) {
201+
if (!$metadata->hasField($field) && (!$metadata->hasAssociation($field) || !$metadata->isSingleValuedAssociation($field))) {
202+
continue;
203+
}
204+
205+
$criteria[$field] =$request->attributes->get($attribute);
206+
}
207+
208+
if ($options->stripNull) {
209+
$criteria =array_filter($criteria,staticfn ($value) =>null !==$value);
210+
}
211+
212+
if (!$criteria) {
213+
returnfalse;
214+
}
215+
216+
try {
217+
return$objectManager->getRepository($class)->findOneBy($criteria);
218+
}catch (NoResultException|ConversionException) {
219+
returnnull;
220+
}
221+
}
222+
223+
privatefunctionfindViaExpression(string$class,Request$request,string$expression,MapEntity$options): ?object
224+
{
225+
if (!$this->expressionLanguage) {
226+
thrownew \LogicException(sprintf('You cannot use the "%s" if the ExpressionLanguage component is not available. Try running "composer require symfony/expression-language".',__CLASS__));
227+
}
228+
229+
$repository =$this->getManager($options->objectManager,$class)->getRepository($class);
230+
$variables =array_merge($request->attributes->all(), ['repository' =>$repository]);
231+
232+
try {
233+
return$this->expressionLanguage->evaluate($expression,$variables);
234+
}catch (NoResultException|ConversionException) {
235+
returnnull;
236+
}
237+
}
238+
239+
privatefunctiongetOptions(ArgumentMetadata$argument):MapEntity
240+
{
241+
/** @var MapEntity $options */
242+
$options =$argument->getAttributes(MapEntity::class, ArgumentMetadata::IS_INSTANCEOF)[0] ??$this->defaults;
243+
244+
return$options->withDefaults($this->defaults,$argument->getType());
245+
}
246+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespaceSymfony\Bridge\Doctrine\Attribute;
13+
14+
/**
15+
* Indicates that a controller argument should receive an Entity.
16+
*/
17+
#[\Attribute(\Attribute::TARGET_PARAMETER)]
18+
class MapEntity
19+
{
20+
publicfunction__construct(
21+
public ?string$class =null,
22+
public ?string$objectManager =null,
23+
public ?string$expr =null,
24+
public ?array$mapping =null,
25+
public ?array$exclude =null,
26+
public ?bool$stripNull =null,
27+
publicarray|string|null$id =null,
28+
public ?bool$evictCache =null,
29+
publicbool$disabled =false,
30+
) {
31+
}
32+
33+
publicfunctionwithDefaults(self$defaults, ?string$class):static
34+
{
35+
$clone =clone$this;
36+
$clone->class ??=class_exists($class ??'') ?$class :null;
37+
$clone->objectManager ??=$defaults->objectManager;
38+
$clone->expr ??=$defaults->expr;
39+
$clone->mapping ??=$defaults->mapping;
40+
$clone->exclude ??=$defaults->exclude ?? [];
41+
$clone->stripNull ??=$defaults->stripNull ??false;
42+
$clone->id ??=$defaults->id;
43+
$clone->evictCache ??=$defaults->evictCache ??false;
44+
45+
return$clone;
46+
}
47+
}

‎src/Symfony/Bridge/Doctrine/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
6.2
55
---
66

7+
* Add`#[MapEntity]` with its corresponding`EntityArgumentResolver`
78
* Add`NAME` constant to`UlidType` and`UuidType`
89

910
6.0

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp