Nette Documentation Preview

syntax
Estrutura de diretórios do aplicativo
*************************************

<div class=perex>

Como criar uma estrutura de diretórios clara e dimensionável para projetos no Nette Framework? Mostraremos práticas comprovadas que o ajudarão a organizar seu código. Você aprenderá:

- como **estruturar logicamente** o aplicativo em diretórios
- como projetar a estrutura para **escalar bem** à medida que o projeto cresce
- quais são as **possíveis alternativas** e suas vantagens ou desvantagens

</div>


É importante mencionar que o Nette Framework em si não insiste em nenhuma estrutura específica. Ele foi projetado para ser facilmente adaptável a quaisquer necessidades e preferências.


Estrutura básica do projeto .[#toc-basic-project-structure]
===========================================================

Embora o Nette Framework não determine nenhuma estrutura de diretório fixa, há um arranjo padrão comprovado na forma de [Projeto Web |https://github.com/nette/web-project]:

/--pre
<b>web-project/</b>
├── <b>app/</b>              ← diretório do aplicativo
├── <b>assets/</b>           ← SCSS, arquivos JS, imagens..., alternativamente resources/
├── <b>bin/</b>              ← scripts de linha de comando
├── <b>config/</b>           ← configuração
├── <b>log/</b>              ← erros registrados
├── <b>temp/</b>             ← arquivos temporários, cache
├── <b>tests/</b>            ← testes
├── <b>vendor/</b>           ← bibliotecas instaladas pelo Composer
└── <b>www/</b>              ← diretório público (raiz do documento)
\--

Você pode modificar livremente essa estrutura de acordo com suas necessidades - renomear ou mover pastas. Em seguida, basta ajustar os caminhos relativos aos diretórios em `Bootstrap.php` e, possivelmente, em `composer.json`. Nada mais é necessário, nenhuma reconfiguração complexa, nenhuma alteração constante. O Nette tem detecção automática inteligente e reconhece automaticamente o local do aplicativo, incluindo sua base de URL.


Princípios de organização do código .[#toc-code-organization-principles]
========================================================================

Quando você explora um novo projeto pela primeira vez, deve ser capaz de se orientar rapidamente. Imagine clicar no diretório `app/Model/` e ver esta estrutura:

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

Com isso, você só saberá que o projeto usa alguns serviços, repositórios e entidades. Você não saberá nada sobre a finalidade real do aplicativo.

Vamos dar uma olhada em uma abordagem diferente: **organização por domínios**:

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

Isso é diferente - à primeira vista, fica claro que se trata de um site de comércio eletrônico. Os próprios nomes dos diretórios revelam o que o aplicativo pode fazer: ele trabalha com pagamentos, pedidos e produtos.

A primeira abordagem (organização por tipo de classe) traz vários problemas na prática: o código que é logicamente relacionado está espalhado em pastas diferentes e você precisa pular entre elas. Portanto, organizaremos por domínios.


Espaços de nome .[#toc-namespaces]
----------------------------------

É convencional que a estrutura de diretórios corresponda aos namespaces no aplicativo. Isso significa que o local físico dos arquivos corresponde ao namespace deles. Por exemplo, uma classe localizada em `app/Model/Product/ProductRepository.php` deve ter o namespace `App\Model\Product`. Esse princípio ajuda na orientação do código e simplifica o carregamento automático.


Singular vs. Plural em nomes .[#toc-singular-vs-plural-in-names]
----------------------------------------------------------------

Observe que usamos o singular para os principais diretórios de aplicativos: `app`, `config`, `log`, `temp`, `www`. O mesmo se aplica dentro do aplicativo: `Model`, `Core`, `Presentation`. Isso ocorre porque cada um representa um conceito unificado.

Da mesma forma, `app/Model/Product` representa tudo sobre produtos. Não o chamamos de `Products` porque não se trata de uma pasta cheia de produtos (que conteria arquivos como `iphone.php`, `samsung.php`). É um namespace que contém classes para trabalhar com produtos - `ProductRepository.php`, `ProductService.php`.

A pasta `app/Tasks` está no plural porque contém um conjunto de scripts executáveis autônomos - `CleanupTask.php`, `ImportTask.php`. Cada um deles é uma unidade independente.

Para fins de consistência, recomendamos o uso de:
- Singular para namespaces que representam uma unidade funcional (mesmo se estiver trabalhando com várias entidades)
- Plural para coleções de unidades independentes
- Em caso de incerteza ou se você não quiser pensar sobre isso, escolha singular


Diretório público `www/` .[#toc-public-directory-www]
=====================================================

Esse diretório é o único acessível pela Web (o chamado document-root). É possível que você encontre com frequência o nome `public/` em vez de `www/` - é apenas uma questão de convenção e não afeta a funcionalidade. O diretório contém:
- [Ponto de entrada |bootstrap#index.php] do aplicativo `index.php`
- Arquivo `.htaccess` com regras mod_rewrite (para Apache)
- Arquivos estáticos (CSS, JavaScript, imagens)
- Arquivos carregados

Para garantir a segurança adequada do aplicativo, é fundamental ter [o document-root configurado |nette:troubleshooting#how-to-change-or-remove-www-directory-from-url] corretamente.

.[note]
Nunca coloque a pasta `node_modules/` nesse diretório, pois ela contém milhares de arquivos que podem ser executáveis e não devem ser acessíveis ao público.


Diretório de aplicativos `app/` .[#toc-application-directory-app]
=================================================================

Este é o diretório principal com o código do aplicativo. Estrutura básica:

/--pre
<b>app/</b>
├── <b>Core/</b>               ← questões de infraestrutura
├── <b>Model/</b>              ← lógica de negócios
├── <b>Presentation/</b>       ← apresentadores e modelos
├── <b>Tasks/</b>              ← scripts de comando
└── <b>Bootstrap.php</b>       ← classe bootstrap do aplicativo
\--

`Bootstrap.php` é a [classe de inicialização do aplicativo |bootstrap] que inicializa o ambiente, carrega a configuração e cria o contêiner DI.

Vamos agora examinar detalhadamente os subdiretórios individuais.


Apresentadores e modelos .[#toc-presenters-and-templates]
=========================================================

Temos a parte de apresentação do aplicativo no diretório `app/Presentation`. Uma alternativa é o curto `app/UI`. Esse é o local para todos os apresentadores, seus modelos e todas as classes auxiliares.

Organizamos essa camada por domínios. Em um projeto complexo que combina comércio eletrônico, blog e API, a estrutura seria semelhante a esta:

/--pre
<b>app/Presentation/</b>
├── <b>Shop/</b>              ← front-end de comércio eletrônico
│   ├── <b>Product/</b>
│   ├── <b>Cart/</b>
│   └── <b>Order/</b>
├── <b>Blog/</b>              ← blog
│   ├── <b>Home/</b>
│   └── <b>Post/</b>
├── <b>Admin/</b>             ← administração
│   ├── <b>Dashboard/</b>
│   └── <b>Products/</b>
└── <b>Api/</b>               ← Pontos de extremidade da API
	└── <b>V1/</b>
\--

Por outro lado, para um blog simples, usaríamos esta estrutura:

/--pre
<b>app/Presentation/</b>
├── <b>Front/</b>             ← front-end do site
│   ├── <b>Home/</b>
│   └── <b>Post/</b>
├── <b>Admin/</b>             ← administração
│   ├── <b>Dashboard/</b>
│   └── <b>Posts/</b>
├── <b>Error/</b>
└── <b>Export/</b>            ← RSS, sitemaps etc.
\--

Pastas como `Home/` ou `Dashboard/` contêm apresentadores e modelos. Pastas como `Front/`, `Admin/` ou `Api/` são chamadas de **módulos**. Tecnicamente, esses são diretórios regulares que servem para a organização lógica do aplicativo.

Cada pasta com um apresentador contém um apresentador com o mesmo nome e seus modelos. Por exemplo, a pasta `Dashboard/` contém:

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

Essa estrutura de diretório é refletida nos namespaces de classe. Por exemplo, `DashboardPresenter` está no namespace `App\Presentation\Admin\Dashboard` (consulte o [mapeamento do apresentador |#presenter mapping]):

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

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

Referimo-nos ao apresentador `Dashboard` dentro do módulo `Admin` no aplicativo usando a notação de dois pontos como `Admin:Dashboard`. Para sua ação `default`, então, como `Admin:Dashboard:default`. Para módulos aninhados, usamos mais dois-pontos, por exemplo, `Shop:Order:Detail:default`.


Desenvolvimento de estrutura flexível .[#toc-flexible-structure-development]
----------------------------------------------------------------------------

Uma das grandes vantagens dessa estrutura é a elegância com que ela se adapta às necessidades crescentes do projeto. Como exemplo, vamos pegar a parte que gera feeds XML. Inicialmente, temos um formulário simples:

/--pre
<b>Export/</b>
├── <b>ExportPresenter.php</b>   ← um apresentador para todas as exportações
├── <b>sitemap.latte</b>         ← modelo para mapa do site
└── <b>feed.latte</b>            ← modelo para feed RSS
\--

Com o tempo, mais tipos de feed são adicionados e precisamos de mais lógica para eles... Sem problemas! A pasta `Export/` simplesmente se torna um módulo:

/--pre
<b>Export/</b>
├── <b>Sitemap/</b>
│   ├── <b>SitemapPresenter.php</b>
│   └── <b>sitemap.latte</b>
└── <b>Feed/</b>
	├── <b>FeedPresenter.php</b>
	├── <b>amazon.latte</b>         ← feed para a Amazon
	└── <b>ebay.latte</b>           ← feed para eBay
\--

Essa transformação é totalmente tranquila - basta criar novas subpastas, dividir o código entre elas e atualizar os links (por exemplo, de `Export:feed` para `Export:Feed:amazon`). Graças a isso, podemos expandir gradualmente a estrutura conforme necessário, pois o nível de aninhamento não é limitado de forma alguma.

Por exemplo, se na administração você tiver muitos apresentadores relacionados ao gerenciamento de pedidos, como `OrderDetail`, `OrderEdit`, `OrderDispatch` etc., poderá criar um módulo (pasta) `Order` para melhor organização, que conterá (pastas para) apresentadores `Detail`, `Edit`, `Dispatch` e outros.


Localização do modelo .[#toc-template-location]
-----------------------------------------------

Nos exemplos anteriores, vimos que os modelos estão localizados diretamente na pasta com o apresentador:

/--pre
<b>Dashboard/</b>
├── <b>DashboardPresenter.php</b>     ← apresentador
├── <b>DashboardTemplate.php</b>      ← classe de modelo opcional
└── <b>default.latte</b>              ← modelo
\--

Esse local se mostra o mais conveniente na prática: você tem todos os arquivos relacionados à mão.

Como alternativa, você pode colocar os modelos em uma subpasta `templates/`. O Nette oferece suporte a ambas as variantes. Você pode até mesmo colocar os modelos completamente fora da pasta `Presentation/`. Tudo sobre as opções de localização de modelos pode ser encontrado no capítulo [Pesquisa de modelos |templates#Template Lookup].


Classes e componentes auxiliares .[#toc-helper-classes-and-components]
----------------------------------------------------------------------

Os apresentadores e modelos geralmente vêm com outros arquivos auxiliares. Nós os colocamos logicamente de acordo com seu escopo:

1. **Diretamente com o apresentador** no caso de componentes específicos para um determinado apresentador:

/--pre
<b>Product/</b>
├── <b>ProductPresenter.php</b>
├── <b>ProductGrid.php</b>        ← componente para listagem de produtos
└── <b>FilterForm.php</b>         ← formulário para filtragem
\--

2. **Para o módulo** - recomendamos o uso da pasta `Accessory`, que é colocada ordenadamente no início do alfabeto:

/--pre
<b>Front/</b>
├── <b>Accessory/</b>
│   ├── <b>NavbarControl.php</b>    ← componentes para front-end
│   └── <b>TemplateFilters.php</b>
├── <b>Product/</b>
└── <b>Cart/</b>
\--

3. **Para todo o aplicativo** - em `Presentation/Accessory/`:
/--pre
<b>app/Presentation/</b>
├── <b>Accessory/</b>
│   ├── <b>LatteExtension.php</b>
│   └── <b>TemplateFilters.php</b>
├── <b>Front/</b>
└── <b>Admin/</b>
\--

Ou você pode colocar classes auxiliares como `LatteExtension.php` ou `TemplateFilters.php` na pasta de infraestrutura `app/Core/Latte/`. E os componentes em `app/Components`. A escolha depende das convenções da equipe.


Modelo - Coração do aplicativo .[#toc-model-heart-of-the-application]
=====================================================================

O modelo contém toda a lógica comercial do aplicativo. Para sua organização, aplica-se a mesma regra: estruturamos por domínios:

/--pre
<b>app/Model/</b>
├── <b>Payment/</b>                   ← tudo sobre pagamentos
│   ├── <b>PaymentFacade.php</b>      ← ponto de entrada principal
│   ├── <b>PaymentRepository.php</b>
│   ├── <b>Payment.php</b>            ← entidade
├── <b>Order/</b>                     ← tudo sobre pedidos
│   ├── <b>OrderFacade.php</b>
│   ├── <b>OrderRepository.php</b>
│   ├── <b>Order.php</b>
└── <b>Shipping/</b>                  ← tudo sobre remessa
\--

No modelo, você normalmente encontra esses tipos de classes:

**Facades**: representam o principal ponto de entrada em um domínio específico no aplicativo. Elas atuam como um orquestrador que coordena a cooperação entre diferentes serviços para implementar casos de uso completos (como "criar pedido" ou "processar pagamento"). Sob sua camada de orquestração, a fachada oculta os detalhes de implementação do restante do aplicativo, fornecendo assim uma interface limpa para trabalhar com o domínio em questão.

```php
class OrderFacade
{
	public function createOrder(Cart $cart): Order
	{
		// validação
		// criação de pedidos
		// envio de e-mails
		// gravação em estatísticas
	}
}
```

**Serviços**: concentram-se em operações comerciais específicas em um domínio. Ao contrário das fachadas que orquestram casos de uso inteiros, um serviço implementa uma lógica comercial específica (como cálculos de preços ou processamento de pagamentos). Os serviços normalmente não têm estado e podem ser usados por fachadas como blocos de construção para operações mais complexas ou diretamente por outras partes do aplicativo para tarefas mais simples.

```php
class PricingService
{
	public function calculateTotal(Order $order): Money
	{
		// Cálculo de preços
	}
}
```

**Repositórios**: lidam com toda a comunicação com o armazenamento de dados, normalmente um banco de dados. Sua tarefa é carregar e salvar entidades e implementar métodos para pesquisá-las. Um repositório protege o restante do aplicativo dos detalhes de implementação do banco de dados e fornece uma interface orientada a objetos para trabalhar com dados.

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

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

**Entidades**: objetos que representam os principais conceitos de negócios no aplicativo que têm sua identidade e mudam com o tempo. Normalmente, são classes mapeadas para tabelas de banco de dados usando ORM (como Nette Database Explorer ou Doctrine). As entidades podem conter regras de negócios relacionadas aos seus dados e à lógica de validação.

```php
// Entidade mapeada para a tabela de pedidos do banco de dados
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,
		]);
	}
}
```

**Objetos de valor**: objetos imutáveis que representam valores sem identidade própria - por exemplo, uma quantia em dinheiro ou um endereço de e-mail. Duas instâncias de um objeto de valor com os mesmos valores são consideradas idênticas.


Código de infraestrutura .[#toc-infrastructure-code]
====================================================

A pasta `Core/` (ou também `Infrastructure/`) abriga a base técnica do aplicativo. O código de infraestrutura normalmente inclui:

/--pre
<b>app/Core/</b>
├── <b>Router/</b>               ← roteamento e gerenciamento de URLs
│   └── <b>RouterFactory.php</b>
├── <b>Security/</b>             ← autenticação e autorização
│   ├── <b>Authenticator.php</b>
│   └── <b>Authorizator.php</b>
├── <b>Logging/</b>              ← registro e monitoramento
│   ├── <b>SentryLogger.php</b>
│   └── <b>FileLogger.php
├── <b>Cache/</b>                ← camada de cache
│   └── <b>FullPageCache.php</b>
└── <b>Integration/</b>          ← integração com serviços externos
	├── <b>Slack/</b>
	└── <b>Stripe/</b>
\--

Para projetos menores, uma estrutura plana é naturalmente suficiente:

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

Este é o código que:

- Lida com a infraestrutura técnica (roteamento, registro, armazenamento em cache)
- Integra serviços externos (Sentry, Elasticsearch, Redis)
- Fornece serviços básicos para todo o aplicativo (correio, banco de dados)
- É, em grande parte, independente do domínio específico - o cache ou o registrador funciona da mesma forma para comércio eletrônico ou blog.

Está se perguntando se uma determinada classe pertence a este site ou ao modelo? A principal diferença é que o código em `Core/`:

- Não sabe nada sobre o domínio (produtos, pedidos, artigos)
- Geralmente pode ser transferido para outro projeto
- Resolve "como funciona" (como enviar e-mail), não "o que faz" (que e-mail enviar)

Exemplo para melhor compreensão:

- `App\Core\MailerFactory` - cria instâncias da classe de envio de e-mail, lida com as configurações de SMTP
- `App\Model\OrderMailer` - usa `MailerFactory` para enviar e-mails sobre pedidos, conhece seus modelos e quando eles devem ser enviados


Scripts de comando .[#toc-command-scripts]
==========================================

Os aplicativos geralmente precisam executar tarefas fora das solicitações HTTP regulares, seja processamento de dados em segundo plano, manutenção ou tarefas periódicas. Scripts simples no diretório `bin/` são usados para execução, enquanto a lógica de implementação real é colocada em `app/Tasks/` (ou `app/Commands/`).

Exemplo:

/--pre
<b>app/Tasks/</b>
├── <b>Maintenance/</b>               ← scripts de manutenção
│   ├── <b>CleanupCommand.php</b>     ← exclusão de dados antigos
│   └── <b>DbOptimizeCommand.php</b>  ← otimização do banco de dados
├── <b>Integration/</b>               ← integração com sistemas externos
│   ├── <b>ImportProducts.php</b>     ← importação do sistema do fornecedor
│   └── <b>SyncOrders.php</b>         ← sincronização de pedidos
└── <b>Scheduled/</b>                 ← tarefas regulares
	├── <b>NewsletterCommand.php</b>  ← envio de boletins informativos
	└── <b>ReminderCommand.php</b>    ← notificações de clientes
\--

O que pertence ao modelo e o que pertence aos scripts de comando? Por exemplo, a lógica para enviar um e-mail faz parte do modelo, enquanto o envio em massa de milhares de e-mails pertence a `Tasks/`.

As tarefas geralmente são [executadas na linha de comando |https://blog.nette.org/en/cli-scripts-in-nette-application] ou via cron. Elas também podem ser executadas por meio de solicitação HTTP, mas a segurança deve ser considerada. O apresentador que executa a tarefa precisa ser protegido, por exemplo, somente para usuários conectados ou com um token forte e acesso a partir de endereços IP permitidos. Para tarefas longas, é necessário aumentar o limite de tempo do script e usar `session_write_close()` para evitar o bloqueio da sessão.


Outros diretórios possíveis .[#toc-other-possible-directories]
==============================================================

Além dos diretórios básicos mencionados, você pode adicionar outras pastas especializadas de acordo com as necessidades do projeto. Vamos dar uma olhada nas mais comuns e em seu uso:

/--pre
<b>app/</b>
├── <b>Api/</b>              ← Lógica de API independente da camada de apresentação
├── <b>Database/</b>         ← scripts de migração e seeders para dados de teste
├── <b>Components/</b>       ← componentes visuais compartilhados em todo o aplicativo
├── <b>Event/</b>            ← útil se estiver usando arquitetura orientada por eventos
├── <b>Mail/</b>             ← modelos de e-mail e lógica relacionada
└── <b>Utils/</b>            ← classes auxiliares
\--

Para componentes visuais compartilhados usados em apresentadores em todo o aplicativo, você pode usar a pasta `app/Components` ou `app/Controls`:

/--pre
<b>app/Components/</b>
├── <b>Form/</b>                 ← componentes de formulários compartilhados
│   ├── <b>SignInForm.php</b>
│   └── <b>UserForm.php</b>
├── <b>Grid/</b>                 ← componentes para listagens de dados
│   └── <b>DataGrid.php</b>
└── <b>Navigation/</b>           ← elementos de navegação
	├── <b>Breadcrumbs.php</b>
	└── <b>Menu.php</b>
\--

É a essa pasta que pertencem os componentes com lógica mais complexa. Se quiser compartilhar componentes entre vários projetos, é bom separá-los em um pacote autônomo do composer.

No diretório `app/Mail`, você pode colocar o gerenciamento de comunicação por e-mail:

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


Mapeamento de apresentadores .[#toc-presenter-mapping]
======================================================

O mapeamento define regras para derivar nomes de classes de nomes de apresentadores. Nós as especificamos na [configuração |configuration] sob a chave `application › mapping`.

Nesta página, mostramos que colocamos os apresentadores na pasta `app/Presentation` (ou `app/UI`). Precisamos informar ao Nette sobre essa convenção no arquivo de configuração. Uma linha é suficiente:

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

Como funciona o mapeamento? Para entender melhor, vamos primeiro imaginar um aplicativo sem módulos. Queremos que as classes de apresentador fiquem sob o namespace `App\Presentation`, de modo que o apresentador `Home` seja mapeado para a classe `App\Presentation\HomePresenter`. Isso é obtido com esta configuração:

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

O mapeamento funciona substituindo o asterisco na máscara `App\Presentation\*Presenter` pelo nome do apresentador `Home`, resultando no nome final da classe `App\Presentation\HomePresenter`. Simples!

Entretanto, como você pode ver nos exemplos deste e de outros capítulos, colocamos as classes de apresentador em subdiretórios homônimos, por exemplo, o apresentador `Home` mapeia para a classe `App\Presentation\Home\HomePresenter`. Conseguimos isso duplicando os dois pontos (requer o Nette Application 3.2):

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

Agora, passaremos a mapear os apresentadores nos módulos. Podemos definir um mapeamento específico para cada módulo:

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

De acordo com essa configuração, o apresentador `Front:Home` mapeia para a classe `App\Presentation\Front\Home\HomePresenter`, enquanto o apresentador `Api:OAuth` mapeia para a classe `App\Api\OAuthPresenter`.

Como os módulos `Front` e `Admin` têm um método de mapeamento semelhante e provavelmente haverá mais módulos desse tipo, é possível criar uma regra geral que os substituirá. Um novo asterisco para o módulo será adicionado à máscara de classe:

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

Isso também funciona para estruturas de diretório aninhadas mais profundas, como o apresentador `Admin:User:Edit`, em que o segmento com asterisco se repete para cada nível e resulta na classe `App\Presentation\Admin\User\Edit\EditPresenter`.

Uma notação alternativa é usar uma matriz composta por três segmentos em vez de uma cadeia de caracteres. Essa notação é equivalente à anterior:

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

Estrutura de diretórios do aplicativo

Como criar uma estrutura de diretórios clara e dimensionável para projetos no Nette Framework? Mostraremos práticas comprovadas que o ajudarão a organizar seu código. Você aprenderá:

  • como estruturar logicamente o aplicativo em diretórios
  • como projetar a estrutura para escalar bem à medida que o projeto cresce
  • quais são as possíveis alternativas e suas vantagens ou desvantagens

É importante mencionar que o Nette Framework em si não insiste em nenhuma estrutura específica. Ele foi projetado para ser facilmente adaptável a quaisquer necessidades e preferências.

Estrutura básica do projeto

Embora o Nette Framework não determine nenhuma estrutura de diretório fixa, há um arranjo padrão comprovado na forma de Projeto Web:

web-project/
├── app/              ← diretório do aplicativo
├── assets/           ← SCSS, arquivos JS, imagens..., alternativamente resources/
├── bin/              ← scripts de linha de comando
├── config/           ← configuração
├── log/              ← erros registrados
├── temp/             ← arquivos temporários, cache
├── tests/            ← testes
├── vendor/           ← bibliotecas instaladas pelo Composer
└── www/              ← diretório público (raiz do documento)

Você pode modificar livremente essa estrutura de acordo com suas necessidades – renomear ou mover pastas. Em seguida, basta ajustar os caminhos relativos aos diretórios em Bootstrap.php e, possivelmente, em composer.json. Nada mais é necessário, nenhuma reconfiguração complexa, nenhuma alteração constante. O Nette tem detecção automática inteligente e reconhece automaticamente o local do aplicativo, incluindo sua base de URL.

Princípios de organização do código

Quando você explora um novo projeto pela primeira vez, deve ser capaz de se orientar rapidamente. Imagine clicar no diretório app/Model/ e ver esta estrutura:

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

Com isso, você só saberá que o projeto usa alguns serviços, repositórios e entidades. Você não saberá nada sobre a finalidade real do aplicativo.

Vamos dar uma olhada em uma abordagem diferente: organização por domínios:

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

Isso é diferente – à primeira vista, fica claro que se trata de um site de comércio eletrônico. Os próprios nomes dos diretórios revelam o que o aplicativo pode fazer: ele trabalha com pagamentos, pedidos e produtos.

A primeira abordagem (organização por tipo de classe) traz vários problemas na prática: o código que é logicamente relacionado está espalhado em pastas diferentes e você precisa pular entre elas. Portanto, organizaremos por domínios.

Espaços de nome

É convencional que a estrutura de diretórios corresponda aos namespaces no aplicativo. Isso significa que o local físico dos arquivos corresponde ao namespace deles. Por exemplo, uma classe localizada em app/Model/Product/ProductRepository.php deve ter o namespace App\Model\Product. Esse princípio ajuda na orientação do código e simplifica o carregamento automático.

Singular vs. Plural em nomes

Observe que usamos o singular para os principais diretórios de aplicativos: app, config, log, temp, www. O mesmo se aplica dentro do aplicativo: Model, Core, Presentation. Isso ocorre porque cada um representa um conceito unificado.

Da mesma forma, app/Model/Product representa tudo sobre produtos. Não o chamamos de Products porque não se trata de uma pasta cheia de produtos (que conteria arquivos como iphone.php, samsung.php). É um namespace que contém classes para trabalhar com produtos – ProductRepository.php, ProductService.php.

A pasta app/Tasks está no plural porque contém um conjunto de scripts executáveis autônomos – CleanupTask.php, ImportTask.php. Cada um deles é uma unidade independente.

Para fins de consistência, recomendamos o uso de:

  • Singular para namespaces que representam uma unidade funcional (mesmo se estiver trabalhando com várias entidades)
  • Plural para coleções de unidades independentes
  • Em caso de incerteza ou se você não quiser pensar sobre isso, escolha singular

Diretório público www/

Esse diretório é o único acessível pela Web (o chamado document-root). É possível que você encontre com frequência o nome public/ em vez de www/ – é apenas uma questão de convenção e não afeta a funcionalidade. O diretório contém:

  • Ponto de entrada do aplicativo index.php
  • Arquivo .htaccess com regras mod_rewrite (para Apache)
  • Arquivos estáticos (CSS, JavaScript, imagens)
  • Arquivos carregados

Para garantir a segurança adequada do aplicativo, é fundamental ter o document-root configurado corretamente.

Nunca coloque a pasta node_modules/ nesse diretório, pois ela contém milhares de arquivos que podem ser executáveis e não devem ser acessíveis ao público.

Diretório de aplicativos app/

Este é o diretório principal com o código do aplicativo. Estrutura básica:

app/
├── Core/               ← questões de infraestrutura
├── Model/              ← lógica de negócios
├── Presentation/       ← apresentadores e modelos
├── Tasks/              ← scripts de comando
└── Bootstrap.php       ← classe bootstrap do aplicativo

Bootstrap.php é a classe de inicialização do aplicativo que inicializa o ambiente, carrega a configuração e cria o contêiner DI.

Vamos agora examinar detalhadamente os subdiretórios individuais.

Apresentadores e modelos

Temos a parte de apresentação do aplicativo no diretório app/Presentation. Uma alternativa é o curto app/UI. Esse é o local para todos os apresentadores, seus modelos e todas as classes auxiliares.

Organizamos essa camada por domínios. Em um projeto complexo que combina comércio eletrônico, blog e API, a estrutura seria semelhante a esta:

app/Presentation/
├── Shop/              ← front-end de comércio eletrônico
│   ├── Product/
│   ├── Cart/
│   └── Order/
├── Blog/              ← blog
│   ├── Home/
│   └── Post/
├── Admin/             ← administração
│   ├── Dashboard/
│   └── Products/
└── Api/               ← Pontos de extremidade da API
	└── V1/

Por outro lado, para um blog simples, usaríamos esta estrutura:

app/Presentation/
├── Front/             ← front-end do site
│   ├── Home/
│   └── Post/
├── Admin/             ← administração
│   ├── Dashboard/
│   └── Posts/
├── Error/
└── Export/            ← RSS, sitemaps etc.

Pastas como Home/ ou Dashboard/ contêm apresentadores e modelos. Pastas como Front/, Admin/ ou Api/ são chamadas de módulos. Tecnicamente, esses são diretórios regulares que servem para a organização lógica do aplicativo.

Cada pasta com um apresentador contém um apresentador com o mesmo nome e seus modelos. Por exemplo, a pasta Dashboard/ contém:

Dashboard/
├── DashboardPresenter.php     ← apresentador
└── default.latte              ← modelo

Essa estrutura de diretório é refletida nos namespaces de classe. Por exemplo, DashboardPresenter está no namespace App\Presentation\Admin\Dashboard (consulte o mapeamento do apresentador):

namespace App\Presentation\Admin\Dashboard;

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

Referimo-nos ao apresentador Dashboard dentro do módulo Admin no aplicativo usando a notação de dois pontos como Admin:Dashboard. Para sua ação default, então, como Admin:Dashboard:default. Para módulos aninhados, usamos mais dois-pontos, por exemplo, Shop:Order:Detail:default.

Desenvolvimento de estrutura flexível

Uma das grandes vantagens dessa estrutura é a elegância com que ela se adapta às necessidades crescentes do projeto. Como exemplo, vamos pegar a parte que gera feeds XML. Inicialmente, temos um formulário simples:

Export/
├── ExportPresenter.php   ← um apresentador para todas as exportações
├── sitemap.latte         ← modelo para mapa do site
└── feed.latte            ← modelo para feed RSS

Com o tempo, mais tipos de feed são adicionados e precisamos de mais lógica para eles… Sem problemas! A pasta Export/ simplesmente se torna um módulo:

Export/
├── Sitemap/
│   ├── SitemapPresenter.php
│   └── sitemap.latte
└── Feed/
	├── FeedPresenter.php
	├── amazon.latte         ← feed para a Amazon
	└── ebay.latte           ← feed para eBay

Essa transformação é totalmente tranquila – basta criar novas subpastas, dividir o código entre elas e atualizar os links (por exemplo, de Export:feed para Export:Feed:amazon). Graças a isso, podemos expandir gradualmente a estrutura conforme necessário, pois o nível de aninhamento não é limitado de forma alguma.

Por exemplo, se na administração você tiver muitos apresentadores relacionados ao gerenciamento de pedidos, como OrderDetail, OrderEdit, OrderDispatch etc., poderá criar um módulo (pasta) Order para melhor organização, que conterá (pastas para) apresentadores Detail, Edit, Dispatch e outros.

Localização do modelo

Nos exemplos anteriores, vimos que os modelos estão localizados diretamente na pasta com o apresentador:

Dashboard/
├── DashboardPresenter.php     ← apresentador
├── DashboardTemplate.php      ← classe de modelo opcional
└── default.latte              ← modelo

Esse local se mostra o mais conveniente na prática: você tem todos os arquivos relacionados à mão.

Como alternativa, você pode colocar os modelos em uma subpasta templates/. O Nette oferece suporte a ambas as variantes. Você pode até mesmo colocar os modelos completamente fora da pasta Presentation/. Tudo sobre as opções de localização de modelos pode ser encontrado no capítulo Pesquisa de modelos.

Classes e componentes auxiliares

Os apresentadores e modelos geralmente vêm com outros arquivos auxiliares. Nós os colocamos logicamente de acordo com seu escopo:

1. Diretamente com o apresentador no caso de componentes específicos para um determinado apresentador:

Product/
├── ProductPresenter.php
├── ProductGrid.php        ← componente para listagem de produtos
└── FilterForm.php         ← formulário para filtragem

2. Para o módulo – recomendamos o uso da pasta Accessory, que é colocada ordenadamente no início do alfabeto:

Front/
├── Accessory/
│   ├── NavbarControl.php    ← componentes para front-end
│   └── TemplateFilters.php
├── Product/
└── Cart/

3. Para todo o aplicativo – em Presentation/Accessory/:

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

Ou você pode colocar classes auxiliares como LatteExtension.php ou TemplateFilters.php na pasta de infraestrutura app/Core/Latte/. E os componentes em app/Components. A escolha depende das convenções da equipe.

Modelo – Coração do aplicativo

O modelo contém toda a lógica comercial do aplicativo. Para sua organização, aplica-se a mesma regra: estruturamos por domínios:

app/Model/
├── Payment/                   ← tudo sobre pagamentos
│   ├── PaymentFacade.php      ← ponto de entrada principal
│   ├── PaymentRepository.php
│   ├── Payment.php            ← entidade
├── Order/                     ← tudo sobre pedidos
│   ├── OrderFacade.php
│   ├── OrderRepository.php
│   ├── Order.php
└── Shipping/                  ← tudo sobre remessa

No modelo, você normalmente encontra esses tipos de classes:

Facades: representam o principal ponto de entrada em um domínio específico no aplicativo. Elas atuam como um orquestrador que coordena a cooperação entre diferentes serviços para implementar casos de uso completos (como „criar pedido“ ou „processar pagamento“). Sob sua camada de orquestração, a fachada oculta os detalhes de implementação do restante do aplicativo, fornecendo assim uma interface limpa para trabalhar com o domínio em questão.

class OrderFacade
{
	public function createOrder(Cart $cart): Order
	{
		// validação
		// criação de pedidos
		// envio de e-mails
		// gravação em estatísticas
	}
}

Serviços: concentram-se em operações comerciais específicas em um domínio. Ao contrário das fachadas que orquestram casos de uso inteiros, um serviço implementa uma lógica comercial específica (como cálculos de preços ou processamento de pagamentos). Os serviços normalmente não têm estado e podem ser usados por fachadas como blocos de construção para operações mais complexas ou diretamente por outras partes do aplicativo para tarefas mais simples.

class PricingService
{
	public function calculateTotal(Order $order): Money
	{
		// Cálculo de preços
	}
}

Repositórios: lidam com toda a comunicação com o armazenamento de dados, normalmente um banco de dados. Sua tarefa é carregar e salvar entidades e implementar métodos para pesquisá-las. Um repositório protege o restante do aplicativo dos detalhes de implementação do banco de dados e fornece uma interface orientada a objetos para trabalhar com dados.

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

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

Entidades: objetos que representam os principais conceitos de negócios no aplicativo que têm sua identidade e mudam com o tempo. Normalmente, são classes mapeadas para tabelas de banco de dados usando ORM (como Nette Database Explorer ou Doctrine). As entidades podem conter regras de negócios relacionadas aos seus dados e à lógica de validação.

// Entidade mapeada para a tabela de pedidos do banco de dados
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,
		]);
	}
}

Objetos de valor: objetos imutáveis que representam valores sem identidade própria – por exemplo, uma quantia em dinheiro ou um endereço de e-mail. Duas instâncias de um objeto de valor com os mesmos valores são consideradas idênticas.

Código de infraestrutura

A pasta Core/ (ou também Infrastructure/) abriga a base técnica do aplicativo. O código de infraestrutura normalmente inclui:

app/Core/
├── Router/               ← roteamento e gerenciamento de URLs
│   └── RouterFactory.php
├── Security/             ← autenticação e autorização
│   ├── Authenticator.php
│   └── Authorizator.php
├── Logging/              ← registro e monitoramento
│   ├── SentryLogger.php
│   └── FileLogger.php
├── Cache/                ← camada de cache
│   └── FullPageCache.php
└── Integration/          ← integração com serviços externos
	├── Slack/
	└── Stripe/

Para projetos menores, uma estrutura plana é naturalmente suficiente:

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

Este é o código que:

  • Lida com a infraestrutura técnica (roteamento, registro, armazenamento em cache)
  • Integra serviços externos (Sentry, Elasticsearch, Redis)
  • Fornece serviços básicos para todo o aplicativo (correio, banco de dados)
  • É, em grande parte, independente do domínio específico – o cache ou o registrador funciona da mesma forma para comércio eletrônico ou blog.

Está se perguntando se uma determinada classe pertence a este site ou ao modelo? A principal diferença é que o código em Core/:

  • Não sabe nada sobre o domínio (produtos, pedidos, artigos)
  • Geralmente pode ser transferido para outro projeto
  • Resolve „como funciona“ (como enviar e-mail), não „o que faz“ (que e-mail enviar)

Exemplo para melhor compreensão:

  • App\Core\MailerFactory – cria instâncias da classe de envio de e-mail, lida com as configurações de SMTP
  • App\Model\OrderMailer – usa MailerFactory para enviar e-mails sobre pedidos, conhece seus modelos e quando eles devem ser enviados

Scripts de comando

Os aplicativos geralmente precisam executar tarefas fora das solicitações HTTP regulares, seja processamento de dados em segundo plano, manutenção ou tarefas periódicas. Scripts simples no diretório bin/ são usados para execução, enquanto a lógica de implementação real é colocada em app/Tasks/ (ou app/Commands/).

Exemplo:

app/Tasks/
├── Maintenance/               ← scripts de manutenção
│   ├── CleanupCommand.php     ← exclusão de dados antigos
│   └── DbOptimizeCommand.php  ← otimização do banco de dados
├── Integration/               ← integração com sistemas externos
│   ├── ImportProducts.php     ← importação do sistema do fornecedor
│   └── SyncOrders.php         ← sincronização de pedidos
└── Scheduled/                 ← tarefas regulares
	├── NewsletterCommand.php  ← envio de boletins informativos
	└── ReminderCommand.php    ← notificações de clientes

O que pertence ao modelo e o que pertence aos scripts de comando? Por exemplo, a lógica para enviar um e-mail faz parte do modelo, enquanto o envio em massa de milhares de e-mails pertence a Tasks/.

As tarefas geralmente são executadas na linha de comando ou via cron. Elas também podem ser executadas por meio de solicitação HTTP, mas a segurança deve ser considerada. O apresentador que executa a tarefa precisa ser protegido, por exemplo, somente para usuários conectados ou com um token forte e acesso a partir de endereços IP permitidos. Para tarefas longas, é necessário aumentar o limite de tempo do script e usar session_write_close() para evitar o bloqueio da sessão.

Outros diretórios possíveis

Além dos diretórios básicos mencionados, você pode adicionar outras pastas especializadas de acordo com as necessidades do projeto. Vamos dar uma olhada nas mais comuns e em seu uso:

app/
├── Api/              ← Lógica de API independente da camada de apresentação
├── Database/         ← scripts de migração e seeders para dados de teste
├── Components/       ← componentes visuais compartilhados em todo o aplicativo
├── Event/            ← útil se estiver usando arquitetura orientada por eventos
├── Mail/             ← modelos de e-mail e lógica relacionada
└── Utils/            ← classes auxiliares

Para componentes visuais compartilhados usados em apresentadores em todo o aplicativo, você pode usar a pasta app/Components ou app/Controls:

app/Components/
├── Form/                 ← componentes de formulários compartilhados
│   ├── SignInForm.php
│   └── UserForm.php
├── Grid/                 ← componentes para listagens de dados
│   └── DataGrid.php
└── Navigation/           ← elementos de navegação
	├── Breadcrumbs.php
	└── Menu.php

É a essa pasta que pertencem os componentes com lógica mais complexa. Se quiser compartilhar componentes entre vários projetos, é bom separá-los em um pacote autônomo do composer.

No diretório app/Mail, você pode colocar o gerenciamento de comunicação por e-mail:

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

Mapeamento de apresentadores

O mapeamento define regras para derivar nomes de classes de nomes de apresentadores. Nós as especificamos na configuração sob a chave application › mapping.

Nesta página, mostramos que colocamos os apresentadores na pasta app/Presentation (ou app/UI). Precisamos informar ao Nette sobre essa convenção no arquivo de configuração. Uma linha é suficiente:

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

Como funciona o mapeamento? Para entender melhor, vamos primeiro imaginar um aplicativo sem módulos. Queremos que as classes de apresentador fiquem sob o namespace App\Presentation, de modo que o apresentador Home seja mapeado para a classe App\Presentation\HomePresenter. Isso é obtido com esta configuração:

application:
	mapping: App\Presentation\*Presenter

O mapeamento funciona substituindo o asterisco na máscara App\Presentation\*Presenter pelo nome do apresentador Home, resultando no nome final da classe App\Presentation\HomePresenter. Simples!

Entretanto, como você pode ver nos exemplos deste e de outros capítulos, colocamos as classes de apresentador em subdiretórios homônimos, por exemplo, o apresentador Home mapeia para a classe App\Presentation\Home\HomePresenter. Conseguimos isso duplicando os dois pontos (requer o Nette Application 3.2):

application:
	mapping: App\Presentation\**Presenter

Agora, passaremos a mapear os apresentadores nos módulos. Podemos definir um mapeamento específico para cada módulo:

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

De acordo com essa configuração, o apresentador Front:Home mapeia para a classe App\Presentation\Front\Home\HomePresenter, enquanto o apresentador Api:OAuth mapeia para a classe App\Api\OAuthPresenter.

Como os módulos Front e Admin têm um método de mapeamento semelhante e provavelmente haverá mais módulos desse tipo, é possível criar uma regra geral que os substituirá. Um novo asterisco para o módulo será adicionado à máscara de classe:

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

Isso também funciona para estruturas de diretório aninhadas mais profundas, como o apresentador Admin:User:Edit, em que o segmento com asterisco se repete para cada nível e resulta na classe App\Presentation\Admin\User\Edit\EditPresenter.

Uma notação alternativa é usar uma matriz composta por três segmentos em vez de uma cadeia de caracteres. Essa notação é equivalente à anterior:

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