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.
Form :: create (); / reset the static buffer
Form :: addOpenDiv ([ 'class' => 'form-group' ]);
Form :: addTypeLabel ([ 'for' => 'login' , 'value' => 'Username' ]);
'class' => 'form-control' ,
'required' => 'required' ,
'placeholder' => 'Type your name…' ,
Form :: addOpenDiv ([ 'class' => 'form-group' ]);
Form :: addTypeLabel ([ 'for' => 'password' , 'value' => 'Password' ]);
Form :: addInputTypePassword ([
'class' => 'form-control' ,
'required' => 'required' ,
Form :: addInputTypeSubmit ([ 'value' => 'Sign in' , 'class' => 'btn btn-primary' ]);
Pase $html a su vista:
View :: assign ([ 'loginForm' => $html]);
(nofilter porque Form::addForm() ya devuelve HTML renderizado — Smarty de lo contrario lo escapa.)
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.
foreach ([ 'standard' , 'admin' , 'editor' ] as $r) {
Form :: addInputTypeRadio ([
'name' => 'role' , 'value' => $r, 'id' => "role- $r " ,
Form :: addTypeLabel ([ 'for' => "role- $r " , 'value' => ucfirst ($r)]);
Form :: addTypeFileUpload ([
'accept' => 'image/png,image/jpeg' ,
No olvides el enctype en el formulario:
'action' => '/profile/upload' ,
'enctype' => 'multipart/form-data' ,
'value' => bin2hex ( random_bytes ( 16 )),
(Guarda el valor en $_SESSION['csrf'] y verifica en POST.)
addOpenDiv / addCloseDiv y addOpenAny / addCloseAny te permiten componer diseños de estilo Bootstrap dentro del mismo flujo fluido:
Form :: addOpenDiv ([ 'class' => 'row' ]);
Form :: addOpenDiv ([ 'class' => 'col-md-6' ]);
Form :: addInputTypeText ([ 'name' => 'first' ]);
Form :: addOpenDiv ([ 'class' => 'col-md-6' ]);
Form :: addInputTypeText ([ 'name' => 'last' ]);
addOpenAny([…, 'tag' => 'fieldset']) abre cualquier otro etiqueta; addCloseAny([…, 'tag' => 'fieldset']) la cierra.
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:
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.
El formsController en thorax.nibiru-framework.com construye su formulario de contacto en el constructor y lo asigna a una propiedad:
use Nibiru\Adapter\Controller ;
class formsController extends Controller {
public function __construct () {
Form :: addTypeLabel ([ 'value' => 'Full Name' , 'for' => 'full-name' ]);
'required' => 'required' ,
'class' => 'contacts-input form-control' ,
$this -> form = Form :: addForm ([
'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.
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).
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.