This is not click bait, but a 11.3 feature.
The issue explained it best:
It's 2025. The render API suspiciously looks like form API when we added it 20 years ago.
While there are some examples in the feature post, I wanted to dive in some more to find out if it is just an abstraction on top of an array or there is more to it.
Base setup
I took the form from the form API introduction.
// I only show the code that I will change here
public function buildForm(array $form, FormStateInterface $form_state) {
$form['phone_number'] = [
'#type' => 'tel',
'#title' => $this->t('Your phone number'),
];
$form['actions']['#type'] = 'actions';
$form['actions']['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Save'),
'#button_type' => 'primary',
];
return $form;
}
I'm running the 11.3 dev version. If you want to do that, just change minimum-stability to dev and prefer-stable to false in composer.json.
Telephone field rewrite
$renderStructure = $this->elementInfoManager()->fromRenderable($form);
// use Drupal\Core\Render\Element\Tel;
$phone = $renderStructure->createChild('phone_number', Tel::class);
$phone->title = $this->t('Your phone number');
The first line takes the array and makes a Drupal\Core\Render\Element\Generic
instance.
The third line does two things, it makes a Drupal\Core\Render\Element\Tel
instance and it adds the instance to the parent by reference. This allows you to call $renderStructure->toRenderable()
without the need to manually add the manipulated instance.
For the forth line the property will not be found by your IDE because they opted for @property comments instead of class properties.
Submit button rewrite
$actions = $renderStructure->createChild('actions', Actions::class);
$actionsSubmit = $actions->createChild('submit', Submit::class);
$actionsSubmit->value = $this->t('Save');
$actionsSubmit->button_type = 'primary';
I had to think for a few seconds to add the third line, because it didn't click right away $form['actions']['submit']
is a child. But now it makes sense and I prefer the object oriented way because the tree feels more explicit. And at the same time the code feels flatter because you manipulate one element at a time.
The full rewrite
// before
public function buildForm(array $form, FormStateInterface $form_state) {
$form['phone_number'] = [
'#type' => 'tel',
'#title' => $this->t('Your phone number'),
];
$form['actions']['#type'] = 'actions';
$form['actions']['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Save'),
'#button_type' => 'primary',
];
return $form;
}
// after
public function buildForm(array $form, FormStateInterface $form_state)
{
$renderStructure = $this->elementInfoManager()->fromRenderable($form);
$phone = $renderStructure->createChild('phone_number', Tel::class);
$phone->title = $this->t('Your phone number');
$actions = $renderStructure->createChild('actions', Actions::class);
$actionsSubmit = $actions->createChild('submit', Submit::class);
$actionsSubmit->value = $this->t('Save');
$actionsSubmit->button_type = 'primary';
return $renderStructure->toRenderable();
}
It is going to be annoying pretty fast to convert between form object and array, so an option is to create a custom FormBase
and add this method.
protected function buildFormObjectHelper(
array $form,
FormStateInterface $form_state,
callable $form_manipulations,
): array {
$renderStructure = $this->elementInfoManager()->fromRenderable($form);
$form_manipulations($renderStructure, $form_state);
return $renderStructure->toRenderable();
}
This allows you to write.
public function buildForm(array $form, FormStateInterface $form_state)
{
return $this->buildFormObjectHelper(
$form,
$form_state,
function(ElementInterface $renderStructure, FormStateInterface $form_state) {
$phone = $renderStructure->createChild('phone_number', Tel::class);
$phone->title = $this->t('Your phone number');
$actions = $renderStructure->createChild('actions', Actions::class);
$actionsSubmit = $actions->createChild('submit', Submit::class);
$actionsSubmit->value = $this->t('Save');
$actionsSubmit->button_type = 'primary';
}
);
}
Conclusion
If the rendering is a pain point in your Drupal application, I wouldn't go for the object oriented elements.
If multi level arrays are not your thing, go for it.
It looks like a promise for the future to replace the render array, but I don't know if that is possible.
Top comments (0)