Я использую Laravel Framework, и этот вопрос напрямую связан с использованием Eloquent внутри Laravel.
Я пытаюсь создать модель Eloquent, которая может использоваться в нескольких разных таблицах. Причина этого в том, что у меня есть несколько таблиц, которые по существу идентичны, но меняются из года в год, но я не хочу дублировать код для доступа к этим различным таблицам.
Я мог бы, конечно, иметь один большой стол с колонкой за год, но с более чем 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);