web-dev-qa-db-ja.com

Laravel Eloquent:プロパティと動的テーブル名へのアクセス

私はLaravel Frameworkを使用していますが、この質問はLaravel内でのEloquentの使用に直接関係しています。

複数の異なるテーブルで使用できるEloquentモデルを作成しようとしています。これは、基本的に同一であるが年ごとに異なる複数のテーブルがありますが、これらの異なるテーブルにアクセスするためにコードを複製したくないためです。

  • gamedata_2015_nations
  • gamedata_2015_leagues
  • gamedata_2015_teams
  • gamedata_2015_players

もちろん、年の列を持つ1つの大きなテーブルを持つこともできますが、毎年350,000を超える行があり、何年も処理する必要があるため、「where」を追加した4つの大きなテーブルではなく、複数のテーブルに分割する方がよいと判断しました。リクエストごとに。

だから私がやりたいのは、それぞれに1つのクラスを持ち、リポジトリクラス内で次のようなことをすることです。

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を使用しているだけだと思いました。ただし、そうではありません。次のようなエラーを強制しようとすると、次のようになります。

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が見つからないというエラーが表示されます。おそらくそれほど驚くことではありません。ただし、getPlayerModel()メソッドに送信するカスタム年を明確に設定しているため、EloquentがTeamクラスのstatic :: $ yearプロパティを使用している可能性はありません。

これで、$ yearがリレーションシップ内に設定され、静的に設定されると、getTable()がそれにアクセスできることがわかりましたが、非静的に設定されると、どこかで失われ、オブジェクトはこのプロパティを認識しません。 getTable()が呼び出されるまでに。

(単に新しいオブジェクトを作成する場合とリレーションシップを使用する場合では、動作が異なることの重要性に注意してください)

私は今、多くの詳細を与えたことに気づきました。それで、私の質問を単純化して明確にするために:

1)static :: $ yearは機能するのに、$ this-> yearは関係に対して機能しないのはなぜですか。どちらも、単に新しいオブジェクトを作成するときに機能します。

2)非静的プロパティを使用して、静的プロパティを使用してすでに達成していることを達成する方法はありますか?

これの正当化:静的プロパティは、1つのオブジェクトを処理し終えて、そのクラスで別のオブジェクトを作成しようとした後でも、クラスに残りますが、これは正しくないようです。

例:

    //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);

これは世界で最悪の事態ではないかもしれません、そして私が言ったように、それは重大なエラーなしで静的プロパティを使用して実際に働いています。ただし、上記のコードサンプルがこのように動作するのは危険なので、適切に実行して、そのような危険を回避したいと思います。

15
John Mellor

この答えを完全に理解するために必要になるので、Laravel API /コードベースをナビゲートする方法を知っていると思います...

免責事項:いくつかのケースをテストしましたが、常に機能することを保証できません。問題が発生した場合はお知らせください。できる限りサポートさせていただきます。

この種の動的テーブル名が必要なケースが複数あるようです。まず、BaseModelを作成して、繰り返す必要がないようにします。

_class BaseModel extends Eloquent {}

class Team extends BaseModel {}
_

これまでのところエキサイティングなものはありません。次に、_Illuminate\Database\Eloquent\Model_のstatic関数の1つを見て、独自の静的関数を記述します。これを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()をオーバーライドする必要があります。このメソッドは、たとえばfind()を使用するときにmy Laravelで使用されます。

_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の関係メソッドをオーバーライドすることもできます。

PastebinBaseModel全体を表示

29
lukasgeiter

まあそれは答えではなく、私の意見です。

おそらく、phpの部分に応じてアプリケーションをスケーリングしようとしています。アプリケーションが時間の経過とともに成長すると予想される場合は、他のすべてのコンポーネントの責任を分散するのが賢明です。データ関連の部分はRDBMSで処理する必要があります。たとえば、mysqlを使用している場合、partitionizeでデータを簡単にYEARできます。また、データを効果的に管理するのに役立つトピックは他にもたくさんあります。

0
Fazal Rasel

たぶん、カスタムコンストラクターが行く方法です。

変化するのは対応する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);
0
nozzleman