sonata-project / SonataAdminBundle

The missing Symfony Admin Generator
https://docs.sonata-project.org/projects/SonataAdminBundle
MIT License
2.11k stars 1.26k forks source link

AppendFormField loses parent admins. #7185

Closed VincentLanglet closed 3 years ago

VincentLanglet commented 3 years ago

When using a childAdmin, with an url like:

/admin/foo/1/admin/bar/create

And the following configureNewInstance:

protected function alterNewInstance(object $object): void
{
    parent::alterNewInstance($object);

    if ($this->isChild()) {
        $parentSubject = $this->getParent()->getSubject();
        if ($parentSubject instanceof Foo) {
            $object->setFoo($parentSubject);
        }
    }
}

The Foo entity is automatically set to the new Bar entity.

Inside the BarAdmin, I use a CollectionType with an entity Baz. In the BazAdmin, I would expect that the Foo entity, accessible by,

$this->getSubject()->getBar()

would have the Foo entity freshly set. But currently it's not set.

This is because the AppendFormFieldElementAction is creating the admin this way: https://github.com/sonata-project/SonataAdminBundle/blob/3.x/src/Action/AppendFormFieldElementAction.php#L54-L59 Here: $admin is a BarAdmin, but the parent admin FooAdmin (with the subject id 1) is not set.

Then, since we're inside a creating https://github.com/sonata-project/SonataAdminBundle/blob/3.x/src/Action/AppendFormFieldElementAction.php#L66-L73 The else part is creating a newInstance, and the Foo entity won't be set.

The childAdmin with the ids should be sended to the request. And the getInstance should create the admin with the parentAdmin set.

VincentLanglet commented 3 years ago

I reopen this because it's not totally solved.

It works fine for the first element of a CollectionType.

Indeed, the Request is: image So, the _route_param exist and is passed to the AppendFormFieldAction. https://github.com/sonata-project/SonataAdminBundle/blob/3.x/src/Resources/views/CRUD/Association/edit_many_script.html.twig#L327

But, the adminFetcher is setting the new Request to the admin. https://github.com/sonata-project/SonataAdminBundle/blob/3.x/src/Request/AdminFetcher.php#L61

So I am loosing all the previous request attributes, in particular the _route_params ones. image Then, as soon as I click again on the Add button, the parent admin is lost.

I find a "solution", using another query param to keep the value of _route_params:

url: '{{ path('sonata_admin_append_form_element', {
    ...
    'toto': sonata_admin.admin.root.request.get('toto', sonata_admin.admin.root.request.attributes.get('_route_params', {}))
} + (
    sonata_admin.admin.root.hasRequest()
    ? sonata_admin.admin.root.request.get('toto', sonata_admin.admin.root.request.attributes.get('_route_params', {}))
    : {}
)) }}',

But I don't think it's the best solution.

Any idea @franmomu how I could solve this ?

franmomu commented 3 years ago

It's normal to not have them, IIRC _route_params are the parameters that are in the route, so for example given the route /admin/foo/{id}/bar/{childId}/baz and this URL /admin/foo/1/bar/2/baz, _route_params will hold:

[
    'id' => 1,
    'childId' => 2,
]

in case of sonata_admin_append_form_element, this route doesn't have any placeholders (therefore it's normal to have an empty _route_params), so all the parameters passed in the URL to this route will be in the query part.

Back to the issue, do you have a small example or if you could create one, I don't see why it works for the first one and not the the others.

VincentLanglet commented 3 years ago

It's normal to not have them, IIRC _route_params are the parameters that are in the route, so for example given the route /admin/foo/{id}/bar/{childId}/baz and this URL /admin/foo/1/bar/2/baz, _route_params will hold:

[
    'id' => 1,
    'childId' => 2,
]

Yes, it's the case as showed in the first screenshot.

in case of sonata_admin_append_form_element, this route doesn't have any placeholders (therefore it's normal to have an empty _route_params), so all the parameters passed in the URL to this route will be in the query part.

Yes, I agree, but then I have the following issue.

Back to the issue, do you have a small example or if you could create one, I don't see why it works for the first one and not the the others.

I don't have a small example, but I will end creating one if I can't be clear.

The script has the following code

url: '{{ path('sonata_admin_append_form_element', {
    ...
} + (
    sonata_admin.admin.root.hasRequest()
    ? sonata_admin.admin.root.request.get('_route_params', {})
    : {}
)) }}',

When loading the page, the Admin has the request from the url /admin/foo/{id}/bar/{childId}/baz, so _route_params will holds:

[
    'id' => 1,
    'childId' => 2,
]

When you click on the Add button, sonata_admin.admin.root.request is the current request so you'll find the '_route_params' ; they will be passed to the sonata_admin_append_form_element route.

Then, in the AppendFormFieldAction, the admin fetcher will look for the admin. In this code, we're doing

$rootAdmin->setRequest($request);

But the request set is the new request, the one sent to the AppendFormFieldAction which mean

So then we're regenerating the oneToMany with Js, and recreating a new add button, the value

sonata_admin.admin.root.request.get('_route_params', {})

use the new request set, with no route params.

I wonder if I should app.request instead, this would maybe avoid the request to be updated. I'll try.

VincentLanglet commented 3 years ago

I wonder if I should app.request instead, this would maybe avoid the request to be updated. I'll try.

Tried, it doesn't work. The app request is updated as soon as I click on the add button.

What I would like is to keep the value of the initial request, even after the call ajax...

franmomu commented 3 years ago

So then we're regenerating the oneToMany with Js, and recreating a new add button, the value

I thought the "add button" was always the same, so every time you add an item, everything gets regenerated?

VincentLanglet commented 3 years ago

I thought the "add button" was always the same, so every time you add an item, everything gets regenerated?

Yes

I found a solution, with

url: '{{ path('sonata_admin_append_form_element', {
    ...
}
+ (
    sonata_admin.admin.root.hasRequest()
    ? sonata_admin.admin.root.request.get('_route_params', {})
    : {}
)
 + app.request.query.all
) }}',

This way query params are not lost, I'll make a PR