#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 только с отношениями и никогда не использовать расширения, но это было бы некрасиво и неинтуитивно. 😉