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.
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 BaseModel
na Pastebin
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
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.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);
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