Weave 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

Laravel Polymorphic Eav Laravel Package

davidwesdijk/laravel-polymorphic-eav

Add flexible, polymorphic EAV attributes to any Laravel model. Store custom fields as attribute-value pairs without schema changes, attach them to multiple models, and query them with familiar Eloquent syntax. Great for dynamic product or user metadata.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package with Composer and publish its assets:

composer require davidwesdijk/laravel-polymorphic-eav
php artisan vendor:publish --provider="Davidwesdijk\LaravelPolymorphicEav\EavServiceProvider"
php artisan migrate

Publishing gives you the config file (table names, model bindings) and the migrations for the EAV tables — typically an attributes table holding the definitions and a values table holding the polymorphic valuable_type / valuable_id pair plus attribute_id and the actual value.

Where to look first:

  1. config/eav.php — confirm table names and which Eloquent models are bound; override them here rather than editing vendor classes.
  2. The published migrations — check column types and indexes before you build on top of them; you will likely want to add an index on (attribute_id, value) for filtering.
  3. The package's trait (usually applied via use HasEavAttributes; on your model) and the Attribute / Value models.

First use case — a Product that needs a different set of fields per category:

use Davidwesdijk\LaravelPolymorphicEav\Traits\HasEavAttributes;

class Product extends Model
{
    use HasEavAttributes;
}

Then create definitions once (seeders, not runtime) and attach values per entity:

$color = Attribute::create(['name' => 'color', 'type' => 'string', 'slug' => 'color']);

$product->setEavAttribute('color', 'red');
$product->save();

$product->getEavAttribute('color'); // 'red'

Exact method names should be confirmed against the README for your installed version; the mental model — definitions are global, values are per-entity and polymorphic — is the part that matters.

Implementation Patterns

Treat attribute definitions as schema, values as data. Create Attribute rows in migrations or seeders so they are versioned. Never let end users create definitions ad hoc; that turns your EAV store into an unvalidated free-for-all and makes reporting impossible.

Seed once, read everywhere. Load the attribute definitions once at boot (a cached collection keyed by slug) so your forms, validation rules and casts all derive from a single source:

$attributes = Cache::rememberForever('eav.attributes', fn () => Attribute::all()->keyBy('slug'));

Flush that cache in an Attribute model observer on save/delete.

Dynamic form generation. Loop the definitions for the entity's category, render the right input per type, then validate with a rule map built from the same definitions. This is the single biggest win the package gives you: one attributes table drives the admin UI for products, pages, users, whatever.

Eager load the relation, always. The values relation plus its attribute is a two-level eager load; skipping it is how EAV turns one query into two hundred:

$products = Product::with('eavValues.attribute')->paginate(25);

Filtering by attribute value is best done with whereHas against the polymorphic relation, or a join on the values table when you need to sort. For anything dashboard-like, write one named scope per hot filter rather than scattering whereHas through controllers.

Keep hot fields as real columns. Fields you sort, index, or aggregate on (price, status, published_at) belong in the main table. EAV is for the long tail of sparse, per-category fields. A hybrid — real column plus EAV overflow — is the only pattern that survives growth.

Polymorphic reuse. Because values are morphable, the same Attribute definitions can be shared across Product, Page and User. Use Relation::enforceMorphMap([...]) in a service provider so valuable_type stores stable aliases instead of FQCNs — this makes refactors safe.

Gotchas and Tips

  • The package is unmaintained. Last release August 2021, zero dependents, six stars. Before committing, verify it installs against your Laravel version, and vendor-fork it into app/ or a private package if you hit a dependency conflict. Budget for owning the code.
  • N+1 is the default failure mode. Any blade loop that touches $model->eavValues without with() will quietly explode. Add a debug assertion or Laravel's Model::preventLazyLoading() in local.
  • No type safety. Values land in a string column. Cast on read, validate on write, and enforce an enum of allowed types on Attribute. A boolean stored as "0"/"1" compared with === true will bite you.
  • Uniqueness per entity. Add a unique index on (valuable_type, valuable_id, attribute_id) in your own migration; without it, duplicate values appear the first time a form double-submits.
  • Orphaned values. Morph relations don't cascade. Add a deleted model event on each EAV-enabled model to purge values, or clean up in a scheduled command — otherwise the values table grows forever.
  • Morph map drift. If you introduce enforceMorphMap after data exists, existing rows keep the old FQCN strings and silently stop resolving. Backfill in the same migration.
  • Bulk writes skip events. Product::insert() or update() on a query builder bypasses the model layer entirely, so anything hooked into saved won't fire. Use save() per model, or handle EAV writes explicitly.
  • Debugging queries. ->toSql() and ->getBindings() on a whereHas chain is the fastest way to see whether your attribute filter is actually constraining the outer query or leaking into a subquery.
  • Extension points are the config bindings: swap the Value model for your own subclass to add casts, accessors or scopes, and register an observer on Attribute to manage caching. Do that instead of patching vendor files, so a future fork stays mergeable.
  • Test the shape, not the storage. Assertions against $product->getEavAttribute('color') survive refactors to JSON columns or real columns later; assertions against raw values rows don't.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource