This is the fourth post in my Missing in PHP7 series. The previous one is about Value Objects.
In this post I’ll outline some problems PHP has with regards to collections, the implications of those problems, and a few mitigation strategies.
PHP arrays
PHPs main collection type is the associative array. These arrays can be used as maps and lists, and form the jack of all trades that is used for nearly all PHP collections.
1 2 |
$oldArray = array( 'pink' => 'fluffy', 'unicorns' ); // PHP <= 5.3 $newArray = [ 'pink' => 'fluffy', 'unicorns' ]; // PHP > 5.3 |
It’s possible to type hint against array, including return types as of PHP7.
1 2 3 4 |
function magicArrayModify( array $inputValues ): array { // This is where the magic happens return $outputValues; } |
Problem 1: lackluster type hinting
There are collections that contain values of multiple types, such as maps containing configuration. Many collections however are meant to hold only a specific type of value. While for scalar values, it is possible to type hint and get all the benefits from static code analysis, the collections, the only thing you can say with code is that they are collections.
Luckily there is a standardized way to specify such types in doc blocks, which is understood by many tools.
1 2 3 4 5 |
/** * @param string[] $inputValues * @return string[] */ function magicArrayModify( array $inputValues ): array {} |
This approach has some drawbacks. Firstly, it’s documentation, which can easily get out of sync with the actual code. Secondly, while it provides much-needed information to static code analysis tools, PHP itself ignores violations of the contracts specified in these docs.
Trick: improved type hinting
As of PHP 5.6, there is a way to type hint against arrays of a specific type with code for parameters. This is done by means of variable-length argument lists.
1 2 3 |
function haveSomeStrings( string ...$inputValues ) {} haveSomeStrings( ...$awesomeStrings ); |
You can easily refactor existing code to follow this approach. The only thing you need to do is add the unpacking operator to the arguments in the calls to the function. When I first tried this out, I was surprised by how many of the callers did not actually pass in a variable length collection. Most of these being in tests. So far, I’ve removed more no longer needed arrays, than having to add the unpacking operator. Example:
1 2 3 4 5 6 7 8 9 |
function testSomething() { haveSomeStrings( [ 'foo', 'bar', 'baz' ] ); // Some assertions } function testSomething() { haveSomeStrings( 'foo', 'bar', 'baz' ); // Some assertions } |
Some people argue you should wrap all collections (be it in PHP or not), which certainly helps with the lack of proper collection type hinting in PHP. This trick is especially nice for the constructors of such wrappers.
The unpacking operator also works for traversables, which makes this approach partially mitigate the next problem.
Problem 2: arrays and Traversable
PHPs Traversable
interface is the base interface for everything that can be looped though. It gets extended by Iterator
and Generator
and is implemented by dozens of classes in PHPs standard library. Iterators are awesome, as I’ve written about before in Lazy iterators in PHP and Python and Some fun with iterators.
Unfortunately it’s not that simple. There is one data structure that is traversable but does not implement Traversable
: PHPs main collection type array
. This means that traversables cannot be given to a function with an array
type hint, and arrays cannot be given to a function with a Traversable
type hint.
1 2 3 4 5 6 7 |
function haveSomeStrings( array $inputValues ) { foreach ( $inputValues as $value ) { echo 'Wow. Such string. Many omg'; } } haveSomeStrings( $stringTraversable ); // Boom! |
This is really silly, as a lot of these functions work with both, without trying, if not for the type hint. The type hint can of course be dropped, though then again we end up with the documentation downside, and also run into the problem that the documentation cannot specify “Traversable of type”.
1 2 3 4 5 6 7 8 |
/** * @param string[]|Traversable $inputValues */ function haveSomeStrings( $inputValues ) { foreach ( $inputValues as $value ) { echo 'Wow. Such string. Many omg'; } } |
I ranted about this before in my Lazy iterators in PHP and Python post.
Edit: I’m very happy that PHP 7.1 has fixed this issue by introducing the iterable pseudo-type.
Additional thoughts
In this post I’ve focused on type hinting, as I felt this is most in line with the goals of this blog post series, and that these problems are both less known and less written about than other Collection features PHP misses. Things such as generics and a better PHP standard collection library are of course very welcome.
Not everyone agrees that type hinting is a wise practice. My personal opinion is that it is generally good, and that it’s extremely bad to not be able to do it at all. This blog post is written from that perspective. If you have a different opinion, feel free to shout at me in a comment.
See also
-
- Ardent – A Collections library for PHP
- Batching Iterator –
- Rewindable Generator –
2 thoughts on “Missing in PHP7: Collections”