Skip to main content

Lifecycle

Fields are nodes, so a field in a FormNode participates in both the normal node lifecycle and the form-specific lifecycle managed by FormService.

The order matters when callbacks read or mutate shared form state. Request callbacks prepare that state first; response callbacks derive the final field metadata before serialization.

Form and field callback order

Node request pass

  1. 1
    FormNodedefaultFieldValuesCallbackinitial only
    When no form-level callback is set, Qore calls each field's setDefaultValue() callback in field order, with setAutofillValue() as its fallback when autofill is enabled.
  2. 2
    Field$field->onUpdate()initial or update
    The update path depends on the request type.
    Initial$field->onUpdate(..., triggerImmediately: true)Opted-in fields run in field order; dependencies do not run.
    Update$field->onUpdate()The changed field runs first, then each $dependentField->setDependency() callback runs in field order.
  3. 3
    Field$field->setFieldIsReadOnly()each field
    The read-only callback runs first while preparing a field.
  4. 4
    Field$field->setFieldIsDisabled()each field
    The disabled callback runs after the read-only callback.
  5. 5
    Field$field->setFieldIsHidden()each field
    The visibility callback updates the field's hidden node state.
  6. 6
    Field$field->setFieldIsMarkedAsRequired()each field
    The required-marker callback runs after visibility.
  7. 7
    Field$field->onFormRequest()each field
    All form request callbacks on the field run in registration order. Qore then prepares the next field.
  8. 8
    FormNode$formNode->onSuccess()non-initial + success state
    Runs after every field has completed its form request callbacks.
  9. 9
    FormNode$formNode->onError()non-initial + error state
    Runs after onSuccess when both response states are present.
  10. 10
    FormNode$formNode->onNodeRequest()
    Application node-request callbacks run after the constructor-registered form lifecycle callback.
  11. 11
    Field$field->onNodeRequest()node layout order
    The normal child-node traversal now reaches each field present in the layout. Its callbacks run in registration order.

Node response pass

  1. 12
    FormNode$formNode->onFormReady()
    The response pass returns to the FormNode. onFormReady runs before any field form-response callback.
  2. 13
    Field$field->onFormResponse()each field
    One callback per field runs in field order and sees mutations made by onFormReady. A later registration replaces the earlier callback.
  3. 14
    FormNode$formNode->onNodeResponse()
    Application node-response callbacks run after onFormReady and every field's onFormResponse callback.
  4. 15
    Field$field->onNodeResponse()node layout order
    The normal child-node traversal reaches each field present in the layout. Its callbacks run in registration order.

Serialization

  1. 16
    Field$field->setRules()
    Rule callbacks are resolved in registration order while the field's form metadata is serialized.
  2. 17
    FrameworkField::getNodeAsArray()
    The completed field metadata, FormNode and response state are returned to the frontend.

Form submission validation is separate from this node-rendering request. Calling FormNode::validate() resolves each field's setRules() callbacks and then its setExtraRules() callbacks in field order. Keep rule callbacks free of side effects because metadata serialization can resolve them more than once.

On Form Request

Use onFormRequest() for work that must run on every form node request after defaults and field updates have been handled. Multiple callbacks on the same field run in registration order.

For example, FileField builds its hint while the form request is being handled:

$this->onFormRequest(function () {
$parts = [];

if ($this->getMinFiles() !== null) {
$parts[] = __('qore::common.file_hint.min_files', [
'count' => $this->getMinFiles(),
]);
}

// ...

$hint = implode(' • ', $parts);

if ($hint) {
$this->setHint($hint);
}
});

On Form Response

Use onFormResponse() when a field needs response-only data after request handling. FormNode::onFormReady() runs first, followed by each field's onFormResponse() callback in field order, so the callback sees mutations made by onFormReady().

A field holds one form response callback. Calling onFormResponse() again on the same field replaces the previous callback.

use Qore\Next\System\Field\Context\FormFieldContext;

$this->onFormResponse(function (FormFieldContext $context) {
if ($this->disabledDatesCallback) {
$callback = $this->disabledDatesCallback;
$this->disabledDates = $this->parseDisabledDates($callback($context));
}

// ...
});

Node Callbacks On Fields

Because Field extends Node, it can also register onNodeRequest() and onNodeResponse() callbacks. These run during the normal child-node traversal, after the corresponding FormNode callbacks:

use Qore\Next\System\Node\NodeManager;

$field
->onNodeRequest(function (NodeManager $manager) {
// Runs after the FormNode request lifecycle.
})
->onNodeResponse(function (NodeManager $manager) {
// Runs after onFormReady() and all field onFormResponse() callbacks.
});

Use the form callbacks when the callback needs a FormFieldContext. Use the node callbacks when it needs the lower-level NodeManager directly.