mirror of
https://github.com/symfony/symfony-docs.git
synced 2026-03-24 00:32:14 +01:00
149 lines
5.2 KiB
ReStructuredText
149 lines
5.2 KiB
ReStructuredText
Referencing Entities with Abstract Classes and Interfaces
|
|
=========================================================
|
|
|
|
In applications where functionality is organized in layers or modules with
|
|
minimal concrete dependencies, such as monoliths split into multiple modules,
|
|
it can be challenging to avoid tight coupling between entities.
|
|
|
|
Doctrine provides a utility called the ``ResolveTargetEntityListener`` to solve
|
|
this issue. It works by intercepting certain calls within Doctrine and rewriting
|
|
``targetEntity`` parameters in your metadata mapping at runtime. This allows you
|
|
to reference an interface or abstract class in your mappings and have it resolved
|
|
to a concrete entity at runtime.
|
|
|
|
This makes it possible to define relationships between entities without
|
|
creating hard dependencies. This feature also works with the ``EntityValueResolver``
|
|
:ref:`as explained in the main Doctrine article <doctrine-entity-value-resolver-resolve-target-entities>`.
|
|
|
|
.. versionadded:: 7.3
|
|
|
|
Support for target entity resolution in the ``EntityValueResolver`` was
|
|
introduced Symfony 7.3
|
|
|
|
Background
|
|
----------
|
|
|
|
Suppose you have an application with two modules: an Invoice module that
|
|
provides invoicing functionality, and a Customer module that handles customer
|
|
management. You want to keep these modules decoupled, so that neither is aware
|
|
of the other's implementation details.
|
|
|
|
In this case, your ``Invoice`` entity has a relationship to the interface
|
|
``InvoiceSubjectInterface``. Since interfaces are not valid Doctrine entities,
|
|
the goal is to use the ``ResolveTargetEntityListener`` to replace all
|
|
references to this interface with a concrete class that implements it.
|
|
|
|
Set up
|
|
------
|
|
|
|
This article uses two basic (incomplete) entities to demonstrate how to set up
|
|
and use the ``ResolveTargetEntityListener``.
|
|
|
|
A ``Customer`` entity::
|
|
|
|
// src/Entity/Customer.php
|
|
namespace App\Entity;
|
|
|
|
use App\Entity\CustomerInterface as BaseCustomer;
|
|
use App\Model\InvoiceSubjectInterface;
|
|
use Doctrine\ORM\Mapping as ORM;
|
|
|
|
#[ORM\Entity]
|
|
#[ORM\Table(name: 'customer')]
|
|
class Customer extends BaseCustomer implements InvoiceSubjectInterface
|
|
{
|
|
// In this example, any methods defined in the InvoiceSubjectInterface
|
|
// are already implemented in the BaseCustomer
|
|
}
|
|
|
|
An ``Invoice`` entity::
|
|
|
|
// src/Entity/Invoice.php
|
|
namespace App\Entity;
|
|
|
|
use App\Model\InvoiceSubjectInterface;
|
|
use Doctrine\ORM\Mapping as ORM;
|
|
|
|
#[ORM\Entity]
|
|
#[ORM\Table(name: 'invoice')]
|
|
class Invoice
|
|
{
|
|
#[ORM\ManyToOne(targetEntity: InvoiceSubjectInterface::class)]
|
|
protected InvoiceSubjectInterface $subject;
|
|
}
|
|
|
|
The interface representing the subject used in the invoice::
|
|
|
|
// src/Model/InvoiceSubjectInterface.php
|
|
namespace App\Model;
|
|
|
|
/**
|
|
* An interface that the invoice Subject object should implement.
|
|
* In most circumstances, only a single object should implement
|
|
* this interface as the ResolveTargetEntityListener can only
|
|
* change the target to a single object.
|
|
*/
|
|
interface InvoiceSubjectInterface
|
|
{
|
|
// List any additional methods that your InvoiceBundle
|
|
// will need to access on the subject so that you can
|
|
// be sure that you have access to those methods.
|
|
|
|
public function getName(): string;
|
|
}
|
|
|
|
Now configure the ``resolve_target_entities`` option to tell Doctrine
|
|
how to replace the interface with the concrete class:
|
|
|
|
.. configuration-block::
|
|
|
|
.. code-block:: yaml
|
|
|
|
# config/packages/doctrine.yaml
|
|
doctrine:
|
|
# ...
|
|
orm:
|
|
# ...
|
|
resolve_target_entities:
|
|
App\Model\InvoiceSubjectInterface: App\Entity\Customer
|
|
|
|
.. code-block:: xml
|
|
|
|
<!-- config/packages/doctrine.xml -->
|
|
<?xml version="1.0" encoding="UTF-8" ?>
|
|
<container xmlns="http://symfony.com/schema/dic/services"
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xmlns:doctrine="http://symfony.com/schema/dic/doctrine"
|
|
xsi:schemaLocation="http://symfony.com/schema/dic/services
|
|
https://symfony.com/schema/dic/services/services-1.0.xsd
|
|
http://symfony.com/schema/dic/doctrine
|
|
https://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd">
|
|
|
|
<doctrine:config>
|
|
<doctrine:orm>
|
|
<!-- ... -->
|
|
<doctrine:resolve-target-entity interface="App\Model\InvoiceSubjectInterface">App\Entity\Customer</doctrine:resolve-target-entity>
|
|
</doctrine:orm>
|
|
</doctrine:config>
|
|
</container>
|
|
|
|
.. code-block:: php
|
|
|
|
// config/packages/doctrine.php
|
|
use App\Entity\Customer;
|
|
use App\Model\InvoiceSubjectInterface;
|
|
use Symfony\Config\DoctrineConfig;
|
|
|
|
return static function (DoctrineConfig $doctrine): void {
|
|
$orm = $doctrine->orm();
|
|
// ...
|
|
$orm->resolveTargetEntity(InvoiceSubjectInterface::class, Customer::class);
|
|
};
|
|
|
|
Final Thoughts
|
|
--------------
|
|
|
|
Using ``ResolveTargetEntityListener`` allows you to decouple your modules
|
|
while still defining relationships between their entities. This makes your
|
|
codebase more modular and easier to maintain over time.
|