Doctrine2: эффективное сопоставление унаследованного класса

#orm #doctrine-orm #symfony

#orm #doctrine-orm #symfony

Вопрос:

У меня проблема с выяснением того, как настроить сопоставление моих классов с Doctrine2.

Допустим, у меня есть эти таблицы:

 Address table 
--------------------- 
- id 
- civic_no 
- road 
- state 
- country 

PersonnalAddress table 
--------------------- 
- id 
- base_address_id 
- type 
- is_primary 

BusinessAddress table 
--------------------- 
- id 
- base_address_id 
- business_name 
- shipping_phone 
- is_primary 
  

И эти объекты PHP:

 class Address{}
class BusinessAddress extends Address{}
class PersonalAddress extends Address{}
  

Учитывая следующие требования:

  • Адрес может существовать сам по себе (класс Address не является абстрактным)
  • Личный адрес и бизнес-адрес могут содержать одни и те же адресные данные
  • Если я удалю или отредактирую адрес, это повлияет на все деловые или личные адреса, которые унаследованы от него.
  • Я не хочу никакого дублирования данных в базе данных (это требование 2-й обычной формы)
  • Прокси-методы означают дублирование кода, я предпочитаю их не иметь.
  • Магические методы не очень хороши с точки зрения тестируемости, я предпочитаю их не использовать.

Чтобы лучше проиллюстрировать проблему, я ожидаю, что данные в базе данных будут выглядеть следующим образом:

 Address table:
id | civic_no | road | state | country
 1        123   test      qc        ca

PersonnalAddress table:
id | base_address_id | type | is_primary 
 1                 1      A            0
 2                 1      B            1

BusinessAddress table:
id | base_address_id | business_name | shipping_phone | is_primary 
 1                 1       chic choc          1231234            1
  

Какова была бы наилучшая стратегия для внедрения решения , соответствующего этим требованиям ?

Ответ №1:

Хорошо, это немного длинновато, но я думаю, что оно охватывает все ваши базы, если у вас есть какие-либо вопросы, не стесняйтесь задавать.

Это связано с оговоркой, которую я не знаю, сможете ли вы сделать Many-To-One для MappedSuperclass . Если это невозможно, то вместо этого вы можете использовать наследование таблицы классов. Попробуйте и скажите нам, работает ли это.

Имейте в виду, что я довольно быстро удалил этот код, он непроверенный, поэтому может быть некорректным, но, надеюсь, вы получите представление о том, как это работает.

Поехали!

Интерфейс, позволяющий упростить определение «что такое адрес» без создания абстрактных классов, переопределяющих методы и вызывающих ощущение «плохого дизайна» 😉

 interface Address {

    function getCivicNo();

    function getRoad();

    function getState();

    function getCountry();
}
  

Абстрактная сущность, которая вам на самом деле не нужна, но если вы ее не используете, вам нужно дублировать идентификационный код.

 /**
 * @ORMMappedSuperclass
 */
abstract class AbstractEntity {

    /**
     * Entity ID column.
     * 
     * @var integer
     * 
     * @ORMId
     * @ORMColumn(type="integer")
     * @ORMGeneratedValue
     */
    private $id;

    public function getId() {
        return $id;
    }

}
  

Базовый адрес, который вы можете хранить отдельно или связать со «сложным адресом»

 /**
 * @ORMEntity
 */
class BasicAddress extends AbstractEntity implements Address {

    /** @ORMColumn() */
    private $road;

    public function getRoad() {
        return $this->road;
    }

    // etc etc

}
  

«ComplexAddress» здесь как раз для того, чтобы позволить вам повторно использовать код для делегирования вызовов на базовый адрес.

 /**
 * @ORMMappedSuperclass
 */
abstract class ComplexAddress extends AbstractEntity implements Address {

    /** @ORMMany-To-One(targetEntity="BasicAddress")
    private $basicAddress;

    public function __construct(BasicAddress $basicAddress) {
        $this->basicAddress = $basicAddress;
    }

    public function getRoad() {
        return $this->basicAddress->getRoad();
    }

    // other methods for implementing "Address" just delegate to BasicAddress

}
  

Общедоступный адрес

 /**
 * @ORMEntity()
 */
class PersonalAddress extends ComplexAddress {

    /** @ORMColumn(type="boolean") */
    private $isPrimary;

    public function isPrimary() {
        return $isPrimary;
    }

    // other personal address methods here

}
  

Бизнес-адрес

 /**
 * @ORMEntity()
 */
class BusinessAddress extends ComplexAddress {

    /** @ORMColumn() */
    private $businessName;

    public function getBusinessName() {
        return $this->businessName;
    }

    // other business address methods here

}
  

Редактировать: Только что заметил, что забыл указать каскадные параметры для удаления, возможно, вам потребуется обработать это напрямую — при удалении BasicAddress также удаляются другие адреса, которые его используют.

Комментарии:

1. Я думаю, это могло бы сработать, в руководстве говорится: «Это означает, что ассоциации «один ко многим» вообще невозможны в сопоставленном суперклассе.», а другие 2 типа наследования не будут работать, поскольку вам нужно поле дискриминатора, которое делает невозможным для двух разных типов иметь одного и того же родителя.

Ответ №2:

Вместо расширения класса Address вам придется создать отношение OneToMany для адреса и отношение ManyToOne для личного адреса и бизнес-адреса. Что-то вроде этого:

 <?php

// ...

use DoctrineCommonCollectionsArrayCollection;

// ...
class Address
{
    // ...

    /**
     * @ORMOneToMany(targetEntity="PersonalAddress", mappedBy="address")
     */
    private $personalAddresses;

    /**
     * @ORMOneToMany(targetEntity="BusinessAddress", mappedBy="address")
     */
    private $businessAddresses;

    public function __construct()
    {
        $this->personalAddresses = new ArrayCollection();
        $this->businessAddresses = new ArrayCollection();
    }

    // ...
}
  

И для дочерних классов:

 <?php

// ...

// ...
class PersonalAddress
{
    // ...

    /**
     * @ORMManyToOne(targetEntity="Address", inversedBy="personalAddresses")
     * @ORMJoinColumn(name="base_address_id", referencedColumnName="id")
     */
    private $address;

    // ...
}
  

Комментарии:

1. Интересно, но почему мой объект Address должен знать о personalAddress и businessAddress ? Кроме того, как я получу доступ к методу Address из класса PersonnalAddress без использования прокси-метода?

2. Вы имеете в виду, как вы можете получить доступ к Address классу из PersonalAddressClass ? Создайте работающие геттеры и установщики, doctrine:generate:entities и вы сможете это сделать $personalAddress->getAddress() .

3. Это была моя первая попытка реализации. С точки зрения PHP, это действительно не то, чего я хочу. Человек, который будет использовать класс, теперь должен обрабатывать объект, имеющий 2 объекта. Итак, чтобы иметь возможность использовать объект, имеющий унифицированный объект, мне пришлось бы создать прокси-метод, например, в PersonAddress, businessAddress и т.д. … Который выглядит следующим образом: общедоступная функция getStreet(){ return $this-> address-> getRoad(); } и это очень плохая практика (даже если она кажется хорошо используемой в сообществе doctrine)

4. Почему? Вы можете просто написать: $businessAddress->getAddress()->getRoad() .

5. Я не хочу, чтобы пользователи, использующие код, должны были понимать логику, стоящую за объектом. Они используют бизнес-адрес, вот и все. Вот почему я хочу использовать наследование, а теперь и отношения, поскольку вы могли бы выполнять 100% потребностей OO только с отношениями и никогда не использовать расширения, но это было бы некрасиво и неинтуитивно. 😉