Laravel Eloquent: доступ к свойствам и имена динамических таблиц
Я использую Laravel Framework, и этот вопрос напрямую связан с использованием Eloquent в 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();
}
}
Это, похоже, работает, однако я беспокоюсь, что он не работает правильно.
Поскольку я использую статическое ключевое слово, свойство $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- > year, это перестанет работать.
Фактическая ошибка связана с тем фактом, что $this- > year не установлен в getTable(), так что вызывается родительский метод getTable() и возвращается неправильное имя таблицы.
Мой следующий шаг состоял в том, чтобы попытаться выяснить, почему он работает со статическим свойством, но не с нестатическим свойством (не уверен в правильном члене для этого). Я предположил, что он просто использовал static:: $year из класса Team при попытке построить отношения с игроком. Однако, это не так. Если я попытаюсь сделать ошибку с чем-то вроде этого:
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 просто использует свойство static:: $year из класса Team, поскольку он четко устанавливает пользовательский год, который я отправляю методу getPlayerModel().
Итак, теперь я знаю, что когда $year устанавливается в пределах отношения и устанавливается статически, getTable() имеет к нему доступ, но если он установлен не статически, он где-то теряется и объект не знает об этом свойстве к моменту получения getTable().
(обратите внимание на значимость его работы при простом создании нового объекта и при использовании отношений)
Я понимаю, что сейчас я дал много деталей, чтобы упростить и уточнить мой вопрос:
1) Почему статические:: $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);
Это может быть не самое худшее в мире, и, как я уже сказал, он фактически работает с использованием статических свойств без критических ошибок. Однако для вышеуказанного образца кода опасно работать таким образом, поэтому я хотел бы сделать это правильно и избежать такой опасности.
Ответы
Ответ 1
Я предполагаю, что вы знаете, как ориентироваться в Laravel API/codebase, так как вам понадобится, чтобы полностью понять этот ответ...
Отказ от ответственности: Несмотря на то, что я тестировал некоторые случаи, я не могу гарантировать, что он всегда работает. Если у вас возникнут проблемы, сообщите мне, и я постараюсь помочь вам.
Я вижу, что у вас есть несколько случаев, когда вам нужно это имя динамической таблицы, поэтому мы начнем с создания 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 делает это в классе модели.
Следующим шагом будет создание функции, которая фактически устанавливает таблицу в экземплярной модели. Позвольте называть это 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
на Pastebin
Ответ 2
Ну, это не ответ, а только мое мнение.
Я думаю, вы пытаетесь масштабировать свое приложение только в зависимости от части php
. Если вы ожидаете, что ваше приложение будет расти по времени, тогда будет разумно распределять обязанности, составляющие все остальные компоненты. Часть данных должна обрабатываться RDBMS
.
Например, если вы используете mysql
, вы можете легко partitionize
указать свои данные на YEAR
. И есть много другой темы, которая поможет вам эффективно управлять своими данными.
Ответ 3
Возможно, пользовательский конструктор - это путь.
Поскольку все, что меняется, - это год имени соответствующего db, ваши модели могут реализовать конструктор, подобный следующему:
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);
Ответ 4
У меня есть очень простое решение этой проблемы. Меня используют в моих проектах.
Вы должны использовать Model Scope
для определения таблицы имени динамического.
напишите код в свой файл Model
public function scopeDefineTable($query)
{
$query->from("deviceLogs_".date('n')."_".date('Y'));
}
Теперь в вашем классе контроллеров
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
},
......
]