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

时间:2014-11-05 12:40:52

标签: php laravel laravel-4 eloquent

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

我正在尝试创建一个可以在多个不同表中使用的Eloquent模型。这样做的原因是我有多个表基本相同,但每年都有所不同,但我不想重复代码来访问这些不同的表。

  • gamedata_2015_nations
  • gamedata_2015_leagues
  • gamedata_2015_teams
  • gamedata_2015_players

我当然可以有一个带有年份列的大表,但是每年有超过350,000行和多年处理我决定将它们分成多个表更好,而不是4个巨大的表和一个额外的表每个请求上的'where'。

所以我想要做的就是为每个类创建一个类,并在Repository类中执行类似的操作:

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();
    }
}

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

因为我使用的是静态关键字,所以属性$ 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;
}

如果我使用静态:: $ year,那么这个工作非常好,但如果我尝试将其更改为使用$ this-> year,那么这将停止工作。

实际错误源于$ this-> year未在getTable()中设置,因此调用了父getTable()方法并返回了错误的表名。

我的下一步是试图找出它为什么使用静态属性而不是非静态属性(不确定在正确的术语上)。我假设在尝试构建Player关系时,它只是使用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)为什么static :: $ year工作但$ this-> year不能用于关系,当两者都在简单地创建新对象时工作。

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);

这可能不是世界上最糟糕的事情,就像我说的那样,它实际上是使用静态属性而没有严重错误。但是上面的代码示例以这种方式工作是很危险的,所以我想正确地做到这一点并避免这种危险。

4 个答案:

答案 0 :(得分:27)

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

免责声明:即使我测试了一些案例,但我无法保证它始终有效。如果您遇到问题,请告诉我,我会尽力帮助您。

我发现你有多种情况需要这种动态表名,所以我们首先要创建一个BaseModel,这样我们就不必重复了。

class BaseModel extends Eloquent {}

class Team extends BaseModel {}

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

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
    }
}

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

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

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

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\Model中的代码。您基本上可以复制粘贴它并进行一些更改。如果您在年度相关模型上使用了很多关系,那么您也可以覆盖BaseModel中的关系方法。

查看Pastebin

上的完整BaseModel

答案 1 :(得分:1)

好吧,这不是答案,只是我的意见。

我想,您正在尝试根据php部分扩展您的应用程序。如果您希望您的应用程序会随着时间的推移而增长,那么明智的做法是分配所有其他组件的职责。数据相关部分应由RDBMS处理。 例如,如果您使用mysql,则可以partitionize轻松YEAR您的数据。还有很多其他主题可以帮助您有效地管理数据。

答案 2 :(得分:0)

也许,自定义构造函数是可行的方法。

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

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);

答案 3 :(得分:-1)

对于这个问题,我有一个非常简单的解决方案。我在我的项目中被使用。

您必须使用Model Scope来定义名称动态

在您的Model文件中编写代码

 public function scopeDefineTable($query)
  {
    $query->from("deviceLogs_".date('n')."_".date('Y'));
  }

现在进入您的 Controller类

function getAttendanceFrom()
 {
   return  DeviceLogs::defineTable()->get();  
 }

但是,如果要管理表名表单 Controller ,则可以遵循以下代码。

Model类中

  public function scopeDefineTable($query,$tableName)
  {
     $query->from($tableName);
  }

Controller类中

 function getAttendanceFrom()
 {
    $table= "deviceLogs_".date('n')."_".date('Y');
   return  DeviceLogs::defineTable($table)->get();
 }

您的输出

 [
  {
    DeviceLogId: 51,
    DownloadDate: "2019-09-05 12:44:20",
    DeviceId: 2,
    UserId: "1",
    LogDate: "2019-09-05 18:14:17",
    Direction: "",
    AttDirection: null,
    C1: "out",
    C2: null
   },
   ......
 ]