How to Shortcircuit a Laravel Query - laravel

Within an Eloquent model, I have a scope which I want to return records if the current user has a certain role and nothing if he does not.
I am looking for a way to 'shortcircuit' the query and return an empty array. For now, I have used the following but I feel there must be a better way:
public function scopeForClient($query, User $user)
{
if ($user->hasRole('client')) {
return $query->whereIn('client_id', $user->id);
} else {
return $query->where('id', false);
}
}

I would leave only this part in your scope return $query->whereIn('client_id', $user->id);
//User model
someFilterFunction($query){
if ($user->hasRole('client'))
return $query->forClient();
return [];
}
//Other parts of your application
$result = someFilterFunction($query);
if (!empty($result)) {
// do something with query Builder instance
}
I would avoid doing additional query to database here $query->where('id', false) when there is no need and you know it will return empty result;

Related

Laravel Model request with several joins

While I am able to make simple requests with Model, I can't say the same for more complicated ones.
I know I don't necessarily have to use Model and can use DB facade but still, I want to know how it's supposed to be done.
Here's a request I made using DB :
DB::table('relationships')
->Join('users','users.id','=','relationships.user_id')
->Join('roles','roles.id','=','relationships.role_id')
->Join('bundles','bundles.id','=','relationships.related_id')
->Join('pools','bundles.id','=','pools.bundle_id')
->whereIn('pools.name',$pools)
->whereIn('roles.name',$roles)
->select('users.first_name','users.last_name','users.mail_address','roles.name AS role_name','bundles.name AS bundle_name', 'pools.name AS pool_name')
->get();
On a first attempt, I tried this:
User::whereHas('relationships', function($req) use($roles) {
$req->whereHas('bundle', function($req){
$req->whereIn('name', $pools);
});
$req->whereHas('role', function ($req){
$req->whereIn('name', $roles);
});
})
->with('relationships', 'relationships.role:id,name', 'relationships.bundle:id,name')
->get();
}
Problem is, using "with" just select everything unconditionally, ignoring previous tests you made earlier (whereHas, whereIn).
So I'd have to again filter on each table in the with statement.
Then I ended up doing this:
$pools = request()->input('pools.*.name');
return $prepReq = User::whereHas('relationships', function($req) use($pools, $roles) {
$req->whereHas('bundle', function($req) use ($pools){
$req->whereHas('pools', function($req) use ($pools){
$req->whereIn('name', $pools);
});
});
$req->whereHas('role', function ($req){
$req->whereIn('name', $roles);
});
})
->with(['relationships' => function ($query) use($pools, $roles){
$query->whereHas('role', function ($query){
$query->whereIn('name', $roles)
->select('id','name');
})->select('id','name');
}])
->get(['id', 'first_name', 'last_name', 'mail_address']);
Then I got lost into this and gave up.
Another thing that made me sweat is that when you go nested using "with", you can select columns only on the last table.
For example: "relations.bundle.pools" => I can select columns on pools but not on relationships or bundles, does that mean i have to imbricate with statements for each table ?
As you can see, I am a bit clueless on how things are supposed to be done
I would like any advice or help regarding this matter
Thanks in advance for your time
When using Laravel, you should be setting up the eloquent relationships for each model
Based on your select statement from above, and assuming you want to get the user, bundle, role, and pool, then I would do the following, may be off depending on how your actual DB and models are set up
// Relationship.php
public function user()
{
return $this->belongsTo(User::class);
}
public function role()
{
return $this->belongsTo(Role::class);
}
public function bundle()
{
return $this->belongsTo(Bundle::class, 'id', 'related_id');
}
// Bundle.php
public function relationship()
{
return $this->hasOne(Relationship::class, 'related_id');
}
// Role.php
public function relationship()
{
return $this->hasOne(Relationship::class);
}
// Pool.php
public function bundle()
{
return $this->belongsTo(Bundle::class);
}
Then you could do something like
Pool::with('bundle.relationship.user')->whereIn('name', $pools);
Role::with('relationship.user')->whereIn('name', $roles);

How to only get users of a certain age based on Date of Birth

I have a User model with a method to get the age.
public function age() {
return $this->date_of_birth->diffInYears(\Carbon\Carbon::now());
}
That works fine.
In a controller I'm trying to get all users over 16, but the issue is users don't need to enter their DOB on signup, only later in the app. How to I retrieve records of all users of a certain age, and discount null entries.
I've tried this
public function index(){
$users = User::whereNotNull('date_of_birth')->take(10);
if(!empty($users)){
$validusers = $users->age();
}
return view('egistrations.index', compact('validusers'));
}
But i get the following error
Call to undefined method Illuminate\Database\Query\Builder::age()
public function index(){
$users_with_age = User::whereNotNull('date_of_birth')
->take(10)
->get()
->each(function ($user) {
$user->age = $user->age();
});
return view('egistrations.index', compact('users_with_age'));
}
public function index(){
$users = User::whereNotNull('date_of_birth')->take(10);
if(!empty($users)){
$validusers = $users->get();
}
return view('egistrations.index', compact('validusers'));
}
you need to use get() as above or you can use all()
You forgot to add get() in your query
$users = User::where('date_of_birth','!=',NULL)->take(10)->get();
You have use map() to use $user->age
public function index(){
$users = User::whereNotNull('date_of_birth')->take(10)->get();
if(!empty($users)){
$validusers = $users->map(function($user){
return $user['age'] = $user->age();
});
}
return view('egistrations.index', compact('validusers'));
}

Laravel if no data from select many to many relation

How to check whether there's data from database or not
My DB looks like this
My user Model
public function monsters()
{
return $this->belongsToMany('App\Monster')->orderBy('star');
}
My Controller
public function list_monster_user()
{
//GET USER LOGIN ID
$id = Auth::user()->id;
$monster_user = User::find($id);
//IF DATA IS NOT EMPTY OR USER HAVE MONSTERS
if(!is_null($monster_user->monsters))
{
return 'yes';
}
//IF DATA IS EMPTY OR USER DOESNT HAVE ANY MONSTER
else
{
return 'nope';
}
}
I'm using ajax, so the result will be return to ajax.
I don't get the return I want. I know something's wrong with my if statement.
But I couldn't figure it out.
Since you are not following naming conventions, you should specify all fields in belongsToMany function. Try this:
return $this->belongsToMany('App\Monster', 'karakter_has_monster', 'karakter_id', 'monster_id');
And to check for results, you could use count(). It would be faster.
if($monster_user->monsters()->count() > 0) {
//has monsters
}

Laravel oneToMany accessor usage in eloquent and datatables

On my User model I have the following:
public function isOnline()
{
return $this->hasMany('App\Accounting', 'userid')->select('rtype')->latest('ts');
}
The accounting table has activity records and I'd like this to return the latest value for field 'rtype' for a userid when used.
In my controller I am doing the following:
$builder = App\User::query()
->select(...fields I want...)
->with('isOnline')
->ofType($realm);
return $datatables->eloquent($builder)
->addColumn('info', function ($user) {
return $user->isOnline;
}
})
However I don't get the value of 'rtype' for the users in the table and no errors.
It looks like you're not defining your relationship correctly. Your isOnline method creates a HasMany relation but runs the select method and then the latest method on it, which will end up returning a Builder object.
The correct approach is to only return the HasMany object from your method and it will be treated as a relation.
public function accounts()
{
return $this->hasMany('App\Accounting', 'userid');
}
Then if you want an isOnline helper method in your App\User class you can add one like this:
public function isOnline()
{
// This gives you a collection of \App\Accounting objects
$usersAccounts = $this->accounts;
// Do something with the user's accounts, e.g. grab the last "account"
$lastAccount = $usersAccounts->last();
if ($lastAccount) {
// If we found an account, return the rtype column
return $lastAccount->rtype;
}
// Return something else
return false;
}
Then in your controller you can eager load the relationship:
$users = User::with('accounts')->get(['field_one', 'field_two]);
Then you can do whatever you want with each App\User object, such as calling the isOnline method.
Edit
After some further digging, it seems to be the select on your relationship that is causing the problem. I did a similar thing in one of my own projects and found that no results were returned for my relation. Adding latest seemed to work alright though.
So you should remove the select part at very least in your relation definition. When you only want to retrieve certain fields when eager loading your relation you should be able to specify them when using with like this:
// Should bring back Accounting instances ONLY with rtype field present
User::with('accounts:rtype');
This is the case for Laravel 5.5 at least, I am not sure about previous versions. See here for more information, under the heading labelled Eager Loading Specific Columns
Thanks Jonathon
USER MODEL
public function accounting()
{
return $this->hasMany('App\Accounting', 'userid', 'userid');
}
public function isOnline()
{
$rtype = $this->accounting()
->latest('ts')
->limit(1)
->pluck('rtype')
->first();
if ($rtype == 'Alive') {
return true;
}
return false;
}
CONTROLLER
$builder = App\User::with('accounting:rtype')->ofType($filterRealm);
return $datatables->eloquent($builder)
->addColumn('info', function (App\User $user) {
/*
THIS HAS BEEN SUCCINCTLY TRIMMED TO BE AS RELEVANT AS POSSIBLE.
ARRAY IS USED AS OTHER VALUES ARE ADDED, JUST NOT SHOWN HERE
*/
$info[];
if ($user->isOnline()) {
$info[] = 'Online';
} else {
$info[] = 'Offline';
}
return implode(' ', $info);
})->make();

Return null from eloquent relationship?

I have this relationship in one of my model classes:
public function userLike()
{
if (Auth::check()) return $this->hasOne('App\Like')->where('user_id', Auth::user()->id);
// RETURN NULL IF RECORD DOESN'T EXIST?
}
As you can see, it checks if the user is logged in and returns the Like record. However, how can I return null if the record doesn't exist?
I tried this:
public function userLike()
{
if (Auth::check()) return $this->hasOne('App\Like')->where('user_id', Auth::user()->id);
return null;
}
But I get the error:
local.ERROR: exception
'Symfony\Component\Debug\Exception\FatalErrorException' with message
'Call to a member function addEagerConstraints() on null' in
/var/www/social/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php:680
As a side question, am I doing this right? Is this the correct way of doing this or is there a better way?
I'm not sure it is the right way to do this. In your model you should only declare your relationships:
public function userLike()
{
return $this->hasOne('App\Like');
}
And then in a controller or something you go get the like of your user like this:
class LikesController extends Controller {
public function getLike()
{
$like = null;
if (Auth::check()) {
$like = Like::where('user_id', Auth::user()->id)->first();
}
}
}
So we have a getLike() function that get the like of a user from your model Like where the user_id is equal to the authenticated user's id.
Hope it helps!
I'm not sure! But I think it is possible to use count! When it returns 0 you know that there are no records, so you can return null.
You can just use this style in your Like Model :
// return the number of likes
function userLikes()
{
$count = $this->where('user_id', $this->post_id)->count();
// check if not null
return $count ? $count : 0; // if null return 0
}

Resources