Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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[]) {} 
like image 435
Yoav Kadosh Avatar asked Dec 24 '13 16:12

Yoav Kadosh


People also ask

Which data types can be declared with Type hinting?

With Type hinting we can specify the expected data type (arrays, objects, interface, etc.) for an argument in a function declaration. This practice can be most advantageous because it results in better code organization and improved error messages.

How many types of arrays are there in PHP?

In PHP, there are three types of arrays: Indexed arrays - Arrays with a numeric index. Associative arrays - Arrays with named keys. Multidimensional arrays - Arrays containing one or more arrays.

Is array an object PHP?

An object is an instance of a class. It is simply a specimen of a class and has memory allocated. Array is the data structure that stores one or more similar type of values in a single name but associative array is different from a simple PHP array. An array which contains string index is called associative array.


Video Answer


2 Answers

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.

like image 60
bishop Avatar answered Oct 19 '22 21:10

bishop


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:

  1. 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).

  2. 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.

  3. 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 
like image 41
Evan Avatar answered Oct 19 '22 19:10

Evan