PHPackages                             pannella/laravel-cti - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. pannella/laravel-cti

ActiveLibrary[Database &amp; ORM](/categories/database)

pannella/laravel-cti
====================

A Laravel package for Class Table Inheritance support with automatic subtype casting.

3.5.2(3w ago)22.0k[1 issues](https://github.com/mattpannella/laravel-cti/issues)MITPHPPHP ^8.1CI passing

Since Jun 1Pushed 3w agoCompare

[ Source](https://github.com/mattpannella/laravel-cti)[ Packagist](https://packagist.org/packages/pannella/laravel-cti)[ Docs](https://github.com/mattpannella/laravel-cti)[ RSS](/packages/pannella-laravel-cti/feed)WikiDiscussions main Synced today

READMEChangelog (10)Dependencies (29)Versions (28)Used By (0)

Laravel CTI
===========

[](#laravel-cti)

[![Tests](https://github.com/mattpannella/laravel-cti/actions/workflows/tests.yml/badge.svg)](https://github.com/mattpannella/laravel-cti/actions/workflows/tests.yml)[![Latest Stable Version](https://camo.githubusercontent.com/e7b8ca5ab28fde3f8245c1e735d5ad90225dbd529c80385e843b154d65b3ffb0/68747470733a2f2f706f7365722e707567782e6f72672f70616e6e656c6c612f6c61726176656c2d6374692f762f737461626c65)](https://packagist.org/packages/pannella/laravel-cti)[![License](https://camo.githubusercontent.com/f79cb5aefd1250ba98f26ace4f189e26198c8fe60dfd1af3e60e8b6e488f8292/68747470733a2f2f706f7365722e707567782e6f72672f70616e6e656c6c612f6c61726176656c2d6374692f6c6963656e7365)](https://packagist.org/packages/pannella/laravel-cti)

A Laravel package for implementing the Class Table Inheritance (CTI) pattern with Eloquent models. Shared columns live in one parent table, type-specific columns live in their own tables, and a foreign key ties them together. The package handles type resolution, querying, and persistence automatically.

Features
--------

[](#features)

- Automatic model type resolution and instantiation
- Seamless saving/updating across parent and subtype tables
- Automatic batch-loading of subtype data (no N+1 queries)
- Support for Eloquent events and relationships
- Database-enforced referential integrity with real foreign keys

Requirements
------------

[](#requirements)

- PHP ^8.1
- Laravel 8.x - 13.x (`illuminate/database` &gt;=8.0 &lt;14.0)

Installation
------------

[](#installation)

```
composer require pannella/laravel-cti
```

Quick Start
-----------

[](#quick-start)

CTI uses three layers of tables: an optional **lookup table** for type definitions, a **parent table** for shared columns, and one or more **subtype tables** for type-specific columns.

```
// Parent table: shared attributes
Schema::create('assessments', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->foreignId('type_id')->constrained('assessment_types');
    $table->timestamps();
});

// Subtype table: quiz-specific attributes
Schema::create('assessment_quiz', function (Blueprint $table) {
    $table->unsignedBigInteger('assessment_id')->primary();
    $table->integer('passing_score')->nullable();
    $table->integer('time_limit')->nullable();
    $table->boolean('show_correct_answers')->default(false);

    $table->foreign('assessment_id')->references('id')->on('assessments')->onDelete('cascade');
});
```

```
// Parent model
class Assessment extends Model
{
    use HasSubtypes;

    protected static $subtypeMap = [
        'quiz' => Quiz::class,
        'survey' => Survey::class,
    ];

    protected static $subtypeKey = 'type_id';
    protected static $subtypeLookupTable = 'assessment_types';
    protected static $subtypeLookupKey = 'id';
    protected static $subtypeLookupLabel = 'label';

    protected $fillable = ['title', 'type_id'];
}

// Subtype model
class Quiz extends SubtypeModel
{
    protected $table = 'assessments'; // must be the parent table
    protected $subtypeTable = 'assessment_quiz';
    protected $subtypeAttributes = ['passing_score', 'time_limit', 'show_correct_answers'];
    protected $ctiParentClass = Assessment::class;

    protected $fillable = ['passing_score', 'time_limit', 'show_correct_answers'];
}
```

```
// Create
$quiz = Quiz::create([
    'title' => 'Final Exam',
    'passing_score' => 80,
    'time_limit' => 60,
]);

// Query (auto-joins subtype table when needed)
$hard = Quiz::where('passing_score', '>', 90)->get();

// Parent queries return correctly-typed subtype instances
$all = Assessment::all(); // mixed collection of Quiz, Survey, etc.
```

For the full setup guide including direct discriminator mode, PHP 8.1 attributes, and fillable/casts inheritance, see the [Getting Started](docs/getting-started.md) guide.

Documentation
-------------

[](#documentation)

- [Getting Started](docs/getting-started.md) - Database schema, model setup, PHP attributes, fillable/casts inheritance
- [Configuration](docs/configuration.md) - Package configuration and missing subtype data handling
- [Querying](docs/querying.md) - CRUD operations, query builder auto-joins, mass updates, supported methods
- [Relationships](docs/relationships.md) - Subtype relationships, foreign key behavior, parent relationship inheritance
- [Events](docs/events.md) - Subtype model events
- [Internals](docs/internals.md) - Type resolution, caching, batch loading, save flow, known limitations
- [API Reference](docs/api-reference.md) - Full method and property reference for all classes and traits

Why CTI?
--------

[](#why-cti)

If you have a type hierarchy in Laravel (for example, `Quiz` and `Survey` are both types of `Assessment`), there are a few common ways to model it. Each has tradeoffs.

### Single Table Inheritance (STI)

[](#single-table-inheritance-sti)

One table holds every column for every type, with a discriminator column to distinguish them.

- **Pros:** Simple queries, no joins, easy to set up.
- **Cons:** You end up with a lot of nullable columns that don't apply to most rows, and the table gets wider every time you add a new type. This violates normalization: you're storing NULLs for columns that are structurally irrelevant to a given row, not just empty.

### Separate Tables

[](#separate-tables)

Each type gets its own table (`quizzes`, `surveys`) with shared columns duplicated in each one.

- **Pros:** Clean per-type schemas, no NULLs.
- **Cons:** Shared columns are duplicated across tables. There's no unified way to query "all assessments." If you need to change a shared attribute, you have to update every table.

### Polymorphic Relations

[](#polymorphic-relations)

Laravel's `morphTo`/`morphMany` pattern stores a `*_type` and `*_id` pair so one entity can relate to multiple unrelated model types.

- **Pros:** Flexible, built into Eloquent, and works well for its intended purpose (e.g., a `Comment` that can belong to either a `Post` or a `Video`).
- **Cons:** Polymorphic relations are a relationship pattern, not an inheritance pattern. They solve "entity A relates to multiple unrelated entity types," not "entities A1 and A2 are specialized versions of entity A." Because the `*_id` column references different tables depending on the type value, you can't put a real foreign key constraint on it. Referential integrity is only enforceable in application code. Trying to use polymorphic relations to model a type hierarchy requires a lot of custom wiring and you lose the ability to query the parent type as a unified collection.

### Class Table Inheritance (this package)

[](#class-table-inheritance-this-package)

Shared attributes live in a parent table, type-specific attributes live in separate subtype tables linked by foreign key.

- **Pros:** Properly normalized. No nullable columns, no duplicated columns, real foreign key constraints everywhere. You can query the parent type and get back a mixed collection of correctly-typed subtype instances. Subtype-specific queries auto-join as needed.
- **Cons:** More tables to manage. Reads require joins (handled automatically by the package). Writes touch multiple tables (wrapped in transactions by the package). Initial setup is a bit more involved than STI.

### When to use CTI

[](#when-to-use-cti)

CTI is the right fit when your subtypes share an identity (a Quiz *is* an Assessment), share common attributes (title, timestamps), and each type also has its own attributes (passing\_score, anonymous). If your types are unrelated entities that just happen to share a relationship, polymorphic relations are the better tool.

License
-------

[](#license)

MIT

###  Health Score

46

—

FairBetter than 92% of packages

Maintenance85

Actively maintained with recent releases

Popularity24

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity56

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 70.5% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~21 days

Total

19

Last Release

23d ago

Major Versions

1.0.5 → v2.x-dev2026-02-06

2.1.1 → 3.1.02026-03-17

PHP version history (3 changes)1.0.0PHP ^7.4|^8.0

1.0.3PHP ^8.0

3.1.0PHP ^8.1

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/4806679?v=4)[Matt Pannella](/maintainers/mattpannella)[@mattpannella](https://github.com/mattpannella)

---

Top Contributors

[![mattpannella](https://avatars.githubusercontent.com/u/4806679?v=4)](https://github.com/mattpannella "mattpannella (55 commits)")[![mattpannella-tovuti](https://avatars.githubusercontent.com/u/269302592?v=4)](https://github.com/mattpannella-tovuti "mattpannella-tovuti (23 commits)")

---

Tags

laravelormeloquentinheritancepolymorphicclass table inheritanceCTIsubtype castingmulti-table

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/pannella-laravel-cti/health.svg)

```
[![Health](https://phpackages.com/badges/pannella-laravel-cti/health.svg)](https://phpackages.com/packages/pannella-laravel-cti)
```

###  Alternatives

[mongodb/laravel-mongodb

A MongoDB based Eloquent model and Query builder for Laravel

7.1k8.4M96](/packages/mongodb-laravel-mongodb)[kirschbaum-development/eloquent-power-joins

The Laravel magic applied to joins.

1.6k32.6M46](/packages/kirschbaum-development-eloquent-power-joins)[ymigval/laravel-model-cache

Laravel package for caching Eloquent model queries

7962.6k4](/packages/ymigval-laravel-model-cache)[cybercog/laravel-ownership

Laravel Ownership simplify management of Eloquent model's owner.

9029.2k3](/packages/cybercog-laravel-ownership)[phaza/single-table-inheritance

Single Table Inheritance Trait

1515.8k](/packages/phaza-single-table-inheritance)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
