phalcon/cphalcon · error · Phalcon\Db\Exceptions\MatchedParameterNotFound

Matched parameter was not found in parameters list

Error message

Matched parameter was not found in parameters list

What it means

convertBoundParams(sql, params) scans SQL for placeholders using BIND_PATTERN '/\?([0-9]+)|:([a-zA-Z0-9_]+):/' — positional ?N and named :name: with a trailing colon — and looks each capture up in params. This variant is the branch where the first lookup fails and there is no alternate name group to try: the placeholder in the SQL has no matching key in params. The SQL and the params array are out of sync.

Source

Thrown at phalcon/Db/Adapter/Pdo/AbstractPdo.zep:366

     *     )
     * );
     *```
     */
    public function convertBoundParams( string sql, array params = []) -> array
    {
        var boundSql, placeHolders, bindPattern, matches, setOrder, placeMatch,
            value;

        let placeHolders = [],
            bindPattern = self::BIND_PATTERN,
            matches = null,
            setOrder = 2;

        if preg_match_all(bindPattern, sql, matches, setOrder) {
            for placeMatch in matches {
                if !fetch value, params[placeMatch[1]] {
                    if unlikely !isset placeMatch[2] {
                        throw new MatchedParameterNotFound();
                    }

                    if unlikely !fetch value, params[placeMatch[2]] {
                        throw new MatchedParameterNotFound();
                    }
                }

                let placeHolders[] = value;
            }

            let boundSql = preg_replace(bindPattern, "?", sql);
        } else {
            let boundSql = sql;
        }

        return [
            "sql"    : boundSql,
            "params" : placeHolders

View on GitHub (pinned to b7419de9cd)

Solutions

  1. Keep SQL placeholders and params keys exactly in sync: every :name: needs params['name'], every ?N needs params[N]
  2. For Postgres ::type casts adjacent to another colon, rewrite as CAST(expr AS type) so the pattern does not match
  3. Rebuild params from the same source that builds the SQL instead of maintaining two artifacts

Example fix

// before
$sql = 'SELECT * FROM users WHERE city = :city:';
$params = ['town' => 'Berlin'];
$result = $connection->convertBoundParams($sql, $params);

// after
$sql = 'SELECT * FROM users WHERE city = :city:';
$params = ['city' => 'Berlin'];
$result = $connection->convertBoundParams($sql, $params);
Defensive patterns

Strategy: validation

Validate before calling

// mirror of the internal BIND_PATTERN '/\?([0-9]+)|:([a-zA-Z0-9_]+):/'
preg_match_all('/\?([0-9]+)|:([a-zA-Z0-9_]+):/', $sql, $m, PREG_SET_ORDER);
foreach ($m as $match) {
    $key = $match[1] !== '' ? (int) $match[1] : $match[2];
    if (!array_key_exists($key, $params)) {
        throw new InvalidArgumentException("SQL placeholder '{$key}' missing from params");
    }
}
$connection->convertBoundParams($sql, $params);

Try / catch

use Phalcon\Db\Exceptions\MatchedParameterNotFound;

try {
    [$sql, $values] = $connection->convertBoundParams($sql, $params);
} catch (MatchedParameterNotFound $e) {
    throw new RuntimeException('SQL/params mismatch: ' . $e->getMessage() . ' in: ' . $sql, 0, $e);
}

Prevention

When it happens

Trigger: SQL contains ?2 but params only has indexes 0/1 (note: ?2 maps to params[2] directly, not the second positional param); SQL contains :city: but params is ['town' => ...] after a rename; params keys renamed or dropped while the SQL string was left unchanged.

Common situations: Renaming bind variable names in raw SQL but not the params array; positional placeholders where the params array was re-indexed (array_values, array_merge, sort) breaking expected indexes; literal text in SQL accidentally matching the pattern — e.g. Postgres double casts like value::numeric::text contain ':numeric:' which is treated as a placeholder.

Related errors


AI-assisted analysis of phalcon/cphalcon@b7419de9cd (2026-08-21). Data as JSON: /api/errors/e10c1c15674a02fc. Report an issue: GitHub.