laravel 4 one to many relationships - iteration - php

I'm trying to improve some code in my app in laravel 4 and trying to implement relationships between models.
I have a table called reservations which has a one to many relationship with items. Items has a one to one relationship with Products. Basically as a reservation the items that are included in the reservation are added to the items table. The specs of the items comes from the products table (type, value etc)
I've set up the relationships in the models as follows:
in the reservations Class:
public function item() {
return $this->hasMany('Item');
}
in the items Class:
public function reservation() {
return $this->belongsTo('Reservation');
}
public function product() {
return $this->hasOne('Product');
}
in the product Class:
public function item() {
return $this->belongsTo('item');
}
I'm trying to now query the reservations for a calendar view. I'm retrieving all the reservations in a month using the following:
$events = Reservation::where('play_date','>=',$start->format('Y-m-d'))
->where('play_date','<=', $end->format('Y-m-d'))
->get();
I'm then trying to iterate through the collection (is it a collection or a result set?) using the following:
$events->each(function($event) { }
I then want to iterate through all the items of the reservation and it's this bit that's confusing me.
$items = $event->item()->get();
which does create an object I can then iterate through this sub collection using another call back but I'm then struggling to get the product information using:
$item->product()->type
I get an error:
Undefined property: Illuminate\Database\Eloquent\Relations\HasOne::$type
How can I iterate through the items correctly using laravel relationships? What is the best practice in this and retrieve details relating to the item from the product table
Thank you

The main point to do what you want is understand the difference between $event->item and $event->item(). Basically, $event->item is the same as $event->item()->get(). Knowing this, you should be doing something like this
$events = Reservation::where('play_date', '>=', $start->format('Y-m-d'))
->where('play_date', '<=', $end->format('Y-m-d'))
->get();
$events->each(function ($event) {
$items = $event->item;
$items->each(function ($item) {
$type = $item->product->type;
});
});
You may also want to take a look at Eager Loading your relationships, as to reduce the number of queries ran.

Related

Property [group] does not exist on this collection instance

I have retrieved an instance of a product item at blade, so when I do dd($product), I get this result:
And the Product Model is connected to GroupProduct Model like this:
public function groupproducts()
{
return $this->hasMany(GroupProduct::class,'product_id','id');
}
So at group_product table, the product has a custom group_id like this:
So when I do dd($product->groupproducts); I get this result properly:
Then at the Model GroupProduct, I've added these two relationships:
public function product()
{
return $this->belongsTo(Product::class,'product_id','id');
}
public function group()
{
return $this->belongsTo(Group::class,'group_id','id');
}
Now I need to access the name of the group that comes with the retrieved product_id, but when I try this:
dd($product->groupproducts->group->name);
I get this error:
Property [group] does not exist on this collection instance.
However the relationships seems to be applied and it should be showing the name of group...
So what's going wrong here? How can I fix this issue?
UPDATE #1:
Controller:
public function addAttribute(Product $product)
{
return view('admin.products.addAttribute', compact('product'));
}
$product->groupproducts is a collection(array) of products.
You can do:
dd($product->groupproducts->first()->group->name);
This will get first element from collection which is instance of Group class.
Your relation from your product model to the group products model is a one to many. This means, that even if there's only one result, you will be receiving a collection.
You can see in your own screenshot that the return value of $product->groupproducts is an instance of a collection with 1 item.
If you only ever want the first, you should change your relation to a one to one.
However, if you just want the first in this particular case, you should call for the first on the query instance, not on the collection instance.
So $product->groupproducts()->first()->group->name. This way you don't load x amounts, but instead only 1, from your database, which is much faster.
you have to use nested relationship:
1- you can use it in your Model via:
public function groupproducts()
{
return $this->hasMany(GroupProduct::class,'product_id','id')->with('group');;
}
2- you can use it in your controller:
Products::with('groupproducts.group')->get()
REFRENCE

Eloquent nested complex relationship

I have some trouble with chaining relationships. I want to chain three of them, but this is not working properly:
return UserModel::with('cars.pieces.attributes')
I want to retrieve a user with its cars. He chose a car which have pieces and for each pieces he chose an attribute.
With only cars.pieces. I have my user, then the array of cars then the array of pieces for this car. When I add attributes, I have attributes not for pieces of cars of users but attributes for pieces whatever cars it is.
It seems like the relationship is only looking for the previous relation and not the whole packet.
public function cars(){
return $this->belongsToMany(CarsModel::class, 'user_cars', 'id_user','id_cars');
}
Then
public function pieces(){
return $this->belongsToMany(PiecesModel::class, 'cars_pieces', 'id_cars','id_pieces')
}
And finally :
public function attributes(){
return $this->belongsToMany(AttributeModel::class, 'user_cars_pieces_attributes', 'id_attribute', 'id_piece')
}
The last entity is using 4 fields for the primary key :
id_user, id_car, id_attribute, id_piece
What could be a way to retrieve attributes for pieces of cars of the user?
Thank you for helping!
You can pass a function to your eager loading attributes:
return UserModel::with(['cars.pieces.attributes' => function ($query) {
$query->where('id_user', DB::raw('users.id'))->where('id_car', DB::raw('cars.id'));
}]);
I haven't tested this but I think it should work.
Remember to import DB Facade: use Illuminate\Support\Facades\DB;

Laravel get only first related model

I want to get the first related model. But this works only for the first model in the collection. The 2nd is empty.
I've found this answer, but I didn't find a solution.
How can I only get the first related model?
$querybuilder->with([
'messages' => function ($query) {
$query->orderBy("created_at", "DESC");
$query->limit(1);
}
]);
You can use a HasOne relationship:
class Conversation extends Model {
public function latestMessage() {
return $this->hasOne(Message::class)->latest();
}
}
$querybuilder->with('latestMessage');
Be aware that this will still fetch all messages from the database. It then discards the "old" ones.
If you want to improve the performance by really only fetching the latest message, you can use this package I created: https://github.com/staudenmeir/eloquent-eager-limit
The package allows you to apply limit() to the relationship:
class Conversation extends Model {
use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;
public function latestMessage() {
return $this->hasOne(Message::class)->latest()->limit(1);
}
}
Your with() actually creates several queries, the last query has the limit, hence the behavior (which is correct). you can use \DB::enableQueryLog();, run your query and then \DB::getQueryLog(); to see how the queries are built.
If you instead want to apply a limit to each model item you could fetch all items and iterate over them to fetch one or more related model items
This is not done i sql but in php (laravel collection method), if you need it in sql you could just join your related model and set it up however you want.
This will cause performance issues if you have large amount of data, but if you don't it's quite convenient.
$result = \App\YourModel::all()
->map(function ($item) {
return $item->YourRelatedModel()
->orderBy('someField')
->first();
});
I forgot ... the above only returns the related model's items, if you also want the parent model you can
$result = \App\YourModel::all()
->map(function ($item) {
$item->YourRelatedModelName = $item
->YourRelatedModel()
->orderBy('someField')
->first();
return $item;
});

Laravel: Is there a way to remove DB query clauses?

Let us say my shop model has a products relationship as follows:
// shop model class
public function products()
{
return $this->hasMany(Model\Product::class)
->orderBy('name');
}
Is there a way that client code, such as my controller, can remove the orderBy() clause?
Yes there is a way to do that, say in your controller you want to fetch some of the products
\App\Products::latest()->getQuery();
getQuery() is a query builder method that contains all the groupings, selects, orders, wheres, joins etc for the query that you are accessing or trying to build.
So you could reset the ordering like so:
\App\Products::latest()->getQuery()->orders= [];
since in Laravel eloquent query builder selects, grouping, ordering are stored as arrays of key values you simply set that to an empty array to reset al the previous states.
For example grouping:
\App\Products::latest()->getQuery()->groupings =[];
For reset
$qry->getQuery()->groups = [];
$qry->getQuery()->wheres = [];
For remove last where or group by column
array_pop($qry->getQuery()->groups);
array_pop($qry->getQuery()->wheres);
public function products($isOrder = true)
{
$hasMany = $this->hasMany(Model\Product::class);
return ($idOrder) ? $hasMany->orderBy('name') : $hasMany;
}
but you can use this only having $shop instance;
$shop->products(false)->...; // your query to get result

Getting count from pivot table in laravel eloquent

I have a many to many relationship for orders and products.
<?php
class Order extends Eloquent {
public function user()
{
return $this->belongsTo('User');
}
public function products()
{
return $this->belongsToMany('Product');
}
}
?>
<?php
class Product extends Eloquent {
public function orders()
{
return $this->belongsToMany('Order');
}
}
?>
Need to fetch the number of times each product is ordered.In mysql,this task can be achieved by using the following query
SELECT products.id, products.description, count( products.id )
FROM products
INNER JOIN order_product ON products.id = order_product.product_id
INNER JOIN orders ON orders.id = order_product.order_id
GROUP BY product_id
LIMIT 0 , 30
Result of the above query is as follows:-
id description count(products.id)
1 Shoes 3
2 Bag 2
3 Sun glasses 2
4 Shirt 2
How this task can be achieved using laravel eloquent (without using query builder)????How can i fetch the number of times each product is ordered using laravel eloquent??
For future viewers, as of Laravel 5.2, there is native functionality for counting relationships without loading them, without involving your resource model or accessors -
In the context of the example in the approved answer, you would place in your controller:
$products = Product::withCount('orders')->get();
Now, when you iterate through $products on your view, there is a orders_count (or, generically, just a {resource}_count) column on each retrieved product record, which you can simply display as you would any other column value:
#foreach($products as $product)
{{ $product->orders_count }}
#endforeach
This method produces 2 fewer database queries than the approved method for the same result, and the only model involvement is ensuring your relationships are set up correctly. If you're using L5.2+ at this point, I would use this solution instead.
Mind that Eloquent uses Query\Builder under the hood, so there is no such thing in Laravel, like 'query eloquent without using query builder'.
And this is what you need:
// additional helper relation for the count
public function ordersCount()
{
return $this->belongsToMany('Order')
->selectRaw('count(orders.id) as aggregate')
->groupBy('pivot_product_id');
}
// accessor for easier fetching the count
public function getOrdersCountAttribute()
{
if ( ! array_key_exists('ordersCount', $this->relations)) $this->load('ordersCount');
$related = $this->getRelation('ordersCount')->first();
return ($related) ? $related->aggregate : 0;
}
This will let you take advantage of eager loading:
$products = Product::with('ordersCount')->get();
// then for each product you can call it like this
$products->first()->ordersCount; // thanks to the accessor
Read more about Eloquent accessors & mutators,
and about dynamic properties, of which behaviour the above accessor mimics.
Of course you could use simple joins to get exactly the same query like in you example.
If you already have the $products object, you can do the following:
$rolecount = $products->roles()->count();
Or if you are using eager loading:
$rolecount = $products->roles->count();
Cheers.
I am using Laravel 5.1 and i am able to accomplish that by doing this
$photo->posts->count()
And the posts method in Photo model looks like this
public function posts(){
return $this->belongsToMany('App\Models\Posts\Post', 'post_photos');
}

Categories