Reputation: 188
I would like to solve the following issue:
I got multiple models like:
Product
Customer
Each model should be able to have one or more Fields
with pivot data.
Field
:
id
title
type
required
Example:
Product
has a field called video_url
, type
should be string
containing the pivot
value http://youtube.com/...
.
Customer
has a field called external_id
, type
should be integer
containing the pivot
value 242
.
The fields should be added dynamically by the user. The user should be able to decide, whether the field is morphing to Product
or Customer
(or even more later).
Maybe this helps to understand:
What I am doing right now
At the moment I created a new Model for each, product
and customer
For customers:
class CustomerField extends Model
{
/**
* @return \Illuminate\Database\Eloquent\Relations\belongsToMany
*/
public function customers()
{
return $this->belongsToMany(Customer::class)->withPivot('value');
}
}
For products:
class ProductField extends Model
{
/**
* @return \Illuminate\Database\Eloquent\Relations\belongsToMany
*/
public function products()
{
return $this->belongsToMany(Product::class)->withPivot('value');
}
}
At the moment this works out, but of course, it's not the most elegant way to solve it.
My question
Is there a possibility to morph a field dynamically to Product
or Customer
with an additional pivot
?
Upvotes: 2
Views: 4630
Reputation: 13394
I think this is what you want Polymorphic:Many-to-Many
You don't need to add ProductField
and CustomerField
models,
you just need to add Product
, Customer
and Field
model.
The fields will dynamically belongs to product or customer by fieldable_type
. Even you have more models, it will store the model name to this fieldable_type
.
And the tables you need to be created like this below:
fieldables
table has fieldable_id
and fieldable_type
;
fieldable_type
will set your model name automatically, like App\Product
, and you can custom that by yourself in AppServiceProvider
:
Relation::morphMap([
'products' => 'App\Product',
'customers' => 'App\Customer',
]);
class Product extends Model
{
/**
* Get all of the fields for the product.
*/
public function fields()
{
return $this->morphToMany('App\Field', 'fieldable')->withPivot('value');
}
}
class Customer extends Model
{
/**
* Get all of the fields for the customer.
*/
public function fields()
{
return $this->morphToMany('App\Field', 'fieldable')->withPivot('value');
}
}
class Field extends Model
{
/**
* Get all of the products that are assigned this field.
*/
public function products()
{
return $this->morphedByMany('App\Product', 'fieldable');
}
/**
* Get all of the customers that are assigned this field.
*/
public function customers()
{
return $this->morphedByMany('App\Customer', 'fieldable');
}
}
After that, you can easily create, get, update, delete pivot value like:
Field::first()->products; # return the products with pivot value
Field::first()->customers; # return the customers with pivot value
Customer::first()->fields;
$field = Field::first();
# create new relationship with pivot value between customer and fields:
Customer::first()->fields()->attach($field, ['value' => 'customer new value field']);
# update pivot with value:
Customer::first()->fields()->sync([$field->id => ['value' => 'update customer value field']]);
# Delete pivot
Customer::first()->fields()->detach($field->id);
Upvotes: 11
Reputation: 2387
The best practice is to use a separate table to hold meta information so that you can easily add/remove "columns" as needed
For example, you could set your meta table up like this:
create table `ProductField` (
products_id int(11),
column_name varchar(255),
value varchar(255),
)
Then in your products model, add functionality to get, insert, check if exists, etc.
public function getMeta($column) {
$meta = DB::table('ProductField ')
->select('column_name', 'value')
->where('products_id', '=', $this->id)
->where('column_name', '=', $column)
->get();
if (!$meta->isEmpty()) {
return $meta;
} else {
return null;
}
}
public function addMeta($column, $value) {
DB::table('ProductField ')->insert(
[
'products_id' => $this->id,
'column_name' => $column,
'value' => $value,
]
);
}
The same way you can achieve dynamic nature for Customers too.
You can also use an array to store the feilds and then dynamically add them to the model
foreach ($request->input('cost') as $key=>$cost) {
Price::create([
'product_id' => $request->product_id[$key],
'date' => Carbon::now(),
'cost' => $cost,
'trend' => 0
]);
}
If you know that there will only be certain dynamic fields ahead of time, you could opt to create accessor methods for them
Upvotes: 0