I have some fairly complex routing rules, that are only achievable with custom code, and not with the default router.
The router has also to take into account the domain name.
Is it possible (and how) to define my own Router class, that would have a method accepting a Request and returning the bundle/controller name?
To achieve what you're asking, you don't need to completely redefine the router
You can simply write your own class that implements UrlMatcherInterface. If you want to be able to generate URLs that match your scheme as well, you'll have to create another class which overrides UrlGeneratorInterface as well.
In order to take into account the domain name, you'll need to use RequestContext, which is passed to their constructors (it's not well documented, but check Router::get{Matcher,Generator} for the details).
Once your classes are prepared, you can inject them into the router simply by overriding the parameters "router.options.generator_class" and "router.options.matcher_class" in your bundle.
However, it may not be the best approach for what you want - overriding parts of the router like that requires a lot of care to preserve all of the caching.
You might want to consider using Symfony2's normal router, but handing it different route files depending on what the request's domain is. This can be done easily if you configure your web server to set a variable or execute a different front controller depending on the domain name.
Using that information, you can then load a different 'environment' per-request, with the only difference between the different environments being that they use different routing files.
After studying Matthias Noback's tutorial, I have made a slight modification for my CRUD routing builder.
Before and after using CrudLoader can be seen here in routing/crud/acompetencies.yml
It is just a workaround or misuse of resource as you can see in this CrudLoader class.
I don't know if it is right or bad practice. It seems to work well.
refer sonata admin bundle which is having custom routing class classes
symfony 2.5 requires parameter for custom matcher: router.options.matcher_base_class
and class which implements Symfony\Component\Routing\Matcher\RequestMatcherInterface
Related
As far as I know, currently Yii 2 doesn't have an out of the box method to resolve ambiguity of controller and module names. An example of module hierarchy to describe what exactly I mean:
app\modules\v1\controllers\UserController // resolves the /v1/users and /v1/users/{id} actions
app\modules\v1\modules\user\Module.php // nested module, resolves the /v1/user/... controllers and their actions, e.g. /v1/user/something/{id}
In this case, the UserController conflicts with the user Module. The main reason of the ambiguity is the singular-plural magic of Yii 2 framework. I didn't find an appropriate solution to resolve this ambiguity. Further my ideas how to resolve it.
Rename the module.
Rename the UserController to the UsersController.
Create an additional submodule, and place there the UserController. E.g. app\modules\v1\modules\root\controllers\UserController
I'm not sure that at least one of these options is a quite elegant one and a proper solution in view of the Yii 2 philosophy.
Coming back to the main question, what is a more appropriate approach to resolve this issue by the Yii 2 philosophy? Controller and Module is two different types of objects, which is differently pluralized or not, thus should be right way to separate them in the routing for the described case.
How I usually deal with this depends a bit on how I'm structuring things.
Let's say I have users, departments, orders and maybe some other stuff. All these concepts have their own module in which all interactions take place. If I want to create REST controllers for all these concepts I can choose to add a controller to every module or I can create a dedicated API module for the controllers.
When creating a dedicated module for this purpose I usually name it api, maybe with some nested versioning module(s) inside. So in this situation I would get the following structure app\modules\api\controllers\UserController which would result in the URL /api/user. No ambiguity there and pretty clear what this is meant for.
When adding such a controller to the module itself I choose a better name than just 'UserController'. When asking myself the question 'What does this controller accomplish?/What does it do?' I find that just UserController doesn't cut it; Especially when inside a User module, resulting in /user/user. This controller is probably going to exist alongside one or more different controllers in the User module, all meant for something different. So, usually, I end up naming it just ApiController, resulting in /user/api. Another controller could be the ProfileController. So when looking at the URLs it's pretty clear what /user/api and /user/profile do. Without the ambiguity.
I am not sure I can fully understand the question, but probably you're asking about classname aliases?
use My\Full\Classname as Another;
I'm making a web JSON application, and I would like to make a version router. Like, I could have the path /v0.0/../.. and /v0.1/../..
And I wonder if it is possible not to load every route but just load the route with the good version.
You could check what URL has been requested using $_SERVER global variable before creating routes using $app->get(....
However, this isn't an ideal solution and I think it's not even necessary. The overhead caused by creating routes that aren't used is very small, basically none. Silex uses Dependency Injection (http://pimple.sensiolabs.org/) which makes it very efficient and controllers that aren't used aren't even instantiated.
So I think you don't need to worry about defining more routes that necessary.
In modern web frameworks (Laravel, Symfony, Silex, to name a few), there seems to be a pattern of using a routes.php file or similar to attach URIs to controllers. Laravel makes it a bit easier with an option to use PHP annotations.
But to me all this feels like a bit of a code repetition, and when you are creating/modifying controller logic, you have to keep routes file always at hand. Interestingly, there's a simpler way I saw in several old frameworks, and I used to use this in my old projects as well:
Controller. All classes in src/controllers folder (old way) or all classes in YourApp\Controllers namespace are being automatically mapped to the first part of the URL by adding "Controller" to it. Example: /auth gets mapped to AuthController, /product/... — to ProductController, and / — to default IndexController.
Action. Action is the second part of the URL, and it gets mapped to the method name. So, /auth/login will call AuthController::loginAction() method. If no second part provided, we try indexAction(). Don't want people to access some internal method? Don't make it public.
Parameters. Next parts of the URL are being mapped to the arguments of the method; if there are Application and/or Request type hintings in the argument list, they are skipped so they can be properly injected; we can access GET/POST variables as usual through Request.
Here's the full example, using all these features together:
URL: https://example.com/shop/category/computers?country=US&sort=brand
namespace MyApp\Controllers;
class ShopController extends BaseController {
public function categoryAction(Application $app, Request $req, $category, $subcategory = null) {
echo $category; // computers
echo $subcategory; // null, it's optional here
echo $req->get('country'); // US
echo $req->get('sort'); // brand
}
}
I'm sure it seems to lack some familiar features at first, but all features that I can think of could be easily added if needed — using attachable providers, connecting middlewares, branching controllers to subcontrollers, specifying HTTP methods, and even performing a bit of a pre-validation on the arguments. It's very flexible.
This approach would really speed up the routing creation and management. So besides having all the routes in one file (which is also not always true, considering various providers, using ->mount() in Silex or bundles in Symfony), what are the reasons modern frameworks seem to prefer this way of doing MVC routing over the simpler way I've described? What am I missing?
I'll speak here from Symfony/Silex perspective:
Decoupling. routes.php provide separation of URL mapping and controllers. Do you need to add or change URL? You go straight to routes.php. Very handy if you want to change a lot of them.
Flexibility. Generally, routes.php is a bit more flexible approach. SEO might go crazy and might require you to have route like /shop_{shopName}/{categoryName}/someStaticKeyword/{anotherVar}. With routes.php you can easily map this to your code, but this might become a problem if your routes are directly mapped to the code. Even more, you can have this only controller, you don't need to write controllers for each slash part. You can even have different controllers handling the same URL with different variable parts, for example /blog/[\d]+ or /blog/[a-z-]+ being handled by different controllers (one might generate redirect to another). You might never need to do something like this, but this is just a demonstration of flexibility of this approach - with it anything is possible.
Validation. Another thing to consider is that routes.php provide simple means of validation via ->assert method. That is, routes.php does not only map URLs to controller methods, but also ensure that these URLs match specific requirements, and you don't have to do this in code (which in most scenarios would take a bit more code to write). Also, you can create default asserts for some variables, for instance, you may ensure that {page} or {userId} are always \d+, single line in routes.php that would take care of all usages of {page} or {userId}.
URL Generation. One more feature of routes.php is url generation. You can assign any route any name (via ->bind() method) and then generate URLs based on that name, and providing variables for parts of URL that change. And once we have this system and use URL generator throughout our code we can change URLs as much as we want, but we won't have any need to edit anything but routes.php. And once again - these are flexible names, that you won't have to change everywhere throughout your project once URL has changed and you are not limited choosing the name. It might be much shorter than the URL, or a bit more verbose.
Maintainability. Say, you might want to change some urls (as in example above - from /blog/[\d+] to /blog/[a-z-]+. Also, you might want to keep both of them for some time, and make old one redirect to the new one). With routes.php you simply add a new line and add a todo memo to remove it in some time if you want to remove it later.
Sure, all of this might be achieved with any aproach. But would that be this simple, flexible, transparent and compact as this approach?
Just as note, the standard edition of Symfony is shipped with SensioFrameworkExtraBundle, that allows to use annotations instead of file-based declaration:
/**
* #Route("/")
*/
public function indexAction()
{
// ...
}
In the same manner, you can set the prefix for the whole controller file:
/**
* #Route("/blog")
*/
class PostController extends Controller
{
/**
* #Route("/{id}")
*/
public function showAction($id)
{
}
}
Still, we have to declare it. I think the main reason is that routing requirements are heavily influenced by SEO optimizations. For anything that faces the public you want some keyword-rich URL. The "public" logic organization may also be different, you may have only one controller to deliver all your static pages, still you want those to be /contact, /about...
In some domains, routes can be inferred with conventions. If you create a REST API using FosRestBundle, it will auto-generate routes based on your controllers/actions names on the ressource approach. In general I think the FosRestBundle got a lot of things in their approach, you can easily parse and validate query parameters at the same time:
class FooController extends Controller
{
/**
* This action route will be /foo/articles, GET method only
*
* #QueryParam(name="val", default="75 %%")
*/
public function getArticlesAction(ParamFetcher $paramFetcher)
{
...
}
I always prefer having the routes in a config file rather than using annotations because, firstly, I feel it's more maintainable and easier to have an oversight. It's easier to ensure that you don't have any conflicting routes when you can see them all together.
Additionally, it's theoretically faster. Annotations require reflection, where the application needs to scan the file system and parse each controller to collect the set of routes.
The reason is probably maintainability for the long term. It doesn't matter how the routes are set up but generally, Laravel as a framework focuses on the simplicity and the small things that make the framework great. As long as it is easy to use for the user (web developer in this case), the goal is reached.
You can use a different name for the routes.php file and also use multiple route files, if you have a lot of routes in your application.
You can try and rethink the whole concept to improve it but I don't think that Laravel's routes need really a big change to make it even easier. It's already simple. Maybe it will be changed for the better in the future but for now, I think it's fine.
Currently learning Symfony. Just wondering about route names, which the book doesn't really seem to say in advance what they are for.
As I understand it, #Route annotations are given a default name based on the bundle, controller and action name. So what's the point in specifying your own route names?
Is it good practice to either leave all routes to have a default name?
Is it better practice to specify your own name for every route?
What if two routes have the same name?
How am I expected to know if I'm using a route name which is already taken, perhaps in another bundle?
Route names are important because your code will often need to build url's. The route name is how you specify which url to construct. Names are also handy for listeners (such as authentication) which process specific url's.
Annotation generated default names are fine but tend to be long and could change on you.
Yes, I would give every route a custom name for convenience and readability. Plus, I don't use annotations. To me at least, storing routes in a central file make the code easier to maintain. It avoids the need to search through multiple controller files trying to determine which code handles which request.
Routes with the same name will replace any previously loaded route. It's useful if you wish to override a route from a third party bundle.
Namespace your route by using some form of your bundle name as a prefix.
I have my own routing rules in routes.php, defined for all the pages that should be accessible via URL, such as mywebsite/blog/ and mywebsite/blog/category/category-name, i.e. the structure of my whole website is covered by my custom routes.
Now, I have a lot of elements that make use of requestAction, such as
$websiteabstract = $this -> requestAction(array(
'controller' => 'assets',
'action' => 'displayHomeAbstract'
));
This gives me an error Error: Controller could not be found, probably because I have not defined a route for /assets/displayHomeAbstract. But why do I have to define a custom route for that, when I explicitly state the name of the controller and the action? Shouldn't that bypass the routing altogether?
Either I have not understand Routing at all. Or do I really have to define ALL the possible routes (even those that are only used by requestAction) in my routes.php? I mean, I don't want to allow users to directly access mywebsite/assets/displayHomeAbstract anyway, only via an Element.
Thank you
EDIT: Here is my routes.php http://pastebin.com/aAKBwNZJ
Please have a look at line 128, this is exactly what I do not want since /assets/displayHomeAbstract is ONLY accessed via requestAction.
EDIT: And this is the element, that makes the request: http://pastebin.com/0tK5dYJk
Okay, after extensive discussion with the devs in IRC, I think I understand this well enough to explain to you:
You do have to define your custom routes for your requestAction in this case. requestAction is emulating a full request. It dispatches a request as if accessed using the string url every time, even when the url provided is an array. The book is referring to how when you have a custom route defined in addition to using the default routes (the last line of routes.php), you can use array urls to be agnostic of those routes. However, these array urls rely on the default routes.php in the /lib/ folder and are used to construct a url string. If you're going to have a custom routing pattern, you have to construct the url strings on your own.
Note: the comments below were from earlier versions of this answer.
The key to your problem is understanding the scope of Cake's routing and how it work.
When you define a route in CakePHP, it isn't just used for mapping URLs to controllers. It's also used by the Router for things like generating link addresses and, in your case, mapping the path supplied to requestAction() to a controller. Behind the scenes, Cake is creating a URL string based on your parameters, and then passes it off to the Router to find the correct controller. Since no such route exists, it fails.
As a solution, I would actually recommend not using a controller for that logic. Depending on what it does, a component or a helper may be a better place.
Look at line 156. You commented out the line that loads CakePHP's default routes.