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.
Related
I'm new to MVC's (this is my first time using one in a real scenario) and I am a little bit confused about how controllers (should) work.
After reading the Laravel documentation, I came to the conclusion that for general tasks like handling loading the sites (different sites are connected together), pages, admin, etc. I need RESTful controllers.
For the simple tasks it worked. It was easy and very fun to use. I had a Route like this:
Route::controller('admin', 'AdminController');
I had functions like
public function getProduct($id)
and it worked. (It was used to get a specific product: ../admin/product/1)
Now I want something more complex. Like
../sites/loadsite/mysite/mypage/mysubpage/123?myoption=yes
How do I do this? How do I begin, how to approach the problem? Do I have to use Route::get() for every single thing or is there a "nicer" way of doing this?
Laravel provides the "Resource Controllers" helper for generating RESTful routes for corresponding controller methods:
http://laravel.com/docs/controllers#resource-controllers
You can use this to easily create the standard REST routes for a given model, or, as shown in the example provided at laravel.com, you can restrict to only certain routes. The table they provide demonstrates how given paths map to given actions / controller methods.
Regarding the example url you give: ../sites/loadsite/mysite/mypage/mysubpage/123?myoption=yes, I'll break the question into two pieces, the url and query string:
Regarding the URL /sites/loadsite/mysite/mypage/mysubpage/123. This would not be considered by many to be a "RESTful" route. Instead of pages and subpages, you should be thinking in terms of models, and sometimes submodels. It is commonly considered best practice to avoid deeply nested routes, which typically means anything more than a single layer of depth: /model/{id}/submodel/[id]
Regarding the query string at the end of the url: ?myoption=yes: Laravel provides access to query string parameters by using the Input::get("Param") function. You do not have to designate query string params in your routes, they can simply be accessed in your controller method.
The "nicer" way is resource controllers - which of course may be combined with route prefixing and filters (e.g. authentication filters), etc.
Inside the methods of resource controllers, you can retrieve additional input (the option in the query string of your example) and process it with validation and whatever you want.
How can I make a router like this
Route::any("/{controller}/{method}/{param}", "$controller#$method");
So that instead of specifing every single method in the routes file, I would be able to define a route for most cases for the convention http://example.com/controller/method/param
I don't really know why you would want to do this, I think you lose flexibility in the routes file with such approach. I'd rather have things explicitly defined, like so:
Route::get('/users/{id}', 'UserController#show');
Route::post('/users', 'UserController#store');
And, as you can see, different routes, despite being handled by methods belonging to the same controller, might have different amounts and kind of parameters (e.g.: getting a specific user requires sending an ID parameter, but storing a new user doesn't require sending parameters, at least not via the URL).
Besides,
Route::any("/{controller}/{method}{param}" ...
means everything inside {} is a parameter, including {param}.
Seems you want a generic one-liner route. Is it really worth it?
You could use Route::controller, but you'd have to do it for every controller:
Route::controller('my-controller', 'MyController');
This will redirect my-controller/test to MyController#test or my-controller/double-test to MyController#doubleTest.
#Emmanuel Figuerola Yes, it is worth to have the routing convention that most frameworks use out there, because if you need to define any special route, you can just define it without breaking anything and it is something very convenient for the developer, as he does not have to deal with hundreds of route definitions in the route files, which may be confusing, error prone and difficult to maintain.
Laravel becomes really cumbersome by defining a route for every view, for every method in a controller and for every AJAX callback when most of those routes can perfectly fit in the common and know pattern "controller/action/id", keeping simplicity, performance, maintainability and smaller code. I am still struggling to find a way to implement something similar in Laravel but it seems my efforts are in vain.
The Route::controller(); was deprecated as of Laravel 4, if I remember well, in favor of the RESTful controllers.
If you're like me, you think CodeIgniter is pretty nice. You also probably hate typing _model every time you load or call a method or property from your models, because it's ugly and time-consuming.
I've been searching for a solution to this for a couple hours with no luck - so I put together a quick fix.
Take a look at the loader class documentation.
Say you've got a class called page_model, you would typically load and use it like this:
$this->load->model('page_model');
$this->page_model->function();`
If you want to avoid typing _model every time you can do this:
$this->load->model('page_model', 'page');
$this->page->function();
When I first started using CodeIgniter I always did this. Now after using CI for several years and a number of websites, I regret that decision.
It's harder to tell what's going on when looking at the code. Having the _model as part of the code that calls the model function removes any ambiguity. For example, in the above function call is page a library or a model?
This is because CodeIgniter does not support namespaces. While there have been discussions of namespace support in CI for some time, support in the stock codebase is still forthcoming.
The solution? Prefix your controllers instead! In typical usage, you're unlikely to need to type the name of the controller more than once per file.
First edit application/config/routes and add the following line after all the other routes:
$route['(:any)'] = "c_$1";
With this rule, you route the first segment of the URI to the matching controller with your prefix. So that:
http://www.domain.com/fishsticks
maps to the following controller:
c_fishsticks
Next, rename your controller files with this prefix, as well as altering the class names inside to match.
That's it! Now you can name your models with relative freedom. You can rename your models at your leisure, but don't forget that you need to change each model's filename, each model's class name, as well as all references to each model. This is easily the most time-consuming step, but on the plus side you don't have to do it all at once.
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
This is a question with no real problem behind, just a product of my sick mind and drive to make things slightly weird :)
So, I have this PHP application build on top of my own MVC oriented framework (yes, I did my own instead of using existing one). And it is done by the book so we have model (data and database manipulation), view (templates filled with data and rendering output) and controller (handles requests, gets appropriate data from model, puts data into view). Classic and boring scenario with request routing done with .htaccess rules.
Yesterday I did some changes in my code, bug fixes, couple improvements, etc. And I felt strong urge to rearrange the code of controllers. They feel somewhat heavy and bloated and number of methods makes it hard to navigate through the file, and such stuff. I'm sure everybody knows what I'm talking about.
I'm thinking about breaking my controller class into many classes, each one handling only one type of request like login or register or showProfile or killMe.
Now controller class has public methods corresponding to parts of user friendly (or maybe SEO friendly) urls and routing class invokes proper controller and it's method according to url content.
Change I'm thinking about would shift a little routing mechanism into invoking specific controller and it's Execute() method.
For example, for url = "www.example.com/users/login"
now it looks like that:
$controller = new url[0]();
$method = url[1];
echo $controller->$method();
and now url would change to "www.example.com/login" and routing code would look like that:
$controller = new url[0]();
controller->Execute();
I omitted parts where I parse urls and extract routing info from them as it is irrelevant to my question.
What benefits I see in that change?
one dedicated class per one request
smaller files
smaller code
easier maintenance
limited danger of breaking working controller when adding new features(new types of request) or fixing bugs
Disadvantages?
possibly a lot of classes
possible performance hit
???
And my question is about what do you think about that idea and does it make any sense at all. And of course I'm more interested why I shouldn't do it than why I should. So if you can think of any reasons why that would be terrible idea and abomination please speak now before it will be too late :)
EDITED
Clarification of a question:
I'm asking whether I should break my single big controller which handles many types of requests by it's methods into many small controllers each of them handling only single type of request.
Now I have controller Users which handles requests like "login", "showLoginForm", "register", "activate", etc. Refactored code would consist of separate controllers for each of these requests.
A disadvantage I can think of for both the old and new methods is that you are mapping urls directly to class names. If you'd like to change the url you'd have to change the class name. If you'd like to have different urls for different languages, you'll have to add a layer that will map urls to class names anyways.
This is why I'd rather have a routing class that will map urls to class names which provides you a seam to change things.