DEV Community

david duymelinck
david duymelinck

Posted on

Drupal: object oriented render array

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;
  }
Enter fullscreen mode Exit fullscreen mode

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');
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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';
    }
  );
}
Enter fullscreen mode Exit fullscreen mode

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)