tightenco/parental
Single Table Inheritance for Laravel Eloquent. Define a parent model with child classes stored in one table, automatically casting records to the right type. Great for polymorphic-like data without multiple tables, with simple setup and familiar Eloquent APIs.
Installation:
composer require tightenco/parental
Add the service provider to config/app.php under providers:
Tightenco\Parental\ParentalServiceProvider::class,
First Use Case:
Define a model with Parent trait and a type column:
use Tightenco\Parental\Parent;
class Vehicle extends Model
{
use Parent;
protected $type = 'vehicle';
}
Create Subclasses: Extend the base model with polymorphic behavior:
class Car extends Vehicle
{
protected $type = 'car';
}
class Truck extends Vehicle
{
protected $type = 'truck';
}
Usage:
$car = new Car(['make' => 'Toyota']);
$car->save(); // Automatically sets type to 'car'
$vehicle = Vehicle::find($car->id); // Returns Car instance
app/Models/Vehicle.php (Base model)app/Models/Car.php (Subclass)database/migrations/ (Ensure type column exists)Dynamic Instantiation:
$vehicle = Vehicle::find($id);
if ($vehicle instanceof Car) {
// Car-specific logic
}
Querying Subclasses:
// Find all cars
$cars = Vehicle::whereType('car')->get();
// Or via polymorphic query
$cars = Car::all();
Mass Assignment:
$data = ['make' => 'Ford', 'model' => 'F-150'];
$truck = new Truck($data);
$truck->save(); // Automatically sets type
Polymorphic Relationships:
class Garage extends Model
{
public function vehicles()
{
return $this->morphToMany(Vehicle::class, 'vehicle');
}
}
Laravel 13.x Compatibility:
Due to changes in Laravel 13's event handling system, ensure model events are registered inside whenBooted callbacks. For example:
class Vehicle extends Model
{
use Parent;
protected static function whenBooted()
{
static::created(function ($model) {
// Event logic for created
});
}
}
APIs: Use instanceof checks in controllers to route logic:
public function handleVehicle(Vehicle $vehicle)
{
if ($vehicle instanceof Car) {
return response()->json(['type' => 'car']);
}
}
Forms: Dynamically render fields based on $vehicle->type:
@if($vehicle instanceof Car)
<input name="make" value="{{ $vehicle->make }}">
@endif
Validation: Extend FormRequest to validate based on type:
public function rules()
{
$rules = ['type' => 'required'];
if ($this->vehicle instanceof Car) {
$rules['make'] = 'required';
}
return $rules;
}
API Resources: Override toArray() per subclass:
class CarResource extends JsonResource
{
public function toArray($request)
{
return [
'make' => $this->make,
'model' => $this->model,
];
}
}
Missing type Column:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'type'Schema::table('vehicles', function (Blueprint $table) {
$table->string('type')->nullable();
});
Incorrect $type Assignment:
$type can lead to ambiguous queries.$type in subclasses:
class ElectricCar extends Car
{
protected $type = 'electric_car'; // Override if needed
}
Caching Quirks:
fresh():
$vehicle = Vehicle::fresh()->find($id);
Mass Assignment Risks:
$type can be overwritten via mass assignment.type from $fillable or use $guarded:
protected $fillable = ['make', 'model']; // Exclude 'type'
Soft Deletes:
Parent trait is used after SoftDeletes:
use Illuminate\Database\Eloquent\SoftDeletes;
use Tightenco\Parental\Parent;
class Vehicle extends Model
{
use SoftDeletes, Parent;
}
Double Event Dispatching in Laravel 13:
whenBooted callbacks to avoid duplication:
class Vehicle extends Model
{
use Parent;
protected static function whenBooted()
{
static::created(function ($model) {
// This will only be called once
});
}
}
Check Type Column:
dd(Vehicle::find($id)->type); // Verify type matches subclass
Query Logs: Enable query logging to debug polymorphic queries:
DB::enableQueryLog();
$vehicle = Vehicle::find($id);
dd(DB::getQueryLog());
Instance Checks:
Use getMorphClass() to debug:
dd(Vehicle::find($id)->getMorphClass()); // Should match subclass name
Event Dispatching: Check if events are being dispatched multiple times by logging inside event handlers:
static::created(function ($model) {
\Log::info('Vehicle created: ' . $model->id);
});
Custom Type Resolution:
Override getMorphClass() in base model:
public function getMorphClass()
{
return $this->type === 'electric' ? 'ElectricCar' : parent::getMorphClass();
}
Dynamic Type Assignment:
Use whenBooted() to set $type dynamically:
protected static function whenBooted()
{
static::creating(function ($model) {
$model->type = strtolower(class_basename($model));
});
}
Global Scopes: Filter by type globally:
class TypeScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
return $builder->where('type', $model->type);
}
}
// Register in base model
protected static function boot()
{
static::addGlobalScope(new TypeScope);
}
API Versioning:
Use Parent with API resources to version endpoints:
Route::get('/vehicles/{vehicle}', function (Vehicle $vehicle) {
return new VehicleResource($vehicle);
})->where([
'vehicle' => '[0-9]+',
]);
How can I help you explore Laravel packages today?