#php #mysql #doctrine-orm
#php #mysql #доктрина-orm
Вопрос:
Я пытаюсь модифицировать Doctrine для большой существующей базы данных. Проблема с тем, как эта база данных обрабатывает внешние ключи. В качестве сокращенного примера у нас есть таблица:
CREATE TABLE `users` (
`user_id` bigint(20) NOT NULL,
`created` datetime NOT NULL,
`org_id` bigint(20) NOT NULL,
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE `orgs` (
`org_id` bigint(20) NOT NULL,
PRIMARY KEY (`org_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Затем объект, соответствующий этой таблице:
/**
* @ORMEntity
* @ORMTable(name="users")
*/
class User {
/**
* @ORMId
* @ORMColumn(type="bigint")
* @ORMGeneratedValue
*/
protected $user_id;
/**
* @ORMColumn(type="datetime")
*/
protected $created;
/**
* @ORMManyToOne(targetEntity="Org")
* @ORMJoinColumn(name="org_id", referencedColumnName="org_id")
*/
protected $org;
}
/**
* @ORMEntity
* @TORMable(name="orgs")
*/
class Org {
/**
* @ORMId
* @ORMColumn(type="bigint")
* @ORMGeneratedValue
*/
protected $org_id;
}
Для пользователя законно не быть связанным с организацией. В базе данных пользователь будет выглядеть так:
{
"user_id": 1,
"org_id": 0
}
Однако при попытке вставить новую строку:
$user = new User;
$em->persist($user);
$em->flush();
Появится сообщение об ошибке:
PDO Exception: An exception occurred while executing 'INSERT INTO users (created, org_id) VALUES (?, ?)' with params ["2020-12-11 14:41:27", null]:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'org_id' cannot be null
Таким образом , кажется , что там , где Доктрина видит отсутствие связи , она вставляет null
. Поэтому я могу решить эту проблему, изменив org_id
столбец на NULL
вместо NOT NULL
. Однако:
- это означает, что будут строки с 0 и строки, с
null
которыми оба указывают на отсутствие связи - существующее приложение, использующее эту таблицу, ожидает
int
возврата, что означает, что потребуется переработать много кода (не говоря уже о запросах, которые будут обрабатывать null и 0 совсем по-разному)
Я полагаю, что люди должны переоборудовать Doctrine в существующие базы данных, поэтому мне интересно, есть ли другое решение, которое не требует изменений на уровне базы данных. Похоже, что какой-то постпроцессор, который улавливает нулевые значения и преобразует их в 0, будет работать, но я не уверен, как это реализовать.
Комментарии:
1. Является
@ID
ли (заглавная буква) преднамеренной?2. Есть ли в вашей таблице orgs организация с идентификатором 0? Если нет, то дурачиться с заменой нулей на ноль не поможет. Я предполагаю, что попытка добавить «0 org» может вызвать собственный набор проблем с другим кодом. Легко создавать базы данных, с которыми Doctrine не может справиться. Замена нулей и нулей — одна из них.
3. @Ocramius я не уверен; проверяя документы, я вижу, что они используют
@Id
, так что, возможно, это опечатка. Будет ли это что-нибудь нарушать?4. @Cerad это не так; это устаревшая база данных, к которой я пытаюсь модифицировать Doctrine; Я упоминал об этом в вопросе. Однако мой вопрос также предполагает, что это, вероятно, не уникальный случай — я вряд ли первый, кто попытается использовать Doctrine в более старой базе данных с дизайном, который не был выполнен с учетом доктрины, и, таким образом, кажется вероятным, что у кого-то будет способ заставить это работать
5. @Ocramius Теперь я обновил
@ID
свои модели@Id
, но, насколько я вижу, это, похоже, ни на что не повлияло
Ответ №1:
Попробуйте
$default = $em->find('Org', 0);
$user = new User;
$user->setDefaultOrg($default);
$user->created = new DateTime();
$em->persist($user);
$em->flush();
Затем в вашем классе User
use DoctrineORMMapping as ORM;
/**
* @ORMEntity
* @ORMHasLifecycleCallbacks
* @ORMTable(name="users")
*/
class User {
protected $default_org;
/**
* @ORMID
* @ORMColumn(type="bigint")
* @ORMGeneratedValue
*/
protected $user_id;
/**
* @ORMColumn(type="datetime")
*/
public $created;
/**
* @ORMManyToOne(targetEntity="Org", cascade={"persist"})
* @ORMJoinColumn(name="org_id", referencedColumnName="org_id")
*/
protected $org;
/**
* @ORMPrePersist()
*/
public function fixLegacyOrg()
{
if (is_null($this->org)) {
$this->org = $this->default_org;
}
}
public function setDefaultOrg(Org $org)
{
$this->default_org = $org;
}
}
Зависит от ваших потребностей в тестировании, которые вы могли бы ввести в конструктор (в идеале только для организации, а не для всего менеджера сущностей)
в качестве дополнительного примечания, чтобы это сработало, вам нужно добавить АвтоИнкремент в запрос таблицы сверху (я полагаю, это ошибка копирования / вставки)
Комментарии:
1. Спасибо, я проверю это. Я предполагаю, что мне придется связать сущности с entity manager, но это неплохая цена за возможность использовать Doctrine без перепрограммирования всего приложения и базы данных.
2. к сожалению, это не работает, поскольку
find()
метод возвращает null из-за того, что организация с идентификатором 0 не существует3. Я пытался использовать другие прослушиватели, такие как PreFlush и postPersist, чтобы принудительно присвоить org_id значение 0, но, похоже, оно перезаписывается null где-то внутри единицы работы. Пытаюсь выяснить, где, чтобы я мог вручную переопределить его.
Ответ №2:
Я решил эту проблему, но я предполагаю, что это решение может расстроить некоторых людей. Я предварю это следующим: это решение для случаев, когда вы абсолютно не можете обновить свою схему, чтобы использовать поля с нулевым значением в ваших отношениях с внешним ключом. Например, в моем случае есть сотни мест, где используются объекты базы данных, где ожидается, что «пустое» поле соединения будет int , а не nullable int .
В этом случае решение, которое работает и определенно противоречит рекомендациям Doctrine, заключается в предварительной установке «пустых» версий ваших объектов в диспетчере объектов перед выполнением любых запросов:
/** @var EntityManager $em **/
$uow = $em->getUnitOfWork();
$uow->createEntity(Org::class, ['org_id' => 0]);
Для каждого случая, когда у вас есть это поле «0 вместо nullable» в соединении, вы можете добавить это в Entity Manager. Вероятно, в ваших моделях также требуется обрабатывать это с нулевыми значениями. Например, в User
public function getOrg(): ?Org{
$org = $this->org;
// Expected null condition
if (!$org){
return null;
}
// UoW hacked "null" entity
if (!$org->org_id){
return null;
}
return $org;
}
Этот механизм позволяет избежать утечки entity manager непосредственно в модели, но добавляет шум внутри моделей, которые должны проверять наличие этих «обнуляемых» объектов. Я не уверен, как это работает в отношении более сложных запросов DDL, но это помогает использовать Doctrine, и я бы посоветовал любому, кто его использует, запускать отдельные проекты для постепенного удаления каждого поддельного объекта, делая его поле внешнего ключа обнуляемым и модифицируя приложение для поддержки этого.