Laravel eager load a relationship based on the result of relationship method - php

I have the following entities:
User
class User extends Eloquent implements UserInterface, RemindableInterface {
use UserTrait, RemindableTrait;
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'members';
protected $primaryKey = 'member_id';
public function licences(){
return $this->hasMany('Licence', 'subid', 'member_id');
}
}
Licence
class Licence extends \Eloquent {
protected $table = 'licence';
protected $primaryKey = 'id';
protected $active = false;
const DATE_FORMAT = 'Y-m-d';
protected $fillable = [];
public function __construct(){
$this->checkifIsActive();
}
public function owner(){
return $this->belongsTo('User', 'member_id', 'subid');
}
public function checkifIsActive(){
if($this->start_date <= date($this->DATE_FORMAT) && $this->end_date >= date($this->DATE_FORMAT)) $this->active = true;
}
}
One user can have many licences, and the licenses that the user has may be either active or inactive - this is determined by a start and end date on the licence.
I'm trying to load a User object, and at the same time pull in their licences, but only those that are active.
Within the licence model, I am setting the 'active' variable to true, when the object is instantiated, so we have a way of knowing the status of the licence.
The code so far that I've tried is:
return User::findOrFail($id)->with('licence.active')->get();
However, this is not quite right - as there's no actual condition check done on the 'licence.active'.
How would I return a user, loaded by an ID, along with the licences they have associated that are have a boolean 'active' variable set to 'true'?

You can use eager loading constraints to query the relation like so;
$user = User::with(array('license' => function($query){
$query->where('start', '<=', Carbon::now())
$query->where('end', '>=', Carbon::now())
}))->find($id);
This will then only return the licenses that are active.
Optionally you could query the result of the relationship like so;
public function activeLicences(){
return $this->hasMany('Licence', 'subid', 'member_id')->where('start', '<=', Carbon::now())->where('end', '>=', Carbon::now());
}
Then all you would have to do is the following to get the results;
$user = User::with('activeLicenses')->find($id)
Please Note: This has not been tested.

Related

Scope and Relation doesn't work together - Laravel

User Model
class User extends Authenticatable
{
use HasApiTokens,HasFactory, Notifiable;
protected $table = "users";
protected $primaryKey = 'ID';
public function scopeActive($query)
{
return $query->where('activated', 1);
}
public function followings()
{
return $this->hasMany('App\Models\Contacts','user_id','ID')->active();
}
Contacts Model
class Contacts extends Model
{
use HasFactory;
protected $table = "contacts";
protected $primaryKey = 'ID';
public function user()
{
return $this->belongsTo(User::class, 'user_id', 'ID');
}
When I call User::find(1)->followings()->count() on Tinker I get
BadMethodCallException with message 'Call to undefined method Illuminate\Database\Eloquent\Relations\HasMany::active()'
How can I get followings whose activated = 1 only?
I fixed it by adding a custom scope to Contacts model
public function scopeActiveFollowings($query)
{
return $query->join('users', function($join)
{
$join->on('users.ID', '=', 'contacts.contact_id')
->where('activated', '=', 1);
});
}
Laravel model has a property called query.
This property is used automatically when you call a query for the model
these two statements are exactly the same:
User::query()->where('id',1)->get();
User::where('id',1)->get();
When you call query() directly or indirectly, Laravel register the scopes in this line
when you use $this->hasMany('App\Models\Contacts','user_id','ID') the result will be from class HasMany and this class doesn't have a method called active(), this is why you see this error.
to fix it, you have to apply the scope when you use User model query:
$result=User::query()->activated()->with('followings')->findOrFail(1);
if you want the followings count for the activated user using scope, you should apply this scope on Following Model:
public function scopeHasActivedUser($query)
{
return $query->whereHas('user', function($query)
{
$query->where('users.activated', 1);
});
}
In the controller:
$fllowingsCountForActivatedUser=Fllowing::query()->hasActivedUser()->count();
If I were you, I would get the result this way:
$user=User::withCount(['followings'=>function($query){
$query->where('users.activated', 1)})])->find(1);
now the $user model instance should have a property called 'followings_count' for the only activated followings count

How can I use soft deleted with relation

I use eloquent with soft delete .I'm getting error because of my query still select data that already use softdelete here is my model
User Model
class User extends Authenticatable
{
use Notifiable, HasRoles, SoftDeletes;
protected $guard_name = 'web';
protected $fillable = [
'username', 'password'
];
protected $dates = ['deleted_at'];
}
for example I've 100 user and I deleted 1 user with softdelete . then I try to
$a = User::all();
dd($a);
I get 99 user . It works! but after I use it relation It doest work here what I do
This is my Parent table and Model
table
|id|user_id|parent_id|
Note : user_id and parent_id are FK in user.id table
class Parent extends Model
{
protected $table = 'parent';
public function user()
{
return $this->belongsTo('App\User');
}
}
$getParent = Parent::with('user')->get();
when I dd($getParent); why I still get null data from user_id that I already use soft deleted ?
UPDATE model User : after I put whereNull I still getting user that I alredy soft deleted
public function user()
{
return $this->belongsTo('App\User')->whereNull('users.deleted_at');
}
https://laravel.com/docs/5.7/eloquent#querying-soft-deleted-models
...
public function customerAddress()
{
return $this->hasOne(Addresses::class, "id", "id_address")->withTrashed();
}
...
Ok, here's what I think is going on...
With soft delete the ondelete event doesn't work (meaning that related models is not deleted). I'm not sure if that changed in later versions of Laravel, but I don't think so. Also deleting User would still not affect the parent model, since you haven't defined the relationship between User and Parent (in the User model), only between Parent and User.
Try defining the relationship in User and then override the boot() function, that sits in the Model class. (This is untested code, but something like this should do the job)
class User extends Authenticatable
{
use Notifiable, HasRoles, SoftDeletes;
protected $guard_name = 'web';
protected $fillable = [
'username', 'password'
];
protected $dates = ['deleted_at'];
// Override Model boot function
protected static function boot()
{
parent::boot();
static::deleting(function ($users) {
foreach ($users->parents()->get() as $parent) {
$parent->delete();
}
});
}
// Define relationship with parent model
public function parents()
{
$this->hasMany('App\Parent');
}
}
You can put a constraint on the Eager Load:
public function groups()
{
return $this
->belongsToMany('Group')
->whereNull('group_user.deleted_at') // Table `group_user` has column `deleted_at`
->withTimestamps(); // Table `group_user` has columns: `created_at`, `updated_at`
}
Instead of HARD deleting the relationship using:
User::find(1)->groups()->detach();
You should use something like this to SOFT delete instead:
DB::table('group_user')
->where('user_id', $user_id)
->where('group_id', $group_id)
->update(array('deleted_at' => DB::raw('NOW()')));

Laravel Model without a Database?

I'm developing a system in which i have different roles for accesing. The role is present in the Users table as an integer value.
I've created a model called roles and in this model i dont need a database connection or a table. I did set some constant values representing roles matching the role field in the user table.
So what's the problem ? It seems that i can't have relations with the roles model if this hasn't a table associated in the database.
This is my roles model:
class Roles extends Model {
const ROL_ADMINISTRADOR = 1;
const ROL_DIRECTOR = 2;
const ROL_PROFESOR = 3;
const ROL_RECOPILADOR = 4;
private $rol_id;
private $roles = [
self::ROL_ADMINISTRADOR => 'Administrador',
self::ROL_DIRECTOR => 'Director',
self::ROL_PROFESOR => 'Profesor',
self::ROL_RECOPILADOR => 'Recopilador'
];
/**
* RELACIONES
*/
public function _usuarios() {
return $this->hasMany(Usuarios::class, "rol", "rol_id");
}
/**
* FORMATEADORES
*/
/**
* FUNCIONES
*/
public function nombre() {
return $this->roles[$this->rol_id];
}
}
This is my users model
class Usuarios extends Authenticatable {
use Notifiable;
const STATUS_ACTIVO = 1;
const STATUS_INACTIVO = 0;
protected $table = 'usuarios';
protected $primaryKey = 'usuario_id';
/*
* RELACIONES
*/
public function _rol() {
return $this->hasOne(Roles::class, 'rol', 'rol_id');
}
public function _perfil() {
return $this->hasOne(Perfiles::class, "usuario", "usuario_id");
}
/*
* FORMATEADORES
*/
}
But i got this error
Base table or view not found: 1146
When i try to do this
<?= Auth::user()->_rol->nombre() ?>
=========================================================================
I think i've found the solution.
I did the following changes:
1.- in the roles model I did add the constructor method and customized it
public function __construct($rol) {
parent::__construct();
$this->rol_id = $rol;
}
2.- In the users model change the method to this
public function _rol() {
return new Roles($this->usuario_id);
}
There is a thing here: if the method is called like this '_rol' the model will think is about a relation and will required a relation response so is neccesary to call it this way '_rol()' (with the parenthesis)
<?= Auth::user()->_rol()->nombre() ?>
You only need the Role model if you have another table to store the roles.
If you, instead, have the role represented as a field in the users table, you can access it just doing:
$role = $user->role;
I suggest you to use https://github.com/artesaos/defender to manage roles and permitions.

Obtaining a list of non-linked models, in many_to_many relations, using Laravel Eloquent

I have a User-Roles model, using Laravel 4 where a user can have many roles, using Eloquent. I can access all roles linked to a user easily using this code :
class User extends Model {
protected $table = 'user';
protected $fillable = array('name');
public function rolesLinked() {
return $this->hasMany('App\UserRoleLink', 'user_id');
}
}
I've been trying to obtain the roles that are not linked to a user, to display on the specific user's page in a select box. Using this function, included in the User class.
public function rolesNotLinked() {
$user = this
$roles = Roles::whereDoesntHave('App\UserRoleLink',function($query) use ($user){
$query->where('user_id',$user->id);
});
}
The problem is, calling this function gives me the following error.
Call to undefined method Illuminate\Database\Query\Builder::App\UserRoleLink()
I've tried using has with < 1 to see if the function was problematic, but after reading this and the online source code, the function call pretty much does what I've tried.
Is something wrong in my function call, or have I messed up configurations somewhere?
For reference, here are my other Model classes:
class UserRoleLink extends Model{
protected $table = 'user_role_link';
protected $fillable = array('role_id','user_id);
public function role() {
return $this->hasOne('App\Role', 'role_id');
}
}
class Role extends Model{
protected $table = 'role';
protected $fillable = array('name');
}
EDIT: I've found out that I messed up by fillables when I copy-pasted. It didn't fix the issue, but I guess that's one step closer.
To use whereDoesntHave method, you must add the relation in your Role Model.
class Role extends Model{
protected $table = 'role';
protected $fillable = array('name');
public function UserRoles() {
return $this->hasMany('App\UserRoleLink', 'id');
}
}
Also, the whereDoesntHave method first parameter is not thte model but the function of the relation:
public function rolesNotLinked() {
$user = this
$roles = Roles::whereDoesntHave('UserRoles',function($query) use ($user){
$query->where('user_id',$user->id);
});
}

Ordering of nested relation by attribute with laravel

Hi there i'm trying to sort a collection by attribute of the relation.
This is my model
class Song extends \Eloquent {
protected $fillable = ['title', 'year'];
public function artist(){
return $this->hasOne('Artist','id', 'artist_id');
}
}
class SongDance extends \Eloquent {
protected $table = 'song_dances';
protected $fillable = ['rating'];
public function dance(){
return $this->belongsTo('Dance', 'dance_id');
}
public function song(){
return $this->belongsTo('Song', 'song_id');
}
}
class Dance extends \Eloquent {
protected $fillable = ['name'];
public function song_dances(){
return $this->hasMany('SongDance','dance_id','id');
}
public function songs(){
return $this->belongsToMany('Song', 'song_dances', 'dance_id', 'song_id');
}
}
this is how far i'm by now:
$dance = Dance::find(1);
$songs = $dance->songs()
->with('artist')->whereHas('artist', function ($query) {
$query->where('urlName','LIKE','%robbie%');})
->where('song_dances.rating', '=', $rating)
->orderBy('songs.title','asc')
->where('songs.year', '=', 2012)
->get();
Yeah i just could add a ->sortBy('artist.name'); to the query, but he result-collection can be quite big (about 6000 items) therefore i would prefer a databased sorting.
is there a possibility to do this?
Since Eloquent's relations are all queried separately (not with JOINs), there's no way to achieve what you want in the database layer. The only thing you can do is sort the collection in your app code, which you've already dismissed.
If you feel you must sort it in the database then you should write your own join queries instead of relying on Eloquent's relations.

Categories