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

Formmetadata Laravel Package

corleonis/formmetadata

Corleonis FormMetadata is a Laravel package for attaching and managing structured metadata on forms, making it easy to store custom field attributes, configuration, and other form-related details in a consistent, retrievable way.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: the package is a Symfony bundle (FormMetadataBundle), not a Laravel package. There is no composer require path into a Laravel app, no service provider, no Eloquent/FormRequest hook. Before you install anything, decide whether you are (a) evaluating this in a Symfony scratch app to understand the pattern, or (b) porting the pattern onto Laravel. The steps below assume the former; the pattern is what you actually take to work.

Where to look first. The README is the entire documentation. Read it for the annotation syntax (@Form\Field("text", options: {...})) and the class-level association. Then read the bundle's Annotation/ and Form/ directories — the annotation-to-FormBuilder mapping is small (a few hundred lines), and the field-type map it maintains is the piece you will reimplement or replace with Symfony's built-in type guessing.

Minimal steps for the Symfony spike:

  1. Require it in a scratch Symfony app whose PHP/Symfony/Doctrine versions you are willing to run:
    composer require corleonis/formmetadata
    
  2. Register the bundle — new Corleonis\FormMetadataBundle\CorleonisFormMetadataBundle() in app/AppKernel.php (pre-Flex) or config/bundles.php (Flex).
  3. Ensure the annotation reader is enabled (framework: { annotations: true } or annotations.yaml). On Symfony 6/7 the annotation reader has been retired — if this step fails, that is your answer about compatibility.
  4. Annotate an entity property:
    /** @Form\Field("text", options={"label" = "Title", "placeholder" = "Enter a title"}) */
    private $title;
    
  5. In a controller, resolve the entity's field metadata and iterate it against the Form builder — this is the "generic form" moment you are evaluating.

First use case to validate. A generic admin CRUD controller over a small CMS entity (Article or Page). If one controller + one annotation set can render create/edit forms for that entity without hand-written FormType classes, the pattern is doing its job. Do not start with a complex entity; the goal is to confirm the metadata contract, not to productionize.

For Laravel, the first step is not installing — it is copying the idea. See Implementation Patterns for the equivalent Laravel surface.

Implementation Patterns

1. Annotation-to-form metadata (the core pattern). Field declarations live with the domain class, not in a separate form type or Blade template:

/** @Form\Field("email", options={"label" = "Email", "required" = true, "maxLength" = 255}) */
private $email;

A resolver walks the class, returns field definitions keyed by property, and a builder iterates those definitions onto a form (Symfony) or a validation array + view data (Laravel). The whole value is that one generic controller can render any entity that carries this metadata.

2. Generic resource controller over a whitelist. The day-to-day workflow is: register entity classes, point the controller at a class, and it renders and persists based on declared fields. Critical detail — never trust the declaration alone for persistence. Build an explicit per-field allowlist that is checked independently of the metadata annotation. In Laravel terms:

// Pseudo - a generic controller that reads a metadata registry
$fields = FormMetadata::for(Article::class);   // registry resolves attributes -> definitions
$rules  = FormMetadata::rules(Article::class); // ['title' => 'required|string|max:255', ...]
$data   = $request->validate($rules);          // only declared + allowed fields

3. Keep validation and presentation aligned from a single source. The reason to centralize is that required, maxLength, input type, and options should be authored once and consumed by both the validator and the view. If you add a Laravel port, define the contract as: field name, type, options, validation constraints, persistence target, authorization rule. Freeze that spec before writing builders or you will rewrite them.

4. Registry + cache, not per-request reflection. In both Symfony and Laravel, resolving attributes via ReflectionClass on every admin page load is waste. Scan at build/compile time and cache the class → field-definitions map (file, config cache, or Redis). In Laravel, the clean hook is a service provider that builds the registry and a cache-busting key tied to deployment.

5. Laravel-native port of the pattern (recommended over integration):

  • Declaration: PHP 8 attributes (not docblock annotations) on Eloquent models.
  • Resolution: a registry that resolves Model::class → definitions, cached.
  • Validation: a mapper that emits FormRequest rules from definitions.
  • Rendering: Blade/Livewire field components selected by declared type.
  • Controller: a generic resource controller that authorizes each declared field explicitly before persisting.

This is a bounded internal library (days, not weeks) and gives you the benefit of the package — one generic controller over heterogeneous entities — without the maintenance risk.

6. Workflow for extending. When a new entity type arrives: author the attribute/annotation set, run the registry, confirm the builder renders it, and confirm the allowlist covers each field. If any step needs hand-written form code, the metadata layer is incomplete for that field type — fix the registry/builder, not the controller.

Gotchas and Tips

Framework mismatch is the headline gotcha. The prompt calls this a Laravel package; it is a Symfony bundle. There is no Laravel integration, service provider, or Eloquent hook. Do not budget for an install — budget for a reimplementation of the concept. On an actual Symfony estate, the annotation reader removal in modern Doctrine/Symfony means it likely does not run unmodified on Symfony 6/7 / PHP 8.2+.

Annotation era is a dead end; attributes are the forward path. @Form\Field(...) depends on docblock annotation reading, which modern Symfony/Doctrine have deprecated/removed. If you fork, convert the annotation driver to PHP 8 attributes before relying on it.

Zero maintenance signal — treat it as owned code. No stars, no dependents, no visible release cadence. There is no community absorbing bugs, no security patch stream, and no compatibility guarantee. If you vendor/fork it, you inherit the maintenance tax, a changelog, and an upgrade review cadence. Budget that explicitly.

Silent metadata drift. An entity column can change type while the annotation does not; the form then writes a value the database rejects. Add a test (or a dev-time check) that compares declared field types against the underlying schema/column types and fails loudly on mismatch.

Mass-assignment and privilege escalation through generic controllers. The generic controller is exactly where a missing authorization check becomes a vulnerability. Every declared field needs an explicit allowlist entry, and field-level authorization (who can edit what) must be enforced independently of whether a field is declared. Do not let the metadata annotation double as the authorization decision.

Field-type map is a permanent tax. The key TPM question — does the bundle wrap Symfony's Form type guessing or maintain its own map? If it maintains its own, every new field type is a manual addition. Prefer to ride the framework's type system where possible.

Caching and stale schemas after deploy. If you compile the registry and cache it, ensure the cache key is tied to deployment/version, or the first render after a deploy can serve an old schema with missing fields. Add an integration test that boots the registry fresh and compares against cache.

Strict types break "required" assumptions. A declared required=true on the form does not imply a non-nullable DB column, and vice versa — align both at the contract level, not in your head.

Extension points are the redeeming feature. The annotation-to-FormBuilder bridge is the prior art worth studying. Read it to learn the shape of the metadata registry and builder; replicate that contract with PHP 8 attributes in your target stack. That is the durable takeaway, not the code.

Verification checklist if you proceed on Symfony: confirm php, symfony/*, doctrine/* constraints and last-commit date; confirm license and fork/transfer path; run one entity end-to-end through the builder; then pin the fork and write golden tests around metadata parsing before it touches production.

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.
kjmtrue/laravel-debugbar
hexincorp/debugbar
razorbacks/laravel-shibboleth
adilazhari/laravel-idempotency
mamitech/laravel-slow-query-log
mamitech/laravel-http-logger
medeirosrafael/docxmustache
webdevlabs/phreak
vis/log-envelope
digitickets/lalit
acacha/names
scool/foundation
frankkessler/guzzle-oauth2-middleware
delatbabel/site-config
delatbabel/applog
eusonlito/laravel-gettext
henzeb/housepedia-intelligence-laravel
aliqsm/laravel-authkit
itsjustvita/laravel-wp-migrator
uslanozan/laravel-cache-contract