1

我有以下两个集合:

$credits = collect([
   ['quantity' => 3, 'product_id' => 1],
   ['quantity' => 2, 'product_id' => 5]
]);

$basketItems = collect([
   ['id' => 1, 'basket_id' => 4, 'product_id' => 1],
   ['id' => 2, 'basket_id' => 4, 'product_id' => 2],
   ['id' => 3, 'basket_id' => 4, 'product_id' => 1],
   ['id' => 4, 'basket_id' => 4, 'product_id' => 1],
   ['id' => 5, 'basket_id' => 4, 'product_id' => 1]
]);

$credits集合告诉我们用户有 3 个可用积分可用于product_id1。

现在我想创建两个新集合。如果用户有他们有可用积分的购物篮项目 - 这可以由 product_id 确定,那么我想将这些项目添加到一个名为 的新集合$basketItemsUseCredits中。

如果购物篮中的商品没有该产品类型的可用积分,我想将这些商品添加到另一个名为$basketItemsPay.

因此,在上面的示例中,我应该以$basketItemsUseCreditsID 为 1,3 和 4$basketItemsPay的购物篮结束。最终应该以 ID 为 2 和 5 的购物篮结束。以下行不通。

  $basketItemsUseCredits = $basketItems->map(function ($basketItem) use ($credits) {

        $qty = $credits->where('product_id', $basketItem->product_id)->get('quantity', 0);

        if ($qty > 0) {
             // update quantity
             $credits->transform(function ($credit) use ($basketItem) {
                    if ($credit->product_id == $basketItem->product_id) {
                         $credit->quantity = $credit->quantity - 1;
                         return $credit;
                    }
                    else
                        return $credit
            });               

           return $basketItem;
        }
  })


   $basketItemsPay = $basketItems->map(function ($basketItem) use ($basketItemsUseCredits) {

        if (!$basketItemsUseCredits->contains('id', $basketItem->id))
              return $basketItem;
    });

    if(!$basketItemsPay->isEmpty()) {
        // do some extra stuff
    }

以下行始终返回 0:

$qty = $credits->where('product_id', $basketItem->product_id)->get('quantity', 0);

还有一件事我注意到了。如果$basketItemsPay为空,例如如果dd($basketItemsPay)我得到以下信息:

Collection {#316 ▼
    #items: array:1 [▼
       0 => null
    ]
 }

那么为什么在上述情况下,以下总是评估为真?

if(!$basketItemsPay->isEmpty()) {
        // do some extra stuff
}

任何帮助表示赞赏。

* 更新 *

通过执行以下操作设法修复 - 除非有人知道更好的解决方案:

$qty = $credits->where('product_id', $basketItem->product_id)->first()['quantity'];

并将拒绝方法链接如下以摆脱空值 - 有人知道更优雅的解决方案吗?

$basketItemsPay = $basketItems->map(function ($basketItem) use ($basketItemsUseCredits) {

      if (!$basketItemsUseCredits->contains('id', $basketItem->id))
             return $basketItem;

})->reject(function ($basketItem) {
        return empty($basketItem);
});
4

1 回答 1

0

我也花了一段时间才弄清楚这一点。下面是我将如何处理这个问题。这是一种更线性的方法,并且比 Collection 方法中的嵌套闭包更易于解释。它也比您的示例多一点代码,但这是个人喜好问题。我试图在代码注释中尽可能地具有描述性。

让我们自己更轻松的最重要部分在于从$credits一开始就改变集合的结构:

// First, let's simplify the $credits structure for easy checking. NOTE: this collection WILL mutate by the code below.
// If the original version is to be preserved for whatever reason, now's the time to duplicate it :)
$credits = collect([
   ['quantity' => 3, 'product_id' => 1],
   ['quantity' => 2, 'product_id' => 5]
])
    ->pluck('quantity', 'product_id')

/* Results in a collection where keys are product_id, and values are quantity:

    Illuminate\Support\Collection {
        all: [
            1 => 3,
            5 => 2
        ]
    }

*/

$basketItems = collect([
   ['id' => 1, 'basket_id' => 4, 'product_id' => 1],
   ['id' => 2, 'basket_id' => 4, 'product_id' => 2],
   ['id' => 3, 'basket_id' => 4, 'product_id' => 1],
   ['id' => 4, 'basket_id' => 4, 'product_id' => 1],
   ['id' => 5, 'basket_id' => 4, 'product_id' => 1]
]);


$basketItemsUseCredits  = new \Illuminate\Support\Collection;

// First, we will filter the $basketItems collection by the condition that the product_id for each item
// does NOT occur as key inside $credits. NOTE: filter() returns a new collection. The original $basketItems
// does not mutate because of this call.
$basketItemsPay = $basketItems->reject(function ($basketItem) use ($credits) {

    // If credits has a key corresponding the product_id of the current $basketItem, REJECT the $basketItem
    return $credits->has($basketItem['product_id']);
});



// Now, we will create an intermediate collection of basket items that need to be compared against the quantity
// of credits available for products of the ID's present in this collection:
$basketItemsCountCredits = $basketItems->filter(function ($basketItem) use ($credits) {

    // If credits has a key corresponding the product_id of the current $basketItem, KEEP the $basketItem
    return $credits->has($basketItem['product_id']);
});

// Lastly we will iterate the $basketItemsCountCredits collection, and determine whether credits are still available
// for each item. If so, push the item to $basketItemsUseCredits AND decrement the amount of available credits for 
// the item's ID. otherwise just push the item to $basketItemsPay.
foreach ($basketItemsCountCredits as $item) {

    $productId = $item['product_id'];
    $remainingCredits = $credits->get($productId);

    // If more than 0 credits are available for products with ID of $item['product_id']
    if ($remainingCredits > 0) {

        // .. push the $item to $basketItemsUseCredits,
        $basketItemsUseCredits->push($item);

        // .. and decrement the amount of available credits. 
        // Collection->put() overwrites the key => value pair in the collection if the key already exists.
        $credits->put($productId, $remainingCredits - 1);
    } else {

        // The amount of available credits might have become 0 in previous iterations of this for-loop
        $basketItemsPay->push($item);
    }
}

Illuminate Collections 上有很多非常强大的方法。当以正确的方式使用时,它们可以允许一些非常干净和简洁的代码!

但是,在不必要的情况下使用这些相同的方法也会使您在代码可读性和复杂性方面倒退。在某些情况下,传递回调函数的方法主要在该回调的定义在其他地方完成时非常有用,并且它依赖于该上下文来完成其工作。从您的示例来看,这里似乎并非如此。

不像我在上面的例子中那样连续使用filterand方法,你可以使用旧的 foreach 循环来同时执行这两个动作。reject但是,这确实需要预先将$basketItemsCountCredits$basketItemsPay变量初始化为新集合。如果我们也去掉注释,它实际上并没有那么多代码,而且仍然完全可读:)

$credits = collect([
   ['quantity' => 3, 'product_id' => 1],
   ['quantity' => 2, 'product_id' => 5]
])->pluck('quantity', 'product_id')

$basketItems = collect([
   ['id' => 1, 'basket_id' => 4, 'product_id' => 1],
   ['id' => 2, 'basket_id' => 4, 'product_id' => 2],
   ['id' => 3, 'basket_id' => 4, 'product_id' => 1],
   ['id' => 4, 'basket_id' => 4, 'product_id' => 1],
   ['id' => 5, 'basket_id' => 4, 'product_id' => 1]
]);


$basketItemsCountCredits = new \Illuminate\Support\Collection;
$basketItemsUseCredits   = new \Illuminate\Support\Collection;
$basketItemsPay          = new \Illuminate\Support\Collection;

// A foreach loop is perfectly sane here
foreach ($basketItems as $item) {
   if ($credits->has($item['product_id'])) {
       $basketItemsCountCredits->push($item);
   } else {
       $basketItemsPay->push($item);
   }
});

foreach ($basketItemsCountCredits as $item) {

    $productId = $item['product_id'];
    $remainingCredits = $credits->get($productId);

    if ($remainingCredits > 0) {
        $basketItemsUseCredits->push($item);
        $credits->put($productId, $remainingCredits - 1);
    } else {
        $basketItemsPay->push($item);
    }
}
于 2017-01-26T21:08:57.777 回答