opifer/eav-bundle
Opifer EAV Bundle brings Entity-Attribute-Value modeling to Symfony and Doctrine apps, letting you attach dynamic, configurable attributes to entities without changing their schema. Define attributes, store values, and manage flexible data.
Read this first: it is a Symfony bundle, not a Laravel package. It ships Doctrine ORM entities, a DI extension, form types, and event subscribers. With 10 stars, zero dependents, and a last release of 2016-01-13, composer require opifer/eav-bundle will not resolve on any modern Laravel/PHP 8 stack (it pins Symfony 2.3/3.0-era constraints and PHP 5.x idioms). Treat it as a reference implementation of the EAV pattern and a source of copyable code (MIT), not a dependency.
If you're evaluating it, read in this order:
Model/AttributeInterface.php + Entity/Attribute.php — the schema: name, slug, type/storage type, options, required, group.Model/ValueSetInterface.php + Entity/ValueSet.php — the bag that holds one entity's values.Entity/AttributeValue.php — the actual EAV row: value_set_id, attribute_id, plus typed columns (string_value, text_value, integer_value, boolean_value, datetime_value).AttributeType/* — the strategy layer deciding storage column, form type, and constraints per type.DependencyInjection/ — service tags and opifer_eav.classes.* overrides.First use case: a product catalog where the long tail of specs (color, weight, material, compliance flags, per-category extras) would otherwise mean a migration per attribute. Attach one ValueSet to the product; each spec becomes an AttributeValue. The pattern pays off exactly there and nowhere else.
entity_id on the value row. Keep a value_sets table and a nullable value_set_id FK on your model. This is the bundle's core design and it's correct — it makes cloning an entity a clone of the ValueSet and makes partial saves coherent.AttributeTypeInterface: a class exposing storageType(), formType(), validationRules(), cast($value). In Laravel, back it with a registry (tagged services or a config/eav.php map) resolved through a small EavManager.AttributeGroups (a template) attached to a category; a new product inherits a ValueSet seeded from the group. This is what makes EAV usable by admins instead of painful.AttributeBag that maps valueSet.values into slug => value with get($slug, $default) and back into rows on save. Controllers should never touch AttributeValue directly.valueSet.values.attribute in a single query and cache the attribute definitions (they rarely change). Never build a multi-join query to filter on an EAV field.attribute_id → insert/update/delete only the delta. This is the single biggest correctness win and the most common place reimplementations get it wrong.HasEav trait, a type registry. Faster and safer than dragging in a dead Symfony bundle through a Doctrine shim.DependencyInjection, Resources/config, and the form types. Budget for that or skip it.AttributeValue rows for the same (value_set_id, attribute_id). Add a unique index and enforce it in the repository — the bundle leans on the form layer.getStorageType() names. Changing an attribute's type later orphans old data — always migrate values, never just flip the type.onDelete('cascade'); ghost rows accumulate silently otherwise.SelectType/MultiSelectType options are entities; deleting or reordering an option referenced by existing values corrupts meaning. Block delete when referenced, store a stable key.value_set_id actually set on the entity, (3) the row's populated typed column matches the attribute's storage type. That's ~90% of EAV bugs.opifer.eav.attribute_type; opifer_eav.classes.* swaps entity classes (any override must still satisfy the interface or Doctrine mappings break). Port the tagged-registry idea, not the YAML.How can I help you explore Laravel packages today?