【问题标题】:How to set doctrine associations?如何设置教义关联?
【发布时间】:2019-02-09 16:22:22
【问题描述】:

我知道实体中的关联属性是实现\Doctrine\Common\Collections\Collection。我知道在构造函数中应该初始化这样的属性:

$this->collection = new \Doctrine\Common\Collections\ArrayCollection()

我知道我可以使用ArrayCollection#add()ArrayCollection#remove() 修改集合。但是我有不同的情况。

假设我有一个新的简单关联实体数组。使用现有方法我需要检查数组中的每个元素:如果实体集合有它。如果否 - 将数组元素添加到实体集合。除此之外,我需要检查实体集合中的每个元素。如果新数组中没有任何集合元素,那么我需要将其从集合中删除。琐碎的事情要做这么多工作。

我想要什么?要实现setProducts 方法:

class Entity {
  private $products;

  // ... constructor

  public function setProducts(array $products)
  {
    // synchronize $products with $this->products
  }
}

我试过了:$this->products = new ArrayCollection($products)。然而,这使得学说删除所有产品并从$products 参数中添加这些产品。我想要类似的结果,但没有数据库查询。

Doctrine 中是否有针对这种情况的内置解决方案?

编辑: 我想在ArrayCollection 中有一个方法,比如fromArray,它将合并集合中的元素,删除不需要的元素。这只会重复使用 add/remove 手动调用集合参数中的每个元素。

【问题讨论】:

  • 您能否详细说明“我想要类似的结果但没有数据库查询”的意思?当您从列表中添加/删除元素时,Doctrine 不会执行(插入/删除/更新)查询。它将在后台记录更改,然后在您在 EntityManager 上调用 flush() 时计算要执行的查询。如果查询性能是您关心的问题,您能否在问题中补充一下您对意外/缓慢查询有什么问题?
  • @dbrumann 我的意思是,如果我使用 $this->products = new ArrayCollection($products) 原则,请先删除所有产品,然后再插入。
  • @dbrumann 但是我刚刚注意到,您提出了一种无需查询即可执行此操作的方法。
  • 当你说“简单的集合数组”时,你的意思是你有一个集合数组,每个集合都有一组元素吗?
  • @Oli 很抱歉混淆,没有注意到它是模棱两可的。我编辑了这个问题。我的意思是一个简单的关联实体数组。 ArrayCollection#toArray 返回什么。

标签: php symfony doctrine


【解决方案1】:

Doctrine 集合没有“合并”功能,可以从另一个集合中的数组或集合中添加/删除实体。

如果您想“简化”您使用添加/删除描述的手动合并过程,您可以使用 array_merge 假设两个数组都不是数字,而是具有某种唯一键,例如实体的spl_object_hash

public function setProducts(array $products)
{
    $this->products = new ArrayCollection(
        array_merge(
            array_combine(
                array_map('spl_object_hash', $this->products->toArray()),
                $this->products->toArray()
            ),
            array_combine(
                array_map('spl_object_hash', $products),
                $products->toArray()
            )
        )
    );
}

您可能希望使用产品 ID 而不是 spl_object_hash,因为 2 个产品具有相同的 ID,但创建为单独的实体 - 例如一个通过 Doctrine 中的 findBy() 和一个使用 new Product() 手动创建的 - 将被识别为 2 个不同的产品,并可能导致另一个插入尝试。

由于您用新的 ArrayCollection 替换原来的 PersistentCollection 来保存您之前获取的产品,这可能仍然会导致不需要的查询或在刷新 EntityManager 时产生意外的结果。更不用说,这种方法可能比在原始 Collection 上显式调用 addElement/removeElement 更难阅读。

【讨论】:

  • 我刚试过你的方法。结果和我的一样。 Doctrine 开始一个事务,删除所有带有DELETE FROM products_categories WHERE category_id = ? 的产品,并在方法参数中插入产品。看起来 Doctrine 使用了$this->products,它在构造函数中初始化并且不喜欢它的重写。
【解决方案2】:

我会通过创建我自己的扩展 Doctrine 数组集合类的集合类来解决它:

use Doctrine\Common\Collections\ArrayCollection;

class ProductCollection extends ArrayCollection
{
}

在实体本身中,您将在 __constructor 中对其进行初始化:

public function __construct()
{
    $this->products = new ProductCollection();
}

在这里,Doctrine 会将您的集合类用于产品结果。在此之后,您可以添加自己的函数来处理您的特殊合并,也许是:

public function mergeProducts(ProductCollection $products): ProductCollection
{
    $result = new ProductCollection();
    foreach($products as $product) {
        $add = true;
        foreach($this->getIterator() as $p) {
            if($product->getId() === $p->getId()) {
                $result->add($product);
                $add = false;
            }
        }
        if($add) {
            $result->add($product);
        }
    }
    return $result;
}

它将返回一个全新的产品集合,您可以替换实体中的其他集合。但是,如果实体已附加并在原则控制下,这将在另一端呈现 SQL,如果您想在不冒数据库更新风险的情况下使用实体,则需要分离实体:

$entityManager->detach($productEntity);

希望对你有帮助

【讨论】:

  • 我同意这是某种方法。然而,这么琐碎的任务需要做这么多工作?我认为 Doctinre 具有执行此任务的内置方法。
  • 很遗憾没有
猜你喜欢
  • 2012-02-10
  • 1970-01-01
  • 1970-01-01
  • 2012-03-07
  • 2017-04-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多