Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Tricks with ArrayCollection

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 $12.00

Oh man, the project manager just came to me with a new challenge. Showing all the notes below is great, but they want a new section on top to easily see how many notes have been posted during the past 3 months.

Hmm. In showAction(), we need to somehow count all the recent notes for this Genus. We could start with $recentNotes = $genus->getNotes()... but that's everything. Do we need to finally stop being lazy and make a custom query? Not necessarily.

Remember: getNotes() returns an ArrayCollection object and it has some tricks on it - like a method for filtering! Chain a call to the filter() method and pass this an anonymous function with a GenusNote argument. The ArrayCollection will call this function for each item. If we return true, it stays. If we return false, it disappears.

Easy enough! Return $note->getCreatedAt() > new \DateTime('-3 months');:

... lines 1 - 12
class GenusController extends Controller
{
... lines 15 - 57
public function showAction($genusName)
{
... lines 60 - 85
$recentNotes = $genus->getNotes()
->filter(function(GenusNote $note) {
return $note->getCreatedAt() > new \DateTime('-3 months');
});
... lines 90 - 94
}
... lines 96 - 120
}

Next, pass a new recentNoteCount variable into twig that's set to count($recentNotes):

... lines 1 - 12
class GenusController extends Controller
{
... lines 15 - 57
public function showAction($genusName)
{
... lines 60 - 90
return $this->render('genus/show.html.twig', array(
'genus' => $genus,
'recentNoteCount' => count($recentNotes)
));
}
... lines 96 - 120
}

In the template, add a new dt for Recent Notes and a dd with {{ recentNoteCount }}:

... lines 1 - 4
{% block body %}
<h2 class="genus-name">{{ genus.name }}</h2>
<div class="sea-creature-container">
<div class="genus-photo"></div>
<div class="genus-details">
<dl class="genus-details-list">
... lines 12 - 17
<dt>Recent Notes</dt>
<dd>{{ recentNoteCount }}</dd>
</dl>
</div>
</div>
<div id="js-notes-wrapper"></div>
{% endblock %}
... lines 25 - 42

All right - give it a try! Refresh. Six notes - perfect: we clearly have a lot more than six in total.

The ArrayCollection has lots of fun methods on it like this, including contains(), containsKey(), forAll(), map() and other goodies.

Don't Abuse ArrayCollection

Do you see any downsides to this? There's one big one: this queries for all of the notes, even though we don't need them all. If you know you'll only ever have a few notes, no big deal. But if you may have many notes: don't do this - you will feel the performance impact of loading up hundreds of extra objects.

So what's the right way? Finally making a custom query that only returns the GenusNote objects we need. Let's do that next.

Leave a comment!

13
Login or Register to join the conversation

I was afraid you were not going to talk about the right way to do it :p
Loading all the notes in memory, just to count them afterward is really not ideal

Reply

Hey orys

Ryan likes to save the best to the end :D

Reply
Default user avatar
Default user avatar Raphael Schubert | posted 5 years ago

Hello!!

I have a problem... i`m trying select from a field where is stored as array...
sample.. i Have the [ roles ] field in database.

Now i need select all users where roles = "ROLE_USER"
But some of they has: "ROLE_USER", "ROLE_VENDOR", "ROLE_FINANCIAL"

how can i query for all where contains ROLE_USER?

Reply

Hey Raphael!

This is the big disadvantage of using Doctrine's array type (array or json_array). You can't really query on this - it's ultimately just a string in the database. However, you can technically query a "fuzzy" query - but it's a little ugly:


$this->createQueryBuilder('u')
    ->andWhere('u.roles LIKE :roleSearch')
    ->setParameter('roleSearch', '%ROLE_USER%')
    ->getQuery()
    ->execute();

If all you need are simple queries - this works in practice. However, if you need to do more complex query, then you'll may need another solution. In this case, I would create a Role entity (it would basically just have a string "role" property) and make a ManyToMany relationship from User to Role. Your User class would look something like this:


    /**
     * @ORM\ManyToMany(targetEntity="Role")
     */
    private $roleObjects;

    // initialize roleObjects to an ArrayCollection in __construct()

    public function getRoles()
    {
        $roles = [];
        foreach ($this->roleObjects as $roleObject) {
            $roles[] = $roleObject->getRole();
        }

        return $roles;
    }

    // add setter and getter for roleObjects like normal

As you can see, you create a normal ManyToMany relationship, then simply loop over that relationship to return the array of string roles in getRoles() (which is what Symfony's security system wants). Then, you can properly query for exact matches.

Cheers!

Reply
Default user avatar
Default user avatar Raphael Schubert | weaverryan | posted 5 years ago

Yeah.. its really ugly... but it worked... !!! cheers!!!

i`ll try the second way later... it seems to be good!!

Thank you for answering me daily!!! i asked around 10 questions... you`re rock!!!

Reply
Default user avatar
Default user avatar Helene Shaikh | posted 5 years ago

Hi,

I'm wondering why my autocomplete doesn't work when typing $genus->getNotes()->filter in showAction. I also cannot go to the declaration when clicking on getNotes(). It did work in getNotesActions, though.

Reply

Hey Helene Shaikh!

Hmm, so I don't know the answer exactly - I would expect you to get autocomplete there. However, I can tell you a bit about what's going on :). In showAction, we query for the $genus using $em->getRepository(...). If you have the Symfony plugin installed and enabled for this project, it should be smart enough to know that the findOneBy method will return a Genus object. And so, it should auto-complete when you call $genus->getNotes(). The fact that you can't go to the declaration of getNotes() tells me that PhpStorm - for some reason - doesn't realize that $genus is a Genus object. You can always hint it if you want to:


/** @var Genus $genus */
$genus = $em->getRepository('AppBundle:Genus')
    ->findOneBy(['name' => $genusName]);

In getNotesAction, we relied on the automatic query mechanism so that we have getNotesAction(Genus $genus) where we've type-hinted the $genus argument. In this case, thanks to the type-hint, PhpStorm 100% knows that $genus is a Genus object and so you can happy autocompletion.

Cheers!

1 Reply
Default user avatar

Same for me

Reply

Hey Ruben,

Does Ryan's comment below help you? I think "@var Genus $genus" annotation should do the trick. Also, please, ensure you have a proper return annotation for Genus::getNotes() method to get further autocompletion. And I'd recommend to upgrade PhpStorm and its plugins if you don't yet.

Cheers!

Reply
Default user avatar
Default user avatar Maksym Minenko | posted 5 years ago

What is this get('logger') line about? There wasn't any in the previous chapter.

Reply

Yo Maksym,

`$this->get('logger')` or `$this->container->get('logger')`, which is the same, is a service call. Service container returns a Logger object which you can use in order to make some logs in your app. Actually, this course is the 4th in the Symfony 3 track. Please, check the previous one out where we explain some basic things about Service Container and Dependency Injection.

Cheers!

Reply
Default user avatar
Default user avatar Maksym Minenko | Victor | posted 5 years ago

I just wonder how it has appeared miraculously between the chapters...

Reply

Well, some Symfony-related courses imply basic Symfony background.

Reply
Cat in space

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

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.1.*", // v3.1.4
        "doctrine/orm": "^2.5", // v2.7.2
        "doctrine/doctrine-bundle": "^1.6", // 1.6.4
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
        "symfony/swiftmailer-bundle": "^2.3", // v2.3.11
        "symfony/monolog-bundle": "^2.8", // 2.11.1
        "symfony/polyfill-apcu": "^1.0", // v1.2.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.4", // 1.4.2
        "doctrine/doctrine-migrations-bundle": "^1.1" // 1.1.1
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.0.7
        "symfony/phpunit-bridge": "^3.0", // v3.1.3
        "nelmio/alice": "^2.1", // 2.1.4
        "doctrine/doctrine-fixtures-bundle": "^2.3" // 2.3.0
    }
}
userVoice