Jak dostat DB connection přes DI do modelu?
- Joacim
- Člen | 229
Mám primitivní model:
<?php
namespace App\Presenters;
use Nette,
App\Model;
/**
* Test presenter.
*/
class TestPresenter extends BasePresenter {
public function renderDefault() {
$this->template->test = $this->user->identity->login_id;
$test = new Model\Test();
$this->template->users = $test->test();
}
}
který zařve:
Recoverable Error
Argument 1 passed to App\Model\Test::__construct() must be an instance of Nette\Database\Context, none given
na řádku:
public function __construct(Nette\Database\Context $database)
Tato konstrukce mi funguje ve všech prezenterech, ale v žádném modelu s vyjímkou SecurityManageru.
<?php
namespace App\Model;
use Nette;
class SecurityManager extends Nette\Object implements Nette\Security\IAuthenticator {
/** @var Nette\Database\Context */
private $database;
public function __construct(Nette\Database\Context $database) {
$this->database = $database;
}
}
Může mi někdo objasnit proč tomu tak je a co udělat za nápravu aby mě DB fungovala i v Modelech ?
- David Matějka
- Moderator | 6445
Tu modelovou tridu registrujes jako sluzbu a pak ji injectnes do presenteru, viz doc: https://doc.nette.org/…dependencies a dalsi kapitoly ohledne DI
- chemix
- Nette Core | 1310
nevytvarej v presentru model zpusobem :
$test = new Model\Test();
ale nech si ho tam injectnout pres DI
automaticky. Ono DI se postara o to aby dostal v konstruktoru to oc
si rika
a nezapomen si registrovat model v config.neon v sekci services. viz dokumentace
presenter
...
/**
* @var \App\Model\Test
* @inject
*/
public $testModel;
...
neon
...
services:
- \App\Model\Test
...
- Joacim
- Člen | 229
Přidal jsem do neonu servisu
- \App\Model\Test
testovací presenter vypadá takto:
<?php
namespace App\Presenters;
use Nette,
App\Model;
/**
* Test presenter.
*/
class TestPresenter extends BasePresenter {
/**
* @var \App\Model\Test
* @inject
*/
public $testModel;
public function __construct(\App\Model\Test $testModel) {
$this->testModel = $testModel;
}
public function renderDefault() {
$this->template->users = $this->testModel->test();
}
}
a Base presenter takto:
<?php
namespace App\Presenters;
use Nette;
/**
* Base presenter for all application presenters.
*/
abstract class BasePresenter extends Nette\Application\UI\Presenter {
/** @var settings obj */
protected $settings;
/** @var Nette\Database\Context */
protected $database;
public function __construct(Nette\Database\Context $database) {
$this->database = $database;
}
public function beforeRender() {
parent::beforeRender();
if ($this->getUser()->isLoggedIn()) {
$this->template->userLogged = $this->user->identity->getId();
$this->settings = (object) $this->database->table('settings')->fetchPairs('name', 'value');
$this->template->webName = $this->settings->WEB_NAME;
} else {
$this->template->userLogged = FALSE;
}
}
public function startup() {
parent::startup();
if (!$this->getUser()->isLoggedIn() && ($this->getName() !== "Login")) {
$this->redirect("Login:default");
} else if (!$this->getUser()->isLoggedIn() && !file_exists('../app/config/install.neon') && ($this->getName() !== "Install")) {
$this->redirect("Install:");
}
}
}
a skončí to hláškou:
Fatal Error
Call to a member function table() on a non-object
na řádku:
27: $this->settings = (object) $this->database->table('settings')->fetchPairs('name', 'value');
Předtím něž jsem přidal do neon servicu a DI do prezenteru test bylo vše ok a tuto chybu to nehlásilo a bohužel mi nějako uniká návaznost
- Azathoth
- Člen | 495
@CZechBoY používat u presenterů konstruktorovou injekci je naprosto v pořádku, pokud ten presenter není abstraktní a nic z něj nedědí. A i když je best practise, volat parent::__construct, tak presenter v Nette to nevyžaduje, a pokud nevyžadje zavolání konstruktoru žádný z tvých abstraktních („base“) presenterů, tak volat konstruktor nemusíš.
Ale určitě nepoužejte nikdy konstruktorovou injekci u presenterů, ze kterých dědíte.
Editoval Azathoth (5. 9. 2015 15:25)
- Azathoth
- Člen | 495
@Joacim
to protože nezavoláš konstruktor rodiče. V OOP a Nette to funguje
takhle:
vytvoří se nová instance TestPresenteru, zavolá se jeho konstruktor,
předají se mu závislosti a pak se spustí životní cyklus presenteru.
Jenže už se nezavolá konstruktor BasePresenteru, protože jsi konstruktor přetížil
Takže doporučuji přepsat závislosti v presenterech z konstruktotové injekce na @inject anotace, aby ses tomu problému vyhnul. Nebo alespoň v BasePresenteru to přepiš. V TestPresenteru to můžeš nechat.
Editoval Azathoth (5. 9. 2015 18:05)
- Joacim
- Člen | 229
Ok chápu, takže v base presenteru jsem nastavil DI takto a poté si v každém presevteru již nemusím do konstruktoru volat database, je proti nějakým pravidlům abych měl $database public ?
/** @inject @var Nette\Database\Context */
public $database;
class LoginPresenter extends BasePresenter {
const BAN_TIME = 15; // BAN time in minutes
/* DEPENDENCY INJECTION */
/** @var Nette\Database\Context */
public $database; // ZDE UŽ NEMUSÍ BÝT
Editoval Joacim (5. 9. 2015 20:10)
- Azathoth
- Člen | 495
@Joacim no, sice to je proti pravidlům OOP mít to public, ale v Nette se došlo k závěru, že mít public proměnné a injectovat do nich přes anotaci (jak jsi ukázal), je nakonec asi nejlepší možnost a ostatní varianty (programátorsky čistší řešení) by měly o hodně více kódu v presenterech (inject metody nebo setup metody), tak to všichni injectují takhle, do public property.
- Ondris
- Člen | 37
Myslím že tady dochází k nějakému nepochopení. Podle předchozí
diskuse model nepoužíváš a jen si do presenteru předáváš databázi. Tu
stačí v configu nakonfigurovat jednou a pak předat do všech
presenterů.
Pokud už si začal dělat s modelem, tak se vytvoří jeden „model“,
správný název je repositář, pro každou tabulku, kromě spojovacích. Pak
si do každého repositáře musíš předat databázi, nebo si udělat
BaseRepository, do té předat databázi a další repositáře od ní
dědit.
Do presenteru si předáš jen ty repositáře se kterými budeš pracovat.
Počet modelových tříd s počtem presenterů nijak nesouvisí.
- CZechBoY
- Člen | 3608
@Joacim
základní repozitář, kde budeš mít nějaký obecný metody pro modely
class BaseRepository extends \Nette\Object
{
/**
* @var \Nette\Database\Context
*/
protected $db;
public function __construct (\Nette\Database\Context $db)
{
$this->db = $db;
}
}
potom konkrétní repozitář bude dědit od základní repozitáře
class UserRepository extends BaseRepository
{
public function getUserByEmail ($email)
{
return $this->db->table('user')->where('email', $email);
}
}
a do neonu jako službu zaregistruješ jen ten UserRepository.
- Ondris
- Člen | 37
Přesně tak. Jednoduše se dá říct, že do configu registruješ ty
třídy, který chceš pak předávat jiným třídám.
Čili, do BaseRepository předáváš databázi, musíš ji mít registrovanou.
V nette je to ve speciální sekci.
BaseRepository nikde nepředáváš, jen od něj dědíš, nikde ho nemusíš
registrovat.
UserRepository chceš předávat do presentrů, musíš ho registrovat. Na
repositáře slouží sekce service.
- Joacim
- Člen | 229
Dobře, ale zdá se mi, že co repozitář to tabulka je složitější než mít pro prezenter Galerie repozitář galerie a tam mít jen metody které mi budou vracet to co potřebuji (např fce která vrací data z tabulky galerie, ale bude si šahat i do tabulky permission a joinovat s gallery_set) ? Mám extrémně hodně JOINů (přes 20 tabulek vyjma M:N), pro weby to klidně udělám, jak mi bylo doporučeno, ale pro CMS se mi to zdá dost složité a navíc nevím, jak bych dělal jednotlivé joiny a multi joiny a multiselecty, kdybych měl co repozitář věnován vlastní tabulce(asi jsem to špatně pochopil).
Co jsem tedy pochopil a již tak dělám:
- co repozitář, to registrace v services v neon.config, vyjma base DB repozitáře
Navíc by mě zajímalo zda-li jste už někdo řešil napsání modulu (prezenter, model, templaty), které bych jen nakopíroval do adresáře např Modules/ kde bych měl config.php(bylo by v něm vytvoření příslušných tabulek pro daný modul, nastavení promených prostředí a scripty pro nastavení nette a refistraci repository) a podle něho bych zjistil zda se jedná o nový nebo již nainstalovaný modul
Editoval Joacim (11. 9. 2015 14:26)