Thursday 8 November 2012

A PHP Class/Object to make html tables

A PHP Class/Object to make html tables

I have been working on a project recently that had cause for a lot of tables to be generated from PHP looking up a database.  After the first couple I got somewhat bored of the amount of time and typing that each table was taking to manualy code on each page.  So I knocked up this little class/object to generate the tables for me, saving me a load of typing in the long run.  I have commented it at the top for usage and stuff, but it's not standards complient, it's just something I put together quick to save me some time, and thought I would share with you all in case you find it helpfull too.

If you need anything explained, or have a suggestion for additions to it, just leave a comment.

p.s. If you lift the code, I'd appreciate if you left a comment for that too ;)

class makeHTMLTable{
/*~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#
Class for dynamicly rendering HTML Tables from nothing more than a single
dataset.

USAGE :-
call something like $table = new makeHTMLTable($myDataset) in your code
where $myDataset is a valid dataset array.
This can be the result of a database query / xml array / dom array / any array
as long as it is a multidimensional, associative array in the form of

array([0]=>array([heading1]=>'value1-1', [heading2]=>'value1-2',....),
array([1]=>array([heading1]=>'value2-1', [heading2]=>'value2-2',....),
...)

you can then call either
$tableOut = $table->newBasicTable($id[string], $class[string], $style[string])
or
$tableOut = $table->newSplitSumTable($split[string], $sum[array], $class[string], $style[string])

newBasicTable :- when calling this method you have the option to pass in three
peices of string information to be applied to the table that is created.
$id will be set as the DOM_id of the table
$class will apply the DOM_class entered to the table
$style applys inline CSS to the table.

all three of these are completly optional and may be left out.
e.g. with no data passed in :-
$tblOut = $tbl->newBasicTable();
this will simply produce
"<table>"

e.g. with extra data :-
$tblOut = $tbl->newBasicTable("myTbl1','content ui-table-widget','color:blue;');
this will produce
"<table id='myTb1' class='content ui-table-widget' style='color:blue;'"


newSplitSumTable :- when calling this class you must pass in the first two
paramaters ($split[string], $sum[array]). $class and $style are exactly the same
as in the newBasicTable, but as this method outputs multiple tables $id has been
dropped.
$split should be the string value that is exactly equal to the heading of the
column that you want to split the data on. $sum should be a simple number key
array of string values that exactly match the headings of the columns that you
want summed.  This sums the values, it does not count the records. As well as
summing each split, there is also an overall total produced at the end.  This
type of table is handy for analysis, such as checking sales by area or duration
 of support tickets by assigned group.

e.g.
$tblOut = $tbl->newSplitSumTable('Sales Area', array('Monthly Turnover','Units Sold', '% To Proffit'));

This will produce a series of tables, one for each sales area, and have a group
totals table after each with the sum per area of the turnover, units and profit
as well as an overall total for everything at the very end.

Additionaly, when the class is created it strips out the heading information,
storing it in $headers.  This can be used for making lists for splitSum tables
so you don't need to depend on user input for the column headers. The data is
also held indipendant of the column headers, so can be manipulated indipendantly.
The class also maintains a "master" copy of the dataset passed to it, so it can
be re-used without needing aditional querys on the datasource.
As well as this the row count and column count are stored, this can be used to
help with pagination, display and further calculations.

$tbl = new makeHTMLTable($dataset);
$headerArray = $tbl->headers;
$dataArray = $tbl->data;
$orginalData = $tbl->dataset;
$rowCount = $tbl->rows;
$columnCount = $tbl->cols;

--added newSplitCountTable()
This works exactly like the newSplitSumTable() but counts the records returned
rather than adding the values.  

~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#*/
    public $headers;
    public $data;
    public $cols;
    public $rows;
    public $dataset;
   
    public function __construct($dataset){
        $this->dataset = $dataset;
        $this->headers = $this->getHeadings();
        $this->cols = count($this->headers);
        $this->rows = count($this->dataset);
        $this->data = $this->getValues();
    }
   
    public function getHeadings(){
        $first = true;
        $results = $this->dataset;
        foreach($results as $record){
            if($first === true){
                foreach($record as $heading=>$value){
                    $headings[] = $heading;   
                }
            $first = false;
            }
        }
        return $headings;
    }
   
    public function getValues($visible=null, $start=0){
        if($visible===null){
            $visible = $this->rows;
        }
        if(($start+$visible) > $this->rows){
            $visible = $this->recCount - $start;
        }
        $dispCount = 0;
        $headArray = $this->headers;
        for ($disp=$start;$disp<($start+$visible);$disp++) {
            foreach($headArray as $valueHeading){
                $values[$dispCount][] = $this->dataset[$disp][$valueHeading];
            }
            $dispCount++;     
        }
        return $values;
    }
   
    public function newBasicTable($id="", $class="", $style=""){
        $meta = "";
        if($id != "") {$meta .= " id=\"{$id}\"";}
        if($class != "") {$meta .= " class=\"{$class}\"";}
        if($style != "") {$meta .= " style=\"${style}\"";}
        $table = "<table{$meta}><tr>";
        foreach($this->headers as $hk=>$hv){
            $table .= "<th>$hv</th>";
        }
        $table .= "</tr>";
        $end = $this->rows-1;
        for($counter=0;$counter<=$end;$counter++){
            $table .= "<tr>";
            $subData = $this->data[$counter];
            foreach($subData as $dk => $dv){
                $table .= "<td>$dv</td>";
            }
            $table .= "</tr>";
        }
        $table .= "</table>";
        return $table;
    }
   
    public function newSplitSumTable($splitField, $sumOnColumns, $class="", $style=""){
        $return = "";
        $subSum = array();
        $totSum = array();
        $meta = "";
        $splitIDX = array_search($splitField, $this->headers);
        if($class != "") {$meta .= " class=\"{$class}\"";}
        if($style != "") {$meta .= " style=\"${style}\"";}
        $sumColCount = count($sumOnColumns)-1;
        foreach($sumOnColumns as $colName){
            $colNameIDX[$colName] = array_search($colName, $this->headers);
            $subSum[$colName] = 0;
            $totSum[$colName] = 0;
            for($i=0;$i<=$this->cols-1;$i++){
                $headings[$i] = $this->headers[$i];
            }
        }
        $colValIDX = array_flip($colNameIDX);
        foreach($colValIDX as $sumByVal => $colName){
            $subSumV[$sumByVal] = 0.00    ;
        }       
        foreach($this->data as $rows){
            $temp[] = $rows[$splitIDX];
        }
        $temp = array_unique($temp);
        foreach($temp as $uselessKey=>$splitVal){
            $splitList[] = $splitVal;
        }   
        $splitCount = count($temp)-1;
        for($i=0;$i<=$splitCount;$i++){
            foreach($this->data as $noGroup){
                if($noGroup[$splitIDX] == $splitList[$i]){
                    $group[$i][]= $noGroup;
                }
            }
        }
        for($i=0;$i<=$splitCount;$i++){
            $return .= "<h2>{$splitList[$i]}</h2>";
            $return .= "<table {$meta}>\n";
            $return .= "<tr>";
            foreach($this->headers as $heading){
                $return .= "<th>{$heading}</th>";
            }
            $return .= "</tr>\n <tr>";
            foreach($group[$i] as $split){
                foreach($split as $key => $value){
                    $return .= "<td>{$value}</td>";
                    $colNo = array_search($key, $colNameIDX);
                    if($colNo){
                        $strToRemove = array("&pound;", ",");
                        $add = (float)$subSum[$colNo] + str_replace($strToRemove, "", $value);
                        $addT = (float)$totSum[$colNo] + str_replace($strToRemove, "", $value);
                        $subSum[$colNo] = $add;
                        $totSum[$colNo] = $addT;
                    }
                }
                $return .="</tr> \n";
            }
            $return .= "</tr>\n </table> \n ";
            $return .= "<h3>Group Totals</h3> ";
            $return .= " <table".$meta."><tr> ";
            foreach ($colValIDX as $h){
                $return .= "<th>{$h}</th>";
            }
            $return .= "</tr>\n <tr>";
            foreach ($subSum as $subKey => $subVal){
                    $subVal = number_format($subVal,2,".",",");
                    $return .= "<td>{$subVal}</td>";
            }
            $return .= "</tr>\n </table> \n <br /> \n <br /> \n";
            foreach($sumOnColumns as $colName){
                $subSum[$colName] = 0;
            }
        }
        $return .= "</tr>\n </table>\n";
        $return .= "<h3>Overall Totals</h3>";
        $return .= "<table{$meta}><tr>";
        foreach ($colValIDX as $oh){
            $return .= "<th>{$oh}</th>";
        }
        $return .= "</tr>\n <tr>";
        foreach ($totSum as $totKey => $totVal){
          $totVal = number_format($totVal,2,".",",");
           $return .= "<td>{$totVal}</td>";
        }
        $return .= "</tr> \n </table> \n";
    return $return;
    }
 
      public function newSplitCountTable($splitField, $countOnColumns, $class="", $style=""){
        $return = "";
        $subSum = array();
        $totSum = array();
        $meta = "";
        $splitIDX = array_search($splitField, $this->headers);
        if($class != "") {$meta .= " class=\"{$class}\"";}
        if($style != "") {$meta .= " style=\"${style}\"";}
        $sumColCount = count($countOnColumns)-1;
        foreach($countOnColumns as $colName){
            $colNameIDX[$colName] = array_search($colName, $this->headers);
            $subSum[$colName] = 0;
            $totSum[$colName] = 0;
            for($i=0;$i<=$this->cols-1;$i++){
                $headings[$i] = $this->headers[$i];
            }
        }
        $colValIDX = array_flip($colNameIDX);
        foreach($colValIDX as $sumByVal => $colName){
            $subSumV[$sumByVal] = 0    ;
        }       
        foreach($this->data as $rows){
            $temp[] = $rows[$splitIDX];
        }
        $temp = array_unique($temp);
        foreach($temp as $uselessKey=>$splitVal){
            $splitList[] = $splitVal;
        }   
        $splitCount = count($temp)-1;
        for($i=0;$i<=$splitCount;$i++){
            foreach($this->data as $noGroup){
                if($noGroup[$splitIDX] == $splitList[$i]){
                    $group[$i][]= $noGroup;
                }
            }
        }
        for($i=0;$i<=$splitCount;$i++){
            $return .= "<h2>{$splitList[$i]}</h2>";
            $return .= "<table{$meta}>\n";
            $return .= "<tr>";
            foreach($this->headers as $heading){
                $return .= "<th>{$heading}</th>";
            }
            $return .= "</tr>\n <tr>";
            foreach($group[$i] as $split){
                foreach($split as $key => $value){
                    $return .= "<td>{$value}</td>";
                    $colNo = array_search($key, $colNameIDX);
                    if($colNo){
                        $strToRemove = array("&pound;", ",");
                        $add = $subSum[$colNo]+1;
                        $addT = $totSum[$colNo]+1;
                        $subSum[$colNo] = $add;
                        $totSum[$colNo] = $addT;
                    }
                }
                $return .="</tr> \n";
            }
            $return .= "</tr>\n </table> \n ";
            $return .= "<h3>Group Totals</h3> ";
            $return .= " <table".$meta."><tr> ";
            foreach ($colValIDX as $h){
                $return .= "<th>Count Of {$h}</th>";
            }
            $return .= "</tr>\n <tr>";
            foreach ($subSum as $subKey => $subVal){
                    $subVal = number_format($subVal,0,".",",");
                    $return .= "<td>{$subVal}</td>";
            }
            $return .= "</tr>\n </table> \n <br /> \n <br /> \n";
            foreach($countOnColumns as $colName){
                $subSum[$colName] = 0;
            }
        }
        $return .= "</tr>\n </table>\n";
        $return .= "<h3>Overall Totals</h3>";
        $return .= "<table{$meta}><tr>";
        foreach ($colValIDX as $oh){
            $return .= "<th>Count Of {$oh}</th>";
        }
        $return .= "</tr>\n <tr>";
        foreach ($totSum as $totKey => $totVal){
          $totVal = number_format($totVal,0,".",",");
           $return .= "<td>{$totVal}</td>";
        }
        $return .= "</tr> \n </table> \n";
    return $return;
    }   
}

Thursday 1 November 2012

PHP Classes - A Simple Sample Class

PHP Classes - A Simple Sample Class


OK, so in the last two posts we covered the basic theory of objects. Now lets go ahead and make one.

To create an object in PHP we use the term "class" in the same way we would use "function". 
So what can we make an object for? Well pretty much anything really.  To keep it simple at the start we'll go through making an object that simply manipulates some text.

So lets make our object, I'm going to be calling it "myText"

<?php
class myText{

}
?>

There, that was easy enough.  Note that unlike functions there are no parenthesis after the name of the class that you can pass variables into.  We'll come back to that later on.  Now we'll add the first method.  The first thing we are going to need to do is get the text that we will be formatting, so lets create a method that will do that, this one I'll be calling "getText".

<?php
class myText{

 public function getText($textIn){
 
 }
}
?>

Hmmm....hang on, what are we going to be doing with this text after we get it?  Well the idea is that we can search the text for occurences of the word that we choose once it's loaded into the object.  So any method could need to access the text.  Guess we had better stick in an object level variable - these are actualy called paramters, but for now I'm going to keep calling them variables -  to keep the text in so that all the other methods will have access to it and then have the getText method send the text it gets out to this variable.

<?php
class myText{
 public $text;

 public function getText($textIn){
  $this->text = $textIn;
 }
}
?>

OK, so that's that done.  See the use of $this->text ? when working anywhere inside a class $this is basically a short way of saying "This Class" or "This Object".  the use of -> is saying which variable or method within the class or object to are referring to.  Also note that the $ is dropped from any variables that come after the -> (but you still need to use the parenthesis at the end of function names). So $this->text=$textIn tells the method that it is going to take the contents of the $textIn variable and send them to class variable $text.  You will also have by now (I hope) noticed the word public at the start of both the $text variable and the getText() function.  These are here to explicitly define the scope of each of these.

Right, so we have a method of getting text into the object now.  Let us now make a method that will do something to that text.

<?php
class myFormat(){
 public $text;

 public function getText($textIn){
  $this->text = $textIn;
 }

 public function makeCaps(){
  $this->text = strtoupper($this->text);
 }
}
?>

So now we have a method that will make all the text uppercase. Not exactly anything to write home about, but it at least lets us see how easy it is to address variables that are stored at the object level from within a method.

Let's blast on and add a couple more methods to play about with the text, we'll take a look at them once we're done adding them all.

<?php
class myText{
public $text;

  public function getText($textIn){
    $this->text = $textIn;
  }
 
  public function makeCaps(){
    $this->text = strtoupper($this->text);
  }
 
  public function delimit(){
    $delimitText = $this->text;
    $delimitText = str_replace(".", "", $delimitText);
    $delimitText = str_replace(",", "", $delimitText);
    $delimitText = str_replace("\n", '|', $delimitText);
    $delimitText = str_replace(' ', ',', $delimitText);
    return $delimitText;
  }
 
  public function txtToArray(){
    $txtForArray = $this->delimit();
    $textArray = explode('|', $txtForArray);
    for($i=0; $i<=count($textArray)-1; $i++){
      $textValue = $textArray[$i];
      $textBack = explode(',', $textValue);
      $textArray[$i] = array_filter($textBack);
    }
    return $textArray;
  }
 
  public function findWord($word){
    $haystack = $this->txtToArray();
    for($i=0; $i<=count($haystack)-1; $i++){
      $result = array_search($word, $haystack[$i]);
      if($result != false){
        $line = $i+1;
        $wordNo = $result+1;
        $return[] = "Line No $line, Word No $wordNo";
      }
    }
    return $return;
  }
}?>

OK, so that's a whole bunch of code that you may or may not fully understand.  Suffice it to say we have just added some methods that take in a text string, strips all full stops and commas out of it, splits the text ito lines (using the linux/unix \n control character, if you want to work with windows controls change the str_replace("\n"... to str_replace("\n\r"... ) and words and then lets someone find a word in the text returning each location by line and then number of words along that line that the number is in.  Look over the delimit() and txtToArray() methods and you will see that they are very similar to the normal functions you would write outside of a class/object.  they take in values and return other values and you can even call one method from inside another.

So now that we have made our class it would be good to get some output from it.  Lets drop down outside the closing } of the class and add the code to make the class do something.

...
}
$mySearch = new myText();
$mySearch -> getText("This is some sample Text\nUsed to test out our sample class.");
$searchResults = $mySearch->findWord("sample");
if(count($searchResults) >= 1){
echo "word \"sample\" found at the following locations:<br>";
}
foreach($searchResults as $result){
echo "$result <br>";
}
echo"<br>Within : <br>{$mySearch->text}";

?>

Notice that there is still the parenthesis when you call a new class, even though we didn't use any to declare the class at the beginning? Remember how I said we would come back to that? well here we go.

Classes have special methods, one of the most important is the constructor.  This lets the class gather everything it needs when it is created.  Lets modify our class a little bit and add the following constructor method at the top :

<?php
class myText{
public $text;

  public function __construct($text){
    $this->getText($text);
  }

  public function getText($textIn){
....

OK, now we have a constructor in place, what this does is load the text into the object as it's created.  This makes sense in the example here because the object depends on having sent into it for it to really do anything. now when we create the new object we add the text into the parenthesis there, and skip the line that calls the getText method altogether.

...
}
$mySearch = new myText("This is some sample Text\nUsed to test out our sample class.");
$searchResults = $mySearch->findWord("sample");
if(count($searchResults) >= 1){
echo "word \"sample\" found at the following locations:<br>";
}
foreach($searchResults as $result){
echo "$result <br>";
}
echo"<br>Within : <br>{$mySearch->text}";

?>

That's almost it for our first look at classes, just one more thing, the scope.  So far we have made everything public, this is the best way to create a new object because it lets you access each of the methods and variables for debugging.  Now we have finished it, we can change some of the scope.  For methods and variables that you don't want anything outside the object to access you can set it to either protected or private.  Private goes a step further and stops child objects and/or parent objects accessing the method/variable either.  Again it's not something we'll be getting into here, but for the sake of an example, change the word public that is before the $text at the top of the class to protected and see what happens when you run the code again.
Not good, huh? It stops the code getting direct access to the variable inside the object, so lets change that back.  Where protected would come in handy would be to use it for the delimit() and txtToArray() methods, this would stop anyone calling these methods directly and potentially using them out of context.

Anyways, that's it for this "beginner class" (pun intended).  I suggest that, assuming I didn't confuse you too much, you look into patterns in PHP. These are guides to standard designs for creating your own objects.