Strings7 min readExamples run on PHP 8.5.10

strcspn()

Learn how PHP 8.4 strcspn() measures the initial segment of a string that avoids a set of characters, with offset and length rules, examples and pitfalls.

Signature

strcspn(
    string $string,
    string $characters,
    int $offset = 0,
    ?int $length = null
): int

Purpose

strcspn() counts how many bytes at the start of a string pass before the first byte that appears in a set of forbidden characters. The name stands for "complement span": it spans the characters that are not in the mask. It is a quick way to find where a token ends without a regular expression and without building a temporary array.

The function never returns the matching character or its position as a separate value. It returns a number, and that number is both the length of the safe prefix and the offset of the first forbidden byte (or the length of the examined text when no forbidden byte exists).

Signature

Parameters and defaults

  • $string: the text to examine.
  • $characters: a string whose individual bytes are all treated as stop characters. Order does not matter, and repeated bytes make no difference.
  • $offset: where to start. The default is 0. A negative value counts from the end of the string.
  • $length: how much of the string to examine. The default null means everything from the offset to the end. A negative value stops that many characters before the end of the string.

When you pass $offset or $length, the result is the same as calling strcspn(substr($string, $offset, $length), $characters). This also means the returned count starts at the offset, not at the beginning of the original string.

Return value and failure cases

The function returns an int that is never negative. It does not return false when nothing matches: if no forbidden byte is found, you get the length of the examined segment. If the very first byte is forbidden, you get 0.

Passing a value of an unsuitable type, such as an array for $string, raises a TypeError. The function is binary-safe, so null bytes in the string are treated like any other byte.

Example 1: basic counts

The first call stops at the first a, the second stops immediately because b, a, and so on are all forbidden, and the third never meets a z, so the whole string length is returned.

Example
<?php
$results = [
    strcspn('banana', 'a'),
    strcspn('banana', 'abcd'),
    strcspn('banana', 'z'),
];
echo json_encode($results), "\n";
Output
[1,0,6]

Example 2: offset and length

In abcdhelloabcd, an offset of -9 starts at helloabcd. The first a there is five bytes in. Adding a length of -5 trims the examined part to hell, which contains no a, so the full segment length of 4 is returned.

Example
<?php
$text = 'abcdhelloabcd';
echo json_encode([
    'from_negative_offset' => strcspn($text, 'a', -9),
    'with_negative_length' => strcspn($text, 'a', -9, -5),
    'positive_offset' => strcspn($text, 'o', 2),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"from_negative_offset":5,"with_negative_length":4,"positive_offset":6}

Example 3: taking the first token

Pairing strcspn() with substr() is a compact way to cut text at the first delimiter. If the delimiter is missing, the whole string is returned. If the text starts with a delimiter, the result is an empty string.

Example
<?php
function firstToken(string $input): string
{
    $length = strcspn($input, " \t,;");
    return substr($input, 0, $length);
}

echo json_encode([
    firstToken('alpha,beta'),
    firstToken("tab\tseparated"),
    firstToken('single'),
    firstToken(',leading'),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
["alpha","tab","single",""]

Edge cases

  • Empty subject: an empty string yields 0.
  • Empty mask: nothing is forbidden, so the whole examined segment is counted. Before PHP 8.4.0, an empty $characters made the search incorrectly stop at the first null byte in the string; PHP 8.4 fixed this.
  • Offset past the end: the examined segment is empty, so the result is 0, as with substr().
  • Nullable length: since PHP 8.0.0, $length may be null explicitly.
  • Bytes, not characters: a multibyte UTF-8 character occupies several bytes, so counts and offsets are in bytes. Use multibyte functions when you need character counts.

Common mistakes

  1. Treating the mask as a substring. strcspn('hello', 'll') stops at the first l byte; it does not look for the sequence ll.
  2. Expecting ranges. A mask such as 'a-z' contains just the bytes a, - and z. Write out every character you want to forbid.
  3. Reading the result as an absolute position when an offset is used. The count begins at the offset, so add the offset back if you need a position in the original string.
  4. Checking for false. The function always returns an integer; compare against the length of the examined text to detect "no forbidden byte found".
  5. Confusing it with strspn(). strspn() counts bytes that are in the mask, while strcspn() counts bytes that are not.
  • strspn() measures the initial segment made only of allowed characters.
  • substr() defines how $offset and $length behave and extracts the segment.
  • strpos() and strpbrk() are alternatives when you want a position or the remainder of the string instead of a count.

Recap

strcspn() returns the number of leading bytes in a string that avoid every byte in a mask. Offset and length behave like substr(), the result is relative to the offset, and the function is binary-safe. Use it for simple delimiter scanning, and remember that the mask is a set of single bytes rather than a pattern.

Tested examples

Basic counts with different masks
<?php
$results = [
    strcspn('banana', 'a'),
    strcspn('banana', 'abcd'),
    strcspn('banana', 'z'),
];
echo json_encode($results), "\n";
Output
[1,0,6]
Using offset and length
<?php
$text = 'abcdhelloabcd';
echo json_encode([
    'from_negative_offset' => strcspn($text, 'a', -9),
    'with_negative_length' => strcspn($text, 'a', -9, -5),
    'positive_offset' => strcspn($text, 'o', 2),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"from_negative_offset":5,"with_negative_length":4,"positive_offset":6}
Extracting the first token before a delimiter
<?php
function firstToken(string $input): string
{
    $length = strcspn($input, " \t,;");
    return substr($input, 0, $length);
}

echo json_encode([
    firstToken('alpha,beta'),
    firstToken("tab\tseparated"),
    firstToken('single'),
    firstToken(',leading'),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
["alpha","tab","single",""]