Подтвердить что ты не робот

База данных "Один ко многим" с двумя полями внешнего ключа в Ларавеле

Я пытаюсь определить некоторую схему базы данных для использования рамки 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).

4b9b3361

Ответ 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
    }
}