Take the State of PHP 2026 Survey →

Object-based token_get_all() alternative View as Markdown

Implemented (PHP 8.0)
Authors
Nikita Popov nikic@php.net
Date
2020-02-13
Target
PHP 8.0
Implementation
https://github.com/php/php-src/pull/5176
Source
wiki.php.net/rfc/token_as_object

Vote

Add object-based token_get_all() alternative? closed
Yes
47
No
0
47 voters
ajf Yes 2020-03-06 22:38
alcaeus Yes 2020-03-09 10:48
ashnazg Yes 2020-03-18 14:46
beberlei Yes 2020-03-06 11:33
brzuchal Yes 2020-03-06 09:40
bwoebi Yes 2020-03-06 13:03
carusogabriel Yes 2020-03-08 22:18
cmb Yes 2020-03-07 13:59
colinodell Yes 2020-03-06 12:16
daverandom Yes 2020-03-18 14:13
derick Yes 2020-03-17 10:39
dmitry Yes 2020-03-06 10:21
duncan3dc Yes 2020-03-06 11:16
ekin Yes 2020-03-20 17:26
galvao Yes 2020-03-07 05:59
gasolwu Yes 2020-03-07 04:06
girgias Yes 2020-03-06 10:29
googleguy Yes 2020-03-07 18:32
kalle Yes 2020-03-06 14:08
kguest Yes 2020-03-11 16:11
kocsismate Yes 2020-03-06 09:34
levim Yes 2020-03-06 14:29
marandall Yes 2020-03-06 11:39
marcio Yes 2020-03-08 15:41
mariano Yes 2020-03-06 13:48
narf Yes 2020-03-10 09:26
nicolasgrekas Yes 2020-03-06 10:01
nikic Yes 2020-03-06 09:32
ocramius Yes 2020-03-06 11:22
pierrick Yes 2020-03-11 00:22
pmjones Yes 2020-03-11 08:38
pollita Yes 2020-03-11 14:16
ralphschindler Yes 2020-03-20 15:58
ramsey Yes 2020-03-06 16:48
reywob Yes 2020-03-06 11:11
ruudboon Yes 2020-03-16 06:20
santiagolizardo Yes 2020-03-18 23:33
sebastian Yes 2020-03-08 17:41
sergey Yes 2020-03-06 09:35
sirsnyder Yes 2020-03-06 10:07
svpernova09 Yes 2020-03-20 18:18
tandre Yes 2020-03-06 14:21
trowski Yes 2020-03-20 14:37
villfa Yes 2020-03-06 13:24
wyrihaximus Yes 2020-03-22 22:54
yunosh Yes 2020-03-09 08:19
zimt Yes 2020-03-10 11:44

Discussions on the mailing list

[RFC] token_get_all() TOKEN_AS_OBJECT mode
by Nikita Popov · 20 messages
[VOTE] Object-based token_get_all() alternative
by Nikita Popov · 10 messages

RFC text

Copied from wiki.php.net, last synchronized Oct 13, 2026 11:51 UTC

Introduction

The token_get_all() function currently returns tokens either as a single-character string, or an array with a token ID, token text and line number. This RFC proposes to add a token_get_all() alternative which returns an array of objects instead. This reduces memory usage and makes code operating on tokens more readable.

Note: PhpToken::getAll() has been renamed to PhpToken::tokenize() prior to the PHP 8.0 release. The RFC text still refers to PhpToken::getAll().

Proposal

A new PhpToken class is introduced with the following properties and methods:

class PhpToken {
    /** One of the T_* constants, or an integer < 256 representing a single-char token. */
    public int $id;
    /** The textual content of the token. */
    public string $text;
    /** The starting line number (1-based) of the token. */
    public int $line;
    /** The starting position (0-based) in the tokenized string. */
    public int $pos;
 
    /**
     * Same as token_get_all(), but returning array of PhpToken.
     * @return static[]
     */
    public static function getAll(string $code, int $flags = 0): array;
 
    final public function __construct(int $id, string $text, int $line = -1, int $pos = -1);
 
    /** Get the name of the token. */
    public function getTokenName(): ?string;
 
    /**
     * Whether the token has the given ID, the given text,
     * or has an ID/text part of the given array.
     * 
     * @param int|string|array $kind
     */
    public function is($kind): bool;
 
    /** Whether this token would be ignored by the PHP parser. */
    public function isIgnorable(): bool;
}

The PhpToken::getAll() method is the replacement for token_get_all(), which returns an array of PhpToken objects instead of a mix of strings and arrays.

It should be emphasized that all tokens are returned as objects, including single-char tokens. While this uses more memory than returning them as strings, experience has shown that the current string/array mix is very inconvenient to work with.

Returning an array of objects has the following advantages over the current approach:

  • The representation of tokens is uniform, it is not necessary to continuously check whether an array or string token is being used.
  • The using code is cleaner, because $token->text is easier to understand than $token[1] and friends.
  • The token stores the position in the file, so that consumers don't have to compute and store it separately.

Finally, the tokens take up significantly less memory, and are faster to construct as well. On a large file:

Default:
    Memory Usage: 14.0MiB
    Time: 0.43s (for 100 tokenizations)
TOKEN_AS_OBJECT:
    Memory Usage: 8.0MiB
    Time: 0.32s (for 100 tokenizations)

Extensibility

The PhpToken::getAll() method returns static[], as such it is possible to seamlessly extend the class:

class MyPhpToken extends PhpToken {
    public function getLowerText() {
        return strtolower($this->text);
    }
}
 
$tokens = MyPhpToken::getAll($code);
var_dump($tokens[0] instanceof MyPhpToken); // true
$tokens[0]->getLowerText(); // works

To guarantee a well-defined construction behavior, the PhpToken constructor is final and cannot be overridden by child classes. This matches the extension approach of the SimpleXMLElement class.

Additional methods

The PhpToken class defines a few additional methods, which are defined in terms of the reference-implementations given below.

public function getTokenName(): ?string {
    if ($this->id < 256) {
        return chr($this->id);
    } elseif ('UNKNOWN' !== $name = token_name($this->id)) {
        return $name;
    } else {
        return null;
    }
}

getTokenName() is mainly useful for debugging purposes. For single-char tokens with IDs below 256, it returns the extended ASCII character corresponding to the ID. For known tokens, it returns the same result as token_name(). For unknown tokens, it returns null.

It should be noted that tokens that are not known to PHP are commonly used, for example when emulating lexer behavior from future PHP versions. In this case custom token IDs are used, so they should be handled gracefully.

public function is($kind): bool {
    if (is_array($kind)) {
        foreach ($kind as $singleKind) {
            if (is_string($singleKind)) {
                if ($this->text === $singleKind) {
                    return true;
                }
            } else if (is_int($singleKind)) {
                if ($this->id === $singleKind) {
                    return true;
                }
            } else {
                throw new TypeError("Kind array must have elements of type int or string");
            }
        }
        return false;
    } else if (is_string($kind)) {
        return $this->text === $kind;
    } else if (is_int($kind)) {
        return $this->id === $kind,
    } else {
        throw new TypeError("Kind must be of type int, string or array");
    }
}

The is() method allows checking for certain tokens, while abstracting over whether it is a single-char token $token->is(';'), a multi-char token $token->is(T_FUNCTION), or whether multiple tokens are allowed $token->is([T_CLASS, T_TRAIT, T_INTERFACE]).

While non-generic code can easily check the appropriate property, such as $token->text == ';' or $token->id == T_FUNCTION, token stream implementations commonly need to be generic over different token kinds and need to support specification of multiple token kinds. For example:

// An example, NOT part of the PhpToken interface.
public function findRight($pos, $findTokenKind) {
    $tokens = $this->tokens;
    for ($count = \count($tokens); $pos < $count; $pos++) {
        if ($tokens[$pos]->is($findTokenKind)) {
            return $pos;
        }
    }
    return -1;
}

These kinds of search/skip/check APIs benefit from having an efficient native implementation of is().

public function isIgnorable(): bool {
    return $this->is([
        T_WHITESPACE,
        T_COMMENT,
        T_DOC_COMMENT,
        T_OPEN_TAG,
    ]);
}

As a special case, it is very common that whitespace and comments need to be skipped during token processing. The isIgnorable() method determines whether a token is ignored by the PHP parser.

Rejected Features

Lazy token stream

PhpToken::getAll() returns an array of tokens. It has been suggested that it could return an iterator instead. This would reduce memory usage if it is sufficient to inspect tokens one-by-one for a given use-case.

This is not supported by the current proposal, because the current PHP lexer doesn't support this in an efficient manner. A full lexer state backup and restore would have to be performed for each token. Even if support for an iterator is added in the future, the ability to directly create an array should still be retained, as this will always be more efficient than going through an iterator (for the use-cases that do need a full token array).

Backward Incompatible Changes

There are no backwards compatibility breaks, apart from the new class name.

Vote

Voting opened 2020-03-06 and closes 2020-03-20.

Vote: Add object-based token_get_all() alternative? — Yes: 47 · No: 0 (closed)