How to exclude slugs from a Laravel route pattern - laravel

I have a Laravel Spark application, and would like to use the first two parameters in a route for team and project, with exceptions like about_us, settings, api etc.
I have set up my routes, similar to:
Route::pattern('team', '[a-zA-Z0-9-]+');
Route::pattern('project', '[a-zA-Z0-9-]+');
Route::get('/home', 'HomeController#show');
Route::group(['prefix' => '{team}'], function () {
Route::get('/', 'TeamController#dashboard');
Route::group(['prefix' => '{project}'], function () {
Route::get('/', 'ProjectController#dashboard');
...
//Spark defines routes such as /settings after the apps routing file is processed;
//thus I cannot route to /settings as it's caught by /{team}.
I am struggling to do one of two things. Either, exclude values like 'api', 'settings' etc from the {team} pattern; or get the Laravel Spark routes to run before my web routes, so that I can ensure all valid routes are checked before the catch-all of /{team}.
Any ideas would be appreciated!

One suggestion I'd have, is to consider having the prefix of teams, then the team name after, you may find you want to add more sort of catch-alls like this for another section and run into more problems down the line. Perhaps listing all the teams using the index of this closure could be of benefit to admins of the system too?
If you'd like to continue down this route, take a look in your config.app.php, I believe that switching around the following two providers may well achieve what you're after. End result order:
App\Providers\SparkServiceProvider::class,
App\Providers\RouteServiceProvider::class,
I'm using the latest version of Spark after a recent install myself, this seems to be the default now, apologies if this is a red-herring!

I appear to have solved it, using the following pattern:
Route::pattern('team', '(?!^settings$)([a-zA-Z0-9-]+)');
For those who are new to the question, the principles are as follows. In a plain Laravel installation, you could re-order your routes to ensure they are processed in the right order, putting wildcards after your fixed routes.
With Spark, there are a number of routes all encapsulated away in the Spark package. Preferring not to mess around with this, allowing for easier Spark upgrades later amongst other things, it is possible to use a route pattern to limit the acceptable values for your parameter. As such, with some Googling on RegExs, I appear to have found a pattern that will exclude slugs matched by my {team} parameter.
I believe that adding more exclusions is as easy as inserting a pipe operator.
This would also obviously work on standard Laravel installations, but re-ordering your routes is probably a better first call.

You should define the routes you want to exclude first.
Then define your patterns below them. They will have precedence over the patterns because in Laravel routes are evaluated in top to bottom order.

Related

Laravel 8 route order from bottom to top

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

Interesting Laravel routing url problems

I've got (what is to me) an interesting url question.
This is my situation. I have what will be a user populated database, so I cannot be sure how many subareas I will have.
I will always have an area, one or more subareas, and a location that ends my url.
example: /area/subarea1/subarea2/location
This is slightly simplified from what I need. I need to be able to service the following urls as well;
/area/subarea1/location
/area/subarea1/subarea2/subarea3/location)
My routes look something like this:
Route::get('area/{subarea1}', 'SubareaController#show');
Route::get('area/{subarea1}/{location}', 'LocationController#show');
Route::get('area/{subarea1}/{subarea2}', 'SubareaController#show2');
Route::get('area/{subarea1}/{subarea2}/{location}', 'LocationController#show2');
So the problem here is that my routes are overriding each other, because they are essentially the same.
My question is this. Is there any way to differentiate these routes when they have the same url structure? And if not, is there a better way to handle multiple subareas between an area, and a location?
EDIT
Ok I've been tried naming my routes, but I can't seem to be able to use the named routes correctly with all my parameters in the view. I may look into the area/{subarea1}/subarea1/{subarea2}/subarea2 solution, even though I would rather not have the longer URL.
This happens because Laravel has no way to distinguish each route from the other. For example, it would route these 2 url's to the same action:
example.com/area/my-subarea-1/my-location
example.com/area/my-subarea-1/my-subarea-2
So you need different paths. Try this:
Route::get('area/subarea1/{subarea1}', 'SubareaController#show');
Route::get('area/subarea1/{subarea1}/location/{location}', 'LocationController#show');
Route::get('area/subarea1/{subarea1}/subarea2/{subarea2}', 'SubareaController#show2');
Route::get('area/subarea1/{subarea1}/subarea2/{subarea2}/location/{location}', 'LocationController#show2');

Laravel Controller Delegation

Trying to keep my Laravel project organized here, while letting it grow.
Currently I use:
Route::controller('/admin', 'AdminController');
...in order to allow the controller to service general admin pages. This is working fine, however I'd like to delegate specific subqueries to other controllers for cleanliness reasons.
For example, I'd like /admin/dashboard to resolve to AdminController#getDashboard. I'd also like /admin/gallery/ to resolve to AdminGalleryController#getIndex, and /admin/foo/bar to resolve to AdminFooController#getBar.
Is there a simple way to slowly expand functionality like this?
We've migrated to Laravel 5 and 5.1, and this still remains a good way to do things. If you aren't using route groups in Laravel, then you aren't doing Laravel right.
You can define those others as controller routes as well. Just do it before Route::controller('admin') because Laravel searches the registered routes in the other you define them. Since /admin/gallery would match Route::controller('admin') as well as Route::controller('admin/gallery') latter has to be defined first:
Route::controller('admin/gallery', 'AdminGalleryController');
Route::controller('admin/foo', 'AdminFooController');
Route::controller('admin', 'AdminController');
Instead of writing admin every time a route group might be a nice improvement as well:
Route::group(['prefix' => 'admin'], function(){
Route::controller('gallery', 'AdminGalleryController');
Route::controller('foo', 'AdminFooController');
Route::controller('/', 'AdminController');
});
Yes. Simply declare your "exception" routes before your main controller route.
Route::get('/admin/gallery','AdminGalleryContoller#getIndex');
Route::get('/admin/dashboard','AdminController#getDasboard');
Route::controller('/admin','AdminController');

Laravel 4.x, using both controller & resource routing

I'm trying using this:
Route::resource('users', 'UserController');
Route::controller('users', 'UserController');
When I'm using one of them - WORK,
otherwise - only resource work.
There is an option to using them both?
Which ever is on-top will take priority so if you put Route::controller on-top then that's the one that would work. I would post this as a comment but I don't have the rep to do it. Also why would you wanna use both of them at the same time?
Try moving Route::controller declaration above Route::resource:
Route::controller('users', 'UserController');
Route::resource('users', 'UserController');
The thing is that Laravel tries to match request with your defined routes by going from top to bottom and stops when it finds one.
localhost/users/example in your example actually hits show method in your UserController class as explained in documentation (see Actions Handled By Resource Controller).
Therefore Route::controller('users', 'UserController'); is ignored in this case.
I believe it's only working with one because once you use Route::resource(), all routes starting with users is going to be grabbed, and since Route::resource() does not work by prepending the action with the last segement in the uri (public function getUsers()), it's failing.
With your example provided, all you should need to use is Route::controller(). If there are some cases where that won't do, before it, add whatever routes you need using Route::get(), Route::post() or Route::any()
Route::resource() and Route::controller() were I believe not designed to work together and there shouldn't be much need to actually use them together.

Multi-language URLs in Laravel 4

I am trying to implement multi-language URLs. Thus I want to have URLs like:
/de/ueber-uns/kontakt and /en/about-us/contact
So far so good, I use App::before() in filters.php to check the locale given. I think I then need a route in routes.php for every controller action in every language.
So I thought of dynamically creating the file routes.php. All I would need for it is to know how I can access all available controllers or get all registered routes in code (like artisan routes but not with CLI).
So the questions are:
is the general approach for multilingual urls correct?
is it possible to access all controllers to extract the methods somehow?
how could I get the RouteCollection that is used within \Illuminate\Routing\Router.php?
Thank you in advance!
I ended up doing the following:
1) Routes in routes.php are dynamically created with a custom artisan command. It parses all Controllers and creates routes for every action in every language that is supported. The language string is handled with routes like
Route::get('{lang}/customer/login', 'CustomerController#getLogin')->where('lang', '[a-z]{2}').
This way users can just change the language string and the site will load in the correct language (if supported).
Routes for different languages all lead to the same controller action. For these languages except english, I need translations (routes.php in /app/lang).
2) a before filter for those controllers whose actions get translated is set in constructor. It basically checks if the language string is valid and replaces it if not. The chosen language will be set in the session.
I hope anybody can use it :)

Resources