Fetching Items from a ManyToMany Collection

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

New mission! On the genus show page, I want to list all of the users that are studying this Genus. If you think about the database - which I told you NOT to do, but ignore me for a second - then we want to query for all users that appear in the genus_scientist join table for this Genus.

Well, it turns out this query happens automagically, and the matching users are set into the $genusScientists property. Yea, Doctrine just does it! All we need to do is expose this private property with a getter: public function, getGenusScientists(), then return $this->genusScientists:

... lines 1 - 14
class Genus
{
... lines 17 - 183
public function getGenusScientists()
{
return $this->genusScientists;
}
}

Now, open up the show.html.twig genus template and go straight to the bottom of the list. Let's add a header called Lead Scientists. Next, add a list-group, then start looping over the related users. What I mean is: for genusScientist in genus.genusScientists, then endfor:

... lines 1 - 4
{% block body %}
... lines 6 - 7
<div class="sea-creature-container">
<div class="genus-photo"></div>
<div class="genus-details">
<dl class="genus-details-list">
... lines 12 - 20
<dt>Lead Scientists</dt>
<dd>
<ul class="list-group">
{% for genusScientist in genus.genusScientists %}
... lines 25 - 31
{% endfor %}
</ul>
</dd>
</dl>
</div>
</div>
<div id="js-notes-wrapper"></div>
{% endblock %}
... lines 40 - 57

The genusScientist variable will be a User object, because genusScientists is an array of users. In fact, let's advertise that above the getGenusScientists() method by adding @return ArrayCollection|User[]:

... lines 1 - 14
class Genus
{
... lines 17 - 183
/**
* @return ArrayCollection|User[]
*/
public function getGenusScientists()
{
return $this->genusScientists;
}
}

We know this technically returns an ArrayCollection, but we also know that if we loop over this, each item will be a User object. By adding the |User[], our editor will give us auto-completion when looping. And that, is pretty awesome.

Inside the loop, add an li with some styling:

... lines 1 - 4
{% block body %}
... lines 6 - 7
<div class="sea-creature-container">
<div class="genus-photo"></div>
<div class="genus-details">
<dl class="genus-details-list">
... lines 12 - 20
<dt>Lead Scientists</dt>
<dd>
<ul class="list-group">
{% for genusScientist in genus.genusScientists %}
<li class="list-group-item">
... lines 26 - 30
</li>
{% endfor %}
</ul>
</dd>
</dl>
</div>
</div>
<div id="js-notes-wrapper"></div>
{% endblock %}
... lines 40 - 57

Then add a link. Why a link? Because before this course, I created a handy-dandy user show page:

... lines 1 - 4
use AppBundle\Entity\User;
... lines 6 - 11
class UserController extends Controller
{
... lines 14 - 44
/**
* @Route("/users/{id}", name="user_show")
*/
public function showAction(User $user)
{
return $this->render('user/show.html.twig', array(
'user' => $user
));
}
... lines 54 - 79
}

Copy the user_show route name, then use path(), paste the route, and pass it an id set to genusScientist.id, which we know is a User object. Then, genusScientist.fullName:

... lines 1 - 4
{% block body %}
... lines 6 - 7
<div class="sea-creature-container">
<div class="genus-photo"></div>
<div class="genus-details">
<dl class="genus-details-list">
... lines 12 - 20
<dt>Lead Scientists</dt>
<dd>
<ul class="list-group">
{% for genusScientist in genus.genusScientists %}
<li class="list-group-item">
<a href="{{ path('user_show', {
'id': genusScientist.id
}) }}">
{{ genusScientist.fullName }}
</a>
</li>
{% endfor %}
</ul>
</dd>
</dl>
</div>
</div>
<div id="js-notes-wrapper"></div>
{% endblock %}
... lines 40 - 57

Why fullName? If you look in the User class, I added a method called getFullName(), which puts the firstName and lastName together:

... lines 1 - 15
class User implements UserInterface
{
... lines 18 - 197
public function getFullName()
{
return trim($this->getFirstName().' '.$this->getLastName());
}
}

It's really not that fancy.

Time for a test drive! When we refresh, we get the header, but this Genus doesn't have any scientists. Go back to /genus/new to create a more interesting Genus. Click the link to view it. Boom! How many queries did we need to write to make this work? None! That's right - we are keeping lazy.

But now, click to go check out the user show page. What if we want to do the same thing here? How can we list all of the genuses that are studied by this User? Time to setup the inverse side of this relationship!

Leave a comment!

This course is built on Symfony 3, but most of the concepts apply just fine to newer versions of Symfony.

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
        "knplabs/knp-markdown-bundle": "^1.4", // 1.4.2
        "doctrine/doctrine-migrations-bundle": "^1.1", // 1.1.1
        "stof/doctrine-extensions-bundle": "^1.2" // v1.2.2
    },
    "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
    }
}