ctype_digit

Checks whether every character in a string is a decimal digit.

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

Parameters

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

Return value

booltrue if every character in text is a decimal 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 return false.

Changelog

  • PHP 8.1Passing a non-string argument is deprecated. An int between -128 and 255 is still read as an ASCII codepoint, so ctype_digit(5) tests chr(5) and returns false — cast to string first.

ctype_digit() is the strictest of PHP's numeric checks: it is true only when every byte is 0 through 9. No sign, no decimal point, no exponent, no leading or trailing whitespace, no thousands separator.

When to reach for it

Use it when the string must stay a string of digits — a ZIP code, an account number, a phone number segment, an ID from a URL. Unlike is_numeric(), it will not quietly accept 1e3, 0x1A, -5 or " 42", and unlike casting to int it does not destroy leading zeros.

What trips people up

The classic bug is passing an int. Because a small int is interpreted as an ASCII codepoint, ctype_digit(5) tests chr(5) and returns false, while ctype_digit(48) tests chr(48), which is the character 0, and returns true. Since PHP 8.1 this also emits a deprecation notice. If the value might be an int, use is_int($v) || ctype_digit((string) $v).

Remember too that a value passing this test may still be larger than PHP_INT_MAX, so validate the range separately before casting.

Example

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

When to use it

  • Validating an ID taken from $_GET before using it in a query
  • Checking a ZIP or postal code where leading zeros must survive
  • Rejecting decimals and negative numbers on a quantity field that must be a whole count

Frequently asked questions

What is the difference between ctype_digit and is_numeric?

ctype_digit is true only for a string of 0-9 characters, while is_numeric also accepts signs, decimal points, exponents, hexadecimal-free floats and leading whitespace. Use ctype_digit when you mean unsigned whole digits and nothing else.

Why does ctype_digit return false for a number?

Passing an int between -128 and 255 makes PHP treat it as an ASCII codepoint, so ctype_digit(5) checks chr(5), a control character. Cast the value with (string) before calling; passing non-strings is deprecated since PHP 8.1 anyway.

Does ctype_digit accept negative numbers or decimals?

No. The minus sign and the decimal point are not digits, so '-5' and '1.5' both return false. Use filter_var with FILTER_VALIDATE_INT or FILTER_VALIDATE_FLOAT for those.

More examples

Safely handling a value that may be an int

Prints true, true, false. The is_int() branch exists because ctype_digit(5) would test ASCII codepoint 5 and return false while also raising a deprecation notice on PHP 8.1 and later.

Example · php
<?php
function isWholeNumber(mixed $value): bool
{
    return is_int($value) || (is_string($value) && ctype_digit($value));
}

var_dump(isWholeNumber(5));
var_dump(isWholeNumber('5'));
var_dump(isWholeNumber('5.0'));

Keeping leading zeros intact

Prints "Valid ZIP: 01234" then "As int: 1234". This is why identifier-like numbers should be validated with ctype_digit and stored as strings rather than converted to integers.

Example · php
<?php
$zip = '01234';

if (ctype_digit($zip) && strlen($zip) === 5) {
    echo "Valid ZIP: $zip\n";
}

// Casting would silently lose the leading zero:
echo 'As int: ', (int) $zip, "\n";

Discussion

  • Be the first to comment on this lesson.