Laravel Route: Multiple Route Group With Prefix and Route Model Binding - laravel

Hello wonderful people of SO!
I have a problem about Laravel Route which I cannot solve.
In User.php model I use getRouteKeyName() function
public function getRouteKeyName()
{
return 'user_name';
}
And also in Post.php model
public function getRouteKeyName()
{
return 'uuid';
}
In users table, 1 have one record
|----------------------------|
| id | ... | user_name | ... |
| 1 |-----| #simple |-----|
In posts table
|------------------------------------|
| id | ... | uuid | ... |
| 1 |-----| abcd-123-efg-456 |-----|
In route (web.php)
// for post (key: uuid)
Route::group(['prefix' => '{post}'], function () {
Route::get('/', function (Post $post) {
return $post;
});
});
// for users (key: user_name)
Route::group(['prefix' => '{user}'], function () {
Route::get('/', function (User $user) {
return $user;
});
});
Then let say we visit url: www.example.test/#simple/
In debugbar, I see query:
select * from posts where uuid = '#simple' limit 1
What I have tried
[#1] I put where clause in route groups for posts and users
Route::group([
'prefix' => '{post}',
'where' => [
'post' => '^[a-zA-Z0-9-]{36}$' // I'm not Regex professional
]
], function () {
Route::get('/', function (Post $post) {
return $post;
});
});
Route::group([
'prefix' => '{user}',
'where' => [
'user' => '^(#)[a-zA-Z0-9]$' // I'm not Regex professional
]
], function () {
Route::get('/', function (User $user) {
return $user;
});
});
So let's try again visit the url: www.example.test/#simple
What i got, 404
[#2] I deleted the getRouteKeyname in both User and post model
revisit url: www.example.test/#simple, still got 404
[#3] I tried to put Route Model Binding Column Name
Route::group([
'prefix' => '{post:uuid}', // This is what I changed
], function () {
Route::get('/', function (Post $post) {
return $post;
});
});
Route::group([
'prefix' => '{user:user_name}', // This is what I changed
], function () {
Route::get('/', function (User $user) {
return $user;
});
});
Still, query result is same: > select * from posts where uuid = '#simple' limit 1
What I want to achieve
Let say we visit url: www.example.test/#simple
Fetch a user with user_name is #simple or if the user is not exist, return 404
And also same for with posts
We visit url: www.example.test/abcd-1234-efgh-5678
Fetch a post with uuid is abcd-1234-efgh-5678 or 404 if not exist
Question:
[#1] How to tell Laravel Route: that I have 2 Route groups with different Model Binding? Sorry if this question is kinda confusing, cause my english is not really good
[#2] Have I implement Best practice for route groups and route model binding in Laravel?
Thanks in advance!

What is the result you intend to obtain?
If you are doing what I think you're doing (trying to see what's inside the post), you need to return something like $post->content (replace content with the column you want to get), you may even want to make a view and make the output nicer, plus use a controller for more processing.
As for route model binding, you can refer to this, both methods, using table:column and using getRouteKeyName are fine, however, the first one doesn't change the default column, and if you use {user} for another route, it will still use the ID column, however, the second one changes the default value, if you use {user} for another route, it will use the column you specified.
Also, you should use something like user/{user:user_name} and post/{post:uuid} instead of just {user:username} and {post:uuid}, as you have said, it won't know which route you're using. The uri has to be different.
Routes are evaluated in the order you put them, meaning that the second route with {post:uuid} will override the route with {user:username} since they have the same kind of uri, that is, they both consist of 1 wildcard and nothing else. To solve this, you simply have to make their uri different by adding a static part, for example, add post/ before {post:uuid} and/or add user/ before {user:user_name} like the example below:
Route::group([
'prefix' => 'post/{post:uuid}',
], function () {
Route::get('/', function (Post $post) {
return $post;
});
});
Route::group([
'prefix' => 'user/{user:user_name}',
], function () {
Route::get('/', function (User $user) {
return $user;
});
});
To make it very clear, your 2 routes have the same uri of 1 wildcard and nothing else, thus, the last one that appears with this uri will override all the previous routes with the same uri. Meaning that all the previous routes with this same uri before this will be treated like they don't exist, and when you go to a path with the uri in the format of /[insert something here], it fits into the format of having 1 wildcard and it will only go to the last one you specified, that is, the one for posts.
Since the route for users is declared before the one for posts and they share the same uri, only the one for posts will be used. Even when you are trying to find the user, it still uses the route for posts, if no such "post" with a uuid same as the user_name you provided exists, it will still return an error even when there is indeed such user with such username.
Also, you don't need a route group if there's simply 1 route, though it would be more readable and convenient if you're going to add more routes to the group in the future.

As far as I could understand your problem, here are the changes you need to make and it will work,
routes/web.php
Route::group([
'prefix' => 'post/{post:uuid}'
], function () {
Route::get('/', function (Post $post) {
return $post;
});
});
Route::group([
'prefix' => 'user/{user:user_name}'
], function () {
Route::get('/', function (User $user) {
return $user;
});
});
Regular Expression that you use above just does filter the {argument} and check if {argument} is alphanumeric basically, in above both cases it works the same except in user_name it also allows '-'

Related

Localization in laravel

I designed a site with Laravel. now I want add new language to it.I read laravel document . It was good but I have a problem.suppose I have a page that show detail of products so I have a route like mysite.com/product/id that get product's id and show it.also I have a method in controller like
public function showProduct($id){
...
}
If I add new Language , the route will change to this: mysite/en/product/id
now I must change my method because now two parameter send my method.something like this :
public function showProduct($lang,$id){
...
}
So two problems arise:
I must change all method in my site which is time consuming
I do not need language parameter in methods because I set $locan via middleware
pay attention that I do not want remove for example en from my URL (because of SEO)
Open your RouteServiceProvider and say that language parameter actually is not a parameter, it's a global prefix.
protected function mapWebRoutes()
{
Route::group([
'middleware' => 'web',
'namespace' => $this->namespace,
'prefix' => Request::segment(1) // but also you need a middleware about that for making controls..
], function ($router) {
require base_path('routes/web.php');
});
}
here is sample language middleware, but it's need to be improve
public function handle($request, Closure $next)
{
$langSegment = $request->segment(1);
// no need for admin side right ?
if ($langSegment === "admin")
return $next($request);
// if it's home page, get language but if it's not supported, then fallback locale gonna run
if (is_null($langSegment)) {
app()->setLocale($request->getPreferredLanguage((config("app.locales"))));
return $next($request);
}
// if first segment is language parameter then go on
if (strlen($langSegment) == 2)
return $next($request);
else
// if it's not, then you may want to add locale language parameter or you may want to abort 404
return redirect(url(config("app.locale") . "/" . implode($request->segments())));
}
So in your controller, or in your routes. you don't have deal with language parameter
Something like
Route::group(['prefix' => 'en'], function () {
App::setLocale('en');
//Same routes pointing to the same methods...
});
Or
Route::group(['prefix' => 'en', 'middleware' => 'yourMiddleware'], function () {
//Same routes pointing to the same methods...
});

Use different route namespace based on middleware in Laravel

I have the following code in my routes/web.php
Route::namespace('Admin')->middleware(['admin'])->group(function() {
Route::get('/posts', 'PostController#index');
});
Route::namespace('User')->middleware(['user'])->group(function() {
Route::get('/posts', 'PostController#index');
});
I wish to use the same uri "/posts" in both cases and keep the role logic (admin, user) out of the controllers, however, in this case, when I request the route "/posts" in always responds with the last one.
I can't seem to find information of what I am missing here.
use prefix for different route for admin and user
/admin/posts
Route::group(['namespace' => 'Admin','middleware=>'admin','prefix' => 'admin'],function() {
Route::get('/posts', 'PostController#index');
});
/user/posts
Route::group(['namespace' => 'User','middleware=>'user','prefix' => 'user'],function() {
Route::get('/posts', 'PostController#index');
});
You may try this one
Route::group(['prefix'=>'admin','middleware'=>'admin'],function (){
Route::get('/posts',['uses'=>' PostController#posts','as'=>'posts.index']);
});
Route::group(['prefix'=>'user','middleware'=>'user'],function (){
Route::get('/index',['uses'=>' PostController#posts','as'=>'posts.index']);
});

Identify Route's Group in Middleware

My ultimate objective is to limit accesses to the group of routes by validating permissions provided to the user.
These target 'group of routes' have ONE COMMON PARENT GROUP and may have zero or more sub-groups, such that, if access to these target 'group of routes' is permitted/accessible to the user then, all its sub-route groups are also accessible to the user.
To achieve this, I believe I need to differentiate these target group of routes by any uniqueString/parameter in middleware, which is indeed answered here.
But, I want to generalize this further, by applying middleware to common SINGLE PARENT GROUP of all these target group of routes and identify these target group of routes by any means in the middleware.
So, my question is how do I identify/differentiate these target group of routes in the middleware? Is there any way to do so?
Sample Code of what I am trying to describe:
Route::group(['prefix' => 'singleParent','middleware' => 'permissionMiddleware'], function (){
Route::group(['prefix' => 'target-group-1', 'groupUniqueString' => 'tsg1'], function (){
Route::group(['prefix' => 'sub-group-1.1'], function (){
});
Route::group(['prefix' => 'sub-group-1.2'], function (){
});
});
Route::group(['prefix' => 'target-group-2', 'groupUniqueString' => 'tsg2'], function (){
Route::get('route-1','Controller#method-of-Route1');
});
});
So, to specify a route group in your middleware to handle some actions, you can do it in this way :
Route::group(['prefix' => 'singleParent','middleware' => 'permissionMiddleware'], function (){
Route::group(['prefix' => 'target-group-1', 'as' => 'tsg1.'], function (){
//...
});
});
This will generate route names with the prefix : tsg1
Now in your middleware you can do like this to get the route group :
function getCurrentRouteGroup() {
$routeName = Illuminate\Support\Facades\Route::current()->getName();
return explode('.',$routeName)[0];
}
Updated
and to check :
if ($request->route()->named('name')) {
//
}
return $next($request);
Or in another approach you can achieve :
To get the prefix of a route group you can do something like this :
$uri = $request->path();
// this will give you the url path like -> if this is the url :
// http://localhost:8000/foo/bar you will get foo/bar
And then :
$prefix = explode('/',$uri)[0];
// and you will get 'foo'
Let me know if this works for you.

How to add dynamically prefix to routes?

In session i set default language code for example de. And now i want that in link i have something like this: www.something.com/de/something.
Problem is that i cant access session in routes. Any suggestion how can i do this?
$langs = Languages::getLangCode();
if (in_array($lang, $langs)) {
Session::put('locale', $lang);
return redirect::back();
}
return;
Route::get('blog/articles', 'StandardUser\UserBlogController#AllArticles');
So i need to pass to route as prefix this locale session.
If you want to generate a link to your routes with the code of the current language, then you need to create routes group with a dynamic prefix like this:
Example in Laravel 5.7:
Route::prefix(app()->getLocale())->group(function () {
Route::get('/', function () {
return route('index');
})->name('index');
Route::get('/post/{id}', function ($id) {
return route('post', ['id' => $id]);
})->name('post');
});
When you use named routes, URLs to route with current language code will be automatically generated.
Example links:
http://website.com/en/
http://website.com/en/post/16
Note: Instead of laravel app()->getLocale() method you can use your own Languages::getLangCode() method.
If you have more questions about this topic then let me know about it.
Maybe
Route::group([
'prefix' => Languages::getLangCode()
], function () {
Route::get('/', ['as' => 'main', 'uses' => 'IndexController#index']);
});

Laravel: always adding user/{id} to routes or different method

I have a Laravel application. In the routes file, I have
Route::group(['prefix' => 'user'], function () {
Route::group(['middleware' => ['auth', 'roles'], 'roles' => ['buyer']], function() {
Route::get('dashboard/buyer', ['as' => 'buyer_dashboard', 'uses' => 'User\Buyer\DashboardController#index']);
});
Route::group(['middleware' => ['auth', 'roles'], 'roles' => ['seller']], function() {
Route::get('dashboard/seller', ['as' => 'seller_dashboard', 'uses' => 'User\Seller\DashboardController#index']);
});
});
I have a middleware that basically checks if the id as supplied in the route, is the same as the current logged in user. If this is not the case, I return an error page. The reason for having this is that I want to prevent that a user can access the dashboard of another user. I also want to prevent that a user can place a bid for someone else (by changing the id in the http request)
The issue is that in the first route, the id is referring to the user. In the second route, the id is referring to the lot id.
Am I obliged to change the second route to:
Route::get('{id}/lot/{lot}/bid/create', ['as' => 'buyer_lot_bid_create', 'uses' => 'User\Buyer\BidsController#create']);
where the first id refers to the user so that I can check the id of the user?
Or is there another way to prevent users from accessing other users pages without explicitly passing the user/{id} in the route?
Doing this in a middleware sounds like a bad idea. You've already come up against one exception to your middleware rule, and you'll certaily come across more.
There's two ways to do this:
Using laravel's built in authorisation tools: https://laravel.com/docs/5.1/authorization
If the check is that the "id as supplied in the route, is the same as the current logged in user" then there's no need to pass the user's id in via the route at all. The user can just visit eg. /dashboard/buyer with no params in the route. Who's dashboard are they visiting? The one of the logged in user, of course. So there's no way for a user to even try to visit another user's dashboard. Likewise with bidding. You can make your bid endpoint so that the bidder's id is not passed in via a route - it's just set to the id of the logged in user in your controller method. So again, there's no way to even try to bid on behalf of another user.
class AuthServiceProvider extends ServiceProvider
{
public function boot(GateContract $gate)
{
$this->registerPolicies($gate);
$gate->define('see-profile', function ($user, $profile) {
return $user->id === $profile->user_id;
});
}
Controller:
public function profile($id)
{
$post = Profile::findOrFail($id);
if (Gate::denies('see-profile', $profile)) {
abort(403);
}
}

Resources