mongodb/laravel-mongodb · error · LogicException

Missing expected ending delimiter

Error message

Missing expected ending delimiter "%s" in regular expression "%s"

What it means

After validating the leading delimiter of a regex where value, the pattern is exploded by the delimiter; fewer than 3 parts means the closing delimiter is missing (e.g. '/^abc'). The library needs the trailing delimiter to separate the pattern body from its flags and throws LogicException otherwise.

Solutions

  1. Close the pattern: ->where('name', 'regex', '/^abc/').
  2. Include flags after the closing delimiter if needed: '/^abc/i'.
  3. Prefer BSON Regex objects: new Regex('^abc', 'i') — no delimiter parsing at all.
  4. Escape inner occurrences of the delimiter (\/ for '/') so the count stays balanced.

Example fix

// before
$query->where('name', 'regex', '/^abc');
// after
$query->where('name', 'regex', '/^abc/i');
Defensive patterns

Strategy: validation

Validate before calling

if (is_string($pattern) && substr_count($pattern, substr($pattern, 0, 1)) < 2) {
    $pattern .= substr($pattern, 0, 1); // close the delimiter
}

Prevention

When it happens

Trigger: ->where('name', 'regex', '/^abc') (no closing /); unbalanced delimiters where an escaped delimiter broke the count; patterns like '/ab/' missing the flags segment count due to typos.

Common situations: Dynamically-built patterns where the closing delimiter was dropped in string interpolation; hand-written patterns from Mongo shell that omit trailing slash.

Related errors


AI-assisted analysis of mongodb/laravel-mongodb@0634653039 (2026-09-15). Data as JSON: /api/errors/37498801c0a9e86e. Report an issue: GitHub.

Appendix: source

Thrown at src/Query/Builder.php:1516

            // For inverse like operations, we can just use the $not operator with the Regex
            $operator = $operator === 'like' ? '=' : 'not';
            // phpcs:ignore Squiz.ControlStructures.ControlSignature.SpaceAfterCloseBrace
        }

        // Manipulate regex operations.
        elseif (in_array($operator, ['regex', 'not regex'])) {
            // Automatically convert regular expression strings to Regex objects.
            if (is_string($value)) {
                // Detect the delimiter and validate the preg pattern
                $delimiter = substr($value, 0, 1);
                if (! in_array($delimiter, self::REGEX_DELIMITERS)) {
                    throw new LogicException(sprintf('Missing expected starting delimiter in regular expression "%s", supported delimiters are: %s', $value, implode(' ', self::REGEX_DELIMITERS)));
                }

                $e = explode($delimiter, $value);
                // We don't try to detect if the last delimiter is escaped. This would be an invalid regex.
                if (count($e) < 3) {
                    throw new LogicException(sprintf('Missing expected ending delimiter "%s" in regular expression "%s"', $delimiter, $value));
                }

                // Flags are after the last delimiter
                $flags = end($e);
                // Extract the regex string between the delimiters
                $regstr = substr($value, 1, -1 - strlen($flags));
                $value = new Regex($regstr, $flags);
            }

            // For inverse regex operations, we can just use the $not operator with the Regex
            $operator = $operator === 'regex' ? '=' : 'not';
        }

        if (! isset($operator) || $operator === '=' || $operator === 'eq') {
            $query = [$column => $value];
        } else {
            $query = [$column => ['$' . $operator => $value]];
        }

View on GitHub (pinned to 0634653039)