Laravel: Lấy đối tượng từ bộ sưu tập theo thuộc tính


91

Trong Laravel, nếu tôi thực hiện một truy vấn:

$foods = Food::where(...)->get();

... sau đó $foodslà một Bộ sưu tập Illuminate của Foodcác đối tượng mô hình. (Về cơ bản là một loạt các mô hình.)

Tuy nhiên, các khóa của mảng này chỉ đơn giản là:

[0, 1, 2, 3, ...]

... vì vậy nếu tôi muốn thay đổi Foodđối tượng có idsố là 24, tôi không thể thực hiện điều này:

$desired_object = $foods->get(24);
$desired_object->color = 'Green';
$desired_object->save();

... bởi vì điều này sẽ chỉ thay đổi phần tử thứ 25 trong mảng, không phải phần tử có idsố 24.

Làm cách nào để lấy một (hoặc nhiều) phần tử từ một bộ sưu tập theo BẤT KỲ thuộc tính / cột nào (chẳng hạn như, nhưng không giới hạn ở, id / color / age / v.v.)?

Tất nhiên, tôi có thể làm điều này:

foreach ($foods as $food) {
    if ($food->id == 24) {
        $desired_object = $food;
        break;
    }
}
$desired_object->color = 'Green';
$desired_object->save();

... nhưng, điều đó thật thô thiển.

Và, tất nhiên, tôi có thể làm điều này:

$desired_object = Food::find(24);
$desired_object->color = 'Green';
$desired_object->save();

... nhưng điều đó thậm chí còn thô thiển hơn , vì nó thực hiện thêm một truy vấn không cần thiết khi tôi đã có đối tượng mong muốn trong $foodsbộ sưu tập.

Cảm ơn trước cho bất kỳ hướng dẫn.

BIÊN TẬP:

Để rõ ràng hơn, bạn có thể gọi ->find()một Bộ sưu tập Illuminate mà không tạo ra một truy vấn khác, nhưng nó chỉ chấp nhận một ID chính. Ví dụ:

$foods = Food::all();
$desired_food = $foods->find(21);  // Grab the food with an ID of 21

Tuy nhiên, vẫn không có cách nào rõ ràng (không lặp lại, không truy vấn) để lấy (các) phần tử theo thuộc tính từ Bộ sưu tập, như sau:

$foods = Food::all();
$green_foods = $foods->where('color', 'green'); // This won't work.  :(

Câu trả lời:


126

Bạn có thể sử dụng filter, như vậy:

$desired_object = $food->filter(function($item) {
    return $item->id == 24;
})->first();

filtercũng sẽ trả về a Collection, nhưng vì bạn biết sẽ chỉ có một, bạn có thể gọi firstvào đó Collection.

Bạn không cần bộ lọc nữa (hoặc có thể là bao giờ, tôi không biết điều này đã gần 4 năm tuổi). Bạn chỉ có thể sử dụng first:

$desired_object = $food->first(function($item) {
    return $item->id == 24;
});

7
Này cảm ơn nhé! Tôi nghĩ rằng tôi có thể sống với điều đó. Theo ý kiến ​​của tôi vẫn còn dài dòng một cách bất thường đối với những gì thường là một khuôn khổ 'Hùng biện' như vậy haha. Nhưng nó vẫn sạch hơn nhiều so với các lựa chọn thay thế cho đến nay, vì vậy tôi sẽ lấy nó.
Leng

Như @squaretastic được chỉ ra trong câu trả lời khác, bên trong đóng cửa của bạn, bạn đang làm cho một assignement và không phải là một so sánh (tức là bạn nên == và không =)
ElementalStorm

24
Trên thực tế, thậm chí không cần thiết để gọi, filter()->first()bạn chỉ có thể gọifirst(function(...))
lukasgeiter

từ tài liệu Bộ sưu tập Laravel. laravel.com/docs/5.5/collections#method-first collect([1, 2, 3, 4])->first(function ($value, $key) { return $value == 2; });
Shiro

2
Bạn có thể làm điều tương tự với hàm where. $desired_object = $food->where('id', 24)->first();
Bhavin Thummar

111

Laravel cung cấp một phương thức được gọi là phương thức keyBycho phép thiết lập các khóa bằng khóa cho trước trong mô hình.

$collection = $collection->keyBy('id');

sẽ trả về bộ sưu tập nhưng với các khóa là giá trị của idthuộc tính từ bất kỳ mô hình nào.

Sau đó, bạn có thể nói:

$desired_food = $foods->get(21); // Grab the food with an ID of 21

và nó sẽ lấy đúng mục mà không cần sử dụng chức năng lọc.


2
Thực sự hữu ích, đặc biệt là đối với hiệu suất, -> first () có thể chậm khi được gọi nhiều lần (foreach trong foreach ...) để bạn có thể "lập chỉ mục" bộ sưu tập của mình như: $exceptions->keyBy(function ($exception) { return $exception->category_id . ' ' . $exception->manufacturer_id;và sử dụng ->get($category->id . ' ' . $manufacturer->id)after!
François Breton

Khóa này có tiếp tục được sử dụng khi các mục mới được thêm vào bộ sưu tập không? Hay tôi cần sử dụng keyBy () mỗi khi một đối tượng hoặc mảng mới được đẩy vào bộ sưu tập?
Jason

Rất có thể bạn phải gọi lại nó vì keyBytrả lại bộ sưu tập mới từ những gì tôi nhớ, tuy nhiên, không chắc chắn, bạn có thể kiểm tra Illuminate/Support/Collectionđể tìm ra. (Không làm việc trong Laravel trong một thời gian khá dài nên ai đó có thể sửa cho tôi).
Maksym Cierzniak

Điều này không hiệu quả với tôi, nó trả về một mục khác, mục tiếp theo, nếu tôi nhập get (1), nó sẽ trả về mục có số 2 là id.
Jaqueline Passos

Hàng loạt tải một bảng và mất một ngày. Đã sử dụng giải pháp này và mất vài phút.
Jed Lynch

23

Từ Laravel 5.5, bạn có thể sử dụng firstWhere ()

Trong trường hợp của bạn:

$green_foods = $foods->firstWhere('color', 'green');

3
Điều này sẽ là câu trả lời chấp nhận sau Laravel 5,5
beerwin

7

Vì tôi không cần lặp lại toàn bộ bộ sưu tập, tôi nghĩ tốt hơn nên có chức năng trợ giúp như thế này

/**
 * Check if there is a item in a collection by given key and value
 * @param Illuminate\Support\Collection $collection collection in which search is to be made
 * @param string $key name of key to be checked
 * @param string $value value of key to be checkied
 * @return boolean|object false if not found, object if it is found
 */
function findInCollection(Illuminate\Support\Collection $collection, $key, $value) {
    foreach ($collection as $item) {
        if (isset($item->$key) && $item->$key == $value) {
            return $item;
        }
    }
    return FALSE;
}

7

Sử dụng các phương thức thu thập tích hợp sẵn chứatìm kiếm , phương thức này sẽ tìm kiếm theo id chính (thay vì khóa mảng). Thí dụ:

if ($model->collection->contains($primaryId)) {
    var_dump($model->collection->find($primaryId);
}

Hàm chứa () thực ra chỉ là lệnh gọi find () và kiểm tra giá trị null, vì vậy bạn có thể rút gọn nó thành:

if ($myModel = $model->collection->find($primaryId)) {
    var_dump($myModel);
}

Chúng tôi hiểu rằng find () chấp nhận một ID chính. Những gì chúng tôi muốn là một phương thức chấp nhận bất kỳ thuộc tính nào , chẳng hạn như "màu" hoặc "tuổi". Cho đến nay, phương pháp của kalley là phương pháp duy nhất hoạt động cho bất kỳ thuộc tính nào.
Leng

5

Tôi biết câu hỏi này ban đầu được hỏi trước khi Laravel 5.0 được phát hành, nhưng kể từ Laravel 5.0, Bộ sưu tập hỗ trợ where()phương pháp cho mục đích này.

Đối với Laravel 5.0, 5.1 và 5.2, where()phương thức trên Collectionsẽ chỉ thực hiện một phép so sánh ngang bằng. Ngoài ra, nó thực hiện một so sánh bằng nghiêm ngặt ( ===) theo mặc định. Để thực hiện so sánh lỏng lẻo ( ==), bạn có thể chuyển falselàm tham số thứ ba hoặc sử dụng whereLoose()phương thức.

Kể từ Laravel 5.3, where()phương thức này đã được mở rộng để hoạt động giống như where()phương thức dành cho trình tạo truy vấn, phương thức này chấp nhận một toán tử làm tham số thứ hai. Cũng giống như trình tạo truy vấn, toán tử sẽ mặc định là so sánh bằng nếu không cung cấp. So sánh mặc định cũng được chuyển từ nghiêm ngặt theo mặc định sang lỏng lẻo theo mặc định. Vì vậy, nếu bạn muốn một so sánh chặt chẽ, bạn có thể sử dụng whereStrict()hoặc chỉ sử dụng ===làm toán tử cho where().

Do đó, kể từ Laravel 5.0, ví dụ mã cuối cùng trong câu hỏi sẽ hoạt động chính xác như dự định:

$foods = Food::all();
$green_foods = $foods->where('color', 'green'); // This will work.  :)

// This will only work in Laravel 5.3+
$cheap_foods = $foods->where('price', '<', 5);

// Assuming "quantity" is an integer...
// This will not match any records in 5.0, 5.1, 5.2 due to the default strict comparison.
// This will match records just fine in 5.3+ due to the default loose comparison.
$dozen_foods = $foods->where('quantity', '12');

3

Tôi phải chỉ ra rằng có một lỗi nhỏ nhưng hoàn toàn CHÍNH XÁC trong câu trả lời của kalley. Tôi đã vật lộn với điều này trong vài giờ trước khi nhận ra:

Bên trong hàm, những gì bạn đang trả về là một so sánh và do đó, một cái gì đó như thế này sẽ chính xác hơn:

$desired_object = $food->filter(function($item) {
    return ($item->id **==** 24);
})->first();

1
Vâng, cảm ơn vì đã chỉ ra điều này. Điều quan trọng cần lưu ý là hàm bộ lọc không khác gì so với foreach()ví dụ về hiệu suất của tôi , bởi vì nó chỉ thực hiện cùng một loại vòng lặp ... trên thực tế, foreach()ví dụ của tôi hoạt động tốt hơn vì nó bị hỏng khi tìm đúng mô hình. Ngoài ra ... {Collection}->find(24)sẽ lấy bằng khóa chính, điều này làm cho nó trở thành tùy chọn tốt nhất ở đây. Bộ lọc Kalley đề xuất thực sự giống hệt với $desired_object = $foods->find(24);.
Leng

1
Chưa bao giờ nhìn thấy người **==**điều hành, nó làm gì?
kiradotee

@kiradotee Tôi nghĩ OP chỉ đang cố gắng nhấn mạnh toán tử so sánh bằng kép ( == ). Câu trả lời ban đầu chỉ sử dụng một dấu bằng, vì vậy nó đang thực hiện một bài tập thay vì so sánh. OP đang cố gắng nhấn mạnh rằng phải có hai dấu hiệu bằng nhau.
patricus


0

Như câu hỏi trên khi bạn đang sử dụng mệnh đề where bạn cũng cần sử dụng phương thức get Or first để lấy kết quả.

/**
*Get all food
*
*/

$foods = Food::all();

/**
*Get green food 
*
*/

$green_foods = Food::where('color', 'green')->get();
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.