Laravel Eloquent: dostęp do właściwości i dynamicznych nazw tabel

Używam frameworka Laravel i to pytanie jest bezpośrednio związane z używaniem elokwentnego w Laravel.

Próbuję stworzyć wymowny model, który może być używany w wielu różnych tabelach. Powodem tego jest to, że mam wiele tabel, które są zasadniczo identyczne, ale różnią się z roku na rok, ale nie chcę powielać kodu, aby uzyskać dostęp do tych różnych stoły.

  • gamedata_2015_nations
  • gamedata_2015_leagues
  • gamedata_2015_teams
  • gamedata_2015_players

Mógłbym oczywiście mieć jeden duży stół z kolumną roku, ale z ponad 350.000 wierszy każdego roku i wiele lat do czynienia z zdecydowałem, że byłoby lepiej podzielić je na wiele tabel, a nie 4 ogromne tabele z dodatkowym "gdzie" na każde żądanie.

Więc chcę mieć po jednej klasie dla każdego i zrobić coś w ten sposób wewnątrz klasy repozytorium:

public static function getTeam($year, $team_id)
    {
        $team = new Team;

        $team->setYear($year);

        return $team->find($team_id);
    }

Użyłem tej dyskusji na forach Laravel, aby zacząć: http://laravel.io/forum/08-01-2014-defining-models-in-runtime

Do tej pory mam to:

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

To chyba działa, ale obawiam się, że nie działa we właściwy sposób.

Ponieważ używam słowa kluczowego static, właściwość $year jest przechowywana w klasie, a nie w każdym pojedynczym obiekcie, więc zawsze, gdy tworzę nowy obiekt przechowuje właściwość $year na podstawie tego, kiedy ostatnio została ustawiona w innym obiekcie. Wolałbym, aby $year była powiązana z jednym obiektem i musiała być ustawiana za każdym razem, gdy tworzyłem obiekt.

Teraz staram się śledzić sposób, w jaki Laravel tworzy elokwentne modele, ale naprawdę staram się znaleźć odpowiednie miejsce, aby to zrobić.

Na przykład, jeśli zmienię to na to:

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

To działa dobrze, gdy próbuje dostać jeden zespół. Jednak w związkach to nie działa. To jest to, co próbowałem ze związkami:

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;
}

Ponownie działa to absolutnie dobrze, jeśli używam static::$year, ale jeśli spróbuję zmienić go na $ this- > year, to przestanie działać.

Rzeczywisty błąd wynika z faktu, że $this - > year nie jest ustawiona w gettable (), więc wywołana jest macierzysta metoda getTable () i zwrócona jest błędna nazwa tabeli.

Moim następnym krokiem było ustalenie, dlaczego działa z właściwością statyczną, ale nie z niestatyczną własności (nie jestem pewien, czy jest to właściwe określenie). Założyłem, że przy próbie zbudowania relacji z graczem po prostu używa static::$year z klasy Team. Jednak tak nie jest. Jeśli spróbuję wymusić błąd z czymś takim:

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

Teraz, co się dzieje, jest to, że dostaję błąd mówiąc gamedata_1800_players nie został znaleziony. Nie aż tak zaskakujące. Ale wyklucza to możliwość, że Eloquent po prostu używa właściwości static::$year z klasy Team, ponieważ wyraźnie ustawia Niestandardowy rok, który wysyłam do metody getPlayerModel ().

Więc teraz wiem, że gdy $year jest ustawiona w relacji i jest ustawiona statycznie, to getTable() ma do niej dostęp, ale jeśli jest ustawiona non-statycznie, to gdzieś się gubi i Obiekt nie wie o tej właściwości do czasu wywołania getTable ().

(zwróć uwagę na znaczenie tego, że działa inaczej, gdy po prostu tworzy nowy obiekt i używa relacji)

Zdaję sobie sprawę podałem teraz wiele szczegółów, więc aby uprościć i wyjaśnić moje pytanie:

1) Dlaczego static::$year działa, ale $this- > year nie działa dla relacji, gdy obie działają po prostu tworząc nowy obiekt.

2) czy istnieje sposób, w jaki mogę użyć niestatycznej właściwości i osiągnąć to, co już osiągam używając statycznej właściwości?

Uzasadnienie: statyczna właściwość pozostanie z klasą nawet po tym, jak skończę z jednym obiektem i spróbuję utworzyć inny obiekt z tą klasą, co nie wydaje się właściwe.

Przykład:

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

To może nie być najgorsza rzecz na świecie, i jak powiedziałem, to działa przy użyciu właściwości statycznych bez krytycznych błędów. Jest to jednak niebezpieczne dla powyższej próbki kodu, aby działać w ten sposób, więc chciałbym zrobić to poprawnie i uniknąć takiego niebezpieczeństwa.

Author: John Mellor, 2014-11-05

3 answers

Zakładam, że wiesz, jak poruszać się po Laravel API / codebase, ponieważ będziesz potrzebować go, aby w pełni zrozumieć tę odpowiedź...

Disclaimer: mimo, że testowałem kilka przypadków, nie mogę zagwarantować, że zawsze działa. Jeśli napotkasz problem, daj mi znać, a postaram się ci pomóc.

Widzę, że masz wiele przypadków, w których potrzebujesz tego rodzaju dynamicznej nazwy tabeli, więc zaczniemy od utworzenia BaseModel, więc nie musimy powtarzać siebie.

class BaseModel extends Eloquent {}

class Team extends BaseModel {}
Na razie nic ciekawego. Następnie przyjrzymy się jednej z funkcji static W Illuminate\Database\Eloquent\Model i napiszemy naszą własną funkcję static, nazwijmy ją year. (Put this in the BaseModel)
public static function year($year){
    $instance = new static;
    return $instance->newQuery();
}

Ta funkcja nie robi teraz nic poza tworzeniem nowej instancji bieżącego modelu, a następnie inicjalizowaniem konstruktora zapytań. W podobny sposób jak Laravel robi to w klasie modelek.

Następnym krokiem będzie utworzenie funkcji, która faktycznie ustawia tabela na modelu utworzonym z instancji. Nazwijmy to setYear. Dodamy również zmienną instancji do przechowywania roku oddzielnie od rzeczywistej nazwy tabeli.

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
    }
}

Teraz musimy zmienić year na faktycznie wywołanie setYear

public static function year($year){
    $instance = new static;
    $instance->setYear($year);
    return $instance->newQuery();
}

I na koniec musimy nadpisać newInstance(). Ta metoda jest używana w moim Laravel podczas używania na przykład find().

public function newInstance($attributes = array(), $exists = false)
{
    $model = parent::newInstance($attributes, $exists);
    $model->setYear($this->year);
    return $model;
}
To są podstawy. Oto jak go używać:
$team = Team::year(2015)->find(1);

$newTeam = new Team();
$newTeam->setTable(2015);
$newTeam->property = 'value';
$newTeam->save();

Następnym krokiem są relacje . I to było naprawdę trudne.

Metody relacji (takie jak: hasMany('Player')) nie wspierają przekazywania w obiektach. Biorą klasę, A następnie tworzą z niej instancję. Najprostszym rozwiązaniem, jakie udało mi się znaleźć, jest ręczne tworzenie obiektu relacji. (w 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);
}

Uwaga: klucz obcy nadal będzie nazywany team_id (bez roku) przypuszczam, że tego chcesz.

Niestety, będziesz musiał to zrobić dla każdy związek, który zdefiniujesz. Dla innych typów relacji spójrz na kod w Illuminate\Database\Eloquent\Model. Możesz w zasadzie skopiować wklej i wprowadzić kilka zmian. Jeśli używasz wielu relacji na swoich modelach zależnych od roku , Możesz również zastąpić metody relacji w swoim BaseModel.

Zobacz cały BaseModelna Pastebin

 24
Author: lukasgeiter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-12 09:16:01

To nie jest odpowiedź, tylko moja opinia. myślę, że próbujesz skalować swoją aplikację tylko w zależności od php części. Jeśli spodziewasz się, że aplikacja będzie rosnąć z czasem, to mądrze będzie dystrybuować ilość obowiązków wszystkie inne komponenty. Część związana z danymi powinna być obsługiwana przez RDBMS. Na przykład, jeśli używasz mysql, możesz łatwo partitionize swoje dane za pomocą YEAR. Istnieje wiele innych tematów, które pomogą Ci efektywnie zarządzać danymi.
 1
Author: Fazal Rasel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-12 15:30:48

Być może, Niestandardowy Konstruktor jest drogą do zrobienia.

Ponieważ wszystko, co się zmienia, to rok w nazwie odpowiedniego db, twoje modele mogą zaimplementować konstruktor w następujący sposób:

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...

}
Nie testowałem tego... Nazwij to tak:
$myTeam = new Team([], 2015);
 0
Author: nozzleman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-05 18:05:48