Krasný index.php s vylepšeným DI Containerem
- Filip Procházka
- Moderator | 4668
Štval mě „zbytečný“ soubor app/boostrap.php
, takže jsem
ho z projektu úplně vyhodil a můj www/index.php
nyní
vypadá takto
<?php
// load bootstrap file
require_once __DIR__ . '/../app/autoload.php';
// Run the application
$app = new Kdyby\Application\Application();
$app->run();
Cože?!! Jak se mu to povedlo? To chci taky!!
Nejprve upravit Application
Zjednodušená verze:
class Application extends Nette\Application\Application
{
public function __construct()
{
$configurator = new Kdyby\Config\Configurator();
// inject application instance
$container = $configurator->getContainer();
$container->configureService('application', $this);
// dependencies
parent::__construct(
$container->nette->presenterFactory,
$container->router,
$container->httpRequest,
$container->httpResponse,
$container->session
);
}
}
Tedy to, co Nette sandbox dělá v app/boostrap.php
já mám
v konstruktoru Application
Autoloading
Celý app/boostrap.php
jsem osekal pouze na načítání tříd
a přejmenoval ho na app/autoload.php
. Můžu ho tedy využít
i v testech i v aplikaci a navzájem si to nic nenastavuje, jenom nastaví
PSR-0
autoloadery a možná
(magicky) RobotLoader.
Container::configureService()
Nojo, ale když vytvoříš instanci mimo container, tak ji nemůžeš Containerem nastavit!
Vskutku nepříjemná situace, ale Kdyby ji řeší velice jednoduše. Do vlastního rozšíření pro kontejner si implementujeme následující metodu
/**
* @param \Nette\Utils\PhpGenerator\ClassType $class
*/
public function afterCompile(Code\ClassType $class)
{
$container = $this->getContainerBuilder();
/** @var \Nette\DI\ServiceDefinition $def */
foreach ($container->getDefinitions() as $name => $def) {
if ($def->class == 'Nette\DI\NestedAccessor' || $def->class === 'Nette\Callback' || $name === 'container' || !$def->shared) {
continue;
}
$createBody = $class->methods[Nette\DI\Container::getMethodName($name)]->body;
if ($lines = Nette\Utils\Strings::split($createBody, '~;[\n\r]*~mi')) {
array_shift($lines); // naive: first line is creation
$configure = $class->addMethod('configure' . ucfirst(strtr($name, '.', '_')));
$configure->visibility = 'private';
$configure->addParameter('service')->typeHint = $def->class;
$configure->setBody(implode(";\n", $lines));
}
}
$configure = $class->addMethod('configureService');
$configure->addParameter('name');
$configure->addParameter('service');
$configure->setBody(
'$this->{"configure" . ucfirst(strtr($name, ".", "_"))}($service);' . "\n" .
'if ($this->hasService($name)) {' . "\n" .
' $this->removeService($name);' . "\n" .
'}' . "\n" .
'$this->addService($name, $service);'
);
}
Voala! Jakoukoliv službu si nyní můžu vytvořit sám, zavolat nad ní
$container->configureService()
a on mi ji nastaví podle toho,
co mám v configu (kromě parametrů konstruktoru, logicky) a vloží do
containeru.
Když nepočítám čas na psaní příspěvku, tak jsem ji napsal před 10ti minutami.
A úplně nejlepší by bylo, kdyby Davídek tohle přijal jako výzvu a implementovat to přímo do Nette a mnohem elegantněji :)
- David Grudl
- Nette Core | 8227
Třeba takto by mělo jít volat
$configuredAbc = $container->createConfigureAbc($abc);
factories:
configureAbc:
class: Authenticator
parameters: [service]
factory: ?(%service%)
- Filip Procházka
- Moderator | 4668
@**David Grudl**: Přemýšlel jsem nad tím, že si to budu nějak generovat pomocí services, ale pak mě od toho odradilo, že bych si „zaneřádil“ DIC (a hlavně panel).
Editoval HosipLan (9. 2. 2012 23:41)
- Aurielle
- Člen | 1281
Mnohem pěknější řešení než moje naprasené, které ale dalo podnět k řešení problému. Což nic nemění na tom, že se mi tenhle způsob bootstrapování moc a moc líbí :)
- hrach
- Člen | 1838
Do praxe imo moc nepouzitelne. Osobne mam 3 bootstrapy.
- bootstrap.php includujici nette, nastavujici debugger, atp.
- bootstrap_web.php – includuje bootstrap.php, pridava routing a spousti aplikaci
- bootstrap_cli.php – includuje bootstrap.php, pridava routing cli verze, detekuje server, nastavuje jinak debugovani, spousti aplikaci.
Bootstrap.php pak používám pro jednorázové skripty – k nezaplacení. S tím tvým indexem se pokaždé dostanu do aplikace … možná to takhle bude fungovat pro blog, cms …
- Filip Procházka
- Moderator | 4668
Do aplikace mám tři vstupy:
www/index.php
– viz výše, prostě vstup pro uživatele (přes prohlížeč)$ app/console
– php script, který místoKdyby\Application\Application
spouští úplně stejněKdyby\Console\Application
$ phpunit
– přesphpunit.xml
se načte soubortests/boostrap.php
, který nezapne aplikaci, ale jen vytvoří configurator a zpřístupní container staticky.
Všechny tři využívají app/autoload.php
a navzájem si
nepřekáží. Kde je problém?
Můj boostrap_cli
by tedy byl pouze vyčleněním 4 (slovy
čtyř) řádků do samostatného souboru :)
Mně to tedy připadá velice použitelné.
Editoval HosipLan (10. 2. 2012 13:11)
- ptacek.pavel
- Člen | 27
Osobně jsem ze stejného důvodu vyhodil z bootstrap $application->run() a volám ho v index.php . Moje pointa je taková, že pokud vyrábím „servisní skript“ který (z jakéhokoliv důvodu) nechci zabalovat do presenteru (ConsoleModule\NecoPresenter) tak si hodím include na bootstrap a mám nastaveno vše, co potřebuju.
ad $ php index.php module:presenter:action
resp. tvoje
app/console
– imho zbytečné rozdělovat, když stačí přidat
CliRouter() ne?
Jinak problém tří bootstrapů řeším momentálně takto:
<?php
// Load default config + configs per developer machine --> with graceful degradation
$configurator->addConfig(__DIR__ . '/config/config.neon');
if($_SERVER['SERVER_NAME'] == 'someproject.com') { // live hosting
$configurator->addConfig(__DIR__ . '/config/live.neon');
\Nette\Diagnostics\Debugger::enable('85.13.110.131');
}
elseif($_SERVER['SERVER_NAME'] == 'someproject.animalgroup.cz') { // stage hosting
$configurator->addConfig(__DIR__ . '/config/stage.neon');
\Nette\Diagnostics\Debugger::enable(\Nette\Diagnostics\Debugger::DEVELOPMENT);
}
elseif($_SERVER['SERVER_NAME'] == 'someproject.ourci.tld') { // pre-staging environment, it's the one with git installation
$configurator->addConfig(__DIR__ . '/config/ourci.neon');
\Nette\Diagnostics\Debugger::enable(\Nette\Diagnostics\Debugger::DEVELOPMENT);
}
elseif($_SERVER['HTTP_HOST'] == 'someproject.ag') { // foglcz's machine
$configurator->addConfig(__DIR__ . '/config/foglcz.neon');
}
elseif($_SERVER['SERVER_ADMIN'] == 'admin@tomas-cap-pc.RD') { // capino's machine
$configurator->addConfig(__DIR__ . '/config/capino.neon');
}
?>
-- to je kvůli tomu, že je z principu live/stage/dev, kdy stage = náhled pro klienta a dev = všechny mašiny všech vývojářů (neboť to neděláme na devel serveru, že.)
- Filip Procházka
- Moderator | 4668
Já ale vůbec nepoužívám CliRouter
a ani ho používat
nechci. Daleko více mi vyhovuje Symfony konzole.
- ptacek.pavel
- Člen | 27
Hosiplan: Chápu tedy, že moje načítání configů bych
přesunul do obou konstruktorů application? Resp. udělal Application Abstract,
od kterého by dědila Application
a
Console\Application
? Nechci totiž volat tvoje
Kdyby\(Console|Application)\Application
(logicky.)
Pak bych měl problém s tím, že abstract neudělám (dependency hell) a
tím pádem config loading budu mít ve dvou konstruktorech metodikou ctrl+c a
ctrl+v, nebo bych to zabalil do nějaké statické metody
(Application::loadConfigs($configurator)
)
Hrozně bych chtěl mít jeden entry point, který mi vrátí
$application
, spíše ale $container
– což může
právě bootstrap.php . Schovávat to do konstruktoru aplikace mi přijde
nešikovné..
edit: všechno ostatní, co potřebuju, můžu nastavit v .neon souborech.. Routování, konekce, služby, .. Můj bootstrap by tedy víceméně mohl jen načíst config a vrátit container – v indexu pak:
<?php
$container = require '../app/bootstrap.php';
$container->application->run();
?>
Zatímco v servisním skriptu:
<?php
$container = require '../../app/bootstrap.php';
$dibi = $container->dibi;
// etc.
?>
Editoval ptacek.pavel (12. 2. 2012 14:56)
- Filip Procházka
- Moderator | 4668
Já mám Nettí Configurator obalený do svého Configuratoru a s tím pracuje moje Application.
Tam bych tedy měl logiku načítání těch tvých configu. Pokud Application nechci, zavolám jen něco jako
require_once __DIR__ . '/../app/autoload.php';
$config = new Kdyby\Config\Configurator;
$container = $config->getContainer();
a píšu script ;) Ať žije kompozice! :P
Editoval HosipLan (12. 2. 2012 14:53)
- ptacek.pavel
- Člen | 27
HosipLan: jsem ještě doeditoval last post – sorry :-)
… každopádně pointa těch configů je, že na každém projektu dělají
jiní lidé – tzn. sice všude je
(config|live|stage|ourci).neon
, ale na některých projektech je
inject pouze capino.neon a někde jenom foglcz.neon někde je obojí etc –
nejde/nechci to dělat univerzálně v libs/Kdyby/Configurator.php –
potřebuju to někde v app.
Nakonec mi přijde, že je to spíš o názvosloví – ty tomu říkáš autoload, já bootstrap což mi přijde logičtější kór když by to jenom vrátilo container :-)
- Filip Procházka
- Moderator | 4668
Můj autoload nastavuje načítání tříd. Nic víc. Tedy to není problém v názvosloví :)
Co se týče těch configů… To je otázka implementace, třídy nejsou final od toho, aby se dědily. Tohle moje je obecné a funkční. Pokud potřebuji něco specifického pro projekt, tak si to prostě upravím.
Hledáte problémy, tam kde žádné nejsou ;)
- juzna.cz
- Člen | 248
Obecne mi index.php
a bootstrap.php
tak jak jsou
v sandboxu/skeletonu prijde takovy nelogicky. Jaky smysl toto
rozdeleni vlastne ma?
Duvod: casta je potreba vice vstupnich bodu do aplikace. Mam to presne tak, jak rikaji @hrach a @hosiplan: 1/ web 2/ konzole 3/ testy. Ve vsech 3 potrebuji mit
Problem: bootstrap neni mozne pouzit samostatne, protoze mu chybi konstanty urcujici cesty k aplikaci (WWW_DIR, APP_DIR, LIBS_DIR) a stejne tak neni znovupouzitelny, protoze okamzite spousti aplikaci. Prijde mi tedy jako takovy jediny vstupni bod do aplikace, kteremu ale neco chybi (to ty cesty).
Reseni od @hosiplan se mi zase moc nelibi, protoze vyzaduje moc kodu „nad Nette“. Mam radeji lehci reseni.
Nejlogictejsi mi prijde reseni, ktere tady zminuje @hrach, kde vsak drobitko zlobi nazvoslovi. bootloader znaci, ze natahuje a spousti aplikaci, coz u nej nedela (lepsi nazev me ale momentalne nenapada).
Jednoduche reseni by znamenalo pouze trosku zamichat index a bootstrap:
- bootstrap by byl samostatny a znovupouzitelny – obsahoval by tedy definice vsech cest, ale primo aplikaci uz by nespoustel. O to by se staral index…
- index by pouze nacetl bootstrap a spustil aplikaci (2 radky);
- konzole jako druhy vstupni bod by se velice podobala indexu – nacetla by bootstrap a spustila si dvoji aplikaci (at uz Nette s CliRouterem, nebo Symfony; opet 2 radky);
- testy by si nacetly bootstrap a delaly si svoje testovani;
- abcxyz neboli jakykoliv dalsi skript/daemon by si nacetl bootstrap a delal by si svoje, treba zustal bezet a komunikoval by si po siti, sledoval servery a tak obecne co by se mu zachtelo.
Rozsirene reseni popsal @hrach, stejne jako predchozi ale vytahuje z bootstrapu cast pro web a pro cli.
- nanuqcz
- Člen | 822
juzna.cz napsal(a):
- bootstrap by byl samostatny a znovupouzitelny – obsahoval by tedy definice vsech cest, ale primo aplikaci uz by nespoustel. O to by se staral index…
- index by pouze nacetl bootstrap a spustil aplikaci (2 radky);
- konzole jako druhy vstupni bod by se velice podobala indexu – nacetla by bootstrap a spustila si dvoji aplikaci (at uz Nette s CliRouterem, nebo Symfony; opet 2 radky);
- testy by si nacetly bootstrap a delaly si svoje testovani;
- abcxyz neboli jakykoliv dalsi skript/daemon by si nacetl bootstrap a delal by si svoje, treba zustal bezet a komunikoval by si po siti, sledoval servery a tak obecne co by se mu zachtelo.
+1. Konečně vidím v rozdělení index/bootstrap nějaký smysl :-)
- pekelnik
- Člen | 462
Asi takhle:
Poznámka: boostrap.php je přímo v root adresáři.
/bootstrap.php
<?php
// 1. Setup parameters
$p['baseDir'] = $base = __DIR__;
$p['config'] = "$base/configs/config.neon";
$p['appsDir'] = "$base/apps";
$p['libsDir'] = "$base/libs";
$p['tempDir'] = "$base/var/temp";
$p['logDir'] = "$base/var/logs";
$p['wwwDir'] = "$base/www";
// 2. Load framework
require $p['libsDir'] . '/Nette/loader.php';
// 3. Create configurator
$configurator = new Configurator();
$configurator->addParameters($p);
$configurator->setTempDirectory($p['tempDir']);
// 4. Enable debugger
$configurator->enableDebugger($p['logDir']);
// 5. Create class autoloader
$configurator->createRobotLoader()
->addDirectory($p['appsDir'])
->addDirectory($p['libsDir'])
->register();
// 6. Create environment container
$configurator->addConfig($p['config'], FALSE);
$container = $configurator->createContainer();
/www/index.php
<?php
require __DIR__ . '/../bootstrap.php';
$container->application->run();
/console
#!/usr/bin/php
<?php
require __DIR__ . '/bootstrap.php';
$container->console->run();
/tests
#!/usr/bin/php
<?php
require __DIR__ . '/bootstrap.php';
$container->tests->run();
/scripts/some-quick-and-dirty-script.php
<?php
require __DIR__ . '/../bootstrap.php';
// do what the fuck you want...
$container->someService->doSomething();
echo 'Done.';
- David Grudl
- Nette Core | 8227
To už spíš:
bootstrap.php:
<?php
...
return $configurator->createContainer();
a index.php:
<?php
$container = require __DIR__ . '/../bootstrap.php';
$container->application->run();
Nicméně to už je krůček k tomu udělat třídu Bootstrap a pak mít v index.php
<?php
require __DIR__ . '/../bootstrap.php';
$container = Bootstrap::boot();
$container->application->run();
- Morlok
- Člen | 26
Tiež som potreboval potreboval nejak „manipulovat“ s boostrapom, teda niekedy toho nacitat viac, niekedy menej, a hlavne znovupouzitelnost.
Napadlo ma taketo riesenie:
Spravit si „Bootstraper“ ktory bude schopny registrovat rozne „Bootstrap
triedy“, ktore budu implementovat potrebnu logiku volanu podla potreby. Ta
logika su vpodstate nejake init-funckie,
ktore mozu vracat „resource“ (configurator, application,… cokolvek co
init-funcke vrati).
Napr kod:
<?php
// boostrap.php
$bootstraper = new Miwo\Bootstrap\Bootstraper();
$bootstraper->addBoostrap(new SystemBootstrap());
$bootstraper->addBoostrap(new MyAppBootstrap());
$bootstraper->configurator; // init configurator
$bootstraper->loader->register(); // init robot loader
$bootstraper->custom; // init custom
// index.php
require APP_DIR . '/bootstrap.php';
$bootstraper->application->run(); // Bootstrap application
// SystemBootstrap.php
class SystemBootstrap extends Bootstrap {
protected function initConfigurator() {
$configurator = new Nette\Config\Configurator;
$configurator->setTempDirectory(TEMP_DIR);
$configurator->enableDebugger(LOG_DIR);
return $configurator; // resource
}
protected function initLoader() {
/* @var $configurator Nette\Config\Configurator */
$configurator = $this->getResource('configurator');
$robotLoader = $configurator->createRobotLoader();
$robotLoader->addDirectory(APP_DIR);
$robotLoader->addDirectory(LIBS_DIR);
return $robotLoader; // resource
}
protected function initCustom() {
$this->boostrap('router');
$this->boostrap('cokolvek');
}
}
?>
Init metody su hladane v registrovanych Bootstrapoch, mozu byt pretazene
podla potreby…
Pripadne je mozne pozadovany „resource“ pozadovat priamo v parametri a nie
je nutne volanie metody getResource()
<?php
protected function initLoader(Nette\Config\Configurator $configurator) {
$robotLoader = $configurator->createRobotLoader();
$robotLoader->addDirectory(APP_DIR);
$robotLoader->addDirectory(LIBS_DIR);
return $robotLoader; // resource
}
?>
Podla vas je to zbytocne? alebo takato technika moze mat potencial?
Editoval Morlok (31. 3. 2012 9:51)
- Patrik Votoček
- Člen | 2221
podle mě je to zbytečné protože toho samého lze dosáhnout pomocí Nette\Config\CompilerExtension
- Morlok
- Člen | 26
Patrik Votoček napsal(a):
podle mě je to zbytečné protože toho samého lze dosáhnout pomocí Nette\Config\CompilerExtension
Zalezi od problemu, mozno konkretne to nastavenie co som uviedol sa da previest do CompilerExtension alebo config.neonu, ale ked potrebujem podla rozneho nastavenia inicializovat configurator, dynamicky pridavat CompilerExtension-s a mat to nakodene v bootstrape sa mi vobec nepaci.
- Filip Procházka
- Moderator | 4668
Jedno slovo: over-engeneering.
Už já toho mám možná zbytečně moc, ale zase to je krásně ohebné.