Settings:
Symfony 3
To stay simple, I will refer to a simple AppBundle, with Entity1 and Entity2 as entities.
There are no specific parameter per entity, just an id.
Entity2 is a child of Entity1, which mean that in the SQL diagram, Entity2 have a foreign key entity1_id.
Summary:
I'm trying to build the following route:
/entity1/{id}/entity2/{id}/show
Where the first {id} is the id of Entity1, and the second of Entity2.
My .yml files:
entity1.yml
entity1_index:
path: /
defaults: { _controller: "AppBundle:Entity1:index" }
methods: GET
entity1_show:
path: /{id}/show
defaults: { _controller: "AppBundle:Entity1:show" }
methods: GET
entity1_new:
path: /new
defaults: { _controller: "AppBundle:Entity1:new" }
methods: [GET, POST]
entity1_edit:
path: /{id}/edit
defaults: { _controller: "AppBundle:Entity1:edit" }
methods: [GET, POST]
entity1_delete:
path: /{id}/delete
defaults: { _controller: "AppBundle:Entity1:delete" }
methods: DELETE
# ENTITY2
entity2:
resource: "#AppBundle/Resources/config/entity2.yml"
prefix: /{id}/entity2
entity2.yml
entity2_index:
path: /
defaults: { _controller: "AppBundle:Entity2:index" }
methods: GET
entity2_show:
path: /{id}/show
defaults: { _controller: "AppBundle:Entity2:show" }
methods: GET
entity2_new:
path: /new
defaults: { _controller: "AppBundle:Entity2:new" }
methods: [GET, POST]
entity2_edit:
path: /{id}/edit
defaults: { _controller: "AppBundle:Entity2:edit" }
methods: [GET, POST]
entity2_delete:
path: /{id}/delete
defaults: { _controller: "AppBundle:Entity2:delete" }
methods: DELETE
Problem:
Route pattern cannot reference variable more than once, which is here my problem.
I don't know what to do so that Symfony can differentiate each {id}.
I suggestion you use Route annotations instead. I personally find it much easier to use.
For example, you could do something like this:
/**
* #Route("/editEntity1/{id1}/entity2/{id2}",
* defaults={"id1" = 0,"id2" = 0},
* name="editEntity1Route")
*/
public function editEntity1Action($id1, $id2, Request $request){
...
// Now you can use both id variables like so:
$eName1 = $id1->getName();
$eName2 = $id2->getName();
...
}
I renamed the route 'editEntity1Route' because it explicitly tells you what is does. In this case Edit Enitity 1. Then in you controller you would have other routes for "showEntity1", "newEntity1" etc...
In another controller the easy way to redirect to the above route is like so:
...
$em = $this->getDoctrine()->getManager();
$qb = $em->createQueryBuilder();
$qb->select('e1')
->from('AppBundle:Entity1', 'e1')
->where('e1.e1_name = :e1_name') // Example
->setParameter('e1_name', "sample name");
$entity1 = $qb->getQuery()->setMaxResults(1)->getOneOrNullResult();
...
$entity2 = $qb->getQuery()->setMaxResults(1)->getOneOrNullResult();
return $this->redirectToRoute('editEntity1Route', array(
'id1' => $entity1->getId(),
'id2' => $entity2->getId(),
));
Also, in Twig it is very easy to setup a link to the route:
<a href="{{ path('editEntity1Route',
{'id1':entity1.getId, 'id2':entity2.getId}) }}">Edit Entity1</a>
In the above twig file, this presumes from your controller you've passed in the variables 'entity1' and 'entity2'.
I think this might give you some ideas on how to achieve this if you use routing annotation. You could still use routes in the Yaml files, I just find it more obvious using routing annotations.
Take a look at your entity1.yml
entity2:
resource: "#SalonBundle/Resources/config/dashboard/soushall.yml"
prefix: /{id}/soushall
Try remove these {id}, so the route becomes /entity1/entity2/{id}/show.
Now, your {id} is the id from Entity2. Since Entity2 have a foreign key entity1_id that linked to the id from Entity2, it is enough for the route to just read one id from Entity2.
Hewwo~
Thanks for the details Alvin.
I haven't tried annotation routing yet, I'm not familiar with it yet.
I will keep that post marked, as of now, I already made too many routes to switch to annotation routing.
But I will try for my next project... ;)
Meanwhile, I figured out how to solve my problem.
Thus, here are the details.
Settings: Same as the first post.
Solution:
Rename route variables:
• entity1.yml
entity1_index:
path: /
defaults: { _controller: "AppBundle:Entity1:index" }
methods: GET
entity1_show:
path: /{idEntity1}/show
defaults: { _controller: "AppBundle:Entity1:show" }
methods: GET
entity1_new:
path: /new
defaults: { _controller: "AppBundle:Entity1:new" }
methods: [GET, POST]
entity1_edit:
path: /{idEntity1}/edit
defaults: { _controller: "AppBundle:Entity1:edit" }
methods: [GET, POST]
entity1_delete:
path: /{idEntity1}/delete
defaults: { _controller: "AppBundle:Entity1:delete" }
methods: DELETE
# ENTITY2
entity2:
resource: "#AppBundle/Resources/config/entity2.yml"
prefix: /{idEntity1}/entity2
• entity2.yml
entity2_index:
path: /
defaults: { _controller: "AppBundle:Entity2:index" }
methods: GET
entity2_show:
path: /{idEntity2}/show
defaults: { _controller: "AppBundle:Entity2:show" }
methods: GET
entity2_new:
path: /new
defaults: { _controller: "AppBundle:Entity2:new" }
methods: [GET, POST]
entity2_edit:
path: /{idEntity2}/edit
defaults: { _controller: "AppBundle:Entity2:edit" }
methods: [GET, POST]
entity2_delete:
path: /{idEntity2}/delete
defaults: { _controller: "AppBundle:Entity2:delete" }
methods: DELETE
Rename $id in entities:
• Entity1.php
private $id; to private $idEntity1;
*• Entity2.php
private $id; to private $idEntity2;
Edit the controllers:
[...] indicate that code was skipped, thus not edited.
• Entity1Controller.php
newAction changes:
return $this->redirectToRoute('entity1_show', array('id' => $entity1->getId()));
to
return $this->redirectToRoute('entity1_show', array('idEntity1' => $entity1->getId()));
For each redirectToRoutewe find, we will rename the id variable to match our route/entity variable.
editAction changes:
public function editAction(Request $request, Entity1 $entity1)
{
[...]
if ($editForm->isSubmitted() && $editForm->isValid()) {
[...]
return $this->redirectToRoute('entity1_edit', array('idEntity1' => $entity1->getId()));
}
[...]
}
• Entity2Controller.php:
In every function, we will add our parent entity as variable.
If not done, our routes won't works
indexAction changes:
public function indexAction()
{
[...]
}
to
public function indexAction(Entity1 $entity1)
{
[...]
return $this->render('AppBundle:Default:index.html.twig', array(
'entity1' => $entity1,
'entity2s' => $entity2s,
));
}
newAction changes:
public function newAction(Request $request, Entity1 $entity1)
{
[...]
if ($form->isSubmitted() && $form->isValid()) {
[...]
return $this->redirectToRoute('entity2_show', array(
'idEntity1'=>$entity1->getId(),
'idEntity2'=>$entity2->getId()
));
}
return $this->render('AppBundle:Default:new.html.twig', array(
'entity1' => $entity1,
'entity2' => $entity2,
'form' => $form->createView(),
));
}
showAction changes:
public function showAction(Entity1 $entity1, Entity2 $entity2)
{
$deleteForm = $this->createDeleteForm($entity1, $entity2);
return $this->render('AppBundle:Default:show.html.twig', array(
'entity1' => $entity1,
'entity2' => $entity2,
'delete_form' => $deleteForm->createView(),
));
}
editActionchanges
public function editAction(Request $request, Entity1 $entity1, Entity2 $entity2)
{
$deleteForm = $this->createDeleteForm($entity1, $entity2);
[...]
if ($editForm->isSubmitted() && $editForm->isValid()) {
[...]
return $this->redirectToRoute('entity2_edit', array(
'idEntity1'=>$idEntity1,
'idEntity2'=>$entity2->getId()
));
}
return $this->render('AppBundle:Default:edit.html.twig', array(
'entity1' => $entity1,
'entity2' => $entity2,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
));
}
deleteAction changes:
public function deleteAction(Request $request, Entity1 $entity1, Entity2 $entity2)
{
$form = $this->createDeleteForm($idEntity1, $entity2);
[...]
return $this->redirectToRoute('entity2_index', array('idEntity1' => $entity1->getId()));
}
createDeleteFormchanges
private function createDeleteForm(Entity1 $entity1, Entity2 $entity2)
{
return $this->createFormBuilder()
->setAction($this->generateUrl('entity2_delete', array('idEntity1' => $entity1->getId(), 'idEntity2' => $entity2->getId())))
->setMethod('DELETE')
->getForm()
;
}
This sum up all the changes needed...
As long as you can replace code through a whole project, it's quite fast to do...
Thank you Alvin and david for your replies.
While I didn't ended up using them, I'm sure they will be usefull in the future.
Be it for me or for someone else... :)
This topic is now solved... ^^
I have a route as follows:
blog_post:
path: /{post_id}/{post_title}.{_format}
defaults: { _controller: BlogBundle:Blog:post }
requirements:
post_id: \d+
post_title: "[a-zA-Z0-9-]+"
_format: 'html'
that is matched by the following URL:
www.website.com/32/my-best-story.html
I do not understand why the routing placeholder {post_id} (which is an integer as required in the route) and the related controller argument $post_id do not have the same type:
class BlogController extends Controller
{
public function postAction(Request $request, $post_id, $post_title)
{
$type = gettype($post_id);
var_dump($type);
die();
}
}
returns
string(6) "string"
I would expect it to return:
string(7) "integer"
Where am I wrong? Thanks.
I do not understand why the routing placeholder {post_id} (which is an integer as required in the route) and the related controller argument $post_id do not have the same type
Be careful, the requirement \d+ means digit not integer
There is no concept of typing in routing placeholder.
I'm trying to internationalize my website using Symfony.
This is my routing.yml:
index:
pattern: /{_locale}
defaults: { _controller: AppBundle:Index:index, _locale: en }
requirements:
_locale: en|fr
When the URL is just "/", "en" locale is set automatically, this is great but I want the browser locale.
For exemple, if I'm in France and I type "/", I want redirect to "/fr/", etc.
Can you help me?
you can get client locale and set redirection in controller
$clientLocale = strtolower(str_split($_SERVER['HTTP_ACCEPT_LANGUAGE'], 2)[0]);
switch ($clientLocale) {
case 'fr':
return $this->redirect($this->generateUrl('fr_route'));
break;
default:
return $this->redirect($this->generateUrl('en_route'));
break;
}
Based on b3da answer, I'll apply it only for the index route (as I think that is the main case when someone type your domain without params) and relying on my YML translations:
#routing.yml
index:
path: /
defaults:
_controller: AppBundle:Pub:index
index2:
path: /{_locale}
defaults:
_controller: AppBundle:Pub:index
#PubController.php
public function indexAction($_locale=null, Request $request) {
if (!$_locale) {
$browserLocale = strtolower(str_split($_SERVER['HTTP_ACCEPT_LANGUAGE'], 2)[0]);
return $this->redirect($this->generateUrl('index2', ['_locale' => $browserLocale]));
}
return $this->render('AppBundle::index.html.twig', []);
}
If the local browser is not translated on your app/Resources/translation YMLs then it will render the texts using your fallback locale (app/config/parameters.yml).
I have similar routes, symfony2 call second route(subcategory), if i type somethinkg like "example.com/cars/insert/.
web_portal_category:
path: /{category}/
defaults: { _controller: WebPortalBundle:Default:category }
web_portal_subcategory:
path: /{category}/{subcategory}/
defaults: { _controller: WebPortalBundle:Default:subcategory }
web_portal_insert:
path: /{category}/insert/
defaults: { _controller: WebPortalBundle:Default:upload }
How can I force them to call right one?
You put them in the right order, because as soon as it finds a match, the routing component will stop and execute that action:
web_portal_insert:
path: /{category}/insert/
defaults: { _controller: WebPortalBundle:Default:upload }
web_portal_subcategory:
path: /{category}/{subcategory}/
defaults: { _controller: WebPortalBundle:Default:subcategory }
web_portal_category:
path: /{category}/
defaults: { _controller: WebPortalBundle:Default:category }
That is correct. Symfony reads te routes from top to bottom and takes the first route that matches. In your example the url cars/insert/ matches with the second and the third route. Because the second route has only variables {category} & {subcategory} all the urls x/y/ will match and the third route will never be reached.
if you swap the second and the third route like this than things will change.
web_portal_category:
path: /{category}/
defaults: { _controller: WebPortalBundle:Default:category }
web_portal_insert:
path: /{category}/insert/
defaults: { _controller: WebPortalBundle:Default:upload }
web_portal_subcategory:
path: /{category}/{subcategory}/
defaults: { _controller: WebPortalBundle:Default:subcategory }
In this case only the /x/insert url will match the second route and all others will continue with the third route in the row.
I want to declare both methods GET and POST for a same route in my routing.yml.
According the documentation it's possible with annotations like that :
/**
* #Route("/edit/{id}")
* #Method({"GET", "POST"})
*/
But how to YAML ? I tried different things :
contact_envoi:
pattern: /contact-envoi
defaults: { _controller: AcmeDemoBundle:Default:contactEnvoi }
requirements:
sf_method: ['get','post']
and
...
requirements:
_method: { 'GET', 'POST' }
but it still doesn't work ... Please help, I found nothing in the documentation about that.
Thanks to Touki for his comment, it works!
I had to declare twice the same URL on two separate shares and each with their own method as explained here for Symfony 2.1 and here for Symfony 2.2.
contact:
path: /contact
defaults: { _controller: AcmeDemoBundle:Main:contact }
methods: [GET]
contact_process:
path: /contact
defaults: { _controller: AcmeDemoBundle:Main:contactProcess }
methods: [POST]
You can get the same route with the GET and POST methods.
contact:
path: /contact
defaults: { _controller: AcmeDemoBundle:Main:contact }
methods: ['GET','POST']
Then manage in your controller the method used.
public function contactAction(Request $request)
{
if ('POST' === $request->getMethod()) {
..
}
}
just remove the methods
contact:
path: /contact
defaults: { _controller: AcmeDemoBundle:Main:contact }