应使用DTO规范隔离数据库字段与前端接口,定义继承BaseDto的UserProfileDto类,通过手动赋值或白名单过滤映射数据,批量查询时用asArray()+array_map构造DTO集合并调用toArray()输出。

在Yii框架中用模型直接返回数据库字段给前端,容易导致敏感字段泄露、字段名与前端需求不一致、查询逻辑和展示逻辑耦合。你需要一套清晰、可控、可复用的数据传输规范,让控制器只负责组装DTO,而非拼接数组或硬编码键名。
定义DTO类并继承基类
新建 common/dto/UserProfileDto.php 文件,不要放在models目录下,避免与AR模型混淆。
继承一个统一的 【BaseDto】 基类,该基类提供 toArray() 和自动类型校验能力——否则每个DTO都要重复写序列化逻辑,后期字段变更时极易漏改。
在DTO类里只声明 public $属性,不写getter/setter,不加注释,不放业务逻辑。字段名必须与前端约定一致,比如前端要 avatar_url,就写 public $avatar_url,而不是照搬数据库的 avatar。
从ActiveRecord映射到DTO
方法一:手动赋值(适合字段少、需做转换的场景)
在控制器或服务层中,实例化DTO后逐个赋值:$dto = new UserProfileDto(); $dto->name = $user->real_name ?: $user->username; $dto->avatar_url = $user->avatar ? Yii::$app->params['cdn'] . $user->avatar : ''; $dto->is_vip = (bool)$user->vip_level;
方法二:用 array_merge + 白名单过滤(适合字段多、结构接近的场景)
先定义白名单数组:$whitelist = ['id', 'username', 'email', 'created_at'];,再用 array_intersect_key($user->getAttributes(), array_flip($whitelist)) 提取原始数据,最后调用 new UserProfileDto($data) 构造——【严禁直接 new UserProfileDto($user->getAttributes())】,否则数据库里的 password_hash、auth_key 会原样透出。
批量查询并生成DTO集合
第一步:用 User::find()->select(['id', 'username', 'status'])->where(['status' => 1])->asArray()->all() 获取原始数组,不要用 ->all() 返回AR对象,避免N+1和内存浪费。
第二步:遍历结果,每条记录走一次DTO构造:array_map(fn($row) => new UserProfileDto($row), $rawData)。
第三步:统一调用 array_map(fn($dto) => $dto->toArray(), $dtos) 输出最终响应体。这一步不能省——DTO对象本身不能直接JSON输出,PHP默认会把所有public属性(包括未初始化的null)都序列化进去,而 toArray() 可控制空字段是否保留、时间戳是否转格式、布尔值是否转0/1等。


















