rasuvaeff / yii3-turnstile
Cloudflare Turnstile CAPTCHA widget and validator for Yii3.
Requires
- php: ^8.3
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
- psr/http-message: ^1.0 || ^2.0
- yiisoft/html: ^4.0
- yiisoft/request-provider: ^1.3
- yiisoft/translator: ^3.0
- yiisoft/validator: ^2.5
- yiisoft/widget: ^2.2
Requires (Dev)
- ergebnis/composer-normalize: ^2.51
- friendsofphp/php-cs-fixer: ^3.95
- guzzlehttp/guzzle: ^7.10
- infection/infection: ^0.29
- maglnet/composer-require-checker: ^4.17
- nyholm/psr7: ^1.8
- phpunit/phpunit: ^11.5
- psalm/plugin-phpunit: ^0.19
- rector/rector: ^2.4
- roave/backward-compatibility-check: ^8.0
- vimeo/psalm: ^6.16
- yiisoft/translator-message-php: ^1.1
Suggests
- guzzlehttp/guzzle: PSR-18 HTTP client + PSR-17 factories
- nyholm/psr7: PSR-17 HTTP message factories
- symfony/http-client: Alternative PSR-18 HTTP client
This package is auto-updated.
Last update: 2026-06-01 13:09:52 UTC
README
Cloudflare Turnstile CAPTCHA widget and server-side validator for Yii3.
Provides a Turnstile widget for rendering the challenge in a form and a
TurnstileRule / TurnstileRuleHandler pair for server-side verification through
the Yii validator pipeline. HTTP calls go through any PSR-18 client.
Using an AI coding assistant? llms.txt contains a compact API reference you can share with the model. Contributors: see AGENTS.md.
Requirements
| Requirement | Version |
|---|---|
| PHP | ^8.3 |
| A PSR-18 HTTP client + PSR-17 factories | any implementation |
yiisoft/widget |
^2.2 |
yiisoft/html |
^4.0 |
yiisoft/validator |
^2.5 |
yiisoft/translator |
^3.0 |
yiisoft/request-provider |
^1.3 |
Installation
composer require rasuvaeff/yii3-turnstile
You also need a PSR-18 client and PSR-17 factories if your project doesn't already ship one:
composer require nyholm/psr7
# or: composer require guzzlehttp/guzzle
Usage
1. Render the widget in a form
use Rasuvaeff\Yii3Turnstile\Turnstile; use Rasuvaeff\Yii3Turnstile\TurnstileTheme; use Rasuvaeff\Yii3Turnstile\TurnstileSize; // siteKey comes from DI config (TurnstileConfig) echo Turnstile::widget() ->withTheme(TurnstileTheme::Light) ->withSize(TurnstileSize::Normal);
Output:
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script> <div class="cf-turnstile" data-sitekey="your-site-key" data-response-field-name="cf-turnstile-response" data-theme="light" data-size="normal"></div>
2. Validate server-side with a validator rule
use Rasuvaeff\Yii3Turnstile\TurnstileRule; use Yiisoft\Validator\Validator; class LoginForm { #[TurnstileRule(secret: 'your-secret')] public string $captcha = ''; } $result = (new Validator())->validate($loginForm);
The rule sends the token to Cloudflare's siteverify endpoint and reports
success/failure through the standard Yii validator Result.
3. Dependency injection (Yii3)
The package ships config/params.php and config/di.php compatible with
yiisoft/config. Override params in your application config:
// config/params.php return [ 'rasuvaeff/yii3-turnstile' => [ 'siteKey' => $_ENV['TURNSTILE_SITE_KEY'], 'secret' => $_ENV['TURNSTILE_SECRET'], 'verifyUrl' => 'https://challenges.cloudflare.com/turnstile/v0/siteverify', 'sendRemoteIp' => true, 'translation.category' => 'yii3-turnstile', ], ];
The DI config registers a CategorySource tagged as translation.categorySource.
When yiisoft/translator-message-php is installed, it reads message files from
messages/<locale>/yii3-turnstile.php. Without it, message IDs are returned as-is.
4. Translations
The package includes Russian translations out of the box:
| Locale | File |
|---|---|
ru |
messages/ru/yii3-turnstile.php |
To add more languages, create messages/<locale>/yii3-turnstile.php:
<?php declare(strict_types=1); return [ 'The CAPTCHA verification failed.' => 'Your translated message.', ];
Components
Turnstile (widget)
Renders the Cloudflare Turnstile HTML + script tag. Extends Yiisoft\Widget\Widget.
| Method | Description |
|---|---|
withSiteKey(string $siteKey): self |
Cloudflare site key (required). |
withTheme(TurnstileTheme $theme): self |
Auto, Light, or Dark. Default: Auto. |
withSize(TurnstileSize $size): self |
Normal, Compact, or Flexible. Default: Normal. |
withResponseFieldName(string $name): self |
Name of the hidden input field. Default: cf-turnstile-response. |
withJsApiUrl(string $url): self |
Override the script URL. Default: Cloudflare CDN. |
render(): string |
Returns the HTML string. Throws if siteKey is not set. |
TurnstileConfig
Immutable configuration DTO.
final readonly class TurnstileConfig { public function __construct( public string $siteKey, public string $secret, public string $verifyUrl = 'https://challenges.cloudflare.com/turnstile/v0/siteverify', public bool $sendRemoteIp = false, ) {} }
TurnstileClient
Sends the token verification POST to Cloudflare. Requires PSR-18 + PSR-17.
final readonly class TurnstileClient { public function __construct( private TurnstileConfig $config, private ClientInterface $httpClient, private RequestFactoryInterface $requestFactory, private StreamFactoryInterface $streamFactory, ) {} public function verify(string $token, ?string $clientIp = null, ?string $idempotencyKey = null): VerificationResult; public function verifyWithSecret(string $token, string $secret, ?string $clientIp = null, ?string $idempotencyKey = null): VerificationResult; }
idempotencyKey is an optional UUID that lets you safely re-verify the same
token (Cloudflare returns the original result instead of an error); it is only
sent when provided. verifyWithSecret() is used by the rule handler when a
per-rule secret override is set.
VerificationResult
DTO returned by TurnstileClient::verify().
final readonly class VerificationResult { public function __construct( public bool $success, public array $errorCodes = [], public ?string $hostname = null, public ?string $action = null, public ?string $challengeTs = null, ) {} }
TurnstileRule / TurnstileRuleHandler
A RuleInterface for the Yii validator. The handler receives TurnstileClient
from DI and calls verify() with the token value. When sendRemoteIp is set,
the handler reads the client IP from the current request via
yiisoft/request-provider (RequestProviderInterface::get(), REMOTE_ADDR
server param); if no request is set the IP is simply omitted. Supports
skipOnEmpty, skipOnError, and when via standard validator traits.
#[TurnstileRule(
message: 'Custom error message',
sendRemoteIp: true,
)]
public string $captcha = '';
| Method | Description |
|---|---|
getHandler(): string |
Returns TurnstileRuleHandler::class. |
getMessage(): string |
Error message on failure. |
getSecret(): ?string |
Override secret (uses DI config if null). |
getSendRemoteIp(): bool |
Whether to forward client IP. |
Enums
| Enum | Values |
|---|---|
TurnstileTheme |
Auto, Light, Dark |
TurnstileSize |
Normal, Compact, Flexible |
Security
- The widget renders a public site key in HTML — this is intentional and safe.
- The secret is only used server-side in
TurnstileClientand never reaches the browser. - Token verification goes over HTTPS to Cloudflare's
siteverifyendpoint. sendRemoteIpis opt-in (disabled by default); the client IP is taken from the current request viaRequestProviderInterface(REMOTE_ADDR), not from user input.
Examples
See examples/ for runnable scripts.
| Script | Shows | Needs server? |
|---|---|---|
widget.php |
Rendering the Turnstile widget | no |
verify.php |
Server-side token verification | no |
Development
No PHP/Composer on the host — run in Docker via the composer:2 image:
docker run --rm -v "$PWD":/app -w /app composer:2 composer install docker run --rm -v "$PWD":/app -w /app composer:2 composer build docker run --rm -v "$PWD":/app -w /app composer:2 composer cs:fix docker run --rm -v "$PWD":/app -w /app composer:2 composer test
Or with Make:
make install
make build
make cs:fix
make test
CI runs composer build on PHP 8.3, 8.4, and 8.5.