Nette Dependency Injection – Konečně zde! Opravdu!

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Filip Procházka
Moderator | 4668
+
0
-

Nette Dependency Injection

Repozitář: https://github.com/…Lan/Nette-DI
Ukázka ke stažení: http://di.kdyby.org/…-example.zip

Zdravím,
inspirován Honzovým článkem, jsem se rozhodl implementovat to o čem píše. Koukl jsem do Nette\Application\Application a zjistil, že by to šlo pěkně napsat, bez jediného zásahu do Nette.

Nakolik jsem uspěl a nakolik je to čisté posuďte sami. Jakékoliv nápady a bugfixy uvítám.

PS: Honzo, kdyby mi tam někde chyběli @author tak dej vědět :)

Instalace:

Stáhnout a nakopírovat https://github.com/…Lan/Nette-DI do LIBS_DIR

nastavit config.ini

service.Nette-Application-Application.option.class = NetteDI\Application\Application
service.Nette-Application-IPresenterLoader = NetteDI\Application\PresenterLoader

Vytvořím si třídu Configurator, která bude vycházet z Nette\Configurator a bude přidávat metodu createServiceContainer

class Configurator extends Nette\Configurator
{

	/**
	 * @return \NetteDI\Service\IServiceContainer
	 */
	public static function createServiceContainer()
	{
		$services = array(
			'ISmartService' => array(
				'class' => 'SmartService',
				'methodCall' => array(
					'setTicklish' => array(TRUE)
				),
				'aliases' => array('Smart'),
				'autowire' => TRUE,
			),
			'IDummyService' => array(
				'factory' => "DummyService::createDummyService",
				'arguments' => array(
						'ticklish' => TRUE
					),
				'aliases' => array('Dummy'),
			)
		);

		return NetteDI\Configurator::createServiceContainer($services);
	}

}

Teď ji musím v bootstrap.php vnutit do Nette\Environment

require_once APP_DIR . '/models/Configurator.php';
Environment::setConfigurator(new Configurator());
Environment::loadConfig();

Třídy DummyService a SmartService jsou přibalené v ukázce

Použití?

V Configurator je ukázka „sexy“ konfigrace služeb, je možné jim vnutit argumenty, je možné jim pomocí autowire předat automaticky služby, které vyžadují v konstruktoru, je možné volat jejich metody, hned po inicializaci.

Argumenty, které NetteDI\Service\ServiceLoader rozpozná

  • %nazev\sluzby – argument s prefixem % se přeloží na instanci konkrétní služby
  • E$wwwDir – tohle, se přiznávám, jsem moc netestovat, ale snad by to mělo prohnat přes Nette\Environment::getVariable('wwwDir')
  • C$database – tohle, se přiznávám, jsem taky moc netestovat, ale snad by to mělo prohnat přes Nette\Environment::getConfig('database')

Co se týče služeb, není to nic nového, Honza Marek už to implementoval, já mu to jenom trochu překroutil. Ale co tohle přidává je DI do Presenterů. Takže pokud váš BasePresenter bude vycházet z NetteDI\Application\Presenter

abstract class BasePresenter extends \NetteDI\Application\Presenter
{
// ...

budou mu AUTOMATICKY předávány v konstruktoru služby

class HomepagePresenter extends BasePresenter
{

	/** @var DummyService */
	protected $dummy;

	/** @var SmartService */
	protected $smart;


	/**
	 * @param IDummyService $dummy
	 * @param ISmartService $smart
	 */
	public function __construct(IDummyService $dummy, ISmartService $smart)
	{
		$this->dummy = $dummy;
		$this->smart = $smart;
	}
// ...

Zatím to vyžaduje, aby se název služby a název implementovaného interface a vyžadovaného interface v konstrutoru shodovali. Jestli je to špatně, to nevím, ale přemýšlel jsem o automatickém aliasování, pro případ kdy bych volal službu, jejíž třída se jmenuje jinak, než jak je pojmenovaná v ServiceContainer, ale zase když jsou tu ty Interfaces, tak s nimi mi to přijde čistší.

Samozřejmě startup ani žádné další funkce by to ovlivnit nemělo.

Otázkou je, kde se to ještě hodí? Má význam to dělat pro Control?

PS: zítra zkusím doplnit více informací

Editoval HosipLan (22. 1. 2011 20:45)

Honza Marek
Člen | 1664
+
0
-

Super. To, že DI půjde implementovat do Nette bezzásahově, je výborná zpráva. Přiznám se, že zneužít interface Nette\Application\IPresenter pro to, čemu říkám PresenterFactory, mě nenapadlo.

Přimlouval bych se ale za podrobnější popis toho tvého řešení. Koukal jsem se do zdrojáku a občas trochu tápu, co se týče rozdělení odpovědností jednotlivých třídy. Třeba mi není jasná role třídy ServiceLoader ve vztah ke třídě ServiceContainer.

Pokud jsem to dobře pochopil, tak po službách je také vyžadováno rozhranní NetteDI\Service\IService. To by byl hrubý omyl z toho důvodu, že službám přece nemůžeš cpát jak mají vypadat. Zejména službám, které nevyrábíš sám, např. Nette\Web\IUser a další. Pokud je NetteDI\Service\IService zamýšleno jen pro objekty, kterým je možné předat kontejner, tak mi to také přijde zbytečné. Je přece jedno, jestli se kontejner dostane do objektu přes metodu setContainer nebo třeba přes konstruktor.

Filip Procházka
Moderator | 4668
+
0
-

V tom případě se pustím do úprav, abych pravdu řekl, moc jsem tomu navrhování nedal, chtěl jsem hlavně dokázat, že to funguje :) teď se to může poladit :)

jantichy
Člen | 24
+
0
-

Tohle drbání se pravou rukou za levým uchem s vytvářením presenteru se mi vůbec nelíbí, takže jsem Davidovi poslal pull request, který zavádí regulérní oficiální tovární třídu pro vytváření instance presenteru. Takže pak stačí jen implementovat vlastní IPresenterFactory. Viz https://github.com/…tte/pull/191

Patrik Votoček
Člen | 2221
+
0
-

Chtělo by to v tom Pull requestu fixnout ten bug… :-)

Filip Procházka
Moderator | 4668
+
0
-

Super super :) teď hlavně, aby to David pullnul :)