Expanding on k_smd's suggestion of using a loop, you could easily make this function a tad more generic using an array as a second argument.
I'd also strongly recommend you use type-hints, and doc-blocks to make your code more maintainable. And as ever looking at the coding standards never hurts.
Anyway, here's a slightly more concise version of your function:
/**
* @param \stdClass $car
* @param array $properties
* @return mixed|bool
*/
function getCarPartColor(stdClass $car, array $properties)
{
$defaults = array('allowners', 'allbrands', 'allparts');
//ensure numeric indexes:
$properties = array_values($properties);
$current = $car;//current object to evaluate
for ($i=0, $j=count($properties);$i<$j;++$i)
{
if (isset($current->{$properties[$i]}))//given property exists
$current = $current->{$properties[$i]};
elseif (isset($current->{$defaults[$i]}))//default exists
$current = $current->{$defaults[$i]};
else//none exist, return false
return false;
}
return $current;
}
With minimal effort, you can make this function even more generic (and re-usable). It would suffice to pass the defaults as third argument, and you can use this function as a generic pseudo-recursive getter:
/**
* @param \stdClass $car
* @param array $properties
* @param array $defaults
* @return mixed|bool
* @throws \InvalidArgumentException
*/
function getSubProperty(stdClass $obj, array $properties, array $defaults)
{
//make sure the size of both arrays match
$j = count($properties);
if (!$j || $j > count($defaults))
throw new \InvalidArgumentException(
sprintf(
'Default argument should contain at least %d values, instead saw %d',
$j,
count($defaults)
)
);
//call array_values on array arguments, to ensure numeric indexes
$current = $obj;
for ($i=0, $j=0;$i<$j;++$i)
{
if (isset($current->{$properties[$i]}))//given property exists
$current = $current->{$properties[$i]};
elseif (isset($current->{$defaults[$i]}))//default exists
$current = $current->{$defaults[$i]};
else//none exist, return false
return false;
}
return $current;
}
Of course, you might have spotted a "feature" you might not care for too much. Suppose you pass an object like this:
$foo = (object) array(
'foobar' => (object) array(
'property' => (object) array(
'value' => 123
)
)
);//$foo->foobar->propery->value
You could then call this last function, trying to get $foo->something->property->value
and pass array('foobar', 'foobar', 'foobar')
as the $defaults
array. The result would be that, during the first iteration of the for
loop, the $properties[$i]
value (something
) would not be found, and instead, the $defaults
value (foobar
) will be used. The next iterations would then pan out just fine, and in the end, the function will return $foo->foobar->property->value
.
This, then, is a potential source of mysterious, and difficult to trace bugs. But don't worry. It's a fairly easy thing to fix:
/**
* @param \stdClass $car
* @param array $properties
* @param array $defaults
* @return mixed|bool
* @throws \InvalidArgumentException
*/
function getSubProperty(stdClass $obj, array $properties, array $defaults)
{
//same as above
for ($i=0, $j=0;$i<$j;++$i)
{
if (!isset($current->{$properties[$i]}))
break;//break the loop!
$current = $current->{$properties[$i]};
}
for ($i;$i<$j;++$i)
{//only use defaults from here on end
if (!isset($current->{$defaults[$i]}))
return false;
$current = $current->{$defaults[$i]};
}
return $current;
}
Putting it all together:
Finally, it might be useful to have the ability to switch back and forth between the $properties
and $defaults
provided in some cases. So in an ideal world, you might want to add another argument to your function, that allows the caller to decide what the appropriate course of action is, using something like this:
/**
* @param \stdClass $car
* @param array $properties
* @param array $defaults
* @param bool $switch = false
* @return mixed|bool
* @throws \InvalidArgumentException
*/
function getSubProperty(stdClass $obj, array $properties, array $defaults, $switch = false)
{
$j = count($properties);
if (!$j || $j > count($defaults))
throw new \InvalidArgumentException(
sprintf(
'Default argument should contain at least %d values, instead saw %d',
$j,
count($defaults)
)
);
$current = $obj;
if ($switch === true)
{//switch is true, function can switch back and forth
for ($i=0, $j=0;$i<$j;++$i)
{
if (isset($current->{$properties[$i]}))//given property exists
$current = $current->{$properties[$i]};
elseif (isset($current->{$defaults[$i]}))//default exists
$current = $current->{$defaults[$i]};
else//none exist, return false
return false;
}
return $current;
}
//No switching: if the $properties value can't be found, try defaults, or fail
for ($i=0, $j=0;$i<$j;++$i)
{
if (!isset($current->{$properties[$i]}))
break;//break the loop!
$current = $current->{$properties[$i]};
}
for ($i;$i<$j;++$i)
{
if (!isset($current->{$defaults[$i]}))
return false;
$current = $current->{$defaults[$i]};
}
return $current;
}
isset
is used (that's the whole point of usingisset
first). With the code above there can never be an scenario where PHP throws notices, warning or errors because every single path is tested with isset before any usage. \$\endgroup\$string
? \$\endgroup\$