Nette Documentation Preview

syntax
Struttura della Directory dell'Applicazione
*******************************************

<div class=perex>

Come progettare una struttura di directory chiara e scalabile per i progetti in Nette Framework? Mostreremo le best practice che ti aiuteranno a organizzare il codice. Imparerai:

- come **dividere logicamente** l'applicazione in directory
- come progettare la struttura in modo che **scali bene** con la crescita del progetto
- quali sono le **alternative possibili** e i loro vantaggi o svantaggi

</div>


È importante menzionare che Nette Framework stesso non impone alcuna struttura specifica. È progettato per essere facilmente adattabile a qualsiasi esigenza e preferenza.


Struttura di base del progetto
==============================

Sebbene Nette Framework non detti alcuna struttura di directory fissa, esiste una disposizione predefinita comprovata sotto forma di [Web Project|https://github.com/nette/web-project]:

/--pre
<b>web-project/</b>
├── <b>app/</b>              ← directory con l'applicazione
├── <b>assets/</b>           ← file SCSS, JS, immagini..., alternativamente resources/
├── <b>bin/</b>              ← script per la riga di comando
├── <b>config/</b>           ← configurazione
├── <b>log/</b>              ← errori registrati
├── <b>temp/</b>             ← file temporanei, cache
├── <b>tests/</b>            ← test
├── <b>vendor/</b>           ← librerie installate da Composer
└── <b>www/</b>              ← directory pubblica (document-root)
\--

Puoi modificare liberamente questa struttura in base alle tue esigenze - rinominare o spostare le cartelle. Successivamente, basta solo aggiornare i percorsi relativi alle directory nel file `Bootstrap.php` e eventualmente `composer.json`. Non è necessario nient'altro, nessuna riconfigurazione complessa, nessuna modifica delle costanti. Nette dispone di un intelligente autodetect e riconosce automaticamente la posizione dell'applicazione, inclusa la sua base URL.


Principi di organizzazione del codice
=====================================

Quando esplori per la prima volta un nuovo progetto, dovresti orientarti rapidamente. Immagina di espandere la directory `app/Model/` e vedere questa struttura:

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

Da essa deduci solo che il progetto utilizza alcuni servizi, repository ed entità. Non impari assolutamente nulla sullo scopo effettivo dell'applicazione.

Vediamo un approccio diverso - **organizzazione per domini**:

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

Qui è diverso - a prima vista è chiaro che si tratta di un e-shop. I nomi stessi delle directory rivelano cosa sa fare l'applicazione - lavora con pagamenti, ordini e prodotti.

Il primo approccio (organizzazione per tipo di classi) porta in pratica una serie di problemi: il codice che è logicamente correlato è frammentato in diverse cartelle e devi saltare tra di esse. Pertanto, organizzeremo per domini.


Namespace
---------

È consuetudine che la struttura delle directory corrisponda ai namespace nell'applicazione. Ciò significa che la posizione fisica dei file corrisponde al loro namespace. Ad esempio, una classe situata in `app/Model/Product/ProductRepository.php` dovrebbe avere il namespace `App\Model\Product`. Questo principio aiuta nell'orientamento nel codice e semplifica l'autoloading.


Singolare vs Plurale nei nomi
-----------------------------

Nota che per le directory principali dell'applicazione usiamo il singolare: `app`, `config`, `log`, `temp`, `www`. Allo stesso modo anche all'interno dell'applicazione: `Model`, `Core`, `Presentation`. Questo perché ognuna di esse rappresenta un concetto unitario.

Allo stesso modo, ad esempio, `app/Model/Product` rappresenta tutto ciò che riguarda i prodotti. Non lo chiameremo `Products`, perché non è una cartella piena di prodotti (ci sarebbero file `nokia.php`, `samsung.php`). È un namespace contenente classi per lavorare con i prodotti - `ProductRepository.php`, `ProductService.php`.

La cartella `app/Tasks` è al plurale perché contiene un insieme di script eseguibili separati - `CleanupTask.php`, `ImportTask.php`. Ognuno di essi è un'unità separata.

Per coerenza, consigliamo di utilizzare:
- Singolare per namespace che rappresentano un'unità funzionale (anche se lavorano con più entità)
- Plurale per collezioni di unità separate
- In caso di incertezza o se non vuoi pensarci, scegli il singolare


Directory pubblica `www/`
=========================

Questa directory è l'unica accessibile dal web (la cosiddetta document-root). Spesso si può incontrare anche il nome `public/` invece di `www/` - è solo una questione di convenzione e non influisce sulla funzionalità del framework. La directory contiene:
- [Punto di ingresso |bootstrapping#index.php] dell'applicazione `index.php`
- File `.htaccess` con regole per mod_rewrite (per Apache)
- File statici (CSS, JavaScript, immagini)
- File caricati

Per una corretta sicurezza dell'applicazione, è fondamentale avere la [document-root configurata correttamente |nette:troubleshooting#Come modificare o rimuovere la directory www dall URL].

.[note]
Non posizionare mai la cartella `node_modules/` in questa directory - contiene migliaia di file che possono essere eseguibili e non dovrebbero essere accessibili pubblicamente.


Directory dell'applicazione `app/`
==================================

Questa è la directory principale con il codice dell'applicazione. Struttura di base:

/--pre
<b>app/</b>
├── <b>Core/</b>               ← questioni infrastrutturali
├── <b>Model/</b>              ← logica di business
├── <b>Presentation/</b>       ← presenter e template
├── <b>Tasks/</b>              ← script di comando
└── <b>Bootstrap.php</b>       ← classe di avvio dell'applicazione
\--

`Bootstrap.php` è la [classe di avvio dell'applicazione|bootstrapping], che inizializza l'ambiente, carica la configurazione e crea il container DI.

Vediamo ora più nel dettaglio le singole sottodirectory.


Presenter e template
====================

La parte di presentazione dell'applicazione si trova nella directory `app/Presentation`. Un'alternativa è la breve `app/UI`. È il posto per tutti i presenter, i loro template e eventuali classi di supporto.

Organizziamo questo layer per domini. In un progetto complesso che combina e-shop, blog e API, la struttura sarebbe simile a questa:

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

Al contrario, per un semplice blog, useremmo la seguente suddivisione:

/--pre
<b>app/Presentation/</b>
├── <b>Front/</b>             ← frontend del sito
│   ├── <b>Home/</b>
│   └── <b>Post/</b>
├── <b>Admin/</b>             ← amministrazione
│   ├── <b>Dashboard/</b>
│   └── <b>Posts/</b>
├── <b>Error/</b>
└── <b>Export/</b>            ← RSS, sitemap, ecc.
\--

Cartelle come `Home/` o `Dashboard/` contengono presenter e template. Cartelle come `Front/`, `Admin/` o `Api/` le chiamiamo **moduli**. Tecnicamente, sono directory normali che servono a dividere logicamente l'applicazione.

Ogni cartella con un presenter contiene un presenter con lo stesso nome e i suoi template. Ad esempio, la cartella `Dashboard/` contiene:

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

Questa struttura di directory si riflette nei namespace delle classi. Ad esempio, `DashboardPresenter` si trova nel namespace `App\Presentation\Admin\Dashboard` (vedi [#Mappatura dei presenter]):

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

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

Al presenter `Dashboard` all'interno del modulo `Admin` facciamo riferimento nell'applicazione usando la notazione con i due punti come `Admin:Dashboard`. Alla sua azione `default` poi come `Admin:Dashboard:default`. In caso di moduli nidificati, usiamo più due punti, ad esempio `Shop:Order:Detail:default`.


Sviluppo flessibile della struttura
-----------------------------------

Uno dei grandi vantaggi di questa struttura è come si adatta elegantemente alle crescenti esigenze del progetto. Prendiamo come esempio la parte che genera feed XML. All'inizio abbiamo una forma semplice:

/--pre
<b>Export/</b>
├── <b>ExportPresenter.php</b>   ← un presenter per tutte le esportazioni
├── <b>sitemap.latte</b>         ← template per la sitemap
└── <b>feed.latte</b>            ← template per il feed RSS
\--

Con il tempo, si aggiungono altri tipi di feed e abbiamo bisogno di più logica per essi... Nessun problema! La cartella `Export/` diventa semplicemente un modulo:

/--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 per Zboží.cz
	└── <b>heureka.latte</b>       ← feed per Heureka.cz
\--

Questa trasformazione è assolutamente fluida - basta creare nuove sottocartelle, dividerci il codice e aggiornare i link (ad esempio da `Export:feed` a `Export:Feed:zbozi`). Grazie a ciò, possiamo espandere gradualmente la struttura secondo necessità, il livello di nidificazione non è limitato in alcun modo.

Se, ad esempio, nell'amministrazione hai molti presenter relativi alla gestione degli ordini, come sono `OrderDetail`, `OrderEdit`, `OrderDispatch` ecc., puoi creare un modulo (cartella) `Order` in questo punto per una migliore organizzazione, che conterrà (le cartelle per) i presenter `Detail`, `Edit`, `Dispatch` e altri.


Posizionamento dei template
---------------------------

Negli esempi precedenti abbiamo visto che i template si trovano direttamente nella cartella con il presenter:

/--pre
<b>Dashboard/</b>
├── <b>DashboardPresenter.php</b>     ← presenter
├── <b>DashboardTemplate.php</b>      ← classe opzionale per il template
└── <b>default.latte</b>              ← template
\--

Questa posizione si rivela in pratica la più comoda - hai tutti i file correlati subito a portata di mano.

In alternativa, puoi posizionare i template in una sottocartella `templates/`. Nette supporta entrambe le varianti. Puoi persino posizionare i template completamente al di fuori della cartella `Presentation/`. Tutto sulle possibilità di posizionamento dei template si trova nel capitolo [Ricerca dei template |templates#Ricerca dei template].


Classi di supporto e componenti
-------------------------------

Ai presenter e ai template spesso appartengono anche altri file di supporto. Li posizioniamo logicamente in base al loro ambito di applicazione:

1. **Direttamente presso il presenter** nel caso di componenti specifici per quel presenter:

/--pre
<b>Product/</b>
├── <b>ProductPresenter.php</b>
├── <b>ProductGrid.php</b>        ← componente per l'elenco dei prodotti
└── <b>FilterForm.php</b>         ← form per il filtraggio
\--

2. **Per il modulo** - consigliamo di utilizzare la cartella `Accessory`, che si posiziona ordinatamente all'inizio dell'alfabeto:

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

3. **Per l'intera applicazione** - in `Presentation/Accessory/`:
/--pre
<b>app/Presentation/</b>
├── <b>Accessory/</b>
│   ├── <b>LatteExtension.php</b>
│   └── <b>TemplateFilters.php</b>
├── <b>Front/</b>
└── <b>Admin/</b>
\--

Oppure puoi posizionare classi di supporto come `LatteExtension.php` o `TemplateFilters.php` nella cartella infrastrutturale `app/Core/Latte/`. E i componenti in `app/Components`. La scelta dipende dalle abitudini del team.


Model - il cuore dell'applicazione
==================================

Il model contiene tutta la logica di business dell'applicazione. Per la sua organizzazione vale di nuovo la regola - strutturiamo per domini:

/--pre
<b>app/Model/</b>
├── <b>Payment/</b>                   ← tutto ciò che riguarda i pagamenti
│   ├── <b>PaymentFacade.php</b>      ← punto di ingresso principale
│   ├── <b>PaymentRepository.php</b>
│   ├── <b>Payment.php</b>            ← entità
├── <b>Order/</b>                     ← tutto ciò che riguarda gli ordini
│   ├── <b>OrderFacade.php</b>
│   ├── <b>OrderRepository.php</b>
│   ├── <b>Order.php</b>
└── <b>Shipping/</b>                  ← tutto ciò che riguarda la spedizione
\--

Nel model si incontrano tipicamente questi tipi di classi:

**Facade**: rappresentano il punto di ingresso principale a un dominio specifico nell'applicazione. Agiscono come orchestratori che coordinano la collaborazione tra diversi servizi allo scopo di implementare use-case completi (come "crea ordine" o "elabora pagamento"). Sotto il suo layer di orchestrazione, la facade nasconde i dettagli implementativi al resto dell'applicazione, fornendo così un'interfaccia pulita per lavorare con il dominio dato.

```php
class OrderFacade
{
	public function createOrder(Cart $cart): Order
	{
		// validazione
		// creazione dell'ordine
		// invio dell'e-mail
		// scrittura nelle statistiche
	}
}
```

**Servizi**: si concentrano su un'operazione di business specifica all'interno del dominio. A differenza della facade, che orchestra interi use-case, un servizio implementa una logica di business specifica (come calcoli di prezzi o elaborazione di pagamenti). I servizi sono tipicamente senza stato e possono essere utilizzati sia dalle facade come blocchi di costruzione per operazioni più complesse, sia direttamente da altre parti dell'applicazione per compiti più semplici.

```php
class PricingService
{
	public function calculateTotal(Order $order): Money
	{
		// calcolo del prezzo
	}
}
```

**Repository**: assicurano tutta la comunicazione con l'archivio dati, tipicamente un database. Il suo compito è caricare e salvare entità e implementare metodi per la loro ricerca. Il repository isola il resto dell'applicazione dai dettagli implementativi del database e fornisce un'interfaccia orientata agli oggetti per lavorare con i dati.

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

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

**Entità**: oggetti che rappresentano i principali concetti di business nell'applicazione, che hanno una loro identità e cambiano nel tempo. Tipicamente si tratta di classi mappate su tabelle di database tramite ORM (come Nette Database Explorer o Doctrine). Le entità possono contenere regole di business relative ai loro dati e logica di validazione.

```php
// Entità mappata sulla tabella di database 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 object**: oggetti immutabili che rappresentano valori senza una propria identità - ad esempio un importo monetario o un indirizzo e-mail. Due istanze di un value object con gli stessi valori sono considerate identiche.


Codice infrastrutturale
=======================

La cartella `Core/` (o anche `Infrastructure/`) è la casa della base tecnica dell'applicazione. Il codice infrastrutturale include tipicamente:

/--pre
<b>app/Core/</b>
├── <b>Router/</b>               ← routing e gestione URL
│   └── <b>RouterFactory.php</b>
├── <b>Security/</b>             ← autenticazione e autorizzazione
│   ├── <b>Authenticator.php</b>
│   └── <b>Authorizator.php</b>
├── <b>Logging/</b>              ← logging e monitoraggio
│   ├── <b>SentryLogger.php</b>
│   └── <b>FileLogger.php</b>
├── <b>Cache/</b>                ← layer di caching
│   └── <b>FullPageCache.php</b>
└── <b>Integration/</b>          ← integrazione con servizi est.
	├── <b>Slack/</b>
	└── <b>Stripe/</b>
\--

Per progetti più piccoli, ovviamente, basta una suddivisione piatta:

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

Si tratta di codice che:

- Risolve l'infrastruttura tecnica (routing, logging, caching)
- Integra servizi esterni (Sentry, Elasticsearch, Redis)
- Fornisce servizi di base per l'intera applicazione (mail, database)
- È per lo più indipendente dal dominio specifico - la cache o il logger funzionano allo stesso modo per un eshop o un blog.

Hai dubbi se una certa classe appartiene qui o al model? La differenza chiave è che il codice in `Core/`:

- Non sa nulla del dominio (prodotti, ordini, articoli)
- È per lo più possibile trasferirlo a un altro progetto
- Risolve "come funziona" (come inviare una mail), non "cosa fa" (quale mail inviare)

Esempio per una migliore comprensione:

- `App\Core\MailerFactory` - crea istanze della classe per l'invio di e-mail, gestisce le impostazioni SMTP
- `App\Model\OrderMailer` - utilizza `MailerFactory` per inviare e-mail sugli ordini, conosce i loro template e sa quando devono essere inviati


Script di comando
=================

Le applicazioni spesso necessitano di eseguire attività al di fuori delle normali richieste HTTP - che si tratti di elaborazione dati in background, manutenzione o attività periodiche. Per l'esecuzione servono semplici script nella directory `bin/`, la logica implementativa la posizioniamo poi in `app/Tasks/` (eventualmente `app/Commands/`).

Esempio:

/--pre
<b>app/Tasks/</b>
├── <b>Maintenance/</b>               ← script di manutenzione
│   ├── <b>CleanupCommand.php</b>     ← cancellazione di dati vecchi
│   └── <b>DbOptimizeCommand.php</b>  ← ottimizzazione del database
├── <b>Integration/</b>               ← integrazione con sistemi esterni
│   ├── <b>ImportProducts.php</b>     ← importazione dal sistema del fornitore
│   └── <b>SyncOrders.php</b>         ← sincronizzazione degli ordini
└── <b>Scheduled/</b>                 ← attività regolari
	├── <b>NewsletterCommand.php</b>  ← invio di newsletter
	└── <b>ReminderCommand.php</b>    ← notifiche ai clienti
\--

Cosa appartiene al model e cosa agli script di comando? Ad esempio, la logica per l'invio di una singola e-mail fa parte del model, l'invio massivo di migliaia di e-mail appartiene già a `Tasks/`.

Le attività vengono solitamente [eseguite dalla riga di comando |https://blog.nette.org/en/cli-scripts-in-nette-application] o tramite cron. Possono essere eseguite anche tramite richiesta HTTP, ma è necessario pensare alla sicurezza. Il presenter che avvia l'attività deve essere protetto, ad esempio solo per utenti loggati o con un token forte e accesso da indirizzi IP consentiti. Per attività lunghe è necessario aumentare il limite di tempo dello script e utilizzare `session_write_close()`, per non bloccare la sessione.


Altre possibili directory
=========================

Oltre alle directory di base menzionate, puoi aggiungere altre cartelle specializzate in base alle esigenze del progetto. Vediamo le più comuni e il loro utilizzo:

/--pre
<b>app/</b>
├── <b>Api/</b>              ← logica per API indipendente dal layer di presentazione
├── <b>Database/</b>         ← script di migrazione e seeder per dati di test
├── <b>Components/</b>       ← componenti visivi condivisi in tutta l'applicazione
├── <b>Event/</b>            ← utile se usi architettura event-driven
├── <b>Mail/</b>             ← template e-mail e logica correlata
└── <b>Utils/</b>            ← classi di utilità
\--

Per i componenti visivi condivisi utilizzati nei presenter in tutta l'applicazione, è possibile utilizzare la cartella `app/Components` o `app/Controls`:

/--pre
<b>app/Components/</b>
├── <b>Form/</b>                 ← componenti form condivisi
│   ├── <b>SignInForm.php</b>
│   └── <b>UserForm.php</b>
├── <b>Grid/</b>                 ← componenti per elenchi di dati
│   └── <b>DataGrid.php</b>
└── <b>Navigation/</b>           ← elementi di navigazione
	├── <b>Breadcrumbs.php</b>
	└── <b>Menu.php</b>
\--

Qui appartengono i componenti che hanno una logica più complessa. Se vuoi condividere componenti tra più progetti, è consigliabile estrarli in un pacchetto composer separato.

Nella directory `app/Mail` puoi posizionare la gestione della comunicazione e-mail:

/--pre
<b>app/Mail/</b>
├── <b>templates/</b>            ← template e-mail
│   ├── <b>order-confirmation.latte</b>
│   └── <b>welcome.latte</b>
└── <b>OrderMailer.php</b>
\--


Mappatura dei presenter
=======================

La mappatura definisce le regole per derivare il nome della classe dal nome del presenter. Le specifichiamo nella [configurazione|configuration] sotto la chiave `application › mapping`.

In questa pagina abbiamo mostrato che posizioniamo i presenter nella cartella `app/Presentation` (eventualmente `app/UI`). Dobbiamo comunicare questa convenzione a Nette nel file di configurazione. Basta una riga:

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

Come funziona la mappatura? Per una migliore comprensione, immaginiamo prima un'applicazione senza moduli. Vogliamo che le classi dei presenter rientrino nel namespace `App\Presentation`, in modo che il presenter `Home` si mappi sulla classe `App\Presentation\HomePresenter`. Cosa che otteniamo con questa configurazione:

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

La mappatura funziona in modo che il nome del presenter `Home` sostituisca l'asterisco nella maschera `App\Presentation\*Presenter`, ottenendo così il nome della classe risultante `App\Presentation\HomePresenter`. Semplice!

Come però vedi negli esempi in questo e altri capitoli, posizioniamo le classi dei presenter in sottodirectory omonime, ad esempio il presenter `Home` si mappa sulla classe `App\Presentation\Home\HomePresenter`. Otteniamo ciò raddoppiando i due punti (richiede Nette Application 3.2):

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

Ora passiamo alla mappatura dei presenter nei moduli. Per ogni modulo possiamo definire una mappatura specifica:

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

Secondo questa configurazione, il presenter `Front:Home` si mappa sulla classe `App\Presentation\Front\Home\HomePresenter`, mentre il presenter `Api:OAuth` sulla classe `App\Api\OAuthPresenter`.

Poiché i moduli `Front` e `Admin` hanno un modo simile di mappatura e probabilmente ci saranno più moduli di questo tipo, è possibile creare una regola generale che li sostituisca. Alla maschera della classe si aggiunge così un nuovo asterisco per il modulo:

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

Funziona anche per strutture di directory più profondamente nidificate, come ad esempio il presenter `Admin:User:Edit`, il segmento con l'asterisco si ripete per ogni livello e il risultato è la classe `App\Presentation\Admin\User\Edit\EditPresenter`.

Una notazione alternativa è usare un array composto da tre segmenti invece di una stringa. Questa notazione è equivalente alla precedente:

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

Struttura della Directory dell'Applicazione

Come progettare una struttura di directory chiara e scalabile per i progetti in Nette Framework? Mostreremo le best practice che ti aiuteranno a organizzare il codice. Imparerai:

  • come dividere logicamente l'applicazione in directory
  • come progettare la struttura in modo che scali bene con la crescita del progetto
  • quali sono le alternative possibili e i loro vantaggi o svantaggi

È importante menzionare che Nette Framework stesso non impone alcuna struttura specifica. È progettato per essere facilmente adattabile a qualsiasi esigenza e preferenza.

Struttura di base del progetto

Sebbene Nette Framework non detti alcuna struttura di directory fissa, esiste una disposizione predefinita comprovata sotto forma di Web Project:

web-project/
├── app/              ← directory con l'applicazione
├── assets/           ← file SCSS, JS, immagini..., alternativamente resources/
├── bin/              ← script per la riga di comando
├── config/           ← configurazione
├── log/              ← errori registrati
├── temp/             ← file temporanei, cache
├── tests/            ← test
├── vendor/           ← librerie installate da Composer
└── www/              ← directory pubblica (document-root)

Puoi modificare liberamente questa struttura in base alle tue esigenze – rinominare o spostare le cartelle. Successivamente, basta solo aggiornare i percorsi relativi alle directory nel file Bootstrap.php e eventualmente composer.json. Non è necessario nient'altro, nessuna riconfigurazione complessa, nessuna modifica delle costanti. Nette dispone di un intelligente autodetect e riconosce automaticamente la posizione dell'applicazione, inclusa la sua base URL.

Principi di organizzazione del codice

Quando esplori per la prima volta un nuovo progetto, dovresti orientarti rapidamente. Immagina di espandere la directory app/Model/ e vedere questa struttura:

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

Da essa deduci solo che il progetto utilizza alcuni servizi, repository ed entità. Non impari assolutamente nulla sullo scopo effettivo dell'applicazione.

Vediamo un approccio diverso – organizzazione per domini:

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

Qui è diverso – a prima vista è chiaro che si tratta di un e-shop. I nomi stessi delle directory rivelano cosa sa fare l'applicazione – lavora con pagamenti, ordini e prodotti.

Il primo approccio (organizzazione per tipo di classi) porta in pratica una serie di problemi: il codice che è logicamente correlato è frammentato in diverse cartelle e devi saltare tra di esse. Pertanto, organizzeremo per domini.

Namespace

È consuetudine che la struttura delle directory corrisponda ai namespace nell'applicazione. Ciò significa che la posizione fisica dei file corrisponde al loro namespace. Ad esempio, una classe situata in app/Model/Product/ProductRepository.php dovrebbe avere il namespace App\Model\Product. Questo principio aiuta nell'orientamento nel codice e semplifica l'autoloading.

Singolare vs Plurale nei nomi

Nota che per le directory principali dell'applicazione usiamo il singolare: app, config, log, temp, www. Allo stesso modo anche all'interno dell'applicazione: Model, Core, Presentation. Questo perché ognuna di esse rappresenta un concetto unitario.

Allo stesso modo, ad esempio, app/Model/Product rappresenta tutto ciò che riguarda i prodotti. Non lo chiameremo Products, perché non è una cartella piena di prodotti (ci sarebbero file nokia.php, samsung.php). È un namespace contenente classi per lavorare con i prodotti – ProductRepository.php, ProductService.php.

La cartella app/Tasks è al plurale perché contiene un insieme di script eseguibili separati – CleanupTask.php, ImportTask.php. Ognuno di essi è un'unità separata.

Per coerenza, consigliamo di utilizzare:

  • Singolare per namespace che rappresentano un'unità funzionale (anche se lavorano con più entità)
  • Plurale per collezioni di unità separate
  • In caso di incertezza o se non vuoi pensarci, scegli il singolare

Directory pubblica www/

Questa directory è l'unica accessibile dal web (la cosiddetta document-root). Spesso si può incontrare anche il nome public/ invece di www/ – è solo una questione di convenzione e non influisce sulla funzionalità del framework. La directory contiene:

  • Punto di ingresso dell'applicazione index.php
  • File .htaccess con regole per mod_rewrite (per Apache)
  • File statici (CSS, JavaScript, immagini)
  • File caricati

Per una corretta sicurezza dell'applicazione, è fondamentale avere la document-root configurata correttamente.

Non posizionare mai la cartella node_modules/ in questa directory – contiene migliaia di file che possono essere eseguibili e non dovrebbero essere accessibili pubblicamente.

Directory dell'applicazione app/

Questa è la directory principale con il codice dell'applicazione. Struttura di base:

app/
├── Core/               ← questioni infrastrutturali
├── Model/              ← logica di business
├── Presentation/       ← presenter e template
├── Tasks/              ← script di comando
└── Bootstrap.php       ← classe di avvio dell'applicazione

Bootstrap.php è la classe di avvio dell'applicazione, che inizializza l'ambiente, carica la configurazione e crea il container DI.

Vediamo ora più nel dettaglio le singole sottodirectory.

Presenter e template

La parte di presentazione dell'applicazione si trova nella directory app/Presentation. Un'alternativa è la breve app/UI. È il posto per tutti i presenter, i loro template e eventuali classi di supporto.

Organizziamo questo layer per domini. In un progetto complesso che combina e-shop, blog e API, la struttura sarebbe simile a questa:

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

Al contrario, per un semplice blog, useremmo la seguente suddivisione:

app/Presentation/
├── Front/             ← frontend del sito
│   ├── Home/
│   └── Post/
├── Admin/             ← amministrazione
│   ├── Dashboard/
│   └── Posts/
├── Error/
└── Export/            ← RSS, sitemap, ecc.

Cartelle come Home/ o Dashboard/ contengono presenter e template. Cartelle come Front/, Admin/ o Api/ le chiamiamo moduli. Tecnicamente, sono directory normali che servono a dividere logicamente l'applicazione.

Ogni cartella con un presenter contiene un presenter con lo stesso nome e i suoi template. Ad esempio, la cartella Dashboard/ contiene:

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

Questa struttura di directory si riflette nei namespace delle classi. Ad esempio, DashboardPresenter si trova nel namespace App\Presentation\Admin\Dashboard (vedi Mappatura dei presenter):

namespace App\Presentation\Admin\Dashboard;

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

Al presenter Dashboard all'interno del modulo Admin facciamo riferimento nell'applicazione usando la notazione con i due punti come Admin:Dashboard. Alla sua azione default poi come Admin:Dashboard:default. In caso di moduli nidificati, usiamo più due punti, ad esempio Shop:Order:Detail:default.

Sviluppo flessibile della struttura

Uno dei grandi vantaggi di questa struttura è come si adatta elegantemente alle crescenti esigenze del progetto. Prendiamo come esempio la parte che genera feed XML. All'inizio abbiamo una forma semplice:

Export/
├── ExportPresenter.php   ← un presenter per tutte le esportazioni
├── sitemap.latte         ← template per la sitemap
└── feed.latte            ← template per il feed RSS

Con il tempo, si aggiungono altri tipi di feed e abbiamo bisogno di più logica per essi… Nessun problema! La cartella Export/ diventa semplicemente un modulo:

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

Questa trasformazione è assolutamente fluida – basta creare nuove sottocartelle, dividerci il codice e aggiornare i link (ad esempio da Export:feed a Export:Feed:zbozi). Grazie a ciò, possiamo espandere gradualmente la struttura secondo necessità, il livello di nidificazione non è limitato in alcun modo.

Se, ad esempio, nell'amministrazione hai molti presenter relativi alla gestione degli ordini, come sono OrderDetail, OrderEdit, OrderDispatch ecc., puoi creare un modulo (cartella) Order in questo punto per una migliore organizzazione, che conterrà (le cartelle per) i presenter Detail, Edit, Dispatch e altri.

Posizionamento dei template

Negli esempi precedenti abbiamo visto che i template si trovano direttamente nella cartella con il presenter:

Dashboard/
├── DashboardPresenter.php     ← presenter
├── DashboardTemplate.php      ← classe opzionale per il template
└── default.latte              ← template

Questa posizione si rivela in pratica la più comoda – hai tutti i file correlati subito a portata di mano.

In alternativa, puoi posizionare i template in una sottocartella templates/. Nette supporta entrambe le varianti. Puoi persino posizionare i template completamente al di fuori della cartella Presentation/. Tutto sulle possibilità di posizionamento dei template si trova nel capitolo Ricerca dei template.

Classi di supporto e componenti

Ai presenter e ai template spesso appartengono anche altri file di supporto. Li posizioniamo logicamente in base al loro ambito di applicazione:

1. Direttamente presso il presenter nel caso di componenti specifici per quel presenter:

Product/
├── ProductPresenter.php
├── ProductGrid.php        ← componente per l'elenco dei prodotti
└── FilterForm.php         ← form per il filtraggio

2. Per il modulo – consigliamo di utilizzare la cartella Accessory, che si posiziona ordinatamente all'inizio dell'alfabeto:

Front/
├── Accessory/
│   ├── NavbarControl.php    ← componenti per il frontend
│   └── TemplateFilters.php
├── Product/
└── Cart/

3. Per l'intera applicazione – in Presentation/Accessory/:

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

Oppure puoi posizionare classi di supporto come LatteExtension.php o TemplateFilters.php nella cartella infrastrutturale app/Core/Latte/. E i componenti in app/Components. La scelta dipende dalle abitudini del team.

Model – il cuore dell'applicazione

Il model contiene tutta la logica di business dell'applicazione. Per la sua organizzazione vale di nuovo la regola – strutturiamo per domini:

app/Model/
├── Payment/                   ← tutto ciò che riguarda i pagamenti
│   ├── PaymentFacade.php      ← punto di ingresso principale
│   ├── PaymentRepository.php
│   ├── Payment.php            ← entità
├── Order/                     ← tutto ciò che riguarda gli ordini
│   ├── OrderFacade.php
│   ├── OrderRepository.php
│   ├── Order.php
└── Shipping/                  ← tutto ciò che riguarda la spedizione

Nel model si incontrano tipicamente questi tipi di classi:

Facade: rappresentano il punto di ingresso principale a un dominio specifico nell'applicazione. Agiscono come orchestratori che coordinano la collaborazione tra diversi servizi allo scopo di implementare use-case completi (come „crea ordine“ o „elabora pagamento“). Sotto il suo layer di orchestrazione, la facade nasconde i dettagli implementativi al resto dell'applicazione, fornendo così un'interfaccia pulita per lavorare con il dominio dato.

class OrderFacade
{
	public function createOrder(Cart $cart): Order
	{
		// validazione
		// creazione dell'ordine
		// invio dell'e-mail
		// scrittura nelle statistiche
	}
}

Servizi: si concentrano su un'operazione di business specifica all'interno del dominio. A differenza della facade, che orchestra interi use-case, un servizio implementa una logica di business specifica (come calcoli di prezzi o elaborazione di pagamenti). I servizi sono tipicamente senza stato e possono essere utilizzati sia dalle facade come blocchi di costruzione per operazioni più complesse, sia direttamente da altre parti dell'applicazione per compiti più semplici.

class PricingService
{
	public function calculateTotal(Order $order): Money
	{
		// calcolo del prezzo
	}
}

Repository: assicurano tutta la comunicazione con l'archivio dati, tipicamente un database. Il suo compito è caricare e salvare entità e implementare metodi per la loro ricerca. Il repository isola il resto dell'applicazione dai dettagli implementativi del database e fornisce un'interfaccia orientata agli oggetti per lavorare con i dati.

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

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

Entità: oggetti che rappresentano i principali concetti di business nell'applicazione, che hanno una loro identità e cambiano nel tempo. Tipicamente si tratta di classi mappate su tabelle di database tramite ORM (come Nette Database Explorer o Doctrine). Le entità possono contenere regole di business relative ai loro dati e logica di validazione.

// Entità mappata sulla tabella di database 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 object: oggetti immutabili che rappresentano valori senza una propria identità – ad esempio un importo monetario o un indirizzo e-mail. Due istanze di un value object con gli stessi valori sono considerate identiche.

Codice infrastrutturale

La cartella Core/ (o anche Infrastructure/) è la casa della base tecnica dell'applicazione. Il codice infrastrutturale include tipicamente:

app/Core/
├── Router/               ← routing e gestione URL
│   └── RouterFactory.php
├── Security/             ← autenticazione e autorizzazione
│   ├── Authenticator.php
│   └── Authorizator.php
├── Logging/              ← logging e monitoraggio
│   ├── SentryLogger.php
│   └── FileLogger.php
├── Cache/                ← layer di caching
│   └── FullPageCache.php
└── Integration/          ← integrazione con servizi est.
	├── Slack/
	└── Stripe/

Per progetti più piccoli, ovviamente, basta una suddivisione piatta:

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

Si tratta di codice che:

  • Risolve l'infrastruttura tecnica (routing, logging, caching)
  • Integra servizi esterni (Sentry, Elasticsearch, Redis)
  • Fornisce servizi di base per l'intera applicazione (mail, database)
  • È per lo più indipendente dal dominio specifico – la cache o il logger funzionano allo stesso modo per un eshop o un blog.

Hai dubbi se una certa classe appartiene qui o al model? La differenza chiave è che il codice in Core/:

  • Non sa nulla del dominio (prodotti, ordini, articoli)
  • È per lo più possibile trasferirlo a un altro progetto
  • Risolve „come funziona“ (come inviare una mail), non „cosa fa“ (quale mail inviare)

Esempio per una migliore comprensione:

  • App\Core\MailerFactory – crea istanze della classe per l'invio di e-mail, gestisce le impostazioni SMTP
  • App\Model\OrderMailer – utilizza MailerFactory per inviare e-mail sugli ordini, conosce i loro template e sa quando devono essere inviati

Script di comando

Le applicazioni spesso necessitano di eseguire attività al di fuori delle normali richieste HTTP – che si tratti di elaborazione dati in background, manutenzione o attività periodiche. Per l'esecuzione servono semplici script nella directory bin/, la logica implementativa la posizioniamo poi in app/Tasks/ (eventualmente app/Commands/).

Esempio:

app/Tasks/
├── Maintenance/               ← script di manutenzione
│   ├── CleanupCommand.php     ← cancellazione di dati vecchi
│   └── DbOptimizeCommand.php  ← ottimizzazione del database
├── Integration/               ← integrazione con sistemi esterni
│   ├── ImportProducts.php     ← importazione dal sistema del fornitore
│   └── SyncOrders.php         ← sincronizzazione degli ordini
└── Scheduled/                 ← attività regolari
	├── NewsletterCommand.php  ← invio di newsletter
	└── ReminderCommand.php    ← notifiche ai clienti

Cosa appartiene al model e cosa agli script di comando? Ad esempio, la logica per l'invio di una singola e-mail fa parte del model, l'invio massivo di migliaia di e-mail appartiene già a Tasks/.

Le attività vengono solitamente eseguite dalla riga di comando o tramite cron. Possono essere eseguite anche tramite richiesta HTTP, ma è necessario pensare alla sicurezza. Il presenter che avvia l'attività deve essere protetto, ad esempio solo per utenti loggati o con un token forte e accesso da indirizzi IP consentiti. Per attività lunghe è necessario aumentare il limite di tempo dello script e utilizzare session_write_close(), per non bloccare la sessione.

Altre possibili directory

Oltre alle directory di base menzionate, puoi aggiungere altre cartelle specializzate in base alle esigenze del progetto. Vediamo le più comuni e il loro utilizzo:

app/
├── Api/              ← logica per API indipendente dal layer di presentazione
├── Database/         ← script di migrazione e seeder per dati di test
├── Components/       ← componenti visivi condivisi in tutta l'applicazione
├── Event/            ← utile se usi architettura event-driven
├── Mail/             ← template e-mail e logica correlata
└── Utils/            ← classi di utilità

Per i componenti visivi condivisi utilizzati nei presenter in tutta l'applicazione, è possibile utilizzare la cartella app/Components o app/Controls:

app/Components/
├── Form/                 ← componenti form condivisi
│   ├── SignInForm.php
│   └── UserForm.php
├── Grid/                 ← componenti per elenchi di dati
│   └── DataGrid.php
└── Navigation/           ← elementi di navigazione
	├── Breadcrumbs.php
	└── Menu.php

Qui appartengono i componenti che hanno una logica più complessa. Se vuoi condividere componenti tra più progetti, è consigliabile estrarli in un pacchetto composer separato.

Nella directory app/Mail puoi posizionare la gestione della comunicazione e-mail:

app/Mail/
├── templates/            ← template e-mail
│   ├── order-confirmation.latte
│   └── welcome.latte
└── OrderMailer.php

Mappatura dei presenter

La mappatura definisce le regole per derivare il nome della classe dal nome del presenter. Le specifichiamo nella configurazione sotto la chiave application › mapping.

In questa pagina abbiamo mostrato che posizioniamo i presenter nella cartella app/Presentation (eventualmente app/UI). Dobbiamo comunicare questa convenzione a Nette nel file di configurazione. Basta una riga:

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

Come funziona la mappatura? Per una migliore comprensione, immaginiamo prima un'applicazione senza moduli. Vogliamo che le classi dei presenter rientrino nel namespace App\Presentation, in modo che il presenter Home si mappi sulla classe App\Presentation\HomePresenter. Cosa che otteniamo con questa configurazione:

application:
	mapping: App\Presentation\*Presenter

La mappatura funziona in modo che il nome del presenter Home sostituisca l'asterisco nella maschera App\Presentation\*Presenter, ottenendo così il nome della classe risultante App\Presentation\HomePresenter. Semplice!

Come però vedi negli esempi in questo e altri capitoli, posizioniamo le classi dei presenter in sottodirectory omonime, ad esempio il presenter Home si mappa sulla classe App\Presentation\Home\HomePresenter. Otteniamo ciò raddoppiando i due punti (richiede Nette Application 3.2):

application:
	mapping: App\Presentation\**Presenter

Ora passiamo alla mappatura dei presenter nei moduli. Per ogni modulo possiamo definire una mappatura specifica:

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

Secondo questa configurazione, il presenter Front:Home si mappa sulla classe App\Presentation\Front\Home\HomePresenter, mentre il presenter Api:OAuth sulla classe App\Api\OAuthPresenter.

Poiché i moduli Front e Admin hanno un modo simile di mappatura e probabilmente ci saranno più moduli di questo tipo, è possibile creare una regola generale che li sostituisca. Alla maschera della classe si aggiunge così un nuovo asterisco per il modulo:

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

Funziona anche per strutture di directory più profondamente nidificate, come ad esempio il presenter Admin:User:Edit, il segmento con l'asterisco si ripete per ogni livello e il risultato è la classe App\Presentation\Admin\User\Edit\EditPresenter.

Una notazione alternativa è usare un array composto da tre segmenti invece di una stringa. Questa notazione è equivalente alla precedente:

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