Přístup k databázi v modelu (jak to udělat správně?)
- jf
- Člen | 13
Hledal jsem ve fóru, ale nějak nechápu, jak to udělat čistě a správně.
Databázi mám nakonfigurovanou v config.neon
database:
dsn: 'mysql:127.0.0.1;dbname=test'
user: 'root'
password: ''
debugger: true
explain: true
reflection: discovered
autowired: true
options:
lazy: yes
A v modelu bych jí potřeboval používat
<?php
$this->database->query('SELECT * FROM table WHERE ...);
?>
Mám Model.php který dědí od BaseModel.php a ten vypadá takto:
<?php
namespace App\Model;
use Nette;
class BaseModel extends Nette\Object
{
/** @var Nette\Database\Context */
public $database;
public function __construct(Nette\Database\Context $database)
{
$this->database = $database;
}
}
?>
A to skončí chybou:
App\Model\BaseModel::__construct() must be an instance of Nette\Database\Context, none given
Děkuji za radu.
- Oli
- Člen | 1215
Předpokládám, že máš jako službu zaregistrovanej konkrétní model.
Například ArticleModel a nevoláš
parent::__construct($database);
Mělo by to v ArticleModel (musí
být jako služba) vypadat zhruba takhle:
<?php
namespace App\Model;
use Nette;
class ArticleModel extends BaseModel
{
public function __construct(Nette\Database\Context $database)
{
parent::__construct($database);
}
}
?>
- jf
- Člen | 13
Tady to je, vycházím z čistého sandboxu:
config.neon
parameters:
php:
date.timezone: Europe/Prague
nette:
application:
errorPresenter: Error
mapping:
*: App\*Module\Presenters\*Presenter
session:
expiration: 14 days
database:
dsn: 'mysql:host=localhost;dbname=test'
user: 'test'
password: ''
debugger: true
explain: true
reflection: discovered
autowired: true
options:
lazy: yes
services:
- App\Model\MessagesModel
- App\Model\UserManager
- App\RouterFactory
router: @App\RouterFactory::createRouter
BaseModel.php
<?php
namespace App\Model;
use Nette;
class BaseModel extends Nette\Object
{
/** @var Nette\Database\Context */
private $database;
public function __construct(Nette\Database\Context $database)
{
$this->database = $database;
}
}
?>
MessagesModel.php
<?php
namespace App\Model;
use Nette,
Nette\Database\Context,
Nette\Utils\Strings;
class MessagesModel extends BaseModel
{
public function __construct(Nette\Database\Context $database)
{
parent::__construct($database);
}
public function doSomething()
{
$this->database->query('SELECT * FROM table WHERE valid >= ?', $start)->dump();
}
}
?>
MessagesPresenter.php
<?php
namespace App\Presenters;
use Nette,
App\Model;
class MessagesPresenter extends BasePresenter
{
public function renderDefault()
{
$this->template->anyVariable = 'any value';
}
public function renderSomething($type)
{
$ml = new \App\Model\MessagesModel();
$ml->doSomething();
}
}
?>
- Marek Šneberger
- Člen | 130
V BaseModel změň
/** @var Nette\Database\Context */
private $database;
Na
/** @var Nette\Database\Context */
protected $database;
Plus ten model nevolej přes new Model(...)
ale injectni si ho
do presenteru.
Editoval Marek Šneberger (2. 11. 2014 14:29)
- Oli
- Člen | 1215
Takže nakonec je problém v tom presenteru ;-)
Pročti si tohle.
Jde o to, že ty vytváříš instanci objektu pomocí
new \App\Model\MessagesModel();
. Problém jsou ty závorky, v nich
je to, co očekává constructor toho MessageModelu
. Pokud
by jsi využil Nette autowire, tak ti tam Nette ty závislosti doplní
automaticky. Můsíš si o to jen říct ideálně v constructoru toho
presenteru. Takže by to potom vypadalo takhle:
namespace App\Presenters;
use Nette,
App\Model;
class MessagesPresenter extends BasePresenter
{
// Nette autoamticky podle tipy hintu pozná jakou službu voláš (proto musí být v config.neonu)
// a předá jí všechny závislosti, které potřebuje (taky musí být v neonu)
function __construct(App\Model\MessageModel $messageModel)
{
$this->messageModel = $messageModel;
}
public function renderSomething($type)
{
$ml = $this->messageModel;
$ml->doSomething();
}
}
- Zax
- Člen | 370
Chyba je na řádku $ml = new \App\Model\MessagesModel();
, model
chce při vytváření Database\Context, což můžeš a) předat ručně při
vytváření (blbej způsob) nebo b) zbavit se zápisu „new …“ a využít
DI kontejner v Nette, který ti to obslouží automaticky: potřebuješ model
zaregistrovat v configu jako službu (už jsi udělal) a pak si jej
v presenteru jednoduše vyžádat. To jde třemi způsoby – přes
konstruktor, inject* metody nebo @inject anotace. Stačí trochu hledat,
v dokumentaci to je určitě popsané.
- Azathoth
- Člen | 495
Oli napsal(a):
Takže nakonec je problém v tom presenteru ;-)
Pročti si tohle.Jde o to, že ty vytváříš instanci objektu pomocí
new \App\Model\MessagesModel();
. Problém jsou ty závorky, v nich je to, co očekává constructor tohoMessageModelu
. Pokud by jsi využil Nette autowire, tak ti tam Nette ty závislosti doplní automaticky. Můsíš si o to jen říct ideálně v constructoru toho presenteru. Takže by to potom vypadalo takhle:namespace App\Presenters; use Nette, App\Model; class MessagesPresenter extends BasePresenter { // Nette autoamticky podle tipy hintu pozná jakou službu voláš (proto musí být v config.neonu) // a předá jí všechny závislosti, které potřebuje (taky musí být v neonu) function __construct(App\Model\MessageModel $messageModel) { $this->messageModel = $messageModel; } public function renderSomething($type) { $ml = $this->messageModel; $ml->doSomething(); } }
Neměly by se do presenterů přidávat závislosti spíš injectem než
konstruktorem? https://phpfashion.com/…-presenterum
A neměl by se volat parent::__construct()?
Editoval Azathoth (2. 11. 2014 16:24)
- Oli
- Člen | 1215
Pokud v jakémkoli předkovy nepoužiješ konstruktor, tak parent::__construct() volat nemusíš. Předávání závislostí je naopak doporučováno (ale jen v potomku) prostřednictvím konstruktoru (nicméně já ale používám Kdyby/Autowire).
V jakémkoli presenteru, ze kterého se dědí dál je doporučováno používat naopak injectMotodu. Inject anotace je z těch 3 možností nejhorší, mimo jiné protože porušuje zapouzdření objektu (musí být public).
Všechno to je i v dokumentaci, kterou jsem linkoval výše. ;-)
- MartinitCZ
- Člen | 580
@Oli Neuč prosím lidi používat __constructor v presenterech. Plno lidí úplně zapomíná na nějaký parent:: a narazí. Postů je tu na toto téma dost.
Editoval MartinitCZ (2. 11. 2014 20:21)
- Oli
- Člen | 1215
@MartinitCZ asi jsi myslel mě, ne? :-)
Záleží na preferenci. Pro mě to je Kdyby/Autowire (jednoduchost, přehlednost) → konstruktor (správnost, přehlednost) → inject metoda (správnost, nepřehlednost) → inject anotace (jednoduchost, nezapouzdreni objektu).
Pokud zapomene na parent:: tak jestli se musí zeptat, tak to nesouvisí s Nette, ale s OOP a jen pro něj dobře, že se to doučí. ;-)
Druhá věc, já jsem parent v presenteru nepoužil nikdy. Jestliže mám presenter, ze kterého se dědí, konstruktor v něm vůbec nevolám…
Poslední věc, jestli je tu na toto téma postů dost, tak si to můžou přečíst a jsou bez problému, ne?
- Zax
- Člen | 370
Já souhlasím s Oli. Konstruktor je „nejsprávnější“, protože když závislosti nepředáš, tak se prostě objekt nevytvoří a vyhodí to celkem srozumitelnou chybu. Kdo neumí číst chybové hlášky, bude mít problém i v jiných situacích a to už s Nette fakt nesouvisí. Já se OOP včetně Nette, Doctriny, používání Composeru, Gitu atd. začal učit cca před rokem (možná ani to ne) a už si vesele bastlím vlastní CMSko a když mi něco nejde, tak si to dohledám a doučím, no problem. Naučit se parent:: je fakt úplně ten nejmenší problém ;-)
- MartinitCZ
- Člen | 580
tak ono to není je o zapomenutém parent::, ale problému kolem
dědění.
Cpát si do HomepagePresenteru přes __contructor 4 služby jen proto, že
2 potřebuje BasePresenter musí být zábava. ;)
- Azathoth
- Člen | 495
Trochu se mi zdá zvláštní ta nekonzistence v tom, že mám presenter, v něm mám závislosti předané konstruktorem a když se z něj rozhodnu dědit, tak musí jeho potomci posílat závislosti i jemu a nebo budu přepisovat injektování konstruktorem na injektování inject metodou, což se mi taky nezdá nic moc.
- Zax
- Člen | 370
@MartinitCZ Však k tomu ty injecty máme, abychom si usnadnili
vkládání závislostí do abstraktních presenterů
(MessagesPresenter extends BasePresenter
v dotazu zrovna moc
abstraktně nevypadá, tak proč by nemohl použít konstruktor?), ale to
pořád není důvod se konstruktoru úplně vyhnout. Já mám na to jednoduchý
postup:
- Je presenter abstraktní? Budu od něj dědit? Pak použiju inject metodu(y).
- Je presenter finální? Nebudu od něj dědit? Pak použiju konstruktor.
Vidíš v tomto způsobu práce snad nějaký problém? Já nikoliv, vidím akorát o něco čistší kód. Když zapomeneš po změnách smazat cache a všude cpeš injecty, tak ti aplikace spadne až ve chvíli, kdy se závislostí začneš pracovat (což může klidně být až třeba v signálu nebo akci a nemusíš si toho hned všimnout – o den později se k tomu vrátíš a pak hodinu řešíš, proč ti to nejede, když vlastně kód máš správně). Pokud máš závislosti v konstruktoru, tak si toho všimneš ihned, protože se ti presenter odmítne nainstancovat. Proto si myslím, že je dobré najít zlatou střední cestu a pokud možno používat konstruktor všude, kde je to možné, tedy v konkrétních presenterech.
A parent::__construct()
bys měl správně v konstruktoru volat
vždy, ale přiznávám, že to také nedodržuji.
Editoval Zax (3. 11. 2014 0:47)
- Azathoth
- Člen | 495
Zax napsal(a):
@MartinitCZ Však k tomu ty injecty máme, abychom si usnadnili vkládání závislostí do abstraktních presenterů (
MessagesPresenter extends BasePresenter
v dotazu zrovna moc abstraktně nevypadá, tak proč by nemohl použít konstruktor?), ale to pořád není důvod se konstruktoru úplně vyhnout. Já mám na to jednoduchý postup:
- Je presenter abstraktní? Budu od něj dědit? Pak použiju inject metodu(y).
- Je presenter finální? Nebudu od něj dědit? Pak použiju konstruktor.
Vidíš v tomto způsobu práce snad nějaký problém? Já nikoliv, vidím akorát o něco čistší kód. Když zapomeneš po změnách smazat cache a všude cpeš injecty, tak ti aplikace spadne až ve chvíli, kdy se závislostí začneš pracovat (což může klidně být až třeba v signálu nebo akci a nemusíš si toho hned všimnout – o den později se k tomu vrátíš a pak hodinu řešíš, proč ti to nejede, když vlastně kód máš správně). Pokud máš závislosti v konstruktoru, tak si toho všimneš ihned, protože se ti presenter odmítne nainstancovat. Proto si myslím, že je dobré najít zlatou střední cestu a pokud možno používat konstruktor všude, kde je to možné, tedy v konkrétních presenterech.
A
parent::__construct()
bys měl správně v konstruktoru volat vždy, ale přiznávám, že to také nedodržuji.
A ty máš presentery jenom abstraktní nebo finální? Není to potom zbytečně moc psaní, když se rozhodneš si trochu upravit strukturu presenterů?
- Zax
- Člen | 370
Azathoth napsal(a):
A ty máš presentery jenom abstraktní nebo finální? Není to potom zbytečně moc psaní, když se rozhodneš si trochu upravit strukturu presenterů?
Co máš na mysli tím „upravit strukturu presenterů“? Přesunout závislost z konkrétního presenteru do abstraktního? Jak často to kdo dělá? Osobně mám při programování téměř vždy celkem jasnou představu, co bude potřeba v abstraktním presenteru, od kterého budu dědit, a co bude potřeba v konkrétním presenteru. Jestli to potřebuji udělat jednou či dvakrát v projektu, pak se s tím prostě nějak poperu, není to jako kdybych najednou musel přepisovat půlku projektu :-D
EDIT: + co napsal Šaman. akorát se přiznám, že z lenosti klíčové slovo final nepoužívám (ničemu to nevadí, od neabstraktních presenterů prostě nedědím).
Editoval Zax (3. 11. 2014 1:06)
- Azathoth
- Člen | 495
Šaman napsal(a):
Presentery by měly být jen abstraktní a finální.
A co když chci používat https://github.com/Kdyby/Aop na presentery?
- Šaman
- Člen | 2666
To neznám, takže nevím, v čem je problém. Ale jako s každým pravidlem, i s těmi abstraktními/finálními presentery je to tak, že:
- nejprve se pravidlo nauč a používej
- pak ho pochop, včetně všech okolností
- a pak ho klidně porušuj, když už víš, proč to děláš a jaké mohou být důsledky :)
Problém je jen pokud ti nějaké pravidlo připadá obtěžující a prostě ho ignoruješ z lenosti.
Editoval Šaman (3. 11. 2014 2:56)
- Jan Suchánek
- Člen | 404
@Azathoth: A proč by něměli? Dědění není žádná výhra.
Editoval jenicek (3. 11. 2014 17:59)
- Azathoth
- Člen | 495
@jenicek no, je to něco navíc, že je nějaká třída finální,
tak se ptám, k čemu je to dobré a proč by se to mělo dělat. Pochopím,
proč by se měly dělat abstraktní presentery, ale uniká mi důvod, proč by
neabstraktní presentery měly být finální.
A nějak jsem asi nepochopil tvou poznámku, V čem dědění není
žádná výhra?
- Zax
- Člen | 370
@Azathoth Proč bys chtěl dědit konkrétní presenter? Pokud ve dvou
presenterech potřebuješ podobnou funkčnost, tak to jim radši vytvoř
samostatný BasePresenter. Pak máš jistotu, že když změníš konkrétní
presenter, neovlivníš tím ten druhý presenter. Pokud potřebuješ změnit
nějakou společnou funkčnost, sáhneš do toho base. Nevidím nikde potřebu
dědit konkrétní presenter.. A klíčové slovo final
je prostě
jen nástroj pro větší striktnost, který ti dědění zabrání.