我正在编写代码来返回指定区域内且接近特定纬度和经度的位置列表。数据库布局是,有一个包含企业的位置表、一个定义区域的区域表(UrlName 是标识区域的 slug)以及一个将区域映射到位置的区域位置表。
SQL 查询非常复杂,但它计算了一个名为“Distance”的虚拟列,我希望在返回的模型中可以访问该列。
这是出现在我的位置模型中的代码的缩短版本:
public static function getByRegionAndLatLong( $regionName, $lat, $long ) {
$sql = "SELECT
`Location`.`LocationId`,
`Location`.`Latitude`,
`Location`.`Longitude`,
(
3959 * acos (
cos ( radians( :Latitude ) )
* cos( radians( latitude ) )
* cos( radians( longitude ) - radians( :Longitude ) )
+ sin ( radians( :Latitude ) )
* sin( radians( latitude ) )
)
) AS Distance
FROM Location
LEFT JOIN RegionLocation RL
ON RL.LocationId = Location.LocationId
LEFT JOIN Region R
ON R.RegionId = RL.RegionId
WHERE R.UrlName= :UrlName
ORDER BY Distance ;
";
return Location::findBySql( $sql, [
':Latitude' => $lat,
':Longitude' => $long,
':UrlName' => $UrlName
] )->all();
}
问题是,当我运行查询时,我希望计算出的“距离”列包含在结果中。但是,由于数据库中没有名为“Distance”的实际字段,Yii2 的 ActiveRecord 类不会将该字段添加到生成的模型中。
我可以通过在位置表中创建一个名为“距离”的列来解决这个问题,但我希望有一种方法可以在不更改数据库的情况下做到这一点。
我的最终目的是让模型返回一个位置对象数组,每个位置对象都有一个“距离”属性。然后,控制器将使用类似于以下的代码生成 json feed:
public function actionGetStudiosByLatLong( $regionName = '', $latitude='', $longitude='') {
\Yii::$app->response->format = 'json';
return Location::getByRegionAndLatLong( $regionName, $latitude, $longitude );
}
您应该简单地将此属性添加到您的类中:
class Location extends \yii\db\ActiveRecord
{
public function attributes()
{
// add distance attribute (will work for json output)
return array_merge(parent::attributes(), ['Distance']);
}
// ...
}
了解有关选择额外字段的更多信息。
问这个问题后不久,我发现了一个有点不雅的答案。
我必须重写位置模型中的填充记录和 hasAttribute 方法。
/**
* Adds "Distance" field to records if applicable.
*/
public static function populateRecord($record, $row)
{
$columns = static::getTableSchema()->columns;
foreach ($row as $name => $value) {
if (isset($columns[$name])) {
$row[$name] = $columns[$name]->phpTypecast($value);
}
}
parent::populateRecord($record, $row);
if( isset( $row['Distance'] ) ) {
$record->setAttribute( 'Distance', $row['Distance'] );
}
}
// Needed to convince the powers that be that "Distance" really is a field.
public function hasAttribute($name)
{
if($name == 'Distance') return true;
return parent::hasAttribute($name);
}
添加这些后,距离属性开始出现在模型中。