Buy Access to Course
05.

Controller Magic: Param Conversion

Share this awesome video!

|

Keep on Learning!

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

Login Subscribe

Time to finally make these genus notes dynamic! Woo!

Remember, those are loaded by a ReactJS app, and that makes an AJAX call to an API endpoint in GenusController. Here it is: getNotesAction():

// ... lines 1 - 12
class GenusController extends Controller
{
// ... lines 15 - 90
/**
* @Route("/genus/{genusName}/notes", name="genus_show_notes")
* @Method("GET")
*/
public function getNotesAction($genusName)
{
// ... lines 97 - 106
}
}

Step 1: use the genusName argument to query for a Genus object. But you guys already know how to do that: get the entity manager, get the Genus repository, and then call a method on it - like findOneBy():

// ... lines 1 - 12
class GenusController extends Controller
{
// ... lines 15 - 54
/**
* @Route("/genus/{genusName}", name="genus_show")
*/
public function showAction($genusName)
{
$em = $this->getDoctrine()->getManager();
$genus = $em->getRepository('AppBundle:Genus')
->findOneBy(['name' => $genusName]);
// ... lines 64 - 88
}
// ... lines 90 - 107
}

Old news.

Let's do something much cooler. First, change {genusName} in the route to {name}, but don't ask why yet. Just trust me:

// ... lines 1 - 12
class GenusController extends Controller
{
// ... lines 15 - 90
/**
* @Route("/genus/{name}/notes", name="genus_show_notes")
* @Method("GET")
*/
public function getNotesAction(Genus $genus)
{
// ... lines 97 - 107
}
}

This doesn't change the URL to this page... but it does break all the links we have to this route.

To fix those, go to the terminal and search for the route name:

git grep genus_show_notes

Oh cool! It's only used in one spot. Open show.html.twig and find it at the bottom. Just change the key from genusName to name:

40 lines | app/Resources/views/genus/show.html.twig
// ... lines 1 - 23
{% block javascripts %}
// ... lines 25 - 30
<script type="text/babel">
var notesUrl = '{{ path('genus_show_notes', {'name': genus.name}) }}';
// ... lines 33 - 37
</script>
{% endblock %}

Using Param Conversion

So... doing all of this didn't change anything. So why did I make us do all that? Let me show you. You might expect me to add a $name argument. But don't! Instead, type-hint the argument with the Genus class and then add $genus:

// ... lines 1 - 12
class GenusController extends Controller
{
// ... lines 15 - 90
/**
* @Route("/genus/{name}/notes", name="genus_show_notes")
* @Method("GET")
*/
public function getNotesAction(Genus $genus)
{
// ... lines 97 - 107
}
}

What? I just violated one of the cardinal rules of routing: that every argument must match the name of a routing wildcard. The truth is, if you type-hint an argument with an entity class - like Genus - Symfony will automatically query for it. This works as long as the wildcard has the same name as a property on Genus. That's why we changed {genusName} to {name}. Btw, this is called "param conversion".

Tip

Param Conversion comes from the SensioFrameworkExtraBundle.

Dump the $genus to prove it's working:

// ... lines 1 - 12
class GenusController extends Controller
{
// ... lines 15 - 94
public function getNotesAction(Genus $genus)
{
dump($genus);
// ... lines 98 - 107
}
}

Go back and refresh! We don't see the dump because it's actually an AJAX call - one that happens automatically each second.

Seeing the Profiler for an AJAX Request

But don't worry! Go to /_profiler to see a list of the most recent requests, including AJAX requests. Select one of these: this is the profiler for that AJAX call, and in the Debug panel... there's the dump. It's alive!

So be lazy: setup your routes with a wildcard that matches a property name and use a type-hint to activate param conversion. If a genus can't be found for this page, it'll automatically 404. And if you can't use param conversion because you need to run a custom query: cool - just get the entity manager and query like normal. Use the shortcut when it helps!