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
Related
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
}
}
I'm trying to achieve this
http://symfony.com/doc/current/cookbook/routing/custom_route_loader.html#more-advanced-loaders
I need the bundle routing to automatically activate itself when the bundle is registered
so I created this file into the path
src/Gabriel\AdminPanelBundle\Routing\AdvancedLoader.php
with the content
<?php
//namespace Acme\DemoBundle\Routing;
namespace Gabriel\AdminPanelBundle\Routing;
use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\Routing\RouteCollection;
class AdvancedLoader extends Loader
{
public function load($resource, $type = null)
{
$collection = new RouteCollection();
$resource = '#GabrielAdminPanelBundle/Resources/config/import_routing.yml';
$type = 'yaml';
$importedRoutes = $this->import($resource, $type);
$collection->addCollection($importedRoutes);
return $collection;
}
public function supports($resource, $type = null)
{
return $type === 'advanced_extra';
}
}
I copied this configuration
gabriel_admin_panel:
resource: "#GabrielAdminPanelBundle/Controller/"
type: annotation
prefix: /superuser
from
/app/config/routing.yml
and pasted it into my own configuration file
/src/Gabriel/AdminPanelBundle/Resources/config/import_routing.yml
The problem:
Symfony2 completely ignores my AdvancedLoader.php file, I can put any
syntax error in it and the site won't even throw an error, also the
router:debug doesn't show the routes that are defined inside of the
bundle unless I move the configuration back into its original router.yml file.
PS: clearing the cache doesn't change anything
Edit: when I add the service and the resource, this error appears
FileLoaderImportCircularReferenceException: Circular reference
detected in "/app/config/routing_dev.yml"
("/app/config/routing_dev.yml" > "/app/config/routing.yml" > "." >
"#GabrielAdminPanelBundle/Controller/" >
"/app/config/routing_dev.yml").
Looks like you could have missed some steps in the process.
First one: did you define the service?
services:
gabriel.routing_loader:
class: Gabriel\AdminPanelBundle\Routing\AdvancedLoader
tags:
- { name: routing.loader }
Note the tag. As the documentation says:
Notice the tag routing.loader. All services with this tag will be
marked as potential route loaders and added as specialized routers to
the DelegatingLoader.
Second but very important because, as the documentation says, if you didn't add this lines your routing loader wouldn't be called:
# app/config/routing.yml
Gabriel_Extra:
resource: .
type: advanced_extra
The important part here is the type key. Its value should be "advanced_extra" in your case. This is the type which your AdvancedLoader supports and this will make sure its load() method gets called. The resource key is insignificant for the AdvancedLoader, so it is set to ".".
I think it will get loaded now.
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...
}
I have set up friendly URLs for a few search result pages, using a custom route for each:
friendly_search_resultpage:
url: /products/my-friendly-alias
param:
module: products
action: search
querystring: searchattribute
querystring2: searchattribute2
This works fine, but when doing the search directly (i.e. browsing to /products/search?querystring=search...) I want to set a <link rel="canonical"> containing the matching friendly URL. This will help Google understand the relation and that there isn't duplicate content.
I put my friendly URL route at the top of routes.yml and hoped for a magic match, but URL parameters aren't recognised in the checking done by symfony. I have dug into sfRoute, with no luck. Is it possible?
I ended up writing a custom routing class to use for these routes, which is executed when url_for() is called. Here is the code:
<?php
class mySearchFriendlyRoute extends sfRoute
{
public function matchesParameters($params, $context = array())
{
// I can't find symfony sorting parameters into order, so I do
// (so that foo=x&bar=y is treated the same as bar=y&foo=x)
ksort($params);
$mine = $this->defaults;
ksort($mine);
if ($params == $mine) {
return true;
}
return false;
}
}
To use, add class: mySearchFriendlyRoute to the routes.yml entry.
Try to use Your own Route:
friendly_search_resultpage:
class: YourRouteClassName
...
And overload sfRoute::generate() there by concrete cannonicalization (return the canonical URL).
This redirects with 301 in my project upon of last sf1.4 revision.
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').