ctype_alnum

Checks whether every character in a string is a letter or a digit.

Signature
ctype_alnum(mixed $text): bool
  • Added in PHP 4.0.4

Parameters

ParameterTypeDescription
$textmixedThe string to test. Every byte must be a letter or a digit for the result to be true. Passing an int between -128 and 255 is interpreted as an ASCII codepoint rather than as digits, which is why non-string arguments are deprecated as of PHP 8.1.

Return value

booltrue if every character in text is a letter or a digit, false otherwise. An empty string always returns false.

Errors

Never throws. A non-string argument raises an E_DEPRECATED notice as of PHP 8.1; values that are neither string nor int simply return false.

Changelog

  • PHP 8.1Passing a non-string argument is deprecated. Ints between -128 and 255 are still read as ASCII codepoints, so cast to string with (string) or convert deliberately with chr().

ctype_alnum() answers one question: is every byte in this string a letter or a digit? It is the cheapest way to enforce a "letters and numbers only" rule on usernames, coupon codes, slugs and table names, and it is noticeably faster than the equivalent preg_match('/^[A-Za-z0-9]+$/', $s) because no pattern has to be compiled.

When to reach for it

  • Validating short identifiers before putting them into a URL, a filename or an SQL identifier.
  • Rejecting input that should never contain spaces, dashes or underscores.
  • Screening imported data for stray punctuation, one field at a time.

What trips people up

The check is byte-based, not Unicode-aware. Under the default C locale only ASCII A-Z, a-z and 0-9 count, so a UTF-8 string like café returns false. For accented or non-Latin text use preg_match('/^[\p{L}\p{N}]+$/u', $s) instead.

The result is also all-or-nothing: one hyphen anywhere makes the whole string fail. If you want to know which character broke the rule, loop over str_split() and test each byte.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Rejecting usernames that contain spaces, dots or symbols before creating an account
  • Validating a fixed-length promo code such as AB12CD34 without a regex
  • Checking that a sort column name from a query string is safe to interpolate into SQL

Frequently asked questions

Why does ctype_alnum return false for an empty string?

By design: the documented behaviour is that an empty string always returns false, because there is no character that satisfies the test. Check for emptiness yourself if a blank value should be allowed.

Does ctype_alnum work with accented or Unicode characters?

No. It inspects raw bytes against the current locale, so under the default C locale only ASCII letters and digits pass and a UTF-8 string such as "café" returns false. Use preg_match with the /u modifier and \p{L}\p{N} for Unicode.

What is the difference between ctype_alnum and preg_match for validation?

They give the same answer for ASCII, but ctype_alnum is faster and more readable for a plain letters-and-digits check. Use a regex when you need length limits, anchors, optional characters or Unicode support.

More examples

A complete validator for a fixed-length code

Prints true, then false, then false. The length test comes first so the empty string is rejected on length rather than relying on the empty-string behaviour of ctype_alnum, which makes the intent explicit to the next reader.

Example · php
<?php
function isValidCode(string $code): bool
{
    return strlen($code) === 8 && ctype_alnum($code);
}

var_dump(isValidCode('AB12CD34'));
var_dump(isValidCode('AB12-CD3'));
var_dump(isValidCode(''));

Finding the offending character

Prints "Bad character '#' at offset 5". ctype_alnum only tells you pass or fail for the whole string, so splitting into single characters is how you build a useful error message.

Example · php
<?php
$input = 'order#42';

if (!ctype_alnum($input)) {
    foreach (str_split($input) as $i => $ch) {
        if (!ctype_alnum($ch)) {
            echo "Bad character '$ch' at offset $i\n";
        }
    }
}

Discussion

  • Be the first to comment on this lesson.