Як визначати відносини з абстрактними класами та інтерфейсами

Дата оновлення перекладу 2023-09-04

Як визначати відносини з абстрактними класами та інтерфейсами

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

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
// 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;
}

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;

/**
 * Інтерфейс, який має реалізовувати обʼєкт Субʼєкта інвойсу.
 * У більшості випадків, лише один обʼєкт має реалізовувати цей
 * інтерфейс, так як ResolveTargetEntityListener може змінюватися
 * ціль лише для одного обʼєкта.
 */
interface InvoiceSubjectInterface
{
    // Вкажіть всі додаткові методи, до яких InvoiceBundle
    // знадобиться отримати доступ, щоб ви могли бути впевнені,
    // що у вас є доступ до цих методів.

    public function getName(): string;
}

Далі, вам потрібно сконфігурувати слухача, який повідомляє DoctrineBundle про заміну:

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

Висновки

За допомогою ResolveTargetEntityListener, ви можете розʼєднувати ваші пакети, щоб їх можна було використовувати окремо, але у вас залишається можливість визначати відносини між різними обʼєктами. Використовуючи цей метод, вашими пакетами буде легше управляти окремо.