Laravel combine multiple scope - laravel

I have a posts table and every post can have 0, 1 or more rows into channels (table channels with post_id in it).
The posts table also has a a privacy field set to 0, 1 or 2.
I want to list specific posts so I defined scopes inside the Post model:
public function scopeNonPrivate($builder)
{
return $builder->where(function ($q) {
$q->where('post_privacy', '!=', Post::PRIVACY_ME);
$q->where('user_id', '!=', auth()->id());
})->orWhere(function ($q) {
$q->where('post_privacy', '=', Post::PRIVACY_ME);
$q->where('user_id', '=', auth()->id());
});
}
public function scopeNonZero(Builder $builder)
{
return $builder->whereHas('channels', function ($query) {
$query->where('channel_id', '!=', 0);
})->orWhereDoesntHave('channels');
}
public function scopeFromFriends(Builder $builder, $friends)
{
return $builder->whereIn('user_id', $friends);
}
I want to combine these scopes to that I can find all post fromFriends, which have a channel different from 0 or which do NOT have a channel, and with the privacyOnlyFriends.
When I combine these the result is not correct, this is what I do:
$post = Channel::find(1)
->posts()
->fromFriends([$friends->pluck('user_id'), $friends->pluck('friend_id')])
->nonZero()
->nonPrivate();
I defined in App\Channel:
public function posts()
{
return $this->belongsToMany(Post::class, 'post_channels', 'channel_id', 'post_id');
}
And I defined in App\Post:
public function channels()
{
return $this->belongsToMany(Channel::class, 'post_channels', 'post_id', 'channel_id');
}
Is there a way to keep data from the different scopes and merged together so that for example fromFriends() does not override nonPrivate() but they are merged?

You can combine multiple scopes, below is an example from my class:
public function scopeGetCommentsOn($queryBuilder, $table, $tableIdentifier)
{
return $queryBuilder->where('table_name', $table)
->where('table_identifier', $tableIdentifier);
}
public function scopeCommentsOnShipments($queryBuilder, $tableIdentifier)
{
return $queryBuilder->getCommentsOn('shipments', $tableIdentifier);
}
In the above example, I am using scope getCommentsOn in commentsOnShipments scope.
Hope this will help you.

Related

How can i use related model scope inside whereHas Laravel 8

I have two models. Task and TaskCheck
in TaskCheck i have
class TaskCheck extends Model
{
public function task(): BelongsTo
{
return $this->belongsTo(Task::class);
}
public function owner(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id', 'id');
}
public function scopeOwnedBy(Builder $query, int $userId): Builder
{
return $query->where('user_id', '=', $userId);
}
}
in Task model i have
class Task extends Model
{
public function taskCheck(): HasMany
{
return $this->hasMany(TaskCheck::class)->with(['taskState', 'owner']);
}
}
And would like to use something like this:
public function scopeHasOwner(Builder $query, int $taskOwnerId): Builder
{
return $query->whereHas('taskCheck', function ($q) use ($taskOwnerId) {
$q->hasOwner($taskOwnerId);
});
}
however this throws exception Call to undefined method App\Models\Task::hasOwner() as it seems inner query is not aware of Task model.
I know I could use this instead and it works
public function scopeHasOwner(Builder $query, int $taskOwnerId): Builder
{
return $query->whereHas('taskCheck', function ($q) use ($taskOwnerId) {
$q->where('user_id', '=', $taskOwnerId);
});
}
but i would rather not repeat the where clause in every related model, because there are more related models deeper in relationships which would use similar functionality and i would like to have it on one place only.
In your TaskCheck model, you have ownedBy() scope, but you called hasOwner() in the whereHas query.
Change your query to ownedBy()
$query->whereHas('taskCheck', function ($q) use ($taskOwnerId) {
$q->ownedBY($taskOwnerId);
});

Laravel : Search Query Within Relationship

When I am adding a new post in my app there is a 7 tables to affect when I add single post. To fetch all posts with all post data my simple query look like:
$userPost = Post::with(['product','postattribute.attribute.category','user.userDetails'])
->offset($offset)
->limit($limit)
->whereStatus("Active")
->whereIn('product_id', $userApprovalProductIDs)
->orderBy('id','desc')
->get();
So it is retrun all data which I want. Now I want to implement search query within all tables, currently I am able to search only posts table.
If I am doing search on category table with categoryTitle I am trying to code like
where('category.title','=', $serachTitle)
But it is not working in my case.
POST model relationship :
public function user() {
return $this->belongsTo(User::class);
}
public function product() {
return $this->belongsTo(Product::class);
}
public function postattribute() {
return $this->hasMany(PostAttribute::class);
}
POSTATTRIBUTES model relationship :
public function post() {
return $this->belongsTo(Post::class);
}
public function attribute() {
return $this->belongsTo(Attribute::class);
}
ATTRIBUTES model relationship :
public function category() {
return $this->belongsTo(Category::class);
}
public function attributes() {
return $this->belongsTo(Attribute::class);
}
How can I do this ?
To apply filter on your nested relations you could use whereHas
$userPost = Post::with(['product','postattribute.attribute.category','user.userDetails'])
->offset($offset)
->limit($limit)
->whereStatus("Active")
->whereIn('product_id', $userApprovalProductIDs)
->whereHas('postattribute.attribute.category', function ($query) use($serachTitle) {
$query->where('title', '=', $searchTitle);
})
->orderBy('id','desc')
->get();
Querying Relationship Existence
From comments what i understood is you want to know how to search within each relation for a post , I already added an example to search with category title
->whereHas('postattribute.attribute', function ($query) use($var) {
$query->where('some_field_of_attribute_table', '=', $var);
})
->whereHas('postattribute', function ($query) use($var) {
$query->where('some_field_of_postattribute_table', '=', $var);
})

Relationships in Laravel Models

How can I write this without a join for a scope in a Laravel model as the 'id' field becomes ambiguous
/**
* Return events that social category name
*/
public function scopeWithSocialEvents($query)
{
return $query->join('categories', 'events.event_type_id', '=', 'categories.id')
->where('categories.name', 'social');
}
Use this:
public function eventType()
{
return $this->belongsTo(Category::class);
}
public function scopeWithSocialEvents($query)
{
return $query->whereHas('eventType', function($query) {
$query->where('name', 'social');
});
}

Laravel: Combining multiple query scopes on nested relationships without overriding the previous ones

I've got the following database structure for an online learning platform: A course consists of modules. A module consists of lessons and tasks. There are user specific information for each of these elements with the respective relationships defined on each model:
class Course extends Model {
public function user() {
return $this->belongsToMany(User::class)->withPivot('start_at', 'end_at');
}
public function modules() {
return $this->hasMany(Module::class);
}
// ...
}
class Module extends Model {
public function user() {
return $this->belongsToMany(User::class)->withPivot('start_at', 'end_at');
}
public function lessons() {
return $this->hasMany(Lesson::class);
}
public function tasks() {
return $this->hasMany(Lesson::class);
}
}
class Lesson extends Model {
public function user() {
return $this->belongsToMany(User::class)->withPivot('favourite', 'completed_at');
}
}
class Task extends Model {
public function user() {
return $this->belongsToMany(User::class)->withPivot('completed_at');
}
}
My goal is to load a complete course with the following scopes on the course model:
class Course extends Model {
// ...
public function scopeWithModules($query) {
return $query->with(['modules' => function ($q) {
$q->with(['user' => function ($q) { $q->where('user_id', auth()->user()->id); }])
->whereHas('user', function ($q) { $q->where('user_id', auth()->user()->id); });
}]);
}
public function scopeWithLessons($query) {
return $query->with(['modules.lessons' => function ($q) {
$q->with(['user' => function ($q) { $q->where('user_id', auth()->user()->id); }])
->whereHas('user', function ($q) { $q->where('user_id', auth()->user()->id); });
}]);
}
public function scopeWithTasks($query) {
return $query->with(['modules.tasks' => function ($q) {
$q->with(['user' => function ($q) { $q->where('user_id', auth()->user()->id); }])
->whereHas('user', function ($q) { $q->where('user_id', auth()->user()->id); });
}]);
}
// ...
}
Each of these scopes alone is working fine. But combining them will override the previous ones, so only the last of these scopes will be executed.
Executing this
$course = Course::whereSlug('learning-abc')
->withModules()
->withLessons()
->withTasks()
->firstOrFail();
will give me the course with modules and tasks (but no lessons and user information for the modules).
Of course I can write one single bulky scope like withModulesLessonsTasks() but I hope there is a more elegant solution to this. Any ideas?
Before I had:
$course = Course::whereSlug('learning-abc')
->with([
'modules.user',
'modules.lessons.user',
'modules.tasks.user'
])
->firstOrFail();
which was working fine except for filtering the auth()->user().

Eloquent hasMany filter and eager loading

I'm creating an application with Laravel and I'm trying to work with Eloquent. I have two tables : Orders and Items.
Each items has a type (int data) :
1 => book
2 => video
Each order has ONE book and many videos.
In my Order model, I would like to have a book and others items relationships. So, I have this code :
public function book()
{
return $this->hasMany('App\Item')->where('type', 1)->first();
}
public function others()
{
return $this->hasMany('App\Item')->where('type', '!=', 1)->get();
}
But, if I use eager loading with my relationship, I got an error :
Order::with(['book', 'others'])->get();
Can you help me to solve this problem ?
Thanks
Define relationships like this to make your code work:
public function book()
{
return $this->hasOne('App\Item')->where('type', 1);
}
public function others()
{
return $this->hasMany('App\Item')->where('type', '!=', 1);
}
But it's better to define relationships without where constraints:
public function book()
{
return $this->hasOne('App\Item');
}
public function others()
{
return $this->hasMany('App\Item');
}
And then do this:
Order::with(['book' => function ($q) {
$q->where('type', 1);
},
'others' => function ($q) {
$q->where('type', '!=', 1);
}])
->get();

Resources