Předávání dalších služeb do své implementace Application

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

Ahoj,

není takový problém pomocí konfigurace změnit, která konkrétní třída se vytváří při vytváření aplikace (Application). Předá se to proste jako option. Nicméně při vytváření Application se vytváří nový kontainer, do kterého se předávají jen některé služby. Potřeboval bych ale do své implementace Application předat další služby (či možná i celý kontainer). Nebyl by problém vytvořit novou factory metodu, ale v originální factory jsou ještě další věci, které se provádějí či nastavují a mě příjde blbé udělat prostý copy&paste tohoto kódu a následnou úpravu.

Dá se tohle nějak rozumně řešit, anebo je to spíše feature request?

Díky moc

Filip Procházka
Moderator | 4668
+
0
-

To tak prostě je. Protože je to správně.

Jsem si na 87% jistý, že to co chceš udělat, nepotřebuješ udělat v Application.

Takže konkrétně, co potřebuješ udělat? Pokud jsi si jistý, že není zbytí, pak koukni jak vyměňuji Application a dosazuji služby navíc v Kdyby.

arron
Člen | 464
+
0
-

Delas tam presne to, co jsem tu popisoval, ze mi prijde jako prasarna :-D Tohle reseni je ve stavajicim stavu velmi primocare, ale jednak duplikujes kod (coz jak vsichni vime je velmi nedobře) a jednak kdyz se zmeni implementace teto funkce v Nette (napriklad se prida nastavovani nejakeho dalsiho parametru), tak to odhalis jen v pripade, ze sledujes commit logy. Jinak se Ti nejspis aplikace zacne chovat prapodivne z neznameho duvodu:-)

Co potrebuju udelat je ve sve Application zaregistrovat nektere dalsi sluzby, jejich konfiguraci nacitam z databaze. Zaroven tam nacitam z databaze routy. Na oboji by se dala napsat nejaka zvlastni trida a delat to v bootstrapu, ale jedna to tam podle me nepatri, a jednak je to proste soucasti te aplikace, takze poděděni a uprava Application se mi jevi z hlediska navrhu jako nejlepsi volba. Z toho vyplyva, ze potrebuju do sve Application predat minimalne nektere modely pro pristup k datum, ze kterych pak uvedene veci builduju. Nehlede na to, ze bych nové sluzby potreboval pridavat do globalniho containeru. Coz by se asi dalo obejit pomoci Container::$instance…mno asi to nebude uplne nejcistci reseni, ale bude to primerene ciste a funkcni :-) Takze asi zatim vyreseno :-D

Tharos
Člen | 1030
+
0
-

Chlapci, a znáte konstrukci parent::rodicovskaMetoda()? :) Proč neudělat jednoduše:

<?php

class MyConfigurator extends Nette\Configurator
{
	public static function createServiceApplication(Nette\DI\Container $container, array $options = NULL)
	{
		$application = parent::createServiceApplication($container, $options);
		$appContainer = $application->getContext();

		// zde překopíruji do kontextu aplikace libovolné služby, například:
		$appContainer->addService('cacheJournal', function() use ($container) {
			return $container->cacheJournal;
		});

		return $application;
	}
}

A v bootstrapu pak použiji:

$configurator = new MyConfigurator;
$configurator->loadConfig(__DIR__ . '/config.neon');
...
...
Filip Procházka
Moderator | 4668
+
0
-

Nepřipadá mi to jako prasárna.

  1. nejde to jinak
  2. sleduji changelog Nette docela podrobně

To jsem chtěl vědět :) Řešil bych to asi takto nějak. Popř by se dal ještě upravit (čti: podědit a přiohnout) Configurator, aby tuto službu využíval přímo, nikoliv pomocí configu, což by bylo čistější.

services:
	applicationSetupper:
		class: MyApplicationSetup
		arguments: [@connection]
		methods:
			- {'install', [@container]}
		tags: ['run']
class MyApplicationSetup extends Nette\Object
{
	public function __construct($connection) {}
	public function install(Nette\DI\Container $container)
	{
		// služby
		foreach ($this->connection->select('*')->from(...
			$container->addService(...

		// routy
		$router = new RouteList;
		foreach ($this->connection->select('*')->from(...
			$router[] = new Route(...
		$container->removeService('router'); // nutné udělat před vytvořením služby application
		$container->addService('router', $router);
	}
}

Editoval HosipLan (22. 7. 2011 9:26)

Tharos
Člen | 1030
+
0
-

@HosipLan: Zrovna na to koukám. Ale to přece nijak zásadně nevadí, stačí mít svou vlastní MyApplication (kterou autor dotazu podle všeho už beztak má) a použít de facto ještě jednodušší konstrukci:

<?php

class MyConfigurator extends Nette\Configurator
{
	public static function createServiceApplication(Nette\DI\Container $container, array $options = NULL)
	{
		$application = parent::createServiceApplication($container, $options);
		//$application instanceof MyApplication === true

		// MyApplication::addServiceIntoContext je programátorem dopsaná public metoda
		$application->addServiceIntoContext('cacheJournal', function() use ($container) {
			return $container->cacheJournal;
		});
		return $application;
	}
}

Samozřejmě je pak zapotřebí nakonfigurovat aplikaci tak, aby se v továrně vytvářela instance MyApplication.

Přijde mi to jako čisté a stručné řešení bez potřeby opakování kódu.

Editoval Tharos (22. 7. 2011 13:10)

Filip Procházka
Moderator | 4668
+
0
-

Ale tím konfiguruješ kontext služby, po jejím vytvoření, to není moc pěkné.

Co uděláš až se někdo rozhodne zavolat $application->context->freeze()?

Tohle je dost spekulativní a je to v podstatě jedno… Podle mě je nejjednodušší to zkopírovat a upravit. Co použiješ ty, je na tobě.

Tharos
Člen | 1030
+
0
-

$application->context->freeze() ale právě díky protected nikdo zvenčí nezavolá.

Aby to mé řešení přestalo fungovat, musel by kontext zmrazit vyloženě David v nějaké další verzi Nette ve výchozí createServiceApplication. Jiné (v úvahu připadající) nebezpečí tam není.

Plus mám výhradu k té „konfiguraci služby po jejím vytvoření“. :) Já ten kontext nekonfiguruji až po vytvoření služby. Službu lze IMHO považovat za vytvořenou až v momentě, kdy ji vrátí tovární metoda (hlavně pak v Nette, kde se to setter injections jen hemží).

Jsem toho názoru, že mezi vytvoření služby a vytvoření instance, která službu ve výsledku reprezentuje, nepatří rovnítko.

Ale necháme to plavat, handrkovali bychom se kvůli blbostem. :)

Editoval Tharos (22. 7. 2011 13:09)

Filip Procházka
Moderator | 4668
+
0
-

Ale zavolá

$context = new Container;
$context->addService(..
$context->freeze()

return new Application($context);
Tharos
Člen | 1030
+
0
-

Tohle je ale něco úplně jiného, než $application->context->freeze(). Tohle v první řadě není zvenčí (psal jsem zvenčí, čímž jsem samozřejmě myslel mimo továrničku a mimo třídu Application). Tohle nepovažuji za nebezpečí v úvahu připadající :) (proč by to někdo dělal? Tohle přece nelze udělat ani v dobré víře). Proč by někdo psal následující továrničku?

public static function createServiceApplication(Nette\DI\Container $container, array $options = NULL)
{
	$application = parent::createServiceApplication($container, $options);
	$context = new Container;
	//$context->addService(...);
	$context->freeze();
	return new Application($context);
}

Podobnými postupy může pochopitelně jakýkoliv programátor kdykoliv shodit celý běh aplikace, v tom máš samozřejmě pravdu. :)

Editoval Tharos (22. 7. 2011 14:02)

arron
Člen | 464
+
0
-

Tharos napsal(a):

Chlapci, a znáte konstrukci parent::rodicovskaMetoda()? :) Proč neudělat jednoduše:

I to me samozrejme napadlo…co se mi na tom nelibi je to, ze v te parent metode by se uplne zbytecne vytvarel context pro aplikaci, ktery bych nasledne vymenil.

HosipLan napsal(a):

Řešil bych to asi takto nějak. Popř by se dal ještě upravit (čti: podědit a přiohnout) Configurator, aby tuto službu využíval přímo, nikoliv pomocí configu, což by bylo čistější.

Ja si prave nejsem jisty, jestli to ma byt takhle oddelene. Priprava prostedi aplikace (routy, servisy), uplne stejne jako konfigurace konkretniho presenteru, patri do tridy Application. Kde bych pak ten seupper volal? V bootstrapu? Mam takovy pocit, ze to proste do toho bootstrapu nepatri :-)

Tharos
Člen | 1030
+
0
-

@arron: V té mé verzi se ale ten kontext nezahazuje, jen se zpřístupňuje pro přidávání služeb zvenčí a ty se pak do něj v té továrničce z hlavního kontejneru předávají. Ale už se v tomto vláknu odmlčuji, protože mé řešení nebylo shledáno hezkým :).

Editoval Tharos (22. 7. 2011 22:05)

arron
Člen | 464
+
0
-

Tharos napsal(a):

@arron: V té mé verzi se ale ten kontext nezahazuje, jen se zpřístupňuje pro přidávání služeb zvenčí a ty se pak do něj v té továrničce z hlavního kontejneru předávají. Ale už se v tomto vláknu odmlčuji, protože mé řešení nebylo shledáno hezkým :).

Oooou…sorry, uz jsem na to vecer, po 13 hodinach prace, nevidel…jop, to by (nebyt toho protected) bylo velmi solidni reseni problemu :-) A vzhledem k tomu, že si do te tridy muzu opravdu pridat co chci, tak to tak nakonec asi i udelam. Neni to mozna uplne 100%, ale ze vsech navrzenych reseni se mi to asi libi nejvic :-) Super, diky moc.

Editoval arron (23. 7. 2011 3:36)

arron
Člen | 464
+
0
-

Mno, tak jsem se k tomuhle problemu zase vratil. Cele navrzene reseni je sice hrozne pekne, ale chybi tomu jedna malinkata drobnost:-) Kde reknu, ze misto ´Nette\Application\Application` chci vytvorit napr. My\Application, ha?

Protoze mit v configu tohle:

services:
    application:
      options:
        class: \My\Application

nema sanci probehnout, protoze sluzby se v Configuratoru vytvareji jeste pred nacteni configu :-)

smasty
Člen | 90
+
0
-

arron:

Jednoducho vytvoríš vlastný Configurator.

arron
Člen | 464
+
0
-

Zkusím přeformulovat otázku:-)

Co mám napsat do config.neon, aby se mi vytvorila Application jako instance tridy MyApplication, ktera od té původní dědí?

hAssassin
Člen | 293
+
0
-

@arron > mam to takhle a vali to (ale stahni si novou betu z 4. srpna):

application: # sluzba pro vlastni aplikaci
	class: MyApplication
	arguments: ['@container']
arron
Člen | 464
+
0
-

Tak ja mam o neco starsi…vyzkousim, diky moc

arron
Člen | 464
+
0
-

Tak jsem zjistil, ze mezi moji verzi Nette a tou nejnovjější se okolo tohoto vubec nic nestalo. Takže to stále nefunguje a řekl bych, že díky tomu, co se děje okolo tohoto řádku ve třídě Configurator, tak to ani fungovat nemůže (pokud jsem tedy něco v kódu nepřehlédl). Takže otázka stále zůstává.

Tharos
Člen | 1030
+
0
-

Mělo by Ti pomoci následující nastavení v config.neonu:

services:
	application:
		option: [class: MyApplication]

Pokud jde skutečně jen o použití vlastní třídy odvozené od Application, není vůbec zapotřebí vyrábět si kvůli tomu vlastní konfigurátor či provádět nějaké jiné čáry máry :).


EDIT: Teď ale koukám, že přesně tohle řešení už jsi zmínil. V čem konkrétně je problém? :) Vyzkoušel jsem to pro jistotu v sandboxu a samozřejmě to funguje.


EDIT 2: Už možná tuším, v čem je problém. Pozor na to, že v konfigurátoru se v té části, na kterou odkazuješ, neinstancují (fuj, hrozné slovo) služby, nýbrž se pouze do kontejneru registrují callbacky pro jejich instanciování (nevíte někdo, jak se tahle hrůza skloňuje?). Tyto „tovární metody“ přijímají parametry, které lze dospecifikovat až v config.neonu. Proto není k jejich definici „až“ v config.neonu pozdě. Tohle by mělo být to nedorozumění. Instance té Application třídy se vytváří skutečně až v bootstrapu někde na konci, tzn. v době, kdy kontejner už v sobě má patřičnou tovární metodu (používající třídu konfigurátor) a zná i její parametry nadefinované v config.neonu (a skrze ně ví, jakou třídu má vlastně instanciovat).

Editoval Tharos (31. 8. 2011 23:47)

arron
Člen | 464
+
0
-

Tharos wrote:
Pokud jde skutečně jen o použití vlastní třídy odvozené od Application, není vůbec zapotřebí vyrábět si kvůli tomu vlastní konfigurátor či provádět nějaké jiné čáry máry :).

Zaroven tam pridavam nejake dalsi sluzby, ktere vyuzivam pri inicializaci application.

EDIT: Teď ale koukám, že přesně tohle řešení už jsi zmínil. V čem konkrétně je problém? :) Vyzkoušel jsem to pro jistotu v sandboxu a samozřejmě to funguje.

To je zajimave, protoze me se to te funkce, ktera vytvari Application nepredaji ty parametry, ktere uvedu v configu. Zkusim pohledat, kde presne se tam maji predavat a pak uz to snad nejak odladim…

EDIT 2: Už možná tuším, v čem je problém. Pozor na to, že v konfigurátoru se v té části, na kterou odkazuješ, neinstancují (fuj, hrozné slovo) služby, nýbrž se pouze do kontejneru registrují callbacky pro jejich instanciování (nevíte někdo, jak se tahle hrůza skloňuje?). Tyto „tovární metody“ přijímají parametry, které lze dospecifikovat až v config.neonu. Proto není k jejich definici „až“ v config.neonu pozdě. Tohle by mělo být to nedorozumění. Instance té Application třídy se vytváří skutečně až v bootstrapu někde na konci, tzn. v době, kdy kontejner už v sobě má patřičnou tovární metodu (používající třídu konfigurátor) a zná i její parametry nadefinované v config.neonu (a skrze ně ví, jakou třídu má vlastně instanciovat).

To ja vim, ze se tam v tom miste primo neinstancuji, jen se pridavaji do containeru. Ten to pak kompiluje do cache. A v te cache to prave je bez parametru (logicky, kdyz vemu v potaz umisteni toho kodu) a nenasel jsem misto, kde by se tomu pri vytvareni instance mely predavat ty parametry.

Tharos
Člen | 1030
+
0
-

Já u sebe parametr, který jsem předal pomocí config.neonu, zkompilovaný v cache mám:

// výňatek z cache _Nette.Configurator

$container->addService('application', function($container) {
	$service = call_user_func(
		array ( 0 => 'Nette\\Configurator', 1 => 'createServiceapplication', ),
		$container,
		array ( 'class' => 'MyApplication', )
	);
	return $service;
}, NULL);

Že by došlo na obligátní radu „zkus vyčistit cache“? :)

arron
Člen | 464
+
0
-

Ne ne, cache to neni :-)

Zahada vyresena…najdi rozdil:

application:
      options:
        class: \My\Application

a

application:
      option:
        class: \My\Application

(to druhe funguje:-))

S timhle by bylo potreba neco udelat, protoze si nemyslim, ze bych byl jediny, kdo si nabehne:-)

To co jsi psal v prispevku #18, tak sice preda spravne parametry, ale nezavola se funkce z Configuratoru, takze se do Application nedostanou vsechny potrebne sluzby a bylo by potreba to delat rucne…

Nicméně díky za pomoc, navedlo mě to kde mam hledat ;-)

Aurielle
Člen | 1281
+
0
-

Option a options je častá chyba, nicméně když jsem navrhoval sjednocení (jako u variable(s) nebo service(s)), tak to vypadá, že option bude do budoucna odstraněno.

juzna.cz
Člen | 248
+
0
-

HosipLan napsal(a):

To tak prostě je. Protože je to správně.

Proc ma aplikace jiny kontext, nez pak maji presentery? Nemas pripadne nejaky link nekam, kde je to vysvetlee? Me to prijde docela matouci.
(A ze to tak je, protoze je to spravne, je nesmysl a ne duvod.)

Vojtěch Dobeš
Gold Partner | 1316
+
0
-

$context je pouze DI\Container. Application jej používá mám dojem čistě z pohodlnosti, není třeba předávat všechny potřebné služby ručně. K takovému hromadnému předání je použit DI\Container, ale není to ten systémový (který používá i klientský kód), protože je zbytečné ho do Application předávat, když je jasné, které služby Application potřebuje. Vysvětlil jsem to srozumitelně?

(například nemá smysl do Application předávat DI\Container se službou dibi, kterou si někdo nadefinuje v config.neon)

Editoval vojtech.dobes (7. 12. 2011 14:29)

Filip Procházka
Moderator | 4668
+
0
-

Ten context tam byl, protože se to dříve zdálo jako dobré řešení. Když se podíváte na aktuální dev, tak zjistíte, že už je dávno fuč, což je úplně nejlepší :)