I have seen other topics regarding this issue, didn't work out.
So in Laravel 5.4 Route Model Binding, we can bind a route to a model like:
define the route in web.php:
web.php:
Route::get('/users/{user}', UsersController#show);
UsersController#show:
public function show(User $user){
// now we already have access to $user because of route model binding
// so we don't need to use User::find($user), we just return it:
return view(users.show, compact('user'));
}
The above code will work just fine, so in our controller we can return the $user without finding the user, we already have it.
but imagine this:
web.php:
Route::patch('/users/archive', UsersController#archive);
EDITED: now the above line makes a patch route and we don't have {user} in the route url, the user id is being posted via the form.
UsersController#archive:
public function archive(Request $request, User $user){
// how can I access the $user here without using User::find($user);
// I get to this action via a form which is posting `user` as a value like `5`
dd($request->user); // this now echo `5`
// I can do:
// $user = User::find($request->user);
// and it works, but is there a way to not repeat it every time in every action
}
What I have tried:
in RouteServiceProvider::boot() I have:
Route::model('user', 'App\User');
The above is what i have found in Google, but not working.
I would appreciate any kind of help.
EDIT:
It seems it's not called Route Model Binding anymore since we don't have the {user} in the route and that's because my code is not working, the user variable is being posted to the controller and it's only accessible via $request->user.
this is route model binding:
Route::patch('users/{user}/archive', UsersController#archive);
this is not:
Route::patch('users/archive', UsersController#archive);
since we don't have {user} and it's being posted via the form and could be accessed only via $request->user.
(please correct me if I am wrong about the definition of route model binding)
SO:
what I want to achieve in a nutshell: in every request being sent to my UsersController, if I am sending user variable as a post variable, it must be bounded to User::findOrFail($request->user) and then $user must be available in my controller actions.
I want to achieve this because in every action I am repeating myself doing User::findOrFail($request->user) and I don't want to do that, so I want to check in every request if I have a variable name like a model name, they should be bounded.
There's no need to bind explicitly to the User class, so Route::model('user', 'App\User'); should be removed; type-hinting should be enough instead.
public function archive(Request $request, User $user) { ... }
should be working, just make sure you are importing the right User class at the top of the file (use App\User;).
Then the model is in your $user variable (method argument), try dd($user).
It's clear now that since the {user} variable is not in the URI, this is not a route model binding issue. You just want the User instance injected as a parameter based on the contents of the request.
$this->app->bind(User::class, function () {
$user_id = request('user') ?: request()->route('user');
return User::findOrFail($user_id);
});
You could add that to the register method in the AppServiceProvider (or any other registered provider) to have the model injected. I leave it to you to generalize this to other model classes.
You don't even need (Request $request) in your controller.
If you correctly imported User class, as alepeino said, you can access all user values from Model with this syntax $user-><value> for example:
public function archive(User $user) {
$userId = $user->id;
}
According to update.
If you use POST request, you can access it's data with such code request()->get('<variable you send as parameter>')
For example:
public function archive() {
$userId = request()->get('user');
$userInfo = User::find($userId);
//Or as you said
$user = User::findOrFail(request()->get('user'));
}
Can you try this;
public function archive(Request $request, $u = User::find($user){
//now variable $u should point to the user with id from url
}
Related
I'm using Laravel and I have a question running on my mind.
Basically, we create a Controller method like this:
public function index(User $user)
{
if(!empty($user)){ ... }
...
}
And we call this method by passing the $user as parameter to this method.
But is it possible to call the parameter optional. I mean if $user didn't pass, still the method works.
So in order to do that, do we have to create two web routes? Because by default, when we pass parameter we have to define that too in the route uri:
Route::get("/{user}", "HomeController#index");
Route::get("/", "HomeController#index");
So how to do this in Laravel? Is it possible or not?
I would really appreciate any idea or suggestion from you guys...
Thanks.
You can make a method parameter optional, yes. That's a PHP feature:
PHP - default function/method parameters
So in your code:
public function index(User $user = null)
{
if(!empty($user)){ ... }
...
}
It seems you want to call HomeController#index in both cases, isn't that misleading? If you visit /1 it will load user with ID 1 and show the homepage. What could be the use-case of that?
As John Lobo suggested, the route should better be /user/{user} to remove that ambiguity.
As an aside: Use Auth to get current user
If this is meant to load the current user: You can load the current user in every controller with Auth. There is no need to pass the user explicitly as a parameter if that works better for you:
$user = Auth::user();
See Laravel docs: Authentication
My routes:
Route::apiResource('courses', 'CourseController');
Route::apiResource('courses.classrooms', 'ClassroomController');
List: php artisan route:list
api/v1/courses/{course}
api/v1/courses/{course}/classrooms/{classroom}
My question is: all my functions in classroom controller needs the course, something like that
public function index($course_id)
{
$classroom = Classroom::where('course_id', $course_id)->get();
return $classroom;
}
public function store($course_id, Request $request)
{
// ...
$classroom->course_id = $course_id;
// ...
}
public function show($course_id, $id)
{
$classroom = Classroom::where('course_id', $course_id)->find($id);
return $classroom;
}
// ...
Have some Policy/Helper in Laravel to accomplish this automatically?
I believe it's not necessary to add the property $course_id in all functions manually, what can I do?
You can use a group to enclose all your routes. Something like:
Route::group(['prefix' => '{course}'], function () {
// you can place your routes here
});
So all the routes that exist in that group will already have the course value in the url path and you don't have to "rewrite it" for every route.
If that field is set by you for example an env variable then inside your RouteServiceProvider you can put the prefix you want in the mapApiRoutes function.
protected function mapApiRoutes()
{
Route::prefix('/api/v1/courses/'.config('app.myVariable'))
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
}
That way ALL your api endpoints will start with that prefix and you can have it in all the endpoints.
If the routes are registered correctly like you posted, your methods in the ClassroomsController should receive an additional parameter that's the course id fragment from the url.
For example if you request /api/v1/courses/1/classrooms route, the controller will receive the correct {course} parameter set to 1 as the first parameter.
You could then implement the index method of the ClassroomsController to use implicit model binding and get the Course instance with the given url id for the course.
To do so you have to type-hint the Course model for the first function's parameter and name the variable as the url fragment you want to use to retrive your model.
In your code example, you should do:
public function index(Course $course)
{
return $course->classrooms;
}
Note: I assume you have a relationship between Course and Classroom models to retrive the classrooms from the course model instance
You can read more about that on the official documentation here
I have a Laravel controller with destroying method and middleware with name CheckRole.
public function destroy(Request $request, $id)
{
// code to delete item
}
I am unable to use the $id in middleware CheckRole.
class CheckRole
{
public function handle($request, Closure $next)
{
}
}
And
$request->id and
$request->route('id')
are also not working with the middleware.
How to fix this?
Getting URL Parameters in Middleware
class CheckRole
{
public function handle($request, Closure $next)
{
$id = $request->route()->parameter('id');
}
}
The $id in the destroy function is the id of the resource you want to delete, so that will never be the authenticated user id a better option for checking the role might be an authorization policy instead of a middleware.
Or get the authenticated user id by using: auth()->id() or $request->user()->id
You can also add a method in your User model, and check like that for example:
class User ...
public function isAdmin()
{
return $this->role === 'admin';
}
In the middleware
$request->user()->isAdmin();
--- EDIT
So if you use a resource when you defined your route for example:
Route::resource('user', 'UsersController');
Then in your middleware you can access the id of the user like this:
$request->user
If your route is like this:
Route::delete('/users/{user}', 'UsersController#destroy');
Then again you can get it using the named parameter $request->user. Make sure that whatever you use on your route in the {} that's the name of the parameter you use to get the id from.
There's no real safe way to do this as the user can just change the ID within the form submitted, unless you just want users who can sign in to be an admin. If this is true I would use #nakov's suggestion.
If you want to have a full permission system with roles, etc I would suggestion: https://github.com/spatie/laravel-permission
A good tutorial can be found here but please do not change anything it states with the password: https://scotch.io/tutorials/user-authorization-in-laravel-54-with-spatie-laravel-permission
Assume the following routes in a Laravel 5.5 API:
// custom routes for THIS user (the user making the request)
Route::get('/user', 'UserController#show');
Route::get('/user/edit', 'UserController#edit');
// register CRUDdy resource routes for users
Route::resource('users', 'UserController');
Let's use edit as the example:
public function edit(User $user)
{
...
}
As you can see, the edit route contains a type-hinted $user parameter. This works just fine for the users/13/edit route, as expected. However, I'd to configure the route /user/edit to pass along the $request->user() user object to the function. I don't want to check for the user object in the actual edit method as that could interfere with other validation (for instance, I don't want to default to the current user if someone passes a non-existent user ID, I want to return an error in that case).
In short: how can I register a route to first create a parameter, then pass it to the given controller method? My first thought was to use a closure:
Route::get('/user/edit', function(Request $request){
$user = $request->user();
});
But once inside the closure, I'm not certain how to then carry the request forward to the appropriate controller method.
Instead of a closure, you could make a new controller method that calls edit with the current user.
Let's say your route is this:
Route::get('/user/edit', 'UserController#editSelf');
Then in your controller:
public function editSelf(Request $request)
{
$this->edit($request->user());
}
I'm trying to make a "Profile settings" section in an application.
The thing is, I learned how to do this the "Admin" way, the route would be /users/{user}/edit, the would call the edit method on the controller and it would return the edit view. There I would have a form which the user would patch to the route users/{user} and it would call the update method on the controller.
But I don't want anyone editing other users, so I'd like to know if there's a way to limit this route to the current user only.
Thanks in advance.
Since version 5.1 Laravel has Policies which are exactly what you need.
You can create a new policy by typing in command:
php artisan make:policy UserPolicy
In your UserPolicy class you can include the following method:
public function updateProfile(User $user, User $updatedUser) {
return $user->id === $updatedUser->id;
}
Please note: The first parameter $user is resolved automatically behind the scenes and is the currently logged in user. When checking the policy through the Gate facade in your application you need to pass only the second parameter $updatedUser.
Then you need to register your policy in the AuthServiceProvider:
use Acme\User;
use Acme\Policies\UserPolicy;
...
class AuthServiceProvider extends ServiceProvider {
protected $policies = [
User::class => UserPolicy::class
]
Now when you have your policy registered you can check it across your app using the Gate facade like so:
if(Gate::allows('updateProfile', $user)) {
// Your logic goes here
}
Or the other approach with I like more using the denies method and include it at the beginning of my controller methods and return http error:
public function edit($id) {
if(Gate::denies('updateProfile', $user)) {
abort(403, 'You do not have permissions to access this page!');
}
// The check is passed and your can include your logic
}
You can also check for permissions in your blade files using can and cannot like so:
#can('updateProfile', $user)
// Show something only to the user that can edit the $user's profile
#endcan
For more info check the docs.
you should not need to pass in the user id as there user is already logged in and there for should be able to edit themselves, thus only targetting the logged in user.
So you can use the routes /user/editand /user/updateetc
Just get the current user details like
Auth::user()->id
or something else like
$user = Auth::user();
Thus only the logged in user (themselves) can be edited.
In the view there should be a button or link, on click pass the ID to the desired route that's it.
Example:
For Grabbing the current logged in User id you should do like
$user = Auth::user()->id;
And directly in the route you can get it like
Edit
Now when someone clicks on the Edit Button/Link, the route will look like route/currentuserid.