我应该如何检查对象集合中的重复实体?
How should I check for dupplicate entity in an object collection?
我一直在读这个 post :
但我实际上不喜欢这个解决方案,因为学说已经提供了 contains() method, which have the advantage to keep logic directly into the object, and then to not load EXTRA_LAZY collections entirely。
所以这里 Cart
实体拥有一个 CartProduct 集合:
/**
* ...
* @ORM\Entity(repositoryClass="App\Repository\CartRepository")
*/
abstract class Cart implements InheritanceInterface{
...
/**
* @ORM\OneToMany(targetEntity="CartProduct", mappedBy="cart", fetch="EXTRA_LAZY", cascade={"persist"})
*/
private Collection $cartProducts;
...
public function __construct()
{
$this->cartProducts = new ArrayCollection();
}
...
}
(CartProduct 必须是一个实体,查看这个 simplify EA model. That's a standard way to proceed 相关实体持有额外字段)
现在我想添加一个新的 ProductCart
实体到我的 Cart
class。
所以我要添加这个方法(由 Symfony make:entity
生成):
abstract class Cart implements InheritanceInterface{
...
public function addCartProduct(CartProduct $cartProduct): self
{
if(!$this->getCartProducts()->contains($cartProduct)) {
$this->cartProducts->add($cartProduct);
$cartProduct->setCart($this);
}
return $this;
}
...
然后我测试这段代码:
public function testAddCartProduct()
{
$cart = new ShoppingCart($this->createMock(ShoppingCartState::class));
$cart_product = new CartProduct();
$cart_product->setProduct(new Product(self::NO_.'1', new Group('1')));
$cart->addCartProduct($cart_product);
$cart_product2 = new CartProduct();
$cart_product2->setProduct(new Product(self::NO_.'1', new Group('1')));
$cart->addCartProduct($cart_product2);
$this->assertCount(1, $cart->getCartProducts());
}
但是当我运行这个测试时,它失败了:
Failed asserting that actual size 2 matches expected size 1.
所以我检查了一下,Cart
.cartProducts
Collection 有两个完全相同的对象的产品。
因为它是一个 ArrayCollection
,我想它只是使用这个方法:
namespace Doctrine\Common\Collections;
class ArrayCollection implements Collection, Selectable {
...
public function contains($element)
{
return in_array($element, $this->elements, true);
}
那么好吧,当然在这种情况下它只是return false,并且对象被认为是不同的。
所以现在,我希望在实现 Collection
对象时可以使用 PersistentCollection
而不是 ArrayCollection
,因为 PersistentCollection
.contains()
方法看起来更好。
abstract class Cart implements InheritanceInterface{
...
public function __construct()
{
-- $this->cartProducts = new ArrayCollection();
++ $this->cartProducts = new PersistentCollection(...);
}
}
但这需要一个 EntityManager
作为参数,因此,将 EntityManager 赋予 Entity 对象有点矫枉过正...
所以我最后不知道什么是检查集合中重复实体的更好方法。
当然,我可以自己实现这样的东西:
abstract class Cart implements InheritanceInterface{
...
public function addCartProduct(CartProduct $cartProduct): self
{
if(!$this->getCartProducts()->filter(
function (CartProduct $cp)use($cartProduct){
return $cp->getId() === $cartProduct->getId();
})->count()) {
$this->cartProducts->add($cartProduct);
$cartProduct->setCart($this);
}
return $this;
}
...
但它需要加载每个实体,我真的不喜欢这个主意。
我个人同意你的意见,我认为实体本身不应该有责任确保没有重复。
该实体无法像存储库那样发出请求,而且我不明白您如何在不查询的情况下确定数据库中没有重复项。
调用 contains
不会在您的情况下触发提取,这意味着集合将保持原样,无论如何这不是您想要的,因为您可能有一个以前持久化的副本,但不会成为其中的一部分集合,因为您将其标记为 EXTRA_LAZY
.
您也不希望获取集合中的所有实体(并将结果转换为对象)只是为了检查是否发生碰撞。
所以恕我直言,您应该在实体的存储库中创建一个方法来检查重复项,一个简单的 SELECT COUNT(id)
。
那就是你真正的问题了。
你做测试的方式永远不会发现碰撞。当你这样做时:
$cart = new ShoppingCart($this->createMock(ShoppingCartState::class));
$cart_product = new CartProduct();
$cart_product->setProduct(new Product(self::NO_.'1', new Group('1')));
$cart->addCartProduct($cart_product);
$cart_product2 = new CartProduct();
$cart_product2->setProduct(new Product(self::NO_.'1', new Group('1')));
$cart->addCartProduct($cart_product2);
$this->assertCount(1, $cart->getCartProducts());
您正在创建 CartProduct
的 两个 个实例,这就是为什么调用 contains
找不到任何东西。
因为 contains
检查对象引用,而不是内容,就像您在其实现中看到的那样:
public function contains($element)
{
return in_array($element, $this->elements, true);
}
因此在您的测试用例中,您真正测试的是:
in_array(new CartProduct(), [new CartProduct()], true);
这将始终 return 错误。
我一直在读这个 post :
但我实际上不喜欢这个解决方案,因为学说已经提供了 contains() method, which have the advantage to keep logic directly into the object, and then to not load EXTRA_LAZY collections entirely。
所以这里 Cart
实体拥有一个 CartProduct 集合:
/**
* ...
* @ORM\Entity(repositoryClass="App\Repository\CartRepository")
*/
abstract class Cart implements InheritanceInterface{
...
/**
* @ORM\OneToMany(targetEntity="CartProduct", mappedBy="cart", fetch="EXTRA_LAZY", cascade={"persist"})
*/
private Collection $cartProducts;
...
public function __construct()
{
$this->cartProducts = new ArrayCollection();
}
...
}
(CartProduct 必须是一个实体,查看这个 simplify EA model. That's a standard way to proceed 相关实体持有额外字段)
现在我想添加一个新的 ProductCart
实体到我的 Cart
class。
所以我要添加这个方法(由 Symfony make:entity
生成):
abstract class Cart implements InheritanceInterface{
...
public function addCartProduct(CartProduct $cartProduct): self
{
if(!$this->getCartProducts()->contains($cartProduct)) {
$this->cartProducts->add($cartProduct);
$cartProduct->setCart($this);
}
return $this;
}
...
然后我测试这段代码:
public function testAddCartProduct()
{
$cart = new ShoppingCart($this->createMock(ShoppingCartState::class));
$cart_product = new CartProduct();
$cart_product->setProduct(new Product(self::NO_.'1', new Group('1')));
$cart->addCartProduct($cart_product);
$cart_product2 = new CartProduct();
$cart_product2->setProduct(new Product(self::NO_.'1', new Group('1')));
$cart->addCartProduct($cart_product2);
$this->assertCount(1, $cart->getCartProducts());
}
但是当我运行这个测试时,它失败了:
Failed asserting that actual size 2 matches expected size 1.
所以我检查了一下,Cart
.cartProducts
Collection 有两个完全相同的对象的产品。
因为它是一个 ArrayCollection
,我想它只是使用这个方法:
namespace Doctrine\Common\Collections;
class ArrayCollection implements Collection, Selectable {
...
public function contains($element)
{
return in_array($element, $this->elements, true);
}
那么好吧,当然在这种情况下它只是return false,并且对象被认为是不同的。
所以现在,我希望在实现 Collection
对象时可以使用 PersistentCollection
而不是 ArrayCollection
,因为 PersistentCollection
.contains()
方法看起来更好。
abstract class Cart implements InheritanceInterface{
...
public function __construct()
{
-- $this->cartProducts = new ArrayCollection();
++ $this->cartProducts = new PersistentCollection(...);
}
}
但这需要一个 EntityManager
作为参数,因此,将 EntityManager 赋予 Entity 对象有点矫枉过正...
所以我最后不知道什么是检查集合中重复实体的更好方法。
当然,我可以自己实现这样的东西:
abstract class Cart implements InheritanceInterface{
...
public function addCartProduct(CartProduct $cartProduct): self
{
if(!$this->getCartProducts()->filter(
function (CartProduct $cp)use($cartProduct){
return $cp->getId() === $cartProduct->getId();
})->count()) {
$this->cartProducts->add($cartProduct);
$cartProduct->setCart($this);
}
return $this;
}
...
但它需要加载每个实体,我真的不喜欢这个主意。
我个人同意你的意见,我认为实体本身不应该有责任确保没有重复。
该实体无法像存储库那样发出请求,而且我不明白您如何在不查询的情况下确定数据库中没有重复项。
调用 contains
不会在您的情况下触发提取,这意味着集合将保持原样,无论如何这不是您想要的,因为您可能有一个以前持久化的副本,但不会成为其中的一部分集合,因为您将其标记为 EXTRA_LAZY
.
您也不希望获取集合中的所有实体(并将结果转换为对象)只是为了检查是否发生碰撞。
所以恕我直言,您应该在实体的存储库中创建一个方法来检查重复项,一个简单的 SELECT COUNT(id)
。
那就是你真正的问题了。
你做测试的方式永远不会发现碰撞。当你这样做时:
$cart = new ShoppingCart($this->createMock(ShoppingCartState::class));
$cart_product = new CartProduct();
$cart_product->setProduct(new Product(self::NO_.'1', new Group('1')));
$cart->addCartProduct($cart_product);
$cart_product2 = new CartProduct();
$cart_product2->setProduct(new Product(self::NO_.'1', new Group('1')));
$cart->addCartProduct($cart_product2);
$this->assertCount(1, $cart->getCartProducts());
您正在创建 CartProduct
的 两个 个实例,这就是为什么调用 contains
找不到任何东西。
因为 contains
检查对象引用,而不是内容,就像您在其实现中看到的那样:
public function contains($element)
{
return in_array($element, $this->elements, true);
}
因此在您的测试用例中,您真正测试的是:
in_array(new CartProduct(), [new CartProduct()], true);
这将始终 return 错误。