nowo-tech/form-kit-bundle
Nowo Tech Form Kit Bundle is a PHP form toolkit that provides reusable form components, configurable fields, and validation helpers, helping developers build consistent, maintainable forms and integrate them smoothly into existing applications.
This is a Symfony bundle, so the mental model maps cleanly onto Laravel form/request patterns: instead of repeating label, placeholder, help, and attr arrays on every ->add(), you declare conventions and let the bundle resolve options. Start by reading the bundle's config tree and README, because the value is entirely in the key-naming contract.
Minimal setup:
composer require nowo-tech/form-kit-bundle
Register the bundle in config/bundles.php:
return [
// ...
Nowo\FormKitBundle\NowoFormKitBundle::class => ['all' => true],
];
Add the smallest possible config first — one global default and one field-type override:
# config/packages/nowo_form_kit.yaml
nowo_form_kit:
defaults:
translation_domain: forms
field_types:
Symfony\Component\Form\Extension\Core\Type\TextType:
attr:
placeholder: true
The first use case to try is a single form with three fields: a text input, an email input, and a textarea. Delete every label, attr.placeholder, and help you would normally pass, then add translation keys matching the documented convention:
# translations/forms.en.yaml
form_registration:
email:
label: "Email address"
placeholder: "[email protected]"
help: "We will never share this."
name:
label: "Your name"
The convention is the important part: form name in snake_case (RegistrationType → registration), field name in snake_case (firstName → first_name), then .label, .placeholder, .help. Look first at:
RegistrationType becomes registration).Do not migrate every form at once. Start with one small form, confirm the resolved options with php bin/console debug:form 'App\Form\RegistrationType', and only then apply conventions broadly.
Treat configuration as a layered design system. The cascade order — global → field type → form → field — is the whole architecture. Use each layer deliberately:
required behavior, common attr classes.TextType gets a placeholder convention; EmailType gets autocomplete: email and an icon class; TextareaType gets rows: 6.Prefer translation-first forms. The day-to-day win is that your form classes become structural, not presentational:
$builder
->add('email', EmailType::class)
->add('name', TextType::class)
->add('message', TextareaType::class);
Reviewers can then read the form for what it collects, and translators can work entirely in YAML. This is the pattern to standardize on for any form with more than two fields.
Use multiple configs for genuinely different contexts. If the bundle supports named configs, define one for the public site and one for the admin area, rather than sprinkling conditionals:
nowo_form_kit:
configs:
frontend:
defaults:
translation_domain: forms
admin:
defaults:
translation_domain: admin_forms
attr:
class: form-control-sm
Then select the config at the form level or via a service. The risk here is drift: two configs with slightly different conventions produce inconsistent UIs. Keep the differences explicit and documented.
Centralize field-type defaults, not field-name defaults. Field-type rules scale across the application; field-name rules (email always gets a certain placeholder) become surprising when a second form uses email differently. If you need field-name behavior, scope it to a specific form.
Integrate with Twig themes rather than replacing them. The bundle resolves options; your form theme still renders them. Keep form_row, form_widget, and custom blocks working as-is. If the bundle provides Twig helpers, use them locally and preserve existing block structure — do not rewrite the theme to adopt the bundle.
Test resolved options, not rendered HTML. A focused form test catches convention regressions:
$form = $this->factory->create(RegistrationType::class);
$config = $form->get('email')->getConfig();
self::assertSame('Email address', $config->getOption('label'));
self::assertSame('[email protected]', $config->getOption('attr')['placeholder']);
For a bundle with a single release and zero dependents, this test is your safety net against upgrade surprises.
Migration workflow that works in practice:
field_types.debug:form and visually verify the rendered page.Extension points: prefer decorating the bundle's option resolver or extending config via YAML over forking. If you need dynamic behavior (for example, tenant-specific translation domains), implement it in a form type extension or a compiler-pass-registered service rather than embedding logic in every form.
The naming convention is the most likely source of silent failure. UserProfileType → user_profile is straightforward, but acronyms, numbers, and names like OAuth2LoginType can resolve differently than you expect. When a label does not appear, verify the resolved form name before touching translations. debug:form is the fastest check.
Clearing cache is not optional. Translation changes and config changes are cached in production-like environments. After editing YAML, run php bin/console cache:clear, then php bin/console debug:translation forms en to confirm the key exists and is not falling back to the key itself.
Merge order surprises. The documented cascade is global → field type → form → field. When two layers both define attr, confirm whether the bundle deep-merges arrays or replaces them. If it replaces, a field-level attr can wipe out a type-level class you relied on. Test this explicitly with one field that has both.
null, false, and empty string are different. label => false hides a label; label => null may mean "derive from convention." The same ambiguity applies to placeholder and help. Decide which value means "suppress" in your codebase and use it consistently.
Silent config typos. Many Symfony bundle configs ignore unknown keys unless validation is strict. A misspelled field_type or translation_domain produces no error, just a missing label. After editing config, run php bin/console debug:config nowo_form_kit (or the bundle's config alias) rather than assuming the YAML was applied.
Multiple configs need an explicit selection mechanism. If two named configs exist, make sure the form actually uses the intended one. The failure mode is subtle: the admin form renders with frontend translations and nobody notices until a translator asks why the admin strings are in the public file.
Avoid dynamic values in convention keys. Convention-based labels work for static text. If a label depends on a user, a locale-specific entity, or a route parameter, override label at the field level — do not try to encode it in YAML.
Beware form inheritance. If AdminUserType extends UserType, confirm whether the parent form's config is also applied. If it is not, you may need to duplicate or explicitly reference the parent config. This is worth a one-off test early.
Translation domain fallback. If the bundle sets a default domain, a field-level translation_domain override should win — verify it. A common bug is a global default swallowing a per-form override.
Placeholders live under attr in Symfony. The bundle may expose a placeholder option that maps to attr.placeholder internally. When debugging, inspect $config->getOption('attr'), not just getOption('placeholder').
Performance is negligible, but do not put logic in config. Option resolution happens during form building. Keep YAML declarative; if you need computed defaults, use a form type extension.
Pin the version. With zero stars and a single release, treat this as an internal dependency: pin the exact version in composer.json, keep the option-resolution test, and be prepared to fall back to explicit options if the bundle stalls. The migration is low-risk precisely because the fallback is deleting the bundle and restoring the arrays.
Do not edit vendor or generated assets. If the bundle's Twig or frontend assets need changes for your project, override templates in templates/bundles/ and use Symfony's asset conventions rather than modifying files under vendor/.
How can I help you explore Laravel packages today?