Laravel/PHP — расширенные полиморфные отношения

В моем веб-приложении пользователи могут загружать documents или emails в channels.

Кроме того, канал может иметь document_tags и email_tags, которые автоматически должны наследовать загруженные документы/электронные письма все.

Кроме того, document_tags и email_tagsбуду имеют разные описания: tag_descriptions. Так, например, если у нас есть документ, загруженный на канал с тегами: animals (id = 1) и pets (id = 2)

  1. Document #55 загружается в Channel #8.
  2. Document #55 автоматически наследует теги, имеющие document_tags.channel_id = 55 (доступ к этому можно получить с помощью следующего отношения: $channel->documenttags). В данном случае animals и pets.
  3. Теперь пользователь должен иметь возможность установить уникальное описание для тегов 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).

Это сложная проблема, которую невозможно решить напрямую с помощью красноречия Laravel. Вы можете найти вдохновение в этом посте laravel.io/forum/04-03-2014-имеетмногочерез-со-многими-ко-многим и особенно в предложении Maxeee09 под названием HasManyThroughBelongsTo расширение отношения модели.

Dimitri Mostrey 26.04.2019 10:11

На первый взгляд, сводная таблица для хранения всех полиморфных отношений была бы лучше.

Jed Lynch 02.05.2019 19:36

Могут ли document_tags и email_tags канала меняться со временем и каковы последствия для существующих документов? На этот вопрос следует ответить как для новых тегов, которые добавляются, так и для тегов, которые удаляются.

Namoshek 03.05.2019 11:15
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
4
3
2 105
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я не уверен, что именно вы пытаетесь сделать, но таблица с двумя полиморфными отношениями не имеет смысла. Таблица, которая включает полиморфную связь, является сводной таблицей. Хотя я понимаю, что вам нужны уникальные описания для каждого тега и типа отношения, сводная таблица должна иметь только два столбца внешнего ключа, один из которых относится к таблице и относится к ней. Есть и другой способ — использовать полиморфную связь в качестве ограничения сводной таблицы. Для начала сводную таблицу в полиморфной связи следует переименовать в «тегируемые». Вам не нужны таблицы «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. Однако, когда я создаю описание тега, как мне это сделать?

oliverbj 03.05.2019 09:55

Итак, я немного попробовал, и это, кажется, работает: $document>tags()->attach($tag, ['description' => "Custom Tag Description"]); Это правильный способ сделать это?

oliverbj 03.05.2019 11:15

Я добавил к ответу, чтобы показать, как опубликовать новый тег. Прикрепить будет работать, но сохранить, я думаю, чище. Одна вещь с сохранением заключается в том, что вам нужны временные метки в вашей схеме базы данных.

Jed Lynch 03.05.2019 18:03

Спасибо вам большое за это. Я потратил недели, пытаясь понять это, и ваше решение работает отлично!!!

oliverbj 04.05.2019 10:28

Другие вопросы по теме