Always eager load relation when using a trait in eloquent model - php

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');

Related

How is Model::find($id) secure in Laravel?

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
}

laravel using get() with specific role

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();

Refactor my Code implementing Repositories in Laravel 5

I'm refactoring all my DB Access related to my User Model in my Laravel app implementing Repositories.
So, now, all my DB Access ( I use Eloquent ) go through my UserRepository.
But I still have some eloquent operations in my model User:
class User extends Model{
....
public static function boot()
{
parent::boot();
static::creating(function ($user) {
$softDeletedUser = User::onlyTrashed()->where('email', '=', $user->email)->first();
if ($softDeletedUser != null) {
$softDeletedUser->restore();
return false;
} else {
$user->token = str_random(30);
if ($user->country_id == 0) {
$user->addGeoData();
}
}
return true;
});
I don't know what to do with that because:
It's about my User Model, not my Controller
It is a static method, so I don't know how to pass $this with (use)
I guess there is something wrong with that....
So... What should I do in this situation?
I don't think that you really need to move that query into a repository but it's up to you to decide. If you really need to reuse that code, add the query into a repository class and use it. Using the same query on 2 places isn't really a big deal but it might become a problem if you have more than 2 places.

Where do we have to write queries in Laravel: in model, controller or routes?

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);
}
}

Laravel: How to set $id to Auth::user()->id; in resource controller methods

I have Laravel's built in auth working. Users can register and login.
What I'd like to do is set the $id parameter for the UserController's show() method with the value from Auth::user()->id;
My thoughts behind this is, to not have to use id's in the routes.
I'm pretty new to OOP, and php in general, so I'm not sure how to tackle this.
Thanks in advance for any tips or help!
1st method
You can have a route with an optional user id:
Route::get('user/show/{id?}', 'UsersController#show')
If the show method of your controller doesn't get an id, it can use Auth::user() instead:
class UsersController extends BaseController {
public function show($id = null)
{
return View::make('userProfile')->with('user', $this->getCurrentUser($id));
}
public function getCurrentUser($id)
{
$this->currentUser = $id ? User::find($id) : Auth::user();
}
}
Then in your view you will be able to always
{{ $user->name }}
2nd method
You could also have a BaseController which does that automatically for you using View::share():
class BaseController extends Controller {
public function __construct()
{
parent::__construct();
$this->shareUser();
}
public function shareUser($id = null)
{
View::share('user', $id ? User::find($id) : Auth::user());
}
}
Then in your controller you don't need to pass the user:
class UsersController extends BaseController {
public function show()
{
return View::make('userProfile');
}
public function thisIsAMethodOverridingIt($id)
{
$this->shareUser($id);
return View::make('userProfile');
}
}
It would even better to have this provided by a Service, but you'll have to read about Service Providers and Facades to make it happen.
And you are still able to do that:
{{ $user->name }}
Because View::share() will send that variable to all your views.
3rd method
If you just need your user everywhere, use a global View::composer():
View::composer('*', function($view)
{
$view->with('currentUserName', Auth::check() ? Auth::user()->firstname : '');
});
You can put this in your routes.php or, better create a file for this purpose, something like app/composers.php and load it in your app/start/global.php:
require app_path().'/composers.php';
As always, you can use it in your view, this way:
{{ $user->currentUserName }}
If you just need it for a couple of views, you can
View::composer(array('profile','dashboard'), function($view)
{
$view->with('currentUserName', Auth::check() ? Auth::user()->firstname : '');
});
This is actually a recent problem that I encountered while working on an API.
The way I handled it, was to introduce /me endpoints, so for example, you'd have:
Route::group(['before' => 'auth'], function() {
Route::get('/user/show/{id}', 'UsersController#show');
Route::get('/me', 'UsersController#show');
}
You'll notice that both routes point to the same code, but have different addresses. This means that you can simplify requests by using the /me convention, without having to duplicate code. You'll also notice that I enclosed these in a group which applies the auth filter. This basically just makes sure that the user is authed, and while it may not be required for the first one, it'd definitely be required for the second.
Then your show method would look like this:
public function show($id = false)
{
$user = $this->getUserOrMe($id);
return View::make('myview', ['user' => $user]);
}
This would require the below function:
private function getUserOrme($id)
{
return $id !== false ? User::find($id) : Auth::user();
}
A controllers methods should be accessed independently of each other, meaning that once the User object is returned, all the relevant code for the current request has access to it. Storing the User object in a class property would just over engineering.
Hope that helps.

Categories