在 symfony 7.4 中执行原生 sql 查询需通过 doctrine dbal connection 安全执行,禁用拼接、强制参数绑定,select 用 executequery(),dml 用 executestatement(),结果为关联数组且需手动处理类型与命名。

在 Symfony 7.4 中执行原生 SQL 查询是可行的,但不推荐作为日常首选——它绕过 Doctrine ORM 的安全层和实体映射,需手动处理参数绑定、类型转换与异常。只有在复杂报表、性能敏感聚合或无法用 DQL/QueryBuilder 表达的场景下才使用。关键不是“怎么写 SQL”,而是“怎么安全地交由 Doctrine 执行”。
获取数据库连接对象
原生 SQL 必须通过 Doctrine DBAL Connection 执行,不能直接用 PDO。Connection 对象由 Doctrine Bundle 自动注册为服务,最常用获取方式是:
- 在控制器或服务中通过构造函数注入
Connection(推荐) - 或从 EntityManager 获取:
$this->getEntityManager()->getConnection()(仅限 Repository 或旧式写法)
示例(控制器中):
namespace App\Controller;use Doctrine\DBAL\Connection;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class ReportController extends AbstractController
{
public function __construct(private Connection $connection) {}
public function salesSummary(): array
{
$sql = 'SELECT DATE(created_at) as day, COUNT(*) as total FROM order WHERE created_at >= :from GROUP BY day ORDER BY day';
return $this->connection->executeQuery($sql, ['from' => new \DateTime('-30 days')])->fetchAllAssociative();
}
}
执行查询:query() vs executeQuery()
Connection 提供两个核心方法:
-
executeQuery():用于
SELECT,返回Result对象,支持fetchAllAssociative()、fetchOne()等安全取值方法 -
executeStatement():用于
INSERT/UPDATE/DELETE,返回影响行数(int),不返回结果集
⚠️ 注意:query() 是低级方法,不支持参数绑定,绝对禁止拼接用户输入!
安全传参:数组 vs 单值 vs 数组参数
所有变量必须通过参数占位符传入,Doctrine 自动转义。三种常见情况:
- 单个标量值:
['status' => 'active'] - 多个字段值:
['min' => 100, 'max' => 500] - IN 子句数组(重点!):
executeQuery(..., [$ids], [Connection::PARAM_INT_ARRAY]),否则会报错或逻辑错误
错误示例(拼接数组):
$sql = "SELECT * FROM user WHERE id IN (" . implode(',', $ids) . ")"; // ❌ SQL 注入风险
处理结果与类型转换
原生查询返回的是纯数组(非实体对象),字段名默认为小写下划线(如 user_name),若数据库列用了驼峰命名(如 userName),需在 SQL 中显式别名:
时间、JSON、枚举等特殊类型不会自动转换,需在 PHP 层手动处理。例如 MySQL 的 JSON 字段需用 json_decode();日期字段建议用 \DateTimeImmutable 构造。











