I want to talk about yet another PHP 7.1 feature. I know, most of this tutorial is actually about PHP 7.1 features... not PHP 7.0. What can I say? They killed it with 7.1.
To show off this feature, open your Genus
class and find the bottom. Add a fun
new function called feed()
with an array $food
argument. Set the return type
to a string.
I'm going to paste in some code. Basically, we pass an array with some food, and this returns a message. And if we pass no food, our genus looks at us funny...
... lines 1 - 18 | |
class Genus | |
{ | |
... lines 21 - 223 | |
public function feed(array $food): string | |
{ | |
if (count($food) === 0) { | |
return sprintf('%s is looking at you in a funny way', $this->getName()); | |
} | |
return sprintf('%s recently ate: %s', $this->getName(), implode(', ', $food)); | |
} | |
} |
Let's go use this! In showAction()
, create a $food
array set to, how about,
shrimp, clams, lobsters, and a shark! Pass a new recentlyAte
variable into the
template set to $genus->feed($food)
. Then, open the genus/show.html.twig
template,
add a new "Diet" key, and print recentlyAte
.
... lines 1 - 15 | |
class GenusController extends Controller | |
{ | |
... lines 18 - 94 | |
public function showAction(Genus $genus) | |
{ | |
... lines 97 - 107 | |
$food = ['shrimp', 'clams', 'lobsters', 'shark']; | |
... line 109 | |
return $this->render('genus/show.html.twig', array( | |
... lines 111 - 112 | |
'recentNoteCount' => count($recentNotes), | |
'recentlyAte' => $genus->feed($food), | |
)); | |
} | |
... lines 117 - 161 | |
} |
... lines 1 - 4 | |
{% block body %} | |
... lines 6 - 7 | |
<div class="sea-creature-container"> | |
... line 9 | |
<div class="genus-details"> | |
<dl class="genus-details-list"> | |
... lines 12 - 19 | |
<dt>Diet</dt> | |
<dd>{{ recentlyAte }}</dd> | |
... lines 22 - 47 | |
</dl> | |
</div> | |
</div> | |
... lines 51 - 92 | |
{% endblock %} |
Nice! Go back and refresh the show page. There it is! Aurelia recently ate shrimp, clams, lobsters, shark.
Now, here's the challenge. Imagine that this feed()
function is part of a re-usable
library that we're creating, and we want to make it as flexible as possible. Right
now, we are requiring the $foods
argument be an array. But... is that necessary?
Really, if you passed me anything that I could loop over, we could make this function
work.
Let's try it! In GenusController
, rename the $food
variable to $foodArray
.
Then, add $food = new \ArrayObject()
and pass it $foodArray
.
... lines 1 - 15 | |
class GenusController extends Controller | |
{ | |
... lines 18 - 94 | |
public function showAction(Genus $genus) | |
{ | |
... lines 97 - 107 | |
$foodArray = ['shrimp', 'clams', 'lobsters', 'shark']; | |
$foodObject = new \ArrayObject($foodArray); | |
... lines 110 - 117 | |
} | |
... lines 119 - 163 | |
} |
If you're not familiar with ArrayObject
, it's a PHP core object, but it looks
and acts like an array. Most importantly, you can foreach over it. So in theory,
our feed()
function should be able to use this, right?
If you refresh... huge error!
Argument one passed to feed() must be an array, object given
Of course: we're requiring an array with the type-hint. Well... that's kind of lame.
Change this to iterable
: a new pseudo-type - like array
- from PHP 7.1. This
is perfect when all you care about is that an argument can be used in foreach
.
... lines 1 - 18 | |
class Genus | |
{ | |
... lines 21 - 223 | |
public function feed(iterable $food): string | |
... lines 225 - 231 | |
} |
Notice, PHPStorm doesn't like this at all. My version of PHPStorm still doesn't think
that iterable
exists. But, it is valid, and this will probably, hopefully be
fixed soon.
Of course, if you refresh now, a new error!
Warning, implode, invalid arguments passed
Our function now allows an array or any iterable object. But... the second
argument to implode()
must be an array. Remember, when you type-hint with iterable,
the only thing you know is that you can foreach over that value. It's not even
legal to use count()
like this!
If I want this to be more flexible, we need to do some refactoring. Create a new
variable called $foodItems
set to an empty array. Then, foreach over $food as $foodItem
.
This is legal! Inside, put each item into the $foodItems
array.
... lines 1 - 18 | |
class Genus | |
{ | |
... lines 21 - 223 | |
public function feed(iterable $food): string | |
{ | |
$foodItems = []; | |
... line 227 | |
foreach ($food as $foodItem) { | |
$foodItems[] = $foodItem; | |
} | |
... lines 231 - 236 | |
} | |
} |
Finally, update the count to use $foodItems
and the same for implode()
.
... lines 1 - 18 | |
class Genus | |
{ | |
... lines 21 - 223 | |
public function feed(iterable $food): string | |
{ | |
... lines 226 - 231 | |
if (count($foodItems) === 0) { | |
... line 233 | |
} | |
... line 235 | |
return sprintf('%s recently ate: %s', $this->getName(), implode(', ', $foodItems)); | |
} | |
} |
And just like that, this function can accept any value that we can loop over.
Now, I wouldn't necessarily do this in my code unless I needed it. If you're always going to pass an array, just type-hint with array! But, you will start seeing this more and more in libraries that you use.