---
title: "Formularios"
description: "Construye formularios fluidamente con el factor estático de Nibiru."
---

Los formularios en Nibiru se construyen **fluentemente** al llamar a métodos estáticos en `\Nibiru\Factory\Form`. Cada llamada anexa un fragmento HTML a un búfer estático interno; una final `Form::addForm()` envuelve el búfer en un elemento `<form>` y devuelve la cadena HTML renderizada.

## Importar la fábrica
```php
use Nibiru\Factory\Form;
```
Ese es el único `use` que necesitas. Cada tipo de entrada es un método estático en esta clase.

## El catálogo completo de métodos

Existen tres tipos de método, por nombramiento histórico.

### `addInputType*` — para elementos `<input>` reales
```
addInputTypeText        addInputTypePassword    addInputTypeEmail
addInputTypeDate        addInputTypeDatetime    addInputTypeColor
addInputTypeRadio       addInputTypeCheckbox    addInputTypeSwitch
addInputTypeSubmit      addInputTypeTextarea
```
### `addType*` — para elementos de formulario no `<input>`
```
addTypeFileUpload       addTypeHidden           addTypeImageSubmit
addTypeNumber           addTypeRange            addTypeReset
addTypeSearch           addTypeTelefon          addTypeUrl
addTypeButton           addTypeLabel
```
### `addSelect` / `addSelectOption` — para `<select>` + `<option>`
```
addSelect               addSelectOption
```
### Ayudantes de diseño
```
addOpenDiv     addCloseDiv     addOpenAny     addCloseAny
addOpenSpan    addCloseSpan
```
### Ciclo de vida
```
create     // reset the static buffer; call before building a new form
addForm    // wrap the buffer in <form>...</form> and return as a string
```
:::caution[La inconsistencia de nombres es intencional, más bien]
`addInputTypePassword` vs `addTypePassword`, `addInputTypeFileupload` vs `addTypeFileUpload` — el prefijo coincide si el elemento es un `<input type="…">` (prefijo Input) o alguna otra etiqueta (prefijo Type). Es incómodo pero estable; la CLI de `./nibiru -c` usa el mismo patrón, por lo que la memoria muscular funciona.

## Construyendo un formulario
```php
use Nibiru\Factory\Form;

Form::create();   // reset the static buffer

Form::addOpenDiv(['class' => 'form-group']);
Form::addTypeLabel(['for' => 'login', 'value' => 'Username']);
Form::addInputTypeText([
    'name'        => 'login',
    'id'          => 'login',
    'class'       => 'form-control',
    'required'    => 'required',
    'placeholder' => 'Type your name…',
]);
Form::addCloseDiv();

Form::addOpenDiv(['class' => 'form-group']);
Form::addTypeLabel(['for' => 'password', 'value' => 'Password']);
Form::addInputTypePassword([
    'name'     => 'password',
    'id'       => 'password',
    'class'    => 'form-control',
    'required' => 'required',
]);
Form::addCloseDiv();

Form::addInputTypeSubmit(['value' => 'Sign in', 'class' => 'btn btn-primary']);

$html = Form::addForm([
    'method' => 'POST',
    'action' => '/login',
    'name'   => 'loginForm',
]);
```
Pase `$html` a su vista:
```php
View::assign(['loginForm' => $html]);
``````smarty
<div class="card-body">
  {$loginForm nofilter}
</div>
```
(`nofilter` porque `Form::addForm()` ya devuelve HTML renderizado — Smarty de lo contrario lo escapa.)

## Recetas

### Seleccionar con opciones
```php
Form::addSelect(['name' => 'country', 'class' => 'form-control', 'id' => 'country']);
Form::addSelectOption(['value' => 'at', 'label' => 'Austria']);
Form::addSelectOption(['value' => 'lu', 'label' => 'Luxembourg']);
Form::addSelectOption(['value' => 'us', 'label' => 'United States']);
```
`addSelect` abre el `<select>` y coloca el estado de recopilación de opciones en la cola; cada `addSelectOption` lo agrega; el envoltorio `</select>` se emite automáticamente cuando ocurre una llamada no opción siguiente.

### Grupo de radio
```php
foreach (['standard', 'admin', 'editor'] as $r) {
    Form::addInputTypeRadio([
        'name' => 'role', 'value' => $r, 'id' => "role-$r",
    ]);
    Form::addTypeLabel(['for' => "role-$r", 'value' => ucfirst($r)]);
}
```
### Carga de archivos
```php
Form::addTypeFileUpload([
    'name'   => 'avatar',
    'accept' => 'image/png,image/jpeg',
]);
```
No olvides el `enctype` en el formulario:
```php
Form::addForm([
    'method'  => 'POST',
    'action'  => '/profile/upload',
    'enctype' => 'multipart/form-data',
]);
```
### Token CSRF oculto
```php
Form::addTypeHidden([
    'name'  => 'csrf',
    'value' => bin2hex(random_bytes(16)),
]);
```
(Guarda el valor en `$_SESSION['csrf']` y verifica en POST.)

## Ayudantes de diseño

`addOpenDiv` / `addCloseDiv` y `addOpenAny` / `addCloseAny` te permiten componer diseños de estilo Bootstrap dentro del mismo flujo fluido:
```php
Form::addOpenDiv(['class' => 'row']);
  Form::addOpenDiv(['class' => 'col-md-6']);
    Form::addInputTypeText(['name' => 'first']);
  Form::addCloseDiv();
  Form::addOpenDiv(['class' => 'col-md-6']);
    Form::addInputTypeText(['name' => 'last']);
  Form::addCloseDiv();
Form::addCloseDiv();
```
`addOpenAny([…, 'tag' => 'fieldset'])` abre cualquier otro etiqueta; `addCloseAny([…, 'tag' => 'fieldset'])` la cierra.

## Cómo funciona por debajo

El renderizado del formulario es **basado en cadenas**, no basado en el DOM. Cada clase de tipo reside en `core/c/type<X>.php` y contiene una plantilla HTML con marcadores de posición:
```php
// core/c/typetext.php
private function _setElement() {
    $this->_element = '<input type="text" name="NAME" value="VALUE" '
                    . 'placeholder="PLACEHOLDER" maxlength="MAXLENGTH" ID CLASS '
                    . 'REQUIRED DATA>' . "\n";
}
```
La fábrica pasa tu array `$attributes` a través de `FormAttributes::loadAttributeValues()`, que reemplaza cada marcador de posición con el valor correspondiente. Los valores vacíos hacen que se borren los marcadores de posición para que los atributos no se rendericen con cadenas vacías.

Esta es la razón por la que **solo las claves conocidas funcionan** — `'name'`, `'value'`, `'placeholder'`, `'class'`, `'id'`, `'required'`, `'data'` son reconocidos; las claves arbitrarias se descartan silenciosamente.

## Patrón desde producción

El `formsController` en `thorax.nibiru-framework.com` construye su formulario de contacto en el constructor y lo asigna a una propiedad:
```php
namespace Nibiru;
use Nibiru\Adapter\Controller;
use Nibiru\Factory\Form;

class formsController extends Controller {
    private string $form;

    public function __construct() {
        parent::__construct();
        Form::create();
        Form::addTypeLabel(['value' => 'Full Name', 'for' => 'full-name']);
        Form::addInputTypeText([
            'name'     => 'full-name',
            'id'       => 'full-name',
            'required' => 'required',
            'class'    => 'contacts-input form-control',
        ]);
        // ...more fields...
        $this->form = Form::addForm([
            'name'   => 'newregister',
            'method' => 'post',
            'action' => '/forms/submit',
        ]);
    }

    public function pageAction() {
        View::assign(['form' => $this->form]);
    }
}
```
Esto mantiene las acciones del controlador pequeñas — el formulario es una línea para renderizar en la plantilla.

## Trucos comunes

- **Olvidar `Form::create()`.** El búfer es estático. Sin `create()` concatenarás en lo que estuviera ahí antes (incluyendo entre solicitudes en procesos PHP de larga duración).
- **Smarty escapando el HTML.** Agrega `nofilter` (o `|nofilter`) al imprimir la cadena renderizada.
- **Atributos personalizados ignorados silenciosamente.** Cada tipo acepta un conjunto fijo de claves de marcador de posición. Usa la clave `data` para los atributos `data-*` (que se expanden), pero los atributos verdaderamente arbitrarios se descartan.
- **Sin escape automático de XSS.** La capa del formulario es basada en cadenas. Si estás renderizando la entrada del usuario como un valor predeterminado, escápalo tú mismo antes de pasar al factoría.
- **Confusión entre `addInputType…` y `addType…`.** Cuando tengas dudas, mira el nombre del archivo `core/c/type<X>.php` — si el elemento HTML del tipo es `<input type="X">`, usa `addInputType<X>`, de lo contrario, usa `addType<X>`. `addSelect` es la única excepción (solo `addSelect`, sin prefijo).

## Validación de formularios

Nibiru no incluye validación del lado del servidor. Patrones comunes:

- `Respeto/Validación` para comprobaciones declarativas (ya presentes en muchas aplicaciones de producción Nibiru).
- Un módulo con un plugin `validate()` por tipo de formulario.
- HTML5 `required`, `pattern`, `min`/`max` como primera línea de defensa.
