Laravel 雄辩:访问属性和动态表名


Laravel Eloquent: Accessing properties and Dynamic Table Names

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

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

  • gamedata_2015_nations
  • gamedata_2015_leagues
  • gamedata_2015_teams
  • gamedata_2015_players

当然可以有一个带有年份列的大表,但是每年有超过 350,000 行和许多年要处理,我决定最好将它们拆分为多个表,而不是 4 个大表,每个请求都有一个额外的"where"。

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

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

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

我假设您知道如何浏览 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() .例如,使用此方法时我的Laravel 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'Model 中的代码。您基本上可以复制粘贴它并进行一些更改。如果在与年份相关的模型上使用大量关系,还可以覆盖BaseModel中的关系方法。

查看关于帕斯泰宾的完整BaseModel

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

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

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

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

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

我有一个非常简单的解决方案来解决这个问题。我正在我的项目中使用。

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

Model文件中编写代码

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

现在在您的控制器类

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

但是,如果要管理控制器形式的表名,则可以按照此代码进行操作。

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
   },
   ......
 ]