ORO crm 核心实体中的 ManyToMany 自引用关联
ORO crm ManyToMany self reference association in core entity
想就当前更新的解决方案征求意见。几个条件和用例:
- Friends 从 UserBundle
扩展了核心 UserEntity 的 manytomany属性
- 当我们添加好友时,所有者实体也应该出现在子实体中
- 当我们删除朋友时,朋友也应该从拥有方的关联中解脱出来
我的解决方案是在onFlush上使用事件订阅器,也许有人有更好的方法?
<?php
namespace App\Bundle\UserBundle\Form\EventListener;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Events;
use Doctrine\ORM\PersistentCollection;
use Oro\Bundle\UserBundle\Entity\User;
use Doctrine\ORM\Event\OnFlushEventArgs;
class UserEventSubscriber implements EventSubscriber
{
/**
* @return array
*/
public function getSubscribedEvents(): array
{
return [
Events::onFlush,
];
}
/**
* @param OnFlushEventArgs $args
*/
public function onFlush(OnFlushEventArgs $args): void
{
$uow = $args->getEntityManager()->getUnitOfWork();
$updates = array_merge(
$uow->getScheduledCollectionUpdates(),
$uow->getScheduledCollectionDeletions()
);
array_walk(
$updates,
fn (PersistentCollection $collection) => $this->processFriendsOnFlush($collection, $args)
);
}
/**
* @param PersistentCollection $collection
* @param OnFlushEventArgs $args
*/
private function processFriendsOnFlush(PersistentCollection $collection, OnFlushEventArgs $args): void
{
$mapping = $collection->getMapping();
if ($collection->getOwner() instanceof User && $mapping['fieldName'] === 'friends')
{
$entityManager = $args->getEntityManager();
/** @var User $ownerUser */
$ownerUser = $collection->getOwner();
$snapshotFriends = $collection->getSnapshot();
$updatedFriends = $collection->getValues();
if (count($updatedFriends) > 0 && $snapshotFriends !== $updatedFriends) {
array_walk($updatedFriends, fn(User $friend) => $this->bindFriendAssociation($entityManager, $ownerUser, $friend));
}
if (count($snapshotFriends) > 0) {
array_walk($snapshotFriends, fn(User $friend) => $this->removeFriendAssociation($entityManager, $ownerUser, $friend));
}
}
}
/**
* Bind friend association to keep bidirectional relation on adding new friend user
*
* @param EntityManager $entityManager
* @param User $ownerUser
* @param User $friend
*
* @throws \Doctrine\ORM\ORMException
*/
private function bindFriendAssociation(EntityManager $entityManager, User $ownerUser, User $friend): void
{
if (!$friend->getFriends()->contains($ownerUser)) {
$friend->addFriend($ownerUser);
$entityManager->persist($friend);
$entityManager->getUnitOfWork()->computeChangeSet($entityManager->getClassMetadata(get_class($friend)), $friend);
}
}
/**
* Remove friend user association to keep bidirectional relation on remove friend user from child
*
* @param EntityManager $entityManager
* @param User $ownerUser
* @param User $friend
*
* @throws \Doctrine\ORM\ORMException
*/
private function removeFriendAssociation(EntityManager $entityManager, User $ownerUser, User $friend): void
{
if ($friend->getFriends()->contains($ownerUser)) {
$friend->removeFriend($ownerUser);
$entityManager->persist($friend);
$entityManager->getUnitOfWork()->computeChangeSet($entityManager->getClassMetadata(get_class($friend)), $friend);
}
}
}
与我添加 ManyToMany 关联的方式相同:
$this->extendExtension->addManyToManyRelation(
$schema,
$schema->getTable('oro_user'),
'friends',
$schema->getTable('oro_user'),
['last_name', 'first_name'],
['last_name', 'first_name'],
['last_name', 'first_name'],
[
'extend' => [
'owner' => ExtendScope::OWNER_CUSTOM,
'target_title' => ['id'],
'target_detailed' => ['id'],
'target_grid' => ['id'],
'cascade' => ['persist'],
],
'dataaudit' => ['auditable' => true],
]
);
确保关系是双向的,并且您已在实体构造函数中为两个属性初始化 ArrayCollection,如文档所述:
https://www.doctrine-project.org/projects/doctrine-orm/en/2.8/reference/association-mapping.html#many-to-many-bidirectional.
并且您有 add*
、remove*
方法并正确实施,例如:
public function addFriend(User $user)
{
if (!$this->friends->contains($user)) {
$this->friends->add($user);
$user->addFriend($this);
}
return $this;
}
public function removeFriend(User $user)
{
if ($this->friends->contains($user)) {
$this->friends->removeElement($user);
$user->removeFriend($this);
}
return $this;
}
如果是OroPlatform生成的代码,你可以通过写一个自定义来影响它Oro\Bundle\EntityExtendBundle\Tools\GeneratorExtensions\AbstractEntityGeneratorExtension
。一个很好的例子是 Oro\Bundle\EntitySerializedFieldsBundle\Tools\GeneratorExtensions\SerializedDataGeneratorExtension.
对你来说,它会是这样的:
# ...
public function generate(array $schema, ClassGenerator $class): void
{
$method = $class->getMethod('addFriend');
$method->setBody('if (!$this->friends->contains($user)) {
$this->friends->add($user);
$user->addFriend($this);
}');
$method = $class->getMethod('removeFriend');
$method->setBody('if ($this->friends->contains($user)) {
$this->friends->removeElement($user);
$user->removeFriend($this);
}');
}
# ...
并且不要忘记使用 oro_entity_extend.entity_generator_extension
标签 register the generator in a service container。
想就当前更新的解决方案征求意见。几个条件和用例:
- Friends 从 UserBundle 扩展了核心 UserEntity 的 manytomany属性
- 当我们添加好友时,所有者实体也应该出现在子实体中
- 当我们删除朋友时,朋友也应该从拥有方的关联中解脱出来
我的解决方案是在onFlush上使用事件订阅器,也许有人有更好的方法?
<?php
namespace App\Bundle\UserBundle\Form\EventListener;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Events;
use Doctrine\ORM\PersistentCollection;
use Oro\Bundle\UserBundle\Entity\User;
use Doctrine\ORM\Event\OnFlushEventArgs;
class UserEventSubscriber implements EventSubscriber
{
/**
* @return array
*/
public function getSubscribedEvents(): array
{
return [
Events::onFlush,
];
}
/**
* @param OnFlushEventArgs $args
*/
public function onFlush(OnFlushEventArgs $args): void
{
$uow = $args->getEntityManager()->getUnitOfWork();
$updates = array_merge(
$uow->getScheduledCollectionUpdates(),
$uow->getScheduledCollectionDeletions()
);
array_walk(
$updates,
fn (PersistentCollection $collection) => $this->processFriendsOnFlush($collection, $args)
);
}
/**
* @param PersistentCollection $collection
* @param OnFlushEventArgs $args
*/
private function processFriendsOnFlush(PersistentCollection $collection, OnFlushEventArgs $args): void
{
$mapping = $collection->getMapping();
if ($collection->getOwner() instanceof User && $mapping['fieldName'] === 'friends')
{
$entityManager = $args->getEntityManager();
/** @var User $ownerUser */
$ownerUser = $collection->getOwner();
$snapshotFriends = $collection->getSnapshot();
$updatedFriends = $collection->getValues();
if (count($updatedFriends) > 0 && $snapshotFriends !== $updatedFriends) {
array_walk($updatedFriends, fn(User $friend) => $this->bindFriendAssociation($entityManager, $ownerUser, $friend));
}
if (count($snapshotFriends) > 0) {
array_walk($snapshotFriends, fn(User $friend) => $this->removeFriendAssociation($entityManager, $ownerUser, $friend));
}
}
}
/**
* Bind friend association to keep bidirectional relation on adding new friend user
*
* @param EntityManager $entityManager
* @param User $ownerUser
* @param User $friend
*
* @throws \Doctrine\ORM\ORMException
*/
private function bindFriendAssociation(EntityManager $entityManager, User $ownerUser, User $friend): void
{
if (!$friend->getFriends()->contains($ownerUser)) {
$friend->addFriend($ownerUser);
$entityManager->persist($friend);
$entityManager->getUnitOfWork()->computeChangeSet($entityManager->getClassMetadata(get_class($friend)), $friend);
}
}
/**
* Remove friend user association to keep bidirectional relation on remove friend user from child
*
* @param EntityManager $entityManager
* @param User $ownerUser
* @param User $friend
*
* @throws \Doctrine\ORM\ORMException
*/
private function removeFriendAssociation(EntityManager $entityManager, User $ownerUser, User $friend): void
{
if ($friend->getFriends()->contains($ownerUser)) {
$friend->removeFriend($ownerUser);
$entityManager->persist($friend);
$entityManager->getUnitOfWork()->computeChangeSet($entityManager->getClassMetadata(get_class($friend)), $friend);
}
}
}
与我添加 ManyToMany 关联的方式相同:
$this->extendExtension->addManyToManyRelation(
$schema,
$schema->getTable('oro_user'),
'friends',
$schema->getTable('oro_user'),
['last_name', 'first_name'],
['last_name', 'first_name'],
['last_name', 'first_name'],
[
'extend' => [
'owner' => ExtendScope::OWNER_CUSTOM,
'target_title' => ['id'],
'target_detailed' => ['id'],
'target_grid' => ['id'],
'cascade' => ['persist'],
],
'dataaudit' => ['auditable' => true],
]
);
确保关系是双向的,并且您已在实体构造函数中为两个属性初始化 ArrayCollection,如文档所述: https://www.doctrine-project.org/projects/doctrine-orm/en/2.8/reference/association-mapping.html#many-to-many-bidirectional.
并且您有 add*
、remove*
方法并正确实施,例如:
public function addFriend(User $user)
{
if (!$this->friends->contains($user)) {
$this->friends->add($user);
$user->addFriend($this);
}
return $this;
}
public function removeFriend(User $user)
{
if ($this->friends->contains($user)) {
$this->friends->removeElement($user);
$user->removeFriend($this);
}
return $this;
}
如果是OroPlatform生成的代码,你可以通过写一个自定义来影响它Oro\Bundle\EntityExtendBundle\Tools\GeneratorExtensions\AbstractEntityGeneratorExtension
。一个很好的例子是 Oro\Bundle\EntitySerializedFieldsBundle\Tools\GeneratorExtensions\SerializedDataGeneratorExtension.
对你来说,它会是这样的:
# ...
public function generate(array $schema, ClassGenerator $class): void
{
$method = $class->getMethod('addFriend');
$method->setBody('if (!$this->friends->contains($user)) {
$this->friends->add($user);
$user->addFriend($this);
}');
$method = $class->getMethod('removeFriend');
$method->setBody('if ($this->friends->contains($user)) {
$this->friends->removeElement($user);
$user->removeFriend($this);
}');
}
# ...
并且不要忘记使用 oro_entity_extend.entity_generator_extension
标签 register the generator in a service container。