Возникла однажды необходимость вывести данные в табличном виде. Для Drupal это не проблема, ведь в Form API есть tableselect, либо можно использовать prefix и suffix, оборачивая таким образом каждый элемент в соответствующий табличный тег. Оба подхода имеют свои недостатки (здесь они описаны не будут), поэтому пришлось искать другой путь, и он был найден.

Сначала создаётся таблица:
$form['my_table'] = array(
  '#tree' => TRUE,
  '#theme' => 'table',
  '#header' => array(t('Город'), t('Население')),
  '#rows' => array(),
);

Где:
'#tree' => TRUE,
формировать «правильный» массив элементов. Подробнее
'#theme' => 'table',
использование стандартной табличной темы Drupal
'#header' => array(t('Город'), t('Население')),
заголовки столбцов
'#rows' => array(),
строки не указываем и в этом особенность данного способа. Если прописать массив строк сразу, то Drupal будет не верно обрабатывать таблицу

Далее в цикле описываются и создаются строки:
foreach($countries as $country){
 $country_name = array(
   '#type' => 'markup',
   '#markup' => $country->name,
 );
 $people = array(
   '#type' => 'textfield',
   '#default_value' => $country->people,
   '#size' => 10,
   '#required' => true,
 );

 /*
  Подключая поля таким образом Drupal правильно их обрабатывает, 
  но они будут проигнорированы при темизации.
 */
 $form['my_table'][] = array(
   'country' => &$country_name,
   'people'  => &$people,
 );

 /*
  Так как используются ссылки на переменные а не простое копирование их значений, 
  то Drupal будет использовать тот же самый массив полей что и выше.
 */
 $form['my_table']['#rows'][] = array(
   array('data' => &$country_name),
   array('data' => &$people),
 ); 

 unset($country_name);
 unset($people);
}

Таким образом мы создаём правильно определённую таблицу, с которой можно спокойно работать.

За основу взята эта статья — Drupal 7: theming forms with tables