Как определить отношение hasMany к нескольким классам?

#php #laravel #eloquent

#php #laravel #красноречивый

Вопрос:

Допустим, в Laravel я хочу иметь миссию с разными MissionObjectives, каждая MissionObjective имеет свою собственную модель. У меня есть Mission, CheckboxObjective, Radioobjective и SpinnerObjective. Мне нужно связать цели с миссией, но я не хочу использовать разные методы для каждой цели, я хочу, чтобы они все были в одном методе. Я пробовал полиморфные отношения, но, похоже, они просто не работают для этого. Мне понадобилось бы что-то вроде отношения «Многие к одному», которого не существует.

Это то, что я хочу:

Класс миссии:

 public function objectives() {
    return *All objectives*;
}
  

Объективные классы:

 public function mission() {
    return $this->hasOne('AppModelsMission');
}
  

Комментарии:

1. Если каждая из ваших таблиц целей имеет одинаковую структуру таблицы (возможно, это не так, но если это так), вы могли бы создать таблицу для всех целей, а затем создать новый столбец, в котором указывается, к какому типу относится эта цель. Таким образом, вы могли бы просто использовать hasMany для одного objective model . Опять же, это может быть неприменимо к вашему вопросу в зависимости от ваших табличных структур, но я подумал, что я бы выбросил его в качестве альтернативы на тот случай, если это может быть.

2. Спасибо @SawyerClever это, вероятно, было бы хорошим решением во многих случаях, но каждая из целей имеет очень разные столбцы. RadioObjective даже имеет RadioObjectiveOption в качестве отношения hasMany. Если вы знаете способ интегрировать это, пожалуйста, дайте мне знать!

3. Я удивлен, что полиморфные отношения не будут работать для этого, поскольку это основной момент такого типа отношений; адаптация одной модели ко многим другим моделям с помощью того же метода отношений. А many-to-one это просто обратная величина a one-to-many . Можете ли вы предоставить базовую схему базы данных таблиц, которые вы пытаетесь подключить? Я понимаю концепцию, к которой вы стремитесь, но визуализация (или, по крайней мере, аренда связанных столбцов в каждой из таблиц) во многом помогла бы решить эту проблему.

4. Да, @TimLewis Я отредактирую свой пост со схемой таблиц.

5. Вам понадобится objectives метод в вашем Mission классе, который возвращает что-то вроде этого $this->hasMany([MainObjective::class, SecondaryObjective::class]); , чтобы вы получили коллекцию, содержащую оба типа (или больше) — к сожалению, этого не существует. Я хотел бы увидеть решение для этого!

Ответ №1:

Я нашел 2 способа сделать это. Оба выполняют свою работу, ни один из них не идеален.

Обзор

  1. Миссии, которые имеют несколько *Objective

    Mission (1: n) *Objective один ко многим

    • Mission hasMany *Objective
    • Objective (Аннотация) принадлежит Mission
    • *Objective расширяет Objective

  1. Миссий, которые имеют несколько, Objective которые, в свою очередь, каждая может быть своего рода *Objective (промежуточный уровень)

    Mission (1: n) Objective один ко многим

    Objective (1: 1) *Objective один к одному (полиморфный)

    • Mission hasMany Objective
    • Objective MorphTo *Objective
    • *Objective расширяет Objective

Вы бы использовали 2 только в том случае, если вам нужен уровень itermediate, если вы хотите наследовать общие атрибуты для *Objective from Objective — что полностью имеет смысл, поскольку все они, по крайней мере, являются общими mission_id .

Вы не сможете получить унаследованные атрибуты *Objective из коробки, потому что модель для любого *Objective привязана к определенной таблице, следовательно, она не может получить атрибуты унаследованного Objective класса. Хотя для этого есть модуль: родительский

редактировать: Неважно, parental поддерживает только STI (наследование одной таблицы), что означает, что у нас может быть несколько моделей, ссылающихся на одну и ту же таблицу.


Решение 1

Таблицы

 missions
  id - int

*_objectives (e.g. main_objectives)
  id - int
  mission_id - int
  

Модели

 class Mission extends Model
{
    const OBJECTIVE_TYPES = [
        MainObjective::class,
        // ...
    ];
    
    public function __get($name)
    {
        switch ($name) {
            case 'objectives':
                $objectives = new Collection();
                foreach (static::OBJECTIVE_TYPES as $objectiveType) {
                    $objectives = $objectives->concat($this->hasMany($objectiveType)->get()->all());
                }
                return $objectives;

            default:
                return parent::__get($name);
        }
    }
}

abstract class Objective extends Model
{
    public function mission() {
        return $this->belongsTo(Mission::class, 'mission_id', 'id');
    }
}

class MainObjective extends Objective
{
}
  

Пример

 $mission = Mission::find(1);
foreach ($mission->objectives as $objective) {
    echo 'Class: ' . get_class($objective) . PHP_EOL;
}
  

Вывод (В случае, если у нас есть 2 MainObjectives и 1 SecondaryObjective)

 Class: AppModelsMainObjective
Class: AppModelsMainObjective
Class: AppModelsSecondaryObjective
  

Решение 2

Таблицы

 missions
  id - int

objectives
  id - int
  mission_id - int
  objectiveable_type - string
  objectiveable_id - int

*_objectives (e.g.: main_objectives)
  id - int
  

objectiveable_id ссылается на id таблицу, используемую моделью в objectiveable_type .
objectiveable_type должно быть полное определение. Пример: AppModelsMainObjective

Модели

 class Mission extends Model
{
    public function __get($name)
    {
        if ($name === 'objectives') {
            return new Collection(array_map(function ($objective) {
                return $objective->objectiveable;
            }, $this->hasMany(Objective::class)->get()->all()));
        }

        return parent::__get($name);
    }
}

class Objective extends Model
{
    public function objectiveable()
    {
        return $this->morphTo();
    }

    protected function _mission() {
        return $this->belongsTo(Mission::class, 'mission_id', 'id');
    }

    public function __get($name)
    {
        switch ($name) {
            case 'mission':
                return $this->morphMany(Objective::class, 'objectiveable')->get()->first()->_mission;

            default:
                return parent::__get($name);
        }
    }
}

class MainObjective extends Objective
{
}
  

Пример

 $mission = Mission::find(1);
foreach ($mission->objectives as $objective) {
    echo 'Class: ' . get_class($objective) . PHP_EOL;
}
  

Вывод (В случае, если у нас есть 2 MainObjectives и 1 SecondaryObjective)

 Class: AppModelsMainObjective
Class: AppModelsMainObjective
Class: AppModelsSecondaryObjective
  

Практически то же самое, что и в первом решении. Большая разница здесь заключается в атрибутах. вы не сможете получить доступ к атрибутам Objective of MainObjective без дальнейшего расширения этого.


@Laravel/Красноречивый

ПОЖАЛУЙСТА, сделайте это проще — у меня болит голова.

Комментарии:

1. Спасибо за ваш очень подробный ответ! Я скоро попробую это и свяжусь с вами!

2. Я использовал решение 1, оно отлично работает. Большое вам спасибо! Это действительно сэкономило мне много времени.