Forms & validation
Forms in Tiger are declarative — you describe the fields, and the framework handles the rest: markup-free rendering, CSRF, and validators that run both at submit and live on blur, from a single declaration.
Declare fields, not markup
Extend Tiger_Form (never Zend_Form directly) and describe the schema in elements():
class Billing_Form_Invoice extends Tiger_Form
{
protected function elements(): array
{
return [
['text', 'amount', [
'required' => true,
'filters' => ['StringTrim'],
'validators' => [['Float'], ['GreaterThan', false, ['min' => 0]]],
'attribs' => ['class' => 'form-control', 'placeholder' => $this->_t('billing.invoice.amount')],
]],
['text', 'due_date', [
'required' => true,
'validators' => [['Date', false, ['format' => 'yyyy-MM-dd']]],
'attribs' => ['class' => 'form-control'],
]],
];
}
}
The base strips decorators to ViewHelper-only — so the view owns all layout, not the form object. (Tiger apps are client/server: forms are submitted over AJAX to a service, so the form's job is fields + validation, not HTML structure.)
Validate: same rules, submit and blur
At submit, the service validates the whole form:
public function create(array $params): void
{
$form = new Billing_Form_Invoice();
if (!$form->isValid($params)) { $this->_formErrors($form); return; } // → inline field errors
// … validated $form->getValues(), then a transaction …
}
Here's the free part: the validators you declared once also run on blur, before submit. Add these attributes to the <form> and Tiger wires it up:
<form id="invoice-form" data-tiger-validate data-module="billing" data-form="Billing_Form_Invoice"
onsubmit="return false;" novalidate>
<?= $this->form->getElement('_csrf') ?>
<!-- fields … -->
</form>
On blur, tiger.validate.js posts {form, field, value} to the public Tiger_Service_Validate, which runs that element's real validators and returns the first error — the field goes .is-invalid with its message inline while it's still easy to fix. You declare the validator once; it fires in both places. No hand-rolled per-field AJAX checks.
Batteries included
Tiger_Form ships with everything Zend validates, plus Tiger extras you'll reach for constantly:
- DB-uniqueness —
Zend_Validate_Db_NoRecordExists(e.g. "email already taken"). - Password policy —
Tiger_Validate_Password(length, history, complexity per config). - reCAPTCHA — a drop-in
['recaptcha', 'recaptcha', []]element that self-attaches its server-side validator (v2 + v3, config-driven, keyless in dev). - Localized country picker —
Tiger_I18n_Country, biased so common countries float to the top. - Address autocomplete — fills city/state/postal/country via the Location service.
The reference form
Don't start from scratch — copy the gold standard. modules/signup/forms/Signup.php and its view exercise every field type, DB-uniqueness, the password policy + strength meter, address autocomplete, and the country picker. When you build a new form, that's the one to crib from.
The rhythm
Every mutation in Tiger is the same shape — validate the form, then wrap the writes in a transaction:
if (!$form->isValid($params)) { $this->_formErrors($form); return; }
$id = $this->_transaction(function ($db) use ($params) { /* inserts/updates; throw to roll back */ });
$this->_success(['id' => $id], 'billing.invoice.created');
Validate → transaction → standard envelope. Learn it once; it's every service. (See Webservices for the response contract.)