[2009–09–25] Adresářová struktura a moduly
- David Grudl
- Nette Core | 8227
Odstraněn jeden dávný pohrobek a úprava adresářové struktury.
Pohrobkové proměnné
Pohrobkem jsou proměnné prostředí:
templatesDir
– používáno jako proměnná Presenter z Application, nemá co dělat v EnvironmentpresentersDir
– používáno jako proměnná PresenterLoaderu z Application, nemá co dělat v EnvironmentcomponentsDir
amodelsDir
– se v „novodobé historii“ nejspíš nikdy na nic nepoužívalo
Proměnné jsem tedy odstranil, i když z důvodu zpětné kompatibility
k proměnným templatesDir
a presentersDir
metody
Presenter::formatLayoutTemplateFiles()
a
formatTemplateFiles()
stále přihlížejí.
Možná zpětná nekompatibilita: pokud vaše aplikace či
komponenty tyto proměnné používají. Rychlý workaround je přidat do
config.ini
:
[common]
variable.templatesDir = %appDir%/templates
variable.presentersDir = %appDir%/presenters
variable.componentsDir = %appDir%/components
variable.modelsDir = %appDir%/models
Moduly
Doporučená adresářová struktura pro moduly je zavádějící a pojmenování jmenných prostorů presenterů v PHP 5.3 nevhodné. Proč?
Především jde o to si uvědomit, co znamená modul. Rozhodně to není
(jen) seskupení více presenterů! Základním a vždy přítomným modulem je
totiž samotná aplikace (adresář app
). Ta obsahuje model,
presentery, šablony, komponenty, nebo pravidla routování. A může obsahovat
další submoduly, respektive samotná aplikace se může stát (byť po drobné
úpravě) submodulem v jiné aplikaci.
Příklad: aplikaci nette.org
tak mohu poskládat z modulů
wiki + fórum, kde fórum se skládá z modulů frontend a backend.
Stará adresářová struktura tenhle pohled spíš zamlžovala, protože
modul nepředstavoval jeden adresář obdobný adresáři app
, ale
byl roztříštěn do více adresářů. Nová struktura naopak moduly do
vlastních adresářů sjednocuje:
app/
ForumModule/
model/
presenters/
HomepagePresenter.php (v PHP 5.3 třída ForumModule\HomepagePresenter)
templates/
WikiModule
FrontendModule/
...
BackendModule/
...
models/
temp/
bootstrap.php
Ve výchozím nastavení se pro šablony používá stará adresářová
struktura, nastavením
Presenter::$oldModuleMode = FALSE
se aktivuje nová
adresářová struktura. Třídy presenterů se sice dohledávají jen dle nové
adresářové struktury, ovšem RobotLoader je stejně dohledá kdekoliv, takže
by se změna neměla projevit. Dále v PHP 5.3 se jako název jmenného
prostoru už nepoužívá jen název modulu, ale je doplněn o příponu
Module
.
Poznámka: jde samozřejmě o vývojovou záležitost a komentáře jsou vítány. Zároveň připomínám, že se bavíme čistě o „doporučené struktuře“, tj. každý můžete používat jakoukoliv jinou strukturu.
- Ondřej Mirtes
- Člen | 1536
Nová doporučená adresářová struktura – hezké :) Ale dělá mi
starost ta složka models
– co když chci jednu třídu
využívat ve více modulech? Např. UserModel – ve WebModule slouží např.
k registraci uživatelů a v AdminModule k jejich editaci a mazání.
Nechtěl bych pro to dělat dvě třídy.
Mimochodem, jak řešíte šablony pro komponenty? Klasika je asi dávat je
do /controls/Name/
, zkrátka k PHP třídě dané komponenty, ale
co kdybych to chtěl mít oddělené? Např. pro to, kdybych měl na hostingu
jednu centrální složku pro všechny Nette komponenty, které by využívalo
více webů, ale každý by samozřejmě potřeboval vlastní šablonu?
- David Grudl
- Nette Core | 8227
LastHunter napsal(a):
Nová doporučená adresářová struktura – hezké :) Ale dělá mi starost ta složka
models
– co když chci jednu třídu využívat ve více modulech? Např. UserModel – ve WebModule slouží např. k registraci uživatelů a v AdminModule k jejich editaci a mazání. Nechtěl bych pro to dělat dvě třídy.
Tomu přece nic nebrání.
Mimochodem, jak řešíte šablony pro komponenty? Klasika je asi dávat je do
/controls/Name/
, zkrátka k PHP třídě dané komponenty, ale co kdybych to chtěl mít oddělené?
Obvykle do app/components
(resp. module/components
)
dávám komponenty specifické pro konkrétní aplikaci/modul, do
libs/components
zase komponenty sdílené více weby nebo pro
komponenty z repozitáře.
Použití vlastní šablony v komponentě už pak spíš záleží na
implementaci komponenty. Takže třeba sdílená komponenta v
libs/components
má v app/components
potomka
s vlastní šablonou.
- Ondřej Mirtes
- Člen | 1536
OK :) Co lze teď použít namísto templatesDir? Myslel jsem, že tak teď
začne fungovat appDir, ale ta mi vede stále pouze do /app/
a ne
do /app/WebModule
. Potřebuju pro pro určení cesty
WebLoaderu.
- edke
- Člen | 198
David Grudl wrote:
Především jde o to si uvědomit, co znamená modul. Rozhodně to není (jen) seskupení více presenterů! Základním a vždy přítomným modulem je totiž samotná aplikace (adresář
app
). Ta obsahuje model, presentery, šablony, komponenty, nebo pravidla routování. A může obsahovat další submoduly, respektive samotná aplikace se může stát (byť po drobné úpravě) submodulem v jiné aplikaci.
Mna by zaujimalo prave to routrovanie. Ako konkretne sa da (ak sa da) v novej strukture rozdelit pre jednotlive moduly ?
- Honza Marek
- Člen | 1664
LastHunter napsal(a):
OK :) Co lze teď použít namísto templatesDir? Myslel jsem, že tak teď začne fungovat appDir, ale ta mi vede stále pouze do
/app/
a ne do/app/WebModule
. Potřebuju pro pro určení cesty WebLoaderu.
Můžeš si ty proměnné nastavit v configu ostatně jako píše David v úvodním příspěvku.
- David Grudl
- Nette Core | 8227
edke napsal(a):
Mna by zaujimalo prave to routrovanie. Ako konkretne sa da (ak sa da) v novej strukture rozdelit pre jednotlive moduly ?
Těch možností je víc, jedna taková může počítat s tím, že modul si umí routy vytvořit sám. Bude na to mít třeba továrničku: (jen koncept, psáno z hlavy)
class ForumModule {
public static function createRouter($prefix = 'forum/')
{
$router = new MultiRouter;
$router[] = new Route("$prefix<presenter>/<action>", array(
'module' => 'forum',
'presenter' => 'Home',
'action' => 'default',
);
$router[] = new Route(...);
$router[] = new Route(...);
return $router;
}
}
a pak v bootstrapu:
$application = Environment::getApplication();
$router = $application->getRouter();
// nastavíme vlastní routy aplikace
$router[] = new Route(...);
// připojíme i routy modulů, tj. třeba fóra na extra subdoméně
$router[] = ForumModule::createRouter('//forum.example.com/');
$application->run();
- Panda
- Člen | 569
Osobně používám následující způsob (momentálně je napsaný pro staré rozmístění modulů, fungovat ale bude nezávisle na adresářové struktuře):
Pro každý modul mám třídu, která dědí od abstraktní třídy
BaseModule
:
<?php
abstract class BaseModule extends Object
{
public function setupRouter(IRouter $router) { }
public function setupPermission(SitePermission $permission) { }
public function setupHooks(SiteHooks $hooks) { }
// ...
}
?>
V dané třídě překryji požadované metody:
<?php
class PageModule extends BaseModule
{
public function setupRouter(IRouter $router)
{
$router[] = new PageRoute('<uri>', array(
'presenter' => 'Page',
'action' => 'default'
));
}
public function setupPermission(SitePermission $permission)
{
$permission->addResource('Page');
$permission->addPrivilege('Page', array('edit', 'create'));
}
public function setupHooks(SiteHooks $hooks)
{
$hooks->add('Blocks', 'PageModule::blocksEntry');
$hooks->add('Dashboard/Blocks', 'PageModule::dashboardEntry');
$hooks->add('AdminMenu', 'PageModule::setupAdminMenu');
}
// ...
}
?>
V config.ini
mám seznam modulů, které si přeji načíst:
[common]
modules[] = Admin
modules[] = Page
modules[] = News
modules[] = Gallery
modules[] = Article
modules[] = Document
Třída SiteApplication
(potomek Application
, je
nastavený v config.ini
jako service pro
Nette\Application\Application
) mi vše načte a spustí ve
správný čas:
<?php
class SiteApplication extends Application
{
protected $modules;
/**
* @var Cache
*/
protected $cache;
protected $hooks;
public function run()
{
$this->cache = Environment::getCache('Application');
$modules = Environment::getConfig('modules');
foreach ($modules as $module)
$this->loadModule($module);
$this->setupRouter();
$this->setupHooks();
// Requires database connection
$this->onRequest[] = array($this, 'setupPermission');
// ...
// Run the application!
parent::run();
$this->cache->release();
}
protected function loadModule($module)
{
$class = $module . 'Module';
$this->modules[$module] = new $class;
}
protected function setupRouter()
{
if (Environment::isProduction() && isset($this->cache['router'])) {
$this->setRouter($this->cache['router']);
} else {
SiteRoute::initialize();
$router = $this->getRouter();
// Homepage
$router[] = new SiteRoute('<? (index\.(php|html?))?>', array(
'presenter' => 'Homepage',
'action' => 'default',
), Route::ONE_WAY);
// Modules routes
foreach ($this->modules as $module)
$module->setupRouter($router);
// Default route
$router[] = new SiteRoute('<presenter>/<action>/<id>', array(
'presenter' => 'Homepage',
'action' => 'default',
'id' => NULL,
));
$this->cache->save('router', $router);
}
}
public function setupPermission()
{
// ...
}
protected function setupHooks()
{
if (Environment::isProduction() && isset($this->cache['hooks'])) {
$this->hooks = $this->cache['hooks'];
} else {
$this->hooks = new SiteHooks();
foreach ($this->modules as $module)
$module->setupHooks($this->hooks);
$this->cache->save('hooks', $this->hooks);
}
}
// ...
}
?>
Celá moje aplikace je napsaná tak, aby podporovala několik propojených
webů běžících na jediné instalaci, takže možnost definovat moduly
v rámci sekce config.ini
je pro mě klíčová.
- David Grudl
- Nette Core | 8227
Panda napsal(a):
Osobně používám následující způsob (momentálně je napsaný pro staré rozmístění modulů, fungovat ale bude nezávisle na adresářové struktuře):
Máš to napsané výborně, v podstatě podobnou cestou půjde vývoj modulů v Nette. Moduly včetně Application budou implementovat IModule. Ještě nevím, jestli to pojmou i jako komponenty. Moduly si budou také určovat vlastní ErrorPresenter.
- sodae
- Nette Evangelist | 250
Jak ale zde řešit když mám několik modulů a jednu administraci jak
udělat aby byl jeden globální @layout.phtml pro admina.
Struktura je
CoreModule
- AdminModule
- presenters
- templates
- @layout.phtml << ten layout globálně pro admina
- FrontModule
- presenters, templates
PageModule
- AdminModule
- presenters
- templates (zde bez @layout.phtml a nebo dědit @layout.phtml v CoreModule)
- FrontModule
- presenters, templates
doufám že to někdo pochopil, diky
- simon
- Člen | 98
Zdravim,
nova adresarova struktura se mi moc libi a proto jsem si ji implementoval. Nevim
si vsak rady s ErrorPresenterem. Predpokladam ze bude mit kazdy modul vlastni.
Nevim vsak jak to nastavit. Kdyz dam do bootstrapu
<?php
$application->errorPresenter = 'Error';
$application->catchExceptions = true;
?>
tak mi to nefunguje a zobrazuje se jen bila stranaka. a v logu je
[28-zář-2009 11:57:45] PHP Fatal error: Uncaught exception
‚BadRequestException‘ with message ‚Cannot load presenter
'Portal:Asdfasdf‘, class ‚Portal_AsdfasdfPresenter‘ was not found in
‚/home/simon/Workspace/php/nette/project/baskytara_com/html/../app/PortalModule/presenters/AsdfasdfPresenter.php‘.'
in
/home/simon/Workspace/php/nette/project/baskytara_com/libs/Nette/Application/Application.php:150
Stack trace:
#0 /home/simon/Workspace/php/nette/project/baskytara_com/app/bootstrap.php(108):
Application->run()
#1 /home/simon/Workspace/php/nette/project/baskytara_com/html/index.php(16):
require(‚/home/simon/Wor…‘)
#2 {main}
[28-zář-2009 11:58:07] PHP Fatal error: Uncaught exception
‚ApplicationException‘ with message ‚An error occured while executing
error-presenter‘ in
/home/simon/Workspace/php/nette/project/baskytara_com/libs/Nette/Application/Application.php:181
Stack trace:
#0 /home/simon/Workspace/php/nette/project/baskytara_com/app/bootstrap.php(108):
Application->run()
#1 /home/simon/Workspace/php/nette/project/baskytara_com/html/index.php(16):
require(‚/home/simon/Wor…‘)
#2 {main}
chapu ze se nedari najit error presenter ale nevim jak to napravit. predem diky
- simon
- Člen | 98
Jeste dodam ze problem je tato vyjimka:
Cannot load presenter ‚Error‘, class ‚ErrorPresenter‘ was not found in ‚/home/simon/Workspace/php/nette/project/baskytara_com/html/../app/presenters/ErrorPresenter.php‘.
Na tom umisteni samozrejme presenter neni. Nevim tedy jak nette donutit aby presenter hledalo v adresari s modulem.
- David Grudl
- Nette Core | 8227
Predpokladam ze bude mit kazdy modul vlastni.
To ano, ale zatím to ještě není implementované. Takže se používá
jeden presenter a modul se zapíše pomocí
dvojteček $application->errorPresenter = 'Admin:Error';
- Jan Jakeš
- Člen | 177
V souvislosti s adresářovou strukturou obecně bych se chtěl zeptat, kam byste umístili následující:
1. V adresáři libs
mám knihovnu
Texy
. Vytvořil jsem si poděděnou třídu, ve které mám handler
pro zvýranění syntaxe pomocí FSHL. Ten rovnou registruji do
Texy
:
class TexyFshl extends Texy
Vím, že toto by asi šlo přidat třeba do BasePresenteru, ale není nějaké lepší řešení, kam to umístit?
2. Funkce getId
a getPath
, které
používám jako masku pro routování. Kam je dát?
Nehodilo by se mít pro tyto případy v app/
ještě nějakou
další složku?
Editoval Juan (29. 9. 2009 14:41)
- Honza Kuchař
- Člen | 1662
To záleží čistě na tobě.
Třeba si vytvoř novou složku v app/ nebo do libs/. Nette tě nijak neomezuje.
- na1k
- Člen | 288
Po nějaké době jsem updatoval na novou dev revizi a s tím jsem
i přeorganizoval adresáře podle prvního Davidova postu. Jenže se mi něco
pokazilo, protože se mi namísto celé stránky vykreslí jen šablona
aktuálního view. Chybí tedy celý @layout.
Netušíte někdo co je špatně? soubor s layoutem mám
v app\FrontModule\templates\@layout.phtml
- Ondřej Mirtes
- Člen | 1536
na1k napsal(a):
Po nějaké době jsem updatoval na novou dev revizi a s tím jsem i přeorganizoval adresáře podle prvního Davidova postu. Jenže se mi něco pokazilo, protože se mi namísto celé stránky vykreslí jen šablona aktuálního view. Chybí tedy celý @layout.
Netušíte někdo co je špatně? soubor s layoutem mám vapp\FrontModule\templates\@layout.phtml
Nový systém počítá s tím, že používáš „nové šablony“ –
s makry {block}
a {extends}
. Doporučuji shlédnout
přednášku: http://blip.tv/file/2514636
- Jerry123456789
- Člen | 37
Šmankote, jak tak koukám na Pandův příklad komplexní aplikace, zjišťuju že se mám eště dst co učit :D
Ale chtělo by to nějaký howto na komplexitu, tohle bych vymejšlel dlouho. Taky by mě zajímalo třeba když máš třídu *Module jak to zapasuješ do MVP? Nebo jsou moduly (*Module, ne adresář) jen na router/permissions…?
- Filip Procházka
- Moderator | 4668
Zdravim, zalíbil se mi Pandův přístup. Něco takového jsem chtěl udělat, ale nevěděl jsem jak přidat další vrstvu do životního cyklu, takže nápad zaregistrovat si vlastní třídu pro Application je parání!
Bohužel jsem nedokázal pochopit jaký význam mají tvoje ‚Hooks‘ v modulech, snažim se nato přijit už dva týdny ale furt jsem nic nevymyslel :( kdyby ses mohl ještě trochu rozepsat o tom tvém příkladu byl bych ti myslím nejenom já vděčný :)
- Panda
- Člen | 569
Nazdar,
moje hooks jsou tak trochu inspirovány hooky v Drupalu (principem, ne
realizací – Drupal není psán objektově). Tam slouží k tomu, aby modul
mohl ovlivnit všechno možné, od nových typů obsahu, přes existující
formuláře až po různá interní zpracování vložených dat a tak
podobně.
Tak daleko, jako jde Drupal, jsem ale nešel. Moje hooky používám na
občasné ovlivnění určitých prvků webu, například jako hlavní stránku
administrační sekce mám nástěnku. Ta je bez modulů prázdná, naplní se
právě až pomocí těch modulů – modul si zaregistruje na hook funkci
(používám statické funkce, abych mohl jednoduše cachovat, ačkoliv to není
z hlediska objektového návrhu úplně ideální), té funkci se předá
objekt (Hashtable
) s aktuálním seznamem bloků a jen do tohoto
objektu přidá záznam. Pak až je potřeba sestavit seznam těchto bloků, tak
se jen projde pole takto zaregistrovaných funkcí, zavolají se a seznam bloků
na nástěnce je na světě.
Další použití, které je v kódu vidět, je sestavování administračního menu. Zaregistrovaná funkce opět přidává do objektu záznam, popř. sadu záznamů. Další využití, které v kódu vidět není a které ještě nemám napsané, by byl modul komentářů. Tam by se registroval hook, který by modifikoval formulář na přidávání článku/novinky/galerie – přidal by do něj možnost výběru, zda je možno přidávat komentáře nebo ne.
Stejně dobře bych si mohl nadefinovat nějakou prázdnou funkci v předkovi všech modulů a pak jí jen podle potřeby přepsat, ale s rostoucím počet hooků by se celá třída stávala dosti velká a při větším množství modulů by se celkem zbytečně volala kopa funkcí, které nic nedělají. Obecně jsem se pak řídil pravidlem, že co bude pravděpodobně ovlivňovat každý modul (např. oprávnění, sitemapa, routy…), jsem realizoval pomocí prázdné funkce v předkovi, ale to, co bude využívat menší množství modulů, jsem vyřešil pomocí hooků.
- ViliamKopecky
- Nette hipster | 230
@Panda: Nechceš to třeba sepsat formou článku, myslím, že tvoje řešení je dost dobré a dotažené, může velmi dobře posloužit k inspiraci. Myslím, že bych nebyl jediný, kdo by to ocenil.
- Panda
- Člen | 569
enoice: Mno já si zrovna myslím, že do dotaženosti má
mé řešení ještě daleko… Ale je fakt, že kdybych něco publikoval, tak
by se to možná dalo společnými silami nějak dotáhnout. Něco tedy
sepíšu, ale až časem. Možná bych se mohl zaměřit i na další věci,
např. jednoduchá cesta k vlastním routám, ještě šikovnější
Permission
a tak.
- vyvazil.jakub
- Člen | 6
Adresářová struktura je super, ale fakt dlouho jsem se propracovával ke všem informacím, které jsem potřeboval. Chápu, že dodělat dokumentaci je hodně práce, ale možná by nebylo na škodu udělat skeleton2 s rozdělením aplikace do modulů, aby tam byl {plink} mezi modulama.
Moje chyba, teď jsem si všiml, že to je v examples :)
Editoval vyvazil.jakub (3. 12. 2009 12:52)
- Vyki
- Člen | 388
Stále je mi ten článek od Pandy velkou inspirací. A adresářovou strukturou jsem si nakonec poradil tak, že jsem vymyslel svojí:
.FirstModule
->frontend
->presenters
->tempates
->backend
->presenters
->tempates
->models
->modul.ini
.SecondModule
->frontend
->presenters
->tempates
->backend
->presenters
->tempates
->models
->modul.ini
.presenters - zde mím BasePresenter
.templates - zde mám uložený globální layout
.components - zde mám obecné
config.ini
Aby toto šlo udělat tak jsem si vytvořil a jako službu
service.Nette-Application-IPresenterLoader
zaregistroval vlastní
(podědil a upravil metodu formatPresenterFile
)
PresenterLoader
, tak aby mi to akceptovalo novou adr. strukturu.
Načítání layoutu a template jsem si ošetřil přepsáním metod presenteru
formatLayoutTemplateFiles
, formatTemplateFiles
, které
jsem uložil do BasePresenteru
. V každém modulu podle toho
schématu mám konfigurační soubor modul.ini
, kde mám uvedeny,
některé záležitosti (hooks, práva..) jako psal Panda. Ty konfigurační
soubory načítám při startu aplikace. Myslíte, že se je vyplatí
cachovat?
Editoval Vyki (18. 3. 2010 14:34)
- Honza Marek
- Člen | 1664
Vyki napsal(a):
Aby toto šlo udělat tak jsem si vytvořil a jako službu
service.Nette-Application-IPresenterLoader
zaregistroval vlastní (podědil a upravil metoduformatPresenterFile
)PresenterLoader
, tak aby mi to akceptovalo novou adr. strukturu.
Vždyť to není potřeba. RobotLoader umí načíst presentery odkudkoliv. Úkolem IPresenterLoaderu je jen zjistit jméno třídy.
- westrem
- Člen | 398
Sice je to uz nejaky ten mesiac co tu Panda pisal svoj prispevok o tom ako ma on zariesenu svoju aplikaciu ale chcel by som sa spytat – napisal potom k tomu aj nejaky ten clanok ako pise vo svojom prispevku? Ak ano, je niekde dostupny?
Pripadne vedel by ma niekto odkazat na nejake rozumne studijne materialy ohladne hooks a ako best practise s nimi spojene?
Vrela vdaka za akykolvek podnet!
- Filip Procházka
- Moderator | 4668
články v dokumentaci wordpressu jsou imho docela k ničemu, protože tam se používá procedurální přístup a já osobně jsem to ani pořádně nepochopil :)
- knyttl
- Člen | 196
Co se týče konfigurace adresářů v config.ini:
[common]
variable.templatesDir = %appDir%/templates
variable.presentersDir = %appDir%/presenters
variable.componentsDir = %appDir%/components
variable.modelsDir = %appDir%/models
Já bych potřeboval ten adresář templatesDir být schopen měnit za běhu aplikace – je toto nějak možné? Přijde mi hloupé kvuli jednomu adresáři přepisovat celou funkci formatTemplateFiles()…