Přístup k databázi v modelu (jak to udělat správně?)

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

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

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

Díky za odpověď, v config.neon po doplnění vypadá takto:

services:
	- App\Model\ArticleModel
	- App\Model\UserManager
	- App\RouterFactory
	router: @App\RouterFactory::createRouter

Bohužel výsledkem je stále stejná chyba.

Šaman
Člen | 2666
+
0
-

Ale Oli nepsal nic o configu, doplň ten parent::__construct :)
Pokud ho tam máš, tak někam nasdílej projekt k projití (GitHub, Bitbucket).

jf
Člen | 13
+
0
-

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

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

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

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

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

    }

}

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

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

jf
Člen | 13
+
0
-

Děkuju všem za odpovědi, to je masakr :-)

Snad se tim nějak prokoušu.

MartinitCZ
Člen | 580
+
-4
-

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

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

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

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

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

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

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ů?

Šaman
Člen | 2666
+
+2
-

Presentery by měly být jen abstraktní a finální.

Zax
Člen | 370
+
0
-

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

Š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
+
+5
-

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:

  1. nejprve se pravidlo nauč a používej
  2. pak ho pochop, včetně všech okolností
  3. 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)

Azathoth
Člen | 495
+
0
-

A proč by měly být presrntery finální?

Jan Suchánek
Člen | 404
+
0
-

@Azathoth: A proč by něměli? Dědění není žádná výhra.

Editoval jenicek (3. 11. 2014 17:59)

Azathoth
Člen | 495
+
0
-

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

Jan Suchánek
Člen | 404
+
0
-

@Azathoth Přečti si ten Davidův článek a pochopíš.

Zax
Člen | 370
+
0
-

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