Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Parental Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require tightenco/parental
    

    Add the service provider to config/app.php under providers:

    Tightenco\Parental\ParentalServiceProvider::class,
    
  2. 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';
    }
    
  3. Create Subclasses: Extend the base model with polymorphic behavior:

    class Car extends Vehicle
    {
        protected $type = 'car';
    }
    
    class Truck extends Vehicle
    {
        protected $type = 'truck';
    }
    
  4. Usage:

    $car = new Car(['make' => 'Toyota']);
    $car->save(); // Automatically sets type to 'car'
    
    $vehicle = Vehicle::find($car->id); // Returns Car instance
    

Key Files to Review

  • app/Models/Vehicle.php (Base model)
  • app/Models/Car.php (Subclass)
  • database/migrations/ (Ensure type column exists)

Implementation Patterns

Common Workflows

  1. Dynamic Instantiation:

    $vehicle = Vehicle::find($id);
    if ($vehicle instanceof Car) {
        // Car-specific logic
    }
    
  2. Querying Subclasses:

    // Find all cars
    $cars = Vehicle::whereType('car')->get();
    
    // Or via polymorphic query
    $cars = Car::all();
    
  3. Mass Assignment:

    $data = ['make' => 'Ford', 'model' => 'F-150'];
    $truck = new Truck($data);
    $truck->save(); // Automatically sets type
    
  4. Polymorphic Relationships:

    class Garage extends Model
    {
        public function vehicles()
        {
            return $this->morphToMany(Vehicle::class, 'vehicle');
        }
    }
    

Integration Tips

  • 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,
            ];
        }
    }
    

Gotchas and Tips

Pitfalls

  1. Missing type Column:

    • Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'type'
    • Fix: Run migrations or add the column manually:
      Schema::table('vehicles', function (Blueprint $table) {
          $table->string('type')->nullable();
      });
      
  2. Incorrect $type Assignment:

    • Issue: Subclasses not properly setting $type can lead to ambiguous queries.
    • Fix: Always define $type in subclasses:
      class ElectricCar extends Car
      {
          protected $type = 'electric_car'; // Override if needed
      }
      
  3. Caching Quirks:

    • Problem: Cached queries may return base model instances instead of subclasses.
    • Fix: Disable caching for polymorphic queries or use fresh():
      $vehicle = Vehicle::fresh()->find($id);
      
  4. Mass Assignment Risks:

    • Issue: $type can be overwritten via mass assignment.
    • Fix: Exclude type from $fillable or use $guarded:
      protected $fillable = ['make', 'model']; // Exclude 'type'
      
  5. Soft Deletes:

    • Gotcha: Soft-deleted subclasses may not restore correctly.
    • Fix: Ensure Parent trait is used after SoftDeletes:
      use Illuminate\Database\Eloquent\SoftDeletes;
      use Tightenco\Parental\Parent;
      
      class Vehicle extends Model
      {
          use SoftDeletes, Parent;
      }
      
  6. Double Event Dispatching in Laravel 13:

    • Issue: If you bind event handlers directly to the parent class, they may be dispatched twice due to Laravel 13's changes.
    • Fix: Register events inside 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
              });
          }
      }
      

Debugging Tips

  • 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);
    });
    

Extension Points

  1. Custom Type Resolution: Override getMorphClass() in base model:

    public function getMorphClass()
    {
        return $this->type === 'electric' ? 'ElectricCar' : parent::getMorphClass();
    }
    
  2. Dynamic Type Assignment: Use whenBooted() to set $type dynamically:

    protected static function whenBooted()
    {
        static::creating(function ($model) {
            $model->type = strtolower(class_basename($model));
        });
    }
    
  3. 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);
    }
    
  4. 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]+',
    ]);
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
besmartand-pro/php-quality-config
sentix/ai-chatbot
codifyo/ts-generator-bundle
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky
spatie/mailcoach-vapor