Laravel Eloquent ORM One-to-Many Relationship, Select from two Tables - php

I have two mySQL tables of an online shop:
products (cols: id, etc...)
pictures (cols: id, product_id, filepath, etc...)
Any product of the products-table may have an infinite number of pictures!
Therefore, my product-model looks like this:
class Product extends Model
{
public function pictures()
{
return $this->hasMany('App\Picture');
}
}
My picture model looks like this:
class Picture extends Model
{
//
}
Now, I'am trying to list all products together with their corresponding picture datasets. I tried running multiple codes. However, none of the following codes works:
\App\Product::pictures()->get()
doesn't work! This error is shown:
Non-static method App\Product::pictures() should not be called statically
I also tried some other methods, e.g.
\App\Product::with('pictures')->get());
Error:
Trying to get property 'filepath' of non-object (View: /home/vagrant/testproject/resources/views/products.blade.php)
Remark: Filepath is a column of pictures. I am trying to access it in the blade like this:
{{$products->picture->filepath}}
I am now working for hours on this tiny thing - but I just don't get it what I am doing wrong? Any help is highly appreciated! Thank you guys!

There are two potential issues with what you are doing.
You are using {{$products->picture->filepath}} so I assume you are doing soething like:
$products = \App\Product::with('pictures')->get());
Here $products is a collection of Product models. The collection does not have a picture attribute.
You are looping over the products something like
#foreach($products as $product)
{{$product->picture->filepath}}
#endforeach
Unless EVERY product has a picture, this can result in the error you stated. If the product does not have a picture, then you will be calling filepath on null. You can address this in several ways, I will list one below:
#foreach($products as $product)
#if(!empty($product->picture))
{{$product->picture->filepath}}
#endif
#endforeach
As OP noted in comments below the root issue is that the relationship is has many. So to use pictures you should do:
#foreach($products as $product)
#foreach($product->pictures as $picture)
{{$picture->filepath}}
#endforeach
#endforeach

Related

Eloquent Eager Loading "Property [service_code] does not exist on this collection instance"

I cannot figure out how eager loading works with the following example. This is my current DB with two tables, Quotes: where the general information is stored, and QuotesDetails: where details of each Quote in Quotes is stored.
In models I have the following structures:
Quotes.php
class Quotes extends Model
{
public function quotesdetails()
{
return $this->hasMany('App\QuotesDetails', 'quoteid', 'id');
}
}
and QuotesDetails.php with the following model:
class QuotesDetails extends Model
{
public function quotes()
{
return $this->belongsTo('App\Quotes', 'id', 'quoteid');
}
}
I used hasMany (in Quotes) because each quote can have/display 3-4 quotesdetails.
In my controller im using the following query:
$returnquotes = Quotes::with('quotesdetails')->where('id', '=', $quoteid)->get();
In my view im using the following structure:
#foreach ($returnquotes as $quotes)
{{$quotes->shipcity }}
{{$quotes->quotesdetails->service_code }}
#endforeach
shipcity displays the information with no problems, but service_code is not displayed and gives error.
Honestly I believe it has to work with this schema but I cannot figure out why is not working. My thoughts:
in controller: using "with('quotesdetails')" in controller it must establish the relation that appears in Quotes.php with name -> quotedetails that hasmany registries associated in QuotesDetails.php
in view: using "$quotes->quotesdetails->service_code" must retrieve the service_code associated to the quotes table (i'm using foreach because quotesdetails can have multiple registries per quote)
in model: Im using "hasMany" for Quotes.php because the dependent table is QuotesDetails and "belongsTo" in QuotesDetails.php for the inverse reason.
any help to understand the logic of eager loading with eloquent and laravel appreciated.
$quotes->quotesdetails is a collection itself (has many) so you need to iterate over it using another foreach:
#foreach ($returnquotes as $quotes)
{{$quotes->shipcity }}
#foreach ($quotes->quotesdetails as $quotedetail)
{{$quotedetail->service_code }}
#endforeach
#endforeach

Laravel retrieves too many model on a simple paginate query with loaded relations

I have a simple database with couple of tables. orders - the main table, about 16_000 records. Each order has payments. The order_payments table is about 17_000 records. Also each could have zero or more travellers in a order_tourists table - 33_000 rows.
The Order model looks like this
namespace App;
class Order extends \Illuminate\Database\Eloquent\Model
{
public function payments()
{
return $this->hasMany('App\OrderPayment');
}
public function tourists()
{
return $this->hasMany('App\OrderTourist');
}
}
I have a simple task to display a paginated list of all orders. Each item in this list should display some order's information (from the orders table), the sum of all payments and names of all travellers.
The first that came to my mind was something like this.
Get all orders with payments and travellers and pass this data to a view
Route::get('search_offers', static function () {
$orders = \App\Order::query()->with(['payments', 'tourists']);
return view('orders', [
'orders' => $orders->paginate(),
]);
});
And my orders.blade.php is pretty straightforward
<ol>
#foreach($orders as $order)
<li>
{{$order->request_id}}; {{ $order->payments->sum('amount') }}; {{ $order->tourists->implode('fullName', ', ') }}
</li>
#endforeach
</ol>
However, even the number of queries is very small I have a lot of manipulations with models. I add a custom hook to track all eloquent.* events and in first case there are more than 23_000 of such events.
Then I try to remove the loading of relations so for each order Laravel have to run a separate query to get all order's payments and travellers. I simply did like this in my controller
return view('orders', [
'orders' => \App\Order::query()->paginate(),
]);
So, I get more queries but page speed improves and a number of affected models significantly decreased
To track affected models I add a listner in a AppServiceProvider::boot method and output a AppServiceProvider::$hydratedModels value in a debugbar panel
Event::listen('eloquent.*', static function ($event) {
if (strpos($event, 'eloquent.retrieved') !== false) {
AppServiceProvider::$hydratedModels++;
}
});
So, my question how it possible that so many models are retrieved even if I display only 15 items. Looks like Laravel somehow get all of them, processed but retruns only those for a required page.
Right now it's not a big deal to run extra queries to fetch some data but I'm wondering maybe I'm doing something wrong and it's possible to load relations but do not cause Laravel to retrieve all models.
I found the issue. Thanks, #dparoli for a suggestion.
My models have a primary key as UUID. But I did not change a Model::$keyType so internally Laravel cast it into an integer and queries look like this.
Instead of a UUID string, I got integers as an order_id for related records.
So, I add protected $keyType = 'string'; and everything starts work as expected.
PS. Although, I'm wondering how it works before.

Merging two models into one database call - laravel

I have 2 models, that I am calling via all:
$blocks = Block::all();
$cats = BlockCategory::all();
A block has a category associated with it and I want to associate the data so that I can display all categories that have certain blocks. I hope I am explaining this correctly.
so I can call:
#foreach($cats as $cats)
{{$cats->title}}
#foreach($cats->blocks as $blocks)
{{$block->title}}
#endforeach
#endforeach
any ideas how to merge the data? I am basically building a menu if that helps.
You need to define relationship between blocks and block category. Make sure that your db table has a foreign key relationship.
Add following code to your block category model
public function blocks()
{
return $this->hasMany('App\Block');
}
Make sure your block model is inside app/ directory.
Now, just retrieve categories in your controller,
$categories = BlockCategory::all();
Then you can display the data like this:
#foreach($categories as $category)
{{$category->title}}
#foreach($category->blocks as $block)
{{$block->title}}
#endforeach
#endforeach
You have a few options on how to approach this.
Define your relation like this:
Model
public function blocks()
{
return $this->hasMany('App\Block');
}
Controller
Using with()
$categories = Blockcategory::with('blocks')->get();
Once you have defined the relationship you can call the blocks() method from your model to retrieve the blocks
Examples
$categories = Blockcategory::find(1)->blocks()->get();
// another way
$blocks = Blockcategory::blocks()->get();
Sagar's answer is fine, but it is always better to get your categories and their relationships in the same query. By just calling Blockcategory::all() you can get the relation in your blade file, but laravel has to do an additional query for each block in your for each loop. This can give performance issues when having 1000s of records.
Laravel relationships

Access relation of pivot table in Laravel

I have three models Bill, Product and Process. Bill has a ManyToMany relationship with Product and the pivot table have some extra fields. I write the Bill model class like follow:
<?php
class Bill extends Model
{
function products(){
return $this->belongsToMany(\App\Product::class)
->withPivot('process_id') // the id of the Process
->withTimestamps();
}
}
The Process model is a simple table with an id and a name. I am associating the id in the pivot table for reference the Process, the name could change over time but still referring the same concept so I can't associate the name.
The show page for a single Bill lists the products associated in a table like follow:
#foreach($bill->products as $product)
<tr>
<td>{{$product->barcode}}</td>
<td>{{$product->pivot->process_id}}</td>
</tr>
#endforeach
So the problem is that I need the name of the process, but I have the id. I'm not sure how I could get the name.
Thanks
I think you can use an own Pivot Model, e.g. ProductBill in order to achieve this.
class ProductBill extends Pivot {
public function process() {
return $this->belongsTo(Process::class);
}
}
By using this model in your products relation on Bill
class Bill extends Model {
function products() {
return $this->belongsToMany(\App\Product::class)
->withPivot('process_id')
->using(ProductBill::class)
->withTimestamps();
}
}
When accessing $product->pivot you should get an instance of ProductBill now, hence you should be able to do the following:
<td>{{$product->pivot->process->name}}</td>
(Unfortunatelly I not able to doublecheck right now :/)
Without having a direct relation to Process you will likely need to add a helper on your Product model to get the name of Process.
In your Product model:
public function processName($processId) {
return Process::where('id', $processId)->pluck('name')->first();
}
In your view:
<td>{{$product->processName($product->pivot->process_id) }}</td>
There may be a better way, but concept this should work.
I know it's not the most elegant of solutions. but you could always simply do:
Process::find($product->pivot->process_id)->name;
I wouldn't advice this though as you are looping through an array already, so the overheads to doing something like this would be quite large.
Another solution would be to create a Pivot class called say BillProductPivot which would have a relationship to return both Product and Process, then when you call them you should be using eager loading to get the relationships. end product might look like this:
$bill->load('productPivots', 'productPivot.process', 'productPivot.product');
#foreach($bill->productPivots as $productPivot)
<tr>
<td>{{$productPivot->product->barcode}}</td>
<td>{{$productPivot->process->name}}</td>
</tr>
#endforeach

Laravel 4.1: View does not echo query even though controller passes correct json

I am following a tutorial on Laravel 4 but I am stuck at a specific point. I want to return a view showing the user with his photos. For this I have the following method in my controller:
public function show($id)
{
$user = User::whereId($id)->with('photo')->first();
return View::make('authenticated.users.show', compact('user'));
}
If I return $user the json I am seeing in the browser seems to be perfectly correct.
However, if I try to display that in the show.blade.php view I am getting this error:
Undefined property: Illuminate\Database\Eloquent\Collection::$id
The code I am using in my view is the following:
<li>{{ $user->photo->id }}</li>
Its exactly as shown in the tutorial but it does not work out.
I would very much appreciate some help.
Thanks.
It's most like that you are using the hasMany relationship in your User model. When using the hasMany relationship, it will return a Collection object, rather than your model itself. Take a look at the API for the Collection object and you'll find many helpful methods.
What you should probably do is something like this.
foreach ($user->photo->all() as $photo)
{
echo $photo;
}
Or, if your user only has one photo, then change your relationship in the User model to hasOne. Then you can use it as you are now.
echo $user->photo;

Categories