migears / captcha
A lightweight captcha generator for PHP 8.1+ with GD extension
Requires
- php: ^8.1
- ext-gd: *
Requires (Dev)
- phpstan/phpstan: ^2.2
- phpunit/phpunit: ^10.0
Suggests
- ext-mbstring: Recommended for Unicode-aware case folding in CaptchaVerifier (falls back to strtolower when unavailable)
Provides
None
Conflicts
None
Replaces
None
README
A lightweight captcha generation library for PHP 8.1+, requiring the GD extension to render images.
Background: miGears is the open-source successor of TinyGears, a self-developed PHP framework. It was renamed and open-sourced recently because the name TinyGears is already taken in the open-source community.
Features
- Generates random captcha strings (numbers + letters, configurable length and character set)
- Generates captcha images (configurable width/height, font, interference lines, noise dots)
- Math captcha mode (
math: true) — an arithmetic puzzle instead of random characters - Preset difficulty levels (
easy/medium/hard) tune noise, lines and distortion - Supports custom TTF fonts
- Supports three output formats: PNG, JPEG, GIF
CaptchaResultis areadonlyvalue object- Multibyte-aware character sets (e.g. CJK)
CaptchaVerifierprovides timing-safe, case-optional validation- Requires the GD extension (images are rendered with GD)
- Minimalist API, outputs nothing, only returns data
Boundaries
In scope
- Generating a random captcha code and rendering it to an image with GD (
Captcha::generate()): configurable length, size, custom TTF font, noise, interference lines and character set; PNG / JPEG / GIF output. - Math mode (
math: true) and the preset difficulty levels (Difficulty::Easy/Medium/Hard). CaptchaResult, areadonlyvalue object carryingimageData,code,mimeTypeandtoDataUri().CaptchaVerifier::verify(): a timing-safe (hash_equals), optionally case-insensitive comparison helper.
Not in scope (by design)
- Storing the generated code (session, cache, ...) — the library does not persist it; caching belongs to
migears/cache. - Expiry and one-time consumption of a code — the library does not track when a code was issued or whether it was already used.
- Sending the HTTP response — the library outputs nothing and only returns data; emitting headers/body belongs to the caller or the framework.
Installation
composer require migears/captcha
Requires PHP 8.1 or higher and the GD extension.
Quick Start
use MiGears\Captcha\Captcha; use MiGears\Captcha\ImageFormat; $captcha = new Captcha( length: 4, width: 120, height: 40, format: ImageFormat::Png, ); $result = $captcha->generate(); // Captcha text echo $result->code; // e.g.: a3fK // Image binary data echo $result->imageData; // MIME type echo $result->mimeType; // image/png // Data URI (convenient for embedding in HTML) echo $result->toDataUri();// data:image/png;base64,...
Math Mode
Pass math: true to render an arithmetic puzzle instead of random characters.
The code is the arithmetic result, which is what you store and verify:
use MiGears\Captcha\CaptchaVerifier; $captcha = new Captcha(math: true); $result = $captcha->generate(); // The image shows e.g. "7 + 3 = ?" echo $result->code; // "10" — the answer // Verifying the user's typed answer: $verifier = new CaptchaVerifier(); $ok = $verifier->verify($input, $result->code);
Uses +, -, * with operands 1–9; subtraction is always positive. When
width/height are omitted, math mode defaults to 170×50 (wider to fit the
extra characters).
length and chars are ignored in math mode: the puzzle supplies its own
characters, so neither parameter is used or validated —
new Captcha(math: true, length: 0, chars: '') is accepted.
Difficulty
Pick a preset that tunes noise, interference lines and character distortion (rotation + vertical drift):
use MiGears\Captcha\Difficulty; $captcha = new Captcha(difficulty: Difficulty::Hard);
| Level | noiseLevel | lineCount | rotation | drift |
|---|---|---|---|---|
Easy |
20 | 1 | ±12° | 3px |
Medium |
50 | 3 | ±22° | 6px |
Hard |
80 | 6 | ±35° | 10px |
Explicit noiseLevel / lineCount always override the preset. When
difficulty is omitted, the package keeps its original defaults (50 noise, 3
lines, no drift), so existing behavior is unchanged. Difficulty::Medium is a
good middle ground when you want moderate distortion.
Configuration Options
| Parameter | Type | Default | Description |
|---|---|---|---|
length |
int |
4 |
Captcha character length (ignored in math mode) |
width |
?int |
120 (170 in math mode) |
Image width in pixels |
height |
?int |
40 (50 in math mode) |
Image height in pixels |
font |
?string |
Built-in font | Custom TTF font file path |
format |
ImageFormat |
ImageFormat::Png |
Output image format |
noiseLevel |
?int |
preset-dependent | Number of noise dots |
lineCount |
?int |
preset-dependent | Number of interference lines |
chars |
string |
See below | Captcha character set (ignored in math mode) |
math |
bool |
false |
Render an arithmetic puzzle instead of random characters |
difficulty |
?Difficulty |
null |
Preset that tunes noise, lines and distortion |
Default character set (easily confused characters 0/O/1/l/I removed):
abCDefGhiJkLmNPQrstUVWXyz23456789
Captcha Storage
This library does not handle captcha storage (Session / cache, etc.), it is managed by the user:
// Generate captcha $result = $captcha->generate(); // Store the code (example: using Session) $_SESSION['captcha_code'] = $result->code; // Output the image header('Content-Type: ' . $result->mimeType); echo $result->imageData;
Verification
Compare a user-submitted answer against the stored code with CaptchaVerifier.
It wraps timing-safe comparison and optional
case folding, so callers don't have to implement these details themselves.
When mbstring is available, folding is Unicode-aware (e.g. É -> é);
otherwise it falls back to ASCII-only folding.
use MiGears\Captcha\CaptchaVerifier; $verifier = new CaptchaVerifier(); // Case-insensitive by default $verifier->verify('a3fk', 'a3fK'); // true // Case-sensitive $verifier->verify('a3fk', 'a3fK', caseInsensitive: false); // false // Empty inputs always fail, no exception is thrown $verifier->verify('', 'a3fK'); // false
Like storage, expiry and one-time consumption are left to the caller. The library does not track when a code was issued or whether it was already used:
$_SESSION['captcha_expires_at'] = time() + 300; // 5 minutes $_SESSION['captcha_used'] = false; // On submission: if (!$_SESSION['captcha_used'] && time() < $_SESSION['captcha_expires_at']) { $ok = $verifier->verify($input, $_SESSION['captcha_code']); if ($ok) { $_SESSION['captcha_used'] = true; // consume } }
Composite Example
Combining everything: a high-strength math captcha rendered to PNG, answered once within a 5-minute window, verified with timing-safe comparison:
use MiGears\Captcha\Captcha; use MiGears\Captcha\Difficulty; use MiGears\Captcha\CaptchaVerifier; // Build a high-strength math captcha $captcha = new Captcha( math: true, difficulty: Difficulty::Hard, format: \MiGears\Captcha\ImageFormat::Png, ); $result = $captcha->generate(); // Store the answer (the math result) and an expiry $_SESSION['captcha_code'] = $result->code; $_SESSION['captcha_expires_at'] = time() + 300; // 5 minutes // Serve the image header('Content-Type: ' . $result->mimeType); echo $result->imageData; // ... later, validate the user's answer exactly once: $verifier = new CaptchaVerifier(); $ok = time() < $_SESSION['captcha_expires_at'] && $verifier->verify($_POST['captcha'] ?? '', $_SESSION['captcha_code']); // Consume regardless of the outcome so a code can't be reused unset($_SESSION['captcha_code'], $_SESSION['captcha_expires_at']);
Multibyte Note
The character set is multibyte-aware: a code generated from the default ASCII
set or a UTF-8 set (e.g. Chinese) is always valid output. However, images are
rendered with the bundled assets/captcha.ttf, which only contains Latin
glyphs. To render non-Latin codes (CJK, Cyrillic, etc.), pass a font that
covers those characters via the font option.
Exceptions
On failure, throws MiGears\Captcha\Exception\CaptchaException:
use MiGears\Captcha\Exception\CaptchaException; try { $captcha = new Captcha(font: '/path/to/font.ttf'); $result = $captcha->generate(); } catch (CaptchaException $e) { // handle the error }
Testing
composer install vendor/bin/phpunit
License
MIT
migears/captcha
轻量级验证码生成库,PHP 8.1+,需要 GD 扩展来渲染图片。
特性
- 生成随机验证码字符串(数字+字母,可配置长度和字符集)
- 生成验证码图片(可配置宽高、字体、干扰线、噪点)
- 数学算式验证码模式(
math: true)——用算术题替代随机字符 - 预设难度档位(
easy/medium/hard)统一调整噪点、干扰线与扭曲 - 支持自定义 TTF 字体
- 支持 PNG、JPEG、GIF 三种输出格式
CaptchaResult为readonly值对象- 字符集支持多字节(如中文)
CaptchaVerifier提供常时安全、可选忽略大小的校验- 需要 GD 扩展(图片由 GD 渲染)
- 极简 API,不输出任何内容,仅返回数据
边界
范围内
- 用 GD 生成随机验证码并渲染成图片(
Captcha::generate()):可配置长度、尺寸、自定义 TTF 字体、噪点、干扰线与字符集;输出 PNG / JPEG / GIF。 - 数学算式模式(
math: true)与预设难度档位(Difficulty::Easy/Medium/Hard)。 CaptchaResult:一个readonly值对象,携带imageData、code、mimeType与toDataUri()。CaptchaVerifier::verify():常时安全(hash_equals)、可选忽略大小写的比较辅助方法。
范围外(刻意不做)
- 存储生成的 code(session、缓存等)—— 本库不做持久化;缓存由
migears/cache负责。 - code 的过期与一次性消费 —— 本库不追踪签发时间,也不记录是否已被用过。
- 发送 HTTP 响应 —— 本库不输出任何内容,仅返回数据;响应头/响应体的发送属于调用方或框架。
安装
composer require migears/captcha
需要 PHP 8.1 及以上并安装 GD 扩展。
快速开始
use MiGears\Captcha\Captcha; use MiGears\Captcha\ImageFormat; $captcha = new Captcha( length: 4, width: 120, height: 40, format: ImageFormat::Png, ); $result = $captcha->generate(); // 验证码文本 echo $result->code; // 例如: a3fK // 图片二进制数据 echo $result->imageData; // MIME 类型 echo $result->mimeType; // image/png // Data URI(方便嵌入 HTML) echo $result->toDataUri();// data:image/png;base64,...
数学算式模式
传入 math: true 渲染一道算术题,而非随机字符。code 即为算术结果,
用于存储和校验:
use MiGears\Captcha\CaptchaVerifier; $captcha = new Captcha(math: true); $result = $captcha->generate(); // 图片显示例如 "7 + 3 = ?" echo $result->code; // "10" —— 答案 // 校验用户输入的答案: $verifier = new CaptchaVerifier(); $ok = $verifier->verify($input, $result->code);
使用 +、-、*,操作数为 1–9,减法恒为正。当未显式指定 width/height
时,数学模式下默认 170×50(更宽以容纳额外字符)。
数学模式下 length 与 chars 被忽略:题目自带字符,二者既不参与生成也不做
校验 —— new Captcha(math: true, length: 0, chars: '') 会被接受。
难度档位
通过预设档位统一调整噪点、干扰线与字符扭曲(旋转角 + 垂直漂移):
use MiGears\Captcha\Difficulty; $captcha = new Captcha(difficulty: Difficulty::Hard);
| 档位 | noiseLevel | lineCount | 旋转角 | 漂移 |
|---|---|---|---|---|
Easy |
20 | 1 | ±12° | 3px |
Medium |
50 | 3 | ±22° | 6px |
Hard |
80 | 6 | ±35° | 10px |
显式传入的 noiseLevel / lineCount 始终优先于预设。当不指定 difficulty
时,包保持原有默认(50 噪点、3 干扰线、无漂移),现有行为不变。想获得适度
扭曲时,Difficulty::Medium 是不错的中间档。
配置选项
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
length |
int |
4 |
验证码字符长度(数学模式忽略) |
width |
?int |
120(数学模式 170) |
图片宽度(像素) |
height |
?int |
40(数学模式 50) |
图片高度(像素) |
font |
?string |
内置字体 | 自定义 TTF 字体文件路径 |
format |
ImageFormat |
ImageFormat::Png |
输出图片格式 |
noiseLevel |
?int |
随档位而定 | 噪点数量 |
lineCount |
?int |
随档位而定 | 干扰线数量 |
chars |
string |
见下 | 验证码字符集(数学模式忽略) |
math |
bool |
false |
用算术题替代随机字符 |
difficulty |
?Difficulty |
null |
预设档位,统一调整噪点/干扰线/扭曲 |
默认字符集(已去除易混淆字符 0/O/1/l/I):
abCDefGhiJkLmNPQrstUVWXyz23456789
验证码存储
本库不负责验证码的存储(Session / 缓存等),由使用者自行管理:
// 生成验证码 $result = $captcha->generate(); // 存储 code(示例:使用 Session) $_SESSION['captcha_code'] = $result->code; // 输出图片 header('Content-Type: ' . $result->mimeType); echo $result->imageData;
验证码校验
使用 CaptchaVerifier 将用户提交的答案与已存储的 code 进行比较。它封装了
常时比较 和可选的大小写折叠,调用方无需
自己实现这些细节。装有 mbstring 时折叠为 Unicode 感知(如 É → é),
否则回退到仅 ASCII 折叠。
use MiGears\Captcha\CaptchaVerifier; $verifier = new CaptchaVerifier(); // 默认忽略大小写 $verifier->verify('a3fk', 'a3fK'); // true // 区分大小写 $verifier->verify('a3fk', 'a3fK', caseInsensitive: false); // false // 空输入恒为 false,不抛异常 $verifier->verify('', 'a3fK'); // false
与存储一样,过期与一次性消费由调用方负责。本库不追踪 code 的签发时间, 也不记录是否已被用过:
$_SESSION['captcha_expires_at'] = time() + 300; // 5 分钟过期 $_SESSION['captcha_used'] = false; // 用户提交时: if (!$_SESSION['captcha_used'] && time() < $_SESSION['captcha_expires_at']) { $ok = $verifier->verify($input, $_SESSION['captcha_code']); if ($ok) { $_SESSION['captcha_used'] = true; // 消费 } }
完整示例
组合所有能力:生成一张高强度数学验证码 PNG,限定 5 分钟内一次性作答, 并用常时比较校验:
use MiGears\Captcha\Captcha; use MiGears\Captcha\Difficulty; use MiGears\Captcha\ImageFormat; use MiGears\Captcha\CaptchaVerifier; // 构建高强度数学验证码 $captcha = new Captcha( math: true, difficulty: Difficulty::Hard, format: ImageFormat::Png, ); $result = $captcha->generate(); // 存储答案(算术结果)与过期时间 $_SESSION['captcha_code'] = $result->code; $_SESSION['captcha_expires_at'] = time() + 300; // 5 分钟 // 输出图片 header('Content-Type: ' . $result->mimeType); echo $result->imageData; // ……稍后,仅允许一次性校验用户的答案: $verifier = new CaptchaVerifier(); $ok = time() < $_SESSION['captcha_expires_at'] && $verifier->verify($_POST['captcha'] ?? '', $_SESSION['captcha_code']); // 无论对错都消费,避免验证码被复用 unset($_SESSION['captcha_code'], $_SESSION['captcha_expires_at']);
多字节说明
字符集支持多字节:无论用默认 ASCII 字符集还是 UTF-8 字符集(如中文),生成
的 code 都是合法的。但图片使用内置 assets/captcha.ttf 渲染,该字体仅含
拉丁字形。 若要渲染非拉丁字符(中文、西里尔等),请通过 font 选项传入
支持这些字符的字体。
异常
失败时抛出 MiGears\Captcha\Exception\CaptchaException:
use MiGears\Captcha\Exception\CaptchaException; try { $captcha = new Captcha(font: '/path/to/font.ttf'); $result = $captcha->generate(); } catch (CaptchaException $e) { // 处理错误 }
测试
composer install vendor/bin/phpunit
License
MIT