Symfony Doctrine - сохранение внешнего ключа

A1x

Новичок
есть некая сущность Server у которой есть внешний ключ customer_id

создаю новую запись в таблице

PHP:
$srv = new Server;
$srv->setName('test');
$srv->setCustomerId(2);

$em->persist($srv);
$em->flush();
при этом запись создается, но в поле внешнего ключа customer_id - NULL

в то же время у существующей записи можно установить это поле без проблем

PHP:
$srv = $em->getRepository(Entity::SERVER)->find(12);
$srv->setCustomerId(1);
$em->flush();
почему так и как правильно установить связь при создании записи?


пока могу делать так

PHP:
$srv = new Server;
$srv->setName('test');

$customer = $em->getRepository(Entity::CUSTOMER)->find(12);
$srv->setCustomer($customer);

$em->persist($srv);
$em->flush();
но непонятно почему при UPDATE достаточно установить $srv->setCustomerId(...);
а при INSERT это не работает

описание связи выглядит так:

PHP:
class Server
{ 
  // .......

  /**
  * @var integer
  *
  * @ORM\Column(name="customer_id", type="integer", nullable=true, options={"unsigned":true})
  */
  private $customerId;

  /**
  * @var Customer
  *
  * @ORM\ManyToOne(targetEntity="Customer")
  * @ORM\JoinColumn(name="customer_id", referencedColumnName="id", nullable=true, onDelete="set null")
  */
  private $customer;

  // ....

}
 

Вурдалак

Продвинутый новичок
*Сдерживает себя по поводу анемичной модели*

А зачем тебе одновременно customerId и customer? Я не знаток Doctrine, но мне кажется, что «нулевой» $customer перезатирает $customerId где-то при маппинге на SQL (при обновлении же Doctrine не видит изменений в $customer, поэтому в дифф для SQL-запроса его значение не попадает).
Странно, что Doctrine не падает с исключением.
 

A1x

Новичок
customerId мне нужен чтобы устанавливать его напрямую, например из формы, как я и пытаюсь сделать
customer нужен чтобы можно было вытянуть его джойном вместе с Server

в примере с UPDATE customer тоже может быть "нулевым", но тем не менее customerId записывается
 

MiksIr

miksir@home:~$
$customer = CustomerRepository->findById(2)
$server->customer = $customer

Два одинаковых поля работать не будет.
 

Вурдалак

Продвинутый новичок
customerId мне нужен чтобы устанавливать его напрямую, например из формы, как я и пытаюсь сделать
customer нужен чтобы можно было вытянуть его джойном вместе с Server
Хорошая модель и use case'ы интересные.
 

A1x

Новичок
Хорошая модель и use case'ы интересные.
модель как модель. Я тоже не знаток доктрины и хочу разобраться с типичными use case'ами для начала

мне не понятен такой момент - если оставить только customer то в случае если мне нужен только customerId я могу его получить только как $obj->getCustomer()->getId() ?
выглядит как-то не очень
 
Последнее редактирование:

WMix

герр M:)ller
Партнер клуба
@A1x, то что id не является частью данных это наоборот правильно. id может быть вообще не нужен снаружи, (а только для joins) подразумевает что есть другой уникальный номер извесный клиенту по которому и будет происходить пойск. и да данные и ключик удобнее держать отдельно $db->update( $data, $id ) тк назначение разное
 

Вурдалак

Продвинутый новичок
мне не понятен такой момент - если оставить только customer то в случае если мне нужен только customerId я могу его получить только как $obj->getCustomer()->getId() ?
выглядит как-то не очень
Это цена за одну-единственную-модель-для-всего (для форм, персистенции, отображения). Привыкай, это типичная проблема для тех, кто пишет код по мануалам к фреймворкам :)
 

A1x

Новичок
Это цена за одну-единственную-модель-для-всего (для форм, персистенции, отображения)
ничего не понял - объясни пожалуйста для тупых. Сделать две разные сущности - одну с customer а другую с customerId ? Отдельную сущность для формы?

мой вариант с customer и customerId в одной сущности вроде работает нормально, только надо пожалуй убрать setCustomerId() от греха подальше

вот например такой запрос
PHP:
"UPDATE srv SET srv.customerId = 2 WHERE srv.customerId = 1"
как его записать без customerId ? Или такое не надо хотеть делать через DQL а только через обычный SQL?
 

keltanas

marty cats
@A1x, он имеет ввиду, что есть подход, когда сущности Доктрины используют только для персистенции. Для храния бизнес-логики - другие. Для общения с внешним миром (апи, шаблоны, формы) - третьи. Это позволяет возлагать на каждый класс меньше обязанностей, благодаря чему код становится более чистым и легко поддерживаемым.

Код:
$server->setCustomer($custromer2);
$uow->persist($server);
$uow->flush();
 
Последнее редактирование:

AnrDaemon

Продвинутый новичок
вот например такой запрос
PHP:
"UPDATE srv SET srv.customerId = 2 WHERE srv.customerId = 1"
как его записать без customerId ? Или такое не надо хотеть делать через DQL а только через обычный SQL?
Такое не надо хотеть делать вообще. Ты по сути говоришь системе, что "вася это маша… ну да, с пенисом, а что?"…
 

A1x

Новичок
@A1x, он имеет ввиду, что есть подход, когда сущности Доктрины используют только для персистенции. Для храния бизнес-логики - другие. Для общения с внешним миром (апи, шаблоны, формы) - третьи. Это позволяет возлагать на каждый класс меньше обязанностей, благодаря чему код становится более чистым и легко поддерживаемым.
это понятно, у меня эта сущность используется только для персистенции и никакой бизнес логики там нет и не предвидится. В данном случае для формы думаю достаточно будет сделать трансформер для поля customerId. Не понятен ход мысли @Вурдалак - как это связано с $obj->getCustomer()->getId() или $obj->getCustomerId()

$server->setCustomer($custromer2);
$uow->persist($server);
$uow->flush();
я имел в виду замену $custromer у нескольких записей одним запросом
 

A1x

Новичок
Такое не надо хотеть делать вообще. Ты по сути говоришь системе, что "вася это маша… ну да, с пенисом, а что?"…
да я и не хочу, но заказчик например говорит - вот у этих товаров поменялся поставщик, хочу чтобы из админки можно было поменять поставщика у нескольких товаров одним кликом
 

fixxxer

К.О.
Партнер клуба
я имел в виду замену $custromer у нескольких записей одним запросом
С чистым persistence ignorance без загрузки всего в память, конечно, не получится.
Если это неприемлемо из соображений производительности/потребления памяти, не вижу ничего криминального в костылике с прямым запросом в базу. Главное, чтобы в этот момент в памяти ничего не было.

Но твой setCustomerId тут тоже не поможет в любом случае.
 

A1x

Новичок
setCustomerId() я уже убрал, но само поле private $customerId; можно оставить, тогда выполняется запрос типа UPDATE Server SET srv.customerId = Y WHERE srv.customerId = X
хотя тут особо без разницы - DQL или SQL

а после запроса можно сделать EntityManager#clear() при необходимости
 

Вурдалак

Продвинутый новичок
ничего не понял - объясни пожалуйста для тупых. Сделать две разные сущности - одну с customer а другую с customerId ? Отдельную сущность для формы?
Ты говоришь, что тебе не нравится $server->getCustomer()->getId(). Ты не уточнил почему, но, возможно, в силу избыточности/неэффективности.
Но такова цена за то, что используешь один и тот же класс Server в разных контекстах.
Никто не мешает тебе сделать ServerReadModel, который будет содержать только $customerId:
PHP:
public function find(int $serverId)
{
    // SELECT * FROM servers WHERE id = $serverId

    return new ServerReadModel($row['id'], $row['customerId'], ...);
}
Помимо этого, инструмент (Doctrine) навязывает тебе свой формат сущности. Спроси себя: зачем тебе $customer в виде объекта в классе Server вообще? Только потому что Doctrine этого требует?

это понятно, у меня эта сущность используется только для персистенции и никакой бизнес логики там нет и не предвидится
А где тогда находится твоя бизнес-логика?
Вот ты спрашиваешь по поводу запроса:
вот например такой запрос
PHP:
"UPDATE srv SET srv.customerId = 2 WHERE srv.customerId = 1"
На самом деле, этот запрос отражает бизнес-логику. Предположу, что называется это примерно как «передать все сервера одного клиента другому». Примерно к одному серверу — «передать сервер другому клиенту». Здесь и начинается бизнес-логика:
PHP:
final class Server
{
    public function transferToAnotherCustomer(int $newCustomerId)
    {
        $this->customerId = $newCustomerId;
    }
}
И можно в цикле выполнить для каждого сервера перенос от одного клиента другому.
Ты же тщательно маскируешь бизнес-логику и говоришь, что у тебя её нет и не предвидится.
У тебя модель — не модель. Просто пачка свойств с сеттерами и геттерами. Для персистенции. И для маппинга из формы и обратно. Для передачи данных в шаблоны. А самого главного — бизнес логики — твоя модель не содержит.

*Сдерживает себя по поводу анемичной модели*
Не получилось-таки.
 
  • Like
Реакции: A1x

fixxxer

К.О.
Партнер клуба
@Вурдалак, ирония на тему "Только потому что Doctrine этого требует?", это, с одной стороны, ирония, но с другой стороны, на практике дело обстоит так, что инструмент все же навязывает и будет навязывать архитектуру, просто потому что программисты - люди ленивые и не любят писать много бойлерплейта. Анемичные активрекорды и псевдоmvc стали популярными не просто так, а во многом благодаря рельсам и их пиару, построенному на "смотрите как все просто" (а на примитивных кейсах типа бложика с комментами действительно выглядит просто, что уж там). Так что никуда не деться и будем кругом видеть навязанную инструментами архитектуру, пока не появится других инструментов.
 

Вурдалак

Продвинутый новичок
@Вурдалак, ирония на тему "Только потому что Doctrine этого требует?", это, с одной стороны, ирония, но с другой стороны, на практике дело обстоит так, что инструмент все же навязывает и будет навязывать архитектуру, просто потому что программисты - люди ленивые и не любят писать много бойлерплейта. Анемичные активрекорды и псевдоmvc стали популярными не просто так, а во многом благодаря рельсам и их пиару, построенному на "смотрите как все просто" (а на примитивных кейсах типа бложика с комментами действительно выглядит просто, что уж там). Так что никуда не деться и будем кругом видеть навязанную инструментами архитектуру, пока не появится других инструментов.
Я прекрасно понимаю почему подход популярен.
Но я не могу согласиться с тем, что «никуда не деться». Меня слабо волнуют другие проекты; в проектах, где участвую я, я буду стараться продвигать свой подход.
 

fixxxer

К.О.
Партнер клуба
Я в глобальном смысле, и скорее к вопросу того, как бы такой инструмент мог выглядеть. За рамками php-мира, может, что-то есть?

У меня, кстати, не совсем праздный интерес, скорее даже вполне практический.
 
Сверху