У меня есть отношение oneToMany - ManyToOne между двумя объектами.
При редактировании на странице редактирования отдела (сторона-владелец, ManyToOne) изменения будут сохранены в таблице отдела,
.
но при редактировании со страницы редактирования Utilisateur (обратная сторона, OneToMany) изменения не будут сохранены в таблице Departement.
Может кто-нибудь мне объяснить, почему не работает?
src/AppBundle/Entity/Utilisateur.php
class Utilisateur implements UserInterface, \Serializable {
/**
* @ORM\OneToMany(targetEntity = "AppBundle\Entity\Departement", mappedBy = "commercial")
*/
private $departements;
/**
* Constructor
*/
public function __construct() {
$this->departements=new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* @param \AppBundle\Entity\Departement $departement
* @return Utilisateur
*/
public function addDepartement(\AppBundle\Entity\Departement $departement)
{
$this->departements[] = $departement;
return $this;
}
/**
* @param \AppBundle\Entity\Departement $departement
*/
public function removeDepartement(\AppBundle\Entity\Departement $departement)
{
$this->departements->removeElement($departement);
}
/**
* @return \Doctrine\Common\Collections\Collection
*/
public function getDepartements()
{
return $this->departements;
}
}
src/AppBundle/Entity/Departement.php
class Departement {
/**
* @ORM\ManyToOne(targetEntity = "AppBundle\Entity\Utilisateur", inversedBy = "departements")
*/
private $commercial;
/**
* @param \AppBundle\Entity\Utilisateur $commercial
* @return Departement
*/
public function setCommercial(\AppBundle\Entity\Utilisateur $commercial=null) {
$this->commercial=$commercial;
return $this;
}
/**
* @return \AppBundle\Entity\Utilisateur
*/
public function getCommercial() {
return $this->commercial;
}
}
src/AppBundle/Form/Utilisateur/Edit3dhType.php
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('departements', EntityType::class, array(
'class'=>Departement::class,
'expanded'=>true,
'multiple'=>true
));
}
src/AppBundle/Controller/UtilisateurController.php
/**
* @Route("/dashboard/utilisateurs/edit-{id}", name = "security_edit_user")
* @Method({"GET", "POST"})
*
* @param Request $request
* @param Utilisateur $utilisateur
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
*/
public function editAction(Request $request, Utilisateur $utilisateur) {
$logo=$utilisateur->getLogo();
if ($utilisateur->getLogo() !== null) {
$utilisateur->setLogo(new File($this->getParameter('dir_picto').$logo));
}
$form=$this->createForm(Edit3dhType::class, $utilisateur, array(
'action'=>$this->generateUrl('security_edit_user', array('id'=>$utilisateur->getId())),
'method'=>'POST',
));
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
if ($utilisateur->getLogo() !== null) {
$utilisateur->setLogo($this->container->get('service.upload')->uploadPicto($utilisateur->getLogo()));
} else {
$utilisateur->setLogo($logo);
}
$em=$this->getDoctrine()->getManager();
$em->flush();
}
return $this->render('security/edit.html.twig', array(
'user'=>$this->getUser(),
'utilisateur'=>$utilisateur,
'form'=>$form->createView(),
));
}
@revengeance $utilisateur->setLogo($this->container->get('service.upload')->uploadPicto($utilisateur->getLogo()));. Кроме того, проблема здесь не в файле ... Он работает нормально и как следует. Он также может быть нулевым. Вне темы, но все же будет немного обновлен для некоторых геттеров и сеттеров.






Вы должны быть уверены, что сущность управляется Doctrine. Вы можете сделать это, вызвав метод persist с вашим объектом в качестве аргумента ($em->persist($utilisateur)).
Хотя вы получили объект через Doctrine, он уже управляется, поэтому Doctrine может не знать о том, что он также должен сохранять объект через отношения OneToMany. Как упоминал @Dirk, установка каскадной операции, безусловно, будет иметь значение. Вы можете добавить это в класс Utilisateur следующим образом:
@ORM\OneToMany(targetEntity = "AppBundle\Entity\Departement", mappedBy = "commercial", cascade = {"persist"})
Вы должны получить исключение, когда пытаетесь сохранить объект (через отношение), которым не управляет Doctrine. Странно, что ты его не получаешь ...
Проверьте, действительно ли форма создает экземпляр Department и добавляет его в класс Utilisateur (вы не указываете тип при добавлении поля «отдел» в форму).
Не имеет отношения, я редактирую, а не создаю ... Объект уже существует
Редактирование или создание не имеет значения, вам нужно сообщить Doctrine, что она должна управлять объектом (путем вызова метода persist для объекта, который вы хотите создать / обновить), иначе ваш вызов flush не будет иметь никакого значения. doctrine-project.org/projects/doctrine-orm/en/2.6/reference/…
Пробовал, ничего не менял, все равно не работает. В Symfony / DC2 вам нужно только сохранить (INSERT), когда вы создаете свою сущность. Если вы редактируете, вам не нужно использовать persist, действие UPDATE будет выполнено до сброса. Symfony со всем справится отлично, когда прочитает ваш объект сущности и «заметит», что это уже существующая запись.
По вашей ссылке (что я сказал проще): If X is a new entity, it becomes managed, If X is a preexisting managed entity, it is ignored by the persist operation. Таким образом, использование persist ничего не изменит, если объект уже существует.
Думаю, я пропустил ту часть, где вы извлекаете объект из Doctrine (в методе editAction), что делает его управляемым. Виноват! Комментарий, опубликованный Дирком, почти правильный, но операция cascade должна выполняться на стороне Utilisateur (которая является сущностью, которую вы обновляете). Вы пробовали это? Мне кажется странным, что у вас не было исключения. Если Doctrine замечает, что вы пытаетесь сохранить объект через отношение, она должна сообщить вам ...
Видно да ... Проверяю вещи. Что касается типа Departement, если вы оставите его пустым, это будет означать, что Symfony сохранит исходный тип (таким образом, здесь тип сущности Departement).
Он по-прежнему не сохраняется ... Не удается получить доступ к странице профилировщика формы после отправки. Проверенный запрос, в транзакции виден только Utilisateur.
Не могли бы вы попробовать запустить код с новым объектом Utilisateur, в котором переменная $department установлена с вновь созданным экземпляром класса Department (просто поместите его в конструктор класса Utilisateur)? Дикая догадка: вы запускали cache:clear? Я заметил, что иногда у Symfony возникают проблемы с обновлением аннотаций.
Не могу с новым пользователем. Записи в Первую часть - наборы, больше-меньше потом не будет. Во-вторых, нельзя приписывать одно или несколько подразделений Utilisateur при сохранении нового. Возможно только при редактировании одного. (должно быть так)
Проверьте мой собственный ответ. Это то, что я искал. ;)
Приятно слышать, что вы нашли решение! Просто предупреждаем: использование сериализации / десериализации уязвимо для внедрения объекта (см. owasp.org/index.php/PHP_Object_Injection). Возможно, вы захотите изучить использование array_udiff (php.net/manual/en/function.array-udiff.php) с пользовательской функцией для сравнения ваших экземпляров Department :)
Вам необходимо определить каскадную операцию с cascade = {"persist"} следующим образом:
class Departement {
/**
* @ORM\ManyToOne(targetEntity = "AppBundle\Entity\Utilisateur", inversedBy = "departements", cascade = {"persist"} )
*/
private $commercial;
}
В вашем FormType вам нужно сделать следующее.
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('departements', CollectionType::class, array(
'entry_type'=> DepartmentType::class,
'allow_add' => true,
'allow_delete' => true,
'allow_extra_fields' => true,
'by_reference' => false // calls addDepartement / removeDepartement on parent(Utilisateur) automatically
));}
Для информации читайте: https://symfony.com/doc/current/form/form_collections.html
Боюсь, что это не то, что мне нужно. Таблица Departement уже заполнена записями. Второй момент, я не могу это проверить, так как он не позволяет мне использовать multiple.
Да, совсем не то, что мне нужно. Он отображает полную форму DepartementType под моим пользователем. Не то, что я пытаюсь сделать.
Не могли бы вы показать мне отправленную форму в вашем профайлере? Затем мы можем проверить формат просмотра и нормализованный формат. Это решит вашу проблему
Ну, я вроде как нашел решение, применив его, прежде чем опубликовать свой ответ. Проблема не в формате просмотра. Но что происходит, когда за отправлено. Часть формы Departement - это выбор с несколькими разрешенными (ibb.co/fCXz39). Читая много документов, на первый намекнул TEDx, я обнаружил, что Symfony на самом деле не обрабатывает обратную сторону. Он проверяет данные только на стороне владельца. Я все еще кое-что проверяю, но я опубликую свой ответ по этому поводу в тот же день
Проверьте мой собственный ответ. Это то, что я искал. ;)
Итак ... Благодаря TEDx, который намекнул мне на некоторые документы, после нескольких часов поисков и чтения, тонны свалок и небольшого разговора с медведем ... Я пришел к пониманию следующего.
Во-первых, Doctrine, похоже, проверяет данные только на стороне-владельце отношения. Это означает, что он не справится с вещами, если будет сделан с обратной стороны.
Во-вторых, в связи с первой проблемой, поскольку я пытаюсь сделать что-то обратное, Doctrine не будет устанавливать departement.commercial в коллекции массивов форм. Таким образом, не обнаружив различий с данными в таблице, он не выполняет никаких запросов.
Наконец, Doctrine не обнаружит непроверенные записи в форме (поэтому установите для departement.commercial значение NULL).
Ниже решение, которое я придумал. Он применяется к обратной стороне отношений OneToMany / ManyToOne. И с некоторыми настройками может также использоваться для отношений ManyToMany.
src/AppBundle/Controller/UtilisateurController.php
public function editAction(Request $request, Utilisateur $utilisateur) {
//Save pre submit data
$preSubmitDepartement=$utilisateur->getDepartements()->toArray();
$form=$this->createForm(UtilisateurType::class, $utilisateur, array(
'action'=>$this->generateUrl('security_edit_user', array('id'=>$utilisateur->getId())),
'method'=>'POST',
'utilisateur'=>$utilisateur,
));
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em=$this->getDoctrine()->getManager();
//Get post submit data
$postSubmitDepartement=$utilisateur->getDepartements()->toArray();
//Keep only IDs for pre and post submit data
/** @var Departement $v */
foreach($preSubmitDepartement as $k=>$v) {
$preSubmitDepartement[$k]=$v->getId();
}
/** @var Departement $v */
foreach($postSubmitDepartement as $k=>$v) {
$postSubmitDepartement[$k]=$v->getId();
}
//Find removed IDs
$prePostSubmitDifference=array_map('unserialize', array_diff(array_map('serialize',$preSubmitDepartement), array_map('serialize',$postSubmitDepartement)));
//Fetch related Departement entries
$departements=$em->getRepository(Departement::class)->findBy(array('id'=>array_merge($postSubmitDepartement, $prePostSubmitDifference)));
//setCommercial to $utilisateur or null
/** @var Departement $departement */
foreach($departements as $departement) {
if (in_array($departement->getId(), $postSubmitDepartement)) {
$departement->setCommercial($utilisateur);
} else if (in_array($departement->getId(), $prePostSubmitDifference)) {
$departement->setCommercial(null);
}
}
$em->flush();
}
return $this->render('security/edit.html.twig', array(
'user'=>$user,
'utilisateur'=>$utilisateur,
'form'=>$form->createView(),
));
}
Теперь я могу установить departement.conseiller с обратной стороны.
Является ли файл логотипа объектом? Если да, то вы делаете весь процесс неправильно, потому что вы не можете сохранить файлы в БД, вы должны переместить их в папку и сохранить только путь. Я просто не вижу всей сущности, интересно ... Не могли бы вы отредактировать код и добавить полные части сеттеров / поле сущности для этого логотипа?