Laravel 8 route order from bottom to top - laravel

I have installed Laravel 8 and work perfectly, and then i tried to learn about routing and try to make some routes like this
Route::view('testing', 'welcome')->name('testingWelcome');
Route::get('testing',[TestingController::class, 'noParameter'])->name('testingNoParam');
Route::view('testing', 'dashboard')->name('testingDashboard');
Some post in here said that routes in web.php work from top to bottom. But, thats not what i get when i called in url http://localhost/laraps/public/testing. it always called the bottom one. i tried to change the order, but still the last one always get called.
Any explanation for this one? or am i made any wrong configuration?
thanks for any help

A short explanation for this would be that each call to Route::{verb} creates a new route entry under your route collection (relevant code). {verb} ban be any HTTP verb e.g. get, or post etc. This entry is created under an array entry [{verb}][domain/url].
This means that when a new route is registered that matches the same URL with the same method it will overwrite the old one.
So in the case
Route::view('testing', 'welcome')->name('testingWelcome');
Route::get('testing',[TestingController::class, 'noParameter'])->name('testingNoParam');
Route::view('testing', 'dashboard')->name('testingDashboard');
Only the 3rd declaration actually "sticks". There are cases where multiple route definitions can match the same URL for example assume you have these routes:
Route::view('testing', 'welcome')->name('testingWelcome');
Route::get('testing/{optionalParameter?}',[TestingController::class, 'parameter'])->name('testingNoParam');
Route::view('testing/{otherParameter?}', 'dashboard')->name('testingDashboard');
In this case all 3 routes are added to the route collection, however when accessing URL example.com/testing the first matched route will be the one that will be called in this case the welcome view. This is because since all 3 routes are declared, once the router finds one matching route, it stops looking for more matches.
Note: There's generally no point in declaring multiple routes with the exact same URL so this is mainly an academic exercise. However there is often a use case for cases like e.g. model/{id} and model/list` to differentiate between getting info for a specific model and getting a list of models. In this case it's important to declare the routes as:
Route::get('model/list', [ ModelController::class, 'list' ]);
Route::get('model/{id}', [ ModelController::class, 'view' ]);
However you can be more explicit in route declarations using:
Route::get('model/{id}', [ ModelController::class, 'view' ])->where('id',
'\d+');
Route::get('model/list', [ ModelController::class, 'list' ]);
in this case the order does not matter because Laravel knows id can only be a number and therefore will not match model/list

Related

Laravel. Conflict when building routes

There are two routes:
Route::get('/{article:slug}', [ArticleController::class, 'showArticlePage']);
and
Route::get('/{user:nickname}', [ProfileInfoController::class, 'getUserByNickname']);
Is there any way for each of the routes to perform its function?You can't change uri
For example:
domain.com/nickname => I have to get the user
There is a search in the table "users"
2.domain.com/my-first-article => I have to get the article
There is a search in the table "articles"
Note that each routes has its own controller and action, but they have a similar uri
You need something to distinguish them it'll be a lot more helpful. E.g use an # in front of usernames.
Another approach would be if you know for sure all slugs will have a hyphen, then you can chain ->where('slug', '...')
See https://pineco.de/handy-regex-constraints-in-laravel-routes/
Otherwise, it'll go through the first defined route.

Laravel - How do I navigate to a url with a parameter to left of subdirectory

I have to test this Route, but I am not sure how to navigate to it.
Route::get('/{type}/Foo/{page}/{date}', 'FooController#index');
I understand that URLs usually have subdirectories defined before the parameters in the URL.
I have worked with URLs like this example
Route::get('/Foo/{type}', 'FooController#index');
which would have an endpoint that looks like
/Foo?type=bar
Does anybody know how to test a route such as the one above?
Well i think that you need to clear out a bit the difference between route and query parameters.
In your case you are trying to use route parameters which will actually look something like:
/{type}/Foo/{page}/{date} => /myType/Foo/15/12-11-2021
Laravel considers the words inside {} as variables that you can retrieve via request so you can do something like:
$request->type
and laravel will return you the string myType as output.
In your second case that you have tried in the past you are referring to query parameters which are also a part of the $request. Consider it as the "body" of a GET request but i don't mean in any way to convert your post routes to GET :)
The thing with your second url is that:
/Foo/{type} is not similar to /Foo?type=bar
instead it should be like: /Foo/bar
In general query parameters are when you want to send an optional field most of the times in your GET endpoint (for filtering, pagination etc etc) and the route parameters are for mandatory fields that lead to sub-directories for example /Foo/{FooId}/Bar/{BarId}
The thing to remember is that you must be careful about your routes because variables can conflict with other routes.
For example a route looking like this:
Route::get('/foo/{fooId}', [FooController::class, 'getFoo']);
Route::get('/foo/bar', [BarController::class, 'getBar']);
will conflict because laravel will consider bar as the variable of the fooId so your second route can never be accessed.
The solution to this is to order your routes properly like:
Route::get('/foo/bar', [BarController::class, 'getBar']);
Route::get('/foo/{fooId}', [FooController::class, 'getFoo']);
So when you give as a route parameter anything else than bar your will go to your second route and have it working as expected.

Broken route in Laravel depending on where it's defined in the file

I have those routes defined in my routes/web.php :
Route::get('references/', 'referenceController#index')
Route::get('references/{reference}', 'referenceController#show')
Route::get('references/create', 'referenceController#create')
Like that, the references/create route goes to a 404 page.
If I put this route one line before, everything works fine :
Route::get('references/', 'referenceController#index')
Route::get('references/create', 'referenceController#create')
Route::get('references/{reference}', 'referenceController#show')
Then it is obviously because of the {reference} part in my route, right? But as I wanted to filter the reference perfectly, I've put a pattern in RouteServiceProvider.php. This pattern should check that my reference is a well-formed UUID :
Route::pattern('reference', '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{10}');
Miles away from the word "create", which doesn't match the pattern.
Do you know why my route is going to a 404 page depending on its position in the file?
This is how Laravel is supposed to work. It isn't very clear in the documentation though I'll admit.
Supplementing Resource Controllers
If you need to add additional
routes to a resource controller beyond the default set of resource
routes, you should define those routes before your call to
Route::resource; otherwise, the routes defined by the resource method
may unintentionally take precedence over your supplemental routes:
Route::get('photos/popular', 'PhotoController#method');
Route::resource('photos', 'PhotoController');
This is also true if you are defining non-resource routes as you noted in your example. This is because it will try to pass "create" as the id of the reference parameter in the route which of course is not valid.
Rule of Thumb
When defining routes that have the same number of url segments, always define the route that does not have a parameter variable first. The routes file will go top-down and find the first route that matches the current request.

laravel advanced routing to multiple controllers

I have in a middleware those two routes so they stay on top of all other routes
Route::get('{slug?}', array(
'as' => 'homeIndex',
'uses' => 'Modules\\Pages\\Controllers\\Pages#index'
))->where('slug', '(.*)?');
Route::get('{company?}', array(
'as' => 'companyProfile',
'uses' => 'Modules\\Company\\Controllers\\Profile#index'
))->where('company', '(.*)?');
what I'm trying to achieve is route all pages through homeIndex and all companies profile through companyProfile all on the first segment.
Is working fine for pages, but for companies profile I get 404.
It's same like facebook if you go on facebook.com/about the result is about page if you replace about with your unique name you get your profile.
Any ideas how to make it work?
Facebook works because about is a route. This works because your unique name can never be about. So they look for the about route first, and if the segment isn't about, they know it's probably a unique name.
Yours is different because your app doesn't know if your first segment is a slug or a company so you need some way to tell it the difference. If you redirected everything to a single function, then in that function did some queries or whatever you need to do to figure out if the first segment is a slug or a company name, then redirect that appropriately, it would work.
Laravel reads routes from the top to the bottom. When you hit the route /some-random-company, Laravel has no idea this is a company. All it knows is it happens to match the first route so it hits the slug route with your company. So another solution would be to update the wheres on your routes so Laravel has some idea if the incoming route parameter is a slug or a company and will know where to route that request to.
I'm terrible at regular expressions and I don't know how you are making slugs or if there is any rules you have setup on what a company name can or can not consist of. What you would have to do is figure out if there is anyway where you can accurately determine if the route parameter is a slug or company. For example, if it has one or more of -, it might be a slug.
Then you'd have to write a regular expression pattern to look for multiple - and then put that pattern into the where for the slug route. Then if the route parameter is a company and it does not match that pattern which was looking for one or more of -, Laravel will know to match this request with the company route.
If there is nothing which you can use to determine if a string is a slug or a company, you will have to update your routes so it looks like company/{company?} and slug/{slug?} and then output your links appropriately. This way Laravel will know for sure where to route that traffic.

Laravel 4 Route Filter Never Called

I am sure that I am doing something wrong that is very obvious, but for some reason I cannot get any filters except App::before to work in my test application.
//routes.php
Route::get('site/login',
array(
'before'=>'science',
'as'=>'site/login',
'uses'=>'HomeController#getLogin',
)
);
Route::controller(site, 'HomeController');
//filters.php
App::before(function($request){
//var_dump("Before"); exit;
});
Route::filter('science',function(){
dd("Science B!TCH!");
exit;
});
//HomeController.php
public function getLogin(){
$this->layout->body = View::make('home.login');
}
The object was first to ensure that a user was not logged in so I was trying to use the built-in "guest" filter, but it was never being called. So I later created the "science" filter to test if ANY routes would work. If I uncomment the var_dump line in App::before, it displays "Before" and exits as expected.
Can anyone see what I am doing wrong here? When I go to the /site/login page I should see my Breaking Bad movie quote instead of the actual page. However, I am seeing my login form as if nothing was happening.
Thanks!
UPDATE:
I changed the route to look like this now:
//routes.php
Route::get('site/login', 'HomeController#getLogin')->before('science');
... and it works. I get the debugging string "SCIENCE ..." on the screen.
It also works if I do the following
//HomeController.php
public function __construct(){
$this->beforeFilter('science');
}
Are there any use cases or conditions in which the array version of routes gets ignored?
UPDATE 2:
In my efforts to simplify my original description I neglected to show other routes that were in routes.php. Take a look below.
//routes.php
Route::get('site/login',
array(
'before'=>'science',
'as'=>'site/login',
'uses'=>'HomeController#getLogin'
)
);
Route::post('site/login',
array(
'as'=>'site/login',
'uses'=>'HomeController#postLogin'
)
);
Having the POST route AFTER the GET route is what is causing the problem. When I put the POST route BEFORE the GET route, the GET route works with the filter as expected.
Now, I was under the impression that Laravel treated GET and POST requests differently, hence the usage of the different static methods in Route. However, apparently, this is not true as the filter on the latter affects the filter of the former.
Is this a correct assumption? Should I start a different thread about this? I would love to understand why this is working this way.
Thanks!
UPDATE 3
---- SOLVED ---
This tidbit of information is not specifically stated in the documentation but you cannot have identical route names even though those route names are going to different REST verbs.
//routes.php BEFORE
Route::get('site/login', array('as'=>'site/login','uses'=>'HomeController#getLogin', 'before'=>'science'));
Route::post('site/login', array('as'=>'site/login', 'uses'=>'HomeController#postLogin',));
In the above solution, the 2nd Route OVERRIDES the previous route because the "as" uses the same name. I thought that these would be treated differently since one is GET and the other POST, but this is not the case. The filter assignments must happen by name in the backend and, as such, using identical names will override each other.
//routes.php AFTER
Route::get('site/login', array('as'=>'site/login','uses'=>'HomeController#getLogin', 'before'=>'science'));
Route::post('site/login', array('as'=>'site/postLogin', 'uses'=>'HomeController#postLogin',));
As you can see here, I renamed the 'as' part of the array to 'site/postLogin' and I can now use different filters for each the POST, GET, and probably PUT, DELETE and etc.
For better practice if two or more routes use the same filter, those routes should belong in a group. I have a feeling that will correct the issue.
From http://laravel.com/docs/routing#route-groups
Route::group(array('before' => 'auth'), function()
{
Route::get('/', function()
{
// Has Auth Filter
});
Route::get('user/profile', function()
{
// Has Auth Filter
});
});
---- SOLVED ---
This tidbit of information is not specifically stated in the documentation but you cannot have identical route names even though those route names are going to different REST verbs.
//routes.php BEFORE
Route::get('site/login', array('as'=>'site/login','uses'=>'HomeController#getLogin', 'before'=>'science'));
Route::post('site/login', array('as'=>'site/login', 'uses'=>'HomeController#postLogin',));
In the above solution, the 2nd Route OVERRIDES the previous route because the "as" uses the same name. I thought that these would be treated differently since one is GET and the other POST, but this is not the case. The filter assignments must happen by name in the backend and, as such, using identical names will override each other.
//routes.php AFTER
Route::get('site/login', array('as'=>'site/login','uses'=>'HomeController#getLogin', 'before'=>'science'));
Route::post('site/login', array('as'=>'site/postLogin', 'uses'=>'HomeController#postLogin',));
As you can see here, I renamed the 'as' part of the array to 'site/postLogin' and I can now use different filters for each the POST, GET, and probably PUT, DELETE and etc.

Resources