Jak dostat DB connection přes DI do modelu?

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Joacim
Člen | 229
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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

CZechBoY
Člen | 3608
+
0
-

Databázi si nedavej do presenteru – předek si model pres injecty.
Konstruktor presenteru nepoužívej a když už, tak volej parent::__construct

Joacim
Člen | 229
+
0
-

A existuje i prozatímní řešení ? Jelikož nemám čas přepisovat celé cms zatím

Azathoth
Člen | 495
+
0
-

@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)

Joacim
Člen | 229
+
0
-
$this->database

má hodnotu NULL poté co jsem přidal servisu, nevím proč

Azathoth
Člen | 495
+
0
-

@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
+
0
-

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
+
0
-

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

Joacim
Člen | 229
+
0
-

@Azathoth – Díky moc, už v Nette začínám mít pořádek, zprvu jsem nevěděl co a jak, ale tohle forum je skutečně silná pomoc, máš můj velký dík

Editoval Joacim (5. 9. 2015 21:16)

Joacim
Člen | 229
+
0
-

Když mám např v cms min 10 presenterů mám si vytvořit i 10 modelů a každý registrovat v config neon jen kvůli DB DI connection, který v těchto modelech budu využívat nebo je i jiný způsob, abych neměl neon.config zaplněn od vrchu až dolů ?

Ondris
Člen | 37
+
0
-

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

Joacim
Člen | 229
+
0
-

Lze si vytvořit abstraktní DB model (repozitář), ten zaregistrovat(nevím jestli mi registrace v neon vytváří instanci, tudíž by to nešlo nebo se chová jinak) v neon.config a pak od něj budou dědit všechny modely s DB query ?

CZechBoY
Člen | 3608
+
+3
-

@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
+
0
-

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
+
0
-

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)