Reputation: 322
What would be the best way to achieve this in Laravel? I'm mentioning Laravel specifically because I'm still rather new to Laravel. I'm trying to implement it therein, and I'm sure there's an aspect of Laravel that I'm not familiar with yet that will solve this elegantly.
Let me clarify with a simple example:
The original design
Suppose you have a car model:
class Car extends Model
{
protected $fillable = [
'manufacturer',
'model',
'engine', //json field emulating a table
]
}
The non-normalized 'engine' string field contains json. The json will always have a 'type' attribute but from there it goes in all directions. Examples (using my incomplete knowledge of car engines):
{ "type": "petrol", "attributes":{"base_engine_type":"V-8", "size":"5.7L"...} }
{ "type": "electric", "attributes":{"battery":"Lithium-Ion", "charge_period_hours":"20"...} }
{ "type": "steam", "attributes":{"pressure_bar":"300", "spontanious_steampunk_world_transport_percent":"42"...} }
and so on.Use case
The Car model will have a Vue page with a dropdown list of the various engine types. On selection, a component-specific to the engine type and it's respective attributes will be presented.
My solutions
I use an Engine JsonResource
to emulate a model for communication between the controller and the page.
Car
model.
I've only speculated on this. The attributes added in the constructor doesn't reflect. But the gist of it is:class Engine extends Model
{
public function __construct(array $attributes=[])
{
parent::__construct($attributes);
foreach ($this->attributes()->get() as $attr) {
$name = $attr->name;
$this->append($name)->$name = $attr->value;
$this->fillable[] = $name;
}
}
public function attributes() {
return $this->hasMany(EngineAttribute::class);
}
}
class EngineAttribute extends Model
{
protected $fillable = [
'name',
'value',
];
}
The wood and the trees
The first solution works the best so far but violates 1NF and isn't properly scalable. The second solution introduces horrible complexity further on in the app, and I'd rather avoid it.
Suggestions, alternate solutions, suggested revisions to my question, or general illumination will be greatly appreciated.
(edit: fixed the JSON examples)
Update
dung ta van: thanks again for your answer my man. But I see your Laravel style and I raise you with something even more Laravel style. It was an arduous journey. Filled with strange traits and terrifying Pivot extending joining models spawned by hideous joining tables capturing the souls of many a model via their true class name. But I return safely with a tatter of sanity luckily still intact and bring us this prize: polymorphic relationships I applied this strange magic I found to your already nice code and lo! It started transforming into:
<?php
class EngineType extends Model {
public function attributes()
{
return $this->morphMany(
EngineAttribute::class,
'attributable'
);
}
}
class EngineAttribute extends Model {
public function attributable()
{
return $this->morphTo();
}
}
class Car extends Model {
public function engineType() {
return $this->hasOne(EngineType::class);
}
}
The tome of Ottwell I referenced above contains the rest of the incantations, but this untested alchemy will provide the idea.
Upvotes: 1
Views: 405
Reputation: 1026
In my opinion, the option 2nd is better, it's clearer and scalable, but we can improve a bit, we can remove Engine
class
In php style
class EngineType {
public $id;
public $typeName;
}
class EngineAttribute {
public $cardId;
public $engineTypeId;
public $name;
public $value;
}
class Car {
public $id;
public $manufacturer;
public $model;
public $engineTypeId;
}
In laravel style:
class EngineType extends Model {
}
class EngineAttribute extends Model {
}
class Car extends Model {
public function engineType() {
return $this->hasOne(EngineType::class);
}
public function attributes() {
return $this->hasManyThrough(EngineAttribute::class, EngineType::class, 'card_id', 'engine_type_id', 'id', 'id');
}
public function toJson() {
return json_encode($this);
}
}
Upvotes: 1