victor-prdh/recaptcha-bundle
A Symfony bundle for integrating Google reCAPTCHA into forms and controllers, with configurable keys, server-side verification, and simple validators to help protect your application from spam and automated abuse.
This is a Symfony bundle, not a Laravel package — there is no service provider, no facade, and no config/recaptcha.php. Treat it as a thin wrapper around google/recaptcha that you wire into Laravel yourself.
composer require victor-prdh/recaptcha-bundle — the 2.2.0 release only widens the google/recaptcha constraint to allow ^2.0, so check what Composer actually resolves:
composer show victor-prdh/recaptcha-bundle
composer show google/recaptcha
src/DependencyInjection/Configuration.php (the config tree), src/Resources/config/services.* (service ids/wiring), and the constraint/validator class that does the actual ReCaptcha::verify() call.google/recaptcha directly — you get the same behaviour with one less abstraction and full control over the HTTP client. Reach for the bundle when you want its validator/constraint semantics and don't mind translating Symfony config into a Laravel binding.First use case: server-side verification of a login, registration, or contact-form token in a FormRequest, returning a 422 on failure.
Bind the client in a service provider. Translate the bundle's config array into the ReCaptcha constructor rather than trying to load Symfony YAML:
$this->app->singleton(ReCaptcha::class, fn () => (new ReCaptcha(
secret: config('services.recaptcha.secret_key'),
options: ['server_hostname' => config('services.recaptcha.hostname')],
))->setExpectedHostname(config('services.recaptcha.hostname')));
Validate in a FormRequest so controllers stay clean:
public function rules(): array
{
return [
'g-recaptcha-response' => ['required', 'string', new ReCaptchaRule()],
];
}
Custom rule wrapping the verifier. Keep the rule dumb — it takes the token, calls verify, and maps the Response to a ValidationException message. Put score/action assertions in the rule, not in the controller, so every form gets the same policy.
v3: check score and action. getScore() is only meaningful for v3; for v2 checkbox it can be null. Guard the comparison and fail closed when the action doesn't match what the page declared. Store the threshold in config, not in code.
Testing. Never hit Google in tests. Bind a fake in TestCase::setUp() or use Http::fake() if you swapped the transport, and assert both the happy path and the "score below threshold" path. Tokens are single-use, so a fixture token will only ever pass once against the real API — another reason to fake.
Integration tip: if you're adopting the bundle purely for constraint parity with an existing Symfony app, wrap the validator's validate() in a Laravel rule object and leave the rest of the bundle unused. The dependency footprint stays small and the upgrade path (this release) is trivial.
google/recaptcha ^1.x nothing changes; if you were pinned to 2.x elsewhere, the bundle is now installable alongside it. Re-run composer update --with-dependencies and diff the lockfile before assuming an upgrade did anything.google/recaptcha 2.x raises the PHP floor (7.4+, and check the exact patch constraint against your CI matrix). If you support 7.2/7.3 you cannot take this release. Pin google/recaptcha explicitly rather than letting two constraints negotiate.google/recaptcha: response accessors and option keys have shifted across majors. If you write code against the underlying library directly, verify getScore(), getAction(), and getHostname() signatures against the version Composer actually picked — don't trust docs for the other major.Configuration class is a Symfony TreeBuilder and will reject unknown keys. Mirror the values into config/services.php instead.setExpectedHostname() is a meaningful hardening step and is easy to forget — verification passes without it, so the omission is silent.config('services.recaptcha.*') inside a closure, run php artisan config:cache after changing the secret, otherwise you'll debug a stale key for an hour.ReCaptcha binding are your two seams — everything else is bundle internals you shouldn't depend on. Keep the secret in .env and out of the repository; rotate it via env vars, never by editing config files on the server.How can I help you explore Laravel packages today?