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

Это может быть не самое худшее в мире, и, как я уже сказал, он фактически работает с использованием статических свойств без критических ошибок. Однако для вышеуказанного образца кода опасно работать таким образом, поэтому я хотел бы сделать это правильно и избежать такой опасности.

Я предполагаю, что вы знаете, как ориентироваться в 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(); } в 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

Ну, это не ответ, а только мое мнение.

Я думаю, вы пытаетесь масштабировать свое приложение только в зависимости от части php . Если вы ожидаете, что ваше приложение будет расти по времени, тогда будет разумно распределять обязанности, составляющие все остальные компоненты. Часть данных должна обрабатываться RDBMS . Например, если вы используете mysql , вы можете легко partitionize свои данные на YEAR . И есть много других тем, которые помогут вам эффективно управлять своими данными.

Может быть, пользовательский конструктор – это путь.

Поскольку все, что меняется, – год в имени соответствующего 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);