Symfony
sponsored by SensioLabs
Menu
  • About
  • Documentation
  • Screencasts
  • Cloud
  • Certification
  • Community
  • Businesses
  • News
  • Download
  1. Home
  2. Documentation
  3. Doctrine
  4. How to Define Relationships with Abstract Classes and Interfaces
  • Documentation
  • Book
  • Reference
  • Bundles
  • Cloud
Search by Algolia

Table of Contents

  • Background
  • Set up
  • Final Thoughts

How to Define Relationships with Abstract Classes and Interfaces

Edit this page

Warning: You are browsing the documentation for Symfony 4.2, which is no longer maintained.

Read the updated version of this page for Symfony 6.2 (the current stable version).

How to Define Relationships with Abstract Classes and Interfaces

One of the goals of bundles is to create discreet bundles of functionality that do not have many (if any) dependencies, allowing you to use that functionality in other applications without including unnecessary items.

Doctrine 2.2 includes a new utility called the ResolveTargetEntityListener, that functions by intercepting certain calls inside Doctrine and rewriting targetEntity parameters in your metadata mapping at runtime. It means that in your bundle you are able to use an interface or abstract class in your mappings and expect correct mapping to a concrete entity at runtime.

This functionality allows you to define relationships between different entities without making them hard dependencies.

Background

Suppose you have an InvoiceBundle which provides invoicing functionality and a CustomerBundle that contains customer management tools. You want to keep these separated, because they can be used in other systems without each other, but for your application you want to use them together.

In this case, you have an Invoice entity with a relationship to a non-existent object, an InvoiceSubjectInterface. The goal is to get the ResolveTargetEntityListener to replace any mention of the interface with a real object that implements that interface.

Set up

This article uses the following two basic entities (which are incomplete for brevity) to explain how to set up and use the ResolveTargetEntityListener.

A Customer entity:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// src/Entity/Customer.php
namespace App\Entity;

use Acme\CustomerBundle\Entity\Customer as BaseCustomer;
use Acme\InvoiceBundle\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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// src/Acme/InvoiceBundle/Entity/Invoice.php
namespace Acme\InvoiceBundle\Entity;

use Acme\InvoiceBundle\Model\InvoiceSubjectInterface;
use Doctrine\ORM\Mapping as ORM;

/**
 * Represents an Invoice.
 *
 * @ORM\Entity
 * @ORM\Table(name="invoice")
 */
class Invoice
{
    /**
     * @ORM\ManyToOne(targetEntity="Acme\InvoiceBundle\Model\InvoiceSubjectInterface")
     * @var InvoiceSubjectInterface
     */
    protected $subject;
}

An InvoiceSubjectInterface:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// src/Acme/InvoiceBundle/Model/InvoiceSubjectInterface.php
namespace Acme\InvoiceBundle\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.

    /**
     * @return string
     */
    public function getName();
}

Next, you need to configure the listener, which tells the DoctrineBundle about the replacement:

  • YAML
  • XML
  • PHP
1
2
3
4
5
6
7
# config/packages/doctrine.yaml
doctrine:
    # ...
    orm:
        # ...
        resolve_target_entities:
            Acme\InvoiceBundle\Model\InvoiceSubjectInterface: App\Entity\Customer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 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="Acme\InvoiceBundle\Model\InvoiceSubjectInterface">App\Entity\Customer</doctrine:resolve-target-entity>
        </doctrine:orm>
    </doctrine:config>
</container>
1
2
3
4
5
6
7
8
9
10
11
12
// config/packages/doctrine.php
use Acme\InvoiceBundle\Model\InvoiceSubjectInterface;
use App\Entity\Customer;

$container->loadFromExtension('doctrine', [
    'orm' => [
        // ...
        'resolve_target_entities' => [
            InvoiceSubjectInterface::class => Customer::class,
        ],
    ],
]);

Final Thoughts

With the ResolveTargetEntityListener, you are able to decouple your bundles, keeping them usable by themselves, but still being able to define relationships between different objects. By using this method, your bundles will end up being easier to maintain independently.

This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
We stand with Ukraine.
Version:
Measure & Improve Symfony Code Performance

Measure & Improve Symfony Code Performance

Check Code Performance in Dev, Test, Staging & Production

Check Code Performance in Dev, Test, Staging & Production

↓ Our footer now uses the colors of the Ukrainian flag because Symfony stands with the people of Ukraine.

Avatar of Javad Adib, a Symfony contributor

Thanks Javad Adib for being a Symfony contributor

1 commit • 6 lines changed

View all contributors that help us make Symfony

Become a Symfony contributor

Be an active part of the community and contribute ideas, code and bug fixes. Both experts and newcomers are welcome.

Learn how to contribute

Symfony™ is a trademark of Symfony SAS. All rights reserved.

  • What is Symfony?
    • Symfony at a Glance
    • Symfony Components
    • Case Studies
    • Symfony Releases
    • Security Policy
    • Logo & Screenshots
    • Trademark & Licenses
    • symfony1 Legacy
  • Learn Symfony
    • Symfony Docs
    • Symfony Book
    • Reference
    • Bundles
    • Best Practices
    • Training
    • eLearning Platform
    • Certification
  • Screencasts
    • Learn Symfony
    • Learn PHP
    • Learn JavaScript
    • Learn Drupal
    • Learn RESTful APIs
  • Community
    • SymfonyConnect
    • Support
    • How to be Involved
    • Code of Conduct
    • Events & Meetups
    • Projects using Symfony
    • Downloads Stats
    • Contributors
    • Backers
  • Blog
    • Events & Meetups
    • A week of symfony
    • Case studies
    • Cloud
    • Community
    • Conferences
    • Diversity
    • Documentation
    • Living on the edge
    • Releases
    • Security Advisories
    • SymfonyInsight
    • Twig
    • SensioLabs
  • Services
    • SensioLabs services
    • Train developers
    • Manage your project quality
    • Improve your project performance
    • Host Symfony projects
    Deployed on
Follow Symfony
Search by Algolia