Dynamically produce (and process) the form, using a data driven design, where the form fields are defined in a data structure (array.) See the following example -
<?php
// recursive trim call-back function
function _trim($val)
{
if(is_array($val))
{
return array_map('_trim',$val);
} else {
return trim($val);
}
}
// define the form fields. the main array index is the field name.
$fields = [];
$fields['field_A'] = ['label'=>'field name a', 'type'=>'text'];
$fields['field_B'] = ['label'=>'field name b', 'type'=>'select', 'options'=>[1=>'option 1',2=>'option 2']];
// note: if more than one select field uses the same option choices, define the options array separately and use it in as many places as needed
$fields['field_C'] = ['label'=>'field name c', 'type'=>'text'];
// you can add elements to the $fields entries to control dynamic validation and usage logic
$errors = []; // array to hold validation errors
$post = []; // array to hold a trimmed working copy of the current data (if editing existing data, fetch it into $post)
// post method form processing
if($_SERVER['REQUEST_METHOD'] == 'POST')
{
$post = array_map('_trim',$_POST); // get a trimmed copy of the submitted data - use $post in the remainder of the code
if(isset($post['submit']))
{
// examine the submitted data
echo '<pre>'; print_r($post); echo '</pre>';
// dynamically validate, then use the submitted form data here... store validation errors in the $errors array.
}
}
// at the point of producing the html form/table
$cols = $post['cols'] ?? 1; // default to 1 column
if(isset($post['add']))
{
$cols++;
}
if(isset($post['sub']) && $cols > 1)
{
// note: this will remove any values that have been entered in the last column
$cols--;
}
?>
<form method='post'>
<input type='hidden' name='cols' value='<?php echo $cols; ?>'>
<table border='1'>
<?php
foreach($fields as $name=>$arr)
{
echo "<tr><td>{$arr['label']}</td>";
foreach(range(1,$cols) as $col)
{
echo "<td>";
switch ($arr['type'])
{
case 'text':
// use for any of the input types except radio and checkbox, which must be handled differently
echo "<input type='{$arr['type']}' name='{$name}[$col]' value='".htmlentities($post[$name][$col] ?? '',ENT_QUOTES)."'>";
break;
case 'select':
echo "<select name='{$name}[$col]'>";
foreach($arr['options'] as $value=>$label)
{
$sel = isset($post[$name][$col]) && $post[$name][$col] == $value ? ' selected' : '';
echo "<option value='$value'$sel>$label</option>";
}
echo "</select>";
break;
}
echo "</td>";
}
echo "</tr>\n";
}
?>
</table>
<input type='submit' name='submit' value='Process'><input type='submit' name='add' value='Add Unit'><input type='submit' name='sub' value='Remove Unit'>
</form>
Note: use css for styling the table (the example uses the old border attribute for simplicity.)