2017-01-26 4 views
1

У меня есть следующие две коллекции:Laravel 5,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_id 1.

Теперь я хочу создать две новые коллекции. Если у пользователя есть элементы корзины, для которых у них есть доступные кредиты, это можно определить с помощью product_id, затем я хочу добавить эти элементы в новую коллекцию под названием $basketItemsUseCredits.

Если у корзинных предметов нет доступных кредитов на тип продукта, я хочу добавить их в другую коллекцию под названием $basketItemsPay.

Итак, в приведенном выше примере мне должно быть $basketItemsUseCredits с элементами корзины, которые имеют идентификаторы 1,3 и 4. $basketItemsPay должны заканчиваться на basketsitems с идентификаторами 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 
} 

Любая помощь оценивается.

* UPDATE *

удалось исправить, выполнив следующие действия - если ни один не знает лучшее решение:

$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); 
}); 
+0

У вас есть хороший повод, чтобы не реализовать 'quantity' ключ в каждом вложенном массиве внутри' $ basketItems' массив, как вы делали с '$ credits'? Я думаю, что это упростит ситуацию. Не могли бы вы также более подробно рассказать, как это не работает? Как результат вывода этого кода и как он отличается от желаемого результата? –

+0

@Jeffrey есть ключ количества в массиве $ basketItems, я просто не показывал его, чтобы все было просто. Следующая строка всегда возвращает 0: '$ credits-> где ('product_id', $ basketItem-> product_id) -> get ('quantity', 0);' – adam78

+0

Хорошо, но чтобы быть понятным, каждый отдельный '$ basketItem 'не имеет ключа' количество', поэтому один и тот же идентификатор продукта потенциально происходит более одного раза. Я постараюсь ответить, может потребоваться некоторое время, хотя :) –

ответ

0

Понадобилось время, чтобы понять это. Ниже я подхожу к этому. Это немного более линейный подход и немного более понятный, чем блокирование вложенности в методах коллекции. Это также немного больше кода, чем ваш пример, но это вопрос личных предпочтений. Я старался как можно более подробно описать комментарии к коду.

Самая важная часть в создании вещи проще для нас заключается в преобразовании структуры $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); 
    } 
} 

There are a lot of pretty powerful methods available на ILLUMINATE коллекций. При правильном использовании они могут обеспечить очень чистый и сжатый код!

Но эти же методы могут также вернуть вас обратно в отношении удобочитаемости кода и -компроксимации при использовании там, где это не необходимо.Методы, в которых вы передали функцию обратного вызова, в некоторых случаях в основном полезны, когда определение этого обратного вызова выполняется в другом месте, и оно полагается на этот контекст для выполнения своей работы. Из вашего примера, похоже, это не так.

Вместо использования методов filter и reject, как и в приведенном выше примере, вы можете использовать старый добрый цикл foreach для выполнения обоих действий сразу. Однако это требует, чтобы переменные $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); 
    } 
} 
Смежные вопросы