Sonata Admin Bundle 一对多关系不保存外国 ID
Posted
技术标签:
【中文标题】Sonata Admin Bundle 一对多关系不保存外国 ID【英文标题】:Sonata Admin Bundle One-to-Many relationship not saving foreign ID 【发布时间】:2013-06-04 07:54:06 【问题描述】:我在将 SonataAdminBunle 与 symfony 2.2 结合使用时遇到问题。 我有一个 Project 实体和一个 ProjectImage 实体,并指定了这两者之间的一对多关系,如下所示:
class Project
/**
* @ORM\OneToMany(targetEntity="ProjectImage", mappedBy="project", cascade="all", orphanRemoval=true)
*/
private $images;
class ProjectImage
/**
* @ORM\ManyToOne(targetEntity="Project", inversedBy="images")
* @ORM\JoinColumn(name="project_id", referencedColumnName="id")
*/
private $project;
我已经配置了 ProjectAdmin 和 ProjectImageAdmin:
class ProjectAdmin extends Admin
protected function configureFormFields(FormMapper $formMapper)
$formMapper
->add('title')
->add('website')
->add('description', 'textarea')
->add('year')
->add('tags')
->add('images', 'sonata_type_collection', array(
'by_reference' => false
), array(
'edit' => 'inline',
'inline' => 'table',
'sortable' => 'id',
))
;
class ProjectImageAdmin extends Admin
protected function configureFormFields(FormMapper $formMapper)
$formMapper
->add('file', 'file', array(
'required' => false
))
;
问题是在数据库的 project_image 表中,project_id 没有保存,而所有其他数据和图像都保存了。在其他任何地方都找不到有效的答案。
【问题讨论】:
你试过删除 by_reference 吗? 是的,我确实尝试过。很遗憾没有结果。 您的项目实体中是否有 setImages、addImage、removeImage 和 getImages 方法? 我没有 setImages 方法,这应该是什么样子? 知道了,谢谢。这里实际上给出了答案:github.com/sonata-project/SonataAdminBundle/issues/… 【参考方案1】:虽然不相关,但我会稍微调整您的一对多注释:
class Project
/**
* @ORM\OneToMany(targetEntity="ProjectImage", mappedBy="project", cascade="persist", orphanRemoval=true)
* @ORM\OrderBy("id" = "ASC")
*/
private $images;
回到正轨,您的注释和 Sonata Admin 表单看起来不错,所以我很确定您在 Project 实体类中缺少这些方法之一:
public function __construct()
$this->images = new \Doctrine\Common\Collections\ArrayCollection();
public function setImages($images)
if (count($images) > 0)
foreach ($images as $i)
$this->addImage($i);
return $this;
public function addImage(\Acme\YourBundle\Entity\ProjectImage $image)
$image->setProject($this);
$this->images->add($image);
public function removeImage(\Acme\YourBundle\Entity\ProjectImage $image)
$this->images->removeElement($image);
public function getImages()
return $this->Images;
在你的管理类中:
public function prePersist($project)
$this->preUpdate($project);
public function preUpdate($project)
$project->setImages($project->getImages());
【讨论】:
这个奏鸣曲包有点毛茸茸的。prePersist()
|preUpdate()
的建议帮助了我.. 但我必须做一些不同的事情。查看要点 - gist.github.com/justinpfister/9634619
也许你应该在setEntries
中改用foreach
?在preUpdate
中这样做似乎很奇怪【参考方案2】:
我解决的一种方法是通过自定义 Sonata 模型管理器手动设置所有反向关联。
<?php
namespace Sample\AdminBundle\Model;
class ModelManager extends \Sonata\DoctrineORMAdminBundle\Model\ModelManager
/**
* @inheritdoc
*/
public function create($object)
try
$entityManager = $this->getEntityManager($object);
$entityManager->persist($object);
$entityManager->flush();
$this->persistAssociations($object);
catch (\PDOException $e)
throw new ModelManagerException('', 0, $e);
/**
* @inheritdoc
*/
public function update($object)
try
$entityManager = $this->getEntityManager($object);
$entityManager->persist($object);
$entityManager->flush();
$this->persistAssociations($object);
catch (\PDOException $e)
throw new ModelManagerException('', 0, $e);
/**
* Persist owning side associations
*/
public function persistAssociations($object)
$associations = $this
->getMetadata(get_class($object))
->getAssociationMappings();
if ($associations)
$entityManager = $this->getEntityManager($object);
foreach ($associations as $field => $mapping)
if ($mapping['isOwningSide'] == false)
if ($owningObjects = $object->'get' . ucfirst($mapping['fieldName'])())
foreach ($owningObjects as $owningObject)
$owningObject->'set' . ucfirst($mapping['mappedBy']) ($object);
$entityManager->persist($owningObject);
$entityManager->flush();
请务必在 services.yml 文件中将其定义为新服务:
services:
sample.model.manager:
class: Sample\AdminBundle\Model\ModelManager
arguments: [@doctrine]
sample.admin.business:
class: Sample\AdminBundle\Admin\BusinessAdmin
tags:
- name: sonata.admin, manager_type: orm, group: "Venues", label: "Venue"
arguments: [~, Sample\AppBundle\Entity\Business, ~]
calls:
- [ setContainer, [@service_container]]
- [ setModelManager, [@sample.model.manager]]
【讨论】:
【参考方案3】:您可以直接在 preUpdate 函数中执行此操作
public function prePersist($societate)
$this->preUpdate($societate);
public function preUpdate($societate)
$conturi = $societate->getConturi();
if (count($conturi) > 0)
foreach ($conturi as $cont)
$cont->setSocietate($societate);
【讨论】:
【参考方案4】:由于 Symfony 表单集合发生了一些变化,现在添加 addChild() 和 removeChild() 并将 by_reference 选项设置为 false 自动持久化 Collection 并按预期在反面设置 ID。
这是一个完整的工作版本: https://gist.github.com/webdevilopers/1a01eb8c7a8290d0b951
protected function configureFormFields(FormMapper $formMapper)
$formMapper
->add('childs', 'sonata_type_collection', array(
'by_reference' => false
), array(
'edit' => 'inline',
'inline' => 'table'
))
;
addChild() 方法必须包含子级父级的设置器:
public function addChild($child)
$child->setParent($this); // !important
$this->childs[] = $child;
return $this;
【讨论】:
这是最好的答案,因为它允许学说隐式设置关系,并将尊重非拥有方的级联定义。【参考方案5】:public function prePersist($user)
$this->preUpdate($user);
public function preUpdate($user)
$user->setProperties($user->getProperties());
这已经完美地解决了我的问题,谢谢!
【讨论】:
【参考方案6】:最简单的解决方案是,自然地替换你的变量名;这对我有用: 'by_reference' => 错误
如下:
protected function configureFormFields(FormMapper $formMapper)
$formMapper
->add('name')
->add('articles', EntityType::class, array(
'class' => 'EboxoneAdminBundle:Article',
'required' => false,
'by_reference' => false,
'multiple' => true,
'expanded' => false,
))
->add('formInputs', CollectionType::class, array(
'entry_type' => FormInputType::class,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
))
;
【讨论】:
以上是关于Sonata Admin Bundle 一对多关系不保存外国 ID的主要内容,如果未能解决你的问题,请参考以下文章
Sonata Admin Bundle 和 Doctrine 问题:如何从反面更新实体?