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

Laravel中如何为Model添加字段并在Eloquent中按距离排序

解决Laravel中自定义访问器字段无法排序的问题

嘿,我来帮你搞定这个问题!核心问题其实很简单:你用访问器生成的pub_distance_to_user是模型加载到内存后才计算的属性,数据库里根本没这个字段,所以Eloquent的orderBy没法直接对它排序——毕竟orderBy是要生成SQL让数据库执行的,数据库哪懂你这个自定义字段呀~

一、实现按距离排序的两种方案

方案1:数据库层面计算并排序(推荐,大数据量更高效)

直接在SQL中用Haversine公式计算距离,让数据库直接返回排序后的结果,这种方式能利用数据库的优化(比如地理索引),数据量大时效率更高。以MySQL为例:

public function getPubs($userLat = 40.4169473, $userLong = -3.7035285) {
    $pubs = Pub::selectRaw("*, 
        ROUND(
            6371 * 
            ATAN2(
                SQRT(
                    POW(COS(RADIANS(latitude)) * SIN(RADIANS(? - longitude)), 2) +
                    POW(COS(RADIANS(?)) * SIN(RADIANS(latitude)) - SIN(RADIANS(?)) * COS(RADIANS(latitude)) * COS(RADIANS(? - longitude)), 2)
                ),
                SIN(RADIANS(?)) * SIN(RADIANS(latitude)) + COS(RADIANS(?)) * COS(RADIANS(latitude)) * COS(RADIANS(? - longitude))
            ), 2
        ) AS pub_distance_to_user")
        ->setBindings([$userLong, $userLat, $userLat, $userLong, $userLat, $userLat, $userLong])
        ->orderBy('pub_distance_to_user')
        ->get();

    return $pubs;
}

这样查询结果里会直接包含计算好的pub_distance_to_user字段,而且已经按距离排好序了。如果用户经纬度是动态的,直接把参数传进来就行。

方案2:PHP内存中排序(适合小数据量)

如果你的Pub数据量不大,也可以先拉取所有数据,再用Laravel集合的sortBy方法排序:

public function getPubs($userLat = 40.4169473, $userLong = -3.7035285) {
    // 把用户经纬度临时绑定到每个Pub实例上
    $pubs = Pub::all()->each(function ($pub) use ($userLat, $userLong) {
        $pub->user_lat = $userLat;
        $pub->user_long = $userLong;
    })->sortBy('pub_distance_to_user');

    // 转换成JSON/数组时记得重置键名
    return $pubs->values();
}

同时要修改你的pubDistance方法,让它能读取动态传入的用户经纬度:

public function pubDistance() {
    // 优先用临时绑定的用户经纬度,没有则用默认值
    $userLat = $this->user_lat ?? 40.4169473;
    $userLong = $this->user_long ?? -3.7035285;
    $pubLat = $this->latitude;
    $pubLong = $this->longitude;

    $earthRadius = 6371;
    $latFrom = deg2rad($userLat);
    $lonFrom = deg2rad($userLong);
    $latTo = deg2rad($pubLat);
    $lonTo = deg2rad($pubLong);

    $latDelta = $latTo - $latFrom;
    $lonDelta = $lonTo - $lonFrom;

    $a = pow(cos($latTo) * sin($lonDelta), 2) + pow(cos($latFrom) * sin($latTo) - sin($latFrom) * cos($latTo) * cos($lonDelta), 2);
    $b = sin($latFrom) * sin($latTo) + cos($latFrom) * cos($latTo) * cos($lonDelta);
    $angle = atan2(sqrt($a), $b);
    $distance = round($angle * $earthRadius, 2);

    return $distance;
}

二、访问器与修改器的正确用法

访问器(Accessor)

  • 核心作用:获取模型属性时自动处理数据,比如把原始值转换成友好格式,或者计算衍生属性(像你这里的距离)。
  • 注意点:访问器是模型实例加载到内存后才执行的,完全无法参与数据库层面的查询、筛选、排序——因为数据库根本不知道这个属性的存在。
  • 你的访问器写法是正确的,但如果依赖外部动态数据(比如用户经纬度),别硬编码默认值,最好通过临时属性或方法参数传递,就像上面的修改那样。

修改器(Mutator)

  • 核心作用:设置模型属性时自动处理数据,比如格式化用户输入、验证数据合法性。
  • 举个例子,确保经度纬度是合法的浮点数:
public function setLatitudeAttribute($value) {
    $this->attributes['latitude'] = is_numeric($value) ? floatval($value) : null;
}

public function setLongitudeAttribute($value) {
    $this->attributes['longitude'] = is_numeric($value) ? floatval($value) : null;
}

内容的提问来源于stack exchange,提问作者Ber Tsacianegu del Tepuy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 04:22:01