Advanced routing in Symfony v3 - php

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

Related

Check if route exists in Symfony 4

How can I test whether a given route exists in Symfony 4 by using the route name.
routes.yaml
home:
path: /
controller: App\Controller\Home::index
methods: [GET]
login:
path: /login
controller: App\Controller\Login::index
methods: [GET]
Controller (making up an exists() method here)
$routes->exists('home'); // true
$routes->exists('login'); // true
$routes->exists('foo'); // false
From the Symfony 4 Documentation...
Check if a Route Exists
In highly dynamic applications, it may be necessary to check whether a route exists before using it to generate a URL. In those cases, don't use the getRouteCollection() method because that regenerates the routing cache and slows down the application.
Instead, try to generate the URL and catch the RouteNotFoundException thrown when the route doesn't exist:
use Symfony\Component\Routing\Exception\RouteNotFoundException;
// ...
try {
$url = $generator->generate($dynamicRouteName, $parameters);
} catch (RouteNotFoundException $e) {
// the route is not defined...
}
You can put that code inside a function and return whatever you need.

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}

Symfony2 - 2 routes to same bundle (optional parameter)

I need my website to be accessible with 2 different URL, for example:
/blog => Homepage
/blog/article/1 => Article page (etc.)
/abcde/blog => Homepage
/abcde/blog/article/1 => Article page (etc.)
When "abcde" is in my URL, I need to have the same controller/action running, but being able to get this "abcde" value (a few changes will result from this parameter).
I have found many similar questions but never exactly what I am looking for:
I am not looking for a language/translation routing bundle
"abcde" parameter must be optional
My bundle has several controllers and actions, I don't want to write 2 routes for each. (Neither do I want to have to update anything each time I add a new controller/action.)
Initial app/config/routing.yml
mywebsite_blog:
resource: "#MywebsiteBlogBundle/Resources/config/routing.yml"
prefix: /blog/
If I simply add a second route, as resource is the same, it overwrites the first one.
I tried to add an optional parameter on the initial route:
mywebsite_blog:
resource: "#MywebsiteBlogBundle/Resources/config/routing.yml"
prefix: /{_label}/blog/
defaults: {_label: mylabel}
requirements:
_label: .*
That way I can access /abcde/blog, //blog, but not /blog (404).
Not finding how to resolve my issue simply with using routing.yml (if it is possible I will be happy to learn how and stop there), I read How to Create a custom Route Loader on Symfony doc. I am not sure I'm going to the right direction but I tried it and manage to do a few things, still not exactly what I want.
LabelLoader.php
class LabelLoader extends Loader {
private $loaded = false;
public function load($resource, $type = null) {
if (true === $this->loaded) {
throw new \RuntimeException('Do not add the "label" loader twice');
}
$routes = new RouteCollection();
// Prepare a new route (this is were I tried to play with it)
$path = '/{_label}/blog/';
$defaults = array(
'_controller' => 'MywebsiteBlogBundle:Index:home',
);
$route = new Route($path, $defaults);
// Add the new route to the route collection
$routeName = 'labelRoute';
$routes->add($routeName, $route);
$this->loaded = true;
return $routes;
}
public function supports($resource, $type = null) {
return 'label' === $type;
}
}
That way, /blog and /abcde/blog works the way I want to. I can access the _label variable in my _init() function (I'm using listeners and InitializableControllerInterface, in case it is important to know here), checking if empty or not, etc.
But obviously it works for only one controller/action (Index:home). I would like to change this so that it can works for my whole MywebsiteBlogBundle.
In my custom Loader, I wanted to test something like:
$routes = new RouteCollection();
// Hypotetical function returning all the routes I want to have twice.
// Methods getPath() and getController() called below are hypotetical too.
$mywebsiteBlogRoutes = getExisitingMywebsiteBlogBundleRoutes();
foreach($mywebsiteBlogRoutes as $blogRoute) {
// Prepare a new route
$path = '/{_label}/blog/'.$blogRoute->getPath();
$defaults = array(
'_controller' => $blogRoute->getController(),
);
$route = new Route($path, $defaults);
// Add the new route to the route collection
$routeName = 'labelRoute';
$routes->add($routeName, $route);
}
But:
It does not seem very clean (but if that's the only way I find to do it for now, I'll try it)
I tried to get all my routes reading this answer and as soon as I try $collection = $router->getRouteCollection(); I have a "Circular reference detected" error that I don't manage to fix. I'm trying to understand what's happening here. Edit: I fixed it. Still, I don't think it's a very clean solution.
Should I go back to routing.yml or is it possible to do something here with the custom Routing Loader?
You should try this in your routing.yml:
mywebsite_blog:
resource: "#MywebsiteBlogBundle/Resources/config/routing.yml"
prefix: /{_label}blog/
defaults: {_label: mylabel/}
requirements:
_label: ([\w\d]+/)?
Results:
app/console router:match /mylabel/blog/ # OK
app/console router:match /blog/ # OK

Passing route url format to symfony2 form with get method

Not sure if I properly wrote the subject but anyway.
Since you can create specific routes with different parameters for eg:
_search:
pattern: /page/{category}/{keyword}
defaults: { _controller: Bundle:Default:page, category: 9, keyword: null }
is there any way from a form with GET method to get to that route specific url format?
At the moment the url is like /page?category=2?keyword=some+keyword
As such is not passing to the route format as you may noticed.
What do I need to do to get it working through this specific format? I really have no idea how to rewrite the page url to match the route settings for the specific url. Even in plain php was stumbled on this ...
Thanks in advance.
It's the default behavior of HTML forms with GET method. You will need to build that URL yourself.
Backend way
Drawback: It makes two requests to the server instead of one
Advantage: It's more maintainable because the URL is built using the routing service
Your routing file
_search:
pattern: /page/{category}/{keyword}
defaults: { _controller: Bundle:Default:page, category: 9, keyword: null }
_search_endpoint:
pattern: /page
defaults: { _controller: Bundle:Default:redirect }
Your controller
public function redirectAction()
{
$category = $this->get('request')->query->get('category');
$keyword = $this->get('request')->query->get('keyword');
// You probably want to add some extra check here and there
// do avoid any kind of side effects or bugs.
$url = $this->generateUrl('_search', array(
'category' => $category,
'keyword' => $keyword,
));
return $this->redirect($url);
}
Frontend way
Using Javascript, you can build the URL yourself and redirect the user afterwards.
Drawback: You don't have access to the routing service (although you could use the FOSJsRoutingBundle bundle)
Advantage: You save one request
Note: You will need to get your own query string getter you can find a Stackoverflow thread here, below I'll use getQueryString on the jQuery object.
(function (window, $) {
$('#theFormId').submit(function (event) {
var category, keyword;
event.preventDefault();
// You will want to put some tests here to make
// sure the code behaves the way you are expecting
category = $.getQueryString('category');
keyword = $.getQueryString('keyword');
window.location.href = '/page/' + category + '/' + keyword;
}):
})(window, jQuery);
You could add a second route that will just match /page
then in the controller you can get the defaults. and merge them with any that are passed.
Take a look at a similar question I answered for some code examples.
KendoUI Grid parameters sending to a symfony2 app
I've too encountered this issue and I managed to resolve it with a slightly different solution.
You could also reroute like #Thomas Potaire suggested, but in the same controller, beginning your controller with :
/**
* #Route("/myroute/{myVar}", name="my_route")
*/
public function myAction(Request $request, $myVar = null)
{
if ($request->query->get('myVar') !== null) {
return $this->redirectToRoute('my_route', array(
'myVar' => str_replace(' ','+',$request->query->get('myVar')) // I needed this modification here
));
}
// your code...
}

Custom PHP URL to Controller mapping similar to Symfony2

I wanna make a simple URL to Controller mapping very much like what Symfony2 does. But that's all I want from Symfony2, rest of it is just too much for me.
For those who don't know what Symfony2 does:
blog_home:
pattern: /blog
defaults: { _controller: BlogBundle:Blog:index }
blog_show:
pattern: /blog/{slug}
defaults: { _controller: BlogBundle:Blog:show }
in a YAML config file.
YAML doesn't matter to me at all. I just wanna achieve the same functionality. To be able to map custom URLs to controller functions.
Maybe an open source mapping class or routing framework? Maybe some tutorials? Ideas to make my own? Any suggestions would be helpful.
I should mention I'm no PHP whiz, I know just enough or maybe a little less than enough. Which is why I don't wanna get into a full featured framework.
This is my url routing framework:
function route($url, $map) {
foreach($map as $re => $fn) {
if(preg_match("~^$re$~", $url, $args)) {
list($class, $method) = explode(".", $fn);
return call_user_func_array(
array(new $class, $method),
array_slice($args, 1));
}
}
error_404();
}
The $map is an array whose keys are regular expressions to match the url against and the values are strings "ClassName.method", like
$map = array(
"/blog/(.+)" => "BlogController.show",
"/blog" => "BlogController.blog",
"/foobar/(\d+)/(\w+)" => "Foobar.stuff",
);
The routing function finds the first matching pattern, instantiates a class and calls a method passing regexp subgroups as arguments. So, an url "/foobar/123/hello" will be routed to Foobar->stuff(123, 'hello').

Categories