学说实体创建策略


Doctrine entity creation strategy

在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'标签已经存在,将使用它,否则将创建一个新标签。