2014-09-07 1 views
8

Я пытаюсь получить доступ к дочерним объектам вложенных отношений, которые возвращают много результатов от объекта parent.Laravel: запрос и доступ к дочерним объектам в вложенных отношениях с where clauses

Скажем, у меня есть 4 модели: Страна - провинций - Города - Муниципалитеты

их отношения следующим образом:

Страна Модель

class Country extends Eloquent 
{ 
    protected $table = 'countries'; 

    public function provinces() 
    { 
     return $this->hasMany('Province'); 
    } 
} 

провинция Модель

class Province extends Eloquent 
{ 
    protected $table = 'provinces'; 

    public function cities() 
    { 
     return $this->hasMany('City'); 
    } 
    public function country() 
    { 
     return $this->belongsTo('Country'); 
    } 
} 

Город Модель

class City extends Eloquent 
{ 
    protected $table = 'cities'; 

    public function municipalities() 
    { 
     return $this->hasMany('Municipality'); 
    } 
    public function province() 
    { 
     return $this->belongsTo('Province'); 
    } 
} 

Самоуправление Модель

class Municipality extends Eloquent 
{ 
    protected $table = 'municipalities'; 

    public function cities() 
    { 
     return $this->belongsTo('City'); 
    } 
} 

Теперь то, что я пытаюсь сделать, это получить все муниципалитеты в данной стране, которые имеют население более 9000 и расположены в провинциях которые считаются Западом.

До сих пор у меня есть что-то вроде этого:

$country_id = 1; 

    $country = Country::whereHas('provinces', function($query){ 
     $query->where('location', 'West'); 
     $query->whereHas('cities', function($query){ 
      $query->whereHas('municipalities', function($query){ 
       $query->where('population', '>', 9000); 
      });    
     }); 
    })->find($country_id); 

Теперь я могу легко получить провинции с $country->provinces, но я не могу пойти глубже, чем это.

EDIT1: Фиксировать принадлежность к отношениям, как заметил Ярек.

EDIT2: В дополнение к ответу Ярека, я хотел поделиться тем, что я также нашел, но, возможно, более подходящий метод Ярека.

Вместо того, чтобы пытаться идти сверху вниз (Страна -> Самоуправление) я решил попробовать другой путь (муниципалитет -> Страна) Вот как это работает (и я проверял, также работает)

  $municipalities = Municipality::where('population', '>', 9000) 
      ->whereHas('city', function($q) use ($country_id){ 
       $q->whereHas('province', function($q) use ($country_id){ 
        $q->where('location', 'West'); 
        $q->whereHas('country', function($q) use ($country_id){ 
          $q->where('id', $country_id); 
        }); 
       }); 
      })->get(); 

Я понятия не имею, если это действительно правильный способ или если производительность будет принята, но, похоже, это трюк для меня, однако ответ Джарека выглядит более элегантным.

ответ

9

Ваш Municipality - City, вероятно, belongsTo, а не hasMany как в пасте.

В любом случае вы можете использовать hasManyThrough отношение доступа к далеко связанной коллекции:

Country - City 
Province - Municipality 

К сожалению, не существует никакого отношения к 3 уровня вложенности, так что вы не можете сделать это так же, как это.


Далее, ваш код с whereHas не ограничивает provinces в west и municipalities к 9000+, но только пределы countries тем, что связаны с ними.В вашем случае это означает, что результатом будет либо Country (если его отношения соответствуют этим требованиям), либо null в противном случае.

Так что, если вы действительно хотите, чтобы ограничить связанные коллекции, то вам нужен этот кусок:

$country = Country::with(['provinces' => function($query){ 
    $query->where('location', 'West'); 
}, 'provinces.cities.municipalities' => function ($query){ 
    $query->where('population', '>', 9000); 
}])->find($country_id); 

Это применение нетерпеливых ограничений загрузки, и что она делает это:

1. loads only West provinces for country with id 1 
2. loads all the cities in these provinces 
3. loads only 9k+ municipalities in these cities 

Поскольку вы» не интересующиеся городами, вы можете использовать hasManyThrough по вопросам Провинции:

// Province model 
public function municipalities() 
{ 
    return $this->hasManyThrough('Municipality', 'City'); 
} 

затем:

$country = Country::with(['provinces' => function($query){ 
    $query->where('location', 'West'); 
}, 'provinces.municipalities' => function ($query){ 
    $query->where('population', '>', 9000); 
}])->find($country_id); 

Однако в обоих случаях вы не можете получить доступ к муниципалитетам непосредственно, но только как это:

// 1 classic 
$country->provinces->first()->cities->first()->municipalities; 
// 2 hasManyThrough 
$country->provinces->first()->municipalities; 

Это, как говорится, если вы хотите работать с все эти муниципалитеты, вам нужен этот трюк:

$country = Country::with(['provinces' => function($query){ 
    $query->where('location', 'West'); 
}, 'provinces.municipalities' => function ($query) use (&$municipalities) { 

    // notice $municipalities is passed by reference to the closure 
    // and the $query is executed using ->get() 
    $municipalities = $query->where('population', '>', 9000)->get(); 
}])->find($country_id); 

Это вызовет дополнительный запрос, но теперь все муниципалитеты находятся в состоянии ingle, плоская коллекция, поэтому с ней очень легко работать. В противном случае вы, скорее всего, получите кучу циклов foreach.

+0

Спасибо! Это то, что я искал, но не мог понять. В то же время я также вычислил другой способ сделать это, и это должно было фильтровать снизу вверх. Я уточню свой вопрос, чтобы отразить то, что я нашел. – sholmes

+0

Я также хотел отметить, что интересно, что вы можете идти только на 3 уровня гнездования, и теперь, когда я знаю, я могу обойти это. – sholmes

+1

Да, вы можете сделать это наоборот, используя 'whereHas'. Это хороший способ, просто зависит от ваших потребностей. В любом случае, я не понимаю, что вы подразумеваете под 3 уровнями гнездования? –