#database #doctrine-orm #class-table-inheritance
#База данных #doctrine-orm #class-table-inheritance
Вопрос:
Как (если это вообще возможно) изменить тип объекта в Doctrine2, используя наследование таблицы классов?
Допустим, у меня есть Person
тип родительского класса и два унаследованных типа Employe
и Client
. Моя система позволяет создать Person и указать его тип — это довольно легко реализовать — но я также хотел бы иметь возможность изменять person с Employee на Client, сохраняя при этом информацию на Person
уровне (его идентификатор и другие связанные записи).
Есть ли простой способ сделать это с Doctrine2?
Ответ №1:
Вчера я также искал такое поведение.
В конце концов, после разговора с людьми в #doctrine на freenode, мне сказали, что это невозможно.
Если вы хотите это сделать, то вам нужно пройти через это:
Обновление пользователя
- Захватите объект Person.
- Обновите столбец discrimator, чтобы он больше не был «person» и измените его на «employee»
- Создайте соответствующую строку в вашей
Employee
таблице для этого наследования.
Удаление наследования
Аналогично, если вы хотите удалить наследование, вы должны..
- Захватите объект Person.
- Обновите столбец discriminator, чтобы он больше не был «сотрудником», и измените его на «person».
- Удалите соответствующую строку в вашей
Employee
таблице. (Да, вы должны удалить его, просто изменить значение discrimator недостаточно).
Это может быть с опозданием на 7 месяцев, но это, по крайней мере, правильный ответ для всего, что хочет поддержать такую функцию.
Комментарии:
1. Потрясающе. Я перестал использовать CTI для этой конкретной проблемы, но это может когда-нибудь понадобиться =)
2. Как насчет обновления объекта? если вы уже загрузили объект User, значение дискриминатора вручную не будет частью уже загруженного объекта user, управляемого unit of work. Таким образом, объект user по-прежнему будет рассматриваться как старый тип. $em-> обновить($user); не работает. Итак, как справиться с этой проблемой?
Ответ №2:
PHP не поддерживает приведение объектов, поэтому Doctrine его не поддерживает. Чтобы обойти проблему, я записываю этот статический метод в родительские классы:
public static function castToMe($obj) {
$class = get_called_class();
$newObj = New $class();
foreach (get_class_vars(get_class($newObj)) as $property => $value) {
if (method_exists($obj, 'get' . ucfirst($property)) amp;amp; method_exists($newObj, 'set' . ucfirst($property))) {
$newObj->{'set' . ucfirst($property)}($obj->{'get' . ucfirst($property)}());
}
}
return $newObj;
}
Вы можете создать этот метод в классе Person и использовать его для приведения от Employee к Client и наоборот:
$employe = New Employe();
$client = Client::castToMe($employe);
Теперь, если вы хотите, вы можете удалить объект $employee.
Ответ №3:
Вы могли бы сделать что-то вроде этого, хотя:
Эта особенность может быть использована в вашем классе репозитория:
namespace AppDoctrineRepository;
trait DiscriminatorTrait
{
abstract public function getClassMetadata();
abstract public function getEntityManager();
private function updateDiscriminatorColumn($id, $class)
{
$classMetadata = $this->getClassMetadata();
if (!in_array($class, $classMetadata->discriminatorMap)) {
throw new Exception("invalid discriminator class: " . $class);
}
$identifier = $classMetadata->fieldMappings[$classMetadata->identifier[0]]["columnName"];
$column = $classMetadata->discriminatorColumn["fieldName"];
$value = array_search($class, $classMetadata->discriminatorMap);
$connection = $this->getEntityManager()->getConnection();
$connection->update(
$classMetadata->table["name"],
[$column => $value],
[$identifier => $id]
);
}
}
Вам все еще может потребоваться некоторая дополнительная работа, например, очистка значений в полях, которые присутствуют только в одном из ваших подклассов
Ответ №4:
В Doctrine2, когда у вас есть родительский класс сущности, Person
задайте как:
/**
* @Entity
* @InheritanceType("JOINED")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee", , "client" = "Client"})
*/
class Person
{
// ...
}
и подклассы, такие как Client
установленные как:
/** @Entity */
class Client extends Person
{
// ...
}
при создании экземпляра Person
как:
$person = new Person();
Doctrine2 проверяет вашу @DiscriminatorMap
инструкцию (выше) на наличие соответствующего сопоставления с Person
и при обнаружении создает строковое значение в столбце таблицы, указанном в @DiscriminatorColumn
выше.
Итак, когда вы решите создать экземпляр Client
в качестве:
$client = new Client();
Следуя этим принципам, Doctrine2 создаст для вас экземпляр, если вы объявили параметры в @DiscriminatorMap
. Также в Person
таблицу в столбце discr будет внесена запись, отражающая тот тип класса объектов, который только что был создан.
Надеюсь, это поможет. Хотя все это есть в документации
Комментарии:
1. Возможно, мой вопрос был недостаточно ясен, но я хотел бы преобразовать объект одного типа в другой тип (т. Е. Клиент -> Сотрудник ), сохранив при этом общие данные, относящиеся к суперклассу (здесь Person). Кроме того, я использую для этого наследование одной таблицы (не нескольких таблиц)
Ответ №5:
я использую этот метод
trait DiscriminatorTrait
{
// ...
public function updateDiscriminatorColumn($id, $class)
{
// ... other code here
$connection->update(
"Person", // <-- just there i put my parent class
[$column => $value],
[$identifier => $id]
);
}
}
и я использую подобный вызов после :
$this->em->getRepository(Client::class)->updateDiscriminatorColumn($cCenter->getId(), Employe::class);
$this->em->close();
// I update the data directly without going through doctrine otherwise it will create a new Person
try {
$query = "
INSERT INTO Employe (id, /* ... other fields */)
VALUES ({$callCenter->getId()}, /* ... other fields */)
";
$results = $this->connection->executeQuery($query)->execute();
} catch (Exception $exception) {
echo $exception->getMessage().PHP_EOL;
}
$this->em->close();
// i restart the connection
/** @var EntityManagerInterface $entityManager */
$entityManager = $this->em;
if ($this->em->isOpen() === false) {
$this->em = $entityManager->create(
$this->em->getConnection(),
$this->em->getConfiguration(),
$this->em->getEventManager()
);
}
// and there a get Employer en update him
$employe = $this->em->getRepository(Employe::class)->find($id);
$employe->setFirstname($callCenter->getFirstName());
// other code
И это работа для меня