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
相关产品推荐
相关产品推荐

