Estrutura de diretórios da aplicação
Como projetar uma estrutura de diretórios clara e escalável para projetos no Nette Framework? Mostraremos as melhores práticas que o ajudarão a organizar seu código. Você aprenderá:
- como dividir logicamente a aplicação em diretórios
- como projetar a estrutura para que ela escale bem com o crescimento do projeto
- quais são as alternativas possíveis e suas vantagens ou desvantagens
É importante mencionar que o próprio Nette Framework não impõe nenhuma estrutura específica. Ele é projetado para ser facilmente adaptável a quaisquer necessidades e preferências.
Estrutura básica do projeto
Embora o Nette Framework não dite nenhuma estrutura de diretórios fixa, existe uma organização padrão comprovada na forma do Web Project:
web-project/ ├── app/ ← diretório com a aplicação ├── assets/ ← arquivos SCSS, JS, imagens..., alternativamente resources/ ├── bin/ ← scripts para a 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 (document-root)
Você pode modificar esta estrutura livremente de acordo com suas necessidades – renomear ou mover pastas. Depois, basta
apenas ajustar os caminhos relativos aos diretórios no arquivo Bootstrap.php
e, opcionalmente,
composer.json
. Nada mais é necessário, nenhuma reconfiguração complicada, nenhuma alteração de constantes.
O Nette possui uma autodeteção inteligente e reconhece automaticamente a localização da aplicação, incluindo sua base
de URL.
Princípios de organização do código
Quando você explora um novo projeto pela primeira vez, deve conseguir se orientar rapidamente nele. Imagine que você abre
o diretório app/Model/
e vê esta estrutura:
app/Model/ ├── Services/ ├── Repositories/ └── Entities/
A partir dela, você só pode deduzir que o projeto usa alguns serviços, repositórios e entidades. Você não aprenderá nada sobre o propósito real da aplicação.
Vejamos outra abordagem – organização por domínios:
app/Model/ ├── Cart/ ├── Payment/ ├── Order/ └── Product/
Aqui é diferente – à primeira vista, fica claro que se trata de uma loja virtual. Os próprios nomes dos diretórios revelam o que a aplicação faz – trabalha com pagamentos, pedidos e produtos.
A primeira abordagem (organização por tipo de classe) traz na prática uma série de problemas: o código que está logicamente relacionado é fragmentado em diferentes pastas e você precisa pular entre elas. Portanto, organizaremos por domínios.
Namespaces
É costume que a estrutura de diretórios corresponda aos namespaces na aplicação. Isso significa que a localização física
dos arquivos corresponde ao seu namespace. Por exemplo, uma classe localizada em
app/Model/Product/ProductRepository.php
deve ter o namespace App\Model\Product
. Este princípio ajuda
na orientação no código e simplifica o autoloading.
Singular vs. plural nos nomes
Observe que para os diretórios principais da aplicação usamos o singular: app
, config
,
log
, temp
, www
. O mesmo vale para o interior da aplicação: Model
,
Core
, Presentation
. Isso ocorre porque cada um deles representa um conceito coeso.
Da mesma forma, por exemplo, app/Model/Product
representa tudo relacionado a produtos. Não o chamaremos de
Products
, porque não é uma pasta cheia de produtos (isso significaria que haveria arquivos nokia.php
,
samsung.php
). É um namespace contendo 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 independentes –
CleanupTask.php
, ImportTask.php
. Cada um deles é uma unidade separada.
Para consistência, recomendamos usar:
- Singular para namespaces que representam uma unidade funcional (mesmo que trabalhe com múltiplas entidades)
- Plural para coleções de unidades independentes
- Em caso de incerteza ou se você não quiser pensar sobre isso, escolha o singular
Diretório público www/
Este diretório é o único acessível pela web (o chamado document-root). Frequentemente, você pode encontrar o nome
public/
em vez de www/
– é apenas uma questão de convenção e não afeta a funcionalidade do
Nette. O diretório contém:
- Ponto de entrada da aplicação
index.php
- Arquivo
.htaccess
com regras para mod_rewrite (no Apache) - Arquivos estáticos (CSS, JavaScript, imagens)
- Arquivos carregados (uploads)
Para a segurança adequada da aplicação, é crucial ter o document-root configurado corretamente.
Nunca coloque a pasta node_modules/
neste diretório – ela contém milhares de arquivos que podem
ser executáveis e não devem estar publicamente acessíveis.
Diretório da aplicação app/
Este é o diretório principal com o código da aplicação. Estrutura básica:
app/ ├── Core/ ← questões de infraestrutura ├── Model/ ← lógica de negócios ├── Presentation/ ← presenters e templates ├── Tasks/ ← scripts de comando └── Bootstrap.php ← classe de inicialização da aplicação
Bootstrap.php
é a classe de inicialização da
aplicação, que inicializa o ambiente, carrega a configuração e cria o contêiner de DI.
Vamos agora examinar os subdiretórios individuais com mais detalhes.
Presenters e templates
A parte de apresentação da aplicação está no diretório app/Presentation
. Uma alternativa é o curto
app/UI
. É o local para todos os presenters, seus templates e quaisquer classes auxiliares.
Organizamos esta camada por domínios. Em um projeto complexo que combina uma loja virtual, um blog e uma API, a estrutura seria assim:
app/Presentation/ ├── Shop/ ← frontend da loja virtual │ ├── Product/ │ ├── Cart/ │ └── Order/ ├── Blog/ ← blog │ ├── Home/ │ └── Post/ ├── Admin/ ← administração │ ├── Dashboard/ │ └── Products/ └── Api/ ← endpoints da API └── V1/
Por outro lado, para um blog simples, usaríamos a seguinte divisão:
app/Presentation/ ├── Front/ ← frontend do site │ ├── Home/ │ └── Post/ ├── Admin/ ← administração │ ├── Dashboard/ │ └── Posts/ ├── Error/ └── Export/ ← RSS, sitemaps, etc.
Pastas como Home/
ou Dashboard/
contêm presenters e templates. Pastas como Front/
,
Admin/
ou Api/
são chamadas de módulos. Tecnicamente, são diretórios comuns que servem para a
divisão lógica da aplicação.
Cada pasta com um presenter contém um presenter de mesmo nome e seus templates. Por exemplo, a pasta Dashboard/
contém:
Dashboard/ ├── DashboardPresenter.php ← presenter └── default.latte ← template
Esta estrutura de diretórios se reflete nos namespaces das classes. Por exemplo, DashboardPresenter
está
localizado no namespace App\Presentation\Admin\Dashboard
(veja mapování
presenterů):
namespace App\Presentation\Admin\Dashboard;
class DashboardPresenter extends Nette\Application\UI\Presenter
{
// ...
}
Referimo-nos ao presenter Dashboard
dentro do módulo Admin
na aplicação usando a notação de dois
pontos como Admin:Dashboard
. À sua ação default
, então, como Admin:Dashboard:default
.
No caso de módulos aninhados, usamos mais dois pontos, por exemplo, Shop:Order:Detail:default
.
Desenvolvimento flexível da estrutura
Uma das grandes vantagens desta estrutura é como ela se adapta elegantemente às necessidades crescentes do projeto. Como exemplo, vejamos a parte que gera feeds XML. No início, temos uma forma simples:
Export/ ├── ExportPresenter.php ← um presenter para todas as exportações ├── sitemap.latte ← template para o sitemap └── feed.latte ← template para o feed RSS
Com o tempo, mais tipos de feeds 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 ├── zbozi.latte ← feed para Zboží.cz └── heureka.latte ← feed para Heureka.cz
Esta transformação é completamente fluida – basta criar novas subpastas, dividir o código nelas e atualizar os links
(por exemplo, de Export:feed
para Export:Feed:zbozi
). Graças a isso, podemos expandir gradualmente a
estrutura conforme necessário, o nível de aninhamento não é limitado de forma alguma.
Se, por exemplo, na administração você tiver muitos presenters relacionados ao gerenciamento de pedidos, como
OrderDetail
, OrderEdit
, OrderDispatch
, etc., você pode criar um módulo (pasta)
Order
neste local para melhor organização, que conterá (pastas para) os presenters Detail
,
Edit
, Dispatch
e outros.
Localização dos templates
Nos exemplos anteriores, vimos que os templates estão localizados diretamente na pasta com o presenter:
Dashboard/ ├── DashboardPresenter.php ← presenter ├── DashboardTemplate.php ← classe opcional para o template └── default.latte ← template
Esta localização se mostra na prática a mais conveniente – todos os arquivos relacionados estão à mão.
Alternativamente, você pode colocar os templates em uma subpasta templates/
. O Nette suporta ambas as variantes.
Você pode até colocar os templates completamente fora da pasta Presentation/
. Tudo sobre as opções de
localização de templates pode ser encontrado no capítulo Procurando templates.
Classes auxiliares e componentes
Frequentemente, presenters e templates são acompanhados por outros arquivos auxiliares. Nós os colocamos logicamente de acordo com seu escopo:
1. Diretamente com o presenter no caso de componentes específicos para esse presenter:
Product/ ├── ProductPresenter.php ├── ProductGrid.php ← componente para listagem de produtos └── FilterForm.php ← formulário para filtragem
2. Para o módulo – recomendamos usar a pasta Accessory
, que é colocada de forma clara no início do
alfabeto:
Front/ ├── Accessory/ │ ├── NavbarControl.php ← componentes para o frontend │ └── TemplateFilters.php ├── Product/ └── Cart/
3. Para toda a aplicação – 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 componentes em app/Components
. A escolha depende dos costumes da
equipe.
Model – o coração da aplicação
O Model contém toda a lógica de negócios da aplicação. Para sua organização, a regra se aplica novamente – 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 envio
No model, você normalmente encontrará estes tipos de classes:
Facades: representam o ponto de entrada principal para um domínio específico na aplicação. 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 facade esconde os detalhes de implementação do resto da aplicação, fornecendo assim uma interface limpa para trabalhar com o domínio dado.
class OrderFacade
{
public function createOrder(Cart $cart): Order
{
// validação
// criação do pedido
// envio de e-mail
// registro nas estatísticas
}
}
Serviços: focam em uma operação de negócios específica dentro do domínio. Ao contrário da facade, que orquestra casos de uso inteiros, um serviço implementa lógica de negócios específica (como cálculos de preços ou processamento de pagamentos). Os serviços são tipicamente sem estado e podem ser usados por facades como blocos de construção para operações mais complexas, ou diretamente por outras partes da aplicação para tarefas mais simples.
class PricingService
{
public function calculateTotal(Order $order): Money
{
// cálculo do preço
}
}
Repositórios: garantem toda a comunicação com o armazenamento de dados, tipicamente um banco de dados. Sua tarefa é carregar e salvar entidades e implementar métodos para sua busca. O repositório isola o resto da aplicação 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 na aplicação, que têm sua identidade e mudam ao longo do tempo. Tipicamente, 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 banco de dados 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 objects: objetos imutáveis que representam valores sem identidade própria – por exemplo, um valor monetário ou um endereço de e-mail. Duas instâncias de um value object com os mesmos valores são consideradas idênticas.
Código de infraestrutura
A pasta Core/
(ou também Infrastructure/
) é o lar da base técnica da aplicação. O código de
infraestrutura normalmente inclui:
app/Core/ ├── Router/ ← roteamento e gerenciamento de URL │ └── RouterFactory.php ├── Security/ ← autenticação e autorização │ ├── Authenticator.php │ └── Authorizator.php ├── Logging/ ← logging e monitoramento │ ├── SentryLogger.php │ └── FileLogger.php ├── Cache/ ← camada de cache │ └── FullPageCache.php └── Integration/ ← integração com serviços ext. ├── Slack/ └── Stripe/
Para projetos menores, uma estrutura plana é obviamente suficiente:
Core/ ├── RouterFactory.php ├── Authenticator.php └── QueueMailer.php
É o código que:
- Lida com a infraestrutura técnica (roteamento, logging, cache)
- Integra serviços externos (Sentry, Elasticsearch, Redis)
- Fornece serviços básicos para toda a aplicação (e-mail, banco de dados)
- É geralmente independente do domínio específico – cache ou logger funciona da mesma forma para uma loja virtual ou blog.
Está em dúvida se uma determinada classe pertence aqui ou ao model? A diferença crucial é que o código em
Core/
:
- Não sabe nada sobre o domínio (produtos, pedidos, artigos)
- Geralmente pode ser transferido para outro projeto
- Lida com “como funciona” (como enviar um e-mail), não “o que faz” (qual e-mail enviar)
Exemplo para melhor compreensão:
App\Core\MailerFactory
– cria instâncias da classe para envio de e-mails, lida com configurações SMTPApp\Model\OrderMailer
– usaMailerFactory
para enviar e-mails sobre pedidos, conhece seus templates e sabe quando devem ser enviados
Scripts de comando
Aplicações frequentemente precisam executar atividades fora das requisições HTTP normais – 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 é 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 agendadas ├── NewsletterCommand.php ← envio de newsletters └── ReminderCommand.php ← notificações para clientes
O que pertence ao model e o que pertence aos scripts de comando? Por exemplo, a lógica para enviar um único e-mail faz
parte do model, o envio em massa de milhares de e-mails já pertence a Tasks/
.
As tarefas são geralmente executadas a partir da linha de
comando ou via cron. Elas também podem ser executadas via requisição HTTP, mas é necessário pensar na segurança.
O presenter que inicia a tarefa precisa ser protegido, por exemplo, apenas para usuários logados ou com um token forte e acesso
de endereços IP permitidos. Para tarefas longas, é necessário aumentar o limite de tempo do script e usar
session_write_close()
para não bloquear a 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. Vejamos as mais comuns e seus usos:
app/ ├── Api/ ← lógica para API independente da camada de apresentação ├── Database/ ← scripts de migração e seeders para dados de teste ├── Components/ ← componentes visuais compartilhados em toda a aplicação ├── Event/ ← útil se você usa arquitetura orientada a eventos ├── Mail/ ← templates de e-mail e lógica relacionada └── Utils/ ← classes auxiliares
Para componentes visuais compartilhados usados em presenters em toda a aplicação, a pasta app/Components
ou
app/Controls
pode ser usada:
app/Components/ ├── Form/ ← componentes de formulário compartilhados │ ├── SignInForm.php │ └── UserForm.php ├── Grid/ ← componentes para listagens de dados │ └── DataGrid.php └── Navigation/ ← elementos de navegação ├── Breadcrumbs.php └── Menu.php
Aqui pertencem componentes que têm lógica mais complexa. Se você deseja compartilhar componentes entre vários projetos, é aconselhável extraí-los para um pacote composer separado.
No diretório app/Mail
, você pode colocar o gerenciamento da comunicação por e-mail:
app/Mail/ ├── templates/ ← templates de e-mail │ ├── order-confirmation.latte │ └── welcome.latte └── OrderMailer.php
Mapeamento de presenters
O mapeamento define regras para derivar o nome da classe a partir do nome do presenter. Especificamo-las na configuração sob a chave application › mapping
.
Nesta página, mostramos que colocamos os presenters na pasta app/Presentation
(ou app/UI
).
Precisamos informar esta convenção ao Nette no arquivo de configuração. Basta uma linha:
application:
mapping: App\Presentation\*\**Presenter
Como funciona o mapeamento? Para melhor compreensão, imaginemos primeiro uma aplicação sem módulos. Queremos que as
classes dos presenters caiam no namespace App\Presentation
, para que o presenter Home
seja mapeado para
a classe App\Presentation\HomePresenter
. O que conseguimos com esta configuração:
application:
mapping: App\Presentation\*Presenter
O mapeamento funciona de forma que o nome do presenter Home
substitui o asterisco na máscara
App\Presentation\*Presenter
, resultando no nome final da classe App\Presentation\HomePresenter
.
Simples!
Mas, como você pode ver nos exemplos neste e em outros capítulos, colocamos as classes dos presenters em subdiretórios
homônimos, por exemplo, o presenter Home
é mapeado para a classe App\Presentation\Home\HomePresenter
.
Conseguimos isso duplicando os dois pontos (requer Nette Application 3.2):
application:
mapping: App\Presentation\**Presenter
Agora vamos mapear presenters para módulos. Para cada módulo, podemos definir um mapeamento específico:
application:
mapping:
Front: App\Presentation\Front\**Presenter
Admin: App\Presentation\Admin\**Presenter
Api: App\Api\*Presenter
De acordo com esta configuração, o presenter Front:Home
é mapeado para a classe
App\Presentation\Front\Home\HomePresenter
, enquanto o presenter Api:OAuth
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 assim, é possível criar uma regra geral que os substitua. Um novo asterisco para o módulo é adicionado à máscara
da classe:
application:
mapping:
*: App\Presentation\*\**Presenter
Api: App\Api\*Presenter
Isso também funciona para estruturas de diretórios mais profundamente aninhadas, como, por exemplo, o presenter
Admin:User:Edit
, o segmento com asterisco se repete para cada nível e o resultado é a classe
App\Presentation\Admin\User\Edit\EditPresenter
.
Uma notação alternativa é usar um array composto por três segmentos em vez de uma string. Esta notação é equivalente à anterior:
application:
mapping:
*: [App\Presentation, *, **Presenter]
Api: [App\Api, '', *Presenter]