Course: Mastering Doctrine Relationships in Symfony 3 Tutorial
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
:
... 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 %} |
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.
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!