andrew
andrew

Reputation: 9583

Check if object with property = value exists in array of objects

I think I could do this with a foreach loop like this:

foreach ($haystack as $item){
   if (isset($item->$needle_field) && $item->$needle_field == $needle){
       return true;
   } 
} 

but I was wondering if it could be done without a loop?

something like:

if(in_array($item->$needle_field == $needle,$haystack){
    return true;
}

Upvotes: 7

Views: 32961

Answers (4)

mickmackusa
mickmackusa

Reputation: 47971

Latest Edit: As of PHP8.4, array_any() is an advantageous functional-style tool because it is designed (for performance) to short circuit upon encountering a true evaluation in the callback. This means that the function is protected from needlessly traversing the entire payload if qualifying data is found. This is effectively the foreach() approach below, but with the elegance of a function call. Demo

$needleField = 'cats';
$needleValue = 2;

var_export(
    array_any(
        $objects,
        fn($obj) => property_exists($obj, $needleField)
            && $obj->$needleField === $needleValue
    )
);

Yes, in modern PHP you can determine if a specific object property contains a specific value without a classic loop by combining the forces of array_column() (which has evolved to also handle arrays of objects) and in_array().

Code: (Demo)

$objects = [
    (object)['cats' => 2],
    (object)['dogs' => 2],
    (object)['fish' => 10],
    (object)['birds' => 1],
];

$needleField = 'cats';
$needleValue = 2;

var_export(
    in_array($needleValue, array_column($objects, $needleField))
);
// output: true

The advantage of this technique is the obviously concise syntax. This is a perfectly acceptable approach for relatively small volumes of data.

A possible disadvantage to this technique is that array_column() will be generating a new array of all of values that relate to the $needleField.

In my above demo, array_column() will only generate a single-element array because there is only one cats property in all of the objects. If we were processing a relatively large volume of data, then it would be inefficient to bother collecting all of the qualifying cats values and then run in_array() when only one match is necessary to return true.

For "large" volumes of data where performance is a primary criterion for script design, a classic foreach loop would be a better choice and as soon as an object satisfies the rules, then the loop should be halted via return or break.

Code: (Demo)

function hasPropertyValue(array $objects, $property, $value): bool {
    foreach ($objects as $object) {
        if (property_exists($object, $property) && $object->{$property} === $value) {
            return true;
        }
    }
    return false;
}
var_export(
    hasPropertyValue($objects, $needleField, $needleValue)
);

Upvotes: 16

Stack Underflow
Stack Underflow

Reputation: 2453

This will not work if the array you are searching is out of your control. But, if you are the one building the array of objects to be searched, you can structure it using the needle as array keys to be used with array_key_exists when you are searching.

For example, instead of making your $haystack array like this:

[
  {
    'needle_field' => $needle
  },
  ...
]

Make it like this:

[
  $needle => {
    'needle_field' => $needle
  },
  ...
]

And search like this:

if (array_key_exists($needle, $haystack)) {
  return true;
}

Finally, if you need to, you can convert back to an integer indexed array by using array_values

$haystack = array_values($haystack);

This may not work in all situations but it worked great for me.

Upvotes: 0

user2555476
user2555476

Reputation:

Maybe with array_key_exists:

if (array_key_exists($needle_field, $haystack) { 
  if ($haystack[$needle_field] == $needle) {
    echo "$needle exists";
  }
}

Upvotes: -1

Waleed Khan
Waleed Khan

Reputation: 11467

It's possible, but it's not any better:

<?php
function make_subject($count, $success) {
    $ret = array();
    for ($i = 0; $i < $count; $i++) {
        $object = new stdClass();
        $object->foo = $success ? $i : null;
        $ret[] = $object;
    }
    return $ret;
}

// Change true to false for failed test.
$subject = make_subject(10, true);

if (sizeof(array_filter($subject, function($value) {
    return $value->foo === 3;
}))) {
    echo 'Value 3 was found!';
} else {
    echo 'Value 3 was not found.';
}

Outputs Value 3 was found!.

I advise you remain with the for loop: it's legible, unlike any tricks to save a line that you might find.

Upvotes: 4

Related Questions