在Doctrine中使用实体时,很明显要为实体添加某些"helper"方法。例如;
class Post {
protected $tags;
public function __construct() {
$this->tags = new ArrayCollection();
}
public function getTags() {
return $this->tags;
}
public function addTag( $tagName ) {
$tag = new Tag();
$tag->setName( $tagName );
$this->getTags()->add( $tag );
}
}
这样做本身是有效的,它使代码简洁:
$post = new Post();
$post->addTag('Economy');
然而,当Tag
是多对多关系时,当向帖子添加标签时,我们想要检查标签是否已经存在时,问题就更大了。例如,一个帖子可能没有"经济"标签,所以从帖子的角度来看,将它添加到帖子中将是一个新标签。然而,如果标签"经济"已经存在于数据库中,我们就有问题了。我们希望我们的实体尽可能是POPO的,即不引用实体管理器或存储库。
解决这个问题的好策略是什么?
您需要在实体外部执行此操作。例如…
class Post {
// ...
public function addTag(Tag $tag) {
$this->getTags()->add($tag);
}
}
和控制器…
$post = new Post();
// I can't remember if this returns null for no records found or throws
// an exception. Needs checking
$tag = $em->getRepository('Tag')->findOneByName('Economy');
if ($tag == null) {
$tag = new Tag();
$tag->setName('Economy');
$em->persist($tag); // don't need to do this if you use cascade persist
}
$post->addTag($tag);
我刚和别人谈过这件事。这个问题实际上可以通过使用生命周期事件很好地解决。
如果在Post实体上实现了预持久化生命周期事件,则可以从生命周期事件对象访问实体管理器。可以从实体管理器请求一个存储库,并且可以调用一个方法来检查添加的标签是否重复。
/**
* @ORM'Entity(repositoryClass="PostRepository")
* @ORM'HasLifecycleCallbacks()
*/
class Post {
public function onPrePersist( LifecycleEventArgs $event ) {
$entityManager = $event->getEntityManager();
$repository = $entityManager->getRepository( get_class( $this ) );
$repository->syncTags( $this );
}
}
同时,在PostRepository
中:
class PostRepository {
public function syncTags( Post $post ) {
$em = $this->getEntityManager();
$tagsRepository = $em->getRepository('Tag');
$tags = $post->getTags();
foreach( $tags as $tag ) {
$tagId = $tag->getId();
if( empty( $tagId ) ) {
$existingTag = $tagsRepository->findOneByName( $tag->getName() );
if( ! empty( $existingTag ) ) {
$tags->removeElement( $tag );
$tags->add( $existingTag );
}
}
}
}
}
上面的syncTags
方法基本上遍历Post标签。如果标记没有Id,我们可以假设它以前从未被持久化过,因此作为新标记添加。然后,我们检查是否已经存在带有该名称的标记。如果没有,我们可以将其作为一个新标签保存,否则我们删除重复的标签并添加现有的标签实体。
我们现在可以保持我们的标签添加逻辑像它应该的那样简单:
$post = new Post();
$post->addTag('Economy');
如果'Economy'标签已经存在,将使用它,否则将创建一个新标签。