PHP strrev(): Reverse Strings Without Corrupting UTF-8
Learn when PHP strrev() is correct, why byte reversal can corrupt UTF-8, and how to reverse code points or grapheme clusters with tested examples.
If you need to reverse a PHP string, strrev() is usually the first function you will find. It is short, easy to read, and exactly right for ordinary ASCII text: strrev('stressed') returns desserts. The important qualification is that PHP strings are byte sequences. strrev() reverses those bytes; it does not inspect UTF-8 character boundaries. That makes it useful for some data and unsafe for many human-language strings.
This guide explains what PHP strrev() actually does, why accented text and emoji can break, and how to choose between byte, code-point, and grapheme-cluster reversal. The goal is not to replace a simple one-liner unnecessarily. It is to help you use the one-liner when it fits and switch methods when “one character” means more than one byte.
What strrev() returns
The official PHP strrev() manual describes the function simply: it accepts a string and returns that string reversed. It returns a new string; the variable passed to it is not modified unless you assign the result back to that variable.
$word = 'stressed';
$backward = strrev($word);
echo $backward; // desserts
echo $word; // stressed
Spaces, tabs, punctuation, and line breaks participate in the same reversal. strrev('red blue') returns eulb der. For this ASCII example, byte reversal and character reversal give the same result. It is not word-order reversal: the letters within each word also move. If your goal is to change red blue into blue red while keeping each word intact, use a word-order approach instead; see How to Reverse Words in a Sentence Without Scrambling the Letters.
For ASCII input, one byte represents one character, so byte reversal and character reversal appear identical. This covers many identifiers, internal keys, hexadecimal strings, and deliberately ASCII-only puzzles. It does not make strrev() a Unicode-aware function.
Why UTF-8 exposes the problem
UTF-8 represents some characters with two, three, or four bytes. The letter é, for example, is encoded as the two bytes c3 a9. A byte-wise reversal changes those bytes to a9 c3, which is not a valid UTF-8 encoding of that character. In PHP, you can see the evidence without relying on a browser or terminal font:
$text = 'café';
echo bin2hex(strrev($text)); // a9c3666163
The ASCII bytes for c, a, and f are reversed, but the two bytes belonging to é are split and swapped. The result is not valid UTF-8 text. Depending on where you display or store it, you may see replacement characters, mojibake, a warning from another system, or apparently missing content. strrev() is still behaving consistently: it reverses bytes. The mismatch is between that operation and the structure of the input.
This is why “binary-safe” and “Unicode-safe” are different ideas. A binary-safe byte operation can handle arbitrary bytes while making no promise that the result is valid text. Do not call strrev() character-safe merely because it works on English examples.
A useful diagnostic is bin2hex(). If an apparently simple input contains multibyte UTF-8 sequences, inspect the bytes before choosing an algorithm. For input that crosses an API or form boundary, you can validate it with mb_check_encoding($text, 'UTF-8'). The PHP mb_check_encoding() documentation describes it as a check that a byte stream is valid for the specified encoding.
Three meanings of character
Before choosing a replacement, decide what should move together.
A byte is the smallest unit strrev() sees. This is appropriate when the input is intentionally binary or restricted to one-byte ASCII. It is not a safe definition for general UTF-8 text.
A Unicode code point is a decoded value such as é, 中, or an emoji component. Reversing code points keeps each UTF-8 code point intact, so the result remains valid UTF-8 when the input is valid. That is often what developers mean by reversing Unicode characters, but it is not always what a reader sees as one character.
An extended grapheme cluster approximates a user-perceived character. It can contain a base letter and combining mark, or a sequence joined into one emoji. For example, e followed by a combining acute accent consists of two code points but is normally displayed as one accented glyph. A family emoji can be several code points connected by zero-width joiners. Splitting those pieces gives a technically valid sequence that may display incorrectly or communicate a different visual unit.
This distinction also explains why reversal is not the same as rendering. A function can preserve grapheme clusters without mirroring right-to-left text, changing bidirectional layout, or making glyphs face the opposite direction. Reversal changes sequence order; the display engine still applies script and direction rules.
Option 1: use strrev() for ASCII or bytes
Use the built-in function when you can state a clear input invariant:
function reverseAscii(string $value): string
{
// The caller guarantees that $value contains ASCII only.
return strrev($value);
}
echo reverseAscii('level'); // level
A typed string boundary documents the PHP value type, but it does not validate encoding or restrict the contents to ASCII. If the guarantee matters, enforce it at the boundary with your application’s validation rules. Do not infer it from the fact that the current sample contains only English letters.
strrev() also makes sense when you truly want bytes reversed, such as a controlled binary exercise. In that case, invalid UTF-8 is not a bug because the value is not being treated as UTF-8 text. Keep that intent visible in the function name or documentation so a future caller does not reuse it for labels, names, or messages. Reversing a password or token is not encryption or protection.
Option 2: reverse valid UTF-8 code points
For a valid UTF-8 string where each decoded code point should move as a unit, use mb_str_split() and array_reverse():
function reverseCodepoints(string $text): string
{
if (!mb_check_encoding($text, 'UTF-8')) {
throw new InvalidArgumentException('Input must be valid UTF-8.');
}
return implode('', array_reverse(
mb_str_split($text, 1, 'UTF-8')
));
}
echo reverseCodepoints('café'); // éfac
mb_str_split() is available from PHP 7.4 and requires the mbstring extension. Its PHP manual entry specifies that length is measured in characters rather than bytes. Passing 'UTF-8' explicitly avoids relying on a process-wide internal encoding setting.
The validation step matters. A code-point splitter cannot repair malformed input. Rejecting invalid UTF-8 gives the caller a clear failure instead of silently producing a result whose meaning is uncertain. If your application already validates text at ingestion, you may centralize that check rather than repeat it in every helper, but document the precondition.
Code-point reversal keeps precomposed é together because it is one code point. It does not keep every visually combined unit together. With decomposed text, e plus a combining acute accent can become a reversed sequence in which the mark precedes the base letter. The bytes remain valid and the code points were handled correctly; the operation simply used a smaller unit than the reader expected.
Option 3: reverse grapheme clusters for displayed text
When the requirement is to reverse what a user perceives as characters, match extended grapheme clusters with PCRE2’s \X token. The following helper targets PHP 8 or later:
function reverseGraphemes(string $text): string
{
$matches = [];
$count = preg_match_all('/\X/u', $text, $matches);
if ($count === false) {
throw new InvalidArgumentException(
'Cannot segment UTF-8 input: ' . preg_last_error_msg()
);
}
return implode('', array_reverse($matches[0]));
}
echo reverseGraphemes("e\u{0301}x"); // xé
echo reverseGraphemes('👨👩👧👦A'); // A👨👩👧👦
The u modifier asks the regular-expression engine to interpret the subject as UTF-8. preg_match_all() returns the number of full matches or false on failure; the PHP documentation documents both outcomes. Checking specifically for false is important. An empty string produces zero matches and is not an error, so the helper correctly returns an empty string. Invalid UTF-8 is rejected; other regex-engine failures also produce a diagnostic rather than a partial reversal.
PCRE2 documents \X as matching a Unicode extended grapheme cluster. Its pattern specification explains the cluster rules. In the tested environment, the combining accent stays with e, and the joined family emoji stays together.
Grapheme clusters fit many display-oriented tasks, but avoid promising that they reproduce every application’s idea of a visible glyph. Unicode segmentation and PCRE2 versions evolve, fonts differ, and bidirectional rendering is a separate concern. This helper preserves clusters according to the regex engine’s Unicode rules; it does not guarantee identical visual output in every interface.
A practical decision tree
Use strrev() when input is ASCII-only or when you intentionally need byte reversal. Use mb_str_split($text, 1, 'UTF-8') when valid UTF-8 code points are the required units and combining sequences do not need to stay grouped. Use the grapheme helper when displayed text, combining marks, or joined emoji must remain grouped.
If you are unsure, identify the consumer first. A binary exercise may need byte semantics. A language exercise may need code-point semantics. A user-facing nickname, caption, or emoji puzzle usually needs grapheme semantics. Do not choose based only on which function has the shortest code.
Run a complete PHP example
Save this program as reverse.php, using a UTF-8 source file, and run php reverse.php from a terminal. It requires PHP 8 or later and the mbstring extension. The PHP examples were checked with PHP 8.3.6, PCRE2 10.42, and mbstring enabled. Do not paste the earlier helper definitions into the same file again, because duplicate function names cause a redeclaration error.
<?php
declare(strict_types=1);
function reverseCodepoints(string $text): string
{
if (!mb_check_encoding($text, 'UTF-8')) {
throw new InvalidArgumentException('Input must be valid UTF-8.');
}
return implode('', array_reverse(mb_str_split($text, 1, 'UTF-8')));
}
function reverseGraphemes(string $text): string
{
$matches = [];
if (preg_match_all('/\X/u', $text, $matches) === false) {
throw new InvalidArgumentException(
'Cannot segment UTF-8 input: ' . preg_last_error_msg()
);
}
return implode('', array_reverse($matches[0]));
}
$sample = 'café 👨👩👧👦';
echo 'Byte reversal (hex): ' . bin2hex(strrev($sample)) . PHP_EOL;
echo 'Code points: ' . reverseCodepoints($sample) . PHP_EOL;
echo 'Grapheme clusters: ' . reverseGraphemes($sample) . PHP_EOL;
The byte result is shown in hexadecimal so malformed UTF-8 is not sent directly to your terminal. The final line displays Grapheme clusters: 👨👩👧👦 éfac. The code-point result may look different because the family’s components move individually. In production, decide how callers handle exceptions rather than showing a raw error to an end user.
Safe display and a final checklist
Reversal does not make untrusted text safe for HTML. If you display a result in an HTML page, escape it for that context, for example:
$safe = htmlspecialchars(
reverseGraphemes($userInput),
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
);
This is an HTML-output example, not a general escaping rule for JavaScript, SQL, or URLs. ENT_SUBSTITUTE handles malformed sequences during output, but it is not a substitute for validating input before reversal. Keep validation, transformation, and output escaping as separate decisions.
Before shipping a PHP string reversal, test an empty string, ASCII words, spaces, tabs, punctuation, accented characters, decomposed accents, joined emoji, and invalid UTF-8. Confirm that the original variable remains unchanged and that your expected unit—byte, code point, or grapheme cluster—actually stays intact. If your real requirement is word order, line order, or mirrored presentation, use an operation designed for that requirement instead of stretching strrev() beyond its meaning.
For JavaScript developers handling the same emoji boundary problem, Reverse a String in JavaScript Without Breaking Emoji provides a related comparison. For a quick user-facing reversal workflow, you can also start at the Text Reverser home page.
The short answer is simple: strrev() is correct for bytes and ASCII, not a universal Unicode character reverser. Once you decide what character means for your input, the right PHP implementation becomes straightforward—and your output stays predictable.
Try the Text Reverser tool
Reverse text, words, or letters instantly — free and entirely in your browser.
Open the tool