Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

The iterable Pseudo-Type

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $6.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

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.

Creating an Iterable Object

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!

The iterable Pseudo-type

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.

Leave a comment!

3
Login or Register to join the conversation
Default user avatar
Default user avatar Andrew M. | posted 5 years ago

The comment on the typo is just so good (missed the remainder of the video due to laughing) :D

1 Reply
Default user avatar
Default user avatar DevDonkey | posted 5 years ago | edited

you could also do:


if ($food instanceof \ArrayObject) {
    $food = $food->getArrayCopy();
}

:)

Reply

Hey DevDonkey ,

Yes, we can, nice tip! But what if we have not an array and not an \ArrayObject instance? :) We just covered more cases in this screencast - actually, we covered all available options ;)

Cheers!

1 Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial uses Symfony 3, but all the concepts around PHP 7 are still ?valid.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.3.*", // v3.3.18
        "doctrine/orm": "^2.5", // v2.7.2
        "doctrine/doctrine-bundle": "^1.6", // 1.10.3
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.2
        "symfony/swiftmailer-bundle": "^2.3", // v2.5.4
        "symfony/monolog-bundle": "^2.8", // v2.12.1
        "symfony/polyfill-apcu": "^1.0", // v1.3.0
        "sensio/distribution-bundle": "^5.0", // v5.0.19
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.25
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "knplabs/knp-markdown-bundle": "^1.4", // 1.5.1
        "doctrine/doctrine-migrations-bundle": "^1.1", // v1.2.1
        "stof/doctrine-extensions-bundle": "^1.2", // v1.2.2
        "composer/package-versions-deprecated": "^1.11" // 1.11.99
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.4
        "symfony/phpunit-bridge": "^3.0", // v3.2.8
        "nelmio/alice": "^2.1", // v2.3.1
        "doctrine/doctrine-fixtures-bundle": "^2.3", // v2.4.1
        "symfony/web-server-bundle": "3.3.*"
    }
}
userVoice