symfony/css-selector · error · SyntaxErrorException

identifier or "*"

Error message

identifier or "*"

What it means

TokenStream::getNextIdentifierOrStar() consumes the next token and accepts either an identifier token (returns its value) or a '*' delimiter token (returns null, the universal-selector / namespace-wildcard case). Any other token triggers SyntaxErrorException::unexpectedToken('identifier or "*"', $next), a CssSelector SyntaxErrorException stating that an identifier or '*' was expected.

Solutions

  1. Correct the selector so the position holds either a valid identifier or a single '*' (e.g. 'ns|div', '*|div', '|div' — not 'div|' or '||').
  2. If a namespace prefix comes from a variable, verify it is non-empty and matches ident rules before interpolation.
  3. Read the exception message to see the offending token and expected 'identifier or "*"' at that stream position.
  4. If consuming the stream directly, guard with $stream->getPeek() and check isIdentifier() or isDelimiter(['*']) before calling.
  5. Catch SyntaxErrorException around selector parsing and reject/handle invalid input cleanly.

Example fix

// before: empty namespace prefix leaves an illegal token where 'identifier or "*"' is required
$filter = "$ns|li"; // $ns === ''
$crawler->filter($filter);

// after: use the universal-namespace '*' when no prefix applies
$filter = ($ns !== '' ? $ns : '*') . '|li';
$crawler->filter($filter);
Defensive patterns

Strategy: validation

Validate before calling

use Symfony\Component\CssSelector\Parser\TokenStream;

function nextTokenIsIdentifierOrStar(TokenStream $stream): bool
{
    $peek = $stream->getPeek();

    return $peek->isIdentifier() || $peek->isDelimiter(['*']);
}

// Namespace prefix validation for dynamic selectors
function isValidNamespacePrefix(?string $ns): bool
{
    return $ns === null || $ns === '*' || preg_match('/^[A-Za-z_][A-Za-z0-9_-]*$/', $ns) === 1;
}

Type guard

function isIdentifierOrStarToken(Symfony\Component\CssSelector\Parser\Token $t): bool
{
    return $t->isIdentifier() || $t->isDelimiter(['*']);
}

Try / catch

try {
    $prefix = $stream->getNextIdentifierOrStar();
} catch (Symfony\Component\CssSelector\Exception\SyntaxErrorException $e) {
    // Expected 'identifier or "*"'; handle malformed namespace/universal selector
    throw new InvalidArgumentException('Invalid selector prefix: ' . $e->getMessage(), 0, $e);
}

Prevention

When it happens

Trigger: Calling getNextIdentifierOrStar() (used for namespace prefixes / universal selector positions, e.g. in '*|div', '|div', 'ns|div') when the next token is neither an identifier nor a '*': e.g. the selector contains a stray character like ':' or ')' or a string where a namespace prefix or '*' should be.

Common situations: Selectors with malformed namespace syntax such as 'div|' or '|*|p'; typos like 'div||span'; selectors built dynamically where the namespace variable is empty or contains characters the tokenizer classifies as a string/delimiter; migrating selectors between libraries whose tokenizers differ slightly.

Related errors


AI-assisted analysis of symfony/css-selector@08e2905152 (2026-09-14). Data as JSON: /api/errors/8fb480c4c82f0b39. Report an issue: GitHub.

Appendix: source

Thrown at Parser/TokenStream.php:142

    /**
     * Returns next identifier or null if star delimiter token is found.
     *
     * @throws SyntaxErrorException If next token is not an identifier or a star delimiter
     */
    public function getNextIdentifierOrStar(): ?string
    {
        $next = $this->getNext();

        if ($next->isIdentifier()) {
            return $next->getValue();
        }

        if ($next->isDelimiter(['*'])) {
            return null;
        }

        throw SyntaxErrorException::unexpectedToken('identifier or "*"', $next);
    }

    /**
     * Skips next whitespace if any.
     */
    public function skipWhitespace(): void
    {
        $peek = $this->getPeek();

        if ($peek->isWhitespace()) {
            $this->getNext();
        }
    }
}

View on GitHub (pinned to 08e2905152)