How with pivot relation set additive filter? - laravel

In laravel 9 app I use many to many relation with table article_vote joining 2 tables :
return new class extends Migration {
public function up()
{
Schema::create('article_vote', function (Blueprint $table) {
$table->id();
$table->foreignId('article_id')->references('id')->on('articles')->onUpdate('RESTRICT')->onDelete('CASCADE');
$table->foreignId('vote_id')->references('id')->on('votes')->onUpdate('RESTRICT')->onDelete('CASCADE');
...
});
}
In app/Models/Article.php model I have relation :
public function onlyActiveVotes(): BelongsToMany
{
return $this->belongsToMany(Vote::class, 'article_vote', 'article_id')
->withTimestamps()
->wherePivot('active', true)
->withPivot(['active', 'expired_at', 'supervisor_id', 'supervisor_notes']);
}
and I want having Article by Id($id)
$article = Article::getById($id)
->firstOrFail();
Using onlyActiveVotes relation to get filtered data from vote :
$voteTableName = ((new Vote)->getTable());
$articleVotes = $article->onlyActiveVotes()::whereHas('votes', function ($query) use($request, $voteTableName) {
$query->where($voteTableName . '.vote_category_id', $request->filter_vote_vote_category_id);
})->get();
It does not work, as I got error :
Method Illuminate\Database\Eloquent\Relations\BelongsToMany::whereHas does not exist. i
Line below returns collection
dd($article->onlyActiveVotes );
Line below returns BelongsToMany:
dd($article->onlyActiveVotes() );
Illuminate\Database\Eloquent\Relations\BelongsToMany {#2283 // app/Repositories/ArticleToManyVotesRepository.php:74
#query: Illuminate\Database\Eloquent\Builder {#2218
#query: Illuminate\Database\Query\Builder {#2268
On
dd($article->onlyActiveVotes()->query );
I got error:
Cannot access protected property Illuminate\Database\Eloquent\Relations\BelongsToMany::$query
If there is a way to use whereHas with onlyActiveVotes relation ?
Updated BLOCK :
I hope I clearly exoplaned what I want : want to get only filtered votes which arerelated with pivot through $article model
Aftet I fixed :
$filteredArticleVotes = $article->onlyActiveVotes()->whereHas
I got other error :
Call to undefined method App\Models\Vote::onlyActiveVotes()
pointing at line
$filteredArticleVotes = $article->onlyActiveVotes()->whereHas('onlyActiveVotes', function ($query) use($request, $voteTableName) {
$query->where($voteTableName . '.vote_category_id', $request->filter_vote_vote_category_id);
})->get();
As I wrote in my post Article model has onlyActiveVotes method and I expected the code above have to work, but it did not...
Thanks!

Since onlyActiveVotes() already returns a query builder for the votes table, you can directly chain the where() method to filter the results by the vote_category_id column. The whereHas() method is not necessary in this case.
$articleVotes = $article->onlyActiveVotes()
->where('vote_category_id', $request->filter_vote_vote_category_id)
->get();

Related

How to use custom model function in eloquent

I want to get all user's online friends, how can I call a custom model function inside the eloquent condition?
this is my code
$friends = $user->friends()->where(function (Builder $query){
$query->where('friend', 'yes');
})
->get();
and this is my function in model
public function getIsOnlineAttribute(): bool
{
// check if the user is online or not
return $this->is_online;
}
I can access is_online after eloquent by foreach, but in my case, I want to check everything in one step ( inside where condition in eloquent). how can I do that???
You can't use conditions for eloquent accessors, in this case you can use (assume 1 is database column value):
$friends = $user->friends()->where('is_online', 1)->get();
or
$friends = $user->friends()->whereIsOnline(1)->get();
or you can create eloquent scope on your model:
public function scopeIsOnline($query) {
$query->where('is_online',1);
}
and you can use this eloquent scope on your controller in this way:
$friends = $user->friends()->isOnline()->get();
this worked for me :)
$friends = $user->friends()
->simplePaginate()
->reject(function ($friend) {
return $friend->is_online === false;
});

How can I get a query with a conditional in a third relation?

Im using Laravel 7 and to get an array like this Array image spect result
And im working with Eloquent. This is my code for that array
**return EvaluateTeacherQuestion::with('evaluate_teacher_possibles_answers')->get();**
And this is my function evaluate_teacher_possibles_answers to make the relationship
public function evaluate_teacher_possibles_answers()
{
return $this->hasMany(EvaluateTeacherPossibilites::class)->withCount('evaluate_teacher_answers')->with('evaluate_teacher_answers');
}
And to get the third condition use this
public function evaluate_teacher_answers()
{
return $this->hasMany(EvaluateTeacherAnswer::class, 'evaluate_teacher_possible_id');
}
The problem is in the table evaluate_teacher_answers, I need get only this that if a condition (teacher_id = $teacher) is right.
When you add the whereHas condition a subquery is added.
Eg:
EvaluateTeacherQuestion::with('evaluate_teacher_possibles_answers')->whereHas('evaluate_teacher_possibles_answers',function($query){
$query->where('column','operation','value');
})->get();
With this way, will included relation in model and will execute conditions on closure from related model
Try this way:
remove with('evaluate_teacher_answers') from evaluate_teacher_possibles_answers relation:
public function evaluate_teacher_possibles_answers()
{
return $this->hasMany(EvaluateTeacherPossibilities::class)->withCount('evaluate_teacher_answers')->;
}
then load that relation with your condition:
$value = EvaluateTeacherQuestion::with(['evaluate_teacher_possibles_answers'=>function($query)use($teacher_id)
{
$query->with(['evaluate_teacher_answers'=>function($query)use($teacher_id){
$query = $query->where('evaluate_teacher_answers.teacher_id',$teacher_id);
}]);
}])->get();

Return Related Books in Laravel

I want to return a book related to a book using the author's name
I have this function in my Controller
public function show(Book $book) {
$book = Book::where('id', '=', $book)->first();
$related = Book::whereHas('author_id', function ($q) use ($book) {
return $q->whereIn('name', $post->author->pluck('id'));
})
->where('id', '!=', $book->id) // So I won't fetch same post
->get();
return view('book')
->with($book)
->with($related);
}
This is the way my book table looks like
public function up()
{
Schema::create('books', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->text('about');
$table->string('image');
$table->string('image_url');
$table->string('epub_url');
$table->integer('author_id');
$table->string('publisher');
$table->year('year');
$table->boolean('recommended')->default(0);
$table->timestamps();
});
}
I have done this in my models
public function books()
{
return $this->hasMany(Book::class);
}
In Bookmodel
public function author()
{
return $this->belongsTo(Author::class);
}
I have done this, and this is not working for me, as I don't know what I am doing wrong. Thanks.
Once you have your $book, you can easily load other books by the same author by querying the author_id field. Just make sure to exclude the original book from the query, and you're good to go.
$relatedBooks = Book::where('author_id', $book->author_id)
->where('id', '!=', $book->id)
->get();
As a side note, you're already passing an instance of Book in your method arguments, so the first line of your code ($book = Book::where('id', '=', $book)->first();) is redundant, you can simply use $book.
You are querying the wrong way. what you can do is first get the book with author as given,
$book = Book::with(['author'])->where(['id' => $BOOK_ID])->first();
then you can load the similar books from the same authors as given
$similarBooks = Book::whereHas('author' => function($query) use ($book){
$query->where(['id' => $book->author->id]);
})->where('id' , '!=' , $book->id)->get();
Alternatively you can load by the author's name by replacing the relation's query.

Laravel: How to get data from 3 tables with relationship

I have 3 Tables:
Customers
id
name
Sales
customer_id
sale_date
Contacts
customer_id
contact_date
There aren't any update operations in the contacts table. Each process opens a new record in the contacts table. So, a user can have more than one records in the contacts table.
Here are my relations in models:
Customer
public function contacts()
{
return $this->hasMany(Contact::class);
}
public function sales()
{
return $this->hasMany(Sale::class);
}
Contact
public function customer()
{
return $this->belongsTo('App\Customer', 'customer_id');
}
Sale
public function customer()
{
return $this->belongsTo('App\Customer');
}
I would like to have the latest record of the contacts table and make it join with the other related tables.
Here is the query which I have tried:
$record = Contact::groupBy('customer_id')
->select(DB::raw('max(id)'));
$result = Customer::query();
$result->where('is_active', 'YES');
$result->with('sales');
$result->whereHas('contacts', function ($q) use($record){
return $q->whereIn('id', $record)->where('result', 'UNCALLED');
});
return $result->get();
In the blade file, I get some result in foreach loops. However, I am unable to get the related data from the sales and contacts table.
#foreach($result as $item)
#foreach($item->sales as $sale) // Has no output and gives error: Invalid argument supplied for foreach()
#foreach($item->contacts as $contact) // Has no output and gives error: Invalid argument supplied for foreach()
Can anyone help me how to display the sale and contact date? Or any idea for how to improve this code quality?
If you want the latest record of the contacts you can declare another relationship on the Customer model, e.g.:
public function latest_contact()
{
return $this->hasOne(Contact::class)->latest('contact_date');
}
BTW you can always declare one or more hasOne additional relationship if you have a hasMany in place the foreign key used is the same.
In this way you can retrieve latest_contact eager loaded with your Customer model:
$customer = Customer::with('latest_contact')->find($id);
Or use this relationship in your queries, something like that:
$customers = Customer::where('is_active', 'YES')
->with('sales')
->with('contacts')
->whereHas('last_contact', function ($q){
return $q->where('result', 'UNCALLED');
})->get();
Or that:
$customers = Customer::where('is_active', 'YES')
->with('sales')
->with('contacts')
->with('last_contact', function ($q){
return $q->where('result', 'UNCALLED');
})->get();
If you want you can declare last_contact with the additional where:
public function latest_contact()
{
return $this->hasOne(Contact::class)
->where('result', 'UNCALLED')
->latest('contact_date');
}
This way all other queries should be easier.
I hope this can help you.
I'm not sure, but can you try to do the following:
return Customer::where('is_active', 'YES')
->with([
'sale',
'contact' => function ($query) use($record) {
return $query->whereIn('id', $record)->where('result', 'UNCALLED');
}
])->get();

non trivial relationship in eloquent

i get:
Relationship method must return an object of type
Illuminate\Database\Eloquent\Relations\Relation
code of model:
class Order extends Model{
public function order_status(){
$q = self::GetQueryWithCurrentOrderStatus();
return $q->where('order.id', '=', $this->id)->get();
}
private static function GetQueryWithCurrentOrderStatus(){
$rawSql = OrderOrderStatus::selectRaw('order_order_status.order_id as id, max(created_at)')->groupBy('order_order_status.order_id')->toSql();
$query = OrderStatus::join('order_order_status', 'order_order_status.order_status_id', '=', 'order_status.id')
->join('order', 'order.id', '=', 'order_order_status.order_id')
->join(DB::raw('( ' . $rawSql . ') CurrentOrderStatus'), function ($join) {
$join->on('order_order_status.id', '=', 'CurrentOrderStatus.id');
});
return $query;
}
}
db structure is written in the answer here:
https://dba.stackexchange.com/questions/151193/good-database-structure-for-scenario-with-orders-that-have-a-state-and-the-state/151195#151195
order_status_history is order_order_status
now i could write in the blade file just:
$order->order_status() instead of $order->order_status ... but why? is there a solution?
If you're trying to call a method, call a method. order_status isn't a property.
If you access it as a property, it requires an Eloquent relationship (like it says) which are created through the hasOne, hasMany, belongsTo, belongsToMany methods: https://laravel.com/docs/master/eloquent-relationships

Resources