idci/contact-form-bundle
A Symfony bundle that provides a ready-to-use contact form for your application, handling form building, submission and email delivery so you can add a configurable contact page with minimal setup and customization.
Start by confirming the hard stop, so it's documented rather than assumed. In a Laravel app:
composer require idci/contact-form-bundle --dry-run
Expect a resolution failure on every axis that matters: symfony/symfony ^2.x, twig/twig, swiftmailer/swiftmailer, and a PHP constraint in the 5.3–5.6 range. Laravel 10 requires PHP 8.1, Laravel 11 requires 8.2. There is no adapter seam — Laravel has no bundle registration, no AppKernel, no container compiler pass. Capture the dry-run output and attach it to the rejection note. That is your day-one deliverable for this package.
Where to look if you're mining it for reference (read-only clone, never require):
DependencyInjection/Configuration.php — the config tree is effectively the feature spec: recipient, sender, subject, spam options, template names.Form/ — the contact form type: this is the field set and the validation constraints, the most portable artifact in the repo.Controller/ — the submit → validate → mail → redirect flow.Resources/views/ — Twig templates; skim once for field layout and copy, then close the tab.Resources/config/routing.* — the default route names and paths.First real use case: spend half a day porting the feature natively. Route + controller + FormRequest + Mailable + Blade view + throttle middleware. That is the same surface area the bundle covers, with none of the framework-mismatch risk, and it's work any Laravel dev on the team can pick up later.
If a legacy Symfony 2 estate genuinely exists, the install path there is the conventional one: register new IDCI\Bundle\ContactFormBundle\IDCIContactFormBundle() in AppKernel.php, import the bundle routing, set the recipient in config.yml, clear the cache. But note that the host app is itself EOL — that's a separate assessment.
Pattern 1 — Extract requirements, not code. Read Configuration.php and the form type, then translate to a spec table your team implements:
| Bundle concept | Laravel equivalent |
|---|---|
| Form type + constraints | FormRequest::rules() |
| Mailer service | Mailable + Mail facade |
| Twig template | Blade view |
| Bundle config keys | config/contact.php |
| Bundle inheritance override | Publish or copy the Blade view into your app |
Keep the table in the PR description. It's how you prove you evaluated the package without adopting it.
Pattern 2 — The native contact flow. This is the day-to-day replacement and it's short:
// routes/web.php
Route::post('/contact', ContactController::class)
->middleware(['throttle:5,1'])
->name('contact.store');
class ContactRequest extends FormRequest
{
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:120'],
'email' => ['required', 'email:rfc,dns', 'max:255'],
'message' => ['required', 'string', 'min:20', 'max:5000'],
'consent' => ['accepted'],
'website' => ['prohibited'], // honeypot
];
}
}
public function __invoke(ContactRequest $request)
{
Mail::to(config('contact.recipient'))
->queue(new ContactMessage($request->validated()));
return back()->with('status', 'Thanks — we\'ll be in touch.');
}
Mark the Mailable as ShouldQueue so SMTP latency never sits in the request cycle. The bundle sends synchronously; that's the failure mode you're buying out of.
Pattern 3 — Spam posture is a decision, not a default. The bundle's spam protection is a honeypot-or-captcha toggle. In Laravel, compose: honeypot field (prohibited rule), IP + email rate limiting via RateLimiter, and a maintained captcha package only if traffic justifies it. Don't inherit a 2015-era captcha integration.
Pattern 4 — Store or don't store. The bundle emails only; it has no submission model. Decide explicitly. If you persist (recommended for audit and for recovering from mail failures), add a contact_submissions table, write inside the same request, and queue the mail. Then add a retention/pruning job — you're ingesting PII and that needs a policy either way.
Pattern 5 — Social sharing is a separate concern. Do not let the package name couple it to the contact form. Ship a Blade partial with signed share URLs plus a Web Share API fallback. Do not pull in the bundle's third-party JS or tracking pixels.
Pattern 6 — Test it like a feature. Mail::fake(), post the form, assert Mail::assertQueued(ContactMessage::class), assert the redirect and flash, then a second test that hammers the endpoint and asserts the 429. That suite is the thing the bundle can never give you.
Never force the install. --ignore-platform-reqs will happily produce a broken lockfile and a runtime fatal on PHP 8. If someone asks "can't we just try it," the dry-run output is the answer.
PHP 5.3-era syntax bites on modern runtimes. Even as read-only reference, expect constructs removed in PHP 8 (each(), create_function, curly-brace string offsets). You're reading it for ideas, not running it.
Swiftmailer is a second EOL dependency. Swiftmailer went EOL in November 2021 in favour of symfony/mailer. Anything that pulls it into your tree conflicts with Laravel's own mailer and puts unpatched code directly in the PII path.
Twig ≠ Blade escaping. If you copy template markup, port the escaping assumptions deliberately: {{ $var }} in Blade is escaped by default, so never "fix" a ported template by switching to {!! !!} for free-text fields. The bundle's autoescape defaults are not automatically your defaults.
Sender/recipient config is the first thing to get wrong. Default placeholder from addresses land in spam or get rejected by SPF/DKIM. In the Laravel version, use a real from on your own domain, set reply_to to the submitter, and add a subject that includes the submitter name so the inbox is scannable.
Debugging in a legacy Symfony 2 host: use the spool (swiftmailer.spool) rather than live SMTP, inspect app/console router:debug for the actual route names, and check the profiler's mailer panel. If mail silently vanishes, it's almost always the spool not being flushed — run swiftmailer:spool:send.
There are no extension points worth relying on. Symfony's bundle inheritance model lets you override templates by directory convention; Laravel has no equivalent, and the upstream repo has 1 star, 0 dependents, and no active maintenance. Any fork is yours forever — no CVE patches, no PR review, bus factor of one. Log it in the dependency-risk register as evaluated-and-rejected so it isn't re-litigated.
Watch the scoring, not just the score. A composite of ~0.71 against an opportunity score of 0.38 signals the metric is rewarding packaging metadata rather than framework fitness. Use the opportunity number as the decision input; the composite is not a proxy for "worth a spike."
The cheapest correct move: one half-day to port the field set and validation rules from Form/ and Resources/views/, then ship the native flow. You get consent capture, rate limiting, queued delivery, accessibility, and a codebase your team already knows.
How can I help you explore Laravel packages today?