#php #laravel #eloquent
#php #laravel #красноречивый
Вопрос:
В моем веб-приложении пользователи могут загружать 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 | AppDocumentTag | 1 | AppDocument | 55 | My unique description for animals.
2 | AppDocumentTag | 2 | AppDocument | 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 | AppDocumentTag | 1 | AppDocument | 55 | My unique description for animals.
2 | AppDocumentTag | 2 | AppDocument | 55 | My unique description for pets.
3 | AppEmailTag | 1 | AppEmail | 20 | Another unique description for animals.
4 | AppEmailTag | 2 | AppEmail | 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).
Комментарии:
1. Это сложная проблема, не решаемая напрямую с помощью Laravel’s eloquent. Возможно, вы найдете вдохновение в этом посте laravel.io/forum/03-04-2014-hasmanythrough-with-many-to-many и особенно предложение Maxeee09 под названием
HasManyThroughBelongsTo
расширение модельных отношений.2. На первый взгляд, сводная таблица для хранения всех полиморфных отношений была бы лучше.
3. Могут ли
document_tags
иemail_tags
канала меняться со временем и каковы последствия для существующих документов? На этот вопрос следует ответить как для новых тегов, которые добавляются, так и для тегов, которые удаляются.
Ответ №1:
Я не совсем уверен, что вы пытаетесь сделать, но таблица с двумя полиморфными отношениями не имеет смысла. Таблица, которая поддерживает полиморфные отношения, является сводной таблицей. Хотя я понимаю, что вам нужны уникальные описания для каждого тега и типа отношений, сводная таблица должна иметь только два столбца внешнего ключа, один из которых относится к таблице и связан с ней.
Существует другой способ, который заключается в использовании полиморфных отношений в качестве ограничения для сводной таблицы. Для начала сводную таблицу в полиморфных отношениях следует переименовать в «taggables». Вам не нужны таблицы «email_tag» и «document_tag», вы можете использовать таблицу под названием «теги». Чтобы получить уникальное описание для каждого тега, вы можете добавить описание в таблицу «tabblables».
Файл миграции выглядит следующим образом….
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');
}
}
Вам не нужна табличная модель «Taggables».
Вот что происходит. Когда вы возитесь…
$email = AppEmail::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` = 'AppEmail'
То, что вы видите, является ограничением полиморфных отношений, может запрашивать уникальное описание. Подводя итог, вы не можете поместить более двух отношений внешнего ключа в сводную таблицу, но вы можете добавить ограничение в свою сводную таблицу.
Я думаю, что добавление этого вашего AppServiceProvider.php файл…
public function boot()
{
Relation::morphMap([
'email' => Email::class,
'document' => Document::class
]);
}
https://laravel.com/docs/5.8/eloquent-relationships#polymorphic-relationships
Это позволит вам сохранить значение «email» или «document» в качестве вашего тегируемого типа.
Для публикации в таблицах, я думаю, это выглядит самым чистым…
$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
Надеюсь, это поможет.
Комментарии:
1. Большое спасибо, Джед, за подробный ответ! Однако у меня есть два вопроса. При создании тега — это должно быть довольно стандартным, верно? Просто сделайте
create
. Однако, когда я создаю описание тега, как я это сделаю?2. Итак, я немного попробовал, и это, кажется, работает:
$document>tags()->attach($tag, ['description' => "Custom Tag Description"]);
Это правильный способ сделать это?3. Я добавил к ответу, чтобы показать, как опубликовать новый тег. Прикрепить будет работать, но сохранить, я думаю, чище. Одна вещь с сохранением заключается в том, что вам нужны временные метки в вашей схеме базы данных.
4. Большое вам спасибо за это. Я потратил недели, пытаясь разобраться в этом, и ваше решение работает отлично!!!