Laravel Eloquent:访问属性和动态表名
我正在使用 Laravel 框架,这个问题与在 Laravel 中使用 Eloquent 直接相关.
I am using the Laravel Framework and this question is directly related to using Eloquent within Laravel.
我正在尝试制作一个可在多个不同表中使用的 Eloquent 模型.这样做的原因是我有多个基本相同但每年都不同的表,但我不想重复代码来访问这些不同的表.
I am trying to make an Eloquent model that can be used across the multiple different tables. The reason for this is that I have multiple tables that are essentially identical but vary from year to year, but I do not want to duplicate code to access these different tables.
- gamedata_2015_nations
- gamedata_2015_leagues
- gamedata_2015_teams
- gamedata_2015_players
我当然可以有一个带有年份列的大表,但是每年有超过 350,000 行并且需要处理很多年,我决定将它们拆分为多个表会更好,而不是 4 个带有额外表的大表每个请求的位置".
I could of course have one big table with a year column, but with over 350,000 rows each year and many years to deal with I decided it would be better to split them into multiple tables, rather than 4 huge tables with an extra 'where' on each request.
所以我想做的是为每个类创建一个类,并在 Repository 类中执行类似的操作:
So what I want to do is have one class for each and do something like this within a Repository class:
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
I have used this discussion on the Laravel forums to get me started: http://laravel.io/forum/08-01-2014-defining-models-in-runtime
到目前为止我有这个:
class Team extends IlluminateDatabaseEloquentModel {
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();
}
}
这似乎有效,但我担心它没有以正确的方式工作.
This seems to work, however i'm worried it's not working in the right way.
因为我使用的是 static 关键字,属性 $year 保留在类中而不是每个单独的对象中,所以每当我创建一个新对象时,它仍然根据上次设置的 $year 属性保存在一个不同的对象.我宁愿 $year 与单个对象相关联,并且每次创建对象时都需要设置.
Because i'm using the static keyword the property $year is retained within the class rather than each individual object, so whenever I create a new object it still holds the $year property based on the last time it was set in a different object. I would rather $year was associated with a single object and needed to be set each time I created an object.
现在我试图追踪 Laravel 创建 Eloquent 模型的方式,但真的很难找到合适的地方来做到这一点.
Now I am trying to track the way that Laravel creates Eloquent models but really struggling to find the right place to do this.
例如,如果我将其更改为:
For instance if I change it to this:
class Team extends IlluminateDatabaseEloquentModel {
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();
}
}
这在尝试获得单个团队时效果很好.但是,对于关系,它不起作用.这是我在人际关系中尝试过的:
This works just fine when trying to get a single Team. However with relationships it doesn't work. This is what i've tried with relationships:
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->year,那么这将停止工作.
Again this works absolutely fine if i'm using static::$year, but if I try and change it to use $this->year then this stops working.
实际的错误源于 $this->year 没有在 getTable() 中设置,因此调用了父 getTable() 方法并返回了错误的表名.
The actual error stems from the fact that $this->year is not set within getTable() so that the parent getTable() method is called and the wrong table name returned.
我的下一步是尝试弄清楚为什么它可以使用静态属性而不是非静态属性(不确定是否正确).我认为它只是在尝试建立 Player 关系时使用 Team 类中的 static::$year .然而,这种情况并非如此.如果我尝试强制出现这样的错误:
My next step was to try and figure out why it was working with the static property but not with the nonstatic property (not sure on the right term for that). I assumed that it was simply using the static::$year from the Team class when trying to build the Player relationship. However this is not the case. If I try and force an error with something like this:
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() 方法的自定义年份.
Now what happens is that I get an error saying gamedata_1800_players isn't found. Not that surprising perhaps. But it rules out the possibility that Eloquent is simply using the static::$year property from the Team class since it is clearly setting the custom year that i'm sending to the getPlayerModel() method.
所以现在我知道当 $year 在关系中设置并静态设置时,getTable() 可以访问它,但是如果它是非静态设置的,那么它会在某处丢失并且对象不知道调用 getTable() 时关于此属性的信息.
So now I know that when the $year is set within a relationship and is set statically then getTable() has access to it, but if it is set non-statically then it gets lost somewhere and the object doesn't know about this property by the time getTable() is called.
(注意在简单地创建新对象和使用关系时它的工作意义不同)
(note the significance of it working different when simply creating a new object and when using relationships)
我意识到我现在已经提供了很多细节,所以为了简化和澄清我的问题:
I realise i've given alot of detail now, so to simplify and clarify my question:
1) 为什么 static::$year 工作但 $this->year 不适用于关系,当两者都在简单地创建新对象时工作.
1) Why does static::$year work but $this->year not work for relationships, when both work when simply creating a new object.
2) 有没有办法可以使用非静态属性并实现我已经使用静态属性实现的目标?
2) Is there a way that I can use a non static property and achieve what I am already achieving using a static property?
理由:即使在我完成一个对象并尝试使用该类创建另一个对象后,静态属性仍将保留在类中,这似乎不正确.
Justification for this: The static property will stay with the class even after I have finished with one object and am trying to create another object with that class, which doesn't seem right.
示例:
//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);
这可能不是世界上最糟糕的事情,就像我说的,它实际上使用静态属性工作,没有严重错误.然而,上面的代码示例以这种方式工作是危险的,所以我想正确地做到这一点,避免这种危险.
This may not be the worst thing in the world, and like I said, it is actually working using the static properties with no critical errors. However it is dangerous for the above code sample to work in that way, so I would like to do it properly and avoid such a danger.
推荐答案
我假设你知道如何浏览 Laravel API/代码库,因为你需要它来完全理解这个答案......
I assume you know how to navigate the Laravel API / codebase since you will need it to fully understand this answer...
免责声明:即使我测试了一些情况,我也不能保证它总是有效.如果您遇到问题,请告诉我,我会尽力帮助您.
Disclaimer: Even though I tested some cases I can't guarantee It always works. If you run into a problem, let me know and I'll try my best to help you.
我看到您有多种情况需要这种动态表名,所以我们将从创建一个 BaseModel
开始,这样我们就不必重复自己了.
I see you have multiple cases where you need this kind of dynamic table name, so we will start off by creating a BaseModel
so we don't have to repeat ourselves.
class BaseModel extends Eloquent {}
class Team extends BaseModel {}
到目前为止没有什么令人兴奋的.接下来我们看一下IlluminateDatabaseEloquentModel
中的一个static函数,编写我们自己的静态函数,姑且称之为year
代码>.(把这个放在BaseModel
)
Nothing exciting so far. Next, we take a look at one of the static functions in IlluminateDatabaseEloquentModel
and write our own static function, let's call it year
.
(Put this in the BaseModel
)
public static function year($year){
$instance = new static;
return $instance->newQuery();
}
这个函数现在除了创建当前模型的一个新实例,然后在它上面初始化查询构建器之外什么都不做.与 Laravel 在 Model 类中的做法类似.
This function now does nothing but create a new instance of the current model and then initialize the query builder on it. In a similar fashion to the way Laravel does it in the Model class.
下一步是创建一个函数,在实例化模型上实际设置表.我们称之为 setYear
.我们还将添加一个实例变量来将年份与实际表名分开存储.
The next step will be to create a function that actually sets the table on an instantiated model. Let's call this one setYear
. And we'll also add an instance variable to store the year separately from the actual table name.
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
Now we have to change the year
to actually call setYear
public static function year($year){
$instance = new static;
$instance->setYear($year);
return $instance->newQuery();
}
最后但并非最不重要的是,我们必须覆盖 newInstance()
.例如,在使用 find()
时,此方法用于我的 Laravel.
And last but not least, we have to override newInstance()
. This method is used my Laravel when using find()
for example.
public function newInstance($attributes = array(), $exists = false)
{
$model = parent::newInstance($attributes, $exists);
$model->setYear($this->year);
return $model;
}
这是基础知识.使用方法如下:
That's the basics. Here's how to use it:
$team = Team::year(2015)->find(1);
$newTeam = new Team();
$newTeam->setTable(2015);
$newTeam->property = 'value';
$newTeam->save();
下一步是关系.这就是它变得非常棘手.
The next step are relationships. And that's were it gets real tricky.
关系的方法(如:hasMany('Player')
)不支持传入对象.他们接受一个类,然后从中创建一个实例.我能找到的最简单的解决方案是手动创建关系对象.(在团队
中)
The methods for relations (like: hasMany('Player')
) don't support passing in objects. They take a class and then create an instance from it. The simplest solution I could found, is by creating the relationship object manually. (in 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
(没有年份)我想这就是您想要的.
Note: the foreign key will still be called team_id
(without the year) I suppose that is what you want.
不幸的是,您必须为您定义的每个关系执行此操作.对于其他关系类型,请查看 IlluminateDatabaseEloquentModel
中的代码.您基本上可以复制粘贴它并进行一些更改.如果您在 year-dependent 模型上使用大量关系,您还可以覆盖 BaseModel
中的关系方法.
Unfortunately, you will have to do this for every relationship you define. For other relationship types look at the code in IlluminateDatabaseEloquentModel
. You can basically copy paste it and make a few changes. If you use a lot of relationships on your year-dependent models you could also override the relationship methods in your BaseModel
.
在 Pastebin
相关文章