You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Doctrine按列表排序报错:索引3的位置参数未绑定值

解决Doctrine中使用自定义FIELD函数排序时的参数绑定异常

问题原因

错误Positional parameter at index 3 does not have a bound value.的核心是自定义FIELD函数的解析逻辑与参数传递方式不兼容:你通过:statuses传递数组参数,但当前的FIELD函数会将其拆分为多个位置参数,导致Doctrine无法找到对应的绑定值。

修复方案

1. 修改自定义FIELD函数,支持数组参数解析

调整函数逻辑,使其能识别数组参数并自动展开为多个绑定占位符:

<?php

namespace App\DQL\Mysql;

use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\AST\Node;

class FieldFunction extends FunctionNode
{
    private Node $field;
    private array $values = [];
    private bool $isArrayParameter = false;

    public function parse(Parser $parser): void
    {
        $parser->match(Lexer::T_IDENTIFIER);
        $parser->match(Lexer::T_OPEN_PARENTHESIS);

        // 解析排序字段
        $this->field = $parser->ArithmeticPrimary();

        $lexer = $parser->getLexer();

        if ($lexer->lookahead['type'] === Lexer::T_COMMA) {
            $parser->match(Lexer::T_COMMA);

            // 判断是否为数组参数(以:开头的标识符)
            if ($lexer->lookahead['type'] === Lexer::T_IDENTIFIER && str_starts_with($lexer->lookahead['value'], ':')) {
                $this->values[] = $parser->ArithmeticPrimary();
                $this->isArrayParameter = true;
            } else {
                // 兼容传统多参数模式
                do {
                    $this->values[] = $parser->ArithmeticPrimary();
                    if ($lexer->lookahead['type'] !== Lexer::T_COMMA) break;
                    $parser->match(Lexer::T_COMMA);
                } while (true);
            }
        }

        $parser->match(Lexer::T_CLOSE_PARENTHESIS);
    }

    public function getSql(SqlWalker $sqlWalker): string
    {
        $sql = 'FIELD(' . $this->field->dispatch($sqlWalker);

        if ($this->isArrayParameter) {
            // 展开数组参数为多个占位符并绑定值
            $paramName = ltrim($this->values[0]->value, ':');
            $paramValue = $sqlWalker->getQuery()->getParameter($paramName)->getValue();
            
            $placeholders = [];
            foreach ($paramValue as $index => $value) {
                $placeholder = ":{$paramName}_{$index}";
                $sqlWalker->getQuery()->setParameter($placeholder, $value);
                $placeholders[] = $placeholder;
            }
            
            $sql .= ', ' . implode(', ', $placeholders);
        } else {
            // 传统多参数拼接逻辑
            foreach ($this->values as $value) {
                $sql .= ', ' . $value->dispatch($sqlWalker);
            }
        }

        $sql .= ')';
        return $sql;
    }
}

2. 调整控制器代码,避免参数覆盖

确保只调用一次setParameters,防止之前的权限过滤参数被覆盖:

$em = $doctrine->getManager();
$realEstateRepo = $em->getRepository(RealEstateAd::class);
$userAdmin = $this->getUser() && $this->getUser()->isAdmin();

$query = $realEstateRepo->createQueryBuilder('rea');
$query->leftJoin("rea.realEstate", "re");

$parameters = [];

if (!$userAdmin) {
    $parameters['deleted'] = false;
    $parameters['realEstateDeleted'] = false;

    $query->where('rea.deleted = :deleted')
        ->andWhere('re.deleted = :realEstateDeleted');
}

// 追加排序参数
$parameters['statuses'] = [
    Util::REAL_ESTATE_STATUS_AVAILABLE,
    Util::REAL_ESTATE_STATUS_RESERVED,
    Util::REAL_ESTATE_STATUS_LET_AGREED
];

// 统一设置所有参数,避免覆盖
$query->addOrderBy('FIELD(re.status, :statuses)')
    ->addOrderBy('rea.createdAt', 'DESC')
    ->setParameters($parameters);

// 后续分页逻辑保持不变
$pageSize = 10;
$allRealEstate = new Paginator($query);
$totalItems = count($allRealEstate);
$pagesCount = ceil($totalItems / $pageSize);

if ($page > $pagesCount && $pagesCount != 0) {
    return $this->redirectToRoute('app_home');
}

$allRealEstate
    ->getQuery()
    ->setFirstResult($pageSize * ($page-1))
    ->setMaxResults($pageSize);

return $this->render('site/home.html.twig', [
    'pageCount' => $pagesCount,
    'page' => $page,
    'realEstate' => $allRealEstate
]);

可选替代方案(无需修改FIELD函数)

如果不想调整FIELD函数,也可以直接传递多个独立参数:

// 排序语句改为多个参数
$query->addOrderBy('FIELD(re.status, :status1, :status2, :status3)');

// 参数逐个绑定
$parameters['status1'] = Util::REAL_ESTATE_STATUS_AVAILABLE;
$parameters['status2'] = Util::REAL_ESTATE_STATUS_RESERVED;
$parameters['status3'] = Util::REAL_ESTATE_STATUS_LET_AGREED;

此方案缺点是排序值数量变化时需同步修改代码,灵活性较差。

内容的提问来源于stack exchange,提问作者Artjom Tjasin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 01:27:15