I would do something like this:
$tableData = array (
0 =>
array (
'id' => 102,
'lastname' => 'Bing',
'title' => 'Doctor',
'timestamp' => '2019-01-1810:17:05',
'member_no' => null,
'firstname' => 'Ross', //intentionally moved to show ordering
'foobar' => 'blah' //added for example, this will be removed by array_intersect_key
),
);
$heading = array(
'id' => '<strong>ID</strong>',
'firstname' => '<strong>First Name</strong>',
'lastname' => '<strong>Last Name</strong>',
'title' => '<strong>Title</strong>',
'timestamp' => '<strong>Date Submitted</strong>',
'member_no' => '<strong>Member Number</strong>'
);
//create a default array
//this takes the keys from $heading, and makes an array with all the values as 'not available'
// ['id' => 'not available','lastname' => 'not available', ... ]
$default = array_fill_keys(array_keys($heading), 'not available');
$rows = [];
foreach($tableData as $key => $row) {
//remove all elements with strlen of 0 (in this case 'member_no')
$row = array_filter($row, function($item){return strlen($item);});
//removes 'foobar' or anything that has a key not in $heading
$row = array_intersect_key($row, $heading);
//combine $default and $data (empty items in $row are filled in from default)
//such as items removed by array_filter above
//the key order will match $default, which matches $headings
$row = array_merge($default, $row);
$rows[] = $row;
}
foreach($heading as $key=>$value) {
print_r(array_merge([['data'=>$value]], array_column($rows, $key)));
}
Output
Array
(
[0] => Array
(
[data] => <strong>ID</strong>
)
[1] => 102
//[2] => 108
//...
)
....
Sandbox
I kept these separate so it would be a bit easier to read, but there is no reason you cannot do it this way.
//...
$default = array_fill_keys(array_keys($heading), 'not available');
foreach($tableData as $key => $row) $rows[] = array_merge($default, array_intersect_key(array_filter($row, function($item){return strlen($item);}), $heading));
foreach($heading as $key=>$value) print_r(array_merge([['data'=>$value]],array_column($rows, $key)));
Sandbox
I had to guess a bit on what the end result was, so I ran your original code and it gave me this:
Array
(
[0] => Array
(
[data] => <strong>ID</strong>
)
[1] => 102
//[2] => 108
//...
)
....
In my code you can replace the print_r with this call $this->table->add_row([..array data..]);. Where array data is the stuff in the print_r call. You could make this a variable, but what's the point if its only used here. That eliminates a few of those loops (see below) and A few other advantages:
- key order of
$headings is preserved, elements appear where they do in the $headings array, regardless of where they are in $tableData. This allows easy re-ording of the data, too, for example: you could even map this to a dynamic array, I do this in CSV files, which allows users to change the order of the headers and columns. They can even rename the headers, because the way the key => value pairing works my_key => their_key...
- Data missing from
$tableData is defaulted to not available pulled in from $default, in theory you could map this manually to different things. For example: you could default timestamp to the current time by doing $default['timestamp'] = date('Y-m-d H:i:s'); right after creating it with array_fill_keys.
- Extra data in
$tableData not defined in $headings is removed. Which is good for forward compatibility.
- And it's A bit easier to make sense of (once you know how it works) because there are less "transient" variables and less fiddling with the keys ect...
Basically what I am doing is giving control over to the $headings array, in your original code. You do this somewhat by looping over the keys (fields), but this complicates things later like this $rows[$key][$key2 + 1]. It leaves you open to undefined array index issues, if the data changes at a later time, such as adding a new field to the DB.
The order of the output is dependent on the data in $tableData which is less intuitive (and less useful) then if it depends on $headings.
Here is an example of these issues with the original code:
//for example if your data changes to this and run your original code
$tableData = array (
0 =>
array (
'id' => 102,
'lastname' => 'Bing',
'title' => 'Doctor',
'timestamp' => '2019-01-1810:17:05',
'member_no' => null,
'firstname' => 'Ross', //intentionally moved to show ordering
'foo' => 'bar' //added this undefined in $headings
),
);
You'll get this notice and also find the last 2 elements are:
<br />
<b>Notice</b>: Undefined index: foo in <b>[...][...]</b> on line <b>30</b><br />
//... all other elements ...
//in orignal: displayed in the order of $tableData
//in my code: order is based on $headings, so this would be moved to the correct location
Array(
[0] => Array (
[data] => <strong>First Name</strong>
)
[1] => Ross
)
//in orignal: $headings['foo'] is not defined so we have no label here
//in my code: this element is removed and doesn't get rendered
Array(
[0] => Array(
[data] => <strong></strong>
)
[1] => bar
)
Sandbox (Orignal Code)
While these things may never cause an issue, it highlights my point about basing the output off of $headings and not $tableData. Things tend to change, and this way if you add/remove a field from this data, you wont have to worry about it breaking this page etc...
The combination of array_fill_keys, array_intersect_key and array_merge can be used to map the headers (As I shown above) of one array to another. You can use array_combine($headings, $row) to physically swap them and you would get something like this:
[
[
'<strong>ID</strong>' => 102,
'<strong>First Name</strong>' => 'Ross',
//...
],
[...]
]
It works great for CSV files (which is what I figured it out on) and anything else you need to remap the keys for.
Anyway, hope it helps you!
ugly. Sorry but they are. You could probably do somearray_intersect_keyandarray_mergemagic to get rid of a few of those.$tableDatalooks like and what$rowslooks like. An example here is when you usearray_mergewith string keys, the data in the second array replaces the data in the first, but the order of the keys in the first array is preserved. You could alsoarray_filterthe input data, removing any empty values, then pre-populate an array (withnot available) with the keys matched to the$headerarray, then when you merge them anything that was removed from the filter retains the default value... etc.