Catch a dynamic URL from controller in Symfony 4 - php

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}

Related

Advanced routing in Symfony v3

I'm after a custom routing method so I can have below links to be handled by single controller action:
/q-query_term
/category-category_name/city-city_name/q-query_term
/city-city_name/category-category_name/q-query_term
/city-city_name/q-query_term
/category-category_name/q-query_term
/city-city_name
/category-category_name
Is it possible even possible? I don't use SensioExtraBundle so routes has to be written down in yaml.
For instance in Zend Framework 2 it is possible quite easily, because I can write a class, which would handle the routing as a wish. Don't know how to achieve the same in Symfony though, any help would be appreciated.
The only way I can come up with is to create a custom route loader, calculate all route permutations and return that collection, but don't know if it the best solution possible.
This question is unique, because I need to use single route name instead of specyfing tens of different route definitions.
A simple solution would be to define the route as catch-all …
# Resources/config/routing.yml
catch_all:
path: /{path}
defaults: { _controller: FooBarBundle:Catchall:dispatcher, path : "" }
requirements:
path: ".*"
… and do all further processing in the controller:
# Controller/CatchallController.php
class CatchallController extends Controller
{
public function dispatcherAction(Request $request, string $path)
{
if (/* path matches pattern 1 */)
return $this->doQuery($request, $path);
elseif (/* path matches pattern 2 */)
return $this->doCategoryCityQuery($request, $path);
// ... and so on
else
return new Response("Page not found", 404);
}
private function doQuery($request, $path)
{
// do stuff and return a Response object
}
private function doCategoryCityQuery($request, $path)
{
// do stuff and return a Response object
}
}

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} !
}

Symfony routing: how to redirect from UUID to custom slug

I have an entity that is reachable both using http://example.com/company/d9c363ae-a1b7-11e6-a66d-9e9923e30d94/ and http://example.com/company/custom-domain.com.
Now, the Company can have a slug, but can also not have one.
So, I'd like to check if it has a domain set and if it has and if the URL is based on UUID, I want to redirect it to the version with the slug. If it hasn't a domain set, I simply show the page using the UUID.
I've done this following this Symfony's tutorial:
# routing.yml
redirect_company_to_domain:
path: /company/{domain_host_or_id}
defaults: { _controller: AppBundle:Company:redirectToDomain }
requirements:
url: .*/$
methods: [GET]
And in my controller I have write this method:
/**
* Redirect the URLs with a trailing slash to the version without it.
*
* #param Request $request
*
* #return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function redirectToDomainAction(Company $company, Request $request)
{
die(dump($company));
}
The problem is that the controller is never called.
What am I missing or doing wrong? Why the path is never intercepted by the route?
Maybe because I've mixed annotations and configuration in routing.yml? Because the routes of the CompanyController is all set using annotations.
But using app/console debug:router I can see correctly the route set:
redirect_company_to_domain GET ANY ANY /company/{domain_host_or_id}
Also, I'm sure the method described in the tutorial works as I've implemented it to remove trailing slashes.
I don't understand why it isn't working to make this redirect.
I think because you've specified the parameter (in brackets) incorrectly.
Can you try this instead:
redirect_company_to_domain:
path: /company/{domain_host_or_id}
defaults: { _controller: AppBundle:Company:redirectToDomain }
requirements:
domain_host_or_id: .*/$
methods: [GET]
I think that's the problem, but I'm not 100% certain. Can you try it out and let us know?

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)
{
//.................
}

Symfony2: Routing priority

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.

Categories