Nette Documentation Preview

syntax
Estructura de directorios de la aplicación
******************************************

<div class=perex>

¿Cómo diseñar una estructura de directorios clara y escalable para proyectos en Nette Framework? Te mostraremos prácticas probadas que te ayudarán a organizar tu código. Aprenderás:

- cómo **estructurar lógicamente** la aplicación en directorios
- cómo diseñar la estructura para **escalar bien** a medida que crece el proyecto
- cuáles son las **posibles alternativas** y sus ventajas o desventajas

</div>


Es importante mencionar que Nette Framework no insiste en ninguna estructura específica. Está diseñado para adaptarse fácilmente a cualquier necesidad y preferencia.


Estructura básica del proyecto .[#toc-basic-project-structure]
==============================================================

Aunque Nette Framework no dicta ninguna estructura de directorios fija, existe una disposición por defecto probada en forma de [Proyecto Web |https://github.com/nette/web-project]:

/--pre
<b>web-project/</b>
├── <b>app/</b>              ← directorio de la aplicación
├── <b>assets/</b>           ← SCSS, archivos JS, imágenes..., alternativamente resources/
├── <b>bin/</b>              ← scripts de línea de comandos
├── <b>config/</b>           ← configuración
├── <b>log/</b>              ← errores registrados
├── <b>temp/</b>             ← archivos temporales, caché
├── <b>tests/</b>            ← pruebas
├── <b>vendor/</b>           ← bibliotecas instaladas por Composer
└── <b>www/</b>              ← directorio público (document-root)
\--

Puede modificar libremente esta estructura según sus necesidades - renombrar o mover carpetas. Entonces sólo tiene que ajustar las rutas relativas a los directorios en `Bootstrap.php` y posiblemente `composer.json`. No se necesita nada más, ni una reconfiguración compleja, ni cambios constantes. Nette tiene una autodetección inteligente y reconoce automáticamente la ubicación de la aplicación incluyendo su URL base.


Principios de organización del código .[#toc-code-organization-principles]
==========================================================================

Cuando exploras por primera vez un nuevo proyecto, deberías ser capaz de orientarte rápidamente. Imagina que haces clic en el directorio `app/Model/` y ves esta estructura:

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

De esto, sólo aprenderás que el proyecto utiliza algunos servicios, repositorios y entidades. No aprenderás nada sobre el propósito real de la aplicación.

Veamos un enfoque diferente - **organización por dominios**:

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

Esto es diferente - a primera vista está claro que se trata de un sitio de comercio electrónico. Los propios nombres de los directorios revelan lo que puede hacer la aplicación: trabaja con pagos, pedidos y productos.

El primer enfoque (organización por tipo de clase) trae varios problemas en la práctica: el código que está lógicamente relacionado está disperso en diferentes carpetas y hay que saltar entre ellas. Por lo tanto, organizaremos por dominios.


Espacios de nombres .[#toc-namespaces]
--------------------------------------

Es convencional que la estructura de directorios se corresponda con los espacios de nombres de la aplicación. Esto significa que la ubicación física de los archivos se corresponde con su espacio de nombres. Por ejemplo, una clase ubicada en `app/Model/Product/ProductRepository.php` debería tener el espacio de nombres `App\Model\Product`. Este principio ayuda a orientar el código y simplifica la carga automática.


Singular y plural en los nombres .[#toc-singular-vs-plural-in-names]
--------------------------------------------------------------------

Observe que utilizamos el singular para los principales directorios de la aplicación: `app`, `config`, `log`, `temp`, `www`. Lo mismo ocurre dentro de la aplicación: `Model`, `Core`, `Presentation`. Esto se debe a que cada uno representa un concepto unificado.

Del mismo modo, `app/Model/Product` representa todo lo relacionado con los productos. No lo llamamos `Products` porque no es una carpeta llena de productos (que contendría archivos como `iphone.php`, `samsung.php`). Es un espacio de nombres que contiene clases para trabajar con productos - `ProductRepository.php`, `ProductService.php`.

La carpeta `app/Tasks` es plural porque contiene un conjunto de scripts ejecutables independientes - `CleanupTask.php`, `ImportTask.php`. Cada uno de ellos es una unidad independiente.

Por coherencia, se recomienda utilizar:
- Singular para los espacios de nombres que representen una unidad funcional (aunque se trabaje con varias entidades).
- Plural para las colecciones de unidades independientes
- En caso de incertidumbre o si no quiere pensar en ello, elija singular


Directorio público `www/` .[#toc-public-directory-www]
======================================================

Este directorio es el único accesible desde la web (llamado document-root). A menudo puede encontrar el nombre `public/` en lugar de `www/` - es sólo una cuestión de convención y no afecta a la funcionalidad. El directorio contiene:
- [Punto de entrada de |bootstrap#index.php] la aplicación `index.php`
- Archivo `.htaccess` con reglas mod_rewrite (para Apache)
- Archivos estáticos (CSS, JavaScript, imágenes)
- Archivos subidos

Para una correcta seguridad de la aplicación, es crucial tener correctamente [configurado el document-root |nette:troubleshooting#how-to-change-or-remove-www-directory-from-url].

.[note]
Nunca coloques la carpeta `node_modules/` en este directorio - contiene miles de archivos que pueden ser ejecutables y no deberían ser accesibles públicamente.


Directorio de aplicaciones `app/` .[#toc-application-directory-app]
===================================================================

Este es el directorio principal con el código de la aplicación. Estructura básica:

/--pre
<b>app/</b>
├── <b>Core/</b>               ← cuestiones de infraestructura
├── <b>Model/</b>              ← lógica empresarial
├── <b>Presentation/</b>       ← presentadores y plantillas
├── <b>Tasks/</b>              ← scripts de comandos
└── <b>Bootstrap.php</b>       ← clase de arranque de la aplicación
\--

`Bootstrap.php` es la [clase de inicio de la aplicación |bootstrap] que inicializa el entorno, carga la configuración y crea el contenedor DI.

Veamos ahora en detalle los subdirectorios individuales.


Presentadores y plantillas .[#toc-presenters-and-templates]
===========================================================

Tenemos la parte de presentación de la aplicación en el directorio `app/Presentation`. Una alternativa es el corto `app/UI`. Este es el lugar para todos los presentadores, sus plantillas y cualquier clase de ayuda.

Organizamos esta capa por dominios. En un proyecto complejo que combine comercio electrónico, blog y API, la estructura tendría este aspecto:

/--pre
<b>app/Presentation/</b>
├── <b>Shop/</b>              ← frontend de comercio electrónico
│   ├── <b>Product/</b>
│   ├── <b>Cart/</b>
│   └── <b>Order/</b>
├── <b>Blog/</b>              ← blog
│   ├── <b>Home/</b>
│   └── <b>Post/</b>
├── <b>Admin/</b>             ← administración
│   ├── <b>Dashboard/</b>
│   └── <b>Products/</b>
└── <b>Api/</b>               ← puntos finales API
	└── <b>V1/</b>
\--

Por el contrario, para un blog sencillo utilizaríamos esta estructura:

/--pre
<b>app/Presentation/</b>
├── <b>Front/</b>             ← frontend del sitio web
│   ├── <b>Home/</b>
│   └── <b>Post/</b>
├── <b>Admin/</b>             ← administración
│   ├── <b>Dashboard/</b>
│   └── <b>Posts/</b>
├── <b>Error/</b>
└── <b>Export/</b>            ← RSS, sitemaps, etc.
\--

Carpetas como `Home/` o `Dashboard/` contienen presentadores y plantillas. Las carpetas como `Front/`, `Admin/` o `Api/` se denominan **módulos**. Técnicamente, se trata de directorios normales que sirven para la organización lógica de la aplicación.

Cada carpeta con un presentador contiene un presentador de nombre similar y sus plantillas. Por ejemplo, la carpeta `Dashboard/` contiene:

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

Esta estructura de directorios se refleja en los espacios de nombres de las clases. Por ejemplo, `DashboardPresenter` se encuentra en el espacio de nombres `App\Presentation\Admin\Dashboard` (véase [la asignación de presentadores |#presenter mapping]):

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

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

Nos referimos al presentador `Dashboard` dentro del módulo `Admin` en la aplicación utilizando la notación de dos puntos como `Admin:Dashboard`. A su acción `default` entonces como `Admin:Dashboard:default`. Para los módulos anidados, utilizamos más dos puntos, por ejemplo `Shop:Order:Detail:default`.


Desarrollo de estructuras flexibles .[#toc-flexible-structure-development]
--------------------------------------------------------------------------

Una de las grandes ventajas de esta estructura es lo elegantemente que se adapta a las crecientes necesidades del proyecto. Como ejemplo, tomemos la parte de generación de feeds XML. Inicialmente, tenemos un simple formulario:

/--pre
<b>Export/</b>
├── <b>ExportPresenter.php</b>   ← un presentador para todas las exportaciones
├── <b>sitemap.latte</b>         ← plantilla para el mapa del sitio
└── <b>feed.latte</b>            ← plantilla para RSS feed
\--

Con el tiempo, se añaden más tipos de feeds y necesitamos más lógica para ellos... No hay problema. La carpeta `Export/` simplemente se convierte en un 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 Amazon
	└── <b>ebay.latte</b>           ← feed para eBay
\--

Esta transformación es completamente fluida: basta con crear nuevas subcarpetas, dividir el código en ellas y actualizar los enlaces (por ejemplo, de `Export:feed` a `Export:Feed:amazon`). Gracias a esto, podemos ampliar gradualmente la estructura según sea necesario, el nivel de anidamiento no está limitado en modo alguno.

Por ejemplo, si en la administración tiene muchos presentadores relacionados con la gestión de pedidos, como `OrderDetail`, `OrderEdit`, `OrderDispatch` etc., puede crear un módulo (carpeta) `Order` para una mejor organización, que contendrá (carpetas para) los presentadores `Detail`, `Edit`, `Dispatch` y otros.


Ubicación de la plantilla .[#toc-template-location]
---------------------------------------------------

En los ejemplos anteriores, vimos que las plantillas se ubican directamente en la carpeta con el presentador:

/--pre
<b>Dashboard/</b>
├── <b>DashboardPresenter.php</b>     ← presentador
├── <b>DashboardTemplate.php</b>      ← clase de plantilla opcional
└── <b>default.latte</b>              ← plantilla
\--

Esta ubicación resulta ser la más conveniente en la práctica - usted tiene todos los archivos relacionados a la mano.

Otra posibilidad es colocar las plantillas en una subcarpeta de `templates/`. Nette admite ambas variantes. Incluso puede colocar las plantillas completamente fuera de la carpeta `Presentation/`. Encontrará toda la información sobre las opciones de ubicación de las plantillas en el capítulo [Búsqueda de plantillas |templates#Template Lookup].


Clases de ayuda y componentes .[#toc-helper-classes-and-components]
-------------------------------------------------------------------

Los presentadores y las plantillas suelen venir acompañados de otros archivos de ayuda. Los colocamos lógicamente según su ámbito de aplicación:

1. **Directamente con el presentador** en caso de componentes específicos para el presentador dado:

/--pre
<b>Product/</b>
├── <b>ProductPresenter.php</b>
├── <b>ProductGrid.php</b>        ← componente para el listado de productos
└── <b>FilterForm.php</b>         ← formulario de filtrado
\--

2. **Para el módulo** - se recomienda utilizar la carpeta `Accessory`, que se coloca ordenadamente al principio del alfabeto:

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

3. **Para toda la aplicación** - en `Presentation/Accessory/`:
/--pre
<b>app/Presentation/</b>
├── <b>Accessory/</b>
│   ├── <b>LatteExtension.php</b>
│   └── <b>TemplateFilters.php</b>
├── <b>Front/</b>
└── <b>Admin/</b>
\--

O puede colocar clases de ayuda como `LatteExtension.php` o `TemplateFilters.php` en la carpeta de infraestructura `app/Core/Latte/`. Y los componentes en `app/Components`. La elección depende de las convenciones del equipo.


Modelo - Corazón de la aplicación .[#toc-model-heart-of-the-application]
========================================================================

El modelo contiene toda la lógica de negocio de la aplicación. Para su organización, se aplica la misma regla - estructuramos por dominios:

/--pre
<b>app/Model/</b>
├── <b>Payment/</b>                   ← todo sobre los pagos
│   ├── <b>PaymentFacade.php</b>      ← punto de entrada principal
│   ├── <b>PaymentRepository.php</b>
│   ├── <b>Payment.php</b>            ← entidad
├── <b>Order/</b>                     ← todo sobre pedidos
│   ├── <b>OrderFacade.php</b>
│   ├── <b>OrderRepository.php</b>
│   ├── <b>Order.php</b>
└── <b>Shipping/</b>                  ← todo sobre envíos
\--

En el modelo, normalmente se encuentran estos tipos de clases:

**Facades**: representan el principal punto de entrada a un dominio específico en la aplicación. Actúan como un orquestador que coordina la cooperación entre diferentes servicios para implementar casos de uso completos (como "crear pedido" o "procesar pago"). Bajo su capa de orquestación, la fachada oculta los detalles de implementación al resto de la aplicación, proporcionando así una interfaz limpia para trabajar con el dominio en cuestión.

```php
class OrderFacade
{
	public function createOrder(Cart $cart): Order
	{
		// validación
		// creación de pedidos
		// envío de correos electrónicos
		// estadísticas
	}
}
```

**Servicios**: se centran en operaciones de negocio específicas dentro de un dominio. A diferencia de las fachadas, que orquestan casos de uso completos, un servicio implementa una lógica de negocio específica (como el cálculo de precios o el procesamiento de pagos). Los servicios suelen carecer de estado y pueden ser utilizados por las fachadas como bloques de construcción para operaciones más complejas, o directamente por otras partes de la aplicación para tareas más sencillas.

```php
class PricingService
{
	public function calculateTotal(Order $order): Money
	{
		// cálculo del precio
	}
}
```

**Repositorios**: gestionan toda la comunicación con el almacenamiento de datos, normalmente una base de datos. Su tarea es cargar y guardar entidades e implementar métodos para buscarlas. Un repositorio protege al resto de la aplicación de los detalles de implementación de la base de datos y proporciona una interfaz orientada a objetos para trabajar con los datos.

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

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

**Entidades**: objetos que representan los principales conceptos de negocio de la aplicación, que tienen su identidad y cambian con el tiempo. Suelen ser clases asignadas a tablas de bases de datos mediante ORM (como Nette Database Explorer o Doctrine). Las entidades pueden contener reglas de negocio relativas a sus datos y lógica de validación.

```php
// Entidad asignada a la tabla de base de datos de pedidos
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 valor**: objetos inmutables que representan valores sin identidad propia - por ejemplo, una cantidad de dinero o una dirección de correo electrónico. Dos instancias de un objeto valor con los mismos valores se consideran idénticas.


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

La carpeta `Core/` (o también `Infrastructure/`) alberga la base técnica de la aplicación. El código de infraestructura suele incluir:

/--pre
<b>app/Core/</b>
├── <b>Router/</b>               ← enrutamiento y gestión de URL
│   └── <b>RouterFactory.php</b>
├── <b>Security/</b>             ← autenticación y autorización
│   ├── <b>Authenticator.php</b>
│   └── <b>Authorizator.php</b>
├── <b>Logging/</b>              ← registro y supervisión
│   ├── <b>SentryLogger.php</b>
│   └── <b>FileLogger.php
├── <b>Cache/</b>                ← capa de caché
│   └── <b>FullPageCache.php</b>
└── <b>Integration/</b>          ← integración con servicios externos
	├── <b>Slack/</b>
	└── <b>Stripe/</b>
\--

Para proyectos más pequeños, una estructura plana es naturalmente suficiente:

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

Esto es código que:

- Gestiona la infraestructura técnica (enrutamiento, registro, almacenamiento en caché)
- Integra servicios externos (Sentry, Elasticsearch, Redis)
- Proporciona servicios básicos para toda la aplicación (correo, base de datos)
- Es en su mayor parte independiente del dominio específico - la caché o el logger funcionan igual para el comercio electrónico o el blog.

¿Te preguntas si una determinada clase debe estar aquí o en el modelo? La diferencia clave es que el código en `Core/`:

- No sabe nada sobre el dominio (productos, pedidos, artículos)
- Normalmente puede transferirse a otro proyecto
- Resuelve "cómo funciona" (cómo enviar correo), no "qué hace" (qué correo enviar)

Ejemplo para una mejor comprensión:

- `App\Core\MailerFactory` - crea instancias de la clase de envío de correo electrónico, gestiona la configuración SMTP
- `App\Model\OrderMailer` - utiliza `MailerFactory` para enviar correos sobre pedidos, conoce sus plantillas y cuándo deben enviarse


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

Las aplicaciones a menudo necesitan realizar tareas fuera de las peticiones HTTP habituales, ya sea procesamiento de datos en segundo plano, mantenimiento o tareas periódicas. Los scripts simples en el directorio `bin/` se utilizan para la ejecución, mientras que la lógica de implementación real se coloca en `app/Tasks/` (o `app/Commands/`).

Ejemplo:

/--pre
<b>app/Tasks/</b>
├── <b>Maintenance/</b>               ← scripts de mantenimiento
│   ├── <b>CleanupCommand.php</b>     ← eliminación de datos antiguos
│   └── <b>DbOptimizeCommand.php</b>  ← optimización de la base de datos
├── <b>Integration/</b>               ← integración con sistemas externos
│   ├── <b>ImportProducts.php</b>     ← importación desde el sistema del proveedor
│   └── <b>SyncOrders.php</b>         ← sincronización de pedidos
└── <b>Scheduled/</b>                 ← tareas periódicas
	├── <b>NewsletterCommand.php</b>  ← envío de boletines
	└── <b>ReminderCommand.php</b>    ← notificaciones a clientes
\--

¿Qué pertenece al modelo y qué a las secuencias de comandos? Por ejemplo, la lógica para enviar un correo electrónico forma parte del modelo, el envío masivo de miles de correos electrónicos pertenece a `Tasks/`.

Las tareas suelen [ejecutarse desde la línea de comandos |https://blog.nette.org/en/cli-scripts-in-nette-application] o mediante cron. También pueden ejecutarse a través de una petición HTTP, pero hay que tener en cuenta la seguridad. El presentador que ejecuta la tarea debe estar protegido, por ejemplo, sólo para usuarios registrados o con un token seguro y acceso desde direcciones IP permitidas. Para tareas largas, es necesario aumentar el límite de tiempo del script y utilizar `session_write_close()` para evitar el bloqueo de la sesión.


Otros directorios posibles .[#toc-other-possible-directories]
=============================================================

Además de los directorios básicos mencionados, puedes añadir otras carpetas especializadas según las necesidades del proyecto. Veamos las más comunes y su uso:

/--pre
<b>app/</b>
├── <b>Api/</b>              ← lógica de la API independiente de la capa de presentación
├── <b>Database/</b>         ← scripts de migración y sembradores de datos de prueba
├── <b>Components/</b>       ← componentes visuales compartidos en toda la aplicación
├── <b>Event/</b>            ← útil si se utiliza una arquitectura basada en eventos
├── <b>Mail/</b>             ← plantillas de correo electrónico y lógica relacionada
└── <b>Utils/</b>            ← clases de ayuda
\--

Para los componentes visuales compartidos que se utilizan en los presentadores de toda la aplicación, puedes utilizar la carpeta `app/Components` o `app/Controls`:

/--pre
<b>app/Components/</b>
├── <b>Form/</b>                 ← componentes de formulario compartidos
│   ├── <b>SignInForm.php</b>
│   └── <b>UserForm.php</b>
├── <b>Grid/</b>                 ← componentes para listados de datos
│   └── <b>DataGrid.php</b>
└── <b>Navigation/</b>           ← elementos de navegación
	├── <b>Breadcrumbs.php</b>
	└── <b>Menu.php</b>
\--

Aquí es donde pertenecen los componentes con una lógica más compleja. Si quieres compartir componentes entre varios proyectos, es bueno separarlos en un paquete compositor independiente.

En el directorio `app/Mail` puedes colocar la gestión de la comunicación por correo electrónico:

/--pre
<b>app/Mail/</b>
├── <b>templates/</b>            ← plantillas de correo electrónico
│   ├── <b>order-confirmation.latte</b>
│   └── <b>welcome.latte</b>
└── <b>OrderMailer.php</b>
\--


Mapa del presentador .[#toc-presenter-mapping]
==============================================

El mapeo define reglas para derivar nombres de clase a partir de nombres de presentador. Las especificamos en la [configuración |configuration] bajo la clave `application › mapping`.

En esta página, hemos mostrado que colocamos los presentadores en la carpeta `app/Presentation` (o `app/UI`). Debemos informar a Nette de esta convención en el archivo de configuración. Una línea es suficiente:

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

¿Cómo funciona el mapeo? Para entenderlo mejor, imaginemos primero una aplicación sin módulos. Queremos que las clases del presentador pertenezcan al espacio de nombres `App\Presentation`, de modo que el presentador `Home` se asigne a la clase `App\Presentation\HomePresenter`. Esto se consigue con esta configuración:

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

El mapeo funciona sustituyendo el asterisco de la máscara `App\Presentation\*Presenter` por el nombre del presentador `Home`, dando como resultado final el nombre de la clase `App\Presentation\HomePresenter`. Muy sencillo.

Sin embargo, como puede ver en los ejemplos de este y otros capítulos, colocamos las clases presentadoras en subdirectorios epónimos, por ejemplo, el presentador `Home` se asigna a la clase `App\Presentation\Home\HomePresenter`. Esto se consigue duplicando los dos puntos (requiere Nette Application 3.2):

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

Ahora pasaremos a mapear presentadores en módulos. Podemos definir una asignación específica para cada módulo:

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

Según esta configuración, el presentador `Front:Home` se asigna a la clase `App\Presentation\Front\Home\HomePresenter`, mientras que el presentador `Api:OAuth` se asigna a la clase `App\Api\OAuthPresenter`.

Dado que los módulos `Front` y `Admin` tienen un método de asignación similar y que probablemente habrá más módulos de este tipo, es posible crear una regla general que los sustituya. Se añadirá un nuevo asterisco para el módulo a la máscara de clase:

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

También funciona para estructuras de directorios anidadas más profundas, como el presentador `Admin:User:Edit`, donde el segmento con asterisco se repite para cada nivel y da como resultado la clase `App\Presentation\Admin\User\Edit\EditPresenter`.

Una notación alternativa consiste en utilizar una matriz formada por tres segmentos en lugar de una cadena. Esta notación es equivalente a la anterior:

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

Estructura de directorios de la aplicación

¿Cómo diseñar una estructura de directorios clara y escalable para proyectos en Nette Framework? Te mostraremos prácticas probadas que te ayudarán a organizar tu código. Aprenderás:

  • cómo estructurar lógicamente la aplicación en directorios
  • cómo diseñar la estructura para escalar bien a medida que crece el proyecto
  • cuáles son las posibles alternativas y sus ventajas o desventajas

Es importante mencionar que Nette Framework no insiste en ninguna estructura específica. Está diseñado para adaptarse fácilmente a cualquier necesidad y preferencia.

Estructura básica del proyecto

Aunque Nette Framework no dicta ninguna estructura de directorios fija, existe una disposición por defecto probada en forma de Proyecto Web:

web-project/
├── app/              ← directorio de la aplicación
├── assets/           ← SCSS, archivos JS, imágenes..., alternativamente resources/
├── bin/              ← scripts de línea de comandos
├── config/           ← configuración
├── log/              ← errores registrados
├── temp/             ← archivos temporales, caché
├── tests/            ← pruebas
├── vendor/           ← bibliotecas instaladas por Composer
└── www/              ← directorio público (document-root)

Puede modificar libremente esta estructura según sus necesidades – renombrar o mover carpetas. Entonces sólo tiene que ajustar las rutas relativas a los directorios en Bootstrap.php y posiblemente composer.json. No se necesita nada más, ni una reconfiguración compleja, ni cambios constantes. Nette tiene una autodetección inteligente y reconoce automáticamente la ubicación de la aplicación incluyendo su URL base.

Principios de organización del código

Cuando exploras por primera vez un nuevo proyecto, deberías ser capaz de orientarte rápidamente. Imagina que haces clic en el directorio app/Model/ y ves esta estructura:

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

De esto, sólo aprenderás que el proyecto utiliza algunos servicios, repositorios y entidades. No aprenderás nada sobre el propósito real de la aplicación.

Veamos un enfoque diferente – organización por dominios:

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

Esto es diferente – a primera vista está claro que se trata de un sitio de comercio electrónico. Los propios nombres de los directorios revelan lo que puede hacer la aplicación: trabaja con pagos, pedidos y productos.

El primer enfoque (organización por tipo de clase) trae varios problemas en la práctica: el código que está lógicamente relacionado está disperso en diferentes carpetas y hay que saltar entre ellas. Por lo tanto, organizaremos por dominios.

Espacios de nombres

Es convencional que la estructura de directorios se corresponda con los espacios de nombres de la aplicación. Esto significa que la ubicación física de los archivos se corresponde con su espacio de nombres. Por ejemplo, una clase ubicada en app/Model/Product/ProductRepository.php debería tener el espacio de nombres App\Model\Product. Este principio ayuda a orientar el código y simplifica la carga automática.

Singular y plural en los nombres

Observe que utilizamos el singular para los principales directorios de la aplicación: app, config, log, temp, www. Lo mismo ocurre dentro de la aplicación: Model, Core, Presentation. Esto se debe a que cada uno representa un concepto unificado.

Del mismo modo, app/Model/Product representa todo lo relacionado con los productos. No lo llamamos Products porque no es una carpeta llena de productos (que contendría archivos como iphone.php, samsung.php). Es un espacio de nombres que contiene clases para trabajar con productos – ProductRepository.php, ProductService.php.

La carpeta app/Tasks es plural porque contiene un conjunto de scripts ejecutables independientes – CleanupTask.php, ImportTask.php. Cada uno de ellos es una unidad independiente.

Por coherencia, se recomienda utilizar:

  • Singular para los espacios de nombres que representen una unidad funcional (aunque se trabaje con varias entidades).
  • Plural para las colecciones de unidades independientes
  • En caso de incertidumbre o si no quiere pensar en ello, elija singular

Directorio público www/

Este directorio es el único accesible desde la web (llamado document-root). A menudo puede encontrar el nombre public/ en lugar de www/ – es sólo una cuestión de convención y no afecta a la funcionalidad. El directorio contiene:

  • Punto de entrada de la aplicación index.php
  • Archivo .htaccess con reglas mod_rewrite (para Apache)
  • Archivos estáticos (CSS, JavaScript, imágenes)
  • Archivos subidos

Para una correcta seguridad de la aplicación, es crucial tener correctamente configurado el document-root.

Nunca coloques la carpeta node_modules/ en este directorio – contiene miles de archivos que pueden ser ejecutables y no deberían ser accesibles públicamente.

Directorio de aplicaciones app/

Este es el directorio principal con el código de la aplicación. Estructura básica:

app/
├── Core/               ← cuestiones de infraestructura
├── Model/              ← lógica empresarial
├── Presentation/       ← presentadores y plantillas
├── Tasks/              ← scripts de comandos
└── Bootstrap.php       ← clase de arranque de la aplicación

Bootstrap.php es la clase de inicio de la aplicación que inicializa el entorno, carga la configuración y crea el contenedor DI.

Veamos ahora en detalle los subdirectorios individuales.

Presentadores y plantillas

Tenemos la parte de presentación de la aplicación en el directorio app/Presentation. Una alternativa es el corto app/UI. Este es el lugar para todos los presentadores, sus plantillas y cualquier clase de ayuda.

Organizamos esta capa por dominios. En un proyecto complejo que combine comercio electrónico, blog y API, la estructura tendría este aspecto:

app/Presentation/
├── Shop/              ← frontend de comercio electrónico
│   ├── Product/
│   ├── Cart/
│   └── Order/
├── Blog/              ← blog
│   ├── Home/
│   └── Post/
├── Admin/             ← administración
│   ├── Dashboard/
│   └── Products/
└── Api/               ← puntos finales API
	└── V1/

Por el contrario, para un blog sencillo utilizaríamos esta estructura:

app/Presentation/
├── Front/             ← frontend del sitio web
│   ├── Home/
│   └── Post/
├── Admin/             ← administración
│   ├── Dashboard/
│   └── Posts/
├── Error/
└── Export/            ← RSS, sitemaps, etc.

Carpetas como Home/ o Dashboard/ contienen presentadores y plantillas. Las carpetas como Front/, Admin/ o Api/ se denominan módulos. Técnicamente, se trata de directorios normales que sirven para la organización lógica de la aplicación.

Cada carpeta con un presentador contiene un presentador de nombre similar y sus plantillas. Por ejemplo, la carpeta Dashboard/ contiene:

Dashboard/
├── DashboardPresenter.php     ← presentador
└── default.latte              ← plantilla

Esta estructura de directorios se refleja en los espacios de nombres de las clases. Por ejemplo, DashboardPresenter se encuentra en el espacio de nombres App\Presentation\Admin\Dashboard (véase la asignación de presentadores):

namespace App\Presentation\Admin\Dashboard;

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

Nos referimos al presentador Dashboard dentro del módulo Admin en la aplicación utilizando la notación de dos puntos como Admin:Dashboard. A su acción default entonces como Admin:Dashboard:default. Para los módulos anidados, utilizamos más dos puntos, por ejemplo Shop:Order:Detail:default.

Desarrollo de estructuras flexibles

Una de las grandes ventajas de esta estructura es lo elegantemente que se adapta a las crecientes necesidades del proyecto. Como ejemplo, tomemos la parte de generación de feeds XML. Inicialmente, tenemos un simple formulario:

Export/
├── ExportPresenter.php   ← un presentador para todas las exportaciones
├── sitemap.latte         ← plantilla para el mapa del sitio
└── feed.latte            ← plantilla para RSS feed

Con el tiempo, se añaden más tipos de feeds y necesitamos más lógica para ellos… No hay problema. La carpeta Export/ simplemente se convierte en un módulo:

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

Esta transformación es completamente fluida: basta con crear nuevas subcarpetas, dividir el código en ellas y actualizar los enlaces (por ejemplo, de Export:feed a Export:Feed:amazon). Gracias a esto, podemos ampliar gradualmente la estructura según sea necesario, el nivel de anidamiento no está limitado en modo alguno.

Por ejemplo, si en la administración tiene muchos presentadores relacionados con la gestión de pedidos, como OrderDetail, OrderEdit, OrderDispatch etc., puede crear un módulo (carpeta) Order para una mejor organización, que contendrá (carpetas para) los presentadores Detail, Edit, Dispatch y otros.

Ubicación de la plantilla

En los ejemplos anteriores, vimos que las plantillas se ubican directamente en la carpeta con el presentador:

Dashboard/
├── DashboardPresenter.php     ← presentador
├── DashboardTemplate.php      ← clase de plantilla opcional
└── default.latte              ← plantilla

Esta ubicación resulta ser la más conveniente en la práctica – usted tiene todos los archivos relacionados a la mano.

Otra posibilidad es colocar las plantillas en una subcarpeta de templates/. Nette admite ambas variantes. Incluso puede colocar las plantillas completamente fuera de la carpeta Presentation/. Encontrará toda la información sobre las opciones de ubicación de las plantillas en el capítulo Búsqueda de plantillas.

Clases de ayuda y componentes

Los presentadores y las plantillas suelen venir acompañados de otros archivos de ayuda. Los colocamos lógicamente según su ámbito de aplicación:

1. Directamente con el presentador en caso de componentes específicos para el presentador dado:

Product/
├── ProductPresenter.php
├── ProductGrid.php        ← componente para el listado de productos
└── FilterForm.php         ← formulario de filtrado

2. Para el módulo – se recomienda utilizar la carpeta Accessory, que se coloca ordenadamente al principio del alfabeto:

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

3. Para toda la aplicación – en Presentation/Accessory/:

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

O puede colocar clases de ayuda como LatteExtension.php o TemplateFilters.php en la carpeta de infraestructura app/Core/Latte/. Y los componentes en app/Components. La elección depende de las convenciones del equipo.

Modelo – Corazón de la aplicación

El modelo contiene toda la lógica de negocio de la aplicación. Para su organización, se aplica la misma regla – estructuramos por dominios:

app/Model/
├── Payment/                   ← todo sobre los pagos
│   ├── PaymentFacade.php      ← punto de entrada principal
│   ├── PaymentRepository.php
│   ├── Payment.php            ← entidad
├── Order/                     ← todo sobre pedidos
│   ├── OrderFacade.php
│   ├── OrderRepository.php
│   ├── Order.php
└── Shipping/                  ← todo sobre envíos

En el modelo, normalmente se encuentran estos tipos de clases:

Facades: representan el principal punto de entrada a un dominio específico en la aplicación. Actúan como un orquestador que coordina la cooperación entre diferentes servicios para implementar casos de uso completos (como „crear pedido“ o „procesar pago“). Bajo su capa de orquestación, la fachada oculta los detalles de implementación al resto de la aplicación, proporcionando así una interfaz limpia para trabajar con el dominio en cuestión.

class OrderFacade
{
	public function createOrder(Cart $cart): Order
	{
		// validación
		// creación de pedidos
		// envío de correos electrónicos
		// estadísticas
	}
}

Servicios: se centran en operaciones de negocio específicas dentro de un dominio. A diferencia de las fachadas, que orquestan casos de uso completos, un servicio implementa una lógica de negocio específica (como el cálculo de precios o el procesamiento de pagos). Los servicios suelen carecer de estado y pueden ser utilizados por las fachadas como bloques de construcción para operaciones más complejas, o directamente por otras partes de la aplicación para tareas más sencillas.

class PricingService
{
	public function calculateTotal(Order $order): Money
	{
		// cálculo del precio
	}
}

Repositorios: gestionan toda la comunicación con el almacenamiento de datos, normalmente una base de datos. Su tarea es cargar y guardar entidades e implementar métodos para buscarlas. Un repositorio protege al resto de la aplicación de los detalles de implementación de la base de datos y proporciona una interfaz orientada a objetos para trabajar con los datos.

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

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

Entidades: objetos que representan los principales conceptos de negocio de la aplicación, que tienen su identidad y cambian con el tiempo. Suelen ser clases asignadas a tablas de bases de datos mediante ORM (como Nette Database Explorer o Doctrine). Las entidades pueden contener reglas de negocio relativas a sus datos y lógica de validación.

// Entidad asignada a la tabla de base de datos de pedidos
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 valor: objetos inmutables que representan valores sin identidad propia – por ejemplo, una cantidad de dinero o una dirección de correo electrónico. Dos instancias de un objeto valor con los mismos valores se consideran idénticas.

Código de infraestructura

La carpeta Core/ (o también Infrastructure/) alberga la base técnica de la aplicación. El código de infraestructura suele incluir:

app/Core/
├── Router/               ← enrutamiento y gestión de URL
│   └── RouterFactory.php
├── Security/             ← autenticación y autorización
│   ├── Authenticator.php
│   └── Authorizator.php
├── Logging/              ← registro y supervisión
│   ├── SentryLogger.php
│   └── FileLogger.php
├── Cache/                ← capa de caché
│   └── FullPageCache.php
└── Integration/          ← integración con servicios externos
	├── Slack/
	└── Stripe/

Para proyectos más pequeños, una estructura plana es naturalmente suficiente:

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

Esto es código que:

  • Gestiona la infraestructura técnica (enrutamiento, registro, almacenamiento en caché)
  • Integra servicios externos (Sentry, Elasticsearch, Redis)
  • Proporciona servicios básicos para toda la aplicación (correo, base de datos)
  • Es en su mayor parte independiente del dominio específico – la caché o el logger funcionan igual para el comercio electrónico o el blog.

¿Te preguntas si una determinada clase debe estar aquí o en el modelo? La diferencia clave es que el código en Core/:

  • No sabe nada sobre el dominio (productos, pedidos, artículos)
  • Normalmente puede transferirse a otro proyecto
  • Resuelve „cómo funciona“ (cómo enviar correo), no „qué hace“ (qué correo enviar)

Ejemplo para una mejor comprensión:

  • App\Core\MailerFactory – crea instancias de la clase de envío de correo electrónico, gestiona la configuración SMTP
  • App\Model\OrderMailer – utiliza MailerFactory para enviar correos sobre pedidos, conoce sus plantillas y cuándo deben enviarse

Scripts de comandos

Las aplicaciones a menudo necesitan realizar tareas fuera de las peticiones HTTP habituales, ya sea procesamiento de datos en segundo plano, mantenimiento o tareas periódicas. Los scripts simples en el directorio bin/ se utilizan para la ejecución, mientras que la lógica de implementación real se coloca en app/Tasks/ (o app/Commands/).

Ejemplo:

app/Tasks/
├── Maintenance/               ← scripts de mantenimiento
│   ├── CleanupCommand.php     ← eliminación de datos antiguos
│   └── DbOptimizeCommand.php  ← optimización de la base de datos
├── Integration/               ← integración con sistemas externos
│   ├── ImportProducts.php     ← importación desde el sistema del proveedor
│   └── SyncOrders.php         ← sincronización de pedidos
└── Scheduled/                 ← tareas periódicas
	├── NewsletterCommand.php  ← envío de boletines
	└── ReminderCommand.php    ← notificaciones a clientes

¿Qué pertenece al modelo y qué a las secuencias de comandos? Por ejemplo, la lógica para enviar un correo electrónico forma parte del modelo, el envío masivo de miles de correos electrónicos pertenece a Tasks/.

Las tareas suelen ejecutarse desde la línea de comandos o mediante cron. También pueden ejecutarse a través de una petición HTTP, pero hay que tener en cuenta la seguridad. El presentador que ejecuta la tarea debe estar protegido, por ejemplo, sólo para usuarios registrados o con un token seguro y acceso desde direcciones IP permitidas. Para tareas largas, es necesario aumentar el límite de tiempo del script y utilizar session_write_close() para evitar el bloqueo de la sesión.

Otros directorios posibles

Además de los directorios básicos mencionados, puedes añadir otras carpetas especializadas según las necesidades del proyecto. Veamos las más comunes y su uso:

app/
├── Api/              ← lógica de la API independiente de la capa de presentación
├── Database/         ← scripts de migración y sembradores de datos de prueba
├── Components/       ← componentes visuales compartidos en toda la aplicación
├── Event/            ← útil si se utiliza una arquitectura basada en eventos
├── Mail/             ← plantillas de correo electrónico y lógica relacionada
└── Utils/            ← clases de ayuda

Para los componentes visuales compartidos que se utilizan en los presentadores de toda la aplicación, puedes utilizar la carpeta app/Components o app/Controls:

app/Components/
├── Form/                 ← componentes de formulario compartidos
│   ├── SignInForm.php
│   └── UserForm.php
├── Grid/                 ← componentes para listados de datos
│   └── DataGrid.php
└── Navigation/           ← elementos de navegación
	├── Breadcrumbs.php
	└── Menu.php

Aquí es donde pertenecen los componentes con una lógica más compleja. Si quieres compartir componentes entre varios proyectos, es bueno separarlos en un paquete compositor independiente.

En el directorio app/Mail puedes colocar la gestión de la comunicación por correo electrónico:

app/Mail/
├── templates/            ← plantillas de correo electrónico
│   ├── order-confirmation.latte
│   └── welcome.latte
└── OrderMailer.php

Mapa del presentador

El mapeo define reglas para derivar nombres de clase a partir de nombres de presentador. Las especificamos en la configuración bajo la clave application › mapping.

En esta página, hemos mostrado que colocamos los presentadores en la carpeta app/Presentation (o app/UI). Debemos informar a Nette de esta convención en el archivo de configuración. Una línea es suficiente:

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

¿Cómo funciona el mapeo? Para entenderlo mejor, imaginemos primero una aplicación sin módulos. Queremos que las clases del presentador pertenezcan al espacio de nombres App\Presentation, de modo que el presentador Home se asigne a la clase App\Presentation\HomePresenter. Esto se consigue con esta configuración:

application:
	mapping: App\Presentation\*Presenter

El mapeo funciona sustituyendo el asterisco de la máscara App\Presentation\*Presenter por el nombre del presentador Home, dando como resultado final el nombre de la clase App\Presentation\HomePresenter. Muy sencillo.

Sin embargo, como puede ver en los ejemplos de este y otros capítulos, colocamos las clases presentadoras en subdirectorios epónimos, por ejemplo, el presentador Home se asigna a la clase App\Presentation\Home\HomePresenter. Esto se consigue duplicando los dos puntos (requiere Nette Application 3.2):

application:
	mapping: App\Presentation\**Presenter

Ahora pasaremos a mapear presentadores en módulos. Podemos definir una asignación específica para cada módulo:

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

Según esta configuración, el presentador Front:Home se asigna a la clase App\Presentation\Front\Home\HomePresenter, mientras que el presentador Api:OAuth se asigna a la clase App\Api\OAuthPresenter.

Dado que los módulos Front y Admin tienen un método de asignación similar y que probablemente habrá más módulos de este tipo, es posible crear una regla general que los sustituya. Se añadirá un nuevo asterisco para el módulo a la máscara de clase:

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

También funciona para estructuras de directorios anidadas más profundas, como el presentador Admin:User:Edit, donde el segmento con asterisco se repite para cada nivel y da como resultado la clase App\Presentation\Admin\User\Edit\EditPresenter.

Una notación alternativa consiste en utilizar una matriz formada por tres segmentos en lugar de una cadena. Esta notación es equivalente a la anterior:

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