В моем веб-приложении пользователи могут загружать documents или emails в channels.
Кроме того, канал может иметь document_tags и email_tags, которые автоматически должны наследовать загруженные документы/электронные письма все.
Кроме того, document_tags и email_tagsбуду имеют разные описания: tag_descriptions. Так, например, если у нас есть документ, загруженный на канал с тегами: animals (id = 1) и pets (id = 2)
Document #55 загружается в Channel #8.Document #55 автоматически наследует теги, имеющие document_tags.channel_id = 55 (доступ к этому можно получить с помощью следующего отношения: $channel->documenttags). В данном случае animals и pets.animals и pets в tag_descriptions, например:tag_descriptions
id | taggable_type | taggable_id | typeable_type | typeable_id | description
1 | App\DocumentTag | 1 | App\Document | 55 | My unique description for animals.
2 | App\DocumentTag | 2 | App\Document | 55 | My unique description for pets.
Теперь в приведенном выше дизайне базы данных загруженный document #55 имеет связанные теги: animals и pets, но, кроме того, эти два тега имеют уникальное описание, уникальное для конкретного документ.
Если я загружу другой документ или электронное письмо (скажем, email #20), то я думаю, что это будет выглядеть так:
tag_descriptions:
id | taggable_type | taggable_id | typeable_type | typeable_id | description
1 | App\DocumentTag | 1 | App\Document | 55 | My unique description for animals.
2 | App\DocumentTag | 2 | App\Document | 55 | My unique description for pets.
3 | App\EmailTag | 1 | App\Email | 20 | Another unique description for animals.
4 | App\EmailTag | 2 | App\Email | 20 | Yet another unique description for pets.
Теперь у email #20 также есть теги animals и pets, но в этом случае пользователь может задать тегам уникальные описания.
Теперь мой вопрос:
Выполним ли вышеописанный дизайн и считается ли он лучшей практикой в Laravel/PHP? Я немного не уверен, как структурировать код, потому что у модели TagDescription внезапно появятся отношения два полиморфных (taggable и typeable), и я не могу найти ничего в документации, что это поддерживается.
Кроме того, я не уверен, смогу ли я использовать приведенный выше дизайн для доступа к уникальным описаниям через конкретный загруженный документ, например:
//In my Document.php model:
public function tagdescriptions()
{
return $this->morphMany(TagDescription::class, 'typeable');
}
Затем используйте его как: $document->tagdescriptions.
И последнее, но не менее важное: я немного не уверен, как сохранить уникальное описание тега для конкретного taggable_id/taggable_type и уникального электронного письма/документа. (typeable_id и typeable_type).
На первый взгляд, сводная таблица для хранения всех полиморфных отношений была бы лучше.
Могут ли document_tags и email_tags канала меняться со временем и каковы последствия для существующих документов? На этот вопрос следует ответить как для новых тегов, которые добавляются, так и для тегов, которые удаляются.






Я не уверен, что именно вы пытаетесь сделать, но таблица с двумя полиморфными отношениями не имеет смысла. Таблица, которая включает полиморфную связь, является сводной таблицей. Хотя я понимаю, что вам нужны уникальные описания для каждого тега и типа отношения, сводная таблица должна иметь только два столбца внешнего ключа, один из которых относится к таблице и относится к ней. Есть и другой способ — использовать полиморфную связь в качестве ограничения сводной таблицы. Для начала сводную таблицу в полиморфной связи следует переименовать в «тегируемые». Вам не нужны таблицы «email_tag» и «document_tag», вы можете использовать таблицу под названием «tags». Чтобы получить уникальное описание для каждого тега, вы можете добавить описание в таблицу «таблицы».
Файл миграции выглядит так....
public function up()
{
Schema::create('taggables', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('tag_id');
$table->unsignedInteger('taggable_id');
$table->string('taggable_type');
$table->string('description');
$table->timestamps();
});
Schema::create('tags', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
});
Schema::create('documents', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
});
Schema::create('emails', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
});
}
Вот что вам нужно сделать в своих моделях электронной почты и документов.
class Email extends Model
{
/**
* Get all of the tags.
*/
public function tags()
{
return $this->morphToMany(Tag::class, 'taggable')->withPivot('description');
}
}
class Document extends Model
{
/**
* Get all of the tag descriptions.
*/
public function tags()
{
return $this->morphToMany(Tag::class, 'taggable')->withPivot('description');
}
}
Функция withPivot вернет значение столбца, указанного в запросе.
Вот что вам нужно сделать в вашей модели тегов.
class Tag extends Model
{
/**
* Get all of the tag descriptions.
*/
public function documents()
{
return $this->morphByMany(Document::class, 'taggable');
}
/**
* Get all of the tag descriptions.
*/
public function emails()
{
return $this->morphByMany(Email::class, 'taggable');
}
}
Вам не нужна модель таблицы «Tagables».
Вот что происходит. Когда ты возишься...
$email = App\Email::find(1)->tags;
Этот запрос будет выполняться...
select `tags`.*,
`taggables`.`taggable_id` as `pivot_taggable_id`,
`taggables`.`tag_id` as `pivot_tag_id`,
`taggables`.`taggable_type` as `pivot_taggable_type`,
`taggables`.`description` as `pivot_description`
from `tags`
inner join `taggables` on `tags`.`id` = `taggables`.`tag_id`
where `taggables`.`taggable_id` = 1
and `taggables`.`taggable_type` = 'App\Email'
Вы видите, что ограничение полиморфных отношений может запрашивать уникальное описание. Подводя итог, вы не можете поместить более двух отношений внешнего ключа в сводную таблицу, но вы можете добавить ограничение в свою сводную таблицу.
Я думаю, что будет чище добавить этот файл AppServiceProvider.php...
public function boot()
{
Relation::morphMap([
'email' => Email::class,
'document' => Document::class
]);
}
https://laravel.com/docs/5.8/eloquent-relationships#polymorphic-relationships
Это позволит вам сохранить значение «электронная почта» или «документ» в качестве вашего помечаемого типа.
Для размещения на столах я думаю, что это выглядит самым чистым...
$tag = Tag::firstOrNew(["name"=>"#tag"]);
$tag->name = "#tag";
$document = new Document();
$document->name = "new doc";
$document->save();
$document->tags()->save($tag,["description"=>"some description"]);
Вы, конечно, можете использовать attach(). Save() использует attach(), как показано здесь... https://github.com/laravel/framework/blob/5.8/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php#L871
Надеюсь это поможет.
Большое спасибо Джед за подробный ответ! У меня есть два вопроса. При создании тега это должно быть довольно стандартно, верно? Просто сделайте create. Однако, когда я создаю описание тега, как мне это сделать?
Итак, я немного попробовал, и это, кажется, работает: $document>tags()->attach($tag, ['description' => "Custom Tag Description"]); Это правильный способ сделать это?
Я добавил к ответу, чтобы показать, как опубликовать новый тег. Прикрепить будет работать, но сохранить, я думаю, чище. Одна вещь с сохранением заключается в том, что вам нужны временные метки в вашей схеме базы данных.
Спасибо вам большое за это. Я потратил недели, пытаясь понять это, и ваше решение работает отлично!!!
Это сложная проблема, которую невозможно решить напрямую с помощью красноречия Laravel. Вы можете найти вдохновение в этом посте laravel.io/forum/04-03-2014-имеетмногочерез-со-многими-ко-многим и особенно в предложении Maxeee09 под названием
HasManyThroughBelongsToрасширение отношения модели.