Как определять отношения с абстрактными классами и интерфейсами

Дата обновления перевода 2023-01-18

Как определять отношения с абстрактными классами и интерфейсами

Одной из целью пакетов является создание разумных пакетов функциональностей, которые не имеют много зависимостей (или не имеют их вообще), что позволяет вам использовать эту функциональность в других приложениях, не включая ненужные вещи.

Doctrine 2.2 включает новую утилиту, называемую ResolveTargetEntityListener, которая функционирует путём перехвата некоторых вызовов внутри Doctrine и переопределением параметром targetEntity в маршрутизации ваших метаданных во время прогона. Это означает, что в вашем пакете вы можете использовать интерфейс, или абстрактный класс в ваших отображениях, и ожидать правильного отображения к точной сущности во время прогона.

Эта функциональность позволяет вам определять отношения между разными сущностями,
не делая их жёсткими зависимостями.

Задний план

Представьте, что у вас есть InvoiceBundle, который предоставляет функциональность выставления счетов-фактур и CustomerBundle, который содержит пользовательские инструменты управления. Вам нужно держать их раздельно, так как они могут быть использованы в других системах друг без друга, но для вашего приложения вы хотите использовать их вместе.

В этом случаев, у вас есть сущность Invoice с отношением к несуществующему объекту InvoiceSubjectInterface. Целью является заставить ResolveTargetEntityListener заменять любое упоминание об интерфейсе реальным объектом, реализующим этот интерфейс.

Установка

Эта статья использует две следующие базовые сущности (которые неполные для краткости) для того, чтобы объяснить, как установить и использовать ResolveTargetEntityListener.

Сущность пользователя:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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
{
    // В этом примере, любые методы, определённые в InvoiceSubjectInterface
    // уже реализованы в BaseCustomer
}

Сущность счёта-фактуры:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 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
{
    /**
     * @var InvoiceSubjectInterface
     */
    #[ORM\ManyToOne(targetEntity: InvoiceSubjectInterface::class)]
    protected $subject;
}

InvoiceSubjectInterface:

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

/**
 * Интерфейс, который должен реализовывать объект счёта-фактуры Subject.
 * В большинстве случаев, только один объект должен реализовывать
 * этот интерфейс, так как ResolveTargetEntityListener может только
 * изменять цель одного объекта.
 */
interface InvoiceSubjectInterface
{
    // Укажите все дополнительные методы, к которым InvoiceBundle
    // понадобится получить доступ, чтобы вы могли
    // быть уверены, что у вас есть доступ к этим методам.

    public function getName(): string;
}

Далее, вам нужно сконфигурировать слушатель, которые сообщает DoctrineBundle о замене:

  • YAML
  • XML
  • PHP
1
2
3
4
5
6
7
# config/packages/doctrine.yaml
doctrine:
    # ...
    orm:
        # ...
        resolve_target_entities:
            App\Model\InvoiceSubjectInterface: App\Entity\Customer

Заключение

С помощью ResolveTargetEntityListener, вы можете разъединять ваши пакеты, чтобы их можно было использовать по-отдельности, но у вас остаётся возможность определять отношения между разными объектами. Используя этот метод, вашими пакетами будет легче управлять по-отдельности.