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

Simple Metadata Bundle Laravel Package

idci/simple-metadata-bundle

A small Symfony bundle for attaching simple metadata to your objects, letting you store extra key-value data alongside entities without schema changes. Lightweight and easy to wire into existing projects, though it has minimal adoption and may need review before production use.

View on GitHub
Deep Wiki
Context7

Getting Started

The honest first step: idci/simple-metadata-bundle will not install or boot in a Laravel application. It is a Symfony 2 bundle — no ServiceProvider, no facade, no publishable config, and its composer.json pins Symfony 2.x components and a Doctrine ORM bridge that hard-conflict with PHP 8.x and any Laravel 9+. composer require idci/simple-metadata-bundle will either fail resolution or install code that never executes in Laravel's kernel. With 2 stars and 0 dependents, there is no community fork to fall back on either.

So "getting started" means one of two things, and you should decide within the first 30 minutes:

  1. Read the source as a design reference (default, recommended). Clone the repo, open the Entity/ and Service/ directories, and read the metadata model plus the get/set/remove service. The whole bundle is small enough to read in an hour. You are extracting the pattern, not the code.
  2. Fork and port (only if the source contains non-trivial logic — typed value coercion, key inheritance, validation — that would take longer to re-derive than strip out). Budget 3–10 days, expect to own the fork forever, and expect zero of the Symfony wiring to survive.

Where to look first when reading the source:

  • The Metadata entity mapping — this tells you the storage model (side table vs. serialized column, polymorphic vs. per-entity). This is the architectural decision you actually care about.
  • The service that reads/writes keys — this defines upsert semantics (overwrite vs. error vs. append), which is the #1 source of production surprises.
  • composer.json — confirm the PHP and Symfony constraints before investing any more time. If it requires symfony/*: ^2.x, stop and build natively.

First use case to target when you reimplement: attaching per-tenant or per-integration attributes to a single domain model (e.g. a Product with partner-specific fields, an Order with fulfillment-provider metadata). Start there rather than attempting a general-purpose metadata layer across every model — the indexing and query needs differ wildly per model.

The 10-minute native baseline if you skip the source entirely: a JSON column with an Eloquent cast covers the majority of "attach arbitrary attributes" needs and is a fraction of the work:

// migration
$table->json('metadata')->nullable();

// model
protected $casts = ['metadata' => 'array'];

Reach for the polymorphic side table only when you need to query across metadata keys efficiently or attach metadata to many heterogeneous models.

Implementation Patterns

Pattern 1 — JSON column with a cast (start here)

Best when metadata is owned by one model, read by key, and queried rarely or coarsely.

class Product extends Model
{
    protected $casts = ['metadata' => 'array'];

    public function setMeta(string $key, mixed $value): static
    {
        $meta = $this->metadata ?? [];
        $meta[$key] = $value;
        $this->metadata = $meta;
        return $this;
    }

    public function getMeta(string $key, mixed $default = null): mixed
    {
        return data_get($this->metadata, $key, $default);
    }
}

This is roughly what the bundle's service layer gives you, without any of the framework mismatch. It is the correct default for a Laravel team.

Pattern 2 — Polymorphic metadata side table (when you must query across keys)

Choose this only if the product needs "find all records where metadata x = y". Implement the bundle's model shape natively:

// metadata table: metable_type, metable_id, key, value, type, timestamps
// composite index on (metable_type, metable_id, key) — and on (key, value) if you filter by key
trait HasMetadata
{
    public function metadata(): MorphMany
    {
        return $this->morphMany(Metadata::class, 'metable');
    }

    public function setMetadata(string $key, mixed $value): Metadata
    {
        return $this->metadata()->updateOrCreate(
            ['key' => $key],
            ['value' => $this->encodeValue($value), 'type' => get_debug_type($value)]
        );
    }
}

Note the updateOrCreate on ['key' => $key] — upsert semantics are the thing the bundle handles and the thing you must specify deliberately.

Pattern 3 — Behind a repository/registry interface

Regardless of storage, expose metadata through a small interface (MetadataStore) and bind it in a service provider. This is the pattern the Symfony bundle enforces structurally via its service layer, and it is the one piece worth copying verbatim in spirit. It means a JSON-column implementation today can become a side-table implementation tomorrow via a container binding, not a refactor.

Integration tips

  • Type fidelity: the bundle's type column exists for a reason — "1", 1, and true round-trip differently. Cast on write and on read; don't rely on the database to preserve type in a string column.
  • Bulk attach: wrap multi-key writes in a transaction and use upsert/updateOrCreate batches; per-key saves are an N+1 storm on save.
  • Eager loading: always ->with('metadata') when listing models, exactly as you would with any morph relation.
  • Validation: the bundle gives you storage, not governance. Maintain an allowed-key registry (config array or enum) and validate keys/values at write time — this is custom work either way.
  • Cascade cleanup: add ->cascadeOnDelete() to the morph foreign key or a model deleting observer; orphaned metadata rows are the classic failure mode of this pattern.

Gotchas and Tips

  • This is not installable in Laravel. Period. Do not attempt --ignore-platform-reqs to force it in — dependency resolution may succeed, but Laravel's kernel never registers Symfony bundles, so you get dead code and a false sense of having adopted a library.
  • Framework mismatch is total. A Symfony bundle ships a Bundle class, a DI extension, YAML/XML service definitions, and Doctrine event hooks. None of that has a Laravel analogue. Even the "reusable" domain layer likely imports Symfony\Component\* and Doctrine mapping annotations throughout — check before assuming anything is salvageable.
  • Symfony 2 is EOL (Nov 2018). No security patches, no upgrade path, no hiring pool. That is a liability, not just a technical constraint.
  • Composer constraints will bite. Expect PHP 5.6/7.0 pins and symfony/*: ^2.x. Under Laravel 10/11 (PHP 8.1/8.2) this is a hard conflict, and even if you port the source, PHP 8 deprecations (dynamic properties, implicit nullables, ${var} interpolation) will surface as fatal errors under Laravel's error handler.
  • Debugging a port is source-reading, not docs. There is no documentation, no test suite (verify — assume none), and no Stack Overflow corpus. Ramp-up is ~0.5 days of reverse-engineering intent.
  • Duplicate-key writes are silent. The single most important behavior to nail down: does setMetadata('x', ...) when x already exists overwrite, throw, or append? The bundle's service defines this; your reimplementation must define it explicitly too, or you get intermittent data loss.
  • EAV query performance is the real risk, not the package. "Find records where metadata X = Y" needs a per-key index or a JSON/GIN-style index — the bundle will not give you either. Design the schema around the actual access patterns (read-by-key vs. filter-by-key vs. bulk-attach) before writing migrations.
  • JSON columns beat side tables for reads, lose for cross-record filters. Choose deliberately: JSON cast for "read this record's attributes", side table for "filter the list by an attribute". Don't inherit the bundle's choice by default.
  • Extension point worth keeping: the interface-plus-container-binding pattern (Pattern 3 above). It is the one design decision from the bundle that pays off long-term.
  • Verdict for day-to-day leverage: treat this repository as a 1-hour design reference, then spend 1–3 days building a native Laravel package (JSON cast + trait, escalating to a morph side table only if filtering by key is a real requirement). The license (MIT) makes any code reuse legal; the framework mismatch, EOL status, and zero adoption make adoption impractical. Keep the capability on the backlog; keep the package out of composer.json.
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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
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