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
- Correct the selector so the position holds either a valid identifier or a single '*' (e.g. 'ns|div', '*|div', '|div' — not 'div|' or '||').
- If a namespace prefix comes from a variable, verify it is non-empty and matches ident rules before interpolation.
- Read the exception message to see the offending token and expected 'identifier or "*"' at that stream position.
- If consuming the stream directly, guard with $stream->getPeek() and check isIdentifier() or isDelimiter(['*']) before calling.
- 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 building 'prefix|name' selectors, default an empty prefix to '*' instead of leaving an empty token.
- Test generated selectors with namespace combinations ('ns|div', '*|div', '|div') in unit tests.
- Use getPeek() to check isIdentifier()/isDelimiter(['*']) before consuming when writing custom grammar handlers.
- Catch SyntaxErrorException at the selector-entry boundary so malformed input never escapes as a crash.
- Avoid hand-assembling namespace syntax; use CssSelector's full parser instead of string concatenation where possible.
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)