Laravel: รับวัตถุจากคอลเลกชันโดยแอตทริบิวต์


92

ใน Laravel ถ้าฉันทำการค้นหา:

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

... จากนั้นก็$foodsคือคอลเลกชัน IlluminateของFoodวัตถุแบบจำลอง (โดยพื้นฐานแล้วคืออาร์เรย์ของโมเดล)

อย่างไรก็ตามคีย์ของอาร์เรย์นี้มีเพียง:

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

... ดังนั้นถ้าฉันต้องการแก้ไขพูดว่าFoodวัตถุที่มีid24 ฉันไม่สามารถทำได้:

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

... เพราะนี่จะเป็นเพียงการเปลี่ยนแปลงองค์ประกอบที่ 25 ในอาร์เรย์ไม่ใช่องค์ประกอบที่มีid24

ฉันจะรับองค์ประกอบเดียว (หรือหลายรายการ) จากคอลเลกชันโดยแอตทริบิวต์ / คอลัมน์ใดก็ได้ (เช่น แต่ไม่ จำกัด เฉพาะ id / color / age / ฯลฯ )

แน่นอนฉันสามารถทำได้:

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

... แต่นั่นเป็นเพียงขั้นต้น

และแน่นอนฉันสามารถทำได้:

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

... แต่นั่นยิ่งแย่กว่าเดิมเพราะมันทำการสืบค้นเพิ่มเติมที่ไม่จำเป็นเมื่อฉันมีวัตถุที่ต้องการใน$foodsคอลเลกชันแล้ว

ขอบคุณล่วงหน้าสำหรับคำแนะนำใด ๆ

แก้ไข:

เพื่อความชัดเจนคุณสามารถเรียก->find()ใช้ Illuminate Collection ได้โดยไม่ต้องสร้างคำถามอื่น แต่ยอมรับเฉพาะ ID หลักเท่านั้น ตัวอย่างเช่น:

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

อย่างไรก็ตามยังไม่มีวิธีที่สะอาด (ไม่วนซ้ำไม่ใช่การสืบค้น) ในการจับองค์ประกอบโดยแอตทริบิวต์จากคอลเล็กชันเช่นนี้:

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

คำตอบ:


128

คุณสามารถใช้filterดังนี้:

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

filterนอกจากนี้ยังจะกลับมาCollectionแต่เนื่องจากคุณรู้ว่าจะมีเพียงคนเดียวที่คุณสามารถเรียกว่าfirstCollection

คุณไม่ต้องการตัวกรองอีกต่อไป (หรืออาจจะเคยฉันไม่รู้ว่านี่เกือบ 4 ปีแล้ว) คุณสามารถใช้first:

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

7
ขอบคุณมาก! ฉันคิดว่าฉันสามารถอยู่กับสิ่งนั้นได้ ในความคิดของฉันยังคงเป็นคำพูดที่ผิดปกติสำหรับสิ่งที่มักจะเป็นกรอบงาน 'Eloquent' ฮ่า ๆ แต่ก็ยังสะอาดกว่าทางเลือกอื่น ๆ มากดังนั้นฉันจะเอาไป
เล้ง

ดังที่ @squaretastic ชี้ให้เห็นในคำตอบอื่น ๆ ภายในการปิดของคุณคุณกำลังทำการมอบหมายไม่ใช่การเปรียบเทียบ (เช่นคุณควร == และไม่ใช่ =)
ElementalStorm

24
จริงๆแล้วไม่จำเป็นต้องโทรหาfilter()->first()คุณก็สามารถโทรได้first(function(...))
lukasgeiter

จากเอกสาร Laravel Collection laravel.com/docs/5.5/collections#method-first collect([1, 2, 3, 4])->first(function ($value, $key) { return $value == 2; });
ชิโระ

2
คุณสามารถทำสิ่งเดียวกันกับฟังก์ชัน where $desired_object = $food->where('id', 24)->first();
ภวินทุมมาร์

111

Laravel มีวิธีการที่เรียกว่าkeyByซึ่งอนุญาตให้ตั้งค่าคีย์ตามคีย์ที่กำหนดในโมเดล

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

จะส่งคืนคอลเล็กชัน แต่มีคีย์เป็นค่าของidแอตทริบิวต์จากรุ่นใด ๆ

จากนั้นคุณสามารถพูดว่า:

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

และจะคว้ารายการที่ถูกต้องโดยไม่ต้องใช้ฟังก์ชันตัวกรองให้วุ่นวาย


2
มีประโยชน์มากโดยเฉพาะอย่างยิ่งสำหรับประสิทธิภาพ -> ครั้งแรก () อาจช้าเมื่อเรียกหลาย ๆ ครั้ง (foreach in foreach ... ) เพื่อให้คุณสามารถ "ดัชนี" คอลเล็กชันของคุณเช่น: $exceptions->keyBy(function ($exception) { return $exception->category_id . ' ' . $exception->manufacturer_id;และใช้->get($category->id . ' ' . $manufacturer->id)หลังจาก!
François Breton

คีย์นี้ยังคงใช้ต่อไปเมื่อมีการเพิ่มรายการใหม่ในคอลเลกชันหรือไม่ หรือฉันจำเป็นต้องใช้ keyBy () ทุกครั้งที่มีการผลักวัตถุหรืออาร์เรย์ใหม่เข้าสู่คอลเลกชัน
Jason

เป็นไปได้มากว่าคุณจะต้องเรียกมันอีกครั้งตั้งแต่keyByส่งคืนคอลเลคชันใหม่จากสิ่งที่ฉันจำได้ แต่ไม่แน่ใจคุณสามารถตรวจสอบIlluminate/Support/Collectionเพื่อหาได้ (ไม่ได้ทำงานใน Laravel มาระยะหนึ่งแล้วเพื่อให้มีคนแก้ไขฉันได้)
Maksym Cierzniak

สิ่งนี้ไม่ได้ผลสำหรับฉันมันส่งคืนรายการอื่นรายการถัดไปถ้าฉันพิมพ์ get (1) มันจะส่งคืนรายการที่มีหมายเลข 2 เป็นรหัส
Jaqueline Passos

การโหลดตารางเป็นชุดและใช้เวลาหนึ่งวัน ใช้วิธีนี้และใช้เวลาไม่กี่นาที
Jed Lynch


7

เนื่องจากฉันไม่จำเป็นต้องวนซ้ำคอลเลกชันทั้งหมดฉันคิดว่าควรมีฟังก์ชันตัวช่วยแบบนี้จะดีกว่า

/**
 * 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

ใช้วิธีการรวบรวมในตัวมีและค้นหาซึ่งจะค้นหาด้วยรหัสหลัก (แทนคีย์อาร์เรย์) ตัวอย่าง:

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

มี () จริงเพียงแค่เรียก find () และตรวจสอบ null ดังนั้นคุณสามารถย่อให้สั้นลงเป็น:

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

เราเข้าใจว่า find () ยอมรับรหัสหลัก สิ่งที่เราต้องการคือวิธีการที่ยอมรับแอตทริบิวต์ใด ๆเช่น "สี" หรือ "อายุ" จนถึงตอนนี้วิธีของ kalley เป็นวิธีเดียวที่ใช้ได้กับแอตทริบิวต์ใด ๆ
เล้ง

5

ฉันรู้ว่าคำถามนี้ถูกถามในตอนแรกก่อนที่ Laravel 5.0 จะเปิดตัว แต่สำหรับ Laravel 5.0 คอลเลกชั่นสนับสนุนwhere()วิธีนี้เพื่อจุดประสงค์นี้

สำหรับ Laravel 5.0, 5.1 และ 5.2 where()วิธีการในCollectionพินัยกรรมจะทำการเปรียบเทียบที่เท่าเทียมกันเท่านั้น นอกจากนี้ยังทำการเปรียบเทียบอย่างเข้มงวดเท่ากับ ( ===) โดยค่าเริ่มต้น หากต้องการทำการเปรียบเทียบแบบหลวม ๆ ( ==) คุณสามารถส่งผ่านfalseเป็นพารามิเตอร์ที่สามหรือใช้whereLoose()วิธีการ

ตั้งแต่ Laravel 5.3 where()เมธอดนี้ถูกขยายให้ทำงานเหมือนกับwhere()เมธอดสำหรับตัวสร้างคิวรีซึ่งยอมรับตัวดำเนินการเป็นพารามิเตอร์ตัวที่สอง เช่นเดียวกับตัวสร้างแบบสอบถามตัวดำเนินการจะเริ่มต้นเป็นการเปรียบเทียบที่เท่าเทียมกันหากไม่มีการระบุ การเปรียบเทียบเริ่มต้นก็เปลี่ยนจากเข้มงวดโดยค่าเริ่มต้นเป็นค่าเริ่มต้นโดยค่าเริ่มต้น ดังนั้นหากคุณต้องการเปรียบเทียบเข้มงวดคุณสามารถใช้whereStrict()หรือเพียงแค่ใช้เป็นผู้ประกอบการสำหรับ===where()

ดังนั้นใน Laravel 5.0 ตัวอย่างโค้ดสุดท้ายในคำถามจะทำงานตรงตามที่ตั้งใจไว้:

$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

ฉันต้องชี้ให้เห็นว่ามีข้อผิดพลาดเล็กน้อย แต่สำคัญอย่างยิ่งในคำตอบของคาลลีย์ ฉันต่อสู้กับสิ่งนี้เป็นเวลาหลายชั่วโมงก่อนที่จะตระหนักว่า:

ภายในฟังก์ชันสิ่งที่คุณส่งกลับมาคือการเปรียบเทียบดังนั้นสิ่งนี้จะถูกต้องมากขึ้น:

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

1
ใช่ขอบคุณที่ชี้ให้เห็น สิ่งสำคัญคือต้องสังเกตว่าฟังก์ชันตัวกรองไม่แตกต่างจากforeach()ตัวอย่างประสิทธิภาพที่ชาญฉลาดของฉันเพราะมันทำวนซ้ำแบบเดียวกัน ... อันที่จริงforeach()ตัวอย่างของฉันทำงานได้ดีกว่าเพราะมันแตกเมื่อพบโมเดลที่ถูกต้อง นอกจากนี้ ... {Collection}->find(24)จะคว้าด้วยคีย์หลักซึ่งทำให้เป็นตัวเลือกที่ดีที่สุดที่นี่ กรอง Kalley $desired_object = $foods->find(24);เสนอเป็นจริงเหมือนกัน
เล้ง

1
ไม่เคยเห็น**==**โอเปอเรเตอร์มันทำอะไร?
kiradotee

@kiradotee ฉันคิดว่า OP กำลังพยายามเน้นตัวดำเนินการเปรียบเทียบที่เท่ากันสองเท่า ( == ) คำตอบเดิมใช้เครื่องหมายเท่ากับเพียงเครื่องหมายเดียวดังนั้นจึงเป็นการมอบหมายงานแทนการเปรียบเทียบ OP พยายามเน้นว่าควรมีสองสัญญาณเท่ากัน
patricus

1

โซลูชันที่หรูหราสำหรับการค้นหาค่า ( http://betamode.de/2013/10/17/laravel-4-eloquent-check-if-there-is-a-model-with-certain-key-value-pair-in -a-collection / ) สามารถปรับเปลี่ยนได้:

$desired_object_key = $food->array_search(24, $food->lists('id'));
if ($desired_object_key !== false) {
   $desired_object = $food[$desired_object_key];
}

0

ดังคำถามข้างต้นเมื่อคุณใช้ where clause คุณต้องใช้ get Or first method เพื่อให้ได้ผลลัพธ์

/**
*Get all food
*
*/

$foods = Food::all();

/**
*Get green food 
*
*/

$green_foods = Food::where('color', 'green')->get();
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.