อาร์กิวเมนต์ไม่ถูกต้องที่ให้มาสำหรับ foreach()


304

มันมักจะเกิดขึ้นกับฉันในการจัดการข้อมูลที่สามารถเป็นได้ทั้งอาเรย์หรือตัวแปรโมฆะและฟีดบางอย่างforeachกับข้อมูลเหล่านี้

$values = get_values();

foreach ($values as $value){
  ...
}

เมื่อคุณป้อนข้อมูล foreach ด้วยข้อมูลที่ไม่ใช่อาร์เรย์คุณจะได้รับคำเตือน:

คำเตือน: มีการระบุอาร์กิวเมนต์ที่ไม่ถูกต้องสำหรับ foreach () ใน [... ]

สมมติว่ามันเป็นไปไม่ได้ที่จะ refactor get_values()ฟังก์ชั่นเพื่อส่งกลับอาร์เรย์เสมอ (ความเข้ากันได้แบบย้อนหลังไม่มีซอร์สโค้ดไม่ว่าด้วยเหตุผลใดก็ตาม) ฉันสงสัยว่าวิธีไหนที่สะอาดและมีประสิทธิภาพที่สุดเพื่อหลีกเลี่ยงคำเตือนเหล่านี้:

  • กำลังส่ง$valuesไปยังอาร์เรย์
  • กำลังเริ่มต้น$valuesกับอาร์เรย์
  • ห่อforeachด้วยif
  • อื่น ๆ (โปรดแนะนำ)

เป็นไปได้สูงที่$valuesไม่ใช่อาเรย์
Bhargav Nanekalva

คำตอบ:


509

โดยส่วนตัวฉันคิดว่านี่เป็นสิ่งที่สะอาดที่สุด - ไม่แน่ใจว่ามันมีประสิทธิภาพมากที่สุดหรือไม่!

if (is_array($values) || is_object($values))
{
    foreach ($values as $value)
    {
        ...
    }
}

เหตุผลสำหรับการตั้งค่าของฉันคือมันไม่ได้จัดสรรอาเรย์ที่ว่างเปล่าเมื่อคุณไม่มีอะไรจะเริ่มด้วย


4
หรือใช้ count () เพื่อค้นหาว่าอาร์เรย์ไม่ว่างเปล่าหรือไม่
Kemo

76
@Kemo: count()ไม่น่าเชื่อถือ ถ้าคุณผ่านcount()ค่า null มันจะคืนค่า 0 หากคุณผ่านอาร์กิวเมนต์ที่ไม่ใช่ค่า null และไม่ใช่อาร์เรย์จะส่งคืนค่า 1 ดังนั้นจึงเป็นไปไม่ได้ที่จะใช้count()เพื่อพิจารณาว่าตัวแปรนั้นเป็นอาร์เรย์หรือไม่เมื่อตัวแปรนั้นอาจเป็นอาร์เรย์ว่างหรือ อาร์เรย์ที่มี 1 รายการ
Andy Shellam

12
โปรดทราบว่าวัตถุบางอย่างสามารถทำซ้ำได้และคำตอบนี้ไม่ได้คำนึงถึงวัตถุเหล่านั้น
แบรดโคช์

32
if (is_array($values) || $values instanceof Traversable)ควรจะเป็น
Bob Stein

3
มันเป็นความอัปยศที่เขาไม่ได้พูดต่อไปว่ามันไม่ได้มีประสิทธิภาพมากที่สุด แต่: D
Gui Prá

116

แล้วอันนี้ละ? ทำความสะอาดมากและทั้งหมดในบรรทัดเดียว

foreach ((array) $items as $item) {
 // ...
 }

7
นี่เป็นสิ่งเดียวที่ทำงานให้ฉัน ด้วยเหตุผลบางอย่าง PHP ไม่เชื่อว่าอาร์เรย์หลายมิติที่ฉันสร้างขึ้นจริง ๆ แล้วเป็นอาร์เรย์ของอาร์เรย์
Justin

1
เช่นเดียวกันนี่คือการแก้ไขที่ดีมากสำหรับอาร์เรย์ที่มีทั้งอาร์เรย์หรือค่า Null เพียงเพิ่มการทดสอบในลูป foreach เพื่อดำเนินการต่อหากข้อมูลเป็นโมฆะ
Lizardx

แก้ไขปัญหาของฉัน ขอบคุณ!
Hitesh

1
รหัสที่ยอดเยี่ยมข้ามถ้าอื่น ๆ สำหรับอาร์เรย์และค่าอาร์เรย์ที่ไม่มีการใช้ $ _POST พร้อมช่องทำเครื่องหมาย!
Yann Chabot

2
หมายเหตุ: ในขณะที่รูปลักษณ์ที่สวยงามและแก้ไขคำเตือน foreach ที่ไม่ถูกต้องวิธีนี้จะส่งกลับคำเตือนตัวแปรที่ไม่ได้กำหนดหากตัวแปรนั้นไม่ได้ถูกตั้งค่าไว้ แต่อย่างใด ใช้งานisset()หรือis_array()หรือทั้งสองอย่างขึ้นอยู่กับสถานการณ์ของคุณเป็นต้น
James

42

ฉันมักจะใช้โครงสร้างคล้ายกับนี้:

/**
 * Determine if a variable is iterable. i.e. can be used to loop over.
 *
 * @return bool
 */
function is_iterable($var)
{
    return $var !== null 
        && (is_array($var) 
            || $var instanceof Traversable 
            || $var instanceof Iterator 
            || $var instanceof IteratorAggregate
            );
}

$values = get_values();

if (is_iterable($values))
{
    foreach ($values as $value)
    {
        // do stuff...
    }
}

โปรดทราบว่ารุ่นนี้ไม่ได้ทดสอบมันพิมพ์ลงใน SO โดยตรงจากหน่วยความจำ

แก้ไข:เพิ่มทะลุตรวจสอบ


3
คำตอบที่ดีที่สุด $var instanceof Traversableยกเว้นฉันคิดว่าคุณควรตรวจสอบว่า ดูที่นี่ เพราะตัวอย่างเช่นคุณสามารถใช้SimpleXMLElementได้ แต่ไม่ใช่อินสแตนซ์ของ Iterator หรือ IteratorAggregate
Bob Stein

2
คุณสามารถลบอีกสองคลาสที่ @Kris พวกเขาทั้งสองขยายTraversable ได้ในตอนนี้และดูเหมือนว่าจะเกิดมาใน 5.0.0 แม้ว่าฉันจะรู้สึกสงสัยเล็กน้อยว่าจะใช้กับการขยายได้หรือไม่
Bob Stein

1
@ BobStein-VisiBone: ใช่ (ยกเว้นเป็นอินเตอร์เฟสไม่ใช่คลาส) อย่างไรก็ตาม; ฉันวาง Traversable ไว้ก่อนหน้านั้นทั้ง Iterator และ IteratorAggregate ไม่จำเป็นต้องตรวจสอบ (วิธีนี้จะไม่ทำให้การดำเนินการช้าลง) ฉันทิ้งไว้เพื่อรักษาคำตอบให้ใกล้เคียงที่สุดกับคำตอบเดิมที่ฉันให้ไว้และเพื่อให้ชัดเจน / อ่านได้
กริช

2
ฉันคิดว่ามันยุติธรรมที่จะเพิ่มis_object($var)ใหม่ php.net/manual/en/language.oop5.iterations.php
Mark Fox

1
@ MarkFox: รู้สึกฟรี แต่ฉันตั้งใจทิ้งมันไว้ ฉันไม่เคยเห็นการใช้งานสำหรับสิ่งที่ไม่ได้รับการปฏิบัติที่ดีขึ้นจากการใช้งานIteratorหรือIteratorAggregateแต่นั่นเป็นเพียงความคิดเห็นของฉันและเป็นอัตนัย
กริช

15

โปรดอย่าพึ่งพาการส่งเป็นวิธีการแก้แม้ว่าผู้อื่นจะแนะนำว่านี่เป็นตัวเลือกที่ถูกต้องเพื่อป้องกันข้อผิดพลาด แต่อาจทำให้เกิดข้อผิดพลาดอีกอย่างหนึ่งได้

ระวัง:ถ้าคุณคาดหวังว่าจะได้อาร์เรย์ในรูปแบบเฉพาะกลับมาสิ่งนี้อาจทำให้คุณล้มเหลว ต้องการการตรวจสอบเพิ่มเติมสำหรับสิ่งนั้น

เช่นการหล่อแบบบูลไปยังอาร์เรย์(array)boolจะไม่ส่งผลในอาร์เรย์ว่างเปล่า แต่อาร์เรย์ที่มีองค์ประกอบหนึ่งที่มีค่าบูลีนเป็น int: หรือ[0=>0][0=>1]

ฉันเขียนข้อสอบด่วนเพื่อนำเสนอปัญหานี้ (นี่คือการทดสอบสำรองในกรณีที่ URL การทดสอบแรกล้มเหลว)

รวมการทดสอบสำหรับ: null, false, trueเป็นclassนักและarrayundefined


ทดสอบอินพุตของคุณก่อนใช้ใน foreach ทุกครั้ง ข้อเสนอแนะ:

  1. การตรวจสอบประเภทอย่างรวดเร็ว :$array = is_array($var) or is_object($var) ? $var : [] ;
  2. พิมพ์ Array hintingในวิธีการก่อนที่จะใช้ foreach และระบุประเภทการคืนสินค้า
  3. ล้อมหน้าไว้ด้วยถ้า
  4. ใช้try{}catch(){}บล็อก
  5. การออกแบบรหัส / การทดสอบที่เหมาะสมก่อนการผลิตออกมา
  6. ในการทดสอบอาร์เรย์กับรูปแบบที่เหมาะสมที่คุณสามารถใช้array_key_existsคีย์ที่เฉพาะเจาะจงหรือทดสอบความลึกของอาร์เรย์ (เมื่อมันเป็นหนึ่ง!)
  7. แยกวิธีผู้ช่วยเหลือของคุณลงในเนมสเปซส่วนกลางเสมอเพื่อลดรหัสที่ซ้ำกัน

8

ลองสิ่งนี้:

//Force array
$dataArr = is_array($dataArr) ? $dataArr : array($dataArr);
foreach ($dataArr as $val) {
  echo $val;
}

;)


1
วิธีนี้ใช้ไม่ได้กับอาร์เรย์ที่เชื่อมโยงกันได้วิธีการ is_array นั้นโดยรวมดีกว่า ... และง่ายขึ้น ...
AO_

4
$values = get_values();

foreach ((array) $values as $value){
  ...
}

ปัญหาเป็นโมฆะเสมอและการหล่อนั้นเป็นวิธีการแก้ปัญหาการทำความสะอาด


3

ก่อนอื่นตัวแปรทุกตัวจะต้องเริ่มต้น เสมอ.
การคัดเลือกนักแสดงไม่ใช่ตัวเลือก
ถ้า get_values ​​(); สามารถกลับตัวแปรประเภทที่แตกต่างกันค่านี้จะต้องตรวจสอบแน่นอน


แคสติ้งเป็นตัวเลือก - ถ้าคุณเริ่มต้นอาร์เรย์โดยใช้$array = (array)null;คุณจะได้รับอาร์เรย์ว่าง แน่นอนว่ามันเป็นการสูญเสียการจัดสรรหน่วยความจำ ;-)
Andy Shellam

2
+1: อ่านจากมุมมองที่มีอารมณ์อ่อนไหวฉันไม่สนใจว่าภาษาจะสามารถทำได้หรือไม่ต้องประกาศตัวแปรและผลลัพธ์ที่ไม่น่าเชื่อถือต้องตรวจสอบ จำเป็นต้องมีสตินักพัฒนาและบันทึกข้อผิดพลาดสั้น ๆ
กริช

3

ส่วนขยายที่กระชับยิ่งขึ้นของรหัสของ@ Kris

function secure_iterable($var)
{
    return is_iterable($var) ? $var : array();
}

foreach (secure_iterable($values) as $value)
{
     //do stuff...
}

โดยเฉพาะอย่างยิ่งสำหรับการใช้โค้ดเทมเพลต

<?php foreach (secure_iterable($values) as $value): ?>
    ...
<?php endforeach; ?>

2
คุณไม่หมายถึงreturn is_iterable($var) ? $var : array($var);เหรอ
SQB

3

หากคุณใช้ php7 และคุณต้องการจัดการข้อผิดพลาดที่ไม่ได้กำหนดเท่านั้นนี่คือ IMHO ที่สะอาดที่สุด

$array = [1,2,3,4];
foreach ( $array ?? [] as $item ) {
  echo $item;
}

2
foreach ($arr ? $arr : [] as $elem) {
    // Does something 
}

นี่ไม่ได้ตรวจสอบว่าเป็นอาร์เรย์หรือไม่ แต่ข้ามการวนลูปถ้าตัวแปรนั้นเป็นโมฆะหรืออาร์เรย์ว่างเปล่า


1

ฉันไม่แน่ใจว่าเป็นกรณีนี้หรือไม่ แต่ปัญหานี้ดูเหมือนจะเกิดขึ้นหลายครั้งเมื่อย้ายข้อมูลเว็บไซต์ wordpress หรือย้ายเว็บไซต์ไดนามิกโดยทั่วไป หากเป็นกรณีนี้ตรวจสอบให้แน่ใจว่าโฮสติ้งที่คุณกำลังโยกย้ายไปใช้เป็นเวอร์ชัน PHP เดียวกันกับที่ไซต์เก่าของคุณใช้

หากคุณไม่ได้ย้ายเว็บไซต์ของคุณและนี่เป็นเพียงปัญหาที่เกิดขึ้นแล้วลองอัปเดตเป็น PHP 5 ซึ่งจะดูแลปัญหาเหล่านี้บางอย่าง อาจดูเหมือนเป็นวิธีโง่ ๆ แต่หลอกลวงให้ฉัน


1

กรณีพิเศษสำหรับการแจ้งเตือนนี้เกิดขึ้นหากคุณตั้งค่าอาร์เรย์เป็นโมฆะภายในลูป foreach

if (is_array($values))
{
    foreach ($values as $value)
    {
        $values = null;//WARNING!!!
    }
}


1

คำเตือนข้อโต้แย้งที่ไม่ถูกต้องให้มาสำหรับการforeach()แสดงทวีต /wp-content/plugins/display-tweets-phpไปที่ จากนั้นใส่รหัสนี้ในหมายเลขบรรทัด 591 มันจะทำงานได้อย่างสมบูรณ์

if (is_array($tweets)) {
    foreach ($tweets as $tweet) 
    {
        ...
    }
}

! น่ากลัว นั่นควรเป็นทางออกที่ยอมรับได้ ในกรณีของฉันฉันได้เพิ่มสิ่งนี้:if (is_array($_POST['auto'])){ // code }
Jodyshop

0

ดูเหมือนว่าจะมีความสัมพันธ์กับสภาพแวดล้อม:

ฉันมีข้อผิดพลาด "อาร์กิวเมนต์ไม่ถูกต้องมา foreach ()" เฉพาะในสภาพแวดล้อม dev แต่ไม่ใช่ใน prod (ฉันทำงานบนเซิร์ฟเวอร์ไม่ใช่ localhost)

แม้จะมีข้อผิดพลาด var_dump ระบุว่ามีอาร์เรย์ที่ดี (ในทั้งสองกรณีแอปและ dev)

if (is_array($array))รอบforeach ($array as $subarray)แก้ปัญหา

ขออภัยที่ฉันไม่สามารถอธิบายสาเหตุได้ แต่เนื่องจากฉันใช้เวลาพอสมควรในการหาวิธีแก้ปัญหาฉันจึงคิดว่าการแบ่งปันสิ่งนี้ดีขึ้นเป็นการสังเกต



0

สิ่งที่เกี่ยวกับการกำหนดอาร์เรย์ว่างเปล่าเป็นทางเลือกถ้าget_value()ว่างเปล่า?
ฉันไม่สามารถคิดถึงวิธีที่สั้นที่สุด

$values = get_values() ?: [];

foreach ($values as $value){
  ...
}

0

ฉันจะใช้การรวมกันของempty, issetและis_arrayเป็น

$array = ['dog', 'cat', 'lion'];

if (!empty($array) && isset($array) && is_array($array) {
    //loop
    foreach ($array as $values) {
        echo $values; 
    }
}

-3

ฉันจะทำสิ่งเดียวกันกับแอนดี้ แต่ฉันจะใช้ฟังก์ชั่น 'ว่าง'

ชอบมาก:

if(empty($yourArray))
{echo"<p>There's nothing in the array.....</p>";}
else
{
foreach ($yourArray as $current_array_item)
  {
    //do something with the current array item here
  } 
}

3
-1, ถ้า$yourArray = 1;มันจะพยายามย้ำและคุณจะได้รับข้อผิดพลาด empty()ไม่ใช่การทดสอบที่เหมาะสม
แบรดโคช์

@BradKoch ถูกต้องอย่างแน่นอน is_array () เป็นวิธีเดียวที่เชื่อถือได้ในการทดสอบว่า $ yourArray เป็นอาร์เรย์หรือไม่ ดูคำตอบอื่น ๆ สำหรับรายละเอียดว่าทำไม is_array () ไม่เพียงพอ - foreach สามารถจัดการตัววนซ้ำได้เช่นกัน
cgeisel
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.