Author: Stefan Kientzler
Updated on: 2021-01-07
Posted on: 2021-01-07
Viewers: 98 (January 2021 until February 2021)
Last month viewers: 33 (February 2021)
Package: PHP PDF Table using FPDF
To generate more advanced PDF documents like those of reports that display data in tables, it is a more complex process as it necessary to create PDF document with table structures with data being displayed in the right table spaces.
Read this tutorial that shows the example PHP code on how to use the package PHP PDF Table using FPDF to create PDF documents with data displayed in tables.
In this article you can read about:
PDF Report Generated with the Example Script in this Article
How to Use the PHP PDF Table Class to Create PDF Reports
Download or Install this Package using Composer
PDF Report Generated with the Example Script in this Article
In this article you will learn how generate a PDF report that looks like in the picture below.
The report that you can see above is based on the following parameter array that defines the the types of the data to be displayed in the table columns.
$row = array(
'text' => <string>,
'weight' => <float>,
'date' => <float>,
'price' => <float>,
'grp_id' => <int>
);
The source code of this example can be found in the package files XFPDFExample.php and XFPDFExample.class.php.
How to Use the PHP PDF Table Class to Create PDF Reports
In order to be able to use this PHP package, it is first necessary to create your own sub-class that extends the XPDF class.
Your class should define everything you need to configure the presentation of the report to specify values for the following aspects:
- Set a logo for the header
- Footer
- Fonts and colors
- Table columns
- Column headers
- Data source for the content
- Width
- Alignment
- Format
- Generation of totals and carry-over lines
- Grouping of lines
Then the report can be created using this class to make it suitable for printing.
Overview of the steps
- Implement the sample class to define the report
- Fonts/colors, logo and footer
- Fonts and colors
- Logo
- Footer
- Column definition of the example table
- Description
- And here the code for it
- The individual lines of code explained in more detail
- Generating the cell contents that are not directly contained in the row data
- Grouping of rows with group header and group total
- Start grouping and output the subtotal
- Output of the last subtotal at the end of the table
- Subheadings, manipulation of line data before output
- Fonts/colors, logo and footer
- Use of the example class
- Start the table
- Transfer of the data
- Close the table
Implement the sample class to define the report
In the example the class ExampleXPDF
is implemented in the file XFPDFExample.class.php. In order to create an extension of the XPDF
class, it has to be refered via theuse
statement.
use SKien\XFPDF\XPDF;
class ExampleXPDF extends XPDF
We design the report in the class constructor. First of all, the Constructor of the parent class must be called for initialization.
public function __construct(string $orientation = 'P')
{
parent::__construct($orientation);
...
Fonts, colors, logo and footer
// ...
$this->initGrid('xfpdf-sample.json');
// define page header and -footer
$this->setLogo('images/elephpant.png');
$this->setLogoHeight(9.0);
$this->addPageFooter("Seite: {PN}/{NP}\tAuthor: S.Kien\t{D} {T}");
Fonts and colors
The fonts and colors can be set using the different methods XPDF::SetXXXFont()
and XPDF::setXXXColors()
.
However, it is faster and easier if all settings are made in a JSON file. (This can also be used multiple times for standardized reports). This file can be passed to the XPDF::initGrid()
method.
$this->initGrid('xfpdf-sample.json');
The structure of this JSON file is self-explanatory and can be found in the example file used here.
Logo
$this->setLogo('images/elephpant.png');
$this->setLogoHeight(9.0);
The logo is printed right-aligned in the header. By default, the logo will be scaled to a height of 8mm. In the example we choose a height of 9mm for our Elephpant.
Footer
$this->addPageFooter("Page: {PN}/{NP}\tAuthor: S.Kien\t{D}");
The page number is extended by the prefix 'Page:', centered is placed an author and right-justified the date without time output.
Following Placeholders are used:
- {PN}: Page number
- {NP}: total number of Pages
- {D}: current date
- {T}: current time (not used in the example)
(default setting is: the page and total number of pages on the left and current date and time on the right)
The date and time in the footer will be formatted according to the localeconv ()
settings.
The locale Settings can be changed from the system default using the
XPDF::setLocale()
method.
Column definitions of the example table
Column description
The table in the example has 8 columns:
A pure numbering column
- heading "Row"
- width 10mm
- right justified
- automatic numbering
- for the totals lines, the respective text should be placed in this column
A date column
- heading "Date"
- width 35mm
- centered
- the content is contained directly in the line data
- Formatting "Sat, 03.12.2020"
A 'normal' text column
- heading "Text"
- dynamic width -> The column becomes so wide that the table fills the entire width of the page
- left-justified
- The content is contained directly in the line data
A column with dynamic content
- heading "Grp."
- width 12mm
- centered
- the content must be generated from the line data
A column with numerical content
- heading "Weight"
- width 20mm
- right justified
- the content is contained directly in the line data
- Formatting with one decimal place and the addition "kg"
- A total have to be calculated for this column
A symbol column
- No heading (column header is connected to the previous column)
- width 8mm
- the symbol to be inserted depends on the value of the column 'weight'
A currency column
- heading "Price"
- width 25mm
- right justified
- the content is contained directly in the line data
- Formatting with currency symbol
- A total have to be calculated for the column
A calculation column
- heading "Cost per Kg"
- width 25mm
- right justified
- the content is calculated from the line data
- Formatting as currency with currency symbol
...and here the code for it
$this->addCol('Row', 10, 'R', XPDF::COL_ROW_NR, XPDF::FLAG_TOTALS_TEXT);
$this->addCol('Date', 35, 'C', 'date', XPDF::FLAG_DATE);
$this->addCol('Text', -1, 'L', 'text');
$this->addCol('Grp.', 12, 'C', self::MY_GRP_COL);
$this->addCol('Weight', 20, 'R', 'weight', XPDF::FLAG_TOTALS_CALC | XPDF::FLAG_NUMBER);
$iImgCol = $this->addCol(-1, 8, 'C', self::MY_IMAGE_COL, XPDF::FLAG_IMAGE | XPDF::FLAG_TOTALS_EMPTY);
$this->addCol('Price', 25, 'R', 'price', XPDF::FLAG_TOTALS_CALC | XPDF::FLAG_CUR_SYMBOL);
$this->addCol('Cost per Kg', 25, 'R', self::MY_CALC_COL, XPDF::FLAG_TOTALS_EMPTY);
// enable the totals/pagetotals and carry-over functionality
$this->enableTotals(XPDF::TOTALS | XPDF::PAGE_TOTALS | XPDF::CARRY_OVER);
$this->setTotalsText(
"My Totals over all:",
"Subtotal on Page {PN}:",
"Carry over from Page {PN-1}:");
// set date and number formating.
$this->setDateFormat('%a, %d.%m.%Y');
$this->setNumberFormat(1, '', ' kg');
// and set meassuring for the image col
$this->setColImageInfo($iImgCol, 1.5, 2.5, 3 );
The individual lines of code explained in more detail:
Column 1
$this->addCol('Row', 10, 'R', XPDF::COL_ROW_NR, XPDF::FLAG_TOTALS_TEXT);
- The column ID
XPDF::COL_ROW_NR
defines the content of the column as an automatic line number. XPDF::FLAG_TOTALS_TEXT
defines the output of the text for total lines in this column. All further columns are connected to this column up to the next total column.
Column 2
addCol('Date', 30, 'C', 'date', XPDF::FLAG_DATE);
- The content of the element 'date' of the line data is interpreted as a date value.
Accepted values:- a DateTime object
- an int as a UNIX timestamp
- a string is tried to be parsed with
strtotime()
- With the
XPDF::FLAG_DATE
the column is formatted according tosetDateFormat()
.
Column 3
addCol('Text', -1, 'L', 'text');
- The content of the element 'text' of the line data is used for output
- With a width of -1 the column is defined as a dynamic column. This gives the Column the remaining available space so that the grid completely fills the width of the page
Column 4
addCol('Grp.', 12, 'C', self::MY_GRP_COL);
- The self-defined unique column ID
self::MY_GRP_COL
ensures that the methodCol()
is called, in which the desired content of the cell can be generated using the row data. TheCol()
method must be overloaded in the derived class.
Column 5
addCol('Weight', 20, 'R', 'weight', XPDF::FLAG_TOTALS_CALC | XPDF::FLAG_NUMBER);
- The content of the element 'weight' of the line data is interpreted as a numerical value.
XPDF::FLAG_TOTALS_CALC
indicates that a total will be calculated for this column.- With
XPDF::FLAG_NUMBER
the column is formatted according toSetNumberFormat()
.
Column 6
$iImgCol = $this->addCol(-1, 8, 'C', self::MY_IMAGE_COL, XPDF::FLAG_IMAGE | XPDF::FLAG_TOTALS_EMPTY);
- A value of -1 as the column heading indicates that the column header is connected to the header of the previous column.
- With the defined column ID
self::MY_IMAGE_COL
the methodcol()
is called, in which the image to be displayed have to be set. - So that a corresponding empty cell is output in a total line in this column,
XPDF::FLAG_TOTALS_EMPTY
must be set for this column after the previous defined sum column. All succeeding columns will be connected until another total column appears or up to the end of the grid. - The column index is saved in
$iImgCol
in order to be able to subsequently set image position and dimensions withsetColImageInfo()
Column 7
addCol('Price', 25, 'R', 'price', XPDF::FLAG_TOTALS_CALC | XPDF::FLAG_CUR_SYMBOL);
- The content of the element 'price' of the line data is interpreted as a numerical value.
XPDF::FLAG_TOTALS_CALC
indicates that a total will be calculated for this column- With
XPDF::FLAG_CUR_SYMBOL
the column is formatted according tosetCurrencyFormat()
Column 8
addCol('Cost per Kg', 25, 'C', self::MY_CALC_COL, XPDF::FLAG_TOTALS_EMPTY);
- In order to calculate a value from the row data, the column is given a column ID (
self::MY_CALC_COL
), which also calls thecol()
method, in which the desired content is calculated and formatted. XPDF::FLAG_TOTALS_EMPTY
is needed for a correct output of the Total lines.
Activate the output of totals lines.
$this->enableTotals(XPDF::TOTALS | XPDF::PAGE_TOTALS | XPDF::CARRY_OVER);
XPDF::TOTALS
: Total amount at the end of the document.XPDF::PAGE_TOTALS
: Subtotal at end of every page.XPDF::CARRY_OVER
: Carry over at the top of the page (starting at the 2nd page).
Set the texts for the various totals lines.
$this->setTotalsText(
"My Totals over all:",
"Subtotal on Page {PN}:",
"Carry over from Page {PN-1}:");
The text is printed in the first column which the flag XPDF::FLAG_TOTALS_TEXT
has set (usually column 1). The text can contain the placeholders '{PN}' for the current page and '{PN-1}' for the previous page.
Set the output format for all Date columns.
$this->setDateFormat('%a, %d.%m.%Y');
The format string must comply with the guidelines of the PHP function 'strftime()'.
See https://www.php.net/manual/en/function.strftime.php
Set the output format for all Numeric columns.
$this->setNumberFormat(1, '', ' kg');
- One decimal place.
- No prefix.
- Extension * 'kg' *.
Decimal and thousand separators are read from the localeconv()
settings.
The locale Settings can ne changed from the system default using the
XPDF::setLocale()
method.
Setting the size and position of the image in the symbol column
$this->setColImageInfo($iImgCol, 1.5, 2.5, 3 );
- the column index that was saved when the column was created.
- position 1.5mm from the top of the line.
- position 2.5mm from the left edge of the cell.
- height 3mm.
- No width is specified so the aspect ratio of the original graphic is retained.
Generating the cell contents that are not directly contained in the row data
If the content of a cell to be printed does not exist directly in the row data, a unique ID can be set for this column instead of the data field. The desired output have to be generated in the method ExampleXPDF::col()
. This is e.g. the case if
- the data contain a numeric ID and a corresponding text have to be printed
- a calculation result based on several values have to be printed
- special formatting is required
- the value to be displayed depends on other external factors
In the example, columns 4, 5 and 8 are generated internally.
/** const for own column ID's */
const MY_GRP_COL = 1;
const MY_IMAGE_COL = 2;
const MY_CALC_COL = 3;
Unique ID's are defined for these columns, which are used when the columns are created with addCol()
.
The processing is implemented in the ExampleXPDF::col()
method:
protected function Col(int $iCol, array $row, bool &$bFill) : string
{
$strCol = '';
switch ($iCol) {
case self::MY_GRP_COL:
$aValues = array( '', 'Grp. A', 'Grp. B', 'Grp. C', 'Grp. D');
if ($row['grp_id'] < 0 && $row['grp_id'] <= 4) {
$strCol = $aValues[$row['grp_id']];
}
break;
case self::MY_IMAGE_COL:
$strCol = 'images/';
$fltWeight = floatval($row['weight']);
if ($fltWeight > 35.0) {
// ... to heavy
$strCol .= 'red.png';
} else if ($fltWeight > 20.0) {
// ... just in the limit
$strCol .= 'yellow.png';
} else {
$strCol .= 'green.png';
}
break;
case self::MY_CALC_COL:
$fltPricePerKg = 0.0;
if (floatval($row['weight']) != 0) {
$fltPricePerKg = floatval($row['price']) / floatval($row['weight']);;
}
$strCol = $this->_FormatCurrency($fltPricePerKg, true);
break;
default:
// very important to call parent class !!
$strCol = parent::col($iCol, $row, $bFill);
break;
}
return $strCol;
}
self::MY_GRP_COL
The corresponding text abbreviation is assigned here instead of the numerical data value.self::MY_IMAGE_COL
A corresponding image is set depending on the value of hte data field 'weight'.- a red flag if weight > 35
- a yellow flag for values between 20 and 35
- a green flag for all the rest
self::MY_CALC_COL
The value is calculated from the row data and formatted as a currency field.default
The method of the parent class must be called in the default
branch!
Grouping of rows with group header and group total
To illustrate the grouping function with subtotals, in the example for each month a subheading and a subtotal is issued. In order to be able to react to a change of month, the class implements the property strMonth
to hold the month of the current row, which is initialized with an empty string.
/** @var string remember month and year of the previous row */
protected string $strMonth = '';
The following steps are required:
- Start of the grouping and printing of the subtotal.
- Output of the last subtotal at the end of the table.
Start grouping and output the subtotal
To determine whether a month change occurs before a new line is printed, the method XPDF::PreRow()
is overloaded in the example class. This method is always called BEFORE the output of a new line.
protected function PreRow(array &$row) : string
{
// for grouping
$date = strtotime($row['date']);
$strMonth = date('Y-m', $date);
if ($this->strMonth != $strMonth) {
// first row we have no subtotals...
if ($this->strMonth != '') {
$this->endGroup();
}
$this->startGroup('Totals ' . strftime('%B %Y', $date) . ':', strftime('%B %Y', $date));
$this->strMonth = $strMonth;
}
...
}
Comparing the month of the current row ($row 'date']
) with the previous month stored in $this->strMonth
, it can be determined whether there is a change of month.
If the previous month is not an empty string (this is the case when printing the first row...), the call XPDF::endGroup ()
prints the subtotal for the previous month and resets the calculated values for next month.
Then the grouping for the new (or first) month is started with XPDF::startGroup()
. If no strHeader
parameter is passed to this function, only the internal calculation of the subtotals started again, but no subheading will be printed.
Finally, the new month is stored in the strMonth
property.
Output of the last subtotal at the end of the table
In order to print the subtotal for the last month of the report, this must be output AFTER the last data line but BEFORE the output of the over all total. Since this understandably cannot be intercepted in XPDF:PreRow()
, the method XPDF :: EndGrid ()
is overloaded for this purpose, which must always be called after all data has been transferred.
public function endGrid() : void
{
// end last group for subtotals before we call the parent (!!! don't forget that!!)
$this->endGroup();
parent::endGrid();
}
Here XPDF::EndGroup()
is called one last time before further processing is passed on to the parent class. (the call of the parent class must not be forgotten under any circumstances!)
Subheadings, manipulation of line data before output
Sometimes it is necessary to change the data for a row due to a specific criterion before output or to add additional information and/or to insert a subheading for better identification. For this task the method XPDF::PreRow()
will also be the proper place to do.
protected function preRow(array &$row) : string
{
...
$strSubRow = '';
if ($this->iRow == 47) {
$strSubRow = '... next Row have been manipulated in ExampleXPDF::preRow(array &$row)!';
$row['text'] = 'manipulated Rowdata!';
}
if ($this->iRow == 56) {
$row['text'] = 'manipulated Rowdata without Subrow!';
}
return $strSubRow;
}
Note that the $ row parameter is passed as a reference. Changes to the data therefore also affect the calling code.
In the example class, a data field is simply changed for a row with a fixed row number (# 47) and this line is preceeded with a corresponding subheading. In the case of a second fixed row (# 56), only the data field is changed and the line then is printed without a subheading.
Use of the example class
The report is created in the file XFPDFExample.php
. The necessary files of the package are integrated via autoloader and the example class is included.
require_once 'autoloader.php';
require_once 'XFPDFExample.class.php';
First, an instance of the example class is created and some information and the headings are set.
$pdf = new ExampleXPDF();
$pdf = new ExampleXPDF();
// set some file information
$pdf->setInfo('XFPDF', 'Example', 'PHP classes', 'Keyword1, Keyword2, ...');
$pdf->setPageHeader('Create PDF Table', 'using extpdf package from PHPClasses.org');
With SetInfo()
, file information is set that is displayed in the created document under the document properties.
- 'XFPDF': title
- 'Example': Brief description
- 'PHP classes': Author
- 'Keyword1, Keyword2, ...': Keywords
setPageHeader()
sets the title and optionally a short description:
- Title left-justified in the font specified with
XPDF::setHeaderFont()
- the short description also left-justified in a new line in the font
XPDF::setSubjectFont()
After all document settings have been made, the table can be generated:
$pdf->prepare();
$date = time();
for ($iRow=1; $iRow <= 100; $iRow++) {
$row = array(
'text' => 'Text in var Col, Line ' . $iRow,
'weight' => (rand(10, 500) / 10),
'date' => date('Y-m-d', $date),
'price' => (rand(10, 2000) / 9),
'grp_id' => rand(1, 4)
);
$pdf->row($row);
$date += 24 * 60 * 60;
}
$pdf->endGrid();
$pdf->createPDF('example');
Start the table
$pdf->prepare();
This call starts the creation of the table.
Transfer of the data
for ($iRow = 1; $iRow <= 100; $iRow++) {
$row = array(
'text' => 'Text in var Col, Line ' . $iRow,
'weight' => (rand(10, 500) / 10),
'date' => date('Y-m-d', $date),
'price' => (rand(10, 2000) / 9),
'grp_id' => rand(1, 4)
);
$pdf->row($row);
$date += 24 * 60 * 60;
}
For the sake of simplicity, the line data in the example is generated in a for loop with random values and transferred with $pdf->row($row)
. In practice, in most cases the data will come from a database query or some other data source. This possibly looks like this:
$db = new mysqli(...)
$dbres = $db->query('some SQL query');
while (($row = $dbres->fetch_array(MYSQLI_ASSOC) !== false) {
$pdf->row($row);
}
Beenden der Tabelle
$pdf->endGrid();
At the end of the table, this function is called, which, if necessary, generates an end total and closes the table.
Generate the PDF file
$pdf->createPDF('example');
Download or Install this Package using Composer
The XPDF class presented in this article can downloaded in this page that also contains instructions on how to install it using the PHP composer tool following the instructions available here.
If you have questions feel free to ask in the comments below or in this package support forum.
You need to be a registered user or login to post a comment
Login Immediately with your account on:
Comments:
No comments were submitted yet.