I have a php method that creates an HTML table with data it retrieves from a property.
My biggest concern is the performance of my application because I deal with a large amount of data.
public function getHTML() {
$phpObj = json_decode($this->data); // array(object, object, object, ....);
$table = "<table><tbody>\n";
if (count($phpObj->query->results->row) > 0) {
$row = $phpObj->query->results->row;
foreach ($row as $value) {
$table .= "<tr>\n";
foreach ($value as $key => $val) { // concerned about loop inside loop
$table .= "<td>" . $value->$key . "</td>";
}
$table .= "\n</tr>\n";
}
$table .= "</tbody></table>";
return $table;
}
else {
return 'HTML table not created.';
}
}
Is there a more efficient way of traversing through the array and objects without creating a loop inside a loop?
Don't concatenate and return the value, echo it immediately instead. Less clean but the performance will be much more interesting since the strings are immediately outputed to the output buffer which is managed more efficiently.
A loop inside a loop is often the best way to traverse a two-dimensional array.
String concatenation is cost-intensive. You could reduce the number of repetitive string concatenations by using arrays:
public function getHTML() {
$phpObj = json_decode($this->data);
if (count($phpObj->query->results->row) > 0) {
$rows = array();
foreach ($phpObj->query->results->row as $row) {
$cells = array();
$rows[] = "<td>" . implode("</td><td>", $row) . "</td>";
}
return "<table><tbody>\n<tr>\n" .
implode("\n<tr>\n<tr>\n", $rows) .
"\n</tr>\n</tbody></table>";
} else {
return 'HTML table not created.';
}
}
You could also use anonymous functions (available since PHP 5.3):
public function getHTML() {
$phpObj = json_decode($this->data);
if (count($phpObj->query->results->row) > 0) {
return "<table><tbody>\n<tr>\n" .
implode("\n<tr>\n<tr>\n", array_map(function($cells) { return "<td>".implode("</td><td>", $cells)."</td>"; }, $phpObj->query->results->row)) .
"\n</tr>\n</tbody></table>";
} else {
return 'HTML table not created.';
}
}
UPDATE
Col. Shrapnel correctly stated that, oddly, string concatenation is actually relatively fast in php.
As Vincent said, don't run a bunch of concatenations, that's killing you. You have two options to speed up your script:
Echo immediately.
Store your lines in a an array, and join the array at the end.
Example of two:
<?php
$mylines = array();
foreach ($row as $value) {
$mylines[] = "<tr>\n";
foreach ($value as $key => $val) { // concerned about loop inside loop
$mylines[] = "<td>" . $value->$key . "</td>";
}
$mylines[] = "\n</tr>\n";
}
return implode('', $mylines);
You could move the HTML building into the frontend and ship the JSON data to the user via AJAX and javascript.
This could also allow your to only ship pieces of the data at a time (depending on your html layout), so they could be dynamically polled when needed (like google/bing image search).
I know I didn't answer the question directly. That is because the code you have is probably the fastest it can be done (in PHP) w/o doing silly little optimizations that would only make the code harder to read/maintain (probably only save a few % anyway).
EDIT: after looking at it again, I bet your code is actually just polling the data from an external source in JSON. You could probably remove this code completely by having the frontend javascript do the HTTP hit and deal with the data there. This removes the need for this PHP code to run at all.
EDIT 2: after reading your comment about this being a fall back for javascript being disabled, I looked at the code your currently doing. It appears to be translatable into implode.
//declared this functions somewhere
function tr_build( $row_value )
{
$tablerow .= "<tr>\n";
if( $row_value ) {
$tablerow .= "<td>".implode( "</td><td>", $row_value )."</td>";
}
$tablerow .= "\n</tr>\n";
return $tablerow;
}
//this replaces the double loop
if( $row ) {
$tablerows = "<tr>\n".implode( "\n</tr>\n<tr>\n", array_map( "tr_build", $row ) )."\n</tr>\n"
} else {
$tablerows = "";
}
Why are you bothering with this?
$table .= "<tr>\n";
foreach ($value as $key => $val) { // concerned about loop inside loop
$table .= "<td>" . $value->$key . "</td>";
}
$table .= "\n</tr>\n";
You never actually use $val so why have this loop at all?
$table .= "<table><td>";
$table .= implode("</td><td>", array_keys($value));
$table .= "</td></table>";
Related
I have a function inside a PHP class that reads as follows:
//properties
var $title;
var $thePage;
function gBuildTable($theArray){
//given a 2D array, builds an HTML table based on that array
$table = "<table> \n";
foreach ($theArray as $row){
$table .= "<tr> \n";
foreach ($row as $cell){
$table .= " <td>$cell</td> \n";
} // end foreach
$table .= "</tr> \n";
} // end foreach
$table .= "</table> \n";
return $table;
} // end gBuildTable
function buildTable($theArray){
$temp = $this->gBuildTable($theArray);
$this->addText($temp);
} // end buildTable
function addText($content){
//given any text (including HTML markup)
//adds the text to the page
$this->thePage .= $content;
$this->thePage .= "\n";
} // end addText
function gAddText($content){
//given any text (including HTML markup)
//returns the text
$temp= $content;
$temp .= "\n";
return $temp;
} // end addText
So then I have a bunch of data which I build into a 2D array as such (hopefully its correct structure):
$data = array(
array("Name", $name),
array("Email", $email),
array("Hobby", $hobby),
array("", $stamp)
);
$obj = &New sH; //the class name
$obj->buildTable($data);
Am I making some boneheaded oversight or something that I am not seeing? This doesn't produce anything, it LITERALLY comes back empty. No errors, No warning, No notices, NADA.
FYI: The variables such as $name have all been previously initialized and set, they are NOT NULL. Any assistance as to what I am doing wrong, would be greatly appreciated. TIA
PS. I used current() inside the foreach loop to echo out what it is seeing at that time, and it sees the data, so I am at a loss why its not actually doing anything.
Update [SOVLED]
I can't believe I missed it and no one else caught it, but its OK, part of the wonderful world that is coding. I resolved it by noticing that its missing ONE line to grab the internal object that is being built:
echo $obj->$thePage;
You need to echo $table; in your function..!
or store the value the function is returning and echo in your main code.
$data = array(
array("Name", $name),
array("Email", $email),
array("Hobby", $hobby),
array("", $stamp)
);
$obj = &New sH; //the class name
$table = $obj->buildTable($data);
echo $table;
Is it possible to loop a function variable from this? I am trying to loop the $item[$a-1]
echo load_func($hid, $item[$a-1]);
And make it something like this but I know this is wrong (just an idea):
echo load_func($hid, for($a=1;$a<=$addctr;$a++){$item[$a-1]});
This is the actual but fail because it loops the whole function.
echo "<select id='drpopitem-' name='drpopitem[]' size='10' multiple>";
for($a=1;$a<=$addctr;$a++){
echo load_func($id, $item[$a-1]);
}
echo "</select>";
The purpose of the function is to automatically select an option based from the record saved on a table.
Try to pass the whole item to load_function();
echo load_func($hid, $item);
And deal with every item in the function itself.
function load_func($hid, $item) {
$return = "<select id='drpopitem-' name='drpopitem[]' size='10' multiple>";
foreach ($item as $option) $return .= $option;
$return .= "</select>";
return $return;
}
From what I am understanding from your question, you should pass the array $addctr to the function. And inside the function you should put your for loop and do the calculations.
something like:
function load_func($id, $items) {
$strOptions = "";
for($a=1;$a<=$items;$a++){
if($items[$a-1] != $id)
$strOptions .= "<option>Your value</option>";
else
$strOptions .= "<option selected>Your value</option>";
}
return $strOptions;
}
So in CodeIgniter, there was a cool feature of creating an HTML table just by passing it an array and it handles the header, etc. Is there ANYTHING out there for Laravel, has anybody been able to use the CI version in Laravel? just asking around
There is nothing like this in Laravel AFAIK but you may create your own, something like (an idea only) this (a php class, not only for Laravel)
class Table {
protected $table = null;
protected $header = null;
protected $attr = null;
protected $data = null;
public function __construct($data = null, $attr = null, $header = null)
{
if(is_null($data)) return;
$this->data = $data;
$this->attr = $attr;
if(is_array($header)) {
$this->header = $header;
}
else {
if(count($this->data) && $this->is_assoc($this->data[0]) || is_object($this->data[0])) {
$headerKeys = is_object($this->data[0]) ? array_keys((array)$this->data[0]) : array_keys($this->data[0]);
$this->header = array();
foreach ($headerKeys as $value) {
$this->header[] = $value;
}
}
}
return $this;
}
public function build()
{
$atts = '';
if(!is_null($this->attr)) {
foreach ($this->attr as $key => $value) {
$atts .= $key . ' = "' . $value . '" ';
}
}
$table = '<table ' . $atts . ' >';
if(!is_null($this->header)) {
$table .= '<thead><tr>';
foreach ($this->header as $value) {
$table .= '<th>' . ucfirst($value) . '</th>';
}
$table .= '</thead></tr>';
}
$table .= '<tbody>';
foreach ($this->data as $value) {
$table .= $this->createRow($value);
}
$table .= '</tbody>';
$table .= '</table>';
return $this->table = $table;
}
protected function createRow($array = null)
{
if(is_null($array)) return false;
$row = '<tr>';
foreach ($array as $value) {
$row .= '<td>' . $value . '</td>';
}
$row .= '</tr>';
return $row;
}
protected function is_assoc($array){
return is_array($array) && array_diff_key($array, array_keys(array_keys($array)));
}
}
Now, you can use it as given below (An Example Here.)
$data = array(
array('name' => 'Heera', 'age'=>'35', 'address' =>'Masimpur', 'phone'=>'123456'),
array('name' => 'Usman', 'age'=>'28', 'address' =>'Kamal Gor', 'phone'=>'654321')
);
$attr = array('class'=>'tbl someClass', 'id'=>'myTbl', 'style'=>'width:400px;color:red', 'border'=>'1');
$t = new Table($data, $attr);
echo $t->build();
Or, set an header using third argument, like
$t = new Table($data, $attr, array('Known As', 'Years', 'Location', 'Contact'));
This is just an idea and could be better. Now just integrate this class with Laravel using Laravel's rule. You may extend Html class or use as an individual class by registering it as a service. Take a look at this answer for extending a class in laravel.
Try nayjest/grids Laravel package.
You could take the script drupal uses and convert it to laravel: https://api.drupal.org/api/drupal/core%21includes%21theme.inc/function/theme_table/8
Doing a quick look at it, you would just need to replace a couple of functions:
Attributes()... I think laravel has something like this you could use. Drupal 8 is using something from symfony2 I think... laravel might be using the same thing.
_theme_table_cell(), tablesort_header(), etc... These type of functions have drupal_render() in them... you don't want to try and port that over... so you probably just delete that from the functions (untested) as it doesn't make sense in the context of laravel .
This would make for a very nice implementation of what your looking for.
EDIT: I also just ran into this: http://kohana.keyframesandcode.com/docs/modules/table/ I've not tested it but it was referenced here: http://forumsarchive.laravel.io/viewtopic.php?id=2178
I have this code:
foreach ($row as $result) {
if (empty($row[28])) {
$string28 = '';
} else {
$string28 = '<div class="add_img">
<h1>Connexion</h1>
<img src="images/' . $row[28] . '.jpeg">
</div>';
}
}
foreach ($row as $result) {
if (empty($row[30])) {
$string30 = '';
} else {
$string30 = '<div class="add_img">
<h1>Fixation</h1>
<img src="images/' . $row[30] . '.jpeg">
</div>';
}
}
foreach ($row as $result) {
if (empty($row[31])) {
$string31 = '';
} else {
$string31 = '<div class="add_img">
<h1>Schéma</h1>
<img src="images/' . $row[31] . '.jpeg">
</div>';
}
}
$applications = array($string28, $string30, $string31);
if (empty($applications)) {
$vide = "<h1>Pas D'Application Pour Ce Produit</h1>";
}
What I want to say here is: if all the variables are empty then show me this:
Pas D'Application Pour Ce Produit (Translated: No application for this product)
But When I use the print_r function it tells to me that the array has no data to deal with.
Please I need Help.
And Thanks to all in advanced
You are not accessing your rows correctly in your foreach loops. When using foreach($row as $result) you need to refer to the array element as $result, not row. If you need to identify a specify array key you can specify that by using foreach($row as $key => $result).
For example, in your first loop you should use this:
$string28 = ''; //Initialize your variable so can be used after the foreach loop exits
foreach ($row as $key => $result) {
if($key == 28 && empty($result[$key]) {
$string28 = '';
} else {
$string28 = '<div class="add_img"><h1>Connexion</h1><img src="images/'.$result[$key].'.jpeg">
}
}
Repeat for the other loops in your script.
EDIT
Yes, you can setup one foreach loop that will go through all your variables and create variables for you. Based on your question, if you don't have any apps an error message shows. What you may wish to do is simply set a flag based on that criteria. You could do do this like so:
$noApps = true;
$applications = array();
foreach($row as $key => $result) {
if(isset($result[$key]) && empty($result[$key])) {
$applications[$key] = '<div class="add_img"><h1>Connexion</h1><img src="images/'.$result[$key].'.jpeg'>;
$noApps = false;
}
}
if($noApps) {
echo "<h1>Pas D'Application Pour Ce Produit</h1>";
} else {
print_r($applications); //For viewing/debugging purposes
}
Your foreach loops is wrong. You are using the whole array instead of the elements as is used within a foreach loop.
You have used
foreach ($row as $result) {
//You are doing something with $row
}
Instead correct usage is
foreach ($row as $result) {
//Do something with $result
}
Hope it helps :)
Your design looks bad. You are essentially repeating the same functionality 3 times.
Your while loops don't work because you're using $row in them, instead of the $result variable.
The second point is, The $string28, $string30, $string31 are created as local variables within the if conditions you have written in the while loops. Once the control exits the "if" loops these variables are nothing. To solve this problem, try initializing these to empty data like$string28 ="" at the beginning point of your code.
However, I would recommend you to look at the structure of your code first.
Update for CodingBiz:
I'm putting this in my code:
for($i=1;$i<=$numRows;$i++) {
$output .= '<tr>';
$row = $this->fetchAssoc($result);
$colRow = $this->fetchAssoc($colResult);
foreach($colRow as $colName) {
$output .= "<td>".$row[$colName]."</td>";
}
$output .= '</tr>';
}
in place of
for($i=1;$i<=$numRows;$i++) {
$output .= '<tr>';
$row = $this->fetchAssoc($result);
for($j=1;$j<=$colNumRows;$j++) {
$colRow = $this->fetchAssoc($colResult);
$output .= "<td>".$row[$colRow["COLUMN_NAME"]]."</td>";
}
$output .= '</tr>';
}
Is there anything wrong with this?
Original Post:
I'm writing a function in a PHP class to display the results of a query in a table. I'm not structuring any of the table myself, I want it everything to be done using PHP. Here is my code so far:
function allResults($table,$cols) {
if(isset($cols)) {
$query = "SELECT $cols FROM $table";
}
else {
$query = "SELECT * FROM $table";
}
$result = $this->query($query);
$numRows = $this->numRows($result);
$colQuery ="SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA='shareride' AND TABLE_NAME='$table'";
$colResult = $this->query($colQuery);
$colNumRows = $this->numRows($colResult);
$output = '<table class="allResults">';
$output .= '<tr>';
for($i=1;$i<=$colNumRows;$i++) {
$colRow = $this->fetchAssoc($colResult);
$output .= "<td>".$colRow["COLUMN_NAME"]."</td>";
}
$output .= '</tr>';
for($i=1;$i<=$numRows;$i++) {
$output .= '<tr>';
$row = $this->fetchAssoc($result);
for($j=1;$j<=$colNumRows;$j++) {
$colRow = $this->fetchAssoc($colResult);
$output .= "<td>".$row[$colRow["COLUMN_NAME"]]."</td>";
}
$output .= '</tr>';
}
$output .= '</table>';
return $output;
}
In case it is unclear, query refers to mysqli_query, numRows refers to mysqli_num_rows, and fetchAssoc refers to mysqli_fetch_assoc. The database name is "shareride."
I know I am missing something in this line:
$output .= "<td>".$row[$colRow["COLUMN_NAME"]]."</td>";
but I just don't know what it is. Right now, I get all the table column titles displayed correctly, and I get the correct number of content rows, but I just can't populate those rows with the actual data from the database.
What am I missing? Any help would be GREATLY appreciated!
Get the data and column names from the same result set
<?php
$i = 0;
$colNames = array();
$data = array();
while($row = ***_fetch_assoc($res)) //where $res is from the main query result not schema information
{
//get the column names into an array $colNames
if($i == 0) //make sure this is done once
{
foreach($row as $colname => $val)
$colNames[] = $colname;
}
//get the data into an array
$data[] = $row;
$i++;
}
?>
UPDATE: Suggested by #YourCommonSense to replace the above code and it worked, simple and shorter - A WAY TO GET THE COLUMN NAMES/ARRAY KEYS WITHOUT LOOPING THROUGH LIKE I DID
$data = array();
while($row = mysql_fetch_assoc($res))
{
$data[] = $row;
}
$colNames = array_keys(reset($data))
Continued as before: Print the table
<table border="1">
<tr>
<?php
//print the header
foreach($colNames as $colName)
{
echo "<th>$colName</th>";
}
?>
</tr>
<?php
//print the rows
foreach($data as $row)
{
echo "<tr>";
foreach($colNames as $colName)
{
echo "<td>".$row[$colName]."</td>";
}
echo "</tr>";
}
?>
</table>
Test Result
You can see how I separated the data retrieval from table generation. They are dependent of each other now and you can test your table generation without the database by populating the arrays with static data
You can also make them into separate functions.
Never mix your database handling code with HTML output code. These are 2 totally different matters. Make your allResults function only return array with data, and then you can make another function to print in fancy way (and not in the database handler class).
You don't need information schema to get column name - you already have it in the returned array keys.
You don't need numRows either - use while() and foreach()
NEVER insert anything into query directly like you do with $cols - eventually it will lead to errors and injections.
Such a function, without accepting some parameters for the query, makes absolutely no sense especially in the context of migrating from mysql to mysqli - you are going yo use it as an old school mysql query inserting variables, not placeholders. So, it makes migration totally useless.
To know "Is there anything wrong with code", one have to run it, not watch. Run and debug your code, outputting key variables and making sure you can see errors occurred.
i'd try replacing the data part with something like:
while($row = $this->fetchAssoc($colResult))
{
echo "<tr>";
foreach($row as $value)
{
echo sprintf("<td>%s</td>",$value)
}
echo "</tr>";
}
i know it's not a proper answer, but it's really hard to read that code imho