软删除内部关系的原则

问题描述 投票:0回答:1

大家好,我有一个关于软删除内部关系的问题。

对于我正在使用的软删除

Gedmo\SoftDeleteable
,这是我的实体(我已经删除了很多东西)

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use JMS\Serializer\Annotation as JMS;


/**
 * Item
 *
 * @ORM\Table(name="item")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\ItemRepository")
 * @Gedmo\SoftDeleteable(fieldName="deletedAt", timeAware=false)
 */
class Item implements EntityInterface
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

   /**
     * One Item has Many Images.
     * @ORM\OneToMany(targetEntity="Image", mappedBy="item", cascade="remove")
     * @MaxDepth(1)
     * @JMS\Groups({"images"})
     */
    private $images;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="deleted_at", type="datetime", nullable=true)
     */
    protected $deletedAt;

    public function getId()
    {
        return $this->id;
    }

    public function __construct()
    {
        $this->images = new \Doctrine\Common\Collections\ArrayCollection();
    }

    public function addImage(\AppBundle\Entity\Image $image)
    {
        $this->images[] = $image;

        return $this;
    }

    public function removeImage(\AppBundle\Entity\Image $image)
    {
        $this->images->removeElement($image);
    }

    public function getImages()
    {
        return $this->images;
    }

    public function getImagePreview()
    {
        foreach($this->getImages() as $image){
            if ($image->getPreview()) {
                return substr($image->getPath(), strpos($image->getPath(), "/images/") + 8);
            }
        }

        return '';
    }

    public function setDeletedAt($deletedAt)
    {
        $this->deletedAt = $deletedAt;

        return $this;
    }


    public function getDeletedAt()
    {
        return $this->deletedAt;
    }
}

我删除了一些图像,并且在我的数据库中,当图像被删除时,字段deleted_at被编译,因此如果我自动创建带有教义的

findAll
,记录不会返回

但是,如果我从模板中调用 getImages,我会检索所有图像以及已删除的图像,但我不想要它。

这就是我在循环内从模板调用 getImages 的方式:

foreach($lot->getImages() as $image) { 
    //code
}

如何使用函数

getImages
仅检索未删除的图像?

谢谢

附注我不想在模板中插入

if condition
,我想在后端解决它

php symfony doctrine-orm doctrine
1个回答
0
投票

Doctrine 有 2 种实体操作方法:

EntityPersister
DQL
QueryBuilder
。对于像
findAll
这样的简单查询,Doctrine 将选择
BasicEntityPersister
来执行查询。

现在的问题是 Gedmo 不扫描已执行实体的关联,它只关注当前查询。在第一个

EntityPersister
模式中,由于缺乏关联意识,它永远不会添加所需的条件。您可以使用第二种模式来欺骗它,使用 QueryBuilder 和
innerJoin
以及
Images
实体 - 这对您来说将是最快的解决方案。

我为

BasicEntityPersister
提供了一个复杂的解决方案,并且它有效。步骤如下:

  1. 创建一个新的 EntityManager 扩展
    EntityManagerDecorator
    并装饰服务:
    entity_manager_proxy:
        public: false
        class: App\Doctrine\EntityManagerProxy
        decorates: doctrine.orm.default_entity_manager
        arguments: [ "@entity_manager_proxy.inner" ]
  1. 为 UOW 创建一个装饰器并在新的 EM 中调用它:
  public function getUnitOfWork()
    {
        return new UnitOfWorkProxy($this);
    }
  1. 在您的 UOW 代理中,您现在可以根据需要覆盖
    EntityPersister
    。我确实喜欢这样:
  public function getEntityPersister($entityName)
    {
        $persister = parent::getEntityPersister($entityName);

        // we are only hooking up the common persister
        if (!$persister instanceof BasicEntityPersister) {
            return $persister;
        }

        $class = $this->em->getClassMetadata($entityName);

        return new SoftDeleteableBasicEntityPersister(
            $this->em,
            $class
        );
    }
  1. 这是实现这一技巧的实际代码:扫描关联并添加所需的条件。幸运的是,我不必覆盖整个
    getSelectColumnsSQL
    方法:
    protected function getSelectColumnsSQL()
    {
        // luckily we don't need to modify the SQL created, but a property in the context
        $sql = parent::getSelectColumnsSQL();

        $aliasCounter = 0;

        // we'll be going through all associations and find those with the soft-deleted trait
        foreach ($this->class->associationMappings as $assocField => $mapping) {
            $targetEntityClass = $mapping['targetEntity'];

            if (!$this->isSoftDeleted($targetEntityClass)) {
                continue;
            }

            if (!$mapping['isOwningSide']) {
                continue;
            }

            $targetEntityClassMetadata = $this->em->getClassMetadata($targetEntityClass);
            if ($mapping['mappedBy'] !== null) {
                $association = $targetEntityClassMetadata->getAssociationMapping($mapping['mappedBy']);
            } else {
                $association = $mapping;
            }
            $assocAlias = 's' . ($aliasCounter++);

            $joinTableAlias = $this->getSQLTableAlias($targetEntityClassMetadata->name, $assocAlias);
            $joinTableName  = $this->quoteStrategy->getTableName($targetEntityClassMetadata, $this->platform);

            $tableAlias = $this->getSQLTableAlias($association['targetEntity'], $assocAlias);

            // if we're sure our entity has a relation with a soft-deleted entity,
            // let's add an INNER JOIN to prevent from listing those which were deleted
            $this->currentPersisterContext->selectJoinSql .= ' INNER JOIN';

            foreach ($association['joinColumns'] as $joinColumn) {
                $sourceCol       = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform);
                $targetCol       = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $this->class, $this->platform);
                $joinCondition[] = $this->getSQLTableAlias($association['sourceEntity'])
                    . '.' . $sourceCol . ' = ' . $tableAlias . '.' . $targetCol;
            }

            // Add filter SQL
            $filterSql = $this->generateFilterConditionSQL($targetEntityClassMetadata, $tableAlias);
            if ($filterSql) {
                $joinCondition[] = $filterSql;
            }

            // the result of our operation is stored inside the context
            // luckily this will be pulled into the main SQL later
            $this->currentPersisterContext->selectJoinSql .= ' ' . $joinTableName . ' ' . $joinTableAlias . ' ON ';
            $this->currentPersisterContext->selectJoinSql .= implode(' AND ', $joinCondition);
        }

        return $sql;
    }

对于 Doctrine 2.4,它可以工作,但如果 Doctrine 的未来版本允许以更一致的方式修改查询,则可能会更改/中断。

对于DQL/QueryBuilder操作方法,您需要以类似的方式重写

EntityRepository
QueryBuilder
,然后基本上您可以根据需要围绕
getQuery()
方法进行操作,使用SqlWalkers等的提示。如果需要可以提供解决方案

希望有帮助!

© www.soinside.com 2019 - 2024. All rights reserved.