Type hinting - specify an array of objects
How can I specify the argument type as an array? Say I have a class named 'Foo':
class Foo {}
and then I have a function that accepts that class type as an argument:
function getFoo(Foo $f) {}
When I pass in an array of 'Foo's I get an error, saying:
Catchable fatal error: Argument 1 passed to getFoo() must be an instance of Foo, array given
Is there a way to overcome this issue? maybe something like
function getFoo(Foo $f[]) {}
If you want to ensure you are working with "Array of Foo" and you want to ensure methods receive "Array of Foo", you can:
class ArrayOfFoo extends \ArrayObject {
public function offsetSet($key, $val) {
if ($val instanceof Foo) {
return parent::offsetSet($key, $val);
}
throw new \InvalidArgumentException('Value must be a Foo');
}
}
then:
function workWithFoo(ArrayOfFoo $foos) {
foreach ($foos as $foo) {
// etc.
}
}
$foos = new ArrayOfFoos();
$foos[] = new Foo();
workWithFoo($foos);
The secret sauce is that you're defining a new "type" of "array of foo", then passing that "type" around using type hinting protection.
The Haldayne library handles the boilerplate for membership requirement checks if you don't want to roll your own:
class ArrayOfFoo extends \Haldayne\Boost\MapOfObjects {
protected function allowed($value) { return $value instanceof Foo; }
}
(Full-disclosure, I'm the author of Haldayne.)
Historical note: the Array Of RFC proposed this feature back in 2014. The RFC was declined with 4 yay and 16 nay. The concept recently reappeared on the internals list, but the complaints have been much the same as levied against the original RFC: adding this check would significantly affect performance.
Old post but variadic functions and array unpacking can be used (with some limitations) to accomplish typed array hinting, at least with PHP7. (I didn't test on earlier versions).
Example:
class Foo {
public function test(){
echo "foo";
}
};
class Bar extends Foo {
//override parent method
public function test(){
echo "bar";
}
}
function test(Foo ...$params){
foreach($params as $param){
$param->test();
}
}
$f = new Foo();
$b = new Bar();
$arrayOfFoo = [$f,$b];
test(...$arrayOfFoo);
//will output "foobar"
The Limitations:
This isn't technically a solution, as you aren't really passing a typed array. Instead, you use the array unpacking operator1 (the "..." in the function call) to convert your array to a list of parameters, each of which must be of the type hinted in the variadic declaration2 (which also employs an ellipsis).
-
The "..." in the function call is absolutely necessary (which isn't surprising, given the above). Trying to call
test($arrayOfFoo)
in the context of the above example will yield a type error, as the compiler expects parameter(s) of foo, not an array. See below for an, albeit hacky, solution to pass in an array of a given type directly, while preserving some type-hinting.
-
Variadic functions may only have one variadic parameter and it must be the last parameter (since otherwise how might the compiler determine where the variadic parameter ends and the next begins) meaning you couldn't declare functions along the lines of
function test(Foo ...$foos, Bar ...$bars){ //... }
or
function test(Foo ...$foos, Bar $bar){ //... }
An Only-Slightly-Better-Than-Just-Checking-Each-Element Alternative:
The following procedure is better than just checking the type of each element insofar as (1) it guarantees the parameters used in the functional body are of the correct type without cluttering the function with type checks, and (2) it throws the usual type exceptions.
Consider:
function alt(Array $foos){
return (function(Foo ...$fooParams){
//treat as regular function body
foreach($fooParams as $foo){
$foo->test();
}
})(...$foos);
}
The idea is define and return the result of an immediately invoked closure that takes care of all the variadic / unpacking business for you. (One could extend the principle further, defining a higher order function that generates functions of this structure, reducing boilerplate). In the above example:
alt($arrayOfFoo) // also outputs "foobar"
The issues with this approach include:
(1) Especially to inexperienced developers, it may be unclear.
(2) It may incur some performance overhead.
(3) It, much like just internally checking the array elements, treats the type checking as an implementational detail, insofar as one must inspect the function declaration (or enjoy type exceptions) to realize that only a specifically typed array is a valid parameter. In an interface or abstract function, the full type hint could not be encoded; all one could do is comment that an implementation of the above sort (or something similar) is expected.
Notes
[1]. In a nutshell: array unpacking renders equivalent
example_function($a,$b,$c);
and
example_function(...[$a,$b,$c]);
[2]. In a nutshell: variadic functions of the form
function example_function(Foo ...$bar){
//...
}
can be validly invoked in any of the following ways:
example_function();
example_function(new Foo());
example_function(new Foo(), new Foo());
example_function(new Foo(), new Foo(), new Foo());
//and so on