Laravel Custom морф отношения - PullRequest
0 голосов
/ 07 мая 2018

Я бы хотел использовать Laravel Eloquent Polymorphic Relationships, однако, похоже, он не настроен для работы со структурой моей таблицы.

По сути, у меня есть таблица игровых данных, которая включает в себя все различные типы игровых данных (нация, лига, команда, игрок и т. Д.). Для каждого типа у меня есть несколько таблиц с информацией, разделенной game_id. Таким образом, в таблице игровых данных будет одна строка для нации "Англия", которая имеет 7 соответствующих строк в таблице наций с данными из 7 различных game_ids.

Я бы хотел иметь возможность выбирать некоторые строки из таблицы игровых данных и соответствующие им строки из соответствующей таблицы в зависимости от ее типа.

Это достаточно легко сделать в отношениях один к одному, но кажется невозможным в отношениях один ко многим.

Вот таблица игровых данных.

CREATE TABLE `gamedata` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `data_type` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL,
  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `data_id` (`data_id`,`type`),
  FULLTEXT KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

А затем множество таких таблиц (для удобства чтения убрано множество столбцов):

CREATE TABLE `nations` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `game_id` tinyint(3) unsigned NOT NULL,
  `gamedata_id` int(10) unsigned NOT NULL,
  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `short_name` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  /* more specific columns removed for ease of reading */
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE `leagues` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `game_id` tinyint(3) unsigned NOT NULL,
  `gamedata_id` int(10) unsigned NOT NULL,
  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `short_name` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  /* more specific columns removed for ease of reading */
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE `teams` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `game_id` tinyint(3) unsigned NOT NULL,
  `gamedata_id` int(10) unsigned NOT NULL,
  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `short_name` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  /* more specific columns removed for ease of reading */
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

Так что некоторые строки в таблице игровых данных могут выглядеть следующим образом:

(144, 'nation', 'Some Nation'),
(145, 'nation', 'Another Nation'),
(146, 'league', 'Some League'),
(147, 'league', 'Another League'),
(148, 'team', 'Some Team'),
(149, 'team', 'Another Team');

Так что я должен иметь возможность сделать полиморфные отношения из столбца «data_type» и «data_id», чтобы получить соответствующую строку из соответствующей таблицы.

Но ни одно из встроенных отношений (morphTo, morphMany, morphedByMany) и т. Д., Кажется, не в состоянии справиться с этим.

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

// This would work fine if I only wanted one related model. "data_type" being the class and "id" corresponding to "gamedata_id" on relevent table.
$this->morphTo('data');

// These require me to be explicit about the class instantiating rather than using from the "data_type" column
$this->morphMany(???, 'data');
$this->morphToMany(???, 'data');
$this->morphedByMany(???, 'data');

Есть ли способ сделать это, используя существующие отношения Laravel? Или есть простой способ создать собственный класс отношений на основе morphTo, который бы соответствовал моим потребностям?

1 Ответ

0 голосов
/ 08 мая 2018

Я думаю, что придумал собственное решение, расширив класс morphTo.

namespace App;

use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\MorphTo;

/**
 * @mixin \Illuminate\Database\Eloquent\Builder
 */
class MorphHasMany extends MorphTo
{
    /**
     * Get the results of the relationship.
     *
     * @return mixed
     */
    public function getResults()
    {
        return $this->ownerKey ? $this->query->get() : null;
    }

    /**
     * Match the results for a given type to their parents.
     *
     * @param  string  $type
     * @param  \Illuminate\Database\Eloquent\Collection  $results
     * @return void
     */
    protected function matchToMorphParents($type, Collection $results)
    {
        $ownerKeyName = $this->ownerKey ?: $results->first()->getKeyName();

        foreach ($results->groupBy($ownerKeyName) as $ownerKey => $result) {
            if (isset($this->dictionary[$type][$ownerKey])) {
                foreach ($this->dictionary[$type][$ownerKey] as $model) {
                    $model->setRelation($this->relation, $result);
                }
            }
        }
    }
}

Класс morphTo () уже возвращает коллекцию результатов, но либо использует first(), либо несколько экземпляров setRelation(), чтобы указать, что существует только один набор. Перегружая getResults() и matchToMorphParents(), я могу изменить это поведение, чтобы разрешить установку Коллекции.

Для определения отношения мне понадобится пользовательский метод morphHasMany(). Это может быть добавлено к базе Model.php, которая расширяет Eloquent\Model.

/**
 * Define a polymorphic has many relationship.
 *
 * @param  string  $name
 * @param  string  $type
 * @param  string  $id
 * @param  string  $ownerKey
 * @return MorphHasMany
 */
public function morphHasMany($name = null, $type = null, $id = null, $ownerKey = null)
{
    // If no name is provided, we will use the backtrace to get the function name
    // since that is most likely the name of the polymorphic interface. We can
    // use that to get both the class and foreign key that will be utilized.
    $name = $name ?: $this->guessBelongsToRelation();

    list($type, $id) = $this->getMorphs(
        Str::snake($name), $type, $id
    );

    // If the type value is null it is probably safe to assume we're eager loading
    // the relationship. In this case we'll just pass in a dummy query where we
    // need to remove any eager loads that may already be defined on a model.
    if (empty($class = $this->{$type})) {
        return new MorphHasMany($this->newQuery()->setEagerLoads([]), $this, $id, $ownerKey, $type, $name);
    } else {
        $instance = $this->newRelatedInstance(
            static::getActualClassNameForMorph($class)
        );

        return new MorphHasMany($instance->newQuery(), $this, $id, $ownerKey ?? $instance->getKeyName(), $type, $name);
    }
}

Затем просто определите его, как обычный morphTo() метод.

public function data()
{
    return $this->morphHasMany();
}

Или в моем случае:

public function data()
{
    return $this->morphHasMany('data', 'data_type', 'id', 'gamedata_id');
}

Пока проблем нет, но, конечно, я могу столкнуться с некоторыми в будущем.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...