-
Notifications
You must be signed in to change notification settings - Fork 1
Migration Guide
initphp/escaper 2.0 is a correctness release. The public API surface is unchanged — every 1.x method still exists with the same signature. What changed is how the escaper signals failure and what happens in a few edge cases that were latent bugs in 1.x.
If your 1.x code only calls Esc::esc() or Escaper::escHtml() etc. on the happy path, you should be able to upgrade without code changes. The notes below cover the cases where you may need to act.
| Area | 1.x | 2.0 |
|---|---|---|
ext-mbstring |
optional | required |
| Encoding-conversion failure | silently returned ''
|
throws EncodingConversionException
|
| Exception base | plain \Exception
|
typed EscaperException (extends \RuntimeException) |
Esc::esc() recursion + encoding |
dropped $encoding on recursive calls |
propagates $encoding correctly |
Esc::esc() instance cache |
rebuilt on every default-encoding call | per-encoding memoisation |
| C1 control replacement | only single-byte forms | also multibyte UTF-8 form (\xC2\x80 … \xC2\x9F) |
Examples/ directory |
shipped | removed (replaced by docs/ and this wiki) |
composer.json now declares ext-mbstring as a hard requirement (ext-iconv remains optional but preferred when present). If your production image does not bundle mbstring you must add it:
RUN docker-php-ext-install mbstringOn a Debian / Ubuntu host:
apt-get install -y php-mbstring1.x threw a plain \Exception. 2.0 ships a dedicated exception tree under InitPHP\Escaper\Exception\ (see Exceptions).
Your existing \Exception or \Throwable catches still work because the new exceptions extend \RuntimeException, but you can now be specific:
use InitPHP\Escaper\Esc;
+use InitPHP\Escaper\Exception\EscaperException;
try {
echo Esc::esc($value, 'attr');
-} catch (\Exception $e) {
+} catch (EscaperException $e) {
// …
}Granular catch for the most common case — log encoding/UTF-8 problems, re-throw programmer errors:
use InitPHP\Escaper\Exception\{
EncodingConversionException,
EncodingNotSupportedException,
InvalidContextException,
InvalidUtf8Exception,
};
try {
$safe = Esc::esc($value, $context, $encoding);
} catch (EncodingNotSupportedException | InvalidContextException $e) {
throw $e; // programmer error
} catch (InvalidUtf8Exception | EncodingConversionException $e) {
error_log($e->getMessage());
$safe = '[invalid value]';
}In 1.x, if iconv / mb_convert_encoding returned false, the escaper silently substituted an empty string and returned it. That silently destroyed data.
2.0 raises EncodingConversionException instead:
// 1.x: returned ''
// 2.0:
(new Escaper('windows-1252'))->escHtmlAttr($problematicBytes);
// EncodingConversionException: Failed to convert string from "windows-1252" to "UTF-8".If you genuinely relied on the old "empty string on failure" behaviour, add an explicit try/catch:
use InitPHP\Escaper\Exception\EncodingConversionException;
try {
$safe = $escaper->escHtmlAttr($value);
} catch (EncodingConversionException $e) {
$safe = '';
}Most callers will prefer the exception — if you were silently corrupting output before, you'll now see it.
This is a bug fix. In 1.x:
Esc::esc(['x' => $v], 'html', 'iso-8859-1');
// Recursed into the array and called itself WITHOUT the encoding.
// Every nested value escaped as UTF-8 regardless of the third argument.2.0 propagates the encoding correctly:
Esc::esc(['x' => $v], 'html', 'iso-8859-1');
// Nested values are also escaped with iso-8859-1.If you were depending on the bug (you passed an encoding but expected UTF-8 for nested values), drop the encoding argument:
-Esc::esc($payload, 'html', 'iso-8859-1');
+Esc::esc($payload, 'html');escHtmlAttr always replaced single-byte C0/C1 controls with the Unicode replacement character U+FFFD. In 1.x the replacement only fired against the first byte of a multibyte sequence, so U+0080–U+009F in their proper 2-byte UTF-8 form (\xC2\x80 … \xC2\x9F) survived as numeric character references (€ … Ÿ) instead of being replaced.
2.0 catches both forms:
Esc::esc("\xC2\x80", 'attr'); // 2.0: �
// 1.x: €Both are XSS-safe; if you were diffing output byte-for-byte across versions, expect this drift for the exact U+0080–U+009F range.
Not a BC break, but worth knowing.
In 1.x the static cache rebuilt the Escaper on every call when $encoding === null, because it compared $escaper->getEncoding() ('utf-8') to the raw $encoding argument (often null), and 'utf-8' !== null is always true.
2.0 normalises null and '' to 'utf-8' before lookup and keys the cache per encoding. No code change needed — your default-encoding calls just got faster.
The runnable PHP files under Examples/ are gone. The same scenarios live under docs/ inside the repo and across this wiki (see the per-context pages: HTML, HTML attribute, JavaScript, CSS, URL).
If you scripted against the example file paths, point your tooling at docs/ instead.
If you have a fork or downstream patches, note that 2.0 adds:
-
phpstan.neon.dist— levelmax, zero reported errors. -
.php-cs-fixer.dist.php—@PSR12 + @PHP74Migrationrule sets. - GitHub Actions CI — PHPUnit on PHP 7.4 → 8.4 with
--prefer-lowestand--prefer-stablevariants, plus PHP-CS-Fixer and PHPStan jobs.
Your local changes should pass composer ci before being submitted as PRs.
-
ext-mbstringavailable in every environment. -
catch (\Exception)→catch (EscaperException)(optional). - Handle
EncodingConversionExceptionif you used to rely on the silent empty-string fallback. - Drop redundant
$encodingarguments that depended on the recursion bug. - Re-run any byte-for-byte output snapshots that include the
U+0080–U+009Frange. - If you forked, run
composer ciagainst your changes.
-
CHANGELOG.md— terse Keep-a-Changelog log of every release. -
UPGRADE-2.0.md— repo-side copy of this guide for code-reading without leaving GitHub. - Exceptions — the new exception tree in full.
- FAQ — common questions after upgrading.
Getting Started
Entry Points
Output Contexts
Reference
Production
Migration & Help