SyliusResourceBundle: Doctrine class metadata not found for extended interface

Sylius version affected: 1.x

Description
For any resource, it should be possible to get the Doctrine class metadata using any of the interfaces for the resource defined at the component / core / app level.

Steps to reproduce

  1. Create a new project from Sylius-Standard.
  2. Try to get Doctrine class metadata using Sylius\Component\Core\Model\ProductInterface.

Possible Solution
I think there is a design problem.

If you look at https://github.com/Sylius/SyliusResourceBundle/blob/v1.5.0/src/Bundle/DependencyInjection/Compiler/DoctrineTargetEntitiesResolverPass.php

it does not go up the inheritance chain - and it’s not safe to do so, as we can’t tell which interfaces should be mapped, and which should not. So if we set interface to a child interface, things would stop working if you try using the parent interface(s).

What is actually expected is that all of the interfaces should work, e.g.:

  • Sylius\Component\Product\Model\ProductInterface
  • Sylius\Component\Core\Model\ProductInterface
  • App\Model\ProductInterface (for example)

So we need a way to map multiple interfaces.

About this issue

  • Original URL
  • State: open
  • Created 5 years ago
  • Reactions: 2
  • Comments: 27 (26 by maintainers)

Commits related to this issue

Most upvoted comments

I’ve been thinking about a long-term solution for this issue. The interface passed in the configuration is used only for resolving Doctrine relationships.

Defining multiple interfaces sounds like an easy way to work around the current limitation, but still requires manual changes whenever changing the interface and the knowledge that the change is possible.

Please let me know what you think, thanks for your patience. @teohhanhui @vvasiloi

Proposed solution

I’d go for autodiscovering interfaces based on model class and using the unique ones for resolving target entities. Let’s assume the following scenario from Sylius:

interface ResourceInterface {}

interface UserInterface extends ResourceInterface {}

interface ShopUserInterface extends UserInterface {}
interface AdminUserInterface extends UserInterface{}

class ShopUser implements ShopUserInterface {}
class AdminUser implements AdminUserInterface {}

The first step would be to gather all the interfaces implemented by each resource:

  • AdminUser: AdminUserInterface, UserInterface, ResourceInterface
  • ShopUser: ShopUserInterface, UserInterface, ResourceInterface

We can always filter out ResourceInterface as it’s too generic. The interfaces that are implemented by more than one resource are also filtered out. This leaves us with:

  • AdminUser: AdminUserInterface, ~UserInterface~, ~ResourceInterface~
  • ShopUser: ShopUserInterface, ~UserInterface, ResourceInterface~

Extending entities

Going further, let’s follow a case in which someone decides to extend AdminUser:

interface MyAdminUserInterface extends AdminUserInterface {}
class MyAdminUser implements MyAdminUserInterface { use SomeTraitFromPlugin; }

The following configuration will be needed (same as now):

sylius_user:
  resources:
    admin:
      user:
        classes:
          model: MyAdminUser

No need to change interface value (configuration merging for multiple interfaces could be tricky), interfaces are autodiscovered.

  • AdminUser: MyAdminUserInterface, AdminUserInterface, ~UserInterface~, ~ResourceInterface~
  • ShopUser: ShopUserInterface, ~UserInterface, ResourceInterface~

Backwards compatibility

If interface configuration option is passed, trigger a deprecation notice and add it to the list of autodiscovered interfaces after filtering it.

Possible improvements outside of MVP

If an unresolved interface is used in the mapping and it has been filtered out from the list earlier, throw an exception containing all matching models and their interfaces.

To get rid of the deprecation we have to remove the default value, but not the key as it will result in errors when it’s defined in end-projects.