Laravel 4 Eager Load Constraints with BelongsTo - laravel

I have two Models... Aircraft and Movement:
In Aircraft:
public function movements() {
return $this->hasMany('Movement');
}
In Movement:
public function aircraft() {
return $this->belongsTo('Aircraft');
}
Table Movement:
Schema::create('movements', function($table)
{
$table->increments('id');
$table->boolean('flag_visible')->default(1);
$table->integer('aircraft_id')->nullable()->default(NULL);
$table->timestamps();
});
Table Aircraft:
Schema::create('aircrafts', function($table)
{
$table->increments('id');
$table->string('identifier');
$table->timestamps();
});
Now I want select all movements with Aircrafts with identifiers = 10:
$movements = Movement::with(array(
'aircraft' => function($query) {
$query->where('identifier', 'like', '%10%');
}
));
I Have only ONE movement record with an aircraft_id where the Identifier is %10%.
But I get ALL movements records, only the one with the right identifier has the relationship "aircraft". But I want an array only with ONE record, only the one with the right identifier.. what is wrong here?

with() creates a separate query to retrieve all the related values at once (eager loading) instead of as-needed (lazy loading).
You're looking to add a constraint on the movement query, so you should use whereHas(), something like this:
$movements = Movement::whereHas('aircraft', function($q)
{
$q->where('identifier', 'like', '%10%');
})->get();

Related

Why in pivot table result is different as I expected?

In laravel 9 app I create many top many relation with table
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');
$table->boolean('active')->default(false);
$table->date('expired_at')->nullable();
$table->integer('supervisor_id')->nullable()->unsigned();
$table->foreign('supervisor_id')->references('id')->on('users')->onDelete('CASCADE');
$table->mediumText('supervisor_notes')->nullable();
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->nullable();
$table->unique(['vote_id', 'article_id'], 'article_vote_vote_id_article_id_index');
$table->index(['vote_id', 'article_id', 'active', 'expired_at'], 'article_vote_vote_id_article_id_active_expired_at_index');
$table->index([ 'expired_at', 'active',], 'article_vote_expired_at_active_index');
$table->index(['created_at'], 'article_vote_created_at_index');
});
Artisan::call('db:seed', array('--class' => 'articleVotesWithInitData'));
}
In app/Models/Vote.php :
public function articles(): BelongsToMany
{
return $this->belongsToMany(Article::class, 'article_vote', 'vote_id')
->withTimestamps()
->withPivot(['active', 'expired_at', 'supervisor_id', 'supervisor_notes']);
}
and in app/Models/Article.php :
public function votes(): BelongsToMany
{
return $this->belongsToMany(Vote::class, 'article_vote', 'article_id')
->withTimestamps()
->withPivot(['active', 'expired_at', 'supervisor_id', 'supervisor_notes']);
}
Running requests:
$article = Article::getById(2)
->firstOrFail();
$articleVotes = $article->votes;
I got sql :
SELECT `votes`.*, `article_vote`.`article_id` AS `pivot_article_id`, `article_vote`.`vote_id` AS `pivot_vote_id`, `article_vote`.`created_at` AS `pivot_created_at`, `article_vote`.`updated_at` AS `pivot_updated_at`, `article_vote`.`active` AS `pivot_active`, `article_vote`.`expired_at` AS `pivot_expired_at`, `article_vote`.`supervisor_id` AS `pivot_supervisor_id`, `article_vote`.`supervisor_notes` AS `pivot_supervisor_notes`
FROM `votes`
INNER JOIN `article_vote` on `votes`.`id` = `article_vote`.`vote_id`
WHERE `article_vote`.`article_id` = 2
But result is different as I expected, as in article_vote table I have rows : https://prnt.sc/wTE5uaPrQu9v
But I see different with the sql : https://prnt.sc/Os14x5K6unyu
Why 4 rows with different vote id ?
Thanks!
Comparing your two screenshots, it seems like different databases, look at the created_at and updated_at values of the pivot table, they are totally different for all the rows. Could that be a mistake that you're querying, for example, local vs live DBs?

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).

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.

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