Symfony2: Routing priority - php

is there anyway to prioritize routes in Symfony2?
i'm using annotation it looks like this
Controllers
//TestController.php
/**
* #Route("/test")
*/
class TestController extends Controller
{
/**
* #Route("/a", name="test_a")
*/
.....
//DummyController.php
/**
* #Route("/")
*/
class DummyController extends Controller
{
/**
* #Route("/{varA}/{varB}", name="dummy_one")
*/
.....
Config
//routing.yml
acme_bundle:
resource: "#Acme/Controller"
type: annotation
Goal
URL , Actual , Goal
/test/a , DummyController , TestController //Wrong
/test/b , DummyController , DummyController //Good
How can i force TestController to be tested first ?
Thanks

from your example i can assume that your dummy and test controller are in the same bundle, if this is the case then you just lists the controllers in that bundle individually in your routing.yml. the order you list them is the order they will be checked.
acme_test:
resource: "#Acme/Controller/TestController.php"
type: annotation
acme_dummy:
resource: "#Acme/Controller/DummyController.php"
type: annotation
if they are in different bundles, just list the bundle with the test controller first.
see the symfony routing doc for details. http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/routing.html#activation

So Symfony will consume the controllers in an alphabetical order and will add the routes one by one.
there is no way up to add priority at the moment without using another bundle for that currently version 2.5
https://github.com/symfony-cmf/Routing is a great bundle if you are looking for advanced routing.

You don't need to reorder everything. Just overwrite the rules for DummyController at the desired ranking. You can do this by creating a route to the controller at the end of your routing.yml.
So in your routing.yml as the last lines you will add:
app_dummy:
ressource: "#YourBundle/Controller/DummyController.php
type: annotation

Adding an answer as it might help other users viewing this question. As of Symfony 5.1, a priority can be assigned to each route:
#[Route(path: '/', name: 'home')]
public function home(): Response
{
return $this->render('home.html.twig');
}
#[Route(path: '/about', name: 'about', priority: 10)]
public function about(): Response
{
return $this->render('about.html.twig');
}
In this case (it's just a dummy example), the about route with a priority of 10 will be evaluated before the home route, which has the default priority of 0.
Check out the feature introduction on the Symfony blog.

Related

Symfony with easyadmin - homepage getting redirected to admin page

I am using Symfony 5.0.4 with easyadmin by following this tutorial.
https://symfony.com/doc/master/bundles/EasyAdminBundle/index.html
After installing easyadmin, I tried to get my homepage https://127.0.0.1:8000/.
But it redirects to /admin.
All config files are set by default.
How can I load the homepage without being redirected?
Thank you so much, Alexandre!
The problem was on the global route. I don't know why.
Example:
/**
* #Route("/", name="labs_")
*/
class IndexController extends AbstractController
{
// ...
}
When I moved route inside everything homepage started working.
You should update the config\packages\easy_admin.yaml file and add a link to homepage into the menu:
easy_admin:
design:
menu:
- { route: 'homepage', label: 'Back to the website', icon: 'home'}
Do not forget to replace 'homepage' by the route name of your route page.
You can find the route to your homepage by type:
symfony console debug:route
PS: If the debug:route command is unknown, you should install debug-pack:
symfony composer req debug --dev
Then you have to search in your code the controller corresponding to the homepage route.
Here is an example:
class DefaultController extends AbstractController
{
/**
* #Route("/", name="homepage") <== This is the method called when I wand to display the homepage
*/
public function index(): Response
{
return new Response('This is an example');
}
}
You should find the method called in your application and verify its code to find why it redirects you to the easyadmin route.

Routing order symfony 4

I want to have a url with a wildcard at the end site.com/{username} after trying to match url's like site.com/photos and site.com/blog. I'm using annotations and have two controllers.
I found an answer here Ordering of routes using annotations
But the folder structure is different in version 4 and in the answer they are using YAML while I'm using annotations, so I don't fully understand where to put what.
Video Controller
/**
* #Route("/videos", name="videos")
*/
public function index()
{
// Show video homepage
}
User Controller
/**
* #Route("/{username}", name="profile")
*/
public function index()
{
// Hello {username} !
}
In this order, site.com/videos reaches User controller instead of videos. Do I have to switch to manually putting all URL structures in yaml format or is there a way to set priority in annotations?
So far the only method I found was to create a controller starting with the letter "Z", and putting the action/route in there, that seems to run the route last.
The question you linked to is actually very relevant. You are asking just the same as the other guy:
But how can you do this if you define your routes as annotations in your controllers? Is there any way to specify the ordering of this routes in this case?
In Symfony 4, your config/routes.yaml is probably empty and config/routes/annotations.yaml probably like this:
controllers:
resource: ../../src/Controller/
type: annotation
You can replace the above YAML with this – notice that the order does matter:
Video:
resource: App\Controller\VideoController
type: annotation
User:
resource: App\Controller\UserController
type: annotation
Then site.com/videos reaches the video controller first.
You have to define your routes with yml/xml to be able to fully configure their order.
If you really want to use annotation you could add a prefix like user-:
/**
* #Route("/user-{username}", name="profile")
*/
public function index()
{
// Hello {username} !
}

Catch a dynamic URL from controller in Symfony 4

I'm using Symfony 4 and the way to capture a route and direct it to a method of a controller is as follows:
/**
* #Route("/my/route", name="name_of_route")
*/
public function myMethod() {
/* some logic that render a view */
}
Within my application, I have a shopping cart. Let's suppose a hypothetical example where I have the following nesting of categories:
Solid
└>sand
└>refined sand
└>gross sand
...etc...
if the user click on refined sand, I want to generate the follow URL:
www.myWeb.com/solid/sand/refined-sand
The problem is, how do I capture this URL with annotations since it is dynamic? That is, I can not do the following:
/**
* #Route("/{category}/{subcategory}/{product}", name="name_of_route")
*/
public function myMethod() {
/* some logic that render a view */
}
Because I do not know how many nesting levels the category has.
I hope my question was understood.
I would suggest creating a catch all route, such as:
public function catchAllAction($url)
{
// ...
}
You can then split the $url into segments on the / character, and match the categories against your entities as required.
$categories = explode('/', $url);
As you're using annotations for your routes, you would need to make sure this was the last route registered in your application to prevent it from mistakenly matching other routes. You can do this in the routing.yml config file.
app:
resource: '#AppBundle/Controller/'
type: annotation
catch_all:
path: /{url}
defaults: { _controller: AppBundle:Category:catchAll }
methods: [ GET ]
requirements:
url: '.+'
If possible, I would also suggest prefixing the route to limit the route patterns it might match. E.g. /categories/{url}

symfony 3 routing with annotations + locale listener - how to avoid duplicate "requirements" for every route?

Clean setup of Symfony 3 framework, added locale listener from here:
http://symfony.com/doc/current/cookbook/session/locale_sticky_session.html
Example action code:
/**
* #Route("/foo/")
* #Route("/{_locale}/foo/", name="foo", requirements={"_locale"="en|ru|tk"})
*/
public function fooAction(Request $request)
{
return new Response('true');
}
This syntax has to be repeated for every action. Is there a way to make it less verbose without using a Bundle? I'd like the requirements portion to reside in a config somewhere, if possible.
Ideally, I would like to move the defaults={"_locale"="en"}, requirements={"_locale"="%allowed_locales%"} part inside the Locale Listener, but from what I've tried, it seems the listener is called after the route has been matched, and so that is not possible, which is really a shame.
Yes, check out How to Use Service Container Parameters in your Routes which is linked from The Locale and the URL in the Symfony Translations documentation, and states:
Read How to Use Service Container Parameters in your Routes to learn how to avoid hardcoding the _locale requirement in all your routes.
You can essentially set those routes in a global parameter like so:
# app/config/config.yml
parameters:
app.locales: en|ru|tk
Then in your route annotations do the following:
/**
* #Route("/foo/")
* #Route("/{_locale}/foo/", name="foo", requirements={"_locale"="%app.locales%"})
*/
public function fooAction(Request $request)
{
return new Response('true');
}
I'm not sure why the documentation only shows that for defining routes in YAML / XML / PHP but it should work just the same using annotations.

Symfony2 Controller Route clash

Hey all I have a bit of a problem with root annotations in Symfony2.
I have two different controllers that call methods from the same URL positions /test.
Controller 1:
**
* #Route("/test", service="myProject.test.controller.art")
* #Cache(expires="+5 minutes", public=true)
*/
class BlogController
{
/**
* #Route("/{text}", defaults={"text" = null})
* #Route("/topic/{tag}", defaults={"tag" = null})
* #Method({"GET"})
*/
public function listAction(ArtQuery $query)
{
//.................
}
}
Controller 2:
**
* #Route("/test" , service="myProject.test.controller.sitemap"))
* #Cache(expires="+5 minutes", public=true)
*/
class SitemapController
{
/**
* #Route("/sitemap.xml/")
* #Method({"GET"})
*/
public function sitemapAction()
{
//..................
}
}
The problem is that the second Controller is never matched only if is add in my #route("/sitemap.xml/") but I realy want the route to be only #route("/sitemap.xml").
I think the problem is when i input the url /test/sitemap.xml Symfony treats sitemap.xml as /{text} variable route in first controller.
Can I make a exception so that first controller ends as soon as it hits sitemap.xml....?
I read something about requirements but dont quiet understand this concept
The router will use the first route that matches the path.
The only way to prioritize a route over another which could match is to ensure that the stricter requirements are check before the weaker ones.
Normally this would be accomplished by placing the sitemapAction method above listAction.
However since you have a controller for each of these, you will have to put the controllers in the correct order.
To do this you will need to add the controllers to the config individually like this:
app_sitemap:
resource: "#AppBundle/Controller/SitemapController.php"
type: annotation
prefix: /
app_blog:
resource: "#AppBundle/Controller/BlogController.php"
type: annotation
prefix: /
This way the controllers will be iterated in this order.
However it is better if you can give each route a unique path, perhaps:
#Route("/query/{text}", defaults={"text" = null})
according to documentation
Earlier Routes always Win
What this all means is that the order of the routes is very important.
If the blog_show route were placed above the blog route, the URL
/blog/2 would match blog_show instead of blog since the {slug}
parameter of blog_show has no requirements. By using proper ordering
and clever requirements, you can accomplish just about anything.
http://symfony.com/doc/current/book/routing.html
i suggest to use yml or xml file for routing
or you can make a requirement in your first route
/**
* #Route("/{text}", defaults={"text" = null}, requirements={"text" = "^(?!sitemap\.xml)$"})
* #Route("/topic/{tag}", defaults={"tag" = null})
* #Method({"GET"})
*/
public function listAction(ArtQuery $query)
{
//.................
}

Categories