folliked/form-bundle
A Laravel form bundle for defining, validating and rendering forms from a single configuration. Build reusable fields, rules and layouts, then output consistent forms across your PHP application with less boilerplate and easier customization.
This bundle predates Symfony Flex and the last tagged release is from June 2020, so treat the first hour as an archaeology exercise rather than a normal composer require.
1. Install and pin
composer require folliked/form-bundle
# If it resolves against an old Symfony, pin explicitly:
composer require "folliked/form-bundle:^1.0" --with-dependencies
Check composer why-not folliked/form-bundle and the package's composer.json constraints before you fight the resolver. If it only allows Symfony 2/3, either run it in a legacy app or fork/vendor it.
2. Register the bundle
Symfony 4+ (config/bundles.php):
return [
// ...
Folliked\SHFormBundle\FollikedSHFormBundle::class => ['all' => true],
];
Legacy (app/AppKernel.php):
$bundles = [
// ...
new Folliked\SHFormBundle\FollikedSHFormBundle(),
];
Confirm the exact FQCN from vendor/folliked/form-bundle/ — with 0 stars and no docs, the README and the bundle class are your source of truth.
3. Configure keys and defaults
Add a config file (name varies by bundle; check for Configuration.php in DependencyInjection/). Typical shape:
# config/packages/sh_form.yaml
sh_form:
recaptcha:
public_key: '%env(RECAPTCHA_PUBLIC_KEY)%'
private_key: '%env(RECAPTCHA_PRIVATE_KEY)%'
captcha:
width: 200
height: 60
4. Wire the front-end assets once, in your base layout
These types are jQuery/jQuery-UI/TinyMCE-driven and do not ship a working asset pipeline for modern Symfony. Load them yourself, in order:
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
<script src="https://cdn.tiny.cloud/1/no-api-key/tinymce/5/tinymce.min.js"></script>
First use case: a contact form with reCAPTCHA + TinyMCE
use Folliked\SHFormBundle\Form\Type\RecaptchaType;
use Folliked\SHFormBundle\Form\Type\TinymceType;
$builder
->add('name', TextType::class)
->add('message', TinymceType::class)
->add('recaptcha', RecaptchaType::class);
Render, submit, and confirm the validator actually fires. That single form exercises the config, the asset loading, and the server-side verification path — the three things that break first.
Treat each type as a thin HTML/JS decorator, not a black box. The value-add here is rendering (captcha_gd image tags, TinyMCE init, jQuery UI widgets). Validation semantics for everything except captcha/reCAPTCHA are typically delegated to your own constraints.
Standard form usage
$builder->add('publishedAt', JqueryDateType::class, [
'widget' => 'single_text',
'attr' => ['class' => 'js-datepicker'],
]);
$builder->add('priority', JquerySliderType::class, [
'attr' => ['min' => 0, 'max' => 100, 'step' => 5],
]);
$builder->add('tags', JqueryAutocompleteType::class, [
'route' => 'app_tag_autocomplete', // confirm option name in the type's configureOptions()
]);
Autocomplete needs an endpoint returning JSON. Keep it a lightweight controller, cap results, and filter server-side:
#[Route('/tags/autocomplete', name: 'app_tag_autocomplete')]
public function tags(Request $request): JsonResponse
{
$q = trim((string) $request->query->get('term', ''));
if (mb_strlen($q) < 2) {
return $this->json([]);
}
return $this->json($this->tagRepo->search($q, 10));
}
File and image uploads — JqueryFileType/JqueryImageType handle the client-side picker/preview, but the move-and-store step is still yours:
$file = $form->get('attachment')->getData();
if ($file) {
$name = bin2hex(random_bytes(8)).'.'.$file->guessExtension();
$file->move($this->getParameter('uploads_dir'), $name);
$entity->setAttachment($name);
}
Add File/Image constraints to the underlying field rather than trusting the widget. Store the filename, not the absolute path.
Captcha GD flow (session-backed)
// controller
#[Route('/captcha/refresh', name: 'app_captcha_refresh')]
public function captcha(): Response { /* delegate to the bundle's captcha controller/service */ }
{{ form_row(form.captcha) }}
<a href="#" id="refresh-captcha">New code</a>
The value is validated against the session, so the session must be started and the form must round-trip through the same session. Don't cache pages containing it.
reCAPTCHA is the one type with meaningful server-side verification. Keep keys in env vars, and verify in the form's validator config or via the bundle's constraint. If the bundle's verification path is broken against modern Google endpoints, do the verification yourself in the controller — it's ~10 lines with HttpClient.
Compose, don't modify. If you need consistent defaults across the app (e.g. every date field uses the same jQuery UI options), create your own type that extends the bundle's and set defaults in configureOptions():
class AppDateType extends JqueryDateType
{
public function configureOptions(OptionsResolver $resolver): void
{
parent::configureOptions($resolver);
$resolver->setDefault('attr', ['class' => 'js-datepicker', 'autocomplete' => 'off']);
}
}
Testing. These are plain form types; test them with TypeTestCase and assert the produced form view. That's the fastest way to catch breakage after a Symfony upgrade:
protected function getExtensions(): array
{
return [new PreloadedExtension([new RecaptchaType()], [])];
}
setDefaultOptions() (removed in Symfony 3) and use getName() on the type. Expect to patch or shim these. Run bin/console debug:form to see whether the types register at all.tinymce.init({...}) with TinyMCE 4 options, loading TinyMCE 6 will silently do nothing. Pin the CDN versions to what the bundle expects, or override the templates.Resources/views/Form/*.html.twig blocks and override them in templates/bundles/.... That's how you fix asset markup, ARIA attributes, and broken JS without forking.stateless firewalls. For SPAs, this type is effectively unusable — move to reCAPTCHA v3 or hCaptcha.$form->getData() and inspect the raw request payload — the widget isn't writing the expected format, usually a jQuery UI date format mismatch (dd/mm/yy vs Y-m-d).|raw policy. Captcha GD is trivially OCR-able; treat it as spam friction, not security. Never commit reCAPTCHA private keys.DependencyInjection/Configuration.php and supply a complete block rather than partial overrides.symfony/ux-tinymce, symfony/ux-autocomplete, the built-in DateType with single_text, and a small custom captcha service cover the same ground with maintained dependencies. Reserve this bundle for legacy applications where it's already installed — for greenfield, the maintenance risk substantially outweighs the convenience.How can I help you explore Laravel packages today?