Laravel自定义变形关系

时间:2018-05-07 06:42:36

标签: php laravel laravel-5 eloquent

我想使用Laravel Eloquent Polymorphic Relationships但是它似乎没有设置为使用我的表结构。

基本上我有一个gamedata表,其中包括所有不同类型的gamedata(国家,联赛,球队,球员等)。对于每种类型,我有多个表,信息由game_id分隔。因此,gamedata表中的国家“England”会有一行,在NATIONAL表中有7个对应的行,其中包含来自7个不同game_ids的数据。

我希望能够从gamedata表中选择一些行,并根据它的类型从相应的表中选择相应的行。

这对于一对一的关系来说很容易,但似乎不可能与一对多的关系。

这是gamedata表。

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;

所以gamedata表上的某些行可能如下所示:

(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关系来做到这一点?或者有一种简单的方法可以根据我的需要创建自己的关系类吗?

1 个答案:

答案 0 :(得分:0)

我认为我通过扩展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');
}

到目前为止没有任何问题,但当然我将来会遇到一些问题。