Manuální přidání databáze do debugbaru

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

Ahoj, začínám s Nette (2.0) a potřeboval bych, aby mi někdo poradil, jak přiřadit DB (\Nette\Database\Connection) do debugbaru. Našel jsem na toto téma několik dotazů, které ale řešily odlišný problém.

Vytvářím aplikaci, která se připojuje k několika databázím. Dokud jsem používal databázi jako service, měl jsem pěkně vypsané dotazy v debugbaru. V určité chvíli mi ale service přestalo vyhovovat a vyrvořil jsem si na vytváření projektů a zároveň i připojení do DB pro daný projekt vlastní třídu (singleton). Od té doby už v debugbaru nemám dotazy:(

<?php
class MyProjectLoader {
	/** @var \Nette\Database\Connection $Database */
    	private $Database = false;

	...

	/**
	 * Get projects DB connection
	 * @return \Nette\Database\Connection
	 */
	public function getDatabase() {
		if($this->Database === false) {
			$this->Database = new \Nette\Database\Connection(...);
			// TODO: zde bych rád zapnul ukládání dotazů do debugbaru jestli to lze
		}
		return $this->Database;
	}

	...

}
?>

Potřebuji tedy pro „$this->Database“ nějak spustit logování dotazů do debugbaru. Lze to udělat?

Editoval Swed (12. 5. 2012 17:47)

jtousek
Člen | 951
+
0
-

Z jakého důvodu jsi zavrhl použití service?

Swed
Člen | 11
+
0
-

Především kvůli rozdělení modelu do několika vrstev (Dao, Manager,…) přičemž jsem DB potřeboval až v nejnížší vrstvě. Dále pak kvůli přehlednosti – service jsem nedefinoval v configu, jelikož jsem ještě neznal přístupy do DB, ale až později a navíc jsem připojení k DB používal ve vlastních knihovnách a ne jen v modelu. A také kvuli nešeptávači v Netbeans:), což by možná šlo vyřešit komentářem, ale musel bych ho psát nad každou proměnnou reprezentující DB.

Z vrstev prozatím sešlo, takže bych to mohl vrátit na používání service, ale tak jak to mám teď mi to příjde přehlednější a obecnější, jen mám problém s tím debuggerem. Takže kdyby někdo věděl, jak to vyřešit bez použití service, byl bych moc rád. Pokud to nepůjde, budu muset nejspíš service použít, jelikož debugger je pro mě velká přidaná hodnota.

Editoval Swed (12. 5. 2012 20:48)

jtousek
Člen | 951
+
0
-

Do nižších vrstev bys tu service měl dostat pomocí dependency injection. Tedy taková je alespoň filozofie Nette.

Psal jsi, že s Nette začínáš. Není úplně nejlepší nápad hned od začátku otáčet vzhůru nohama základní postupy. Časem by ses k nim stejně nejspíš sám od sebe zase vrátil. Spíše je lepší je nastudovat, pochopit a trochu se přizpůsobit. ;-)

Swed
Člen | 11
+
0
-

OK, asi se tedy přizpůsobím filozofii Nette, i když je mi proti srsti příjmout, že pokud bych uvažoval o modelu, který má například 5 vrstev, tak bych připojení k databázi předával přes všechny vrstvy (ve kterých připojení vůbec nepotřebuji). To mi příjde šílené, ale pokud chápu správně, tak přizpůsobit se filosofii Nette znamená o takovém modelu ani neuvažovat a držet se toho, jak je to vymyšlené původně. K základní adresářové struktuře Nette projektu se už asi nevrátím:) (mám jednu administraci pro mnoho projektů a potřebuji v tom mít pořádek), ale budu se snažit se méně odlišovat:) Díky za názor, který mě trochu navrátil do reality. Občas je asi potřeba trochu přistřihnout křídla.

jtousek
Člen | 951
+
0
-

Není zač. :-)

Nejsem na DI zrovna odborník, ale nemohl bys tu nejnižší vrstvu modelu mít rovněž jako service, které bys už v config.neon předal odkaz na Nette\Database?

Swed
Člen | 11
+
0
-

Nemohl. Jak jsem již říkal, problém je v tom, že ve chvíli, kdy načítám config.neon, ještě neznám přístupy do DB daného projektu. Ty zjistím až později podle toho, o jaký projekt se jedná.

Nevadí, vrstvy už jsem vzdal. Není to při jednoduchosti tohoto systému potřeba. Horší problém je ten našeptávač v Netbeans (Další z problémů proč jsem zanevřel na service). Nemáš nějaký nápad, jak donutit prostředí, aby napovídalo metody u proměnné reprezentující service? Je to pro mě dost důležité. Pracuji na několika projektech a u každého na jiném frameworku a pamatovat si syntaxi každého z nich je nemožné.

Zkoušel jsem použít komentář a to nepomáhá:

<?php
	/** @var Nette\Database\Connection $Db */
	$Db = $Container->getService('db');
	$Db->...
?>

Způsob, kterým jsem to měl předtím, byl výhodný právě v tom, že prostředí vědělo, jaký objekt se v proměnné nachází a napovídalo mi jeho metody atd.

jtousek
Člen | 951
+
0
-

Bohužel nevím. Tohle mi na Nette taky vadí.

EDIT: Hm moment. Co tohle?

$db = $container->db;
$db->...

Editoval jtousek (13. 5. 2012 13:19)

Swed
Člen | 11
+
0
-

To je moc dobrý nápad! Ale funguje mi to bohužel jen pro service nadefinované v config.neon. Pro service definované až v bootstrapu to nejde:(

<?php
	$Container->addService('Db', function($Container) {
    		return new Nette\Database\Connection(
        		$Container->parameters['dsn'],
        		$Container->parameters['user'],
        		$Container->parameters['password']
    		);
	});
	$Db = $Container->Db;
	$Db->... //Netbeans nenapovídá
?>

Nicméně já v configu Db definuji a z ní pak přečtu přístupy do další DB a vytvářim service s novými přístupy + starou service odstraňuji. Myslíš, že by šlo místo toho harakiri pouze v určité chvíli zkrátka u té service jen změnit přístupové údaje a tím z jednoho připojení udělat jiné, aniž bych staré mazal a nové vytvářel? To by mohlo být finální řešení.

Editoval Swed (14. 5. 2012 0:58)

Swed
Člen | 11
+
0
-

Tak ono to stejně jde jen v bootstrapu:(

<?php
	$db = $container->db;
	$db->...
?>

V presenteru či modelu uz to použít nelze.
V presenteru jde jen $this->getService(‚db‘) které můžu předat modelu:(
Nebo tedy pokud to lze, tak nevím jak.

jtousek
Člen | 951
+
0
-

V presenteru musíš přes kontext. ;-)

$this->getContext()->db->... //tady už by nápověda měla fungovat

Změnit přístupové údaje by možná šlo – Nette\Database spojení myslím vytváří až když je třeba, takže by to mohlo jít.

Nebylo by ale možné mít pro každou aplikaci zvláštní config a v bootstrapu loadovat nejdříve ten společný a potom na základě podmínek nějaký další? Tohle jsem nikdy nepotřeboval ani nezkoušel takže jen hádám, nevím jestli je to možné.

EDIT: Jo aha, ty ty přístupy získáš až dynamicky, tím to samozřejmě padá (leda bys dynamicky vytvářel config).

EDIT2: Nejspíš bys ale mohl v configu vytvořit nějakou dummy service (klidně se špatnými přístupy), v bootstrapu ji odstranit ($container->removeService(‚…‘)) a poté přidat tu správnou. Tuhle dummy service by navíc stačilo vytvářet jen v development režimu.

Editoval jtousek (13. 5. 2012 15:06)

Swed
Člen | 11
+
0
-

Smyslem této aplikace je v administraci vytvařet celé nové weby včetně DB pro ně. Proto mám všechny důležité informace v DB. Možná by šlo generovat pomocí PHP ty configy pro jednotlivé projekty a zachovat striktní oprávnění k adresáři s configy. To už delší dobu zvažuji, ale radši pracuji s DB než se soubory:)

jtousek
Člen | 951
+
0
-

Chápu. Ono to vytváření service dynamicky v bootrapu není problém, jen pak chybí to napovídání jestli tě dobře chápu. V tom případě by v tom development režimu opravdu mělo stačit přidat do configu connection s nefunkčními přístupy a pak ji v bootstrapu nahradit tou správnou. IDE nic nepozná.

petr.pavel
Člen | 535
+
0
-

Napovídání služeb v IDE: Já jsem ošálil Eclipse tím, že jsem do vlastností projektu přidal /temp/cache/_Nette.Configurator jako build path. Chápu, že tohle přímo není řešení pro tebe, ale mohl by sis napsat nějaký IDEHelper.php, který by byl jinak k ničemu (nevolal by se), jen bys ho přidal do build path.

Editoval petr.pavel (13. 5. 2012 16:04)

Swed
Člen | 11
+
0
-

Tak jsem to předělal a používám tedy service. V bootstrapu ji přepíšu a našeptávání už funguje dobře!

Nicméně v debugbaru stále SQL nemám:( Takže jediná změna oproti mému původnímu řešení je, že když volám constructor modelu v presenteru, musím mu předávat připojení k DB:-D. Takže se vracím k původní otázce: Nenapadá někoho jak SQL v debugbaru zapnout?:)

Swed
Člen | 11
+
0
-

Ve fóru jsem našel odkaz na toto: https://doc.nette.org/cs/configuring#…
mohlo by to nějak pomoci, ale nevím jak to zkombinovat s DB v service vytvořené v bootstrapu.

Editoval Swed (13. 5. 2012 23:19)

petr.pavel
Člen | 535
+
0
-

Tohle jsem vyčetl z NetteExtension.php:

			if (!$container->parameters['productionMode'] && $info['debugger']) {
				$panel = $container->addDefinition($this->prefix("database.{$name}ConnectionPanel"))
					->setClass('NDatabasePanel')
					->setAutowired(FALSE)
					->addSetup('$explain', !empty($info['explain']))
					->addSetup('NDebugger::$bar->addPanel(?)', array('@self'));

				$connection->addSetup('$service->onQuery[] = ?', array(array($panel, 'logQuery')));
			}

Třeba tě to nějak inspiruje.

jtousek
Člen | 951
+
0
-

Když se podívám co vygeneruje Nette.Configurator tak je tam tohle. Zřejmě to budeš muset v tom bootstrapu udělat nějak podobně.

	/**
	 * @return Nette\Database\Connection
	 */
	protected function createServiceNette__database__default()
	{
		$service = new Nette\Database\Connection('mysql:host=localhost;dbname=databasename', 'user', 'passwd', NULL);
		$service->setCacheStorage($this->getService('cacheStorage'));
		Nette\Diagnostics\Debugger::$blueScreen->addPanel('Nette\\Database\\Diagnostics\\ConnectionPanel::renderException');
		$service->setDatabaseReflection(new Nette\Database\Reflection\DiscoveredReflection($this->getService('cacheStorage')));
		$service->onQuery[] = array(
			$this->getService('nette.database.defaultConnectionPanel'),
			'logQuery',
		);
		return $service;
	}

EDIT: A ještě tohle bude podstatné:

	/**
	 * @return Nette\Database\Diagnostics\ConnectionPanel
	 */
	protected function createServiceNette__database__defaultConnectionPanel()
	{
		$service = new Nette\Database\Diagnostics\ConnectionPanel;
		$service->explain = TRUE;
		Nette\Diagnostics\Debugger::$bar->addPanel($service);
		return $service;
	}

Editoval jtousek (14. 5. 2012 12:19)

Swed
Člen | 11
+
0
-

Funkce createServiceNette__database__default() a createServiceNette__database__defaultConnectionPanel() jsem včera také objevil, ale nepodařilo se mi bohužel pomocí nich nic změnit. Každopádně Vám oběma děkuji.

Zato jsem zkusil ještě jednu věc, která problém vyřešila. Zkončila ale zajímavou chybou, která mi vrtala hlavou až do půl 3 do rána a vlastně vrtá do teď:) Zkusil jsem se připojit k DB hned za zavoláním konstruktoru Configuratoru a ještě před načtením neonu. Zjistil jsem přístupy do DB a ty jsem pak předal Configuratoru pomocí

<?php
	$Configurator->addParameters($params);
?>

a použil v configu:) Našeptávání funguje, v debugbaru se objevily dotazy, ale aplikace přestala fungovat a začala hlásit chyby při zpracování výsledků dotazů.

Při bližším zkoumání jsem zjistil, že DB funguje správně, ale přestal fungovat přístup, jakým jsem přistupoval k výsledkům dotazů. Je možné, že jsem to předtím používal špatně a teď správně, ale proč to předtím jelo a teď ne? To je mi záhadou. Navíc teď nevím, jak udělat rekurzivní výpis stromu, který jsem měl tak dobře udělaný:(

Zkrátka původně jsem se v jednotlivých řádcích výsledku dotazu mohl pohybovat přes indexy např takto:

<?php
	$Tab = $this->Database->table('menu')->where('active', 1);
	$Tab[0] //object reprezentující 1. řádek
?>

No a to teď nefunguje. Ani když použiju (array) $Tab, tak nedostanu stejný výstup jako před změnami. Vadí mi to víceméně jen u těch rekurzivních algoritmů, ale je to pro mě záhada. Jak je možné, že jednou jde s výsledkem dotazu nějaké třídy pracovat nějak a jindy při použití stejné třídy už ne?

Editoval Swed (14. 5. 2012 22:38)

Swed
Člen | 11
+
0
-

Rekurzivní algoritmy upravyeny. Nefungují sice indexy ale naštěstí funguje toto:

<?php
	$Tab = $Database->table(...);
	$Row1 = $Tab->fetch();
	$Row2 = $Tab->fetch();
?>

Vše je tedy vyřešeno!
Děkuji

jtousek
Člen | 951
+
0
-

To je zvláštní, ten ArrayAccess by měl normálně fungovat. Nevidím žádný důvod proč by v určitém případě fungovat neměl. Neměnils něco v těch dotazech, že pak třeba nevracely žádné výsledky nebo tak něco?

Pokud jde o rekurzi tak v šablonách se používá rekurzivní volání bloků. Mimo šablony pokud to potřebuješ jen projít a zpracovat mi stejně připadá lepší než index do pole používat iterátor – což ti více méně dělá ten fetch.

No hlavně že už to funguje. :-) Tu databázi používáš poměrně specifickým / málo obvyklým způsobem se kterým se zrovna nepočítalo. Samozřejmě, že v tomhle případě bych to asi neřešil jinak, respektive jediná alternativa je jeden centrální uživatel s přístupem ke všem databázím. Předpokládám ale že jsou / časem mohou být rozeseté po různých serverech a v takovém případě by to stejně nešlo.