I have an app where I create entries based on who is signed in. If I use the find($id) method it returns json response. The function is like this:
public function edit($id)
{
$chore = Chore::find($id);
return response()->json($chore);
}
Now if I where to edit the id value I might be able to access other user's data which isn't secure at all.
So I added and extra column user_id that checks who is signed in:
public function edit($id)
{
$chore = Chore::find($id)
->where('user_id', Auth::id());
return response()->json($chore);
}
But of course laravel can't make it easy so it doesn't work. Adding ->get() returns an array instead of a json response.
First of all how is find($id) ever secure in any app that uses authentication and secondly how do I add another condition under the find($id) clause? I need data returned in JSON otherwise I will need to rewrite all my front-end which isn't ideal at this point.
I also tried:
public function edit($id)
{
$chore = Chore::where('id', $id)
->where('user_id', Auth::id());
return response()->json($chore);
}
but no luck
You just need to call the where method before the find method
$chore = Chore::where('user_id', Auth::id())
->find($id);
As an alternative, If you've set up relationships properly on user model
// In User.php
public function chores()
{
return $this->hasMany(Chore::class, 'user_id', 'id');
}
then you could also do
$chore = Auth::user()->chores()->find($id);
While this seems like extra work, it's more convenient and easier to maintain.
If Chore is in a one-to-one relationship with your User model, then you can create a relationship in your User.php model.
public function chore() {
return $this->hasOne(Chore::class);
}
Then in your controller, you could simply call auth()->user()->chore.
The Eloquent's find() method finds only one record by the primary key. If you use additional validation it's perfectly safe. You could use route model binding to simplify the code.
web.php
Route::get('/chore/{chore}/', 'ChoreController#edit');
Then in your controller
public function edit(Chore $chore)
{
if (! $chore->user_id === auth()->id()) {
// throw error or redirect, or whetever
}
return response()->json($chore);
}
To simplify the controller a little bit more, you could create a form request and inject it into controller's method as a regular request. (https://laravel.com/docs/7.x/validation#authorizing-form-requests)
Then you could move the validation into your form request. It should look something like this:
public function authorize() {
return $this->route('chore')->user_id === $this->user()->id
}
Related
In my API I used "with" method to get parent's model relation and everything works fine.
I want to add an attribute in my relation and return it in my API but I should use request in my model.
Something like this :
Book.php
protected $appends = ['userState'];
public function getUserStateAttribute () {
return User::find($request->id); //request not exists here currently
}
I have $request in my controller (api controller)
Controller.php
public function get(Request $request) {
Post::with('books')->all();
}
I believe using static content to append in array of model is so easy but how about using request's based content ?
I guess you can use request() helper :
public function getUserStateAttribute () {
return User::find(request()->get('id'));
}
Sure this is not really MVC pattern, but it can work
You want to take request as a parameter here:
public function getUserStateAttribute (Request $request) {
return User::find($request->id);
}
That way you can access it in the function. You will just need to always pass the Request object whenever you call that function.
e.g. $book->getUserStateAttribute($request);
Alternatively, you could just pass the ID, that way you need not always pass a request, like so:
public function getUserStateAttribute ($id) {
return User::find($id);
}
Which you would call like:
e.g. $book->getUserStateAttribute($request->id);
6 projects
and there is user model and this is a method in user model
public function setPasswordAttribute($password)
{
$this->attributes['password'] = bcrypt($password);
}
Everything is working fine when I am using get() in laravel like this
User::get();
It returns all users.
My question is, is there a way to write method in the model that can return users with conditions, not all users,
like this
public function setRoleToGetData()
{
$user = Auth::user()->getSex // getSex is method in the model
if($user == 1)
return users in the whole program like this
User::where('user_sex','=',1)->get();
else
// return the reverse
}
i dont want to write where('user_sex','=',1) in every time i want to get users
thanks
You are probably looking for scopes.
https://laravel.com/docs/5.6/eloquent#local-scopes
From the docs:
Local scopes allow you to define common sets of constraints that you may easily re-use throughout your application
Example:
public function scopePopular($query)
{
return $query->where('votes', '>', 100);
}
Then use as
SomeModel::popular()->get();
I'm providing a HasTranslation-Trait for any of my eloquent models. All models using this trait will receive a one-to-many-relation like this (where you can see my basic Model to ModelLanguages relations):
public function languages()
{
return $this->hasMany(get_class($this).'Lang', 'master_id', 'id');
}
What I want to do is:
Always eager load a "hasOne"-relationship with the translation of current user's language. So whenever the user is logged in, my models should have something like $model->userLanguage being eager loaded and is of type ModelLang.
This looks like this and works great:
public function userLanguage()
{
$user = \Auth::user();
if (!$user)
{
throw new \Exception(__CLASS__.': userLanguage not available because there is no session');
}
return $this->hasOne(get_class($this).'Lang', 'master_id', 'id')->where('language_id', $user->language_id);
}
I'm struggling with the possibility to automatically (eager) load this relations for all models by just including this trait.
What I've tried so far
Use a constructor within the trait: Can work, but no good idea, because this can collide with other trait's contructor. Basically I'd confirm the statement: Do not use the constructor in any trait.
Use your boot-Trait method (bootHasTranslation) but there I do not have the concrete object to call load or with method on. I did not find any hook into the instantiated eloquent model where I want to add my relation to eager load
Any ideas? Is there something obvious I've overlooked here?
You can create a middleware for the logged in user language.
public function handle($request, Closure $next)
{
if (!Auth::check()) {
return redirect('/')->with('Success','You have successfully logged out');
}
$user = \Auth::user();
$language = $user->languages()->where('language_id', $user->language_id);
$request->attributes->add([
'user_language' => $language
]);
$next($request);
}
You can get this data after middleware like
$request->attributes->get('user_language');
I'm little confused with the database queries in the laravel, where do we have to write our queries: in controllers, models or routes ?
I've been through many tutorials and I see so much difference. Just creating confusion.
Kindly explain somebody
It depends on different factors, but in short you can write them both in Model, Controllers or Repositories
If you're writing a controller action and you need a query that you'll use only once, it's perfectly fine to write the query directly in the controller (or even in the route's closure ).
For example, if you want to get all users of type admin:
$admins = User::where('type', 'admin')->get();
Now, suppose you need to get the admins in more than one controller method; instead of rewriting the same query, you can create a Repository class to wrap the access to the users' Model and write the query inside the Repository:
class UserRepository
{
public function getAllAdmins()
{
return User::where('type', 'admin')->get();
}
}
Now in your controllers you can inject the Repository and use the same method of the Repository to get the admin users: this will keep your code DRY as you don't have to repeat the same query among the controllers' actions
Controller
public function __construct(UserRepository $userRepo)
{
$this->userRepo = $userRepo;
}
//controller action
public function index()
{
$admins = $this->userRepo->getAllAdmins();
}
Finally, let's suppose you need a query to count the number of the admin users. You could write this query in the UserRepository:
public function getAdminNum()
{
return User::where('type', 'admin')->count();
}
And it would be ok, but we can note that the User::where('type', 'admin') fragment of the query is shared with the query in getAllAdmins So we can improve this by using query scopes :
User Model
public function scopeAdmins($query)
{
return $query->where('type', 'admin');
}
by this, in the UserRepository methods we can rewrite our previous queries as:
public function getAllAdmins()
{
return User::admins()->get();
}
public function getAdminNum()
{
return User::admins()->count();
}
And i've just showed you a case in which a query would be writed inside a Model
You do not write any query in Model. Model is just for mapping the class that you are going to use for a table like User Model will be mapped to users (plural of model name).
You do not write queries in the routes closures like this
Route::get('/', ['as' => 'home', function(){
$totalProblems = Problem::count();
$solvedProblems = Problem::where('solved', 1)->get()->count();
$unsolvedProblems = Problem::where('solved', 0)->get()->count();
return view('Pages.index', ['totalProblems' => $totalProblems, 'solvedProblems' => $solvedProblems, 'unsolvedProblems' => $unsolvedProblems]);
}]);
This is considered as bad practice, its just for testing purposes.
You always write your queries in the controller method associated with your route like this
Route::get('test', 'HomeController#test');
and in your HomeController
<?php namespace App\Http\Controllers;
use App\Problem;
class HomeController extends Controller {
public function test(){
$user = User::all();
return view('Pages.test')->withUser($user); //or
return view('Pages.test')->with('user' , $user);
}
}
I'm pretty new to Laravel and am struggeling with the following problem at the moment:
I want to create a User-Group-Management. So I want to add Users to a Group and add Rights to these Groups, to have an authorization. My aim is to have a User::hasRight('exampleRight') function, which i can easily call. For that I wanted to have a function chaining inside of the User-Class. I have this function to create a Connection to the UserGroup-Table (which connects an User-ID to a Group-ID):
public function role()
{
return $this->hasOne('UserGroup');
}
The next function shall return an Array of rights. My plan was to write something like
public function rights()
{
$rights = Groups::find($this->role()->groups_id)->rights;
return $rights;
}
The GroupsModel of course has this function to get the Rights:
public function rights()
{
return $this->hasMany('Rights');
}
But obviously $this->role()->groups_id doesn't give me the groups_id but instead throws the Error Undefined property: Illuminate\Database\Eloquent\Relations\HasOne::$groups_id. When i leave the ->groups_id out, and do add it instead in the controller like:
(Attention Controller, no model!)
public function getUserRole()
{
return User::find(10)->rights->groups_id;
}
it gives me the right answer. Can somebody tell me, whats the error? I don't really get whats wrong...
Thanks you in advance!
Regards
In this particular method you need to remove the parenthesis:
public function rights()
{
$rights = Groups::find($this->role->groups_id)->rights;
return $rights;
}
When you do role() you're actually telling Laravel to return the relation object, so you can do things like
$this->role()->where('x', '=', 'y')->get();
Why do you have an intermediary table between User and Group? Simply give the User a group_id. Here is how I have built this in the past.
User belongsTo Group
Group belongsToMany Right
// Inside User Model
public function can($name)
{
foreach ($this->group->rights as $right)
{
if ($right->name == $name)
{
return true;
}
}
return false;
}
// Elsewhere
if (!$user->can('edit_blogs'))
{
return 'FORBIDDEN';
}
Look into eager loading to mitigate performance issues.
Look into filters to apply these access restrictions at the controller or route level.