Forms
Build forms fluently with Nibiru's static factory.
Forms in Nibiru are built fluently by calling static methods on \Nibiru\Factory\Form. Each call appends an HTML fragment to an internal static buffer; a final Form::addForm() wraps the buffer in a <form> element and returns the rendered HTML string.
Import the factory
Section titled “Import the factory”use Nibiru\Factory\Form;That’s the only use you need. Every input type is a static method on this class.
The full method catalogue
Section titled “The full method catalogue”There are three flavours of method, by historical naming.
addInputType* — for actual <input> elements
Section titled “addInputType* — for actual <input> elements”addInputTypeText addInputTypePassword addInputTypeEmailaddInputTypeDate addInputTypeDatetime addInputTypeColoraddInputTypeRadio addInputTypeCheckbox addInputTypeSwitchaddInputTypeSubmit addInputTypeTextareaaddType* — for non-<input> form elements
Section titled “addType* — for non-<input> form elements”addTypeFileUpload addTypeHidden addTypeImageSubmitaddTypeNumber addTypeRange addTypeResetaddTypeSearch addTypeTelefon addTypeUrladdTypeButton addTypeLabeladdSelect / addSelectOption — for <select> + <option>
Section titled “addSelect / addSelectOption — for <select> + <option>”addSelect addSelectOptionLayout helpers
Section titled “Layout helpers”addOpenDiv addCloseDiv addOpenAny addCloseAnyaddOpenSpan addCloseSpanLifecycle
Section titled “Lifecycle”create / reset the static buffer; call before building a new formaddForm / wrap the buffer in <form>...</form> and return as a stringBuilding a form
Section titled “Building a form”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',]);Pass $html to your view:
View::assign(['loginForm' => $html]);<div class="card-body"> {$loginForm nofilter}</div>(nofilter because Form::addForm() already returns rendered HTML — Smarty otherwise escapes it.)
Recipes
Section titled “Recipes”Select with options
Section titled “Select with options”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 opens the <select> and queues option-collection state; each addSelectOption appends to it; the wrapping </select> is emitted automatically when the next non-option call happens.
Radio group
Section titled “Radio group”foreach (['standard', 'admin', 'editor'] as $r) { Form::addInputTypeRadio([ 'name' => 'role', 'value' => $r, 'id' => "role-$r", ]); Form::addTypeLabel(['for' => "role-$r", 'value' => ucfirst($r)]);}File upload
Section titled “File upload”Form::addTypeFileUpload([ 'name' => 'avatar', 'accept' => 'image/png,image/jpeg',]);Don’t forget enctype on the form:
Form::addForm([ 'method' => 'POST', 'action' => '/profile/upload', 'enctype' => 'multipart/form-data',]);Hidden CSRF token
Section titled “Hidden CSRF token”Form::addTypeHidden([ 'name' => 'csrf', 'value' => bin2hex(random_bytes(16)),]);(Stash the value in $_SESSION['csrf'] and verify on POST.)
Layout helpers
Section titled “Layout helpers”addOpenDiv / addCloseDiv and addOpenAny / addCloseAny let you compose Bootstrap-style layouts inside the same fluent stream:
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']) opens any other tag; addCloseAny([…, 'tag' => 'fieldset']) closes it.
How it works under the hood
Section titled “How it works under the hood”Form rendering is string-based, not DOM-based. Each type class lives at core/c/type<X>.php and contains an HTML template with placeholders:
private function _setElement() { $this->_element = '<input type="text" name="NAME" value="VALUE" ' . 'placeholder="PLACEHOLDER" maxlength="MAXLENGTH" ID CLASS ' . 'REQUIRED DATA>' . "\n";}The factory passes your $attributes array through FormAttributes::loadAttributeValues() which str_replaces each placeholder with the corresponding value. Empty values get the placeholder erased so attributes don’t render with empty strings.
This is why only known keys work — 'name', 'value', 'placeholder', 'class', 'id', 'required', 'data' are recognised; arbitrary keys are dropped silently.
Pattern from production
Section titled “Pattern from production”The formsController on thorax.nibiru-framework.com builds its contact form in the constructor and assigns it to a property:
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]); }}This keeps the controller’s actions tiny — the form is a one-liner to render in the template.
Common pitfalls
Section titled “Common pitfalls”- Forgetting
Form::create(). The buffer is static. Withoutcreate()you’ll concatenate onto whatever was there last (including across requests in long-running PHP processes). - Smarty escaping the HTML. Add
nofilter(or|nofilter) when echoing the rendered string. - Custom attributes silently ignored. Each type accepts a fixed set of placeholder keys. Use the
datakey fordata-*attributes (which gets expanded), but truly arbitrary attributes are dropped. - No automatic XSS escaping. The form layer is string-based. If you’re rendering user input as a default
value, escape it yourself before passing to the factory. addInputType…vsaddType…confusion. When in doubt, look at thecore/c/type<X>.phpfilename — if the type’s HTML element is<input type="X">useaddInputType<X>, otherwiseaddType<X>.addSelectis the lone exception (justaddSelect, no prefix).
Form validation
Section titled “Form validation”Nibiru does not ship server-side validation. Common patterns:
Respect/Validationfor declarative checks (already in many production Nibiru apps).- A module with a
validate()plugin per form type. - HTML5
required,pattern,min/maxfor the first line of defence.