Return Related Books in Laravel - 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.

Related

How with pivot relation set additive filter?

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

filtering the most likes withcount not working

All the rest of the functionality works fine, follow and latest but for some reason it won't filter the likes. I think it is the count method, but if I do likes->count() I end up with too many queries, any solutions for this?
public function index(Request $request) {
$search = request()->input('title');
$posts = Post::query()->latest();
if ($search) {
$posts->where('title', 'LIKE', '%' . request()->title. '%');
} elseif ($request->has('likes')) {
$posts->withCount('likes')->orderByDesc('likes_count');
} elseif ($request->has('latest')) {
$posts->orderByDesc('created_at');
} elseif ($request->has('follow')) {
$posts->whereIn('user_id', $request->user()->following()->pluck('users.id'));
}
return view('welcome', [
'posts' => $posts->withCount(['comments', 'likes'])->paginate(12)
]);
}
Schema::create('likes', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->foreignId('post_id')->constrained()->onDelete('cascade');
$table->timestamps();
});
This is the issue
$posts = Post::query()->latest();
"->latest()" is already sorting by date.
If you take it out, it should work just fine.

how to retrieve limited number of related model and sort collection by related model in laravel?

I have 3 model
Shop model, Product model, Picture model
I want to retrieve a collection of shops with last 3 Product model with their pictures and sort my collection based on newest product.
I tried leftjoint and joint in laravel 6 to be able to sort the result but i get all shops`product (i only need last 3 product for each shop),
when I use joint I cant retrieve product pictures
I also have tried “with” method in laravel , I couldnt sort the result based on product.creatred_at and also i get all related product in this method too.(as i mentioned i need the last 3 product)
class Shop extends Model
{
public function products()
{
return $this->hasMany('App\Product');
}
}
class Product extends Model
{
public function shop()
{
return $this->belongsTo('App\Shop');
}
public function pictures()
{
return $this->morphMany('App\hPicture', 'pictureable');
}
}
Shop::select('shops.*', 'products.id', 'products.shop_id', 'products.name as pname', 'products.user_id', 'products.code', 'products.price')
->with(['pictures', 'products.pictures'])
->leftjoin('products', function ($leftJoin) {
$leftJoin->on('shops.id', '=', 'products.shop_id');
});
$dataList = $dataList->orderBy($field, $order);
$dataList = $dataList->paginate(5)->appends(['sortField' => $field, 'sortOrder' => $order]);
the table layout for product and shop model is:
Schema::create('shops', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('slug');
$table->string('phone')->nullable();
$table->string('address')->nullable();
$table->timestamps();
$table->string('description')->nullable();
$table->uuid('uuid');
});
Schema::create('products', function (Blueprint $table) {
$table->increments('id');
$table->integer('shop_id')->unsigned();
$table->foreign('shop_id')->references('id')->on('shops');
$table->string('name');
$table->string('code');
$table->string('slug');
$table->integer('price');
$table->uuid('uuid');
$table->timestamps();
});
There are only 2 ways of resolving this:
Either you pull in all products, and trim them in the end(advisable only if not too many products per shop):
$shops = Shop::with(['products' => function($subQuery) {
$subQuery
->with('pictures') //Running in scope of product, also load each product's pictures
->orderBy('created_at', 'desc');
}])
->get();
foreach ($shops as $shop) {
$shop->setRelation('products', $shop->products->take(3));
}
NOTE:
You will load every single product that is linked to the shops you load. You could get memory issues with this.
Take only what you need, but introduce a n+1 query issue(advisable only with small quantities of $shops:
$shops = Shop::get();
foreach ($shops as $shop) {
$shop->load([
'products' => function($query) {
$query
->orderBy('created_at', 'desc')
->limit(3)
->get();
}]);
}
NOTE:
N+1 query problem: You are performing a new query for each shop, so if you have a million shops, it will be a million extra queries.
EDIT: (answering comment question)
Q: How can i sort $shops based on their latest product created_at field?
$sortedShops = $shops->sortBy(function ($shop, $key) {
return $shop->products->first()->created_at;
})->values()->all();
sortBy is called on the collection(not uery). It allows you to go over each element(in this case shops) and use each object. Please do note that this function will fail if you have no products linked to the shop.
The ->values()->all() at the end makes sure that when you convert your shops to json, you will create an array, and not an object in js.
Source:
https://laravel.com/docs/7.x/collections#method-sortby
EDIT: (deleted original answer as it did not work)
Previous answer does not work, because limit(3) will limit the total amound of products loaded, in stead of 3 products per shop(my bad).

Where clause for arrays of single column in laravel

I'm building a small application on laravel 5.5 where I'm retrieving models with the relationship absence, something like this:
$milestone = Milestone::where('unique_id', $request->id)
->whereDoesntHave('block')
->first();
This is working fine but when I convert milestone variable to an array I'm getting absurd results, I tried:
$milestone = Milestone::where('unique_id', $request->id)
->whereDoesntHave('block', function ($q) use($request){
foreach($request->milestone as $m)
$q->where('unique_id', $m);
})
->first();
Edit:
Following is my relationship:
Milestone Model:
public function block()
{
return $this->morphMany('App\Models\Team\Block', 'block');
}
Block Model:
public function block()
{
return $this->morphTo();
}
and my block database table:
Schema::create('blocks', function (Blueprint $table) {
$table->increments('id');
$table->string('unique_id');
$table->string('block_type');
$table->integer('block_id');
$table->integer('generic_id');
$table->string('type');
$table->integer('user_id');
$table->timestamps();
$table->softDeletes();
});
Help me out in this, Thanks
You should use whereIn rather than a foreach with multiple where clauses. I.e.
$q->whereIn('unique_id', $mArray);
If you chain the where clauses together, those are AND where clauses. As an example
App\Models\User::where('id', 1)->where('id', 2)->toSql();
"select * from `users` where `id` = ? and `id` = ?"
App\Models\User::whereIn('id', [1,2])->toSql();
"select * from `users` where `id` in (?, ?)"
the former gives no results, while the latter gives the results you might expect
Chain whereDoesntHave():
$milestone = Milestone::where('unique_id', $request->id);
foreach($request->milestone as $m) {
$milestone = $milestone->whereDoesntHave('block', function ($q) use($m) {
$q->where('unique_id', $m);
});
}
$milestone = $milestone->first();
Or use whereHas() with whereNotIn():
$milestone = Milestone::where('unique_id', $request->id)
->whereHas('block', function ($q) use($request) {
$q->whereNotIn('unique_id', $request->milestone);
})
->first();

Laravel: How can I retrieve a list of users who have a particular role in this example?

User Migration:
/**
* Users
*/
Schema::create('users', function(Blueprint $t) {
$t->increments('id');
$t->string('email', 100)->unique();
$t->string('password', 60);
$t->string('firstname', 30);
$t->string('lastname', 30);
$t->string('company', 60)->nullable();
$t->string('phone')->nullable();
$t->rememberToken();
$t->timestamps();
});
Role Migration:
Schema::create('roles', function(Blueprint $t) {
$t->increments('id');
$t->string('name', 30)->unique();
});
Pivot Table:
Schema::create('role_user', function(Blueprint $t) {
$t->increments('id');
$t->integer('role_id')->unsigned()->index();
$t->foreign('role_id')->references('id')->on('roles')->onDelete('cascade');
$t->integer('user_id')->unsigned()->index();
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
});
The relationship is working fine, but I need to retrieve a list of all users who have a particular role, lets say "Agent"
Ultimately I want to add this user list to a Form::select, so I will need only id, firstname, lastname fields from the users table.
OK, After typing all of that, I figured this out. Wanted to still submit in case someone else finds it helpful, or shows me a better way. Below is working:
I first added this to the User model:
public function getFullNameAttribute()
{
return $this->attributes['firstname'] . ' ' . $this->attributes['lastname'];
}
Then I receive prepare the data in the controller:
$agents = User::select('firstname', 'lastname', 'id')->with(['roles' => function($query) {
$query->where('name', 'Agent');
}])->get();
$agentsList = $agents->lists('fullName', 'id');
That seems to work, but I am not sure if it is a proper way to handle it. The page is rarely ever used, so performance wont matter much.
You may try this, whereHas will fetch only those with name=Agent but with will fetch all:
$agents = User::whereHas('roles', function($query) {
$query->where('name', 'Agent');
})->get(['firstname', 'lastname', 'id'])->lists('fullName', 'id');

Resources