mael/recaptcha-bundle
Mael RecaptchaBundle integrates Google reCAPTCHA into your PHP application's forms to help block spam and automated bot submissions. It provides straightforward configuration and server-side verification of user responses, making it easy to add basic bot protection to existing forms.
Architecture fit. The first thing a TPM needs on the record is scoping: despite being pitched into a "Laravel/PHP package" evaluation, mael/recaptcha-bundle is a Symfony bundle, not a Laravel package. It ships Symfony-specific contracts — a Bundle subclass for auto-registration, a DependencyInjection/Extension with a Configuration tree, Twig extensions, and a Symfony FormType/validator. Laravel has no bundle registration, no YAML config tree, no Twig form theme, and no AbstractType pipeline, so there is nothing to "install" in a Laravel app. From a delivery standpoint this is a reference implementation to read, not a dependency to adopt, and any estimate that assumes "composer require and done" is wrong by an order of magnitude.
Integration feasibility. Effectively zero as packaged on Laravel; moderate as a template. The actual problem being solved is small and well understood: render the reCAPTCHA widget, carry a g-recaptcha-response token to the server, POST it with the secret to Google's siteverify endpoint, and validate the response. That is roughly 60–120 lines of PHP plus a Blade component in Laravel — cheaper to write natively than to port, and fully owned. A port would mean reimplementing the config surface (config/services.php + .env instead of YAML), a Blade component/directive instead of a Twig function, a Laravel validation Rule instead of a Symfony form type, and an Http/Guzzle client instead of the bundle's wiring. Net: the package saves us at most a few hours of design and costs us a fork we must maintain.
Technical risk. High, and driven almost entirely by provenance and staleness rather than by any review of the source (which I have not done). The signals are unambiguous:
| Risk | Evidence | Impact |
|---|---|---|
| Abandonment | Last release 2020-05-23 (~5 years stale) | No fixes for Google API/script changes, PHP 8.2+/8.3+ deprecations, Symfony 6/7 support |
| No adoption | 2 stars, 0 dependents, score 0.01 | No community triage, no battle testing; we would be the only user |
| Single maintainer | Personal namespace (Mael-91) |
Bus factor of 1; no security disclosure process likely |
| Protocol drift | 2020-era bundle | Likely v2-checkbox only; probably no v3 score/action handling, no render=/onload= support, no Enterprise path |
| Secret-handling pattern | Symfony bundles conventionally read YAML config | Risk of secrets copied into committed config if the pattern is replicated verbatim |
Key questions (for a spike, since the maintainer is unlikely to answer):
action verification, or only v2 checkbox/invisible?success the only field checked, or are hostname, challenge_ts, action, and score also validated? Checking success alone is a known bypass pattern.siteverify call, and what are the timeout/retry semantics?SECURITY.md or any disclosure policy?Verdict for the roadmap: do not adopt. Use it as a design reference for a ~1-day in-house implementation, or skip it entirely in favour of a maintained Laravel package (biscolab/laravel-recaptcha, anhskohbo/no-captcha, or google/recaptcha as a thin SDK).
Stack fit. Zero out of the box on Laravel — the correct framing is "reimplement the pattern," not "integrate the package." If part of the estate is genuinely Symfony + Twig + Symfony Forms, the bundle's shape is right, but the 2020 version matrix almost certainly targets Symfony 4/5 and PHP 7.x, so even a Symfony shop must verify PHP 8.2+ and Symfony 6/7 compatibility before counting on it. There is also a real chance it pins an older Guzzle major that conflicts with the host app's HTTP client.
Migration path. Recommended sequence for a Laravel target:
RecaptchaService behind an interface — verify(string $token, ?string $ip, ?string $action): Result — plus a Blade component for the widget, a Laravel ValidationRule, config in config/services.php, and secrets in .env only. Keep the verifier swappable so Turnstile/hCaptcha can drop in without touching call sites.Compatibility. Blocking unknowns: Symfony 6/7 and PHP 8.2+ support; Twig 2 vs 3; whether the widget emits modern render=/onload= script tags required for v3; whether Google's current siteverify response shape (including score, action, error-codes) is fully parsed. None of these are answerable from the README alone.
Sequencing. Do not put this on the critical path for any auth or anti-abuse milestone. Run the spike in parallel with normal delivery; make the build-vs-port-vs-buy decision before the sprint that needs bot mitigation; and land the vendor abstraction first so the concrete provider is a one-file swap. If multiple surfaces need protection (contact, signup, password reset, checkout), decide policy per surface up front — fail-closed on registration, fail-open on low-risk contact — because that policy, not the library, is what will generate support tickets.
Maintenance. Effectively zero upstream. Five years without a release means we own every future break: Google script changes, PHP deprecations, Guzzle majors, framework upgrades. Every hour spent integrating is an hour of permanent ownership — which is precisely the argument for writing the ~100 lines ourselves and owning something we understand, rather than inheriting an unfamiliar bundle's conventions.
Support. No vendor, no SLA, no maintainer responsiveness signal (2 stars and 0 dependents implies no issue traffic at all). Internal escalations terminate at whoever last read the source. If reCAPTCHA gates signup or checkout, that is an unacceptable support posture for a revenue-adjacent control — the on-call engineer at 2am needs code the team wrote, with tests.
Scaling. The outbound siteverify call is a synchronous third-party dependency on every protected request, and that concern exists regardless of which package we choose. Plan for typical 50–200ms added latency, connection pooling/keep-alive, a hard ~2s timeout, and an explicit fail-open vs fail-closed policy per surface. Note that tokens are single-use with ~2-minute validity, so caching is not an option — capacity planning must assume a 1:1 outbound call per protected submission. Verify Google's rate limits against peak traffic (e.g., a marketing campaign driving a signup spike).
Failure modes. Enumerate and decide policy for each before launch:
siteverify timeout or 5xx → fail open (abuse risk) vs fail closed (conversion risk). Decide per surface.!isset($json['success']) and default to allow; audit for this explicitly.Ramp-up. Realistic estimate for the recommended path: 0.5 day spike + 1–2 days implementation + 0.5 day test and policy work, plus code review — call it 3–4 engineer-days end to end. Add 1–2 weeks of calendar time if we choose to fork, because the team is also absorbing an unfamiliar bundle's conventions with no upstream documentation. Transferable knowledge is high for the in-house path (the team learns the vendor integration) and near zero for the fork path (the team learns one person's 2020 bundle design).
Bottom line. Score 0.01, 0 dependents, and a 2020 last release is a clear "reference only" signal. Fund a small in-house implementation or a maintained Laravel package, keep the verifier behind an interface, and treat any future move to Symfony as a fresh evaluation of a current bundle — not as a reason to resurrect this one.
How can I help you explore Laravel packages today?