Wildfire Application Framework User Manual

Table of contents

Introduction

The Wildfire Framework is a set of PHP class files packed with features to ease your development pains. To use the framework in a PHP script, you only need to require one file.

<?php
require "/path/to/wildfire/common/initFramework.php";
?>

That's it! If you're curious as to what this file actually does, we've detailed it right here, but you might just want to skip it and read on about the different packages of classes available to you now that you've turned on the framework.

  1. Defines several constants including copyright information, version number, and the operating directory.
  2. Adds several paths to wfObject in which to search for classes.
  3. Starts the page timer (see wfTimer)
  4. Initializes the error handler (see wfError)

[wf]

This is the root package to the Wildfire framework. It contains a few classes you'll want to remember.

wfObject

wfObject is responsible for loading and finding classes. It has only static methods. In addition to its capabilities regarding loading classes, it has three assert methods that class authors are encouraged to use: _assertTraversable, _assertType, and _assertClassExists.

wfError

wfError is a simple error handler. The framework initialization script calls a method on this class that registers it as the default error handler. Any errors that come through this class are turned into Exceptions and thrown.

wfI18N

wfI18N is the gateway to internationalization strings. The i18n folder has (read: will have) a folder for all translated languages. In each is one or more files that contain associative arrays of strings. wfI18N has two important methods:

get
Finds and returns the string at the key given.
format
Performs a sprintf on an i18n key with arguments given

wfOptions

This class provides a means to get and store application settings. There is an "options.ini.php" file in the "common" folder, containing these settings. There are three methods to this static class.

get
Returns an option's value
set
Sets the value of an option
store
Stores any sets performed back to the "options.ini.php" file

wfTimer

This static class provides a simple page timer, which is started by the framework initialization script. There are only two methods, to get the current execution time, and to restart the timer.

wfString

Another static class, wfString was a place to gather all the useful string manipulation methods that piled up as the framework grew. As a note for future development, similar string methods should be put here.

wfEnum

PHP lacks a native enumerated type, so this was our best attempt. The rest of this description is copied from the class doc block.

Enum classes are used to represent set types of things, for instance, if we created a class to represent different operating systems, it might provide enum methods like this:

$unix = OperatingSystem::Unix();
$win = OperatingSystem::Windows();
$mac = OperatingSystem::Mac();
echo $unix->getName(); // prints Unix

Table of contents

[wf.collections]

The collections package has several interfaces and implementing classes that have done your dirty work for you. Call it a tip of the hat and an improvement upon Java's collections package. As such, we use some of the same names you might have heard when referring to collections in other programming languages.

Collection
The base term for an iterable grouping of objects.
Bag
A collection that can contain multiple entries of the same object.
Set
A collection that can contain only one entry of an object.
Map
A collection whose entries are associated with names or keys.
Mutable
A mutable collection is one that can be altered (added to, cleared, merged, etc.)

Taking these concepts and running, we devised the following classes and interfaces for your use.

Interfaces

Classes

Why do all this? PHP's array capabilities are quite impressive indeed, you might say. Just as the SPL allows you to have Iterable classes, so too did we want to have an object that can do many of the things one can do with arrays with intelligent exception throwing.

Table of contents

[wf.data]

The data package provides for two kinds of objects: data adapters and a record set wrapper.

Adapters

The data adapters build on top of PDO, adding some needed or convenient capabilities. Currently, adapters exist for the following databases:

The data adapters add the following methods onto of the PDO class.

getColumn
Fetches an entire column
getPairs
Fetches two columns into an associative array: one as the key and the other as the value
getScalar
Fetches the first column of the first row
getRow
Fetches the first row
queryLimit
Performs a select, returning an offset/limited number of records
describeTable
Describes the columns in a table
getTables
Gets a list of all available tables in the data store

There is a convenient factory class to instantiate these adapters for you.

$info = array( "hostname"=>"localhost", "username"=>"foo", "password"=>"bar", "dbname"=>"test" );
$options = array(PDO::ATTR_PERSISTENT => true);
$mysql = wfDb::get( 'mysql', $info, $options );
$info = array( "uri"=>"/var/www/sqlite/wildfire.sqlite" );
$sqlite = wfDb::get( 'sqlite', $info );

Record Set

The record set is an editable result set with collections of its columns and rows. There are five classes involved with the record set.

wfRecordSet
The big boss here, contains one each of the following collections
wfDataColumnCollection
A collection of…
wfDataColumn
Objects that describe the type of data in each field.
wfDataRowCollection (extends wfSortableSet)
A collection of…
wfDataRow
Objects that store the values in each row.

These classes aren't tied to the adapters at all, but a wfRecordSet can be created from a PDOStatement. We needed these classes because the PDOStatement object is immutable, so the runtime object query mechanism (outlined in the domain model section) needed another avenue to create its own record sets. Also, PDOStatements, and all record sets for that matter lack manipulation capabilities. With our record set, you can remove or add columns, sort the rows, or change row values.

Table of contents

[wf.domainModel]

We're aware there are ORM (object-relational mapper) systems out and about for PHP. Most of them have adopted the design pattern popular with the Ruby on Rails folks, the Active Record pattern. While there is nothing wrong with that pattern, we felt it necessary to introduce other patterns as well, because you're entitled to our opinions.

For those of you who study software design patterns, we have taken the good ideas behind Data Mappers, the Identity Map, and the Unit of Work. The domain model is made up of these several parts that all work together to achieve data object persistence.

For example's sake, let's say your domain model has to do with a message board system, comprised of Users, then Boards which contain Threads, which contain Posts. For simplicity and example's sake, all Users have permission to all boards, and each board has only one moderator.

wfDataEntity

The data entity is the holder for information out of the data store. Essentially, each logical "thing" you can come up with should be an entity. In our example case, we would create the following entity classes (we strongly recommend your objects be named in singular):

The wfDataEntity has the logic to load and contain relations between itself and other entities. It doesn't know about its own collection, and it only knows about the object retrieval methods of the wfWorkUnit. (That's a lie - when the entity is changed in any way, it alerts the wfWorkUnit that it is dirty and needs to be updated in the data store).

wfEntityCollection

This is an extension of wfSortableSet, and is designed to hold entities of one type. In our case, we would create a UserCollection, BoardCollection, ThreadCollection, and PostCollection.

Why use strongly-typed collections instead of arrays? This allows class authors to typehint a collection of one type of entity and be sure they're only getting the one type, instead of having to test each entry in a collection to make sure it's the entity they expect.

wfDataMapper

The data mapper is responsible for performing CRUD operations with the data store (create, retrieve, update, delete). It turns raw data into entities. There is one mapper per entity class. Thus, for the example, we would create a UserMapper, BoardMapper, ThreadMapper, and PostMapper. wfDataMapper is an abstract class, and currently, the only subclass to it is wfSQLMapper. We wanted to design it that way in case there were some other data store used besides a database (xml files, for instance). The mapper is responsible for a few things aside from CRUD.

Supported entity and corresponding collection

Each mapper has a method for returning the class name of its supported entity and a method that returns an instantiated collection appropriate to that entity.

Caching Duration

The mapper is not responsible for caching, however it is responsible for defining the duration the entity should be cached. Each mapper has a method that returns an EntityCacheType stating this duration. There are three durations:

Relationships

The mapper defines the relationships between the entity it owns and other entities. For information on the relationship definition, see the API documentation for the getRelationships method in the wfDataMapper class.

In our example, the relationships are as follows

The mappers are also responsible for saving changes to linked entity collections. For example, if we have a Board object and delete from its ThreadCollection a thread, the BoardMapper should make sure to tell the ThreadMapper to delete that thread (if we set the relationship to do that - which we would).

Data mappers should have no knowledge whatsoever of the wfWorkUnit.

wfIdentityMap

The identity map is a place to store the entity once it has been retrieved by the data mapper. The work unit takes care of this, so don't worry about interacting with the identity map directly.

Entities are stored here so that if they are requested again within their cache time (see above), they can simply be pulled out of the identity map instead of having to take an expensive trip back to the data store.

The wfIdentityMap keeps track of those entities that should be persisted only for the current request, for a certain time, or forever. When the identity map is serialized to the session as a property of the work unit, all request-only entities are removed, and any time-out entities are checked for expiration (the identity map has a wfTimedBag collection with entities in it).

Again, it's all very seamless and it's all handled by the wfWorkUnit.

wfWorkUnit

The work unit is a session-persistant object that is the gateway between userland and the ORM model. The work unit allows you to CRUD objects without any knowledge of their storage methods. You can do queries against these objects and even execute reports with grouping and aggregate functions (see wf.domainModel.query). The work unit has a few responsibilities and uses.

Get me an entity

The work unit receives requests for entities in one of four ways.

get
This method gets one entity of a certain type and a certain id
getAll
This method gets all entities of a certain type (be careful with this when working with large numbers of records)
find
This method gets one entity of a certain type given certain criteria (an associative array of values it must have)
findAll
As find, but all matching entities

When get or getAll is called, first, the work unit checks the identity map to see if the requested entity or entities are stored there. If not, it uses the entity's data mapper to get the requested object(s), puts it or them into the identity map, then returns the entity or collection to the user. The methods find and findAll do not check the identity map but go straight to the mapper.

Save me!

Secondly, if an entity is new and you wish to put it in the data store, you simply register the object as new, and on the next commit, it will be inserted.

I'm dirty!

Thirdly, if an entity is changed (its own values or the members of a linked collection), it contacts the wfWorkUnit itself and says "Hey, I'm different." On the next commit, any pending transactions will be taken care of.

Goodbye, cruel world!

If you want to remove an entity, the work unit can do your dirty work and bump it, off it, whack it, etc.

Afraid of commitment?

The work unit has a commit and rollback function. Any changes you perform are considered volatile data until you commit them. Keep in mind that since the work unit persists across sessions, if you have entities that are cached for longer that just a request, any uncommitted changes you make will still be there until they're rolled back or committed. If you've had it with the current session, you can destroy it and unload all data.

Table of contents

[wf.domainModel.query]

The query package of the domain model allows you to construct with SQL-like syntax, or via the programmatic API, query statements that can be executed to return collections of entities or wfRecordSet objects with aggregated data. There are several facets to this package.

Tokens

The parts of the query statement are arranged into tokens. There are three kinds of tokens: Criteria, Clauses, and Clause Items.

Criteria

There is an abstract class that extends wfqlToken called Criterion (singular for Criteria, grammar nerds!). It has two implementing classes: Expression and Junction. When forming an if statement in PHP or a where clause in SQL, an Expression can be likened to a boolean statement (a > b), likewise a Junction is two or more (a > b AND b < c ).

Clauses and Clause Items

There are three types of wfqlClause objects: SelectClause, GroupClause, and SortClause. Similarly, they each have a corresponding wfqlClauseItem: Select, Group, and Sort.

Aggregate

This is an enum representing the different functions that can be performed on grouped data.

References and the Reference Manager

The WFQL engine translates the string representation of columns and method calls into references. To put it simply, they tell the engine how to get the value out of the reference, if it has any aliases in the rest of the query, if it has an aggregate function attached, and soforth. The reference manager keeps tabs on all of these references for a single query. These classes are used by the engine and don't need your attention. Move along! :)

Miscellaneous

There are two more classes, wfqlParser and Tuple, but you'll never need them, so they shall remain a mystery to the world of documentation.

wfQuery

wfQuery allows you to specify criteria and sorting to get a collection. You can add Criterion objects and Sort objects, or parse a statement from a string consisting of "WHERE [criteria] ORDER BY [sorts]". Once executed, it returns a collection of entities.

wfReportQuery

Take wfQuery and add the ability to specify which fields you want and how to group them (if you want to group them), and some having criteria for the groups (if you've grouped them). Once executed, it returns a wfRecordSet with the data you specified.

Examples

What you've been waiting for, right? Our first example will demonstrate programmatically building a query to get Post objects authored by User #24.

<?php

include "common/initFramework.php";

$unit = wfWorkUnit::getInstance();

$query = $unit->query("Post");
$query
    ->addWhere( Expression::eq('userId',24) )
    ->addSort( Sort::descending('postedOn') );

$posts = $query->execute();

?>

Here's the same but as a parsed statement.

<?php

include "common/initFramework.php";

$unit = wfWorkUnit::getInstance();

$query = $unit->query("Post",'where userId = 24 order by postedOn desc');
$posts = $query->execute();

?>

You can even use field names that deal with linked entities. Let's say the post has a relationship with its author.

<?php

include "common/initFramework.php";

$unit = wfWorkUnit::getInstance();

$query = $unit->query("Post",'where author->lastName = "Smith" order by postedOn desc');
$posts = $query->execute();

?>

Getting excited? Now let's try a report query for the total number of posts each user has made.

<?php

include "common/initFramework.php";

$unit = wfWorkUnit::getInstance();

$query = $unit->reportQuery("Post");
$query
    ->addSelect( Select::column('userId') )
    ->addSelect( Select::aggregate(Aggregate::Count(),'id','posts') )
    ->addGroup( Group::parse('userId') )
    ->addHaving( Expression::gte('posts',10) )
    ->addSort( Sort::descending('posts') );

$recordSet = $query->execute();
// the recordset will have two columns, userId and posts, sorted by posts descending,
// and only have the records for users who have posted at least ten messages

?>

The same thing parsed:

<?php

include "common/initFramework.php";

$unit = wfWorkUnit::getInstance();

$statement = "SELECT userId, count(id) as posts " .
    " GrOuP By userId HAViNG posts > 10 " .
    " ORDER BY posts desc";

$query = $unit->reportQuery("Post",$statement);
$recordSet = $query->execute();
// the recordset will have two columns, userId and posts, sorted by posts descending,
// and only have the records for users who have posted at least ten messages

?>

Some notes on WFQL

You can use methods instead of property names, even methods that don't correspond to database fields. You can even give them arguments.

Literals like strings must be quoted with double-quotes.

Case for tokens doesn't matter - but property and method case does!

Table of contents

[wf.io]

The new classes added to the PHP SPL for file and directory access are nice, they're just not what we like to use. We seperated the functionality of different streams out into different classes. Local files have a lot more methods than remote ones, for instance. Directories have some different features than regular files. The interfaces and classes below support the seperation of capability in these different types of resources.

Interfaces

Objects can implement both interfaces for streams, but should implement at least one.

Classes

All classes in wf.io inherit from the abstract class wfStream. wfStreamFactory takes a file/directory URI and creates the correct object for it.

Any read-capable stream can be copied to a write-capable stream. (Files to directories, directories to other directories, etc.) The raw stream resources can be passed to PHP stream functions if desired.

Table of contents

[wf.observer]

The observer pattern is nothing new to you savvy software engineers. It allows for popular objects (read: Subjects), to notify other suck-up objects (read: Observers), of their changes via messages (read: Messages). The whole thing works very much like LiveJournal.

The good thing about this pattern means that an object can publicize its events without any knowledge of the objects it's notifying. Observers opt-in to receive these notifications. Let's say there's an extension to Wildfire called Extension X. There's another called Extension Z. Extension X has a very nifty object that changes values over time. Extension Z developers want to use all that nice code, but not to change it. If Extension X's nifty object implements ISubject, and Extension Z's interested objects implement IObserver and opt-in to messages from the ISubject, it can be tied into events without altering any of Extension X's code.

An object can be both an IObserver and an ISubject. Hell, it can even notify itself of changes.

Table of contents

[wf.web]

The web package borrowed ideas from ASP.NET and from JSP in the handling of responses and requests (and our own spin on things). Basically, there are three classes that will be your new best friends: wfHttpRequest, wfHttpResponse, and wfSession.

wfHttpRequest

This object should be available globally as $request. (It's not a superglobal like $_GET or $_POST, but it's good enough.)

There are three things available on wfHttpRequest of which you should take note.

getQueryString

This is a wfQueryString object. wfQueryString is an IMap containing the $_GET data. You can reference it any of the following ways:

wfQueryString and wfFormData both inherit from the abstract class wfRequestValueData, which provides scrubbing methods.

getForm

This is a wfFormData object. wfFormData functions in every way like wfQueryString, only containing the $_POST data, not $_GET. You can reference it any of the following ways:

getHeaders

This method returns an array containing all of the user-submitted request headers.

wfHttpResponse

This object should be available globally as $response. This class lets you control all response-related output to the client browser including:

wfSession

This object should be available globally as $session. It is a special object that serves two purposes. First off, it is a MutableMap that is directly referenced to the global session array. Secondly, it has two notable methods that you will use profusely.

getClient

This is an instance of wfHttpClient, an object that contains information about a client's IP address, browser software, operating system, and more.

getCookies

This method will return to you a wfCookieCollection containing the cookies for the user. Each wfCookie object can have anything as its value. It is serialized and gzipped when sent to the client for storage. See the wfCookie and wfCookieCollection API for further details.

Table of contents

Copyright © 2005-2006 Team Wildfire. All Rights Reserved.