Codeigniter Table Library Handle Empty / Null Values - php

I'm using Codeigniter 3 and the table library in order to display some data in the following format;
+---------------+---------------------+
| id | 102 |
+---------------+---------------------+
| First Name | Ross |
+---------------+---------------------+
| Last Name | Bing |
+---------------+---------------------+
| Title | Doctor |
+---------------+---------------------+
| Timestamp | 2019-01-18 10:17:05 |
+---------------+---------------------+
| Member Number | |
+---------------+---------------------+
A vardump of $tableData is;
Array
(
[0] => Array
(
[id] => 102
[firstname] => Ross
[lastname] => Bing
[title] => Doctor
[timestamp] => 2019-01-18 10:17:05
[member_no] =>
)
)
The PHP code I use to generate the HTML table is;
$tableData = $this->My_model->getData();
$heading = array(
'id' => 'ID',
'firstname' => 'First Name',
'lastname' => 'Last Name',
'title' => 'Title',
'timestamp' => 'Date Submitted',
'member_no' => 'Member Number'
);
$fields = array_keys($tableData[0]);
$rows = array();
foreach($fields as $key => $field) {
$rows[$key][0] = array(
'data' => '<strong>' . $heading[$field] . '</strong>'
);
foreach($tableData as $key2 => $item) {
$rows[$key][$key2 + 1] = $item[$field];
}
}
foreach($rows as $row) {
$this->table->add_row($row);
}
The above code works fine, however if a row is empty (see member_no above) i'd like to do one of two things (whichever is easiest);
hide the table row completely
display not available in the table cell
How can I achieve this?

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!

Related

Merge multiple multidimensional arrays by value

Problem
I have the following array, consisting of N different services, where each entry consists of an identifier and a unique (user)name.
$input = [
'service_1' => [
'1234' => 'John_Doe_1',
'4567' => 'Jane Doe X',
'7891' => 'J.Doe1',
],
'service_2' => [
'100001' => 'Jane Doe X',
'100002' => 'John_Doe_1',
'100003' => 'J.Doe1',
],
'service_N' => [
'07faed21-2920-4d7d-a263-88deba9c422c' => 'John_Doe_1',
'1160178c-dfbf-4091-b4c0-a8ec55c22800' => 'J.Doe1',
],
];
Now I'm looking for a way to format it in a way that I get the identifiers across each (user)name for the different services:
$output = [
'John_Doe_1' => [
'service_1' => '1234',
'service_2' => '100002',
'service_N' => '07faed21-2920-4d7d-a263-88deba9c422c',
],
'Jane Doe X' => [
'service_1' => '4567',
'service_2' => '100001',
'service_N' => null, // either value should be null or key should not exist
],
'J.Doe1' => [
'service_1' => '7891',
'service_2' => '100003',
'service_N' => '1160178c-dfbf-4091-b4c0-a8ec55c22800',
],
];
I'm looking for a flexible way (with N services) to do this but I can't come up with a good solution.
The code below should do the trick. This is how it works.
Loops over the keys of the outer array to get the keys for the output array.
Loops over the key value pair within every inner array.
Creates an empty array in the output array with the username as key if it does not exist.
Adds the 'ID' of the server under the name that we created earlier under the key it's currently looping through.
This should also still work within a reasonable time if your array input gets really big (e.g. 10000 elements).
$output = [];
// Loop over service_1, service_2, service_N etc.
foreach(array_keys($input) as $service_name)
{
// Loop over the inner key value pair (e.g. 10001 => John Doe X)
foreach($input[$service_name] as $service_id => $username)
{
// Create a key with the name if it does not exist in the output.
if(!isset($output[$username]))
{
$output[$username] = [];
}
// Add the key value pair to the correct output name.
$output[$username][$service_name] = $service_id;
}
}
That code will produce the following output.
Array
(
[John_Doe_1] => Array
(
[service_1] => 1234
[service_2] => 100002
[service_N] => 07faed21-2920-4d7d-a263-88deba9c422c
)
[Jane Doe X] => Array
(
[service_1] => 4567
[service_2] => 100001
)
[J.Doe1] => Array
(
[service_1] => 7891
[service_2] => 100003
[service_N] => 1160178c-dfbf-4091-b4c0-a8ec55c22800
)
)
I've been on a functional programming kick recently and figured I'd dive into PHP to see what I could come up with. Here's a nested array_walk method that seems to do the trick!
$output = Array();
array_walk($input, function($item, $key) use (&$output) {
array_walk($item, function($item, $key, $parent_key) use (&$output) {
$output[$parent_key][$item] = $key;
}, $key);
});
var_dump($output);

how to turn this function to be recursive with multiple dimensional array?

Hello I am think about how to build this array with a recursive function with a lot of layer.
So the data would like that.
id belongs_to
1a NULL
2a NULL
3a 1a
4a NULL
5a 2a
And non-recursive function like:
foreach ($first_layer as $first_layer_obj) {
$array[$first_layer_obj->id] = [];
$second_layer = /* SELECT id FROM client WHERE belongs_to $first_layer_obj->id */;
foreach ($second_layer as $second_layer_obj) {
$array[$first_layer_obj->id][$second_layer_obj->id] = [];
$third_layer = /* SELECT id FROM client WHERE belongs_to $second_layer_obj->id */;
foreach ($third_layer as $third_layer_obj->id) {
$array[$first_layer_obj->id][$second_layer_obj->id][$third_layer_obj->id] = [];
}
}
I am expecting the output is:
array(3) {
["1a"]=>
array(1){
["3a"]=>[]
}
["2a"]=>
array(1){
["5a"]=>[]
}
["4a"]=>[]
}
Certainly the first piece of advice that I have is, you should avoid performing recursive/iterated calls to your database. You should make a single call to extract all of the desired rows in a single result set and let php do the hard part.
I've decided to try a non-recursive approach. To permit this, the result set must be prepared so that "grander" children are listed first. Now, I realize that it is entirely possible that your sample data doesn't actually represent your project values and sorting cannot be used to prepare the result set adequately -- you'll have to let me know (and perhaps update your question with more accurate sample data).
[see inline comments for what's happening in my script]
*If you aren't using php7+, then my null coalescing operator ($row1['children'] ?? []) will cause issues. You can use: (isset($row1['children']) ? $row1['children'] : []
Code: (Demo)
// use ORDER BY belongs_to DESC, id ASC ... or usort() to prepare result set
$resultset = [
['id' => '6a', 'belongs_to' => '5a'],
['id' => '5a', 'belongs_to' => '3a'],
['id' => '8a', 'belongs_to' => '3a'],
['id' => '3a', 'belongs_to' => '1a'],
['id' => '1a', 'belongs_to' => null],
['id' => '2a', 'belongs_to' => null],
['id' => '4a', 'belongs_to' => null],
['id' => '7a', 'belongs_to' => null]
];
foreach ($resultset as $index1 => &$row1) { // make input array modifiable by reference (not working with a copy)
if ($row1['belongs_to']) { // original belongs_to value is not null (not a top-level parent)
foreach ($resultset as $index2 => $row2) { // search for targeted parent
if ($row2['id'] == $row1['belongs_to']) { // parent found
$resultset[$index2]['children'][] = [$row1['id'] => $row1['children'] ?? []]; // store original row as child
unset($resultset[$index1]); // remove original row (no reason to iterate it again in outer loop)
break; // halt inner loop (no reason to iterate further)
}
}
} else { // original belongs_to value is null (top-level parent)
$output[$row1['id']] = $row1['children'] ?? []; // store children to top
}
}
var_export($output);
Output:
array (
'1a' =>
array (
0 =>
array (
'3a' =>
array (
0 =>
array (
'5a' =>
array (
0 =>
array (
'6a' =>
array (
),
),
),
),
1 =>
array (
'8a' =>
array (
),
),
),
),
),
'2a' =>
array (
),
'4a' =>
array (
),
'7a' =>
array (
),
)

PHP: create subarray based on similar value types

Hello to the community: in the following array, I'd like to gather all the hobbies of the same user under that user's name into a subarray I'd call hobbies.
My current solution is to compare whether the email value is the same for both arrays, and if so, then push the hobby into its own array.
The problem is that the loops seems right but does not produce the results I expect it and I can't see where lies the problem. I thank you all for your time.
for($x = 0; $x <= count($majorArray); $x++) {
if($majorArray[$x]['email'] == $majorArray[$x+1]['email'])
array_push($hobbies, $majorArray[$x]['hobby']);
}
The array:
Array
(
[1] => Array
(
[fname] => Eli
[lname] => Solo
[hobby] => plants
[id] => 1
[email] => elis#elis.com
)
[2] => Array
(
[fname] => Eli
[lname] => Solo
[hobby] => hiking
[id] => 1
[email] => elis#elis.com
)
The problem with your code is that you compare the email of the current contents with the email of the next.
This check does not check if the user is already present, it just checks the next user in the array. This will only work if the next user is also the current, not if there is something in between.
Also, this code will eventually give you an undefined index error, as it will try to fetch the data under the next key in the last iteration.
Also it's not clear where $hobbies is from, according to you explanation, you want to create a hobbies array, and append all data there.
You can try the following (untested):
<?php
$majorArray = [
[
'fname' => 'Eli',
'lname' => 'Solo',
'hobby' => 'plants',
'id' => 1,
'email' => 'elis#elis.com',
],
[
'fname' => 'Eli',
'lname' => 'Solo',
'hobby' => 'hiking',
'id' => 1,
'email' => 'elis#elis.com',
],
];
$output = [];
foreach ($majorArray as $userData) {
// check if email already exists as key, you can also just use id for this
if (!array_key_exists($userData['email'], $output)) {
$newUserData = [
'fname' => $userData['fname'],
'lname' => $userData['lname'],
'id' => $userData['id'],
'email' => $userData['email'],
'hobbies' => [], // create the hobbies array
];
// add the newly created user data array to the output with email as key
$output[$userData['email']] = $newUserData;
}
// append the hobby to the hobbies array
$output[$userData['email']]['hobbies'][] = $userData['hobby'];
}
// array_values will reset the array keys if you need to
print_r(array_values($output));
Simply write a new array and use the ID as key
$betterArray = array();
foreach($majorArray as $data){
$betterArray[$data['id']]['fname'] = $data['fname'];
$betterArray[$data['id']]['lname'] = $data['lname'];
$betterArray[$data['id']]['email'] = $data['email'];
$betterArray[$data['id']]['hobbies'][] = $data['hobby'];
}
notice the [] in the last line in the loop to add a new element to the subarray each time it loops trough

set id for each element in recursive array

i want to set an id or index for each element and sub and the sub of the sub and so on
<?php
$data = array(
array(
'title' => 'a',
'children' => array(
array(
'title' => 'a-a',
'children' => array(
array(
'title' => 'a-a-a',
'children' => array(
)
)
)
),
array(
'title' => 'a-b',
'children' => array(
)
)
)
),
array(
'title' => 'b',
'children' => array(
)
)
);
?>
i'm looking for php code or function that work with recursion to add the index key witn number (desc) something like this output
<?php
$data = array(
array(
'index' => 1,
'children' => array(
array(
'index' => 2,
'children' => array(
array(
'index' => 3,
'children' => array(
)
)
)
),
array(
'index' => 4,
'children' => array(
)
)
)
),
array(
'index' => 5,
'children' => array(
)
)
);
?>
Once upon a time there a study that came to the conclusion that the biggest challenges a young padawan-programmer on his quest to become a Jedi-senior-programmers are:
[...] there are three major semantic hurdles which trip up novice imperative programmers. In order they are:
assignment and sequence
recursion / iteration
concurrency
I first encountered this theory in Jeff Atwood's blog entry: Separating Programming Sheep from Non-Programming Goats which is based on the scientific paper: The camel has two humps (working title)
This is the code that achieves what you want (minus the echo calls which are only to visualize the steps easier):
function addIndexRecursive(&$data) {
static $index = 0;
echo "The index is: $index" . PHP_EOL;
foreach ($data as &$item) {
$index++;
echo "The index was incremented: {$index}" . PHP_EOL;
$item = array('index' => $index) + $item;
echo "Add the index to the item with title: {$item['title']}" . PHP_EOL;
if (count($item['children']) > 0) {
echo "This item has children so call the index function" . PHP_EOL;
addIndexRecursive($item['children'], $index);
// this else branch is only for illustration purposes only
} else {
echo "There are no children therefore I stop" . PHP_EOL;
}
}
}
So lets dissect it a bit.
First thing to notice: I pass the array by reference. It's just a matter of taste, I prefer to modify the array directly rather then building another copy.
The static $index will help me keep track of the current index and will be incremented for every item is encountered. Lets see what kind of black magic this static does (according to Variable scope):
A static variable exists only in a local function scope, but it does not lose its value when program execution leaves this scope.
foreach ($data as &$item) again I want the elements to be passed by reference so I can modify them directly in the loop.
Now that we discussed the implementation details lets see what the back-trace of the function would be:
addIndexRecursive was call and the index is: 0
The index was incremented: 1
Add the index to the item with title: a
This item has children so call the function
addIndexRecursive was call and the index is: 1
The index was incremented: 2
Add the index to the item with title: a-a
This item has children so call the function
addIndexRecursive was call and the index is: 2
The index was incremented: 3
Add the index to the item with title: a-a-a
There are no children therefore I stop
The index was incremented: 4
Add the index to the item with title: a-b
There are no children therefore I stop
The index was incremented: 5
Add the index to the item with title: b
There are no children therefore I stop
You will keep running into these kind of problems so the sooner you grasp the recursion concept the better.
This is an example of 'nested arrays' where the individual arrays need:
1) an entry to be added: 'index' => <incrementing number> where there is a 'children' entry in the array.
2) an entry to be removed where: 'title' exists in the array.
As arrays can be 'nested' then 'recursion' is the most appropriate way to process the 'data' array.
I find it easier to modify the array 'in place' rather than build up the 'output' array. Therefore 'array references' (&$arrayEntry) are used quite a lot.
I use the supplied test data and test the 'required' array and 'indexed' array to be equal as regards 'structure'.
This is tested code: PHP 5.3.18 on Windows XP. Working code at: viper-7.com
The function that does the work:
function addChildrenIndex(&$destData, &$idxNew)
{
foreach($destData as $currentKey => &$currentValue) { // pass be reference so i can amend it
if (is_array($currentValue)) { // ensure is 'children' array
if (isset($currentValue['children'])) { // add the 'index' entry
$currentValue['index'] = $idxNew;
$idxNew++;
}
if (isset($currentValue['title'])) { // remove the 'title' entry!
unset($currentValue['title']);
}
// as it is an array we 'recurse' around it - always...
addChildrenIndex($currentValue, $idxNew);
}
}
}
Driving Code:
$idxNew = 1; // starting index.
// COPY the source data so i can test it later if required...
$indexedData = $data; // output array
addChildrenIndex($indexedData, $idxNew); // re-index the array 'in place'
// echo 'Required Array: <pre>';
// print_r($requiredData);
// echo '</pre>';
// echo 'OutputArray: <pre>';
// print_r($indexedData);
// echo '</pre>';
// test array structures to contain same entries...
var_dump($indexedData == $requiredData, 'ARE EQUAL?');
Required Test data: see question
Indexed Output:
Array
(
[0] => Array
(
[children] => Array
(
[0] => Array
(
[children] => Array
(
[0] => Array
(
[children] => Array
(
)
[index] => 3
)
)
[index] => 2
)
[1] => Array
(
[children] => Array
(
)
[index] => 4
)
)
[index] => 1
)
[1] => Array
(
[children] => Array
(
)
[index] => 5
)
)

Retrieve first key in multi-dimensional array using PHP

I would like to retrieve the first key from this multi-dimensional array.
Array
(
[User] => Array
(
[id] => 2
[firstname] => first
[lastname] => last
[phone] => 123-1456
[email] =>
[website] =>
[group_id] => 1
[company_id] => 1
)
)
This array is stored in $this->data.
Right now I am using key($this->data) which retrieves 'User' as it should but this doesn't feel like the correct way to reach the result.
Are there any other ways to retrieve this result?
Thanks
There are other ways of doing it but nothing as quick and as short as using key(). Every other usage is for getting all keys. For example, all of these will return the first key in an array:
$keys=array_keys($this->data);
echo $keys[0]; //prints first key
foreach ($this->data as $key => $value)
{
echo $key;
break;
}
As you can see both are sloppy.
If you want a oneliner, but you want to protect yourself from accidentally getting the wrong key if the iterator is not on the first element, try this:
reset($this->data);
reset():
reset() rewinds array 's internal
pointer to the first element and
returns the value of the first array
element.
But what you're doing looks fine to me. There is a function that does exactly what you want in one line; what else could you want?
Use this (PHP 5.5+):
echo reset(array_column($this->data, 'id'));
I had a similar problem to solve and was pleased to find this post. However, the solutions provided only works for 2 levels and do not work for a multi-dimensional array with any number of levels. I needed a solution that could work for an array with any dimension and could find the first keys of each level.
After a bit of work I found a solution that may be useful to someone else and therefore I included my solution as part of this post.
Here is a sample start array:
$myArray = array(
'referrer' => array(
'week' => array(
'201901' => array(
'Internal' => array(
'page' => array(
'number' => 201,
'visits' => 5
)
),
'External' => array(
'page' => array(
'number' => 121,
'visits' => 1
)
),
),
'201902' => array(
'Social' => array(
'page' => array(
'number' => 921,
'visits' => 100
)
),
'External' => array(
'page' => array(
'number' => 88,
'visits' => 4
)
),
)
)
)
);
As this function needs to display all the fist keys whatever the dimension of the array, this suggested a recursive function and my function looks like this:
function getFirstKeys($arr){
$keys = '';
reset($arr);
$key = key($arr);
$arr1 = $arr[$key];
if (is_array($arr1)){
$keys .= $key . '|'. getFirstKeys($arr1);
} else {
$keys = $key;
}
return $keys;
}
When the function is called using the code:
$xx = getFirstKeys($myArray);
echo '<h4>Get First Keys</h4>';
echo '<li>The keys are: '.$xx.'</li>';
the output is:
Get First Keys
The keys are: referrer|week|201901|Internal|page|number
I hope this saves someone a bit of time should they encounter a similar problem.

Categories