提问者:小点点

使用关系表查询远程关系


我正在使用Laravel5.7和MySQL。我有一个services表和一个allowed_locations表,其中包含每个城市中可用的服务。可以在1个或多个城市提供1项服务。每个城市属于一个州,每个州属于一个国家。我想返回服务、城市、州和国家,但我不确定如何在每个模型中建立关系。我可以检索城市、州和国家吗?

城市

id
state_id

国家

id
country_id
name

国家

id
name

服务

id
name

允许的位置

city_id (id from cities table)
service_id (id from Services table)

国家id,名称1,美国

国家

id, country_id, name
3, 1, California
4, 1, Washington
5, 1, Oregon

城市

id, state_id, name
1, 3, San Diego  
2, 4, Seattle
3, 5, Portland

服务

id, name 
1, Tax Services
2, Legal Services

允许的位置

city_id, service_id
1, 1
2, 1
3, 2

services.php模型

public function locations () {
    return $this->belongsToMany('App\cities', 'services_by_location', 'service_id','city_id');
}

cities.php模型

public $with = ['state'];

 public function state() {
    return $this->hasOne(states::class, 'id', 'state_id');
}

states.php模型

public $with = ['country'];

public function country() {
    return $this->hasOne(Countries::class, 'id', 'country_id');
}

countries.php模型

//

AllowedLocations.php模型

//

控制器

$data = Services::with(['locations'])->get();
return response()->json($data, 200);

当前我返回的是这样的响应

{
    {
        id: 1,
        name: Tax Services
        locations: 
        {
            {
                city_id: 1,
                city: San Diego,
                state: {
                    name: California
                }
                country: {
                    name: USA,
                }
            },
            {
                city_id: 2,
                city: Seattle,
                state: {
                    name: Washington
                }
                country: {
                    name: USA,
                }
            },
        }
    },
    {
        id: 2,
        name: Legal Services
        locations: 
        {
            {
                city_id: 3,
                city: Portland,
                state: {
                    name: Oregon
                }
                country: {
                    name: USA,
                }
            },
        }   
    }
}

与嵌套的statecountry不同,我希望在locations嵌套数组中返回城市、州和国家名称。这可能吗?

{
    {
        id: 1,
        name: Tax Services
        locations: 
        {
            {
                city_id: 1,
                city: San Diego,
                state: California,
                country: USA
            },
            {
                city_id: 2,
                city: Seattle,
                state: Washington,
                country: USA
            },
        }
    },
    {
        id: 2,
        name: Legal Services
        locations: 
        {
            {
                city_id: 3,
                city: Portland,
                state: Oregon,
                country: USA
            },
        }   
    }
}

共2个答案

匿名用户

这是一个有趣的挑战,所以我在一个测试机器上设置了它,并解决了它。毫无疑问,还有其他方法可以做到这一点,但这是可行的,并将有州和国家可用在您想要的最高级别。

这些参数不会出现在dd()转储中,但它们可以在顶层直接访问,如果通过JSON转储,则会出现。

我认为最简单的帮助方法就是在City模型上设置访问器。然后,当从服务模型中调用locations()方法时,所有城市都具有您需要的信息。

城市模型:

protected $fillable=[
    'name',
    'state_id',
];

protected $appends = ['state_name', 'country_name'];

public function state() {
    return $this->hasOne(State::class, 'id', 'state_id');
}

public function getStateNameAttribute ()
{
    return $this->state->name;
}
public function getCountryNameAttribute ()
{
    return $this->state->country->name;
}

注意,保护了$appends=['state_name','country_name'];。这允许在转储到JSON时显示那些额外的字段。如果您只需要访问额外的字段(state_namecountry_name),则不需要这一行--您现在可以从数组的City level部分提取字段,因为我们在City模型中有访问器。

查询现在需要包括更急切的加载,这样那些访问器就不会试图从空值中获取名称。(您应该在访问器中添加一个空签)我建议像这样一次将其全部拉入:

$data = \App\Service::with(['locations' => function($query){
    $query->with(['state' => function($query){
        $query->with('country');
    }]);
}])->get();

$data上执行for-loop现在将为您提供城市级别所需的一切。例如:

foreach($data as $service){
   echo "Service id: ".$service->id.": ";
   echo $service->name."<br/>";

   echo $service->locations->first()->name.", ";
   echo $service->locations->first()->state_name." -- in the country: ";
   echo $service->locations->first()->country_name;

   echo "<br/><br/><br/>";
}

这将成功地产生:

Service id: 1: Tax Services
Annapolis, Maryland -- in the country: USA

Service id: 2: Legal Services
Potomac, Maryland -- in the country: USA

匿名用户

如果我没理解错的话,你们的关系应该是这样的:

service.php

public function cities()
{
    return $this->belongsToMany(City::class);
}

city.php

public function services()
{
    return $this->belongsToMany(Service::class);
}

public function state()
{
    return $this->belongsTo(State::class);
}

如果可能的话,我会保持默认值,并将数据透视表重命名为city_service

state.php

public function cities()
{
    return $this->hasMany('App\City');
}

public function country()
{
    return $this->belongsTo('App\Country');
}

country.php

public function states()
{
    return $this->hasMany('App\State');
}

为了获得所需的结果,您必须使用访问器

city.php

public function getState ()
{
    return $this->state->name;
}

public function getCountry ()
{
    $country = Country::findOrFail($this->state->country_id);
    return $country->name;
}

现在,这样做的代价有点高,但是hasManyThrough()在这里不起作用,因为您需要相反的关系。这是有人提出的,但没有被接受。有一个包裹,但不确定是否稳定。好像被抛弃了。

无论如何,您应该能够通过以下操作查询该结果并获得所需的结果:

$services = Service::with('cities')->get();