Skip to content

[LiveComponent] LiveComponentHydrator doesn't handle interfaces #1590

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
gremo opened this issue Mar 7, 2024 · 9 comments · Fixed by #1593
Closed

[LiveComponent] LiveComponentHydrator doesn't handle interfaces #1590

gremo opened this issue Mar 7, 2024 · 9 comments · Fixed by #1593

Comments

@gremo
Copy link
Contributor

gremo commented Mar 7, 2024

My model is a Product which has a typed discount property of type DiscountInterface:

namespace App\Model;

class Product
{
    public ?DiscountInterface $discount = null;
}

I'musing Product as a LiveProp (it doesn't matter how I use it or where, keep reading).

During the de/hydration process, a warning is raised:

Warning: foreach() argument must be of type array|object, null given.
in vendor/symfony/ux-live-component/src/LiveComponentHydrator.php (line 490)

Investigating more, around the dehydrateObjectValue of LiveComponentHydrator:

  • The passed $value is the instance of App\Model\StackableDiscount
  • The $classType is DiscountInterface
  • The getProperties on PropertyInfoExtractor returns null (hence the foreach warning)

image

I think this is a bug but I don't know how to fix it. Logically, we can't get properties from an interface. $classType is cleary wrong here.

Maybe:

if (is_subclass_of($value, $classType, true)) {
    $classType = get_class($value);
}
@gremo gremo changed the title [LiveComponent] LiveComponentHydrator bug with interfaces [LiveComponent] LiveComponentHydrator <s>bug with interfaces</s> Mar 7, 2024
@gremo gremo changed the title [LiveComponent] LiveComponentHydrator <s>bug with interfaces</s> [LiveComponent] LiveComponentHydrator doesn't handle objects with no public properties "well" Mar 7, 2024
@gremo gremo changed the title [LiveComponent] LiveComponentHydrator doesn't handle objects with no public properties "well" [LiveComponent] LiveComponentHydrator doesn't handle interfaces Mar 7, 2024
@smnandre
Copy link
Member

smnandre commented Mar 7, 2024

The documentation sadly states

You can also use a DTO (i.e. data transfer object / any simple class) with LiveProp as long as the property has the correct type.

But i don't bring only bad news... :) Just after that are listed the different option you can use when you're out of the standard cases:

Maybe one of those is the right fit for you ?

@gremo
Copy link
Contributor Author

gremo commented Mar 7, 2024

The documentation sadly states

You can also use a DTO (i.e. data transfer object / any simple class) with LiveProp as long as the property has the correct type.

But i don't bring only bad news... :) Just after that are listed the different option you can use when you're out of the standard cases:

Maybe one of those is the right fit for you ?

Thanks for the input. As interfaces resolve to concrete classes, why this can't implement in the source code? Basically only the classType is wrong ..

@weaverryan
Copy link
Member

There would need to be some way to hint to the system that the DiscountInterface should be hydrated into a Discount object -e.g.

namespace App\Model;

class Product
{
    // something here ... or on the `product` LiveProp in your component to hint that the dehydrated data for this should be rehydrated into a specific class
    public ?DiscountInterface $discount = null;
}

So, this is solveable - but not sure the best path at the moment. Btw, the root of the issue is that we don't expose your server-side metadata to the client. So by the time the data is sent to the frontend, this DiscountInterface object has been dehydrated to scalar data, with no mention of the concrete class they originally came from. Then when we make an Ajax call and send that data back, the system need to figure out which concrete DiscountInterface class it should use, without knowing what the original data class was.

@gremo
Copy link
Contributor Author

gremo commented Mar 7, 2024

@weaverryan I see now. So the problem isn't the dehydrate but the hydrate where the lack of "metadata", that is... which class that implements the DiscountInterface should I instantiate?

Anyways, in the meanwhile, a problem remain:

(new PropertyInfoExtractor([new ReflectionExtractor()]))->getProperties($classType)

This return null for interfaces (maybe for abastract classes too?)... and this will causa an obscure warning in developer. It takes time to figure out which property is causing that warning.

Why just not throw if object class it's an interface, after checking for extensions to handle it?

@norkunas
Copy link
Contributor

norkunas commented Mar 8, 2024

@gremo I wanted to improve same thing in symfony/symfony#41420
if you really need to stick to interface you could add your own PropertyInfoExtractor in chain which would resolve interface <-> class mapping

@weaverryan
Copy link
Member

This return null for interfaces (maybe for abastract classes too?)... and this will causa an obscure warning in developer. It takes time to figure out which property is causing that warning.

Throwing a better error would be a simple, but huge step forward. Do you think you could create a PR?

@gremo
Copy link
Contributor Author

gremo commented Mar 8, 2024

This return null for interfaces (maybe for abastract classes too?)... and this will causa an obscure warning in developer. It takes time to figure out which property is causing that warning.

Throwing a better error would be a simple, but huge step forward. Do you think you could create a PR?

I'm afraid I don't have enough knowledge on how that component works (I mean the PropertyInfoExtractor) 😢

@smnandre
Copy link
Member

smnandre commented Mar 8, 2024

I'm opening the PR in 20mn

Adding the following exception message:

Unable to dehydrate value of type "App\Entity\ConcreteProduct" for property "product" typed as "App\Entity\ProductInterface" on component "App\Component\ProductSearchComponent". Change this to a concrete type of an object that can be dehydrated. Or set the hydrateWith/dehydrateWith options in LiveProp or set "useSerializerForHydration: true" on the LiveProp to use the serializer.

@smnandre
Copy link
Member

smnandre commented Mar 8, 2024

20mn = 1hour.... classic 😆

@kbond kbond closed this as completed in 500d3f9 Mar 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants