Laravel Eloquent: доступ к свойствам и именам динамических таблиц

Я использую Laravel Framework, и этот вопрос напрямую связан с использованием Eloquent в Laravel.

Я пытаюсь создать модель Eloquent, которая может использоваться в нескольких разных таблицах. Причина этого в том, что у меня есть несколько таблиц, которые по существу идентичны, но меняются из года в год, но я не хочу дублировать код для доступа к этим различным таблицам.

  • gamedata_2015_nations
  • gamedata_2015_leagues
  • gamedata_2015_teams
  • gamedata_2015_players

Конечно, у меня могла бы быть одна большая таблица со столбцом года, но с более чем 350 000 строк в год и многими годами я решил, что будет лучше разделить их на несколько таблиц, чем на 4 огромных таблицы с дополнительным «где» на каждый запрос.

Итак, я хочу иметь один класс для каждого и делать что-то подобное в классе Repository:

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-> year, то это перестанет работать.

Фактическая ошибка проистекает из того факта, что $ this-> year не установлен внутри getTable (), так что вызывается родительский метод getTable () и возвращается неправильное имя таблицы.

Следующим моим шагом было попытаться выяснить, почему он работает со статическим свойством, а не с нестатическим (не уверен в правильности этого термина). Я предположил, что он просто использовал static :: $ year из класса Team при попытке построить отношения Player. Однако, это не так. Если я попытаюсь вызвать ошибку с чем-то вроде этого:

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) Почему static :: $ 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);

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

12

Решение

Я предполагаю, что вы знаете, как ориентироваться в API / кодовой базе Laravel, так как он понадобится вам для полного понимания этого ответа …

отказ: Хотя я проверял некоторые случаи, я не могу гарантировать, что это всегда работает. Если у вас возникнут проблемы, дайте мне знать, и я сделаю все возможное, чтобы помочь вам.

Я вижу, у вас есть несколько случаев, когда вам нужно такое динамическое имя таблицы, поэтому мы начнем с создания 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 на Pastebin

25

Другие решения

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

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

1

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

Поскольку все, что меняется, это год в названии соответствующей БД, ваши модели могут реализовать конструктор, подобный следующему:

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);
0
По вопросам рекламы [email protected]