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
   },
   ......
 ]