将根据 SQL 查询计算的字段添加到 Yii2 ActiveRecord 模型

Add field calculated from SQL query to Yii2 ActiveRecord model

我正在编写代码以 return 指定区域内的位置列表,并且接近特定的纬度和经度。数据库布局是有一个包含业务的 Location table,一个定义区域的 Region table(UrlName 是标识区域的 slug),以及一个 regionLocation table将区域映射到位置。

SQL 查询非常麻烦,但它计算了一个名为 "Distance" 的虚拟列,我希望在模型 returned 中可以访问它。

这是出现在我的位置模型中的代码的简化版本:

        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" 列包含在结果中。但是,由于数据库中没有名为 "Distance" 的实际字段,Yii2 的 ActiveRecord class 不会将字段添加到生成的模型中。

我可以通过在位置 table 中创建一个名为 "Distance" 的列来解决这个问题,但我希望有一种方法可以在不更改数据库的情况下执行此操作。

我的最终目的是让模型 return 一个 Location 对象数组,每个 Location 对象都有一个 "Distance" 属性。然后,控制器将使用类似于以下的代码生成 json 提要:

    public function actionGetStudiosByLatLong( $regionName = '', $latitude='', $longitude='') {
        \Yii::$app->response->format = 'json';

        return Location::getByRegionAndLatLong( $regionName, $latitude, $longitude );
    }

您只需将此属性添加到您的 class :

class Location extends \yii\db\ActiveRecord
{

    public function attributes()
    {
        // add distance attribute (will work for json output)
        return array_merge(parent::attributes(), ['Distance']);
    }

    // ...
}

详细了解 Selecting extra fields

问完这个问题后不久,我发现了一个略显不雅的答案。

我不得不重写位置模型中的填充记录和 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);
    }

添加这些后,距离 属性 开始出现在模型中。