+1 571-297-6383 | info@sonjara.com

Chapter 4: Data Formatting

Before we dive any further into the mechanics of querying the database, a few words about how to format the data once you get it out. One of the most powerful features of Fakoli is the format() method that is supplied by the DataItem base class. This method allows you to output object values based on a formatting template string. Since the method is available on all the DataItems that make up your data model it provides the backbone for flexible templating with Fakoli's user interface helper classes (such as DataListView, or PagedList).

The format specifier syntax is highly flexible, and supports simple field substitution with formatting hints and relationship traversal, as well as local and static method calls when more complicated or conditional formatting is required.

Simple Field Substitutions

The key syntax for the format() method is the format specification point. These are indicated by a pair of parentheses containing the substitution specifier. The simplest form of specifier is a field name:

echo $employee->format("Full Employee Name: {title} {first_name} {last_name}");

Formatting Hints

To provide more precise control over the formatted output, the format method provides a hinting syntax. The general form is "{field:hint}". Formatting hints are specific to different datatypes.

Formatting Hints for Strings and Text

Hint SyntaxExampleDescription
upper {first_name:upper} - ANDREW Converts the field to upper case
lower {first_name:lower} - andrew Converts the field to lower case
ucwords {first_name:ucwords} - Andrew Converts the first letter of each word to upper case
prettify {identifier:prettify} Converts an underscored or camel case string to a human readable label
codify {name:codify} - andrew_green Converts the string to a valid 'code' format (all lower case, underscores for spaces, punctuation removed
stripHTML {description:stripHTML} Strips all HTML markup from the string
html {multiple_paragraphs:html} Formats plain text into HTML paragraphs with line breaks
xml {some_data:xml} Escapes the text to make it valid XML data. HTML tags are escaped, HTML entities are converted to numeric XML entities.
phone {phone_number:phone} (703) 123 4567 Renders a string containing a phone number in a standard US format
jsSafe {some_string:jsSafe} Escapes the string to make it a valid Javascript-safe string, with quotes and carriage returns escaped
Numeric value {first_name:5} - Andre... Cuts the output at the specified length and appends an ellipsis (if necessary)

Numeric and Currency Format Specifiers

Hint SyntaxExampleDescription
Numeric Value {price:2} - 99.99 The numeric value specifies the number of decimal points. Default is no decimal points

Date, DateTime and Timestamp Format Specifiers

Date and time formatting hints support the full set of format specifiers provided by PHP's built-in date() function. Some examples are given below:

Hint SyntaxExampleDescription
m/d/Y {end_date:m/d/Y} - 03/01/2013 Simple month/day/year
c {end_date:c} - 2013-03-01T15:19:21+00:00 ISO 8601 date format
r {end_date:c} - Fri, 1 Mar 2013 16:01:07 +0200 RFC 2822 date format

Boolean Format Specifier

Hint SyntaxExampleDescription
True / False {is_checked:Yes/No} - Yes for true, No for false Substitutes the appropriate value from the format hint based on the value of the boolean field

The Alternation Specifier

The alternation specifier allows you to provide an alternate output string that will be emitted when the specified field is empty. Often this is useful for providing default text when, say, a title field is empty. The format is {field|Text} so {title|None Specified} would output the text "None Specified" whenever the title field is empty.

Helper Methods

Sometimes you hit a situation where your formatting logic is more complex than simply substituting fields. In these cases you can make use of a couple of different syntaxes provided by format() that allow you to call out to PHP methods to provide formatting for all or part of your output.

Local Helper Methods

You define these methods on your DataItem object. Examples might be getFullName() to format a user's full name neatly including prefixes and suffixes or rank as necessary, or getFullAddress() to format a postal address with the appropriate line breaks. These methods should take no parameters. The syntax for calling the method from within a format template is simply {methodname()}.

For example, we might define an Employee record as follows:

class Employee extends DataItem
{
	var $table = "employee";
	var $primary_key = "employee_id";
	
	var $fields = array("employee_id" => Number,
                        "first_name"  => String,
						"last_name"   => String,
						"position"    => String);
						
    function getFullName()
	{
		if ($this->position)
		{
			return "{$this->first_name} {$this->last_name}, {$this->position}";
		}
		else
		{
			return "{$this->first_name} {$this->last_name}";
		}
	}
	
	function Office()
	{
        return $this->getRelated(Office);
	}
}

We would then be able to output the full name using the helper method like this:

$employee = new Employee(1); // Load Employee record from database
$employee->format("{getFullName()}");

Obviously in this case we might just as well call the method directly, but the neat thing is that the helper function format specifier can be used as part of a longer format template, or the format string could be passed to an object handling the output that knows nothing about our Employee class (see Chapter 8–DataListViews).

Static Helper Methods

The second formulation for helper methods is to put them as static methods on an external class. This can help keep our DataModel object uncluttered while helping us build an internal "API" for working with our data model objects. This is quite a common pattern of development in Fakoli applications, especially when developing components for applications using the Fakoli CMS.

In this pattern, we might create a Manager class to handle operations related to Employee objects. This class would not be limited to simple formatting operations–in fact it would most probably contain most of the application's business logic related to Employees (i.e. the code that manipulates the data model according to business rules). Our super simple EmployeeManager class might look like this:

class EmployeeManager
{
	static getFullName($employee)
	{
		if ($employee->position)
		{
			return "{$employee->first_name} {$employee->last_name}, {$employee->position}";
		}
		else
		{
			return "{$employee->first_name} {$employee->last_name}";
		}
	}
}

Note that the code is almost identical to the local helper method, with the exception that the Employee object is being passed in as a parameter, rather than as a $this context. To use the static helper within a format template, the code would look like this:

$employee = new Employee(1); // Load Employee record from database
$employee->format("{EmployeeManager::getFullName}");

Relationship Traversal

One of the most powerful features of the format() method is the ability to traverse relationships within format specifiers. This allows you to output information held not just in the object on which you initially called format(), but also any related objects, including those accessed through one-to-many relations. The syntax is simple. Suppose we had an Employee class and an Office class, defined as such:

class Employee extends DataItem
{
	var $table = "employee";
	var $primary_key = "employee_id";
	
	var $fields = array("employee_id" => Number,
                        "first_name"  => String,
						"last_name"   => String,
						"position"    => String,
						"office_id"   => Number);
						
	function getFullName()
	{
		if ($this->position)
		{
			return "{$this->first_name} {$this->last_name}, {$this->position}";
		}
		else
		{
			return "{$this->first_name} {$this->last_name}";
		}
	}
	
	function Office()
	{
        return $this->getRelated(Office);
	}
}

class Office extends DataItem
{
    var $table = "office";
	var $primary_key = "office_id";
	
	var $fields = array("office_id" => Number,
	                    "address"   => String,
						"phone"     => PhoneNumber);
	
    function Employees($constraint = "")
    {
        return $this->getRelatedList(Employee, "", $constraint);
    }
}

Then we could output information about the office where employee #1 works like this:

$employee = new Employee(1); // Load the Employee from the database
$employee->format("{first_name} {last_name} works at {Office.address} {Office.phone}");

But that's not all. We can also traverse the one-to-many relation back again to concisely specify a format that lists all the employees at a given office:

$office = new Office(1); // Load the Office from the database
$office->format("Employees at {address} {phone}:<br>{Employees.getFullName()}");

This would output a comma-separated list of the full names of every employee at office #1. We can change the list separator easily using the second parameter of the format() method, like so:

$office = new Office(1); // Load the Office from the database
$office->format("Employees at {address} {phone}:<br>{Employees.getFullName()}", "<br>");

Default Format

The last format function is more of a DataItem class feature than a feature of the format() method itself. Any DataItem object can optionally provide a default format in its class definition that is used if no format template is passed to the format() method. For instance, if we added

    ...
	var $default_format = "{getFullName()}";

to our Employee class, then a call to $employee->format() would use this format template by default.

Summary

As I hope you can see, the DataItem::format() method provides an exceedingly powerful set of tools to help you format output from your data. Whereas you can use the method directly, its real power and benefit will be felt once you start using classes such as DataListView to present your data to your users. For that you will have to wait for (or skip ahead to) Chapter 8.


Chapter 5: Working with Joins » « Chapter 3. More Data Model Shenanigans