I had a situation come up where I needed an Eloquent model to hydrate dynamically into a specific class that extends my "normal" eloquent model. Here's a generic layout loosely based on typical electronics:
Data Object:
['id', 'model_type', 'name', 'serial',...]
If the data is all in a single table (MySQL), Laravel doesn't really have a way to pull that data directly as a polymorph. You can polymorph a relationship, but not a model directly.
Essentially the reasoning for this is to separate logic that may be concerned with one type, but not another. For example, Model Type A
and Model Type B
can both implement an interface which describes their capabilities, but the specific logic for A doesn't need to pollute B.
My solution for this is to override the newFromBuilder
method on the model (Laravel 5.6). Like so:
App\Schemes\BaseScheme
abstract class BaseScheme extends Electronic
{
// abstract methods to implement
// ...
}
App\Schemes\ElectronicTypeA
class ElectronicTypeA extends BaseScheme
{
// Electronic Type A logic
}
App\Schemes\ElectronicTypeB
class ElectronicTypeB extends BaseScheme
{
// Electronic Type B logic
}
App\Models\Electronic
use Illuminate\Database\Eloquent\Model;
class Electornic extends Model
{
public function newFromBuilder($attributes = [], $connection = null)
{
if (!class_exists($attributes->model_type)) {
throw new \Exception("Invalid Scheme ({$attributes->model_type})");
}
$class = $attributes->model_type;
$model = new $class;
if (!$model instanceof \App\Schemes\BaseScheme) {
throw new \Exception("Scheme class is invalid ({$attributes->model_type})");
}
$model->exists = true;
$model->setRawAttributes((array) $attributes, true);
$model->setConnection($connection ?: $this->getConnectionName());
$model->fireModelEvent('retrieved', false);
return $model;
}
Where \App\Schemes\BaseScheme
is the abstract all the logic models extend. \App\Schemes\BaseScheme
also extends the original Eloquent model.
The really nice thing about this is that it works on the returned collection too. So you can interact with the model just as if it were a normal model--but you're really interacting with the specific classes (typeA, typeB).