Я пытаюсь создать двунаправленную связь ManyToMany в моей модели тегов, но столкнулся с этой «проблемой».
Моя модель выглядит так:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Tag extends Model
{
protected $table = 'tags';
public $timestamps = false;
public function tags()
{
return $this->belongsToMany(Tag::class, 'tag_tag', 'tag_one_id', 'tag_two_id');
}
}
Итак, на данный момент, скажем, у меня есть Tag1 и Tag2 в моей таблице тегов, а затем я свяжу Tag2 с Tag1. Теперь моя сводная таблица будет выглядеть так:
+----+------------+------------+
| id | tag_one_id | tag_two_id |
+----+------------+------------+
| 1 | 1 | 2 |
+----+------------+------------+
Когда я пробую этот код:
$tag = Tag::find(1);
$tag->tags()->get();
Я получаю экземпляр Tag2, и это правильно.
Но когда я пытаюсь запустить этот код:
$tag = Tag::find(2);
$tag->tags()->get();
Я хотел бы получить экземпляр Tag1, но не могу.
Можно ли сделать это с Eloquent по умолчанию в Laravel, используя только один метод в модели?






Это возможно. Вам нужно будет передать параметр методу tags() и изменить поля первичного/внешнего ключа в отношении, используя этот параметр. Это может быть полной болью, с которой придется иметь дело, и почти наверняка было бы меньше головной боли, если бы просто сделать второй метод отношений. Это просто выглядело бы так:
public function tags($tag1 = 'tag_one_id', $tag2 = 'tag_two_id')
{
return $this->belongsToMany(Tag::class, 'tag_tag', $tag1, $tag2);
}
и тогда вам просто нужно будет изменить значения, когда вам нужно Tag::find(2)->tags('tag_two_id', 'tag_one_id')
Этот могу должен быть загружен, как описано здесь: https://laracasts.com/discuss/channels/general-discussion/eager-load-with-parameters
Ваш вариант использования может быть намного сложнее, чем предлагает ваш пост, что может сделать это более разумным. В связи с этим я бы рассмотрел несколько других вариантов.
Я не уверен, что ты имеешь в виду.
Я надеялся на что-то подобное, но таким образом, чтобы я мог вернуть отношения из метода. stackoverflow.com/a/46316876/5280969
А, значит, вы не хотите, чтобы построитель запросов выполнял SQL. Так же, как метод доступа в этом примере, но вместо этого он возвращает отношение?
Да, точно, можно?
Не уверен. Проблема в том, что вы просто получаете набор обоих тегов на одном уровне, без какого-либо реального способа заметить, что они связаны. Это было бы то же самое, что простое получение обоих тегов Tag::whereIn('id', $ids)->get() вернет одно и то же, если вы передадите идентификаторы тегов, которые вам нужны. Я пытаюсь выяснить вариант использования для этого.
Тогда я скажу вам, для чего я пытаюсь его использовать. У меня есть объект тега с именем, но мне также нужно хранить переводы этого тега. Допустим, у меня есть тег «Автомобиль», у меня также есть «Автомобиль» или «Voiture» на разных языках. Но это тоже разные теги. Поэтому, когда я запрашиваю тег «Автомобиль», мне также нужно получить связанные теги («Автомобиль» и «Voiture»). Когда я запрашиваю «Voiture», мне нужно получить связанные («Автомобиль» и «Автомобиль»).
Как насчет нетерпеливой загрузки тегов и тегов этого тега. Нравится Tag::with('tags', 'tags.tags'). Это должно позволить вам получить все теги, связанные с текущим тегом, и теги, связанные с ним. Таким образом, если у вас есть связь между автомобилем и автомобилем, а также между автомобилем и Voiture, вы все равно можете получить доступ к Voiture из автомобиля.
Я пытался, но id не сработал, я думаю, что сделаю пакет, который обрабатывает такие отношения.
Почему это не работает?
Это не работает, потому что в отношениях, которые вы добавили в модели Tag, определено, что они работают в одну сторону. Но не наоборот.
Это сработало бы, если бы мы могли определить два метода с именем tags(), как показано ниже:
public function tags()
{
return $this->belongsToMany(Tag::class, 'tag_tag', 'tag_one_id', 'tag_two_id');
}
//and
public function tags()
{
return $this->belongsToMany(Tag::class, 'tag_tag', 'tag_two_id', 'tag_one_id');
}
К сожалению, это невозможно.
Итак, какое может быть возможное решение
Одним из возможных решений может быть не трогать отношения. Вместо этого, если вам каким-то образом удастся вставить две связи для этих отношений, тогда это сработает. Например:
+----+------------+------------+
| id | tag_one_id | tag_two_id |
+----+------------+------------+
| 1 | 1 | 2 |
+----+------------+------------+
| 1 | 2 | 1 |
+----+------------+------------+
Это решение приходит мне в голову прямо сейчас. Может быть и лучшее решение.
Я нашел решение, и я решил это так.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Tag extends Model
{
/**
* @inheritdoc
*
* @var string
*/
protected $table = 'tags';
/**
* @inheritdoc
*
* @var bool
*/
public $timestamps = false;
/*
|--------------------------------------------------------------------------
| RELATIONS
|--------------------------------------------------------------------------
*/
/**
* Every tag can contain many related tags (TagOne has many TagTwo).
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
protected function tagsOneTwo()
{
return $this->belongsToMany(Tag::class, 'tag_tag', 'tag_one_id', 'tag_two_id');
}
/**
* Every tag can contain many related tags (TagTwo has many TagOne).
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
protected function tagsTwoOne()
{
return $this->belongsToMany(Tag::class, 'tag_tag', 'tag_two_id', 'tag_one_id');
}
/**
* This method returns a collection with all the tags related with this tag.
* It is not a real relation, but emulates it.
*
* @return \Illuminate\Database\Eloquent\Collection
*/
public function tags()
{
return $this->tagsOneTwo()->get()->merge($this->tagsTwoOne()->get())->unique('id');
}
/*
|--------------------------------------------------------------------------
| FUNCTIONS
|--------------------------------------------------------------------------
*/
/**
* Function to relate two tags together.
*
* @param Tag $tag
* @return void;
*/
public function attach(Tag $tag)
{
if ($this->tags()->contains('id', $tag->getKey())) {
return;
}
$this->tagsOneTwo()->attach($tag->getKey());
}
/**
* Function to un-relate two tags.
*
* @param Tag $tag
* @return void;
*/
public function detach(Tag $tag)
{
if ($this->tags()->contains('id', $tag->getKey())) {
// Detach the relationship in both ways.
$this->tagsOneTwo()->detach($tag->getKey());
$this->tagsTwoOne()->detach($tag->getKey());
}
}
/*
|--------------------------------------------------------------------------
| ACCESORS
|--------------------------------------------------------------------------
*/
/**
* Let access the related tags like if it was preloaded ($tag->tags).
*
* @return mixed
*/
public function getTagsAttribute()
{
if (! array_key_exists('tags', $this->relations)) {
$this->setRelation('tags', $this->tags());
};
return $this->getRelation('tags');
}
}
На самом деле, я уже думал об этом, но проблема в том, что мне нужно объединить оба отношения (tag_one_id -> tag_two_id и tag_two_id -> tag_one_id).