How does Laravel choose between routes that appear the same? - php

I am (re-)learning Laravel and chose to do so via an excellent YouTube video in which we built a CRUD app. The app is mostly working very well but I have had a few problems with Laravel not "seeing" some routes until I moved them around in the web.php file.
This is my current web.php file, minus comments and routes used by a second controller:
<?php
use App\Models\Listing;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\UserController;
use App\Http\Controllers\ListingController;
//Show the full set of listings.
Route::get('/', [ListingController::class, 'index']);
//Manage listings belonging to the signed-in user
Route::get('/listings/manage', [ListingController::class, 'manage'])->middleware('auth');
//Show form to create job listing
Route::get('/listings/create', [ListingController::class, 'create'])->middleware('auth');
//Store new listing
Route::post('/listings', [ListingController::class, 'store'])->middleware('auth');
//Show form to edit job listing
Route::get('/listings/{listing}/edit', [ListingController::class, 'edit'])->middleware('auth');
//Submit completed edit form to database
Route::put('/listings/{listing}', [ListingController::class, 'update'])->middleware('auth');
//Delete listing from database
Route::delete('/listings/{listing}', [ListingController::class, 'destroy'])->middleware('auth');
//Show a single listing
Route::get('/listings/{listing}', [ListingController::class, 'show']);
My original web.php was exactly the same as this except that the '/listings/manage' route, currently the second in the list, was last. When that happened, every time I tried to invoke the /listings/manage route, the Clockwork browser extension indicated a 404 error and apparently indicated that it was trying to execute the show() method of the ListingController, rather than the manage() method. I was absolutely baffled by this and only eventually solved the problem by moving the /listings/manage route higher up in web.php.
On further testing, I'm finding that I now have the same problem when I'm trying to invoke the destroy() method to delete a listing from the database. Again, I get a 404 error and Laravel seems to be trying to execute the show() method if I am reading Clockwork correctly.
I really don't understand what is going on here with both cases. I could possibly solve the problem by moving the route that is trying to do the destroy() but I don't understand why I'm having the problems in the first place so I want to resolve that FIRST.
Can anyone enlighten me on this matter? I'm particularly curious how Laravel resolves the last three routes in the current web.php since they have the exact same URL, /listings/{listing}, so I'm wondering how it knows whether to invoke update(), destroy() or show().
UPDATE: To the anonymous individual who thought that the post he/she cited completely answered my question, it didn't. It didn't explain why the last three of my routes, which have an identical path, were apparently not the source of my problem. It also didn't give as complete a discussion of the possible solutions as some of the other answers I've obtained.

You have these to routes:
Route::get('/listings/{listing}',[ListingController::class, 'show']);
Route::get('/listings/manage', [ListingController::class, 'manage'])->middleware('auth')
laravel starts from first line of the script
and if requested path matches any route return it.
So suppose the request /listings/manage.
Laravel routing engine starts from first line of web.php .
First arrives to show method. Request matches it!!
The {listing} parameter could be anything even manage so routing engine stops here and return the show method. Not checking other routes to find more compatible route.
If you put the manage route at top of show so routing engine first arrives to it and matches to it and return it.

Laravel differs in the same routes by their request methods.
let's suppose the same path is defined in 3 routes but they have different request methods.(post,put,get).
The laravel get the path and match the request method and call that route.
Route::get('laravel/first',function(){});
Route::put('laravel/first',function(){});
Route::delete('laravel/first',function(){});
for more accuracy you may use the names of the routes. As:
Route::get('laravel/first',function(){})->name('getFirstData');
Route::delete('laravel/first',function(){})->name('deleteFirstData');

In carefully considering the answers I was given to my question, plus the older answers to similar questions, I've come up with a slightly different solution to my problem that seems to work fine. I'm posting it here now for two reasons:
To help others who may have difficulties along the same lines I had.
To get your feedback on this solution to see if it is reasonable or whether it should be avoided because of side-effects I don't see yet.
Here is my revised web.php with several routes added and several of the existing routes commented out:
<?php
use App\Models\Listing;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\UserController;
use App\Http\Controllers\ListingController;
//Show the full set of listings.
Route::get('/', [ListingController::class, 'index']); //default URL
Route::get('/listings/index', [ListingController::class, 'index']);
//Manage listings belonging to the signed-in user
Route::get('/listings/manage', [ListingController::class, 'manage'])->middleware('auth');
//Show form to create job listing
Route::get('/listings/create', [ListingController::class, 'create'])->middleware('auth');
//Store new listing
// Route::post('/listings', [ListingController::class, 'store'])->middleware('auth');
Route::post('/listings/store', [ListingController::class, 'store'])->middleware('auth');
//Show form to edit job listing
// Route::get('/listings/{listing}/edit', [ListingController::class, 'edit'])->middleware('auth');
Route::get('/listings/edit/{listing}', [ListingController::class, 'edit'])->middleware('auth');
//Submit completed edit form to database
// Route::put('/listings/{listing}', [ListingController::class, 'update'])->middleware('auth');
Route::put('/listings/update/{listing}', [ListingController::class, 'update'])->middleware('auth');
//Delete listing from database
// Route::delete('/listings/{listing}', [ListingController::class, 'destroy'])->middleware('auth');
Route::delete('/listings/destroy/{listing}', [ListingController::class, 'destroy'])->middleware('auth');
//Show a single listing
// Route::get('/listings/{listing}', [ListingController::class, 'show']);
Route::get('/listings/show/{listing}', [ListingController::class, 'show']);
I left the sequence of routes/functions the same but I could have changed it around as much as I like and still be confident that it would work. Basically, whenever the path had a parameter in it, I put the parameter last and preceded it with the name of the function that would be executing in the Controller. That ensured that every route (except '/') started with the Controller name, listings, followed by the function name, and possibly followed by a parameter if it was a specific listing, so that there could never be any confusion over which function the route was going to invoke.
Some routes didn't have to change at all because they already followed the pattern, namely /listings/manage and /listing/create. One route, '/', had a second route added for /listings/index because that's what is really being done; I just keep the '/' route as a reasonable default behaviour in case the user doesn't know what naming convention I am using for the routes and tries '/'.
Is that approach likely to bite me later in ways I haven't anticipated yet?

Related

Laravel getting 404 error when creating new route

it appears that when I created a new route, I receive the 404 error when trying to access the url, which is funny,. because all of my other routes are working just fine.
My web.php looks like so:
Auth::routes();
Route::post('follow/{user}', 'FollowsController#store');
Route::get('/acasa', 'HomeController#index')->name('acasa');
Route::get('/{user}', 'ProfilesController#index')->name('profil');
Route::get('/profil/{user}/edit', 'ProfilesController#edit')->name('editareprofil');
Route::patch('/profil/{user}', 'ProfilesController#update')->name('updateprofil');
Route::get('/alerte', 'PaginaAlerte#index')->name('alerte');
Route::get('/alerte/url/{user}', 'UrlsController#index')->name('editurl');
Route::post('/alerte/url/{user}', 'UrlsController#store')->name('updateurl');
Route::get('/alerte/url/{del_id}/delete','UrlsController#destroy')->name('deleteurl');
The one that is NOT working when I am visiting http://127.0.0.1:8000/alerte is:
Route::get('/alerte', 'PaginaAlerte#index')->name('alerte');
The controller looks like so:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Auth;
class PaginaAlerte extends Controller
{
public function __construct() {
$this->middleware('auth');
}
public function index(User $user)
{
return view('alerte');
}
}
I am banging my head around as I cannot see which is the problem. It is not a live website yet, I am just developing on my Windows 10 pc using WAMP.
Moved my comment to a little bit explained answer.
So, in your route collection, you have two conflicting routes
Route::get('/{user}', 'ProfilesController#index')->name('profil');
and
Route::get('/alerte', 'PaginaAlerte#index')->name('alerte');
Imagine that Laravel is reading all routings from top to bottom and it stops to reading next one after the first match.
In your case, Laravel is thinking that alerte is a username and going to the ProfilesController#index controller. Then it tries to find a user with alerte username and returning 404 because for now, you don't have a user with this username.
So to fix 404 error and handle /alerte route, you just need to move the corresponding route before /{username} one.
But here is the dilemma that you got now. What if you will have a user with alerte username? In this case, the user can't see his profile page because now alerte is handling by another route.
And I'm suggesting to use a bit more friendly URL structure for your project. Like /user/{username} to handle some actions with users and still use /alerte to handle alert routes.
The following route catches the url /alerte as well
Route::get('/{user}', 'ProfilesController#index')->name('profil');
Since this one is specified before
Route::get('/alerte', 'PaginaAlerte#index')->name('alerte');
The /alerte will go the the ProfilesController instead.
To fix this change the order of the url definitions or change either of the urls to have nesting e.g. /alerte/home or /user/{user}
Well.
Maybe this is too late, but I have all week dealing with this problem.
I made my own custom.php file and add it in the routes path of my Laravel project, and none of the routes were working at all.
This is how I solved it:
You must remember to edit the RouteServiceProvider.php file located in app\Providers path. In the map() function, you must add your .php file. That should work fine!
To avoid unexpected behaviors, map your custom routes first. Some Laravel based systems can "stop" processing routes if no one of the expected routes rules were satisfied. I face that problem, and was driving me crazy!
I would wish suggest to you declare your URL without the "/", like your first "post" route, because sometimes, I have been got this kind of errors (404).
So, my first recomendation is change the declaration of the route. After that, you should test your middleware, try without the construct, and try again.
Good luck!

Laravel returning a blank page only on certain routes

I'm having an issue where a route is returning a blank page. I am using Homestead as my dev environment and I'm unsure how to debug.
The /storage/logs/laravel ... isn't returning any exceptions when I visit the white page.
web.php (where it's failing):
Route::get('/clinic/register', 'ClinicController#register');
Controller.php:
public function register()
{
return view('clinic.register', ['specialisms' => Specialism::pluck('specialism', 'id')]);
}
Yet when I visit /clinic/register I am shown a blank white page. How can I see why it's failing? Surely a white page will return an exception somewhere?
As you have not provided your entire route setup. This answer is my best guess. See if it helps.
Your issue hint at improper route setup. If you have created a clinic resource then clinic/register route should precede it.
// clinic/register route should come first
Route::get('clinic/register','ClinicController#register');
// followed by rest of the routes which resource will create
Route::resource('clinic','ClinicController');
The reason behind getting a blank pages is because Route::resource will create some route with wildcards.
For e.g. clinic/{clinic} which will map to show method on controller. So when you make a get request to clinic/register it will be mapped to this show method instead of your register method.
One possibility for not getting any errors is your show method does not have any code yet. Hence, a blank response.
To summarize: Order in which you register your routes matters

Mapping route to another route in Laravel

I am developing an application in the Laravel 5.2 which must have a friendly URL-s. This is not problem with the regular way, where the {slug} wildcard is handled by a controller but I want to make it in a different way.
For now I have only two controllers:
ProductsController#show to show details product
CategoriesController#show to show selected category with listing products which are assigned to it
And routes:
Route::get('product/{product}', 'ProductsCategory#show')->name('product.show');
Route::get('category/{category}', 'CategoriesController#show')->name('category.show');
So when I want to echo a just use route('product.show', compact('product'))
Nothing special until I want to handle different URL-s which are fetched from a database. I thought it will be possible to make an another routes which are assigned to existing and when I use a route(...) helper it will be handled automatically. But it is not. So for example I have a new URL:
domain.com/new-url-for-product.html
so by route it should be assigned to the regular 'product.show' route with some ID, which is handled by route model binder for {product} wildcard. The same for route(..) helper it should print friendly URL-s.
I don't know if my strategy is good. How do you handle with the similar problems?
of course route will handle this automatically. have a look into this. I am just giving you example, you have to set this code as per your need.
route('product.show', 'product'=>'new-url-for-product.html');
will generate this
domain.com/new-url-for-product.html
route('product.show', 'product'=>'new-url2-for-product.html');
will generate this URL
domain.com/new-url2-for-product.html
and so on, and then you have to handle all this in your controller method.
eg: your controller method for this route is ProductsCategory#show which is
public function show($product){
if($product == 'new-url-for-product.html'){
//perform this
}
if($product == 'new-url2-for-product.html'){
//perform this
}
}
this just an example, you have to map this according to your need
Edited Here is the example I tested it
Route::get('/product/{product}.html', ['as'=>'product.show', 'uses'=>'ProductsCategory#show']);

Laravel 4 routing - the ability to skip a route if conditions are not met

So we have a load of content within the database, let's call these Articles. The paths for the Articles start from the application root and can contain slashes. So we want to search the DB to see if we match an Article, and if not skip the route and give other routes the opportunity to respond.
In Sinatra (which I believe has inspired the routing within Laravel) you have the option to pass to the next route. It might be this that's leading me astray.
Route::get( '{uri}', function( URI $uri ) {
// check database, if we find a record we'll need to pass it to the appropriate controller
// we'll have a class that handles this responsiblity
// pass if we don't find anything
} )->where('uri', '.*');
Route::get( 'other/page', 'OtherController#sayHello' );
The issue Allow skip the route based on conditions #1899 talks about this, although I can't see how filters cater for this, they will simply intercept and give you an opportunity to stop route execution (throw exception, redirect to route specifically etc.), you can't return FALSE; without error. There is an example chaining a condition method to a route, although this method doesn't seem to exist (in 4.2).
Any ideas?
In addition to this, we're also are thinking about containing this logic within a package so it can be shared across applications, and wonder if you can influence the order of execution of routes provided by package?
You can have a group of routes, which you contain within all the routes that match it. If it fails that group then it skips to the next section.
Route::group(array('before' => 'auth'), function()
{
Route::get('/', function()
{
// Has Auth Filter
});
Route::get('user/profile', function()
{
// Has Auth Filter
});
});
http://laravel.com/docs/4.2/routing

RESTful trailing slash routes in laravel

I have the following routes in my Laravel 3 RESTful api project
Route::delete('/(:any)', 'resources#destroy');
Route::delete('users/(:any)', 'users#destroy');
The problem I am having is when a user sends a delete request to /users/
What I want to happen is that the users#destroy route is called with parameter null. In my controller I have an exception for a user trying to delete a null resource.
What seems to be happening is that the resource#destroy route is called with parameter users. This obviously has the undesired affect of deleting the users resource.
I know I could modify my .htaccess but technically /users/ does belong to the users controller not the resources controller. I want to maintain that relationship.
I was wondering if there is a simple way to solve this from within Laravel?
EDIT: have the above working with the answer below. Now I have an error in my get routes
Route::get('users/(:any?)', 'users#show');
Route::get('users', 'users#index');
/users and /users/ both call users#index which I don't want.
I need GET /users to go to users#index and GET /users/ to go to users#show with null parameter
I worked around the trailing slash by adding a filter to my routes
Route::group(array('before' => 'trailingslash'), function()
{
//routes in here
});
Route::filter('trailingslash', function() {
$current = URI::full();
if(substr($current, -1) == '/'){
return return Response::error('404');
}
});
one point you need to consider.
Routes are defined without a front-slash. The only exception to this is the default route which is represented with only a front-slash.
so Route::delete('/(:any)', 'resources#destroy') will cause undesired result.
Moreover, your order is wrong.
(:any) will match user too and will send the request to resources controller.
so what you need to do is,
change the order (make it reverse).
change the route of resources considering according to the rules. like resources/delete etc.....
What I want to happen is that the users#destroy route is called with parameter null. In my controller I have an exception for a user trying to delete a null resource.
to do this, (after making the changes above....)
change the route of user/(:any) to user/(:any?) which will make the 2nd segment optional.
After that, straight forward.
$foo = URI::segment(2, null);
//Rest will follow.
EDIT
Now, the following code,
Route::get('users/(:any?)', 'users#show');
Route::get('users', 'users#index');
does not make any sense.
if i type user, what the router is suppose to take?
user#show with no optional segment or user#index?
Routes are design so that it removes the ambiguity. You are going in the opposite direction with making it all ambiguity.
just make a simple route.
like show
user/show
delete
user/delete
edit
user/edit
etc....
The type of routing you are applying is confusing for both users and developers as it carries ambiguity.

Categories