Laravel Eloquent:访问属性和动态表名称

2022-08-31 00:31:14

我正在使用Laravel框架,这个问题与在Laravel中使用Eloquent直接相关。

我正在尝试制作一个可以跨多个不同表使用的雄辩模型。这样做的原因是我有多个表,这些表本质上是相同的,但每年都有所不同,但我不想重复代码来访问这些不同的表。

  • gamedata_2015_nations
  • gamedata_2015_leagues
  • gamedata_2015_teams
  • gamedata_2015_players

我当然可以有一个带有年份列的大表,但是每年有超过350,000行,并且需要处理很多年,我决定最好将它们拆分为多个表,而不是4个大表,每个请求都有一个额外的“位置”。

因此,我想做的是为每个类设置一个类,并在存储库类中执行类似操作:

public static function getTeam($year, $team_id)
    {
        $team = new Team;

        $team->setYear($year);

        return $team->find($team_id);
    }

我在Laravel论坛上使用了这个讨论来让我开始:http://laravel.io/forum/08-01-2014-defining-models-in-runtime

到目前为止,我有这个:

class Team extends \Illuminate\Database\Eloquent\Model {

    protected static $year;

    public function setYear($year)
    {
        static::$year= $year;
    }

    public function getTable()
    {
        if(static::$year)
        {
            //Taken from https://github.com/laravel/framework/blob/4.2/src/Illuminate/Database/Eloquent/Model.php#L1875
            $tableName = str_replace('\\', '', snake_case(str_plural(class_basename($this))));

            return 'gamedata_'.static::$year.'_'.$tableName;
        }

        return Parent::getTable();
    }
}

这似乎有效,但我担心它没有以正确的方式工作。

因为我使用的是 static 关键字,所以属性$year保留在类中,而不是每个单独的对象中,所以每当我创建新对象时,它仍然保留基于上次在不同对象中设置它的时间的 $year 属性。我宁愿$year与单个对象相关联,并且每次创建对象时都需要设置。

现在,我正试图追踪Laravel创建Eloquent模型的方式,但真的很难找到合适的地方来做到这一点。

例如,如果我将其更改为:

class Team extends \Illuminate\Database\Eloquent\Model {

    public $year;

    public function setYear($year)
    {
        $this->year = $year;
    }

    public function getTable()
    {
        if($this->year)
        {
            //Taken from https://github.com/laravel/framework/blob/4.2/src/Illuminate/Database/Eloquent/Model.php#L1875
            $tableName = str_replace('\\', '', snake_case(str_plural(class_basename($this))));

            return 'gamedata_'.$this->year.'_'.$tableName;
        }

        return Parent::getTable();
    }
}

当尝试获得单个团队时,这工作正常。但是,对于关系,它不起作用。这就是我在人际关系中尝试过的:

public function players()
{
    $playerModel = DataRepository::getPlayerModel(static::$year);

    return $this->hasMany($playerModel);
}

//This is in the DataRepository class
public static function getPlayerModel($year)
{
    $model = new Player;

    $model->setYear($year);

    return $model;
}

同样,如果我使用static::$year,这绝对可以正常工作,但是如果我尝试将其更改为使用$this>年,那么这将停止工作。

实际的错误源于这样一个事实,即在 getTable() 中没有设置$this->year,因此调用父 getTable() 方法并返回错误的表名。

我的下一步是尝试弄清楚为什么它使用静态属性,而不是非静态属性(不确定正确的术语)。我以为它只是在尝试构建玩家关系时使用来自 Team 类的 static::$year。然而,事实并非如此。如果我尝试用这样的东西强制错误:

public function players()
{
    //Note the hard coded 1800
    //If it was simply using the old static::$year property then I would expect this still to work
    $playerModel = DataRepository::getPlayerModel(1800);

    return $this->hasMany($playerModel);
}

现在发生的事情是,我得到一个错误,说gamedata_1800_players找不到。也许这并不奇怪。但它排除了Eloquent只是使用Team类中的static::$year属性的可能性,因为它清楚地设置了我发送到getPlayerModel()方法的自定义年份。

所以现在我知道,当$year在关系中设置并且是静态设置的,那么getTable()可以访问它,但是如果它以非静态方式设置,那么它会在某个地方丢失,并且对象在调用getTable()时不知道这个属性。

(请注意,在简单地创建新对象和使用关系时,它的工作方式不同的重要性)

我意识到我现在已经给出了很多细节,所以为了简化和澄清我的问题:

1)为什么静态::$year工作,但$this>年不适用于关系,当两者都在简单地创建新对象时起作用时。

2)有没有办法使用非静态属性并实现我使用静态属性已经实现的目标?

理由:即使我完成了一个对象并尝试使用该类创建另一个对象,静态属性仍将保留在类中,这似乎不对。

例:

    //Get a League from the 2015 database
    $leagueQuery = new League;

    $leagueQuery->setYear(2015);

    $league = $leagueQuery->find(11);

    //Get another league
    //EEK! I still think i'm from 2015, even though nobodies told me that!
    $league2 = League::find(12);

这可能不是世界上最糟糕的事情,就像我说的,它实际上是使用静态属性工作的,没有严重的错误。但是,上面的代码示例以这种方式工作是危险的,因此我想正确执行此操作并避免此类危险。


答案 1

我假设您知道如何浏览Laravel API /代码库,因为您将需要它来完全理解这个答案...

免责声明:即使我测试了一些案例,我也不能保证它总是有效。如果您遇到问题,请告诉我,我会尽力帮助您。

我看到您有多种情况需要这种动态表名称,因此我们将首先创建一个,这样我们就不必重复自己了。BaseModel

class BaseModel extends Eloquent {}

class Team extends BaseModel {}

到目前为止,没有什么令人兴奋的。接下来,我们来看看其中的一个静态函数,并编写我们自己的静态函数,我们称之为。(把这个放在Illuminate\Database\Eloquent\ModelyearBaseModel)

public static function year($year){
    $instance = new static;
    return $instance->newQuery();
}

此函数现在不执行任何操作,只需创建当前模型的新实例,然后在其上初始化查询生成器。与 Laravel 在 Model 类中的做法类似。

下一步将是创建一个函数,该函数在实例化模型上实际设置表。让我们称之为.我们还将添加一个实例变量,以将年份与实际表名称分开存储。setYear

protected $year = null;

public function setYear($year){
    $this->year = $year;
    if($year != null){
        $this->table = 'gamedata_'.$year.'_'.$this->getTable(); // you could use the logic from your example as well, but getTable looks nicer
    }
}

现在我们必须将实际调用更改为yearsetYear

public static function year($year){
    $instance = new static;
    $instance->setYear($year);
    return $instance->newQuery();
}

最后但并非最不重要的一点是,我们必须覆盖 。例如,使用此方法时,我的Laravel。newInstance()find()

public function newInstance($attributes = array(), $exists = false)
{
    $model = parent::newInstance($attributes, $exists);
    $model->setYear($this->year);
    return $model;
}

这是基础。以下是使用它的方法:

$team = Team::year(2015)->find(1);

$newTeam = new Team();
$newTeam->setTable(2015);
$newTeam->property = 'value';
$newTeam->save();

下一步是关系。这就是它变得非常棘手的时候。

关系的方法(如:)不支持传入对象。他们取一个类,然后从中创建一个实例。我能找到的最简单的解决方案是手动创建关系对象。(在hasMany('Player')Team)

public function players(){
    $instance = new Player();
    $instance->setYear($this->year);

    $foreignKey = $instance->getTable.'.'.$this->getForeignKey();
    $localKey = $this->getKeyName();

    return new HasMany($instance->newQuery(), $this, $foreignKey, $localKey);
}

注意:外键仍然会被调用(没有年份),我想这就是你想要的。team_id

不幸的是,你必须为你定义的每一段关系这样做。有关其他关系类型,请查看 中的代码。您基本上可以复制粘贴它并进行一些更改。如果在依赖于年份的模型上使用大量关系,则还可以覆盖 .Illuminate\Database\Eloquent\ModelBaseModel

查看粘贴器上的完整内容BaseModel


答案 2

也许,自定义构造函数是要走的路。

由于所有变化都是以相应数据库的名称命名的年份,因此您的模型可以实现如下所示的构造函数:

class Team extends \Illuminate\Database\Eloquent\Model {

    public function __construct($attributes = [], $year = null) {
        parent::construct($attributes);

        $year = $year ?: date('Y');

        $this->setTable("gamedata_$year_teams");
    }

    // Your other stuff here...

}

虽然没有测试过这个...这样称呼它:

$myTeam = new Team([], 2015);

推荐