Nette Documentation Preview

syntax
Adresářová struktura aplikace
*****************************

<div class=perex>

Jak navrhnout přehlednou a škálovatelnou adresářovou strukturu pro projekty v Nette Framework? Ukážeme si osvědčené postupy, které vám pomohou s organizací kódu. Dozvíte se:

- jak **logicky rozčlenit** aplikaci do adresářů
- jak strukturu navrhnout tak, aby **dobře škálovala** s růstem projektu
- jaké jsou **možné alternativy** a jejich výhody či nevýhody

</div>


Důležité je zmínit, že Nette Framework samotný na žádné konkrétní struktuře nelpí. Je navržen tak, aby se dal snadno přizpůsobit jakýmkoliv potřebám a preferencím.


Základní struktura projektu
===========================

Přestože Nette Framework nediktuje žádnou pevnou adresářovou strukturu, existuje osvědčené výchozí uspořádání v podobě [Web Project|https://github.com/nette/web-project]:

/--pre
<b>web-project/</b>
├── <b>app/</b>              ← adresář s aplikací
├── <b>assets/</b>           ← soubory SCSS, JS, obrázky..., alternativně resources/
├── <b>bin/</b>              ← skripty pro příkazovou řádku
├── <b>config/</b>           ← konfigurace
├── <b>log/</b>              ← logované chyby
├── <b>temp/</b>             ← dočasné soubory, cache
├── <b>tests/</b>            ← testy
├── <b>vendor/</b>           ← knihovny instalované Composerem
└── <b>www/</b>              ← veřejný adresář (document-root)
\--

Tuto strukturu můžete libovolně upravovat podle svých potřeb - složky přejmenovat či přesouvat. Poté stačí pouze upravit relativní cesty k adresářům v souboru `Bootstrap.php` a případně `composer.json`. Nic víc není potřeba, žádná složitá rekonfigurace, žádné změny konstant. Nette disponuje chytrou autodetekcí a automaticky rozpozná umístění aplikace včetně její URL základny.


Principy organizace kódu
========================

Když poprve prozkoumáváte nový projekt, měli byste se v něm rychle zorientovat. Představte si, že rozkliknete adresář `app/Model/` a uvidíte tuto strukturu:

/--pre
<b>app/Model/</b>
├── <b>Services/</b>
├── <b>Repositories/</b>
└── <b>Entities/</b>
\--

Z ní vyčtete jen to, že projekt používá nějaké služby, repozitáře a entity. O skutečném účelu aplikace se nedozvíte vůbec nic.

Podívejme se na jiný přístup - **organizaci podle domén**:

/--pre
<b>app/Model/</b>
├── <b>Cart/</b>
├── <b>Payment/</b>
├── <b>Order/</b>
└── <b>Product/</b>
\--

Tady je to jiné - na první pohled je jasné, že jde o e-shop. Už samotné názvy adresářů prozrazují, co aplikace umí - pracuje s platbami, objednávkami a produkty.

První přístup (organizace podle typu tříd) přináší v praxi řadu problémů: kód, který spolu logicky souvisí, je roztříštěný do různých složek a musíte mezi nimi přeskakovat. Proto budeme organizovat podle domén.


Jmenné prostory
---------------

Je zvykem, že adresářová struktura koresponduje se jmennými prostory v aplikaci. To znamená, že fyzické umístění souborů odpovídá jejich namespace. Například třída umístěná v `app/Model/Product/ProductRepository.php` by měla mít namespace `App\Model\Product`. Tento princip pomáhá v orientaci v kódu a zjednodušuje autoloading.


Jednotné vs množné číslo v názvech
----------------------------------

Všimněte si, že u hlavních adresářů aplikace používáme jednotné číslo: `app`, `config`, `log`, `temp`, `www`. Stejně tak i uvnitř aplikace: `Model`, `Core`, `Presentation`. Je to proto, že každý z nich představuje jeden ucelený koncept.

Podobně třeba `app/Model/Product` reprezentuje vše kolem produktů. Nenazveme to `Products`, protože nejde o složku plnou produktů (to by tam byly soubory `nokia.php`, `samsung.php`). Je to namespace obsahující třídy pro práci s produkty - `ProductRepository.php`, `ProductService.php`.

Složka `app/Tasks` je v množném čísle proto, že obsahuje sadu samostatných spustitelných skriptů - `CleanupTask.php`, `ImportTask.php`. Každý z nich je samostatnou jednotkou.

Pro konzistenci doporučujeme používat:
- Jednotné číslo pro namespace reprezentující funkční celek (byť pracující s více entitami)
- Množné číslo pro kolekce samostatných jednotek
- V případě nejistoty nebo pokud nad tím nechcete přemýšlet, zvolte jednotné číslo


Veřejný adresář `www/`
======================

Tento adresář je jediný přístupný z webu (tzv. document-root). Často se můžete setkat i s názvem `public/` místo `www/` - je to jen otázka konvence a na funkčnost rostlináře to nemá vliv. Adresář obsahuje:
- [Vstupní bod |bootstrap#index.php] aplikace `index.php`
- Soubor `.htaccess` s pravidly pro mod_rewrite (u Apache)
- Statické soubory (CSS, JavaScript, obrázky)
- Uploadované soubory

Pro správné zabezpečení aplikace je zásadní mít správně [nakonfigurovaný document-root |nette:troubleshooting#Jak změnit či ostranit z URL adresář www].

.[note]
Nikdy neumisťujte do tohoto adresáře složku `node_modules/` - obsahuje tisíce souborů, které mohou být spustitelné a neměly by být veřejně dostupné.


Aplikační adresář `app/`
========================

Toto je hlavní adresář s aplikačním kódem. Základní struktura:

/--pre
<b>app/</b>
├── <b>Core/</b>               ← infrastrukturní záležitosti
├── <b>Model/</b>              ← business logika
├── <b>Presentation/</b>       ← presentery a šablony
├── <b>Tasks/</b>              ← příkazové skripty
└── <b>Bootstrap.php</b>       ← zaváděcí třída aplikace
\--

`Bootstrap.php` je [startovací třída aplikace|bootstrap], která inicializuje prostředí, načítá konfiguraci a vytváří DI kontejner.

Pojďme se nyní podívat na jednotlivé podadresáře podrobněji.


Presentery a šablony
====================

Prezentační část aplikace máme v adresáři `app/Presentation`. Alternativou je krátké `app/UI`. Je to místo pro všechny presentery, jejich šablony a případné pomocné třídy.

Tuto vrstvu organizujeme podle domén. V komplexním projektu, který kombinuje e-shop, blog a API, by struktura vypadala takto:

/--pre
<b>app/Presentation/</b>
├── <b>Shop/</b>              ← e-shop frontend
│   ├── <b>Product/</b>
│   ├── <b>Cart/</b>
│   └── <b>Order/</b>
├── <b>Blog/</b>              ← blog
│   ├── <b>Home/</b>
│   └── <b>Post/</b>
├── <b>Admin/</b>             ← administrace
│   ├── <b>Dashboard/</b>
│   └── <b>Products/</b>
└── <b>Api/</b>               ← API endpointy
	└── <b>V1/</b>
\--

Naopak u jednoduchého blogu bychom použili členění:

/--pre
<b>app/Presentation/</b>
├── <b>Front/</b>             ← frontend webu
│   ├── <b>Home/</b>
│   └── <b>Post/</b>
├── <b>Admin/</b>             ← administrace
│   ├── <b>Dashboard/</b>
│   └── <b>Posts/</b>
├── <b>Error/</b>
└── <b>Export/</b>            ← RSS, sitemapy atd.
\--

Složky jako `Home/` nebo `Dashboard/` obsahují presentery a šablony. Složky jako `Front/`, `Admin/` nebo `Api/` nazýváme **moduly**. Technicky jde o běžné adresáře, které slouží k logickému členění aplikace.

Každá složka s presenterem obsahuje stejně pojmenovaný presenter a jeho šablony. Například složka `Dashboard/` obsahuje:

/--pre
<b>Dashboard/</b>
├── <b>DashboardPresenter.php</b>     ← presenter
└── <b>default.latte</b>              ← šablona
\--

Tato adresářová struktura se odráží ve jmenných prostorech tříd. Například `DashboardPresenter` se nachází ve jmenném prostoru `App\Presentation\Admin\Dashboard` (viz [#mapování presenterů]):

```php
namespace App\Presentation\Admin\Dashboard;

class DashboardPresenter extends Nette\Application\UI\Presenter
{
	// ...
}
```

Na presenter `Dashboard` uvnitř modulu `Admin` odkazujeme v aplikaci pomocí dvojtečkové notace jako na `Admin:Dashboard`. Na jeho akci `default` potom jako na `Admin:Dashboard:default`. V případě zanořených modulů používáme více dvojteček, například `Shop:Order:Detail:default`.


Flexibilní vývoj struktury
--------------------------

Jednou z velkých výhod této struktury je, jak elegantně se přizpůsobuje rostoucím potřebám projektu. Jako příklad si vezměme část generující XML feedy. Na začátku máme jednoduchou podobu:

/--pre
<b>Export/</b>
├── <b>ExportPresenter.php</b>   ← jeden presenter pro všechny exporty
├── <b>sitemap.latte</b>         ← šablona pro sitemapu
└── <b>feed.latte</b>            ← šablona pro RSS feed
\--

Časem přibydou další typy feedů a potřebujeme pro ně více logiky... Žádný problém! Složka `Export/` se jednoduše stane modulem:

/--pre
<b>Export/</b>
├── <b>Sitemap/</b>
│   ├── <b>SitemapPresenter.php</b>
│   └── <b>sitemap.latte</b>
└── <b>Feed/</b>
	├── <b>FeedPresenter.php</b>
	├── <b>zbozi.latte</b>         ← feed pro Zboží.cz
	└── <b>heureka.latte</b>       ← feed pro Heureka.cz
\--

Tato transformace je naprosto plynulá - stačí vytvořit nové podsložky, rozdělit do nich kód a aktualizovat odkazy (např. z `Export:feed` na `Export:Feed:zbozi`). Díky tomu můžeme strukturu postupně rozšiřovat podle potřeby, úroveň zanoření není nijak omezena.

Pokud například v administraci máte mnoho presenterů týkajících se správy objednávek, jako jsou `OrderDetail`, `OrderEdit`, `OrderDispatch` atd., můžete pro lepší organizovanost v tomto místě vytvořit modul (složku) `Order`, ve kterém budou (složky pro) presentery `Detail`, `Edit`, `Dispatch` a další.


Umístění šablon
---------------

V předchozích ukázkách jsme viděli, že šablony jsou umístěny přímo ve složce s presenterem:

/--pre
<b>Dashboard/</b>
├── <b>DashboardPresenter.php</b>     ← presenter
├── <b>DashboardTemplate.php</b>      ← volitelná třída pro šablonu
└── <b>default.latte</b>              ← šablona
\--

Toto umístění se v praxi ukazuje jako nejpohodlnější - všechny související soubory máte hned po ruce.

Alternativně můžete šablony umístit do podsložky `templates/`. Nette podporuje obě varianty. Dokonce můžete šablony umístit i úplně mimo `Presentation/` složku. Vše o možnostech umístění šablon najdete v kapitole [Hledání šablon|templates#Hledání šablon].


Pomocné třídy a komponenty
--------------------------

K prezenterům a šablonám často patří i další pomocné soubory. Umístíme je logicky podle jejich působnosti:

1. **Přímo u presenteru** v případě specifických komponent pro daný presenter:

/--pre
<b>Product/</b>
├── <b>ProductPresenter.php</b>
├── <b>ProductGrid.php</b>        ← komponenta pro výpis produktů
└── <b>FilterForm.php</b>         ← formulář pro filtrování
\--

2. **Pro modul** - doporučujeme využít složku `Accessory`, která se umístí přehledně hned na začátku abecedy:

/--pre
<b>Front/</b>
├── <b>Accessory/</b>
│   ├── <b>NavbarControl.php</b>    ← komponenty pro frontend
│   └── <b>TemplateFilters.php</b>
├── <b>Product/</b>
└── <b>Cart/</b>
\--

3. **Pro celou aplikaci** - v `Presentation/Accessory/`:
/--pre
<b>app/Presentation/</b>
├── <b>Accessory/</b>
│   ├── <b>LatteExtension.php</b>
│   └── <b>TemplateFilters.php</b>
├── <b>Front/</b>
└── <b>Admin/</b>
\--

Nebo můžete pomocné třídy jako `LatteExtension.php` nebo `TemplateFilters.php` umístit do infrastrukturní složky `app/Core/Latte/`. A komponenty do `app/Components`. Volba závisí na zvyklostech týmu.


Model - srdce aplikace
======================

Model obsahuje veškerou business logiku aplikace. Pro jeho organizaci platí opět pravidlo - strukturujeme podle domén:

/--pre
<b>app/Model/</b>
├── <b>Payment/</b>                   ← vše kolem plateb
│   ├── <b>PaymentFacade.php</b>      ← hlavní vstupní bod
│   ├── <b>PaymentRepository.php</b>
│   ├── <b>Payment.php</b>            ← entita
├── <b>Order/</b>                     ← vše kolem objednávek
│   ├── <b>OrderFacade.php</b>
│   ├── <b>OrderRepository.php</b>
│   ├── <b>Order.php</b>
└── <b>Shipping/</b>                  ← vše kolem dopravy
\--

V modelu se typicky setkáte s těmito typy tříd:

**Fasády**: představují hlavní vstupní bod do konkrétní domény v aplikaci. Působí jako orchestrátor, který koordinuje spolupráci mezi různými službami za účelem implementace kompletních use-cases (jako "vytvoř objednávku" nebo "zpracuj platbu"). Pod svojí orchestrační vrstvou fasáda skrývá implementační detaily před zbytkem aplikace, čímž poskytuje čisté rozhraní pro práci s danou doménou.

```php
class OrderFacade
{
	public function createOrder(Cart $cart): Order
	{
		// validace
		// vytvoření objednávky
		// odeslání e-mailu
		// zapsání do statistik
	}
}
```

**Služby**: zaměřují se na specifickou business operaci v rámci domény. Na rozdíl od fasády, která orchestruje celé use-cases, služba implementuje konkrétní byznys logiku (jako výpočty cen nebo zpracování plateb). Služby jsou typicky bezstavové a mohou být použity buď fasádami jako stavební bloky pro komplexnější operace, nebo přímo jinými částmi aplikace pro jednodušší úkony.

```php
class PricingService
{
	public function calculateTotal(Order $order): Money
	{
		// výpočet ceny
	}
}
```

**Repozitáře**: zajišťují veškerou komunikaci s datovým úložištěm, typicky databází. Jeho úkolem je načítání a ukládání entit a implementace metod pro jejich vyhledávání. Repozitář odstiňuje zbytek aplikace od implementačních detailů databáze a poskytuje objektově orientované rozhraní pro práci s daty.

```php
class OrderRepository
{
	public function find(int $id): ?Order
	{
	}

	public function findByCustomer(int $customerId): array
	{
	}
}
```

**Entity**: objekty reprezentující hlavní byznys koncepty v aplikaci, které mají svou identitu a mění se v čase. Typicky jde o třídy mapované na databázové tabulky pomocí ORM (jako Nette Database Explorer nebo Doctrine). Entity mohou obsahovat business pravidla týkající se jejich dat a validační logiku.

```php
// Entita mapovaná na databázovou tabulku orders
class Order extends Nette\Database\Table\ActiveRow
{
	public function addItem(Product $product, int $quantity): void
	{
		$this->related('order_items')->insert([
			'product_id' => $product->id,
			'quantity' => $quantity,
			'unit_price' => $product->price,
		]);
	}
}
```

**Value objekty**: neměnné objekty reprezentující hodnoty bez vlastní identity - například peněžní částka nebo e-mailová adresa. Dvě instance value objektu se stejnými hodnotami jsou považovány za identické.


Infrastrukturní kód
===================

Složka `Core/` (nebo také `Infrastructure/`) je domovem pro technický základ aplikace. Infrastrukturní kód typicky zahrnuje:

/--pre
<b>app/Core/</b>
├── <b>Router/</b>               ← routování a URL management
│   └── <b>RouterFactory.php</b>
├── <b>Security/</b>             ← autentizace a autorizace
│   ├── <b>Authenticator.php</b>
│   └── <b>Authorizator.php</b>
├── <b>Logging/</b>              ← logování a monitoring
│   ├── <b>SentryLogger.php</b>
│   └── <b>FileLogger.php</b>
├── <b>Cache/</b>                ← cachovací vrstva
│   └── <b>FullPageCache.php</b>
└── <b>Integration/</b>          ← integrace s ext. službami
	├── <b>Slack/</b>
	└── <b>Stripe/</b>
\--

U menších projektů pochopitelně stačí ploché členění:

/--pre
<b>Core/</b>
├── <b>RouterFactory.php</b>
├── <b>Authenticator.php</b>
└── <b>QueueMailer.php</b>
\--

Jde o kód, který:

- Řeší technickou infrastrukturu (routování, logování, cachování)
- Integruje externí služby (Sentry, Elasticsearch, Redis)
- Poskytuje základní služby pro celou aplikaci (mail, databáze)
- Je většinou nezávislý na konkrétní doméně - cache nebo logger funguje stejně pro eshop či blog.

Tápete, jestli určitá třída patří sem, nebo do modelu? Klíčový rozdíl je v tom, že kód v `Core/`:

- Neví nic o doméně (produkty, objednávky, články)
- Je většinou možné ho přenést do jiného projektu
- Řeší "jak to funguje" (jak poslat mail), nikoliv "co to dělá" (jaký mail poslat)

Příklad pro lepší pochopení:

- `App\Core\MailerFactory` - vytváří instance třídy pro odesílání e-mailů, řeší SMTP nastavení
- `App\Model\OrderMailer` - používá `MailerFactory` k odesílání e-mailů o objednávkách, zná jejich šablony a ví, kdy se mají poslat


Příkazové skripty
=================

Aplikace často potřebují vykonávat činnosti mimo běžné HTTP požadavky - ať už jde o zpracování dat v pozadí, údržbu, nebo periodické úlohy. Pro spouštění slouží jednoduché skripty v adresáři `bin/`, samotnou implementační logiku pak umisťujeme do  `app/Tasks/` (případně `app/Commands/`).

Příklad:

/--pre
<b>app/Tasks/</b>
├── <b>Maintenance/</b>               ← údržbové skripty
│   ├── <b>CleanupCommand.php</b>     ← mazání starých dat
│   └── <b>DbOptimizeCommand.php</b>  ← optimalizace databáze
├── <b>Integration/</b>               ← integrace s externími systémy
│   ├── <b>ImportProducts.php</b>     ← import z dodavatelského systému
│   └── <b>SyncOrders.php</b>         ← synchronizace objednávek
└── <b>Scheduled/</b>                 ← pravidelné úlohy
	├── <b>NewsletterCommand.php</b>  ← rozesílání newsletterů
	└── <b>ReminderCommand.php</b>    ← notifikace zákazníkům
\--

Co patří do modelu a co do příkazových skriptů? Například logika pro odeslání jednoho e-mailu je součástí modelu, hromadná rozesílka tisíců e-mailů už patří do `Tasks/`.

Úlohy obvykle [spouštíme z příkazového řádku |https://blog.nette.org/cs/cli-skripty-v-nette-aplikaci] nebo přes cron. Lze je spouštět i přes HTTP požadavek, ale je nutné myslet na bezpečnost. Presenter, který úlohu spustí, je potřeba zabezpečit, například jen pro přihlášené uživatele nebo silným tokenem a přístupem z povolených IP adres. U dlouhých úloh je nutné zvýšit časový limit skriptu a použít `session_write_close()`, aby se nezamykala session.


Další možné adresáře
====================

Kromě zmíněných základních adresářů můžete podle potřeb projektu přidat další specializované složky. Podívejme se na nejčastější z nich a jejich použití:

/--pre
<b>app/</b>
├── <b>Api/</b>              ← logika pro API nezávislá na prezentační vrstvě
├── <b>Database/</b>         ← migrační skripty a seedery pro testovací data
├── <b>Components/</b>       ← sdílené vizuální komponenty napříč celou aplikací
├── <b>Event/</b>            ← užitečné pokud používáte event-driven architekturu
├── <b>Mail/</b>             ← e-mailové šablony a související logika
└── <b>Utils/</b>            ← pomocné třídy
\--

Pro sdílené vizuální komponenty používané v presenterech napříč aplikací lze použít složku `app/Components` nebo `app/Controls`:

/--pre
<b>app/Components/</b>
├── <b>Form/</b>                 ← sdílené formulářové komponenty
│   ├── <b>SignInForm.php</b>
│   └── <b>UserForm.php</b>
├── <b>Grid/</b>                 ← komponenty pro výpisy dat
│   └── <b>DataGrid.php</b>
└── <b>Navigation/</b>           ← navigační prvky
	├── <b>Breadcrumbs.php</b>
	└── <b>Menu.php</b>
\--

Sem patří komponenty, které mají komplexnější logiku. Pokud chcete komponenty sdílet mezi více projekty, je vhodné je vyčlenit do samostatného composer balíčku.

Do adresáře `app/Mail` můžete umístit správu e-mailové komunikace:

/--pre
<b>app/Mail/</b>
├── <b>templates/</b>            ← e-mailové šablony
│   ├── <b>order-confirmation.latte</b>
│   └── <b>welcome.latte</b>
└── <b>OrderMailer.php</b>
\--


Mapování presenterů
===================

Mapování definuje pravidla pro odvozování názvu třídy z názvu presenteru. Specifikujeme je v [konfiguraci|configuration] pod klíčem `application › mapping`.

Na této stránce jsme si ukázali, že presentery umísťujeme do složky `app/Presentation` (případně `app/UI`). Tuto konvenci musíme Nette sdělit v konfiguračním souboru. Stačí jeden řádek:

```neon
application:
	mapping: App\Presentation\*\**Presenter
```

Jak mapování funguje? Pro lepší pochopení si nejprve představme aplikaci bez modulů. Chceme, aby třídy presenterů spadaly do jmenného prostoru `App\Presentation`, aby se presenter `Home` mapoval na třídu `App\Presentation\HomePresenter`. Což dosáhneme touto konfigurací:

```neon
application:
	mapping: App\Presentation\*Presenter
```

Mapování funguje tak, že název presenteru `Home` nahradí hvězdičku v masce `App\Presentation\*Presenter`, čímž získáme výsledný název třídy `App\Presentation\HomePresenter`. Jednoduché!

Jak ale vidíte v ukázkách v této a dalších kapitolách, třídy presenterů umisťujeme do eponymních podadresářů, například presenter `Home` se mapuje na třídu `App\Presentation\Home\HomePresenter`. Toho dosáhneme zdvojením dvojtečky (vyžaduje Nette Application 3.2):

```neon
application:
	mapping: App\Presentation\**Presenter
```

Nyní přistoupíme k mapování presenterů do modulů. Pro každý modul můžeme definovat specifické mapování:

```neon
application:
	mapping:
		Front: App\Presentation\Front\**Presenter
		Admin: App\Presentation\Admin\**Presenter
		Api: App\Api\*Presenter
```

Podle této konfigurace se presenter `Front:Home` mapuje na třídu `App\Presentation\Front\Home\HomePresenter`, zatímco presenter `Api:OAuth` na třídu `App\Api\OAuthPresenter`.

Protože moduly `Front` i `Admin` mají podobný způsob mapování a takových modulů bude nejspíš více, je možné vytvořit obecné pravidlo, které je nahradí. Do masky třídy tak přibude nová hvězdička pro modul:

```neon
application:
	mapping:
		*: App\Presentation\*\**Presenter
		Api: App\Api\*Presenter
```

Funguje to i pro hlouběji zanořené adresářové struktury, jako je například presenter `Admin:User:Edit`, se segment s hvězdičkou opakuje pro každou úroveň a výsledkem je třída `App\Presentation\Admin\User\Edit\EditPresenter`.

Alternativním zápisem je místo řetězce použít pole skládající se ze tří segmentů. Tento zápis je ekvivaletní s předchozím:

```neon
application:
	mapping:
		*: [App\Presentation, *, **Presenter]
		Api: [App\Api, '', *Presenter]
```

Adresářová struktura aplikace

Jak navrhnout přehlednou a škálovatelnou adresářovou strukturu pro projekty v Nette Framework? Ukážeme si osvědčené postupy, které vám pomohou s organizací kódu. Dozvíte se:

  • jak logicky rozčlenit aplikaci do adresářů
  • jak strukturu navrhnout tak, aby dobře škálovala s růstem projektu
  • jaké jsou možné alternativy a jejich výhody či nevýhody

Důležité je zmínit, že Nette Framework samotný na žádné konkrétní struktuře nelpí. Je navržen tak, aby se dal snadno přizpůsobit jakýmkoliv potřebám a preferencím.

Základní struktura projektu

Přestože Nette Framework nediktuje žádnou pevnou adresářovou strukturu, existuje osvědčené výchozí uspořádání v podobě Web Project:

web-project/
├── app/              ← adresář s aplikací
├── assets/           ← soubory SCSS, JS, obrázky..., alternativně resources/
├── bin/              ← skripty pro příkazovou řádku
├── config/           ← konfigurace
├── log/              ← logované chyby
├── temp/             ← dočasné soubory, cache
├── tests/            ← testy
├── vendor/           ← knihovny instalované Composerem
└── www/              ← veřejný adresář (document-root)

Tuto strukturu můžete libovolně upravovat podle svých potřeb – složky přejmenovat či přesouvat. Poté stačí pouze upravit relativní cesty k adresářům v souboru Bootstrap.php a případně composer.json. Nic víc není potřeba, žádná složitá rekonfigurace, žádné změny konstant. Nette disponuje chytrou autodetekcí a automaticky rozpozná umístění aplikace včetně její URL základny.

Principy organizace kódu

Když poprve prozkoumáváte nový projekt, měli byste se v něm rychle zorientovat. Představte si, že rozkliknete adresář app/Model/ a uvidíte tuto strukturu:

app/Model/
├── Services/
├── Repositories/
└── Entities/

Z ní vyčtete jen to, že projekt používá nějaké služby, repozitáře a entity. O skutečném účelu aplikace se nedozvíte vůbec nic.

Podívejme se na jiný přístup – organizaci podle domén:

app/Model/
├── Cart/
├── Payment/
├── Order/
└── Product/

Tady je to jiné – na první pohled je jasné, že jde o e-shop. Už samotné názvy adresářů prozrazují, co aplikace umí – pracuje s platbami, objednávkami a produkty.

První přístup (organizace podle typu tříd) přináší v praxi řadu problémů: kód, který spolu logicky souvisí, je roztříštěný do různých složek a musíte mezi nimi přeskakovat. Proto budeme organizovat podle domén.

Jmenné prostory

Je zvykem, že adresářová struktura koresponduje se jmennými prostory v aplikaci. To znamená, že fyzické umístění souborů odpovídá jejich namespace. Například třída umístěná v app/Model/Product/ProductRepository.php by měla mít namespace App\Model\Product. Tento princip pomáhá v orientaci v kódu a zjednodušuje autoloading.

Jednotné vs množné číslo v názvech

Všimněte si, že u hlavních adresářů aplikace používáme jednotné číslo: app, config, log, temp, www. Stejně tak i uvnitř aplikace: Model, Core, Presentation. Je to proto, že každý z nich představuje jeden ucelený koncept.

Podobně třeba app/Model/Product reprezentuje vše kolem produktů. Nenazveme to Products, protože nejde o složku plnou produktů (to by tam byly soubory nokia.php, samsung.php). Je to namespace obsahující třídy pro práci s produkty – ProductRepository.php, ProductService.php.

Složka app/Tasks je v množném čísle proto, že obsahuje sadu samostatných spustitelných skriptů – CleanupTask.php, ImportTask.php. Každý z nich je samostatnou jednotkou.

Pro konzistenci doporučujeme používat:

  • Jednotné číslo pro namespace reprezentující funkční celek (byť pracující s více entitami)
  • Množné číslo pro kolekce samostatných jednotek
  • V případě nejistoty nebo pokud nad tím nechcete přemýšlet, zvolte jednotné číslo

Veřejný adresář www/

Tento adresář je jediný přístupný z webu (tzv. document-root). Často se můžete setkat i s názvem public/ místo www/ – je to jen otázka konvence a na funkčnost rostlináře to nemá vliv. Adresář obsahuje:

  • Vstupní bod aplikace index.php
  • Soubor .htaccess s pravidly pro mod_rewrite (u Apache)
  • Statické soubory (CSS, JavaScript, obrázky)
  • Uploadované soubory

Pro správné zabezpečení aplikace je zásadní mít správně nakonfigurovaný document-root.

Nikdy neumisťujte do tohoto adresáře složku node_modules/ – obsahuje tisíce souborů, které mohou být spustitelné a neměly by být veřejně dostupné.

Aplikační adresář app/

Toto je hlavní adresář s aplikačním kódem. Základní struktura:

app/
├── Core/               ← infrastrukturní záležitosti
├── Model/              ← business logika
├── Presentation/       ← presentery a šablony
├── Tasks/              ← příkazové skripty
└── Bootstrap.php       ← zaváděcí třída aplikace

Bootstrap.php je startovací třída aplikace, která inicializuje prostředí, načítá konfiguraci a vytváří DI kontejner.

Pojďme se nyní podívat na jednotlivé podadresáře podrobněji.

Presentery a šablony

Prezentační část aplikace máme v adresáři app/Presentation. Alternativou je krátké app/UI. Je to místo pro všechny presentery, jejich šablony a případné pomocné třídy.

Tuto vrstvu organizujeme podle domén. V komplexním projektu, který kombinuje e-shop, blog a API, by struktura vypadala takto:

app/Presentation/
├── Shop/              ← e-shop frontend
│   ├── Product/
│   ├── Cart/
│   └── Order/
├── Blog/              ← blog
│   ├── Home/
│   └── Post/
├── Admin/             ← administrace
│   ├── Dashboard/
│   └── Products/
└── Api/               ← API endpointy
	└── V1/

Naopak u jednoduchého blogu bychom použili členění:

app/Presentation/
├── Front/             ← frontend webu
│   ├── Home/
│   └── Post/
├── Admin/             ← administrace
│   ├── Dashboard/
│   └── Posts/
├── Error/
└── Export/            ← RSS, sitemapy atd.

Složky jako Home/ nebo Dashboard/ obsahují presentery a šablony. Složky jako Front/, Admin/ nebo Api/ nazýváme moduly. Technicky jde o běžné adresáře, které slouží k logickému členění aplikace.

Každá složka s presenterem obsahuje stejně pojmenovaný presenter a jeho šablony. Například složka Dashboard/ obsahuje:

Dashboard/
├── DashboardPresenter.php     ← presenter
└── default.latte              ← šablona

Tato adresářová struktura se odráží ve jmenných prostorech tříd. Například DashboardPresenter se nachází ve jmenném prostoru App\Presentation\Admin\Dashboard (viz mapování presenterů):

namespace App\Presentation\Admin\Dashboard;

class DashboardPresenter extends Nette\Application\UI\Presenter
{
	// ...
}

Na presenter Dashboard uvnitř modulu Admin odkazujeme v aplikaci pomocí dvojtečkové notace jako na Admin:Dashboard. Na jeho akci default potom jako na Admin:Dashboard:default. V případě zanořených modulů používáme více dvojteček, například Shop:Order:Detail:default.

Flexibilní vývoj struktury

Jednou z velkých výhod této struktury je, jak elegantně se přizpůsobuje rostoucím potřebám projektu. Jako příklad si vezměme část generující XML feedy. Na začátku máme jednoduchou podobu:

Export/
├── ExportPresenter.php   ← jeden presenter pro všechny exporty
├── sitemap.latte         ← šablona pro sitemapu
└── feed.latte            ← šablona pro RSS feed

Časem přibydou další typy feedů a potřebujeme pro ně více logiky… Žádný problém! Složka Export/ se jednoduše stane modulem:

Export/
├── Sitemap/
│   ├── SitemapPresenter.php
│   └── sitemap.latte
└── Feed/
	├── FeedPresenter.php
	├── zbozi.latte         ← feed pro Zboží.cz
	└── heureka.latte       ← feed pro Heureka.cz

Tato transformace je naprosto plynulá – stačí vytvořit nové podsložky, rozdělit do nich kód a aktualizovat odkazy (např. z Export:feed na Export:Feed:zbozi). Díky tomu můžeme strukturu postupně rozšiřovat podle potřeby, úroveň zanoření není nijak omezena.

Pokud například v administraci máte mnoho presenterů týkajících se správy objednávek, jako jsou OrderDetail, OrderEdit, OrderDispatch atd., můžete pro lepší organizovanost v tomto místě vytvořit modul (složku) Order, ve kterém budou (složky pro) presentery Detail, Edit, Dispatch a další.

Umístění šablon

V předchozích ukázkách jsme viděli, že šablony jsou umístěny přímo ve složce s presenterem:

Dashboard/
├── DashboardPresenter.php     ← presenter
├── DashboardTemplate.php      ← volitelná třída pro šablonu
└── default.latte              ← šablona

Toto umístění se v praxi ukazuje jako nejpohodlnější – všechny související soubory máte hned po ruce.

Alternativně můžete šablony umístit do podsložky templates/. Nette podporuje obě varianty. Dokonce můžete šablony umístit i úplně mimo Presentation/ složku. Vše o možnostech umístění šablon najdete v kapitole Hledání šablon.

Pomocné třídy a komponenty

K prezenterům a šablonám často patří i další pomocné soubory. Umístíme je logicky podle jejich působnosti:

1. Přímo u presenteru v případě specifických komponent pro daný presenter:

Product/
├── ProductPresenter.php
├── ProductGrid.php        ← komponenta pro výpis produktů
└── FilterForm.php         ← formulář pro filtrování

2. Pro modul – doporučujeme využít složku Accessory, která se umístí přehledně hned na začátku abecedy:

Front/
├── Accessory/
│   ├── NavbarControl.php    ← komponenty pro frontend
│   └── TemplateFilters.php
├── Product/
└── Cart/

3. Pro celou aplikaci – v Presentation/Accessory/:

app/Presentation/
├── Accessory/
│   ├── LatteExtension.php
│   └── TemplateFilters.php
├── Front/
└── Admin/

Nebo můžete pomocné třídy jako LatteExtension.php nebo TemplateFilters.php umístit do infrastrukturní složky app/Core/Latte/. A komponenty do app/Components. Volba závisí na zvyklostech týmu.

Model – srdce aplikace

Model obsahuje veškerou business logiku aplikace. Pro jeho organizaci platí opět pravidlo – strukturujeme podle domén:

app/Model/
├── Payment/                   ← vše kolem plateb
│   ├── PaymentFacade.php      ← hlavní vstupní bod
│   ├── PaymentRepository.php
│   ├── Payment.php            ← entita
├── Order/                     ← vše kolem objednávek
│   ├── OrderFacade.php
│   ├── OrderRepository.php
│   ├── Order.php
└── Shipping/                  ← vše kolem dopravy

V modelu se typicky setkáte s těmito typy tříd:

Fasády: představují hlavní vstupní bod do konkrétní domény v aplikaci. Působí jako orchestrátor, který koordinuje spolupráci mezi různými službami za účelem implementace kompletních use-cases (jako „vytvoř objednávku“ nebo „zpracuj platbu“). Pod svojí orchestrační vrstvou fasáda skrývá implementační detaily před zbytkem aplikace, čímž poskytuje čisté rozhraní pro práci s danou doménou.

class OrderFacade
{
	public function createOrder(Cart $cart): Order
	{
		// validace
		// vytvoření objednávky
		// odeslání e-mailu
		// zapsání do statistik
	}
}

Služby: zaměřují se na specifickou business operaci v rámci domény. Na rozdíl od fasády, která orchestruje celé use-cases, služba implementuje konkrétní byznys logiku (jako výpočty cen nebo zpracování plateb). Služby jsou typicky bezstavové a mohou být použity buď fasádami jako stavební bloky pro komplexnější operace, nebo přímo jinými částmi aplikace pro jednodušší úkony.

class PricingService
{
	public function calculateTotal(Order $order): Money
	{
		// výpočet ceny
	}
}

Repozitáře: zajišťují veškerou komunikaci s datovým úložištěm, typicky databází. Jeho úkolem je načítání a ukládání entit a implementace metod pro jejich vyhledávání. Repozitář odstiňuje zbytek aplikace od implementačních detailů databáze a poskytuje objektově orientované rozhraní pro práci s daty.

class OrderRepository
{
	public function find(int $id): ?Order
	{
	}

	public function findByCustomer(int $customerId): array
	{
	}
}

Entity: objekty reprezentující hlavní byznys koncepty v aplikaci, které mají svou identitu a mění se v čase. Typicky jde o třídy mapované na databázové tabulky pomocí ORM (jako Nette Database Explorer nebo Doctrine). Entity mohou obsahovat business pravidla týkající se jejich dat a validační logiku.

// Entita mapovaná na databázovou tabulku orders
class Order extends Nette\Database\Table\ActiveRow
{
	public function addItem(Product $product, int $quantity): void
	{
		$this->related('order_items')->insert([
			'product_id' => $product->id,
			'quantity' => $quantity,
			'unit_price' => $product->price,
		]);
	}
}

Value objekty: neměnné objekty reprezentující hodnoty bez vlastní identity – například peněžní částka nebo e-mailová adresa. Dvě instance value objektu se stejnými hodnotami jsou považovány za identické.

Infrastrukturní kód

Složka Core/ (nebo také Infrastructure/) je domovem pro technický základ aplikace. Infrastrukturní kód typicky zahrnuje:

app/Core/
├── Router/               ← routování a URL management
│   └── RouterFactory.php
├── Security/             ← autentizace a autorizace
│   ├── Authenticator.php
│   └── Authorizator.php
├── Logging/              ← logování a monitoring
│   ├── SentryLogger.php
│   └── FileLogger.php
├── Cache/                ← cachovací vrstva
│   └── FullPageCache.php
└── Integration/          ← integrace s ext. službami
	├── Slack/
	└── Stripe/

U menších projektů pochopitelně stačí ploché členění:

Core/
├── RouterFactory.php
├── Authenticator.php
└── QueueMailer.php

Jde o kód, který:

  • Řeší technickou infrastrukturu (routování, logování, cachování)
  • Integruje externí služby (Sentry, Elasticsearch, Redis)
  • Poskytuje základní služby pro celou aplikaci (mail, databáze)
  • Je většinou nezávislý na konkrétní doméně – cache nebo logger funguje stejně pro eshop či blog.

Tápete, jestli určitá třída patří sem, nebo do modelu? Klíčový rozdíl je v tom, že kód v Core/:

  • Neví nic o doméně (produkty, objednávky, články)
  • Je většinou možné ho přenést do jiného projektu
  • Řeší „jak to funguje“ (jak poslat mail), nikoliv „co to dělá“ (jaký mail poslat)

Příklad pro lepší pochopení:

  • App\Core\MailerFactory – vytváří instance třídy pro odesílání e-mailů, řeší SMTP nastavení
  • App\Model\OrderMailer – používá MailerFactory k odesílání e-mailů o objednávkách, zná jejich šablony a ví, kdy se mají poslat

Příkazové skripty

Aplikace často potřebují vykonávat činnosti mimo běžné HTTP požadavky – ať už jde o zpracování dat v pozadí, údržbu, nebo periodické úlohy. Pro spouštění slouží jednoduché skripty v adresáři bin/, samotnou implementační logiku pak umisťujeme do app/Tasks/ (případně app/Commands/).

Příklad:

app/Tasks/
├── Maintenance/               ← údržbové skripty
│   ├── CleanupCommand.php     ← mazání starých dat
│   └── DbOptimizeCommand.php  ← optimalizace databáze
├── Integration/               ← integrace s externími systémy
│   ├── ImportProducts.php     ← import z dodavatelského systému
│   └── SyncOrders.php         ← synchronizace objednávek
└── Scheduled/                 ← pravidelné úlohy
	├── NewsletterCommand.php  ← rozesílání newsletterů
	└── ReminderCommand.php    ← notifikace zákazníkům

Co patří do modelu a co do příkazových skriptů? Například logika pro odeslání jednoho e-mailu je součástí modelu, hromadná rozesílka tisíců e-mailů už patří do Tasks/.

Úlohy obvykle spouštíme z příkazového řádku nebo přes cron. Lze je spouštět i přes HTTP požadavek, ale je nutné myslet na bezpečnost. Presenter, který úlohu spustí, je potřeba zabezpečit, například jen pro přihlášené uživatele nebo silným tokenem a přístupem z povolených IP adres. U dlouhých úloh je nutné zvýšit časový limit skriptu a použít session_write_close(), aby se nezamykala session.

Další možné adresáře

Kromě zmíněných základních adresářů můžete podle potřeb projektu přidat další specializované složky. Podívejme se na nejčastější z nich a jejich použití:

app/
├── Api/              ← logika pro API nezávislá na prezentační vrstvě
├── Database/         ← migrační skripty a seedery pro testovací data
├── Components/       ← sdílené vizuální komponenty napříč celou aplikací
├── Event/            ← užitečné pokud používáte event-driven architekturu
├── Mail/             ← e-mailové šablony a související logika
└── Utils/            ← pomocné třídy

Pro sdílené vizuální komponenty používané v presenterech napříč aplikací lze použít složku app/Components nebo app/Controls:

app/Components/
├── Form/                 ← sdílené formulářové komponenty
│   ├── SignInForm.php
│   └── UserForm.php
├── Grid/                 ← komponenty pro výpisy dat
│   └── DataGrid.php
└── Navigation/           ← navigační prvky
	├── Breadcrumbs.php
	└── Menu.php

Sem patří komponenty, které mají komplexnější logiku. Pokud chcete komponenty sdílet mezi více projekty, je vhodné je vyčlenit do samostatného composer balíčku.

Do adresáře app/Mail můžete umístit správu e-mailové komunikace:

app/Mail/
├── templates/            ← e-mailové šablony
│   ├── order-confirmation.latte
│   └── welcome.latte
└── OrderMailer.php

Mapování presenterů

Mapování definuje pravidla pro odvozování názvu třídy z názvu presenteru. Specifikujeme je v konfiguraci pod klíčem application › mapping.

Na této stránce jsme si ukázali, že presentery umísťujeme do složky app/Presentation (případně app/UI). Tuto konvenci musíme Nette sdělit v konfiguračním souboru. Stačí jeden řádek:

application:
	mapping: App\Presentation\*\**Presenter

Jak mapování funguje? Pro lepší pochopení si nejprve představme aplikaci bez modulů. Chceme, aby třídy presenterů spadaly do jmenného prostoru App\Presentation, aby se presenter Home mapoval na třídu App\Presentation\HomePresenter. Což dosáhneme touto konfigurací:

application:
	mapping: App\Presentation\*Presenter

Mapování funguje tak, že název presenteru Home nahradí hvězdičku v masce App\Presentation\*Presenter, čímž získáme výsledný název třídy App\Presentation\HomePresenter. Jednoduché!

Jak ale vidíte v ukázkách v této a dalších kapitolách, třídy presenterů umisťujeme do eponymních podadresářů, například presenter Home se mapuje na třídu App\Presentation\Home\HomePresenter. Toho dosáhneme zdvojením dvojtečky (vyžaduje Nette Application 3.2):

application:
	mapping: App\Presentation\**Presenter

Nyní přistoupíme k mapování presenterů do modulů. Pro každý modul můžeme definovat specifické mapování:

application:
	mapping:
		Front: App\Presentation\Front\**Presenter
		Admin: App\Presentation\Admin\**Presenter
		Api: App\Api\*Presenter

Podle této konfigurace se presenter Front:Home mapuje na třídu App\Presentation\Front\Home\HomePresenter, zatímco presenter Api:OAuth na třídu App\Api\OAuthPresenter.

Protože moduly Front i Admin mají podobný způsob mapování a takových modulů bude nejspíš více, je možné vytvořit obecné pravidlo, které je nahradí. Do masky třídy tak přibude nová hvězdička pro modul:

application:
	mapping:
		*: App\Presentation\*\**Presenter
		Api: App\Api\*Presenter

Funguje to i pro hlouběji zanořené adresářové struktury, jako je například presenter Admin:User:Edit, se segment s hvězdičkou opakuje pro každou úroveň a výsledkem je třída App\Presentation\Admin\User\Edit\EditPresenter.

Alternativním zápisem je místo řetězce použít pole skládající se ze tří segmentů. Tento zápis je ekvivaletní s předchozím:

application:
	mapping:
		*: [App\Presentation, *, **Presenter]
		Api: [App\Api, '', *Presenter]