Konfigurace prostředí
Systémový kontejner je DI kontejner, ve kterém se nacházejí všechny služby a parametry potřebné pro běh aplikace. Řekneme si:
- jak systémový kontejner vytvořit
- jak se konfiguruje pomocí NEON souborů
- jak na produkční a vývojářský režim
- jak používat a vytvářet rozšíření pro kontejner
Omlouváme se, tato stránka je ještě v přípravě.
Systémový kontejner je statický Dependency Injection kontejner, ve kterém se nacházejí všechny služby a parametry potřebné pro běh aplikace. Tedy nejen služby frameworku samotného, ale i všech knihoven, které se rozhodnete použít. Jak takový základní kontejner vypadá, se můžete podívat třeba tady.
V praxi se ukázalo, že programování kontejnerů je značně rutinní a nezáživná činnost. Navíc s tím, jak aplikace roste, je stále těžší se v něm orientovat. Pohodlným řešením je nechat si kontejner vygenerovat. Což Nette Framework umí. Stručným konfiguračním jazykem popíšeme, jaké služby má obsahovat a framework sám PHP kód vytvoří. S pravdou ven: i kód výše odkazovaného kontejneru byl vygenerován.
Configurator
Generování kontejneru je úkolem třídy Nette\Config\Configurator. Samotný PHP kód se vytváří jen jednou a poté uloží do cache, proto musíme určit složku pro dočasné soubory:
$configurator = new Nette\Config\Configurator;
$configurator->setTempDirectory(__DIR__ . '/../temp');
Poté stačí uvést cestu ke konfiguračnímu souboru:
$configurator->addConfig(__DIR__ . '/config/config.neon');
A vytvoření instance je už maličkost:
// vrací objekt třídy SystemContainer
$container = $configurator->createContainer();
Prostředí
Configurator se pokouší detekovat, zda aplikace běží na ostrém
(production) nebo vývojářském (development) serveru. Činí tak podle IP
adresy počítače, z něhož k serveru přistupujete. Pokud je 127.0.0.1,
považuje server za vývojářský. Zjistit prostředí nemůžete, ale to
nevadí, protože se používá pouze pro volbu sekce při načítání
konfigurace a to můžete ovlivnit parametrem při volání
addConfig().
$environment = Nette\Config\Configurator::detectDebugMode('vaše ip adresa')
? $configurator::PRODUCTION : $configurator::DEVELOPMENT;
$configurator->addConfig(__DIR__ . '/config/config.neon', $environment);
Režim
Trochu jiná věc je režim, v jakém k aplikaci přistupujete. Pro aktivaci laděnky můžete chtít přistupovat k produkčnímu serveru jako vývojář. Rozpoznává se podle stejného principu. Režim zjistíte metodou isDebugMode() a nastavíte pomocí setDebugMode(). Hodí se především, pokud aktivujete debugger pomocí enableDebugger(), která musí následovat až po nastavení režimu.
// aktivuje laděnku pouze pro dané ip adresy
$configurator->setDebugMode(array("90.180.45.360", "90.180.45.361"));
// nebo všem
$configurator->setDebugMode($configurator::NONE); // = FALSE
$configurator->enableDebugger(__DIR__ . "/../log");
RobotLoader
Pro sestavení kontejneru je potřeba načíst všechny třídy, které se
v konfiguračním souboru uvádějí. K tomu je obvykle třeba aktivovat auto-loading. Třída Configurator nám
vychází vstříc metodou, která vyrobí RobotLoader, nám pak stačí uvést
indexované adresáře a robota aktivovat. Nezapomeňte tak učinit ještě
před samotným voláním createContainer().
$configurator->createRobotLoader()
->addDirectory(APP_DIR)
->addDirectory(LIBS_DIR)
->register();
Konfigurace v NEON souboru
Se syntaxí si můžete pohrát na stránce http://ne-on.org.
Nette Framework má několik vlastních nastavení, jejich přehled je zde.
Definice služeb
services:
database:
class: Nette\Database\Connection(%dsn%, %user%, %password%)
#nebo ve dvou řádcích:
class: Nette\Database\Connection
arguments: [%dsn%, %user%, %password%]
Vygeneruje:
function createServiceDatabase()
{
return new Nette\Database\Connection(
$this->parameters['dsn'],
$this->parameters['user'],
$this->parameters['password']
);
}
Definice služby:
services:
database:
class: Nette\Database\Connection
factory: DbFactory::createConnection
DbFactory::createConnection:
class DbFactory
{
static function createConnection(Nette\DI\Container $container)
{
...
}
}
Vygeneruje:
function createServiceDatabase()
{
return DbFactory::createConnection($this);
}
Setup
services:
database:
class: Nette\Database\Connection(%dsn%, %user%, %password%)
setup:
- setCacheStorage(@cacheStorage)
Vygeneruje:
function createServiceDatabase()
{
$service = new Nette\Database\Connection(...);
$service->setCacheStorage($this->cacheStorage);
return $service;
}
Autowiring umí odkazy na jiné služby doplnit automaticky, takže lze parametry úplně vynechat:
setup:
- setCacheStorage
Pokud služba cacheStorage neexistuje, můžeme jako parametr
uvést výsledek volání funkce:
setup:
- setCacheStorage( Factory::createStorage() )
# nebo metody jiné služby:
- setCacheStorage( @factory::createStorage() )
Případně nově vytvořenou třídu:
setup:
- setCacheStorage( Nette\Caching\Storages\FileStorage(%tempDir%) )
# vygeneruje: $service->setCacheStorage(new Nette\Caching\Storages\FileStorage(...));
Lze nastavovat i hodnoty proměnných:
substitutions
setup:
- $substitutions( [db: test] )
# vygeneruje: $service->substitutions = array('db' => 'test');
Kompletní příklad:
parameters:
database:
driver: mysql
host: localhost
dbname: test
user: jim
password: beam
substitutions:
db: test
services:
database:
class: Nette\Database\Connection(
'%database.driver%:host=%database.host%;dbname=%database.dbname%',
%database.user%, %database.password%, null,
Nette\Database\Reflection\DiscoveredReflection()
)
setup:
- setCacheStorage
- $substitutions( %database.substitutions% )
Dědičnost služeb
services:
dev_database < database
setup:
- Diagnostics\ConnectionPanel::initialize
Továrny
Továrny se definují takřka stejně jako služby:
factories:
article:
class: Article
Vygeneruje:
function createArticle()
{
return new Article($this->database);
}
Továrnu pak voláme přímo $article =
$container->createArticle();
Továrnám lze narozdíl od služeb předávat parametry:
factories:
article:
parameters: [id, title: null]
class: Article(..., %id%)
setup:
- $title(%title%)
Vygeneruje:
function createArticle($id, $title = NULL)
{
$service = new Article($this->database, $id);
$service->title = $title;
return $service;
}
Auto-wiring
Auto-wiring umí automaticky předávat do konstruktoru a dalších metod
požadované služby. Řídí se podle type hintů a anotací
@return. Služba odpovídající hledané třídě musí být
v kontejneru právě jedna, jinak se vyhodí výjimka.
Pokud potřebujeme definovat více služeb stejného typu, můžeme je z auto-wiringu vyřadit:
services:
cacheStorage:
class: Nette\Caching\Storages\FileStorage(%tempDir%)
tempCacheStorage:
class: Nette\Caching\Storages\DevNullStorage
autowired: no
Pokud upravujeme základní služby Nette Frameworku, nesmíme se zapomenout
ujistit, že kontejner zná třídy naší implementace. Pokud tedy nastavujeme
vlastní factory pro službu, tak to znamená mít správně
absolutní název třídy v annotaci @return, nebo nastavovat
vždy i třídu do class.
Více konfiguračních souborů
Vkládané soubory se uvádějí v sekci includes.
includes:
- parameters.php
- services.neon
- presenters.neon
Konfigurace se slučují tak, že nejvyšší prioritu má soubor
obsahující sekci includes a nejnižší první vkládaný soubor.
Slučování polí lze zabránit uvedením vykřičníku za název pole:
argument!: [1, 2, 3]
Rozšíření pro kontejner
Configurator sám však žádný kód negeneruje, o to se
stará Nette\Config\Compiler
a Nette\DI\ContainerBuilder.
Nejdříve se spustí Nette\Config\Loader,
který načte konfigurační soubory a předá je Compileru. Do
Compileru si můžeme připojit vlastní rozšíření.
$configurator->onCompile[] = function ($configurator, $compiler) {
$compiler->addExtension('blog', new MyBlogExtension);
};
Každé rozšíření Compileru musí dědit od Nette\Config\CompilerExtension
a může implementovat tři různé metody, které jsou postupně volány,
během sestavování Containeru.
CompilerExtension::loadConfiguration()
Metoda je nad všemi rozšířeními volána jako první a je určena k načtení dodatečných konfiguračních souborů, vytvoření dalších služeb pomocí Nette\DI\ContainerBuilder a hlavně zpracování konfigurace aplikace.
V configu je možné uvést sekci, která se jmenuje stejně jako naše rozšíření. Pokud tedy budeme uvažovat předchozí příklad, mohlo by v konfigu přibýt například toto
blog: # stejné jméno jako má extension
postsPerPage: 10
comments: FALSE
Nastavení si lze přečíst zavoláním metody getConfig() v rozšíření.
class MyBlogExtension extends Nette\Config\CompilerExtension
{
public function loadConfiguration()
{
$config = $this->getConfig();
// array(2) [ 'postsPerPage' => 10, 'comments' => FALSE ]
Protože uznáváme princip konvence před
konfigurací, nastavíme si výchozí hodnoty, aby aplikace fungovala i bez
toho, že budeme něco nastavovat. V prvním argumentu metody
getConfig() můžeme poskytnout výchozí hodnoty, do kterých bude
sloučena příslušná sekce konfiguračního souboru.
class MyBlogExtension extends Nette\Config\CompilerExtension
{
public $defaults = array(
'postsPerPage' => 5,
'comments' => TRUE
);
public function loadConfiguration()
{
$config = $this->getConfig($this->defaults);
Nastavení máme v poli $config, takže je aplikujeme na
služby, které vytvoříme. Použijeme k tomu ContainerBuilder,
který umožňuje to stejné, jako zápis služby v konfiguračním
souboru.
$builder = $this->getContainerBuilder();
Vytvoříme si službu blog.articles, která bude reprezentovat
model pro práci s články a jejich načítání.
Konvence je, prefixovat služby v rozšíření jeho názvem,
aby nevznikaly konflikty. Pokud konvenci dodržíme, budeme moct volat služby
jako $container->blog->articles. Pomůže nám s tím metoda
prefix().
$builder->addDefinition($this->prefix('articles'))
->setClass('MyBlog\ArticlesModel', array('@connection'));
Dále si vytvoříme továrničku na komponentu, které předáme
blog.articles a nastavíme počet zobrazených příspěvků na
stránku. Jak používat továrničky na komponenty si ukážeme později.
$builder->addDefinition($this->prefix('articlesList'))
->setClass('MyBlog\Components\ArticlesList', array($this->prefix('@articles')))
->addSetup('setPostsPerPage', $config['postsPerPage'])
->setShared(FALSE)->setAutowired(FALSE); // ze služby se stane továrnička
Budeme potřebovat i modelovou třídu pro komentáře
blog.comments, které předáme připojení k databázi a
blog.articles a pokud jsou vyplé komentáře, tak je
zakážeme.
$comments = $builder->addDefinition($this->prefix('comments'))
->setClass('MyBlog\CommentsModel', array('@connection', $this->prefix('@articles')));
if (!$config['comments']) { // volitelné vypnutí komentářů
$comments->addSetup('disableComments');
}
Komentáře se musí také někde zobrazovat a psát, takže přidáme
ještě jednu továrničku na komponentu blog.commentsControl,
která nám bude komentáře zobrazovat a dovolí nám psát nové, pokud
nebudou vyplé.
$builder->addDefinition($this->prefix('commentsControl'))
->setClass('MyBlog\Components\CommentsControl', array($this->prefix('@comments')))
->setShared(FALSE)->setAutowired(FALSE); // ze služby se stane továrnička
Toto rozdělení na modely a komponenty je pouze ilustrativní.
Načítání dodatečné konfigurace
Pokud se nám nelíbí vytvářet všechny služby v rozšíření, můžeme jeho část přesunout do samostatného konfiguračního souboru.
services:
blog.articles:
class: MyBlog\ArticlesModel(@connection)
blog.comments:
class: MyBlog\CommentsModel(@connection, @blog.articles)
factories:
blog.articlesList:
class: MyBlog\Components\ArticlesList(@blog.articles)
blog.commentsControl:
class: MyBlog\Components\CommentsControl(@blog.comments)
Který načteme a dodatečně služby nastavíme
public function loadConfiguration()
{
$config = $this->getConfig($this->defaults);
$builder = $this->getContainerBuilder();
// načtení konfiguračního souboru pro rozšíření
$this->compiler->parseServices($builder, $this->loadFromFile(__DIR__ . '/blog.neon'));
// počet článků na stránku v komponentě
$builder->getDefinition($this->prefix('articlesList'))
->addSetup('setPostsPerPage', $config['postsPerPage']);
// volitelné vypnutí komentářů
if (!$config['comments']) {
$builder->getDefinition($this->prefix('comments'))
->addSetup('disableComments');
}
}
Rozšíření se nám krásně vyčistilo a díky syntaxi Neon, jsou definice služeb mnohem lépe čitelné.
CompilerExtension::beforeCompile()
V této fázi sestavování už by neměly přibývat další služby. Můžeme ovšem upravovat existující a doplnit některé potřebné vazby mezi službami, například pomocí tagů.
CompilerExtension::afterCompile(Nette\Utils\PhpGenerator\ClassType $class)
V této fázi už je třída Containeru vygenerována,
obsahuje všechny metody, které vytváří služby a je připravena na zápis
do cache. Díky api třídy Nette\Utils\PhpGenerator\ClassType
můžeme přidávat vlastní kód do kontejneru a upravovat tak výsledný kód,
který se stará o vytvoření služeb.
Samotný Nette Framework například přidává metodu
initialize, ve které zpracovává některá
uživatelská nastavení. Tuto metodu pak sám vždy volá po
instanciování kontejneru.
Ukážeme si kousek kódu, kde Nette Framework doplňuje do metody
initialize startování session a automatické spouštění
služeb, které mají tag run.
public function afterCompile(Nette\Utils\PhpGenerator\ClassType $class)
{
$container = $this->getContainerBuilder();
$config = $this->getConfig($this->defaults);
// metoda initialize
$initialize = $class->methods['initialize'];
// automatické startování session
if ($config['session']['autoStart']) {
$initialize->addBody('$this->session->start();');
}
// služby s tagem run musejí být spouštěny po vytvoření kontejneru
foreach ($container->findByTag('run') as $name => $foo) {
$initialize->addBody('$this->getService(?);', array($name));
}
}
Metody beforeCompile() a afterCompile() se liší
pouze v tom, že jedna má přístup ke kódu výsledného
Containeru a druhá ne.
Comments 
semtex.989 | 20. 1. 2012, 8:09 | comment
Díky za návod Rozšíření pro kontejner, Filipe!
bojovyletoun | 20. 1. 2012, 14:35 | comment
Taky díky za návod, nesledoval jsem fórum, ale při pullnutí jsem našel právě změny v nette extension $ defaults, tak tuším, že se peče něco velkého.
Jan Tvrdík | 4. 2. 2012, 23:25 | comment
juzna.cz | 5. 3. 2012, 23:53 | comment
Pokud chcete mit cofig.neon verzovany (napr v gitu), ale nemit tam hesla (abyste to mohli dat na github), tak si udelejte navic config.local.neon a ten si dejte do .gitignore. Nacteni obou souboru udelate pomoci:
$configurator->addConfig(__DIR__ . '/config/config.neon')->addConfig(__DIR__ . '/config/config.local.neon');
juzna.cz | 5. 3. 2012, 23:55 | comment
Pokud na projektu dela vice lidi a kazdy chce mit na svem stroji cast
vlastniho nastaveni, doporucuji take vytvorit soubor
app/config/environment.php ktery bude v .gitignore, a
ten v bootstrap.php nacist:
// Configure application
$configurator = new Nette\Config\Configurator;
if(file_exists(__DIR__ . '/config/environment.php')) {
require_once __DIR__ . '/config/environment.php';
}
V environment.php muzete mit napr:
define('ENVIRONMENT_NAME', 'development');
$configurator->setProductionMode(false);
ricco24 | 18. 4. 2012, 19:58 | comment
Nemala by byť pri definovaní továrničky uvedená aj referencia na db servise ?
factories:
article:
class: Article (@database)
Aby sa vygeneroval kód
function createArticle()
{
return new Article($this->database);
}
frosty22 | 2. 5. 2012, 15:56 | comment
Reference netřeba, jelikož autowiring jí sám doplní, pokud má článek definovanou typovou kontrolu
bojovyletoun | 8. 5. 2012, 1:14 | comment
Chápu to správně, že pro prostředí hraje pouze roli adresa serveru(na různém prostředí různé loginy do db) a pro režim hraje roli spíš adresa klienta ()?
bojovyletoun | 8. 5. 2012, 1:37 | comment
trochu matoucí na začátku jsou příklady s database, kde by bylo vhodné upozornit, že jedno je dibi a druhé NDB Pak ještě tady: http://doc.nette.org/cs/configuring#… se sekce a název rozšíření nejmenují stejně.

mcmatak | 19. 1. 2012, 18:56 | comment
nevím jestli to tady někde je napsané v době psaní tohoto příspěvku, ale trvalo mi asi dvě hodiny, než jsem přišel na to, že když chci nahradit nějaký nette service (builtin) vlastním, třeba jako usera, tak si ho musím také v neonu pojmenovat!
user: class: Nette\Security\User factory: [@model, createServiceUser]bez toho řádku class to nebude fungovat