База данных "Один ко многим" с двумя полями внешнего ключа в Ларавеле
Я пытаюсь определить некоторую схему базы данных для использования рамки laravel. Я хочу смоделировать футбольный матч. Первый шаг, который я хотел сделать, - это определить диаграмму Entity Relationship, но я обнаружил, что это (что, по моему мнению, было бы довольно тривиальным) было бы запутанным в некоторых аспектах.
Во-первых, очевидный подход заключается в том, что Матч связан с двумя командами, а команда связана с любым количеством совпадений. Таким образом, у нас было бы отношение "от многих до многих".
Но реализация отношения "многие-многие" состоит в том, чтобы иметь две таблицы и промежуточную таблицу для связывания обоих объектов. Я думаю, что это будет слишком много, когда я знаю, что в матче всегда будет две Команды, и просто иметь два столбца (local_id и visitant_id) с внешними ключами в таблице команд будет достаточно. Кроме того, я хочу иметь возможность:
Match::find(1)->local() or Match::find(1)->visitant();
Итак, размышляя над этим, я реализую отношение "Один к многим", но с этим у меня другая проблема. Чтобы получить все совпадения, которые сыграла Команда, я хотел бы сделать:
Team::find(1)->matches();
Но я не могу этого сделать, потому что я могу указать только один ключевой столбец при определении метода match() в красноречивом (по умолчанию это будет team_id, но он должен быть visitant_id и local_id).
Ответы
Ответ 1
После некоторого дополнительного копирования в исходный код, который я нашел, есть способ фактически сохранить мою схему базы данных так, как есть, и достичь того, что я хочу (по крайней мере, в Laravel 4). Я опубликовал свою проблему в github, и Тейлор Отуэлл (создатель фреймворка) дал мне правильный ответ: https://github.com/laravel/framework/issues/1272
Цитируя его, это должно быть так просто:
class Team extends Eloquent {
public function allMatches()
{
return $this->hasMany('Match', 'visitant_id')->orWhere('local_id', $this->id);
}
}
И затем...
$team = Team::find(2);
$matches = $team->allMatches;
Обновление: Ссылка github не работает, потому что laravel больше не принимает отчеты об ошибках: http://laravel-news.com/2014/09/laravel-removes-github-issues/
Ответ 2
Это одна из тех известных проблем с дизайном базы данных. Дружеские отношения, например, страдают от этой же трудности. Поскольку вы используете Eloquent, я предлагаю вам придерживаться подхода многих и многих и иметь дополнительный логический столбец local
на вашей промежуточной таблице
class Match extends Eloquent {
public $includes = array('team'); // Always eager load teams
public function teams() {
return $this->has_many_and_belongs_to('team')->with('local');
}
public function get_local() {
foreach ($this->teams as $team) {
if ($team->pivot->local) return $team;
}
}
public function get_visitant() {
foreach ($this->teams as $team) {
if (!$team->pivot->local) return $team;
}
}
}
class Team extends Eloquent {
public function matches() {
return $this->has_many_and_belongs_to('match')->with('local');
}
// I'm doing separate queries here because a team may have
// hundreds of matches and it not worth looping through
// all of them to retrieve the local ones
public function matches_as_local() {
return $this->has_many_and_belongs_to('match')->with('local')
->where('pivot_local', '=', 1);
}
public function matches_as_visitant() {
return $this->has_many_and_belongs_to('match')->with('local')
->where('pivot_local', '=', 0);
}
}
Obs:
Метод has_many_and_belongs_to(...)->with('field')
не имеет ничего общего с нетерпением загрузки. Он сообщает Eloquent загрузить столбец промежуточной таблицы field
и поместить его в свод..
Применение:
$match = Match::find(1);
$match->local; // returns local team
$match->visitant; // returns visitant team
$team = Team::find(1);
$team->matches; // returns all matches
$team->matches_as_local; // ...
$team->matches_as_visitant; // ...
foreach ($team->matches as $match) {
if ($match->pivot->local) {
// put nice local icon here
} else {
// put nice visitant icon here
}
}