I'm trying to build a store and everything is going great. But I'm running into an issue that I'm having trouble wrapping my head around to find the best option.
I have an Item Model:
class Item extends \Eloquent implements \Cviebrock\EloquentSluggable\SluggableInterface
{
protected $table = 'store_items';
public function options()
{
return $this->belongsToMany('\Dcn\Store\Option', 'store_items_options', 'items_id', 'options_id');
}
}
And an options model:
class Option extends \Eloquent implements \Cviebrock\EloquentSluggable\SluggableInterface
{
protected $table = 'store_options';
public function values()
{
return $this->belongsToMany('\Dcn\Store\OptionValues', 'store_option_values', 'options_id', 'op-values_id');
}
}
And an OptionValues Model:
class OptionValues extends \Eloquent
{
protected $table = 'store_op-values';
public function options()
{
return $this->belongsToMany('\Dcn\Store\Option', 'store_option_values', 'op-values_id', 'options_id');
}
}
And Finally an Invoice Model:
class Invoice extends \Eloquent
{
protected $table = 'store_invoices';
public function items()
{
return $this->belongsToMany('\Dcn\Store\Item', 'store_invoices_items', 'invoice_id', 'item_id');
}
}
When a user checkout I currently create an invoice and attach the items, but I need to be able to store the options (color, size, etc) for each item in the invoice.
I'm just not sure what the best way to store the values for the Items's options in the invoice because a user could have multiple items that are just different colors. Any input would be greatly appreciated.
One possible solution involves the following models/tables/relations:
Invoice
One To Many relation with InvoiceItems.
Invoice->hasMany('InvoiceItem')
InvoiceItem
This model contains the line items for each invoice, with invoice_id (foreign key) linking the InvoiceItem to Invoice, and item_id linking the InvoiceItem to an Item from your inventory. (Other order details would be stored here as well, such as quantity of the item, etc.)
InvoiceItem->belongsTo('Invoice')
InvoiceItem->belongsTo('Item')
Then, to store the options chosen for each InvoiceItem and their values, there's a One To Many relation with InvoiceItemOption.
InvoiceItem->hasMany('InvoiceItemOption')
InvoiceItemOption
For each InvoiceItem (linked by invoice_item_id), this model stores 0, 1, or more options (linked from the Option model by option_id) and that option's value (linked from the Value model by value_id).
InvoiceItemOption->belongsTo('InvoiceItem')
InvoiceItemOption->belongsTo('Option')
InvoiceItemOption->belongsTo('Value')
Item
This contains your list of items, i.e. your inventory. It has a One to Many relation with InvoiceItem (i.e. each Item can appear on many InvoiceItems, and each InvoiceItem has one Item). Each item also has 0, 1, or more Options available to it, so it is linked through a Many To Many relation with Option.
Item->hasMany('InvoiceItem')
Item->belongsToMany('Option')
item_option
This is a pivot table, linking each Item to its available Options (with item_id and option_id)
Option
All the available options for all Items. Each option has 1 or more possible Values, and is linked through a Many To Many relation with Value
Option->belongsToMany('Item')
Option->belongsToMany('Value')
option_value
This is a pivot table, linking each Option to its available Values (with option_id and value_id).
Value
All the available values for all Options.
Value->belongsToMany('Option')
For each relation, I've defined the inverse relation as well, which would give you maximum flexibility. Depending on your needs, though, some of those inverse relations might be unnecessary.
There are two other alternatives, neither of which I think are good ones:
You could link Invoices and Items via a Many To Many relation (as you've done), and store your invoice line item information in the intermediate invoice_item pivot table. To do this, though, you would need to create a custom pivot model (rather than a simple pivot table), since that model would need to have a hasMany('InvoiceItemOption') relation to store the selected options and values. This would limit your flexibility somewhat in how you interact with the InvoiceItem model, and there's nothing really to be gained by this approach.
(But, if your interested in a good example of how to set up such a custom pivot model, see https://github.com/laravel/framework/issues/2093#issuecomment-39154456.)
A third and even less optimal option would be to link Invoices and Items via a Many To Many relation, and store the options and values in a serialized form in the intermediary invoice_item pivot table, along with any other order details like quantity. To do this, you would need to specify the extra fields when you define the Invoice relation:
$this->belongsToMany('Items')->withPivot('quantity', 'options_serialized')->withTimestamps();
This method is very inflexible, though—running a query for orders containing extra-large green shirts, for example, would be a pain.
Related
Need tips for displaying this one to many related data on a heirarchical
order in laravel view page. Here vegetable, groceries are category and
other's are there related product.
Here is the image for data.
Need to show this every 1 to many related data on a hierarchical order on
a single view page. I want to loop through these array and
want my result like tree view:
Based on your given information I will try to help you as much as possible.
Looking at your image, shows me that you already have duplicated data in your database. Where possible try to use different tables for different entities. In this case your Category is used over and over again. So try to create a new table for it.
That being said, you still have 2 options for this. A one to many or many to many relationship between your ingredient and category. This depends on what you want, do you want an ingredient to be able to have one or many categories. I have added a image below to show the 2 types of database structures for this. (Left: One to many, Right: Many to many)
You will have to create 2 models for both the Category and the Ingredient and make sure that you can use those models to store and receive data from your database. If you would like some more information about models take a look here:
https://laravel.com/docs/6.x/eloquent#defining-models
After creating your models, you should define your relation methods inside your models. This is different based on the choice you have made. (many to many or one to many).
One to many:
In the one to many example an ingredient can have only 1 category. Therefor you have to create a method in your ingredient called category and this should return a HasOne:
/**
* #return HasOne
*/
public function category(): HasOne
{
return $this->hasOne(Category::class);
}
Then your category can have multiple Ingredients so your Category should get a method called ingredients and it must return a BelongsToMany:
/**
* #return BelongsToMany
*/
public function ingredients(): BelongsToMany
{
return $this->belongsToMany(Ingredient::class);
}
Many to many:
In a many to many relation your category will be the same because it still has many ingredients. Only the ingredient will change because it not also has many categories. The method for the categories will now just be the same as the one for the ingredients:
/**
* #return BelongsToMany
*/
public function categories(): BelongsToMany
{
return $this->belongsToMany(Category::class);
}
That is that! Now how to use this (and hopefully the answer to your question). When you have a category like:
$category = Category::first();
You are able to use the ingredients method to get all the ingredients having this category:
$category = Category::first();
$ingredients = $category->ingredients;
This will give you a Collection with all the ingredients of this category.
You could also use this for every category.
$categories = Category::all(); //Get all categories
foreach ($categories as $category) {
$ingredients = $category->ingredients; //Get all ingredients
}
Extra:
Note that i'm not using the () after ingredients, this has to do with the mysql queries that are executed (Without () it will look if it has results (if yes it will use these of no it will query your database and then use the results)).
Can I have an eloquent relationship where there will only be a partial match on the joins?
To explain, here is one Model;
class PostcodeCoord extends Model {
public function payment()
{
return $this->hasMany('App\Models\Payment', 'Vendor ZIP', 'postcode');
}
}
And my other model;
class Payment extends Model {
public function postcodeCoord()
{
return $this->belongsTo('App\Models\PostcodeCoord', 'postcode', 'postcode');
}
}
My payments table has a column called Vendor ZIP. And my postcode_coords table has a column called postcode.
The problem, is that the postcode column is only 3 or 4 characers long (the first section of a UK postcode Eg SW1A). Whereas, Vendor ZIP, is the full 6 or 7 characters. Eg SW1A2AA.
AFAIK, there is no way to create a relationship on a partial match between the ID fields. That said, I can think of two different ways to solve this:
Add a new column to your payments table called something like postalcodegroup. When the model is created or updated, ensure that this column is filled in with the first part of the postal code. The best way to do this would be via model events. Then you can create your relationship between payments.postcodegroup and postcode_coords.postcode.
Write an accessor on the Payment model which will perform a database query to retrieve the relevant data from the postcode_coords table when it's called.
Of the two, option 1 is probably better (cleaner), although it does require adding columns to your tables.
I'm trying to get my head around using polymorphic relationships for a many-to-many relationship between suppliers and products:
products
id
name
suppliers
id
name
product_supplier
id
product_id // belongsToMany easily takes care of this id
supplier_id // and this id
price // this can be fetched using withPivot('price')
deliverymethod_id // I'm having difficulties "joining" this one.
I'm confident in using belongsToMany(), I can easily do something like this:
public function products()
{
return $this
->belongsToMany('Supplier')
->withPivot('price');
}
But the catch here is joining to that third column in the relationship table:
deliverymethods
id
name
I am unsure how to do this. I've been told that Polymorphic Relationships are what I'm after however I'm unsure how to implement them for my situation.
http://laravel.com/docs/4.2/eloquent#many-to-many-polymorphic-relations
According to the documentation, I would have to rename my table columns to include *able_id and *able_type. This is really confusing.
I was expecting laravel to having something like belongsToMany('Supplier')->withAlso('Deliverymethod')
I'm afraid that method does not exist (yet?).
What I fall back to is manually filling in the 3rd relation:
public function products()
{
return $this
->belongsToMany('Supplier')
->withPivot('price', 'delivermethod_id');
}
Now I can access ->pivot->deliverymethod_id on every Product that I get via Supplier.
You could even add a function in your Product model that fills this in automatically:
Class Product ... {
protected $appends = array('deliverymethod');
public function getDeliverymethodAttribute()
{
return Deliverymethod::find($this->pivot->delivermethod_id);
}
Now every time you request a product via it's relation to the supplier, it automatically includes a deliverymethod attribute with the object in it.
(To have it not throw an error when you get a Product directly, just remove the $appends variable from the Product model and call the getDeliverymethodAttribute() method manually whenever you need it.)
Short explanation about polymorphic relations:
Polymorphic relations are for relations, where two models are related to a third model at the same time. So for example both a User and a Product can have a Picture of them. Now, it doesn't make sense to have two models for the pictures (UserPicture and ProductPicture), since they both have the same characteristics. This would be a perfect reason to use a polymorphic relation, where the Picture can both belong to a User or a Product.
However, in your case the Deliverymethod applies directly to the relation between Supplier and Product. So this is not where polymorphic relations would work, but it has instead to be done the way you did it.
I have two tables:
treatments (
id,
name
)
companies (
id,
name
)
And I need to build a relation to a "price" table. I thougth in something like follows:
prices (
treatment_id,
company_id,
price
)
But i don know how to apply the ORM to a php aplication. I'm using Laravel with Eloguent's ORM. I think that the real question would be if this is a good way to design the db. Perhaps I should make it diferent?
Any advices?
Thanks,
Ban.
If a Company can have multiple Treatments and a treatment can be bought from multiple companies at different prices, then you have a Many-to-many relationship, with prices being the pivot table (which if you would adhere to convention would be named company_treament, but that's not a must). So you'll need to have two models for Treatments and Companies, which would look like this:
class Company extends \Eloquent {
public function treatments()
{
return $this->belongsToMany('Treatment', 'prices')->withPivot('price');
}
and
class Treatment extends \Eloquent {
public function companies()
{
return $this->belongsToMany('Company', 'prices')->withPivot('price');
}
}
The treatments() and companies() methods from the models are responsible for fetching the related items. Usually the hasMany method only requires the related model as the first parameter, but in your case the pivot table name is non-standard and is set to prices by passing it as the second parameter. Also normally for the pivot table only the relation columns would be fetched (treatment_id and company_id) so you need to specify the the extra column using withPivot. So if you want to get the treatments for a company with the id 1 list you whould to something like this:
$treatments = Company::find(1)->treatments;
The opposite is also true:
$companies = Treatment::find(1)->companies;
If you need to access the price for any of those relations you can do it like this:
foreach ($treatments as $treatment)
{
$price = $treatment->pivot->price;
}
You can read more about how to implement relationships using Eloquent in the Laravel Docs.
EDIT
To insert a relation entry in the pivot table you can use attach and to remove one use detach (for more info read the Docs).
$treatment->companies()->attach($companyId, array('price' => $price));
$treatment->companies()->detach($companyId);
To update a pivot table entry use updateExistingPivot:
$treatment->companies()->updateExistingPivot($companyId, array('price' => $price));
I have three tables: users, items and user_items. A user has many items and a item belongs to many users.
The tables:
Users
id
username
password
Items
id
name
equipable
User_items
id
user_id
item_id
equipped
The models:
class User extends Eloquent {
public function items()
{
return $this->belongsToMany('Item', 'user_items')
->withPivot('equipped');
}
}
class Item extends Eloquent {
public function users()
{
return $this->belongsToMany('User', 'user_items');
}
}
In the pivot (user_items) table I've a very important column named "equipped".
I've a form where users can equip, unequip and throw items. This form has a hidden field with the pivot (user_items) table row id. So, when a user tries to equip an item, the system checks if the item is equipable.
So, I want a object with the pivot data and the item data, based on item_id from the pivot table, I can send to the handler (where all logic is handled).
So what I've to do is to first access the pivot table and then access the item table.
Something like this (does not work):
$item = User::find(1)->items->pivot->find(1);
Is this possible to do?
You first have to include 'equipable' on your pivot call:
return $this->belongsToMany('User', 'user_items')
->withPivot('equipable');
Then you can ask Eloquent if your item is equipable or not:
$item = User::with('items')->get()->find(1)->items->find(2)->pivot->equipable;
Keep two things in mind:
Eloquent uses 'items' as a key internally, so that might interfere.
Whatever method you put before "get()" is part of the db query. Everything after "get()" is handled by PHP on the Object. The latter will be slower in most cases.