This document contains all of the class and API documentation, plus selected general documentation for Flourish v0.9.0. The Class Documentation and General Documentation links in the header provide for quick access to the table of contents. Any links preceeded by a small graphical arrow will leave this document.
The fActiveRecord class is an abstract class that follows the active record pattern. It provides an object-oriented interface for creating, retrieving, storing and deleting a single row (or record) in a database. All interaction with the database is done automatically without the need to write any SQL.
In addition to providing an interface to the columns in a single table, data from other database tables related via FOREIGN KEY
constraints can be easily and efficiently retrieved. To query for and return multiple fActiveRecord objects, please see the class fRecordSet.
The following discussion is built on top of the content of ORM Conventions. Topics include database schema structure, various notation standards and information about MySQL and SQLite databases. Please be sure to read over it since it include important information, such as the fact that column and table names must use underscore_notation
by default.
In order to use the fActiveRecord class, a database table must exist to be modeled. Below is an example users tableplease note that the table has been designed to demonstrate the features of the class, not as an example of a well-designed schema.
CREATE TABLE users (
user_id INTEGER PRIMARY KEY AUTOINCREMENT,
email VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
first_name VARCHAR(100) NOT NULL,
middle_initial VARCHAR(5) NOT NULL DEFAULT '',
last_name VARCHAR(100) NOT NULL,
date_created TIMESTAMP NOT NULL,
email_validated BOOLEAN NOT NULL DEFAULT FALSE,
membership_fee DECIMAL(10,2) NOT NULL,
profile TEXT NOT NULL DEFAULT '',
status VARCHAR(20) NOT NULL CHECK(status IN ('Active', 'Inactive'))
);
Once a table has been created, a PHP class will need to be made. The name of the class should be in UpperCamelCase
notation and should be a singular form of the table name. Thus for the users
table a class User
would be created that extends fActiveRecord.
class User extends fActiveRecord
{
protected function configure()
{
}
}
A blank extension of the configure()
method has been included since that is where all class functionality configuration is placed. This method is called exactly once per script execution and is the preferred location to call code to extend or set up the class, but not the individual object.
In addition to the fActiveRecord class and database table, a method to connect to the database needs to be set up. To do this, an instance of the fDatabase class needs to be passed to fORMDatabase::attach() this is commonly done in the sites initilization script.
// Set up a SQLite database for use by fActiveRecord and fRecordSet
fORMDatabase::attach(
new fDatabase('sqlite', '/path/to/database')
);
The fDatabase instance attached to fORMDatabase will also be used by fRecordSet.
It is possible to change the class to table association for modeling an existing incompatible database. The static method fORM::mapClassToTable() will accept the $class
and the $table
to map to the class to.
This method should be called in the site-wide configuration and should not be called in the configure()
method.
// Class to table mapping should occur before any classes are used
// such as when the database is attached via fORMDatabase::attach()
fORMDatabase::attach($db);
fORM::mapClassToTable('User', 'user');
When a class models a table in a non-default schema (public
for PostgreSQL, dbo
for MSSQL and the username for Oracle and DB2), the static method fORM::mapClassToTable() should be called with first parameter the $class
to map, and the second parameter, $table
, should be in the format schema.table
.
This method should be called in the site-wide configuration and should not be called in the configure()
method.
// This maps the User class to the users table in the authorization schema
fORM::mapClassToTable('User', 'authorization.users');
When multiple databases are configured via fORMDatabase, classes can model tables on the non-default
database by calling the method fORM::mapClassToDatabase(). The first parameter is the $class
to map, and the second is the $name
of the database set in fORMDatabase::attach().
// Attach a second database as "commerce_db" and have the User class model the users table in it
fORMDatabase::attach($db, 'commerce_db');
fORM::mapClassToDatabase('User', 'commerce_db');
Like fORM::mapClassToTable(), this method should be called in the site-wide configuration and should not be call in the configure()
method. This method is not required for classes modeling tables in the default database if no $name
was provided to fORMDatabase::attach(), then the database is the default.
A new record can be created by simply creating an object without any parameters.
$new_user_1 = new User();
$new_user_2 = new User();
Existing records can be loaded from the database by passing the primary key value to the constructor. If a primary key has multiple columns, use an associative array with the keys being the columns.
// Loading a record with a single column primary key
$user = new User(2);
// Loading a record with a multiple column primary key
$permission = new Permission(array('user_id' => 2, 'resource_id' => 3));
It is also possible to load a record based on the values from columns in a UNIQUE
constraint. When loading via a UNIQUE
constraint, an associative array must be used, even if there is only a single column in the constraint.
// This loads a user by their unique email address
$user = new User(array('email' => 'will@flourishlib.com'));
Please note that an fActiveRecord object is a reference object. All objects of the same class will share the same data and any operations will affect all instances. Thus if the User
object for user 3
has the first name changed, all other objects representing user 3
will also have the first name changed.
For every column in a record there are at least five different operations that can be performed. ORM plugins can change these default behaviors or add even more.
Action | Description |
get |
Retrieves the columns value |
set |
Sets a new value for the column - empty string '' are converted to NULL |
encode |
Encodes all special HTML characters should be used when content is not trusted or for displaying in HTML tag attributes |
prepare |
Encodes all special HTML characters, but leaves HTML tags and entities unencoded should only be used for trusted content |
inspect |
Returns information about the column, including information such as the data type and valid values |
These five operations are combined with the column name into a camelCase
method name. Below are some examples:
$first_name = $user->getFirstName();
$user->setFirstName($first_name);
echo $user->encodeFirstName();
echo $user->prepareFirstName();
$max_length = $user->inspectFirstName('max_length');
As mentioned on the ORM Conventions page, all columns in the database should be created using underscore_notation
. This assumes that numbers are separated from words by an underscore, such as address_2
. If a number is not separated by an underscore, or you are having other notation conversion issues, you man need to customize the notation conversion using fGrammar.
When dealing with date, time or timestamp columns, the prepare
and encode
methods require a single parameter, $date_formatting_string
. The formatting string can be any valid date()
formatting string, or a format name that was created with fTimestamp::defineFormat().
echo $user->prepareDateCreated('n/j/y');
echo $user->encodeDateCreated('my_date_format');
Floating point columns without an explicit precision require a single parameter, $decimal_places
, when calling the prepare
and encode
methods. Floating point columns with an explicit precision can optionally pass an integer for $decimal_places
.
echo $user->prepareMembershipFee(2);
Columns that are string columns (VARCHAR
, CHAR
and TEXT
) can optionally pass TRUE
to their prepare
and encode
methods to cause all email addresses and website addresses to be converted to HTML links and to cause content without block-level HTML to have newline characters converted to <br />
tags.
echo $user->prepareEmail(TRUE);
echo $user->prepareProfile(TRUE);
Every column has an inspect
method that will return an associative array of data about the column. It is also possible to retrieve a single value by passing the optional parameter, $element
.
$column_info = $user->inspectFirstName();
$max_length = $user->inspectFirstName('max_length');
The fActiveRecord class supports storing both scalar values (strings, integers, booleans, etc) and objects in columns. The only special consideration with storing objects in columns is that they should have a __toString()
method so that they can be converted to a scalar to be saved in the database.
All of the Flourish value objects include such a __toString()
method, making them work perfectly with fActiveRecord. In fact, fActiveRecord even loads date
, time
and timestamp
columns out of the database into fDate, fTime and fTimestamp objects respectively. Nothing needs to be called or configured to enable this functionality. Thus, when getting date
, time
or timestamp
values from a record, be sure to treat them as fDate, fTime and fTimestamp objects.
// Chaining the fDate::format() method off of the get method
echo $user->getDateCreated()->format('n/j/y');
// Objects can be stored in a column as long as they have a __toString() method
$user->setLastLoginTimestamp(new fTimestamp());
The following methods allow manipulation of an active record object:
Method | Description |
store() |
Performs validate() and then executes an INSERT or UDPATE query |
validate() |
Ensures a record can be successfully saved to the database without actually doing it |
delete() |
Deletes a record from the database |
load() |
Reloads a records values from the database |
populate() |
Values from the HTTP request will automatically be set to the various columns of the record |
replicate() / clone |
Creates a copy of the record, cloning all contained objects and removing any auto incrementing primary key, can also replicate related records |
exists() |
Indicates if a record has already been stored in the database |
reflect() |
Returns a string containing the method signature for every method of the record |
The store()
method will ensure that the record can be properly saved in the database and will store it there. If any errors are found, an fValidationException will be thrown with a message that is suitable for display to end users. The validation is performed by store()
calling validate()
.
store()
will also automatically begin a database and filesystem transaction if they are not already in progress. This allows database and filesystem actions to be performed in extending code and child objects without the need to keep track of changes manually and revert them.
try {
$user = new User();
$user->setFirstName('Will');
$user->setLastName('Bond');
$user->store();
} catch (fExpectedException $e) {
echo $e->printMessage();
}
There is a single optional parameter, $force_cascade
, that affects how related records in one-to-many and one-to-one relationships are stored. See the Related Records Operations section for more details about how related records can be accessed and manipulated.
If related records (which we will call child records) have been set via an associate
or populate
method, and one or more of the original child records is no longer associated, and that child records has child records (grandchildren of the original) with an ON DELETE RESTRICT
or ON DELETE NO ACTION
clause in the FOREIGN KEY
constraint, that child and the associated grandchildren records will all be deleted anyway. Normally an exception would be thrown indicating the child to be deleted had a grandchild record referencing it.
By default this parameter is set to FALSE
to obey the restrictions in the database schema.
$user->populateFakeRelatedRecords();
$user->store(TRUE);
This forced cascade effect is accomplished by first finding the related records and explicitly deleting them before the originally associated record.
Validation of a record is performed based on the database schema and any additional validation rules set via the fORMValidation class, ORM plugins and fORM hooks.
The following rules are used to determine if a record is valid to store in the database:
NOT NULL
constraint and does not have a DEFAULT
value, a value other than an empty string must be setFOREIGN KEY
constraint, the value must reference a valid valueUNIQUE
or PRIMARY KEY
contraint, the value must be NULL
or uniqueCHECK
constraint with an IN (...)
expression, the value must be in the list - for MySQL this holds true for ENUM
columnsVARCHAR
or CHAR
column, the value string length must be less than or equal to the size of the columnIf any of these validation checks do not work out, an fValidationException will be thrown contains an error message suitable for end users.
There are many additional validation rules that can be added to a record via fORMValidation. If the desired functionality is not available via fORMValidation, an ORM hook can be used. Please see the Adding Functionality to fActiveRecord and Custom Validation Using a Hook section of the fORM page for details and example code.
Many of the various ORM plugins that come with Flourish (fORMColumn, fORMDate, fORMFile, fORMMoney and fORMOrdering) add additional validation rules.
Since this method is automatically called when executing store()
, it will typically only be called in situations where storing is not possible, such as multi-page forms.
try {
// ...
$user->validate();
} catch (fValidationException $e) {
echo $e->printMessage();
}
It is also possible to return an array of errors by passing TRUE
to the first parameter of validate()
, $return_messages
. This prevents an exception from being thrown.
$errors = $user->validate(TRUE);
The returned array will have keys in the following format with the value being the error message.
,
::
followed by the column name (or column names joined by ,
)[
followed by the zero-based record number, followed by ]
. The value of this key will be an associative array containing two keys, name and errors. The name key will have a user-friendly name for the related record and the errors key will contain an array of error messages for the related record.Below is an example of a returned array:
array(
// Regular columns in the users table
'first_name' => 'First Name: Please enter a value',
'last_name' => 'Last Name: Please enter a value',
// A message involving multiple columns, from fORMValidate::addOneOrMoreRule()
'email,phone' => 'Email, Phone: Please enter at least one',
// A message from fORMValidate::addManyToManyRule()
'groups' => 'Groups: Please select at least one',
// A message from the bio column in the one-to-one related user_details table
'user_details::bio' => 'User Details Bio: Please enter a value',
// Messages from one-to-many related favorites table
'favorites[0]' => array(
'name' => 'Favorite #1',
'errors' => array(
'name' => 'Name: Please enter a value'
)
),
'favorites[2]' => array(
'name' => 'Favorite #3',
'errors' => array(
'name' => 'Name: Please enter a value'
)
)
);
If you are interested in changing the name
of a one-to-many child record, please see the fORMRelated section Overriding Child Record Validation Names.
When passing TRUE
to $return_messages
, it is also possible to pass TRUE
to the second parameter, $remove_column_names
. This will remove the names of the columns from the error messages themselves, leaving the array with the regular keys, but anonymous messages useful for inclusion next to inputs.
$errors = $user->validate(TRUE, TRUE);
The returned array would look like:
array(
// Regular columns in the users table
'first_name' => 'Please enter a value',
'last_name' => 'Please enter a value',
// A message involving multiple columns, from fORMValidate::addOneOrMoreRule()
'email,phone' => 'Please enter at least one',
// A message from fORMValidate::addManyToManyRule()
'groups' => 'Please select at least one',
// A message from the bio column in the one-to-one related user_details table
'user_details::bio' => 'Please enter a value',
// Messages from one-to-many related favorites table
'favorites[0]' => array(
'name' => 'Favorite #1',
'errors' => array(
'name' => 'Please enter a value'
)
),
'favorites[2]' => array(
'name' => 'Favorite #3',
'errors' => array(
'name' => 'Please enter a value'
)
)
);
The delete()
method will remove the record from the database. Obviously, any cascading FOREIGN KEY
constraints could cause other records to be deleted as well. This method will throw an fValidationException if the record is referenced by another record via a FOREIGN KEY
constraint with an ON DELETE RESTRICT
or ON DELETE NO ACTION
clause.
try {
$user->delete();
} catch (fValidationException $e) {
echo $e->printMessage();
}
There is an optional parameter, $force_cascade
, that when set to TRUE
will delete all records that reference the record being deleted, even if they have an ON DELETE RESTRICT
or ON DELETE NO ACTION
clause in the FOREIGN KEY
constraint. By default this is set to FALSE
to obey the restrictions in the database schema.
$user->delete(TRUE);
This is accomplished by first finding the related records and explicitly deleting them before the original record.
The load()
method causes the values for the record to be reloaded from the database, overwriting any values that have been changed since the object was first loaded.
$user = new User(3);
$user->setFirstName('Joe');
// Reset the first name
$user->load();
The populate()
method sets the values for a record from the fields and value contained in an HTTP request. For values to be pulled from the request, the HTML field names should be exactly the same as the database column names. Values from the form that are a blank strings are automatically converted to NULL
.
The following HTML form when combined with the populate()
method would cause the first_name
, last_name
and email
column values to be set:
<form action="" method="post">
<fieldset>
<p>
<label for="first_name">First Name</label>
<input type="text" id="first_name" name="first_name" />
</p>
<p>
<label for="last_name">Last Name</label>
<input type="text" id="last_name" name="last_name" />
</p>
<p>
<label for="email">Email</label>
<input type="text" id="email" name="email" />
</p>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
</form>
try {
$user = new User();
// This will set first_name, last_name and email
$user->populate();
$user->store();
} catch (fExpectedException $e) {
echo $e->printMessage();
}
By default, populate()
calls the individual set
methods for each column in a record. This ensures that overridden methods are correctly called. The set
methods in fActiveRecord will by default convert an empty string value ''
to NULL
. This treats empty HTML form input as if the user entered nothing.
Blank strings can be stored in a database columninstead of NULL
by setting the column to NOT NULL
and DEFAULT ''
. When fActiveRecord finds a NOT NULL
column with a NULL
value and a non-NULL
default, the default is substituted in place of the NULL
.
When using checkboxes in forms to be populated, it is best to preceed the checkbox input with a hidden input using the same name but a FALSE
value. This way, if the checkbox is unchecked, PHP gets the value from the hidden input, changing the column to a FALSE
value. When the checkbox is checked, PHP gets the value from the checkbox input, changing the column to a TRUE
value.
<form action="" method="post">
<fieldset>
<p>
<input type="hidden" name="enroll_me" value="0" />
<label for="enroll_me">Enroll Me</label>
<input type="checkbox" id="enroll_me" name="enroll_me" value="1" />
</p>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
</form>
Please note that there IS a difference between setting the hidden input to a blank string value and a FALSE
value such as 0
. As mentioned in the previous section, empty strings are converted to NULL
, whereas a value such as 0
or false
would not be.
Certain fORMValidation methods check to see if a column has a non-null value. If an empty string is used for the hidden input, the validation method would see that no value has been selected. If a non-null false value is used, the validation method would see that a non-null value has been selected. Depending on the requirements of the application, this distinction may be very important.
The replicate()
method, passed no parameters is equivalent to a record being cloned. When an fActiveRecord object is cloned, all values and cache entries are copied (or cloned if the value is an object), all related records and old values are purged, any auto incrementing primary keys are erased and the record is changed to look as though it does not yet exist in the database.
$user = new User(1);
// Both $user2 and $user3 will have no $user_id and will return FALSE from ->exists()
$user2 = clone $user;
$user3 = $user->replicate();
It is also possible to replicate related records along with a record by passing plural class names into replicate()
. There may be any number of class names and the classes must be for related records that are in a many-to-many or one-to-many relationship with the record. Below is an example of replicating a user and including all of their group memberships and favorites (see Related Records Operations for the database schema).
$user = new User(1);
$new_user = $user->replicate('Groups', 'Favorites');
If a record is has more than one relationship route to the related record class, the route should be specified between curly braces ({
and }
), like below:
$user = new User(1);
$new_user = $user->replicate('Resources{read_permissions}');
The exists()
method simply returns a boolean indicating if the record was loaded from the database.
if (!$user->exists()) {
$user->sendWelcomeEmail();
}
This function is mostly useful when dealing with an object in a different scope than that which it was created. For instance, checking a record that has been passed into a function or an fActiveRecord hook callback.
Please note that this only checks to see if the object was constructed by passing a primary or unique key into the constructor. It can not be used with set
methods to check and see if a record exists in the database.
The following code will NOT check to see if a user with the id 3 exists in the database. This call to exists will always return FALSE
since the object was constructed without any parameters.
$user = new User();
$user->setUserId(3);
if ($user->exists()) {
//
}
The proper way to check if a record exists in the database is to try and create an instance, catching an fNotFoundException:
try {
$user = new User(3);
} catch (fNotFoundException $e) {
//
}
From a technical perspective, for the first example to work, fActiveRecord object would have to have a mutable identity, meaning the object would change what row it represented over its lifetime. This would likely cause many other unintended side effect in common code.
Since fActiveRecord classes have dynamic functionality based on the structure of a database table, sometimes it can be useful to check and see what exactly is available for a specific class. The reflect()
method returns a preformatted text block containing the method signature for every method, concrete and dynamic, in the class.
echo '<pre>' . $user->reflect() . '</pre>';
would output something like
public function getFirstName();
public function setFirstName($first_name);
public function prepareFirstName();
public function encodeFirstName();
public function inspectFirstName($element=NULL);
It is also possible to pass a TRUE
parameter to reflect()
to also return the PHPDoc doc block for a method.
// Request the PHPDoc doc block too
echo $user->reflect(TRUE);
One of the useful features of fActiveRecord is that it automatically finds all related tables in a database schema by looking at FOREIGN KEY
constraints. Absolutely no configuration is needed in the class.
The following tables definitions will be used for the examples below and are purposefully simple to help focus on the features, not the SQL.
CREATE TABLE groups (
name VARCHAR(100) PRIMARY KEY
);
CREATE TABLE users (
user_id SERIAL PRIMARY KEY,
email VARCHAR(100) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL
);
CREATE TABLE user_details (
user_id INTEGER PRIMARY KEY REFERENCES users(user_id) ON DELETE CASCADE,
photo VARCHAR(255) NOT NULL
);
CREATE TABLE users_groups (
group VARCHAR(100) NOT NULL REFERENCES groups(name) ON DELETE CASCADE,
user_id INTEGER NOT NULL REFERENCES users(user_id) ON DELETE CASCADE,
PRIMARY KEY (group, user_id)
);
CREATE TABLE favorites (
favorite_id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(user_id) ON DELETE CASCADE,
url VARCHAR(255) NOT NULL
);
CREATE TABLE resources (
resource_id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
owner INTEGER NOT NULL REFERENCES users(user_id) ON DELETE RESTRICT
);
CREATE TABLE read_permissions (
resource_id INTEGER NOT NULL REFERENCES resources(resource_id) ON DELETE CASCADE,
user_id INTEGER NOT NULL REFERENCES users(user_id) ON DELETE CASCADE,
PRIMARY KEY (resource_id, user_id)
);
For the sake of the examples below, assume that the following classes have been defined to extend fActiveRecord:
Favorite
Group
Resource
User
UserDetail
Where there are multiple relationships between two table, such as from users
to resources
, fActiveRecord contains optional parameters for all related record methods that allows the proper relationship route to be specified. For details, please see the Relationship Routes section.
The detection of related tables allows for the following functionality to be provided:
When a column in a table contains a FOREIGN KEY
constraint to another table, the two tables are in either a many-to-one or one-to-one relationship. The relationship would only be one-to-one if there was a UNIQUE
constraint on the column with the FOREIGN KEY
.
For instance, the resources
table references the users
table via the owner
column. Because this FOREIGN KEY
exists, it is possible to call a create
action to instantiate the related User
object if one exists, or an empty User
object if none exists.
$resource = new Resource(1);
$user = $resource->createUser();
See Method Naming for info about create
and other method verbs.
Since the user_details
table references the user_id
as a PRIMARY KEY
, there can only be a single user_details
record for each entry in users
. This makes it a one-to-one relationship. For one-to-one relationships, it is also possible to call the populate
and has
actions just like *-to-many relationships. When working with one-to-one relationships, the related record name is singular rather than plural.
$user = new User(1);
$user->populateUserDetail();
if ($user->hasUserDetail()) {
//
}
When a column is referenced by a FOREIGN KEY
constraint in another table, the two tables involved will end up being in either a one-to-many or many-to-many relationship. Many-to-many relationships happen when a joining table is used and the FOREIGN KEY
constraints live in the joining table.
For the examples below the one-to-many relationship between the users
table and the favorites
table will be used along with the many-to-many relationship between the users
and groups
tables.
Each user on the system can have multiple favorites simply by creating new favorites and having each one reference the same user. The build
action will create an fRecordSet of all records in such a relationship:
$favorites = $user->buildFavorites();
The $favorites
record set may be empty, or it may contain quite a number of records.
In situations where the related records dont need to be created, but a primary key will suffice, it is also possible to list
related records. This will return an array of related primary keys.
$favorite_ids = $user->listFavorites();
The count
action can be called for any related record and it will return the number of related records.
$number_of_favorites = $user->countFavorites();
If a number of records is not required, but just that related records exist, the has
action can be used.
if ($user->hasFavorites()) {
//
}
Both one-to-many and many-to-many relationships support the build
, count
, list
and has
actions.
In many-to-many relationships, both types of records can exist without directly referencing each other. Thus often it is necessary to take one set of records and associate it with a specific record. The associate
action will do this, such as below where a user is being associated with every group.
$groups = fRecordSet::build('Group');
$user->associateGroups($groups);
$user->store();
associate
methods will accept an fRecordSet, an array of fActiveRecord objects or an array of primary keys. It is also possible to call associate
methods to associate records in a one-to-many relationship, however the link
action discussed next only works with many-to-many relationships.
It is also possible to parse associations from the fields in an HTTP request. In that situation the HTML form fields must be named in the format {plural_underscore_related_class}::{foreign_column}[]
. The link
action will grab values from the HTTP request and use them for associating records in a many-to-many relationship.
<form action="" method="post">
<fieldset>
<p>
<label for="first_name">First Name</label>
<input type="text" id="first_name" name="first_name" />
</p>
<p>
<label for="last_name">Last Name</label>
<input type="text" id="last_name" name="last_name" />
</p>
<p>
<label for="email">Email</label>
<input type="text" id="email" name="email" />
</p>
<p>
<label for="group_a">Group A</label>
<input type="checkbox" id="group_a" name="groups::name[]" value="Group A" />
</p>
<p>
<label for="group_b">Group B</label>
<input type="checkbox" id="group_b" name="groups::name[]" value="Group B" />
</p>
<p>
<label for="group_c">Group C</label>
<input type="checkbox" id="group_c" name="groups::name[]" value="Group C" />
</p>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
</form>
$user->linkGroups();
$user->store();
Please note that store()
must be called to actually save the new relationships between the two tables.
For records that have "child" object in one-to-many relationships, the populate
action will call the populate()
method for each of a list of related records. In addition, when store()
is called on the master record, all of the child records will be saved too.
Below is an example of the kind of HTML form that is needed for creating and populating child objects. Normally the HTML for the child object would be added and removed from the page on the fly using javascript, otherwise the validate()
method from a child object could stop the records from being saved when a child objects values are not complete. The initial printing of the HTML form elements is normally handled server side by iterating trough the fRecordSet of related records.
Each set of inputs for child object should always contain the PRIMARY KEY
column and the column with the FOREIGN KEY
constraint that references this table. Any other columns should only be included if new data is desired.
Each input for a related record needs to be prefixed with the plural underscore_notation
version of the class name plus ::
. In addition, each input should use array syntax at the end with a key shared for all inputs of the same record. The key can be any number or string, but must be the same for each input of the record. The example below uses 0
, 1
and 2
as a simple example.
<form action="" method="post">
<fieldset>
<p>
<label for="first_name">First Name</label>
<input type="text" id="first_name" name="first_name" />
</p>
<p>
<label for="last_name">Last Name</label>
<input type="text" id="last_name" name="last_name" />
</p>
<p>
<label for="email">Email</label>
<input type="text" id="email" name="email" />
</p>
<p>
<label for="fav_1">Favorite #1</label>
<input type="name" id="fav_1" name="favorites::url[0]" value="" />
<input type="hidden" name="favorites::user_id[0]" value="2" />
<input type="hidden" name="favorites::favorite_id[0]" value="" />
</p>
<p>
<label for="fav_2">Favorite #2</label>
<input type="checkbox" id="fav_2" name="favorites::url[1]" value="" />
<input type="hidden" name="favorites::user_id[1]" value="2" />
<input type="hidden" name="favorites::favorite_id[1]" value="" />
</p>
<p>
<label for="fav_3">Favorite #3</label>
<input type="checkbox" id="fav_3" name="favorites::url[2]" value="C" />
<input type="hidden" name="favorites::user_id[2]" value="2" />
<input type="hidden" name="favorites::favorite_id[2]" value="" />
</p>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
</form>
The following PHP would actually create 3 Favorite
objects and would set them to be saved when store()
is called on the User
object.
try {
$user->populate();
$user->populateFavorites();
$user->store();
} catch (fExpectedException $e) {
echo $e->printMessage();
}
When there are multiple one-to-many, many-to-many or *-to-one relationships for two tables, the proper route must be specified when calling the various related record methods. The appropriate route name can be determined by viewing the Relationship Routes section of the ORM Conventions page.
Routes can be specified in any of the following methods even if only one route exists, however that route will be automatically detected if not specified.
The build
, count
, create
, link
, list
and populate
action methods all optionally accept the route as the first parameter.
$record->createRelatedRecord('route');
$record->buildRelatedRecords('route');
$record->countRelatedRecords('route');
$record->linkRelatedRecords('route');
$record->listRelatedRecords('route');
$record->populateRelatedRecords('route');
associate
action methods optionally accept the route as the second parameter.
$record->associateRelatedRecords($related_records, 'route');
When working with relationship routes in HTML forms, the relationship route name should be enclosed in {}
directly after the foreign table name. This applies to forms being used with both the link
and populate
actions. Below is an example of a form for selecting the resources related to a user:
<p>
<input type="checkbox" id="resource_1" name="resources{read_permissions}::resource_id[]" value="1" />
<label for="resource_1">Resource 1</label>
</p>
<p>
<input type="checkbox" id="resource_2" name="resources{read_permissions}::resource_id[]" value="2" />
<label for="resource_2">Resource 2</label>
</p>
As mentioned in the Setup section, the instance method configure()
is called once per script execution for each classes that extends fActiveRecord. This method is designed to be the preferred place to execute any configuration code for an active record class.
class User extends fActiveRecord
{
protected function configure()
{
// Configure the extra feature and overrides using the supporting ORM classes
}
}
The following classes provide methods that extends and change the way that fActiveRecord classes work. Each classs documentation page will include the necessary details on how to configure each bit of functionality and how it affects the standard use of active record classes.
fORM | Provides functionality to override database table to class mapping, change the names used for records and column and extends fActiveRecord and fRecordSet through registering callbacks for various hooks |
fORMColumn | Allows changing the validation of columns to require email addresses or links, can configure columns to always create fNumber objects when loaded or have a column filled with a random string the first time a record is saved |
fORMDate | Can set columns to automatically save the date a record was created or last updated and can tie a timestamp column to another column to allow for saving timezones |
fORMFile | Provides functionality to automatically handle file or image uploads, including options to automatically duplicate and manipulate images |
fORMJSON | Extends both fActiveRecord and fRecordSet to have toJSON() methods |
fORMMoney | Can set columns to be loaded out of the database as fMoney objects and can tie fMoney columns to a second column to store currencies |
fORMOrdering | Allows configuring a column (either individually or in a group of columns) as the arbitrary ordering column for a class |
fORMRelated | Provides functionality to set the order in which related records are returned or modify what they are called (in context) |
fORMValidation | Allows setting additional validation rules (including conditional, one-or-more, many-to-many, etc), set the order for validation messages and configure UNIQUE constraints as case-insensitive |
An active record pattern base class
This class uses fORMSchema to inspect your database and provides an OO interface to a single database table. The class dynamically handles method calls for getting, setting and other operations on columns. It also dynamically handles retrieving and storing related records.
1.0.0b81 | Fixed a bug with updating a record that contains only an auto-incrementing primary key 9/6/11 |
---|---|
1.0.0b80 | Added support to checkCondition() for the ^~ and $~ operators 6/20/11 |
1.0.0b79 | Fixed some bugs in handling relationships between PHP 5.3 namespaced classes 5/26/11 |
1.0.0b78 | Backwards Compatibility Break - reflect() now returns an associative array instead of a string 5/10/11 |
1.0.0b77 | Fixed inspect() to not throw an fProgrammerException when a valid element has a NULL value 5/10/11 |
1.0.0b76 | Added clearIdentityMap() 5/9/11 |
1.0.0b75 | Fixed a bug where child records of a record with a non-auto-incrementing primary key would not be saved properly for a new record 12/6/10 |
1.0.0b74 | Updated populate() to use the binary type for fRequest::get() 11/30/10 |
1.0.0b73 | Backwards Compatibility Break - changed column set methods to treat strings of all whitespace the same as empty string and convert them to NULL 11/29/10 |
1.0.0b72 | Added the new comment element to the reflection signature for inspect methods 11/28/10 |
1.0.0b71 | Updated class to use fORM::getRelatedClass() 11/24/10 |
1.0.0b70 | Added support for PHP 5.3 namespaced fActiveRecord classes 11/11/10 |
1.0.0b69 | Backwards Compatibility Break - changed validate() to return a nested array of validation messages when there are validation errors on child records 10/3/10 |
1.0.0b68 | Added hooks to replicate() 9/7/10 |
1.0.0b67 | Updated code to work with the new fORM API 8/6/10 |
1.0.0b66 | Fixed a bug with store() and non-primary key auto-incrementing columns 7/5/10 |
1.0.0b65 | Fixed bugs with inspect() making some min_value and max_value elements available for non-numeric types, fixed reflect() to list the min_value and max_value elements 6/8/10 |
1.0.0b64 | BackwardsCompatibilityBreak - changed validate()'s returned messages array to have field name keys - added the option to validate() to remove field names from messages 5/26/10 |
1.0.0b63 | Changed how is_subclass_of() is used to work around a bug in 5.2.x 4/6/10 |
1.0.0b62 | Fixed a bug that could cause infinite recursion starting in v1.0.0b60 4/2/10 |
1.0.0b61 | Fixed issues with handling populate actions when working with mapped classes 3/31/10 |
1.0.0b60 | Fixed issues with handling associate and has actions when working with mapped classes, added validateClass() 3/30/10 |
1.0.0b59 | Changed an extended UTF-8 arrow character into the correct -> 3/29/10 |
1.0.0b58 | Fixed reflect() to specify the value returned from set methods 3/15/10 |
1.0.0b57 | Added the post::loadFromIdentityMap() hook and fixed __construct() to always call the post::__construct() hook 3/14/10 |
1.0.0b56 | Fixed $force_cascade in delete() to work even when the restricted relationship is once-removed through an unrestricted relationship 3/9/10 |
1.0.0b55 | Fixed load() to that related records are cleared, requiring them to be loaded from the database 3/4/10 |
1.0.0b54 | Fixed detection of route name for one-to-one relationships in delete() 3/3/10 |
1.0.0b53 | Fixed a bug where related records with a primary key that contained a foreign key with an on update cascade clause would be deleted when changing the value of the column referenced by the foreign key 12/17/09 |
1.0.0b52 | Backwards Compatibility Break - Added the $force_cascade parameter to delete() and store() - enabled calling prepare() and encode() for non-column get methods, added ::has{RelatedRecords}() methods 12/16/09 |
1.0.0b51 | Made changed() properly recognize that a blank string and NULL are equivalent due to the way that set() casts values 11/14/09 |
1.0.0b50 | Fixed a bug with trying to load by a multi-column primary key where one of the columns was not specified 11/13/09 |
1.0.0b49 | Fixed a bug affecting where conditions with columns that are not null but have a default value 11/3/09 |
1.0.0b48 | Updated code for the new fORMDatabase and fORMSchema APIs 10/28/09 |
1.0.0b47 | Changed ::associate{RelatedRecords}(), ::link{RelatedRecords}() and ::populate{RelatedRecords}() to allow for method chaining 10/22/09 |
1.0.0b46 | Changed SQL statements to use value placeholders and identifier escaping 10/22/09 |
1.0.0b45 | Added support for !~, &~, >< and OR comparisons to checkConditions(), made object handling in checkConditions() more robust 9/21/09 |
1.0.0b44 | Updated code for new fValidationException API 9/18/09 |
1.0.0b43 | Updated code for new fRecordSet API 9/16/09 |
1.0.0b42 | Corrected a grammar bug in hash() 9/9/09 |
1.0.0b41 | Fixed a bug in the last version that would cause issues with classes containing a custom class to table mapping 9/1/09 |
1.0.0b40 | Added a check to the configuration part of __construct() to ensure modelled tables have primary keys 8/26/09 |
1.0.0b39 | Changed set{ColumnName}() methods to return the record for method chaining, fixed a bug with loading by multi-column unique constraints, fixed a bug with load() 8/26/09 |
1.0.0b38 | Updated changed() to do a strict comparison when at least one value is NULL 8/17/09 |
1.0.0b37 | Changed __construct() to allow any Iterator object instead of just fResult 8/12/09 |
1.0.0b36 | Fixed a bug with setting NULL values from v1.0.0b33 8/10/09 |
1.0.0b35 | Fixed a bug with unescaping data in loadFromResult() from v1.0.0b33 8/10/09 |
1.0.0b34 | Added the ability to compare fActiveRecord objects in checkConditions() 8/7/09 |
1.0.0b33 | Performance enhancements to __call() and __construct() 8/7/09 |
1.0.0b32 | Changed delete() to remove auto-incrementing primary keys after the post::delete() hook 7/29/09 |
1.0.0b31 | Fixed a bug with loading a record by a multi-column primary key, fixed one-to-one relationship API 7/21/09 |
1.0.0b30 | Updated reflect() for new fORM::callReflectCallbacks() API 7/13/09 |
1.0.0b29 | Updated to use new fORM::callInspectCallbacks() method 7/13/09 |
1.0.0b28 | Fixed a bug where records would break the identity map at the end of store() 7/9/09 |
1.0.0b27 | Changed hash() from a protected method to a static public/internal method that requires the class name for non-fActiveRecord values 7/9/09 |
1.0.0b26 | Added checkConditions() from fRecordSet 7/8/09 |
1.0.0b25 | Updated validate() to use new fORMValidation API, including new message search/replace functionality 7/1/09 |
1.0.0b24 | Changed validate() to remove duplicate validation messages 6/30/09 |
1.0.0b23 | Updated code for new fORMValidation::validateRelated() API 6/26/09 |
1.0.0b22 | Added support for the $formatting parameter to encode methods on char, text and varchar columns 6/19/09 |
1.0.0b21 | Performance tweaks and updates for fORM and fORMRelated API changes 6/15/09 |
1.0.0b20 | Changed replacement values in preg_replace() calls to be properly escaped 6/11/09 |
1.0.0b19 | Added list{RelatedRecords}() methods, updated code for new fORMRelated API 6/2/09 |
1.0.0b18 | Changed store() to use new fORMRelated::store() method 6/2/09 |
1.0.0b17 | Added some missing parameter information to reflect() 6/1/09 |
1.0.0b16 | Fixed bugs in __clone() and replicate() related to recursive relationships 5/20/09 |
1.0.0b15 | Fixed an incorrect variable reference in store() 5/6/09 |
1.0.0b14 | store() no longer tries to get an auto-incrementing ID from the database if a value was set 5/2/09 |
1.0.0b13 | delete(), load(), populate() and store() now return the record to allow for method chaining 3/23/09 |
1.0.0b12 | set() now removes commas from integers and floats to prevent validation issues 3/22/09 |
1.0.0b11 | encode() no longer adds commas to floats 3/22/09 |
1.0.0b10 | __wakeup() no longer registers the record as the definitive copy in the identity map 3/22/09 |
1.0.0b9 | Changed __construct() to populate database default values when a non-existing record is instantiated 1/12/09 |
1.0.0b8 | Fixed exists() to properly detect cases when an existing record has one or more NULL values in the primary key 1/11/09 |
1.0.0b7 | Fixed __construct() to not trigger the post::__construct() hook when force-configured 12/30/08 |
1.0.0b6 | __construct() now accepts an associative array matching any unique key or primary key, fixed the post::__construct() hook to be called once for each record 12/26/08 |
1.0.0b5 | Fixed replicate() to use plural record names for related records 12/12/08 |
1.0.0b4 | Added replicate() to allow cloning along with related records 12/12/08 |
1.0.0b3 | Changed __clone() to clone objects contains in the values and cache arrays 12/11/08 |
1.0.0b2 | Added the __clone() method to properly duplicate a record 12/4/08 |
1.0.0b | The initial implementation 8/4/07 |
Caches callbacks for methods
array
An array of flags indicating a class has been configured
array
Maps objects via their primary key
array
Caches method name parsings
array
Keeps track of the recursive call level of replication so we can clear the map
integer
Keeps a list of records that have been replicated
array
Contains a list of what columns in each class need to be unescaped and what data type they are
array
A data store for caching data related to a record, the structure of this is completely up to the developer using it
array
The old values for this record
Column names are the keys, but a column key will only be present if a value has changed. The value associated with each key is an array of old values with the first entry being the oldest value. The static methods assign(), changed(), hasOld() and retrieveOld() are the best way to interact with this array.
array
Records that are related to the current record via some relationship
This array is used to cache related records so that a database query is not required each time related records are accessed. The fORMRelated class handles most of the interaction with this array.
array
The values for this record
This array always contains every column in the database table as a key with the value being the current value.
array
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Sets a value to the $values array, preserving the old value in $old_values
void assign( array &$values, array &$old_values, string $column, mixed $value )
array | &$values | The current values |
array | &$old_values | The old values |
string | $column | The column to set |
mixed | $value | The value to set |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Checks to see if a value has changed
boolean changed( array &$values, array &$old_values, string $column )
array | &$values | The current values |
array | &$old_values | The old values |
string | $column | The column to check |
If the value for the column specified has changed
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Ensures a class extends fActiveRecord
boolean checkClass( string $class )
string | $class | The class to check |
If the class is an fActiveRecord descendant
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Checks to see if a record matches all of the conditions
boolean checkConditions( fActiveRecord $record, array $conditions )
fActiveRecord | $record | The record to check |
array | $conditions | The conditions to check - see fRecordSet::filter() for format details |
If the record meets all conditions
Clears the identity map
void clearIdentityMap( )
Composes text using fText if loaded
string compose( string $message, mixed $component [, ... ] )
string | $message | The message to compose |
mixed | $component [, ... ] | A string or number to insert into the message |
The composed and possible translated message
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Ensures that configure() has been called for the class
void forceConfigure( string $class )
string | $class | The class to configure |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Takes a row of data or a primary key and makes a hash from the primary key
string|NULL hash( fActiveRecord|array|string|int $record, string $class=NULL )
fActiveRecord|array|string|int | $record | An fActiveRecord object, an array of the records data, an array of primary key data or a scalar primary key value |
string | $class | The class name, if $record isn't an fActiveRecord |
A hash of the record's primary key value or NULL if the record doesn't exist yet
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Checks to see if an old value exists for a column
boolean hasOld( array &$old_values, string $column )
array | &$old_values | The old values |
string | $column | The column to set |
If an old value for that column exists
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Resets the configuration of the class
void reset( )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Retrieves the oldest value for a column or all old values
mixed retrieveOld( array &$old_values, string $column, mixed $default=NULL, boolean $return_all=FALSE )
array | &$old_values | The old values |
string | $column | The column to get |
mixed | $default | The default value to return if no value exists |
boolean | $return_all | Return the array of all old values for this column instead of just the oldest |
The old value for the column
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Ensures a class extends fActiveRecord
void validateClass( string $class )
string | $class | The class to verify |
Creates a new record or loads one from the database - if a primary key or unique key is provided the record will be loaded
fActiveRecord __construct( mixed $key=NULL )
mixed | $key | The primary key or unique key value(s) - single column primary keys will accept a scalar value, all others must be an associative array of (string) {column} => (mixed) {value} |
Handles all method calls for columns, related records and hook callbacks
Dynamically handles get, set, prepare, encode and inspect methods for each column in this record. Method names are in the form verbColumName().
This method also handles associate, build, count, has, and link verbs for records in many-to-many relationships; build, count, has and populate verbs for all related records in one-to-many relationships and create, has and populate verbs for all related records in one-to-one relationships, and the create verb for all related records in many-to-one relationships.
Method callbacks registered through fORM::registerActiveRecordMethod() will be delegated via this method.
mixed __call( string $method_name, array $parameters )
string | $method_name | The name of the method called |
array | $parameters | The parameters passed |
The value returned by the method called
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Creates a clone of a record
If the record has an auto incrementing primary key, the primary key will be erased in the clone. If the primary key is not auto incrementing, the primary key will be left as-is in the clone. In either situation the clone will return FALSE from the exists() method until store() is called.
fActiveRecord __clone( )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
All requests that hit this method should be requests for callbacks
callback __get( string $method )
string | $method | The method to create a callback for |
The callback for the method requested
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Configure itself when coming out of the session. Records from the session are NOT hooked into the identity map.
void __wakeup( )
Allows the programmer to set features for the class
This method is only called once per page load for each class.
void configure( )
Creates the fDatabase::translatedQuery() insert statement params
array constructInsertParams( )
The parameters for an fDatabase::translatedQuery() SQL insert statement
Creates the fDatabase::translatedQuery() update statement params
array constructUpdateParams( )
The parameters for an fDatabase::translatedQuery() SQL update statement
Deletes a record from the database, but does not destroy the object
This method will start a database transaction if one is not already active.
fActiveRecord delete( boolean $force_cascade=FALSE )
boolean | $force_cascade | When TRUE, this will cause all child objects to be deleted, even if the ON DELETE clause is RESTRICT or NO ACTION |
The record object, to allow for method chaining
Retrieves a value from the record and prepares it for output into an HTML form element.
Below are the transformations performed:
string encode( string $column, string $formatting=NULL )
string | $column | The name of the column to retrieve |
string | $formatting | The formatting string |
The encoded value for the column specified
Checks to see if the record exists in the database
boolean exists( )
If the record exists in the database
Loads a record from the database based on a UNIQUE key
void fetchResultFromUniqueKey( array $values )
array | $values | The UNIQUE key values to try and load with |
Retrieves a value from the record
mixed get( string $column )
string | $column | The name of the column to retrieve |
The value for the column specified
Retrieves information about a column
mixed inspect( string $column, string $element=NULL )
string | $column | The name of the column to inspect |
string | $element | The metadata element to retrieve |
The metadata array for the column, or the metadata element specified
Loads a record from the database
fActiveRecord load( )
The record object, to allow for method chaining
Tries to load the object (via references to class vars) from the fORM identity map
boolean loadFromIdentityMap( array $row, string $hash )
array | $row | The data source for the primary key values |
string | $hash | The unique hash for this record |
If the load was successful
Loads a record from the database directly from a result object
boolean loadFromResult( Iterator $result, boolean $ignore_identity_map=FALSE )
Iterator | $result | The result object to use for loading the current object |
boolean | $ignore_identity_map | If the identity map should be ignored and the values loaded no matter what |
If the record was loaded from the identity map
Sets the values for this record by getting values from the request through the fRequest class
fActiveRecord populate( )
The record object, to allow for method chaining
Retrieves a value from the record and prepares it for output into html.
Below are the transformations performed:
string prepare( string $column, mixed $formatting=NULL )
string | $column | The name of the column to retrieve |
mixed | $formatting | The formatting parameter, if applicable |
The formatted value for the column specified
Generates the method signatures for all methods (including dynamic ones)
array reflect( boolean $include_doc_comments=FALSE )
boolean | $include_doc_comments | If the doc block comments for each method should be included |
An associative array of method name => method signature
Generates a clone of the current record, removing any auto incremented primary key value and allowing for replicating related records
This method will accept three different sets of parameters:
The class names specified can be a simple class name if there is only a single route between the two corresponding database tables. If there is more than one route between the two tables, the class name should be substituted with a string in the format 'RelatedClass{route}'.
fActiveRecord replicate( string $related_class=NULL [, ... ] )
string | $related_class [, ... ] | The plural related class to replicate - see method description for details |
The cloned record
Sets a value to the record
fActiveRecord set( string $column, mixed $value )
string | $column | The column to set the value to |
mixed | $value | The value to set |
This record, to allow for method chaining
Stores a record in the database, whether existing or new
This method will start database and filesystem transactions if they have not already been started.
fActiveRecord store( boolean $force_cascade=FALSE )
boolean | $force_cascade | When storing related records, this will force deleting child records even if they have their own children in a relationship with an RESTRICT or NO ACTION for the ON DELETE clause |
The record object, to allow for method chaining
Validates the values of the record against the database and any additional validation rules
void|array validate( boolean $return_messages=FALSE, boolean $remove_column_names=FALSE )
boolean | $return_messages | If an array of validation messages should be returned instead of an exception being thrown |
boolean | $remove_column_names | If column names should be removed from the returned messages, leaving just the message itself |
If $return_messages is TRUE, an array of validation messages will be returned
The fAuthorization class is a static class provides functionality to restrict access to pages based on either simple a authorization level or more complex access control lists (ACLs).
Since the class is static, no instantiation is required, however to use the features some setup will need to be performed. The only setup common to using either authorization levels or ACLs is to set up a login page. For maintainability, I recommend you perform your setup in a common configuration like described on the Getting Started page:
// Set up a login page in your local.config.php
fAuthorization::setLoginPage('/login/');
The login page URL should be an absolute URL, relative to the domain name.
If need be, the login page URL can be retrieved with the static method getLoginPage()
.
fURL::redirect(fAuthorization::getLoginPage());
The simplest way to control access to pages is to use authorization levels. Each user is assigned a single authorization level and can view any page that requires that level or a level below.
After you have setup your login page, you are going to need to define the different authorization levels. Just like with the login page, you will probably want to place these in a common configuration file. Here is a simple example:
fAuthorization::setAuthLevels(
array(
'admin' => 100,
'user' => 50,
'guest' => 25
)
);
Youll notice that each authorization level is assigned a number. If a user has a number that is the same or above the required level, they can view a page. If not, they will be redirected to the login page.
The setUserAuthLevel()
method provides the functionality to assign an authorization level to a user when they log in:
// This would be executed after the username and password were verified
fAuthorization::setUserAuthLevel('admin');
To actually require an authorization level you will need to call requireAuthLevel()
at the top of your page:
fAuthorization::requireAuthLevel('user');
If you wish to use a users authorization level to control other aspects of your site, you can use the checkAuthLevel()
method:
if (fAuthorization::checkAuthLevel('admin')) {
// Execute admin specific code
}
Last, but not least, if you need to get the users authorization level, that can be accomplished by calling getUserAuthLevel()
:
$auth_level = fAuthorization::getUserAuthLevel();
Access control lists (ACLs) allow for more fine-grained permissions than authorization levels. With ACLs you associate resource names with specific permissions. For a user to be able to access a page they need to have the required permission for the resource specified.
ACLs do not require any setup beyond assigning the users ACLs when they log in. Also note that the string '*'
acts as a wildcard when doing resource and permission comparisons.
// This would be executed after the username and password were verified
fAuthorization::setUserACLs(
array(
'posts' => array('*'),
'users' => array('add', 'edit', 'delete'),
'groups' => array('add'),
'*' => array('list')
)
);
The above user ACLs would imply the user has permissions to: do anything with posts; add, edit and delete users; add groups and list anything.
To require a user have a specific ACL to view a page, use the method requireACL()
at the top of a page:
fAuthorization::requireACL('users', 'list');
The above code would require a user to have the list
permission for the users
resource in order to view the page.
In addition to control page views, ACLs can be useful for controlling access on a smaller level. If you wish to perform a conditional branch based on a users ACLs you can use the checkACL()
method:
if (fAuthorization::checkACL('users', 'edit')) {
// Code to be executed for users who can edit users
}
If you need to get a list of all ACLs assigned to the current user (in the same array format they are set), you can use the getUserACLs()
method:
$user_acls = fAuthorization::getUserACLs();
Once you have decided if you wish to go with authorization levels or ACLs you can move on to actually logging the user in.
Sometimes when a user visits the login page, they will have entered the URL manually, or will have followed a link. In this sort of situation you will need a default page to redirect them to. The rest of the time users will usually get directed to the login page because they tried to access a restricted page. You can get this information with the getRequestedURL()
method.
getRequestedURL()
requires a single parameter, $clear
, which controls if the requested URL is erased when returned, or if it is to be left for later access. A second, optional, parameter is the default URL to use if the user was not redirected to the login page.
Here is an example of logging a user in and redirecting them to the requested page (or the home page if no page was requested):
if ('login' == $action && fRequest::isPost()) {
if ($email == 'john@example.com' && sha1($password) == '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8') {
fAuthorization::setUserAuthLevel('admin');
fURL::redirect(fAuthorization::getRequestedURL(TRUE, '/'));
}
}
Please note that the code above is simplified to demonstration, please check out the fCryptography Class for information on hashing passwords.
If for some reason you need to manually set the requested URL, that can be accomplished with the setRequestedURL()
method.
In addition, it is usually necessary to remember some sort of information about the user that is logging in so you can retrieve their information on other pages. The setUserToken()
and getUserToken()
methods allow storing some sort of user identifier and getting it back later. Here is the above example with the user token code added:
if ('login' == $action && fRequest::isPost()) {
if ($email == 'john@example.com' && sha1($password) == '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8') {
fAuthorization::setUserAuthLevel('admin');
fAuthorization::setUserToken('john@example.com');
fURL::redirect(fAuthorization::getRequestedURL(TRUE, '/'));
}
}
If you are using either authorization levels or access control lists, there are two simple methods that can be used to tell if a user is logged in. To require a user is logged in to view a page you will want to use the requireLoggedIn()
, whereas if you want to create conditions based on whether or not the user is logged in you can use the checkLoggedIn()
method:
// Require a user is logged in
fAuthorization::requireLoggedIn();
// Branch based on the users login status
if (fAuthorization::checkLoggedIn()) {
// Code to execute if the user is logged in
}
Sometimes it may be a requirement to only execute code if the remote connection is coming from a specific IP address or range. The method checkIP()
provides this functionality. A single parameter $ip_ranges
is required, which should like the IP address, CIDR range or IP/subnet-mask combination to check for. It is also possible to pass an array of any valid IP/range descriptors.
// Check for the IP 192.168.1.1
if (fAuthorization::checkIP('192.168.1.1')) {
// ...
}
// Check for the IPs 192.168.1.1-255
if (fAuthorization::checkIP('192.168.1.0/24')) {
// ...
}
// Check for the IPs 192.168.1.1-255
if (fAuthorization::checkIP('192.168.1.0/255.255.255.0')) {
// ...
}
// Check for the IPs 192.168.1.1-255 or 192.168.2.1
if (fAuthorization::checkIP(array('192.168.1.0/24', '192.168.2.1'))) {
// ...
}
It is also possible to create named IPs/ranges using the method addNamedIPRange()
. Simply define the $name
and $ip_ranges
and then you can use that name with checkIP()
:
// Create a named IP/range
fAuthorization::addNamedIPRange('office', '192.168.1.0/24');
// Check the named IP/range
if (fAuthorization::checkIP('office')) {
// ...
}
When a user logs out, it is important that their authorization level or ACLs are erased. This is quite simple to do using the destroyUserInfo()
method:
fAuthorization::destroyUserInfo();
One of the simplest ways for an attacker to gain unauthorized privileges is via session fixation. An attack would be executed by sending a link with a known session ID to a user and then waiting for them to log in. Once the user has logged in, the attacker can send the same session ID and receive the privileges of the user. Below is an example:
http://example.com/login.php?PHPSESSID=abcdef1234567890
By default fSession prevents such an attack by requiring that session IDs be passed in cookies, however it is theoretically possible runtime configuration of the session.use_only_cookies INI setting could be disabled.
To prevent attacks based on knowledge of the users session ID, fAuthorization automatically regenerates the session ID via session_regenerate_id()
whenever setUserACLs()
, setUserAuthLevel()
or setUserToken()
is called. The attacker wont know the newly regenerated ID, and wont be able to access the users account.
Allows defining and checking user authentication via ACLs, authorization levels or a simple logged in/not logged in scheme
1.0.0b6 | Fixed checkIP() to not trigger a notice when $_SERVER['REMOTE_ADDR'] is not set 5/10/11 |
---|---|
1.0.0b5 | Added getLoginPage() 3/9/10 |
1.0.0b4 | Updated class to use new fSession API 10/23/09 |
1.0.0b3 | Updated class to use new fSession API 5/8/09 |
1.0.0b2 | Fixed a bug with using named IP ranges in checkIP() 1/10/09 |
1.0.0b | The initial implementation 6/14/07 |
Adds a named IP address or range, or array of addresses and/or ranges
This method allows checkIP() to be called with a name instead of the actual IPs.
void addNamedIPRange( string $name, mixed $ip_ranges )
string | $name | The name to give the IP addresses/ranges |
mixed | $ip_ranges | This can be string (or array of strings) of the IPs or IP ranges to restrict to - please see checkIP() for format details |
Checks to see if the logged in user meets the requirements of the ACL specified
boolean checkACL( string $resource, string $permission )
string | $resource | The resource we are checking permissions for |
string | $permission | The permission to require from the user |
If the user has the required permissions
Checks to see if the logged in user has the specified auth level
boolean checkAuthLevel( string $level )
string | $level | The level to check against the logged in user's level |
If the user has the required auth level
Checks to see if the user is from the IPs or IP ranges specified
The $ip_ranges parameter can be either a single string, or an array of strings, each of which should be in one of the following formats:
boolean checkIP( mixed $ip_ranges )
mixed | $ip_ranges | A string (or array of strings) of the IPs or IP ranges to restrict to - see method description for details |
If the user is coming from (one of) the IPs or ranges specified
Checks to see if the user has an auth level or ACLs defined
boolean checkLoggedIn( )
If the user is logged in
Destroys the user's auth level and/or ACLs
void destroyUserInfo( )
Returns the login page set via setLoginPage()
string getLoginPage( )
The login page users are redirected to if they don't have the required authorization
Returns the URL requested before the user was redirected to the login page
string getRequestedURL( boolean $clear, string $default_url=NULL )
boolean | $clear | If the requested url should be cleared from the session after it is retrieved |
string | $default_url | The default URL to return if the user was not redirected |
The URL that was requested before they were redirected to the login page
Gets the ACLs for the logged in user
array getUserACLs( )
The logged in user's ACLs
Gets the authorization level for the logged in user
string getUserAuthLevel( )
The logged in user's auth level
Gets the value that was set as the user token, NULL if no token has been set
mixed getUserToken( )
The user token that had been set, NULL if none
Redirect the user to the login page if they do not have the permissions required
void requireACL( string $resource, string $permission )
string | $resource | The resource we are checking permissions for |
string | $permission | The permission to require from the user |
Redirect the user to the login page if they do not have the auth level required
void requireAuthLevel( string $level )
string | $level | The level to check against the logged in user's level |
Redirect the user to the login page if they do not have an auth level or ACLs
void requireLoggedIn( )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Resets the configuration of the class
void reset( )
Sets the authorization levels to use for level checking
void setAuthLevels( array $levels )
array | $levels | An associative array of (string) {level} => (integer) {value}, for each level |
Sets the login page to redirect users to
void setLoginPage( string $url )
string | $url | The URL of the login page |
Sets the restricted URL requested by the user
void setRequestedURL( string $url )
string | $url | The URL to save as the requested URL |
Sets the ACLs for the logged in user.
Array should be formatted like:
array (
(string) {resource name} => array(
(mixed) {permission}, ...
), ...
)
The resource name or the permission may be the single character '*' which acts as a wildcard.
void setUserACLs( array $acls )
array | $acls | The logged in user's ACLs - see method description for format |
Sets the authorization level for the logged in user
void setUserAuthLevel( string $level )
string | $level | The logged in user's auth level |
Sets some piece of information to use to identify the current user
void setUserToken( mixed $token )
mixed | $token | The user's token. This could be a user id, an email address, a user object, etc. |
fAuthorizationException is a sub-class of fExpectedException that indicates some sort of authorization error has occurred.
This space intentionally left blank
An exception caused by an authorization error
1.0.0b | The initial implementation 5/9/11 |
---|
Exception | --fException | --fExpectedException | --fAuthorizationException
The fBuffer class is a fairly straight-forward static class designed to make the output buffer functions in PHP a little more user-friendly. Only a single level of buffering is supported, however it has been supplemented with buffer capture support and replace functionality.
Output buffering is essential if you wish to change the headers a page sends after some of the output has been sent, and is also utilized by the fTemplating class for fully buffered output that allows changing values until right before the buffer is sent to the user.
Normally when using the output buffer function you call ob_start()
to start output buffering and one of the ob_end_*()
methods to stop buffering. The fBuffer class instead uses the start()
and stop()
methods. Here is an example:
fBuffer::start();
// Execute code that produces output
// Send a header
// Execute more code that creates output
fBuffer::stop();
Gzip compression of the output buffer can be turned (if the zlib extension is installed) by passing TRUE
to start()
.
// Start the output buffer with gzip compression
fBuffer::start(TRUE);
You can check to see if buffering has been started by calling isStarted()
:
if (!fBuffer::isStarted()) {
fBuffer::start();
}
To get the current contents of the output buffer, simply call get()
:
$current_buffer = fBuffer::get();
If you wish to get rid of the buffered contents, call erase()
:
// Get rid of the current buffer contents
fBuffer::erase();
Also sometimes useful is the ability to replace a given string in the buffer with another. In this situation you can use the replace()
method:
// Output stuff
// This would replace every instance of 'foo' with 'bar'
fBuffer::replace('foo', 'bar');
Some of the built-in PHP functions (and other third party code) will only output content, as opposed to returning it for further processing. The fBuffer class provides two methods, startCapture()
and stopCapture()
, that make it easy to intercept such output.
// Begin capturing, everything passed to print or echo after here will be captured
fBuffer::startCapture();
// Execute code to output content
// ...
// Grab the captured output
$captured_content = fBuffer::stopCapture();
Provides a single, simplified interface for output buffering to prevent nested buffering issues and provide a more logical API
1.0.0b3 | Added a check to ensure the zlib extension is installd when doing gzipped buffering 5/20/10 |
---|---|
1.0.0b2 | Added the $gzip parameter to start() 5/19/10 |
1.0.0b | The initial implementation 3/16/08 |
Erases the output buffer
void erase( )
Returns the contents of output buffer
string get( )
The contents of the output buffer
Checks if buffering has been started
boolean isStarted( )
If buffering has been started
Replaces a value in the output buffer
void replace( string $find, string $replace )
string | $find | The string to find |
string | $replace | The string to replace |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Resets the configuration and buffer of the class
void reset( )
Starts output buffering
void start( boolean $gzip=FALSE )
boolean | $gzip | If the buffered output should be gzipped using ob_gzhandler() |
Starts capturing output, should be used with stopCapture() to grab output from code that does not offer an option of returning a value instead of outputting it
void startCapture( )
Stops output buffering, flushing everything to the browser
void stop( )
Stops capturing output, returning what was captured
string stopCapture( )
The captured output
The fCRUD class is a static class that provides functionality for the CRUD (create, read, update, delete) pages that power most dynamic websites and online applications. The various methods have been implemented to help reduce the amount of code needed to a standard page, letting the developer focus on the unique aspects of sites.
When filtering the objects to display when executing the list
action for a CRUD page, it will often times be required to remember the filtered values even after the user had edited or deleted an object. While it is possible to pass the filter values around in the URL, it leads to lots of extra code and more complex URLs.
As a solution, the getSearchValue()
method will always return the last value selected by a user for a given GET
or POST
parameter. When a value is pulled out of GET
or POST
data, the value is saved in the users session. If the user then leaves the page and comes back without a value, the value will be looked up in the session. This method should be used in place of fRequest::get().
Here is an example of the usage:
// Get the parameter search_terms from GET, POST or the session
$search_terms = fCRUD::getSearchValue('search_terms');
// Here $search_terms would be used to filter the objects being displayed
// The SetCreator class is not a real class, but used as an example
$results = SetCreator::findUsers($search_terms);
In order to provide for accessibility and usability, it is recommended to redirect the user whenever values are pulled from a location other than GET
data. This way the user can bookmark results, send a link via email, etc. The redirectWithLoadedValues()
method will take the currently requested URL and will redirect the user to a new URL that includes all of the search values (and sorting values) that were pulled from the session. If no values are pulled from the session, no redirection will happen.
Here is the example above with the redirection code added:
// Get the parameter search_terms from GET, POST or the session
$search_terms = fCRUD::getSearchValue('search_terms');
// Redirect the user if any values were loaded from the session
fCRUD::redirectWithLoadedValues();
// Here $search_terms would be used to filter the objects being displayed
// The SetCreator class is not a real class, but used as an example
$results = SetCreator::findUsers($search_terms);
Having sortable columns on a CRUD page is often a huge usability boost. Unfortunately sortable columns can also be a pain to implement. The fCRUD class provides a few methods to help make it a little easier: getSortColumn()
, getSortDirection()
, printSortableColumn()
and getColumnClass()
.
The getSortColumn()
takes a single parameter, a $possible_columns
array, and returns the one specified by the GET
value for the parameter sort
. The method also will save the last sort column and will reload it from the session if none is specified in the GET
data. If neither of these methods can determine the sort column it will default to the first value in the $possible_columns
array.
getSortDirection()
takes a single parameter, $default_direction
, and will return the sorting direction specified in the GET
data for the parameter dir
. The possible values for $default_direction
(and the return value) are 'asc'
and 'desc'
. If no value is specified in the GET
data, it will try to load the last sort direction from the session. If this can not be done the sort direction will default to the values specified in $default_direction
.
These two methods work with the redirectWithLoadedValues()
method the same way that getSearchValue()
does. Here is an example of the three methods being used:
// Set the users to be sortable by name or email, defaulting to name
$sort = fCRUD::getSortColumn(array('name', 'email'));
// Set the sorting to default to ascending
$dir = fCRUD::getSortDirection('asc');
// Redirect the user if one of the values was loaded from the session
fCRUD::redirectWithLoadedValues();
// Use the sort column and direction in your code to load the objects in the proper order
$users = SetCreator::findUsers($sort, $dir);
The second step to getting sortable columns on CRUD pages is to create the links to allow sorting. The printSortableColumn()
method accomplishes this task.
printSortableColumn()
takes two parameters, with the second one being optional. The first is the $column
to make sortable. This value will be returned when calling getSortColumn()
. The second, optional, parameter $column_name
allows you to specify a display name to be used for $column
. If $column_name
is not specified, the fGrammar::humanize()ed version of $column
will be used instead.
printSortableColumn()
prints out an a
tag containing a link to the current URL with the current query string, except the sort
and dir
parameters will be changed to the correct values for the link. If a user clicks a sortable column link that is already sorting the object, the direction will be reversed. In addition, the a
tag will have the CSS class sortable_column
applied to it. If the link being created is for the column currently being sorted, a second CSS class asc
or desc
will be added to the a
tag as appropriate. Here is an example of the PHP and corresponding HTML:
<table>
<tr>
<th><? fCRUD::printSortableColumn('name') ?></th>
<th><? fCRUD::printSortableColumn('email', 'E-Mail') ?></th>
</tr>
...
</table>
<!-- The following HTML is presented as if the name column is currently being sorted ascending -->
<table>
<tr>
<th><a href="{current_url}?sort=name&dir=desc" class="sortable_column asc">Name</a></th>
<th><a href="{current_url}?sort=email&dir=asc" class="sortable_column">E-Mail</a></th>
</tr>
...
</table>
Finally, when displaying the object information it might be nice to include a visual indication of which column is sorted on each row. This way if the user has scrolled so the column headers are out of view, they can still remember which column is being sorted. This can be accomplished by using the getColumnClass()
method:
<tr>
<td class="<? fCRUD::getColumnClass('name') ?>"><?php echo $object->getName() ?></td>
<td class="<? fCRUD::getColumnClass('email') ?>"><?php echo $object->getEmail() ?></td>
</tr>
<!-- The following HTML is presented as if the name column is currently being sorted ascending -->
<tr>
<td class="sorted">Will</th>
<td class="">will@example.com</td>
</tr>
If you are using columns from related tables, simple include the table name and a .
when passing the column name to printSortableColumn()
and getColumnClass()
. To sort by the name
column in the groups
table, just use groups.name
.
Since search values and sortable columns are saved in the sessions, whenever a user returns to the page the saved values will be loaded. If you want to allow the user to reset their stored values, simply add a reset
parameter (without an =
or a value) to the end of the query string. This will cause all stored values to be erased, and the user to be redirected to the same URL without the reset
parameter. Here are a couple of examples:
<!--
All sticky search values and sorting information would be erased the the user would be redirected to /users/
-->
<a href="/users/?reset">Users</a>
<!--
In this case the user would be redirected to /galleries/?gallery_id=3
-->
<a href="/galleries/?gallery_id=3&reset">Gallery</a>
<!--
This would do nothing since reset is not at the end of the query string
-->
<a href="/galleries/?reset&gallery_id=3">Gallery</a>
<!--
This would do nothing since reset is followed by =. By not allowing an =, we prevent the possibility of
conflicting with a real query string parameter called reset.
-->
<a href="/galleries/?gallery_id=3&reset=">Gallery</a>
A common feature for list tables is to include alternating row colors (via CSS) to allow users to track rows across a table. The getRowClass()
method provides functionality to accomplish this in a dynamic way that will also highlight a row that has just been added or updated.
The method takes two parameters, $row_value
and $affected_value
. When these two values are not equal the method will return 'odd'
and 'even'
on an alternating basis. When the two parameters are equal the method will return highlighted
.
Here is an example of usage:
$user_id = fRequest::get('user_id');
$users = SetCreator::findUsers();
foreach ($users as $user) {
?>
<tr class="<?php echo fCRUD::getRowClass($user->getUserId(), $user_id) ?>">
<td><?php echo $user->getName() ?></td>
<td><?php echo $user->getEmail() ?></td>
</tr>
<?
}
The output from the above PHP would be:
<!-- The following HTML is presented as if $user_id = 1 and the user id for Will was 1 -->
<tr class="odd">
<td>Joe</td>
<td>joe@example.com</td>
</tr>
<tr class="highlighted">
<td>Will</td>
<td>will@example.com</td>
</tr>
<tr class="even">
<td>Zach</td>
<td>zach@example.com</td>
</tr>
Provides miscellaneous functionality for CRUD-like pages
1.0.0b5 | Updated class to use new fSession API 10/23/09 |
---|---|
1.0.0b4 | Updated class to use new fSession API 5/8/09 |
1.0.0b3 | Backwards Compatiblity Break - moved printOption() to fHTML::printOption(), showChecked() to fHTMLshowChecked(), removeListItems() and reorderListItems() to fException::splitMessage(), generateRequestToken() to fRequestgenerateCSRFToken(), and validateRequestToken() to fRequest::validateCSRFToken() 5/8/09 |
1.0.0b2 | Fixed a bug preventing loaded search values from being included in redirects 3/18/09 |
1.0.0b | The initial implementation 6/14/07 |
Return the string 'sorted' if $column is the column that is currently being sorted by, otherwise returns ''
This method will only be useful if used with the other sort methods printSortableColumn(), getSortColumn() and getSortDirection().
string getColumnClass( string $column )
string | $column | The column to check |
The CSS class for the column, either '' or 'sorted'
Returns a CSS class name for a row
Will return 'even', 'odd', or 'highlighted' if the two parameters are equal and not NULL. The first call to this method will return the appropriate class concatenated with ' first'.
string getRowClass( mixed $row_value=NULL, mixed $affected_value=NULL )
mixed | $row_value | The value from the row |
mixed | $affected_value | The value that was just added or updated |
The css class
Gets the current value of a search field
If a value is an empty string and no cast to is specified, the value will become NULL.
If a query string of ?reset is passed, all previous search values will be erased.
mixed getSearchValue( string $column, string $cast_to=NULL, string $default=NULL )
string | $column | The column that is being pulled back |
string | $cast_to | The data type to cast to |
string | $default | The default value |
The current value
Gets the current column to sort by, defaults to first one specified
string getSortColumn( string $possible_column [, ... ] )
string | $possible_column [, ... ] | The columns that can be sorted by, defaults to first |
The column to sort by
Gets the current sort direction
string getSortDirection( string $default_direction )
string | $default_direction | The default direction, 'asc' or 'desc' |
The direction, 'asc' or 'desc'
Prints a sortable column header a tag
The a tag will include the CSS class 'sortable_column' and the direction being sorted, 'asc' or 'desc'.
fCRUD::printSortableColumn('name', 'Name');
would create the following HTML based on the page context
Name
Name
void printSortableColumn( string $column, string $column_name=NULL )
string | $column | The column to create the sortable header for |
string | $column_name | This will override the humanized version of the column |
Checks to see if any values (search or sort) were loaded from the session, and if so redirects the user to the current URL with those values added
void redirectWithLoadedValues( )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Resets the configuration and data of the class
void reset( )
The fCache class provides a consistent caching interface that can use the APC, database, directory, file, Memcache, Redis or XCache backends. It can be used to cache any end-developer data, but can also be used by the fDatabase, fSQLTranslation and fSchema classes.
Creating an instance of fCache requires a $type
, and possibly a $data_store
and $options
. The following types are supported. The $data_store
and $config
parameters are explained for each backend separately.
Using APC for the backend only requires the $type
parameter be set to 'apc'
. The $data_store
and $config
parameters are unused.
$cache = new fCache('apc');
APC is best suited for smaller values where performance is of utmost concern.
Please note that APC shares values server-wide, so cache keys should be appropriately unique in case there are multiple sites being hosted on the same server.
Using a database for the backend requires the $type
parameter be set to 'database'
and the $data_store
be an fDatabase object. The $config
parameter must be an array with the following keys:
table
: The database table to use for cachingkey_column
: The column to store the cache key in - must support at least 250 character stringsvalue_column
: The column to store the serialized value in - this should probably be a TEXT column to support large values, or BLOB if binary serialization is usedvalue_data_type
: If a BLOB column is being used for the value_column
, this should be set to 'blob'
, otherwise 'string'
ttl_column
: The column to store the expiration timestamp of the cached entry - this should be an integer$db = new fDatabase('mysql', 'mydb', 'username', 'password');
$config = array(
'table' => 'sessions',
'key_column' => 'session_id',
'value_column' => 'values',
'value_data_type' => 'string',
'ttl_column' => 'expiration'
);
$cache = new fCache('database', $db, $config);
Each value is stored in a separate row in the table. A database backend is useful for caching values that need to be shared across servers.
Using a directory for the backend requires the $type
parameter be set to 'directory'
and the $data_store
be the path to a directory that is writable. The $config
parameter is unused.
$cache = new fCache('directory', '/path/to/dir');
A directory backend is generally useful for caching larger values on a single server.
Each value in a directory cache is stored in a separate file, with the filenames being hashes of the cache key. The first line of the file is the integer unix timestamp of when the value expires. All subsequent lines in the file are part of the value. An expiration timestamp of 0
indicates no expiration.
For file caches, the $type
should be 'file'
and the $data_store
should be a file path. The $config
parameter is unused.
$cache = new fCache('file', '/path/to/cache');
A file backend is most useful for simple single-server caching that requires no extra hosting infrastructure or uncommon PHP extensions.
All values are stored in a serialized array containing the keys, values and expiration timestamps.
For memcache caches, the $type
should be set to 'memcache'
and the $data_store
should be a Memcache or Memcached object. The $config
parameter is unused.
// Using the memcache extension
$memcache = new Memcache();
$memcache->connect('localhost', 11211);
$cache = new fCache('memcache', $memcache);
// Using the memcached extension
$memcached = new Memcached();
$memcached->addServer('localhost', 11211);
$cache = new fCache('memcache', $memcached);
A memcache backend is useful for caching all sorts of different values, often for larger sites or sites with multiple web servers.
For Redis caches, the $type
should be set to 'redis'
and the $data_store
should be an instance of the Redis
class from the phpredis extension. The $config
parameter is unused.
$redis = new Redis();
$redis->connect('localhost');
$cache = new fCache('redis', $redis);
A redis backend is useful for caching all sorts of different values, often for larger sites or sites with multiple web servers.
For XCache caches, the $type
should be set to 'xcache'
. The $data_store
and $config
parameters are unused.
$cache = new fCache('xcache');
XCache is best suited for smaller values where performance is of utmost concern.
Please note that XCache shares values server-wide, so cache keys should be appropriately unique in case there are multiple sites being hosted on the same server.
The method set()
accepts a $key
, $value
and optional $ttl
(time-to-live). The $key
should be a string of 250 characters or less, and the $value
should be any PHP data type that can be serialized. The main PHP data type that can not be serialized in a resource
.
The $key
and $value
combination will be stored in the cache permanently, unless a $ttl
is provided. The $ttl
is the number of seconds the cached $value
will be accessible. Values with a $ttl
will be cleaned up by whatever back-end is providing the cache. Warning: the APC back-end currently functions in such a way that the $ttl
will be ignored when getting a value in the same script execution that it is set.
Please note that all PHP values are serialized before being stored in the cache. This ensures that the exact same value that goes into the cache will come back out, even if the back-end only supports basic types such as strings and integers.
// This value will last until explicitly deleted or the cache is cleared
$cache->set('computed_value', $computed_value);
// This value will last for 60 seconds
$cache->set('other_computed_value', $other_computed_value, 60);
It is also possible to only set a value if the $key
does not already exist in the cache. This is performed using the add()
method. add()
takes the exact same parameters as set()
, however it returns a boolean indicating if the value was added.
if ($cache->add('master_value', $master_value)) {
// Compute a related value and store it also
}
The method get()
takes the $key
to retrieve the value for, and an optional $default
to return if the $key
is not set. If $default
is not specified, NULL
will be returned for any $key
that is not currently set.
// This will return NULL if there is no value for 'computed_value'
$cached_value = $cache->get('computed_value');
// This will return 10 if there is no value for 'computed_value'
$cached_value = $cache->get('computed_value', 10);
Values can be deleted individually from the cache by calling the method delete()
and passing the $key
to delete.
$cache->delete('computed_value');
In addition to deleting specific cache entries, it is also possible to clear all of the entries in the cache. This will delete all key/value pairs in your cache, and depending on your cache type, may affect all other websites on the same server or all web servers.
Please note that the XCache back-end may require an administrator login and password to clear the cache. This setting, and the login/password settings are controlled by ini settings.
$cache->clear();
A simple interface to cache data using different backends
1.0.0b6 | Fixed a bug with add() setting a value when it shouldn't if no ttl was given for the file backend 1/12/12 |
---|---|
1.0.0b5 | Added missing documentation for using Redis as a backend 8/25/11 |
1.0.0b4 | Added the database, directory and redis types, added support for the memcached extention and support for custom serialization callbacks 6/21/11 |
1.0.0b3 | Added 0 to the memcache delete method call since otherwise the method triggers notices on some installs 5/10/11 |
1.0.0b2 | Fixed API calls to the memcache extension to pass the TTL as the correct parameter 2/1/11 |
1.0.0b | The initial implementation 4/28/09 |
The cache configuration, used for database, directory and file caches
The array structure for database caches is:
array(
'table' => (string) {the database table to use},
'key_column' => (string) {the varchar column to store the key in, should be able to handle at least 250 characters},
'value_column' => (string) {the text/varchar column to store the value in},
'ttl_column' => (string) {the integer column to store the expiration time in}
)
The array structure for directory caches:
array(
'path' => (string) {the directory path with trailing slash}
)
The array structure for file caches:
array(
'path' => (string) {the file path},
'state' => (string) {clean or dirty, used to appropriately save}
)
array
The data store to use
Either the:
Not used for apc, directory or xcache
mixed
The type of cache
The valid values are:
string
Set the type and master key for the cache
A file cache uses a single file to store values in an associative array and is probably not suitable for a large number of keys.
Using an apc or xcache cache will have far better performance than a file or directory, however please remember that keys are shared server-wide.
$config is an associative array of configuration options for the various backends. Some backend require additional configuration, while others provide provide optional settings.
The following $config options must be set for the database backend:
The following $config for the following items can be set for all backends:
Common serialization callbacks include:
Please note that using JSON for serialization will exclude all non-public properties of objects from being serialized.
A custom serialize and unserialze option is string, which will cast all values to a string when storing, instead of serializing them. If a __toString() method is provided for objects, it will be called.
fCache __construct( string $type, mixed $data_store=NULL, array $config=array() )
string | $type | The type of caching to use: 'apc', 'database', 'directory', 'file', 'memcache', 'redis', 'xcache' |
mixed | $data_store | The path for a file or directory cache, an Memcache or Memcached object for a memcache cache, an fDatabase object for a database cache or a Redis object for a redis cache - not used for apc or xcache |
array | $config | Configuration options - see method description for details |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Cleans up after the cache object
void __destruct( )
Tries to set a value to the cache, but stops if a value already exists
boolean add( string $key, mixed $value, integer $ttl=0 )
string | $key | The key to store as, this should not exceed 250 characters |
mixed | $value | The value to store, this will be serialized |
integer | $ttl | The number of seconds to keep the cache valid for, 0 for no limit |
If the key/value pair were added successfully
Removes all cache entries that have expired
void clean( )
Clears the WHOLE cache of every key, use with caution!
xcache may require a login or password depending on your ini settings.
boolean clear( )
If the cache was successfully cleared
Deletes a value from the cache
boolean delete( string $key )
string | $key | The key to delete |
If the delete succeeded
Returns a value from the cache
mixed get( string $key, mixed $default=NULL )
string | $key | The key to return the value for |
mixed | $default | The value to return if the key did not exist |
The cached value or the default value if no cached value was found
Only valid for file caches, saves the file to disk
void save( )
Serializes a value before storing it in the cache
string serialize( mixed $value )
mixed | $value | The value to serialize |
The serialized value
Sets a value to the cache, overriding any previous value
boolean set( string $key, mixed $value, integer $ttl=0 )
string | $key | The key to store as, this should not exceed 250 characters |
mixed | $value | The value to store, this will be serialized |
integer | $ttl | The number of seconds to keep the cache valid for, 0 for no limit |
If the value was successfully saved
Unserializes a value before returning it
mixed unserialize( string $value )
string | $value | The serialized value |
The PHP value
fConnectivityException is a sub-class of fUnexpectedException that indicates some sort of unrecoverable connection error has occurred. This could be used in situations where a database server is not reachable, a remote API could not be contacted, or an FTP connection timed out.
This space intentionally left blank
An exception caused by a connectivity error
1.0.0b | The initial implementation 6/14/07 |
---|
Exception | --fException | --fUnexpectedException | --fConnectivityException
The fCookie class helps to make PHPs cookie API more consistent, while adding the ability to set default parameters and create httponly cookies in older versions of PHP 5.
The static method set()
is a wrapper around the function setcookie()
, with the addition of optional default parameters and backwards compatibility for creating httponly cookies in PHP 5.1 and earlier. In addition, the expires parameter has been extended to allow any valid strtotime()
string or a timestamp. Below are some examples of using set()
:
// Set a cookie for the current path
fCookie::set('my_cookie', 'my_value');
// Set a cookie to expire in 1 week
fCookie::set('my_cookie', 'my_value', '+1 week');
// Set a cookie for the whole site
fCookie::set('my_cookie', 'my_value', '+1 week', '/');
// Set a cookie for the whole site that expires when the browser is closed
fCookie::set('my_cookie', 'my_value', 0, '/');
// Set a cookie for all subdomains of example.com
fCookie::set('my_cookie', 'my_value', '+1 week', '/', '.example.com');
// Ensure that the cookie is only set over a secure connection (https://)
fCookie::set('my_cookie', 'my_value', '+1 week', '/', '.example.com', TRUE);
// Set the cookie to only be accessible to HTTP (not javascript)
fCookie::set('my_cookie', 'my_value', '+1 week', '/', '.example.com', TRUE, TRUE);
Please note that set()
follows the same functionality as setcookie()
when it comes to deleting cookies and storing boolean values. Any value that is set to a cookie that is equal to FALSE
will cause the cookie to be deleted. This means storing boolean values in a cookie is not recommended since a FALSE
value will cause the cookie to be erased - instead use '0'
and '1'
. Also, setting the expiration date to a time in the past will cause the cookie to be deleted.
The static method get()
replaces direct access to the $_COOKIE
superglobal and adds the ability to specify default values if no cookie is found:
// Get the value 'default_value' if no cookie of that name exists
$value = fCookie::get('my_cookie', 'default_value');
get()
will also remove any slashes added by the ini setting magic_quotes_gpc
being enabled.
The fCookie class also allows defining default parameters to use for set()
. Since most sites will often use the same $path
, $domain
, $secure
and $httponly
parameters for cookies, setting default eliminates unnecessary duplication throughout the code. If a parameter is passed as NULL
to set()
and a default is defined, the default will be used. Here are the methods to set default parameters:
Parameter | Method |
$expires |
setDefaultExpires() |
$path |
setDefaultPath() |
$domain |
setDefaultDomain() |
$secure |
setDefaultSecure() |
$httponly |
setDefaultHTTPOnly() |
Provides a consistent cookie API, HTTPOnly compatibility with older PHP versions and default parameters
1.0.0b3 | Added the delete() method 9/30/09 |
---|---|
1.0.0b2 | Updated for new fCore API 2/16/09 |
1.0.0b | The initial implementation 9/1/08 |
Deletes a cookie - uses default parameters set by the other set methods of this class
void delete( string $name, string $path=NULL, string $domain=NULL, boolean $secure=NULL )
string | $name | The cookie name to delete |
string | $path | The path of the cookie to delete |
string | $domain | The domain of the cookie to delete |
boolean | $secure | If the cookie is a secure-only cookie |
Gets a cookie value from $_COOKIE, while allowing a default value to be provided
mixed get( string $name, mixed $default_value=NULL )
string | $name | The name of the cookie to retrieve |
mixed | $default_value | If there is no cookie with the name provided, return this value instead |
The value
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Resets the configuration of the class
void reset( )
Sets a cookie to be sent back to the browser - uses default parameters set by the other set methods of this class
The following methods allow for setting default parameters for this method:
void set( string $name, mixed $value, string|integer $expires=NULL, string $path=NULL, string $domain=NULL, boolean $secure=NULL, boolean $httponly=NULL )
string | $name | The name of the cookie to set |
mixed | $value | The value of the cookie to set |
string|integer | $expires | A relative string to be interpreted by strtotime() or an integer unix timestamp |
string | $path | The path this cookie applies to |
string | $domain | The domain this cookie applies to |
boolean | $secure | If the cookie should only be transmitted over a secure connection |
boolean | $httponly | If the cookie should only be readable by HTTP connection, not javascript |
Sets the default domain to use for cookies
This value will be used when the $domain parameter of the set() method is not specified or is set to NULL.
void setDefaultDomain( string $domain )
string | $domain | The default domain to use for cookies |
Sets the default expiration date to use for cookies
This value will be used when the $expires parameter of the set() method is not specified or is set to NULL.
void setDefaultExpires( string|integer $expires )
string|integer | $expires | The default expiration date to use for cookies |
Sets the default httponly flag to use for cookies
This value will be used when the $httponly parameter of the set() method is not specified or is set to NULL.
void setDefaultHTTPOnly( boolean $httponly )
boolean | $httponly | The default httponly flag to use for cookies |
Sets the default path to use for cookies
This value will be used when the $path parameter of the set() method is not specified or is set to NULL.
void setDefaultPath( string $path )
string | $path | The default path to use for cookies |
Sets the default secure flag to use for cookies
This value will be used when the $secure parameter of the set() method is not specified or is set to NULL.
void setDefaultSecure( boolean $secure )
boolean | $secure | The default secure flag to use for cookies |
The fCore class centralizes debugging, error and exception handling and more.
When maintaining production systems, knowing about errors and unhandled exceptions that have occurred is key. The fCore class provides two handy methods to simply this task.
The enableErrorHandling()
and enableExceptionHandling()
methods each take a first parameter that is the destination for errors and unhandled exceptions respectively. The options are an email address, a file, or the string 'html'
for output into the currently rendering page.
Since unhandled exceptions cause the page execution to stop immediately, enableExceptionHandling()
takes a callback as a second parameter to allow you to cleanly finish HTML output. The optional third parameter is an array of parameters to send to the callback.
Both the error and exception handling provide full backtraces, allowing for an easy time finding bugs.
// Set the site to send me an email every time an error or unhandled exception gets thrown
fCore::enableErrorHandling('will@flourishlib.com');
fCore::enableExceptionHandling('will@flourishlib.com', array($templating, 'place'), array('footer'));
// Set the site to log errors and exceptions to my logs dir
fCore::enableErrorHandling($_SERVER['DOCUMENT_ROOT'] . '/writable/logs/errors.log');
fCore::enableExceptionHandling($_SERVER['DOCUMENT_ROOT'] . '/writable/logs/exceptions.log', array($templating, 'place'), array('footer'));
// Set the site to display errors and exception in the html
fCore::enableErrorHandling('html');
fCore::enableExceptionHandling('html', array($templating, 'place'), array('footer'));
Multiple email addresses may be specified, separated by commas.
fCore::enableErrorHandling('will@flourishlib.com,john.smith@example.com');
By default, error and exception output includes a dump of the state of a number of PHP superglobals including $_GET
, $_POST
, $_FILES
, $_COOKIE
, $_SESSION
and $_SERVER
to aid in debugging. It is possible to disable this functionality by calling disableContext()
.
// Disable the context dumps that happen with error and exception handling
fCore::disableContext();
By default, error and exception emails are sent using the mail()
function. An SMTP server may be used by passing an instance of fSMTP and a From
email address to configureSMTP()
.
$smtp = new fSMTP('server.example.com');
$smtp->authenticate('username', 'password');
fCore::configureSMTP($smtp, 'noreply@example.com');
While exceptions are fairly easy to use due to the way they bubble up to the closest matching catch
block, errors in PHP are not as simple. Errors don't interrupt program flow and can only be captured by an error handler. startErrorCapture()
and stopErrorCapture()
provide functionality that allows capturing errors and returning them in an array for futher processing.
// Here we don't need warning messages if this fails, we can just check to see if $result is FALSE
fCore::startErrorCapture();
$result = file_get_contents('http://example.com');
$errors = fCore::stopErrorCapture();
While a similar result can be accomplished by using the error_reporting()
function and simplying lowering the error reporting level, this may not be available if it has been set via the php_admin_value
Apache configuration directive.
In addition, turning down error_reporting()
does not allow for acting upon errors, just silencing them. Since stopErrorCapture()
returns an array of information about each error that occurred, the messages can be used to determine the next course of action or combined into an exception.
fCore::startErrorCapture();
$connection = pg_connect('dbname=example');
$errors = fCore::stopErrorCapture();
if (!$connection) {
$error_strings = array();
foreach ($errors as $error) {
$error_strings[] = $error['string'];
}
throw new fConnectivityException("Unable to connect to the PostgreSQL database:\n%s", join("\n", $error_strings));
}
It is possible to capture only specific $types
of errors by passing them as the first parameter to startErrorCapture()
. The values are the same as those passed to error_reporting()
, a bitmask of the desired error types. The captured messages can be further restricted by passing a PCRE $regex
as the second parameter. Any errors that are not captured are passed on to the normal error handler.
Please note that all errors that match $types
will be captured, even if they are excluded by the current error_reporting()
setting.
// Capture only warnings about SSL
fCore::startErrorCapture(E_WARNING, '#ssl#i');
It is also possible to capture all errors, but only return some from stopErrorCapture()
. This is accomplished by by passing a PCRE $regex
as the first parameter.
// Capture all warnings, but only return errors about SSL
fCore::startErrorCapture(E_WARNING);
//
$errors = fCore::stopErrorCapture('#ssl#i');
fCore provides a few useful functions when you are trying to debug code. The simplest way to debug is to use the expose()
method to show the contents of a file. expose()
creates output similar to print_r()
, however it uses symbols to differentiate between ''
, NULL
and FALSE
. Here is a usage example.
fCore::expose(array('foo', 1, '', NULL, FALSE, TRUE));
The above call to expose()
would create the following HTML:
<pre class="exposed">Array
(
[0] => foo
[1] => 1
[2] => {empty_string}
[3] => {null}
[4] => {false}
[5] => {true}
)</pre>
If you want to set your code up to conditionally display debugging information, youll want to use the debug()
method. Content sent to debug()
is displayed via expose()
only if enableDebugging()
has been passed TRUE
or if the second parameter, $force
, is TRUE
.
// Enable debugging
fCore::enableDebugging(TRUE);
// Display a debugging message only when fCore::enableDebugging() has been called
fCore::debug('This is only shown when fCore::enableDebugging(TRUE) is called before this code', FALSE);
// Display a debugging message even if fCore::enableDebugging() has not been called
fCore::debug('This will always be shown', TRUE);
If you wish to pass debug information to another debugging or logging system, a callback can be registered via the static method registerDebugCallback()
. This method accepts a single parameter, the $callback
to send all debug messages to. The $callback
should accept a single parameter, a string debug message.
// Create a function to handle debug messages
function handleDebug($message)
{
// Code to pass message to another debugging or logging system
}
// Register the function as the message handler
fCore::registerDebugCallback('handleDebug');
The backtrace()
method provides a compact and nicely formatted version of debug_backtrace()
. Below is an example of usage:
class Example {
static public function backtrace()
{
fCore::expose(fCore::backtrace());
}
}
Example::backtrace();
Which would produce the following HTML:
<pre class="exposed">
{doc_root}/example.php(8): Example::backtrace()
{doc_root}/example.php(4): fCore::backtrace()
</pre>
In some situations it is necessary to write code based on the version of PHP or the operating system the code is running on.
The static method checkVersion()
will return TRUE
if the currently running version of PHP is greater or equal to the version string passed in.
if (fCore::checkVersion('5.1')) {
echo 'You are running PHP version 5.1 or newer';
}
The static method checkOS()
will return TRUE
if the current operating system is one of the OSes passed in as a parameter. Valid operating system strings include:
'linux'
'bsd'
'osx'
'solaris'
'windows'
if (fCore::checkOS('bsd', 'osx')) {
echo 'You are running either a BSD or OSX';
}
Provides low-level debugging, error and exception functionality
1.0.0b24 | Backwards Compatibility Break - moved detectOpcodeCache() to fLoader::hasOpcodeCache() 8/26/11 |
---|---|
1.0.0b23 | Backwards Compatibility Break - changed the email subject of error/exception emails to include relevant file info, instead of the timestamp, for better email message threading 6/20/11 |
1.0.0b22 | Fixed a bug with dumping arrays containing integers 5/26/11 |
1.0.0b21 | Changed startErrorCapture() to allow "stacking" it via multiple calls, fixed a couple of bugs with dump() mangling strings in the form int(1), fixed mispelling of occurred 5/9/11 |
1.0.0b20 | Backwards Compatibility Break - Updated expose() to not wrap the data in HTML when running via CLI, and instead just append a newline 2/24/11 |
1.0.0b19 | Added detection of AIX to checkOS() 1/19/11 |
1.0.0b18 | Updated expose() to be able to accept multiple parameters 1/10/11 |
1.0.0b17 | Fixed a bug with backtrace() triggering notices when an argument is not UTF-8 8/17/10 |
1.0.0b16 | Added the $types and $regex parameters to startErrorCapture() and the $regex parameter to stopErrorCapture() 8/9/10 |
1.0.0b15 | Added startErrorCapture() and stopErrorCapture() 7/5/10 |
1.0.0b14 | Changed enableExceptionHandling() to only call fException::printMessage() when the destination is not html and no callback has been defined, added configureSMTP() to allow using fSMTP for error and exception emails 6/4/10 |
1.0.0b13 | Added the $backtrace parameter to backtrace() 3/5/10 |
1.0.0b12 | Added getDebug() to check for the global debugging flag, added more specific BSD checks to checkOS() 3/2/10 |
1.0.0b11 | Added detectOpcodeCache() 10/6/09 |
1.0.0b10 | Fixed expose() to properly display when output includes non-UTF-8 binary data 6/29/09 |
1.0.0b9 | Added disableContext() to remove context info for exception/error handling, tweaked output for exceptions/errors 6/28/09 |
1.0.0b8 | enableErrorHandling() and enableExceptionHandling() now accept multiple email addresses, and a much wider range of emails 6/1/09 |
1.0.0b7 | backtrace() now properly replaces document root with {doc_root} on Windows 5/2/09 |
1.0.0b6 | Fixed a bug with getting the server name for error messages when running on the command line 3/11/09 |
1.0.0b5 | Fixed a bug with checking the error/exception destination when a log file is specified 3/7/09 |
1.0.0b4 | Backwards compatibility break - getOS() and getPHPVersion() removed, replaced with checkOS() and checkVersion() 2/16/09 |
1.0.0b3 | handleError() now displays what kind of error occurred as the heading 2/15/09 |
1.0.0b2 | Added registerDebugCallback() 2/7/09 |
1.0.0b | The initial implementation 9/25/07 |
Creates a nicely formatted backtrace to the the point where this method is called
string backtrace( integer $remove_lines=0, array $backtrace=NULL )
integer | $remove_lines | The number of trailing lines to remove from the backtrace |
array | $backtrace | A backtrace from debug_backtrace() to format - this is not usually required or desired |
The formatted backtrace
Performs a call_user_func(), while translating PHP 5.2 static callback syntax for PHP 5.1 and 5.0
Parameters can be passed either as a single array of parameters or as multiple parameters.
// Passing multiple parameters in a normal fashion
fCore::call('Class::method', TRUE, 0, 'test');
// Passing multiple parameters in a parameters array
fCore::call('Class::method', array(TRUE, 0, 'test'));
To pass parameters by reference they must be assigned to an array by reference and the function/method being called must accept those parameters by reference. If either condition is not met, the parameter will be passed by value.
// Passing parameters by reference
fCore::call('Class::method', array(&$var1, &$var2));
mixed call( callback $callback, array $parameters=array() )
callback | $callback | The function or method to call |
array | $parameters | The parameters to pass to the function/method |
The return value of the called function/method
Translates a Class::method style static method callback to array style for compatibility with PHP 5.0 and 5.1 and built-in PHP functions
array callback( callback $callback )
callback | $callback | The callback to translate |
The translated callback
Returns is the current OS is one of the OSes passed as a parameter
Valid OS strings are:
boolean checkOS( string $os [, ... ] )
string | $os [, ... ] | The operating system to check - see method description for valid OSes |
If the current OS is included in the list of OSes passed as parameters
Checks to see if the running version of PHP is greater or equal to the version passed
boolean checkVersion( $version )
$version |
If the running version of PHP is greater or equal to the version passed
Sets an fSMTP object to be used for sending error and exception emails
void configureSMTP( fSMTP $smtp, string $from_email )
fSMTP | $smtp | The SMTP connection to send emails over |
string | $from_email | The email address to use in the From: header |
Prints a debugging message if global or code-specific debugging is enabled
void debug( string $message, boolean $force=FALSE )
string | $message | The debug message |
boolean | $force | If debugging should be forced even when global debugging is off |
Disables including the context information with exception and error messages
The context information includes the following superglobals:
void disableContext( )
Creates a string representation of any variable using predefined strings for booleans, NULL and empty strings
The string output format of this method is very similar to the output of print_r() except that the following values are represented as special strings:
string dump( mixed $data )
mixed | $data | The value to dump |
The string representation of the value
Enables debug messages globally, i.e. they will be shown for any call to debug()
void enableDebugging( boolean $flag )
boolean | $flag | If debugging messages should be shown |
Turns on a feature where undefined constants are automatically created with the string value equivalent to the name
This functionality only works if enableErrorHandling() has been called first. This functionality may have a very slight performance impact since a E_STRICT error message must be captured and then a call to define() is made.
void enableDynamicConstants( )
Turns on developer-friendly error handling that includes context information including a backtrace and superglobal dumps
All errors that match the current error_reporting() level will be redirected to the destination and will include a full backtrace. In addition, dumps of the following superglobals will be made to aid in debugging:
The superglobal dumps are only done once per page, however a backtrace in included for each error.
If an email address is specified for the destination, only one email will be sent per script execution. If both error and exception handling are set to the same email address, the email will contain both errors and exceptions.
void enableErrorHandling( string $destination )
string | $destination | The destination for the errors and context information - an email address, a file path or the string 'html' |
Turns on developer-friendly uncaught exception handling that includes context information including a backtrace and superglobal dumps
Any uncaught exception will be redirected to the destination specified, and the page will execute the $closing_code callback before exiting. The destination will receive a message with the exception messaage, a full backtrace and dumps of the following superglobals to aid in debugging:
The superglobal dumps are only done once per page, however a backtrace in included for each error.
If an email address is specified for the destination, only one email will be sent per script execution.
If an email address is specified for the destination, only one email will be sent per script execution. If both exception and error handling are set to the same email address, the email will contain both exceptions and errors.
void enableExceptionHandling( string $destination, callback $closing_code=NULL, array $parameters=array() )
string | $destination | The destination for the exception and context information - an email address, a file path or the string 'html' |
callback | $closing_code | This callback will happen after the exception is handled and before page execution stops. Good for printing a footer. If no callback is provided and the exception extends fException, fException::printMessage() will be called. |
array | $parameters | The parameters to send to $closing_code |
Prints the dump() of a value
The dump will be printed in a <pre> tag with the class exposed if PHP is running anywhere but via the command line (cli). If PHP is running via the cli, the data will be printed, followed by a single line break (\n).
If multiple parameters are passed, they are exposed as an array.
void expose( mixed $data [, ... ] )
mixed | $data [, ... ] | The value to show |
If debugging is enabled
boolean getDebug( boolean $force=FALSE )
boolean | $force | If debugging is forced |
If debugging is enabled
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Handles an error, creating the necessary context information and sending it to the specified destination
void handleError( integer $error_number, string $error_string, string $error_file=NULL, integer $error_line=NULL, array $error_context=NULL )
integer | $error_number | The error type |
string | $error_string | The message for the error |
string | $error_file | The file the error occurred in |
integer | $error_line | The line the error occurred on |
array | $error_context | A references to all variables in scope at the occurence of the error |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Handles an uncaught exception, creating the necessary context information, sending it to the specified destination and finally executing the closing callback
void handleException( object $exception )
object | $exception | The uncaught exception to handle |
Registers a callback to handle debug messages instead of the default action of calling expose() on the message
void registerDebugCallback( callback $callback )
callback | $callback | A callback that accepts a single parameter, the string debug message to handle |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Resets the configuration of the class
void reset( )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Sends an email or writes a file with messages generated during the page execution
This method prevents multiple emails from being sent or a log file from being written multiple times for one script execution.
void sendMessagesOnShutdown( )
Temporarily enables capturing error messages
void startErrorCapture( integer $types=NULL, string $regex=NULL )
integer | $types | The error types to capture - this should be as specific as possible - defaults to all (E_ALL | E_STRICT) |
string | $regex | A PCRE regex to match against the error message |
Stops capturing error messages, returning all that have been captured
array stopErrorCapture( string $regex=NULL )
string | $regex | A PCRE regex to filter messages by |
The captured error messages
The fCryptography class provides a simple interface for password hashing, symmetric key encryption and public key encryption. All of the cryptographic methods have been chosen since they are considered to be best practices and fairly secure (which is obviously a relative term). In each section the general algorithm will be explained to help in understanding what level of security is afforded.
Also note that all of the method return values will contain only ASCII characters — base64 encoding is used to ensure this.
Please do note that this is not meant to be a definitive resource for cryptographic information, I highly recommend reading more about cryptography from other sources.
The built in random number generator in PHP, rand() can suffer from a number of attacks, such as outlined by Steffan Esser in his article mt_srand and not so random numbers. To help combat this issue, the fCryptography class provides a random()
method that will ensure that the random number generator has been seeded with a good seed value. Be sure not to call the PHP functions srand()
or mt_srand()
in your code.
// Get a random number
$rand = fCryptography::random();
// Get a random number between 1 and 100
$rand = fCryptography::random(1, 100);
If you need a random string (such as for an authentication code), you can use the method randomString()
. It requires one parameter, $length
, which is the desired string length. A second optional parameter, $type
, allows specifying what set of characters will be used in the string. Options include 'alphanumeric'
(the default), 'alpha'
, 'numeric'
, and 'hexadecimal'
.
// Generate an 8 character alphanumeric string
$code = fCryptography::randomString(8);
// Generate a 32 character hexadecimal string
$code = fCryptography::randomString(32, 'hexadecimal');
Password security is easy to overlook when you dont have the whole picture. Even if your site doesnt store any vital information, most users use the same password for most if not all of their accounts, possibly even the email account they used on your site. Because of this fact password security is a big deal for all sites, and storing passwords in plain text is a very insecure practice.
A common practice is to hash a users password using a method such as MD5. Passwords that have been hashed this way are susceptible to rainbow table attacks. In order to provide a reasonable amount of security, all passwords should be hashed using a salt.
By using a salt when hashing the password you pretty much require that a hacker use brute force to decrypt the password. As processors have been increasing in speed the number of hashes that can be checked per second is constantly increasing, not to mention that hardware exists specially built for hashing. Thus to help prevent brute force attacks it is a common practice to make the hash take a significant enough amount of computation so that brute force attacks take even longer.
The key is to balance the computation time to be short enough that a normal user logging in would not notice a performance issue, while a hacker would be significantly slowed in their task of generating millions of hashes.
The hashPassword()
method takes the provided password and runs it through a non-trivial number of rounds of SHA-1 hashing, alternating including and excluding a random salt from the value being hashed.
The output of includes an indicator that the fCryptography class was used for hashing, the salt used (which is unique for each invocation of the method), and the hashed password. The password can be verified by sending this return value and the password to test to the checkPasswordHash()
.
Here is an example of using the methods:
$hash = fCryptography::hashPassword('Example password');
// ...
if (fCryptography::checkPasswordHash('Example password', $hash)) {
echo 'The correct password was entered';
}
Here is what the output of hashPassword()
looks like:
fCryptography::password_hash#Gu19bpZN94#ac74c4ad9ed7103e051e583af86599b95237e9af
Symmetric key encryption is encryption where the same secret key is used for both encryption and decryption. This type of encryption can easily be thwarted if the secret key is stored on the server and that machine is compromised. Because of this it is not recommended as a transparent encryption happening behind the scenes, but rather when a user will provide the secret key each time they access a site.
The symmetricKeyEncrypt()
method uses the Rijndael block cipher, which is the basis of the Advanced Encryption Standard (or AES). The Rijndael cipher is used with a 192 bit block size and a 256 bit key in cipher feedback mode with a random IV. The encrypted plain text and IV are then run through HMAC-SHA-1 for data integrity checking during decryption.
When decrypting using symmetricKeyDecrypt()
, the HMAC is verified to ensure the encrypted value has not been corrupted or altered. The secret key and IV are then used to decrypt the original plain text.
If you arent familiar with cryptography or the algorithms mentioned, you can rest assured that the technique and algorithms used are approved for use by the U.S. government when storing TOP SECRET documents.
The fCryptography class does enforce the requirement that the secret key is at least 8 characters long. In addition, the return value from symmetricKeyEncrypt()
is not simply an encrypted string, but also contains information indicating the encrypted text came from Flourish, the IV, the encrypted plain text and the HMAC.
Here is an example of using symmetricKeyEncrypt()
and symmetricKeyDecrypt()
:
// Encrypt the data
$ciphertext = fCryptography::symmetricKeyEncrypt('This is a secret message to be encrypted', 'Secret Key');
// Decrypt the data
$plaintext = fCryptography::symmetricKeyDecrypt($ciphertext, 'Secret Key');
Here is an example of what the ciphertext would look like:
fCryptography::symmetric#1l2rt6kP0kqdIDuSVpGrSoTy08sE33fAMf6Y0M0CtOU=#csRlMH6l6dnks6hCOhI+IxDAA69GAI/d5L3L77G0parEdlc/dDDz1z/ASX/I8suj/uAEXjxShhcrEwo0IzYODuoeSdmJvGKZJtquCWkKPg==#ef5973e32808e01f5be2745a0a9ef61396992ddf
Please note that the symmetric key methods require both the mcrypt and hash extensions for PHP to be installed.
Public-key encryption is encryption where a readily available public key can be used by anyone to encrypt data that only the corresponding private key can decrypt. The private key is usually protected by a password, allowing for it to be stored on a server without fear of compromise by simply reading it.
Public-key cryptography would allow for encryption to happen behind the scenes on the front-end of a web site, while requiring users decrypting the information to provide the private-key password.
To perform public-key encryption you will need an X.509 public-key certificate and private key in PEM format. Please visit the Obtaining a Secure Certificate Key Pair page for more information.
The public certificate is not actually used to do the encryption, but rather a randomly generated 128-bit key is used for the encryption process and then the public certificate (which is 1024 or 2048 bits in length) is used to encrypt that random key. The cipher used for the encryption is the RC4 stream cipher. On the receiving end the private key is used to decrypt the random RC4 key and then that is in turn used to decrypt the actual data.
To ensure that the data is not tampered with between encryption and decryption, the encrypted random key and the ciphertext are run through HMAC-SHA-1 and included in the output. This HMAC is verified when the data is decrypted.
To encrypt a string you will need the public certificate and the method publicKeyEncrypt()
. Simply pass the message and the path to the public certificate to the method and you will receive an encrypted string:
$ciphertext = fCrytography::publicKeyEncrypt('This is a secret message!', './public.cer');
Here is an example of what the ciphertext would look like:
fCryptography::public#VHpfAoWC2h2sqFtdRy7Ihv7P2C1VPQ0SQduHT5div6+nq8Y0o6+sM5XgLDl+zXMnmY4+xOtohsBaFQ/MDiWA7VI5vXgK0j04vv6bcnkGwFz1M+o3Tuyo8Yu152Gj7iajJz9S1fiLOo4PMiRnafxbtfyExMFKJ6wiyc7AfjiGUUM=#YLehWyfNOvUEPrsFtRFeBHtvKJOy#4258dbd7e6bd144ab1aa98a0f5d2a9f8be9fb231
To decrypt the ciphertext the private key and private key password are required to be passed with the ciphertext to the publicKeyDecrypt()
method. If the private key in unencrypted (not recommended when placed on the same server as the encrypted data) then an empty string can be passed as the password parameter:
// Using a private key that is encrypted with a password
$plaintext = fCryptography::publicKeyDecrypt($ciphertext, './private.key', 'private key password');
// Using a private key that is not encrypted
$plaintext = fCryptography::publicKeyDecrypt($ciphertext, './private.key', '');
Public-key cryptography also provides functionality to verify that a message has originated from a specific author through signing and verification. The private key can be used to create a secure message digest of a message or string and then the public key can be used to verify it.
The method publicKeySign()
will create the signature for a plaintext source. It takes three parameters, the $plaintext
to create a signature for, the $private_key_file
path and the $password
for the private key. The signature will be returned by the method in base-64 encoding to allow for transmission over channels that may not properly transmit binary data.
$signature = fCryptography::publicKeySign($plaintext, './private.key', 'private key password');
The signature of any plaintext can be authenticated by using the authors public key and the method publicKeyVerify()
. This method will return a boolean indicating if the claimed author actually created the plaintext in question. The three parameters are the $plaintext
, the base-64 encoded $signature
and the authors $public_key_file
.
if (fCryptography::publicKeyVerify($plaintext, $signature, './public.cer')) {
echo 'This message has been verified authentic!';
}
If you are hashing or encrypting data using the fCryptography class but need to reverse the process on another system you will probably want to refer to this section as a starting point, and then look at the source code.
If you simply wish to decrypt data encrypted with or check a hash created by the fCryptography class, please check the appropriate section above the relevant decrypt or check method.
hashPassword()
outputs a string in the following format:
fCryptography::password_hash#{salt}#{hash}
{salt}
is a random 10 character alphanumeric string that prepended to the password before being run through sha1()
. {hash}
is a result of another non-trivial number of iterations of executing sha1()
on the result of the last sha1()
call appended with the original password or salt in alternating fashion.
symmetricKeyEncrypt()
outputs a string in the following format:
fCryptography::symmetric#{iv}#{ciphertext}#{hmac}
The {iv}
is the randomly generated initialization vector that has been base-64 encoded.
The {ciphertext}
is the provided plaintext that has been encrypted using Rijndael-192 in CFB mode using the initialization vector and then base-64 encoded.
The {hmac}
is the encrypted initialization vector concatenated with the encrypted plaintext (neither having been base-64 encoded yet). It is then run through a SHA-1 HMAC using the secret key provided.
publicKeyEncrypt()
outputs a string in the following format:
fCrytography::public#{secret_key}#{ciphertext}#{hmac}
The {secret_key}
is a random 128-bit RC4 secret key that has been encrypted (via RSA) using provided public certificate. It is base64-encoded before added to the output string.
{ciphertext}
is the plaintext that has been encrypted via the RC4 stream cipher using the random RC4 key and is encoded using base64.
{hmac}
is a SHA-1 HMAC of the concatenated encrypted secret key and the ciphertext generated using the plaintext as the key. Neither the encrypted secret key, nor the ciphertext is base64 encoded before the HMAC is calculated.
publicKeySign()
returns a base-64 encoded SHA-1 hash of the plaintext that has been encrypted using the private key.
Provides cryptography functionality, including hashing, symmetric-key encryption and public-key encryption
1.0.0b14 | Added the base36, base56 and custom types to randomString() 8/25/11 |
---|---|
1.0.0b13 | Updated documentation about symmetric-key encryption to explicitly state block and key sizes, added base64 type to randomString() 11/6/10 |
1.0.0b12 | Fixed an inline comment that incorrectly references AES-256 11/4/10 |
1.0.0b11 | Updated class to use fCore::startErrorCapture() instead of error_reporting() 8/9/10 |
1.0.0b10 | Added a missing parameter to an fProgrammerException in randomString() 7/29/10 |
1.0.0b9 | Added hashHMAC() 4/20/10 |
1.0.0b8 | Fixed seedRandom() to pass a directory instead of a file to disk_free_space() 3/9/10 |
1.0.0b7 | SECURITY FIX: fixed issue with random() and randomString() not producing random output on OSX, made seedRandom() more robust 10/6/09 |
1.0.0b6 | Changed symmetricKeyEncrypt() to throw an fValidationException when the $secret_key is less than 8 characters 9/30/09 |
1.0.0b5 | Fixed a bug where some windows machines would throw an exception when generating random strings or numbers 6/9/09 |
1.0.0b4 | Updated for new fCore API 2/16/09 |
1.0.0b3 | Changed @ error suppression operator to error_reporting() calls 1/26/09 |
1.0.0b2 | Backwards compatibility break - changed symmetricKeyEncrypt() to not encrypt the IV since we are using HMAC on it 1/26/09 |
1.0.0b | The initial implementation 11/27/07 |
Checks a password against a hash created with hashPassword()
boolean checkPasswordHash( string $password, string $hash )
string | $password | The password to check |
string | $hash | The hash to check against |
If the password matches the hash
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Provides a pure PHP implementation of hash_hmac() for when the hash extension is not installed
string hashHMAC( string $algorithm, string $data, string $key )
string | $algorithm | The hashing algorithm to use: 'md5' or 'sha1' |
string | $data | The data to create an HMAC for |
string | $key | The key to generate the HMAC with |
The HMAC
Hashes a password using a loop of sha1 hashes and a salt, making rainbow table attacks infeasible
string hashPassword( string $password )
string | $password | The password to hash |
An 80 character string of the Flourish fingerprint, salt and hashed password
Decrypts ciphertext encrypted using public-key encryption via publicKeyEncrypt()
A public key (X.509 certificate) is required for encryption and a private key (PEM) is required for decryption.
string publicKeyDecrypt( string $ciphertext, string $private_key_file, string $password )
string | $ciphertext | The content to be decrypted |
string | $private_key_file | The path to a PEM-encoded private key |
string | $password | The password for the private key |
The decrypted plaintext
Encrypts the passed data using public key encryption via OpenSSL
A public key (X.509 certificate) is required for encryption and a private key (PEM) is required for decryption.
string publicKeyEncrypt( string $plaintext, string $public_key_file )
string | $plaintext | The content to be encrypted |
string | $public_key_file | The path to an X.509 public key certificate |
A base-64 encoded result containing a Flourish fingerprint and suitable for decryption using publicKeyDecrypt()
Creates a signature for plaintext to allow verification of the creator
A private key (PEM) is required for signing and a public key (X.509 certificate) is required for verification.
string publicKeySign( string $plaintext, string $private_key_file, string $password )
string | $plaintext | The content to be signed |
string | $private_key_file | The path to a PEM-encoded private key |
string | $password | The password for the private key |
The base64-encoded signature suitable for verification using publicKeyVerify()
Checks a signature for plaintext to verify the creator - works with publicKeySign()
A private key (PEM) is required for signing and a public key (X.509 certificate) is required for verification.
boolean publicKeyVerify( string $plaintext, string $signature, string $public_key_file )
string | $plaintext | The content to check |
string | $signature | The base64-encoded signature for the plaintext |
string | $public_key_file | The path to an X.509 public key certificate |
If the public key file is the public key of the user who signed the plaintext
Generates a random number using mt_rand() after ensuring a good PRNG seed
integer random( integer $min=NULL, integer $max=NULL )
integer | $min | The minimum number to return |
integer | $max | The maximum number to return |
The psuedo-random number
Returns a random string of the type and length specified
string randomString( integer $length, string $type='alphanumeric' )
integer | $length | The length of string to return |
string | $type | The type of string to return: 'base64', 'base56', 'base36', 'alphanumeric', 'alpha', 'numeric', or 'hexadecimal', if a different string is provided, it will be used for the alphabet |
A random string of the type and length specified
Decrypts ciphertext encrypted using symmetric-key encryption via symmetricKeyEncrypt()
Since this is symmetric-key cryptography, the same key is used for encryption and decryption.
string symmetricKeyDecrypt( string $ciphertext, string $secret_key )
string | $ciphertext | The content to be decrypted |
string | $secret_key | The secret key to use for decryption |
The decrypted plaintext
Encrypts the passed data using symmetric-key encryption
Since this is symmetric-key cryptography, the same key is used for encryption and decryption.
string symmetricKeyEncrypt( string $plaintext, string $secret_key )
string | $plaintext | The content to be encrypted |
string | $secret_key | The secret key to use for encryption - must be at least 8 characters |
An encrypted and base-64 encoded result containing a Flourish fingerprint and suitable for decryption using symmetricKeyDecrypt()
The fDatabase class abstracts interaction with MySQL, PostgreSQL, SQLite, Microsoft SQL Server (MSSQL), Oracle and IBM DB2 databases.
The fDatabase class allows for interaction with a number of popular relational database management systems. Rather than requiring a specific PHP extension to interact with each of these DBs, Flourish shows its portable nature by automatically detecting and using the installed extension for the database type specified.
Here is a list of the supported DBs and the PHP extensions that are currently supported:
DB | PHP Extensions |
MSSQL | sqlsrv, pdo_dblib, mssql (or sybase) |
MySQL | mysql, mysqli, pdo_mysql |
Oracle | oci8, pdo_oci |
PostgreSQL | pgsql, pdo_pgsql |
SQLite | pdo_sqlite (for v3.x), sqlite (for v2.x) |
DB2 | ibm_db2, pdo_ibm |
As a first step to interacting with a database, a connection needs to be made. This is done by creating a new instance of the fDatabase class. The constructor takes the database $type
, $name
, $username
, $password
, $server
, $port
and $timeout
as parameters. MSSQL, MySQL, Oracle and PostgreSQL databases require all parameters except for the $server
, $port
and $timeout
. SQLite databases only require the $type
and $name
parameters.
// Connecting to a MSSQL database on localhost running on the default port
$mssql_db = new fDatabase('mssql', 'my_database', 'username', 'password');
// Connecting to a MySQL database on the server example.com
$mysql_db = new fDatabase('mysql', 'my_database', 'username', 'password', 'example.com');
// Connecting to an Oracle database on localhost
$oracle_db = new fDatabase('oracle', 'my_database', 'username', 'password');
// Connecting to a PostgreSQL database on the current server using a non-standard port
$pgsql_db = new fDatabase('postgresql', 'my_database', 'username', 'password', 'localhost', 1234);
// Connection to an SQLite database
$sqlite_db = new fDatabase('sqlite', '/path/to/database/file');
// Connecting to a remote DB2 server
$db2_db = new fDatabase('db2', 'my_database', 'username', 'password', 'remote.host.com', 60000);
// Connect on the default port with a timeout of 1 second
$pgsql_db = new fDatabase('postgresql', 'my_database', 'username', 'password', 'localhost', NULL, 1);
It is possible to connect to a MySQL database using a socket connection by passing sock:/path/to/the/socket
as the $server
parameter. For a PostgreSQL socket connection, simply pass sock:
in the $server
parameter.
The $timeout
parameter accepts integers, and represents the number of seconds after which to stop trying to connect to the database.
When creating an fDatabase instance, a connection to the server is not automatically established. Instead, once a response is required from the server, then fDatabase will establish the connection. To force a connection at a specific time, usually for the sake of handling connection exceptions, call the method connect()
.
try {
$db = new fDatabase('postgresql', 'my_database', 'username', 'password');
// Please note that calling this method is not required, and simply
// causes an exception to be thrown if the connection can not be made
$db->connect();
} catch (fAuthorizationException $e) {
$e->printMessage();
}
When connecting to a server, fDatabase can throw either an fAuthorizationException when a username or password is incorrect, or an fConnectivityException when a server does not respond, a hostname lookup fails, or the database specified can not be accessed.
Catching both fAuthorizationException and fConnectivityException objects can be useful for handling validation of user-supplied connection parameters.
try {
$db = new fDatabase($type, $database, $username, $password, $server, $port);
$db->connect();
} catch (fAuthorizationException $e) {
fMessaging::create('error', $e->getMessage());
} catch (fConnectivityException $e) {
fMessaging::create('error', $e->getMessage());
}
Catching fConnectivityException objects can also be used to handle failover in replicated database environments.
// Use APC to cache the server status
$cache = new fCache('apc');
$servers = array('server1', 'server2', 'server3');
foreach ($servers as $server) {
try {
// Skip servers that are down
if ($cache->get($server . '-down')) { continue; }
// Use a one second timeout for fast failover
$db = new fDatabase('postgresql', 'my_database', 'username', 'password', $server, NULL, 1);
$db->connect();
break;
// If the connection failed, mark the server as down for 5 minutes
} catch (fConnectivityException $e) {
$cache->set($server . '-down', TRUE, 300);
}
}
Please note that the database password is stored in the object, and may be exposed via print_r()
, fCore::expose(), or similar methods. fResult, fUnbufferedResult, fSchema, fStatement and fSQLTranslation objects also contain a reference to an fDatabase object and thus could expose password data in a similar fashion.
Once you have established a databases connection you can start executing queries using the query()
method. This method executes a query and returns an instance of the fResult class to access returned rows and get information about the query that was executed.
Please note this method executes queries in a buffered manner. This means that all results are loaded into PHP memory, which can cause performance issues for very large result sets (in the order of 500+ rows). For large result sets, unbuffered queries will generally yield better performance (at the cost of certain other restrictions).
// Execute a SQL query and retrieve all returned rows
$result = $mysql_db->query('SELECT * FROM users LIMIT 5');
foreach ($result as $row) {
// Access the row
}
For more information about what can be done with a query result, please see the fResult page.
Unbuffered queries will often have better performance for large results sets, however the exact details can vary from database driver to driver. Many database drivers will only allow a single unbuffered query to be active at any point. If another database query is called, it will either cause the previous call to close or will fail itself.
Calling unbufferedQuery()
will return an instance of fUnbufferedResult. This is similar to an fResult object, however does not have the ability to retrieve the number of returned rows, or seek to different rows in the set.
Please note that some database/extension combinations do not provide unbuffered query functionality, and thus will not necessarily gain the same performance benefits as others. The following database extensions are known to have unbuffered benefits: pdo_*, mysql, mysqli, sqlite.
$result = $mysql_db->unbufferedQuery('SELECT * FROM users');
foreach ($result as $row) {
// Don't execute another query in here or the original result will be destroyed
}
In situations where no result is required to be iterated over, such as an UPDATE
statement, the execute()
method can be used. This method takes all of the same parameters as query()
, however it does not return an fResult object.
$db->execute("UPDATE users SET name = %s WHERE name = %s", 'Will', 'William');
Like the query()
and unbufferedQuery()
methods, an fSQLException will be thrown if a SQL error occurs.
One of the features fDatabase provides for portable code is the ability to run SQL queries that work across all supported databases. The Flourish SQL page includes a list of all supported SQL syntax and what it is translated into for each different database engine.
The two methods translatedQuery()
and unbufferedTranslatedQuery()
work exactly the same as query()
and unbufferedQuery()
except that the SQL statements are translated from Flourish SQL into the SQL dialect supported by the current database.
For instance, if you are familiar with MSSQL databases, you will know that the LIMIT
syntax is not valid, but instead it required you to use the TOP
keyword. The following PHP:
$result = $mssql_db->translatedQuery("SELECT * FROM users LIMIT 5");
Would actually be executed as the following SQL:
SELECT TOP 5 * FROM users
If you aren't familiar with what database transactions are, please read the Wikipedia page Database transaction first.
The fDatabase class by default executes all SQL queries immediately, in what is referred to as auto-commit mode. To perform one or more queries in a transaction that can be rolled back or commited, basic SQL queries are used instead of method calls.
fDatabase does minimal translation of transaction SQL queries since there isn't a single consistent set of commands for all supported databases. Each of these queries will work with any of the supported databases.
// Start a transaction
$db->query('BEGIN');
// Commit changes made during this transaction
$db->query('COMMIT');
// Rollback changes that have not been committed
$db->query('ROLLBACK');
Please note that MySQL MyISAM tables do not support transactions and will auto-commit even if a transaction has been started. Please see the ORM Conventions: MySQL Storage Engine section for details about this and other drawbacks to using MyISAM.
If you have any experience with database interaction you are probably familiar with SQL injection attacks. For this reason, and the fact that the supported databases have varying representations for the various data types, it is recommended that all data going in and coming out of the database be escaped and unescaped respectively.
Arbitrary SQL escaping data can be done at any point by the escape()
method, but should normally be done when calling query()
(also translatedQuery()
, unbufferedQuery()
, unbufferedTranslatedQuery()
).
The query
methods accept $sql
as the first parameter, followed by the required number of values to bind/inject into the query. This injection is done via data type placeholders in the $sql
, and fully escapes the values based on the data type. These placeholder are similar to some of the formatting strings in sprintf(). Here is a list of the various placeholders and what data type the value will be escaped as:
'%l'
: blob'%b'
: boolean'%d'
: date'%f'
: float'%i'
: integer'%s'
: string'%t'
: time'%p'
: timestamp// Escape a user id and name into the SQL statement and execute it
$result = $db->query('SELECT * FROM users WHERE age = %i AND last_name = %s', 18, "O'Shea");
The query methods by default do not use prepared statements, but instead create fully escaped SQL commands and execute them. For repeat queries or large string/binary values, 32k+ for Oracle/DB2, larger for other databases, a prepared fStatement object should be passed in place of the SQL string.
When using the escape()
method, two or more parameters are required. The first, $sql_or_type
, allows passing the data type to be escaped, or an SQL statement with data type placeholders. The second (and subsequent) parameter, $value
is the PHP value to escape.
The permissible data types to pass into $sql_or_type
include:
'blob'
'boolean'
'date'
'float'
'integer'
'string'
'time'
'timestamp'
Escaping not only protects against SQL injection attacks, but also ensures that you are comparing proper data types in your SQL since all values are validated before being escaped. Below are some examples using escape()
in various ways:
// Escape a string
$sql_string = $db->escape('string', "This ain't gonna break your SQL");
// Escape a boolean
$sql_boolean = $db->escape('boolean', TRUE);
// Escape a float
$sql_float = $db->escape('%f', '12.39');
The above statements would produce the following SQL (in a SQLite database):
'This ain''t gonna break your SQL'
'1'
12.39
In addition to escaping single values for SQL, it is also possible to escape an array of values. When passing a data type or placeholder as the first parameter and the array of values as the second, an array of escaped values will be returned.
$escaped_integers = $db->escape('integer', array(1, 5, 'not an int'));
If the first parameter is a SQL string, the array of values will be inserted into the SQL, separated by commas.
// This will return "SELECT * FROM users WHERE user_id IN (1, 3, 7, 10)"
$sql = $db->escape(
"SELECT * FROM users WHERE user_id IN (%i)",
array(1, 3, 7, 10)
);
All databases support quoting table and columns to allow SQL reserved words to be used as identifiers. The SQL standard is to use double quotes. Just like data-type placeholders, there is an identifier placeholder, %r
, that can be used with escape()
and the various query()
methods.
$result = $db->query("SELECT * FROM %r WHERE %r = %i", 'users', 'user_id', 1);
This functionality will only be useful when dynamically creating SQL commands. With static SQL, developers can simply wrap identifiers in double quotes themselves.
$result = $db->query('SELECT * FROM "users" WHERE "user_id" = %i', 1);
In addition to databases requiring that data going in be formatted a certain way, many database/driver combinations in PHP dont deliver values back in the appropriate PHP data type. The unescape()
method provides a consistent way to ensure that all data coming out of the database is stored correctly in PHP.
The first parameter, $data_type
, should be one of the string data type names or a data type placeholder as convered with the escape()
method. The second parameter, $value
, is the value being returned from the database.
Here are some examples of using the unescape()
method:
$is_authorized = $db->unescape('boolean', $row['is_authorized']);
$date_created = $db->unescape('%d', $row['date_created']);
Please note that if you are using the ORM in Flourish, unescaping will be done automatically.
Prepared statements can improve performance when working with a query that will be executed multiple times with different sets of data. The prepare()
and translatedPrepare()
methods accept a single parameter, $sql
, which should contain the SQL statement to prepare. They both return an fStatement object which can in turn be passed to execute()
, query()
or unbufferedQuery()
in place of a SQL string.
$statement = $db->prepare("INSERT INTO users (first_name, last_name) VALUES (%s, %s)");
$db->query($statement, 'Will', 'Bond');
$db->query($statement, 'John', 'Smith');
Just like with query()
and translatedQuery()
, the values for the placeholders are passed as parameters after the fStatement object.
Computation is saved since the placeholders are parsed only when the statement is created, instead of upon every execution. If translatedPrepare()
is called, the SQL translation is also performed only once.
In addition to Flourish level optimizations, many databases and PHP database extensions support prepared statements and may improve performance by caching query plans. For the databases/extensions that don't support prepared statements, a prepared statement will be executed behind the scenes using the normal query()
method.
Please note, due to the way that fDatabase is written, prepared statements are not necessary to prevent SQL injection attacks.
The one limitation of using prepared statements instead of a normal query is that multiple values can not be passed for a placeholder. For those situations, execute()
, query()
or translatedQuery()
should be used instead.
// This will NOT work
$statement = $db->prepare("SELECT * FROM users WHERE user_id IN (%i)");
$res = $db->query($statement, array(1, 2, 3));
foreach (res as $row) {
//
}
There is some useful debugging functionality built into the class that can help when diagnosing SQL issues. If enableDebugging()
or fCore::enableDebugging() (for global debugging) is called with TRUE
, the fDatabase class will display each SQL statement and how long it took to perform. In addition, when the class is destructed, a total SQL execution time will be output.
// Enable SQL statement printing
$db->enableDebugging(TRUE);
Both PostgreSQL and Oracle databases require some schema information about the database to properly fetch the last generated auto-incrementing primary key that is generated from an INSERT
SQL statement. The fDatabase class will automatically retrieve that schema information, however in the interest of performance, you may wish to cache the results.
Along a similar vein, MSSQL databases don't support UTF-8 as the character set for non-national data types, so the fDatabase class will determine the databases character set to ensure proper transcoding to UTF-8 is performed.
The enableCaching()
method accepts an instance of the fCache class, and will save this schema information so it does not need to be fetched on each page load.
$db->enableCaching(new fCache('file', '/path/to/db.cache'));
The method clearCache()
will clear out the cached information, which would be useful when the database schema changes.
When using the Flourish ORM, the fORM class provides some useful caching functionality that will automatically clear the cache when database errors occur.
One of the most advanced features of fDatabase is the ability to be able to pass all SQL to a callback at various points in execution to allow for modification or logging. The method registerHookCallback()
accepts two parameters, the $hook
to register for and the $callback
to register.
There are three different hooks avaliable, 'unmodified'
, 'extracted'
and 'run'
. The 'unmodified'
hook is called with the raw SQL passed to fDatabase, the 'extracted'
hooks provides the SQL with all string literals extracted, and the 'run'
hook provides the SQL after it has been executed.
The API documentation has details about the required method signatures for each callback. Below are some examples of usage:
// Using the 'extracted' hook to collapsing excess whitespace for easier to read logs
function trim_sql($db, &$sql, &$values) {
$sql = preg_replace('#\s+#', ' ', $sql);
}
$db->registerHookCallback('extracted', 'trim_sql');
// Using the 'run' hook for logging of slow queries
function log_sql($db, $statement, $query_time, $result) {
// Don't log queries unless they take half a second or more
if ($query_time < 0.5) { return; }
// This handles prepared statements since the statement and values are separate
if (is_array($statement)) {
$sql = '"' . $statement[0]->getSQL() . '" with the values: ' . join(", ", array_map('fCore::dump', $values));
} else {
$sql = '"' . $statement . '"';
}
echo 'The following query took ' . $query_time . " seconds: \n" . $sql;
}
$db->registerHookCallback('run', 'log_sql');
Provides a common API for different databases - will automatically use any installed extension
This class is implemented to use the UTF-8 character encoding. Please see UTF-8 for more information.
The following databases are supported:
The class will automatically use the first of the following extensions it finds:
The odbc and pdo_odbc extensions are not supported due to character encoding and stability issues on Windows, and functionality on non-Windows operating systems.
1.0.0b41 | Fixed an array to string conversion notice 9/21/12 |
---|---|
1.0.0b40 | Fixed a bug with notices being triggered when failing to connect to a SQLite database 6/20/11 |
1.0.0b39 | Fixed a bug with detecting some MySQL database version numbers 5/24/11 |
1.0.0b38 | Backwards Compatibility Break - callbacks registered to the extracted hook via registerHookCallback() no longer receive the $strings parameter, instead all strings are added into the $values parameter - added getVersion(), fixed a bug with SQLite messaging, fixed a bug with __destruct(), improved handling of transactional queries, added close(), enhanced class to throw four different exceptions for different connection errors, silenced PHP warnings upon connection error 5/9/11 |
1.0.0b37 | Fixed usage of the mysqli extension to only call mysqli_set_charset() if it exists 3/4/11 |
1.0.0b36 | Updated escape() and methods that use escape() to handle float values that don't contain a digit before or after the . 2/1/11 |
1.0.0b35 | Updated the class to replace LIMIT and OFFSET value placeholders in the SQL with their values before translating since most databases that translate LIMIT statements need to move or add values together 1/11/11 |
1.0.0b34 | Fixed a bug with creating translated prepared statements 1/9/11 |
1.0.0b33 | Added code to explicitly set the connection encoding for the mysql and mysqli extensions since some PHP installs don't see to fully respect SET NAMES 12/6/10 |
1.0.0b32 | Fixed handling auto-incrementing values for Oracle when the trigger was on INSERT OR UPDATE instead of just INSERT 12/4/10 |
1.0.0b31 | Fixed handling auto-incrementing values for MySQL when the INTO keyword is left out of an INSERT statement 11/4/10 |
1.0.0b30 | Fixed the pgsql, mssql and mysql extensions to force a new connection instead of reusing an existing one 8/17/10 |
1.0.0b29 | Backwards Compatibility Break - removed enableSlowQueryWarnings(), added ability to replicate via registerHookCallback() 8/10/10 |
1.0.0b28 | Backwards Compatibility Break - removed ODBC support. Added support for the pdo_ibm extension. 7/31/10 |
1.0.0b27 | Fixed a bug with running multiple copies of a SQL statement with string values through a single translatedQuery() call 7/14/10 |
1.0.0b26 | Updated the class to use new fCore functionality 7/5/10 |
1.0.0b25 | Added IBM DB2 support 4/13/10 |
1.0.0b24 | Fixed an auto-incrementing transaction bug with Oracle and debugging issues with all databases 3/17/10 |
1.0.0b23 | Resolved another bug with capturing auto-incrementing values for PostgreSQL and Oracle 3/15/10 |
1.0.0b22 | Changed clearCache() to also clear the cache on the fSQLTranslation 3/9/10 |
1.0.0b21 | Added execute() for result-less SQL queries, prepare() and translatedPrepare() to create fStatement objects for prepared statements, support for prepared statements in query() and unbufferedQuery(), fixed default caching key for enableCaching() 3/2/10 |
1.0.0b20 | Added a parameter to enableCaching() to provide a key token that will allow cached values to be shared between multiple databases with the same schema 10/28/09 |
1.0.0b19 | Added support for escaping identifiers (column and table names) to escape(), added support for database schemas, rewrote internal SQL string spliting 10/22/09 |
1.0.0b18 | Updated the class for the new fResult and fUnbufferedResult APIs, fixed unescape() to not touch NULLs 8/12/09 |
1.0.0b17 | Added the ability to pass an array of all values as a single parameter to escape() instead of one value per parameter 8/11/09 |
1.0.0b16 | Fixed PostgreSQL and Oracle from trying to get auto-incrementing values on inserts when explicit values were given 8/6/09 |
1.0.0b15 | Fixed a bug where auto-incremented values would not be detected when table names were quoted 7/15/09 |
1.0.0b14 | Changed determineExtension() and determineCharacterSet() to be protected instead of private 7/8/09 |
1.0.0b13 | Updated escape() to accept arrays of values for insertion into full SQL strings 7/6/09 |
1.0.0b12 | Updates to unescape() to improve performance 6/15/09 |
1.0.0b11 | Changed replacement values in preg_replace() calls to be properly escaped 6/11/09 |
1.0.0b10 | Changed date/time/timestamp escaping from strtotime() to fDate/fTime/fTimestamp for better localization support 6/1/09 |
1.0.0b9 | Fixed a bug with escape() where floats that start with a . were encoded as NULL 5/9/09 |
1.0.0b8 | Added Oracle support, change PostgreSQL code to no longer cause lastval() warnings, added support for arrays of values to escape() 5/3/09 |
1.0.0b7 | Updated for new fCore API 2/16/09 |
1.0.0b6 | Fixed a bug with executing transaction queries when using the mysqli extension 2/12/09 |
1.0.0b5 | Changed @ error suppression operator to error_reporting() calls 1/26/09 |
1.0.0b4 | Added a few error suppression operators back in so that developers don't get errors and exceptions 1/14/09 |
1.0.0b3 | Removed some unnecessary error suppresion operators 12/11/08 |
1.0.0b2 | Fixed a bug with PostgreSQL when using the PDO extension and executing an INSERT statement 12/11/08 |
1.0.0b | The initial implementation 9/25/07 |
The extension to use for the database specified
Options include:
string
A cache of database-specific code
array
Composes text using fText if loaded
string compose( string $message, mixed $component [, ... ] )
string | $message | The message to compose |
mixed | $component [, ... ] | A string or number to insert into the message |
The composed and possible translated message
Configures the connection to a database - connection is not made until the first query is executed
Passing NULL to any parameter other than $type and $database will cause the default value to be used.
fDatabase __construct( string $type, string $database, string $username=NULL, string $password=NULL, string $host=NULL, integer $port=NULL, integer $timeout=NULL )
string | $type | The type of the database: 'db2', 'mssql', 'mysql', 'oracle', 'postgresql', 'sqlite' |
string | $database | Name of the database. If SQLite the path to the database file. |
string | $username | Database username - not used for SQLite |
string | $password | The password for the username specified - not used for SQLite |
string | $host | Database server host or IP, defaults to localhost - not used for SQLite. MySQL socket connection can be made by entering 'sock:' followed by the socket path. PostgreSQL socket connection can be made by passing just 'sock:'. |
integer | $port | The port to connect to, defaults to the standard port for the database type specified - not used for SQLite |
integer | $timeout | The number of seconds to timeout after if a connection can not be made - not used for SQLite |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Closes the open database connection
void __destruct( )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
All requests that hit this method should be requests for callbacks
callback __get( string $method )
string | $method | The method to create a callback for |
The callback for the method requested
Clears all of the schema info out of the object and, if set, the fCache object
void clearCache( )
Closes the database connection
void close( )
Connects to the database specified, if no connection exists
This method is only intended to force a connection, all operations that require a database connection will automatically call this method.
void connect( )
Determines the character set of a SQL Server database
void determineCharacterSet( )
Figures out which extension to use for the database type selected
void determineExtension( )
Sets the schema info to be cached to the fCache object specified
void enableCaching( fCache $cache, string $key_token=NULL )
Sets if debug messages should be shown
void enableDebugging( boolean $flag )
boolean | $flag | If debugging messages should be shown |
Escapes a value for insertion into SQL
The valid data types are:
In addition to being able to specify the data type, you can also pass in an SQL statement with data type placeholders in the following form:
Depending on what $sql_or_type and $value are, the output will be slightly different. If $sql_or_type is a data type or a single placeholder and $value is:
If $sql_or_type is a SQL string and $value is:
If $sql_or_type is a SQL string, it is also possible to pass an array of all values as a single parameter instead of one value per parameter. An example would look like the following:
$db->escape(
"SELECT * FROM users WHERE status = %s AND authorization_level = %s",
array('Active', 'Admin')
);
mixed escape( string $sql_or_type, mixed $value [, ... ] )
string | $sql_or_type | This can either be the data type to escape or an SQL string with a data type placeholder - see method description |
mixed | $value [, ... ] | The value to escape - both single values and arrays of values are supported, see method description for details |
The escaped value/SQL or an array of the escaped values
Executes one or more SQL queries without returning any results
void execute( string|fStatement $statement, mixed $value [, ... ] )
string|fStatement | $statement | One or more SQL statements in a string or an fStatement prepared statement |
mixed | $value [, ... ] | The optional value(s) to place into any placeholders in the SQL - see escape() for details |
Returns the database connection resource or object
mixed getConnection( )
The database connection
Gets the name of the database currently connected to
string getDatabase( )
The name of the database currently connected to
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Gets the php extension being used
string getExtension( )
The php extension used for database interaction
Gets the host for this database
string getHost( )
The host
Gets the port for this database
string getPort( )
The port
Gets the fSQLTranslation object used for translated queries
fSQLTranslation getSQLTranslation( )
The SQL translation object
Gets the database type
string getType( )
The database type: 'mssql', 'mysql', 'postgresql' or 'sqlite'
Gets the username for this database
string getUsername( )
The username
Gets the version of the database system
string getVersion( )
The database system version
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Injects an fSQLTranslation object to handle translation
void inject( fSQLTranslation $sql_translation )
fSQLTranslation | $sql_translation | The SQL translation object |
Will indicate if a transaction is currently in progress
boolean isInsideTransaction( )
If a transaction has been started and not yet rolled back or committed
Prepares a single fStatement object to execute prepared statements
Identifier placeholders (%r) are not supported with prepared statements. In addition, multiple values can not be escaped by a placeholder - only a single value can be provided.
fStatement prepare( string $sql )
string | $sql | The SQL to prepare |
A prepared statement object that can be passed to query(), unbufferedQuery() or execute()
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Preprocesses SQL by escaping values, spliting queries, cleaning escaped semicolons, fixing backslashed single quotes and translating
array preprocess( string $sql, array $values, boolean $translate, array &$rollback_queries=NULL )
string | $sql | The SQL to process |
array | $values | Literal values to escape into the SQL |
boolean | $translate | If the SQL should be translated |
array | &$rollback_queries | MySQL doesn't allow transactions around ALTER TABLE statements, and some of those require multiple statements, so this is an array of "undo" SQL statements |
The split out SQL queries, queries that have been translated will have a string key of a number, : and the original SQL, non-translated SQL will have a numeric key
Executes one or more SQL queries and returns the result(s)
fResult|array query( string|fStatement $statement, mixed $value [, ... ] )
string|fStatement | $statement | One or more SQL statements in a string or a single fStatement prepared statement |
mixed | $value [, ... ] | The optional value(s) to place into any placeholders in the SQL - see escape() for details |
The fResult object(s) for the query
Registers a callback for one of the various query hooks - multiple callbacks can be registered for each hook
The following hooks are available:
Methods for the 'unmodified' hook should have the following signature:
Methods for the 'extracted' hook should have the following signature:
The extracted hook is the best place to modify the SQL since there is no risk of breaking string literals. Please note that there may be empty strings ('') present in the SQL since some databases treat those as NULL.
Methods for the 'run' hook should have the following signature:
void registerHookCallback( string $hook, callback $callback )
string | $hook | The hook to register for |
callback | $callback | The callback to register - see the method description for details about the method signature |
Translates one or more SQL statements using fSQLTranslation and executes them without returning any results
void translatedExecute( string $sql, mixed $value [, ... ] )
string | $sql | One or more SQL statements |
mixed | $value [, ... ] | The optional value(s) to place into any placeholders in the SQL - see escape() for details |
Translates a SQL statement and creates an fStatement object from it
Identifier placeholders (%r) are not supported with prepared statements. In addition, multiple values can not be escaped by a placeholder - only a single value can be provided.
fStatement translatedPrepare( string $sql )
string | $sql | The SQL to prepare |
A prepared statement object that can be passed to query(), unbufferedQuery() or execute()
Translates one or more SQL statements using fSQLTranslation and executes them
fResult|array translatedQuery( string $sql, mixed $value [, ... ] )
string | $sql | One or more SQL statements |
mixed | $value [, ... ] | The optional value(s) to place into any placeholders in the SQL - see escape() for details |
The fResult object(s) for the query
Executes a single SQL statement in unbuffered mode. This is optimal for
large results sets since it does not load the whole result set into memory first. The gotcha is that only one unbuffered result can exist at one time. If another unbuffered query is executed, the old result will be deleted.
fUnbufferedResult unbufferedQuery( string|fStatement $statement, mixed $value [, ... ] )
string|fStatement | $statement | A single SQL statement |
mixed | $value [, ... ] | The optional value(s) to place into any placeholders in the SQL - see escape() for details |
The result object for the unbuffered query
Translates the SQL statement using fSQLTranslation and then executes it
in unbuffered mode. This is optimal for large results sets since it does not load the whole result set into memory first. The gotcha is that only one unbuffered result can exist at one time. If another unbuffered query is executed, the old result will be deleted.
fUnbufferedResult unbufferedTranslatedQuery( string $sql, mixed $value [, ... ] )
string | $sql | A single SQL statement |
mixed | $value [, ... ] | The optional value(s) to place into any placeholders in the SQL - see escape() for details |
The result object for the unbuffered query
Unescapes a value coming out of a database based on its data type
The valid data types are:
mixed unescape( string $data_type, mixed $value )
string | $data_type | The data type being unescaped - see method description for valid values |
mixed | $value | The value or array of values to unescape |
The unescaped value
The fDate class is a value object representation of a date. One of the primary attributes of the object is that its value can not be changed, but instead a new object is created.
This class is built on top of the PHP date/time functions and can only handle dates ranging from 1901–2038.
The fDate constructor takes a single argument, either a string, integer or object (with a __toString()
method) representing a date. Any string format accepted by strtotime()
will work.
$date1 = new fDate('today');
$date2 = new fDate('sunday');
$date3 = new fDate('3 Feb 2008');
$date4 = new fDate('2005-01-02');
Rather than allowing an fDate object value to be modified, which can create issues since objects are passed by reference, all changes to a date create a new object.
Usually when modifying a date, only one or two components (such as month or year) of the date will change. The modify()
method leverages the formatting codes from the date()
function to keep parts of the existing date while replacing others.
Here are some examples of modify()
:
// The new dates year would be 2007 while the month and day would be the same
$new_date = $date1->modify('2007-m-d');
// The new date would be the 1st day (Monday) of the 9th week of the year
$new_date = $date2->modify('Y-\W9-1');
// The new date would simply change the day of the month to the 1st
$new_date = $date3->modify('Y-m-01');
// The new date would have the same year and day, but the month would be June
$new_date = $date4->modify('Y-06-d');
Occasionally you may have the need to adjust a date. The adjust()
method takes a single parameter which can contain any relative time measurement that strtotime()
accepts. Since the fDate class is an immutable value object, calls to adjust()
return a new fDate object.
$new_date = $date1->adjust('tomorrow');
$new_date = $date2->adjust('+1 day');
$new_date = $date3->adjust('-2 years +1 week');
$new_date = $date4->adjust('next wednesday');
To format the date, simply call the format()
method with any valid date formatting string from date()
. Here are some examples:
// Normal date formatting
echo $date1->format('Y-m-d');
echo $date2->format('n/j/y');
There are five different methods available to compare dates, eq()
, gt()
, gte()
, lt()
and lte()
. Each method optionally accepts a parameter $other_date
. If no $other_date
is specified, the date is compared to the current date. If $other_date
is specified, the two are compared. $other_date
accepts any valid date descriptor that works with __construct()
.
Here are some examples:
$today = new fDate();
$tomorrow = new fDate('+1 day');
// These return TRUE
$today->eq();
$today->eq('now');
$today->lt($tomorrow);
$today->lte($tomorrow);
$today->lt('+1 year');
// These calls return FALSE
$tomorrow->lt($today);
$today->gt($tomorrow);
$today->gte($tomorrow);
If you are looking to get a fuzzy difference between two dates for display, youll want to use the getFuzzyDifference()
method. The first parameter, $other_date
, optionally accepts a valid date descriptor that can be passed to __construct()
. If one is passed, the difference will be between the two dates, if nothing is passed, the difference will be between the fDate and the current date.
The value returned by getFuzzyDifference()
will be a string representing the most broad time measurement between the two dates. In addition, if the difference is just shy of the next largest time measurement, it will be rounded up. Thus 3.5 weeks would become 1 month.
Here are some examples to clarify. The following examples are comparing two date descriptors:
$date1 = new fDate('2008-01-01');
$date2 = new fDate('2008-01-04');
echo $date1->getFuzzyDifference($date2);
// Output: 3 days before
echo $date2->getFuzzyDifference($date1);
// Output: 3 days after
$date3 = new fDate('2008-01-10');
echo $date3->getFuzzyDifference('2008-01-01');
// Output: 1 week after
$date4 = new fDate('2008-01-28');
echo $date4->getFuzzyDifference('2008-01-01');
// Output: 1 month after
These examples show output when comparing an fDate object with the current date:
// First, lets assume today is January 1st, 2008.
$date1 = new fDate('2008-01-04');
$date2 = new fDate('2008-01-09');
$date2 = new fDate('2007-12-02');
echo $date1->getFuzzyDifference();
// Output: 3 days from now
echo $date2->getFuzzyDifference();
// Output: 1 week from now
echo $date3->getFuzzyDifference();
// Output: 1 month ago
An optional boolean parameter, $simple
, can also be passed to getFuzzyDifference()
. When TRUE
, this parameter causes the method to return the difference in time, but not the direction.
$date1 = new fDate('2008-01-01');
$date2 = new fDate('2008-01-04');
echo $date1->getFuzzyDifference($date2, TRUE);
// Output: 3 days
echo $date2->getFuzzyDifference($date1, TRUE);
// Output: 3 days
$date3 = new fDate('2008-01-10');
echo $date3->getFuzzyDifference('2008-01-01', TRUE);
// Output: 1 week
Represents a date as a value object
1.0.0b11 | Fixed a method signature 8/24/11 |
---|---|
1.0.0b10 | Fixed a bug with the constructor not properly handling unix timestamps that are negative integers 6/2/11 |
1.0.0b9 | Changed the $date attribute to be protected 3/20/11 |
1.0.0b8 | Added the $simple parameter to getFuzzyDifference() 3/15/10 |
1.0.0b7 | Added a call to fTimestamp::callUnformatCallback() in __construct() for localization support 6/1/09 |
1.0.0b6 | Backwards compatibility break - Removed getSecondsDifference(), added eq(), gt(), gte(), lt(), lte() 3/5/09 |
1.0.0b5 | Updated for new fCore API 2/16/09 |
1.0.0b4 | Fixed __construct() to properly handle the 5.0 to 5.1 change in strtotime() 1/21/09 |
1.0.0b3 | Added support for CURRENT_TIMESTAMP and CURRENT_DATE SQL keywords 1/11/09 |
1.0.0b2 | Removed the adjustment amount check from adjust() 12/31/08 |
1.0.0b | The initial implementation 2/10/08 |
A timestamp of the date
integer
Composes text using fText if loaded
string compose( string $message, mixed $component [, ... ] )
string | $message | The message to compose |
mixed | $component [, ... ] | A string or number to insert into the message |
The composed and possible translated message
Creates the date to represent, no timezone is allowed since dates don't have timezones
fDate __construct( fDate|object|string|integer $date=NULL )
fDate|object|string|integer | $date | The date to represent, NULL is interpreted as today |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
All requests that hit this method should be requests for callbacks
callback __get( string $method )
string | $method | The method to create a callback for |
The callback for the method requested
Returns this date in Y-m-d format
string __toString( )
The Y-m-d format of this date
Changes the date by the adjustment specified, only adjustments of a day or more will be made
fDate adjust( string $adjustment )
string | $adjustment | The adjustment to make |
The adjusted date
If this date is equal to the date passed
boolean eq( fDate|object|string|integer $other_date=NULL )
fDate|object|string|integer | $other_date | The date to compare with, NULL is interpreted as today |
If this date is equal to the one passed
Formats the date
string format( string $format )
string | $format | The date() function compatible formatting string, or a format name from fTimestamp::defineFormat() |
The formatted date
Returns the approximate difference in time, discarding any unit of measure but the least specific.
The output will read like:
Examples of output for a date passed might be:
Examples of output for no date passed might be:
You would never get the following output since it includes more than one unit of time measurement:
Values that are close to the next largest unit of measure will be rounded up:
string getFuzzyDifference( fDate|object|string|integer $other_date=NULL, boolean $simple=FALSE )
string getFuzzyDifference( boolean $simple=FALSE )
fDate|object|string|integer | $other_date | The date to create the difference with, NULL is interpreted as today |
boolean | $simple | When TRUE, the returned value will only include the difference in the two dates, but not from now, ago, after or before |
The fuzzy difference in time between the this date and the one provided
If this date is greater than the date passed
boolean gt( fDate|object|string|integer $other_date=NULL )
fDate|object|string|integer | $other_date | The date to compare with, NULL is interpreted as today |
If this date is greater than the one passed
If this date is greater than or equal to the date passed
boolean gte( fDate|object|string|integer $other_date=NULL )
fDate|object|string|integer | $other_date | The date to compare with, NULL is interpreted as today |
If this date is greater than or equal to the one passed
If this date is less than the date passed
boolean lt( fDate|object|string|integer $other_date=NULL )
fDate|object|string|integer | $other_date | The date to compare with, NULL is interpreted as today |
If this date is less than the one passed
If this date is less than or equal to the date passed
boolean lte( fDate|object|string|integer $other_date=NULL )
fDate|object|string|integer | $other_date | The date to compare with, NULL is interpreted as today |
If this date is less than or equal to the one passed
Modifies the current date, creating a new fDate object
The purpose of this method is to allow for easy creation of a date based on this date. Below are some examples of formats to modify the current date:
fDate modify( string $format )
string | $format | The current date will be formatted with this string, and the output used to create a new object |
The new date
The fDirectory class is a simple object representation of a directory on the filesystem. It provides an object-based interface to common directory functions and allows actions to be grouped into transactions via the fFilesystem class.
The fDirectory constructor takes a single argument, the filesystem path to a directory. The path can be absolute or relative.
$directory1 = new fDirectory('/var/www/vhosts/examples.com/httpdocs/images/');
$directory2 = new fDirectory('./output/');
$directory3 = new fDirectory('../uploads/documents/');
It is also possible to create a directory on the filesystem and instantiate an fDirectory object for that new directory by calling the static method create()
. create()
takes up to two parameters, with the first being $directory_path
and the second optional parameter being $mode
. The $mode
parameter allows setting the permissions for the new directory, and usually is set using an octal number (represented by a number with a leading zero such as 0777).
Please note that directory creation is recursive, so any non-existant parent directories will also be created as needed.
$new_directory = fDirectory::create('./logs/');
The fDirectory class includes a few methods to grab some basic information about a directory. These include:
Method | Description |
getName() |
Returns the name of the directory as a string - does not include the full path |
getSize() |
Returns the size of the directory and all contents in bytes as an integer, or optionally formatted for easy human readability |
getParent() |
Returns the fDirectory object for the directorys parent directory |
getPath() |
Returns the full path to the directory as a string |
isWritable() |
Indicates if the directory can be written to by the current user |
If you want more specific information about a directory, you can pass the output of getPath()
into the various PHP filesystem functions.
The following methods provide a straight-forward interface for some standard manipulation of directories. Also note that these manipulations can be wrapped in a filesystem transactions to allow for rolling back changes in the event of a later error.
Method | Description |
rename() |
Renames the directory to a new path |
move() |
Moves the directory into a different parent directory, keeping the current name |
delete() |
Removes the directory and all contained files and folders from the filesystem. Please note that if inside of a filesystem transaction, this event will be deferred until commit is called, but instances of fDirectory and fFile for all affected files and directories will act as if the directory/file no longer exists. |
clear() |
Removes all contained files and folders from the filesystem. Please note that if inside of a filesystem transaction, this event will be deferred until commit is called, but instances of fDirectory and fFile for all affected files and directories will act as if the directory/file no longer exists. |
The scan()
and scanRecursive()
method provide functionality for listing all fFile, fImage and fDirectory children of an fDirectory object. Both return an array of objects, however scan()
returns only direct children, whereas scanRecursive()
returns all descendants.
$children = $dir->scan();
$descendants = $dir->scanRecursive();
Both methods accept a single optional parameter, $filter
. The filter can be a valid PCRE regex pattern, or a string pattern containing *
and ?
wildcards. *
will match zero or more characters, while ?
will match zero or one characters. When matching the $filter
, all file paths will have /
directory separators, and all directories will end in /
.
// This will list all directory children
$directories = $dir->scan('*/');
// This will find all jpg and gif files under the current directory
$images = $dir->scanRecursive('#\.(jpe?g|gif)$#i');
Represents a directory on the filesystem, also provides static directory-related methods
1.0.0b14 | Fixed a bug in delete() where a non-existent method was being called on fFilesystem, added a permission check to delete() 8/23/11 |
---|---|
1.0.0b13 | Added the clear() method 1/10/11 |
1.0.0b12 | Fixed scanRecursive() to not add duplicate entries for certain nested directory structures 8/10/10 |
1.0.0b11 | Fixed scan() to properly add trailing /s for directories 3/16/10 |
1.0.0b10 | BackwardsCompatibilityBreak - Fixed scan() and scanRecursive() to strip the current directory's path before matching, added support for glob style matching 3/5/10 |
1.0.0b9 | Changed the way directories deleted in a filesystem transaction are handled, including improvements to the exception that is thrown 3/5/10 |
1.0.0b8 | Backwards Compatibility Break - renamed getFilesize() to getSize(), added move() 12/16/09 |
1.0.0b7 | Fixed __construct() to throw an fValidationException when the directory does not exist 8/21/09 |
1.0.0b6 | Fixed a bug where deleting a directory would prevent any future operations in the same script execution on a file or directory with the same path 8/20/09 |
1.0.0b5 | Added the ability to skip checks in __construct() for better performance in conjunction with fFilesystem::createObject() 8/6/09 |
1.0.0b4 | Refactored scan() to use the new fFilesystem::createObject() method 1/21/09 |
1.0.0b3 | Added the $regex_filter parameter to scan() and scanRecursive(), fixed bug in scanRecursive() 1/5/09 |
1.0.0b2 | Removed some unnecessary error suppresion operators 12/11/08 |
1.0.0b | The initial implementation 12/21/07 |
A backtrace from when the file was deleted
array
The full path to the directory
string
Creates a directory on the filesystem and returns an object representing it
The directory creation is done recursively, so if any of the parent directories do not exist, they will be created.
This operation will be reverted by a filesystem transaction being rolled back.
fDirectory create( string $directory, numeric $mode=0777 )
string | $directory | The path to the new directory |
numeric | $mode | The mode (permissions) to use when creating the directory. This should be an octal number (requires a leading zero). This has no effect on the Windows platform. |
Makes sure a directory has a / or \ at the end
string makeCanonical( string $directory )
string | $directory | The directory to check |
The directory name in canonical form
Creates an object to represent a directory on the filesystem
If multiple fDirectory objects are created for a single directory, they will reflect changes in each other including rename and delete actions.
fDirectory __construct( string $directory, boolean $skip_checks=FALSE )
string | $directory | The path to the directory |
boolean | $skip_checks | If file checks should be skipped, which improves performance, but may cause undefined behavior - only skip these if they are duplicated elsewhere |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
All requests that hit this method should be requests for callbacks
callback __get( string $method )
string | $method | The method to create a callback for |
The callback for the method requested
Returns the full filesystem path for the directory
string __toString( )
The full filesystem path
Removes all files and directories inside of the directory
void clear( )
Will delete a directory and all files and directories inside of it
This operation will not be performed until the filesystem transaction has been committed, if a transaction is in progress. Any non-Flourish code (PHP or system) will still see this directory and all contents as existing until that point.
void delete( )
Gets the name of the directory
string getName( )
The name of the directory
Gets the parent directory
fDirectory getParent( )
The object representing the parent directory
Gets the directory's current path
If the web path is requested, uses translations set with fFilesystem::addWebPathTranslation()
string getPath( boolean $translate_to_web_path=FALSE )
boolean | $translate_to_web_path | If the path should be the web path |
The path for the directory
Gets the disk usage of the directory and all files and folders contained within
This method may return incorrect results if files over 2GB exist and the server uses a 32 bit operating system
integer|string getSize( boolean $format=FALSE, integer $decimal_places=1 )
boolean | $format | If the filesize should be formatted for human readability |
integer | $decimal_places | The number of decimal places to format to (if enabled) |
If formatted, a string with filesize in b/kb/mb/gb/tb, otherwise an integer
Check to see if the current directory is writable
boolean isWritable( )
If the directory is writable
Moves the current directory into a different directory
Please note that rename() will rename a directory in its current parent directory or rename it into a different parent directory.
If the current directory's name already exists in the new parent directory and the overwrite flag is set to false, the name will be changed to a unique name.
This operation will be reverted if a filesystem transaction is in progress and is later rolled back.
fDirectory move( fDirectory|string $new_parent_directory, boolean $overwrite )
fDirectory|string | $new_parent_directory | The directory to move this directory into |
boolean | $overwrite | If the current filename already exists in the new directory, TRUE will cause the file to be overwritten, FALSE will cause the new filename to change |
The directory object, to allow for method chaining
Renames the current directory
This operation will NOT be performed until the filesystem transaction has been committed, if a transaction is in progress. Any non-Flourish code (PHP or system) will still see this directory (and all contained files/dirs) as existing with the old paths until that point.
void rename( string $new_dirname, boolean $overwrite )
string | $new_dirname | The new full path to the directory or a new name in the current parent directory |
boolean | $overwrite | If the new dirname already exists, TRUE will cause the file to be overwritten, FALSE will cause the new filename to change |
Performs a scandir() on a directory, removing the . and .. entries
If the $filter looks like a valid PCRE pattern - matching delimeters (a delimeter can be any non-alphanumeric, non-backslash, non-whitespace character) followed by zero or more of the flags i, m, s, x, e, A, D, S, U, X, J, u - then preg_match() will be used.
Otherwise the $filter will do a case-sensitive match with * matching zero or more characters and ? matching a single character.
On all OSes (even Windows), directories will be separated by /s when comparing with the $filter.
array scan( string $filter=NULL )
string | $filter | A PCRE or glob pattern to filter files/directories by path - directories can be detected by checking for a trailing / (even on Windows) |
The fFile (or fImage) and fDirectory objects for the files/directories in this directory
Performs a recursive scandir() on a directory, removing the . and .. entries
array scanRecursive( string $filter=NULL )
string | $filter | A PCRE or glob pattern to filter files/directories by path - see scan() for details |
The fFile (or fImage) and fDirectory objects for the files/directories (listed recursively) in this directory
Throws an exception if the directory has been deleted
void tossIfDeleted( )
The fEmail class provides an interface to very easily send well-formed emails with UTF-8 content, HTML, attachments and S/MIME encryption and signing. fEmail uses PHPs built-in mail()
function by default, but can be used with fSMTP for mass emailing or mailing via a remote mail server.
Creating a new email is as simple as making a new instance of fEmail:
$email = new fEmail();
fEmail supports To:
, CC:
and BCC:
recipients through the methods addRecipient()
, addCCRecipient()
and addBCCRecipient()
. Each method requires a single parameter $email
and accepts a second optional parameter $name
. The add methods can be called any number of times to add any number of recipients:
// Add our recipient
$email->addRecipient('will@example.com', 'Will');
// Add a few people to the CC and BCC lists
$email->addCCRecipient('john@example.com');
$email->addCCRecipient('bob@example.com', 'Bob Smith, Jr.');
$email->addBCCRecipient('alice@example.com');
If need be, all regular, CC and BCC recipients can be cleared by calling the method clearRecipients()
.
$email->clearRecipients();
When sending a message you will also need to set the From:
address via the method setFromEmail()
. The first parameter, $email
, is required, however the second parameter, $name
, is optional. Unfortunately the implementation of mail()
on Windows does not allow setting the $name
parameter. A $name
will work on Windows if you use fSMTP.
// Set who the email is from
$email->setFromEmail('will@example.com');
// Include the users name
$email->setFromEmail('will@example.com', 'Will');
It is also possible to set the Return-Path:
header on almost all servers. This email address is listed as the true source of the email and will be the recipient of any bounces or delivery notifications. The method setBounceToEmail()
sets this address with just a single parameter $email
:
$email->setBounceToEmail('will@example.com');
All email must have a subject and body set by the methods setSubject()
and setBody()
. Both of these methods accept a single parameter, a UTF-8 string.
// Set the subject include UTF-8 curly quotes
$email->setSubject('This wont break email programs because it is properly encoded by the class');
// Set the body to include a string containing UTF-8
$email->setBody('This is the body of the email');
The setBody()
method can also take a second parameter, $unindent_expand_constants
, which is a boolean. When this parameter is TRUE
, the body will be unindented as much as possible, and will have all {CONSTANT_NAME}
strings replaced with the constants value, if defined. This parameter is useful for inline generation of bodies where you dont want to deal with the issues of having a fully unindented end to a HEREDOC
string.
For example, the following PHP:
// Unindenting the body and exapanding curly-braced constant names
if ($email_address) {
$email = new fEmail();
$email->addRecipient($email_address);
//
$email->setBody("
Hello $name,
Thanks for submitting your request, well get back to you as soon as we can!
{EMAIL_SIGNATURE}
", TRUE);
}
would create the email body:
Hello John Smith,
Thanks for submitting your request, well get back to you as soon as we can!
Sincerely,
The ACME Co. Team
Without the parameter, the body would be:
Hello John Smith,
Thanks for submitting your request, well get back to you as soon as we can!
{EMAIL_SIGNATURE}
The HEREDOC
equivalent of our desired output would be:
if ($email_address) {
$email = new fEmail();
//
$signature = EMAIL_SIGNATURE;
$email->setBody(<<<EOF
Hello $name,
Thanks for submitting your request, well get back to you as soon as we can!
$signature
EOF
, TRUE);
}
In short, the $unindent_expand_constants
parameter allows for cleaner PHP with logical indentation levels.
When dealing with a more complex body example, or wishing to separate the email templates from your PHP, the method loadBody()
can be used. This methods accepts two parameters, the $file
to load the body from and an array of $replacements
to be performed.
The $file
parameter can be either a file path or an fFile object. The $replacements
parameter should be an associative array with the terms to search for being the keys and the strings to replace them with being the values.
$email->loadBody(
'./email_templates/welcome.txt',
array(
'{NAME}' => $name,
'{LOGIN}' => $login
)
);
Please note that the terms to search for do not have to be in any particular format, a simple str_replace()
call is used to do the replacements.
It is also possible to add an HTML version of the body by passing it to setHTMLBody()
. This HTML content should be encoded using UTF-8:
$email->setHTMLBody('<p>This it the HTML version of the body</p>');
Just like loadBody()
for the plaintext body, the HTML body can be loaded from a file via loadHTMLBody()
. This methods accepts two parameters, the $file
to load the body from and an array of $replacements
to be performed.
The $file
parameter can be either a file path or an fFile object. The $replacements
parameter should be an associative array with the terms to search for being the keys and the strings to replace them with being the values.
$email->loadHTMLBody(
'./email_templates/welcome.html',
array(
'{NAME}' => $name,
'{LOGIN}' => $login
)
);
Please note that the terms to search for do not have to be in any particular format, a simple str_replace()
call is used to do the replacements.
When using an HTML body, it is possible to add files to the email that can be referenced in the HTML, but do not appear as an attachment. Such related files are added by the addRelatedFile()
method. This method requires either one or two parameters: $contents
, or $contents
and $filename
. If $contents
is an fFile object, no $filename
is necessary. A URL will be returned for direct embedding into HTML src
or href
attributes.
$logo_url = $email->addRelatedFile(new fFile('./images/logo.gif'));
$email->setHTMLBody("<p>Thanks for your input!</p><p>ACME Corp.<img src='$logo_url' alt='ACME Corp'></p>");
There is an optional third parameter, $mime_type
, which can be used to fix incorrect detection of the mime type of a related file.
$logo_url = $email->addRelatedFile(new fFile('./images/logo.gif'), 'email_logo.gif', 'image/gif');
Attachments can be added to an email by calling the method addAttachment()
. The method requires either one or two parameters: $contents
, or $contents
and $filename
. If $contents
is an fFile object, no $filename
is necessary.
foreach ($result as $row) {
$csv_contents .= join(",", $row) . "\n";
}
// Pass in the contents of the file, plus a filename for it
$email->addAttachment($csv_contents, 'report.csv');
// Passing an fFile object
$email->addAttachment(new fFile('./report.pdf'));
There is an optional third parameter, $mime_type
, which can be used to fix incorrect detection of the mime type of an attachment.
$email->addAttachment($file_contents, 'filename.tab', 'text/csv');
There is no limit to the number of attachments.
The fEmail class provides support for encrypting and signing messages using S/MIME. To encrypt a message, the PEM-encoded public certificate will be needed. To sign a message, the PEM-encoded private key will be needed. To encrypt and sign you will need both (but not for the user). Please see Obtaining a Secure Certificate/Key Pair for information about how to get the necessary files.
To encrypt a message, call the method encrypt()
and pass the path to the recipients PEM-encoded public certificate:
$email->encrypt('/path/to/recipients.cer');
To sign a message, call sign()
and pass in the senders public certificate path, private key path and private key password (if applicable):
$email->sign('/path/to/senders.cer', '/path/to/senders.key', 'key_password');
If you call both sign()
and encrypt()
(in either order), the message will be signed, then encrypted and then signed again. This is the most secure method, however certain older email clients may not open such emails.
To send an email, simply call the send()
method.
$message_id = $email->send();
The return value is the generated Message-ID
header for the email, which can be used with the In-Reply-To
header for tracking replies. The fMailbox class provides functionality for checking and parsing email.
If there is a need to add custom headers to an email message, the method addCustomHeader()
will accept the header $name
and $value
. The $value
should be a plain string - the method will handle any necessary encoding or line wrapping necessary to keep the headers standards-compliant.
$email->addCustomHeader('X-My-Header', 'This is the value I want to send!');
It is possible to set multiple headers at once by passing an associative array to addCustomHeader()
.
// Set headers that some programs use to indicate priority
$email->addCustomHeader(array(
'X-Priority' => '1',
'Importance' => 'High'
));
On some linux/unix server running qmail as the sendmail replacement, you may experience issues with emails looking corrupted to the recipients. This will often take the form of equal signs (=) appearing throughout the content and lines being wrapped in odd places.
The technical cause of the issue is that the qmail sendmail binary is automatically replacing every line feed (\n) with a CR-LF (\r\n) because the email specifications require emails use CR-LF and linux uses LF as the line ending. Qmail is trying to ensure you are sending emails that meet specifications, however it is not checking to see if the email already has the proper line endings.
The first way to try and solve this issue is to use fSMTP and connect to the SMTP server on localhost
. This by-passes the command-line interface to qmail:
$smtp = new fSMTP('localhost');
$email->send($smtp);
If that is not possible, the static method fixQmail()
should fix the issue. On servers where open_basedir
and safe_mode
are not in effect, fEmail will make a commandline call to sendmail and will replace all CR-LF with just LF. If that is not possible, fEmail will simply replace all CR-LFs with LF, however emails with long headers may still have issues.
Normally you would only enable this fix if you experience the issue. Since the fix affects all instances of fEmail, youll normally want to call it in a configuration file.
fEmail::fixQmail();
In certain situations it may be necessary to validate an email address when not using fValidation or fORMValidation with the ORM. The fEmail class provides two regular expression constants to help with the task.
These regular expressions are designed to fully match the mailbox
specification of RFC2822 Section 3.4 except for allowing comments and folding white space. Quoted strings are supported along with the +
, -
and other special characters.
Below is an example of some of the various valid email address formats that are supported:
# regular address
will@example.com
# with + suffix
will+foo@example.com
# with - suffix
will-foo@example.com
# periods
will.foo@example.com
# quoted strings
"will foo"@example.com
# combinations
"will foo".bar+baz@example.com
The two constants are EMAIL_REGEX
and NAME_EMAIL_REGEX
. EMAIL_REGEX
will match an email address, while NAME_EMAIL_REGEX
will match a name <email>
string.
EMAIL_REGEX
captures 3 subpatterns in the following format:
For example:
preg_match(fEmail::EMAIL_REGEX, 'will@example.com', $matches);
echo $matches[0] . "\n";
echo $matches[1] . "\n";
echo $matches[2];
will output the following:
will@example.com
will
example.com
NAME EMAIL_REGEX
captures 5 subpatterns in the following format:
For example:
preg_match(fEmail::NAME_EMAIL_REGEX, 'Will <will@example.com>', $matches);
echo $matches[0] . "\n";
echo $matches[1] . "\n";
echo $matches[2] . "\n";
echo $matches[3] . "\n";
echo $matches[4];
will output the following:
Will <will@example.com>
Will
will@example.com
will
example.com
Allows creating and sending a single email containing plaintext, HTML, attachments and S/MIME encryption
Please note that this class uses the mail() function by default. Developers that are sending multiple emails, or need SMTP support, should use fSMTP with this class.
This class is implemented to use the UTF-8 character encoding. Please see UTF-8 for more information.
1.0.0b30 | Changed methods to return instance for method chaining 9/12/11 |
---|---|
1.0.0b29 | Changed combineNameEmail() to be a static method and to be exposed publicly for use by other classes 7/26/11 |
1.0.0b28 | Fixed addAttachment() and addRelatedFile() to properly handle duplicate filenames 5/17/11 |
1.0.0b27 | Fixed a bug with generating FQDNs on some Windows machines 2/24/11 |
1.0.0b26 | Added addCustomerHeader() 2/2/11 |
1.0.0b25 | Fixed a bug with finding the FQDN on non-Windows machines 1/19/11 |
1.0.0b24 | Backwards Compatibility Break - the $contents parameter of addAttachment() is now first instead of third, addAttachment() will now accept fFile objects for the $contents parameter, added addRelatedFile() 12/1/10 |
1.0.0b23 | Fixed a bug on Windows where emails starting with a . would have the . removed 9/11/10 |
1.0.0b22 | Revamped the FQDN code and added getFQDN() 9/7/10 |
1.0.0b21 | Added a check to prevent permissions warnings when getting the FQDN on Windows machines 9/2/10 |
1.0.0b20 | Fixed send() to only remove the name of a recipient when dealing with the mail() function on Windows and to leave it when using fSMTP 6/22/10 |
1.0.0b19 | Changed send() to return the message id for the email, fixed the email regexes to require [] around IPs 5/5/10 |
1.0.0b18 | Fixed the name of the static method unindentExpand() 4/28/10 |
1.0.0b17 | Added the static method unindentExpand() 4/26/10 |
1.0.0b16 | Added support for sending emails via fSMTP 4/20/10 |
1.0.0b15 | Added the $unindent_expand_constants parameter to setBody(), added loadBody() and loadHTMLBody(), fixed HTML emails with attachments 3/14/10 |
1.0.0b14 | Changed send() to not double .s at the beginning of lines on Windows since it seemed to break things rather than fix them 3/5/10 |
1.0.0b13 | Fixed the class to work when safe mode is turned on 10/23/09 |
1.0.0b12 | Removed duplicate MIME-Version headers that were being included in S/MIME encrypted emails 10/5/09 |
1.0.0b11 | Updated to use the new fValidationException API 9/17/09 |
1.0.0b10 | Fixed a bug with sending both an HTML and a plaintext body 6/18/09 |
1.0.0b9 | Fixed a bug where the MIME headers were not being set for all emails 6/12/09 |
1.0.0b8 | Added the method clearRecipients() 5/29/09 |
1.0.0b7 | Email names with UTF-8 characters are now properly encoded 5/8/09 |
1.0.0b6 | Fixed a bug where <> quoted email addresses in validation messages were not showing 3/27/09 |
1.0.0b5 | Updated for new fCore API 2/16/09 |
1.0.0b4 | The recipient error message in validate() no longer contains a typo 2/9/09 |
1.0.0b3 | Fixed a bug with missing content in the fValidationException thrown by validate() 1/14/09 |
1.0.0b2 | Fixed a few bugs with sending S/MIME encrypted/signed emails 1/10/09 |
1.0.0b | The initial implementation 6/23/08 |
A regular expression to match an email address, exluding those with comments and folding whitespace
The matches will be:
A regular expression to match a name <email> string, exluding those with comments and folding whitespace
The matches will be:
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Turns a name and email into a "name" <email> string, or just email if no name is provided
This method will remove newline characters from the name and email, and will remove any backslash (\) and double quote (") characters from the name.
string combineNameEmail( string $name, string $email )
string | $name | The name associated with the email address |
string | The email address |
The '"name" <email>' or 'email' string
Composes text using fText if loaded
string compose( string $message, mixed $component [, ... ] )
string | $message | The message to compose |
mixed | $component [, ... ] | A string or number to insert into the message |
The composed and possible translated message
Sets the class to try and fix broken qmail implementations that add \r to \r\n
Before trying to fix qmail with this method, please try using fSMTP to connect to localhost and pass the fSMTP object to send().
void fixQmail( )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Returns the fully-qualified domain name of the server
string getFQDN( )
The fully-qualified domain name of the server
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Resets the configuration of the class
void reset( )
Returns TRUE for non-empty strings, numbers, objects, empty numbers and string-like numbers (such as 0, 0.0, '0')
boolean stringlike( mixed $value )
mixed | $value | The value to check |
If the value is string-like
Takes a block of text, unindents it and replaces {CONSTANT} tokens with the constant's value
string unindentExpand( string $text )
string | $text | The text to unindent and replace constants in |
The unindented text
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
All requests that hit this method should be requests for callbacks
callback __get( string $method )
string | $method | The method to create a callback for |
The callback for the method requested
Adds an attachment to the email
If a duplicate filename is detected, it will be changed to be unique.
fEmail addAttachment( string|fFile $contents, string $filename=NULL, string $mime_type=NULL )
string|fFile | $contents | The contents of the file |
string | $filename | The name to give the attachement - optional if $contents is an fFile object |
string | $mime_type | The mime type of the file - this allows overriding the mime type of the file if incorrectly detected |
The email object, to allow for method chaining
Adds a blind carbon copy (BCC) email recipient
fEmail addBCCRecipient( string $email, string $name=NULL )
string | The email address to BCC | |
string | $name | The recipient's name |
The email object, to allow for method chaining
Adds a carbon copy (CC) email recipient
fEmail addCCRecipient( string $email, string $name=NULL )
string | The email address to BCC | |
string | $name | The recipient's name |
The email object, to allow for method chaining
Allows adding a custom header to the email
If the method is called multiple times with the same name, the last value will be used.
Please note that this class will properly format the header, including adding the : between the name and value and wrapping values that are too long for a single line.
fEmail addCustomHeader( string $name, string $value )
fEmail addCustomHeader( array $headers )
string | $name | The name of the header |
string | $value | The value of the header |
array | $headers | An associative array of {name} => {value} |
The email object, to allow for method chaining
Adds an email recipient
fEmail addRecipient( string $email, string $name=NULL )
string | The email address to send to | |
string | $name | The recipient's name |
The email object, to allow for method chaining
Adds a “related” file to the email, returning the Content-ID for use in HTML
The purpose of a related file is to be able to reference it in part of the HTML body. Image src URLs can reference a related file by starting the URL with cid: and then inserting the Content-ID.
If a duplicate filename is detected, it will be changed to be unique.
string addRelatedFile( string|fFile $contents, string $filename=NULL, string $mime_type=NULL )
string|fFile | $contents | The contents of the file |
string | $filename | The name to give the attachement - optional if $contents is an fFile object |
string | $mime_type | The mime type of the file - this allows overriding the mime type of the file if incorrectly detected |
The fully-formed cid: URL for use in HTML src attributes
Removes all To, CC and BCC recipients from the email
fEmail clearRecipients( )
The email object, to allow for method chaining
Sets the email to be encrypted with S/MIME
fEmail encrypt( string $recipients_smime_cert_file )
string | $recipients_smime_cert_file | The file path to the PEM-encoded S/MIME certificate for the recipient |
The email object, to allow for method chaining
Loads the plaintext version of the email body from a file and applies replacements
The should contain either ASCII or UTF-8 encoded text. Please see UTF-8 for more information.
fEmail loadBody( string|fFile $file, array $replacements=array() )
string|fFile | $file | The plaintext version of the email body |
array | $replacements | The method will search the contents of the file for each key and replace it with the corresponding value |
The email object, to allow for method chaining
Loads the plaintext version of the email body from a file and applies replacements
The should contain either ASCII or UTF-8 encoded text. Please see UTF-8 for more information.
fEmail loadHTMLBody( string|fFile $file, array $replacements=array() )
string|fFile | $file | The plaintext version of the email body |
array | $replacements | The method will search the contents of the file for each key and replace it with the corresponding value |
The email object, to allow for method chaining
Sends the email
The return value is the message id, which should be included as the Message-ID header of the email. While almost all SMTP servers will not modify this value, testing has indicated at least one (smtp.live.com for Windows Live Mail) does.
string send( fSMTP $connection=NULL )
fSMTP | $connection | The SMTP connection to send the message over |
The message id for the message - see method description for details
Sets the plaintext version of the email body
This method accepts either ASCII or UTF-8 encoded text. Please see UTF-8 for more information.
fEmail setBody( string $plaintext, boolean $unindent_expand_constants=FALSE )
string | $plaintext | The plaintext version of the email body |
boolean | $unindent_expand_constants | If this is TRUE, the body will be unindented as much as possible and {CONSTANT_NAME} will be replaced with the value of the constant |
The email object, to allow for method chaining
Adds the email address the email will be bounced to
This email address will be set to the Return-Path header.
fEmail setBounceToEmail( string $email )
string | The email address to bounce to |
The email object, to allow for method chaining
Adds the From: email address to the email
fEmail setFromEmail( string $email, string $name=NULL )
string | The email address being sent from | |
string | $name | The from email user's name - unfortunately on windows this is ignored |
The email object, to allow for method chaining
Sets the HTML version of the email body
This method accepts either ASCII or UTF-8 encoded text. Please see UTF-8 for more information.
fEmail setHTMLBody( string $html )
string | $html | The HTML version of the email body |
The email object, to allow for method chaining
Adds the Reply-To: email address to the email
fEmail setReplyToEmail( string $email, string $name=NULL )
string | The email address to reply to | |
string | $name | The reply-to email user's name |
The email object, to allow for method chaining
Adds the Sender: email address to the email
The Sender: header is used to indicate someone other than the From: address is actually submitting the message to the network.
fEmail setSenderEmail( string $email, string $name=NULL )
string | The email address the message is actually being sent from | |
string | $name | The sender email user's name |
The email object, to allow for method chaining
Sets the subject of the email
This method accepts either ASCII or UTF-8 encoded text. Please see UTF-8 for more information.
fEmail setSubject( string $subject )
string | $subject | The subject of the email |
The email object, to allow for method chaining
Sets the email to be signed with S/MIME
fEmail sign( string $senders_smime_cert_file, string $senders_smime_pk_file, string $senders_smime_pk_password )
string | $senders_smime_cert_file | The file path to the sender's PEM-encoded S/MIME certificate |
string | $senders_smime_pk_file | The file path to the sender's S/MIME private key |
string | $senders_smime_pk_password | The password for the sender's S/MIME private key |
The email object, to allow for method chaining
fEmptySetException is a sub-class of fExpectedException that indicates an fRecordSet does not contain any records. This type of exception will only be thrown by Flourish code when requested via the tossIfEmpty()
method.
This space intentionally left blank
An exception when an fRecordSet does not contain any elements
1.0.0b | The initial implementation 6/14/07 |
---|
Exception | --fException | --fExpectedException | --fEmptySetException
fEnvironmentException is a sub-class of fUnexpectedException that indicates some sort of required code, extension or other server environment issue has prevented futher execution of the code. Examples of this type of exception being tossed include a missing PHP extension for cryptography, no image manipulation library present, or an outdated version of PHP being used.
This space intentionally left blank
An exception caused by an environment error such a file permissions
1.0.0b | The initial implementation 6/14/07 |
---|
Exception | --fException | --fUnexpectedException | --fEnvironmentException
fException is the base Flourish exception class. It supplements the standard Exception class methods most notably with the ability to easily print the exception to the screen, but it also adds the ability to change the exception message after the object is created.
Exceptions are used throughout Flourish in an attempt to fail as noisily and quickly as possible when the code can not repair the situations. This follows the Rule of Repair from the Art of Unix Programming.
The exceptions in Flourish purposefully use inheritance to allow for classification of exceptions when catching them. You probably dont ever really want to toss an fException, and normally wouldnt want to toss an fExpectedException or fUnexpectedException, but instead should use one of the specific sub-classes. However, when creating catch
statements it may be beneficial to catch one of the generalized higher-level exception classes.
To the right you will see a list of all of the Flourish exception classes.
fException has a constructor that is compatible with a normal exception class, but also adds the ability to be able to perform sprintf()
interpolation and hooks in with the fText class to allow for easy localization.
The constructor requires just a single parameter, the message to be thrown. If that message contains any formatting codes that are compatible with sprintf()
, additional parameters will be used for the values. If an extra parameters is passed, it will be set as the exception code.
Please note all of these example use fProgrammerException since fException is abstract.
// Simple usage
throw new fProgrammerException('The method specified does not exist');
// Using sprintf for interpolation
throw new fProgrammerException('The method specified, %s, does not exist', $method);
// Adding an exception code
throw new fProgrammerException('The method specified, %s, does not exist', $method, 1234);
// An exception code without interpolation
throw new fProgrammerException('The method specified does not exist', 1234);
One of the main benefits of using interpolation is that the non-interpolated message will be passed to fText::compose() if fText has been loaded, which allows for localization of exception messages. This technique is used throughout Flourish.
The method printMessage()
allows for easy display and formatting of the message. It will echo a <p>
tag containing the message if the message has no block-level HTML tags, or a <div>
tag if the message contains block-level HTML. The tag will have a class
attribute that is set to exception {exception_class_name}
.
The examples below use sub-classes of fException since it is abstract and thus can not be instantiated.
$exception = new fValidationException('This is the message');
$exception->printMessage();
$exception2 = new fValidationException('<p>This is the message</p>');
$exception2->printMessage();
Would output the following HTML:
<p class="exception validation_exception">
This is the message
</p>
<div class="exception validation_exception">
<p>This is the message</p>
</div>
Sometimes when handling an error you need to modify the message in an exception, without losing the backtrace information. If you were to get the message from one exception, modify it, and then create a new exception with that message, the whole backtrace would be lost.
In these situations it is essential to be able to modify the exception message. This can be done by passing the new message to the method setMessage()
. Here is an example:
$exception = new fValidationException('This is a test');
$exception->setMessage('This is the test');
$exception->printMessage();
The PHP above would echo the following HTML:
<p class="exception validation_exception">
This is the test
</p>
Most of the validation in Flourish, such as fValidation and fActiveRecord, create exception messages containing a list of all of the encountered errors. Sometimes it is necessary to reorder the list of errors so they coincide with the order of HTML form inputs.
The method reorderMessage()
accepts any number of strings to match, and reorders the list items in the message based on the parameter order. Below is an example to show how it works. First, assume the exception contains the following message:
<p>The following problems were found:</p>
<ul>
<li>Address: Please enter a value</li>
<li>Email: Please enter a value</li>
<li>City: Please enter a value</li>
<li>State: Please enter a value</li>
<li>Zip Code: Please enter a value</li>
<li>Last Name: Please enter a value</li>
<li>First Name: Please enter a value</li>
</ul>
The following PHP would reorder the list items:
$exception->reorderMessage('First Name', 'Last', 'Email', 'Address');
The exception message would then be changed to:
<p>The following problems were found:</p>
<ul>
<li>First Name: Please enter a value</li>
<li>Last Name: Please enter a value</li>
<li>Email: Please enter a value</li>
<li>Address: Please enter a value</li>
<li>City: Please enter a value</li>
<li>State: Please enter a value</li>
<li>Zip Code: Please enter a value</li>
</ul>
Any list items that do not match a parameter will be included at the end of the list, in the same order they existed in the original message.
The matching of strings in done in a case-sensitive manner and the most-specific strings are matched first. Thus if the parameters to reorderMessage()
were Name
and Last Name
, Last Name:…
would be matched first and placed as the second list item. Then the Name
string would match against First Name:…
and it would be set as the first list item.
Most of the validation in Flourish, such as fValidation and fActiveRecord, create exception messages containing a list of all of the encountered errors. These lists are built using HTML unordered lists. Sometimes when creating long forms, it is more usable to split the messages into multiple parts to display in the appropriate places on the form.
The instance method splitMessage()
accepts any number of arrays of strings, $list_item_matches
, and returns an array of filtered messages. Any list item that contains one of the strings will be included in the corresponding filtered message, with the non-list portions of the message also being included in each filtered message.
The easiest way to understand the functionality is to see an example. First, assume the exception contains the following message:
<p>The following problems were found:</p>
<ul>
<li>First Name: Please enter a value</li>
<li>Last Name: Please enter a value</li>
<li>Email: Please enter a value</li>
<li>Address: Please enter a value</li>
<li>City: Please enter a value</li>
<li>State: Please enter a value</li>
<li>Zip Code: Please enter a value</li>
</ul>
Passing these two $list_item_matches
to splitMessage()
would split the exception into two strings:
list ($name_exception, $address_exception) = $exception->splitMessage(
array('First Name', 'Last Name', 'Email'),
array('Address', 'City', 'State', 'Zip Code')
);
The resulting strings would be:
<p>The following problems were found:</p>
<ul>
<li>First Name: Please enter a value</li>
<li>Last Name: Please enter a value</li>
<li>Email: Please enter a value</li>
</ul>
and
<p>The following problems were found:</p>
<ul>
<li>Address: Please enter a value</li>
<li>City: Please enter a value</li>
<li>State: Please enter a value</li>
<li>Zip Code: Please enter a value</li>
</ul>
Notice that the resulting strings will contain the list items in the same order that the strings are set to the $list_item_matches
arrays. This allows re-ordering the list items while also filtering them.
It is possible to pass any number of $list_item_matches
to splitMessage()
, resulting in an equal number of strings in the array result.
If no list items are found in the message, the first value in the returned array will contain the original message and all other array values will be an empty string.
Every exception that is thrown include a full backtrace of all of the function and methods calls that lead up to the point when the exception was thrown. The base Exception class includes two methods to access the backtrace, getTrace()
and getTraceAsString()
. getTrace()
returns an array of the information for each step in the backtrace, while getTraceAsString()
formats all of the backtrace information into a string.
fException includes two methods to supplement the built-in backtrace support, formatTrace()
and printTrace()
. formatTrace()
takes the formatted backtrace string and modifies it to be a little more readable. Below is an example of the type of output created.
{doc_root}/example.php(326): fActiveRecord->store()
{doc_root}/inc/classes/trunk/classes/fActiveRecord.php(1386): fActiveRecord->validate()
{doc_root}/inc/classes/trunk/classes/fActiveRecord.php(1564): fCore::toss('fValidationExce...', 'The followin...')
The method printTrace()
takes the formatted trace and displays it inside of a pre
tag with CSS classes in the format exception {exception_class_name} trace
.
<pre class="exception validation_exception trace">
{doc_root}/example.php(326): fActiveRecord->store()
{doc_root}/inc/classes/trunk/classes/fActiveRecord.php(1386): fActiveRecord->validate()
{doc_root}/inc/classes/trunk/classes/fActiveRecord.php(1564): fCore::toss('fValidationExce...', 'The followin...')
</pre>
An exception that allows for easy l10n, printing, tracing and hooking
1.0.0b8 | Added a missing line of backtrace to formatTrace() 6/28/09 |
---|---|
1.0.0b7 | Updated __construct() to no longer require a message, like the Exception class, and allow for non-integer codes 6/26/09 |
1.0.0b6 | Fixed splitMessage() so that the original message is returned if no list items are found, added reorderMessage() 6/2/09 |
1.0.0b5 | Added splitMessage() to replace fCRUDremoveListItems() and fCRUD::reorderListItems() 5/8/09 |
1.0.0b4 | Added a check to __construct() to ensure that the $code parameter is numeric 5/4/09 |
1.0.0b3 | Fixed a bug with printMessage() messing up some HTML messages 3/27/09 |
1.0.0b2 | compose() more robustly handles $components passed as an array, __construct() now detects stray % characters 2/5/09 |
1.0.0b | The initial implementation 6/14/07 |
Exception | --fException
Composes text using fText if loaded
string compose( string $message, mixed $component [, ... ] )
string | $message | The message to compose |
mixed | $component [, ... ] | A string or number to insert into the message |
The composed and possible translated message
Creates a string representation of any variable using predefined strings for booleans, NULL and empty strings
The string output format of this method is very similar to the output of print_r() except that the following values are represented as special strings:
string dump( mixed $data )
mixed | $data | The value to dump |
The string representation of the value
Adds a callback for when certain types of exceptions are created
The callback will be called when any exception of this class, or any child class, specified is tossed. A single parameter will be passed to the callback, which will be the exception object.
void registerCallback( callback $callback, string $exception_type=NULL )
callback | $callback | The callback |
string | $exception_type | The type of exception to call the callback for |
Sets the message for the exception, allowing for string interpolation and internationalization
The $message can contain any number of formatting placeholders for string and number interpolation via sprintf(). Any % signs that do not appear to be part of a valid formatting placeholder will be automatically escaped with a second %.
The following aspects of valid sprintf() formatting codes are not accepted since they are redundant and restrict the non-formatting use of the % sign in exception messages:
fException __construct( string $message='', mixed $component [, ... ], mixed $code )
string | $message | The message for the exception. This accepts a subset of sprintf() strings - see method description for more details. |
mixed | $component [, ... ] | A string or number to insert into the message |
mixed | $code | The exception code to set |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
All requests that hit this method should be requests for callbacks
callback __get( string $method )
string | $method | The method to create a callback for |
The callback for the method requested
Gets the backtrace to currently called exception
string formatTrace( )
A nicely formatted backtrace to this exception
Returns the CSS class name for printing information about the exception
void getCSSClass( )
Prepares content for output into HTML
string prepare( $content )
$content |
The prepared content
Prints the message inside of a div with the class being 'exception %THIS_EXCEPTION_CLASS_NAME%'
void printMessage( )
Prints the backtrace to currently called exception inside of a pre tag with the class being 'exception %THIS_EXCEPTION_CLASS_NAME% trace'
void printTrace( )
Reorders list items in the message based on simple string matching
fException reorderMessage( string $match [, ... ] )
string | $match [, ... ] | This should be a string to match to one of the list items - whatever the order this is in the parameter list will be the order of the list item in the adjusted message |
The exception object, to allow for method chaining
Allows the message to be overwriten
void setMessage( string $new_message )
string | $new_message | The new message for the exception |
Splits an exception with an HTML list into multiple strings each containing part of the original message
This method should be called with two or more parameters of arrays of string to match. If any of the provided strings are matching in a list item in the exception message, a new copy of the message will be created containing just the matching list items.
Here is an exception message to be split:
The following problems were found:
- First Name: Please enter a value
- Last Name: Please enter a value
- Email: Please enter a value
- Address: Please enter a value
- City: Please enter a value
- State: Please enter a value
- Zip Code: Please enter a value
The following PHP would split the exception into two messages:
list ($name_exception, $address_exception) = $exception->splitMessage(
array('First Name', 'Last Name', 'Email'),
array('Address', 'City', 'State', 'Zip Code')
);
The resulting messages would be:
The following problems were found:
- First Name: Please enter a value
- Last Name: Please enter a value
- Email: Please enter a value
and
The following problems were found:
- Address: Please enter a value
- City: Please enter a value
- State: Please enter a value
- Zip Code: Please enter a value
If no list items match the strings in a parameter, the result will be an empty string, allowing for simple display:
fHTML::show($name_exception, 'error');
An empty string is returned when none of the list items matched the strings in the parameter. If no list items are found, the first value in the returned array will be the existing message and all other array values will be an empty string.
array splitMessage( array $list_item_matches [, ... ] )
array | $list_item_matches [, ... ] | An array of strings to filter the list items by, list items will be ordered in the same order as this array |
This will contain an array of strings corresponding to the parameters passed - see method description for details
fExpectedException is a sub-class of fException meant to provide a common parent class for all exceptions that should be expected and handled in the site/application code. There are no special features or functionality for this class.
Below is a list of all child classes that extend fExpectedException. If any of these classes are tossed by a Flourish method, there will be a Throws: entry in the methods API documentation. Note that exceptions that extend fUnexpectedException are not documented in the same way.
Class | Description |
fEmptySetException | This type of exception indicates an fRecordSet is empty, and will only be thrown when requested via tossIfEmpty() . |
fNoRemainingException | This type of exception indicates that a element was requested even though an iterator has iterated over all existing elements. |
fNoRowsException | This type of exception is for use when no rows are returned by a SQL query. |
fNotFoundException | This type of exception indicates that something could not be found and should include the item type in the message. |
fValidationException | This type of exception indicates there was an error when checking data input. The message will indicate what could not be validated. |
An exception that should be handled by the display code
1.0.0b | The initial implementation 6/14/07 |
---|
Exception | --fException | --fExpectedException
The fFile class is a simple object representation of a file on the filesystem. It provides an object-based interface to common file functions and allows actions to be grouped into transactions via the fFilesystem class.
The fFile constructor normally takes a single argument, the filesystem path to a file.
$file1 = new fFile('/var/www/vhosts/examples.com/httpdocs/images/example.gif');
$file2 = new fFile('./output.txt');
$file3 = new fFile('../uploads/documents/example.doc');
A second argument can be passed to the constructor allowing for an exception object to be set for the file, however this is primarily for use by the fUpload class.
It is also possible to create an fFile object by calling the static create()
method. Not only does it create an instance of fFile, but also allows creates a new file on the filesystem. This method requires two parameters, the $file_path
and the $contents
to write.
$new_file = fFile::create('./output.log', 'This is the string to be stored in the new file.');
The fFile class includes a few methods to grab some basic information about a file. These include:
Method | Description |
getName() |
Returns the filename of the file as a string |
getExtension() |
Returns the extension of the file |
getParent() |
Returns the fDirectory object for the directory containing this file |
getPath() |
Returns a string with the full path to the file |
getSize() |
Returns the size in bytes as an integer, or optionally formatted for easy human readability |
getMimeType() |
Returns the mime type of the file, see the API docs for a list of all supported file types |
getMTime() |
Returns an fTimestamp object representing the date and time the file was last modified |
isWritable() |
Indicates if the file can be written to by the current user |
If you want more specific information about a file, you can pass the output of getPath()
into the various PHP filesystem functions.
In most situations, manipulation of a file is going to be a requirement. The following methods provide a straight-forward interface for standard manipulation. Also note that these manipulations can be wrapped in a filesystem transactions to allow for rolling back changes in the event of a later error.
Method | Description |
read() |
Returns the contents of the file as a string |
write() |
Accepts a string to overwrite the file contents with |
append() |
Appends content to the file |
rename() |
Renames the file to a new filename/path - if no directory / is found in the new path, the file is renamed in the current directory |
move() |
Moves the file to a new directory, keeping the existing filename |
duplicate() |
Will create a copy of the file, optionally in a different directory. If in a different directory, you can specify if you want an existing file with the same name to be overwritten. |
delete() |
Removes the file from the filesystem. Please note that if inside of a filesystem transaction, this event will be deferred until commit is called, but instances of fFile will act as if the file no longer exists. |
output() |
Will echo the contents of the file to the user, optionally including the appropriate HTTP headers |
When it is necessary to use PHP to control access to files, a file can be sent to a user via the output()
method. The first parameters, $headers
, is required and controls whether or not mime-type and filesize headers are included with the file contents. If this is set to FALSE
, the headers should be manually sent before calling the method.
// Output the file with relevant headers
$file->output(TRUE);
If included, the second parameter, $filename
, will send the file as an attachment to the current page instead of using the filename from the current URL.
// If on index.php, this file will be sent to the user as index.php
$file->output(TRUE);
// This file will be sent as document.doc
$file->output(TRUE, 'document.doc');
When outputting a file, please be certain to turn off any output buffering, whether it be controller by ob_start()
, fTemplating or fBuffer. If output buffering is turned on, the whole contents of the file will be stored in web server memory before being sent to the user. This can obviously cause issues with large files.
Due to the way that PHP prevents overwriting session values, only one page can access the session at a time. If the session was opened on the page before calling output()
and is not explicitly closed via fSession::close(), the user will not be able to visit any other pages on the site until the download completes. This is obviously more of an issue with large files or slow connections.
When an fFile object is cloned, a duplicate of the file is created on the filesystem in the files current directory. Essentially clone
is the same as calling duplicate()
with no parameters.
$file = new fFile('/path/to/file.txt');
// $file2 will be a new file with the path /path/to/file_copy1.txt
$file2 = clone $file;
fFile implements the Iterator interface, meaning that ever fFile object can be used in a foreach loop, with each iteration returning a line from the line. Each line will include the original line break.
$file = new fFile('report.csv');
foreach ($file as $line) {
$fields = str_getcsv($line);
//
}
Represents a file on the filesystem, also provides static file-related methods
1.0.0b39 | Backwards Compatibility Break - output() now automatically ends any open output buffering and discards the contents 8/24/11 |
---|---|
1.0.0b38 | Added the Countable interface to the class 6/3/11 |
1.0.0b37 | Fixed mime type detection of BMP images 3/7/11 |
1.0.0b36 | Added the $remove_extension parameter to getName() 1/10/11 |
1.0.0b35 | Added calls to clearstatcache() in append() and write() to prevent incorrect data from being returned by getMTime() and getSize() 11/27/10 |
1.0.0b34 | Added getExtension() 5/10/10 |
1.0.0b33 | Fixed another situation where rename() with the same name would cause the file to be deleted 4/13/10 |
1.0.0b32 | Fixed rename() to not fail when the new and old filename are the same 3/16/10 |
1.0.0b31 | Added append() 3/15/10 |
1.0.0b30 | Changed the way files deleted in a filesystem transaction are handled, including improvements to the exception that is thrown 3/5/10 |
1.0.0b29 | Fixed a couple of undefined variable errors in determineMimeTypeByContents() 3/3/10 |
1.0.0b28 | Added support for some JPEG files created by Photoshop 12/16/09 |
1.0.0b27 | Backwards Compatibility Break - renamed getFilename() to getName(), getFilesize() to getSize(), getDirectory() to getParent(), added move() 12/16/09 |
1.0.0b26 | getDirectory(), getFilename() and getPath() now all work even if the file has been deleted 10/22/09 |
1.0.0b25 | Fixed __construct() to throw an fValidationException when the file does not exist 8/21/09 |
1.0.0b24 | Fixed a bug where deleting a file would prevent any future operations in the same script execution on a file or directory with the same path 8/20/09 |
1.0.0b23 | Added the ability to skip checks in __construct() for better performance in conjunction with fFilesystem::createObject() 8/6/09 |
1.0.0b22 | Fixed __toString() to never throw an exception 8/6/09 |
1.0.0b21 | Fixed a bug in determineMimeType() 7/21/09 |
1.0.0b20 | Fixed the exception message thrown by output() when output buffering is turned on 6/26/09 |
1.0.0b19 | rename() will now rename the file in its current directory if the new filename has no directory separator 5/4/09 |
1.0.0b18 | Changed __sleep() to not reset the iterator since it can cause side-effects 5/4/09 |
1.0.0b17 | Added __sleep() and __wakeup() for proper serialization with the filesystem map 5/3/09 |
1.0.0b16 | output() now accepts TRUE in the second parameter to use the current filename as the attachment filename 3/23/09 |
1.0.0b15 | Added support for mime type detection of MP3s based on the MPEG-2 (as opposed to MPEG-1) standard 3/23/09 |
1.0.0b14 | Fixed a bug with detecting the mime type of some MP3s 3/22/09 |
1.0.0b13 | Fixed a bug with overwriting files via rename() on Windows 3/11/09 |
1.0.0b12 | Backwards compatibility break - Changed the second parameter of output() from $ignore_output_buffer to $filename 3/5/09 |
1.0.0b11 | Changed __clone() and duplicate() to copy file permissions to the new file 1/5/09 |
1.0.0b10 | Fixed duplicate() so an exception is not thrown when no parameters are passed 1/5/09 |
1.0.0b9 | Removed the dependency on fBuffer 1/5/09 |
1.0.0b8 | Added the Iterator interface, output() and getMTime() 12/17/08 |
1.0.0b7 | Removed some unnecessary error suppresion operators 12/11/08 |
1.0.0b6 | Added the __clone() method that duplicates the file on the filesystem when cloned 12/11/08 |
1.0.0b5 | Fixed detection of mime type for JPEG files with Exif information 12/4/08 |
1.0.0b4 | Changed the constructor to ensure the path is to a file and not directory 11/24/08 |
1.0.0b3 | Fixed mime type detection of Microsoft Office files 11/23/08 |
1.0.0b2 | Made rename() and write() return the object for method chaining 11/22/08 |
1.0.0b | The initial implementation 6/14/07 |
A backtrace from when the file was deleted
array
The full path to the file
string
Creates a file on the filesystem and returns an object representing it.
This operation will be reverted by a filesystem transaction being rolled back.
fFile create( string $file_path, string $contents )
string | $file_path | The path to the new file |
string | $contents | The contents to write to the file, must be a non-NULL value to be written |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Determines the file's mime type by either looking at the file contents or matching the extension
Please see the getMimeType() description for details about how the mime type is determined and what mime types are detected.
string determineMimeType( string $file, string $contents=NULL )
string | $file | The file to check the mime type for - must be a valid filesystem path if no $contents are provided, otherwise just a filename |
string | $contents | The first 4096 bytes of the file content - the $file parameter only need be a filename if this is provided |
The mime type of the file
Creates an object to represent a file on the filesystem
If multiple fFile objects are created for a single file, they will reflect changes in each other including rename and delete actions.
fFile __construct( string $file, boolean $skip_checks=FALSE )
string | $file | The path to the file |
boolean | $skip_checks | If file checks should be skipped, which improves performance, but may cause undefined behavior - only skip these if they are duplicated elsewhere |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Duplicates a file in the current directory when the object is cloned
fFile __clone( )
The new fFile object
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
All requests that hit this method should be requests for callbacks
callback __get( string $method )
string | $method | The method to create a callback for |
The callback for the method requested
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
The iterator information doesn't need to be serialized since a resource can't be
array __sleep( )
The instance variables to serialize
Returns the filename of the file
string __toString( )
The filename
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Re-inserts the file back into the filesystem map when unserialized
void __wakeup( )
Appends the provided data to the file
If a filesystem transaction is in progress and is rolled back, this data will be removed.
fFile append( mixed $data )
mixed | $data | The data to append to the file |
The file object, to allow for method chaining
Returns the number of lines in the file
integer count( )
The number of lines in the file
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Returns the current line of the file (required by iterator interface)
array current( )
The current row
Deletes the current file
This operation will NOT be performed until the filesystem transaction has been committed, if a transaction is in progress. Any non-Flourish code (PHP or system) will still see this file as existing until that point.
void delete( )
Creates a new file object with a copy of this file
If no directory is specified, the file is created with a new name in the current directory. If a new directory is specified, you must also indicate if you wish to overwrite an existing file with the same name in the new directory or create a unique name.
This operation will be reverted by a filesystem transaction being rolled back.
fFile duplicate( string|fDirectory $new_directory=NULL, boolean $overwrite=NULL )
string|fDirectory | $new_directory | The directory to duplicate the file into if different than the current directory |
boolean | $overwrite | If a new directory is specified, this indicates if a file with the same name should be overwritten. |
The new fFile object
Gets the file extension
string getExtension( )
The extension of the file
Gets the file's mime type
This method will attempt to look at the file contents and the file extension to determine the mime type. If the file contains binary information, the contents will be used for mime type verification, however if the contents appear to be plain text, the file extension will be used.
The following mime types are supported. All other binary file types will be returned as application/octet-stream and all other text files will be returned as text/plain.
Archive:
Audio:
Document:
Image:
Text:
Video/Animation:
string getMimeType( )
The mime type of the file
Returns the last modification time of the file
fTimestamp getMTime( )
The timestamp of when the file was last modified
Gets the filename (i.e. does not include the directory)
string getName( boolean $remove_extension=FALSE )
boolean | $remove_extension | If the extension should be removed from the filename |
The filename of the file
Gets the directory the file is located in
fDirectory getParent( )
The directory containing the file
Gets the file's current path (directory and filename)
If the web path is requested, uses translations set with fFilesystem::addWebPathTranslation()
string getPath( boolean $translate_to_web_path=FALSE )
boolean | $translate_to_web_path | If the path should be the web path |
The path (directory and filename) for the file
Gets the size of the file
The return value may be incorrect for files over 2GB on 32-bit OSes.
integer|string getSize( boolean $format=FALSE, integer $decimal_places=1 )
boolean | $format | If the filesize should be formatted for human readability |
integer | $decimal_places | The number of decimal places to format to (if enabled) |
If formatted a string with filesize in b/kb/mb/gb/tb, otherwise an integer
Check to see if the current file is writable
boolean isWritable( )
If the file is writable
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Returns the current one-based line number (required by iterator interface)
integer key( )
The current line number
Moves the current file to a different directory
Please note that rename() will rename a file in its directory or rename it into a different directory.
If the current file's filename already exists in the new directory and the overwrite flag is set to false, the filename will be changed to a unique name.
This operation will be reverted if a filesystem transaction is in progress and is later rolled back.
fFile move( fDirectory|string $new_directory, boolean $overwrite )
fDirectory|string | $new_directory | The directory to move this file into |
boolean | $overwrite | If the current filename already exists in the new directory, TRUE will cause the file to be overwritten, FALSE will cause the new filename to change |
The file object, to allow for method chaining
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Advances to the next line in the file (required by iterator interface)
void next( )
Prints the contents of the file
This method is primarily intended for when PHP is used to control access to files.
Be sure to close the session, if open, to prevent performance issues. Any open output buffers are automatically closed and discarded.
fFile output( boolean $headers, mixed $filename=NULL )
boolean | $headers | If HTTP headers for the file should be included |
mixed | $filename | Present the file as an attachment instead of just outputting type headers - if a string is passed, that will be used for the filename, if TRUE is passed, the current filename will be used |
The file object, to allow for method chaining
Reads the data from the file
Reads all file data into memory, use with caution on large files!
This operation will read the data that has been written during the current transaction if one is in progress.
string read( )
The contents of the file
Renames the current file
If the filename already exists and the overwrite flag is set to false, a new filename will be created.
This operation will be reverted if a filesystem transaction is in progress and is later rolled back.
fFile rename( string $new_filename, boolean $overwrite )
string | $new_filename | The new full path to the file or a new filename in the current directory |
boolean | $overwrite | If the new filename already exists, TRUE will cause the file to be overwritten, FALSE will cause the new filename to change |
The file object, to allow for method chaining
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Rewinds the file handle (required by iterator interface)
void rewind( )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Returns if the file has any lines left (required by iterator interface)
boolean valid( )
If the iterator is still valid
Writes the provided data to the file
Requires all previous data to be stored in memory if inside a transaction, use with caution on large files!
If a filesystem transaction is in progress and is rolled back, the previous data will be restored.
fFile write( mixed $data )
mixed | $data | The data to write to the file |
The file object, to allow for method chaining
The fFilesystem class is a static class that provides filesystem level functionality, including the ability to bundle operations into transactions.
The static method createObject()
acts as a factory to create the appropriate filesystem object when dealing with paths that may contain a directory, image or file.
In the case that a directory is detected, an fDirectory object will be returned. In the case that a file that is compatible with the fImage object is detected, an fImage object will be returned. If neither of those conditions hold true, an fFile object will be returned.
// This would return an fImage object
$file = fFilesystem::createObject('./path/to/image.jpg');
// This would return an fFile object
$file = fFilesystem::createObject('./path/to/info.txt');
// This would return an fDirectory object
$dir = fFilesystem::createObject('./path/to/dir/');
When speaking of ACID-compliant relational databases, transactions are one of the key elements. The fFilesystem class provides transaction-like functionality for filesystem operations. Please note that the level of transactional features is nothing like an ACID-compliant RDBMS. If the web server crashes or exits in the middle of a transaction, the changes made to that point will be permanent. Please see below for a list of exactly what is affected by filesystem transactions.
There are four methods used when dealing with filesystem transactions:
Method | Description |
begin() |
Begins a transaction, indicating that all following Flourish filesystem operations should be grouped together for the purposes of rolling back or committing them. |
rollback() |
Allows all changes since the transaction was started to be reversed. |
commit() |
Causes all pending operations (such as deletes) to actually be performed. |
isInsideTransaction() |
Indicates if a transaction has been started but not rolled back or committed yet. |
Here is a list of how the various fDirectory, fFile and fImage methods are affected by filesystem transactions:
Method | Active Transaction Behaviour |
fDirectory::delete() | Directory will not be deleted from the filesystem until transaction is committed, however trying to manipulate the directory (or an sub-files or directories) via the Flourish filesystem classes will result in an exception being thrown. |
fDirectory::getSize() | The disk usage returned will include files that have been deleted during the current transaction and also placeholder files that have not yet been deleted. |
fDirectory::rename() | Directory will be renamed immediately, however a small file will be written to the old directory name to allow for a rollback to revert the directory name. The small placeholder file is deleted when a transaction is committed. |
fDirectory::scan() | A list of all file and folders contained within the directory will be returned, including files that have been deleted and placeholder files. |
fDirectory::scanRecursive() | A list of all file and folders contained within the directory (and all sub-directories) will be returned, including files that have been deleted and placeholder files. |
fFile::delete() | File will not be deleted from the filesystem until transaction is committed, however trying to manipulate the file via the Flourish filesystem classes will result in an exception being thrown. |
fFile::duplicate() | File will be duplicated immediately, however the copy will be deleted if the transaction is rolled back. |
fFile::read() | Will always read the most recently written file contents. |
fFile::rename() | File will be renamed immediately, however a small file will be written to the old filename to allow for a rollback to revert the filename. The small placeholder file is deleted when a transaction is committed. |
fFile::write() | The new file contents will be written to the file immediately, however the old file contents are saved to allow a rollback to restore the file to its original state. Warning: Large file contents can quickly increase the memory usage of PHP since the original contents are stored in memory. |
fImage::saveChanges() | The image changes will be written to disk immediately. If the new image name is the same as the old one, the old file contents will be kept in memory in case of a rollback. If the new image name is different than the old one, the old file will be kept until the transaction is committed. |
fFilesystem provides three helper functions for dealing with file paths, getPathInfo()
, makeUniqueName()
and makeURLSafe()
.
getPathInfo()
provides basically the same functionality as the PHP function pathinfo(), however it provides the filename
element for PHP 5.1 and also returns the dirname
element with a trailing slash.
makeUniqueName()
requires a single parameter, $file
, to specify the desired filename, and optionally allows a second parameter, $extension
, which allows a new file extension to be specified. If a file (or directory) with the path of $file
exists, a new filename will be created by appending _copy#
. If $extension
is provided, the desired filename will use that extension instead of the one included in $file
.
makeURLSafe()
accepts a $filename
and changes it to only include lower case characters, numbers and the _
, -
and .
characters.
When displaying filesystem paths to access resources on the web, translation must occur. The static method translateToWebPath()
accepts a filesystem path and returns the equivalent web server path. By default it simply removes the $_SERVER['DOCUMENT_ROOT']
. Additional translations can be set up by passing the $search
string and $replace
strings to the static method addWebPathTranslation()
.
Normally it would make sense to define the translations in the site config file, while translateToWebPath()
would be used in the page code.
// Config file
fFilesystem::addWebPathTranslation('/var/www/vhosts/example.com/assets', '');
<ul>
<?
foreach ($files as $file) {
?>
<li><a href="<?php echo fFilesystem::translateToWebPath($file->getPath()) ?>"><?php echo $file->getName() ?></a></li>
<?
}
?>
</ul>
In addition to some file path helpers, fFilesystem provides two methods to assist with file size manipulation. The method formatFilesize()
accepts a filesize in bytes and will return a human-readable size such as 2.1tb
or 5.6mb
. convertToBytes()
takes a human-readable filesize such as 200k
or 1.3GB
and converts it into bytes.
formatFilesize()
accepts up to two parameters with the first, $bytes
, being the filesize of the file/directory in bytes and the second optional parameter, $decimal_places
, being the number of decimal places to show for the formatted file size.
convertToBytes()
accepts just a single parameters, $size
, which can be any human-readable filesize that includes units of measure such as k
, kilo
, m
, mega
, g
, giga
, t
, tera
, b
and bytes
. The result of the method is the filesize converted into bytes.
Handles filesystem-level tasks including filesystem transactions and the reference map to keep all fFile and fDirectory objects in sync
1.0.0b16 | Added a call to clearstatcache() to rollback() to prevent incorrect data from being returned by fFilegetMTime() and fFile::getSize() 11/27/10 |
---|---|
1.0.0b15 | Fixed translateToWebPath() to handle Windows \s 4/9/10 |
1.0.0b14 | Added recordAppend() 3/15/10 |
1.0.0b13 | Changed the way files/directories deleted in a filesystem transaction are handled, including improvements to the exception that is thrown 3/5/10 |
1.0.0b12 | Updated convertToBytes() to properly handle integers without a suffix and sizes with fractions 11/14/09 |
1.0.0b11 | Corrected the API documentation for getPathInfo() 9/9/09 |
1.0.0b10 | Updated updateExceptionMap() to not contain the Exception class parameter hint, allowing NULL to be passed 8/20/09 |
1.0.0b9 | Added some performance tweaks to createObject() 8/6/09 |
1.0.0b8 | Changed formatFilesize() to not use decimal places for bytes, add a space before and drop the B in suffixes 7/12/09 |
1.0.0b7 | Fixed formatFilesize() to work when $bytes equals zero 7/8/09 |
1.0.0b6 | Changed replacement values in preg_replace() calls to be properly escaped 6/11/09 |
1.0.0b5 | Changed formatFilesize() to use proper uppercase letters instead of lowercase 6/2/09 |
1.0.0b4 | Added the createObject() method 1/21/09 |
1.0.0b3 | Removed some unnecessary error suppresion operators 12/11/08 |
1.0.0b2 | Fixed a bug where the filepath and exception maps weren't being updated after a rollback 12/11/08 |
1.0.0b | The initial implementation 3/24/08 |
Adds a directory to the web path translation list
The web path conversion list is a list of directory paths that will be converted (from the beginning of filesystem paths) when preparing a path for output into HTML.
By default the $_SERVER['DOCUMENT_ROOT'] will be converted to a blank string, in essence stripping it from filesystem paths.
void addWebPathTranslation( string $search_path, string $replace_path )
string | $search_path | The path to look for |
string | $replace_path | The path to replace with |
Starts a filesystem pseudo-transaction, should only be called when no transaction is in progress.
Flourish filesystem transactions are NOT full ACID-compliant transactions, but rather more of an filesystem undo buffer which can return the filesystem to the state when begin() was called. If your PHP script dies in the middle of an operation this functionality will do nothing for you and all operations will be retained, except for deletes which only occur once the transaction is committed.
void begin( )
Commits a filesystem transaction, should only be called when a transaction is in progress
void commit( )
Takes a file size including a unit of measure (i.e. kb, GB, M) and converts it to bytes
Sizes are interpreted using base 2, not base 10. Sizes above 2GB may not be accurately represented on 32 bit operating systems.
integer convertToBytes( string $size )
string | $size | The size to convert to bytes |
The number of bytes represented by the size
Takes a filesystem path and creates either an fDirectory, fFile or fImage object from it
fDirectory|fFile|fImage createObject( string $path )
string | $path | The path to the filesystem object |
Takes the size of a file in bytes and returns a friendly size in B/K/M/G/T
string formatFilesize( integer $bytes, integer $decimal_places=1 )
integer | $bytes | The size of the file in bytes |
integer | $decimal_places | The number of decimal places to display |
Returns info about a path including dirname, basename, extension and filename
array getPathInfo( string $path, string $element=NULL )
string | $path | The file/directory path to retrieve information about |
string | $element | The piece of information to return: 'dirname', 'basename', 'extension', or 'filename' |
The file's dirname, basename, extension and filename
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Hooks a file/directory into the deleted backtrace map entry for that filename
Since the value is returned by reference, all objects that represent this file/directory always see the same backtrace.
mixed &hookDeletedMap( string $file )
string | $file | The name of the file or directory |
Will return NULL if no match, or the backtrace array if a match occurs
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Hooks a file/directory name to the filename map
Since the value is returned by reference, all objects that represent this file/directory will always be update on a rename.
mixed &hookFilenameMap( string $file )
string | $file | The name of the file or directory |
Will return NULL if no match, or the exception object if a match occurs
Indicates if a transaction is in progress
void isInsideTransaction( )
Returns a unique name for a file
string makeUniqueName( string $file, string $new_extension=NULL )
string | $file | The filename to check |
string | $new_extension | The new extension for the filename, should not include . |
The unique file name
Changes a filename to be safe for URLs by making it all lower case and changing everything but letters, numers, - and . to _
string makeURLSafe( string $filename )
string | $filename | The filename to clean up |
The cleaned up filename
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Stores what data has been added to a file so it can be removed if there is a rollback
void recordAppend( fFile $file, string $data )
fFile | $file | The file that is being written to |
string | $data | The data being appended to the file |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Keeps a record of created files so they can be deleted up in case of a rollback
void recordCreate( object $object )
object | $object | The new file or directory to get rid of on rollback |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Keeps track of file and directory names to delete when a transaction is committed
void recordDelete( fFile|fDirectory $object )
fFile|fDirectory | $object | The filesystem object to delete |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Keeps a record of duplicated files so they can be cleaned up in case of a rollback
void recordDuplicate( fFile $file )
fFile | $file | The duplicate file to get rid of on rollback |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Keeps a temp file in place of the old filename so the file can be restored during a rollback
void recordRename( string $old_name, string $new_name )
string | $old_name | The old file or directory name |
string | $new_name | The new file or directory name |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Keeps backup copies of files so they can be restored if there is a rollback
void recordWrite( fFile $file )
fFile | $file | The file that is being written to |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Resets the configuration of the class
void reset( )
Rolls back a filesystem transaction, it is safe to rollback when no transaction is in progress
void rollback( )
Takes a filesystem path and translates it to a web path using the rules added
string translateToWebPath( string $path )
string | $path | The path to translate |
The filesystem path translated to a web path
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Updates the deleted backtrace for a file or directory
void updateDeletedMap( string $file, array $backtrace )
string | $file | A file or directory name, directories should end in / or \ |
array | $backtrace | The backtrace for this file/directory |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Updates the filename map, causing all objects representing a file/directory to be updated
void updateFilenameMap( string $existing_filename, string $new_filename )
string | $existing_filename | The existing filename |
string | $new_filename | The new filename |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Updates the filename map recursively, causing all objects representing a directory to be updated
Also updates all files and directories in the specified directory to the new paths.
void updateFilenameMapForDirectory( string $existing_dirname, string $new_dirname )
string | $existing_dirname | The existing directory name |
string | $new_dirname | The new dirname |
The fGrammar class was built to handle various grammar tasks. English is the default language, however there is functionality to allow for some localization.
Due to the way the English language works, most messages and output that references quantities will need to change when there is a single item in question, or a quantity other than one. For instance, you may have a message say:
There are currently 15 users.
Which is great as long as your quantity is zero or greater than one. If there is only a single user, the message should change to:
There is currently one user.
Certainly some conditional code could be written to handle the situation, but why introduce a big if
statement (or worse, an unreadable tertiary statement) in the middle of display code? The fGrammar class provides a convenient method to handle such situations, inflectOnQuantity()
.
inflectOnQuantity()
has three required parameters and one optional one we will talk about in a minute. The first parameter is the quantity being measured. This can be an integer, or an array of items. If an array is passed, the size of the array will be used for the quantity. The second parameter is the string to be selected if quantity is one, the third parameter is the string to be selected if the quantity is not one. The third parameter can include %d
to be replaced with the actual quantity.
echo 'There ' . fGrammar::inflectOnQuantity(1, 'is one user', 'are %d users') . ".\n";
echo 'There ' . fGrammar::inflectOnQuantity(5, 'is one user', 'are %d users') . ".";
The output from the code above would be:
There is one user.
There are 5 users.
The optional fourth parameter is a flag to indicate if you want single digit quantities to be replaced by the word for that quantity:
echo 'There ' . fGrammar::inflectOnQuantity(5, 'is one user', 'are %d users', TRUE) . ".";
Would result in:
There are five users.
Another common situation is to have a variable number of terms to display, however it is desirable to produce a well-formed sentence. The joinArray()
method will take an array of strings and will return the string in a well-formed manner. Here are some examples:
echo fGrammar::joinArray(array()) . "\n";
echo fGrammar::joinArray(array('one')) . "\n";
echo fGrammar::joinArray(array('one', 'two')) . "\n";
echo fGrammar::joinArray(array('one', 'two', 'three')) . "\n";
echo fGrammar::joinArray(array('one', 'two', 'three', 'four'));
Here would be the HTML output:
one
one and two
one, two, and three
one, two, three, and four
Although it is unlikely that this functionality will be necessary for the majority of web sites and applications, it is good to talk a little about plurals and singulars. Most of the ORM code in Flourish uses the pluralize()
and singularize()
methods to handle detecting what a developer is trying to do.
Here are some examples of how pluralize()
and singularize()
can be used:
// Simple plural and singular forms
echo fGrammar::pluralize('user') . "\n";
echo fGrammar::singularize('users') . "\n";
// Most irregular forms are automatically handed
echo fGrammar::pluralize('person') . "\n";
echo fGrammar::singularize('people');
The output would be:
users
user
people
person
Unfortunately the English language has some rather complex rules about pluralization with quite a number of exceptions. To compound the matter further, singularization rules are almost non-existent in a formalized nature, and mostly have to be reverse engineered from the pluralization rules. Finally, many website and applications use acronyms heavily, which often times do not follow the same rules as words with similar structures.
Because of these issues, a day will come when fGrammar will not get a pluralization or singularization of a noun correct. As a fall-back, the static method addSingularPluralRule()
has been included. Simply pass the singular form of a noun as the first parameter and the plural form as the second parameter. All subsequent requests to singularize()
and pluralize()
will check this rule before the default rules.
Here is a rather contrived example of when to use addSingularPluralRule()
:
// These will produce incorrect results
echo fGrammar::pluralize('phalanx') . "\n";
echo fGrammar::singularize('phalanxes') . "\n";
// Add a custom rule for phalanx
fGrammar::addSingularPluralRule('phalanx', 'phalanxes');
// Now the singularization and pluralization work properly
echo fGrammar::pluralize('phalanx') . "\n";
echo fGrammar::singularize('phalanxes') . "\n";
The output of the PHP above would be:
phalanxs
phalanxe
phalanxes
phalanx
Notation conversion is also used heavily by the ORM code in Flourish to translate between the various coding standards that exist.
Underscore notation is characterized by all lower-case letters with underscores separating words, like underscore_notation
.
The method underscorize()
will convert camel case notation or space separated words to underscore notation.
echo fGrammar::underscorize('FirstName') . "\n";
echo fGrammar::underscorize('Last Name') . "\n";
echo fGrammar::underscorize('middleInitial');
would produce the following output:
first_name
last_name
middle_initial
Camel case notation uses upper and lower-case letters, with words being separated by a capital letter, like CamelCase
. Lower camel case is identical to camel case, except the first letter is always lower-case, like lowerCamelCase
.
The method camelize()
will convert underscore notation or space separated words to camelCase. The first parameter, $string
is the string to be converted and the second parameter $upper
indicates if the output should be UpperCamelCase instead of lowerCamelCase.
echo fGrammar::camelize('first_name', FALSE) . "\n";
echo fGrammar::camelize('Last name', FALSE) . "\n";
echo fGrammar::camelize('MIDDLE INITIAL', TRUE);
would produce the following output:
firstName
lastName
MiddleInitial
In addition to converting between these two formats, it is sometimes required to convert from underscore or camel case notation to a human friendly form. humanize()
will take any underscore notation or camelCase string and produce output containing spaces and the first letter of each word being capitalized. For specific words know to be all capitals, it will change those words to all capitals.
echo fGrammar::humanize('first_name') . "\n";
echo fGrammar::humanize('LastName') . "\n";
echo fGrammar::humanize('middleInitial') . "\n";
echo fGrammar::humanize('PdfUpload');
would produce the following output:
First Name
Last Name
Middle Initial
PDF Upload
Occasionally conversion from camelCase to underscore_notation doesnt work properly due to the splitting dynamics. In underscorize()
, any number or capital letter following a lower-case letter will cause an _
to be inserted. Thus, if a number exists in the middle of an acronym, the conversion will not occur correctly.
To fix such situation, pass the $camel_case
version of the string and the $underscore_notation
version of the string to addCamelUnderscoreRule()
:
// In this situation the underscores are put in the wrong place
echo fGrammar::underscorize('w3cCompliant') . "\n";
fGrammar::addCamelUnderscoreRule('W3cCompliant', 'w3c_compliant');
// In this situation the underscores are put in the right place
echo fGrammar::underscorize('w3cCompliant');
The above PHP would output:
w_3c_compliant
w3c_compliant
In addition, calling humanize()
can sometimes create results that are not desired. The method uses a simple system of splitting words and capitalizing the first letter. In some situations more than one letter need to be capitalized. The method addHumanizeRule()
allows fixing incorrect results.
echo fGrammar::humanize('w3c_compliant') . "\n";
fGrammar::addHumanizeRule('w3c_compliant', 'W3C Compliant');
echo fGrammar::humanize('w3c_compliant');
would output the following:
W3c Compliant
W3C Compliant
Localization of the method joinArray()
can be accomplished by using registerJoinArrayCallback()
. This method accepts a single callback and all calls made to joinArray()
are then sent to the registered callback. Obviously the method parameters and return value of the callback should match the real method.
Provides english word inflection, notation conversion, grammar helpers and internationlization support
1.0.0b15 | Added length checking to ensure blank strings are not being passed to various methods 6/20/11 |
---|---|
1.0.0b14 | Fixed a bug in singularization that would affect words containing the substring mice or lice 2/24/11 |
1.0.0b13 | Fixed the pluralization of video 8/10/10 |
1.0.0b12 | Updated singularize() and pluralize() to be able to handle underscore_CamelCase 8/6/10 |
1.0.0b11 | Fixed custom camelCase to underscore_notation rules 6/23/10 |
1.0.0b10 | Removed e flag from preg_replace() calls 6/8/10 |
1.0.0b9 | Fixed a bug with camelize() and human-friendly strings 6/8/10 |
1.0.0b8 | Added the stem() method 5/27/10 |
1.0.0b7 | Added the $return_error parameter to pluralize() and singularize() 3/30/10 |
1.0.0b6 | Added missing compose() method 3/3/10 |
1.0.0b5 | Fixed reset() to properly reset the singularization and pluralization rules 10/28/09 |
1.0.0b4 | Added caching for various methods - provided significant performance boost to ORM 6/15/09 |
1.0.0b3 | Changed replacement values in preg_replace() calls to be properly escaped 6/11/09 |
1.0.0b2 | Fixed a bug where some words would lose capitalization with pluralize() and singularize() 1/25/09 |
1.0.0b | The initial implementation 9/25/07 |
Adds a custom camelCase to underscore_notation and underscore_notation to camelCase rule
void addCamelUnderscoreRule( string $camel_case, string $underscore_notation )
string | $camel_case | The lower camelCase version of the string |
string | $underscore_notation | The underscore_notation version of the string |
Adds a custom mapping of a non-humanized string to a humanized string for humanize()
void addHumanizeRule( string $non_humanized_string, string $humanized_string )
string | $non_humanized_string | The non-humanized string |
string | $humanized_string | The humanized string |
Adds a custom singular to plural and plural to singular rule for pluralize() and singularize()
void addSingularPluralRule( string $singular, string $plural )
string | $singular | The singular version of the noun |
string | $plural | The plural version of the noun |
Converts an underscore_notation, human-friendly or camelCase string to camelCase
string camelize( string $string, boolean $upper )
string | $string | The string to convert |
boolean | $upper | If the camel case should be UpperCamelCase |
The converted string
Composes text using fText if loaded
string compose( string $message, mixed $component [, ... ] )
string | $message | The message to compose |
mixed | $component [, ... ] | A string or number to insert into the message |
The composed and possible translated message
Makes an underscore_notation, camelCase, or human-friendly string into a human-friendly string
string humanize( string $string )
string | $string | The string to humanize |
The converted string
Returns the singular or plural form of the word or based on the quantity specified
string inflectOnQuantity( mixed $quantity, string $singular_form, string $plural_form=NULL, boolean $use_words_for_single_digits=FALSE )
mixed | $quantity | The quantity (integer) or an array of objects to count |
string | $singular_form | The string to be returned for when $quantity = 1 |
string | $plural_form | The string to be returned for when $quantity != 1, use %d to place the quantity in the string |
boolean | $use_words_for_single_digits | If the numbers 0 to 9 should be written out as words |
Returns the passed terms joined together using rule 2 from Strunk & White's 'The Elements of Style'
string joinArray( array $strings, string $type )
array | $strings | An array of strings to be joined together |
string | $type | The type of join to perform, 'and' or 'or' |
The terms joined together
Returns the plural version of a singular noun
string pluralize( string $singular_noun, boolean $return_error=FALSE )
string | $singular_noun | The singular noun to pluralize |
boolean | $return_error | If this is TRUE and the noun can't be pluralized, FALSE will be returned instead |
The pluralized noun
Allows replacing the joinArray() function with a user defined function
This would be most useful for changing joinArray() to work with languages other than English.
void registerJoinArrayCallback( callback $callback )
callback | $callback | The function to replace joinArray() with - should accept the same parameters and return the same type |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Resets the configuration of the class
void reset( )
Returns the singular version of a plural noun
string singularize( string $plural_noun, boolean $return_error=FALSE )
string | $plural_noun | The plural noun to singularize |
boolean | $return_error | If this is TRUE and the noun can't be pluralized, FALSE will be returned instead |
The singularized noun
Uses the Porter Stemming algorithm to create the stem of a word, which is useful for searching
See http://tartarus.org/~martin/PorterStemmer/ for details about the algorithm.
string stem( string $word )
string | $word | The word to get the stem of |
The stem of the word
Converts a camelCase, human-friendly or underscore_notation string to underscore_notation
string underscorize( string $string )
string | $string | The string to convert |
The converted string
The fHTML class helps to manipulate text so that it can be reliably converted for HTML display.
When developing websites that allow for user-generated content, it can be challenging to ensure that all content entered is displayed as it was intended to be displayed. Certain content may allow HTML, while other may not. The fHTML class provides two static methods for escaping data to be displayed, encode()
and prepare()
. Both methods treat all content as UTF-8.
If there is a need to escape content that does not allow HTML, the static method encode()
should be used. This method will encode special characters, including the < and > characters used for HTML tags. Because of this, all HTML tags will be displayed as plain text, and will not function as HTML.
It is very important that all untrusted user input be escaped using this method to prevent cross-site scripting attacks. See the security page for more information.
echo fHTML::encode($form_submitted_content);
encode()
also properly escapes characters for display inside of HTML input
, textarea
and select
tags.
prepare()
will ensure that all special characters in the provided content will be properly displayed, while allowing HTML tags and entities. This method should only be used for content coming from trusted sources otherwise the code will be vulnerable to cross-site scripting attacks.
echo fHTML::prepare($trusted_content);
In addition to preparing content for valid display by encoding content, often times content needs to have some basic HTML formatting applied to it. The fHTML class provides two methods, convertNewlines()
and makeLinks()
, to help with common formatting tasks.
convertNewlines()
will look at content as a mixture of plain text and HTML. If neither the <br />
or any block-level HTML tags are found, the content will have all newline characters converted to <br />
. If the converse is true, the content will be returned as is.
Below is an example of the conversion happening:
// First example, no block-level HTML
$content = <<<TEXT
Here is content to be formatted.
Newline characters will be converted into HTML break tags.
TEXT;
echo fHTML::convertNewlines($content);
The output from above would be:
Here is content to be formatted.<br />
<br />
Newline characters will be converted into HTML break tags.
Here is an example of newlines being left as-is due to block-level HTML:
// Second example, block-level HTML present
$content2 = <<<TEXT
<p>
Here is content to be formatted.
</p>
<p>
Newline characters will be left alone since there are block-level HTML tags present.
</p>
TEXT;
echo fHTML::convertNewlines($content2);
The output from above would be:
<p>
Here is content to be formatted.
</p>
<p>
Newline characters will be left alone since there are block-level HTML tags present.
</p>
The makeLinks()
method will parse through a string and create HTML links out of anything that resembles a URL or email address, as long as it is not already part of an <a>
tag.
Here is an example of it in action:
$content = "Example 1: www.example.com.\n";
$content .= "Example 2: https://example.com.\n";
$content .= "Example 3: john@example.com.\n";
$content .= "Example 4: ftp://john:password@example.com.\n";
$content .= "Example 5: www.example.co.uk.\n";
$content .= "Example 6: john@example.co.uk.\n";
$content .= 'Example 7: <a href="http://example.com">http://example.com</a>.';
echo fHTML::makeLinks($content);
The output would be:
Example 1: <a href="http://www.example.com">www.example.com</a>.
Example 2: <a href="https://example.com">https://example.com</a>.
Example 3: <a href="mailto:john@example.com">john@example.com</a>.
Example 4: <a href="ftp://john:password@example.com">ftp://john:password@example.com</a>.
Example 5: <a href="http://www.example.co.uk">www.example.co.uk</a>.
Example 6: <a href="mailto:john@example.co.uk">john@example.co.uk</a>.
Example 7: <a href="http://example.com">http://example.com</a>.
When displaying select
and checkbox
inputs it is necessary to print specific attributes to specify the current state of the inputs. The fHTML class provides two helper methods to simply this process: printOption()
and showChecked()
.
printOption()
will display a select
input option
tag while marking it with selected="selected"
if the options value equals the currently selected value. The following PHP:
<select name="status">
<?
$statuses = array(
'active' => 'Active',
'inactive' => 'Inactive',
'pending' => 'Pending'
);
$current_status = 'active';
foreach ($statuses as $value => $text) {
fHTML::printOption($text, $value, $current_status);
}
?>
</select>
would produce the following HTML:
<select name="status">
<option value="active" selected="selected">Active</option>
<option value="inactive">Inactive</option>
<option value="pending">Pending</option>
</select>
showChecked()
provides similar functionality for checkboxes, however since checkboxes can include many different attributes, showChecked()
only handles printing the checked="checked"
part. Here is an example of using showChecked()
:
<?php
$is_authenticated = TRUE;
$is_super_admin = FALSE;
?>
<p>
<label for="form-is_authenticated">Is Authenticated</label><br />
<input type="checkbox" id="form-is_authenticated" name="is_authenticated" value="1" <? fHTML::showChecked($is_authenticated, TRUE) ?> />
</p>
<p>
<label for="form-is_super_admin">Is Super Admin</label><br />
<input type="checkbox" id="form-is_super_admin" name="is_super_admin" value="1" <? fHTML::showChecked($is_super_admin, TRUE) ?> />
</p>
would produce the following output:
<p>
<label for="form-is_authenticated">Is Authenticated</label><br />
<input type="checkbox" id="form-is_authenticated" name="is_authenticated" value="1" checked="checked" />
</p>
<p>
<label for="form-is_super_admin">Is Super Admin</label><br />
<input type="checkbox" id="form-is_super_admin" name="is_super_admin" value="1" />
</p>
If you default content is HTML, the method sendHeader()
should be called ensure that the Content-Type
header is set to the correct value of text/html; charset=utf-8
. The utf-8
character set encoding is specified since all of Flourish is built to work with UTF-8.
Provides HTML-related methods
This class is implemented to use the UTF-8 character encoding. Please see UTF-8 for more information.
1.0.0b8 | Changed encode() and prepare() to handle arrays of strings 5/19/10 |
---|---|
1.0.0b7 | Fixed a bug where some conditional comments were causing the regex in prepare() to break 11/4/09 |
1.0.0b6 | Updated showChecked() to require strict equality if one parameter is NULL 6/2/09 |
1.0.0b5 | Fixed prepare() so it does not encode multi-line HTML comments 5/9/09 |
1.0.0b4 | Added methods printOption() and showChecked() that were in fCRUD 5/8/09 |
1.0.0b3 | Fixed a bug where makeLinks() would double-link some URLs 1/8/09 |
1.0.0b2 | Fixed a bug where makeLinks() would create links out of URLs in HTML tags 12/5/08 |
1.0.0b | The initial implementation 9/25/07 |
Checks a string of HTML for block level elements
boolean containsBlockLevelHTML( string $content )
string | $content | The HTML content to check |
If the content contains a block level tag
Converts newlines into br tags as long as there aren't any block-level HTML tags present
void convertNewlines( string $content )
string | $content | The content to display |
Converts all HTML entities to normal characters, using UTF-8
string decode( string $content )
string | $content | The content to decode |
The decoded content
Converts all special characters to entites, using UTF-8.
string encode( string|array $content )
string|array | $content | The content to encode |
The encoded content
Takes a block of text and converts all URLs into HTML links
string makeLinks( string $content, integer $link_text_length=0 )
string | $content | The content to parse for links |
integer | $link_text_length | If non-zero, all link text will be truncated to this many characters |
The content with all URLs converted to HTML link
Prepares content for display in UTF-8 encoded HTML - allows HTML tags
string prepare( string|array $content )
string|array | $content | The content to prepare |
The encoded html
Prints an option tag with the provided value, using the selected value to determine if the option should be marked as selected
void printOption( string $text, string $value, string $selected_value=NULL )
string | $text | The text to display in the option tag |
string | $value | The value for the option |
string | $selected_value | If the value is the same as this, the option will be marked as selected |
Sets the proper Content-Type header for a UTF-8 HTML (or pseudo-XHTML) page
void sendHeader( )
Prints a p (or div if the content has block-level HTML) tag with the contents and the class specified - will not print if no content
boolean show( string $content, string $css_class='' )
string | $content | The content to display |
string | $css_class | The CSS class to apply |
If the content was shown
Prints a checked="checked" HTML input attribute if $value equals $checked_value, or if $value is in $checked_value
Please note that if either $value or $checked_value is NULL, a strict comparison will be performed, whereas normally a non-strict comparison is made. Thus 0 and FALSE will cause the checked attribute to be printed, but 0 and NULL will not.
boolean showChecked( string $value, string|array $checked_value )
string | $value | The value for the current HTML input tag |
string|array | $checked_value | The value (or array of values) that has been checked |
If the checked attribute was printed
The fImage class is a representation of an image file on the filesystem. It provides all of the functionality of the fFile class and adds the ability to perform image manipulation via GD or ImageMagick.
As mentioned above, the fImage class provide image manipulation by using either the command line program ImageMagick or the PHP GD extension. fImage prefers ImageMagick since it provides superior performance and does not have the restriction of the PHP memory limit, like the GD extension does.
If the GD extension is not installed and ImageMagick can not be found in a standard location, fImage will need to be configured with the location of ImageMagick. This can be done by passing the ImageMagick binarys directory to the static method setImageMagickDirectory()
.
// An example of manually configuring ImageMagick
fImage::setImageMagickDirectory('/usr/local/imagemagick/bin');
In addition to configuring the path to the ImageMagick binaries, it is also possible to set a temporary directory for ImageMagick to use when processing files. The setImageMagickTempDir()
static method performs this task and takes a single parameter $temp_dir
.
// Setting a custom temporary directory for ImageMagick
fImage::setImageMagickTempDir('/tmp/imagemagick');
The fImage class includes a few methods to provide basic metadata about the image. getWidth()
and getHeight()
provide straight-forward access to image dimensions. getDimensions()
returns a two-element array of the width, then the height.
$width = $image->getWidth();
$height = $image->getHeight();
list($witdh, $height) = $image->getDimensions();
The method getType()
will return a string with the image type. Types include: 'jpg'
, 'gif'
, 'png'
and 'tif'
.
$type = $image->getType();
Probably the most significant functionality of the fImage class is the ability to modify an image. There are currently three different modifications that can be performed: crop()
, cropToRatio()
, resize()
and desaturate()
.
Calling any of these modifications will not actually cause the changes to be written to disk until saveChanges()
is called.
crop()
accepts four parameters, the pixels dimensions for $new_width
and $new_height
, $crop_from_x
, $crop_from_y
, and will perform pixel-specific cropping.
// Create a 16x16 icon from the top left of an image
$image1 = new fImage('./example.gif');
$image1->crop(16, 16, 0, 0);
$image1->saveChanges();
It is also possible to crop with $crop_from_x
and $crop_from_y
being a string position. The possible values for $crop_from_x
are left
, center
and right
. The possible values for $crop_from_y
are top
, center
and bottom
.
// Create a 16x16 icon from the top right of an image
$image1 = new fImage('./example.gif');
$image1->crop(16, 16, 'right', 'top');
$image1->saveChanges();
cropToRatio()
will crop the largest rectangle our of an image that conforms to the ratio passed in via $ratio_width
and $ratio_height
. By default, the crop is made from the center of the image.
// Create a 32x32 icon from an image
$image3 = new fImage('./example.gif');
$image3->cropToRatio(1, 1);
$image3->resize(32, 32);
$image3->saveChanges();
Similar to crop()
, it is possible to crop to a ratio from a specific position in the image. The third parameter, $horizontal_position
accepts either left
, center
or right
. The fourth parameter, $vertical_position
, accepts top
, center
or bottom
.
// Create a 32x32 icon from the bottom right of an image
$image3 = new fImage('./example.gif');
$image3->cropToRatio(1, 1, 'right', 'bottom');
$image3->resize(32, 32);
$image3->saveChanges();
desaturate()
will cause the image to lose all color information and become grayscale.
// Change the image to be grayscale
$image3 = new fImage('./example.gif');
$image3->desaturate();
$image3->saveChanges();
resize()
will scale the image proportionally to fit the canvas size defined by the parameters $canvas_width
and $canvas_height
. That is to say, the aspect ratio of the image will be retained, and it will not be stretched in either dimension. If either parameter is empty()
or 0
, the image will be resized to fit the one specified dimension.
// Ensure an image is never wider than 250 pixels
$image4 = new fImage('./example.gif');
$image4->resize(250, 0);
$image4->saveChanges();
resize()
can also accept an optional third parameter, $allow_upsizing
. By default resize()
will only make the image smaller, but when this is TRUE
the image will be made to fit the dimensions even if it must be increased in size.
// Make an image exactly 100 pixels wide
$image5 = new fImage('./example.gif');
$image5->resize(100, 0, TRUE);
$image5->saveChanges();
saveChanges()
can accept from zero to three parameters. The first optional parameter is the $new_image_type
which can be 'jpg'
, 'gif'
or 'png'
. If no $new_image_type
is specified, the image will be resaved in the current format.
// Saving as a PNG
$image2 = new fImage('./example.gif');
$image2->resize(250, 0);
$image2->saveChanges('png');
If the 'jpg'
image type is specified, there is a second optional parameter, the $jpeg_quality
to use. If no $jpeg_quality
is specified, 90
is used as the default.
// Saving as a 60 quality JPEG
$image2 = new fImage('./example.gif');
$image2->resize(250, 0);
$image2->saveChanges('jpeg', 60);
A third optional parameter, $overwrite
, accepts a boolean and will cause a file with the same name and type to be overwritten. For example, if the file is currently image.gif
and you call saveChanges()
with the parameter 'jpg'
and another file named image.jpg
exists, by default image.gif
will be saved with changes as image_copy1.jpg
. When $overwrite
is TRUE
, image.jpg
will be overwritten.
// Saving as a PNG and overwrite any existing example.png
$image2 = new fImage('./example.gif');
$image2->resize(250, 0);
$image2->saveChanges('png', TRUE);
Represents an image on the filesystem, also provides image manipulation functionality
1.0.0b33 | Fixed a method signature 8/24/11 |
---|---|
1.0.0b32 | Added a call to clearstatcache() to saveChanges() to solve a bug when fFile::output() is called in the same script execution 5/23/11 |
1.0.0b31 | Fixed a bug in using ImageMagick to convert files with a colon in the filename 3/20/11 |
1.0.0b30 | Added a check for systems using the GD extension and no memory limit, plus a check for ImageMagick's convert command failing 3/20/11 |
1.0.0b29 | Added checks for AIX 1/19/11 |
1.0.0b28 | Added the rotate() method, added code to try and prevent fatal errors due to hitting the memory limit when using GD 11/29/10 |
1.0.0b27 | Backwards Compatibility Break - changed the parameter order in crop() from $crop_from_x, $crop_from_y, $new_width, $new_height to $new_width, $new_height, $crop_from_x, $crop_from_y - added $horizontal_position and $vertical_position parameters to cropToRatio() 11/9/10 |
1.0.0b26 | Fixed a bug where processing via ImageMagick was not properly setting the default RGB colorspace 10/19/10 |
1.0.0b25 | Fixed the class to not generate multiple files when saving a JPG from an animated GIF or a TIF with a thumbnail 9/12/10 |
1.0.0b24 | Updated class to use fCore::startErrorCapture() instead of error_reporting() 8/9/10 |
1.0.0b23 | Fixed the class to detect when exec() is disabled and the function has a space before or after in the list 7/21/10 |
1.0.0b22 | Fixed isImageCompatible() to handle certain JPGs created with Photoshop 4/3/10 |
1.0.0b21 | Fixed resize() to allow dimensions to be numeric strings instead of just integers 4/9/10 |
1.0.0b20 | Added append() 3/15/10 |
1.0.0b19 | Updated for the new fFile API 3/5/10 |
1.0.0b18 | Fixed a bug in saveChanges() that would incorrectly cause new filenames to be created, added the $overwrite parameter to saveChanges(), added the $allow_upsizing parameter to resize() 3/3/10 |
1.0.0b17 | Fixed a couple of bug with using ImageMagick on Windows and BSD machines 3/2/10 |
1.0.0b16 | Fixed some bugs with GD not properly handling transparent backgrounds and desaturation of .gif files 10/27/09 |
1.0.0b15 | Added getDimensions() 8/7/09 |
1.0.0b14 | Performance updates for checking image type and compatiblity 7/31/09 |
1.0.0b13 | Updated class to work even if the file extension is wrong or not present, saveChanges() detects files that aren't writable 7/29/09 |
1.0.0b12 | Fixed a bug where calling saveChanges() after unserializing would throw an exception related to the image processor 5/27/09 |
1.0.0b11 | Added a crop() method 5/27/09 |
1.0.0b10 | Fixed a bug with GD not saving changes to files ending in .jpeg 3/18/09 |
1.0.0b9 | Changed processWithGD() to explicitly free the image resource 3/18/09 |
1.0.0b8 | Updated for new fCore API 2/16/09 |
1.0.0b7 | Changed @ error suppression operator to error_reporting() calls 1/26/09 |
1.0.0b6 | Fixed cropToRatio() and resize() to always return the object even if nothing is to be done 1/5/09 |
1.0.0b5 | Added check to see if exec() is disabled, which causes ImageMagick to not work 1/3/09 |
1.0.0b4 | Fixed saveChanges() to not delete the image if no changes have been made 12/18/08 |
1.0.0b3 | Fixed a bug with $jpeg_quality in saveChanges() from 1.0.0b2 12/16/08 |
1.0.0b2 | Changed some int casts to round() to fix resize() dimension issues 12/11/08 |
1.0.0b | The initial implementation 12/19/07 |
fFile | --fImage
Creates an image on the filesystem and returns an object representing it
This operation will be reverted by a filesystem transaction being rolled back.
fImage create( string $file_path, string $contents )
string | $file_path | The path to the new image |
string | $contents | The contents to write to the image |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Returns an array of acceptable mime types for the processor that was detected
array getCompatibleMimetypes( )
The mime types that the detected image processor can manipulate
Gets the dimensions and type of an image stored on the filesystem
The 'type' key will have one of the following values:
mixed getInfo( string $image_path, string $element=NULL )
string | $image_path | The path to the image to get stats for |
string | $element | The element to retrieve: 'type', 'width', 'height' |
An associative array: 'type' => {mixed}, 'width' => {integer}, 'height' => {integer}, or the element specified
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Checks to make sure the class can handle the image file specified
boolean isImageCompatible( string $image )
string | $image | The image to check for incompatibility |
If the image is compatible with the detected image processor
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Resets the configuration of the class
void reset( )
Sets the directory the ImageMagick binary is installed in and tells the class to use ImageMagick even if GD is installed
void setImageMagickDirectory( string $directory )
string | $directory | The directory ImageMagick is installed in |
Sets a custom directory to use for the ImageMagick temporary files
void setImageMagickTempDir( string $temp_dir )
string | $temp_dir | The directory to use for the ImageMagick temp dir |
Creates an object to represent an image on the filesystem
fImage __construct( string $file_path, boolean $skip_checks=FALSE )
string | $file_path | The path to the image |
boolean | $skip_checks | If file checks should be skipped, which improves performance, but may cause undefined behavior - only skip these if they are duplicated elsewhere |
Prevents a programmer from trying to append an image
void append( mixed $data )
mixed | $data | The data to append to the image |
Crops the image by the exact pixel dimensions specified
The crop does not occur until saveChanges() is called.
fImage crop( numeric $new_width, numeric $new_height, numeric|string $crop_from_x, numeric|string $crop_from_y )
numeric | $new_width | The width in pixels to crop the image to |
numeric | $new_height | The height in pixels to crop the image to |
numeric|string | $crop_from_x | The number of pixels from the left of the image to start the crop from, or a horizontal position of 'left', 'center' or 'right' |
numeric|string | $crop_from_y | The number of pixels from the top of the image to start the crop from, or a vertical position of 'top', 'center' or 'bottom' |
The image object, to allow for method chaining
Crops the biggest area possible from the center of the image that matches the ratio provided
The crop does not occur until saveChanges() is called.
fImage cropToRatio( numeric $ratio_width, numeric $ratio_height, string $horizontal_position='center', string $vertical_position='center' )
numeric | $ratio_width | The width ratio to crop the image to |
numeric | $ratio_height | The height ratio to crop the image to |
string | $horizontal_position | A horizontal position of 'left', 'center' or 'right' |
string | $vertical_position | A vertical position of 'top', 'center' or 'bottom' |
The image object, to allow for method chaining
Converts the image to grayscale
Desaturation does not occur until saveChanges() is called.
fImage desaturate( )
The image object, to allow for method chaining
Returns the width and height of the image as a two element array
array getDimensions( )
In the format 0 => (integer) {width}, 1 => (integer) {height}
Returns the height of the image
integer getHeight( )
The height of the image in pixels
Returns the type of the image
string getType( )
The type of the image: 'jpg', 'gif', 'png', 'tif'
Returns the width of the image
integer getWidth( )
The width of the image in pixels
Sets the image to be resized proportionally to a specific size canvas
Will only size down an image. This method uses resampling to ensure the resized image is smooth in appearance. Resizing does not occur until saveChanges() is called.
fImage resize( integer $canvas_width, integer $canvas_height, boolean $allow_upsizing=FALSE )
integer | $canvas_width | The width of the canvas to fit the image on, 0 for no constraint |
integer | $canvas_height | The height of the canvas to fit the image on, 0 for no constraint |
boolean | $allow_upsizing | If the image is smaller than the desired canvas, the image will be increased in size |
The image object, to allow for method chaining
Sets the image to be rotated
Rotation does not occur until saveChanges() is called.
void rotate( integer $degrees )
integer | $degrees | The number of degrees to rotate - 90, 180, or 270 |
Saves any changes to the image
If the file type is different than the current one, removes the current file once the new one is created.
This operation will be reverted by a filesystem transaction being rolled back. If a transaction is in progress and the new image type causes a new file to be created, the old file will not be deleted until the transaction is committed.
fImage saveChanges( string $new_image_type=NULL, integer $jpeg_quality=90, boolean $overwrite=FALSE )
fImage saveChanges( string $new_image_type=NULL, boolean $overwrite=FALSE )
string | $new_image_type | The new file format for the image: 'NULL (no change), 'jpg', 'gif', 'png'` |
integer | $jpeg_quality | The quality setting to use for JPEG images - this may be ommitted |
boolean | $overwrite | If an existing file with the same name and extension should be overwritten |
The image object, to allow for method chaining
The fJSON class is primarily a compatibility class to allow for encoding and decoding JSON strings on server running PHP 5.0 and 5.1PHP 5.2 comes bundled with json_encode()
and json_decode()
. In addition, the class provides a simple method to send the proper headers with a JSON response.
The methods encode()
and decode()
allow for encoding and decoding JSON respectively. For servers that do have the json
PHP extension, the json
extension functions will be used. A native PHP implementation will automatically be used for server that do not have the json
extension installed.
encode()
takes a single parameter, the PHP value to encode. Associative arrays will be encoded as JSON objects, with the rest of PHP data types being converted into the equivalent JSON data type. Technically all valid JSON needs to be either an array or an object, however the PHP json
extension does not follow this restriction.
The following PHP
echo fJSON::encode(array());
echo "\n\n";
echo fJSON::encode(
array(1, 2, 3, 4, 'Hello!', TRUE, FALSE, NULL)
);
echo "\n\n";
echo fJSON::encode(
array('foo' => 'bar', 'key' => TRUE
);
would output the following JSON:
[]
[1,2,3,4,"Hello!",true,false,null]
{"foo":"bar","key":true}
decode()
takes a JSON string and will convert it into the equivalent PHP data types. An optional second parameter $assoc
when set to TRUE
will cause JSON objects to be converted to associative arrays instead of an instance of the PHP class stdClass
.
The following PHP
echo fCore::dump(fJSON::decode('[]'));
echo "\n\n";
echo fCore::dump(fJSON::decode('[1,2,3,4,"Hello!",true,false,null]'));
echo "\n\n";
echo fCore::dump(fJSON::decode('{"foo":"bar","key":true}'));
echo "\n\n";
echo fCore::dump(fJSON::decode('{"foo":"bar","key":true}', TRUE));
would output the following text:
Array
(
)
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => Hello!
[5] => {true}
[6] => {false}
[7] => {null}
)
stdClass Object
(
[foo] => bar
[key] => {true}
)
Array
(
[foo] => bar
[key] => {true}
)
If you are returning JSON in a response, calling the sendHeader()
mehod will ensure that the Content-Type
header is set to the correct value of application/json; charset=utf-8
.
Usually when writing a script that creates JSON, the header will need to be sent and a PHP value will need to be encoded and output. The static method output()
does all of this.
// These two blocks are equivalent
fJSON::sendHeader();
echo fJSON::encode($value);
fJSON::output($value);
Provides encoding and decoding for JSON
This class is a compatibility class for the json extension on servers with PHP 5.0 or 5.1, or servers with the json extension compiled out.
This class will handle JSON values that are not contained in an array or object - such values are not valid according to the JSON spec, but the functionality is included for compatiblity with the json extension.
1.0.0b6 | Removed e flag from preg_replace() calls 6/8/10 |
---|---|
1.0.0b5 | Added the output() method 3/15/10 |
1.0.0b4 | Fixed a bug with decode() where JSON objects could lose all but the first key: value pair 5/6/09 |
1.0.0b3 | Updated the class to be consistent with PHP 5.2.9+ for encoding and decoding invalid data 5/4/09 |
1.0.0b2 | Changed @ error suppression operator to error_reporting() calls 1/26/09 |
1.0.0b | The initial implementation 7/12/08 |
Please note: this constant is primarily intended for internal use by Flourish and will normally not be useful in site/application code
An abstract representation of ]
Please note: this constant is primarily intended for internal use by Flourish and will normally not be useful in site/application code
An abstract representation of , in a JSON array
Please note: this constant is primarily intended for internal use by Flourish and will normally not be useful in site/application code
An abstract representation of [
Please note: this constant is primarily intended for internal use by Flourish and will normally not be useful in site/application code
An abstract representation of :
Please note: this constant is primarily intended for internal use by Flourish and will normally not be useful in site/application code
An abstract representation of a boolean false
Please note: this constant is primarily intended for internal use by Flourish and will normally not be useful in site/application code
An abstract representation of a floating value
Please note: this constant is primarily intended for internal use by Flourish and will normally not be useful in site/application code
An abstract representation of an integer
Please note: this constant is primarily intended for internal use by Flourish and will normally not be useful in site/application code
An abstract representation of a JSON object key
Please note: this constant is primarily intended for internal use by Flourish and will normally not be useful in site/application code
An abstract representation of null
Please note: this constant is primarily intended for internal use by Flourish and will normally not be useful in site/application code
An abstract representation of }
Please note: this constant is primarily intended for internal use by Flourish and will normally not be useful in site/application code
An abstract representation of , in a JSON object
Please note: this constant is primarily intended for internal use by Flourish and will normally not be useful in site/application code
An abstract representation of {
Please note: this constant is primarily intended for internal use by Flourish and will normally not be useful in site/application code
An abstract representation of a string
Please note: this constant is primarily intended for internal use by Flourish and will normally not be useful in site/application code
An abstract representation of a boolean true
Decodes a JSON string into native PHP data types
This function is very strict about the format of JSON. If the string is not a valid JSON string, NULL will be returned.
array|stdClass decode( string $json, boolean $assoc=FALSE )
string | $json | This should be the name of a related class |
boolean | $assoc | If this is TRUE, JSON objects will be represented as an assocative array instead of a stdClass object |
A PHP equivalent of the JSON string
Encodes a PHP value into a JSON string
string encode( mixed $value )
mixed | $value | The PHP value to encode |
The JSON string that is equivalent to the PHP value
Sets the proper Content-Type header and outputs the value, encoded as JSON
void output( mixed $value )
mixed | $value | The PHP value to output as JSON |
Sets the proper Content-Type header for UTF-8 encoded JSON
void sendHeader( )
The fLoader class is designed to load Flourish class. By default it detects the optimal type of loading to perform based on the server environment. In addition to loading classes, it creates constructor functions for the various Flourish value object classes.
Loading Flourish should normally be performed by calling the static method best()
.
include './path/to/fLoader.php';
fLoader::best();
If an opcode cache (e.g. APC, XCache, etc) is detected, all class file are immediately included. If an opcode cache is not detected, an autoload function is registered via spl_autoload_register()
.
If you need explicit control over what type of loading is performed, you should call one of the static methods eager()
or lazy()
instead.
Constructor functions is a term used to refer to functions that simply call the constructor of a class. The functions have the same name as the class, which effectively means that the new
keyword is not required, and that method calls may be changed off of the constructor.
// Without constructor functions
$date = new fDate();
$date = $date->modify('Y-m-01');
// With constructor functions
$date = fDate()->modify('Y-m-01');
fLoader automatically creates constructor functions for all value object classes:
The static method hasOpcodeCache()
can be used to create intelligent loading functionality for other code.
include './other/project/Loader.php';
if (fLoader::hasOpcodeCache()) {
Loader::includeAll();
} else {
Loader::autoload();
}
A class that loads Flourish
1.0.0b3 | Added fEmail() constructor function 9/12/11 |
---|---|
1.0.0b2 | Added fPagination 9/6/11 |
1.0.0b | The initial implementation 8/26/11 |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Tries to load a Flourish class
void autoload( string $class )
string | $class | The class to load |
Performs eager loading if an op-code cache is present, otherwise lazy
void best( )
Loads all Flourish classes when called
void eager( )
Check if a PHP opcode cache is installed
The following opcode caches are currently detected:
boolean hasOpcodeCache( )
If a PHP opcode cache is loaded
The fMailbox class provides an interface to list, fetch and delete emails from POP3 and IMAP servers. It fully parses the messages, handles attachments, related content and inline files. All text content is converted to UTF-8. This class does not require the PHP imap extension. Secure connections are supported if the openssl extension is installed.
To create a mailbox object, you must provide the $type
(either pop3
or imap
), $server
, $username
and $password
.
// Connect to a local pop3 server
$mailbox = new fMailbox('pop3', 'localhost', 'user', 'password');
// Connect to a remote imap server
$mailbox = new fMailbox('imap', 'example.com', 'user', 'password');
The $port
is automatically determined by the type, but can be overridden as a fifth parameter.
// Connect to an imap server on port 874
$mailbox = new fMailbox('imap', 'example.com', 'user', 'password', 874);
If the connection needs to be $secure
, pass TRUE
to the sixth parameter.
// Make a secure connection on the default secure IMAP port
$mailbox = new fMailbox('imap', 'example.com', 'user', 'password', NULL, TRUE);
The default socket $timeout
in seconds can be adjusted with the seventh parameter.
// Timeout after 5 seconds
$mailbox = new fMailbox('imap', 'example.com', 'user', 'password', NULL, FALSE, 5);
A list of messages can be retrieved from a mailbox by calling the listMessages()
method.
// Retrieve an overview of all messages
$messages = $mailbox->listMessages();
The result in an array of messages, with the key being the message's unique identifier (UID) and the value being an array with the keys:
uid
: a unique identifier for this message on this server integerreceived
: date message was received stringsize
: size of message in bytes integerdate
: date message was sent stringfrom
: the From:
header value stringsubject
: the message subject stringmessage_id
: the message-id header value, should be globally unique string, optionalto
: the to header value string. optionalin_reply_to
: the in-reply-to header value string, optionalBelow is an example of a list containing a single example messages:
array(
7556 => array(
'uid' => 7556,
'received' => '28 Jan 2010 19:45:07 -400',
'size' => 1735,
'date' => '28 Jan 2010 19:44:48 -400',
'from' => '"John Smith" <john@example.com>',
'subject' => 'Thanks for Signing Up!',
'message_id' => '<788a8c8865e19b0c3243@example.com>',
'to' => 'will@flourishlib.com, Joe <joe@example.com>',
'in_reply_to' => '',
)
)
listMessages()
has two optional parameters, the $limit
and $page
which control how many and which messages should be retrieved.
// Get the first five messages
$messages = $mailbox->listMessages(5);
// Get messages 11 to 15
$messages = $mailbox->listMessage(5, 3);
Individual messages can be retrieved in a parsed format by calling the method fetchMessage()
. It requires a single parameter, $uid
, which can be retrieved by the listMessages()
method.
$message = $mailbox->fetchMessage(7556);
The return value is an associative array containing information about the message. The array will always include the following keys:
uid
: The UID of the messagereceived
: The date the message was received by the serverheaders
: An associative array of mail headers, the keys being the lowercase header names. By default the values are a string, but the following headers are further parsed:
received
: An array of the Recieved
headersto
: An array of parsed addressees, each being an associative array in the format:
mailbox
: The part before the @
host
: The part after the @
personal
: The addressee's name optionalcc
: An array of parsed addressees, in the same format as to
from
: An associative array of the from address, in the same format as to
sender
: An associative array of the sender address, in the same format as to
reply-to
: An associative array of the address to reply to, in the same format as to
content-type
: An associative array in the format:
value
: The main header valuefields
: An associative array of the ;
separated key=value pairs after the main valuecontent-disposition
: An associative array in the same format as content-type
And one or more of the following keys:
text
: The plaintext bodyhtml
: The HTML bodyattachment
: An array of attachments, each containing:
filename
: The name of the filemimetype
: The mimetype of the filedata
: The raw contents of the fileinline
: An array of inline files, each containing:
filename
: The name of the filemimetype
: The mimetype of the filedata
: The raw contents of the filerelated
: An associative array of related files, such as embedded images, with the key cid:{content-id}
and an array value containing:
mimetype
: The mimetype of the filedata
: The raw contents of the fileverified
: If the message contents were verified via an S/MIME certificate - if not verified the smime.p7s
file will be listed as an attachmentdecrypted
: If the message contents were decrypted via an S/MIME private key - if not decrypted the smime.p7m
file will be listed as an attachmentAll values in headers, text and body will have been decoded to UTF-8. Files in the attachment, inline and related array will all retain their original encodings.
Here is an example of a parsed message:
array(
'received' => '28 Apr 2010 22:00:38 -0400',
'headers' => array(
'received' => array(
0 => '(qmail 25838 invoked from network); 28 Apr 2010 22:00:38 -0400',
1 => 'from example.com (HELO ?192.168.10.2?) (example) by example.com with (DHE-RSA-AES256-SHA encrypted) SMTP; 28 Apr 2010 22:00:38 -0400'
),
'message-id' => '<4BD8E815.1050209@flourishlib.com>',
'date' => 'Wed, 28 Apr 2010 21:59:49 -0400',
'from' => array(
'personal' => 'Will Bond',
'mailbox' => 'tests',
'host' => 'flourishlib.com'
),
'user-agent' => 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.9) Gecko/20100317 Thunderbird/3.0.4',
'mime-version' => '1.0',
'to' => array(
0 => array(
'mailbox' => 'tests',
'host' => 'flourishlib.com'
)
),
'subject' => 'This message is encrypted'
),
'text' => 'This message is encrypted',
'decrypted' => TRUE,
'uid' => 15
);
The second, optional boolean parameter $convert_newlines
will convert all \r\n
line-endings to \n
inside of the text
and html
elements when set to TRUE
.
// Convert all line-endings to \n for the text and html
$message = $mailbox->fetchMessage(7556, TRUE);
One or more messages can be deleted at a time by calling deleteMessages()
and passing a single UID, or an array of UIDs.
// Delete a single message
$mailbox->deleteMessages(7556);
// Delete multiple messages
$mailbox->deleteMessages(array(54, 928, 892));
The static method parseMessage()
can be used to parse a full MIME email message into the same format that fetchMessage()
returns, minus the uid
key.
$parsed_message = fMailbox::parseMessage(file_get_contents('/path/to/email'));
Like fetchMessage()
, it also supports a second, optional boolean parameter $convert_newlines
to change \r\n
into \n
for text
and html
.
One of the more advanced features of fMailbox is the ability to seamlessly handle S/MIME signed and encrypted messages. The static method addSMIMEPair()
accepts an $email_address
and either a single S/MIME $certificate_file
path or that combined with an S/MIME $private_key_file
path and $private_key_password
.
// Adds the certificate and private key for will@flourishlib.com
fMailbox::addSMIMEPair('will@flourishlib.com', '/path/to/will.crt', '/path/to/will.key', 'password');
If the certificate is provided, messages coming from the $email_address
and S/MIME signed will be checked for authenticity. Currently the class is configured to only verify messages with an explicitly added key, and will not use the certificate included in the signature.
If the private key and certificate are provided, any messages coming to the $email_address
and S/MIME encrypted will be decrypted. The parsed message will contain the decrypted content in the appropriate text
, html
, attachment
, etc. sections of the parsed message array.
If verification succeeds, the verified
key will be set to TRUE
in the parsed message array. If decryption is successful, the decrypted
key will be set to TRUE
. If either fail, or a certificate or key is not available for the specified email address, the S/MIME signature or encrypted message will appear as a file in the attachment
element.
Retrieves and deletes messages from a email account via IMAP or POP3
All headers, text and html content returned by this class are encoded in UTF-8. Please see UTF-8 for more information.
1.0.0b18 | Fixed a bug in fetchMessageSource() where IMAP connections would add an extra \r\n to the end of the source 9/16/12 |
---|---|
1.0.0b17 | Updated the class to be more forgiving when parsing the response for STATUS and FETCH IMAP commands 9/15/12 |
1.0.0b16 | Added method fetchMessageSource() 9/15/12 |
1.0.0b15 | Fixed handling of bounces with no headers 9/15/12 |
1.0.0b14 | Added a workaround for iconv having issues in MAMP 1.9.4+ 7/26/11 |
1.0.0b13 | Fixed handling of headers in relation to encoded-words being embedded inside of quoted strings 7/26/11 |
1.0.0b12 | Enhanced the error checking in write() 6/3/11 |
1.0.0b11 | Added code to work around PHP bug #42682 (http://bugs.php.net/bug.php?id=42682) where stream_select() doesn't work on 64bit machines from PHP 5.2.0 to 5.2.5, improved connectivity error handling and timeouts while reading data 1/10/11 |
1.0.0b10 | Fixed parseMessage() to properly handle a header format edge case and properly set the text and html keys even when the email has an explicit Content-disposition: inline header 11/25/10 |
1.0.0b9 | Fixed a bug in parseMessage() that could cause HTML alternate content to be included in the inline content array instead of the html element 9/20/10 |
1.0.0b8 | Fixed parseMessage() to be able to handle non-text/non-html multipart parts that do not have a Content-disposition header 9/18/10 |
1.0.0b7 | Fixed a typo in read() 9/7/10 |
1.0.0b6 | Fixed a typo from 1.0.0b4 7/21/10 |
1.0.0b5 | Fixes for increased compatibility with various IMAP and POP3 servers, hacked around a bug in PHP 5.3 on Windows 6/22/10 |
1.0.0b4 | Added code to handle emails without an explicit Content-type header 6/4/10 |
1.0.0b3 | Added missing static method callback constants 5/11/10 |
1.0.0b2 | Added the missing enableDebugging() 5/5/10 |
1.0.0b | The initial implementation 5/5/10 |
Adds an S/MIME certificate, or certificate + private key pair for verification and decryption of S/MIME messages
void addSMIMEPair( string $email_address, fFile|string $certificate_file, fFile $private_key_file=NULL, string $private_key_password=NULL )
string | $email_address | The email address the certificate or private key is for |
fFile|string | $certificate_file | The file the S/MIME certificate is stored in - required for verification and decryption |
fFile | $private_key_file | The file the S/MIME private key is stored in - required for decryption only |
string | $private_key_password | The password for the private key |
Parses a MIME message into an associative array of information
The output includes the following keys:
And one or more of the following:
All values in headers, text and body will have been decoded to UTF-8. Files in the attachment, inline and related array will all retain their original encodings.
array parseMessage( string $message, boolean $convert_newlines=FALSE )
string | $message | The full source of the email message |
boolean | $convert_newlines | If \r\n should be converted to \n in the text and html parts the message |
The parsed email message - see method description for details
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Resets the configuration of the class
void reset( )
Configures the connection to the server
Please note that the GMail POP3 server does not act like other POP3 servers and the GMail IMAP server should be used instead. GMail POP3 only allows retrieving a message once - during future connections the email in question will no longer be available.
fMailbox __construct( string $type, string $host, string $username, string $password, integer $port=NULL, boolean $secure=FALSE, integer $timeout=NULL )
string | $type | The type of mailbox, 'pop3' or 'imap' |
string | $host | The server hostname or IP address |
string | $username | The user to log in as |
string | $password | The user's password |
integer | $port | The port to connect via - only required if non-standard |
boolean | $secure | If SSL should be used for the connection - this requires the openssl extension |
integer | $timeout | The timeout to use when connecting |
Disconnects from the server
void __destruct( )
Closes the connection to the server
void close( )
Deletes one or more messages from the server
Passing more than one UID at a time is more efficient for IMAP mailboxes, whereas POP3 mailboxes will see no difference in performance.
void deleteMessages( integer|array $uid )
integer|array | $uid | The UID(s) of the message(s) to delete |
Sets if debug messages should be shown
void enableDebugging( boolean $flag )
boolean | $flag | If debugging messages should be shown |
Retrieves a single message from the server
The output includes the following keys:
And one or more of the following:
All values in headers, text and body will have been decoded to UTF-8. Files in the attachment, inline and related array will all retain their original encodings.
array fetchMessage( integer $uid, boolean $convert_newlines=FALSE )
integer | $uid | The UID of the message to retrieve |
boolean | $convert_newlines | If \r\n should be converted to \n in the text and html parts the message |
The parsed email message - see method description for details
Retrieves the raw source of a single message from the server
This method is primarily useful for storing the raw source of a message. Normal use of fMailbox would involved calling fetchMessage(), which calls this method and then parseMessage().
string fetchMessageSource( integer $uid )
integer | $uid | The UID of the message to retrieve |
The raw message source of the email
Gets a list of messages from the server
The structure of the returned array is:
array(
(integer) {uid} => array(
'uid' => (integer) {a unique identifier for this message on this server},
'received' => (string) {date message was received},
'size' => (integer) {size of message in bytes},
'date' => (string) {date message was sent},
'from' => (string) {the from header value},
'subject' => (string) {the message subject},
'message_id' => (string) {optional - the message-id header value, should be globally unique},
'to' => (string) {optional - the to header value},
'in_reply_to' => (string) {optional - the in-reply-to header value}
), ...
)
All values will have been decoded to UTF-8.
array listMessages( integer $limit=NULL, integer $page=NULL )
integer | $limit | The number of messages to retrieve |
integer | $page | The page of messages to retrieve |
A list of messages on the server - see method description for details
The fMessaging class is a simple session-based messaging tool that allows for messaging to be pulled out of your URLs and kept behind the scenes.
The method create()
will construct a message and save it for later retrieval. It accepts three parameters, the $name
of the message, the $recipient
and the $message
itself. Here is an example of creating a message:
fMessaging::create('success', '/admin/users', 'The user was successfully created');
As you can see above, I used a URL as the recipient, however this is not a requirement. Any string can be used as a recipient, you will just need to use the same recipient when retrieving the message, as you will see below.
Once a message has been created, it can then be retrieved. The action of retrieving a message deletes it from the session, thus messages are write-once/read-once. The retrieve()
method requires two parameters, the $name
of the message and the $recipient
. Both parameters need to be the same as when the message was created.
If a message for the $name
and $recipient
combination has not been created, NULL
will be returned instead.
Here is an example of retrieving a message and displaying it:
if ($success = fMessaging::retrieve('success', '/admin/users')) {
echo $success;
}
Since calling retrieve()
removes the message and prevents it from being retrieved on another page, sometimes it is more appropriate to call the method check()
. check()
will return a boolean, indicating if a message with the $name
and $recipient
currently exists:
if (fMessaging::check('success', '/admin/users')) {
// ...
}
The show()
method will display any message with the $name
and $recipient
specified. If a message exists, it be displayed in a p
or div
tag. By default, the $name
of the message will be used as the CSS class for the HTML tag, however if a different class is desired, the optional third parameter $css_class
can be specified.
show()
will only print the output if the string is not empty, and will detect if the provided content contains any block-level HTML tags, automatically switching from printing a p
tag to a div
tag.
Here are a few examples:
// Example 1: Show the success message for /admin/users
fMessaging::show('success', '/admin/users');
// Example 2: Show the error message for /admin/users with the CSS class 'message'
fMessaging::show('error', '/admin/users', 'message');
And here is the output for the two calls to show()
(whitespace added to the HTML for readability):
<!-- Example 1 -->
<p class="success">
The user was successfully created!
</p>
<!-- Example 2 -->
<div class="message">
<p>
The following fields need a value:
</p>
<ul>
<li>Name</li>
<li>Email</li>
</ul>
</div>
It is also possible to show multiple messages with a single call by passing an array of message names or '*'
for all messages. Please note that using either of these options will disable the functionality of the third parameter, $css_class
, and the message names will be used for the CSS classes.
// Show 'success' and 'error'
fMessaging::show(
array('success', 'error'),
'/admin/users'
);
// Show all messages for the recipient
fMessaging::show('*', '/admin/users');
It is possible to use all of the fMessaging methods without a recipient. To do this, simply leave out the recipient parameter. Please note that this means each method will have one less parameter, it does not mean that NULL
should be passed in instead.
Here are the methods without a recipient:
fMessaging::create('success', 'The user was successfully created');
$success = fMessaging::retrieve('success');
if (fMessaging::check('success')) { }
fMessaging::show('success');
The only downside to not specifying a recipient is the possibility of overwriting messages in a multi-tab browser environment.
Provides session-based messaging for page-to-page communication
1.0.0b7 | Fixed a small PHPDoc error 3/15/10 |
---|---|
1.0.0b6 | Updated class to use new fSession API 10/23/09 |
1.0.0b5 | Made the $recipient parameter optional for all methods 7/8/09 |
1.0.0b4 | Added support for '*' and arrays of names to check() 6/2/09 |
1.0.0b3 | Updated class to use new fSession API 5/8/09 |
1.0.0b2 | Changed show() to accept more than one message name, or * for all messages 1/12/09 |
1.0.0b | The initial implementation 3/5/08 |
Checks to see if a message exists of the name specified for the recipient specified
boolean check( string $name, string $recipient=NULL )
string | $name | The name or array of names of the message(s) to check for, or '*' to check for any |
string | $recipient | The intended recipient |
If a message of the name and recipient specified exists
Creates a message that is stored in the session and retrieved by another page
void create( string $name, string $recipient, string $message )
void create( string $name, string $message )
string | $name | A name for the message |
string | $recipient | The intended recipient - this may be ommitted |
string | $message | The message to send |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Resets the data of the class
void reset( )
Retrieves and removes a message from the session
string retrieve( string $name, string $recipient=NULL )
string | $name | The name of the message to retrieve |
string | $recipient | The intended recipient |
The message contents
Retrieves a message, removes it from the session and prints it - will not print if no content
The message will be printed in a p tag if it does not contain any block level HTML, otherwise it will be printed in a div tag.
boolean show( mixed $name, string $recipient=NULL, string $css_class=NULL )
mixed | $name | The name or array of names of the message(s) to show, or '*' to show all |
string | $recipient | The intended recipient |
string | $css_class | Overrides using the $name as the CSS class when displaying the message - only used if a single $name is specified |
If one or more messages was shown
The fMoney class is a value object for representing a monetary value. There is support for precise calculation, multiple currencies and formatting.
By default the fMoney class comes with a single defined currency USD (United States Dollar). fMoney is built in such a way that multiple currencies can be used and converted between, however there is no built-in functionality to fetch exchange rate information. A few sources of free exchange rate information include:
The rest of the documentation will assume that if multiple currencies are being used that the work has been done to fetch the appropriate exchange rate information in a regular fashion.
Whenever a system must use a currency other than USD, the new currency must be defined by calling defineCurrency()
. This method takes a total of five parameters that define all of the relevant information about a currency. The parameters are $iso_code
, $name
, $symbol
, $precision
and $value
.
Here is an example of defining the currency Pound Sterling:
fMoney::defineCurrency(
'GBP', // The three digit ISO code
'Pound Sterling', // The name of the currency
'', // The currency symbol
'2', // The precision after the decimal point
'1.91865000' // The current exchange rate with USD
);
The $value
of a currency should be defined relative to USD, that is USD has a value of 1.00000000
.
Once a currency has been defined, information about it can be retrieved by using the method getCurrencyInfo()
. There is one required parameter, $iso_code
, which indicates what currency should be returned. A second optional parameter, $element
, allows selecting a single piece of information including:
'name'
'symbol'
'precision'
'value'
It is also possible to get a list of all currencies by calling getCurrencies()
.
While not required, setting a default currency is often useful, especially if only a single currency is supported. By setting the default currency, it is no longer necessary to specify the currency code when creating new fMoney objects.
The method setDefaultCurrency()
accepts a single parameter, the $iso_code
for the desired default currency. Below is an example of setting the default to United States Dollar:
fMoney::setDefaultCurrency('USD');
The default currency can be retrieved by calling getDefaultCurrency()
.
Create an fMoney object requires either one or two parameters depending on whether or not a default currency has been set. If a default has not been set, or a currency other than the default is desired, an $amount
and a $currency
must be specified:
$price = new fMoney('12.25', 'USD');
When setting the amount of an fMoney object, a float value should never be used due to the inherit loss of precision when storing floating point values. Instead, always use an integer, or a floating point value in a string.
If a default currency has been set and the default currency is desired, only a single parameter, $amount
is required:
fMoney::setDefaultCurrency('USD');
$twelve_dollars = new fMoney('12.00');
$five_dollars = new fMoney('5.00');
Note that by default the currency symbol and all commas (,) will be removed from any monetary value before parsing it as a number. For details about how to customize this behaviour, please see the localization section.
There are two accessors for the fMoney class, getAmount()
and getCurrency()
which return the amount of the value and the currency respectively.
echo $five_dollars->getAmount() . "\n";
echo $five_dollars->getCurrency();
will output the following:
5.00
USD
Comparison of fMoney objects is accomplished by the method eq()
, gt()
, gte()
, lt()
and lte()
. Each method will convert any non USD values to USD before comparison to ensure that comparisons are done correctly. Below is a table of the comparison methods:
Method | Comparison |
eq() |
If the two values are equal |
gt() |
If the object being called is greater than the value or object passed |
gte() |
If the object being called is greater than or equal to the value or object passed |
lt() |
If the object being called is less than the value or object passed |
lte() |
If the object being called is less than or equal to the value being passed |
It is possible to pass values other than an fMoney object for comparison. These values will be converted to an fMoney object using the default currency if defined. If no default currency is defined, an exception will be thrown.
Here are a few examples:
if (!$twelve_dollars->eq($five_dollars)) {
echo 'Twelve is not equal to five';
}
// Passing the string '5.00' only works because a default currency has been defined
if ($five_dollars->eq('5.00')) {
echo 'Yes, five dollars is equal to five dollars';
}
if ($five_dollars->lte($twelve_dollars)) {
echo 'Five dollars is less than or equal to twelve';
}
fMoney objects can be added, subtracted, multiplied and allocated (non-lossy division). All math operations are performed using an extra digit of precision and then the results are rounded using the common method. All math operations also take into account different currencies, with the result being in the currency of the object being called.
Please be sure to avoid floating point numbers in PHP when working with monetary values. Their inherent lack of precision make them a poor choice for precise calculations. Instead, use strings containing floating decimal values.
Addition is accomplished using the method add()
. A single parameter, $addend
, is required. The addend may be an fMoney object, or a string or integer if a default currency is defined.
$seventeen_dollars = $five_dollars->add($twelve_dollars);
$six_dollars = $five_dollars->add('1.00');
Subtraction is accomplished by the method sub()
. A single parameter, $subtrahend
, is required. The subtrahend may be an fMoney object, or a string or integer if a default currency is defined.
$seven_dollars = $twelve_dollars->sub($five_dollars);
$four_dollars = $five_dollars->sub('1.00');
To multiply a monetary value, simply pass a string, integer, or fNumber multiplier to mul()
.
$fourty_eight_dollars = $twelve_dollars->mul(4);
$five_dollars_five_cents = $five_dollars->mul('1.1');
$number = new fNumber('+5.5');
$twenty_seven_dollars_fifty_cents = $five_dollars->mul($number);
Instead of providing a division method, which can easily lead to missing pennies, the fMoney class provides the method allocate()
. This method splits up a monetary value into chunks that total the original value.
allocate()
accepts two or more parameters, each being a string or fNumber fraction that represents the portion of the total each result should hold. The result is an array of fMoney objects with as many elements as parameters specified.
list($four_dollars, $one_dollar) = $five_dollars->allocate('0.8', '0.2');
The resulting monetary values will always add up to exactly the original value. This prevents money from being lost in calculations.
// All three thirds will be equal to four dollars
list($first_third, $second_third, $fourth_third) = $twelve_dollars->allocate('0.333', '0.333', '0.334');
If you have defined at least one currency other than USD (such as we did with GBP in the Currencies section) you can convert monetary values between currencies on the fly. The method convert()
requires a single parameter, the ISO code of the currency to convert to.
$usd_price = new fMoney('5.00');
$gbp_price = $usd_price->convert('GBP');
$usd_price = $gbp_price->convert('USD');
Normally when displaying a monetary value it is desired to display the currency symbol and the value in a standard format with separators at the thousands, millions, etc. The method format()
will perform such formatting.
echo $five_dollars->format() . "\n";
$one_thousand_two_hundred_dollars = new fMoney('1200.00');
echo $one_thousand_two_hundred_dollars->format() . "\n";
$five_pounds = new fMoney('5.00', 'GBP');
echo $five_pounds->format();
will output the following:
$5.00
$1,200.00
5.00
If the parameter $remove_zero_fraction
is set to TRUE
and the value has a fraction that is just zeros, the resulting output will not contain a decimal point or a fraction.
// This will print: $5
echo $five_dollars->format(TRUE);
$two_fifty_three = new fMoney('2.53');
// This will print: $2.53
echo $two_fifty_three->format(TRUE);
The method __toString()
will return the value without the currency symbol or the thousands separators.
echo $five_dollars->__toString() . "\n";
echo $one_thousand_two_hundred_dollars->__toString() . "\n";
echo $five_pounds->__toString();
will output the following:
5.00
1200.00
5.00
When formatting monetary values in different locales, it will often be the case that the thousands separator and decimal point are different than the one in the United States. The methods registerFormatCallback()
and registerUnformatCallback()
allow for both creating a different formatting and also removing such formatting when creating a new fMoney object.
// Function to format monetary values for Italian
function italian_money_format(fNumber $value, $currency, $remove_zero_fraction=FALSE)
{
$info = fMoney::getCurrencyInfo($currency);
if ($remove_zero_fraction && $value->eq($value->trunc())) {
$info['precision'] = 0;
}
return $info['symbol'] . number_format($value->__toString(), $info['precision'], ',', '.');
}
// Function to change a monetary value to a plain number
function italian_money_unformat($value, $currency)
{
$symbol = fMoney::getCurrencyInfo($currency, 'symbol');
return str_replace(
array('.', ',', $symbol),
array('', '.', ''),
$value
);
}
fMoney::registerFormatCallback('italian_money_format');
fMoney::registerUnformatCallback('italian_money_unformat');
Represents a monetary value - USD are supported by default and others can be added via defineCurrency()
1.0.0b3 | Added the $remove_zero_fraction parameter to format() 6/9/10 |
---|---|
1.0.0b2 | Fixed a bug with calling format() when a format callback is set, fixed NULL $element handling in getCurrencyInfo() 3/24/09 |
1.0.0b | The initial implementation 8/10/08 |
Allows adding a new currency, or modifying an existing one
void defineCurrency( string $iso_code, string $name, string $symbol, integer $precision, string $value )
string | $iso_code | The ISO code (three letters, e.g. 'USD') for the currency |
string | $name | The name of the currency |
string | $symbol | The symbol for the currency |
integer | $precision | The number of digits after the decimal separator to store |
string | $value | The value of the currency relative to some common standard between all currencies |
Lists all of the defined currencies
array getCurrencies( )
The 3 letter ISO codes for all of the defined currencies
Allows retrieving information about a currency
mixed getCurrencyInfo( string $iso_code, string $element=NULL )
string | $iso_code | The ISO code (three letters, e.g. 'USD') for the currency |
string | $element | The element to retrieve: 'name', 'symbol', 'precision', 'value' |
An associative array of the currency info, or the element specified
Gets the default currency
string getDefaultCurrency( )
The ISO code of the default currency
Allows setting a callback to translate or modify any return values from format()
void registerFormatCallback( callback $callback )
Allows setting a callback to clean any formatted values so they can be passed to fNumber
void registerUnformatCallback( callback $callback )
callback | $callback | The callback to pass formatted strings to. Should accept a formatted string and a currency code and return a string suitable to passing to the fNumber constructor. |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Resets the configuration of the class
void reset( )
Sets the default currency to use when creating fMoney objects
void setDefaultCurrency( string $iso_code )
string | $iso_code | The ISO code (three letters, e.g. 'USD') for the new default currency |
Creates the monetary to represent, with an optional currency
fMoney __construct( fNumber|string $amount, string $currency=NULL )
fNumber|string | $amount | The monetary value to represent, should never be a float since those are imprecise |
string | $currency | The currency ISO code (three letters, e.g. 'USD') for this value |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
All requests that hit this method should be requests for callbacks
callback __get( string $method )
string | $method | The method to create a callback for |
The callback for the method requested
Returns the monetary value without a currency symbol or thousand separator (e.g. 2000.12)
string __toString( )
The monetary value without currency symbol or thousands separator
Adds the passed monetary value to the current one
fMoney add( fMoney|string|integer $addend )
fMoney|string|integer | $addend | The money object to add - a string or integer will be converted to the default currency (if defined) |
The sum of the monetary values in this currency
Splits the current value into multiple parts ensuring that the sum of the results is exactly equal to this amount
This method takes two or more parameters. The parameters should each be fractions that when added together equal 1.
array allocate( fNumber|string $ratio1, fNumber|string $ratio2, fNumber|string ... )
fNumber|string | $ratio1 | The ratio of the first amount to this amount |
fNumber|string | $ratio2 [, ... ] | The ratio of the second amount to this amount |
fMoney objects each with the appropriate ratio of the current amount
Converts this money amount to another currency
fMoney convert( string $new_currency )
string | $new_currency | The ISO code (three letters, e.g. 'USD') for the new currency |
A new fMoney object representing this amount in the new currency
Checks to see if two monetary values are equal
boolean eq( fMoney|string|integer $money )
fMoney|string|integer | $money | The money object to compare to - a string or integer will be converted to the default currency (if defined) |
If the monetary values are equal
Formats the amount by preceeding the amount with the currency symbol and adding thousands separators
string format( boolean $remove_zero_fraction=FALSE )
boolean | $remove_zero_fraction | If TRUE and all digits after the decimal place are 0, the decimal place and all zeros are removed |
The formatted (and possibly converted) value
Returns the fNumber object representing the amount
fNumber getAmount( )
The amount of this monetary value
Returns the currency ISO code
string getCurrency( )
The currency ISO code (three letters, e.g. 'USD')
Checks to see if this value is greater than the one passed
boolean gt( fMoney|string|integer $money )
fMoney|string|integer | $money | The money object to compare to - a string or integer will be converted to the default currency (if defined) |
If this value is greater than the one passed
Checks to see if this value is greater than or equal to the one passed
boolean gte( fMoney|string|integer $money )
fMoney|string|integer | $money | The money object to compare to - a string or integer will be converted to the default currency (if defined) |
If this value is greater than or equal to the one passed
Checks to see if this value is less than the one passed
boolean lt( fMoney|string|integer $money )
fMoney|string|integer | $money | The money object to compare to - a string or integer will be converted to the default currency (if defined) |
If this value is less than the one passed
Checks to see if this value is less than or equal to the one passed
boolean lte( fMoney|string|integer $money )
fMoney|string|integer | $money | The money object to compare to - a string or integer will be converted to the default currency (if defined) |
If this value is less than or equal to the one passed
Mupltiplies this monetary value times the number passed
fMoney mul( fNumber|string|integer $multiplicand )
fNumber|string|integer | $multiplicand | The number of times to multiply this ammount - don't use a float since they are imprecise |
The product of the monetary value and the multiplicand passed
Subtracts the passed monetary value from the current one
fMoney sub( fMoney|string|integer $subtrahend )
fMoney|string|integer | $subtrahend | The money object to subtract - a string or integer will be converted to the default currency (if defined) |
The difference of the monetary values in this currency
fNoRemainingException is a sub-class of fExpectedException that indicates an iterator has reached the end, yet another element has been requested.
This space intentionally left blank
An exception caused when trying to get a value from an iterator and there is nothing left
1.0.0b2 | Fixed a typo in the documentation 7/14/10 |
---|---|
1.0.0b | The initial implementation 6/14/07 |
Exception | --fException | --fExpectedException | --fNoRemainingException
fNoRowsException a sub-class of fExpectedException that indicates a SQL query did not return any rows.
This space intentionally left blank
An exception when no rows are returned from a SQL query
1.0.0b | The initial implementation 6/14/07 |
---|
Exception | --fException | --fExpectedException | --fNoRowsException
fNotFoundException is a sub-class of fExpectedException that indicates an item could not be found, such as when using object relational mapping code. This class may also be suitable to toss in other situations where code is unable to find a specified element or item.
This space intentionally left blank
An exception when an fActiveRecord is not found in the database
1.0.0b | The initial implementation 6/14/07 |
---|
Exception | --fException | --fExpectedException | --fNotFoundException
The fNumber class is a value object to represent large integers and arbitrary precision decimal values. The fNumber class is essential when dealing with calculations that must be precise, such as monetary value (consequently the fMoney class is built on top of fNumber). It supplies the same math functionality that is built into PHP, minus the trigonometric operations.
An fNumber object requires just a single parameter for the constructor, the value to represent. It can be a string, an object with a __toString()
method, or an integer. If the value being represented is too large to store in an integer, the integer value should be enter via a string to prevent data loss. Also, float values should never be used since they have an inherent loss of precision that this class is designed to overcome.
$five = new fNumber(5);
$four_point_two = new fNumber('4.2');
$zero_point_one = new fNumber('0.1');
There is a second optional parameter to the constructor that allows specifying the precision of the number. If no precision is specified, the precision of the value is used.
// Represents 1.000
$one = new fNumber(1, 3);
Basic arithmetic is performed using the method add()
, sub()
, mul()
and div()
. Since the fNumber class is a value object, all operations return a new fNumber object instead of modifying the existing one. Also, all methods accept any valid number representation including strings, fNumber objects and integers.
The returned fNumber object will have the same precision as the object being called unless the optional parameter $scale
is included.
Addition is performed by calling the method add()
and providing an $addend
.
// Addition using different addend number forms
$six = $five->add(1);
$six = $five->add($one);
$eight_point_four = $four_point_two->add('4.2');
// Without scale the 0.1 is lost
$five = $five->add('0.1');
// By specifying a scale of 1 we retain the 0.1
$five_point_one = $five->add('0.1', 1);
Subtraction is performed by calling the method sub()
and providing a $subtrahend
.
// Basic subtraction
$two = $five->sub(3);
// Since $five has a scale of zero, the result is still five
$five = $five->sub('0.3');
// All arithmetic methods support positive and negative numbers
$five_point_three = $five->sub('-0.3', 1);
Multiplication is performed by calling the method mul()
and providing a $multiplicand
.
// Simple multiplication
$ten = $five->mul(2);
// A comparison of multiplication with 0 and 1 scale
$twelve = $five->mul('2.5');
$twelve_point_five = $five->mul('2.5', 1);
Division is performed by calling the method div()
and providing a $divisor
.
// Basic division
$two = $five->div(2);
// Division with a specific scale
$two_point_five = $five->div(2, 1);
The remainder of integer division can be calculated by the method mod()
and the remainder of fractional division can be calculated by the method fmod()
.
The mod()
method will convert the current number to an integer and then divide it by the single parameter, $divisor
, that is passed to the method. The $divisor
is also converted to an integer before the calculation is performed.
// Simple integer division
$one = $three->mod('2');
// The number and divisor are both converted to integers first
$one = $three_point_two->mod('2.1');
The fmod()
method allows dividing a fractional number by another fractional number, returning the remainder. The first parameter is the $divisor
to use, while a second optional parameter, $scale
, allows specifying the scale of the remainder that is being returned.
$zero_point_three = $four_point_seven->fmod('1.1');
$one_point_four_zero = $four_point_four->fmod('1.5', 2);
The fNumber class provides functionality to calculate both square roots and integer powers via the sqrt()
and pow()
methods.
The sqrt()
method accepts a single optional parameter, the $scale
of the result.
$three = $nine->sqrt();
$three_point_eight = $fifteen->sqrt(1);
The pow()
method accepts two parameters, the required $exponent
to raise the number to, and the optional $scale
for the resulting number.
$twenty_five = $five->pow(2);
$nine_point_two_six = $two_point_one->pow(3, 2);
Under certain situations, especially when dealing with cryptography, it is necessary to raise integers to large powers and then calculate the remainder of the division of that product by another integer. Normal calculation by raising the original number to a large power and then dividing to find the remainder often takes far too much computation. There are, however, some mathematical shortcuts to make such calculations significantly faster.
The powmod()
method allows calculating the remainder of the original number raised to an $exponent
and then divided by the $modulus
.
$four_fourty_five = $four->powmod('13', '497');
The fNumber class include comparison methods for testing equality - eq()
, less than - lt()
, less than or equal - lte()
, greater than - gt()
and greater than or equal - gte()
. Each method accepts two parameters, the $number
to compare to and an optional $scale
to use for comparison.
The $number
to compare to may be any valid fNumber object, string or integer. The $scale
parameter sets how many digits after the decimal point to use during comparison. If no scale is specified, the highest scale of the two numbers will be used.
$true = $five->eq(5);
$false = $five->eq('5.1');
$true = $five->eq('5.1', 0);
$true = $five->lt($six);
$true = $five->lte('5.00', 4);
$false = $five->gt($six, 0);
$false = $five->gte('5.1', 1);
There are two options to format fNumber objects, either __toString()
or format()
. The format()
method will include thousands separators in the returned value, while __toString()
will not. The inherent scale of the number will be used when displaying the value. To change the scale, use the trunc()
or round()
method first.
echo $one_thousand_two_hundred->format() . "\n";
echo $one_thousand_two_hundred->__toString() . "\n";
echo $negative_five_point_two->format() . "\n";
echo $negative_five_point_two->__toString() . "\n";
echo $negative_five_point_two->trunc()->__toString();
would output the following
1,200
1200
-5.2
-5.2
-5
If the parameter $remove_zero_fraction
is set to TRUE
and the value has a fraction that is just zeros, the resulting output will not contain a decimal point or a fraction.
// This will print: 5
$five = new fNumber('5.0');
echo $five->format(TRUE);
$two_fifty_three = new fNumber('2.53');
// This will print: 2.53
echo $two_fifty_three->format(TRUE);
The precision of an fNumber can be modified by using the ceil()
, floor()
, round()
and trunc()
methods.
The ceil()
method performs a ceiling operation, rounding up to the next highest integer.
$six = $five_point_two->ceil();
The floor()
method preforms a floor operation, rounding down to the next lowest integer.
$negative_six = $negative_five_point_two->floor();
The trunc()
method changes the scale of the number to 0 without performing any rounding.
$five = $five_point_two->trunc();
The round()
method allows rounding a number to a specified number of decimal places, using the $scale
parameter. It is even possible to round left of the decimal point using negative scales. Rounding is done using the common method, that is when the digit one place beyond the $scale
is 5 or greater the $scale
digit is increased vy 1, otherwise the digit is left the same.
// Rounding positive numbers
$five = $five_point_two->round();
$six = $five_point_five->round();
// Rounding negative numbers
$negative_two = $negative_one_point_six->round();
$negative_one = $negative_one_point_one->round();
// Rounding to a specific scale
$one_point_three = $one_point_three_three->round(1);
$ten = $thirteen->round(-1);
The fNumber class can calculate the absolute value of a number via the abs()
method, the negated value via the neg()
method and can return the sign of a number via the sign()
method.
Here are a few basic example of using the abs()
and neg()
methods:
// Calculating the absolute value
$five = $five->abs();
$five = $negative_five->abs();
// Negating numbers
$negative_five = $five->neg();
$five = $negative_five->neg();
The sign()
method will return -1
if the number is negative, 0
if the number is zero and 1
if the number is positive.
$true = $five->sign() == 1;
$true = $zero->sign() == 0;
$false = $zero->sign() == -1;
$true = $negative_one->sign() == -1;
The value of pi can be obtained up to a scale of 500 by calling the static method pi()
and providing the desired $scale
.
// 3.14
$pi = fNumber::pi(2);
// 3.1415926535897932384626433
$pi = fNumber::pi(25);
For some calculations, representing numbers in a base other than base 10 is necessary. The static method baseConvert()
allow converting integers between any two bases ranging from base 2 (binary) to base 16 (hexadecimal). Three parameters are required, the integer to convert, the base being converted from and the base being converted to.
echo fNumber::baseConvert($five, 10, 2) . "\n";
echo fNumber::baseConvert('10110100110', 2, 16) . "\n";
echo fNumber::baseConvert('10110100110', 2, 8);
would output the following
101
5A6
2646
When formatting numbers in different locales, it will often be the case that the thousands separator and decimal point are different than the one in the United States. The methods registerFormatCallback()
and registerUnformatCallback()
allow for both creating a different formatting and also removing such formatting when creating a new fNumber object.
// Function to format numbers for Italian
function italian_number_format($number, $remove_zero_fraction=FALSE)
{
$parts = explode('.', $number);
$integer = $parts[0];
$fraction = (isset($parts[1])) ? ',' . $parts[1] : '';
if ($remove_zero_fraction && rtrim($fraction, ',0') === '') {
$fraction = '';
}
return number_format($integer, 0, ',', '.') . $fraction;
}
// Function to change a formatted to a plain number
function italian_number_unformat($value)
{
return str_replace(array('.', ','), array('', '.'), $value);
}
fNumber::registerFormatCallback('italian_number_format');
fNumber::registerUnformatCallback('italian_number_unformat');
Provides large/precise number support
1.0.0b3 | Added the $remove_zero_fraction parameter to format() 2/2/11 |
---|---|
1.0.0b2 | Fixed a bug with parsing decimal numbers in scientific notation 4/13/10 |
1.0.0b | The initial implementation 7/21/08 |
Converts any positive integer between any two bases ranging from 2 to 16
string baseConvert( fNumber|string $number, integer $from_base, integer $to_base )
fNumber|string | $number | The positive integer to convert |
integer | $from_base | The base to convert from - must be between 2 and 16 |
integer | $to_base | The base to convert to - must be between 2 and 16 |
The number converted to the new base
Returns the value for pi with a scale of up to 500
fNumber pi( integer $scale )
integer | $scale | The number of places after the decimal to return |
Pi
Allows setting a callback to translate or modify any return values from format()
The callback should accept two parameters:
The callback should return a string, the formatted $value.
void registerFormatCallback( callback $callback )
callback | $callback | The callback to pass the fNumber value to - see method description for parameters |
Allows setting a callback to clean any formatted values so they can be properly parsed - useful for languages where , is used as the decimal point
void registerUnformatCallback( callback $callback )
callback | $callback | The callback to pass formatted strings to. Should accept a formatted string and return a string the is a valid number using . as the decimal point. |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Resets the configuration of the class
void reset( )
Creates a large/precise number
fNumber __construct( string $value, integer $scale=NULL )
string | $value | The value for the number - any valid PHP integer or float format including values with e exponents |
integer | $scale | The number of digits after the decimal place, defaults to number of digits in $value |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
All requests that hit this method should be requests for callbacks
callback __get( string $method )
string | $method | The method to create a callback for |
The callback for the method requested
Converts the object to an string
string __toString( )
Returns the absolute value of this number
fNumber abs( integer $scale=NULL )
integer | $scale | The number of places after the decimal - overrides the scale for this number |
The absolute number
Adds two numbers together
fNumber add( fNumber|string $addend, integer $scale=NULL )
fNumber|string | $addend | The addend |
integer | $scale | The number of places after the decimal - overrides the scale for this number |
The sum
Rounds the number to the next highest integer
fNumber ceil( )
The next highest integer
Divides this number by the one passed
fNumber div( fNumber|string $divisor, integer $scale=NULL )
fNumber|string | $divisor | The divisor |
integer | $scale | The number of places after the decimal - overrides the scale for this number |
The quotient
Indicates if this value is equal to the one passed
boolean eq( fNumber|string $number, integer $scale=NULL )
fNumber|string | $number | The number to compare to |
integer | $scale | The number of decimal places to compare - will use all available if not specified |
If this number is equal to the one passed
Rounds the number to the next lowest integer
fNumber floor( )
The next lowest integer
Returns the float remainder of dividing this number by the divisor provided
fNumber fmod( fNumber|string $divisor, integer $scale=NULL )
fNumber|string | $divisor | The divisor |
integer | $scale | The number of places after the decimal - overrides the scale for this number |
The remainder
Formats the number to include thousands separators
string format( boolean $remove_zero_fraction=FALSE )
boolean | $remove_zero_fraction | If TRUE and all digits after the decimal place are 0, the decimal place and all zeros are removed |
The formatted value
Returns the scale of this number
integer getScale( )
The scale of this number
Indicates if this value is greater than the one passed
boolean gt( fNumber|string $number, integer $scale=NULL )
fNumber|string | $number | The number to compare to |
integer | $scale | The number of decimal places to compare - will use all available if not specified |
If this number is less than the one passed
Indicates if this value is greater than or equal to the one passed
boolean gte( fNumber|string $number, integer $scale=NULL )
fNumber|string | $number | The number to compare to |
integer | $scale | The number of decimal places to compare - will use all available if not specified |
If this number is greater than or equal to the one passed
Indicates if this value is less than the one passed
boolean lt( fNumber|string $number, integer $scale=NULL )
fNumber|string | $number | The number to compare to |
integer | $scale | The number of decimal places to compare - will use all available if not specified |
If this number is less than the one passed
Indicates if this value is less than or equal to the one passed
boolean lte( fNumber|string $number, integer $scale=NULL )
fNumber|string | $number | The number to compare to |
integer | $scale | The number of decimal places to compare - will use all available if not specified |
If this number is less than or equal to the one passed
Returns the remainder of dividing this number by the divisor provided. All floats are converted to integers.
fNumber mod( fNumber|string $divisor )
fNumber|string | $divisor | The divisor - will be converted to an integer if it is a float |
The remainder
Multiplies two numbers
fNumber mul( fNumber|string $multiplier, integer $scale=NULL )
fNumber|string | $multiplier | The multiplier |
integer | $scale | The number of places after the decimal - overrides the scale for this number |
The product
Negates this number
fNumber neg( integer $scale=NULL )
integer | $scale | The number of places after the decimal - overrides the scale for this number |
The negated number
Raise this number to the power specified
fNumber pow( integer $exponent, integer $scale=NULL )
integer | $exponent | The power to raise to - all non integer values will be truncated to integers |
integer | $scale | The number of places after the decimal - overrides the scale for this number |
The product
Gets the remainder of this integer number raised to the integer $exponent, divided by the integer $modulus
This method is faster than doing $num->pow($exponent)->mod($modulus) and is primarily useful for cryptographic functionality.
fNumber powmod( fNumber|string $exponent, fNumber|string $modulus )
fNumber|string | $exponent | The power to raise to - all non integer values will be truncated to integers |
fNumber|string | $modulus | The value to divide by - all non integer values will be truncated to integers |
The remainder
Rounds this number to the specified number of digits after the decimal - negative scales round the number by places to the left of the decimal
fNumber round( integer $scale )
integer | $scale | The number of places after (or before if negative) the decimal to round to |
The rounded result
Returns the sign of the number
integer sign( )
-1 if negative, 0 if zero, 1 if positive
Returns the square root of this number
fNumber sqrt( integer $scale=NULL )
integer | $scale | The number of places after the decimal - overrides the scale for this number |
The square root
Subtracts two numbers
fNumber sub( fNumber|string $subtrahend, integer $scale=NULL )
fNumber|string | $subtrahend | The subtrahend |
integer | $scale | The number of places after the decimal - overrides the scale for this number |
The difference
Scales (truncates or expands) the number to the specified number of digits after the decimal - negative scales round the number by places to the left of the decimal
fNumber trunc( integer $scale )
integer | $scale | The number of places after (or before if negative) the decimal |
The square root
The fORM class is a static class that implements core ORM functionality for much of the Flourish ORM. It provides means to configure and extend fActiveRecord classes.
By default, when mapping fActiveRecord classes to database tables, an UpperCamelCase
singular class name will be mapped the the underscore_notation
plural database table of the same noun. For example, the User
class is mapped to the users
table. The static method mapClassToTable()
allows overriding this default by passing the $class
and $table
in.
The following example would set the User
class to map to the user
table instead of users
. This code should be executed during site-wide configuration and should not be placed inside of the configure()
method for a class that extends fActiveRecord.
// Class to table mapping should occur before any classes are used
// such as when the database is attached via fORMDatabase::attach()
fORMDatabase::attach($db);
fORM::mapClassToTable('User', 'user');
When writing custom ORM code, the class that is associated with a table can be determined by calling the static method classize()
with the parameter $table
.
$class = fORM::classize($table);
return new $class();
To translate from the class to the database table simply pass the class to the static method tablize()
. The class can be either a class name or an instance of the class.
$object = new User();
$table = fORM::tablize($object);
When a class models a table in a non-default schema (public
for PostgreSQL, dbo
for MSSQL and the username for Oracle and DB2), the static method mapClassToTable()
should be called with first parameter, the $class
to map and the second parameter, $table
, should be in the format schema.table
.
This code should be executed during site-wide configuration and should not be placed inside of the configure()
method for a class that extends fActiveRecord.
// This maps the User class to the users table in the authorization schema
fORM::mapClassToTable('User', 'authorization.users');
When multiple databases are configured via fORMDatabase, classes can model tables on the non-default
database by calling the method mapClassToDatabase()
. The first parameter is the $class
to map, and the second is the $name
of the database set in fORMDatabase::attach().
// Attach a second database as "commerce_db" and have the User class model the users table in it
fORMDatabase::attach($db, 'commerce_db');
fORM::mapClassToDatabase('User', 'commerce_db');
Like mapClassToTable()
, this code should be executed during site-wide configuration and should not be placed inside of the configure()
method for a class that extends fActiveRecord. This method is not required for classes modeling tables in the default database if no $name
was provided to fORMDatabase::attach(), then the database is the default.
Whenever class and column names are used in messaging, such as in fValidationException, the class or column name is run through fGrammar::humanize() to create a human-friendly version. Obviously in some situations this technique will not get capitalization or punctuation correct. The static methods overrideRecordName()
and overrideColumnName()
allow setting custom names for classes and columns respectively. It is also possible to set a class name in the context of being a related classsee the fORMRelated class documentation for more details.
The example below shows changing the column email
to display as E-Mail
instead of Email
and the FaqEntry
class to display as FAQ Entry
instead of Faq Entry
.
class User extends fActiveRecord
{
protected function configure()
{
fORM::overrideColumnName($this, 'email', 'E-Mail');
}
}
class FaqEntry extends fActiveRecord
{
protected function configure()
{
fORM::overrideRecordName($this, 'FAQ Entry');
}
}
If you are having issues with your column names not being properly converted from CamelCase (for methods) to underscore_notation (for your database and HTML), please see the Fixing Notation Conversion Issues section on the fGrammar page.
Since the schema information is dynamically pulled out of the database, this can add at least a few database calls to each request that is processed. If the database schema is not changing on a regular basis and better performance is required, the schema can be cached by calling the static method enableSchemaCaching()
.
enableSchemaCaching()
accepts a single parameter, an fCache object to cache the schema information to. This enables caching on the fDatabase, fSQLTranslation and fSchema objects that are used for the ORM.
An additional feature is that the cached schema information will be cleared if an fUnexpectedException is thrown. This would normally happen if a programmer tried to perform an action that was invalid based on the cached schema.
fORM::enableSchemaCaching(new fCache('file', '/file/path/to/cache/file'));
The Flourish ORM is built in such a way that it can be easily extended without having to actually extends individual classes. Both the fActiveRecord and fRecordSet classes allow registering callbacks to handle methods that dont exist (and thus fall through to the __call
magic method) while in addition, the fActiveRecord class includes a number of predefined "hooks" that allow for injecting functionality using callbacks. There is further functionality that allows defining callbacks to handle the tasks of translating objects to scalar values, scalar values to objects and method reflection.
A large part of the ORM classes built into Flourish use these features to implement their functionality:
The two static methods registerActiveRecordMethod()
and registerRecordSetMethod()
allow for setting callbacks to handle method calls for methods that dont exist in the fActiveRecord and fRecordSet classes respectively. The static method registerHookCallback()
allows setting a hook to be executed at one of the pre-defined hooks in fActiveRecord.
Once a callback has been registered to handle a method call or hook, it will be automatically called at the appropriate time and will be passed the pre-defined parameters listed below. The actual work of calling the callback and passing the parameters is handled by the fActiveRecord and fRecordSet classes so all that the end-developer needs to worry about is the callback parameter signature and the functionality in the callback.
If you wish to add a method to a single fActiveRecord class, simply create the method inside of that class. The following functionality is for the purpose of dynamically adding methods to fActiveRecord at run time. This technique is used to create ORM plugins, such as fORMFile, fORMOrdering, etc.
registerActiveRecordMethod()
accepts the $class
and $method
to register for and the $callback
to register. The $class
can also be '*'
to register the callback for all fActiveRecord classes. The $callback
should be a callback for a method or function that accepts the following parameters:
$object
: The fActiveRecord instance&$values
: The values array for the record&$old_values
: The old values array for the record&$related_records
: The related records array for the record&$cache
: The cache array for the record$method_name
: The method that was called$parameters
: The parameters passed to the method
The following example registers the method toXML()
:
class User extends fActiveRecord
{
protected function configure()
{
fORM::registerActiveRecordMethod($this, 'toXML', 'User::convertToXML');
}
public function convertToXML($object, &$value, &$old_values, &$related_records, &$cache, $method_name, $parameters)
{
//
}
}
$user = new User();
echo $user->toXML();
registerRecordSetMethod()
accepts the $method
to register for and the $callback
to register. The $callback
should be a callback for a method or function that accepts the following parameters:
$object
: The actual record set$class
: The class of each record&$records
: The ordered array of fActiveRecord objects$method_name
: The name of the method that was called$parameters
: The parameters passed to the method
The following example adds a method named toXML()
to all fRecordSet objects:
class ORMXML
{
public function extend()
{
fORM::registerRecordSetMethod('toXML', 'ORMXML::convertToXML');
}
public function convertToXML($object, $class, &$records, $method_name, $parameters)
{
//
}
}
ORMXML::extend();
$users = fRecordSet::build('User');
echo $users->toXML();
Rather than requiring all additional functionality for fActiveRecord classes to be defined in each class or requiring that methods be overridden in order to add functionality, the static method registerHookCallback()
allows callbacks to be registered that will be executed a predefined places. These hooks make it possible to write plugins for the ORM that can be easily reused.
registerHookCallback()
accepts three parameters, the $class
and $hook
to register for and the $callback
to register. The $class
can be either a class name or '*'
to register for all fActiveRecord classes. The $hook
should be one of the hooks listed below:
Hook | Location |
'post::__construct()' |
At the very end of __construct() |
'pre::delete()' |
At the very beginning delete() |
'post-begin::delete()' |
After the database and filesystem transactions have been started |
'pre-commit::delete()' |
Just before the database and filesystem transactions are committed |
'post-commit::delete()' |
After the database and filesystem transactions have been committed |
'post-rollback::delete()' |
When an error occurs, right after the database and filesystem transactions are rolled back |
'post::delete()' |
At the very end of delete() |
'post::loadFromIdentityMap()' |
Right after a record is attached to the identity map, is not triggered if loaded from a result |
'post::loadFromResult()' |
Right after a record is loaded from the database, is not triggered if loaded from the identity map |
'pre::populate()' |
At the very beginning of populate() |
'post::populate()' |
At the very end of populate() |
'pre::replicate()' |
At the very beginning of replicate() /clone , on the original record |
'post::replicate()' |
At the very end of replicate() /clone , on the original record |
'cloned::replicate()' |
At the very end of replicate() , on the newly cloned record |
'pre::store()' |
At the very beginning of store() |
'post-begin::store()' |
After the database and filesystem transactions have been started |
'post-validate::store()' |
After validation successfully completes |
'pre-commit::store()' |
Just before the database and filesystem transactions are committed |
'post-commit::store()' |
After the database and filesystem transactions have been committed |
'post-rollback::store()' |
When an error occurs, right after the database and filesystem transactions are rolled back |
'post::store()' |
At the end of store() , just before the existence is changed, thus $record->exists() will still return FALSE for a new record |
'pre::validate()' |
Before any of the built-in validation is done, the $validation_messages array will be empty |
'post::validate()' |
After all of the built-in validation is done, the $validation_messages array will contain all of the messages, however the messages ordering is done after this hook |
The $callback
specified should have the following signature:
$object
: The fActiveRecord instance&$values
: The values array for the record - see $values for details&$old_values
: The old values array for the record - see $old_values for details&$related_records
: The related records array for the record - see $related_records for details&$cache
: The cache array for the record - see $cache for details
The two hooks, 'pre::validate()'
and 'post::validate()'
accept one extra parameter:
&$validation_messages
: An associative array of the error messages, with the keys being column or table names - see $validation_messages for details
The three hooks, 'pre::replicate()'
, 'post::replicate()'
and 'cloned::replicate()'
accept one extra parameter:
$replication_level
: An integer representing the level of recursion - the object being replicated will be 0
, children will be 1
, grandchildren 2
and so on
Below is an example of extending a User
class to confirm that the password confirmation is identical to the password when using populate:
class User extends fActiveRecord
{
protected function configure()
{
fORM::registerHookCallback($this, 'post::validate()', 'User::validatePassword');
}
static public function validatePassword($object, &$values, &$old_values, &$related_records, &$cache, &$validation_messages)
{
// If a new password was set, it came through the request and does not match the field password confirmation, add an error message
if (fActiveRecord::hasOld($old_values, 'password') && fRequest::get('password') && fRequest::get('password') != fRequest::get('password_confirmation')) {
$validation_messages['password'] = 'Password: The value entered does not match Password Confirmation';
}
}
}
When writing callbacks for adding methods or functionality to fActiveRecord, most often there will be a need to work with the $values
, $old_values
, $related_records
and $cache
arrays.
Each of these arrays is implemented in such a way that all instances of an fActiveRecord class that represent the same record will share the arrays. If a change is made to the values for one instance of User
with the ID 1
, all other instance of User
1
will also see those changes.
It is also important to note that all callbacks registered for fActiveRecord method calls and hooks should accept these arrays by reference, otherwise any changes to the arrays will be lost.
The $values
array is an associative array of the current values for a record. Each column in the database is a key in the array and points to the current value for that column. Below is an example of what the $values
array would look like for a simple User
record with a hashed password:
Array
(
[user_id] => 1
[first_name] => Will
[last_name] => Bond
[email] => will@flourishlib.com
[password] => fCryptography::password_hash#Gu19bpZN94#ac74c4ad9ed7103e051e583af86599b95237e9af
)
The best practice for assigning new values to the $values
array is to use the static method assign()
since it will automatically move the old value into the $old_values
array.
The $old_values
array is an associative array of every previous value contained by each of the columns in the record since it was last loaded from the database. The original value will be at key 0
, and further revisions will be appended to the array.
The keys in the array are the database column names, however a column will only be present as a key if a value in the record has changed. The value associated with each key is an array of all of the old values. Below is an example of the $old_values
array for a User
object that has had the first name change twice and the email changed once.
Array
(
[first_name] => Array
(
[0] => William
[1] => will
)
[email] => Array
(
[0] => will@flourishlib.com
)
)
Records that are new and have not been stored in the database will have all values set to NULL
, thus the $old_values
array for a new User
record that has had each field set once will look like the following:
Array
(
[user_id] => Array
(
[0] => {null}
)
[first_name] => Array
(
[0] => {null}
)
[last_name] => Array
(
[0] => {null}
)
[email] => Array
(
[0] => {null}
)
[password] => Array
(
[0] => {null}
)
)
There are a few fActiveRecord static methods that make working with the $old_values
array a little easier. changed()
will return a boolean indicating if the value of a column has actually changedFALSE
will be returned if there is an old value and the old and current values match. hasOld()
returns a boolean indicating if there is an old value for a column and will return TRUE
even if the old and current values are the same. retrieveOld()
will return either the oldest value for a column, or an array of all old values depending on what parameters are passed.
The $related_records
associative array contains a cache of all related records that have been pulled out of the database. This array helps prevent lots of duplicate database queries from being executed.
The structure of the array is as follows:
Array
(
[$related_table] => Array
(
[$route] => Array
(
[record_set] => fRecordSet,
[primary_keys] => array(),
[associate] => boolean,
[count] => integer
)
)
)
The array is only populated as the related records are requested. The $related_table
is the database table corresponding to the related record class. The $route
is name of the relationship route between the table for the current class and the $related_table
.
The 'record_set'
key (which will not be present if the record has only been counted, or if only the primary keys have been accessed) will point to an fRecordSet object. The 'primary_keys'
key will point to an array of the primary keys for the related records, but will only be present if a link
method has been called. The 'count'
key (which will always be present) will point to an integer containing the number of related records. The 'associate'
key points to a boolean indicating if the related records should be stored when the parent records store()
method is executed.
In general, the $related_records
array should not be manipulated directly, and may cause custom code to be more fragile in the face of future Flourish internal code updates. Instead, try to use the various static methods on the fORMRelated class. For normal end-developer use, almost all of the fORMRelated functionality is exposed through the fActiveRecord related records operations.
The $cache
array is an array implemented for use by end-developers or ORM plugins. The structure is completely up to the discretion of the programmer. This array can be useful for temporarily storing data, such as an unhashed password for the purposes of mailing to user, or for caching an expensive calculation.
The $validation_messages
array keys are generated via the following rules. Whenever the array is modified, special care should be taken to add new entries properly. The fActiveRecord::validate() documentation has examples of each type of entry in the array.
,
::
followed by the column name (or column names joined by ,
)[
followed by the zero-based record number, followed by ]
. The value of this key will be an associative array containing two keys, name
and errors
. The name
key will have a user-friendly name for the related record and the errors
key will contain an array of error messages for the related record.
While not a feature that should normally be used in a production environment, the static method defineActiveRecordClass()
will automatically create an fActiveRecord class for a class that properly maps to a database table. By placing this method call in an __autoload
function, it is possible to start working with the ORM without having to create a class for each database table.
function __autoload($class)
{
$file = '/path/to/class/files/' . $class . '.php';
if (file_exists($file)) {
include($file);
return;
}
try {
fORM::defineActiveRecordClass($class);
} catch (fProgrammerException $e) {
fCore::toss('fProgrammerException', sprintf('The class %s could not be found', $class));
}
}
Dynamically handles many centralized object-relational mapping tasks
1.0.0b28 | Updated getColumnName() and getRecordName() to use fText if loaded 2/2/11 |
---|---|
1.0.0b27 | Added links to the detailed documentation for the parameters passed to hooks 11/27/10 |
1.0.0b26 | Added getRelatedClass() for handling related classes in PHP 5.3 namespaces 11/17/10 |
1.0.0b25 | Added support for PHP 5.3 namespaced fActiveRecord classes 11/11/10 |
1.0.0b24 | Backwards Compatibility Break - Callbacks registered via registerRecordSetMethod() should now accept the $method_name in the position where the $pointer parameter used to be passed 9/28/10 |
1.0.0b23 | Added the 'pre::replicate()', 'post::replicate()' and 'cloned::replicate()' hooks 9/7/10 |
1.0.0b22 | Internal Backwards Compatibility Break - changed parseMethod() to not underscorize the subject of the method 8/6/10 |
1.0.0b21 | Fixed some documentation to reflect the API changes from v1.0.0b9 7/14/10 |
1.0.0b20 | Added the ability to register a wildcard active record method for all classes 4/22/10 |
1.0.0b19 | Added the method isClassMappedToTable() 3/30/10 |
1.0.0b18 | Added the post::loadFromIdentityMap() hook 3/14/10 |
1.0.0b17 | Changed enableSchemaCaching() to rely on fDatabaseclearCache() instead of explicitly calling fSQLTranslation::clearCache() 3/9/10 |
1.0.0b16 | Backwards Compatibility Break - renamed addCustomClassToTableMapping() to mapClassToTable(). Added getDatabaseName() and mapClassToDatabase(). Updated code for new fORMDatabase and fORMSchema APIs 10/28/09 |
1.0.0b15 | Added support for fActiveRecord to getRecordName() 10/6/09 |
1.0.0b14 | Updated documentation for registerActiveRecordMethod() to include info about prefix method matches 8/7/09 |
1.0.0b13 | Updated documentation for registerRecordSetMethod() 7/14/09 |
1.0.0b12 | Updated callReflectCallbacks() to accept a class name instead of an object 7/13/09 |
1.0.0b11 | Added registerInspectCallback() and callInspectCallbacks() 7/13/09 |
1.0.0b10 | Fixed a bug with objectify() caching during NULL date/time/timestamp values and breaking further objectification 6/18/09 |
1.0.0b9 | Added caching for performance and changed some method APIs to only allow class names instead of instances 6/15/09 |
1.0.0b8 | Updated documentation to reflect removal of $associate parameter for callbacks passed to registerRecordSetMethod() 6/2/09 |
1.0.0b7 | Added enableSchemaCaching() to replace fORMSchema::enableSmartCaching() 5/4/09 |
1.0.0b6 | Added the ability to pass a class instance to addCustomClassTableMapping() 2/23/09 |
1.0.0b5 | Backwards compatibility break - renamed addCustomTableClassMapping() to addCustomClassTableMapping() and swapped the parameters 1/26/09 |
1.0.0b4 | Fixed a bug with retrieving fActiveRecord methods registered for all classes 1/14/09 |
1.0.0b3 | Fixed a static method callback constant 12/17/08 |
1.0.0b2 | Added replicate() and registerReplicateCallback() for fActiveRecord::replicate() 12/12/08 |
1.0.0b | The initial implementation 8/4/07 |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Calls the hook callbacks for the class and hook specified
void callHookCallbacks( fActiveRecord $object, string $hook, array &$values, array &$old_values, array &$related_records, array &$cache, mixed &$parameter=NULL )
fActiveRecord | $object | The instance of the class to call the hook for |
string | $hook | The hook to call |
array | &$values | The current values of the record |
array | &$old_values | The old values of the record |
array | &$related_records | Records related to the current record |
array | &$cache | The cache array of the record |
mixed | &$parameter | The parameter to send the callback |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Calls all inspect callbacks for the class and column specified
void callInspectCallbacks( string $class, string $column, array &$metadata )
string | $class | The class to inspect the column of |
string | $column | The column to inspect |
array | &$metadata | The associative array of data about the column |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Calls all reflect callbacks for the class passed
void callReflectCallbacks( string $class, array &$signatures, boolean $include_doc_comments )
string | $class | The class to call the callbacks for |
array | &$signatures | The associative array of {method_name} => {signature} |
boolean | $include_doc_comments | If the doc comments should be included in the signature |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Checks to see if any (or a specific) callback has been registered for a specific hook
boolean checkHookCallback( string $class, string $hook, array $callback=NULL )
string | $class | The name of the class |
string | $hook | The hook to check |
array | $callback | The specific callback to check for |
If the specified callback exists
Takes a table and turns it into a class name - uses custom mapping if set
string classize( string $table )
string | $table | The table name |
The class name
Will dynamically create an fActiveRecord-based class for a database table
Normally this would be called from an __autoload() function.
This method will only create classes for tables in the default ORM database.
void defineActiveRecordClass( string $class )
string | $class | The name of the class to create |
Enables caching on the fDatabase, fSQLTranslation and fSchema objects used for the ORM
This method will cache database schema information to the three objects that use it during normal ORM operation: fDatabase, fSQLTranslation and fSchema. To allow for schema changes without having to manually clear the cache, all cached information will be cleared if any fUnexpectedException objects are thrown.
This method should be called right after fORMDatabase::attach().
void enableSchemaCaching( fCache $cache, string $database_name='default', string $key_token=NULL )
fCache | $cache | The object to cache schema information to |
string | $database_name | The database to enable caching for |
string | $key_token | This is a token that is used in cache keys to prevent conflicts for server-wide caches - when non-NULL the document root is used |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Returns a matching callback for the class and method specified
The callback returned will be determined by the following logic:
1. If an exact callback has been defined for the method, it will be returned 2. If a callback in the form {prefix}* has been defined that matches the method, it will be returned 3. NULL will be returned
string|null getActiveRecordMethod( string $class, string $method )
string | $class | The name of the class |
string | $method | The method to get the callback for |
The callback for the method or NULL if none exists - see method description for details
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Takes a class name or class and returns the class name
string getClass( mixed $class )
mixed | $class | The object to get the name of, or possibly a string already containing the class |
The class name
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Returns the column name
The default column name is the result of calling fGrammar::humanize() on the column.
string getColumnName( string $class, string $column )
string | $class | The class name the column is part of |
string | $column | The database column |
The column name for the column specified
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Returns the name for the database used by the class specified
string getDatabaseName( string $class )
string | $class | The class name to get the database name for |
The name of the database to use
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Returns the record name for a class
The default record name is the result of calling fGrammar::humanize() on the class.
string getRecordName( string $class )
string | $class | The class name to get the record name of |
The record name for the class specified
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Returns a matching callback for the method specified
The callback returned will be determined by the following logic:
1. If an exact callback has been defined for the method, it will be returned 2. If a callback in the form {action}* has been defined that matches the method, it will be returned 3. NULL will be returned
string|null getRecordSetMethod( string $method )
string | $method | The method to get the callback for |
The callback for the method or NULL if none exists - see method description for details
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Takes a class name and related class name and ensures the related class has the appropriate namespace prefix
string getRelatedClass( string $class, string $related_class )
string | $class | The primary class |
string | $related_class | The related class name |
The related class name, with the appropriate namespace prefix
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Checks if a class has been mapped to a table
boolean isClassMappedToTable( mixed $class )
mixed | $class | The name of the class |
If the class has been mapped to a table
Sets a class to use a database other than the "default"
Multiple database objects can be attached for the ORM by passing a unique $name to the attach() method.
void mapClassToDatabase( mixed $class, string $database_name )
mixed | $class | The name of the class, or an instance of it |
string | $database_name | The name given to the database when passed to attach() |
Allows non-standard class to table mapping
By default, all database tables are assumed to be plural nouns in underscore_notation and all class names are assumed to be singular nouns in UpperCamelCase. This method allows arbitrary class to table mapping.
void mapClassToTable( mixed $class, string $table )
mixed | $class | The name of the class, or an instance of it |
string | $table | The name of the database table |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Takes a scalar value and turns it into an object if applicable
mixed objectify( string $class, string $column, mixed $value )
string | $class | The class name of the class the column is part of |
string | $column | The database column |
mixed | $value | The value to possibly objectify |
The scalar or object version of the value, depending on the column type and column options
Allows overriding of default column names
By default a column name is the result of fGrammar::humanize() called on the column.
void overrideColumnName( mixed $class, string $column, string $column_name )
mixed | $class | The class name or instance of the class the column is located in |
string | $column | The database column |
string | $column_name | The name for the column |
Allows overriding of default record names
By default a record name is the result of fGrammar::humanize() called on the class.
void overrideRecordName( mixed $class, string $record_name )
mixed | $class | The class name or instance of the class to override the name of |
string | $record_name | The human version of the record |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Parses a camelCase method name for an action and subject in the form actionSubject()
array parseMethod( string $method )
string | $method | The method name to parse |
An array of 0 => {action}, 1 => {subject}
Registers a callback for an fActiveRecord method that falls through to fActiveRecord::__call() or hits a predefined method hook
The callback should accept the following parameters:
void registerActiveRecordMethod( mixed $class, string $method, callback $callback )
mixed | $class | The class name or instance of the class to register for, '*' will register for all classes |
string | $method | The method to hook for - this can be a complete method name or {prefix}* where * will match any column name |
callback | $callback | The callback to execute - see method description for parameter list |
Registers a callback for one of the various fActiveRecord hooks - multiple callbacks can be registered for each hook
The method signature should include the follow parameters:
The 'pre::validate()' and 'post::validate()' hooks have an extra parameter:
The 'pre::replicate()', 'post::replicate()' and 'cloned::replicate()' hooks have an extra parameter:
Below is a list of all valid hooks:
void registerHookCallback( mixed $class, string $hook, callback $callback )
mixed | $class | The class name or instance of the class to hook, '*' will hook all classes |
string | $hook | The hook to register for |
callback | $callback | The callback to register - see the method description for details about the method signature |
Registers a callback to modify the results of fActiveRecord::inspect() methods
void registerInspectCallback( mixed $class, string $column, callback $callback )
mixed | $class | The class name or instance of the class to register for |
string | $column | The column to register for |
callback | $callback | The callback to register. Callback should accept a single parameter by reference, an associative array of the various metadata about a column. |
Registers a callback for when objectify() is called on a specific column
void registerObjectifyCallback( mixed $class, string $column, callback $callback )
mixed | $class | The class name or instance of the class to register for |
string | $column | The column to register for |
callback | $callback | The callback to register. Callback should accept a single parameter, the value to objectify and should return the objectified value. |
Registers a callback for an fRecordSet method that fall through to fRecordSet::__call()
The callback should accept the following parameters:
void registerRecordSetMethod( string $method, callback $callback )
string | $method | The method to hook for |
callback | $callback | The callback to execute - see method description for parameter list |
Registers a callback to modify the results of fActiveRecord::reflect()
Callbacks registered here can override default method signatures and add method signatures, however any methods that are defined in the actual class will override these signatures.
The callback should accept three parameters:
void registerReflectCallback( mixed $class, callback $callback )
mixed | $class | The class name or instance of the class to register for, '*' will register for all classes |
callback | $callback | The callback to register. Callback should accept a three parameters - see method description for details. |
Registers a callback for when a value is replicated for a specific column
void registerReplicateCallback( mixed $class, string $column, callback $callback )
mixed | $class | The class name or instance of the class to register for |
string | $column | The column to register for |
callback | $callback | The callback to register. Callback should accept a single parameter, the value to replicate and should return the replicated value. |
Registers a callback for when scalarize() is called on a specific column
void registerScalarizeCallback( mixed $class, string $column, callback $callback )
mixed | $class | The class name or instance of the class to register for |
string | $column | The column to register for |
callback | $callback | The callback to register. Callback should accept a single parameter, the value to scalarize and should return the scalarized value. |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Takes and value and returns a copy is scalar or a clone if an object
The registerReplicateCallback() allows for custom replication code
mixed replicate( string $class, string $column, mixed $value )
string | $class | The class the column is part of |
string | $column | The database column |
mixed | $value | The value to copy/clone |
The copied/cloned value
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Resets the configuration of the class
void reset( )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
If the value passed is an object, calls __toString() on it
mixed scalarize( mixed $class, string $column, mixed $value )
mixed | $class | The class name or instance of the class the column is part of |
string | $column | The database column |
mixed | $value | The value to get the scalar value of |
The scalar value of the value
Takes a class name (or class) and turns it into a table name - Uses custom mapping if set
string tablize( string $class )
string | $class | The class name |
The table name
The fORMColumn class is an ORM plugin to provide miscellaneous additional functionality to individual columns.
The static method configureEmailColumn()
sets a column to be validated as an email address. The first parameter is the $class
the column is located in and the second parameter is the $column
. As with most active record plugins, this method should normally be called from the configure()
method.
class User extends fActiveRecord
{
protected function configure()
{
fORMColumn::configureEmailColumn($this, 'email');
}
}
Columns can be configured as a link column by passing the $class
and $column
to the static method fORMColumn::configureLinkColumn(). When the record is validated, the link will be checked to make sure it contains a valid domain name, however http://
is not required. When calling the prepare
method for the column, it will ensure that the link begings with http://
for output into a HTML <a>
tag.
class NewsArticle extends fActiveRecord
{
protected function configure()
{
fORMColumn::configureLinkColumn($this, 'link');
}
}
$news_article = new NewsArticle();
$news_article->setLink('www.example.com');
echo $news_article->getLink();
echo $news_article->prepareLink();
The above PHP would output the following:
www.example.com
http://www.example.com
While fActiveRecord automatically handles both integer and floating point numbers, sometimes it is necessary to have a columns value be represented as an fNumber object. The static method configureNumberColumn()
ensures that values loaded out of the database are automatically converted to an fNumber object. The first parameter is the $class
and the second parameter is the $column
.
class Product extends fActiveRecord
{
protected function configure()
{
fORMColumn::configureNumberColumn($this, 'weight');
}
}
In some situations a random string is useful for providing security when resetting passwords, validating email accounts or establishing access authentication. The static method configureRandomColumn()
will cause a column to be set to a random string when the record is first saved and will allow generating a new random string at any time by calling generateColumnName()
.
configureRandomColumn()
accepts four parameters, the $class
to be configured, the $column
, the $type
of random string (see fCryptography::randomString() for options) and the $length
of the string.
class User extends fActiveRecord
{
protected function configure()
{
fORMColumn::configureRandomColumn($this, 'access_code', 'alphanumeric', 16);
}
}
// Create a new user and get their access code
$user = new User();
$user->store();
$access_code = $user->getAccessCode();
// Generate a new random string for the access code
$user->generateAccessCode();
$access_code = $user->getAccessCode();
Provides special column functionality for fActiveRecord classes
1.0.0b15 | Fixed a bug with empty string email values passing through required validation 7/29/11 |
---|---|
1.0.0b14 | Updated code to work with the new fORM API 8/6/10 |
1.0.0b13 | Fixed reflect() to include some missing parameters 6/8/10 |
1.0.0b12 | Changed validation messages array to use column name keys 5/26/10 |
1.0.0b11 | Fixed a bug with prepareLinkColumn() returning http:// for empty link columns and not adding http:// to links that contained a /, but did not start with it 3/16/10 |
1.0.0b10 | Fixed reflect() to specify the value returned from set and generate methods, changed generate() methods to return the newly generated string 3/15/10 |
1.0.0b9 | Changed email columns to be automatically trimmed if they are a value email address surrounded by whitespace 3/14/10 |
1.0.0b8 | Made the validation on link columns a bit more strict 3/9/10 |
1.0.0b7 | Updated code for the new fORMDatabase and fORMSchema APIs 10/28/09 |
1.0.0b6 | Changed SQL statements to use value placeholders, identifier escaping and schema support 10/22/09 |
1.0.0b5 | Updated to use new fORM::registerInspectCallback() method 7/13/09 |
1.0.0b4 | Updated code for new fORM API 6/15/09 |
1.0.0b3 | Updated code to use new fValidationException::formatField() method 6/4/09 |
1.0.0b2 | Fixed a bug with objectifying number columns 11/24/08 |
1.0.0b | The initial implementation 5/27/08 |
Sets a column to be formatted as an email address
void configureEmailColumn( mixed $class, string $column )
mixed | $class | The class name or instance of the class to set the column format |
string | $column | The column to format as an email address |
Sets a column to be formatted as a link
void configureLinkColumn( mixed $class, string $column )
mixed | $class | The class name or instance of the class to set the column format |
string | $column | The column to format as a link |
Sets a column to be returned as an fNumber object from calls to get{ColumnName}()
void configureNumberColumn( mixed $class, string $column )
mixed | $class | The class name or instance of the class to set the column format |
string | $column | The column to return as an fNumber object |
Sets a column to be a random string column - a random string will be generated when the record is saved
void configureRandomColumn( mixed $class, string $column, string $type, integer $length )
mixed | $class | The class name or instance of the class |
string | $column | The column to set as a random column |
string | $type | The type of random string, must be one of: 'alphanumeric', 'alpha', 'numeric', 'hexadecimal' |
integer | $length | The length of the random string |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Encodes a number column by calling fNumber::__toString()
string encodeNumberColumn( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache, string $method_name, array $parameters )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
string | $method_name | The method that was called |
array | $parameters | The parameters passed to the method |
The encoded number
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Generates a new random value for the column
string generate( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache, string $method_name, array $parameters )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
string | $method_name | The method that was called |
array | $parameters | The parameters passed to the method |
The newly generated random value
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Adds metadata about features added by this class
void inspect( string $class, string $column, array &$metadata )
string | $class | The class being inspected |
string | $column | The column being inspected |
array | &$metadata | The array of metadata about a column |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Turns a numeric value into an fNumber object
mixed objectifyNumber( string $class, string $column, mixed $value )
string | $class | The class this value is for |
string | $column | The column the value is in |
mixed | $value | The value |
The fNumber object or raw value
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Prepares a link column so that the link will work properly in an a tag
string prepareLinkColumn( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache, string $method_name, array $parameters )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
string | $method_name | The method that was called |
array | $parameters | The parameters passed to the method |
The formatted link
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Prepares a number column by calling fNumber::format()
string prepareNumberColumn( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache, string $method_name, array $parameters )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
string | $method_name | The method that was called |
array | $parameters | The parameters passed to the method |
The formatted link
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Adjusts the fActiveRecord::reflect() signatures of columns that have been configured in this class
void reflect( string $class, array &$signatures, boolean $include_doc_comments )
string | $class | The class to reflect |
array | &$signatures | The associative array of {method name} => {signature} |
boolean | $include_doc_comments | If doc comments should be included with the signature |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Resets the configuration of the class
void reset( )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Sets the value for an email column, trimming the value if it is a valid email
fActiveRecord setEmailColumn( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache, string $method_name, array $parameters )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
string | $method_name | The method that was called |
array | $parameters | The parameters passed to the method |
The record object, to allow for method chaining
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Sets the appropriate column values to a random string if the object is new
string setRandomStrings( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
The formatted link
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Validates all email columns
void validateEmailColumns( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache, array &$validation_messages )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
array | &$validation_messages | An array of ordered validation messages |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Validates all link columns
void validateLinkColumns( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache, array &$validation_messages )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
array | &$validation_messages | An array of ordered validation messages |
The fORMDatabase class provides database-related functionality to the Flourish ORM. End-developers will use the class to attach fDatabase objects for the rest of ORM.
Since an ORM by definition maps objects to a database, it is obvious that an instance of the fDatabase class will be needed for all database operations. To make an instance of fDatabase available to all of the ORM classes, pass it to the static method attach()
.
fORMDatabase::attach(new fDatabase('postgresql', 'database', 'username', 'password'));
When writing code for the ORM that requires use of the database class, the static method retrieve()
will return the instance set with attach()
.
$result = fORMDatabase::retrieve()->translatedQuery("SELECT * FROM users");
Multiple databases are support by the Flourish ORM, both in master-slave and vertical partitioning setups.
Vertical partitioning is when different tables are split up over multiple databases or servers. For instance, perhaps all users and permissions are stored on one database server and all orders and products are stored on another.
In this type of a setup, it is possible to attach more than one databases for the ORM by passing a second parameter, $name
, to the static method attach()
.
// Attach the users db as the default
fORMDatabase::attach($users_db);
// Attach the ecommerce db with the name "ecommerce_db"
fORMDatabase::attach($ecommerce_db, 'ecommerce_db');
Now individual classes can be mapped to the database by calling fORM::mapClassToDatabase().
fORM::mapClassToDatabase('User', 'ecommerce_db');
When a database cluster is set up to use a master-slave setup, one database will be designated the master and one or more as slaves. Flourish allows for such a setup with attach()
by specifying the third parameter, $role
. The $role
parameter defaults to both
, which means the server will be used for reading and writing. To attach the master server, set the $role
to write
. For the slave, set the $role
to read
.
// Attach the master for writing and a slave for reading
fORMDatabase::attach($master_db, 'default', 'write');
fORMDatabase::attach($random_slave_db, 'default', 'read');
It is also possible to combine vertical partitioning with master-slave setups by specifying a $name
for the database other than default
.
As implied by the slave database variable $random_slave_db
in the code example, attach()
will not accept multiple databases for the read
role and random pick one. For setups where there is more than one slave, code must be manually written to select a slave and provide it to the attach()
method.
Holds a single instance of the fDatabase class and provides database manipulation functionality for ORM code
1.0.0b32 | Added support to addWhereClause() for the ^~ and $~ operators 6/20/11 |
---|---|
1.0.0b31 | Fixed a bug with addWhereClause() generating invalid SQL 5/10/11 |
1.0.0b30 | Fixed insertFromAndGroupByClauses() to insert MAX() around columns in related tables in the ORDER BY clause when a GROUP BY is used 2/3/11 |
1.0.0b29 | Added code to handle old PCRE engines that don't support unicode character properties 12/6/10 |
1.0.0b28 | Fixed a bug in the fProgrammerException that is thrown when an improperly formatted OR condition is provided 11/24/10 |
1.0.0b27 | Fixed addWhereClause() to ignore fuzzy search clauses with no values to match 10/19/10 |
1.0.0b26 | Fixed insertFromAndGroupByClauses() to handle SQL where a table is references in more than one capitalization 7/26/10 |
1.0.0b25 | Fixed insertFromAndGroupByClauses() to properly handle recursive relationships 7/22/10 |
1.0.0b24 | Fixed parseSearchTerms() to work with non-ascii terms 6/30/10 |
1.0.0b23 | Fixed error messages in retrieve() 4/23/10 |
1.0.0b22 | Added support for IBM DB2, fixed an issue with building record sets or records that have recursive relationships 4/13/10 |
1.0.0b21 | Changed injectFromAndGroupByClauses() to be able to handle table aliases that contain other aliases inside of them 3/3/10 |
1.0.0b20 | Fixed a bug where joining to a table two separate ways could cause table alias issues and incorrect SQL to be generated 12/16/09 |
1.0.0b19 | Added the ability to compare columns with the =:, !:, <:, <=:, >: and >=: operators 12/8/09 |
1.0.0b18 | Fixed a bug affecting where conditions with columns that are not null but have a default value 11/3/09 |
1.0.0b17 | Added support for multiple databases 10/28/09 |
1.0.0b16 | Internal Backwards Compatibility Break - Renamed methods and significantly changed parameters and functionality for SQL statements to use value placeholders, identifier escaping and to handle schemas 10/22/09 |
1.0.0b15 | Streamlined intersection operator SQL and added support for the second value being NULL 9/21/09 |
1.0.0b14 | Added support for the intersection operator >< to createWhereClause() 7/13/09 |
1.0.0b13 | Added support for the AND LIKE operator &~ to createWhereClause() 7/9/09 |
1.0.0b12 | Added support for the NOT LIKE operator !~ to createWhereClause() 7/8/09 |
1.0.0b11 | Added support for concatenated columns to escapeBySchema() 6/19/09 |
1.0.0b10 | Updated createWhereClause() to properly handle NULLs for arrays of values when doing = and != comparisons 6/17/09 |
1.0.0b9 | Changed replacement values in preg_replace() calls to be properly escaped 6/11/09 |
1.0.0b8 | Fixed a bug with creatingWhereClause() where a null value would not be escaped property 5/12/09 |
1.0.0b7 | Fixed a bug where an OR condition in createWhereClause() could not have one of the values be an array 4/22/09 |
1.0.0b6 | insertFromAndGroupByClauses() will no longer wrap ungrouped columns if in a CAST or CASE statement for ORDER BY clauses of queries with a GROUP BY clause 3/23/09 |
1.0.0b5 | Fixed parseSearchTerms() to include stop words when they are the only thing in the search string 12/31/08 |
1.0.0b4 | Fixed a bug where loading a related record in the same table through a one-to-many relationship caused recursion 12/24/08 |
1.0.0b3 | Fixed a bug from 1.0.0b2 12/5/08 |
1.0.0b2 | Added support for != and <> to createWhereClause() and createHavingClause() 12/4/08 |
1.0.0b | The initial implementation 8/4/07 |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Creates a HAVING clause from an array of conditions
array addHavingClause( fDatabase $db, fSchema $schema, array $params, string $table, array $conditions )
fDatabase | $db | The database the query will be executed on |
fSchema | $schema | The schema for the database |
array | $params | The params for the fDatabase::query() call |
string | $table | The table the query is being executed on |
array | $conditions | The array of conditions - see fRecordSet::build() for format |
The params with the HAVING clause added
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Adds an ORDER BY clause to an array of params for an fDatabase::query() call
array addOrderByClause( fDatabase $db, fSchema $schema, array $params, string $table, array $order_bys )
fDatabase | $db | The database the query will be executed on |
fSchema | $schema | The schema object for the database the query will be executed on |
array | $params | The parameters for the fDatabase::query() call |
string | $table | The table any ambigious column references will refer to |
array | $order_bys | The array of order bys to use - see fRecordSet::build() for format |
The params with a SQL ORDER BY clause added
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Add the appropriate SQL and params for a WHERE clause condition for primary keys of the table specified
array addPrimaryKeyWhereParams( fSchema $schema, array $params, string $table, string $table_alias, array &$values, array &$old_values )
fSchema | $schema | The schema for the database the query will be run on |
array | $params | The currently constructed params for fDatabase::query() - the first param should be a SQL statement |
string | $table | The table to build the where clause for |
string | $table_alias | The alias for the table |
array | &$values | The values array for the fActiveRecord object |
array | &$old_values | The old values array for the fActiveRecord object |
The params to pass to fDatabase::query(), including the new primary key where condition
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Adds a WHERE clause, from an array of conditions, to the parameters for an fDatabase::query() call
array addWhereClause( fDatabase $db, fSchema $schema, array $params, string $table, array $conditions )
fDatabase | $db | The database the query will be executed on |
fSchema | $schema | The schema for the database |
array | $params | The parameters for the fDatabase::query() call |
string | $table | The table any ambigious column references will refer to |
array | $conditions | The array of conditions - see fRecordSet::build() for format |
The params with the SQL WHERE clause added
Allows attaching an fDatabase-compatible objects for by ORM code
If a $name other than default is used, any fActiveRecord classes that should use it will need to be configured by passing the class name and $name to mapClassToDatabase(). The $name parameter should be unique per database or database master/slave setup.
The $role is used by code to allow for master/slave database setups. There can only be one database object attached for either of the roles, 'read' or 'write'. If the role 'both' is specified, it will be applied to both the 'read' and 'write' roles. Any sort of logic for picking one out of multiple databases should be done before this method is called.
void attach( fDatabase $database, string $name='default', string $role='both' )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Finds all of the table names in the SQL and creates the appropriate FROM and GROUP BY clauses with all necessary joins
The SQL string should contain two placeholders, :from_clause and :group_by_clause, although the later may be omitted if necessary. All columns should be qualified with their full table name.
Here is an example SQL string to pass in presumming that the tables users and groups are in a relationship:
SELECT users.* FROM :from_clause WHERE groups.group_id = 5 :group_by_clause ORDER BY lower(users.first_name) ASC
array injectFromAndGroupByClauses( fDatabase $db, fSchema $schema, array $params, string $table )
fDatabase | $db | The database the query is to be executed on |
fSchema | $schema | The schema for the database |
array | $params | The parameters for the fDatabase::query() call |
string | $table | The main table to be queried |
The params with the SQL FROM and GROUP BY clauses injected
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Makes a condition for a SQL statement out of fDatabase::escape() placeholders
string makeCondition( fSchema $schema, string $table, string $column, string $comparison_operator, mixed $value )
fSchema | $schema | The schema object for the database the query will be executed on |
string | $table | The table to create the condition for |
string | $column | The column to make the condition for |
string | $comparison_operator | The comparison operator for the condition |
mixed | $value | The value for the condition, which allows the $comparison_operator to be tweaked for NULL values |
A SQL condition using fDatabase::escape() placeholders
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Parses a search string into search terms, supports quoted phrases and removes extra punctuation
void parseSearchTerms( string $terms, boolean $ignore_stop_words=FALSE )
string | $terms | A text string from a form input to parse into search terms |
boolean | $ignore_stop_words | If stop words should be ignored, this setting will be ignored if all words are stop words |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Resets the configuration of the class
void reset( )
Return the instance of the fDatabase class
fDatabase retrieve( string $class='fActiveRecord', string $role='either' )
string | $class | The class to retrieve the database for - if not specified, the default database will be returned |
string | $role | If the database will be used for 'write', 'read' or 'either' operations |
The database instance
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Removed aggregate function calls from where conditions array and puts them in a having conditions array
array splitHavingConditions( array &$where_conditions )
array | &$where_conditions | The where conditions to look through for aggregate functions |
The conditions to be put in a HAVING clause
The fORMDate class is an ORM plugin to provide additional functionality for date and time columns.
The static method configureDateCreatedColumn()
sets an active record class to automatically set a date
, time
or timestamp
column to the date/time when the record is first saved in the database.
class User extends fActiveRecord
{
protected function configure()
{
fORMDate::configureDateCreatedColumn($this, 'date_created');
}
}
The static method configureDateUpdatedColumn()
sets an active record class to automatically set a date
, time
or timestamp
column to the current date/time each time the record is saved in the database.
class User extends fActiveRecord
{
protected function configure()
{
fORMDate::configureDateUpdatedColumn($this, 'last_edited');
}
}
Since not all supported databases support timezone information in timestamp columns, the fORMDate class allows associating a timestamp column with another column to store the timezone name. The static method configureTimezoneColumn()
accepts three parameters, the $class
, the $timestamp_column
and the $timezone_column
.
class NewsArticle extends fActiveRecord
{
protected function configure()
{
fORMDate::configureTimezoneColumn($this, 'date_posted', 'timezone_posted');
}
}
The timezone column will be used to store the timezone stored in any fTimestamp objects that are set to the timestamp column. If a new timestamp is set, it will be combined with the existing timezone into a new fTimestamp object. The object will be stored in the timestamp column. If a new timezone is set, it will be combined with the existing timestamp and the new fTimestamp object will be stored in the timestamp column.
Provides additional date/time functionality for fActiveRecord classes
1.0.0b9 | Updated code to work with the new fORM API 8/6/10 |
---|---|
1.0.0b8 | Changed validation messages array to use column name keys 5/26/10 |
1.0.0b7 | Fixed the set methods to return the record object in order to be consistent with all other set methods 3/15/10 |
1.0.0b6 | Fixed an issue with calling a non-existent method on fTimestamp instances 11/3/09 |
1.0.0b5 | Updated code for the new fORMDatabase and fORMSchema APIs 10/28/09 |
1.0.0b4 | Fixed setting up the inspect callback in configureTimezoneColumn() 10/11/09 |
1.0.0b3 | Updated to use new fORM::registerInspectCallback() method 7/13/09 |
1.0.0b2 | Updated code to use new fValidationException::formatField() method 6/4/09 |
1.0.0b | The initial implementation 9/5/08 |
Sets a column to be a date created column
When a new record is stored in the database, date created columns will be filled with the timestamp of the store operation.
void configureDateCreatedColumn( mixed $class, string $column )
mixed | $class | The class name or instance of the class |
string | $column | The column to set as a date created column |
Sets a column to be a date updated column
Whenever a record is stored in the database, a date updated column will be set to the timestamp of the operation.
void configureDateUpdatedColumn( mixed $class, string $column )
mixed | $class | The class name or instance of the class |
string | $column | The column to set as a date updated column |
Sets a timestamp column to store the timezone in another column
Since not all databases support timezone information in timestamp columns, this method allows storing the timezone in another columns. When the timestamp and timezone are retrieved from the database, they will be automatically combined together into an fTimestamp object.
void configureTimezoneColumn( mixed $class, string $timestamp_column, string $timezone_column )
mixed | $class | The class name or instance of the class to set the column format |
string | $timestamp_column | The timestamp column to store the timezone for |
string | $timezone_column | The column to store the timezone in |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Adds metadata about features added by this class
void inspect( string $class, string $column, array &$metadata )
string | $class | The class being inspected |
string | $column | The column being inspected |
array | &$metadata | The array of metadata about a column |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Creates fTimestamp objects for every timestamp/timezone combination in the object
void makeTimestampObjects( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Turns a timestamp value into an fTimestamp object with a timezone specified by another column
void objectifyTimestampWithTimezone( array &$values, array &$old_values, string $timestamp_column, string $timezone_column )
array | &$values | The current values |
array | &$old_values | The old values |
string | $timestamp_column | The column holding the timestamp |
string | $timezone_column | The column holding the timezone |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Resets the configuration of the class
void reset( )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Sets the appropriate column values to the date the object was created (for new records)
void setDateCreated( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Sets the appropriate column values to the date the object was updated
void setDateUpdated( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Sets the timestamp column and then tries to objectify it with an related timezone column
fActiveRecord setTimestampColumn( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache, string $method_name, array $parameters )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
string | $method_name | The method that was called |
array | $parameters | The parameters passed to the method |
The record object, to allow for method chaining
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Sets the timezone column and then tries to objectify the related timestamp column
fActiveRecord setTimezoneColumn( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache, string $method_name, array $parameters )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
string | $method_name | The method that was called |
array | $parameters | The parameters passed to the method |
The record object, to allow for method chaining
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Validates all timestamp/timezone columns
void validateTimezoneColumns( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache, array &$validation_messages )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
array | &$validation_messages | An array of ordered validation messages |
The fORMFile class is an ORM plugin to provide file and file upload handling to fActiveRecord classes.
Any varchar
, char
or text
field can be configured to act as a file upload column by calling the static method configureFileUploadColumn()
. The method takes three parameters, the $class
, the $column
and the $directory
to store file in. The $directory
can be a string file path or an fDirectory object.
class NewsArticle extends fActiveRecord
{
protected function configure()
{
fORMFile::configureFileUploadColumn(
$this,
'attachment',
'/path/to/attachment/upload/dir'
);
}
}
Once a file has been configured as a file upload column, the fActiveRecord::populate() method will automatically move any files uploaded in a field with the same name as the column. It is also possible to upload a file for the column separately by calling the upload
method for a column.
$user = new User();
if (fRequest::isPost()) {
// Populate column values from the request and
// upload files into file upload columns
$user->populate();
// Upload just the attachment
$user->uploadAttachment();
}
The set
method for the column will also accept a file path or fFile object and make a copy of the file in the directory specified for the column. When a record is deleted, the file associated with it will be deleted from the directory.
// This file will be copied into the directory defined
// in the fORMFile::configureFileUploadColumn() call
$user->setAttachment('/path/to/a/file.txt');
The get
method for a file upload column will return the fFile object of the file, while the prepare
and encode
methods will return just the filename. If the parameter TRUE
is passed to the prepare
method, the web server path to the file will be returnedsee fFilesystem::addWebPathTranslation() for details about how filesystem paths are mapped to web server paths.
// The get method will return an fFile or fImage object
switch ($user->getAttachment()->getMimeType()) {
case 'image/png':
//
}
// This will output the filename safe for HTML
echo $user->encodeAttachment();
// This will return the web path to the file
echo $user->prepareAttachment(TRUE);
In order to upload a file through an HTML form the enctype
attribute must be set to multipart/form-data
. In order for an fActiveRecord class to properly detect a file and move it to the columns directory, the file upload input must have the same name as the column.
The fORMFile plugin provides functionality where an uploaded file can be remembered even if a validation error happens and a file can be deleted through the use of a checkbox. The existing file hidden input field should be named existing-column_name
and the delete checkbox should be named delete-column_name
. Since the delete field is a checkbox, it is recommended to put a hidden field right before it with a value of 0
to ensure the field is always submitted.
<form action="" method="post" enctype="multipart/form-data">
<fieldset>
<legend>Add a News Article</legend>
...
<p>
<label for="news_article-attachment">Attachment</label>
<input id="news_article-attachment" type="file" name="attachment" />
<?
if ($news_article->getAttachment()) {
?>
<input type="hidden" name="existing-attachment" value="<?php echo $news_article->encodeAttachment() ?>" />
<input type="hidden" name="delete-attachment" value="0" />
Existing file: <a href="<?php echo $news_article->prepareAttachment(TRUE) ?>"><?php echo $news_article->prepareAttachment() ?></a>
<label for="news_article-delete-attachment">Delete existing attachment</label>
<input type="checkbox" id="news_article-delete-attachment" name="delete-attachment" value="1" />
<?
}
?>
</p>
...
</fieldset>
</form>
Since the fUpload class is used to move and validate files that are uploaded, it is possible to configure the fORMFile plugin to call methods on the fUpload object used to move a file. The static method addFUploadMethodCall()
requires four parameters, the $class
being configured, the $column
to apply the option to, the $method
to call and an array of the $parameters
to pass to the method.
Below is an example of restricting the file upload size using both client-side restrictions and server-side restrictions. The client-side MAX_FILE_SIZE
input field prevents the browser from wasting time sending a file that is too large, while the server-side setMaxSize()
will stop field that are too large and are sent by clients that dont send the MAX_FILE_SIZE
field. Please note that the MAX_FILE_SIZE
must be less than both the upload_max_filesize and post_max_size ini settings.
class NewsArticle extends fActiveRecord
{
protected function configure()
{
fORMFile::configureFileUploadColumn($this, 'attachment', '/path/to/attachment/upload/dir');
fORMFile::addFUploadMethodCall($this, 'attachment', 'setMaxSize', array('2mb'));
}
}
<form action="" method="post" enctype="multipart/form-data">
<fieldset>
<legend>Add a News Article</legend>
...
<p>
<input type="hidden" name="MAX_FILE_SIZE" value="2097152" />
<label for="news_article-attachment">Attachment</label>
<input id="news_article-attachment" type="file" name="attachment" />
...
</p>
...
</fieldset>
</form>
The only restriction for setting fUpload method calls is that the method fUpload::enableOverwrite() is not available since it could lead to two records referencing the same file, which creates issues when one record is deleted.
It is also possible to configure an image upload column, which allows for automatic image processing and automatic rejection of non-image files. The static method configureImageUploadColumn()
accepts four parameters, the $class
to configure, the $column
to store the image name in, the $directory
to store the files in.
class NewsArticle extends fActiveRecord
{
protected function configure()
{
fORMFile::configureImageUploadColumn($this, 'photo', '/path/to/photo/upload/dir');
}
}
Configuring an image upload column will restrict the uploaded files to the following mime types:
image/gif
image/jpeg
image/pjpeg
image/png
In addition to checking the mime type supplied by the browser, a server-side check is done to ensure the image type reported by the browser is correct.
When working with an image upload column, it is possible to add any number of fImage operations to be executed on the image before it is saved in the upload directory. The static method addFImageMethodCall()
accepts four parameters, the $class
being configured, the $column
to add the call to, the fImage $method
to call and the $parameters
to pass to it. The operations will be executed in the order they are added.
The following configuration will cause the photo to be cropped and resized to 200x200 pixels.
class NewsArticle extends fActiveRecord
{
protected function configure()
{
fORMFile::configureImageUploadColumn($this, 'photo', '/path/to/photo/upload/dir');
fORMFile::addFImageMethodCall($this, 'photo', 'cropToRatio', array(1, 1));
fORMFile::addFImageMethodCall($this, 'photo', 'resize', array(200, 200));
}
}
When creating cropped versions of an image, placing the crop operation before the resize operation will almost always create the desired size, while placing them in the opposite order will often lead to images that are too small.
addFImageMethodCall()
can also be used to specify the file type, or specific parameters to using when saving the image. If no call to fImage::saveChanges() is added, it will be called implicitly with default parameters.
The following configuration will always save the image as a JPEG with a quality of 50
.
class NewsArticle extends fActiveRecord
{
protected function configure()
{
fORMFile::configureImageUploadColumn($this, 'photo', '/path/to/photo/upload/dir');
fORMFile::addFImageMethodCall($this, 'photo', 'saveChanges', array('jpg', 50));
}
}
When uploading images or photos for a site, it is often a requirement to create multiple sizes for display in different layouts. The static method configureColumnInheritance()
will allow a file to be uploaded to a single column, and will then duplicate the uploaded file into another column. All file duplication is done before any image operations are executed. If the column being duplicated into is an image upload column with fImage operations, those will be executed on the duplicated file.
configureColumnInheritance()
accepts three parameters, the $class
being configured, the $column
to inherit the uploaded file, and the $inherit_from_column
which will act as the source column. It is possible to set up multiple columns to inherit from a single master column.
The following example shows small and medium thumbnail columns both inheriting from the main photo column. The upload form for this record would only require that the user upload a single master photo.
class NewsArticle extends fActiveRecord
{
protected function configure()
{
fORMFile::configureImageUploadColumn($this, 'photo', '/path/to/photo/upload/dir', 'jpg');
fORMFile::addFImageMethodCall($this, 'photo', 'resize', array(600, NULL));
fORMFile::configureImageUploadColumn($this, 'photo_medium', '/path/to/photo_medium/upload/dir', 'jpg');
fORMFile::addFImageMethodCall($this, 'photo_medium', 'resize', array(200, NULL));
fORMFile::configureColumnInheritance($this, 'photo_medium', 'photo');
fORMFile::configureImageUploadColumn($this, 'photo_small', '/path/to/photo_small/upload/dir', 'jpg');
fORMFile::addFImageMethodCall($this, 'photo_small', 'resize', array(100, NULL));
fORMFile::configureColumnInheritance($this, 'photo_small', 'photo');
}
}
Provides file manipulation functionality for fActiveRecord classes
1.0.0b30 | Updated code for the new fUpload API 8/24/11 |
---|---|
1.0.0b29 | Fixed a bug when uploading a new file to a column with an existing file that was not found on the filesystem 5/10/11 |
1.0.0b28 | Backwards Compatibility Break - configureImageUploadColumn() no longer accepts the optional $image_type as the fourth parameter, instead addFImageMethodCall() must be called with saveChanges as the $method and the image type as the first parameter 11/30/10 |
1.0.0b27 | Fixed column inheritance to properly handle non-images and inheriting into image upload columns 9/18/10 |
1.0.0b26 | Enhanced configureColumnInheritance() to ensure both columns specified have been set up as file upload columns 8/18/10 |
1.0.0b25 | Updated code to work with the new fORM API 8/6/10 |
1.0.0b24 | Changed validation messages array to use column name keys 5/26/10 |
1.0.0b23 | Fixed a bug with upload() that could cause a method called on a non-object error in relation to the upload directory not being defined 5/10/10 |
1.0.0b22 | Updated the TEMP_DIRECTORY constant to not include the trailing slash, code now uses DIRECTORY_SEPARATOR to fix issues on Windows 4/28/10 |
1.0.0b21 | Fixed set() to perform column inheritance, just like upload() does 3/15/10 |
1.0.0b20 | Fixed the set and process methods to return the record instance, changed upload methods to return the fFile object, updated reflect() with new return values 3/15/10 |
1.0.0b19 | Fixed a few missed instances of old fFile method names 12/16/09 |
1.0.0b18 | Updated code for the new fFile API 12/16/09 |
1.0.0b17 | Updated code for the new fORMDatabase and fORMSchema APIs 10/28/09 |
1.0.0b16 | fImage method calls for file upload columns will no longer cause notices due to a missing image type 9/9/09 |
1.0.0b15 | addFImageMethodCall() no longer requires column be an image upload column, inheritance to an image column now only happens for fImage objects 7/29/09 |
1.0.0b14 | Updated to use new fORM::registerInspectCallback() method 7/13/09 |
1.0.0b13 | Updated code for new fORM API 6/15/09 |
1.0.0b12 | Changed replacement values in preg_replace() calls to be properly escaped 6/11/09 |
1.0.0b11 | Updated code to use new fValidationException::formatField() method 6/4/09 |
1.0.0b10 | Fixed a bug where an inherited file upload column would not be properly re-set with an existing- input 5/26/09 |
1.0.0b9 | upload() and set() now set the $values entry to NULL for filenames that are empty 3/2/09 |
1.0.0b8 | Changed set() to accept objects and reject directories 1/21/09 |
1.0.0b7 | Changed the class to use the new fFilesystem::createObject() method 1/21/09 |
1.0.0b6 | Old files are now checked against the current file to prevent removal of an in-use file 12/23/08 |
1.0.0b5 | Fixed replicate() to ensure the temp directory exists and set() to use the temp directory 12/23/08 |
1.0.0b4 | objectify() no longer throws an exception when a file can't be found 12/18/08 |
1.0.0b3 | Added replicate() so that replicated files get pu in the temp directory 12/12/08 |
1.0.0b2 | Fixed a bug with objectifying file columns 11/24/08 |
1.0.0b | The initial implementation 5/28/08 |
Please note: this constant is primarily intended for internal use by Flourish and will normally not be useful in site/application code
The temporary directory to use for various tasks
Adds an fImage method call to the image manipulation for a column if an image file is uploaded
Any call to fImage::saveChanges() will be called last. If no explicit method call to fImage::saveChanges() is made, it will be called implicitly with default parameters.
void addFImageMethodCall( mixed $class, string $column, string $method, array $parameters=array() )
mixed | $class | The class name or instance of the class |
string | $column | The column to call the method for |
string | $method | The fImage method to call |
array | $parameters | The parameters to pass to the method |
void addFUploadMethodCall( mixed $class, string $column, string $method, array $parameters=array() )
mixed | $class | The class name or instance of the class |
string | $column | The column to call the method for |
string | $method | The fUpload method to call |
array | $parameters | The parameters to pass to the method |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Begins a transaction, or increases the level
void begin( )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Commits a transaction, or decreases the level
void commit( )
Takes one file or image upload columns and sets it to inherit any uploaded/set files from another column
void configureColumnInheritance( mixed $class, string $column, string $inherit_from_column )
mixed | $class | The class name or instance of the class |
string | $column | The column that will inherit the uploaded file |
string | $inherit_from_column | The column to inherit the uploaded file from |
Sets a column to be a file upload column
Configuring a column to be a file upload column means that whenever fActiveRecord::populate() is called for an fActiveRecord object, any appropriately named file uploads (via $_FILES) will be moved into the directory for this column.
Setting the column to a file path will cause the specified file to be copied into the directory for this column.
void configureFileUploadColumn( mixed $class, string $column, fDirectory|string $directory )
mixed | $class | The class name or instance of the class |
string | $column | The column to set as a file upload column |
fDirectory|string | $directory | The directory to upload/move to |
Sets a column to be an image upload column
This method works exactly the same as configureFileUploadColumn() except that only image files are accepted.
To alter an image, including the file type, use addFImageMethodCall().
void configureImageUploadColumn( mixed $class, string $column, fDirectory|string $directory )
mixed | $class | The class name or instance of the class |
string | $column | The column to set as a file upload column |
fDirectory|string | $directory | The directory to upload to |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Deletes the files for this record
void delete( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Deletes old files for this record that have been replaced by new ones
void deleteOld( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Encodes a file for output into an HTML input tag
void encode( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache, string $method_name, array $parameters )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
string | $method_name | The method that was called |
array | $parameters | The parameters passed to the method |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Adds metadata about features added by this class
void inspect( string $class, string $column, array &$metadata )
string | $class | The class being inspected |
string | $column | The column being inspected |
array | &$metadata | The array of metadata about a column |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Moves uploaded files from the temporary directory to the permanent directory
void moveFromTemp( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
mixed objectify( string $class, string $column, mixed $value )
string | $class | The class this value is for |
string | $column | The column the value is in |
mixed | $value | The value |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Performs the upload action for file uploads during fActiveRecord::populate()
void populate( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Prepares a file for output into HTML by returning filename or the web server path to the file
void prepare( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache, string $method_name, array $parameters )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
string | $method_name | The method that was called |
array | $parameters | The parameters passed to the method |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Handles re-processing an existing image file
fActiveRecord process( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache, string $method_name, array $parameters )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
string | $method_name | The method that was called |
array | $parameters | The parameters passed to the method |
The record object, to allow for method chaining
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Performs image manipulation on an uploaded/set image
void processImage( string $class, string $column, fFile $image )
string | $class | The name of the class we are manipulating the image for |
string | $column | The column the image is assigned to |
fFile | $image | The image object to manipulate |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Adjusts the fActiveRecord::reflect() signatures of columns that have been configured in this class
void reflect( string $class, array &$signatures, boolean $include_doc_comments )
string | $class | The class to reflect |
array | &$signatures | The associative array of {method name} => {signature} |
boolean | $include_doc_comments | If doc comments should be included with the signature |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Creates a copy of an uploaded file in the temp directory for the newly cloned record
mixed replicate( string $class, string $column, mixed $value )
string | $class | The class this value is for |
string | $column | The column the value is in |
mixed | $value | The value |
The cloned fFile object
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Resets the configuration of the class
void reset( )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Rolls back a transaction, or decreases the level
void rollback( )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Copies a file from the filesystem to the file upload directory and sets it as the file for the specified column
This method will perform the fImage calls defined for the column.
fActiveRecord set( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache, string $method_name, array $parameters )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
string | $method_name | The method that was called |
array | $parameters | The parameters passed to the method |
The record object, to allow for method chaining
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Uploads a file
fFile upload( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache, string $method_name, array $parameters )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
string | $method_name | The method that was called |
array | $parameters | The parameters passed to the method |
The uploaded file
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Validates uploaded files to ensure they match all of the criteria defined
void validate( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache, array &$validation_messages )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
array | &$validation_messages | The existing validation messages |
The fORMJSON class is an ORM plugin to provide JSON encoding functionality to fActiveRecord and fRecordSet objects.
To add the JSON encoding functionality for whole records and record sets, simply call the static method extend()
.
fORMJSON::extend();
The fORMJSON class adds a toJSON()
method to both fActiveRecord and fRecordSet. Calling toJSON()
on a record will create a single JSON object with each column being the property name and the value being the value. Calling toJSON()
on a record set will create an array of the record JSON objects.
$user = new User(3);
echo $user->toJSON();
$users = fRecordSet::build('User');
echo $users->toJSON();
When outputting JSON to a browser, it is best practice to include the appropriate Content-Type
header by calling fJSON::sendHeader().
fJSON::sendHeader();
$user = new User(3);
echo $user->toJSON();
Adds JSON functionality to fActiveRecord and fRecordSet
1.0.0b3 | Removed the $pointer parameter from toJSONRecordSet() since fRecordSet no longer has a pointer 9/28/10 |
---|---|
1.0.0b2 | Updated the code to remove the $associate parameter for the record set method callback 6/2/09 |
1.0.0b | The initial implementation 6/25/08 |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Adjusts the fActiveRecord::reflect() signatures of columns that have been added by this class
void reflect( string $class, array &$signatures, boolean $include_doc_comments )
string | $class | The class to reflect |
array | &$signatures | The associative array of {method name} => {signature} |
boolean | $include_doc_comments | If doc comments should be included with the signature |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Returns a JSON object representation of the record
string toJSON( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache, string $method_name, array $parameters )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
string | $method_name | The method that was called |
array | $parameters | The parameters passed to the method |
The JSON object that represents the values of this record
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Returns a JSON object representation of a record set
string toJSONRecordSet( fRecordSet $record_set, string $class, array &$records )
fRecordSet | $record_set | The fRecordSet instance |
string | $class | The class of the records |
array | &$records | The fActiveRecord objects |
The JSON object that represents an array of all of the fActiveRecord objects
The fORMMoney class is an ORM plugin to provide functionality to treat columns as monetary values.
When dealing with monetary values, it is usually very important that the values stored and calculated are accurate. The fMoney class provides this sort of accuracy and the fORMMoney class allows a column to be configured so that values coming out of the database are automatically converted to fMoney object. The static method configureMoneyColumn()
accepts three parameters, the $class
being configured, the $column
to set as a money column, and optionally a $currency_column
to store the currency of the monetary value in.
When configuring a money column without a corresponding currency column, a default currency must be set via fMoney::setDefaultCurrency(). All fMoney objects created will use this default currency. If an fMoney object is set to the value of the money column, and it contains a different currency than the default and no currency column is set, the currency will be lost when the record is saved in the database.
class Order extends fActiveRecord
{
protected function configure()
{
fORMMoney::configureMoneyColumn($this, 'total');
}
}
When calling the get
method for a money column, an fMoney object should be expected whenever a record has been freshly loaded from the databaseit is possible that if a value has been set that it is not a valid money value, and thus will not be an fMoney object. The prepare
method for a money column will call the fMoney::format() method on the fMoney object, while the encode
method will return the output of fMoney::__toString().
If a currency column is set for the money column, the currency contained in the fMoney object will be stored in that column when the record is saved to the database. If the set
method is called on the money column with a non-fMoney object, a new fMoney object will be created with the currency currently stored in the currency column. If the set
method is called on the currency column, a new fMoney object will be created with that currency and the current money amount. Setting an fMoney object to the money column will cause the currency column to be updated to the currency of the object.
Provides money functionality for fActiveRecord classes
1.0.0b11 | Fixed the generation of validation messages when a non-monetary value is supplied 5/17/11 |
---|---|
1.0.0b10 | Updated code to work with the new fORM API 8/6/10 |
1.0.0b9 | Added the $remove_zero_fraction parameter to prepare methods 6/9/10 |
1.0.0b8 | Changed validation messages array to use column name keys 5/26/10 |
1.0.0b7 | Fixed the set methods to return the record object in order to be consistent with all other set methods 3/15/10 |
1.0.0b6 | Fixed duplicate validation messages and fProgrammerException object being thrown when NULL is set 3/3/10 |
1.0.0b5 | Updated code for the new fORMDatabase and fORMSchema APIs 10/28/09 |
1.0.0b4 | Updated to use new fORM::registerInspectCallback() method 7/13/09 |
1.0.0b3 | Updated code to use new fValidationException::formatField() method 6/4/09 |
1.0.0b2 | Fixed bugs with objectifying money columns 11/24/08 |
1.0.0b | The initial implementation 9/5/08 |
Sets a column to be formatted as an fMoney object
void configureMoneyColumn( mixed $class, string $column, string $currency_column=NULL )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Encodes a money column by calling fMoney::__toString()
string encodeMoneyColumn( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache, string $method_name, array $parameters )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
string | $method_name | The method that was called |
array | $parameters | The parameters passed to the method |
The encoded monetary value
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Adds metadata about features added by this class
void inspect( string $class, string $column, array &$metadata )
string | $class | The class being inspected |
string | $column | The column being inspected |
array | &$metadata | The array of metadata about a column |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Makes fMoney objects for all money columns in the object that also have a currency column
void makeMoneyObjects( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Turns a monetary value into an fMoney object
mixed objectifyMoney( string $class, string $column, mixed $value )
string | $class | The class this value is for |
string | $column | The column the value is in |
mixed | $value | The value |
The fMoney object or raw value
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Turns a monetary value into an fMoney object with a currency specified by another column
void objectifyMoneyWithCurrency( array &$values, array &$old_values, string $value_column, string $currency_column )
array | &$values | The current values |
array | &$old_values | The old values |
string | $value_column | The column holding the value |
string | $currency_column | The column holding the currency code |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Prepares a money column by calling fMoney::format()
string prepareMoneyColumn( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache, string $method_name, array $parameters )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
string | $method_name | The method that was called |
array | $parameters | The parameters passed to the method |
The formatted monetary value
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Adjusts the fActiveRecord::reflect() signatures of columns that have been configured in this class
void reflect( string $class, array &$signatures, boolean $include_doc_comments )
string | $class | The class to reflect |
array | &$signatures | The associative array of {method name} => {signature} |
boolean | $include_doc_comments | If doc comments should be included with the signature |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Resets the configuration of the class
void reset( )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Sets the currency column and then tries to objectify the related money column
fActiveRecord setCurrencyColumn( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache, string $method_name, array $parameters )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
string | $method_name | The method that was called |
array | $parameters | The parameters passed to the method |
The record object, to allow for method chaining
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Sets the money column and then tries to objectify it with an related currency column
fActiveRecord setMoneyColumn( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache, string $method_name, array $parameters )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
string | $method_name | The method that was called |
array | $parameters | The parameters passed to the method |
The record object, to allow for method chaining
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Validates all money columns
void validateMoneyColumns( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache, array &$validation_messages )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
array | &$validation_messages | An array of ordered validation messages |
The fORMOrdering class is an ORM plugin to provide record ordering functionality for fActiveRecord classes.
An ordering column is used to provide arbitrary ordering for fActiveRecord objects. For a column to be an ordering column, it must be an integer column that allows for negative numbers and is part of a UNIQUE
constraint. While an ordering value will never be negative, negative integers are used in the process of re-arranging values.
If the column is the only column in the UNIQUE
constraint, all records in the table will be part of one ordered set. If the column is part of a multi-column UNIQUE
constraint, all records that have the same values for each column other than the ordering will be part of an ordered set.
To configure a column to be treated as an ordering column, the static method configureOrderingColumn()
must be called with the $class
to configure and the $column
to set as an ordering column.
class Photo extends fActiveRecord
{
protected function configure()
{
fORMOrdering::configureOrderingColumn($this, 'display_order');
]
}
Once a column has been set to be an ordering column, any new records inserted into the set will be automatically added at the end, unless a specific value is set for the ordering column.
// Assuming there are no other photos, this photo would become #1
$photo = new Photo();
$photo->store();
// This photo would be come number 2
$photo2 = new Photo();
$photo2->store();
Whenever the value for the ordering column is changed, the records around it will be adjusted so there is always a continuous sequence of order numbers.
// This photo would become number 2 and $photo2 would change to number 3
$photo3 = new Photo();
$photo3->setDisplayOrder(2);
$photo3->store();
Below is an example of a multi-column UNIQUE
constraint and the dynamics related to it. Each photo is part of a photo gallery, thus it is desired to have a separate ordering for each gallery. Flourish will automatically detect that the display_order
column is in a UNIQUE
constraint with photo_gallery_id
and will order each photo gallery separately.
CREATE TABLE photo_galleries (
name VARCHAR(200) PRIMARY KEY
);
CREATE TABLE photos (
photo_id SERIAL PRIMARY KEY,
photo VARCHAR(255) NOT NULL,
caption VARCHAR(255) NOT NULL DEFAULT '',
display_order INTEGER NOT NULL,
photo_gallery VARCHAR(200) NOT NULL REFERENCES photo_galleries(name) ON DELETE CASCADE ON UPDATE CASCADE,
UNIQUE(photo_gallery_id, display_order)
);
class Photo extends fActiveRecord
{
protected function configure()
{
fORMOrdering::configureOrderingColumn($this, 'display_order');
]
}
The following example shows how photos will be ordered in their respective galleries.
// Create the galleries to use
$photo_gallery = new PhotoGallery();
$photo_gallery->setName('Gallery 1');
$photo_gallery->store();
$photo_gallery2 = new PhotoGallery();
$photo_gallery2->setName('Gallery 2');
$photo_gallery2->store();
// This photo will have its display order set to 1
$photo = new Photo();
$photo->setPhotoGallery('Gallery 1');
$photo->setPhoto($photo_path);
$photo->store();
// This photo will have its display order set to 1 also, but as part of Gallery 2
$photo2 = new Photo();
$photo2->setPhotoGallery('Gallery 2');
$photo2->setPhoto($photo_path);
$photo2->store();
// This photo will have its display order set to 2
$photo3 = new Photo();
$photo3->setPhotoGallery('Gallery 1');
$photo3->setPhoto($photo_path);
$photo3->store();
If the gallery was changed for $photo
to Gallery 2
, it would be moved out of the display order of Gallery 1
and added to the end of the display order for Gallery 2
.
$photo->setPhotoGallery('Gallery 2');
$photo->store();
// This would output 2 since it was added at the end of Gallery 2
echo $photo->getDisplayOrder();
Allows a column in an fActiveRecord class to be a relative sort order column
1.0.0b19 | Updated code to work with the new fORM API 8/6/10 |
---|---|
1.0.0b18 | Changed configureOrderingColumn() to ensure the column specified can store negative values 7/21/10 |
1.0.0b17 | Changed validation messages array to use column name keys 5/26/10 |
1.0.0b16 | Updated the class to allow for multiple ordering columns per class 5/10/10 |
1.0.0b15 | Fixed a bug with ordering columns that are part of a multi-column unique constraint 11/13/09 |
1.0.0b14 | Fixed a bug affecting where conditions with columns that are not null but have a default value 11/3/09 |
1.0.0b13 | Updated code for the new fORMDatabase and fORMSchema APIs 10/28/09 |
1.0.0b12 | Changed SQL statements to use value placeholders, identifier escaping and schema support 10/22/09 |
1.0.0b11 | Fixed another bug with deleting records in the middle of a set, added support for reordering multiple records at once 7/17/09 |
1.0.0b10 | Fixed a bug with deleting multiple in-memory records in the same set 7/15/09 |
1.0.0b9 | Fixed a bug with using fORM::registerInspectCallback() 7/15/09 |
1.0.0b8 | Updated to use new fORM::registerInspectCallback() method 7/13/09 |
1.0.0b7 | Fixed validate() so it properly ignores ordering columns in multi-column unique constraints 6/17/09 |
1.0.0b6 | Updated code for new fORM API 6/15/09 |
1.0.0b5 | Updated class to automatically correct ordering values that are too high 6/14/09 |
1.0.0b4 | Updated code to use new fValidationException::formatField() method 6/4/09 |
1.0.0b3 | Fixed a bug with setting a new record to anywhere but the end of a set 3/18/09 |
1.0.0b2 | Fixed a bug with inspect(), 'max_ordering_value' was being returned as 'max_ordering_index' 3/2/09 |
1.0.0b | The initial implementation 6/25/08 |
Sets a column to be an ordering column
There can only be one ordering column per class/table and it must be part of a single or multi-column UNIQUE constraint.
void configureOrderingColumn( mixed $class, string $column )
mixed | $class | The class name or instance of the class |
string | $column | The column to set as an ordering column |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Re-orders other records in the set when the record specified is deleted
void delete( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Returns the metadata about a column including features added by this class
mixed inspect( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache, string $method_name, array $parameters )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
string | $method_name | The method that was called |
array | $parameters | The parameters passed to the method |
The metadata array or element specified
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Adjusts the fActiveRecord::reflect() signatures of columns that have been configured in this class
void reflect( string $class, array &$signatures, boolean $include_doc_comments )
string | $class | The class to reflect |
array | &$signatures | The associative array of {method name} => {signature} |
boolean | $include_doc_comments | If doc comments should be included with the signature |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Re-orders the object based on it's current state and new position
void reorder( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Resets the configuration of the class
void reset( )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Makes sure the ordering value is sane, removes error messages about missing values
void validate( fActiveRecord $object, array &$values, array &$old_values, array &$related_records, array &$cache, array &$validation_messages )
fActiveRecord | $object | The fActiveRecord instance |
array | &$values | The current values |
array | &$old_values | The old values |
array | &$related_records | Any records related to this record |
array | &$cache | The cache array for the record |
array | &$validation_messages | An array of ordered validation messages |
The fORMRelated class is a built-in part of the ORM that provides related-record functionality for fActiveRecord classes.
When calling a build
method on an fActiveRecord object, by default no ORDER BY
clause is used to order the record set. The static method setOrderBys()
allows setting an fRecordSet::build() style order bys array for any records created through a build
method. The method requires four parameters, the $class
being configured, the $related_class
to apply the order bys to, the $order_bys
array and the $route
to the related class. The $route
parameter can be omitted if there is only one route.
The following example will cause related groups to be returned order alphabetically.
class User extends fActiveRecord
{
protected function configure()
{
fORMRelated::setOrderBys(
$this,
'Group',
array('groups.name' => 'asc')
);
}
}
// The groups record set created here will be ordered by name
$user = new User(1);
$groups = $user->buildGroups();
Record names are used in messaging related to records, as the fORM documentation discusses. Sometimes, however, a record will need to have a different name when presented in reference to another record. The static method overrideRelatedRecordName()
allows defining a custom name to use in the context of being a related record.
CREATE TABLE groups (
name VARCHAR(255) PRIMARY KEY
);
CREATE TABLE users (
user_id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL
);
CREATE TABLE members (
group VARCHAR(255) NOT NULL REFERENCES groups(name) ON DELETE CASCADE ON UPDATE CASCADE,
user_id INTEGER NOT NULL REFERENCES users(user_id) ON DELETE CASCADE,
PRIMARY KEY(group, user_id)
);
CREATE TABLE administrators(
group VARCHAR(255) NOT NULL REFERENCES groups(name) ON DELETE CASCADE ON UPDATE CASCADE,
user_id INTEGER NOT NULL REFERENCES users(user_id) ON DELETE CASCADE,
PRIMARY KEY(group, user_id)
);
The schema presented above related users
to groups
in two ways, as members
of the group, or as administrators
. If a validation rule was set up to require at least one user as a member and one user as an administrator, it wouldnt make sense if the error messages for both just referenced users.
overrideRelatedRecordName()
accepts four parameters, the $class
being configured, the $related_class
to set the name for, the $record_name
to set and optionally the $route
to the related class. The $route
is only required if there is more than one.
class Group extends fActiveRecord
{
protected function configure()
{
fORMRelated::overrideRelatedRecordName($this, 'User', 'Member', 'members');
fORMRelated::overrideRelatedRecordName($this, 'User', 'Administrator', 'administrators');
}
}
For one-to-many
relationships, it is possible to override the name given to the child record when being referenced in the validation message (or array). By default, the name of the record will be in the form Child Record #1
. The method registerValidationNameMethod()
allows setting a method to be called on a record to retrieve its name.
registerValidationNameMethod()
requires three parameters, the $class
, the $related_class
and the $method
to call on the related record. An optional fourth parameter, $route
, may be specified in there is more than one relationship route between $class
and $related_class
.
class User extends fActiveRecord
{
protected function configure()
{
fORMRelated::registerValidationNameMethod('User', 'UserPreference', 'makeValidationName');
}
}
class UserPreference extends fActiveRecord
{
public function makeValidationName()
{
return $this->getName() . ' Preference';
}
}
The registered method may accept an optional parameter, which will contain a one-based index of its position in the set of child records.
class UserPreference extends fActiveRecord
{
public function makeValidationName(number)
{
return 'Preference #' . $number;
}
}
Handles related record tasks for fActiveRecord classes
The functionality of this class only works with single-field FOREIGN KEY constraints.
1.0.0b44 | Added missing information for has and list methods to reflect() 9/7/11 |
---|---|
1.0.0b43 | Fixed some bugs in handling relationships between PHP 5.3 namespaced classes 5/26/11 |
1.0.0b42 | Fixed a bug with associateRecords() not associating record set via primary key 5/23/11 |
1.0.0b41 | Fixed a bug in generating errors messages for many-to-many relationships 3/7/11 |
1.0.0b40 | Updated getRelatedRecordName() to use fText if loaded 2/2/11 |
1.0.0b39 | Fixed a bug with validate() not properly removing validation messages about a related primary key value not being present yet, if the column and related column names were different 11/24/10 |
1.0.0b38 | Updated overrideRelatedRecordName() to prefix any namespace from $class to $related_class if not already present 11/24/10 |
1.0.0b37 | Fixed a documentation typo 11/4/10 |
1.0.0b36 | Fixed getPrimaryKeys() to not throw SQL exceptions 10/20/10 |
1.0.0b35 | Backwards Compatibility Break - changed the validation messages array to use nesting for child records 10/3/10 |
1.0.0b35 | Updated getPrimaryKeys() to always return primary keys in a consistent order when no order bys are specified 7/26/10 |
1.0.0b34 | Updated the class to work with fixes in fORMRelated 7/22/10 |
1.0.0b33 | Fixed the related table populate action to use the plural underscore_notation version of the related class name 7/8/10 |
1.0.0b32 | Backwards Compatibility Break - related table populate action now use the underscore_notation version of the class name instead of the related table name, allowing for related tables in non-standard schemas 6/23/10 |
1.0.0b31 | Fixed reflect() to properly show parameters for associate methods 6/8/10 |
1.0.0b30 | Fixed a bug where related record error messages could be overwritten if there were multiple related records with the same error 5/29/10 |
1.0.0b29 | Changed validation messages array to use column name keys 5/26/10 |
1.0.0b28 | Updated associateRecords() to accept just a single fActiveRecord 5/6/10 |
1.0.0b27 | Updated the class to force configure classes before peforming actions with them 3/30/10 |
1.0.0b26 | Fixed reflect() to show the proper return values for associate, link and populate methods 3/15/10 |
1.0.0b25 | Fixed a bug when storing a one-to-one related record with different column names on each end of the relationship 3/4/10 |
1.0.0b24 | Added the ability to associate a single record via primary key 3/3/10 |
1.0.0b23 | Fixed a column aliasing issue with SQLite 1/25/10 |
1.0.0b22 | Fixed a bug with associating a non-contiguous array of fActiveRecord objects 12/17/09 |
1.0.0b21 | Added support for the $force_cascade parameter of fActiveRecord::store(), added hasRecords() and fixed a bug with creating non-existent one-to-one related records 12/16/09 |
1.0.0b20 | Updated code for the new fORMDatabase and fORMSchema APIs 10/28/09 |
1.0.0b19 | Internal Backwards Compatibility Break - Added the $class parameter to storeManyToMany() - also fixed countRecords() to work across all databases, changed SQL statements to use value placeholders, identifier escaping and support schemas 10/22/09 |
1.0.0b18 | Fixed a bug in countRecords() that would occur when multiple routes existed to the table being counted 10/5/09 |
1.0.0b17 | Updated code for new fRecordSet API 9/16/09 |
1.0.0b16 | Fixed a bug with createRecord() not creating non-existent record when the related value is NULL 8/25/09 |
1.0.0b15 | Fixed a bug with createRecord() where foreign keys with a different column and related column name would not load properly 8/17/09 |
1.0.0b14 | Fixed a bug with createRecord() when a foreign key constraint is on a column other than the primary key 8/10/09 |
1.0.0b13 | setOrderBys() now (properly) only recognizes *-to-many relationships 7/31/09 |
1.0.0b12 | Changed how related record values are set and how related validation messages are ignored because of recursive relationships 7/29/09 |
1.0.0b11 | Fixed some bugs with one-to-one relationships 7/21/09 |
1.0.0b10 | Fixed a couple of bugs with validating related records 6/26/09 |
1.0.0b9 | Fixed a bug where store() would not save associations with no related records 6/23/09 |
1.0.0b8 | Changed associateRecords() to work for *-to-many instead of just many-to-many relationships 6/17/09 |
1.0.0b7 | Updated code for new fORM API, fixed API documentation bugs 6/15/09 |
1.0.0b6 | Updated code to use new fValidationException::formatField() method 6/4/09 |
1.0.0b5 | Added getPrimaryKeys() and setPrimaryKeys(), renamed setRecords() to setRecordSet() and tallyRecords() to setCount() 6/2/09 |
1.0.0b4 | Updated code to handle new association method for related records and new $related_records structure, added store() and validate() 6/2/09 |
1.0.0b3 | associateRecords() can now accept an array of records or primary keys instead of only an fRecordSet 6/1/09 |
1.0.0b2 | populateRecords() now accepts any input field keys instead of sequential ones starting from 0 5/3/09 |
1.0.0b | The initial implementation 12/30/07 |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Creates associations for one-to-one relationships
void associateRecord( string $class, array &$related_records, string $related_class, fActiveRecord|array|string|integer $record, string $route=NULL )
string | $class | The class to get the related values for |
array | &$related_records | The related records existing for the fActiveRecord class |
string | $related_class | The class we are associating with the current record |
fActiveRecord|array|string|integer | $record | The record (or primary key of the record) to be associated |
string | $route | The route to use between the current class and the related class |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Creates associations for *-to-many relationships
void associateRecords( string $class, array &$related_records, string $related_class, fRecordSet|array $records_to_associate, string $route=NULL )
string | $class | The class to get the related values for |
array | &$related_records | The related records existing for the fActiveRecord class |
string | $related_class | The class we are associating with the current record |
fRecordSet|array | $records_to_associate | An fRecordSet, an array or records, or an array of primary keys of the records to be associated |
string | $route | The route to use between the current class and the related class |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Builds a set of related records along a one-to-many or many-to-many relationship
fRecordSet buildRecords( string $class, array &$values, array &$related_records, string $related_class, string $route=NULL )
string | $class | The class to get the related values for |
array | &$values | The values for the fActiveRecord class |
array | &$related_records | The related records existing for the fActiveRecord class |
string | $related_class | The class that is related to the current record |
string | $route | The route to follow for the class specified |
A record set of the related records
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Counts the number of related one-to-many or many-to-many records
integer countRecords( string $class, array &$values, array &$related_records, string $related_class, string $route=NULL )
string | $class | The class to get the related values for |
array | &$values | The values for the fActiveRecord class |
array | &$related_records | The related records existing for the fActiveRecord class |
string | $related_class | The class that is related to the current record |
string | $route | The route to follow for the class specified |
The number of related records
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Builds the object for the related class specified
fActiveRecord createRecord( string $class, array $values, array &$related_records, string $related_class, string $route=NULL )
string | $class | The class to create the related record for |
array | $values | The values existing in the fActiveRecord class |
array | &$related_records | The related records for the record |
string | $related_class | The related class name |
string | $route | The route to the related class |
An instance of the class specified
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Figures out the first primary key column for a related class that is not the related column
string determineFirstPKColumn( string $class, string $related_class, string $route )
string | $class | The class name of the main class |
string | $related_class | The related class being filtered for |
string | $route | The route to the related class |
The first primary key column in the related class
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Figures out what filter to pass to fRequest::filter() for the specified related class
string determineRequestFilter( string $class, string $related_class, string $route )
string | $class | The class name of the main class |
string | $related_class | The related class being filtered for |
string | $route | The route to the related class |
The prefix to filter the request fields by
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Sets the related records for a *-to-many relationship to be associated upon fActiveRecord::store()
void flagForAssociation( string $class, array &$related_records, string $related_class, string $route=NULL )
string | $class | The class to associate the related records to |
array | &$related_records | The related records existing for the fActiveRecord class |
string | $related_class | The class we are associating with the current record |
string | $route | The route to use between the current class and the related class |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Gets the ordering to use when returning an fRecordSet of related objects
array getOrderBys( string $class, string $related_class, string $route )
string | $class | The class to get the order bys for |
string | $related_class | The related class the ordering rules apply to |
string | $route | The route to the related table, should be a column name in the current table or a join table name |
An array of the order bys - see fRecordSet::build() for format
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Gets the primary keys of the related records for *-to-many relationships
array getPrimaryKeys( string $class, array &$values, array &$related_records, string $related_class, string $route=NULL )
string | $class | The class to get the related primary keys for |
array | &$values | The values for the fActiveRecord class |
array | &$related_records | The related records existing for the fActiveRecord class |
string | $related_class | The class that is related to the current record |
string | $route | The route to follow for the class specified |
The primary keys of the related records
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Returns the record name for a related class
The default record name of a related class is the result of fGrammar::humanize() called on the class.
string getRelatedRecordName( string $class, string $related_class, $route=NULL )
string | $class | The class to get the related class name for |
string | $related_class | The related class to get the record name of |
$route |
The record name for the related class specified
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Indicates if a record has a one-to-one or any *-to-many related records
void hasRecords( string $class, array &$values, array &$related_records, string $related_class, string $route=NULL )
string | $class | The class to check related records for |
array | &$values | The values for the record we are checking |
array | &$related_records | The related records for the record we are checking |
string | $related_class | The related class we are checking for |
string | $route | The route to the related class |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Parses associations for many-to-many relationships from the page request
void linkRecords( string $class, array &$related_records, string $related_class, string $route=NULL )
string | $class | The class to get link the related records to |
array | &$related_records | The related records existing for the fActiveRecord class |
string | $related_class | The related class to populate |
string | $route | The route to the related class |
Allows overriding of default record names or related records
The default record name of a related record is the result of fGrammar::humanize() called on the class name.
void overrideRelatedRecordName( mixed $class, mixed $related_class, string $record_name, string $route=NULL )
mixed | $class | The class name or instance of the class to set the related record name for |
mixed | $related_class | The name of the related class, or an instance of it |
string | $record_name | The human version of the related record |
string | $route | The route to the related class |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Sets the values for records in a one-to-many relationship with this record
void populateRecords( string $class, array &$related_records, string $related_class, string $route=NULL )
string | $class | The class to populate the related records of |
array | &$related_records | The related records existing for the fActiveRecord class |
string | $related_class | The related class to populate |
string | $route | The route to the related class |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Adds information about methods provided by this class to fActiveRecord
void reflect( string $class, array &$signatures, boolean $include_doc_comments )
string | $class | The class to reflect the related record methods for |
array | &$signatures | The associative array of {method_name} => {signature} |
boolean | $include_doc_comments | If the doc block comments for each method should be included |
Registers a method to use to get a name for a related record when doing validation
void registerValidationNameMethod( string|fActiveRecord $class, string $related_class, string $method, string $route=NULL )
string|fActiveRecord | $class | The class to register the method for |
string | $related_class | The related class to register the method for |
string | $method | The method to be called on the related class that will return the name |
string | $route | The route to the related class |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Resets the configuration of the class
void reset( )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Records the number of related one-to-many or many-to-many records
void setCount( string $class, array &$related_records, string $related_class, integer $count, string $route=NULL, array &$values )
string | $class | The class to set the related records count for |
array | &$values | The values for the fActiveRecord class |
array | &$related_records | The related records existing for the fActiveRecord class |
string | $related_class | The class that is related to the current record |
integer | $count | The number of records |
string | $route | The route to follow for the class specified |
Sets the ordering to use when returning an fRecordSet of related objects
void setOrderBys( mixed $class, string $related_class, array $order_bys, string $route=NULL )
mixed | $class | The class name or instance of the class this ordering rule applies to |
string | $related_class | The related class we are getting info from |
array | $order_bys | An array of the order bys for this table.column combination - see fRecordSet::build() for format |
string | $route | The route to the related table, this should be a column name in the current table or a join table name |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Sets the related records for *-to-many relationships, providing only primary keys
void setPrimaryKeys( string $class, array &$related_records, string $related_class, array $primary_keys, string $route=NULL )
string | $class | The class to set the related primary keys for |
array | &$related_records | The related records existing for the fActiveRecord class |
string | $related_class | The class we are setting the records for |
array | $primary_keys | The records to set |
string | $route | The route to use between the current class and the related class |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Sets the related records for *-to-many relationships
void setRecordSet( string $class, array &$related_records, string $related_class, fRecordSet $records, string $route=NULL )
string | $class | The class to set the related records for |
array | &$related_records | The related records existing for the fActiveRecord class |
string | $related_class | The class we are associating with the current record |
fRecordSet | $records | The records are associating |
string | $route | The route to use between the current class and the related class |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Stores any many-to-many associations or any one-to-many records that have been flagged for association
void store( string $class, array &$values, array &$related_records, boolean $force_cascade )
string | $class | The class to store the related records for |
array | &$values | The current values for the main record being stored |
array | &$related_records | The related records array |
boolean | $force_cascade | This flag will be passed to the fActiveRecord::delete() method on related records that are being deleted |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Associates a set of many-to-many related records with the current record
void storeManyToMany( string $class, array &$values, array $relationship, array $related_info )
string | $class | The class the relationship is being stored for |
array | &$values | The current values for the main record being stored |
array | $relationship | The information about the relationship between this object and the records in the record set |
array | $related_info | An array containing the keys 'record_set', 'count', 'primary_keys' and 'associate' |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Stores a set of one-to-many related records in the database
void storeOneToStar( string $class, array &$values, array &$related_records, string $related_class, string $route, boolean $force_cascade )
string | $class | The class to store the related records for |
array | &$values | The current values for the main record being stored |
array | &$related_records | The related records array |
string | $related_class | The related class being stored |
string | $route | The route to the related class |
boolean | $force_cascade | This flag will be passed to the fActiveRecord::delete() method on related records that are being deleted |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Validates any many-to-many associations or any one-to-many records that have been flagged for association
void validate( string $class, array &$values, array &$related_records )
string | $class | The class to validate the related records for |
array | &$values | The values for the object |
array | &$related_records | The related records for the object |
The fORMSchema class provides database schema information to the Flourish ORM. For end-developers the class provides access to a single instance of the fSchema class and allows for simple caching of the schema data.
Since the Flourish ORM bases almost all of its functionality on the schema of the database connected to, an instance of the fSchema class is required for it to properly function. As long as an instance of fDatabase has been properly attached using fORMDatabase, an instance of fSchema will be created and automatically attached to fORMSchema.
If fSchema was extended or for some reason a custom instance needs to be attached, the static method attach()
will do that.
fORMSchema::attach(new SomeExtensionOfFSchema(fORMDatabase::retrieve()));
When writing custom code for the ORM, the fSchema singleton can be access by calling the static method retrieve()
.
$primary_keys = fORMSchema::retrieve()->getKeys('users', 'primary');
Provides fSchema class related functions for ORM code
1.0.0b9 | Enhanced various exception messages 9/19/10 |
---|---|
1.0.0b8 | Added 'one-to-one' support to getRouteNameFromRelationship(), '!many-to-one' to getRoute() 3/3/10 |
1.0.0b7 | Added support for multiple databases 10/28/09 |
1.0.0b6 | Internal Backwards Compatibility Break - Added the $schema parameter to the beginning of getRoute(), getRouteName(), getRoutes() and isOneToOne() - added '!many-to-one' relationship type handling 10/22/09 |
1.0.0b5 | Fixed some error messaging to not include {empty_string} in some situations 7/31/09 |
1.0.0b4 | Added isOneToOne() 7/21/09 |
1.0.0b3 | Added routes caching for performance 6/15/09 |
1.0.0b2 | Backwards Compatiblity Break - removed enableSmartCaching(), fORM::enableSchemaCaching() now provides equivalent functionality 5/4/09 |
1.0.0b | The initial implementation 6/14/07 |
Allows attaching an fSchema-compatible object as the schema singleton for ORM code
void attach( fSchema $schema, string $name='default' )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Returns information about the specified route
void getRoute( fSchema $schema, string $table, string $related_table, string $route, string $relationship_type=NULL )
fSchema | $schema | The schema object to get the route from |
string | $table | The main table we are searching on behalf of |
string | $related_table | The related table we are searching under |
string | $route | The route to get info about |
string | $relationship_type | The relationship type: NULL, '*-to-many', '*-to-one', '!many-to-one', 'one-to-one', 'one-to-meny', 'many-to-one', 'many-to-many' |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Returns the name of the only route from the specified table to one of its related tables
string getRouteName( fSchema $schema, string $table, string $related_table, string $route=NULL, string $relationship_type=NULL )
fSchema | $schema | The schema object to get the route name from |
string | $table | The main table we are searching on behalf of |
string | $related_table | The related table we are trying to find the routes for |
string | $route | The route that was preselected, will be verified if present |
string | $relationship_type | The relationship type: NULL, '*-to-many', '*-to-one', '!many-to-one', 'one-to-one', 'one-to-many', 'many-to-one', 'many-to-many' |
The only route from the main table to the related table
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Returns the name of the route specified by the relationship
string getRouteNameFromRelationship( string $type, array $relationship )
string | $type | The type of relationship: '*-to-one', 'one-to-one', 'one-to-many', 'many-to-one', 'many-to-many' |
array | $relationship | The relationship array from fSchema::getKeys() |
The name of the route
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Returns an array of all routes from a table to one of its related tables
array getRoutes( fSchema $schema, string $table, string $related_table, string $relationship_type=NULL )
fSchema | $schema | The schema object to get the routes for |
string | $table | The main table we are searching on behalf of |
string | $related_table | The related table we are trying to find the routes for |
string | $relationship_type | The relationship type: NULL, '*-to-many', '*-to-one', '!many-to-one', 'one-to-one', 'one-to-many', 'many-to-one', 'many-to-many' |
All of the routes from the main table to the related table
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Indicates if the relationship specified is a one-to-one relationship
boolean isOneToOne( fSchema $schema, string $table, string $related_table, string $route=NULL )
fSchema | $schema | The schema object the tables are from |
string | $table | The main table we are searching on behalf of |
string | $related_table | The related table we are trying to find the routes for |
string | $route | The route between the two tables |
If the table is in a one-to-one relationship with the related table over the route specified
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Resets the configuration of the class
void reset( )
The fORMValidation class is a built-in part of the ORM that provides validation functionality for fActiveRecord classes.
While most databases support a fairly broad set of restrictions on data format and validity, some validation tasks are too complicated or not possible with the standard databases. The fORMValidation class allows for supplementing the database schema with additional validation rules to help ensure valid data is being stored. These rules will be checked whenever fActiveRecord::validate() or fActiveRecord::store() is called.
By default, any database column that is set to NOT NULL
and does not have a DEFAULT
value, will be required. The static method addRequiredRule()
allow setting columns that do allow NULL
or have a default to also be required. It accepts a $class
and one or more $column
names.
class NewsArticle extends fActiveRecord
{
protected function configure()
{
// Makes the photo column required
fORMValidation::addRequiredRule($this, 'photo');
// Makes the publish_date and removal_date columns required
fORMValidation::addRequiredRule($this, array('publish_date', 'removal_date'));
}
}
The static method addConditionalRule()
allows adding a rule where a column can be required to be filled in based on the presence of a value in another column. addConditionalRule()
accepts four parameters, the $class
being configured, the (one or more) $main_columns
to trigger the rule, the optional $conditional_values
to trigger the rule, and the $conditional_columns
to require. If $conditional_values
is NULL
, the $conditional_columns
will be required if any value is entered into any of the $main_columns
, otherwise one of the values in $conditional_values
would need to be entered.
The following example will require a photo if the type is set to photo:
class NewsArticle extends fActiveRecord
{
protected function configure()
{
fORMValidation::addConditionalRule($this, 'type', 'Photo', 'photo');
}
}
This example show how to require all three of a set of columns must be filled in if any of them are filled in:
class Product extends fActiveRecord
{
protected function configure()
{
fORMValidation::addConditionalRule(
$this,
array('price', 'discount_price', 'member_price'),
NULL,
array('price', 'discount_price', 'member_price')
);
}
}
Many-to-many validations rules all requiring that a record have at least one of another type of record related to it in a many-to-many relationship. The static method addManyToManyRule()
accepts three parameters, the $class
to configure, the $related_class
to require and optionally the $route
to the $related_class
.
The following example will require that a user is associated with at least one group:
class User extends fActiveRecord
{
protected function configure()
{
fORMValidation::addManyToManyRule($this, 'Group');
}
}
One-to-many validations rules all requiring that a record have at least one of another type of record related to it in a one-to-many relationship. The static method addOneToManyRule()
accepts three parameters, the $class
to configure, the $related_class
to require and optionally the $route
to the $related_class
.
The following example will require that a survey question has at least one option to pick from:
class SurveyQuestion extends fActiveRecord
{
protected function configure()
{
fORMValidation::addOneToManyRule($this, 'SurveyQuestionOption');
}
}
In some situations it is necessary to require that a user fills in at least one of a group of columns. This can be accomplished by calling the static method addOneOrMoreRule()
. The method accepts two parameters, the $class
to configure and an array of $columns
to require one or more of.
The following example would require that at least one of photo, link and body were entered:
class NewsArticle extends fActiveRecord
{
protected function configure()
{
fORMValidation::addOneOrMoreRule(
$this,
array(
'photo',
'link',
'body'
)
);
}
}
When presenting multiple options to users, sometimes it is necessary to restrict the user to only be able to select one from a number of choices. The static method addOnlyOneRule()
accepts the $class
to configure, plus an array of the $columns
to restrict.
The following example would only allow the entry of one of photo, link and body:
class NewsArticle extends fActiveRecord
{
protected function configure()
{
fORMValidation::addOnlyOneRule(
$this,
array(
'photo',
'link',
'body'
)
);
}
}
The static method addRegexRule()
allow validating a value against a perl-compatible regular expression (PCRE). This method accepts the $class
and $column
to test, the $regex
to use and the $message
for when the regular expression does not match. The rule will not be checked if the value is NULL
.
The following example would allow the length only allow a number, followed by zero or more spaces and in
, inches
, cm
, m
, meters
, ft
or feet
:
class Box extends fActiveRecord
{
protected function configure()
{
fORMValidation::addRegexRule(
$this,
'length',
'#^\d+\s*(in(ches)?|cm|m(eters)?|ft|feet)$#',
'Please enter a length in cm, in, ft or m'
);
}
}
While it is possible to restrict the valid input to a column via a CHECK
constraint (or the MySQL ENUM
data type), there is also a method in fORMValidation to do the same thing. addValidValuesRule()
accepts the $class
to configure, the $column
to restrict the value of and an array of the $valid_values
. Please note that NULL
is always allowed as long as the column is not set as NOT NULL
.
The following example would only allow 'Active'
or 'Inactive'
in the status column:
class NewsArticle extends fActiveRecord
{
protected function configure()
{
fORMValidation::addValidValuesRule(
$this,
'status',
array(
'Active',
'Inactive'
)
);
}
}
The list of valid values can be retrieved by requesting the 'valid_values'
element during an inspect method call. This is also true if the valid values are defined in the database by a CHECK
constraint.
$valid_values = $news_article->inspectStatus('valid_values');
foreach ($valid_values as $valid_status) {
// ...
}
In addition to using the built-in validation rules, it is possible to do custom validation by using an ORM hook via fORM. Please see the Adding Functionality to fActiveRecord and Custom Validation Using a Hook sections for details and example code.
UNIQUE
constraints on databases are normally case sensitive, meaning that the values will@flourishlib.com
and Will@flourishlib.com
can both exist in a column with a UNIQUE
constraint. For most users this distinction is more confusing than useful. The static method setColumnCaseInsensitive()
restricts values to be unique in a case-insensitive manner. The method accepts two parameters, the $class
to configure and the $column
to treat as case-insensitive.
The following example will ensure that category names must have a unique spelling, not just unique case:
class Category extends fActiveRecord
{
protected function configure()
{
fORMValidation::setColumnCaseInsensitive($this, 'name');
}
}
When fActiveRecord::validate() finds one of more validation issues (either by being called explicitly, or through fActiveRecord::store()), the error messages are created in the order they are detected. The static method setMessageOrder()
accepts two parameters, the $class
to configure and an ordered array of $matches
to use to set the order of the error messages.
The $matches
array should contain strings that will be matched to the error messages, with the first array entry causing any matching error message to be first, the second array match to be next, and so forth. All matches are done in a case-insensitive manner.
The longest matches are made first to help prevent unintended substring matches. Any messages that dont match anything will be placed at the end of the exception message. Please note that there is no special format necessary to the match stringit can include any part of the message, including punctuation.
Below is an example that will ensure that the error messages are displayed with first name first, last name second and email last:
class User extends fActiveRecord
{
protected function configure()
{
fORMValidation::setMessageOrder(
$this,
array(
'First Name:',
'Last Name:',
'Email:'
)
);
}
}
There are two static methods to modifying validation messages, addStringReplacement()
and addRegexReplacement()
. Each accepts the $class
to apply the replacement to, the string/regex to $search
for, and the string to $replace
with. The search and replace action is performed on every validation message generated in the class, and is performed right before the messages are reordered.
addRegexReplacement()
accepts Perl-compatible regular expressions (PCRE) and thus the $replace
parameter needs to escape any literal $
or \
characters.
The following example shows how the text of an error message can be easily changed:
class User extends fActiveRecord
{
protected function configure()
{
fORMValidation::addStringReplacement($this, 'Favorite Genre: Please enter a value', 'Favorite Genre: Please check at least one');
}
}
Handles validation for fActiveRecord classes
1.0.0b32 | Fixed an array to string conversion notice 9/21/12 |
---|---|
1.0.0b31 | Fixed checkConditionalRule() to require columns that default to an empty string and are currently set to that value 6/14/11 |
1.0.0b30 | Fixed a bug with setMessageOrder() not accepting a variable number of parameters like fValidation::setMessageOrder() does 3/7/11 |
1.0.0b29 | Updated addManyToManyRule() and addOneToManyRule() to prefix any namespace from $class to $related_class if not already present 11/24/10 |
1.0.0b28 | Updated the class to work with the new nested array structure for validation messages 10/3/10 |
1.0.0b27 | Fixed hasValue() to properly detect zero-value floats, made hasValue() internal public 7/26/10 |
1.0.0b26 | Improved the error message for integers to say whole number instead of just number 5/29/10 |
1.0.0b25 | Added addRegexRule(), changed validation messages array to use column name keys 5/26/10 |
1.0.0b24 | Added addRequiredRule() for required columns that aren't automatically handled via schema detection 4/6/10 |
1.0.0b23 | Added support for checking integers and floats to ensure they fit within the range imposed by the database schema 3/17/10 |
1.0.0b22 | Made the value checking for one-or-more and only-one rules more robust when detecting the absence of a value 12/17/09 |
1.0.0b21 | Fixed a bug affecting where conditions with columns that are not null but have a default value 11/3/09 |
1.0.0b20 | Updated code for the new fORMDatabase and fORMSchema APIs 10/28/09 |
1.0.0b19 | Changed SQL statements to use value placeholders, identifier escaping and schema support 10/22/09 |
1.0.0b18 | Fixed checkOnlyOneRule() and checkOneOrMoreRule() to consider blank strings as NULL 8/21/09 |
1.0.0b17 | Added @internal methods removeStringReplacement() and removeRegexReplacement() 7/29/09 |
1.0.0b16 | Backwards Compatibility Break - renamed addConditionalValidationRule() to addConditionalRule(), addManyToManyValidationRule() to addManyToManyRule(), addOneOrMoreValidationRule() to addOneOrMoreRule(), addOneToManyValidationRule() to addOneToManyRule(), addOnlyOneValidationRule() to addOnlyOneRule(), addValidValuesValidationRule() to addValidValuesRule() 7/13/09 |
1.0.0b15 | Added addValidValuesValidationRule() [wb/jt, 2009-07-13] |
1.0.0b14 | Added addStringReplacement() and addRegexReplacement() for simple validation message modification 7/1/09 |
1.0.0b13 | Changed reorderMessages() to compare string in a case-insensitive manner 6/30/09 |
1.0.0b12 | Updated addConditionalValidationRule() to allow any number of $main_columns, and if any of those have a matching value, the condtional columns will be required 6/30/09 |
1.0.0b11 | Fixed a couple of bugs with validating related records 6/26/09 |
1.0.0b10 | Fixed UNIQUE constraint checking so it is only done once per constraint, fixed some UTF-8 case sensitivity issues 6/17/09 |
1.0.0b9 | Updated code for new fORM API 6/15/09 |
1.0.0b8 | Updated code to use new fValidationException::formatField() method 6/4/09 |
1.0.0b7 | Updated validateRelated() to use new fORMRelated::validate() method and checkRelatedOneOrMoreRule() to use new $related_records structure 6/2/09 |
1.0.0b6 | Changed date/time/timestamp checking from strtotime() to fDate/fTime/fTimestamp for better localization support 6/1/09 |
1.0.0b5 | Fixed a bug in checkOnlyOneRule() where no values would not be flagged as an error 4/23/09 |
1.0.0b4 | Fixed a bug in checkUniqueConstraints() related to case-insensitive columns 2/15/09 |
1.0.0b3 | Implemented proper fix for addManyToManyValidationRule() 12/12/08 |
1.0.0b2 | Fixed a bug with addManyToManyValidationRule() 12/8/08 |
1.0.0b | The initial implementation 8/4/07 |
Adds a conditional rule
If a non-empty value is found in one of the $main_columns, or if specified, a value from the $conditional_values array, all of the $conditional_columns will also be required to have a value.
void addConditionalRule( mixed $class, string|array $main_columns, mixed $conditional_values, string|array $conditional_columns )
mixed | $class | The class name or instance of the class this rule applies to |
string|array | $main_columns | The column(s) to check for a value |
mixed | $conditional_values | If NULL, any value in the main column will trigger the conditional column(s), otherwise the value must match this scalar value or be present in the array of values |
string|array | $conditional_columns | The column(s) that are to be required |
Add a many-to-many rule that requires at least one related record is associated with the current record
void addManyToManyRule( mixed $class, string $related_class, string $route=NULL )
mixed | $class | The class name or instance of the class to add the rule for |
string | $related_class | The name of the related class |
string | $route | The route to the related class |
Adds a one-or-more rule that requires at least one of the columns specified has a value
void addOneOrMoreRule( mixed $class, array $columns )
mixed | $class | The class name or instance of the class the columns exists in |
array | $columns | The columns to check |
Add a one-to-many rule that requires at least one related record is associated with the current record
void addOneToManyRule( mixed $class, string $related_class, string $route=NULL )
mixed | $class | The class name or instance of the class to add the rule for |
string | $related_class | The name of the related class |
string | $route | The route to the related class |
Add an only-one rule that requires exactly one of the columns must have a value
void addOnlyOneRule( mixed $class, array $columns )
mixed | $class | The class name or instance of the class the columns exists in |
array | $columns | The columns to check |
Adds a call to preg_replace() for each message
Regex replacement is done after the post::validate() hook, and right before the messages are reordered.
If a message is an empty string after replacement, it will be removed from the list of messages.
void addRegexReplacement( mixed $class, string $search, string $replace )
mixed | $class | The class name or instance of the class the columns exists in |
string | $search | The PCRE regex to search for - see http://php.net/pcre for details |
string | $replace | The string to replace with - all $ and \ are used in back references and must be escaped with a \ when meant literally |
Adds a rule to validate a column against a PCRE regular expression - the rule is not run if the value is NULL
void addRegexRule( mixed $class, string $column, string $regex, string $message )
mixed | $class | The class name or instance of the class the columns exists in |
string | $column | The column to match with the regex |
string | $regex | The PCRE regex to match against - see http://php.net/pcre for details |
string | $message | The message to use if the value does not match the regular expression |
Requires that a column have a non-NULL value
Before using this method, try setting the database column to NOT NULL and remove any default value. Such a configuration will trigger the same functionality as this method, and will enforce the rule on the database level for any other code that queries it.
void addRequiredRule( mixed $class, array $columns )
mixed | $class | The class name or instance of the class the column(s) exists in |
array | $columns | The column or columns to check - each column will require a value |
Adds a call to str_replace() for each message
String replacement is done after the post::validate() hook, and right before the messages are reordered.
If a message is an empty string after replacement, it will be removed from the list of messages.
void addStringReplacement( mixed $class, string $search, string $replace )
mixed | $class | The class name or instance of the class the columns exists in |
string | $search | The string to search for |
string | $replace | The string to replace with |
Restricts a column to having only a value from the list of valid values
Please note that NULL values are always allowed, even if not listed in the $valid_values array, if the column is not set as NOT NULL.
This functionality can also be accomplished by added a CHECK constraint on the column in the database, or using a MySQL ENUM data type.
void addValidValuesRule( mixed $class, string $column, array $valid_values )
mixed | $class | The class name or instance of the class this rule applies to |
string | $column | The column to validate |
array | $valid_values | The valid values to check - NULL values are always allows if the column is not set to NOT NULL |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Checks to see if a columns has a value, but based on the schema and if the column allows NULL
If the columns allows NULL values, than anything other than NULL will be returned as TRUE. If the column does not allow NULL and the value is anything other than the "empty" value for that data type, then TRUE will be returned.
The values that are considered "empty" for each data type are as follows. Please note that there is no "empty" value for dates, times or timestamps.
string hasValue( fSchema $schema, string $class, array &$values, $column, array $columns )
fSchema | $schema | The schema object for the table |
string | $class | The class the column is part of |
array | &$values | An associative array of all values for the record |
array | $columns | The column to check |
$column |
An error message for the rule
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Adds metadata about features added by this class
void inspect( string $class, string $column, array &$metadata )
string | $class | The class being inspected |
string | $column | The column being inspected |
array | &$metadata | The array of metadata about a column |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Removes a regex replacement
void removeRegexReplacement( mixed $class, string $search, string $replace )
mixed | $class | The class name or instance of the class the columns exists in |
string | $search | The string to search for |
string | $replace | The string to replace with |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Removes a string replacement
void removeStringReplacement( mixed $class, string $search, string $replace )
mixed | $class | The class name or instance of the class the columns exists in |
string | $search | The string to search for |
string | $replace | The string to replace with |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Reorders list items in an html string based on their contents
array reorderMessages( string $class, array $messages )
string | $class | The class to reorder messages for |
array | $messages | An array of the messages |
The reordered messages
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Takes a list of messages and performs string and regex replacements on them
array replaceMessages( string $class, array $messages )
string | $class | The class to reorder messages for |
array | $messages | The array of messages |
The new array of messages
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Resets the configuration of the class
void reset( )
Sets a column to be compared in a case-insensitive manner when checking UNIQUE and PRIMARY KEY constraints
void setColumnCaseInsensitive( mixed $class, string $column )
mixed | $class | The class name or instance of the class the column is located in |
string | $column | The column to set as case-insensitive |
Allows setting the order that the list items in a message will be displayed
All string comparisons during the reordering process are done in a case-insensitive manner.
void setMessageOrder( mixed $class, array $matches )
mixed | $class | The class name or an instance of the class to set the message order for |
array | $matches | This should be an ordered array of strings. If a line contains the string it will be displayed in the relative order it occurs in this array. |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Validates values for an fActiveRecord object against the database schema and any additional rules that have been added
array validate( fActiveRecord $object, array $values, array $old_values )
fActiveRecord | $object | The instance of the class to validate |
array | $values | The values to validate |
array | $old_values | The old values for the record |
An array of messages
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Validates related records for an fActiveRecord object
array validateRelated( fActiveRecord $object, array &$values, array &$related_records )
fActiveRecord | $object | The object to validate |
array | &$values | The values for the object |
array | &$related_records | The related records for the object |
An array of messages
fProgrammerException is a sub-class of fUnexpectedException that indicates the programmer has written invalid code that will not allow for further execution. This exception is one of the most prevalent in the Flourish code base and is used to indicate improper parameter values, incorrect code sequencing and similar errors.
This space intentionally left blank
An exception caused by programmer error
1.0.0b | The initial implementation 6/14/07 |
---|
Exception | --fException | --fUnexpectedException | --fProgrammerException
The fRecordSet class provides functionality to load sets of fActiveRecord objects from the database and manipulate them.
Since the class contains quite a number of features and handles finding data even with complex database schemas, having an example database schema to reference is important. The following database tables will be used as the basis for the examples on this page:
CREATE TABLE groups (
name VARCHAR(100) PRIMARY KEY
);
CREATE TABLE users (
user_id SERIAL PRIMARY KEY,
email VARCHAR(100) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL,
address VARCHAR(255) NOT NULL,
city VARCHAR(100) NOT NULL,
state VARCHAR(2) NOT NULL,
zip_code VARCHAR(10) NOT NULL,
date_created TIMESTAMP NOT NULL,
last_login TIMESTAMP NOT NULL,
status VARCHAR(20) NOT NULL CHECK(status IN ('Active', 'Inactive'))
);
CREATE TABLE users_groups (
group VARCHAR(100) NOT NULL REFERENCES groups(name) ON DELETE CASCADE,
user_id INTEGER NOT NULL REFERENCES users(user_id) ON DELETE CASCADE,
PRIMARY KEY (group, user_id)
);
CREATE TABLE favorites (
favorite_id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(user_id) ON DELETE CASCADE,
url VARCHAR(255) NOT NULL
);
CREATE TABLE resources (
resource_id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
owner INTEGER NOT NULL REFERENCES users(user_id) ON DELETE RESTRICT
);
CREATE TABLE read_permissions (
resource_id INTEGER NOT NULL REFERENCES resources(resource_id) ON DELETE CASCADE,
user_id INTEGER NOT NULL REFERENCES users(user_id) ON DELETE CASCADE,
PRIMARY KEY (resource_id, user_id)
);
Record sets can be created from simple condition arrays, SQL statements, or from an fActiveRecord class. Under the majority of circumstances, the shorthand condition array method provides for an efficient and expressive method to build a record set.
The static method build()
accepts between one and five parameters to build a record set. The only required parameter is the first, $class
, which specified the type of record to build. Passing on this parameter will cause all records of that type to be created in the set.
$users = fRecordSet::build('User');
The second parameter is the array of $where_conditions
. This parameter accepts an array that contains columns and operaters as the key and the value(s) to match as the value. A simple example of matching all users with a status of Active
is shown below:
$active_users = fRecordSet::build(
'User',
array('status=' => 'Active')
);
Each key => value
pair in the conditions array represents a single expression that will be joined by AND
logic to create there WHERE
clause. It is also possible, with slightly different syntax, to create simple OR
conditions and use aggregate functions in the HAVING
clause.
The column name can be any column in the record being created, or can be columns in related records. The following example would select users in a group with the name Administrators
:
$administrators = fRecordSet::build(
'User',
array('groups.name=' => 'Administrators')
);
It is also possible to specify columns in a table that is twice-removed from the record being created. The following example would create the groups that contain users who have a favorite including http://example.com
:
$groups = fRecordSet::build(
'Group',
array('users=>favorites.url=' => 'http://example.com')
);
When there is more than one relationship between two tables, the relationship route can be specified by putting it in {}
after the table name. For example, the following SQL would select all users who are the owner of a resource called money
:
$owners = fRecordSet::build(
'User',
array('resources{owner}.name=' => 'money')
);
All of the examples in the column specification section above use a simple equation operator, =
. The following is a list of all supported operators:
=
- equal!
- not equal<
- less than<=
- less than or equal to>
- greater than>=
- greater than or equal to~
- LIKE '%value%'
match!~
- NOT LIKE '%value%'
match^~
- LIKE 'value%'
match$~' -
LIKE '%value' match
All seven operators work with any single value, and will properly handle the SQL variations needed—such as
= becoming
IS for
NULL values. Below are some examples:
{{{
#!php
// All users with any name but John
$users = fRecordSet::build('User', array('first_name!' => 'John'));
// Any user created before 2008
$users = fRecordSet::build('User', array('date_created<' => '2008-01-01'));
// Any resource that include blue in the name
$resources = fRecordSet::build('Resource', array('name~' => 'blue'));
The =
, !
, ~
and !~
operators also support comparison with an array of values. The following example will find all users with the first name James
or John
:
$users = fRecordSet::build(
'User',
array('first_name=' => array('James', 'John'))
);
This example will return all users with a name like Joe
or Fred
:
$users = fRecordSet::build(
'User',
array('first_name~' => array('Joe', 'Fred'))
);
It is also possible to do ~
matching over multiple columns. If a single string value is set for such a condition, it will be parsed for individual words and quoted phrases. In addition, stop words (such as "the" and "a") will be removed from the parsed words and punctuation will be stripped from the beginning and end of each word parsed.
The following example would find any users that contained john
, smith
and west peabody
in any combination of the first_name
, last_name
, address
and city
columns:
$users = fRecordSet::build(
'User',
array('first_name|last_name|address|city~' => 'john "west peabody" smith.')
);
If another method of parsing search terms is required, it is also possible to specify the value of the conditions as an array of string to match. In this case no further parsing will be done.
There are two other operators available that work with multiple values, the &~
(AND LIKE) and ><
(intersection) operators.
&~
accepts two or more LIKE
patterns and requires that each values match every pattern. The example below would require that the persons email include both example.com
and john
:
$users = fRecordSet::build(
'User',
array('email&~' => array('example.com', 'john'))
);
The intersection operator works with ranges of values, so it is really only applicable to date/time and numeric fields. It requires two columns in the database table and two values to compare with. If the range of values in the two database columns in any way intersects the range between the two values specified, a match will be made.
For example, if there is an events table in the database that has a start and end date, it is possible to find any single or multi-day events that will occur during the next week with the following code:
$events = fRecordSet::build(
'Event',
array('start_date|end_date><' => array(new fDate(), new fDate('+7 days')))
);
While adding key => value
pairs to the conditions array always joins the conditions using AND
logic, it is possible to create simple OR
conditions too. If the array key is two or more column specifications with operators, separated by |
, and the value is an array of values equal in size to the number of column specifications, an OR
condition will be created.
The following example will return all users with the first name John
or that were created after January 1st, 2008:
$users = fRecordSet::build(
'User',
array('first_name=|date_created>' => array('John', '2008-01-01'))
);
These OR
conditions dont have any technical restrictions, however may become unwieldy to maintain if they are too complex.
Aggregate functions are supported in place of single columns for all operators in the conditions array. Currently the aggregate functions AVG()
, COUNT()
, MAX()
, MIN()
and SUM()
are supported. The following example will return all users who are part of two or more groups:
$users = fRecordSet::build(
'User',
array('count(groups.name)>=' => 2)
);
Conditions using aggregate functions will be automatically placed into the HAVING
clause of the query that is executed.
In addition to being able to compare columns to values, it is also possible to compare two columns with each other. These comparisons use a slight modified version of the standard operators, where a :
is appended.
=:
- compare equality of two columns!:
- compare inequality of two columns<:
- if the first column is less than the second<=:
- if the first column is less than or equal to the second>:
- if the first column is greater than the second>=:
- if the first column is greater than or equal to the second// Find users who have only ever logged in when they created their account
$users = fRecordSet::build(
'User',
array('date_created=:' => 'last_login')
);
It is also possible to use the column comparison operators with aggregate functions.
When building a record set from conditions, it is also possible to pass an array of $order_bys
to specify the order in which the records are returned. The $order_bys
parameter is an associative array of the column name, or expression, to order by as the key, and the direction to sort as the value.
The following example sort the users by their status, in an ascending manner:
$users = fRecordSet::build(
'User',
array('status=' => 'Active'),
array('status' => 'asc')
);
In addition to being able to sort by columns, it is also possible to sort by expressions, such as CASE
statements, and by aggregate functions, such as COUNT()
, on related tables. The following example sorts by the number of groups the user is part of in a descending manner:
$users = fRecordSet::build(
'User',
array('status=' => 'Active'),
array('count(groups.name)' => 'desc')
);
In certain situations it may be necessary to create a record set from record objects that have already been loaded from the database. The method buildFromArray()
requires two parameters, the $class
of record to create the set for and an array of $records
to put in the set.
$users = fRecordSet::buildFromArray('User', array($user1, $user2, $user3));
It is possible to pass information for use with pagination as the $total_records
, $limit
and $page
parameters. These values power the methods getLimit()
, getPage()
and getPages()
. The actual pagination of the records should either be done before the array is passed, or via the slice()
method.
$users = fRecordSet::buildFromArray(
'User',
array($user1, $user2, $user3),
8, // $total_records
3, // $limit
1 // $page
);
With buildFromArray()
it is possible to create a record set from two or more different types of records. To create such a set, the $class
parameter should be changed to an array containing each class name and the $records
array should contain the different objects.
Please note that creating a record set from more than one kind of record will disable certain manipulation methods, such as retrieving primary keys and preloading related data. The section on manipulation contains details about what methods and features are unavailable in such a circumstance.
$calendar_objects = fRecordSet::buildFromArray(
array('Event', 'Meeting'),
array($event1, $meeting1, $event2, $event3)
);
There are a number of limitations when using a conditions array to create a record set. Specific types of JOIN
operations, GROUP BY
clauses and OR
conditions are impossible, or can not be tailored to the situation. In such instances, the buildFromSQL()
method allows a raw Flourish SQL statement to be passed as the source for the records.
The first parameter is the $class
of records to create and the second is the $sql
to retrieve the records' data.
$users = fRecordSet::buildFromSQL(
'User',
"SELECT users.* FROM users WHERE (first_name = 'John' AND last_name = 'Smith') OR date_created < '2008-01-01'"
);
The SQL statement passed to buildFromSQL()
should select every column for the table for the record type specified. It will often also be desired to ensure that statements using JOIN
s along many-to-many relationships have an appropriate GROUP BY
clause to ensure that duplicate records are not returned.
If a LIMIT
clause is used in the SQL passed to buildFromSQL()
, it is recommended to pass a third parameter, $non_limited_count_sql
, containing a SQL query that will return the number of rows that would be returned if no LIMIT
clause was present. This additional SQL statement powers the functionality of ->count(TRUE)
, which counts the number of records that would be returned with no LIMIT
clause.
In addition to the $non_limited_count_sql
, the $limit
and $page
parameters should be passed to power the getLimit()
, getPage()
and getPages()
methods.
$users = fRecordSet::buildFromSQL(
'User',
"SELECT * FROM users LIMIT 5",
"SELECT count(*) FROM users",
5, // $limit
1 // $page
);
When using buildFromSQL()
, you will usually need to include one or more dynamic values. Instead of passing a SQL string to $sql
or $non_limited_count_sql
, an array may be passed that contains a SQL string plus any values to escape into it.
$users = fRecordSet::buildFromSQL(
'User',
array("SELECT * FROM users WHERE date_created > %d LIMIT %i", $start_date, 5),
array("SELECT count(*) FROM users WHERE date_created > %d", $start_date),
5, // $limit
1 // $page
);
Results from both call and map operations can be turned into record sets by the buildFromCall()
and buildFromMap()
methods. These methods take the exact same parameters as call()
and map()
, but take a resulting array of fActiveRecord objects and turn them into an fRecordSet.
// This creates a record set of the owners for a set of
// resources by calling createUser() on each resource
$owners = $resources->buildFromCall('createUser');
// This uses a function to create a record
function make_record($class, $primary_key)
{
return new $class($primary_key);
}
$owners = $resources->buildFromMap('make_record', 'User', '{record}::getOwner');
When two classes are related to each other through another table, building a record set from related records is often the easiest way to get what you are looking for. While is is possible to use buildFromCall()
with a createRelatedRecord()
method from the fActiveRecord class, there is a built-in dynamic build
method that does all of this for you.
// Creating owners manually
$owners = $resources->buildFromCall('createUser');
// Creating them from the dynamic build method
$owners = $resources->buildUsers();
The dynamic build
methods also take advantage of the preloading functionality to improve database performance.
There are a number of different ways that records can be retrieved from a record set.
One of the most basic operations for a record set is iteration. Just like an array, an fRecordSet can be used with a foreach
loop to access each record sequentially.
foreach ($records as $record) {
// Do something with the record
}
The method getRecord()
will return the record at the index specified. This is good for retrieving a single record out of the set, but will throw an fNoRemainingException if there is no record to fetch.
$first_record = fRecordSet::build('User')->getRecord(0);
It is also possible to use array-style referencing to accomplish the same result as getRecord()
.
$first_record = $record_set[0];
To retrieve an array of all records in the set, simply call the method getRecords()
.
$records = $record_set->getRecords();
In addition to retrieving the record from a record set, other information about the set and records is available.
The size of a record set can often be important since it affect if code can be executed or not. The count()
method gives a simple total containing the number of records in the set:
if ($records->count()) {
echo 'Records were found!';
}
In the situation that a record set is actually a LIMIT
ed number of records from the full query results, a single TRUE
value can be passed to count()
to retrieve the total number of records that exist.
echo $records->count() . ' records displayed, ' . $records->count(TRUE) . ' records total';
It is also possible to throw an exception if no records are contained in a set. The method tossIfEmpty()
will throw an fEmptySetException if called on a record set with zero records. By default the exception will contain a message indicating that no matching records could be found. It is possible to set a custom message by passing it as the first parameter to tossIfEmpty()
.
// Throw a general exception message if no records were found
try {
$records->tossIfEmpty()
} catch (fEmptySetException $e) {
$e->printMessage();
}
// Throw a specific exception message
try {
$records->tossIfEmpty('No active users could be found')
} catch (fEmptySetException $e) {
$e->printMessage();
}
If a limit was specified when calling build()
, it will be available from getLimit()
.
$limit = $record_set->getLimit();
If a limit was specified when calling build()
, it will be available from getPage()
.
$page = $record_set->getPage();
The number of pages will be available from getPages()
.
$total_pages = $record_set->getPages();
If only the primary keys of the records are needed, the method getPrimaryKeys()
does just that.
$primary_keys = $record_set->getPrimaryKeys();
The method getClass()
will return the class (or classes) of the record in the record set.
if ($record_set->getClass() == 'Example') {
// ...
}
Once a record set has been created, it can be manipulated in a number of different ways to retrieve information. A number of array-like functions are built into the class, and there is support for the map, reduce and filter operations common in functional programming.
The map, reduce and filter operations are declarative constructs that are common in functional programming. Map applies a function or method to a set of records and returns the results. Filter applies a function/method to each record and uses the return value to determine if a record should be removed. Reduce uses a function/method to convert all records into a single value via an iterative process of passing two values to the function at a time. Call is a feature implemented on fRecordSet that is not normally included with map, reduce and filter. Call allows a method to be called on every object in the record set, returning all of the values as an array.
PHP include a built-in array_map()
function that allows an array of values to be passed to a callback. This works great for callbacks that require only one parameter, however if two or more parameters are required then all parameters must be arrays of equal length. In practical terms, this requires heavy usage of array_fill()
and leads to code that is a pain to write and difficult to read.
The map()
method in fRecordSet provides some features to reduce the extra work. By default when calling map()
, each record will be passed to the callback as the first parameter.
function convert_records($record)
{
return new ArrayObject($record);
}
$array_objects = $record_set->map('convert_records');
If the callback takes more than one parameter, extra parameters can be passed to map()
. Any parameter that is not an array will automatically be converted, so there is no need to calls to array_fill()
. The example below would cause the $upper
parameter to be set to TRUE
for every record.
function camel_case($record, $upper)
{
return fGrammar::camelize($record->getName(), $upper);
}
$names = $record_set->map('camel_case', TRUE);
Another important feature is that the output of a method call to each record can be passed to the callback by passing a string such as '{record}::methodName'
. If this is the case, the record will not be automatically passed as the first parameter. The example below will cause the output of the getFirstName()
method to be passed to fUTF8::lower():
$names = $record_set->map('fUTF8::lower', '{record}::getFirstName');
It is also possible to pass the complete record (as opposed to just the output of a method) in a position other than the first parameter by passing '{record}'
.
function camel_case($upper, $record)
{
return fGrammar::camelize($record->getName(), $upper);
}
$names = $record_set->map('camel_case', TRUE, '{record}');
The reduce()
method accepts a callback that accepts two values and iteratively performs an operation on the result of the last operation plus the next record. It uses the same dynamics as the function array_reduce()
, except that the initial value can be of any data type.
The first call to the callback will pass NULL
as the first parameter and the first record as the second parameter. All subsequent calls will pass the result of the last call as the first parameter and the next record as the second parameter.
function add_products($tally, $next_record)
{
return $tally + $next_record->getPrice();
}
$sum = $record_set->reduce('add_products');
It is also possible to seed the operation with an initial value other than NULL
by passing it as the second parameter to reduce()
.
function concat_names($string, $next_record)
{
return $string . ' ' . $next_record->getName();
}
$names = $record_set->reduce('concat_names', 'Names:');
The filter()
method allows for creating a new record set by removing records from the existing set by checking with a callback, testing the result of a method call on the record, or by comparing with a conditions array. It functions almost identically to array_filter()
, except for supporting other methods of checking beyond a simple callback.
In the most basic form, a callback is passed and each record is passed one at a time to the callback. If the callback returns a value equal to FALSE
the record will not be copied to the new record set.
function check_name($record)
{
return (boolean) $record->getName();
}
$users_with_name = $record_set->filter('check_name');
It is also possible to filter a record set based on the return value of a method. This is accomplished by passing a string in the form '{record}::methodName'
. The example below will remove all users with no middle initial:
$users = $record_set->filter('{record}::getMiddleInitial');
As a final option, it is also possible to filter records based on whether or not they match all of the conditions in a conditions array. The conditions array should contain keys that are method names followed by a comparison operator and values to make the comparison with. Below is an example of checking to see if a users status is 'Active'
:
$active_users = $record_set->filter(array('getStatus=' => 'Active'));
The following operators are supported for values that are not arrays:
=
: If the output of the method equals the value!
: If the output of the method does not equal the value>
: If the output of the method is greater than the value>=
: If the output of the method is greater than or equal to the value<
: If the output of the method is less than the value<=
: If the output of the method id less than or equal to the value~
: If the output of the method contains the value (case insensitive)!~
: If the output of the method does not contain the value (case insensitive)If the value is an array, the following operators are supported:
=
: If the output of the method is contained in the array!
: If the output of the method is not contained in the array~
: If the output of the method contains one of the values in the array (case insensitive)!~
: If the output of the method contains none of the values in the array (case insensitive)&~
: If the output of the method contains all of the values in the array (case insensitive)
The following example will filter out any users who dont have the first name John
, who dont have the last name Smith
or Henry
or who were created on or before January 1st, 2008.
$users = $record_set->filter(
array(
'getFirstName=' => 'John',
'getLastName=' => array('Smith', 'Henry'),
'getDateCreated>' => '2008-01-01'
)
);
It is also possible to do a search through the output of multiple method using the ~
operator. Method names should be separated by the |
character.
If the value is a string, it will be parsed as search terms, which allows for quoted phrases, will removed punctuation from words and will ignore stop words (such as "the' and "a"). If the only words found are stop words, they will be included. If the value is an array, the strings in the array will be searched for.
The following example will find all users who have the words and phrases john
, smith
and west peabody
in the output of any combination of getFirstName()
, getLastName()
, getAddress()
and getCity()
:
$users = $record_set->filter(
array('getFirstName|getLastName|getAddress|getCity~' => 'John Smith "west peabody"')
);
It is possible to perform an OR
comparison using |
separated list of method names with operators and the value to be an array of values, with one for each method. For example, the following code will check if getFirstName()
returns John
or getEmailAddress()
contains smith.com
:
$users = $record_set->filter(
array('getFirstName=|getEmailAddress~' => array('John', 'smith.com'))
);
The last supported operator in the intersection operator ><
. This operator checks to see if two columns that form a range intersect in any way with two values that form a range. This is most useful for dates, but can be used with numbers too.
The following example checks to see if getStartDate()
and getEndDate()
form a date range that includes any days between January 1, 2010 and January 3, 2010:
$users = $record_set->filter(
array('getStartDate|getEndDate><' => array(new fDate('2010-01-01'), new fDate('2010-01-03')))
);
The optional boolean second parameter, $remember_original_count
, will save the number of records in the current record set as the non-limited count on the new set. See the section on Size for details about how to retrieve this number.
$users = $record_set->filter('{record}::getMiddleInitial', TRUE);
The call()
method of fRecordSet returns an array of the return values from a call to a method on each record in the set. The first parameter is a string with the method to call.
$first_names = $record_set->call('getFirstName');
It is also possible to pass parameters to the method by adding them to the call()
method. The following example will pass TRUE
to each call of the method prepareLink()
:
$links = $record_set->call('prepareLink', TRUE);
While the fRecordSet class uses a minimal number of database queries to fetch a set of records, in turn getting records related to each of those will cause at least one database query to be executed per record. The three actions prebuild
, precount
and precreate
all allow for running a single database query to fetch records related to every record in the set.
For example, if a record set of users needs to be displayed included how many comments they have left on a blog, the blog comments for each will need to be counted. Normally a call to countBlogComments()
be all that is necessary. However, if 100 users are displayed on a page then an additional 100 database queries would be performed. Calling precountBlogComments()
on the record set would cause a single database query to be executed to collect the counts for each user.
// Count related comments for ever user in a single query
$record_set->precountBlogComments();
The prebuild
method action would be appropriate to call when it is necessary to build a set of related records that are in a many-to-many or one-to-many.
$record_set->prebuildBlogComments();
foreach ($record_set as $record) {
$comments = $record_set->buildBlogComments();
}
The precreate
method action allows creating objects from a column that is part of a FOREIGN KEY
constraint.
$record_set->precreateStates();
foreach ($record_set as $record) {
echo $record->createState()->prepareName();
}
In a manner similar to arrays, an fRecord contains quite a number of methods to add, remove and change records in a record set.
slice()
The slice()
method takes up to two parameters, the zero-based $offset
to start slicing at and the $length
of a slice to make. If the $offset
is negative, the slice will start that many records from the end of the set. If the $length
is negative, the slice will stop that many records from the end of the set. If the $length
is omitted or NULL
, all records until the end of the set will be returned.
The following will create a new record set from the first three records.
$new_set = $record_set->slice(0, 3);
The optional boolean third parameter, $remember_original_count
, will save the number of records in the current record set as the non-limited count on the new set. See the section on Size for details about how to retrieve this number.
$new_set = $record_set->slice(0, 3, TRUE);
If $remember_original_count
is TRUE
and the slicing is done in such a way that a valid $limit
and $page
can be determined, they will be appropriately set on the new record set.
merge()
The merge()
method accepts a single parameter, the $records
, and returns a new record set containing all records from both. All of the records from the second record set will be found after the records from the first. The $records
parameter will accept an fRecordSet, an array of fActiveRecord objects or a single fActiveRecord.
Please note that it is possible to merge records sets of different types of records. If a record set contains records of more than one class, however, the prebuild
, precount
and precreate
method actions will be unavailable, along with the methods getPrimaryKeys()
, flagAssociate()
and isFlaggedForAssociation()
.
$events_and_meetings = $events->merge($meetings);
diff()
The diff()
method accepts a single parameter, the $records
to remove from the current record set. The $records
parameter will accept an fRecordSet, an array of fActiveRecord objects or a single fActiveRecord.
$active_events = $events->diff($inactive_events);
The optional boolean second parameter, $remember_original_count
, will save the number of records in the current record set as the non-limited count on the new set. See the section on Size for details about how to retrieve this number.
$active_events = $events->diff($inactive_events, TRUE);
intersect()
The intersect()
method accepts a single parameter, the $records
to create an intersection with the current record set. All records not in both will be removed. The $records
parameter will accept an fRecordSet, an array of fActiveRecord objects or a single fActiveRecord.
$our_free_days = $my_free_days->intersect($your_free_days);
The optional boolean second parameter, $remember_original_count
, will save the number of records in the current record set as the non-limited count on the new set. See the section on Size for details about how to retrieve this number.
$our_free_days = $my_free_days->intersect($your_free_days, TRUE);
unique()
The unique()
method takes the current record set and removes all duplicate records, returning a new record set.
$sessions = $sessions->unique();
The optional boolean parameter, $remember_original_count
, will save the number of records in the current record set as the non-limited count on the new set. See the section on Size for details about how to retrieve this number.
$sessions = $sessions->unique(TRUE);
For display purposes, it can be useful to segment an fRecordSet into multiple smaller fRecordSet objects. The chunk()
and split()
methods to exactly this.
chunk()
The chunk()
method accepts a $number
of records to place in each resulting fRecordSet. The returned value will be an array of fRecordSet objects that each contain $number
records, although the last one may have fewer if there are not enough to fill it.
// This statement will segment 10 users into 4 records sets containing 3, 3, 3 and 1 users respectively
$sets = $users->chunk(3);
split()
The split()
method accepts a $number
of fRecordSet objects to return. The returned value will be an array of fRecordSet objects that each contain ceil(total records/$number)
records, although the last set may have fewer if there are not enough to fill it.
// This statement will split 10 users into 3 records sets containing 4, 4 and 2 users respectively
$sets = $users->split(3);
The contains()
method accepts a single fActiveRecord record and checks if is is present in the record set.
if ($users->contains($user)) {
// ...
}
When building a record set from conditions, the records can be sorted by the $order_bys
parameter. See the section Ordering for more details.
Two methods are available with fRecordSet to reorder the records in the set after it has been created. The method sort()
accepts two parameters, the $method
to call to get the value to compare, and the $direction
to sort those values in. The $direction
can be either 'asc'
or 'desc'
. The sorting is done using fUTF8::inatcmp() for comparison. A new sorted fRecordSet object is returned.
$sorted_set = $record_set->sort('getName', 'asc');
If a different sorting method is required, the method sortByCallback()
will be of interest. This method requires a single parameter, a $callback
that accepts two records and returns a negative value if the first record is less than the second, 0
if they are equal, or a positive value if the first record is greater than the second.
function method_sort($record_a, $record_b)
{
return strnatcasecmp($record_a->getFirstName(), $record_b->getFirstName());
}
$sorted_set = $record_set->sortByCallback('method_sort');
A lightweight, iterable set of fActiveRecord-based objects
1.0.0b47 | Fixed the new version of precount() to work with tables having an explicit schema 9/21/12 |
---|---|
1,0,0b46 | Fixed a bug with precount() not working for self-joining tables 9/16/12 |
1.0.0b45 | Added support for the starts with like, ^~, and ends with like, $~, operators to both build() and filter() 6/20/11 |
1.0.0b44 | Backwards Compatibility Break - sort() and sortByCallback() now return a new fRecordSet instead of sorting the record set in place 6/20/11 |
1.0.0b43 | Added the ability to pass SQL and values to buildFromSQL(), added the ability to manually pass the $limit and $page to buildFromArray() and buildFromSQL(), changed slice() to remember $limit and $page if possible when $remember_original_count is TRUE 1/11/11 |
1.0.0b42 | Updated class to use fORM::getRelatedClass() 11/24/10 |
1.0.0b41 | Added support for PHP 5.3 namespaced fActiveRecord classes 11/11/10 |
1.0.0b40 | Added the tally() method 9/28/10 |
1.0.0b39 | Backwards Compatibility Break - removed the methods fetchRecord(), http://www.php.net/current, http://www.php.net/key, http://www.php.net/next, http://www.php.net/rewind and valid() and the Iterator interface - and the $pointer parameter for callbacks registered via fORM::registerRecordSetMethod() was replaced with the $method_name parameter - added the methods getIterator(), getLimit(), getPage(), getPages(), getRecord(), offsetExists(), offsetGet(), offsetSet() and offsetUnset() and the IteratorAggregate and ArrayAccess interfaces 9/28/10 |
1.0.0b38 | Updated code to work with the new fORM API 8/6/10 |
1.0.0b37 | Fixed a typo/bug in reduce() 6/30/10 |
1.0.0b36 | Replaced create_function() with a private method call 6/8/10 |
1.0.0b35 | Added the chunk() and split() methods 5/20/10 |
1.0.0b34 | Added an integer cast to count() to fix issues with the dblib MSSQL driver 4/9/10 |
1.0.0b33 | Updated the class to force configure classes before peforming actions with them 3/30/10 |
1.0.0b32 | Fixed a column aliasing issue with SQLite 1/25/10 |
1.0.0b31 | Added the ability to compare columns in build() with the =:, !:, <:, <=:, >: and >=: operators 12/8/09 |
1.0.0b30 | Fixed a bug affecting where conditions with columns that are not null but have a default value 11/3/09 |
1.0.0b29 | Updated code for the new fORMDatabase and fORMSchema APIs 10/28/09 |
1.0.0b28 | Fixed prebuild() and precount() to work across all databases, changed SQL statements to use value placeholders, identifier escaping and schema support 10/22/09 |
1.0.0b27 | Changed fRecordSet::build() to fix bad $page numbers instead of throwing an fProgrammerException 10/5/09 |
1.0.0b26 | Updated the documentation for build() and filter() to reflect new functionality 9/21/09 |
1.0.0b25 | Fixed map() to work with string-style static method callbacks in PHP 5.1 9/18/09 |
1.0.0b24 | Backwards Compatibility Break - renamed buildFromRecords() to buildFromArray(). Added buildFromCall(), buildFromMap() and ::build{RelatedRecords}() 9/16/09 |
1.0.0b23 | Added an extra parameter to diff(), filter(), intersect(), slice() and unique() to save the number of records in the current set as the non-limited count for the new set 9/15/09 |
1.0.0b22 | Changed __construct() to accept any Iterator instead of just an fResult object 8/12/09 |
1.0.0b21 | Added performance tweaks to prebuild() and precreate() 7/31/09 |
1.0.0b20 | Changed the class to implement Countable, making the count() function work 7/29/09 |
1.0.0b19 | Fixed bugs with diff() and intersect() and empty record sets 7/29/09 |
1.0.0b18 | Added method chaining support to prebuild, precount and precreate methods 7/15/09 |
1.0.0b17 | Changed __call() to pass the parameters to the callback 7/14/09 |
1.0.0b16 | Updated documentation for the intersection operator >< 7/13/09 |
1.0.0b15 | Added the methods diff() and intersect() 7/13/09 |
1.0.0b14 | Added the methods contains() and unique() 7/9/09 |
1.0.0b13 | Added documentation to build() about the intersection operator >< 7/9/09 |
1.0.0b12 | Added documentation to build() about the AND LIKE operator &~ 7/9/09 |
1.0.0b11 | Added documentation to build() about the NOT LIKE operator !~ 7/8/09 |
1.0.0b10 | Moved the private method checkConditions() to fActiveRecord::checkConditions() 7/8/09 |
1.0.0b9 | Changed build() to only fall back to ordering by primary keys if one exists 6/26/09 |
1.0.0b8 | Updated merge() to accept arrays of fActiveRecords or a single fActiveRecord in addition to an fRecordSet 6/2/09 |
1.0.0b7 | Backwards Compatibility Break - Removed flagAssociate() and isFlaggedForAssociation(), callbacks registered via fORM::registerRecordSetMethod() no longer receive the $associate parameter 6/2/09 |
1.0.0b6 | Changed tossIfEmpty() to return the record set to allow for method chaining 5/18/09 |
1.0.0b5 | build() now allows NULL for $where_conditions and $order_bys, added a check to the SQL passed to buildFromSQL() 5/3/09 |
1.0.0b4 | __call() was changed to prevent exceptions coming from fGrammar when an unknown method is called 3/27/09 |
1.0.0b3 | sort() and sortByCallback() now return the record set to allow for method chaining 3/23/09 |
1.0.0b2 | Added support for != and <> to build() and filter() 12/4/08 |
1.0.0b | The initial implementation 8/4/07 |
Creates an fRecordSet by specifying the class to create plus the where conditions and order by rules
The where conditions array can contain key => value entries in any of the following formats:
'column=' => VALUE, // column = VALUE
'column!' => VALUE // column <> VALUE
'column!=' => VALUE // column <> VALUE
'column<>' => VALUE // column <> VALUE
'column~' => VALUE // column LIKE '%VALUE%'
'column^~' => VALUE // column LIKE 'VALUE%'
'column$~' => VALUE // column LIKE '%VALUE'
'column!~' => VALUE // column NOT LIKE '%VALUE%'
'column<' => VALUE // column < VALUE
'column<=' => VALUE // column <= VALUE
'column>' => VALUE // column > VALUE
'column>=' => VALUE // column >= VALUE
'column=:' => 'other_column' // column = other_column
'column!:' => 'other_column' // column <> other_column
'column!=:' => 'other_column' // column <> other_column
'column<>:' => 'other_column' // column <> other_column
'column<:' => 'other_column' // column < other_column
'column<=:' => 'other_column' // column <= other_column
'column>:' => 'other_column' // column > other_column
'column>=:' => 'other_column' // column >= other_column
'column=' => array(VALUE, VALUE2, ... ) // column IN (VALUE, VALUE2, ... )
'column!' => array(VALUE, VALUE2, ... ) // column NOT IN (VALUE, VALUE2, ... )
'column!=' => array(VALUE, VALUE2, ... ) // column NOT IN (VALUE, VALUE2, ... )
'column<>' => array(VALUE, VALUE2, ... ) // column NOT IN (VALUE, VALUE2, ... )
'column~' => array(VALUE, VALUE2, ... ) // (column LIKE '%VALUE%' OR column LIKE '%VALUE2%' OR column ... )
'column^~' => array(VALUE, VALUE2, ... ) // (column LIKE 'VALUE%' OR column LIKE 'VALUE2%' OR column ... )
'column$~' => array(VALUE, VALUE2, ... ) // (column LIKE '%VALUE' OR column LIKE '%VALUE2' OR column ... )
'column&~' => array(VALUE, VALUE2, ... ) // (column LIKE '%VALUE%' AND column LIKE '%VALUE2%' AND column ... )
'column!~' => array(VALUE, VALUE2, ... ) // (column NOT LIKE '%VALUE%' AND column NOT LIKE '%VALUE2%' AND column ... )
'column!|column2<|column3=' => array(VALUE, VALUE2, VALUE3) // (column <> '%VALUE%' OR column2 < '%VALUE2%' OR column3 = '%VALUE3%')
'column|column2><' => array(VALUE, VALUE2) // WHEN VALUE === NULL: ((column2 IS NULL AND column = VALUE) OR (column2 IS NOT NULL AND column <= VALUE AND column2 >= VALUE))
// WHEN VALUE !== NULL: ((column <= VALUE AND column2 >= VALUE) OR (column >= VALUE AND column <= VALUE2))
'column|column2|column3~' => VALUE // (column LIKE '%VALUE%' OR column2 LIKE '%VALUE%' OR column3 LIKE '%VALUE%')
'column|column2|column3~' => array(VALUE, VALUE2, ... ) // ((column LIKE '%VALUE%' OR column2 LIKE '%VALUE%' OR column3 LIKE '%VALUE%') AND (column LIKE '%VALUE2%' OR column2 LIKE '%VALUE2%' OR column3 LIKE '%VALUE2%') AND ... )
When creating a condition in the form column|column2|column3~, if the value for the condition is a single string that contains spaces, the string will be parsed for search terms. The search term parsing will handle quoted phrases and normal words and will strip punctuation and stop words (such as "the" and "a").
The order bys array can contain key => value entries in any of the following formats:
'column' => 'asc' // 'first_name' => 'asc'
'column' => 'desc' // 'last_name' => 'desc'
'expression' => 'asc' // "CASE first_name WHEN 'smith' THEN 1 ELSE 2 END" => 'asc'
'expression' => 'desc' // "CASE first_name WHEN 'smith' THEN 1 ELSE 2 END" => 'desc'
The column in both the where conditions and order bys can be in any of the formats:
'column' // e.g. 'first_name'
'current_table.column' // e.g. 'users.first_name'
'related_table.column' // e.g. 'user_groups.name'
'related_table{route}.column' // e.g. 'user_groups{user_group_id}.name'
'related_table=>once_removed_related_table.column' // e.g. 'user_groups=>permissions.level'
'related_table{route}=>once_removed_related_table.column' // e.g. 'user_groups{user_group_id}=>permissions.level'
'related_table=>once_removed_related_table{route}.column' // e.g. 'user_groups=>permissions{read}.level'
'related_table{route}=>once_removed_related_table{route}.column' // e.g. 'user_groups{user_group_id}=>permissions{read}.level'
'column||other_column' // e.g. 'first_name||last_name' - this concatenates the column values
In addition to using plain column names for where conditions, it is also possible to pass an aggregate function wrapped around a column in place of a column name, but only for certain comparison types. Note that for column comparisons, the function may be placed on either column or both.
'function(column)=' => VALUE, // function(column) = VALUE
'function(column)!' => VALUE // function(column) <> VALUE
'function(column)!= => VALUE // function(column) <> VALUE
'function(column)<>' => VALUE // function(column) <> VALUE
'function(column)~' => VALUE // function(column) LIKE '%VALUE%'
'function(column)^~' => VALUE // function(column) LIKE 'VALUE%'
'function(column)$~' => VALUE // function(column) LIKE '%VALUE'
'function(column)!~' => VALUE // function(column) NOT LIKE '%VALUE%'
'function(column)<' => VALUE // function(column) < VALUE
'function(column)<=' => VALUE // function(column) <= VALUE
'function(column)>' => VALUE // function(column) > VALUE
'function(column)>=' => VALUE // function(column) >= VALUE
'function(column)=:' => 'other_column' // function(column) = other_column
'function(column)!:' => 'other_column' // function(column) <> other_column
'function(column)!=:' => 'other_column' // function(column) <> other_column
'function(column)<>:' => 'other_column' // function(column) <> other_column
'function(column)<:' => 'other_column' // function(column) < other_column
'function(column)<=:' => 'other_column' // function(column) <= other_column
'function(column)>:' => 'other_column' // function(column) > other_column
'function(column)>=:' => 'other_column' // function(column) >= other_column
'function(column)=' => array(VALUE, VALUE2, ... ) // function(column) IN (VALUE, VALUE2, ... )
'function(column)!' => array(VALUE, VALUE2, ... ) // function(column) NOT IN (VALUE, VALUE2, ... )
'function(column)!=' => array(VALUE, VALUE2, ... ) // function(column) NOT IN (VALUE, VALUE2, ... )
'function(column)<>' => array(VALUE, VALUE2, ... ) // function(column) NOT IN (VALUE, VALUE2, ... )
The aggregate functions AVG(), COUNT(), MAX(), MIN() and SUM() are supported across all database types.
Below is an example of using where conditions and order bys. Please note that values should not be escaped for the database, but should just be normal PHP values.
return fRecordSet::build(
'User',
array(
'first_name=' => 'John',
'status!' => 'Inactive',
'groups.group_id=' => 2
),
array(
'last_name' => 'asc',
'date_joined' => 'desc'
)
);
fRecordSet build( string $class, array $where_conditions=array(), array $order_bys=array(), integer $limit=NULL, integer $page=NULL )
string | $class | The class to create the fRecordSet of |
array | $where_conditions | The column => value comparisons for the WHERE clause |
array | $order_bys | The column => direction values to use for the ORDER BY clause |
integer | $limit | The number of records to fetch |
integer | $page | The page offset to use when limiting records |
A set of fActiveRecord objects
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Creates an fRecordSet from an array of records
fRecordSet buildFromArray( string|array $class, array $records, integer $total_records=NULL, integer $limit=NULL, integer $page=1 )
string|array | $class | The class or classes of the records |
array | $records | The records to create the set from, the order of the record set will be the same as the order of the array. |
integer | $total_records | The total number of records - this should only be provided if the array is a segment of a larger array - this is informational only and does not affect the array |
integer | $limit | The maximum number of records the array was limited to - this is informational only and does not affect the array |
integer | $page | The page of records the array is from - this is informational only and does not affect the array |
A set of fActiveRecord objects
Creates an fRecordSet from an SQL statement
The SQL statement should select all columns from a single table with a * pattern since that is what an fActiveRecord models. If any columns are left out or added, strange error may happen when loading or saving records.
Here is an example of an appropriate SQL statement:
SELECT users.* FROM users INNER JOIN groups ON users.group_id = groups.group_id WHERE groups.name = 'Public'
Here is an example of a SQL statement that will cause errors:
SELECT users.*, groups.name FROM users INNER JOIN groups ON users.group_id = groups.group_id WHERE groups.group_id = 2
The $non_limited_count_sql should only be passed when the $sql contains a LIMIT clause and should contain a count of the records when a LIMIT is not imposed.
Here is an example of a $sql statement with a LIMIT clause and a corresponding $non_limited_count_sql:
fRecordSet::buildFromSQL('User', 'SELECT * FROM users LIMIT 5', 'SELECT count(*) FROM users');
The $non_limited_count_sql is used when count() is called with TRUE passed as the parameter.
Both the $sql and $non_limited_count_sql can be passed as a string SQL statement, or an array containing a SQL statement and the values to escape into it:
fRecordSet::buildFromSQL(
'User',
array("SELECT * FROM users WHERE date_created > %d LIMIT %i OFFSET %i", $start_date, 10, 10*($page-1)),
array("SELECT * FROM users WHERE date_created > %d", $start_date),
10,
$page
)
fRecordSet buildFromSQL( string $class, string|array $sql, string|array $non_limited_count_sql=NULL, integer $limit=NULL, integer $page=1 )
string | $class | The class to create the fRecordSet of |
string|array | $sql | The SQL to create the set from, or an array of the SQL statement plus values to escape |
string|array | $non_limited_count_sql | An SQL statement, or an array of the SQL statement plus values to escape, to get the total number of rows that would have been returned if a LIMIT clause had not been used. Should only be passed if a LIMIT clause is used in $sql. |
integer | $limit | The number of records the SQL statement was limited to - this is information only and does not affect the SQL |
integer | $page | The page of records the SQL statement returned - this is information only and does not affect the SQL |
A set of fActiveRecord objects
Composes text using fText if loaded
string compose( string $message, mixed $component [, ... ] )
string | $message | The message to compose |
mixed | $component [, ... ] | A string or number to insert into the message |
The composed and possible translated message
Counts the number of records that match the conditions specified
integer tally( string $class, mixed $where_conditions=array() )
string | $class | The class of records to count |
mixed | $where_conditions | An array of where clause parameters in the same format as build() |
The number of records
Sets the contents of the set
fRecordSet __construct( string|array $class, Iterator|array $records=NULL, string|integer $non_limited_count=NULL, integer $limit=NULL, integer $page=1 )
string|array | $class | The type(s) of records the object will contain |
Iterator|array | $records | The Iterator object of the records to create or an array of records |
string|integer | $non_limited_count | An SQL statement to get the total number of records sans a LIMIT clause or a integer of the total number of records |
integer | $limit | The number of records the set was limited to |
integer | $page | The page of records that was built |
Allows for preloading various data related to the record set in single database queries, as opposed to one query per record
This method will handle methods in the format verbRelatedRecords() for the verbs build, prebuild, precount and precreate.
build calls create{RelatedClass}() on each record in the set and returns the result as a new record set. The relationship route can be passed as an optional parameter.
prebuild builds *-to-many record sets for all records in the record set. precount will count records in *-to-many record sets for every record in the record set. precreate will create a *-to-one record for every record in the record set.
void __call( string $method_name, string $parameters )
string | $method_name | The name of the method called |
string | $parameters | The parameters passed |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
All requests that hit this method should be requests for callbacks
callback __get( string $method )
string | $method | The method to create a callback for |
The callback for the method requested
Calls a specific method on each object, returning an fRecordSet of the results
fRecordSet buildFromCall( string $method, mixed $parameter [, ... ] )
string | $method | The method to call |
mixed | $parameter [, ... ] | A parameter to pass for each call to the method |
A set of records that resulted from calling the method
Maps each record in the set to a callback function, returning an fRecordSet of the results
fRecordSet buildFromMap( callback $callback, mixed $parameter [, ... ] )
callback | $callback | The callback to pass the values to |
mixed | $parameter [, ... ] | The parameter to pass to the callback - see method description for details |
A set of records that resulted from the mapping operation
Calls a specific method on each object, returning an array of the results
array call( string $method, mixed $parameter [, ... ] )
string | $method | The method to call |
mixed | $parameter [, ... ] | A parameter to pass for each call to the method |
An array the size of the record set with one result from each record/method
Chunks the record set into an array of fRecordSet objects
Each fRecordSet would contain $number records, except for the last, which will contain between 1 and $number records.
array chunk( integer $number )
integer | $number | The number of fActiveRecord objects to place in each fRecordSet |
An array of fRecordSet objects
Checks if the record set contains the record specified
boolean contains( fActiveRecord $record )
fActiveRecord | $record | The record to check, must exist in the database |
If the record specified is in this record set
Returns the number of records in the set
integer count( boolean $ignore_limit=FALSE )
boolean | $ignore_limit | If set to TRUE, this method will return the number of records that would be in the set if there was no LIMIT clause |
The number of records in the set
Removes all passed records from the current record set
fRecordSet diff( fRecordSet|array|fActiveRecord $records, boolean $remember_original_count=FALSE )
fRecordSet|array|fActiveRecord | $records | The record set, array of records, or record to remove from the current record set, all instances will be removed |
boolean | $remember_original_count | If the number of records in the current set should be saved as the non-limited count for the new set - the page will be reset to 1 either way |
The records not present in the passed records
Filters the records in the record set via a callback
The $callback parameter can be one of three different forms to filter the records in the set:
The conditions array can use one or more of the following key => value syntaxes to perform various comparisons. The array keys are method names followed by a comparison operator.
// The following forms work for any $value that is not an array
'methodName=' => $value // If the output is equal to $value
'methodName!' => $value // If the output is not equal to $value
'methodName!=' => $value // If the output is not equal to $value
'methodName<>' => $value // If the output is not equal to $value
'methodName<' => $value // If the output is less than $value
'methodName<=' => $value // If the output is less than or equal to $value
'methodName>' => $value // If the output is greater than $value
'methodName>=' => $value // If the output is greater than or equal to $value
'methodName~' => $value // If the output contains the $value (case insensitive)
'methodName^~' => $value // If the output starts with the $value (case insensitive)
'methodName$~' => $value // If the output ends with the $value (case insensitive)
'methodName!~' => $value // If the output does not contain the $value (case insensitive)
'methodName|methodName2|methodName3~' => $value // Parses $value as a search string and make sure each term is present in at least one output (case insensitive)
// The following forms work for any $array that is an array
'methodName=' => $array // If the output is equal to at least one value in $array
'methodName!' => $array // If the output is not equal to any value in $array
'methodName!=' => $array // If the output is not equal to any value in $array
'methodName<>' => $array // If the output is not equal to any value in $array
'methodName~' => $array // If the output contains one of the strings in $array (case insensitive)
'methodName^~' => $array // If the output starts with one of the strings in $array (case insensitive)
'methodName$~' => $array // If the output ends with one of the strings in $array (case insensitive)
'methodName!~' => $array // If the output contains none of the strings in $array (case insensitive)
'methodName&~' => $array // If the output contains all of the strings in $array (case insensitive)
'methodName|methodName2|methodName3~' => $array // If each value in the array is present in the output of at least one method (case insensitive)
// The following works for an equal number of methods and values in the array
'methodName!|methodName2<|methodName3=' => array($value, $value2, $value3) // An OR statement - one of the method to value comparisons must be TRUE
// The following accepts exactly two methods and two values, although the second value may be NULL
'methodName|methodName2><' => array($value, $value2) // If the range of values from the methods intersects the range of $value and $value2 - should be dates, times, timestamps or numbers
fRecordSet filter( callback|string|array $procedure, boolean $remember_original_count=FALSE )
callback|string|array | $procedure | The way in which to filter the records - see method description for possible forms |
boolean | $remember_original_count | If the number of records in the current set should be saved as the non-limited count for the new set - the page will be reset to 1 either way |
A new fRecordSet with the filtered records
Returns the class name of the record being stored
string|array getClass( )
The class name(s) of the records in the set
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Returns an iterator for the record set
This method is required by the IteratorAggregate interface.
ArrayIterator getIterator( )
An iterator for the record set
Returns the number of records the set was limited to
integer getLimit( )
The number of records the set was limited to
Returns the page of records this set represents
integer getPage( )
The page of records this set represents
Returns the number of pages of records exist for the limit used when creating this set
integer getPages( )
The number of pages of records that exist for the limit specified
Returns the primary keys for all of the records in the set
array getPrimaryKeys( )
The primary keys of all the records in the set
Returns the record at the zero-based index specified
fActiveRecord getRecord( integer $index )
integer | $index | The index of the record to return |
The record requested
Returns all of the records in the set
array getRecords( )
The records in the set
Returns all records in the current record set that are also present in the passed records
fRecordSet intersect( fRecordSet|array|fActiveRecord $records, boolean $remember_original_count=FALSE )
fRecordSet|array|fActiveRecord | $records | The record set, array of records, or record to create an intersection of with the current record set |
boolean | $remember_original_count | If the number of records in the current set should be saved as the non-limited count for the new set - the page will be reset to 1 either way |
The records present in the current record set that are also present in the passed records
Performs an array_map() on the record in the set
The record will be passed to the callback as the first parameter unless it's position is specified by the placeholder string '{record}'.
Additional parameters can be passed to the callback in one of two different ways:
If an array parameter is too long (more items than records in the set) it will be truncated. If an array parameter is too short (less items than records in the set) it will be padded with NULL values.
To allow passing the record as a specific parameter to the callback, a placeholder string '{record}' will be replaced with a the record. It is also possible to specify '{record}::methodName' to cause the output of a method from the record to be passed instead of the whole record.
It is also possible to pass the zero-based record index to the callback by passing a parameter that contains '{index}'.
array map( callback $callback, mixed $parameter [, ... ] )
callback | $callback | The callback to pass the values to |
mixed | $parameter [, ... ] | The parameter to pass to the callback - see method description for details |
An array of the results from the callback
Merges the record set with more records
fRecordSet merge( fRecordSet|array|fActiveRecord $records )
fRecordSet|array|fActiveRecord | $records | The record set, array of records, or record to merge with the current record set, duplicates will not be removed |
The merged record sets
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Checks to see if an offset exists
This method is required by the ArrayAccess interface.
boolean offsetExists( mixed $offset )
mixed | $offset | The offset to check |
If the offset exists
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Returns a record based on the offset
This method is required by the ArrayAccess interface.
fActiveRecord offsetGet( mixed $offset )
mixed | $offset | The offset of the record to get |
The requested record
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Prevents setting values to the record set
This method is required by the ArrayAccess interface.
void offsetSet( mixed $offset, mixed $value )
mixed | $offset | The offset to set |
mixed | $value | The value to set to the offset |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Prevents unsetting values from the record set
This method is required by the ArrayAccess interface.
void offsetUnset( mixed $offset )
mixed | $offset | The offset to unset |
Reduces the record set to a single value via a callback
The callback should take two parameters and return a single value:
function my_reduce($sum, $record)
{
return $sum + $record->getQuantity();
)
// For the first record, 0.0 will be passed as the $sum, then subsequent
// calls end up getting the return value of the last call to my_reduce()
$total_quantity = $record_set->reduce('my_reduce', 0.0);
mixed reduce( callback $callback, mixed $initial_value=NULL )
callback | $callback | The callback to pass the records to - see method description for details |
mixed | $initial_value | The initial value to seed reduce with |
The result of the reduce operation
Slices a section of records from the set and returns a new set containing those
fRecordSet slice( integer $offset, integer $length=NULL, boolean $remember_original_count=FALSE )
integer | $offset | The index to start at, negative indexes will slice that many records from the end |
integer | $length | The number of records to return, negative values will stop that many records before the end, NULL will return all records to the end of the set - if there are not enough records, less than $length will be returned |
boolean | $remember_original_count | If the number of records in the current set should be saved as the non-limited count for the new set - the page will be reset to 1 either way |
The new slice of records
Sorts the set by the return value of a method from the class created and rewind the interator
This methods uses fUTF8::inatcmp() to perform comparisons.
fRecordSet sort( string $method, string $direction )
string | $method | The method to call on each object to get the value to sort by |
string | $direction | Either 'asc' or 'desc' |
A new record set object, with the records sorted as requested
Sorts the set by passing the callback to usort() and rewinds the interator
fRecordSet sortByCallback( mixed $callback )
mixed | $callback | The function/method to pass to usort() |
A new record set object, with the records sorted as requested
Splits the record set into an array of fRecordSet objects
Each fRecordSet would contain ceil(number of records/$number) records, except for the last, which will contain between 1 and ceil(…) records.
array split( integer $number )
integer | $number | The number of fRecordSet objects to create |
An array of fRecordSet objects
Throws an fEmptySetException if the record set is empty
fRecordSet tossIfEmpty( string $message=NULL )
string | $message | The message to use for the exception if there are no records in this set |
The record set object, to allow for method chaining
Returns a new fRecordSet containing only unique records in the record set
fRecordSet unique( boolean $remember_original_count=FALSE )
boolean | $remember_original_count | If the number of records in the current set should be saved as the non-limited count for the new set - the page will be reset to 1 either way |
The new record set with only unique records
The fRequest class is a static class that allows access to data sent via GET
, POST
, PUT
and DELETE
HTTP requests. It also provides functionality to determine the HTTP method used and retrieve relevant Accept
and Accept-Language
values sent by the browser.
While fRequest does more than just get values, most developers will be primarily interested in the two methods get()
and getValid()
. Both methods pull values from the $_GET
and $_POST
superglobals and the php://input
stream (for PUT
and DELETE
requests).
The get()
method will pull a value, but you can also typecast the value and provide a default. The first parameter, $key
, specifies the key to get the value for. The second (optional) parameter, $cast_to
, will set the data type of the value. It accepts any type string that is valid for the PHP function settype()
. NULL
can be used if no type casting should happen.
The third (optional) parameter, $default_value
, specifies what should be returned if the key is not found in $_POST
, $_GET
or php://input
. The fourth (optional) parameter, $use_default_for_blank
, causes the $default
value to be returned if a blank string value was provided for the key, or the key is not found.
Here are some examples:
<?php
// The first_name field will be returned in whatever format was submitted - string or array
$first_name = fRequest::get('first_name');
// An array will always be returned, defaulting to an array of 1 if no value is provided
$group_ids = fRequest::get('group_ids', 'array', array(1));
// Integer return value - 1 will be returned if no value, but a blank value will be cast to 0
$user_id = fRequest::get('user_id', 'integer', 1);
// Integer return value - 1 will be returned if no value or a blank string is provided
$total_cost = fRequest::get('total_cost', 'integer', 1, TRUE);
?>
An important aspect of the get()
method is to use the $cast_to
parameter whenever possible. This helps to restrict data coming in and is part of creating an effective solution against cross-site scripting.
// IDs that can only ever be integers should be cast to integers
$blog_id = fRequest::get('blog_id', 'integer');
Currently the following data types are supported:
When accepting information from the user, different data types have to be handled differently. Of special note are arrays, binary, booleans, dates/times/timestamps, integers and strings.
For arrays, PHP will automatically create an array when input field names end with []
. Thus if you wanted to capture an array of groups ids, you could create a checkbox for each group id and give each one the name group_ids[]
. When PHP parses the request data it will join all group_ids[]
values into an array and assign it to the key groups_ids
in the appropriate superglobal.
The array features mentioned above are built into PHP, however Flourish takes the array processing a step further. If a value is to be cast to an array, and is a string that contains commas, the string will be exploded on the comma character. Additionally, if an array contains a single entry of either a blank string or a NULL
value, Flourish will return an empty array. This prevents having to manually filter arrays for meaningless values.
See strict arrays to create a single-dimensional array of a specific data type.
Binary data does not have any modification made to it when it is returned. There is no guarantee what encoding it will be in, and it may even contain null bytes.
Booleans are interpreted from string values in as logical a way as possible. Empty values (according to PHPs empty()
function) and the strings 'f'
, 'false'
and 'no'
(case-insensitive) will result in a FALSE
value. This is obviously also the case if the key is not present in the request. Any other string value such as '1'
, 't'
, 'true'
, 'yes'
, etc. will be interpreted as a TRUE
value.
When the date
, time
and timestamp
data types are specified, the returned values will be fDate, fTime and fTimestamp objects, respectively.
When the integer
or int
type is specified, all values are cast to a real PHP integer, except for large integer values, which are kept as strings. Large integer values are value that can not be represented by a PHP integer. If a real PHP integer is always required, the integer!
type may be used instead. This type may cause the modification of some large integer values.
All strings that are passed through the get()
method are expected to be UTF-8 and any invalid UTF-8 characters are removed by passing the data through fUTF::clean(). In addition to invalid UTF-8 byte sequences, all low byte non-printable characters are removed. This is true even if a value is not explicitly cast to a string, or if the string is contained inside of an array. If you need raw data, use the binary type.
When typecasting values, it is sometimes useful for the absence of a value to return NULL
, while still casting all non-NULL
values. By adding a ?
to the end of the data type, NULL
will be returned if the key is not present in the request data, or if the value is an empty string ''
.
// Get an integer or NULL
$count = fRequest::get('count', 'integer?');
// Get an fDate object or NULL
$date = fRequest::get('date', 'date?');
The array
type ensures that the value returned will be an array containing string values, with an unlimited number of dimensions. It is also possible to add []
to the end of any other data type to return a single-dimensional array containing values of that type.
// Return a single dimensional array of integers
$ids = fRequest::get('ids', 'integer[]');
The method getValid()
simplifies the process a great deal by taking exactly two parameters, $key
, the key to request the value for and $valid_values
, an array of permissible values. If the value is not one of the valid value, the first valid value will be picked. Here is an example:
<?php
// $action will get the value 'list' if no value was present
$action = fRequest::getValid('action', array('list', 'search'));
?>
The static methods encode()
and prepare()
provide a simple short to calling get()
and then wrapping the result in fHTML::encode() or fHTML::prepare() respectively. These two methods have the exact same signature as get()
and don't perform any processing other than passing the resulting value to fHTML.
// Output a trusted value from the request
echo fRequest::prepare('name');
// Output an untrusted value from the request
echo fRequest::encode('name');
Just as with fHTML::prepare(), be sure to only ever use prepare()
if the user input can be trusted. prepare()
allows for HTML to be inserted into the page unescaped. If the input is untrusted, the encode()
method should be used instead.
Sometimes when dealing with different input types, it is useful to be able to alter or fix the request data. The method set()
allows a new value to be set to any key. It accepts two parameters, the $key
to set and the $value
to assign.
if ($datetime = fRequest::get('date_time')) {
fRequest::set('date', date('Y-m-d', strtotime($datetime)));
fRequest::set('time', date('g:ia', strtotime($datetime)));
}
When a request value is an array, it is possible to use array dereference syntax in the field name to access a specific array key. This syntax works with get()
, getValid()
, set()
, check()
, encode()
and prepare()
.
An input using array notation will automatically be converted to an array when PHP processes the request. This means the following inputs:
<input name="users[first_name]" value="John" />
<input name="users[last_name]" value="Smith" />
will cause the $_POST
superglobal to contain:
array(
'user' => array(
'first_name' => 'John',
'last_name' => 'Smith'
)
)
fRequest uses array dereference syntax that is identical to the <input>
tag syntax.
// This will echo John
echo fRequest::get('user[first_name]');
Array dereferencing can be any number of layers deep.
echo fRequest::get('user[groups][0][name]');
Cross-site request forgery (CSRF) is a technique where malicious websites will take advantage of the fact that a user has a valid session on a site, and will generate unauthorized requests on their behalf. These unauthorized requests can take the form of either GET
or POST
requests and can affect any page that does not validate the request properly.
GET
request exploits are very easy to implement by taking advantage of the src
attribute of various HTML tags. Imagine the HTML below living on http://example2.com
:
<img src="http://example.com/my_messages/delete_all.php" />
If the script located at /my_messages/delete_all.php
allowed a GET
request to cause all messages to delete and the user who visited example2.com
was logged into example.com
, all of their messages would be deleted.
POST
request exploits are slightly more difficult to take advantage of since a user will have to actually submit a form. Imagine the HTML below living on http://example2.com
:
<p>
Sign up for a free MP3 player!
</p>
<form action="http://example.com/my_messages/delete_all.php" method="post">
<p>
<label for="email">Your Email</label>
<input id="email" name="email" value="" />
</p>
<p>
<input type="submit" value="Sign Up!" />
</p>
</form>
This form would work even if the script requires a POST
request, however it requires convincing the user to submit a form or use javascript to automatically trigger it.
Obviously quite a number of things can help to prevent there CSRF attacks, however the best security is implemented by giving the user a single-use crytographically random token for them to resubmit with the request. This requires that the user request the page first (to get the token) and then resubmit it.
Since the attack relies on the user being logged in to a site in their browser, the attacking site will not be able to use a server-side request to retrieve the page, and thus the token. In addition, browsers prevent javascript from requesting pages across domains, so that precludes an attacking using it to retrieve the page and then resubmit.
The static method generateCSRFToken()
will create a single-use token to place into a form to protect against CSRF attacks. When processing a form, the static method validateCSRFToken()
should be used to ensure the form submission is valid by passing the $token
as the first parameter. If the token is not valid, an fValidationException will be thrown.
The following HTML form and corresponding PHP will ensure that only users who request the form will then be able to delete their messages.
<form action="" method="post">
<p>
<input type="submit" value="Yes, delete my messages!" />
<input type="hidden" name="request_token" value="<?php echo fRequest::generateCSRFToken() ?>" />
</p>
</form>
if (fRequest::isPost()) {
try {
fRequest::validateCSRFToken(fRequest::get('request_token'));
// Delete all of the users messages
} catch (fExpectedException $e) {
$e->printMessage();
}
}
By default both generateCSRFToken()
and validateCSRFToken()
will use the current page as the identifier to use when checking the token. If one page is submitting the value, but another is checking it, the $url
parameter of each method can be used.
<input type="hidden" name="request_token" value="<?php echo fRequest::generateCSRFToken('/processing_page.php') ?>" />
fRequest::validateCSRFToken(fRequest::get('request_token'), '/processing_page.php');
In addition, each URL can have multiple valid tokens at one time. This ensures that tabbed browsing will not cause the user to receive error messages about unauthenticated requests.
When creating RESTful applications it is useful to know the HTTP method used to request the current page. There are four methods to check each of the four HTTP methods:
HTTP Method | !fRequest Method |
GET | isGet() |
POST | isPost() |
PUT | isPut() |
DELETE | isDelete() |
HEAD | isHead() |
Since browsers only currently support GET
and POST
method, isPost()
will commonly be the only one used unless creating a RESTful API.
if (fRequest::isPost()) {
// Perform constructive/destructive action
}
It is also possible to see if a request was made via AJAX by using the static method isAjax()
. This checks the HTTP_X_REQUESTED_WITH
HTTP header to see if it is set to xmlhttprequest
.
if (fRequest::isAjax()) {
// Perform partial interaction
}
When building applications or sites that deliver different formats or translations of content, the HTTP Accept
and Accept-Language
headers include important information about what the client expects in response.
The Accept
header includes a list of acceptable mime-types that the client expects in return. In addition, the HTTP spec includes a q
value to indicate which mime-types are preferred over others. The Accept-Language
header include a similar list for the language of the response. Accept-Language
value can also include a q
value for each language.
The static methods getAcceptTypes()
and getAcceptLanguages()
will each return a list of the acceptable mime-types and languages, respectively, ordered by the q
values for each.
$mime_types = fRequest::getAcceptTypes();
$languages = fRequest::getAcceptLanguages();
If no accepts headers are sent by the client, an empty array will be returned.
It is also possible to provide a list of valid accept types or languages and pick the clients preferred one by using the methods getBestAcceptType()
and getBestAcceptLanguage()
.
Each method accepts a single parameter, an ordered array of the mime type or languages supported by your application. The type or language with the highest q
value for the client will be returned.
$mime_type = fRequest::getBestAcceptType(array(
'application/json',
'text/html'
));
$language = fRequest::getBestAcceptLanguage(array(
'en-us',
'fr-ca'
));
If no value in the array matches, FALSE
will be returned. If any value is acceptable, the first value in the array will be returned.
If no array of valid values is specified, and the client accepts any value, NULL
will be returned. Otherwise the value specified by the client with the highest q
will be returned.
There are sure to be times when a user is given multiple options when submitting a form and where you dont want to require the user to have javascript enabled in their browser. The overrideAction()
method allows for a form to have multiple submit buttons and for them to trigger different functionality.
This solution stems from the problem that the only way to tell which submit button was click is that buttons name and value pair is added to the GET
or POST
data, while all other submit buttons are ignored. Rather than requiring the developer to manually check for specific values when creating multiple submit buttons, overrideAction()
allows the name of a submit button to be set to action::{action_to_trigger}
.
If a submit button with a name as above is clicked and overrideAction()
is called in the destination page, the action
parameter of $_GET
or $_POST
will all be overridden with this action.
Here is an example:
<form action="<?php echo fURL::get() ?>" method="post">
...
<p>
<input type="hidden" name="action" value="delete" />
<input type="submit" value="Yes, delete this user" />
<input type="submit" name="action::list" value="No, keep this user" />
</p>
</form>
And the PHP to handle the form submission:
// We will call overrideAction() as the very first thing so everything else see the modified `$_POST` superglobal
fRequest::overrideAction();
// Get the action to execute
$action = fRequest::get('action');
If the user were to click Yes, delete this user
then the action delete
would be executed, however if the user clicked No, keep this user
then the list
action would be executed.
It is also possible to pass a redirection URL to overrideAction()
. If the string %action%
is found in the redirection URL, it will be replaced with the overridden action. Here is an example:
// Redirect the user to /admin/users/{action} if an overridden action is posted
fRequest::overrideAction('/admin/users/%action%');
Provides request-related methods
This class is implemented to use the UTF-8 character encoding. Please see UTF-8 for more information.
Please also note that using this class in a PUT or DELETE request will cause the php://input stream to be consumed, and thus no longer available.
1.0.0b20 | Added isHead(), fixed ability to call set() on HEAD requests 11/23/11 |
---|---|
1.0.0b19 | Added the $use_default_for_blank parameter to get() 6/3/11 |
1.0.0b18 | Backwards Compatibility Break - getBestAcceptType() and getBestAcceptLanguage() now return either NULL, FALSE or a string instead of NULL or a string, both methods are more robust in handling edge cases 2/6/11 |
1.0.0b17 | Fixed support for 3+ dimensional input arrays, added a fixed for the PHP DoS float bug #53632, added support for type-casted arrays in get() 1/9/11 |
1.0.0b16 | Backwards Compatibility Break - changed get() to remove binary characters when casting to a string, changed int and integer to cast to a real integer when possible, added new types of binary and integer! 11/30/10 |
1.0.0b15 | Added documentation about [sub-key] syntax, added [sub-key] support to check() 9/12/10 |
1.0.0b14 | Rewrote set() to not require recursion for array syntax 9/12/10 |
1.0.0b13 | Fixed set() to work with PUT requests 6/30/10 |
1.0.0b12 | Fixed a bug with getBestAcceptLanguage() returning the second-best language 5/27/10 |
1.0.0b11 | Added isAjax() 3/15/10 |
1.0.0b10 | Fixed get() to not truncate integers to the 32bit integer limit 3/5/10 |
1.0.0b9 | Updated class to use new fSession API 10/23/09 |
1.0.0b8 | Casting to an integer or string in get() now properly casts when the $key isn't present in the request, added support for date, time, timestamp and ? casts 8/25/09 |
1.0.0b7 | Fixed a bug with filter() not properly creating new $_FILES entries 7/2/09 |
1.0.0b6 | filter() now works with empty prefixes and filtering the $_FILES superglobal has been fixed 7/2/09 |
1.0.0b5 | Changed filter() so that it can be called multiple times for multi-level filtering 6/2/09 |
1.0.0b4 | Added the HTML escaping functions encode() and prepare() 5/27/09 |
1.0.0b3 | Updated class to use new fSession API 5/8/09 |
1.0.0b2 | Added generateCSRFToken() from fCRUDgenerateRequestToken() and validateCSRFToken() from fCRUD::validateRequestToken() 5/8/09 |
1.0.0b | The initial implementation 6/14/07 |
Indicated if the parameter specified is set in the $_GET or $_POST superglobals or in the post data of a PUT or DELETE request
boolean check( string $key )
string | $key | The key to check - array elements can be checked via [sub-key] syntax |
If the parameter is set
Gets a value from get() and passes it through fHTML::encode()
string encode( string $key, string $cast_to=NULL, mixed $default_value=NULL )
string | $key | The key to get the value of - array elements can be accessed via [sub-key] syntax |
string | $cast_to | Cast the value to this data type |
mixed | $default_value | If the parameter is not set in the DELETE/PUT post data, $_POST or $_GET, use this value instead |
The encoded value
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Parses through $_FILES, $_GET, $_POST and the PUT/DELETE post data and filters out everything that doesn't match the prefix and key, also removes the prefix from the field name
void filter( string $prefix, mixed $key )
string | $prefix | The prefix to filter by |
mixed | $key | If the field is an array, get the value corresponding to this key |
Returns a request token that should be placed in each HTML form to prevent cross-site request forgery
This method will return a random 15 character string that should be placed in a hidden input element on every HTML form. When the form contents are being processed, the token should be retrieved and passed into validateCSRFToken().
The value returned by this method is stored in the session and then checked by the validate method, which helps prevent cross site request forgeries and (naive) automated form submissions.
Tokens generated by this method are single use, so a user must request the page that generates the token at least once per submission.
string generateCSRFToken( string $url=NULL )
string | $url | The URL to generate a token for, default to the current page |
The token to be submitted with the form
Gets a value from the DELETE/PUT post data, $_POST or $_GET superglobals (in that order)
A value that exactly equals '' and is not cast to a specific type will become NULL.
Valid $cast_to types include:
It is possible to append a ? to a data type to return NULL whenever the $key was not specified in the request, or if the value was a blank string.
The array and unspecified $cast_to types allow for multi-dimensional arrays of string data. It is possible to cast an input value as a single-dimensional array of a specific type by appending [] to the $cast_to.
All string, array or unspecified $cast_to will result in the value(s) being interpreted as UTF-8 string and appropriately cleaned of invalid byte sequences. Also, all low-byte, non-printable characters will be stripped from the value. This includes all bytes less than the value of 32 (Space) other than Tab (\t), Newline (\n) and Cariage Return (\r).
To preserve low-byte, non-printable characters, or get the raw value without cleaning invalid UTF-8 byte sequences, plase use the value of binary for the $cast_to parameter.
Any integers that are beyond the range of 32bit storage will be returned as a string. The returned value can be forced to always be a real integer, which may cause truncation of the value, by passing integer! as the $cast_to.
mixed get( string $key, string $cast_to=NULL, mixed $default_value=NULL, boolean $use_default_for_blank=FALSE )
string | $key | The key to get the value of - array elements can be accessed via [sub-key] syntax |
string | $cast_to | Cast the value to this data type - see method description for details |
mixed | $default_value | If the parameter is not set in the DELETE/PUT post data, $_POST or $_GET, use this value instead. This value will get cast if a $cast_to is specified. |
boolean | $use_default_for_blank | If the request value is a blank string and $default_value is specified, this flag will cause the $default_value to be returned |
The value
Returns the HTTP Accept-Languages sorted by their q values (from high to low)
array getAcceptLanguages( )
An associative array of {language} => {q value} sorted (in a stable-fashion) from highest to lowest q - if no header was sent, an empty array will be returned
Returns the HTTP Accept types sorted by their q values (from high to low)
array getAcceptTypes( )
An associative array of {type} => {q value} sorted (in a stable-fashion) from highest to lowest q - if no header was sent, an empty array will be returned
Returns the best HTTP Accept-Language (based on q value) - can be filtered to only allow certain languages
Special conditions affecting the return value:
string|NULL|FALSE getBestAcceptLanguage( array $filter=array() )
string|NULL|FALSE getBestAcceptLanguage( string $language [, ... ] )
array | $filter | An array of languages that are valid to return - these should be in the form {language}-{territory}, e.g. en-us |
string | $language [, ... ] | A language that is valid to return |
The best language listed in the Accept-Language header - see method description for edge cases
Returns the best HTTP Accept type (based on q value) - can be filtered to only allow certain types
Special conditions affecting the return value:
string|NULL|FALSE getBestAcceptType( array $filter=array() )
string|NULL|FALSE getBestAcceptType( string $type [, ... ] )
array | $filter | An array of types that are valid to return |
string | $type [, ... ] | A type that is valid to return |
The best type listed in the Accept header - see method description for edge cases
Gets a value from the DELETE/PUT post data, $_POST or $_GET superglobals (in that order), restricting to a specific set of values
mixed getValid( string $key, array $valid_values )
string | $key | The key to get the value of - array elements can be accessed via [sub-key] syntax |
array | $valid_values | The array of values that are permissible, if one is not selected, picks first |
The value
Indicates if the URL was accessed via an XMLHttpRequest
boolean isAjax( )
If the URL was accessed via an XMLHttpRequest
Indicates if the URL was accessed via the DELETE HTTP method
boolean isDelete( )
If the URL was accessed via the DELETE HTTP method
Indicates if the URL was accessed via the GET HTTP method
boolean isGet( )
If the URL was accessed via the GET HTTP method
Indicates if the URL was accessed via the HEAD HTTP method
boolean isHead( )
If the URL was accessed via the HEAD HTTP method
Indicates if the URL was accessed via the POST HTTP method
boolean isPost( )
If the URL was accessed via the POST HTTP method
Indicates if the URL was accessed via the PUT HTTP method
boolean isPut( )
If the URL was accessed via the PUT HTTP method
Overrides the value of 'action' in the DELETE/PUT post data, $_POST or $_GET superglobals based on the 'action::{action_name}' value
This method is primarily intended to be used for hanlding multiple submit buttons.
void overrideAction( string $redirect=NULL )
string | $redirect | The url to redirect to if the action is overriden. %action% will be replaced with the overridden action. |
Gets a value from get() and passes it through fHTML::prepare()
string prepare( string $key, string $cast_to=NULL, mixed $default_value=NULL )
string | $key | The key to get the value of - array elements can be accessed via [sub-key] syntax |
string | $cast_to | Cast the value to this data type |
mixed | $default_value | If the parameter is not set in the DELETE/PUT post data, $_POST or $_GET, use this value instead |
The prepared value
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Resets the configuration and data of the class
void reset( )
Sets a value into the appropriate $_GET or $_POST superglobal, or the local PUT/DELETE post data based on what HTTP method was used for the request
void set( string $key, mixed $value )
string | $key | The key to set the value to - array elements can be modified via [sub-key] syntax |
mixed | $value | The value to set |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Returns $_GET, $_POST and $_FILES and the PUT/DELTE post data to the state they were at before filter() was called
void unfilter( )
Validates a request token generated by generateCSRFToken()
This method takes a request token and ensures it is valid, otherwise it will throw an fValidationException.
void validateCSRFToken( string $token, string $url=NULL )
string | $token | The request token to validate |
string | $url | The URL to validate the token for, default to the current page |
The fResult class is an iterable result object that is returned when an SQL query is executed. It contains all of the information about a query including any rows that may have been returned. This class uses result buffering to provide more features than fUnbufferedResult, albeit at the cost of extra memory usage proportional to amount of data returned.
The fResult class can be created by executing the methods fDatabase::query() or fDatabase::translatedQuery().
// Creation of fResult from a normal query
$result = $db->query("SELECT * FROM users");
// Creation of an fResult object from a translated query
$result2 = $db->translatedQuery("SELECT * FROM users LIMIT 5");
By default when retrieving rows from a result object, they are returned as associative arrays. In the case of Oracle databases, where columns are case-insensitive, the array keys are lowercase.
It is possible to retrieve stdClass
objects back for each row instead of associative arrays. This is done by calling the method asObjects()
. The method returns the fResult object, allowing for method chaining:
// Set the result to return objects
$res = $db->query("SELECT * FROM users")->asObjects();
If you are looking for objects with more functionality, please see fActiveRecord.
While it is certainly possible to manually unescape data from a result set, one row and column at a time, unescaping the whole result is often much easier. The method unescape()
accepts an associative array of the column name as the key and the data type as the value. This method should be called before any rows are fetched.
$result = $db->query("SELECT * FROM users");
$result->unescape(array(
'first_name' => 'string',
'last_name' => 'string',
'is_authenticated' => 'boolean',
'last_login' => 'timestamp'
));
foreach ($result as $row) {
// $row now contains the data in native PHP data types
}
The fResult class implements the Iterator interface, which means that you can use the foreach
construct to loop through all resulting rows. Below is an example of iterating over a result:
$result = $db->query("SELECT * FROM users");
foreach ($result as $row) {
echo $row['name'] . '<br />';
}
If the result did not return any rows, the foreach
construct will not be looped at all and the execution of the code will continue. If you wish to execute different code if no rows are returned, see the next section about exceptions.
If you wish to do manual iteration you can use the fetchRow()
and valid()
methods as shown below:
// Iteration using valid() and fetchRow()
while ($result->valid()) {
$row = $result->fetchRow();
}
Since an fResult object is a buffered result of a database call, it is possible to seek forwards and backwards in the set. The method seek()
accepts a single integer parameter $row
that will change the zero-based pointer in the result set.
// This would set the current row to be row #3
$result->seek(2);
In addition to allowing for iteration over the database query result, there are also two method that allow for simple data retrieval.
The method fetchScalar()
will return the first value from the first row of a result set. This is useful if it is known that a query will only return a single value. If no rows are returned, a fNoRowsException will be thrown.
$result = $db->query("SELECT count(*) FROM users");
$count = $result->fetchScalar();
While fetchScalar()
allows retrieving data in amounts less than a row, the method fetchAllRows()
does the opposite by allowing access to all rows at once. The return value of the method is an array of all row arrays.
$rows = $result->fetchAllRows();
As mentioned in the last section, if you iterate through the result and no rows are returned, nothing will happen. If you do need to execute different code when no rows are returned, you will want to call the tossIfNoRows()
to cause an fNoRowsException to be thrown:
try {
$result = $db->query("SELECT * FROM users");
$result->tossIfNoRows();
?>
<h1>Users</h1>
<?
foreach ($result as $row) {
echo $row['name'] . '<br />';
}
} catch (fNoRowsException $e) {
?>
<p>No users were found</p>
<?php
}
In addition to calling tossIfNoRows()
, an fNoRowsException will be thrown if any Iterator interface methods (such as current()
, next()
, etc), fetchRow()
or fetchAllRows()
are called on a result object which returned no rows.
Along with providing an iterable interface to a query result, the fResult class stores some information about the query including:
Method | Description |
countAffectedRows() |
Returns how many rows were affected by a DELETE , INSERT or UPDATE query |
countReturnedRows() |
Returns how many rows were returned by a SELECT query |
getAutoIncrementedValue() |
Returns the last value generated by an auto-incrementing integer field |
getSQL() |
Returns the SQL statement executed for this result |
getUntranslatedSQL() |
Returns the SQL from before translation happened - only applicable for results from translatedQuery() |
Representation of a result from a query against the fDatabase class
1.0.0b12 | Added a workaround for iconv having issues in MAMP 1.9.4+ 7/26/11 |
---|---|
1.0.0b12 | Fixed MSSQL to have a properly reset row array, added silenceNotices(), fixed pdo_dblib on Windows when using the Microsoft DBLib driver 5/9/11 |
1.0.0b11 | Backwards Compatibility Break - removed ODBC support 7/31/10 |
1.0.0b10 | Added IBM DB2 support 4/13/10 |
1.0.0b9 | Added support for prepared statements 3/2/10 |
1.0.0b8 | Fixed a bug with decoding MSSQL national column when using an ODBC connection 9/18/09 |
1.0.0b7 | Added the method unescape(), changed tossIfNoRows() to return the object for chaining 8/12/09 |
1.0.0b6 | Fixed a bug where fetchAllRows() would throw a fNoRowsException 6/30/09 |
1.0.0b5 | Added the method asObjects() to allow for returning objects instead of associative arrays 6/23/09 |
1.0.0b4 | Fixed a bug with not properly converting SQL Server text to UTF-8 6/18/09 |
1.0.0b3 | Added support for Oracle, various bug fixes 5/4/09 |
1.0.0b2 | Updated for new fCore API 2/16/09 |
1.0.0b | The initial implementation 9/25/07 |
Composes text using fText if loaded
string compose( string $message, mixed $component [, ... ] )
string | $message | The message to compose |
mixed | $component [, ... ] | A string or number to insert into the message |
The composed and possible translated message
Turns off notices about broken database extensions much as the MSSQL DBLib driver
void silenceNotices( )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Configures the result set
fResult __construct( fDatabase $database, string $character_set=NULL )
fDatabase | $database | The database object this result set was created from |
string | $character_set | MSSQL only: the character set to transcode from since MSSQL doesn't do UTF-8 |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Frees up the result object to save memory
void __destruct( )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
All requests that hit this method should be requests for callbacks
callback __get( string $method )
string | $method | The method to create a callback for |
The callback for the method requested
Sets the object to return rows as objects instead of associative arrays (the default)
fResult asObjects( )
The result object, to allow for method chaining
Returns the number of rows affected by the query
integer countAffectedRows( )
The number of rows affected by the query
Returns the number of rows returned by the query
integer countReturnedRows( )
The number of rows returned by the query
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Returns the current row in the result set (required by iterator interface)
array|stdClass current( )
The current row
Returns all of the rows from the result set
array fetchAllRows( )
The array of rows
Returns the row next row in the result set (where the pointer is currently assigned to)
array|stdClass fetchRow( )
The next row in the result
Wraps around fetchRow() and returns the first field from the row instead of the whole row
string|number|boolean fetchScalar( )
The first scalar value from fetchRow()
Returns the last auto incremented value for this database connection. This may or may not be from the current query.
integer getAutoIncrementedValue( )
The auto incremented value
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Returns the result
mixed getResult( )
The result of the query
Returns the SQL used in the query
string getSQL( )
The SQL used in the query
Returns the SQL as it was before translation
string getUntranslatedSQL( )
The SQL from before translation
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Returns the current row number (required by iterator interface)
integer key( )
The current row number
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Advances to the next row in the result (required by iterator interface)
void next( )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Rewinds the query (required by iterator interface)
void rewind( )
Seeks to the specified zero-based row for the specified SQL query
void seek( integer $row )
integer | $row | The row number to seek to (zero-based) |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Sets the number of affected rows
void setAffectedRows( integer $affected_rows )
integer | $affected_rows | The number of affected rows |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Sets the auto incremented value
void setAutoIncrementedValue( integer $auto_incremented_value )
integer | $auto_incremented_value | The auto incremented value |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Sets the result from the query
void setResult( mixed $result )
mixed | $result | The result from the query |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Sets the number of rows returned
void setReturnedRows( integer $returned_rows )
integer | $returned_rows | The number of rows returned |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Sets the SQL used in the query
void setSQL( string $sql )
string | $sql | The SQL used in the query |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Sets the SQL from before translation
void setUntranslatedSQL( string $untranslated_sql )
string | $untranslated_sql | The SQL from before translation |
Throws an fNoResultException if the query did not return any rows
fResult tossIfNoRows( string $message=NULL )
string | $message | The message to use for the exception if there are no rows in this result set |
The result object, to allow for method chaining
Sets the result object to unescape all values as they are retrieved from the object
The data types should be from the list of types supported by fDatabase::unescape().
fResult unescape( array $column_data_type_map )
array | $column_data_type_map | An associative array with column names as the keys and the data types as the values |
The result object, to allow for method chaining
Returns if the query has any rows left
boolean valid( )
If the iterator is still valid
The fSMTP class allows for sending emails with fEmail via an SMTP server. It supports multiple authentication methods, secure connections (including via STARTTLS
) and increased sending performance via SMTP pipelining.
Creating an SMTP connection can be as simple as passing the IP address or hostname of the $server
.
$smtp = new fSMTP('example.com');
In addition to the $server
, it is also possible to specify the $port
, if the connection is $secure
and the $timeout
.
// This sets up fSMTP to connect to the gmail SMTP server
// with a 5 second timeout. Gmail requires a secure connection.
$smtp = new fSMTP('smtp.gmail.com', 465, TRUE, 5);
Secure connections require the openssl extension. If a secure connection is not requested, but the SMTP server provides STARTTLS
functionality, fSMTP will automatically upgrade the connection to be secure.
fSMTP also supports SMTP authentication. Simple pass a $username
and $password
to authenticate()
. This should be called before trying to send any emails.
$smtp->authenticate('username', 'password');
Behind the scenes, there are four authentication methods supported: digest-md5, cram-md5, plain and login. The more secure digest-md5 and cram-md5 methods will be used if possible since they do not send login credentials in the clear.
Sending messages via fEmail and fSMTP is extremely simple, just pass the fSMTP object to fEmail::send():
$email->send($smtp);
The connection to the SMTP server will be automatically closed at the end of the script. If there is a need to close it before then, simply call close()
.
$smtp->close();
Creates a connection to an SMTP server to be used by fEmail
1.0.0b11 | Enhanced the error checking for write() 6/3/11 |
---|---|
1.0.0b10 | Added code to work around PHP bug #42682 (http://bugs.php.net/bug.php?id=42682) where stream_select() doesn't work on 64bit machines from PHP 5.2.0 to 5.2.5, improved timeouts while reading data 1/10/11 |
1.0.0b9 | Fixed a bug where lines starting with . and containing other content would have the . stripped 9/11/10 |
1.0.0b8 | Updated the class to use fEmail::getFQDN() 9/7/10 |
1.0.0b7 | Updated class to use new fCore::startErrorCapture() functionality 8/9/10 |
1.0.0b6 | Updated the class to use new fCore functionality 7/5/10 |
1.0.0b5 | Hacked around a bug in PHP 5.3 on Windows 6/22/10 |
1.0.0b4 | Updated the class to not connect and authenticate until a message is sent, moved message id generation in fEmail 5/5/10 |
1.0.0b3 | Fixed a bug with connecting to servers that send an initial response of 220- and instead of 220 4/26/10 |
1.0.0b2 | Fixed a bug where STARTTLS would not be triggered if it was last in the SMTP server's list of supported extensions 4/20/10 |
1.0.0b | The initial implementation 4/20/10 |
Configures the SMTP connection
The SMTP connection is only made once authentication is attempted or an email is sent.
Please note that this class will upgrade the connection to TLS via the SMTP STARTTLS command if possible, even if a secure connection was not requested. This helps to keep authentication information secure.
fSMTP __construct( string $host, integer $port=NULL, boolean $secure=FALSE, integer $timeout=NULL )
string | $host | The hostname or IP address to connect to |
integer | $port | The port number to use |
boolean | $secure | If the connection should be secure - if STARTTLS is available, the connection will be upgraded even if this is FALSE |
integer | $timeout | The timeout for the connection - defaults to the default_socket_timeout ini setting |
Closes the connection to the SMTP server
void __destruct( )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
All requests that hit this method should be requests for callbacks
callback __get( string $method )
string | $method | The method to create a callback for |
The callback for the method requested
Authenticates with the SMTP server
This method supports the digest-md5, cram-md5, login and plain SMTP authentication methods. This method will try to use the more secure digest-md5 and cram-md5 methods first since they do not send information in the clear.
void authenticate( string $username, string $password )
string | $username | The username |
string | $password | The password |
Closes the connection to the SMTP server
void close( )
Sets if debug messages should be shown
void enableDebugging( boolean $flag )
boolean | $flag | If debugging messages should be shown |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Sends a message via the SMTP server
void send( string $from [, ... ], string $headers, string $body )
string | $from | The email address being sent from - this will be used as the Return-Path header |
array | $to | All of the To, Cc and Bcc email addresses to send the message to - this does not affect the message headers in any way |
string | $headers | The message headers - the Bcc header will be removed if present |
string | $body | The mail body |
fSQLException is a sub-class of fUnexpectedException that indicates an error has occurred while executing an SQL query. This type of exception will not be tossed if a query returns no results, but would be tossed if there is a syntax error in the SQL statement. The fNoRowsException would be the type of exception tossed when an SQL query returns no rows.
This space intentionally left blank
An exception occurred while executing a SQL statement
1.0.0b | The initial implementation 6/14/07 |
---|
Exception | --fException | --fUnexpectedException | --fSQLException
The fSQLSchemaTranslation class is an internal class used by fSQLTranslation for translating Flourish SQL DDL statements, such as ALTER TABLE
and CREATE TABLE
, into the dialect of SQL supported by the current database. To take advantage of the features of this class, be sure to call translatedQuery()
instead of query()
.
fSQLSchemaTranslation is separate from fSQLTranslation due to the sheer amount of code necessary to implement ALTER TABLE
functionality that works consistently across databases. In addition, this functionality is not used as frequently as the DML statements supported by fSQLTranslation.
Adds cross-database CREATE TABLE, ALTER TABLE and COMMENT ON COLUMN statements to fSQLTranslation
1.0.0b3 | Fixed associating a sequence with a column in PostgreSQL when setting auto-increment, fixed detection of some Oracle CHECK(IN) constraints, fixed default values for SQLite ON DELETE and ON UPDATE clauses 1/12/12 |
---|---|
1.0.0b2 | Fixed detection of explicitly named SQLite foreign key constraints 8/23/11 |
1.0.0b | The initial implementation 5/9/11 |
Composes text using fText if loaded
string compose( string $message, mixed $component [, ... ] )
string | $message | The message to compose |
mixed | $component [, ... ] | A string or number to insert into the message |
The composed and possible translated message
Sets up the class
fSQLSchemaTranslation __construct( fDatabase $database )
fDatabase | $database | The database being translated for |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
All requests that hit this method should be requests for callbacks
callback __get( string $method )
string | $method | The method to create a callback for |
The callback for the method requested
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Translates a Flourish SQL DDL statement into the dialect for the current database
array translate( string $sql, array &$rollback_statements=NULL )
string | $sql | The SQL statement to translate |
array | &$rollback_statements | SQL statements to rollback the returned SQL statements if something goes wrong - only applicable for MySQL ALTER TABLE statements |
An array containing the translated $sql statement and an array of extra statements
The fSQLTranslation class is an internal class used by fDatabase for translating Flourish SQL into the dialect of SQL supported by the current database. To take advantage of the features of this class, be sure to call translatedQuery()
instead of query()
.
For more information about translatedQuery()
and query()
, please see the Queries section of the fDatabase page.
The method enableDebugging()
, accepts a single boolean parameter to turn debugging on or off. This method is automatically passed the same value as fDatabase::enableDebugging() when that is called.
When debugging is enabled, the class will print the original and translated queries, allowing developers to track down translation issues.
$sql_translation->enableDebugging(TRUE);
Part of the functionality that fSQLTranslation provides is to provide a way to get national character data (NCHAR, NVARCHAR and NTEXT columns) out of Microsoft SQL Server. Most of the MSSQL extensions for PHP do not properly retrieve national character data since it is encoded in UCS-2, which contains NULL
bytes. fSQLTranslation translates the SQL query to request data from such columns as binary data, which allows NULL
bytes, and then changes the encoding to UTF-8 before being returned to the developer.
Part of this process is to retrieve a list of all national character columns. The enableCaching()
method accepts an instance of the fCache class, and will save the appropriate database schema information so it does not need to be fetched on each page load.
$sql_translation->enableCaching(new fCache('file', '/path/to/cache/file'));
The method clearCache()
will clear out the cached information, which would be useful when the database schema changes.
When using the Flourish ORM, the fORM class provides some useful caching functionality that will automatically clear the cache when database errors occur.
Takes a subset of SQL from IBM DB2, MySQL, PostgreSQL, Oracle, SQLite and MSSQL and translates into the various dialects allowing for cross-database code
1.0.0b20 | Added fix for PostgreSQL to handle INSERT statements that don't specify any columns or values 9/6/11 |
---|---|
1.0.0b19 | Removed the stray method removeSQLiteIndexes() that was left over from moving code into fSQLSchemaTranslation 5/17/11 |
1.0.0b18 | Fixed LENGTH() and SUBSTR() functions for non-ascii characters being stored in MySQL, SQLite and DB2, moved CREATE TABLE support to fSQLSchemaTranslation 5/9/11 |
1.0.0b17 | Internal Backwards Compatiblity Break - changed the array keys for translated queries returned from translate() to include a number plus : before the original SQL, preventing duplicate keys 7/14/10 |
1.0.0b16 | Added IBM DB2 support 4/13/10 |
1.0.0b15 | Fixed a bug with MSSQL national character conversion when running a SQL statement with a sub-select containing joins 12/18/09 |
1.0.0b14 | Changed PostgreSQL to cast columns in LOWER() calls to VARCHAR to allow UUID columns (which are treated as a VARCHAR by fSchema) to work with default primary key ordering in fRecordSet 12/16/09 |
1.0.0b13 | Added a parameter to enableCaching() to provide a key token that will allow cached values to be shared between multiple databases with the same schema 10/28/09 |
1.0.0b12 | Backwards Compatibility Break - Removed date translation functionality, changed the signature of translate(), updated to support quoted identifiers, added support for PostgreSQL, MSSQL and Oracle schemas 10/22/09 |
1.0.0b11 | Fixed a bug with translating MSSQL national columns over an ODBC connection 9/18/09 |
1.0.0b10 | Changed last bug fix to support PHP 5.1.6 9/18/09 |
1.0.0b9 | Fixed another bug with parsing table aliases for MSSQL national columns 9/18/09 |
1.0.0b8 | Fixed a bug with parsing table aliases that occurs when handling MSSQL national columns 9/9/09 |
1.0.0b7 | Fixed a bug with translating NOT LIKE operators in PostgreSQL 7/15/09 |
1.0.0b6 | Changed replacement values in preg_replace() calls to be properly escaped 6/11/09 |
1.0.0b5 | Update code to only translate data types inside of CREATE TABLE queries 5/22/09 |
1.0.0b4 | Added the missing __get() method for callback support 5/6/09 |
1.0.0b3 | Added Oracle and caching support, various bug fixes 5/4/09 |
1.0.0b2 | Fixed a notice with SQLite foreign key constraints having no ON clauses 2/21/09 |
1.0.0b | The initial implementation 9/25/07 |
Composes text using fText if loaded
string compose( string $message, mixed $component [, ... ] )
string | $message | The message to compose |
mixed | $component [, ... ] | A string or number to insert into the message |
The composed and possible translated message
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Callback for custom SQLite function; calculates the cotangent of a number
numeric sqliteCotangent( numeric $x )
numeric | $x | The number to calculate the cotangent of |
The contangent of $x
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Callback for custom SQLite function; returns the current date
string sqliteDate( )
The current date
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Callback for custom SQLite length function; returns the number of UTF-8 characters in a string
string sqliteLength( string $string )
string | $string | The string to measure |
The current date
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Callback for custom SQLite function; calculates the log to a specific base of a number
numeric sqliteLogBaseFirst( integer $base, numeric $num )
integer | $base | The base for the log calculation |
numeric | $num | The number to calculate the logarithm of |
The logarithm of $num to $base
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Callback for custom SQLite function; returns the sign of the number
numeric sqliteSign( numeric $x )
numeric | $x | The number to change the sign of |
-1 if a negative sign, 0 if zero, 1 if positive sign
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Callback for custom SQLite function; creates a substring
string sqliteSubstr( string $string, integer $start, integer $length )
string | $string | The string to take a substring of |
integer | $start | The one-based position to start the substring at |
integer | $length | The length of the substring to take |
The substring
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Callback for custom SQLite function; returns the current time
string sqliteTime( )
The current time
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Callback for custom SQLite function; returns the current timestamp
string sqliteTimestamp( )
The current date
Sets up the class and creates functions for SQLite databases
fSQLTranslation __construct( fDatabase $database )
fDatabase | $database | The database being translated for |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
All requests that hit this method should be requests for callbacks
callback __get( string $method )
string | $method | The method to create a callback for |
The callback for the method requested
Clears all of the schema info out of the object and, if set, the fCache object
void clearCache( )
Sets the schema info to be cached to the fCache object specified
void enableCaching( fCache $cache, $key_token=NULL )
fCache | $cache | The cache to cache to |
$key_token |
Sets if debug messages should be shown
void enableDebugging( boolean $flag )
boolean | $flag | If debugging messages should be shown |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Translates Flourish SQL into the dialect for the current database
array translate( array $statements, array &$rollback_statements=NULL )
array | $statements | The SQL statements to translate |
array | &$rollback_statements | SQL statements to rollback the returned SQL statements if something goes wrong - only applicable for MySQL ALTER TABLE statements |
The translated SQL statements all ready for execution. Statements that have been translated will have string key of the number, : and the original SQL, all other will have a numeric key.
The fSchema class provides information about the structure of a database, from table and column info to keys and relationships. The database schema is converted to a standard format that can then be used by other code wishing to interact with the database.
The commands throughout this page are used to display information about database structure. Here are the CREATE TABLE
statements for the example database:
CREATE TABLE groups (
group_id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(100) NOT NULL UNIQUE
);
CREATE TABLE users (
user_id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(200) NOT NULL UNIQUE,
posts INTEGER NOT NULL DEFAULT 0,
birthday DATE,
status VARCHAR(20) NOT NULL DEFAULT 'Inactive' CHECK(status IN ('Active', 'Inactive')),
group_id INTEGER REFERENCES groups(group_id) ON DELETE CASCADE ON UPDATE CASCADE
);
The fSchema class simply requires and instance of the fDatabase class in order to be instantiated:
$db = new fDatabase('sqlite', $_SERVER['DOCUMENT_ROOT'] . '/example.db');
$schema = new fSchema($db);
One of the most basic tasks to perform with the fSchema class is to return a list of all tables in the database by using getTables()
:
$tables = $schema->getTables();
fCore::expose($tables);
The output of the above PHP would be:
<pre class="exposed">Array
(
[0] => groups
[1] => users
)</pre>
Column information can be returned for any table in the database using the getColumnInfo()
method. The returned associative array contains the following information about each column: type
, not_null
, default
, valid_values
, max_length
, min_value
, max_value
, decimal_places
and auto_increment
. For details about the returned array and the data type mapping that occurs, please view the documentation for getColumnInfo()
.
Here is an example:
$column_info = $schema->getColumnInfo('users');
fCore::expose($column_info);
The HTML output would be:
<pre class="exposed">Array
(
[user_id] => Array
(
[type] => integer
[not_null] => {true}
[auto_increment] => {true}
[default] => {null}
[valid_values] => {null}
[min_value] => -2147483648
[max_value] => 2147483647
[max_length] => {null}
[decimal_places] => {null}
[comment] => {empty_string}
)
[name] => Array
(
[type] => varchar
[max_length] => 200
[not_null] => {true}
[default] => {null}
[valid_values] => {null}
[min_value] => {null}
[max_value] => 2147483647
[decimal_places] => {null}
[auto_increment] => {false}
[comment] => {empty_string}
)
[posts] => Array
(
[type] => integer
[not_null] => {true}
[default] => 0
[valid_values] => {null}
[max_length] => {null}
[min_value] => -2147483648
[max_value] => 2147483647
[decimal_places] => {null}
[auto_increment] => {false}
[comment] => {empty_string}
)
[birthday] => Array
(
[type] => date
[not_null] => {false}
[default] => {null}
[valid_values] => {null}
[max_length] => {null}
[min_value] => {null}
[max_value] => {null}
[decimal_places] => {null}
[auto_increment] => {false}
[comment] => {empty_string}
)
[status] => Array
(
[type] => varchar
[max_length] => 20
[not_null] => {true}
[default] => Inactive
[valid_values] => Array
(
[0] => Active
[1] => Inactive
)
[min_value] => {null}
[max_value] => {null}
[decimal_places] => {null}
[auto_increment] => {false}
[comment] => {empty_string}
)
[group_id] => Array
(
[type] => integer
[not_null] => {false}
[default] => {null}
[valid_values] => {null}
[min_value] => -2147483648
[max_value] => 2147483647
[max_length] => {null}
[auto_increment] => {false}
[comment] => {empty_string}
)
)</pre>
In addition to determining basic information about table and columns in a database, fSchema can also detect all of the primary, foreign and unique keys in a database using the getKeys()
method.
Here is the PHP to get the key information:
$keys = $schema->getKeys('users');
fCore::expose($keys);
And here is the HTML that would be output:
<pre class="exposed">Array
(
[primary] => Array
(
[0] => user_id
)
[foreign] => Array
(
[0] => Array
(
[column] => group_id
[foreign_table] => groups
[foreign_column] => group_id
[on_delete] => cascade
[on_update] => cascade
)
)
[unique] => Array
(
[0] => Array
(
[0] => name
)
)
)</pre>
Key information for the database is useful, but another very useful bit of information is how the different tables in the database are related. getRelationships()
method uses the key information to determine the one-to-one
, one-to-many
, many-to-one
and many-to-many
relationships present:
$users_relationships = $schema->getRelationships('users');
fCore::expose($users_relationships);
$groups_relationships = $schema->getRelationships('groups');
fCore::expose($groups_relationships);
The output for the users
relationships would be:
<pre class="exposed">Array
(
[one-to-one] => Array
(
)
[many-to-one] => Array
(
[0] => Array
(
[column] => group_id
[related_table] => groups
[related_column] => group_id
)
)
[one-to-many] => Array
(
)
[many-to-many] => Array
(
)
)</pre>
While the output for the groups
relationships would be:
<pre class="exposed">Array
(
[one-to-one] => Array
(
)
[many-to-one] => Array
(
)
[one-to-many] => Array
(
[0] => Array
(
[column] => group_id
[related_table] => users
[related_column] => group_id
[on_delete] => cascade
[on_update] => cascade
)
)
[many-to-many] => Array
(
)
)</pre>
The fSchema class is used extensively by the object relational mapping code built into Flourish. Occasionally Flourish will support certain features based on database structure that are impossible to accomplish in a certain type of database.
To allow the object relational mapping code to still perform the necessary tasks, even if the database engine doesnt support a feature, the setColumnInfoOverride()
and setKeysOverride()
methods allow schema information to be overridden.
Please note that faking foreign-key relationships for MyISAM tables in MySQL may cause your data to get into an inconsistent state. This is because MyISAM tables do not support transactions, which Flourish uses for the purpose of atomic changes to the database across multiple tables at a time.
Below are some example tables to show how foreign keys can be faked to help provide relational data integrity. These examples are presented using MySQL.
CREATE TABLE users (
user_id INTEGER PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL,
email_address VARCHAR(200) NOT NULL UNIQUE,
hashed_password VARCHAR(100) NOT NULL
);
CREATE TABLE groups (
group_id INTEGER PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL UNIQUE,
group_leader INTEGER,
group_founder INTEGER
);
CREATE TABLE users_groups (
user_id INTEGER NOT NULL,
group_id INTEGER NOT NULL,
PRIMARY KEY(user_id, group_id)
);
The following PHP will create relationships between groups and users. There will not be any normal situations where you would want to override the primary or unique keys on a table. If you are using the ORM, this code should be executed before any fActiveRecord objects are used, such as in the bootstrap page, or something called from it.
The exact array structure to use with setKeysOverride()
can be found in the getKeys()
method documentation.
$schema = new fSchema($database);
// If we are using the ORM well want to attach this instance
fORMSchema::attach($schema);
// Set up the foreign keys from groups to users
$schema->setKeysOverride(
array(
array(
'column' => 'group_founder',
'foreign_table' => 'users',
'foreign_column' => 'user_id',
'on_delete' => 'cascade',
'on_update' => 'cascade'
),
array(
'column' => 'group_leader',
'foreign_table' => 'users',
'foreign_column' => 'user_id',
'on_delete' => 'cascade',
'on_update' => 'cascade'
)
),
'groups',
'foreign'
);
// Set up the keys for users_groups to create a join table
// for a many-to-many relationship between users and groups
$schema->setKeysOverride(
array(
array(
'column' => 'user_id',
'foreign_table' => 'users',
'foreign_column' => 'user_id',
'on_delete' => 'cascade',
'on_update' => 'cascade'
),
array(
'column' => 'group_id',
'foreign_table' => 'groups',
'foreign_column' => 'group_id',
'on_delete' => 'cascade',
'on_update' => 'cascade'
)
),
'users_groups',
'foreign'
);
Since the fSchema class executes a number of database calls to determine the structure of the database, it may be desirable to cache the information to reduce database load and script execution time. The method enableCaching()
accepts an instance of the fCache object and will use it to save all of the schema information.
$schema->enableCaching(new fCache('file', '/path/to/cache/file'));
The method clearCache()
will clear out the cached information, which would be useful when the database schema changes.
When using the Flourish ORM, the fORM class provides some useful caching functionality that will automatically clear the cache when database errors occur.
Gets schema information for the selected database
1.0.0b51 | Fixed handling of getting tables in table creation order when a table references itself, fixed default value detection for the last column in a MySQL table 1/12/12 |
---|---|
1.0.0b50 | Fixed detection of explicitly named SQLite foreign key constraints 8/23/11 |
1.0.0b49 | Added support for spatial/geometric data types in MySQL and PostgreSQL 5/26/11 |
1.0.0b48 | Fixed a bug with getTables() not working on MySQL 4.x, fixed getKeys() to always return a reset array 5/24/11 |
1.0.0b47 | Backwards Compatibility Break - getTables(), getColumnInfo(), getDatabases(), getKeys() and getRelationships() now return database, schema, table and column names in lowercase, added the $creation_order parameter to getTables(), fixed bugs with getting column and key information from MSSQL, Oracle and SQLite 5/9/11 |
1.0.0b46 | Enhanced SQLite schema detection to cover situations where UNIQUE constraints are defined separately from the table and when comments are used in CREATE TABLE statements 2/6/11 |
1.0.0b45 | Fixed Oracle auto incrementing detection to work with INSERT OR UPDATE triggers, fixed detection of dynamic default date/time/timestamp values for DB2 and Oracle 12/4/10 |
1.0.0b44 | Fixed the list of valid elements for getColumnInfo() 11/28/10 |
1.0.0b43 | Added the comment element to the information returned by getColumnInfo() 11/28/10 |
1.0.0b42 | Fixed a bug with MySQL detecting default ON DELETE clauses 10/19/10 |
1.0.0b41 | Fixed handling MySQL table names that require quoting 8/24/10 |
1.0.0b40 | Fixed bugs in the documentation and error message of getColumnInfo() about what are valid elements 7/21/10 |
1.0.0b39 | Fixed a regression where key detection SQL was not compatible with PostgreSQL 8.1 4/13/10 |
1.0.0b38 | Added Oracle support to getDatabases() 4/13/10 |
1.0.0b37 | Fixed getDatabases() for MSSQL 4/9/10 |
1.0.0b36 | Fixed PostgreSQL to properly report explicit NULL default values via getColumnInfo() 3/30/10 |
1.0.0b35 | Added max_length values for various text and blob data types across all databases 3/29/10 |
1.0.0b34 | Added min_value and max_value attributes to getColumnInfo() to specify the valid range for numeric columns 3/16/10 |
1.0.0b33 | Changed it so that PostgreSQL unique indexes containing functions are ignored since they can't be properly detected at this point 3/14/10 |
1.0.0b32 | Fixed getTables() to not include views for MySQL 3/14/10 |
1.0.0b31 | Fixed the creation of the default caching key for enableCaching() 3/2/10 |
1.0.0b30 | Fixed the class to work with lower privilege Oracle accounts and added detection of Oracle number columns 1/25/10 |
1.0.0b29 | Added on_delete and on_update elements to one-to-one relationship info retrieved by getRelationships() 12/16/09 |
1.0.0b28 | Fixed a bug with detecting some multi-column unique constraints in SQL Server databases 11/13/09 |
1.0.0b27 | Added a parameter to enableCaching() to provide a key token that will allow cached values to be shared between multiple databases with the same schema 10/28/09 |
1.0.0b26 | Added the placeholder element to the output of getColumnInfo(), added support for PostgreSQL, MSSQL and Oracle "schemas", added support for parsing quoted SQLite identifiers 10/22/09 |
1.0.0b25 | One-to-one relationships utilizing the primary key as a foreign key are now properly detected 9/22/09 |
1.0.0b24 | Fixed MSSQL support to work with ODBC database connections 9/18/09 |
1.0.0b23 | Fixed a bug where one-to-one relationships were being listed as many-to-one 7/21/09 |
1.0.0b22 | PostgreSQL UNIQUE constraints that are created as indexes and not table constraints are now properly detected 7/8/09 |
1.0.0b21 | Added support for the UUID data type in PostgreSQL 6/18/09 |
1.0.0b20 | Add caching of merged info, improved performance of getColumnInfo() 6/15/09 |
1.0.0b19 | Fixed a couple of bugs with setKeysOverride() 6/4/09 |
1.0.0b18 | Added missing support for MySQL mediumint columns 5/18/09 |
1.0.0b17 | Fixed a bug with clearCache() not properly reseting the tables and databases list 5/13/09 |
1.0.0b16 | Backwards Compatibility Break - setCacheFile() changed to enableCaching() and now requires an fCache object, flushInfo() renamed to clearCache(), added Oracle support 5/4/09 |
1.0.0b15 | Added support for the three different types of identifier quoting in SQLite 3/28/09 |
1.0.0b14 | Added support for MySQL column definitions containing the COLLATE keyword 3/28/09 |
1.0.0b13 | Fixed a bug with detecting PostgreSQL columns having both a CHECK constraint and a UNIQUE constraint 2/27/09 |
1.0.0b12 | Fixed detection of multi-column primary keys in MySQL 2/27/09 |
1.0.0b11 | Fixed an issue parsing MySQL tables with comments 2/25/09 |
1.0.0b10 | Added the getDatabases() method 2/24/09 |
1.0.0b9 | Now detects unsigned and zerofill MySQL data types that do not have a parenthetical part 2/16/09 |
1.0.0b8 | Mapped the MySQL data type 'set' to 'varchar', however valid values are not implemented yet 2/1/09 |
1.0.0b7 | Fixed a bug with detecting MySQL timestamp columns 1/28/09 |
1.0.0b6 | Fixed a bug with detecting MySQL columns that accept NULL 1/19/09 |
1.0.0b5 | setColumnInfo(): fixed a bug with not grabbing the real database schema first, made general improvements 1/19/09 |
1.0.0b4 | Added support for MySQL binary data types, numeric data type options unsigned and zerofill, and per-column character set definitions 1/17/09 |
1.0.0b3 | Fixed detection of the data type of MySQL timestamp columns, added support for dynamic default date/time values 1/11/09 |
1.0.0b2 | Fixed a bug with detecting multi-column unique keys in MySQL 1/3/09 |
1.0.0b | The initial implementation 9/25/07 |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
All requests that hit this method should be requests for callbacks
callback __get( string $method )
string | $method | The method to create a callback for |
The callback for the method requested
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Clears all of the schema info out of the object and, if set, the fCache object
void clearCache( )
Sets the schema to be cached to the fCache object specified
void enableCaching( fCache $cache, string $key_token=NULL )
Returns column information for the table specified
If only a table is specified, column info is in the following format:
array(
(string) {column name} => array(
'type' => (string) {data type},
'placeholder' => (string) {fDatabase::escape() placeholder for this data type},
'not_null' => (boolean) {if value can't be null},
'default' => (mixed) {the default value},
'valid_values' => (array) {the valid values for a varchar field},
'max_length' => (integer) {the maximum length in a varchar field},
'min_value' => (numeric) {the minimum value for an integer/float field},
'max_value' => (numeric) {the maximum value for an integer/float field},
'decimal_places' => (integer) {the number of decimal places for a decimal/numeric/money/smallmoney field},
'auto_increment' => (boolean) {if the integer primary key column is a serial/autoincrement/auto_increment/indentity column},
'comment' => (string) {the SQL comment/description for the column}
), ...
)
If a table and column are specified, column info is in the following format:
array(
'type' => (string) {data type},
'placeholder' => (string) {fDatabase::escape() placeholder for this data type},
'not_null' => (boolean) {if value can't be null},
'default' => (mixed) {the default value-may contain special strings CURRENT_TIMESTAMP, CURRENT_TIME or CURRENT_DATE},
'valid_values' => (array) {the valid values for a varchar field},
'max_length' => (integer) {the maximum length in a char/varchar field},
'min_value' => (fNumber) {the minimum value for an integer/float field},
'max_value' => (fNumber) {the maximum value for an integer/float field},
'decimal_places' => (integer) {the number of decimal places for a decimal/numeric/money/smallmoney field},
'auto_increment' => (boolean) {if the integer primary key column is a serial/autoincrement/auto_increment/indentity column},
'comment' => (string) {the SQL comment/description for the column}
)
If a table, column and element are specified, returned value is the single element specified.
The 'type' element is homogenized to a value from the following list:
Please note that MySQL reports boolean data types as tinyint(1), so all tinyint(1) columns will be listed as boolean. This can be fixed by calling:
$schema->setColumnInfoOverride(
array(
'type' => 'integer',
'placeholder' => '%i',
'default' => {default integer},
'min_value' => new fNumber(-128),
'max_value' => new fNumber(127)
),
'{table name}',
'{column name}'
);
The 'comment' element pulls from the database's column comment facility with the exception of MSSQL and SQLite.
For MSSQL, the comment is pulled from the MS_Description extended property, which can be added via the Description field in SQL Server Management Studio, or via the sp_addextendedproperty stored procedure.
For SQLite, the comment is extracted from any SQL comment that is placed at the end of the line on which the column is defined:
CREATE TABLE users (
user_id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(200) NOT NULL -- This is the full name
);
For the SQLite users table defined above, the name column will have the comment This is the full name.
mixed getColumnInfo( string $table, string $column=NULL, string $element=NULL )
string | $table | The table to get the column info for |
string | $column | The column to get the info for |
string | $element | The element to return: 'type', 'placeholder', 'not_null', 'default', 'valid_values', 'max_length', 'min_value', 'max_value', 'decimal_places', 'auto_increment', 'comment' |
The column info for the table/column/element specified - see method description for format
Returns the databases on the current server
array getDatabases( )
The databases on the current server
Returns a list of primary key, foreign key and unique key constraints for the table specified
The structure of the returned array is:
array(
'primary' => array(
{column name}, ...
),
'unique' => array(
array(
{column name}, ...
), ...
),
'foreign' => array(
array(
'column' => {column name},
'foreign_table' => {foreign table name},
'foreign_column' => {foreign column name},
'on_delete' => {the ON DELETE action: 'no_action', 'restrict', 'cascade', 'set_null', or 'set_default'},
'on_update' => {the ON UPDATE action: 'no_action', 'restrict', 'cascade', 'set_null', or 'set_default'}
), ...
)
)
array getKeys( string $table, string $key_type=NULL )
string | $table | The table to return the keys for |
string | $key_type | The type of key to return: 'primary', 'foreign', 'unique' |
An array of all keys, or just the type specified - see method description for format
Returns a list of one-to-one, many-to-one, one-to-many and many-to-many relationships for the table specified
The structure of the returned array is:
array(
'one-to-one' => array(
array(
'table' => (string) {the name of the table this relationship is for},
'column' => (string) {the column in the specified table},
'related_table' => (string) {the related table},
'related_column' => (string) {the related column},
'on_delete' => (string) {the ON DELETE action: 'no_action', 'restrict', 'cascade', 'set_null', or 'set_default'},
'on_update' => (string) {the ON UPDATE action: 'no_action', 'restrict', 'cascade', 'set_null', or 'set_default'}
), ...
),
'many-to-one' => array(
array(
'table' => (string) {the name of the table this relationship is for},
'column' => (string) {the column in the specified table},
'related_table' => (string) {the related table},
'related_column' => (string) {the related column}
), ...
),
'one-to-many' => array(
array(
'table' => (string) {the name of the table this relationship is for},
'column' => (string) {the column in the specified table},
'related_table' => (string) {the related table},
'related_column' => (string) {the related column},
'on_delete' => (string) {the ON DELETE action: 'no_action', 'restrict', 'cascade', 'set_null', or 'set_default'},
'on_update' => (string) {the ON UPDATE action: 'no_action', 'restrict', 'cascade', 'set_null', or 'set_default'}
), ...
),
'many-to-many' => array(
array(
'table' => (string) {the name of the table this relationship is for},
'column' => (string) {the column in the specified table},
'related_table' => (string) {the related table},
'related_column' => (string) {the related column},
'join_table' => (string) {the table that joins the specified table to the related table},
'join_column' => (string) {the column in the join table that references 'column'},
'join_related_column' => (string) {the column in the join table that references 'related_column'},
'on_delete' => (string) {the ON DELETE action: 'no_action', 'restrict', 'cascade', 'set_null', or 'set_default'},
'on_update' => (string) {the ON UPDATE action: 'no_action', 'restrict', 'cascade', 'set_null', or 'set_default'}
), ...
)
)
array getRelationships( string $table, string $relationship_type=NULL )
string | $table | The table to return the relationships for |
string | $relationship_type | The type of relationship to return: 'one-to-one', 'many-to-one', 'one-to-many', 'many-to-many' |
An array of all relationships, or just the type specified - see method description for format
Returns the tables in the current database
array getTables( boolean|string $creation_order=NULL )
boolean|string | $creation_order | TRUE to return in a valid table creation order, or a table name to return that table and any tables that depend on it, in table creation order |
The tables in the current database, all converted to lowercase
Allows overriding of column info
Performs an array merge with the column info detected from the database.
To erase a whole table, set the $column_info to NULL. To erase a column, set the $column_info for that column to NULL.
If the $column_info parameter is not NULL, it should be an associative array containing one or more of the following keys. Please see getColumnInfo() for a description of each.
The following keys may be set to NULL:
The key 'auto_increment' should be a boolean.
The 'type' key should be one of:
void setColumnInfoOverride( array $column_info, string $table, string $column=NULL )
array | $column_info | The modified column info - see method description for format |
string | $table | The table to override |
string | $column | The column to override |
Allows overriding of key info. Replaces existing info, so be sure to provide full key info for type selected or all types.
void setKeysOverride( array $keys, string $table, string $key_type=NULL )
array | $keys | The modified keys - see getKeys() for format |
string | $table | The table to override |
string | $key_type | The key type to override: 'primary', 'foreign', 'unique' |
The fSession class provides an enhanced interface to PHPs native session handling and $_SESSION
superglobal features.
There are three options for configuring the session, the setPath()
, setLength()
and ignoreSubdomain()
static methods. All must be called before any other fSession methods.
The most important method to call when setting up a site is setPath()
. This static method accepts a single parameter, the $directory
to save all session files in. The directory specified must be writable by the web server, and should not contain anything except for session files because the session manager will delete old files after the predetermined session time has expired.
By default, all sites on a server use the same temporary directory to store the session files. This opens the opportunity for cross-site session transfer since a valid session ID can be pasted from from one session cookie to another. By setting the session directory per site, this type of attack is prevented. For additional security, it is wise to set the session directory to a location that is not readable by other users so they can not find it and set their session directory to the same place.
fSession::setPath('/path/to/private/writable/dir');
The static method setLength()
allows you to set the minimum length of the session, using English descriptions of a timespan. Note that the minimum length, not the exact length, is specified since the session garbage collector uses a probabilistic approach to cleaning up session data. If the session length is set, the session directory should also be set via setPath()
or else other sites on the server may delete session files that they consider "old", but that have not expired for the current site.
Here are a few example of setting the session length:
fSession::setLength('30 minutes');
fSession::setLength('1 hour');
fSession::setLength('1 day 2 hours');
There is a second, optional, parameter $persistent_timespan
which is discussed in the section Keeping Users Logged In.
By default PHP will only allow access to the $_SESSION
superglobal values by pages on the same subdomain, such that www.example.com
could access the session, but example.com
could not. Calling ignoreSubdomain()
removes that restriction and allows access to any subdomain.
Session fixation is an exploit where an attacker provides a user with a known session id and then uses the same session id to access their authentication session once they have logged in. Below is a simple example of a URL that allows the attacker to know the users session id:
http://example.com/login.php?PHPSESSID=abcdef1234567890
After the unsuspecting user has logged into the site, the attacker simply needs to set the same session id in his browser and hell have full access to the users session and information.
The fSession class prevents against such session fixation attacks by automatically setting the session.use_cookies
and session.use_only_cookies
ini settings so that session ids will not be accepted in a query string or POST
data, but only in cookies.
When using the fAuthorization class, an additional layer of protected is added because all operations that add user information to the session will automatically regenerate the session id. This way even if an attacker was able to influence the session id, it will change once any useful information is present.
A session can be in one of three states: open, closed, and non-existent. An open session can have data written to the $_SESSION
superglobal. A closed session retains all information, however the information can not be read or written. A non-existent session is exactly that, not present at all.
The session is automatically opened when any session method such as set()
, get()
or destroy()
is called. It can also be opened explicitly by calling the static method open()
. In the case that a Cannot send session cache limiter - headers already sent
warning is generated, be sure to call open()
before any output is sent to the browser.
// If you arent using the session until after content has been output,
// be sure to explicitly open the session before any content is echoed
fSession::open();
To close the session, simply call close()
. The session information can be erased by calling destroy()
.
During normal usage of (see Storing and Retrieving Values for details) you can read and write throughout the script or page. However, if close()
has been called on a page, no data can be read from or written to the session cache after that point.
There is, however, some benefit to closing the session once you are done, rather than waiting for the page to finish execution and the session to be closed automatically. The biggest limitation of PHP is that only a single page can be reading from or writing to a single session. This means a user with multiple browsers or tabs open to a site will only be able to load data from one page at a time. Any other pages being requested that need session data will have to wait until the first page is complete. On most sites with fast-loading pages this may not be an issue, however if any pages take any significant amount of time to the load, users may notice the site will become unresponsive.
// If you are about to execute a time-intensive block of code and no longer need the seesion, close it
fSession::close();
Finally, the destroy()
method will completely erase all data from a session and destroy the session id, preventing it from being opened again. This method is most useful when a logged-in user logs out.
// If a user is logging out, remove the information you have stored about them
fSession::destroy();
On most sites that have a user login system, it will often be desirable to provide an option for a user to stay logged in even after their browser closes. Obviously this can be a security issue, however many large websites control the functionality via a checkbox in the login form that is labelled Keep me logged in. This will usually keep a user logged in for a week or two.
To implement this is Flourish, the static method fSession::setLength() allows for an optional second parameter, $persistent_timespan
, which enables persistent logins and sets their length. Whenever using this functionality please be sure to set a custom session file path.
fSession::setLength('30 minutes', '1 week');
This will not cause all users to stay logged in for a week. The session files will only be garbage collected after a week, but fSession uses a timestamp in the session superglobal to log normal sessions out after 30 minutes.
To enable a user to stay logged in for the whole $persistent_timespan
and to stay logged in across browser restarts, the static method fSession::enablePersistence() must be called when they log in. Here is an example:
if ($login == 'test' && fCryptography::checkPasswordHash($password, $hash)) {
fAuthorization::setUserToken('test');
if (fRequest::get('keep_me_logged_in', 'boolean')) {
fSession::enablePersistence();
}
}
Please note that setLength()
must be called before enablePersistence()
.
Now that we have discussed how to control the session, lets get into the heart of the matter, storing and retrieving values. There are two methods available to accomplish this task, set()
and get()
.
The set()
method takes two parameters, $key
and $value
. In a fairly straight-forward manner, $key
specifies what identifier to save the $value
under.
The default prefix is 'fSession::'
. It is recommended that under normal use the prefix is not changed. A logical place to change the prefix would be for values specific to another class. For example, the fAuthorization class changes the prefix to that all authorization-related session data does not conflict with anything a developer may add.
Here are some examples of adding data to the session:
// This is equivalent to $_SESSION['fSession::current_user_id'] = 5;
fSession::set('current_user_id', 5);
fSession::set('last_viewed_article', 42);
// Using the prefix here could allow us to not worry about overwriting values
// This is equivalent to $_SESSION['forum::current_user_id'] = 2;
fSession::set('current_user_id', 2, 'forum::');
Hand-in-hand with the set()
method is get()
. get()
allows retrieval of session values with a twist. The first parameter, $key
specifies what value to retrieve. The second (optional) parameter is $default_value
. This value will be returned if the requested $key
has no value set. Here are some example of getting values out of the session:
$current_user_id = fSession::get('current_user_id');
$user_groups = fSession::get('user_groups', array(1,2));
If you wish to unset a session value, simply use the delete()
method. It accepts the name of the $key
and returns the value:
$name = fSession::delete('name');
An optional second parameter allows providing a $default_value
to be returned if the key specified is not set.
$name = fSession::delete('name', 'No name specified');
To delete all keys for a specific prefix, use the clear()
method:
// Clear the default fSession:: prefix
fSession::clear();
// Clear all keys that start with MyPrefix_
fSession::clear('MyPrefix_');
The static methods add()
and remove()
allow adding and removing values from arrays stored in the session. add()
accepts a $key
and the $value
to add. If the key is not an array, an array will be created and the new value will be added.
// Add John Smith at the end of users
fSession::add('users', 'John Smith');
The new value will be added at the end of the array unless the optional third parameter, $beginning
, is set to TRUE
.
// Add Jane Smith at the beginning of users
fSession::add('users', 'Jane Smith', TRUE);
remove()
accepts one parameter, the $key
to remove a value from, and returns the removed value. The value will be removed from the end of the array unless the second optional parameter, $beginning
, is set to TRUE
.
$last_value = fSession::remove('users');
$first_value = fSession::remove('users', TRUE);
When a value stored in the session is an array, it is possible to use array dereference syntax in the element name to access a specific array key. This syntax works with set()
, get()
, delete()
, add()
and remove()
.
fSession::set(
'user',
array(
'first_name' => 'John',
'last_name' => 'Smith'
)
);
// This will echo John
echo fSession::get('user[first_name]');
Array dereferencing can be any number of layers deep.
echo fSession::get('user[groups][0][name]');
Wraps the session control functions and the $_SESSION superglobal for a more consistent and safer API
A Cannot send session cache limiter warning will be triggered if open(), add(), clear(), delete(), get() or set() is called after output has been sent to the browser. To prevent such a warning, explicitly call open() before generating any output.
1.0.0b22 | Fixed destroy() to no longer call regenerateID() since it fails after a session is destroyed 9/20/12 |
---|---|
1.0.0b21 | Changed regenerateID() to not fail silently if the session has not been opened yet 9/15/12 |
1.0.0b20 | Fixed bugs with reset() introduced in 1.0.0b19 8/23/11 |
1.0.0b19 | Fixed some session warning messages for PHP 5.1.6 7/29/11 |
1.0.0b18 | Added support for storing session data in memcache, redis and databases using fCache and setBackend() 6/21/11 |
1.0.0b17 | Updated ignoreSubdomain() to use $_SERVER['HTTP_HOST'] when $_SERVER['SERVER_NAME'] is not set 2/1/11 |
1.0.0b16 | Changed delete() to return the value of the key being deleted 9/19/10 |
1.0.0b15 | Added documentation about [sub-key] syntax 9/12/10 |
1.0.0b14 | Backwards Compatibility Break - add(), delete(), get() and set() now interpret [ and ] as array shorthand and thus they can not be used in keys - added $beginning parameter to add(), added remove() method 9/12/10 |
1.0.0b13 | Fixed a bug that prevented working with existing sessions since they did not have the fSession::expires key 8/24/10 |
1.0.0b12 | Changed enablePersistence() to always regenerate the session ID, which ensures the function works even if the ID has already been regenerated by fAuthorizaton 8/21/10 |
1.0.0b11 | Updated the class to make sure enablePersistence() is called after ignoreSubdomain(), setLength() and setPath() 5/29/10 |
1.0.0b10 | Fixed some documentation bugs 3/3/10 |
1.0.0b9 | Fixed a bug in destroy() where sessions weren't always being properly destroyed 12/8/09 |
1.0.0b8 | Fixed a bug that made the unit tests fail on PHP 5.1 10/27/09 |
1.0.0b7 | Backwards Compatibility Break - Removed the $prefix parameter from the methods delete(), get() and set() - added the methods add(), enablePersistence(), regenerateID() 10/23/09 |
1.0.0b6 | Backwards Compatibility Break - the first parameter of clear() was removed, use delete() instead 5/8/09 |
1.0.0b5 | Added documentation about session cache limiter warnings 5/4/09 |
1.0.0b4 | The class now works with existing sessions 5/4/09 |
1.0.0b3 | Fixed clear() to properly handle when $key is NULL 2/5/09 |
1.0.0b2 | Made open() public, fixed some consistency issues with setting session options through the class 1/6/09 |
1.0.0b | The initial implementation 6/14/07 |
Adds a value to an already-existing array value, or to a new array value
void add( string $key, mixed $value, boolean $beginning=FALSE )
string | $key | The name to access the array under - array elements can be modified via [sub-key] syntax, and thus [ and ] can not be used in key names |
mixed | $value | The value to add to the array |
boolean | $beginning | If the value should be added to the beginning |
Removes all session values with the provided prefix
This method will not remove session variables used by this class, which are prefixed with fSession::.
void clear( string $prefix=NULL )
string | $prefix | The prefix to clear all session values for |
Closes the session for writing, allowing other pages to open the session
void close( )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Callback to close the session
boolean closeCache( )
If the operation succeeded
Deletes a value from the session
mixed delete( string $key, mixed $default_value=NULL )
string | $key | The key of the value to delete - array elements can be modified via [sub-key] syntax, and thus [ and ] can not be used in key names |
mixed | $default_value | The value to return if the $key is not set |
The value of the $key that was deleted
Destroys the session, removing all values
void destroy( )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Callback to destroy a session
boolean destroyCache( string $id )
string | $id | The session to destroy |
If the operation succeeded
Changed the session to use a time-based cookie instead of a session-based cookie
The length of the time-based cookie is controlled by setLength(). When this method is called, a time-based cookie is used to store the session ID. This means the session can persist browser restarts. Normally, a session-based cookie is used, which is wiped when a browser restart occurs.
This method should be called during the login process and will normally be controlled by a checkbox or similar where the user can indicate if they want to stay logged in for an extended period of time.
void enablePersistence( )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Callback to garbage-collect the session cache
boolean gcCache( )
If the operation succeeded
Gets data from the $_SESSION superglobal
mixed get( string $key, mixed $default_value=NULL )
string | $key | The name to get the value for - array elements can be accessed via [sub-key] syntax, and thus [ and ] can not be used in key names |
mixed | $default_value | The default value to use if the requested key is not set |
The data element requested
Sets the session to run on the main domain, not just the specific subdomain currently being accessed
This method should be called after any calls to session_set_cookie_params().
void ignoreSubdomain( )
Opens the session for writing, is automatically called by clear(), get() and set()
A Cannot send session cache limiter warning will be triggered if this, add(), clear(), delete(), get() or set() is called after output has been sent to the browser. To prevent such a warning, explicitly call this method before generating any output.
void open( boolean $cookie_only_session_id=TRUE )
boolean | $cookie_only_session_id | If the session id should only be allowed via cookie - this is a security issue and should only be set to FALSE when absolutely necessary |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Callback to open the session
boolean openCache( )
If the operation succeeded
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Callback to read a session's values
string readCache( string $id )
string | $id | The session to read |
The session's serialized data
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Regenerates the session ID, but only once per script execution
void regenerateID( )
Removes and returns the value from the end of an array value
mixed remove( string $key, boolean $beginning=FALSE )
string | $key | The name of the element to remove the value from - array elements can be modified via [sub-key] syntax, and thus [ and ] can not be used in key names |
boolean | $beginning | If the value should be removed to the beginning |
The value that was removed
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Resets the configuration of the class
void reset( )
Sets data to the $_SESSION superglobal
void set( string $key, mixed $value )
string | $key | The name to save the value under - array elements can be modified via [sub-key] syntax, and thus [ and ] can not be used in key names |
mixed | $value | The value to store |
Sets an fCache object to store sessions in
While any type of fCache backend should technically work, it would be unwise to use the file and directory types. The file caching backend stores all values in a single file, which would quickly become a performance bottleneck and could cause data loss with many concurrent users. The directory caching backend would not make sense since it is the same general functionality as the default session handler, but it would be slightly slower since it is written in PHP and not C.
It is recommended to set the serializer and unserializer $config settings on the fCache object to string for the best performance and minimal storage space.
For better performance, check out using the built-in session handlers that are bundled with the following extensions:
The igbinary extension can provide even more of a performance boost by storing serialized data in binary format instead of as text.
void setBackend( fCache $backend, string $key_prefix='' )
Sets the minimum length of a session - PHP might not clean up the session data right away once this timespan has elapsed
Please be sure to set a custom session path via setPath() to ensure another site on the server does not garbage collect the session files from this site!
Both of the timespan can accept either a integer timespan in seconds, or an english description of a timespan (e.g. '30 minutes', '1 hour', '1 day 2 hours').
void setLength( string|integer $normal_timespan, string|integer $persistent_timespan=NULL )
string|integer | $normal_timespan | The normal, session-based cookie, length for the session |
string|integer | $persistent_timespan | The persistent, timed-based cookie, length for the session - this is enabled by calling enabledPersistence() during login |
Sets the path to store session files in
This method should always be called with a non-standard directory whenever setLength() is called to ensure that another site on the server does not garbage collect the session files for this site.
Standard session directories usually include /tmp and /var/tmp.
void setPath( string|fDirectory $directory )
string|fDirectory | $directory | The directory to store session files in |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Callback to write a session's values
string writeCache( string $id, string $values )
string | $id | The session to write |
string | $values | The serialized values |
The session's serialized data
The fStatement class is an internal class used by fDatabase for representing a database prepared statement. There are no public methods for this class, all functionality is exposed through fDatabase. Please see fDatabase for more information about creating and using prepared statements.
This space intentionally left blank
Representation of a prepared statement for use with the fDatabase class
1.0.0b7 | Fixed handling of arrays of values for execute(), executeQuery() and executeUnbufferedQuery(), fixed escaping of values that become NULL 5/9/11 |
---|---|
1.0.0b6 | Added getUntranslatedSQL() 1/9/11 |
1.0.0b5 | Fixed an edge case where the mysqli extension would leak memory when fetching a TEXT or BLOB column 8/28/10 |
1.0.0b4 | Updated class to use fCore::startErrorCapture() instead of error_reporting() 8/9/10 |
1.0.0b3 | Backwards Compatibility Break - removed ODBC support. Fixed UTF-8 support for the pdo_dblib extension. 7/31/10 |
1.0.0b2 | Added IBM DB2 support 4/13/10 |
1.0.0b | The initial implementation 3/2/10 |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Sets up a prepared statement
fStatement __construct( fDatabase $database, string $query, array $placeholders, $untranslated_sql, string $untranslated_query )
fDatabase | $database | The database object this result set was created from |
string | $query | The SQL statement to prepare |
array | $placeholders | The data type placeholders |
string | $untranslated_query | The original untranslated SQL, if applicable |
$untranslated_sql |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Frees up the result object to save memory
void __destruct( )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
All requests that hit this method should be requests for callbacks
callback __get( string $method )
string | $method | The method to create a callback for |
The callback for the method requested
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Executes the statement without returning a result
mixed execute( array $params, mixed &$extra, boolean $different )
array | $params | The parameters for the statement |
mixed | &$extra | A variable to place extra information needed by some database extensions |
boolean | $different | If this statement is different than the last statement run on the fDatabase instance |
The (usually boolean) result of the extension function/method call
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Executes the statement in buffered mode
void executeQuery( fResult $result, array $params, mixed &$extra, boolean $different )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Executes the statement in unbuffered mode (if possible)
void executeUnbufferedQuery( fUnbufferedResult $result, array $params, mixed &$extra, boolean $different )
fUnbufferedResult | $result | The object to place the result into |
array | $params | The parameters for the statement |
mixed | &$extra | A variable to place extra information needed by some database extensions |
boolean | $different | If this statement is different than the last statement run on the fDatabase instance |
Returns the SQL for the prepared statement
string getSQL( )
The SQL statement
Returns the untranslated SQL for the prepared statement
string getUntranslatedSQL( )
The untranslated SQL statement
The fTemplating class allows for simple PHP templating with abstraction of HTML into a separate scope. This is a contrast to system like Smarty where a complete language has been developed, and must be learned, to use templating.
When creating an instance of the fTemplating class, a single optional parameter $root
can be passed. This controls the directory that is used as the basis for relative paths. If no root is passed, the $_SERVER['DOCUMENT_ROOT']
is used as a default.
$template = new fTemplating('/var/www/inc/templates/');
The fTemplating class is built around the concept of elements. Elements can be any data type, and may represent anything from a chunk of data, to an object, or a file path.
The first operation to be performed is setting an element via the set()
method. The first parameter, $element
, is an identifier for the element you are setting. The second parameter is the $value
.
Here are some example of setting various elements:
// These value would usually be set in the initialization script for a site
$template->set('header', 'header.php');
$template->set('footer', 'footer.php');
$template->set('db', $database);
Multiple elements can be set in one method call by passing an associative array to set()
.
$template->set(array(
'header' => 'header.php',
'footer' => 'footer.php'
));
Elements can be retrieved by calling the get()
method. This method required the first parameter, $element
, which is what you want to retrieve. You can also optionally pass a second parameter $default_value
to specify what should be returned in the element has not yet been set. If no $default_value
is specified and the element has not been set, NULL
will be returned.
// This will be the name or NULL
$name = $template->get('name');
// This will be the name or 'No name provided'
$name = $template->get('name', 'No name provided');
Multiple elements can be retrieved in one method call by passing an array of element names to get()
. If default values are required, an associative array can be passed with the element name being the key and the default value being the value.
// Get an array with two values - the element names will be the keys and the
// values will be the values, NULL will be returned if an element is not set
$values = $template->get(array('first_name', 'last_name'));
// This returns the same elements, but with the explicit default values N/A
$values = $template->get(array(
'first_name' => 'N/A',
'last_name' => 'N/A'
));
Elements can be removed by passing the $element
name to delete()
. The value of the element being deleted will be returned.
$value = $template->delete('name');
A $default_value
can be passed as the second parameter to delete()
, and it will be returned if the element specified was not set.
$value = $template->delete('name', 'None specified');
Multiple elements can be deleted at a time by passing an array of element names to delete()
. Default values can be provided by passing an associative array of element names as keys and default values as values.
// Delete the first_name and last_name elements
$values = $template->delete(array('first_name', 'last_name'));
// Delete the first_name and last_name elements, providing defaults
$values = $template->delete(array(
'first_name' => 'No first name provided',
'last_name' => 'No last name provided'
));
In addition to set()
, there is a method called add()
that will allow appending a value to an element that is an array. It will also initialize an element to an array if the element does not already exist.
$template->add('js', '/sup/js/main.js');
$template->add('css', array('path' => '/sup/css/print.css', 'media' => 'print'));
$template->add('css', '/sup/css/base.css');
A value can be added at the beginning by passing TRUE
as the third parameter.
$template->add('js', '/sup/js/jquery-min.js', TRUE);
The remove()
method can used to remove a value from an array element. The removed value will be returned.
$template->add('numbers', 1);
$template->add('numbers', 2);
$two = $template->remove('numbers');
A value may be removed from the beginning of an array element by passing TRUE
as a second parameter to remove()
.
$one = $template->remove('numbers');
Values may be removed from an array element by calling filter()
with the $element
as the first parameter, and the $value
to remove as the second.
$template->filter('numbers', 1);
The filter()
method does a normal equality comparison, so filtering 0
will also removed FALSE
and other empty values. All matching values will be removed from the array, not just the first one.
While simply retrieving elements is useful in certain situations, much of the time there will be a need to process text content for output in the HTML. The encode()
and prepare()
methods function the same was as get()
except that they run the returned value through fHTML::encode() and fHTML::prepare(), respectively.
// Encode turns <, >, & and " into HTML entities to prevent XSS
echo $template->encode('name');
// ::prepare() should only be used with trusted content to make
// sure special characters outside of HTML tags are encoded
echo $template->prepare('html_bio');
When an element is an array, it is possible to use array dereference syntax in the element name to access a specific array key. This syntax works with set()
, get()
, delete()
, add()
, remove()
, filter()
, encode()
and prepare()
.
$template->set(
'user',
array(
'first_name' => 'John',
'last_name' => 'Smith'
)
);
// This will echo John
echo $template->get('user[first_name]');
Array dereferencing can be any number of layers deep.
echo $template->get('user[groups][0][name]');
Probably the most useful aspect of the fTemplating class is the place()
method. This method takes a single parameter, $element
, in which you specify what element you want to place in the page.
place()
works differently depending of the type of path is contained in the element. If the element contains a path is to a .php (.inc or .php5) file, the file will be included inside the scope of the fTemplating class (i.e. $this
will return to the instance of the class). If a .css, .js or .rss (.xml) path is placed, the proper XHTML tags will be echoed.
In order to handle the title
attribute for RSS feeds and the media
attribute for CSS files it is possible to set the value of an element to be an associative array containing a key path
and a key corresponding to the appropriate attributes.
Here are some examples of using place()
for different types of files:
$template->set('title', 'Flourish');
$template->add('rss', array('path' => '/sup/rss/blog.rss', 'title' => 'Blog Posts'));
$template->place('header');
?>
<h1>Flourish</h1>
...
<?php
$template->place('footer');
Placing the header would include the file header.php
from the root we defined earlier of /var/www/inc/templates/
. Placing the footer would do that same thing for footer.php
.
Here is an example header.php
:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title><?php echo $this->prepare('title') ?></title>
<?php echo $this->place('css') ?>
<?php echo $this->place('js') ?>
<?php echo $this->place('rss') ?>
</head>
<body>
The HTML output from the above script would be:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Flourish</title>
<link rel="stylesheet" type="text/css" href="/sup/css/base.css" media="all" />
<link rel="stylesheet" type="text/css" href="/sup/css/print.css" media="print" />
<script type="text/javascript" src="/sup/js/main.js"></script>
<link rel="alternate" type="application/rss+xml" href="/sup/rss/blog.rss" title="Blog Posts" />
</head>
<body>
<h1>Flourish</h1>
...
</body>
</html>
If paths are contained in element that do not end in one of the extensions listed below, you can force all paths in an element to be displayed as a certain type of file by passing the type of file as the second parameter to place.
Here are the valid extensions:
.php
, .php5
, .inc
.js
.css
.rss
, .xml
Here is how you could force all paths in an element to be displayed as a certain type regaredless of the path extensions:
// JS files
$this->place('element1', 'js');
// CSS files
$this->place('element2', 'css');
// RSS files
$this->place('element3', 'rss');
// PHP files
$this->place('element4', 'php');
The method inject()
works identically to place()
, except that is accepts a file name or path instead of an element name. This allows placing a file without having to pass it to set()
first.
// Include a PHP file in the template root
$template->inject('sidebar.php');
// Add a JS file
$template->inject('/path/to/example.js');
The second parameter also functions identically to place()
, allowing the developer to specify the type of file being injected if it can't be auto-detected.
// Specifying the file type
$template->inject('/path/to/rss', 'rss');
fTemplating includes functionality that will minify javascript and CSS, combine multiple files into one, and add a query string to allow for far-futures expire headers. All of these features provide increased performance for HTTP clients.
Minification reduces the number of bytes sent to a user by removing whitespace and other unnecessary syntactic sugar. Combining multiple files reduces the number of HTTP requests made for each script execution. Adding a query string of the cache-file creation date allows use of far-futures expires headers without worrying about out-dated browser caches, which further reduces the number of HTTP requests.
In this section of documentation, a **cache file** refers to a file containing minified code of one or more original code files.
JS and CSS minification is enabled by calling enableMinification()
with a $mode
, $cache_directory
and optional $path_prefix
. This should be called before calling place()
on any elements since it works through the place()
method.
$template->enableMinification('development', $_SERVER['DOCUMENT_ROOT'] . '/sup/minification_cache/');
The $mode
can be either 'development'
or 'production'
. In 'development'
mode, all files are checked for modifications on each script execution. If a file has been modified since the cache was last created, the cache is regenerated. In 'production'
mode, cache files are only regenerated if they are missing.
The $cache_directory
should be a web-accessible directory for fTemplating to write the cache files to. It needs to be web-accessible since <script>
and <link>
tags will be referencing cache files stored inside of it.
The $path_prefix
is optional, and defaults to $_SERVER['DOCUMENT_ROOT']
. The $path_prefix
is appended to all JS and CSS paths in order to load them from the filesystem. For example a CSS file /css/main.css
would be prefixed with $_SERVER['DOCUMENT_ROOT']
, resulting in fTemplating looking for the file in /document/root/css/main.css
.
Since the minified files are stored on the filesystem, fTemplating must know how to translate the filesystem path into a URL. This translation is done by using fFilesystem::translateToWebPath(). If fTemplating is generating incorrect URLs for the minified files, please use fFilesystem::addWebPathTranslation() to indicate what translation should be done. fFilesystem: Translation has an example showing how the method is used.
Once minification is enabled, all calls to place()
that end up placing one or more CSS or JS files will automatically be minified.
$template->add('css', '/css/structure.css');
$template->add('css', '/css/typography.css');
$template->place('css');
Would normally result in the following HTML being written to the output:
<link rel="stylesheet" type="text/css" href="/css/structure.css" />
<link rel="stylesheet" type="text/css" href="/css/typography.css" />
With minification enabled, it would be written as:
<link rel="stylesheet" type="text/css" href="/cache/ae9210f4cbd017f4cbes.css?v=10298382912" />
fTemplating uses Douglas Crockfords JSMin algorithm, but has its own implementation that is optimized for PHP.
For CSS, there is not a definitive algorithm like JSMin. Currently fTemplating performs the following steps to minify CSS:
;
, {
, }
, ,
, >
and +
is removed:
inside of rule blocks is removed;}
is reduced to }
CSS minification does not touch @import
statements or url()
literals. Depending on the location of the $cache_directory
relative to the normal CSS directory, some @import
statements may break. However, with enableMinification()
, @import
statements should not generally be necessary. Similarly, url()
literals may break if the $cache_directory
is on a different directory level than the original CSS directory. This can be remedied by placing them on the same level, or using website-relative URLs.
PHP short tags, such as <?
and <?=
often make for easy-to-read and easy-to-write templates in raw PHP. Unfortunately, short tags can be disabled by an ini setting, and cant be relied upon when developing PHP for different environments.
The method enablePHPShortTags()
allows any PHP file directly included via place()
or inject()
to use PHP short tags even if they are turned off. This is accomplished by transforming short tags into long tags and saving the transformed PHP into a cache directory. The method accepts two parameters, $mode
and $cache_directory
.
$template->enablePHPShortTags('development', '/path/to/php/cache');
The $mode
can be either 'development'
or 'production'
. In 'development'
mode, all files are checked for modifications on each script execution. If a file has been modified since the cache was last created, the cache is regenerated. In 'production'
mode, cache files are only regenerated if they are missing.
The $cache_directory
is where the transformed PHP files are saved. This directory should not be web-accessible, but must be writable by the web server. fTemplating will properly preserve the value of __FILE__
and __DIR__
constants that are contained within templates that are transformed.
In certain circumstances it is useful to buffer the output of the page to allow for elements to be changed after content has already been output. The buffer()
method allows such buffering to be enabled.
When buffering is enabled, all calls to place()
actually insert a text token into the output. When the fTemplating class destructor is called (explicitly, or at the end of the page execution) the text tokens are all replaced by the output of the place()
call. The place()
method is actually executed in the destructor, so it will have the state of all elements at the end of the page, thus allowing elements to be modified after they have been seemingly already output.
There are two caveats to using the fTemplating output buffering.
place()
call and then in other non-templating code, it will be executed in the wrong order due to the fact that the place()
call is not done until the end of the page. If this is the case, you should probably not use buffering, or consider a way to refactor your code so that such sequential code execution is not required.Here is an example of using buffered output:
// Create the template and turn on buffering
$temp = new fTemplating();
$temp->buffer();
// Set up out elements
$temp->set('header', 'header.php');
$temp->set('title', 'This is the old title');
// Output the header
$temp->place('header');
// Change the title after the fact
$temp->set('title', 'This is the new title');
Lets assume that header.php
looks like this:
echo $this->get('title');
The output from above would be:
This is the new title
since the place()
call is not executed until the $temp
destructor is called at the end of the code (implicitly) and the title was changed before the end of the code.
This buffering technique can greatly reduce the complexity of the code that needs to be executed before the first output can be sent to the user.
It is possible to attach and retrieve fTemplating objects in any scope using the static methods attach()
and retrieve()
. attach()
accepts an fTemplating instance and an optional $name
for it. If no $name
is provided, it will be set to default
.
fTemplating::attach(new fTemplating('/path/to/root'));
The method retrieve()
accepts the $name
of a previously creating instance and returns it. If no $name
is passed, the default
template is returned.
$tmpl = fTemplating::retrieve();
It is possible to nest fTemplating objects for more complex situations. The concept is to have a child fTemplating object that will be placed by a parent. Such a child fTemplating object will automatically call place()
on the __main__
element when it itself is placed.
The __main__
element will normally be set by passing a PHP file path to the second parameter of the constructor. Like calls to set()
, any file path that is not absolute and does not start with ./
will be relative to the template root.
$user_info = new fTemplating('/templating/root/', 'sub_templates/user_info.php');
The fTemplating object may then be set to another fTemplating object and placed.
$template->set('user_info', $user_info);
// At this point sub_templates/user_info.php is placed
$template->place('user_info');
All sub-templates will inherit all minification and PHP short tag settings from their parent if not explicitly set.
Allows for quick and flexible HTML templating
1.0.0b23 | Added a default $name for retrieve() to mirror attach() 8/31/11 |
---|---|
1.0.0b22 | Backwards Compatibility Break - removed the static method create(), added the static method attach() to fill its place 8/31/11 |
1.0.0b21 | Fixed a bug in enableMinification() where the minification cache directory was sometimes not properly converted to a web path 8/31/11 |
1.0.0b20 | Fixed a bug in CSS minification that would reduce multiple zeros that are part of a hex color code, fixed minification of + ++ and similar constructs in JS 8/31/11 |
1.0.0b19 | Corrected a bug in enablePHPShortTags() that would prevent proper translation inside of HTML tag attributes 1/9/11 |
1.0.0b18 | Fixed a bug with CSS minification and black hex codes 10/10/10 |
1.0.0b17 | Backwards Compatibility Break - delete() now returns the values of the element or elements that were deleted instead of returning the fTemplating instance 9/19/10 |
1.0.0b16 | Fixed another bug with minifying JS regex literals 9/13/10 |
1.0.0b15 | Fixed a bug with minifying JS regex literals that occur after a reserved word 9/12/10 |
1.0.0b14 | Added documentation about [sub-key] syntax 9/12/10 |
1.0.0b13 | Backwards Compatibility Break - add(), delete(), get() and set() now interpret [ and ] as array shorthand and thus they can not be used in element names, renamed remove() to filter() - added $beginning parameter to add() and added remove() method 9/12/10 |
1.0.0b12 | Added enableMinification(), enablePHPShortTags(), the ability to be able to place child fTemplating objects via a new magic element __main__ and the $main_element parameter for __construct() 8/31/10 |
1.0.0b11 | Fixed a bug with the elements not being initialized to a blank array 8/12/10 |
1.0.0b10 | Updated place() to ignore URL query strings when detecting an element type 7/26/10 |
1.0.0b9 | Added the methods delete() and remove() 7/15/10 |
1.0.0b8 | Fixed a bug with placing absolute file paths on Windows 7/9/10 |
1.0.0b7 | Removed e flag from preg_replace() calls 6/8/10 |
1.0.0b6 | Changed set() and add() to return the object for method chaining, changed set() and get() to accept arrays of elements 6/2/10 |
1.0.0b5 | Added encode() 5/20/10 |
1.0.0b4 | Added create() and retrieve() for named fTemplating instances 5/11/10 |
1.0.0b3 | Fixed an issue with placing relative file path 4/23/10 |
1.0.0b2 | Added the inject() method 1/9/09 |
1.0.0b | The initial implementation 6/14/07 |
The directory to look for files
string
Attaches a named template that can be accessed from any scope via retrieve()
void attach( fTemplating $templating, string $name='default' )
fTemplating | $templating | The fTemplating object to attach |
string | $name | The name for this templating instance |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Resets the configuration of the class
void reset( )
Retrieves a named template
fTemplating retrieve( string $name='default' )
string | $name | The name of the template to retrieve |
The specified fTemplating instance
Initializes this templating engine
fTemplating __construct( string $root=NULL, string $main_element=NULL )
string | $root | The filesystem path to use when accessing relative files, defaults to $_SERVER['DOCUMENT_ROOT'] |
string | $main_element | The value for the __main__ element - this is used when calling place() without an element, or when placing fTemplating objects as children |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Finishing placing elements if buffering was used
void __destruct( )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
All requests that hit this method should be requests for callbacks
callback __get( string $method )
string | $method | The method to create a callback for |
The callback for the method requested
Adds a value to an array element
fTemplating add( string $element, mixed $value, boolean $beginning=FALSE )
string | $element | The element to add to - array elements can be modified via [sub-key] syntax, and thus [ and ] can not be used in element names |
mixed | $value | The value to add |
boolean | $beginning | If the value should be added to the beginning of the element |
The template object, to allow for method chaining
Enables buffered output, allowing set() and add() to happen after a place() but act as if they were done before
Please note that using buffered output will affect the order in which code is executed since the elements are not actually place()'ed until the destructor is called.
If the non-template code depends on template code being executed sequentially before it, you may not want to use output buffering.
void buffer( )
Deletes an element from the template
mixed delete( string $element, mixed $default_value=NULL )
mixed delete( array $elements )
string | $element | The element to delete - array elements can be modified via [sub-key] syntax, and thus [ and ] can not be used in element names |
mixed | $default_value | The value to return if the $element is not set |
array | $elements | The elements to delete - an array of element names or an associative array of keys being element names and the values being the default values |
The value of the $element that was deleted - an associative array of deleted elements will be returned if an array of $elements was specified
Erases all output since the invocation of the template - only works if buffering is on
void destroy( )
Enables minified output for CSS and JS elements
For CSS and JS, compilation means that the file will be minified and cached. The filename will change whenever the content change, allowing for far-futures expire headers.
Please note that this option requires that all CSS and JS paths be relative to the $_SERVER['DOCUMENT_ROOT'] and start with a /. Also this class will not clean up old cached files out of the cache directory.
This functionality will be inherited by all child fTemplating objects that do not have their own explicit minification settings.
void enableMinification( string $mode, fDirectory|string $cache_directory, fDirectory|string $path_prefix=NULL )
string | $mode | The compilation mode - 'development' means that file modification times will be checked on each load, 'production' means that the cache files will only be regenerated when missing |
fDirectory|string | $cache_directory | The directory to cache the compiled files into - this needs to be inside the document root or a path added to fFilesystem::addWebPathTranslation() |
fDirectory|string | $path_prefix | The directory to prepend to all CSS and JS paths to load the files from the filesystem - this defaults to $_SERVER['DOCUMENT_ROOT'] |
Converts PHP short tags to long tags when short tags are turned off
Please note that this only affects PHP files that are directly evaluated with place() or inject(). It will not affect PHP files that have been evaluated via include or require statements inside of the directly evaluated PHP files.
This functionality will be inherited by all child fTemplating objects that do not have their own explicit short tag settings.
void enablePHPShortTags( string $mode, fDirectory|string $cache_directory )
string | $mode | The compilation mode - 'development' means that file modification times will be checked on each load, 'production' means that the cache files will only be regenerated when missing |
fDirectory|string | $cache_directory | The directory to cache the compiled files into - this directory should not be accessible from the web |
Gets the value of an element and runs it through fHTML::encode()
mixed encode( string $element, mixed $default_value=NULL )
string | $element | The element to get - array elements can be accessed via [sub-key] syntax, and thus [ and ] can not be used in element names |
mixed | $default_value | The value to return if the element has not been set |
The value of the element specified run through fHTML::encode(), or the default value if it has not been set
Removes a value from an array element
fTemplating filter( string $element, mixed $value )
string | $element | The element to remove from - array elements can be modified via [sub-key] syntax, and thus [ and ] can not be used in element names |
mixed | $value | The value to remove - compared in a non-strict manner, such that removing 0 will remove a blank string and false also |
The template object, to allow for method chaining
Gets the value of an element
mixed get( string $element, mixed $default_value=NULL )
mixed get( array $elements )
string | $element | The element to get - array elements can be accessed via [sub-key] syntax, and thus [ and ] can not be used in element names |
mixed | $default_value | The value to return if the element has not been set |
array | $elements | An array of elements to get, or an associative array where a string key is the element to get and the value is the default value |
The value of the element(s) specified, or the default value(s) if it has not been set
Combines an array of CSS or JS files and places them as a single file
void handleMinified( string $type, string $element, array $values )
string | $type | The type of compilation, 'css' or 'js' |
string | $element | The element name |
array | $values | An array of file paths |
Includes the file specified - this is identical to place() except a filename is specified instead of an element
Please see the place() method for more details about functionality.
void inject( string $file_path, string $file_type=NULL )
string | $file_path | The file to place |
string | $file_type | Will force the file to be placed as this type of file instead of auto-detecting the file type. Valid types include: 'css', 'js', 'php' and 'rss'. |
Minifies JS or CSS
For JS, this function is based on the JSMin algorithm (not the code) from http://www.crockford.com/javascript/jsmin with the addition of preserving /*! comment blocks for things like licenses. Some other versions of JSMin change the contents of special comment blocks, but this version does not.
string minify( string $code, string $type )
string | $code | The code to minify |
string | $type | The type of code, 'css' or 'js' |
The minified code
Takes a block of CSS or JS and reduces the number of characters
void minifyCode( string &$part, string &$buffer, &$stack, mixed $type='js', array $stack )
string | &$part | The part of code to minify |
string | &$buffer | A buffer containing the last code or literal encountered |
array | $stack | A stack used to keep track of the nesting level of CSS |
mixed | $type | The type of code, 'css' or 'js' |
&$stack |
Takes a literal and either discards or keeps it
void minifyLiteral( mixed &$part, mixed &$buffer, string $type )
mixed | &$part | The literal to process |
mixed | &$buffer | The last literal or code processed |
string | $type | The language the literal is in, 'css' or 'js' |
Includes the element specified - element must be set through set() first
If the element is a file path ending in .css, .js, .rss or .xml an appropriate HTML tag will be printed (files ending in .xml will be treated as an RSS feed). If the element is a file path ending in .inc, .php or .php5 it will be included.
Paths that start with ./ will be loaded relative to the current script. Paths that start with a file or directory name will be loaded relative to the $root passed in the constructor. Paths that start with / will be loaded from the root of the filesystem.
You can pass the media attribute of a CSS file or the title attribute of an RSS feed by adding an associative array with the following formats:
array(
'path' => (string) {css file path},
'media' => (string) {media type}
);
array(
'path' => (string) {rss file path},
'title' => (string) {feed title}
);
void place( string $element='__main__', string $file_type=NULL )
string | $element | The element to place |
string | $file_type | Will force the element to be placed as this type of file instead of auto-detecting the file type. Valid types include: 'css', 'js', 'php' and 'rss'. |
Prints a CSS link HTML tag to the output
void placeCSS( mixed $info )
mixed | $info | The path or array containing the 'path' to the CSS file. Array can also contain a key 'media'. |
Performs the action of actually placing an element
void placeElement( string $element, string $file_type )
string | $element | The element that is being placed |
string | $file_type | The file type to treat all values as |
Prints a javascript HTML tag to the output
void placeJS( mixed $info )
mixed | $info | The path or array containing the 'path' to the javascript file |
Includes a PHP file
void placePHP( string $element, string $path )
string | $element | The element being placed |
string | $path | The path to the PHP file |
Prints an RSS link HTML tag to the output
void placeRSS( mixed $info )
mixed | $info | The path or array containing the 'path' to the RSS xml file. May also contain a 'title' key for the title of the RSS feed. |
Gets the value of an element and runs it through fHTML::prepare()
mixed prepare( string $element, mixed $default_value=NULL )
string | $element | The element to get - array elements can be access via [sub-key] syntax, and thus [ and ] can not be used in element names |
mixed | $default_value | The value to return if the element has not been set |
The value of the element specified run through fHTML::prepare(), or the default value if it has not been set
Removes and returns the value from the end of an array element
mixed remove( string $element, boolean $beginning=FALSE )
string | $element | The element to remove from to - array elements can be modified via [sub-key] syntax, and thus [ and ] can not be used in element names |
boolean | $beginning | If the value should be removed from the beginning of the element |
The value that was removed
Sets the value for an element
fTemplating set( string $element, mixed $value )
fTemplating set( array $elements )
string | $element | The element to set - the magic element __main__ is used for placing the current fTemplating object as a child of another fTemplating object - array elements can be modified via [sub-key] syntax, and thus [ and ] can not be used in element names |
mixed | $value | The value for the element |
array | $elements | An associative array with the key being the $element to set and the value being the $value for that element |
The template object, to allow for method chaining
Ensures the value is valid
string verifyValue( string $element, mixed $value, string $file_type=NULL )
string | $element | The element that is being placed |
mixed | $value | A value to be placed |
string | $file_type | The file type that this element will be displayed as - skips checking file extension |
The file type of the value being placed
The fText class is a static class with the sole purpose of creating a hook to allow for writing internationalized application and localizing Flourish. None of the Flourish classes require it be loaded, but if it is, all message/parameter interpolation is passed through this class.
The fText class includes a key method to help with the i18n of code. The method compose()
allows creating a message in multiple pieces that are later interpolated, allowing for efficient translation efforts.
Typically, a call to compose()
will look like:
// Example variable values
$message_type = 'error';
$message_part_name = 'parts';
echo fText::compose(
'This is an example of displaying an %1$s message containing variable %2$s',
$message_type,
$message_part_name
);
which would produce the result:
This is an example of displaying an error message containing variable parts
At its core, compose()
is simply a wrapper around sprintf()
. However, being a wrapper allows setting up two hooks for translation. The method registerComposeCallback()
allows us to intercept any string sent to compose()
and modify it. The first parameter, $timing
, allow registering the callback 'pre'
or 'post'
the sprintf()
call. The second parameter, $callback
, defines which method to pass the string to.
Any callback registered pre
will get the un-interpolated string, while any callback registered post
will get the interpolated string. Here is an example of translation using compose:
function translate($string)
{
static $translations = array(
'This is an example of displaying an %1$s message containing variable %2$s' => 'This is an %1$s message containing %2$s'
);
if (isset($translations[$string])) {
return $translations[$string];
}
return $string;
}
fText::registerComposeCallback('pre', 'translate');
$message_type = 'error';
$message_part_name = 'parts';
echo fText::compose(
'This is an example of displaying an %1$s message containing variable %2$s',
$message_type,
$message_part_name
);
The above PHP would output the following:
This is an error message containing parts
Every exception and error in Flourish will be passed through compose()
, thus allowing for localization of the Flourish code base. Please see the MessagesList page for a list of all messages and their location in the source code.
Provides internationlization support for strings
1.0.0b2 | Updated compose() to more handle $components passed as an array 2/5/09 |
---|---|
1.0.0b | The initial implementation 11/12/08 |
Performs an sprintf() on a string and provides a hook for modifications such as internationalization
string compose( string $message, mixed $component [, ... ] )
string | $message | A message to compose |
mixed | $component [, ... ] | A string or number to insert into the message |
The composed message
Adds a callback for when a message is created using compose()
The primary purpose of these callbacks is for internationalization of error messaging in Flourish. The callback should accept a single parameter, the message being composed and should return the message with any modifications.
The timing parameter controls if the callback happens before or after the actual composition takes place, which is simply a call to sprintf(). Thus the message passed 'pre' will always be exactly the same, while the message 'post' will include the interpolated variables. Because of this, most of the time the 'pre' timing should be chosen.
void registerComposeCallback( string $timing, callback $callback )
string | $timing | When the callback should be executed - 'pre' or 'post' performing the actual composition |
callback | $callback | The callback |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Resets the configuration of the class
void reset( )
The fTime class is a value object representation of a time. One of the primary attributes of the object is that its value can not be changed, but instead a new object is created.
The fTime constructor takes a single argument, a string, object or integer representing a time of day. For strings and objects (with __toString()
methods), any format accepted by strtotime()
will work. If the parameter is an integer, it will be interpreted as a unix timestamp and the date portion will be discarded.
$time1 = new fTime('now');
$time2 = new fTime('+1 hour');
$time3 = new fTime('9:14 am');
$time4 = new fTime(1223926420);
Rather than allowing an fTime object value to be modified, which can create issues since objects are passed by reference, all changes to a time create a new object.
Usually when modifying a time, only one or two components (such as hour or minute) of the time will change. The modify()
method leverages the formatting codes from the date()
function to keep parts of the existing time while replacing others.
Here are some examples of modify()
:
// The new time would have the same minutes and seconds, but the hour would be set to 5am
$new_time = $time1->modify('5:i:s');
// The new time would be the same hour, but the very beginning
$new_time = $time2->modify('H:00:00');
// The new time would be the same hour, but the very end
$new_time = $time3->modify('H:59:59');
Occasionally you may have the need to adjust a time. The adjust()
method takes a single parameter which can contain any relative time measurement that strtotime()
accepts. Since fTime is a value object, a new object is returned with the adjusted time. Here are some examples:
$new_time = $time1->adjust('+1 hour');
$new_time = $time2->adjust('-2 hours +5 minutes');
To format the time, simply call the format()
method with any valid time formatting string from date()
. Here are some examples:
echo $time1->format('g:ia');
echo $time2->format('H:i:s');
There are five different methods available to compare times, eq()
, gt()
, gte()
, lt()
and lte()
. Each method optionally accepts a parameter $other_time
. If no $other_time
is specified, the time is compared to the current time. If $other_time
is specified, the two are compared. $other_time
accepts any valid date string that works with __construct()
.
Here are some examples:
$now = new fTime();
$hour_ago = new fTime('-1 hour');
// These return TRUE
$now->eq();
$now->eq('now');
$now->gt($hour_ago);
$now->gte($hour_ago);
$now->lt('+5 min');
// These calls return FALSE
$hour_ago->lt($now);
$now->gt($hour_ago);
$now->gte($hour_ago);
If you are looking to get a fuzzy difference between two times for display, youll want to use the getFuzzyDifference()
method. The first parameter, $other_time
, optionally accepts a valid time descriptor that can be passed to __construct()
. If a valid time descriptor is passed, the difference will be between the two times, if nothing is passed, the difference will be between the fTime and the current time.
The value returned by getFuzzyDifference()
will be a string representing the most broad time measurement between the two times. In addition, if the difference is just shy of the next largest time measurement, it will be rounded up. Thus 52 minutes would become 1 hour.
Here are some examples to clarify. The following examples are comparing two times:
$time1 = new fTime('12:24 pm');
$time2 = new fTime('3:24 pm');
echo $time1->getFuzzyDifference($time2);
// Output: 3 hours before
echo $time2->getFuzzyDifference($time1);
// Output: 3 hours after
$time3 = new fTime('12:31 pm');
echo $time3->getFuzzyDifference('12:24 pm');
// Output: 7 minutes after
$time4 = new fTime('12:24:57 pm');
echo $time4->getFuzzyDifference('12:24 pm');
// Output: 1 minute after
These examples show output when comparing an fTime object with the current time:
// First, lets assume the time is currently 1:19 am.
$time1 = new fTime('11:59 pm');
$time2 = new fTime('12:53 am');
$time3 = new fTime('7:00 am');
echo $time1->getFuzzyDifference();
// Output: 1 day from now
echo $time2->getFuzzyDifference();
// Output: 26 minutes ago
echo $time3->getFuzzyDifference();
// Output: 6 hours from now
An optional boolean parameter, $simple
, can also be passed to getFuzzyDifference()
. When TRUE
, this parameter causes the method to return the difference in time, but not the direction.
$time1 = new fTime('12:24 pm');
$time2 = new fTime('3:24 pm');
echo $time1->getFuzzyDifference($time2, TRUE);
// Output: 3 hours
echo $time2->getFuzzyDifference($time1, TRUE);
// Output: 3 hours
$time3 = new fTime('12:31 pm');
echo $time3->getFuzzyDifference('12:24 pm', TRUE);
// Output: 7 minutes
Represents a time of day as a value object
1.0.0b12 | Fixed a method signature 8/24/11 |
---|---|
1.0.0b11 | Fixed a bug with the constructor not properly handling unix timestamps that are negative integers 6/2/11 |
1.0.0b10 | Changed the $time attribute to be protected 3/20/11 |
1.0.0b9 | Added the $simple parameter to getFuzzyDifference() 3/15/10 |
1.0.0b8 | Added a call to fTimestamp::callUnformatCallback() in __construct() for localization support 6/1/09 |
1.0.0b7 | Backwards compatibility break - Removed getSecondsDifference(), added eq(), gt(), gte(), lt(), lte() 3/5/09 |
1.0.0b6 | Fixed an outdated fCore method call 2/23/09 |
1.0.0b5 | Updated for new fCore API 2/16/09 |
1.0.0b4 | Fixed __construct() to properly handle the 5.0 to 5.1 change in strtotime() 1/21/09 |
1.0.0b3 | Added support for CURRENT_TIMESTAMP and CURRENT_TIME SQL keywords 1/11/09 |
1.0.0b2 | Removed the adjustment amount check from adjust() 12/31/08 |
1.0.0b | The initial implementation 2/12/08 |
A timestamp of the time
integer
Composes text using fText if loaded
string compose( string $message, mixed $component [, ... ] )
string | $message | The message to compose |
mixed | $component [, ... ] | A string or number to insert into the message |
The composed and possible translated message
Creates the time to represent, no timezone is allowed since times don't have timezones
fTime __construct( fTime|object|string|integer $time=NULL )
fTime|object|string|integer | $time | The time to represent, NULL is interpreted as now |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
All requests that hit this method should be requests for callbacks
callback __get( string $method )
string | $method | The method to create a callback for |
The callback for the method requested
Returns this time in 'H:i:s' format
string __toString( )
The 'H:i:s' format of this time
Changes the time by the adjustment specified, only adjustments of 'hours', 'minutes', and 'seconds' are allowed
fTime adjust( string $adjustment )
string | $adjustment | The adjustment to make |
The adjusted time
If this time is equal to the time passed
boolean eq( fTime|object|string|integer $other_time=NULL )
fTime|object|string|integer | $other_time | The time to compare with, NULL is interpreted as today |
If this time is equal to the one passed
Formats the time
string format( string $format )
string | $format | The date() function compatible formatting string, or a format name from fTimestamp::defineFormat() |
The formatted time
Returns the approximate difference in time, discarding any unit of measure but the least specific.
The output will read like:
Examples of output for a time passed might be:
Examples of output for no time passed might be:
You would never get the following output since it includes more than one unit of time measurement:
Values that are close to the next largest unit of measure will be rounded up:
string getFuzzyDifference( fTime|object|string|integer $other_time=NULL, boolean $simple=FALSE )
string getFuzzyDifference( boolean $simple=FALSE )
fTime|object|string|integer | $other_time | The time to create the difference with, NULL is interpreted as now |
boolean | $simple | When TRUE, the returned value will only include the difference in the two times, but not from now, ago, after or before |
The fuzzy difference in time between the this time and the one provided
If this time is greater than the time passed
boolean gt( fTime|object|string|integer $other_time=NULL )
fTime|object|string|integer | $other_time | The time to compare with, NULL is interpreted as now |
If this time is greater than the one passed
If this time is greater than or equal to the time passed
boolean gte( fTime|object|string|integer $other_time=NULL )
fTime|object|string|integer | $other_time | The time to compare with, NULL is interpreted as now |
If this time is greater than or equal to the one passed
If this time is less than the time passed
boolean lt( fTime|object|string|integer $other_time=NULL )
fTime|object|string|integer | $other_time | The time to compare with, NULL is interpreted as today |
If this time is less than the one passed
If this time is less than or equal to the time passed
boolean lte( fTime|object|string|integer $other_time=NULL )
fTime|object|string|integer | $other_time | The time to compare with, NULL is interpreted as today |
If this time is less than or equal to the one passed
Modifies the current time, creating a new fTime object
The purpose of this method is to allow for easy creation of a time based on this time. Below are some examples of formats to modify the current time:
fTime modify( string $format )
string | $format | The current time will be formatted with this string, and the output used to create a new object |
The new time
The fTimestamp class is a value object representation of a date/time. One of the primary attributes of the object is that its value can not be changed, but instead a new object is created. This object has full support for timezones.
This class is built on top of the PHP date/time functions and can only handle dates ranging from 19012038.
The fTimestamp class is fully compatible with timezones thanks the the great changes in PHP 5.1. Classically timezones were represented by short abbreviations such as EST
, GMT
, PST
, etc. To help remove abiguity, PHP now recommends using the zoneinfo timezone names.
Due to the revamped timezone support in PHP 5.1, it is now necessary to define the default timezone. This is done to ensure the proper timezone is being used and also prevent strict error reporting messages from appearing. fTimestamp provides two methods for setting and getting the default timezone, setDefaultTimezone()
and getDefaultTimezone()
.
// Set the default timezone to New York time
fTimestamp::setDefaultTimezone('America/New_York');
echo fTimestamp::getDefaultTimezone();
// Output: America/New_York
There will be more timezone discussion throughout the rest of this document in the relevant places.
The fTimestamp constructor takes two parameters, including $datetime
and $timezone
. $datetime
is a string, object (with a __toString()
method) or integer representing a date/time. For strings and objects, any format accepted by strtotime()
will work. Integers are interpreted as a unix timestamp. $timezone
is optional and if passed should be a string with a zoneinfo timezone name. This timezone will be used throughout the life of the timestamp.
// Date/times without a timezone
$timestamp1 = new fTimestamp('today');
$timestamp2 = new fTimestamp('now');
$timestamp3 = new fTimestamp('3 Feb 2008 5:12 pm');
$timestamp4 = new fTimestamp('8 am');
$timestamp5 = new fTimestamp('next wednesday');
$timestamp6 = new fTimestamp(1223926420);
// Date/times with a timezone
$timestamp7 = new fTimestamp('2008-03-01 1 pm', 'America/New_York');
$timestamp8 = new fTimestamp('2008-03-01 1 pm', 'America/Los_Angeles');
$timestamp9 = new fTimestamp('2008-03-01 1 pm', 'Europe/London');
Rather than allowing an fTimestamp object value to be modified, which can create issues since objects are passed by reference, all changes to a timestamp create a new object.
Usually when modifying a timestamp, only one or two components (such as month, year, hour or minute) of the timestamp will change. The modify()
method leverages the formatting codes from the date()
function to keep parts of the existing timestamp while replacing others.
Here are some examples of modify()
:
// The new timestamps year would be 2007 while the rest would be the same
$new_timestamp = $timestamp1->modify('2007-m-d H:i:s');
// The new timestamp would be the 1st day (Monday) of the 9th week of the year
$new_timestamp = $timestamp2->modify('Y-\W9-1 00:00:00');
// The new timestamp would be moved to the beginning of the hour
$new_timestamp = $timestamp3->modify('Y-m-d H:00:00');
// The new timestamp would be moved to the end of the hour
$new_timestamp = $timestamp4->modify('Y-m-d H:59:59');
It is also possible to set the timezone of the new timestamp. The timezone can be passed as the optional second parameter to modify()
. If no timezone is specified, the new timestamp will have the same timezone as the original.
$timestamp = new fTimestamp('now', 'America/New_York');
// This new timestamp is different by three hours due to the timezone change
$new_timestamp = $timestamp->modify('c', 'America/Los_Angeles');
Occasionally you may have the need to adjust a timestamp. The adjust()
method takes a single parameter which can contain any relative time measurement that strtotime()
accepts. Since fTimestamp is a value object, a new object is returned with the adjusted value. Here are some examples:
$new_timestamp = $timestamp1->adjust('tomorrow');
$new_timestamp = $timestamp2->adjust('+1 day');
$new_timestamp = $timestamp3->adjust('-2 years +1 week +5 hours');
$new_timestamp = $timestamp4->adjust('next wednesday');
$new_timestamp = $timestamp5->adjust('+1 hour');
$new_timestamp = $timestamp6->adjust('-2 hours +5 minutes +3 seconds');
Adjustments can also be adjustments of timezone. If a valid timezone is passed, the actual date/time will not be changed, however the date/time will appear different from format()
.
$new_timestamp = $timestamp1->adjust('America/Los_Angeles');
// Since the unix timestamp is always in UTC, these will be equal
if ($new_timestamp->format('U') == $timestamp1->format('U')) {
echo 'The date/time has not changed, but the timezone has';
}
To format the timestamp, simply call the format()
method with any valid formatting string from date()
. Here are some examples:
// Normal date/time formatting
echo $timestamp1->format('Y-m-d H:i:s');
echo $timestamp2->format('n/j/y g:ia');
// Using format to retrieve the timezone
echo $timestamp3->format('e');
When dealing with date across a site or application, it is easy to create inconsistent formatting. In an effort to encourage consistency and at the same time prevent the need to clutter the global namespace with constants, the defineFormat()
static method allows for creating named formats for use with the format()
method and the method fTime::format() and fDate::format().
Definition of the formats would logically go in a configuration file. defineFormat()
takes two parameters, the $name
and the $formatting_string
.
fTimestamp::defineFormat('list_date', 'n/j/y');
fTimestamp::defineFormat('list_time', 'g:ia');
Once defined, the format names can be passed into the format()
method of fDate, fTime and fTimestamp.
echo $timestamp->format('list_date');
echo $time->format('list_time');
There are five different methods available to compare timestamps, eq()
, gt()
, gte()
, lt()
and lte()
. Each method optionally accepts a parameter $other_timestamp
. If no $other_timestamp
is specified, the timestamp is compared to the current timestamp. If $other_timestamp
is specified, the two are compared. $other_timestamp
accepts any valid date string that works with __construct()
.
Here are some examples:
$now = new fTimestamp();
$day_ago = new fTimestamp('-1 day');
// These return TRUE
$now->eq();
$now->eq('now');
$now->gt($day_ago);
$now->gte($day_ago);
$now->lt('+5 min');
// These calls return FALSE
$day_ago->gt($now);
$now->lt($day_ago);
$now->lte($day_ago);
If you are looking to get a fuzzy difference between two timestamps for display, youll want to use the getFuzzyDifference()
method. The first parameter, $other_timestamp
, optionally accepts a valid timestamp descriptor that can be passed to __construct()
. If a valid timestamp descriptor is passed, the difference will be between the two timestamps, if nothing is passed, the difference will be between the fTimestamp and the current timestamp.
The value returned by getFuzzyDifference()
will be a string representing the most broad time measurement between the two timestamps. In addition, if the difference is just shy of the next largest time measurement, it will be rounded up. Thus 3.5 weeks would become 1 month.
Here are some examples to clarify. The following examples are comparing two timestamps:
$timestamp1 = new fTimestamp('2008-01-01 8:00 am');
$timestamp2 = new fTimestamp('2008-01-04 5:00 pm');
echo $timestamp1->getFuzzyDifference($timestamp2);
// Output: 3 days before
echo $timestamp2->getFuzzyDifference('2008-01-01 8:00 am');
// Output: 3 days after
$timestamp3 = new fTimestamp('2008-01-10 1:00 am');
echo $timestamp3->getFuzzyDifference($timestamp1);
// Output: 1 week after
$timestamp4 = new fTimestamp('2008-01-28 12:00pm');
echo $timestamp4->getFuzzyDifference('2008-01-01 8:00 am');
// Output: 1 month after
These examples show output when comparing an fTimestamp object with the current timestamp:
// First, lets assume the current day/time is January 1st, 2008 at 9:00 am.
$timestamp1 = new fTimestamp('2008-01-01 12:00 pm');
$timestamp2 = new fTimestamp('2008-01-09 9:00 am');
$timestamp3 = new fTimestamp('2007-12-02 5:00 pm');
echo $timestamp1->getFuzzyDifference();
// Output: 3 hours from now
echo $timestamp2->getFuzzyDifference();
// Output: 1 week from now
echo $timestamp3->getFuzzyDifference();
// Output: 1 month ago
An optional boolean parameter, $simple
, can also be passed to getFuzzyDifference()
. When TRUE
, this parameter causes the method to return the difference in time, but not the direction.
$timestamp1 = new fTimestamp('2008-01-01 8:00 am');
$timestamp2 = new fTimestamp('2008-01-04 5:00 pm');
echo $timestamp1->getFuzzyDifference($timestamp2, TRUE);
// Output: 3 days
echo $timestamp2->getFuzzyDifference('2008-01-01 8:00 am', TRUE);
// Output: 3 days
$timestamp3 = new fTimestamp('2008-01-10 1:00 am');
echo $timestamp3->getFuzzyDifference($timestamp1, TRUE);
// Output: 1 week
PHP contains built-in support for formatting date and times in different languages via the setlocale()
function. This function, however, has a number of shortcomings including it requiring a non-threaded web server and requiring that locale files be installed for each locale to support.
The fTimestamp class provides a hook for formatting fDate, fTime and fTimestamp objects in whatever fashion is necessary. A callback can be assigned to the hook by passing it to the static method registerFormatCallback()
. The callback should accept a single string and return a single string.
It is also possible to parse locale-specific date/time/timestamp strings by passing a callback to registerUnformatCallback()
. The callback should accept a string and return a string that will properly be parsed by strtotime()
. An example of a valid return string would be 2009-05-01 15:22:01
.
Below is an example of how the hooks could be used.
function parse_uk_dates($date_time_string)
{
if (preg_match('#^(\d{1,2})/(\d{1,2})/(\d{2}|\d{4})$#', $date_time_string, $matches)) {
if (strlen($matches[3]) == 2) {
$matches[3] = ($matches[3] <= 37) ? '20' . $matches[3] : '19' . $matches[3];
}
return $matches[3] . '-' . $matches[2] . '-' . $matches[1];
}
return $date_time_string;
}
function translate_dates_to_spanish($formatted_string)
{
$replacements = array(
'Monday' => 'lunes',
// ...
'Mon' => 'lun',
// ...
);
return strtr($formatted_string, $replacements);
}
fTimestamp::registerFormatCallback('translate_dates_to_spanish');
fTimestamp::registerUnformatCallback('parse_uk_dates');
Represents a date and time as a value object
1.0.0b13 | Fixed a method signature 8/24/11 |
---|---|
1.0.0b12 | Fixed a bug with the constructor not properly handling unix timestamps that are negative integers 6/2/11 |
1.0.0b11 | Changed the $timestamp and $timezone attributes to be protected 3/20/11 |
1.0.0b10 | Fixed a bug in __construct() with specifying a timezone other than the default for a relative time string such as "now" or "+2 hours" 7/5/10 |
1.0.0b9 | Added the $simple parameter to getFuzzyDifference() 3/15/10 |
1.0.0b8 | Fixed a bug with fixISOWeek() not properly parsing some ISO week dates 10/6/09 |
1.0.0b7 | Fixed a translation bug with getFuzzyDifference() 7/11/09 |
1.0.0b6 | Added registerUnformatCallback() and callUnformatCallback() to allow for localization of date/time parsing 6/1/09 |
1.0.0b5 | Backwards compatibility break - Removed getSecondsDifference() and getSeconds(), added eq(), gt(), gte(), lt(), lte() 3/5/09 |
1.0.0b4 | Updated for new fCore API 2/16/09 |
1.0.0b3 | Removed a useless double check of the strtotime() return value in __construct() 1/21/09 |
1.0.0b2 | Added support for CURRENT_TIMESTAMP, CURRENT_DATE and CURRENT_TIME SQL keywords 1/11/09 |
1.0.0b | The initial implementation 2/12/08 |
The date/time
integer
The timezone for this date/time
string
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
If a format callback is defined, call it
string callFormatCallback( string $formatted_string )
string | $formatted_string | The formatted date/time/timestamp string to be (possibly) modified |
The (possibly) modified formatted string
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
If an unformat callback is defined, call it
string callUnformatCallback( string $date_time_string )
string | $date_time_string | A raw date/time/timestamp string to be (possibly) parsed/modified |
The (possibly) parsed or modified date/time/timestamp
Composes text using fText if loaded
string compose( string $message, mixed $component [, ... ] )
string | $message | The message to compose |
mixed | $component [, ... ] | A string or number to insert into the message |
The composed and possible translated message
Creates a reusable format for formatting fDate, fTime, and fTimestamp objects
void defineFormat( string $name, string $formatting_string )
string | $name | The name of the format |
string | $formatting_string | The format string compatible with the date() function |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Fixes an ISO week format into 'Y-m-d' so strtotime() will accept it
string fixISOWeek( string $date )
string | $date | The date to fix |
The fixed date
Provides a consistent interface to getting the default timezone. Wraps the date_default_timezone_get() function.
string getDefaultTimezone( )
The default timezone used for all date/time calculations
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Checks to see if a timezone is valid
boolean isValidTimezone( string $timezone )
string | $timezone | The timezone to check |
If the timezone is valid
Allows setting a callback to translate or modify any return values from format(), fDateformat() and fTime::format()
void registerFormatCallback( callback $callback )
callback | $callback | The callback to pass all formatted dates/times/timestamps through. Should accept a single string and return a single string. |
Allows setting a callback to parse any date strings passed into __construct(), fDate__construct() and fTime::__construct()
void registerUnformatCallback( callback $callback )
callback | $callback | The callback to pass all date/time/timestamp strings through. Should accept a single string and return a single string that is parsable by strtotime(). |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Resets the configuration of the class
void reset( )
Provides a consistent interface to setting the default timezone. Wraps the date_default_timezone_set() function.
void setDefaultTimezone( string $timezone )
string | $timezone | The default timezone to use for all date/time calculations |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Takes a format name set via defineFormat() and returns the date() function formatting string
string translateFormat( string $format )
string | $format | The format to translate |
The formatting string. If no matching format was found, this will be the same as the $format parameter.
Creates the date/time to represent
fTimestamp __construct( fTimestamp|object|string|integer $datetime=NULL, string $timezone=NULL )
fTimestamp|object|string|integer | $datetime | The date/time to represent, NULL is interpreted as now |
string | $timezone | The timezone for the date/time. This causes the date/time to be interpretted as being in the specified timezone. If not specified, will default to timezone set by setDefaultTimezone(). |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
All requests that hit this method should be requests for callbacks
callback __get( string $method )
string | $method | The method to create a callback for |
The callback for the method requested
Returns this date/time
string __toString( )
The 'Y-m-d H:i:s' format of this date/time
Changes the date/time by the adjustment specified
fTimestamp adjust( string $adjustment )
string | $adjustment | The adjustment to make - may be a relative adjustment or a different timezone |
The adjusted date/time
If this timestamp is equal to the timestamp passed
boolean eq( fTimestamp|object|string|integer $other_timestamp=NULL )
fTimestamp|object|string|integer | $other_timestamp | The timestamp to compare with, NULL is interpreted as today |
If this timestamp is equal to the one passed
Formats the date/time
string format( string $format )
string | $format | The date() function compatible formatting string, or a format name from defineFormat() |
The formatted date/time
Returns the approximate difference in time, discarding any unit of measure but the least specific.
The output will read like:
Examples of output for a timestamp passed might be:
Examples of output for no timestamp passed might be:
You would never get the following output since it includes more than one unit of time measurement:
Values that are close to the next largest unit of measure will be rounded up:
string getFuzzyDifference( fTimestamp|object|string|integer $other_timestamp=NULL, boolean $simple=FALSE )
string getFuzzyDifference( boolean $simple=FALSE )
fTimestamp|object|string|integer | $other_timestamp | The timestamp to create the difference with, NULL is interpreted as now |
boolean | $simple | When TRUE, the returned value will only include the difference in the two timestamps, but not from now, ago, after or before |
The fuzzy difference in time between the this timestamp and the one provided
If this timestamp is greater than the timestamp passed
boolean gt( fTimestamp|object|string|integer $other_timestamp=NULL )
fTimestamp|object|string|integer | $other_timestamp | The timestamp to compare with, NULL is interpreted as now |
If this timestamp is greater than the one passed
If this timestamp is greater than or equal to the timestamp passed
boolean gte( fTimestamp|object|string|integer $other_timestamp=NULL )
fTimestamp|object|string|integer | $other_timestamp | The timestamp to compare with, NULL is interpreted as now |
If this timestamp is greater than or equal to the one passed
If this timestamp is less than the timestamp passed
boolean lt( fTimestamp|object|string|integer $other_timestamp=NULL )
fTimestamp|object|string|integer | $other_timestamp | The timestamp to compare with, NULL is interpreted as today |
If this timestamp is less than the one passed
If this timestamp is less than or equal to the timestamp passed
boolean lte( fTimestamp|object|string|integer $other_timestamp=NULL )
fTimestamp|object|string|integer | $other_timestamp | The timestamp to compare with, NULL is interpreted as today |
If this timestamp is less than or equal to the one passed
Modifies the current timestamp, creating a new fTimestamp object
The purpose of this method is to allow for easy creation of a timestamp based on this timestamp. Below are some examples of formats to modify the current timestamp:
fTimestamp modify( string $format, string $timezone=NULL )
string | $format | The current timestamp will be formatted with this string, and the output used to create a new object. The format should not include the timezone (character e). |
string | $timezone | The timezone for the new object if different from the current timezone |
The new timestamp
The fURL class provides a simple and useful set of features for dealing with URLs in your site or application.
There are a few static methods that allow retrieval of URL information. All of these static methods return information about the URL as requested by the browser. All server-side rewrites are ignored.
Method | Description |
get() |
Returns the current URL minus the query string and domain name |
getQueryString() |
Returns the current query string |
getWithQueryString() |
Returns the current URL minus the domain name |
getDomain() |
Returns the current domain name with protocol prefix |
Here are examples of them being used:
// Since all of the methods are static, no instance of the fURL object is required
$url = fURL::get();
$qs = fURL::getQueryString();
$url_qs = fURL::getWithQueryString();
$domain = fURL::getDomain();
Beyond simply retrieving information about the URL, the fURL class also provides some functionality to modify the query string.
The replaceInQueryString()
and removeFromQueryString()
methods allow addition/replacement and deletion of a values in the query string, respectively. Similar to the various get
methods, these methods work with the URL before any server-side rewrites were applied.
replaceInQueryString()
takes two parameters, $key
and $value
. Any existing value for the key will be replaced. If no value exists for the key, the key will be added with the new value. Here is the method in use:
// The following code assumes the query string is: example=one&example_2=false
// $new_qs would end up being: example=one&example_2=false&example_3=today
$new_qs = fURL::replaceInQueryString('example_3', 'today');
// $new_qs would end up being: example=two&example_2=false
$new_qs = fURL::replaceInQueryString('example', 'two');
removeFromQueryString()
takes any number of parameters with each one being a GET
key/field. Any matching key in the query string will be removed. Here is an example of the method in action:
// The following code assumes the query string is: example=one&example_2=false
// $new_qs would end up being: example=one
$new_qs = fURL::removeFromQueryString('example_2');
The redirect()
method was built to simplify the task of redirecting a user to another web page. Simply pass the name of the site/page to redirect to as the only parameter and the method will determine the type of link it is (relative, web server absolute, etc.).
Since headers can only be sent before content, you will either need to ensure no content has been sent to the user yet, or that output buffering has been enabled. The fBuffer class may be of interest if you are going to use output buffering.
The method will redirect to almost any URL provided, and will do so in standard-compliant way by sending a fully-formed (including protocol and domain name) URL in the HTTP headers. Here are some examples of how to use the redirect()
method:
// Redirect to an absolute web server path
fURL::redirect('/supplemental/example.php');
// Redirect to a relative page
fURL::redirect('example.php');
// Redirect to the current page with a different query string
fURL::redirect('?parameter=TRUE');
// Redirect to a fully-formed URL
fURL::redirect('http://example.com');
fURL::redirect('https://example.com/example.php');
When creating websites with friendly URLs it is useful to be able to convert the title of something into a human-readable url without having to worry about punctuation or characters with diacritics creating those nasty hexadecimal codes.
The makeFriendly()
method performs this URL creation task. You can pass in any UTF-8 string and you will get back a URL made up of lowercase letters, numbers and the underscore character. All characters with diacritics will be converted to their plain counterparts.
Here are some examples:
// $url will be set to: /users/view/1/john_smith
$url = '/users/view/1/' . fURL::makeFriendly('John Smith');
// $url will be set to: /users/view/2/renee_smith
$url = '/users/view/2/' . fURL::makeFriendly('Rene Smith');
// $url will be set to: /news/view/1/notice_this_title_will_work_fine
$url = '/news/view/1/' . fURL::makeFriendly('Notice: This Title Will Work Fine!');
It is possible to limit the character length of the generated string by passing a number to the optional second parameter $max_length
.
// Limit the URL to 40 characters
$limited_url = fURL::makeFriendly('This is a test of limiting the length of a URL', 40);
By default makeFriendly()
uses the _
character to separate words. The optional third parameter, $delimiter
, allows for changing this. If no length restriction is desired, the $delimiter
can be passed as the second parameter.
// Limit the URL to 40 characters and use - for the delimiter
$limited_url = fURL::makeFriendly('This is a test of length limiting and using a different delimiter', 40, '-');
// Use - for the delimiter
$limited_url = fURL::makeFriendly('This is a test of using a separate delimiter', '-');
Provides functionality to retrieve and manipulate URL information
This class uses $_SERVER['REQUEST_URI'] for all operations, meaning that the original URL entered by the user will be used, or that any rewrites will not be reflected by this class.
1.0.0b10 | Fixed some method signatures 8/24/11 |
---|---|
1.0.0b9 | Fixed redirect() to handle no parameters properly 6/13/11 |
1.0.0b8 | Added the $delimiter parameter to makeFriendly() 6/3/11 |
1.0.0b7 | Fixed redirect() to be able to handle unqualified and relative paths 3/2/11 |
1.0.0b6 | Added the $max_length parameter to makeFriendly() 9/19/10 |
1.0.0b5 | Updated redirect() to not require a URL, using the current URL as the default 7/29/09 |
1.0.0b4 | getDomain() now includes the port number if non-standard 5/2/09 |
1.0.0b3 | makeFriendly() now changes _-_ to - and multiple _ to a single _ 3/24/09 |
1.0.0b2 | Fixed makeFriendly() so that _ doesn't appear at the beginning of URLs 3/22/09 |
1.0.0b | The initial implementation 6/14/07 |
Returns the requested URL, does no include the domain name or query string
This will return the original URL requested by the user - ignores all rewrites.
string get( )
The requested URL without the query string
Returns the current domain name, with protcol prefix. Port will be included if not 80 for HTTP or 443 for HTTPS.
string getDomain( )
The current domain name, prefixed by http:// or https://
Returns the current query string, does not include parameters added by rewrites
string getQueryString( )
The query string
Returns the current URL including query string, but without domain name - does not include query string parameters from rewrites
string getWithQueryString( )
The URL with query string
Changes a string into a URL-friendly string
string makeFriendly( string $string, integer $max_length=NULL, string $delimiter=NULL )
string makeFriendly( string $string, string $delimiter=NULL )
string | $string | The string to convert |
integer | $max_length | The maximum length of the friendly URL |
string | $delimiter | The delimiter to use between words, defaults to _ |
The URL-friendly version of the string
Redirects to the URL specified, without requiring a full-qualified URL
void redirect( string $url=NULL )
string | $url | The url to redirect to |
Removes one or more parameters from the query string
This method uses the query string from the original URL and will not contain any parameters that are from rewrites.
string removeFromQueryString( string $parameter [, ... ] )
string | $parameter [, ... ] | A parameter to remove from the query string |
The query string with the parameter(s) specified removed, first character is ?
Replaces a value in the query string
This method uses the query string from the original URL and will not contain any parameters that are from rewrites.
string replaceInQueryString( string|array $parameter, string|array $value )
string|array | $parameter | The query string parameter |
string|array | $value | The value to set the parameter to |
The full query string with the parameter replaced, first char is ?
The class fUTF8 is a static class that provides UTF-8 compatible versions of almost every string function that is provided with PHP. Since UTF-8 uses multiple bytes of data for some characters and the built-in PHP string functions are built to work with single-byte encodings, many of the PHP string functions will perform incorrectly on UTF-8 strings.
There is a PHP extension called mbstring that is designed for dealing with multi-byte string encodings, however it is not installed by default, does not include many commonly used functions, and contains some bugs. The fUTF8 class will use the mbstring extension for performance benefits in appropriate situations if it is installed.
The table below contains a list of the built-in PHP string functions with the equivalent fUTF8 method beside it. Any additional features or differences will also be listed.
PHP Function | !fUTF8 Method | Differences |
chr() |
chr() |
Accepts U+hex or decimal Unicode code point instead of ASCII decimal value |
explode() |
explode() |
Parameter order is switched to $string , $delimeter - also accepts NULL delimeter to explode into characters |
ltrim() |
ltrim() |
|
ord() |
ord() |
Returns U+hex Unicode code point instead of ASCII decimal value |
rtrim() |
rtrim() |
|
str_ireplace() |
ireplace() |
|
str_pad() |
pad() |
|
str_replace() |
replace() |
|
strcasecmp() |
icmp() |
Letters that are ASCII letters with diacritics are sorted right after the base ASCII letter |
strcmp() |
cmp() |
Letters that are ASCII letters with diacritics are sorted right after the base ASCII letter |
stripos() |
ipos() |
|
stristr() |
istr() |
|
strlen() |
len() |
|
strnatcasecmp() |
inatcmp() |
Letters that are ASCII letters with diacritics are sorted right after the base ASCII letter |
strnatcmp() |
natcmp() |
Letters that are ASCII letters with diacritics are sorted right after the base ASCII letter |
strpos() |
pos() |
|
strrev() |
rev() |
|
strripos() |
irpos() |
|
strrpos() |
rpos() |
|
strstr() |
str() |
|
strtolower() |
lower() |
|
strtoupper() |
upper() |
|
substr() |
sub() |
|
trim() |
trim() |
|
ucfirst() |
ucfirst() |
|
ucwords() |
ucwords() |
|
wordwrap() |
wordwrap() |
Due to the way that UTF-8 is implemented, certain character combinations are not allowed. Allowing such invalid data into a system could easily lead to all sorts of bugs with character parsing. To solve this issue, the clean()
method will remove any malformed UTF-8 characters from a string.
This method should be used when importing data into a system from an external data source that may contain invalid data. Please note that fRequest::get() and fCookie::get() automatically call this method, so it is not necessary to clean it again.
$cleaned_string = fUTF8::clean($imported_string);
Provides string functions for UTF-8 strings
This class is implemented to provide a UTF-8 version of almost every built-in PHP string function. For more information about UTF-8, please visit UTF-8.
1.0.0b16 | Added code to clean() to use mbstring if available since recent versions of iconv and IGNORE now return FALSE for bad encodings 9/21/12 |
---|---|
1.0.0b15 | Fixed a bug with using IBM's iconv implementation on AIX 7/29/11 |
1.0.0b14 | Added a workaround for iconv having issues in MAMP 1.9.4+ 7/26/11 |
1.0.0b13 | Fixed notices from being thrown when invalid data is sent to clean() 6/10/11 |
1.0.0b12 | Fixed a variable name typo in sub() 5/9/11 |
1.0.0b11 | Updated the class to not using phpinfo() to determine the iconv implementation 11/4/10 |
1.0.0b10 | Fixed a bug with capitalizing a lowercase i resulting in a dotted upper-case I 11/1/10 |
1.0.0b9 | Updated class to use fCore::startErrorCapture() instead of error_reporting() 8/9/10 |
1.0.0b8 | Removed e flag from preg_replace() calls 6/8/10 |
1.0.0b7 | Added the methods trim(), rtrim() and ltrim() 5/11/10 |
1.0.0b6 | Fixed clean() to work with PHP installs that use an iconv library that doesn't support IGNORE 3/2/10 |
1.0.0b5 | Changed ucwords() to also uppercase words right after various punctuation 9/18/09 |
1.0.0b4 | Changed replacement values in preg_replace() calls to be properly escaped 6/11/09 |
1.0.0b3 | Fixed a parameter name in rpos() from $search to $needle 2/6/09 |
1.0.0b2 | Fixed a bug in explode() with newlines and zero-length delimiters 2/5/09 |
1.0.0b | The initial implementation 6/1/08 |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Maps UTF-8 ASCII-based latin characters, puntuation, symbols and number forms to ASCII
Any characters or symbols that can not be translated will be removed.
This function is most useful for situation that only allows ASCII, such as in URLs.
Translates elements form the following unicode blocks:
string ascii( string $string )
string | $string | The string to convert |
The input string in pure ASCII
Converts a unicode value into a UTF-8 character
string chr( mixed $unicode_code_point )
mixed | $unicode_code_point | The character to create, either the U+hex or decimal code point |
The UTF-8 character
Removes any invalid UTF-8 characters from a string or array of strings
string clean( array|string $value )
array|string | $value | The string or array of strings to clean |
The cleaned string
Compares strings, with the resulting order having latin characters that are based on ASCII letters placed after the relative ASCII characters
Please note that this function sorts based on English language sorting rules only. Locale-sepcific sorting is done by strcoll(), however there are technical limitations.
integer cmp( string $str1, string $str2 )
string | $str1 | The first string to compare |
string | $str2 | The second string to compare |
< 0 if $str1 < $str2, 0 if they are equal, > 0 if $str1 > $str2
Explodes a string on a delimiter
If no delimiter is provided, the string will be exploded with each characters being an element in the array.
array explode( string $string, string $delimiter=NULL )
string | $string | The string to explode |
string | $delimiter | The string to explode on. If NULL or '' this method will return one character per array index. |
The exploded string
Compares strings in a case-insensitive manner, with the resulting order having characters that are based on ASCII letters placed after the relative ASCII characters
Please note that this function sorts based on English language sorting rules only. Locale-sepcific sorting is done by strcoll(), however there are technical limitations.
integer icmp( string $str1, string $str2 )
string | $str1 | The first string to compare |
string | $str2 | The second string to compare |
< 0 if $str1 < $str2, 0 if they are equal, > 0 if $str1 > $str2
Compares strings using a natural order algorithm in a case-insensitive manner, with the resulting order having latin characters that are based on ASCII letters placed after the relative ASCII characters
Please note that this function sorts based on English language sorting rules only. Locale-sepcific sorting is done by strcoll(), however there are technical limitations.
integer inatcmp( string $str1, string $str2 )
string | $str1 | The first string to compare |
string | $str2 | The second string to compare |
< 0 if $str1 < $str2, 0 if they are equal, > 0 if $str1 > $str2
Finds the first position (in characters) of the search value in the string - case is ignored when doing performing a match
mixed ipos( string $haystack, string $needle, integer $offset=0 )
string | $haystack | The string to search in |
string | $needle | The string to search for. This match will be done in a case-insensitive manner. |
integer | $offset | The character position to start searching from |
The integer character position of the first occurence of the needle or FALSE if no match
Replaces matching parts of the string, with matches being done in a a case-insensitive manner
If $search and $replace are both arrays and $replace is shorter, the extra $search string will be replaced with an empty string. If $search is an array and $replace is a string, all $search values will be replaced with the string specified.
string ireplace( string $string, mixed $search, mixed $replace )
string | $string | The string to perform the replacements on |
mixed | $search | The string (or array of strings) to search for - see method description for details |
mixed | $replace | The string (or array of strings) to replace with - see method description for details |
The input string with the specified replacements
Finds the last position (in characters) of the search value in the string - case is ignored when doing performing a match
mixed irpos( string $haystack, string $needle, integer $offset=0 )
string | $haystack | The string to search in |
string | $needle | The string to search for. This match will be done in a case-insensitive manner. |
integer | $offset | The character position to start searching from. A negative value will stop looking that many characters from the end of the string |
The integer character position of the last occurence of the needle or FALSE if no match
Matches a string needle in the string haystack, returning a substring from the beginning of the needle to the end of the haystack
Can optionally return the part of the haystack before the needle. Matching is done in a case-insensitive manner.
mixed istr( string $haystack, string $needle, boolean $before_needle=FALSE )
string | $haystack | The string to search in |
string | $needle | The string to search for. This match will be done in a case-insensitive manner. |
boolean | $before_needle | If a substring of the haystack before the needle should be returned instead of the substring from the needle to the end of the haystack |
The specified part of the haystack, or FALSE if the needle was not found
Determines the length (in characters) of a string
integer len( string $string )
string | $string | The string to measure |
The number of characters in the string
Converts all uppercase characters to lowercase
string lower( string $string )
string | $string | The string to convert |
The input string with all uppercase characters in lowercase
Trims whitespace, or any specified characters, from the beginning of a string
string ltrim( string $string, string $charlist=NULL )
string | $string | The string to trim |
string | $charlist | The characters to trim |
The trimmed string
Compares strings using a natural order algorithm, with the resulting order having latin characters that are based on ASCII letters placed after the relative ASCII characters
Please note that this function sorts based on English language sorting rules only. Locale-sepcific sorting is done by strcoll(), however there are technical limitations.
integer natcmp( string $str1, string $str2 )
string | $str1 | The first string to compare |
string | $str2 | The second string to compare |
< 0 if $str1 < $str2, 0 if they are equal, > 0 if $str1 > $str2
Converts a UTF-8 character to a unicode code point
string ord( string $character )
string | $character | The character to decode |
The U+hex unicode code point for the character
Pads a string to the number of characters specified
string pad( string $string, integer $pad_length, string $pad_string=' ', string $pad_type='right' )
string | $string | The string to pad |
integer | $pad_length | The character length to pad the string to |
string | $pad_string | The string to pad the source string with |
string | $pad_type | The type of padding to do: 'left', 'right', 'both' |
The input string padded to the specified character width
Finds the first position (in characters) of the search value in the string
mixed pos( string $haystack, string $needle, integer $offset=0 )
string | $haystack | The string to search in |
string | $needle | The string to search for |
integer | $offset | The character position to start searching from |
The integer character position of the first occurence of the needle or FALSE if no match
Replaces matching parts of the string
If $search and $replace are both arrays and $replace is shorter, the extra $search string will be replaced with an empty string. If $search is an array and $replace is a string, all $search values will be replaced with the string specified.
string replace( string $string, mixed $search, mixed $replace )
string | $string | The string to perform the replacements on |
mixed | $search | The string (or array of strings) to search for - see method description for details |
mixed | $replace | The string (or array of strings) to replace with - see method description for details |
The input string with the specified replacements
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Resets the configuration of the class
void reset( )
Reverses a string
string rev( string $string )
string | $string | The string to reverse |
The reversed string
Finds the last position (in characters) of the search value in the string
mixed rpos( string $haystack, string $needle, integer $offset=0 )
string | $haystack | The string to search in |
string | $needle | The string to search for. |
integer | $offset | The character position to start searching from. A negative value will stop looking that many characters from the end of the string |
The integer character position of the last occurence of the needle or FALSE if no match
Trims whitespace, or any specified characters, from the end of a string
string rtrim( string $string, string $charlist=NULL )
string | $string | The string to trim |
string | $charlist | The characters to trim |
The trimmed string
Matches a string needle in the string haystack, returning a substring from the beginning of the needle to the end of the haystack
Can optionally return the part of the haystack before the needle.
mixed str( string $haystack, string $needle, boolean $before_needle=FALSE )
string | $haystack | The string to search in |
string | $needle | The string to search for |
boolean | $before_needle | If a substring of the haystack before the needle should be returned instead of the substring from the needle to the end of the haystack |
The specified part of the haystack, or FALSE if the needle was not found
Extracts part of a string
mixed sub( string $string, integer $start, integer $length=NULL )
string | $string | The string to extract from |
integer | $start | The zero-based starting index to extract from. Negative values will start the extraction that many characters from the end of the string. |
integer | $length | The length of the string to extract. If an empty value is provided, the remainder of the string will be returned. |
The extracted subtring or FALSE if the start is out of bounds
Trims whitespace, or any specified characters, from the beginning and end of a string
string trim( string $string, string $charlist=NULL )
string | $string | The string to trim |
string | $charlist | The characters to trim, .. indicates a range |
The trimmed string
Converts the first character of the string to uppercase.
string ucfirst( string $string )
string | $string | The string to process |
The processed string
Converts the first character of every word to uppercase
Words are considered to start at the beginning of the string, or after any whitespace character.
string ucwords( string $string )
string | $string | The string to process |
The processed string
Converts all lowercase characters to uppercase
string upper( string $string )
string | $string | The string to convert |
The input string with all lowercase characters in uppercase
Wraps a string to a specific character width
string wordwrap( string $string, integer $width=75, string $break="\n", boolean $cut=FALSE )
string | $string | The string to wrap |
integer | $width | The character width to wrap to |
string | $break | The string to insert as a break |
boolean | $cut | If words longer than the character width should be split to fit |
The input string with all lowercase characters in uppercase
The fUnbufferedResult class is an iterable result object that is returned when an SQL query is executed. It contains similar information to fResult, however some features are missing due to the nature of the results not being buffered. The quality of being unbuffered causes this object to use less memory than an fResult object.
The fUnbufferedResult class can be created by executing the methods fDatabase::unbufferedQuery() or fDatabase::unbufferedTranslatedQuery().
// Creation of fUnbufferedResult from a normal query
$result = $db->unbufferedQuery("SELECT * FROM users");
// Creation of an fUnbufferedResult object from a translated query
$result2 = $db->unbufferedTranslatedQuery("SELECT * FROM users LIMIT 5");
By default when retrieving rows from an unbuffered result object, they are returned as associative arrays. In the case of Oracle databases, where columns are case-insensitive, the array keys are lowercase.
It is possible to retrieve stdClass
objects back for each row instead of associative arrays. This is done by calling the method asObjects()
. The method returns the fUnbufferedResult object, allowing for method chaining:
// Set the result to return objects
$res = $db->unbufferedQuery("SELECT * FROM users")->asObjects();
If you are looking for objects with more functionality, please see fActiveRecord.
While it is certainly possible to manually unescape data from a result set, one row and column at a time, unescaping the whole result is often much easier. The method unescape()
accepts an associative array of the column name as the key and the data type as the value. This method should be called before any rows are fetched.
$result = $db->unbufferedQuery("SELECT * FROM users");
$result->unescape(array(
'first_name' => 'string',
'last_name' => 'string',
'is_authenticated' => 'boolean',
'last_login' => 'timestamp'
));
foreach ($result as $row) {
// $row now contains the data in native PHP data types
}
The fUnbufferedResult class implements the Iterator interface, which means that you can use the foreach
construct to loop through all resulting rows. Below is an example of iterating over a result:
$result = $db->unbufferedQuery("SELECT * FROM users");
foreach ($result as $row) {
echo $row['name'] . '<br />';
}
If the result did not return any rows, the foreach
construct will not be looped at all and the execution of the code will continue. If you wish to execute different code if no rows are returned, see the next section about exceptions.
If you wish to to manual iteration you can use the fetchRow()
and valid()
methods as shown below:
// Iteration using valid() and fetchRow()
while ($result->valid()) {
$row = $result->fetchRow();
}
As mentioned in the last section, if you iterate through the result and no rows are returned, nothing will happen. If you do need to execute different code when no rows are returned, you will want to call the tossIfNoRows()
to cause an fNoRowsException to be thrown:
try {
$result = $db->unbufferedQuery("SELECT * FROM users");
$result->tossIfNoRows();
?>
<h1>Users</h1>
<?
foreach ($result as $row) {
echo $row['name'] . '<br />';
}
} catch (fNoRowsException $e) {
?>
<p>No users were found</p>
<?php
}
In addition to calling tossIfNoRows()
, an fNoRowsException will be thrown if any Iterator interface methods (such as current()
, next()
, etc) or fetchRow()
is called on a result object which returned no rows.
Since fUnbufferedResult does not use buffering, only a fraction of the information about a query is available compared to the fResult class.
Method | Description |
getSQL() |
Returns the SQL statement executed for this result |
getUntranslatedSQL() |
Returns the SQL from before translation happened - only applicable for results from unbufferedTranslatedQuery() |
Representation of an unbuffered result from a query against the fDatabase class
1.0.0b13 | Added a workaround for iconv having issues in MAMP 1.9.4+ 7/26/11 |
---|---|
1.0.0b12 | Fixed MSSQL to have a properly reset row array, added silenceNotices(), fixed pdo_dblib on Windows when using the Microsoft DBLib driver 5/9/11 |
1.0.0b11 | Fixed some bugs with the mysqli extension and prepared statements 8/28/10 |
1.0.0b10 | Backwards Compatibility Break - removed ODBC support 7/31/10 |
1.0.0b9 | Added IBM DB2 support 4/13/10 |
1.0.0b8 | Added support for prepared statements 3/2/10 |
1.0.0b7 | Fixed a bug with decoding MSSQL national column when using an ODBC connection 9/18/09 |
1.0.0b6 | Added the method unescape(), changed tossIfNoRows() to return the object for chaining 8/12/09 |
1.0.0b5 | Added the method asObjects() to allow for returning objects instead of associative arrays 6/23/09 |
1.0.0b4 | Fixed a bug with not properly converting SQL Server text to UTF-8 6/18/09 |
1.0.0b3 | Added support for Oracle, various bug fixes 5/4/09 |
1.0.0b2 | Updated for new fCore API 2/16/09 |
1.0.0b | The initial implementation 5/7/08 |
Composes text using fText if loaded
string compose( string $message, mixed $component [, ... ] )
string | $message | The message to compose |
mixed | $component [, ... ] | A string or number to insert into the message |
The composed and possible translated message
Turns off notices about broken database extensions much as the MSSQL DBLib driver
void silenceNotices( )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Configures the result set
fUnbufferedResult __construct( fDatabase $database, string $character_set=NULL )
fDatabase | $database | The database object this result was created from |
string | $character_set | MSSQL only: the character set to transcode from since MSSQL doesn't do UTF-8 |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Frees up the result object
void __destruct( )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
All requests that hit this method should be requests for callbacks
callback __get( string $method )
string | $method | The method to create a callback for |
The callback for the method requested
Sets the object to return rows as objects instead of associative arrays (the default)
fUnbufferedResult asObjects( )
The result object, to allow for method chaining
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Returns the current row in the result set (required by iterator interface)
array|stdClass current( )
The current row
Returns the row next row in the result set (where the pointer is currently assigned to)
array|stdClass fetchRow( )
The next row in the result
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Returns the result
mixed getResult( )
The result of the query
Returns the SQL used in the query
string getSQL( )
The SQL used in the query
Returns the SQL as it was before translation
string getUntranslatedSQL( )
The SQL from before translation
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Returns the current row number (required by iterator interface)
integer key( )
The current row number
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Advances to the next row in the result (required by iterator interface)
void next( )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Rewinds the query (required by iterator interface)
void rewind( )
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Sets the result from the query
void setResult( mixed $result )
mixed | $result | The result from the query |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Sets the SQL used in the query
void setSQL( string $sql )
string | $sql | The SQL used in the query |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Sets the SQL from before translation
void setUntranslatedSQL( string $untranslated_sql )
string | $untranslated_sql | The SQL from before translation |
Throws an fNoResultException if the query did not return any rows
fUnbufferedResult tossIfNoRows( string $message=NULL )
string | $message | The message to use for the exception if there are no rows in this result set |
The result object, to allow for method chaining
Sets the result object to unescape all values as they are retrieved from the object
The data types should be from the list of types supported by fDatabase::unescape().
fUnbufferedResult unescape( array $column_data_type_map )
array | $column_data_type_map | An associative array with column names as the keys and the data types as the values |
The result object, to allow for method chaining
Returns if the query has any rows left
boolean valid( )
If the iterator is still valid
fUnexpectedException is a sub-class of fException meant to provide a common parent class for all exceptions that should not be expected or handled in the site/application code.
All classes that extend fUnexpectedException change the functionality of the printMessage()
method. Instead of printing the exception message, a generic error message is printed. These classes are designed to be handled by the fCore::enableExceptionHandling() method, which intercepts all uncaught exceptions and delivers the backtrace via one of three methods (see the fCore class for details).
Below is a list of all child classes that extend fUnexpectedException. These classes are not documented using a Throws: entry in the API documentation since they should not be handled via normal site/application code.
Class | Description |
fConnectivityException | This type of exception indicates some sort of connection error occured, whether it be with a database server, remote API, etc. |
fEnvironmentException | This type of exception indicates something is wrong with the server environment that is preventing proper execution of code. |
fProgrammerException | This type of exception indicates that the programming building the site/application wrote invalid code, or somehow otherwise violated an expected format or sequence. |
fSQLException | This type of exception indicates an error was returned when executing an SQL query. This will not be thrown in the situation where a query returns no results, but rather when SQL syntax is incorrect. |
An exception that should probably not be handled by the display code, fCore::enableExceptionHandler() is recommended
1.0.0b2 | Updated printMessage() to use an ASCII dash to prevent encoding issues when an output encoding is not specified 5/9/11 |
---|---|
1.0.0b | The initial implementation 6/14/07 |
Exception | --fException | --fUnexpectedException
Prints out a generic error message inside of a div with the class being 'exception {exception_class_name}'
void printMessage( )
The fUpload class provides functionality to validate and move files uploaded through HTML forms.
In order to use the fUpload class, a file needs to uploaded to PHP by the POST
HTTP method. If the POST
is coming from an HTML form, the form will need to have its enctype
set to multipart/form-data
.
<form action="" method="POST" enctype="multipart/form-data">
<fieldset>
<p>
<label for="file">File</label>
<input id="file" type="file" name="file" />
</p>
</fieldset>
</form>
The constructor does not accept any parameters.
$uploader = new fUpload();
Uploaded files can be validated by configuring the fUpload instance using setMIMETypes()
, setMaxSize()
, setOptional()
, enableOverwrite()
, allowPHP()
and allowDotFiles()
. All of these methods must be called before the methods move()
or validate()
are called.
As with all other data accepted from users, file uploads should be filtered to only allow safe content. The method setMIMETypes()
allows defining the mime types to allow with an appropriate error message. The first parameter is an array of valid mime types and the second parameter is the error message to display if the mime types are not matched.
$uploader->setMIMETypes(
array(
'image/gif',
'image/jpeg',
'image/pjpeg',
'image/png'
),
'The file uploaded is not an image'
);
While the $_FILES
superglobal includes a mime type for the uploaded file, fUpload does not use this value since it is provided by the user and thus not trustworthy. Instead, mime type checking is done on the server side by inspecting the contents of the file. Please see the fFile::getMimeType() method documentation for a list of all supported mime types.
setMaxSize()
allows validating a file to ensure it does not exceed a specific size. It accepts a single parameter, $size
, that can be any filesize with units or an integer to be interpreted as bytes.
This method works in concert with two other settings, the upload_max_filesize ini setting and the MAX_FILE_SIZE hidden input. The upload_max_filesize
ini setting controls the maximum size of a file that can be uploaded by a user, and must be larger or equal to the value passed to setMaxSize()
. This setting can not be controlled in the PHP script since file uploads are processed before the request executes PHP.
The MAX_FILE_SIZE
hidden input is primarily designed for the convenience of end-users since it will reject large files before they are fully uploaded. Since the hidden input element is controlled by the program submitting the request, it can no be trusted and the setMaxSize()
method must be used as a server-side failsafe.
// These three method calls will all limit the uploaded file to 2 megabytes
$uploader->setMaxSize('2m');
$uploader->setMaxSize('2MB');
$uploader->setMaxSize('2048k');
By default, fUpload treats every file as required and will throw and exception or return an error if no file is uploaded. The method setOptional()
changes this behavior to not treat the absence of a file as an error. This will cause the move()
method to return NULL
when no file is uploaded.
$uploader->setOptional();
When uploading files, if there is an existing file with the same name in the destination directory the fUpload class will rename the uploaded file to a unique name. If this is not the desired action, the method enableOverwrite()
will allow the class to overwrite existing files.
$uploader->enableOverwrite();
By default the fUpload class does not allow files ending in .php
, .php4
or .php5
or beginning with .
to be uploaded due to security concerns. If there is a need for PHP files to uploaded the method allowPHP()
can be called. Files beginning with .
may be allowed by calling allowDotFiles()
.
$uploader->allowPHP();
$uploader->allowDotFiles();
Once options have been set it is time to try moving (or possibly just validating) the uploaded file or files. The method move()
will move the file uploading in the specified field to the directory requested, while the validate()
method will simply ensure the uploaded file matches the options that were set. Calling move()
will also cause validate()
to happen.
The move()
accepts three parameters, the $directory
to move the file to, the $field
the file was uploaded through and optionally the $index
of the file if the file upload was part of an array of file upload fields. The return value is an fFile object representing the newly moved file. An fValidationException will be thrown if no file was uploaded or one of the options is not met.
// An example of uploading a file passing the destination directory as a string
$file = $uploader->move('/uploaded/file/destination', 'file_input_name');
// An example of passing the destination directory as an fDirectory object
$dir = new fDirectory('/some/directory');
$file = $uploader->move($dir, 'file_input_name');
If setOptional()
has been called and no file was uploaded, NULL
will be returned instead of an fFile object.
The validate()
method accepts two parameters, the $field
to validate and optionally the $index
of the file if the file upload was part of an array of file upload fields. As with all other Flourish methods that begin with validate
, this method will throw an fValidationException if one of the options is not met or no file is uploaded.
$uploader->validate('file_input_name');
It is also possible to return an error instead of throwing an exception by passing TRUE
as the second parameter.
// $error will contain any error that occurs, or NULL if there is none
$error = $uploader->validate('file_input_name', TRUE);
When it is necessary to have a user upload multiple file, it is possible to take advantage of PHP array syntax for input fields. Any input field name that ends in []
will cause an array to be created in the $_GET
or $_POST
superglobals for normal inputs, or in the $_FILES
superglobal for file uploads. Please note that on the PHP side, the []
is dropped from the input name, e.g. file_input_name[]
would be found in $_FILES['file_input_name']
.
<form action="" method="POST" enctype="multipart/form-data">
<fieldset>
<p>
<label for="file_1">File 1</label>
<input id="file_1" type="file" name="file_input_name[]" />
</p>
<p>
<label for="file_2">File 2</label>
<input id="file_2" type="file" name="file_input_name[]" />
</p>
</fieldset>
</form>
fUpload provides the static method count()
to determine how many files were uploaded by a user. This can be used with the optional third parameter, $index
, of move()
or optional second parameter, $index
of validate()
to perform the action on the specified file.
$files = array();
$uploaded = fUpload::count('file_input_name');
for ($i=0; $i < $uploaded; $i++) {
$files[] = $uploader->move($dir, 'file_input_name', $i);
}
When an index is specified for validate()
, the boolean $return_message
parameter to return the message instead of throw an exception is passed as the third parameter.
// Return an error for the second file upload named attachment
$error = $uploader->validate('attachment', 1, TRUE);
When multiple files are uploaded for a field, it is common for one or more file upload fields to not actually upload a file. The static method filter()
accepts a $field
name and will removed info about any file upload fields where a file was not uploaded.
fUpload::filter('attachments');
$uploader = new fUpload();
$attachment = array();
$uploaded = fUpload::count('attachments');
for ($i=0; $i < $uploaded; $i++) {
$attachments[] = $uploader->move($dir, 'attachments', $i);
}
Provides validation and movement of uploaded files
1.0.0b15 | Fixed an undefined variable error in setMaxSize() 9/16/12 |
---|---|
1.0.0b14 | Fixed some method signatures 8/24/11 |
1.0.0b13 | Changed the class to throw fValidationException objects instead of fProgrammerException objects when the form is improperly configured - this is to prevent error logs when bad requests are sent by scanners/hackers 8/24/11 |
1.0.0b12 | Fixed the filter() callback constant 11/24/10 |
1.0.0b11 | Added setImageDimensions() and setImageRatio() 11/11/10 |
1.0.0b10 | BackwardsCompatibilityBreak - renamed setMaxFilesize() to setMaxSize() to be consistent with fFile::getSize() 5/30/10 |
1.0.0b9 | BackwardsCompatibilityBreak - the class no longer accepts uploaded files that start with a . unless allowDotFiles() is called - added setOptional() 5/30/10 |
1.0.0b8 | BackwardsCompatibilityBreak - validate() no longer returns the $_FILES array for the file being validated - added $return_message parameter to validate(), fixed a bug with detection of mime type for text files 5/26/10 |
1.0.0b7 | Added filter() to allow for ignoring array file upload field entries that did not have a file uploaded 10/6/09 |
1.0.0b6 | Updated move() to use the new fFilesystem::createObject() method 1/21/09 |
1.0.0b5 | Removed some unnecessary error suppression operators from move() 1/5/09 |
1.0.0b4 | Updated validate() so it properly handles upload max filesize specified in human-readable notation 1/5/09 |
1.0.0b3 | Removed the dependency on fRequest 1/5/09 |
1.0.0b2 | Fixed a bug with validating filesizes 11/25/08 |
1.0.0b | The initial implementation 6/14/07 |
Checks to see if the field specified is a valid file upload field
boolean check( string $field, boolean $throw_exception=TRUE )
string | $field | The field to check |
boolean | $throw_exception | If an exception should be thrown when there are issues with the form |
If the field is a valid file upload field
Composes text using fText if loaded
string compose( string $message, mixed $component [, ... ] )
string | $message | The message to compose |
mixed | $component [, ... ] | A string or number to insert into the message |
The composed and possible translated message
Returns the number of files uploaded to a file upload array field
integer count( string $field )
string | $field | The field to get the number of files for |
The number of uploaded files
Removes individual file upload entries from an array of file inputs in $_FILES when no file was uploaded
array filter( string $field )
string | $field | The field to filter |
The indexes of the files that were uploaded
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
All requests that hit this method should be requests for callbacks
callback __get( string $method )
string | $method | The method to create a callback for |
The callback for the method requested
Sets the upload class to allow files starting with a .
Files starting with . may change the behaviour of web servers, for instance .htaccess files for Apache.
void allowDotFiles( )
Sets the upload class to allow PHP files
void allowPHP( )
Set the class to overwrite existing files in the destination directory instead of renaming the file
void enableOverwrite( )
Moves an uploaded file from the temp directory to a permanent location
fFile|NULL move( string|fDirectory $directory, string $field, mixed $index=NULL )
string|fDirectory | $directory | The directory to upload the file to |
string | $field | The file upload field to get the file from |
mixed | $index | If the field was an array file upload field, upload the file corresponding to this index |
An fFile (or fImage) object, or NULL if no file was uploaded
Sets the allowable dimensions for an uploaded image
void setImageDimensions( integer $min_width, integer $min_height, integer $max_width=0, integer $max_height=0 )
integer | $min_width | The minimum width - 0 for no minimum |
integer | $min_height | The minimum height - 0 for no minimum |
integer | $max_width | The maximum width - 0 for no maximum |
integer | $max_height | The maximum height - 0 for no maximum |
Sets the allowable dimensions for an uploaded image
void setImageRatio( numeric $width, numeric $height, string $allow_excess_dimension )
numeric | $width | The minimum ratio width |
numeric | $height | The minimum ratio height |
string | $allow_excess_dimension | The dimension that should allow for excess pixels |
Sets the maximum size the uploaded file may be
This method should be used with the MAX_FILE_SIZE hidden form input since the hidden form input will reject a file that is too large before the file completely uploads, while this method will wait until the whole file has been uploaded. This method should always be used since it is very easy for the MAX_FILE_SIZE post field to be manipulated on the client side.
This method can only further restrict the upload_max_filesize ini setting, it can not increase that setting. upload_max_filesize must be set in the php.ini (or an Apache configuration) since file uploads are handled before the request is handed off to PHP.
void setMaxSize( string $size )
string | $size | The maximum file size (e.g. 1MB, 200K, 10.5M) - 0 for no limit |
Sets the file mime types accepted
void setMIMETypes( array $mime_types, string $message )
array | $mime_types | The mime types to accept |
string | $message | The message to display if the uploaded file is not one of the mime type specified |
Sets the file upload to be optional instead of required
void setOptional( )
Validates the uploaded file, ensuring a file was actually uploaded and that is matched the restrictions put in place
NULL|string validate( string $field, mixed $index=NULL, boolean $return_message=NULL )
NULL|string validate( string $field, boolean $return_message=NULL )
string | $field | The field the file was uploaded through |
mixed | $index | If the field was an array of file uploads, this specifies which one to validate |
boolean | $return_message | If any validation error should be returned as a string instead of being thrown as an fValidationException |
If $return_message is not TRUE or if no error occurs, NULL, otherwise a string error message
The fValidation class provides an interface for validating standalone forms, such as a contact form. In these situations there are usually a mix of required fields and fields that require specific formatting. In addition, form contents that are emailing will often need to be checked to prevent email header injection attacks.
The fValidation class must be instantiated before options can be set and the validation can be performed.
$validator = new fValidation();
To have the class actually perform any validation it will need to know what fields and rules it is checking.
The simplest validation is done by specifying individual fields against data types or input formats.
Method | Description |
addRequiredFields() |
Requires that the field(s) specified have a value. |
addBooleanFields() |
Requires that the field(s) specified can be interpreted as a boolean if a value is entered. |
addDateFields() |
Requires that the field(s) specified can be interpreted as a date if a value is entered. |
addFloatFields() |
Requires that the field(s) specified contain a floating point/decimal number if a value is entered. |
addIntegerFields() |
Requires that the field(s) specified contain an integer if a value is entered. |
addEmailFields() |
Requires that the fields specified are formatted like an email address if a value is entered. |
addEmailHeaderFields() |
Checks field(s) for line breaks to prevent email header injection. Should be used whenever embedding user input into email headers. |
addURLFields() |
Requires that the field(s) specified are formatted like a full URL (http://example.com) if a value is entered. |
Each of these methods takes any number of parameters with each parameter being a field name. Subsequent calls to the methods will add to the list of fields.
// Require that name and email be filled in
$validator->addRequiredFields('name', 'email');
// Require that name and email be filled in, and that the email field contains an email address
$validator->addRequiredFields('name');
$vaildator->addRequiredFields('email');
$validator->addEmailFields('email');
// Require that name be filled in, and if birthday is specified that
// it will be interpreted as a date or time by fTimestamp
$validator->addRequiredFields('name');
$validator->addDateFields('birthday');
More advanced validation rules can be checked by using the following methods.
addCallbackRule()
accepts a $field
to check, a $callback
to execute and retrieve a boolean result from, and a $message
to use when the result is FALSE
.
function check_length($value) {
return strlen($value) >= 8;
}
$validator->addCallbackRule('password', 'check_length', 'Please enter at least 8 characters');
The method addConditionalRule()
allows requiring one or more fields, if another field (or set of fields) has any value, or a value from a pre-defined set. The first parameter $main_fields
accepts a single string field, or an array of fields. The second parameter, $conditional_values
, accepts NULL
or an array of values. The third parameter, $conditional_fields
, accepts a single string field, or an array of fields.
If $conditional_values
is NULL
, and value present in any of the $main_fields
will cause all $conditional_fields
to be required. If $conditional_values
is an array of values, if any of the $main_fields
contains one of those values, all of the $conditional_columns
will be required.
// If the address, city, state_province, country or postal_code is filled
// in, require that at least the address and city are filled in
$validator->addConditionalRule(
array('address', 'city', 'state_province', 'country', 'postal_code'),
NULL,
array('address', 'city')
);
// If the country is US or Canada, require the state_province and postal_code
$validator->addConditionalRule('country', array('US', 'CA'), array('state_province', 'postal_code'));
When creating a form that includes file uploads via fUpload, the addFileUploadRule()
method will combine the validation performed by fUpload with the rest of the fValidation fields and rules. The first parameter is the file upload $field
, the second parameter is the fUpload instance, $uploader
. All validation options for the file, including if it is required, are set via the fUpload object.
$uploader = new fUpload();
// Make the file upload optional
$uploader->setOptional();
$uploader->setMaxSize('1MB');
$validator->addFileUploadRule('attachment', $uploader);
If a file upload field being validated is part of an array of file uploads, the array index can be passed as the second parameter to addFileUploadRule()
, and the $uploader
parameter should be passed as the third parameter.
$uploader = new fUpload();
// Validate just the first attachment
$validator->addFileUploadRule('attachment', 0, $uploader);
The method addOneOrMoreRule()
accepts two or more $field
parameters, and requires that at least one of them has a value.
$validator->addOneOrMoreRule('email', 'phone', 'cell_phone');
The method addOnlyOneRule()
accepts two or more $field
parameters, and requires that exactly one of them has a value. It is considered an error if none, or more than one have a value.
$validator->addOnlyOneRule('ssn', 'ein');
The addRegexRule()
method allows validating non-blank values against a Perl-compatible regular expression (PCRE). The first parameter is the $field
to validate, followed by the $regex
to test and the $message
to use if the value does not match the expression.
// Require the value is a length in in or cm
$validator->addRegexRule('length', '#^\d+\s*(in|cm)$#', 'Please enter a length');
A value can be tested against a set of valid values by calling the method addValidValuesRule()
. This method accepts a $field
to validate, followed by an array of $valid_values
.
// Require the value be 'yes', 'no' or 'maybe'
$validator->addValidValuesRule('answer', array('yes', 'no', 'maybe'));
Once all of the fields and rules have been set, the validate()
method will check them all against the current request using fRequest. If any errors are found an fValidationException will be thrown containing an explanation of the errors that occurred.
The validate()
method will always evaluate all fields and rules so users will not have to submit a form multiple times to figure out everything they are doing wrong. Here is a full example:
try {
$validator = new fValidation();
$validator->addRequiredFields('name');
$validator->addOneOrMoreRule('email', 'phone');
$validator->addEmailFields('email');
$validator->addEmailHeaderFields('name', 'email');
$validator->addDateFields('birthday');
$validator->addURLFields('my_blog');
$validator->validate();
// Here would be the action of the contact form, such as sending an email
} catch (fValidationException $e) {
fMessaging::create('error', $e->getMessage());
}
The exception message that is generated will contain HTML including <p>The following problems were found:</p>
followed by a <ul>
containing one <li>
for each error detected. Below are some examples.
<p>The following problems were found:</p>
<ul>
<li>Name: Please enter a value</li>
<li>Email, Phone: Please enter at least one</li>
</ul>
<p>The following problems were found:</p>
<ul>
<li>Email: Please enter an email address in the form name@example.com</li>
</ul>
If an exception is not desired, an array of individual error messages can be retrieved by passing TRUE
to the first parameter of validate()
, $return_messages
. The returned array will have keys that are the field name for individual fields, or the field names joined by ,
s for multiple fields. The values will be the error messages, including formatted field names.
// Return the messages instead of throwing an exception
$messages = $validator->validate(TRUE);
The $messages
array will have a structure such as:
array(
'name' => 'Name: Please enter a value',
'email,phone' => 'Email, Phone: Please enter at least one'
)
If the messages are being used for inline validation, it may be useful to remove the field names from the error messages. The field names can be removed by passing TRUE
to the second parameter of validate()
, $remove_field_names
.
$messages = $validator->validate(TRUE, TRUE);
In this case, the $messages
array would look like:
array(
'name' => 'Please enter a value',
'email,phone' => 'Please enter at least one'
)
Often the most usable way to notify users of an error is to produce a full error message listing all errors on a form, but also to individually highlight the fields with errors. The $return_messages
parameter and fValidationException can be used together to provide such functionality.
First, the messages should be returned by passing TRUE
to the $return_messages
parameter. The static method fValidationException::removeFieldNames() can be used to remove the field names from the error messages for inline field highlighting and then the fValidationException constructor can be used to generate a full error message.
try {
// ...
$errors = $validator->validate(TRUE);
$field_errors = fValidationException::removeFieldNames($errors);
if ($errors) {
throw new fValidationException(
'The following problems were found:',
$errors
);
}
// ...
} catch (fValidationException $e) {
// fMessaging allows for creating session-based one-time-use messages
fMessaging::create('error', $e->getMessage();
fMessaging::create('field_errors', $field_errors);
}
Three methods exist to customize the messages generated, overrideFieldName()
, addRegexReplacement()
and addStringReplacement()
. Both regular expression and string replacements are suitable for changing error messages to use slightly different wording. More advanced message modification can be done library wide by using the features of fText.
By default, all field names are converted from field_name
to Field Name
for all error messages by fGrammar::humanize(). This conversion may be incorrect for non-English words or acronyms. overrideFieldName()
accepts a $field
and the $name
to use for it.
$validator->overrideFieldName('zip_code', 'ZIP Code');
It is also possible to pass an associative array with fields as the keys and names as the values.
$validator->overrideFieldName(array(
'zip_code' => 'ZIP Code',
'state_province' => 'State/Province'
));
overrideFieldName()
can be called any number of times in either the two parameter, or one parameter signatures. If a field name is overridden more than once, the last override will be the one used.
addRegexReplacement()
accepts two parameters, the Perl-compatible regular expression to $search
for and the string to $replace
it with. This search and replace action is performed right before messages are reordered.
$validator->addRegexReplacement('#Please enter a value$#', 'Please enter something');
Please note that $replace
should be a string with all normal $
and \
characters escaped with a \
since preg_replace()
is used under-the-hood. Because preg_replace()
is used, it is also possible to use back-references in $replace
.
// This will change "Name: Please enter a value" to "Please enter a value for Name"
$validator->addRegexReplacement('#^(.*?): (.*)$#', '\2 for \1');
It is also possible to pass an associative array as a single with they keys being $search
expressions and the values being $replace
strings.
$validator->addRegexReplacement(array(
'#Please enter a value$#' => 'Please enter something',
'#^(.*?): (.*)$#' => '\2 for \1'
));
addRegexReplacement()
can be called any number of times, and the replacements will be added together. If a search value is passed more than once, the $replace
parameter from the last method call will be the one used.
The addStringReplacement()
method works exactly like addRegexReplacement()
, except that the first parameter is a string to $search
for and the second is plain string to $replace
with. $replace
does not require any escaping and obviously does not support back-references.
$validator->addStringReplacement('Please enter a value', 'Please enter something');
Also supported is a single parameter containing an associative array with the key being a string to search for and the value being the string to replace it with.
$validator->addStringReplacement(array(
'Please enter a value' => 'Please enter something',
'Please enter a whole number' => 'Please enter an integer'
));
addStringReplacement()
can be called any number of times to add multiple search/replace pairs. If the same search string is passed more than once, the replace string from the last method call will be used.
The order in which error messages appear in an exception, or the returned messages array, is based upon what order the class checks the various field and rules. Thus, while sometimes the Email
field may appear first in the list, other times it may appear last. The setMessageOrder()
method allows for consistent ordering of messages.
setMessageOrder()
accepts any number of parameters, which are strings to search for. Any message that contains the first parameter will be listed first, any message that contains the second parameter will be listed second, and so on. Matches are done in a longest match first to ensure accidental matches dont occur. Errors that do not match any parameter are left at the end.
With the following exception message:
The following problems were found:
- Last Name: Please enter a value
- First Name: Please enter a value
- Comments: Please enter a value
- Company Email: Please enter a value
- Personal Email: Please enter an email address in the form name@example.com
And calling setMessageOrder()
with the following parameters:
$validator->setMessageOrder('First Name', 'Last Name', 'Email', 'Company Email', 'Comments');
Will result in the message being changed to:
The following problems were found:
- First Name: Please enter a value
- Last Name: Please enter a value
- Personal Email: Please enter an email address in the form name@example.com
- Company Email: Please enter a value
- Comments: Please enter a value
Notice that Personal Email
is listed before Company Email
- this is because the fourth parameter 'Company Email'
was a longer match and the error was placed into position 4, while Personal Email
was matched by the shorter parameter 3 and was placed in position 3.
Messages that match the parameters will always be ordered in the same relative order as the parameters, although they may not have the exact same position. This may occur if there is no error that matches a parameter, or if multiple messages match a parameter.
Provides validation routines for standalone forms, such as contact forms
1.0.0b12 | Fixed some method signatures 8/24/11 |
---|---|
1.0.0b11 | Fixed addCallbackRule() to be able to handle multiple rules per field 6/2/11 |
1.0.0b10 | Fixed addRegexRule() to be able to handle multiple rules per field 8/30/10 |
1.0.0b9 | Enhanced all of the add fields methods to accept one field per parameter, or an array of fields 6/24/10 |
1.0.0b8 | Added/fixed support for array-syntax fields names 6/9/10 |
1.0.0b7 | Added the ability to pass an array of replacements to addRegexReplacement() and addStringReplacement() 5/31/10 |
1.0.0b6 | BackwardsCompatibilityBreak - moved one-or-more required fields from addRequiredFields() to addOneOrMoreRule(), moved conditional required fields from addRequiredFields() to addConditionalRule(), changed returned messages array to have field name keys - added lots of functionality 5/26/10 |
1.0.0b5 | Added the $return_messages parameter to validate() and updated code for new fValidationException API 9/17/09 |
1.0.0b4 | Changed date checking from strtotime() to fTimestamp for better localization support 6/1/09 |
1.0.0b3 | Updated for new fCore API 2/16/09 |
1.0.0b2 | Added support for validating date and URL fields 1/23/09 |
1.0.0b | The initial implementation 6/14/07 |
Composes text using fText if loaded
string compose( string $message, mixed $component [, ... ] )
string | $message | The message to compose |
mixed | $component [, ... ] | A string or number to insert into the message |
The composed and possible translated message
Returns TRUE for non-empty strings, numbers, objects, empty numbers and string-like numbers (such as 0, 0.0, '0')
boolean stringlike( mixed $value )
mixed | $value | The value to check |
If the value is string-like
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
All requests that hit this method should be requests for callbacks
callback __get( string $method )
string | $method | The method to create a callback for |
The callback for the method requested
Adds fields to be checked for 1/0, t/f, true/false, yes/no
fValidation addBooleanFields( string $field [, ... ] )
fValidation addBooleanFields( array $fields )
string | $field [, ... ] | A field that should contain a boolean value |
array | $fields | Any number of fields that should contain a boolean value |
The validation object, to allow for method chaining
Adds a callback validation of a field, with a custom error message
fValidation addCallbackRule( string $field, callback $callback, string $message )
string | $field | The field to test with the callback |
callback | $callback | The callback to test the value with - this callback should accept a single string parameter and return a boolean |
string | $message | The error message to return if the regular expression does not match the value |
The validation object, to allow for method chaining
Adds fields to be conditionally required if another field has any value, or specific values
fValidation addConditionalRule( string|array $main_fields, mixed $conditional_values, string|array $conditional_fields )
string|array | $main_fields | The fields(s) to check for a value |
mixed | $conditional_values | If NULL, any value in the main field(s) will trigger the conditional field(s), otherwise the value must match this scalar value or be present in the array of values |
string|array | $conditional_fields | The field(s) that are to be required |
The validation object, to allow for method chaining
Adds form fields to the list of fields to be blank or a valid date
Use addRequiredFields() disallow blank values.
fValidation addDateFields( string $field [, ... ] )
fValidation addDateFields( array $fields )
string | $field [, ... ] | A field that should contain a valid date |
array | $fields | Any number of fields that should contain a valid date |
The validation object, to allow for method chaining
Adds form fields to the list of fields to be blank or a valid email address
Use addRequiredFields() disallow blank values.
fValidation addEmailFields( string $field [, ... ] )
fValidation addEmailFields( array $fields )
string | $field [, ... ] | A field that should contain a valid email address |
array | $fields | Any number of fields that should contain a valid email address |
The validation object, to allow for method chaining
Adds form fields to be checked for email injection
Every field that is included in email headers should be passed to this method.
fValidation addEmailHeaderFields( string $field [, ... ] )
fValidation addEmailHeaderFields( array $fields )
string | $field [, ... ] | A field to be checked for email injection |
array | $fields | Any number of fields to be checked for email injection |
The validation object, to allow for method chaining
Add a file upload field to be validated using an fUpload object
fValidation addFileUploadRule( string $field, mixed $index, fUpload $uploader )
fValidation addFileUploadRule( string $field, fUpload $uploader )
string | $field | The field to validate |
mixed | $index | The index for array file upload fields |
fUpload | $uploader | The uploader to validate the field with |
The validation object, to allow for method chaining
Adds fields to be checked for float values
fValidation addFloatFields( string $field [, ... ] )
fValidation addFloatFields( array $fields )
string | $field [, ... ] | A field that should contain a float value |
array | $fields | Any number of fields that should contain a float value |
The validation object, to allow for method chaining
Adds fields to be checked for integer values
fValidation addIntegerFields( string $field [, ... ] )
fValidation addIntegerFields( array $fields )
string | $field [, ... ] | A field that should contain an integer value |
array | $fields | Any number of fields that should contain an integer value |
The validation object, to allow for method chaining
Adds a rule to make sure at least one field of multiple has a value
fValidation addOneOrMoreRule( string $field, string $field_2 [, ... ] )
string | $field | One of the fields to check for a value |
string | $field_2 [, ... ] | Another field to check for a value |
The validation object, to allow for method chaining
Adds a rule to make sure at exactly one field of multiple has a value
fValidation addOnlyOneRule( string $field, string $field_2 [, ... ] )
string | $field | One of the fields to check for a value |
string | $field_2 [, ... ] | Another field to check for a value |
The validation object, to allow for method chaining
Adds a call to preg_replace() for each message
Replacement is done right before the messages are reordered and returned.
If a message is an empty string after replacement, it will be removed from the list of messages.
fValidation addRegexReplacement( string $search, string $replace )
fValidation addRegexReplacement( array $replacements )
string | $search | The PCRE regex to search for - see http://php.net/pcre for details |
string | $replace | The string to replace with - all $ and \ are used in back references and must be escaped with a \ when meant literally |
array | $replacements | An associative array with keys being regular expressions to search for and values being the string to replace with |
The validation object, to allow for method chaining
Adds regular expression validation of a field, with a custom error message
fValidation addRegexRule( string $field, string $regex, string $message )
string | $field | The field to test with the regular expression |
string | $regex | The PCRE regex to search for - see http://php.net/pcre for details |
string | $message | The error message to return if the regular expression does not match the value |
The validation object, to allow for method chaining
Adds form fields to be required
fValidation addRequiredFields( string $field [, ... ] )
fValidation addRequiredFields( array $fields )
string | $field [, ... ] | A field to require a value for |
array | $fields | Any number of fields to require a value for |
The validation object, to allow for method chaining
Adds a call to str_replace() for each message
Replacement is done right before the messages are reordered and returned.
If a message is an empty string after replacement, it will be removed from the list of messages.
fValidation addStringReplacement( string $search, string $replace )
fValidation addStringReplacement( array $replacements )
string | $search | The string to search for |
string | $replace | The string to replace with |
array | $replacements | An associative array with keys being strings to search for and values being the string to replace with |
The validation object, to allow for method chaining
Adds form fields to the list of fields to be blank or a valid URL
Use addRequiredFields() disallow blank values.
fValidation addURLFields( string $field [, ... ] )
fValidation addURLFields( array $fields )
string | $field [, ... ] | A field that should contain a valid URL |
array | $fields | Any number of fields that should contain a valid URL |
The validation object, to allow for method chaining
Adds a rule to make sure a field has one of the specified valid values
A strict comparison will be made from the string request value to the array of valid values.
fValidation addValidValuesRule( string $field, array $valid_values )
string | $field | The field to check the value of |
array | $valid_values | The valid values |
The validation object, to allow for method chaining
Allows overriding the default name used for a field in the error message
By default, all fields are referred to by the field name run through fGrammar::humanize(). This may not be correct for acronyms or complex field names.
fValidation overrideFieldName( string $field, string $name )
fValidation overrideFieldName( array $field_names )
string | $field | The field to set the custom name for |
string | $name | The custom name for the field |
array | $field_names | An associative array of custom field names where the keys are the field and the values are the names |
The validation object, to allow for method chaining
Allows setting the order that the individual errors in a message will be displayed
All string comparisons during the reordering process are done in a case-insensitive manner.
fValidation setMessageOrder( string $match, string $match_2=NULL [, ... ] )
string | $match | The string match to order first |
string | $match_2 [, ... ] | The string match to order second |
The validation object, to allow for method chaining
Checks for required fields, email field formatting and email header injection using values previously set
void|array validate( boolean $return_messages=FALSE, boolean $remove_field_names=FALSE )
boolean | $return_messages | If an array of validation messages should be returned instead of an exception being thrown |
boolean | $remove_field_names | If field names should be removed from the returned messages, leaving just the message itself |
If $return_messages is TRUE, an array of validation messages will be returned
fValidationException is a sub-class of fExpectedException that indicates that some sort of input did not meet the requirements of the code. This class is used by Flourish code quite a bit and would be suitable to toss on almost any web site or application at some point.
This functionality is specific to fValidationException, where it is most common and useful. In addition to being able to pass a $message
to __construct()
(and possibly some values to interpolate), it is possible to pass a string $message
followed by an array of $sub_messages
. The $message
will be placed in a <p>
tag and the $sub_messages
will each be placed in an <li>
tag inside of a <ul>
tag.
Thus, the following example code:
throw new fValidationException(
'The following problems were found:',
array(
'First Name: Please enter a value',
'Last Name: Please enter a value',
'Email: Please enter your email in the format name@host.com'
)
);
will create the message:
<p>The following problems were found:</p>
<ul>
<li>First Name: Please enter a value</li>
<li>Last Name: Please enter a value</li>
<li>Email: Please enter your email in the format name@host.com</li>
</ul>
Both fValidation and fORMValidation throw fValidationException objects when data has been detected that does not validate according to the established rules. In the message for these exceptions there are unordered lists of each field/column name followed by the error message for that field/column. By default the field/column name is printed followed by :
.
Sometimes it may be desirable to change the formatting of the field name, to include HTML tags such as <strong>
or <em>
. The static method setFieldFormat()
accepts a single parameter that is the format for field/column names. The token %s
is replaced with the field/column name. Any literal %
symbols should be changed to %%
to prevent formatting issues.
Here is an example of adding <strong>
tags around field/column names:
fValidationException::setFieldFormat('<strong>%s</strong>: ');
When fActiveRecord::validate() or fValidation::validate() is called with the $return_messages
parameter set to TRUE
, an array of error messages will be returned, with the column or field names included in the error messages. For situations where the messages are going to be displayed next to the field in question, having the field name in the actual error message is not desirable. The static method removeFieldNames()
will accept an array of error messages and will return an array with the same error messages, sans field names.
The code below will produce a full exception including the field names, but will also provide an associative array of the key being the field/column and the value being the error message without the field name.
$errors = $user->validate(TRUE);
$field_errors = fValidationException::removeFieldNames($errors);
if ($errors) {
throw new fValidationException('The following problems were found:', $errors);
}
The exception message would be:
The following problems were found:
- First Name: Please enter a value
- Last Name: Please enter a value
And the $field_errors
array would contain:
array(
'first_name' => 'Please enter a value',
'last_name' => 'Please enter a value'
);
An exception caused by a data not matching a rule or set of rules
1.0.0b4 | Added support for nested error arrays 10/3/10 |
---|---|
1.0.0b3 | Added removeFieldNames() 5/26/10 |
1.0.0b2 | Added a custom __construct() to handle arrays of messages 9/17/09 |
1.0.0b | The initial implementation 6/14/07 |
Exception | --fException | --fExpectedException | --fValidationException
The formatting string to use for field names
string
Accepts a field name and formats it based on the formatting string set via setFieldFormat()
string formatField( string $field )
string | $field | The name of the field to format |
The formatted field name
Removes the field names from normal validation messages, leaving just the message part
array removeFieldNames( array $messages )
array | $messages | The messages to remove the field names from |
The messages without field names
Set the format to be applied to all field names used in fValidationExceptions
The format should contain exactly one %s sprintf() conversion specification, which will be replaced with the field name. Any literal % characters should be written as %%.
The default format is just %s: , which simply inserts a : and space after the field name.
void setFieldFormat( string $format )
string | $format | A string to format the field name with - %s will be replaced with the field name |
Sets the message for the exception, allowing for custom formatting beyond fException
If this method receives exactly two parameters, a string and an array, the string will be used as a message in a HTML <p> tag and the array will be turned into an unorder list <ul> tag with each element in the array being an <li> tag. It is possible to pass an optional exception code as a third parameter.
The following PHP:
throw new fValidationException(
'The following problems were found:',
array(
'Please provide your name',
'Please provide your email address'
)
);
Would create the message:
The following problems were found:
- Please provide your name
- Please provide your email address
If the parameters are anything else, they will be passed to fException::__construct().
fException __construct( string $message='', array $sub_messages, mixed $code )
string | $message | The beginning message for the exception. This will be placed in a <p> tag. |
array | $sub_messages | An array of strings to place in a <ul> tag |
mixed | $code | The optional exception code |
The fXML class provides functionality to read and create XML. An fXML object represents an XML string for reading/traversing, but does not allow modification. The static methods encode()
and sendHeader()
are useful when creating XML.
The fXML constructor will accept four different types of sources for the XML to represent:
// From an fFile
$file = new fFile('/path/to/file');
$xml = new fXML($file);
// From a file path
$xml = new fXML('/path/to/file');
// From a URL
$xml = new fXML('http://example.com/rss');
// From an XML string
$xml = new fXML('<?xml ');
XML files with invalid data will cause an fValidationException to be thrown.
When fetching XML from a URL, an optional second parameter, $timeout
, can be specified. If not specified, the default_socket_timeout
ini setting is used.
// Fetch the XML with a 5 second timeout
$xml = new fXML('http://examples.com/rss', 5);
Two of the most common errors when creating XML are encoding characters as HTML entities for XML and delivering ISO-8859-1
(or Windows-1252
) encoded XML as UTF-8
. An optional boolean parameter, $fix_entities_encoding
can fix both of these problems.
// Used with an HTTP timeout
$xml = new fXML('http://example.com/rss', 5, TRUE);
// Used without a timeout
$xml = new fXML('./foo.xml', TRUE);
In XML, there are only five pre-defined named entities: &
, >
, <
, "
and '
. All other named entities from HTML will cause a parse error if included in XML without further encoding. For instance, ’
is invalid, but &rsquo;
or ’
is valid.
$fix_entities_encoding
will take
<book><title>Isn’t Valid XML</title></book>
and convert it to
<book><title>Isnt Valid XML</title></book>
before passing the XML to the parser.
If no encoding is specified for an XML document, the encoded is assumed to be UTF-8. Many developers not familiar with XML and issues related to encoding will omit the encoding attribute of the <?xml>
tag and will insert ISO-8859-1
or Windows-1252
(also called Latin or Latin 1) content.
fXML will throw an exception when such an XML document is parsed, since the parser being used will find invalid UTF-8 characters and give up encoding. The $fix_entities_encoding
parameter will detect non-UTF-8 characters for documents defined as UTF-8 (whether explicitly or by omission of the encoding attribute) and convert the content.
Information about the root XML element that was passed into the constructor can be accessed by the following methods:
Method | Description |
getName() |
Returns the name of the element without any preceding namespace prefix |
getNamespace() |
Returns the full namespace URI for the element, if any |
getPrefix() |
Returns the namespace prefix for the element, if any |
getText() |
Returns the text content of the element |
The raw XML being modeled by the fXML object can be retrieved by calling the method toXML()
.
echo $xml->toXML();
Attributes of the XML element can be accessed using array notation.
// Normal attribute
echo $xml['my_attribute'];
// Attribute with namespace prefix
echo $xml['ns:attribute'];
The text content of children of the XML element can be accessed by requesting object memebers.
// The content of the child element "firstName"
echo $xml->firstName;
// Child "title" in the "ns" prefix
echo $xml->{'ns:title'};
For anything beyond attribute and child element text content of the root XML element, the xpath()
method will need to be used. This method returns an array of all matching parts of the XML file.
If you aren't familiar with XPath, the Wikipedia page about XPath 1.0 is a good place to start. XPath allow traversal of the XML file using element names combined with predicates and functions.
xpath()
accepts two parameter, the XPath $path
, and optionally a boolean flag, $first_only
, to return only the first match.
// Select every "item" element in the "channel"
foreach ($xml->xpath("channel/item") as $item) {
echo $item->title;
}
The items matched by xpath()
may be child elements, text content or attributes. Here are the data types for various types of matches:
It is also possible to pull back just the first matched element by passing the second parameter as TRUE
.
// Pull the first attribute only, thus returning a string
echo $xml->xpath('@attribute', TRUE);
When using XPath, array-style attribute access or child element text content access, it can be useful to set a custom namespace prefix to allow for compatibility with various XML sources. The method addCustomPrefix()
accepts two parameters, the $ns_prefix
to register and its corresponding $namespace
. One registered, this prefix will be available throughout fXML.
$xml = new fXML('http://example.com/rss');
$xml->addCustomPrefix('pf', 'http://namspace');
echo $xml['pf:attribute'];
echo $xml->{'pf:child'};
foreach ($xml->xpath('pf:*') as $element) {
echo $element->getText();
}
When creating XML documents, such as RSS feeds, it is necessary to create properly encoded XML, otherwise strict XML parsers will not be able to parse the document. The encode()
method will ensure that all content is properly encoded for including in a UTF-8 XML file.
Here is an example of usage:
$xml = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
$xml .= "<articles>";
foreach ($articles as $article) {
$xml .= "<article><title>";
$xml .= fXML::encode($article->getTitle());
$xml .= "</title><description>";
$xml .= fXML::encode($article->getDescription());
$xml .= "</description></article>";
}
$xml .= "</articles>";
When sending XML files over HTTP, the method sendHeader()
should be called ensure that the Content-Type
header is set to the correct value of text/xml; charset=utf-8
. The utf-8
character set encoding is specified since all of Flourish is built to work with UTF-8 and all XML parsers must support that character set.
Provides functionality for XML files
This class is implemented to use the UTF-8 character encoding. Please see UTF-8 for more information.
1.0.0b8 | Fixed a method signature 8/24/11 |
---|---|
1.0.0b7 | Added a workaround for iconv having issues in MAMP 1.9.4+ 7/26/11 |
1.0.0b6 | Updated class to use fCore::startErrorCapture() instead of error_reporting() 8/9/10 |
1.0.0b5 | Added the $fix_entities_encoding parameter to __construct() 8/8/10 |
1.0.0b4 | Updated the class to automatically add a __ prefix for the default namespace and to use that for attribute and child element access 4/6/10 |
1.0.0b3 | Added the $http_timeout parameter to __construct() 9/16/09 |
1.0.0b2 | Added instance functionality for reading of XML files 9/1/09 |
1.0.0b | The initial implementation 1/13/08 |
Custom prefix => namespace URI mappings
array
The dom element for this XML
DOMElement
The XML string for serialization
string
An XPath object for performing xpath lookups
DOMXPath
Encodes content for display in a UTF-8 encoded XML document
string encode( string $content )
string | $content | The content to encode |
The encoded content
Sets the proper Content-Type HTTP header for a UTF-8 XML file
void sendHeader( )
Create the XML object from a string, fFile or URL
The $default_namespace will be used for any sort of methods calls, member access or array access when the element or attribute name does not include a :.
fXML __construct( fFile|string $source, numeric $http_timeout=NULL, boolean $fix_entities_encoding=NULL )
fXML __construct( fFile|string $source, boolean $fix_entities_encoding=NULL )
fFile|string | $source | The source of the XML, either an fFile object, a string of XML, a file path or a URL |
numeric | $http_timeout | The timeout to use in seconds when requesting an XML file from a URL |
boolean | $fix_entities_encoding | This will fix two common XML authoring errors and should only be used when experiencing decoding issues - HTML entities that haven't been encoded as XML, and XML content published in ISO-8859-1 or Windows-1252 encoding without an explicit encoding attribute |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Allows access to the text content of a child tag
The child element name ($name) may start with a namespace prefix and a : to indicate what namespace it is part of. A blank namespace prefix (i.e. an element name starting with :) is treated as the XML default namespace.
fXML|NULL __get( string $name )
string | $name | The child element to retrieve |
The child element requested
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
The child element name ($name) may start with a namespace prefix and a : to indicate what namespace it is part of. A blank namespace prefix (i.e. an element name starting with :) is treated as the XML default namespace.
boolean __isset( string $name )
string | $name | The child element to check - see method description for details about namespaces |
If the child element is set
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Prevents users from trying to set elements
void __set( string $name, mixed $value )
string | $name | The element to set |
mixed | $value | The value to set |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
The XML needs to be made into a string before being serialized
array __sleep( )
The members to serialize
Gets the string inside the root XML element
string __toString( )
The text inside the root element
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Prevents users from trying to unset elements
void __unset( string $name )
string | $name | The element to unset |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
The XML needs to be made into a DOMElement when woken up
void __wakeup( )
Adds a custom namespace prefix to full namespace mapping
This namespace prefix will be valid for any operation on this object, including calls to xpath().
void addCustomPrefix( string $ns_prefix, string $namespace )
string | $ns_prefix | The custom namespace prefix |
string | $namespace | The full namespace it maps to |
Returns the name of the current element
string getName( )
The name of the current element
Returns the namespace of the current element
string getNamespace( )
The namespace of the current element
Returns the namespace prefix of the current element
string getPrefix( )
The namespace prefix of the current element
Returns the string text of the current element
string getText( )
The string text of the current element
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Provides functionality for isset() and empty() (required by arrayaccess interface)
Offsets refers to an attribute name. Attribute may start with a namespace prefix and a : to indicate what namespace the attribute is part of. A blank namespace prefix (i.e. an offset starting with :) is treated as the XML default namespace.
boolean offsetExists( string $offset )
string | $offset | The offset to check |
If the offset exists
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Provides functionality for get [index] syntax (required by ArrayAccess interface)
Offsets refers to an attribute name. Attribute may start with a namespace prefix and a : to indicate what namespace the attribute is part of. A blank namespace prefix (i.e. an offset starting with :) is treated as the XML default namespace.
string offsetGet( string $offset )
string | $offset | The attribute to retrieve the value for |
The value of the offset
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Required by ArrayAccess interface
void offsetSet( integer|string $offset, $value )
integer|string | $offset | The offset to set |
$value |
Please note: this method is public, however it is primarily intended for internal use by Flourish and will normally not be useful in site/application code
Required by ArrayAccess interface
void offsetUnset( integer|string $offset )
integer|string | $offset | The offset to unset |
Performs an XPath query on the current element, returning the raw results
array query( string $path )
string | $path | The XPath path to query |
The matching elements
Returns a well-formed XML string from the current element
string toXML( )
The XML
Executes an XPath query on the current element, returning an array of matching elements
array|string|fXML xpath( string $path, boolean $first_only=FALSE )
string | $path | The XPath path to query |
boolean | $first_only | If only the first match should be returned |
An array of matching elements, or a string or fXML object if $first_only is TRUE
On this page you will find a list of all breaks in backwards compatibility. Once Flourish becomes stable this will only happen with major version releases. During the alpha phase they are bound to happen on a regular basis, whereas during beta they should be rare.
If you work with any protected methods or @internal
public methods, be sure to check out the internal backwards
compatiblity breaks page.
fTemplating::create()
was removed and a new method fTemplating::attach() was created to provide similar functionality.
fCore::detectOpcodeCache()
was renamed to fLoader::hasOpcodeCache()
fFile::output() now automatically closes any open output buffering and discards the contents.
The email subject of error/exception emails sent by fCore has changed.
fRecordSet::sort() and fRecordSet::sortByCallback() now return a new record set instead of sorting the record set in place - this helps prevent side effects.
fActiveRecord::reflect() now returns an associative array instead of a string.
fSchema::getTables(), fSchema::getColumnInfo(), fSchema::getDatabases(), fSchema::getKeys() and fSchema::getRelationships() now return database, schema, table and column names in lowercase. This may change the behavior of, or break ORM classes for SQLite, MySQL, PostgreSQL or MSSQL databases containing mixed-case identifiers, although such mixed-cased identifiers have never been officially supported. Please test ORM functionality when upgrading to this revision.
Callbacks registered to the extracted
hook via fDatabase::registerHookCallback() no longer receive the $strings
parameter, instead all strings are added into the $values
parameter.
Updated fCore::expose() to not wrap the value in an HTML tag when PHP is being run via the CLI.
fRequest::getBestAcceptType() and fRequest::getBestAcceptLanguage() now return either NULL
, FALSE
or a string instead of NULL
or a string. See documentation for details of return values.
The $contents
parameter of fEmail::addAttachment() is now first instead of third.
fORMFile::configureImageUploadColumn() no longer accepts the optional $image_type
as the fourth parameter, instead fORMFile::addFImageMethodCall() must be called with saveChanges
as the $method
and the image type as the first parameter.
fRequest::get() now strips out low bytes characters if no $cast_to
, or if a string
or array
$cast_to
is specified. Using the new binary
$cast_to
will leave all bytes intact.
fActiveRecord column set
methods now treat strings of all whitespace the same as empty strings and convert them to NULL
.
Changed the parameter order in fImage::crop() from $crop_from_x
, $crop_from_y
, $new_width
, $new_height
to $new_width
, $new_height
, $crop_from_x
, $crop_from_y
.
Changed the array structure for child record in validation messages. Instead of all main record and child record messages being in a single array with keys in the form of child_table[0]::column
, there is now a child_table[0]
key that points to an array containing a name
key and an errors
key. The value for name
is the name of the child record, e.g. Child Record #1
. The value for errors
is an array of validation messages with the keys being the column names and the values being the validation messages. Thus:
array(
'first_name' => 'First Name: Please enter a value',
'user_permissions[0]::resource' => 'Resource: Please enter a value'
)
would now be represented as:
array(
'first_name' => 'First Name: Please enter a value',
'user_permissions[0]' => array(
'name' => 'User Permission #1',
'errors' => array(
'resource' => 'Resource: Please enter a value'
)
)
)
Methods registered to the pre::validate()
and post::validate()
hooks will need to handle this new array structure. Code that calls ->validate(TRUE)
on an fActiveRecord object will need to handle this new array structure. fORMValidation::addStringReplacement(), fORMValidation::addRegexReplacement() and fORMValidation::setMessageOrder() will now only affect the specified class specified and will not affect messages from child records.
Restructured individual record access, the iterator interface and callback parameters for fRecordSet.
Removed the method fRecordSet::fetchRecord()
- functionality can be replicated via the new method fRecordSet::getRecord().
Manual iteration via fRecordSet::current()
, fRecordSet::key()
, fRecordSet::next()
, fRecordSet::rewind()
and fRecordSet::valid()
(the Iterator interface) was removed and should be replaced by retrieving an ArrayIterator from the new method fRecordSet::getIterator(). Manual iteration can be performed on the ArrayIterator object.
The $pointer
parameter was replaced with $method_name
for callbacks registered via fORM::registerRecordSetMethod().
Changed fTemplating::delete() to return the value(s) of the element(s) deleted instead of the fTemplating instance.
fSession::add(), fSession::delete(), fSession::get(), fSession::set(), fTemplating::add(), fTemplating::delete(), fTemplating::get() and fTemplating::set() all now interpret [
and ]
as array syntax and thus these can not be used in key or element names.
fTemplating::remove()
was renamed to fTemplating::filter().
Removed fDatabase::enableSlowQueryWarnings()
, added ability to replicate via fDatabase::registerHookCallback().
Removed support for the odbc
and pdo_odbc
extensions from fDatabase, fResult, fStatement and fUnbufferedResult. With this change, Flourish no longer supports any kind of ODBC connections.
fActiveRecord::populate{RelatedRecords}()
changed from requiring the database table name as the input prefix to the underscore_notation
version of the class name. This will only affect related records where the class was mapped using fORM::mapClassToTable().
Renamed fUpload::setMaxFilesize()
to fUpload::setMaxSize() to be consistent with fFile::getSize().
fUpload no longer accepts uploaded files that start with a .
unless fUpload::allowDotFiles() is called.
fActiveRecord::validate() now uses column names as array keys if messages are returned, the $validation_messages
parameter for the pre::validate()
and post::validate()
hooks now requires array keys be column names and fValidation::validate() now uses field names as array keys if messages are returned.
fUpload::validate() no longer returns a $_FILES
array.
fValidation::addRequiredFields() no longer accepts one-or-more rules, instead use fValidation::addOneOrMoreRule(). fValidation::addRequiredFields() no longer accepts conditional rules, instead use fValidation::addConditionalRule().
fDirectory::scan() and fDirectory::scanRecursive() to strip the current directory's path before matching the $filter
Renamed fFile::getFilename()
to fFile::getName(), fFile::getFilesize()
to fFile::getSize(), fFile::getDirectory()
to fFile::getParent() and fDirectory::getFilesize()
to fDirectory::getSize()
Added the $force_cascade
parameter to fActiveRecord::delete() and fActiveRecord::store(). This change require any classes that override those methods be updated to include at least one parameter.
fORM::addCustomClassTableMapping()
was renamed to fORM::mapClassToTable()
Removed the $prefix
parameter from the methods fSession::delete(), fSession::get() and fSession::set()
Removed support for date function translation in fDatabase/fSQLTranslation
Renamed fRecordSet::buildFromRecords()
to fRecordSet::buildFromArray()
Before this revision one-to-one
relationships were not properly detected, so some one-to-many
functionality such as fActiveRecord::populate{RelatedRecords}()
, fActiveRecord::build{RelatedRecords}()
and fActiveRecord::associate{RelatedRecords}()
was working on these one-to-one
relationships when it should not have.
one-to-one
relationships support fActiveRecord::create{RelatedRecord}()
, fActiveRecord::populate{RelatedRecord}()
and fActiveRecord::associate{RelatedRecord}()
instead.
Renamed fORMValidation::addConditionalValidationRule()
to fORMValidation::addConditionalRule(), fORMValidation::addManyToManyValidationRule()
to fORMValidation::addManyToManyRule(), fORMValidation::addOneOrMoreValidationRule()
to fORMValidation::addOneOrMoreRule(), fORMValidation::addOneToManyValidationRule()
to fORMValidation::addOneToManyRule(), fORMValidation::addOnlyOneValidationRule()
to fORMValidation::addOnlyOneRule(), fORMValidation::addValidValuesValidationRule()
to fORMValidation::addValidValuesRule()
Removed fRecordSet::flagAssociate()
and fRecordSet::isFlaggedForAssociation()
, the $associate
parameter is no longer passed to callbacks registered via fORM::registerRecordSetMethod()
The first parameter of fSession::clear() was removed, fSession::delete() should now be used instead
Moved fCRUD::printOption()
to fHTML::printOption(), fCRUD::showChecked()
to fHTML::showChecked(), fCRUD::removeListItems()
and fCRUD::reorderListItems()
to fException::splitMessage(), fCRUD::generateRequestToken()
to fRequest::generateCSRFToken(), and fCRUD::validateRequestToken()
to fRequest::validateCSRFToken()
Removed fORMSchema::enableSmartCaching()
, fORM::enableSchemaCaching() now provides equivalent functionality. fSchema::setCacheFile()
changed to fSchema::enableCaching() and now requires an fCache object. fSchema::flushInfo()
was renamed to fSchema::clearCache().
Changed the second parameter of fFile::output() from $ignore_output_buffer
to $filename
Removed fDate::getSecondsDifference()
, fTime::getSecondsDifference()
, fTimestamp::getSecondsDifference()
and fTimestamp::getSeconds()
fCore::getOS()
and fCore::getPHPVersion()
were removed and replaced with fCore::checkOS() and fCore::checkVersion()
Renamed fORM::addCustomTableClassMapping()
to fORM::addCustomClassTableMapping() and swapped the parameters
Changed fCryptography::symmetricKeyEncrypt() to not encrypt the IV since we are using HMAC on it
Most users will not have code from the Flourish alpha
fCore::stringlike()
was removed
fCore::trigger()
was removed
!fPrintableException was renamed to fException
fCore::toss()
was removed and all exceptions are now normally thrown with throw new ExceptionName()
. fCore::registerTossCallback()
was renamed to fPrintableException::registerCallback()
, fGrammar::compose()
was renamed to fText::compose() and fGrammar::registerComposeCallback()
was renamed to fText::registerComposeCallback().
fActiveRecord hooks and method calls now have a &$cache
parameter after the &$related_records
parameter, but before any other parameters. Any method registered to a hook will need to have the method signature updated.
Swapped the order of the last two parameters of fORMRelated::setOrderBys() so that $route
(which is really just NULL
most of the time) was last and optional
Changed the existing file upload field name from __flourish_existing_column_name
to existing-column_name
and changed the delete file upload field name from __flourish_delete_column_name
to delete-column_name
fNoResultsException
was renamed to fNoRowsException, fResult::tossIfNoResults()
was renamed to fRequest::tossIfNoRows(), fUnbufferedResult::tossIfNoResults()
was renamed to fUnbufferedResult::tossIfNoRows(), fResult::getAffectedRows()
was renamed to fResult::countAffectedRows() and fResult::getReturnedRows()
was renamed to fRequest::countReturnedRows()
fActiveRecord::has()
was renamed to fActiveRecord::hasOld() and fActiveRecord::retrieve()
was renamed to fActiveRecord::retrieveOld()
replace::
hooks are no longer allowed in fORM::registerHookCallback(), instead use fORM::registerActiveRecordMethod(). fRecordSet::registerMethodCallback()
has been renamed to fORM::registerRecordSetMethod().
Renamed fORMDatabase::getInstance()
to fORMDatabase::retrieve() and renamed fORMSchema::getInstance()
to fORMSchema::retrieve()
fUTF8::detect()
went from being public to private
fResult::getPointer()
was removed, same functionality is available from fResult::key()
fResult::areRemainingRows()
was removed, same functionality is available from fResult::valid()
fUpload::upload()
was renamed to fUpload::move()
Changed fUpload from a static class to an instance class
Removed fPrintableException::prepareMessage()
Removed fTimestamp::combine()
Changed the replacement token in fRequest::overrideAction() from %%action%%
to %action%
Changed the separator between the table name and column name for related form inputs from . back to :: because PHP converts . to _ - this reverts changes in r264
fRecordSet::build{RelatedRecords}()
was renamed to fRecordSet::prebuild{RelatedRecords}()
and fRecordSet::count{RelatedRecords}()
was renamed to fRecordSet::precount{RelatedRecords}()
Removed fRecordSet::countWithoutLimit()
and added the $ignore_limit
parameter to fRecordSet::count()
Changed the separator between the table name and column name for related form inputs from ::
to .
- users::user_id[]
will now be users.user_id[]
Removed fRecordSet::buildFromPrimaryKeys()
, changed fORMRelated::associateRecords() to only accept an fRecordSet instead of an fRecordSet or an array of primary keys
Renamed fORM::createActiveRecordClass()
to fORM::defineActiveRecordClass()
Renamed fFilesystem::createUniqueName()
to fFilesystem::makeUniqueName()
Renamed fTimestamp::createFormat()
to fTimestamp::defineFormat()
Removed fORMDatabase::escapeByType()
, added support for shorthand table.column names to fORMDatabase::escapeBySchema() to provide replacement functionality
The fourth parameter of fRecordSet::build() is now the page number instead of the offset. The page number is subtracted by one and multiplied by the limit to get the correct offset.
Removed fGrammar::registerHumanizeCallback()
fGrammar::replaceHumanize()
was renamed to fGrammar::registerHumanizeCallback(), fGrammar::replaceJoinArray()
was renamed to fGrammar::registerJoinArrayCallback(). fGrammar::addAllCapitalsWord()
was removed and fGrammar::addHumanizeRule() was added with similar functionality.
fDate::setDate()
, fDate::setISODate()
, fTime::setTime()
, fTimestamp::setDate()
, fTimestamp::setISODate()
, fTimestamp::setTime()
, fTimestamp::setTimezone()
, fTimestamp::getTimezone()
were removed. The fDate, fTime and fTimestamp classes all became value objects (immutable) and fDate::adjust(), fTime::adjust() and fTimestamp::adjust() all now return new objects. Each class also gained a method called modify()
which returns a new object based on the current object, replacing the various set methods.
Changed fCRUD::removeListItems() so its parameters were consistent with fCRUD::reorderListItems()
fXML::prepare()
was renamed to fXML::encode()
fRecordSet::flagForAssociation()
was renamed to fRecordSet::flagAssociate()
fRecordSet::registerCallback()
was renamed to fRecordSet::registerMethodCallback()
fORM::getClassName()
was renamed to fORM::getClass() and fRecordSet::getClassName()
was renamed to fRecordSet::getClass()
fHTML::checkForBlockLevelHTML()
was renamed to fHTML::containsBlockLevelHTML() and fHTML::createLinks()
was renamed to fHTML::makeLinks()
fCryptography::generateRandomString()
was renamed to fCryptography::randomString()
fCRUD::createSortableColumn()
was renamed to fCRUD::printSortableColumn()
Removed fFinancialTransaction and fShippingRates. Both were unlike the rest of Flourish in that they were tied to the APIs of other services and neither was very comprehensive in terms of the number of services supported.
Removed fORMDatabase::initialize()
, you must now construct an fDatabase instance and pass it to fORMDatabase::attach()
fORMDatabase::prepareBySchema()
was renamed to fORMDatabase::escapeBySchema() and fORMDatabase::prepareByType()
was renamed to fORMDatabase::escapeByType()
fDatabase::escape() replaces fDatabase::escapeBlob()
, fDatabase::escapeBoolean()
, fDatabase::escapeDate()
, fDatabase::escapeFloat()
, fDatabase::escapeInteger()
, fDatabase::escapeString()
, fDatabase::escapeTime()
and fDatabase::escapeTimestamp()
. fDatabase::unescape() replaces fDatabase::unescapeBlob()
, fDatabase::unescapeBoolean()
, fDatabase::unescapeDate()
, fDatabase::unescapeFloat()
, fDatabase::unescapeInteger()
, fDatabase::unescapeString()
, fDatabase::unescapeTime()
and fDatabase::unescapeTimestamp()
.
The second and third parameters of fMessaging::create() were swapped to be consistent with the other fMessaging methods
fORMColumn::configureDateCreatedColumn()
was moved to fORMDate::configureDateCreatedColumn() and fORMColumn::configureDateUpdatedColumn()
was moved to fORMDate::configureDateUpdatedColumn()
fORMColumn::configureMoneyColumn()
was moved to fORMMoney::configureMoneyColumn()
fMoney::multiply()
renamed to fMoney::mul() and fMoney::subtract()
renamed to fMoney::sub() to be consistent with fNumber
fORMRelated::constructRecord()
renamed to fORMRelated::createRecord(), fORMRelated::constructRecordSet()
renamed to fORMRelated::buildRecords()
Renamed fRecordSet::create()
to fRecordSet::build(), removed fRecordSet::createEmpty()
, renamed fRecordSet::createFromObjects()
to fRecordSet::buildFromRecords() and added a new first parameter, renamed fRecordSet::createFromPrimaryKeys()
to fRecordSet::buildFromPrimaryKeys(), renamed fRecordSet::createFromSQL()
to fRecordSet::buildFromSQL().
fValidation no longer has the methods fValidation::setEmailFields()
, fValidation::setEmailHeaderFields()
or fValidation::setRequiredFields()
. The various fValidation::add*()
methods should be used instead.
The method fImage::getInfo() went from being public
to protected
.
fRecordSet::preload{RelatedRecords}()
became fRecordSet::build{RelatedRecords}()
.
The $debug
parameter was removed from all calls to fActiveRecord hook callbacks.
Removed fRecordSet::preload{RelatedRecords}()
.
fInflection
was renamed to fGrammar.
fMessaging::show() now behaves differently than it did before.
In most programming languages where functions are first-class members, the syntax for a function or method callback is simply the function/method name without the parenthesis. The below snippet of javascript defines a function and then uses a callback for setTimeout to cause the function to be executed at a later time.
function foo() {
// Do stuff
}
setTimeout(foo, 100);
Unfortunately PHP does not treat function at first-class members of the language. It uses strings and arrays for function and method callbacks respectively. In an attempt to make the callback syntax a little more intuitive, Flourish uses class constants and the __get()
magic method to make javascript-style callbacks.
Flourish defines class constants with the same name as all static methods:
// Uppercase a UTF-8 string
$string = fUTF8::upper($string);
// Uppercase an array of UTF-8 strings (PHP 5.2 only)
$strings = array_map(fUTF8::upper, $strings);
The only caveat with these class constants is that in PHP 5.1 they need to be passed to fCore::callback() if they are going to be used with built-in PHP functions. This is because the constants are the string-style static method callbacks, which were added in PHP 5.2. The method fCore::callback() translates them into the array-style syntax that works in 5.1.
// PHP 5.1 compatible callbacks for PHP functions
$string = array_map(fCore::callback(fUTF8::upper), $strings);
For instance method callbacks, Flourish uses the __get()
magic method to create the appropriate array-syntax callback for the object being called.
$ten_dollars = new fMoney(10);
echo $ten_dollars->format();
// Callback syntax for format
fCore::call($ten_dollars->format);
Both the instance and static method callback syntaxes work on every method of every Flourish class.
One of the aims of Flourish is to make a portable PHP library so that applications can be deployed on different servers with little to no changes. With the multitude of database servers available and different platforms, one of the main hurdles for portability is the different dialects of SQL.
Flourish SQL has grown a common subset of the SQL dialects supported by IBM DB2, Microsoft SQL Server, MySQL, PostgreSQL and SQLite, with a few additions for ease-of-use. This document describes what features and syntax are supported across all databases.
In the Flourish SQL column you will find the supported syntax for a function, data type, etc. The other columns contain a ✓
if the syntax is the same, or the database-specific syntax highlighted in red.
The information on this page is targeted at the following database versions, or newer editions:
If targeting MSSQL or DB2, please be aware that UNIQUE
constraints only allow a single NULL
value to be present in all rows. To work around this, split the column in to a separate table and use a foreign key to associate it to the original table.
Oracle does not support the ON UPDATE
clause for foreign keys. MSSQL does not allow ON UPDATE CASCADE
or ON DELETE CASCADE
clauses for foreign keys that could cause cycles during handling. This is often caused by multiple columns in a table referencing the same foreign table.
MySQL, DB2, Oracle and MSSQL all have various constraints on TEXT
and BLOB
data types. To achieve cross-database support, be sure not to do the following with such columns:
DEFAULT
valueGROUP BY
clausePRIMARY KEY
or FOREIGN KEY
UNIQUE
constraint
Since fRecordSet uses GROUP BY
clauses to allow for selection of records by values in related tables, it is highly recommended that TEXT
and BLOB
columns always be split out into a separate table and to use a foreign key to associate it with the original table. The Flourish ORM has strong support to easily retrieving such related records.
The following Flourish SQL is supported when calling the fDatabase::translatedQuery() and fDatabase::unbufferedTranslatedQuery() methods. The Cross-Database SQL section of the fDatabase page has more information.
Most dialects of SQL support a large number of common data types, however a things like booleans, binary data and dates and times are often different. Here is an outline of the data types supported by Flourish SQL:
Flourish SQL | DB2 | MSSQL | MySQL | Oracle | PostgreSQL | SQLite |
---|---|---|---|---|---|---|
smallint | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
integer | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
bigint | ✓ | ✓ | ✓ | integer | ✓ | ✓ |
integer autoincrement primary key | integer generated by default as identity primary key | integer identity(1) primary key | integer auto_increment primary key | integer + sequence + trigger | serial primary key | integer primary key autoincrement |
float | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
real | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
decimal | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
char | ✓ | nchar | ✓ | ✓ | ✓ | ✓ |
varchar | ✓ | nvarchar | ✓ | varchar2 | ✓ | ✓ |
text | clob | ntext | mediumtext | clob | ✓ | ✓ |
blob | ✓ | image | longblob | ✓ | bytea | ✓ |
timestamp | ✓ | datetime | datetime | ✓ | ✓ | ✓ |
date | ✓ | date for 2008, datetime for 2005 | ✓ | ✓ | ✓ | ✓ |
time | ✓ | time for 2008, datetime for 2005 | ✓ | timestamp | ✓ | ✓ |
boolean | char(1) | bit | ✓ | number(1) | ✓ | ✓ |
In all cases except for the date
and time
are the data types consistent across the databases. MSSQL does not support date
or time
, so the more precise datetime
is used instead. Oracle does not support time
and uses timestamp
instead. In both cases, the unnecessary portions are removed by the unescape()
method of fDatabase.
One other thing to note is the apparent perfect compatibility for SQLite data types. This is not because the SQLite data types were chosen as the basis for Flourish SQL, but rather that SQLite is loosely-typed and allows anything to be entered for a data type.
Since boolean fields are implemented quite differently across databases, it comes as no surprise that there are different values to use for boolean fields:
Flourish SQL | DB2 | MSSQL | MySQL | Oracle | PostgreSQL | SQLite |
---|---|---|---|---|---|---|
TRUE | '1' | '1' | ✓ | 1 | ✓ | '1' |
FALSE | '0' | '0' | ✓ | 0 | ✓ | '0' |
Transaction control differs slightly among the supported databases:
Flourish SQL | DB2 | MSSQL | MySQL | Oracle | PostgreSQL | SQLite |
---|---|---|---|---|---|---|
BEGIN | auto-commit disabled | BEGIN TRANSACTION | ✓ | auto-commit disabled | ✓ | ✓ |
COMMIT | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
ROLLBACK | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
SAVEPOINT savepoint_name | ✓ | SAVE TRANSACTION savepoint_name | ✓ | ✓ | ✓ | ✓ |
ROLLBACK TO SAVEPOINT savepoint_name | ✓ | ROLLBACK TRANSACTION savepoint_name | ✓ | ✓ | ✓ | ✓ |
The following constructs are are used with the data manipulation language (DML) statements in SQL. Such statements include SELECT
, INSERT
, UPDATE
and DELETE
.
Operators are fairly consistent across the different databases, with the exception of the concatenation operator. Please note that ||
works for concatenation in MySQL only when MySQL is in ANSI mode (Flourish automatically switches into ANSI mode when a connection to a MySQL database is initiated).
Flourish SQL | DB2 | MSSQL | MySQL | Oracle | PostgreSQL | SQLite |
---|---|---|---|---|---|---|
Mathematical Operators | ||||||
+ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
- | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
/ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
* | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
% | mod() | ✓ | ✓ | mod() | ✓ | ✓ |
String Operators | ||||||
|| (concatenation) | ✓ | + | ✓ | ✓ | ✓ | ✓ |
Comparison Operators | ||||||
< | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
> | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
<= | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
>= | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
<> (inequality) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
!= | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
= | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
IN | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
NOT IN | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
IS (equality with NULL) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
IS NOT (inequality with NULL) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
LIKE (case insensitive) | lower(value) LIKE lower(pattern) | ✓ | ✓ | lower(value) LIKE lower(pattern) | ILIKE | ✓ |
Boolean Operators | ||||||
AND | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
OR | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
There a quite a few functions that are consistent across the different databases:
Flourish SQL | DB2 | MSSQL | MySQL | Oracle | PostgreSQL | SQLite |
---|---|---|---|---|---|---|
Mathematical Functions | ||||||
abs(x) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
acos(x) | ✓ | ✓ | ✓ | ✓ | ✓ | PHP Callback |
asin(x) | ✓ | ✓ | ✓ | ✓ | ✓ | PHP Callback |
atan(x) | ✓ | ✓ | ✓ | ✓ | ✓ | PHP Callback |
atan2(x, y) | ✓ | atn2(x, y) | ✓ | ✓ | ✓ | PHP Callback |
ceil(x) | ceiling(x) | ceiling(x) | ✓ | ✓ | ✓ | PHP Callback |
ceiling(x) | ✓ | ✓ | ✓ | ceil(x) | ✓ | PHP Callback |
cos(x) | ✓ | ✓ | ✓ | ✓ | ✓ | PHP Callback |
cot(x) | (1/tan(x)) | ✓ | ✓ | (1/tan(x)) | ✓ | PHP Callback |
degrees(x) | ✓ | ✓ | ✓ | (x * 57.295779513083) | ✓ | PHP Callback |
exp(x) | ✓ | ✓ | ✓ | ✓ | ✓ | PHP Callback |
floor(x) | ✓ | ✓ | ✓ | ✓ | ✓ | PHP Callback |
ln(x) | ✓ | log(x) | ✓ | ✓ | ✓ | PHP Callback |
log(b, x) | (ln(x)/ln(b)) | (log(x)/log(b)) | ✓ | ✓ | ✓ | PHP Callback |
pi() | 3.14159265358979 | ✓ | (pi()+0.0000000000000) | 3.14159265358979 | ✓ | PHP Callback |
power(x, y) | ✓ | ✓ | ✓ | ✓ | ✓ | PHP Callback |
radians(x) | ✓ | ✓ | ✓ | (x * 0.017453292519943) | ✓ | PHP Callback |
random() | rand() | rand() | rand() | (abs( dbms_random.random ) / 2147483647) | ✓ | (abs( random()) / 9223372036854775807) |
round(x) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
sign(x) | ✓ | ✓ | ✓ | ✓ | ✓ | PHP Callback |
sqrt(x) | ✓ | ✓ | ✓ | ✓ | ✓ | PHP Callback |
sin(x) | ✓ | ✓ | ✓ | ✓ | ✓ | PHP Callback |
tan(x) | ✓ | ✓ | ✓ | ✓ | ✓ | PHP Callback |
Aggregate Functions | ||||||
avg(l) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
count(l) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
max(l) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
min(l) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
sum(l) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
String Functions | ||||||
trim(s) | ✓ | rtrim(ltrim(s)) | ✓ | ✓ | ✓ | ✓ |
rtrim(s) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
ltrim(s) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
upper(s) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
lower(s) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
substr(s, start, length) | substring(s, start, length, CODEUNITS32) | substring(s, start, length) | ✓ | ✓ | ✓ | ✓ |
replace(s, find, replace) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
length(s) | character_length(s, CODEUNITS32) | len(s) | char_length(s) | ✓ | ✓ | ✓ |
coalesce(a, b,...) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
Date/Time Functions | ||||||
CURRENT_TIMESTAMP | ✓ | ✓ | ✓ | ✓ | ✓ | datetime( CURRENT_TIMESTAMP, 'localtime') |
A quick scan of the SQLite column indicates that the trigonometric functions are all implemented as PHP callbacks. SQLite made the decision to include only specific functions in the supported SQL dialect, however they added support for hooks into other programming languages. Flourish uses this ability to provide support for trigonometric functions.
There are a few expressions that are consistent across databases:
Flourish SQL | DB2 | MSSQL | MySQL | Oracle | PostgreSQL | SQLite |
---|---|---|---|---|---|---|
BETWEEN | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
CASE (simple) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
CASE (complex) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
There are quite a few different types of join supported by the four different databases, however the common join functionality is a small subset. Note that all joins except CROSS
and ,
require use of an ON
clause, while CROSS
and ,
can not use an ON
clause.
Flourish SQL | DB2 | MSSQL | MySQL | Oracle | PostgreSQL | SQLite |
---|---|---|---|---|---|---|
, (cross join) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
CROSS JOIN | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
[INNER] JOIN | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
LEFT [OUTER] JOIN | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
The brackets indicate the words OUTER
and INNER
are optional.
You may notice the lack of a FULL [OUTER] JOIN
, RIGHT [OUTER] JOIN
, the keyword NATURAL
and USING
clauses. Unfortunately DB2, MySQL and SQLite do not support FULL
joins, SQLite does not support RIGHT
joins and DB2 and MSSQL do not support NATURAL
joins. USING (col, ...)
clauses are not supported by MSSQL.
FULL
joins can be achieved by a somewhat complex combination of a SELECT
from the first table with extra columns UNION
ed with an INNER JOIN
, UNION
ed with a SELECT
from the second table with extra columns.
RIGHT
joins can be performed by switching the order of the tables in the FROM
clause.
NATURAL
joins are just shorthand for the common columns between tables, so all that needs to be done is an INNER
join with manually specified columns in the ON
condition.
USING
clauses can be approximated with a ON
condition, however the USING
operator ensures that only one copy of each column is included in the returned rows, whereas ON
does not.
There are a few clauses that are supported across all databases:
Flourish SQL | DB2 | MSSQL | MySQL | Oracle | PostgreSQL | SQLite |
---|---|---|---|---|---|---|
DISTINCT (follows SELECT) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
ALL (follows SELECT) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
LIMIT (without an offset) | FETCH FIRST n ROWS ONLY | TOP | ✓ | subquery with rownum clause | ✓ | ✓ |
LIMIT/OFFSET | subquery with row_number() clause and removal of extra column via PHP | subquery with row_number() clause and removal of extra column via PHP | ✓ | nested subqueries with rownum clauses and removal of extra column via PHP | ✓ | ✓ |
UNION | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
UNION ALL | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
The following are the supported data definition language (DDL) statements in Flourish SQL. Some examples of DDL statements include CREATE TABLE
, ALTER TABLE
and CREATE INDEX
.
While DML statements between the supported databases tend to be fairly consistent, DDL statements tend to be much more varied. For instance, all of the supported databases use different constructs to achieve auto-incrementing primary keys.
The following grammars will show the supported Flourish SQL syntax, but do not cover exactly what is run for each type of database. To see what processing takes place, please see the fSQLSchemaTranslation source code, or call fDatabase::enableDebugging() before executing DDL statements.
The following syntax is supported for CREATE TABLE
statements. By strictly following this syntax, and using the data types previously
defined, CREATE TABLE
statements should work identically across all six supported database systems.
CREATE TABLE table_name (
{
column_name data_type [ DEFAULT default_value ] [ column_constraint [ ... ] ] |
table_constraint
}, ...
)
where column_constraint is:
{
NOT NULL |
NULL |
UNIQUE |
[ AUTOINCREMENT ] PRIMARY KEY |
CHECK ( column_name IN ( value1 [, value2 [, ... ] ] ) ) |
REFERENCES referenced_table ( referenced_column ) [ ON DELETE action ]
}
table_constraint is:
{
UNIQUE ( column_name [, ... ] ) |
PRIMARY KEY ( column_name [, ... ] ) |
CHECK ( column_name IN ( value1 [, value2 [, ... ] ] ) ) |
FOREIGN KEY ( column_name ) REFERENCES referenced_table ( referenced_column )
[ ON DELETE action ]
}
and action is:
{ RESTRICT | CASCADE | SET NULL | NO ACTION }
The ALTER TABLE
statements are some of the most powerful aspects of Flourish SQL since the native support for ALTER TABLE
varies wildly between databases. In addition to varying syntax, many databases require explicit constraint names for dropping UNIQUE
, CHECK
, FOREIGN KEY
and PRIMARY KEY
constraints. These constraint names are usually generated by the system and are not consistent across different databases.
To solve these problems, fSQLSchemaTranslation introspects the database and alters the SQL statements to work with the different database engines. In some cases multiple SQL statements must be executed. fSQLSchemaTranslation is written in such a way that each of the statements listen below should be individually atomic. Thus, if the statement fails, the database should remain at the state before the statement was executed.
PostgreSQL, SQLite and MSSQL all fully support transactions for DDL statements, which makes this easy. For DB2, most DDL statements run within a transaction, and the one that doesn't (REORG TABLE
) is only executed once the primary statement succeeds. For Oracle, all of these statement correspond to a single SQL statement, so the statement will either succeed or fail. MySQL requires storing rollback statements for all database operations, and such statements are run if a statement fails. The tests for Flourish include a bunch of tests to ensure that statements are run atomically.
The syntax is strongly based on PostgreSQL's ALTER TABLE
statements, since they are powerful and succinct. Users who are more familiar with other database systems, especially SQLite and MySQL, will hopefully find these statement significantly easier to use than native commands.
Of special note among the supported database systems is SQLite, since it only natively supports renaming tables and adding columns for version 3.x and nothing for version 2.x. Flourish does a fairly significant amount of work for SQLite, which includes:
Some of these steps require significant sub-steps, such as in SQLite 2, where table renaming does not exists. Luckily SQLite allows DDL statements within transactions, so everything is safe, but ALTER TABLE
statements may run more slowly because of the extensive amount of work required.
ALTER TABLE table_name RENAME TO new_table_name
An fSQLException will be thrown if a table with the new name already exists.
ALTER TABLE table_name ADD COLUMN column_name data_type [ DEFAULT default_value ] [ column_constraint [ ... ] ]
The data_type
, default_value
and column_constraint
values are all exactly the same as those for Create Table statements. An fSQLException will be thrown if a column with the same name already exists or there is an error in the column definition.
ALTER TABLE table_name RENAME COLUMN column_name TO new_column_name
An fSQLException will be thrown if a column with the new name already exists.
ALTER TABLE table_name DROP COLUMN column_name
Any UNIQUE
, FOREIGN KEY
or PRIMARY KEY
constraints that involve this column will also be dropped. An fSQLException will be thrown if the column does not exist.
ALTER TABLE table_name ALTER COLUMN column_name TYPE data_type
For many of the supported databases, there are restrictions about what data types can be automatically converted. In general it is safe to change the data type to increase the size, such as making a VARCHAR
longer, or changing from an INTEGER
to a BIGINT
.
For more drastic data type changes, it is normally necessary to:
CAST()
statementUNIQUE
, FOREIGN KEY
and PRIMARY KEY
constraints that existed on the old columnALTER TABLE table_name ALTER COLUMN column_name SET DEFAULT default_value
Sets the default value for a column, overriding any previous value. Some databases only support constant values for default_value
. For example, not all databases allow setting CURRENT_TIMESTAMP()
to be a default value since it is a function.
ALTER TABLE table_name ALTER COLUMN column_name DROP DEFAULT
Drops the default value for a column. This statement will succeed even if no default value exists.
ALTER TABLE table_name ALTER COLUMN column_name SET NOT NULL [ DEFAULT default_value ]
Sets a column to not allow NULL
values, and optionally sets the default value for a column. This statement will succeed even if the column already restricts NULL
values.
This combined form of SET NOT NULL
and SET DEFAULT
is useful to support NOT NULL DEFAULT ''
configurations on Oracle databases, where blank strings are automatically converted to NULL
. NOT NULL DEFAULT ''
is useful for writing simpler SQL statements since it is no longer necessary to specially test for NULL
values via (column_name IS NULL OR column_name = '')
.
ALTER TABLE table_name ALTER COLUMN column_name DROP NOT NULL
Changes a column to allow NULL
values. For most databases this will not succeed on columns that are part of a PRIMARY KEY
constraint. This statement will succeed even if the column already allows NULL
s.
ALTER TABLE table_name ALTER COLUMN column_name SET CHECK IN (string_value [, ... ] )
Creates a CHECK(column_name IN ('string value', 'string value'))
check constraint for the column. This check constraint will override any existing check constraint.
MySQL doesn't support CHECK
constraints, but the ENUM()
data type is basically a check constraint for a set of string values. Thus for MySQL, this statement will turn a VARCHAR
column into an ENUM()
column with the strings provided.
ALTER TABLE table_name ALTER COLUMN column_name DROP CHECK
Drop the CHECK
constraint for a column. An fSQLException will be thrown if no check constraint exists.
MySQL doesn't support CHECK
constraints, but the ENUM()
data type is basically a check constraint for a set of string values. Thus the only cross- database compatible CHECK
constraint is CHECK(column_name IN ('string value', 'string value 2'))
, which will be automatically converted to an ENUM()
for MySQL. Consequently, when DROP CHECK
is called for MySQL, an ENUM()
column will be coverted to a VARCHAR
.
ALTER TABLE table_name ADD PRIMARY KEY (column_name [, ... ] ) [ AUTOINCREMENT ]
Creates a primary key for table_name
with the columns specified. An fSQLException will be thrown if a primary key already exists.
If only one column is specified and the AUTOINCREMENT
keyword is provided, the column will be configured to automatically created auto-incrementing integer values when no value is provided. The actual implementation of AUTOINCREMENT
varies widely between databases. Please see Data Types for details.
ALTER TABLE table_name DROP PRIMARY KEY
Drops the primary key for table_name
. Any foreign keys that reference this primary key will also be dropped. An fSQLException will be thrown if a primary key does not exist.
ALTER TABLE table_name ADD FOREIGN KEY (column_name) REFERENCES foreign_table(foreign_column) [ ON DELETE action ]
Adds a foreign key to the column specified. Currently, Flourish as a whole, only supports single-column foreign key constraints. This restriction is also present for this ALTER TABLE
statement. The valid action
values are the same as those supported by Create Table. An fSQLException will be thrown if a foreign key already exist.
Since DB2 and Oracle do not fully support ON UPDATE
clauses, such a clause will be automatically removed for those databases.
ALTER TABLE table_name DROP FOREIGN KEY (column_name)
Drops the foreign key for the column specified. Currently, Flourish as a whole, only supports single-column foreign key constraints. An fSQLException will be thrown if a foreign key does not exist.
ALTER TABLE table_name ADD UNIQUE (column_name [, ... ] )
Adds a unique constraint to the column(s) specified. Some databases, such as MSSQL and DB2, treat a NULL
value as a distinct value, only allowing one per column. PostgreSQL, SQLite, Oracle and MySQL treat NULL
specially and allow any number of NULL
s in a unique column.
ALTER TABLE table_name DROP UNIQUE (column_name [, ... ] )
Drop the unique constraint that exists for the column(s) specified. Any foreign keys that reference this unique constraint will also be dropped. An fSQLException will be thrown if a UNIQUE
constraint does not exist for the column(s) specified.
COMMENT ON COLUMN table_name.column_name IS 'Comment value'
For SQLite, this statement adds an inline SQL comment at the end of the line that defines the column. All other databases have a native system for storing comments.
DROP TABLE table_name
The following syntax is supported for CREATE INDEX
statements.
CREATE [ UNIQUE ] INDEX index_name ON table_name ( column_name [, ... ] )
DROP INDEX index_name
All of the databases supported by Flourish support foreign key constraints through some method. By using translatedQuery()
to execute CREATE TABLE
statements, you can be sure that foreign keys will be enforced.
PostgreSQL, MSSQL, Oracle and DB2 support foreign keys completely natively. MySQL supports them natively as long as the InnoDB engine type is specified in the CREATE TABLE
statement. This is automatically added when using translatedQuery()
.
SQLite supports the syntax, however enforcement before version 3.6.19 has to be done through triggers. translatedQuery()
will create the necessary triggers to enforce foreign key constraints on an SQLite database.
Please note that Oracle does not support ON UPDATE
clauses, and DB2 only supports the NO ACTION
and RESTRICT
actions for ON UPDATE
clauses. Because of these limitations, it is best to avoid ON UPDATE
clauses for cross-database applications.
In addition, Flourish is designed to support only single-column foreign keys. While most databases support multi-column foreign keys, Flourish's SQLite foreign key trigger generation only supports a single column. In addition, the ALTER TABLE
through Flourish SQL is only designed and tested against single- column foreign keys.
Since Flourish isnt a framework, getting started might seem a little daunting. Lets start with getting the Flourish code and setting up our pages to include it.
As a first step, download flourish and extract it into a directory on your server. To help ensure that a server misconfiguration wont expose your PHP (possibly including database credentials), it is best to try and place your includes outside of the document root.
{doc_root}/../inc/flourish/
At the beginning of every page we arent going to want to include every class file or configuration file, so a logical first step is to create an initialization script to handle that. I prefer to create the script at:
{doc_root}/../inc/init.php
Inside of init.php, put:
<?php
include($_SERVER['DOCUMENT_ROOT'] . '/../inc/config.php');
For better separation, I prefer to put all configuration code into a separate file from the initialization code. I put all of my configuration code in:
{doc_root}/../inc/config.php
Inside of config.php
we will put our auto-loading function:
<?php
/**
* Automatically includes classes
*
* @throws Exception
*
* @param string $class_name Name of the class to load
* @return void
*/
function __autoload($class_name)
{
// Customize this to your root Flourish directory
$flourish_root = $_SERVER['DOCUMENT_ROOT'] . '/../inc/flourish/';
$file = $flourish_root . $class_name . '.php';
if (file_exists($file)) {
include $file;
return;
}
throw new Exception('The class ' . $class_name . ' could not be loaded');
}
Performance tip: If you are running Flourish on a server that has APC, eAccelerator, Turck MMCache or XCache check out the section about class loading.
Now that we have our init script setup, we can start creating pages and using Flourish. Just add this snippet to the top of each of your pages:
<?php
include_once($_SERVER['DOCUMENT_ROOT'] . '/../inc/init.php');
Alternatively if you run Apache and have access to the Apache conf file, you can use PHPs auto_prepend_file setting with the Apache PHP settings directives.
If you havent read it already, the How Do I ? page is a good resource for exploring some of the functionality of Flourish. The documentation includes links to detailed information about each class, class APIs and general documentation.
The Flourish Demo Site is a good way to dive into some code to see how Flourish can be used.
For a simpler more guided introduction to Flourish, try checking out the following classes:
The documentation below includes some topics that most PHP developers should know or learn about:
This page is meant to be sort of a compass to new users of Flourish to help them figure out where to look for solutions to their problems.
Task at Hand | Where to Look |
Handle exceptions and errors gracefully | fCore |
Ensure content is properly encoded (HTML entities) | fHTML |
Handle UTF-8 content | fUTF8 |
Perform simple form validation (contact forms, etc) | fValidation |
Simple (non-ORM) interaction with a database | fDatabase, fResult, fUnbufferedResult |
Retrieve the structure of the database I am connected to | fSchema |
Interact with the database simply by using object-relational mapping (ORM) | fActiveRecord, fRecordSet |
Get and sanitize information from the users request, including GET , POST , PUT and DELETE data |
fRequest |
Pass transient messages around without polluting URLs | fMessaging |
Store and retrieve session information | fSession |
Handle user permissions and access control | fAuthorization |
Ensure that data is stored securely (passwords, confidential information, etc) | fCryptography |
Handle dates and times, including timezones | fDate, fTime, fTimestamp |
Manipulate images | fImage |
Manipulate and query files and directories | fFile, fDirectory |
Handle file uploads | fUpload |
Use templating to reduce code duplication and keep logic and presentation separate | fTemplating |
Conditionally build English language constructs and convert between syntax notations | fGrammar |
Handle translation or modification of messages | fText |
Send emails and validate email addresses | fEmail |
Encode and decode JSON data | fJSON |
Deal with precise numbers and calculations | fNumber |
Represent, manipulate and translate monetary values | fMoney |
Handle sticky search values and sortable columns for list pages | fCRUD |
Get and set cookies, including HTTPOnly cookies and default values | fCookie |
Encode content for XML files | fXML |
The following pages include some more in-depth information about topics, including links to the relevant Flourish classes.
See also non-internal backwards compatibility breaks
Public methods flagged with the PHPDoc tag @internal
and all protected methods and attributes are considered internal to Flourish and are subject to change even in minor revisions or during the beta. Below is a list of changes that affect internal code.
Normally this list will only be applicable if you write ORM plugins or mess around with the ORM internals.
Changed fORM::parseMethod() to not underscorize the subject of the method.
Changed the structure of the array returned from fSQLTranslation::translate() to include a number plus :
before the original SQL in the array keys.
Removed:
fORMDatabase::addTableToKeys()
fORMDatabase::addTableToValues()
fORMDatabase::escapeBySchema()
fORMDatabase::escapeByType()
Rewrote:
fORMDatabase::createHavingClause()
to fORMDatabase::addHavingClause()fORMDatabase::createOrderByClause()
to fORMDatabase::addOrderByClause()fORMDatabase::insertFromAndGroupByClauses()
to fORMDatabase::injectFromAndGroupByClauses()
Added the $schema
parameter to the beginning of:
Added the $class
parameter to the beginning of fORMRelated::storeManyToMany()
fORMRelated::createRecord() now has an extra parameter in the middle, $related_records
. fORMRelated::storeOneToMany()
was renamed to fORMRelated::storeOneToStar().
Changed fActiveRecord::hash() from a protected method to a static public/internal method that requires the class name for non-fActiveRecord values
The method signatures for fORMRelated::validate() and fORMValidation::validateRelated() changed
The following methods no longer accept an object instance, just a class name:
Renamed fORMRelated::setRecords()
to fORMRelated::setRecordSet() and fORMRelated::tallyRecords()
to fORMRelated::setCount()
When creating code for audiences in more than one language, it is necessary to write it with internationalization (i18n) and localization (L10n) in mind.
A locale is a combination of a language and standards for formatting of numbers, date, currencies and more. Normally an locale is identified by a combination of a country and language code. Different languages require different sort ordering (or collation) for words and numbers often have different thousands and decimal separators. Other differences include date formatting and the words used for days and months.
Internationalization is the act of writing code than can be customized to work in multiple locales. Localization is the act of customizing code to work in a specific locale. Flourish is written with internationalization in mind, however there is no localization done, that is left for the teams working on individual projects.
The following classes in Flourish are in some way internationalized, thus capable of being localized.
Please note that every class in Flourish that creates messages hooks into fText (once loaded) and thus all messages can be translated through the features available in fText. The message list page includes a list of every message created by a Flourish class, along with the location in the source code.
The source code and documentation for Flourish are licensed under two separate licenses due to their nature of being two vastly different things.
The Flourish logo and http://flourishlib.com site design are (c) 2007-2012 Will Bond and are not licensed under either of these licenses.
Info about the MIT License:
Copyright (c) 2007-2010 Will Bond <will@flourishlib.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
The license for Flourish documentation is based on the FreeBSD Documentation License with modifications to address the format in which the Flourish documentation is presented.
Info about the FreeBSD Documentation License:
Copyright (c) 2007-2010 Will Bond <will@flourishlib.com>. All rights reserved.
Redistribution and use in any form (html, pdf, etc) with or without modification, are permitted provided that the following condition is met:
Redistributions must reproduce the above copyright notice, this condition and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS DOCUMENTATION IS PROVIDED BY WILLIAM BOND "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL WILLIAM BOND BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
This page contains resources for use when translating the messages used internally by Flourish.
A ready-made PHP array is available that includes every string as an array key and blank string values to use for the translations. This array can then be used with fText to perform translation of internal Flourish messaging. See the fText documentation for an example.
If you fill one of these out for a language, please send a copy to will@wbond.net so I may post it on the site for others to use also.
The following translations have been generously created by the community. It is possible that the messages may be slightly out-of-date due to changes in Flourish since the revision listed.
The following table contains a list of every message used in the current revision of Flourish, and their locations in the source code.
Flourish uses some standards in method names to help developers easily identify functionality without having to review the documentation. Below is a list of verbs and their connotations in Flourish code:
The Flourish ORM also has a few conventions for method names:
When dealing with database-driven web applications (and sites) often times interacting with the DB is one of the most repetitive and messy tasks. Having to manually build SELECT
, INSERT
, UPDATE
, and DELETE
queries for a multitude of tables is time consuming and mostly deterministic.
Object-Relational Mapping (ORM) helps to reduce the tedium, creates cleaner code, make relationships easy to access. Rows from a relational database management system (RDBMS) such as MySQL, PostgreSQL, SQL Server, Oracle, DB2, etc. are translated into objects in the programming language.
The ORM features of Flourish are primarily accomplished by the following two classes:
There are a number of other classes in Flourish that provide functionality to fActiveRecord and fRecordSet:
Many of the big PHP frameworks (CakePHP, Code Igniter, Symfony, Zend Framework) perform ORM to some degree. There are also a few PHP ORM suites (Propel and Doctrine) focused on strictly ORM tasks.
You may be wondering, why build another PHP "framework" with ORM functionality? Flourish is intended to be more of a library of PHP code as opposed to a framework (defined as strict set of conventions, rules and methods that must be followed to build a site). In addition Flourish sits somewhere between the frameworks and ORM suites by providing some of the more advanced ORM features that most of the frameworks are missing, while being lighter weight and easier to use than the ORM suites.
You may also be interested in checking out some of the motivations for creating Flourish instead of using an existing solution.
When dealing with public- key encryption using the fCryptography class or sending secure emails via the fEmail class, you will need an x509 public-key certificate and a PEM-encoded private key.
Certificate authorities offer certificates that will validate your identity by the public, however, self-generated keys can be used for any purposes where identity verification is not important, just encryption.
InstantSSL and StartSSL offer certificates for free, while VeriSign and many others offer them for a fee. Most of these services tend to install the certificate in your browser, and thus only work with some browsers. As of January 2010, Google Chrome seems to be the only major browser not providing this functionality. Also, since the private key/certificate is installed in your browser, you'll need to figure out how to use your browser to export the .p12
file.
Self-signed certificates are not recommended for a secure connection (https://) on a public web server since browsers will not recognize you as an authorized certificate authority (CA) and warning messages will be displayed.
There are various methods to generate a self-signed SSL certificate, however one of the simplest to explain in using the OpenSSL executable. Most Linux/BSD distributions and OSX all have it installed by default while Windows users can download an installer or use it through Cygwin. If you run the Windows installer, be sure to open a command prompt and cd
to the {install_dir}\bin
before executing these commands.
First, lets generate the private key file (it will be output in PEM format) by executing the following command:
openssl genrsa -des3 -out private.key 1024
The output will look something like the text below and will prompt for a passphrase (and a repeat). This is essential for restricting access to the key if it is stored on the same server as the encrypted data.
Generating RSA private key, 1024 bit long modulus
.....................................++++++
........................++++++
e is 65537 (0x10001)
Enter pass phrase for private.key:
Verifying - Enter pass phrase for private.key:
Next we need to create a certificate signing request (CSR) which is used to generate the certificate:
openssl req -new -x509 -key private.key -out public.crt
You will be asked a series of questions used for identifying the owner of the certificate (in this case, you). Below is an example with my answers in bold:
Enter pass phrase for private.key: You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [AU]:US State or Province Name (full name) [Some-State]:Massachusetts Locality Name (eg, city) []:Newburyport Organization Name (eg, company) [Internet Widgits Pty Ltd]:Flourish Organizational Unit Name (eg, section) []:Web Development Common Name (eg, YOUR name) []:William Bond Email Address []:will@flourishlib.com
When performing public-key encryption with the fCryptography you will need the public.crt
and private.key
files.
If you are going to use the certificate you generated with the fEmail class you will need public.crt
for the web server and a PKCS#12 formatted file for your email program. You can generate the PKCS!#12 file by executing the following:
openssl pkcs12 -export -inkey private.key -in public.crt -out private_public.p12 -name "Flourish Certificate"
Notice that the PKCS!#12 file contains both the public and the private keys. You will asked for a password for the PKCS!#12 file, which you will also have to provide to the email program you use it with:
Enter pass phrase for private.key:
Enter Export Password:
Verifying - Enter Export Password:
Depending on where you obtain a secure certificate from, you may only receive a .p12 file since it contains both the private key and public certificate. If you are going to use this certificate with fCryptography or fEmail you are going to need the private key and public certificate separated into individual files in specific formats.
The following command will export a public.crt
file from an PKCS!#12 named private_public.p12
:
openssl pkcs12 -in private_public.p12 -clcerts -nokeys | openssl x509 -out public.crt
Youll be asked for the .p12 files password:
Enter Import Password:
MAC verified OK
Next, well export a private.key
file from the same .p12 file:
openssl pkcs12 -in private_public.p12 -nocerts -nodes | openssl rsa -des3 -out private.key
First you will be prompted for the .p12 password, after which you will be prompted for the private key password (with repeat).
Enter Import Password:
MAC verified OK
writing RSA key
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
You will now have public.crt
and private.key
files for use with fCryptography and fEmail.
The Flourish ORM uses a few different conventions to prevent needless configuration and to reduce typing. Most of the conventions have to do with the database schema and various notations.
The whole ORM is built in such a way that all tables you are using with it should have primary keys. Without primary keys, things may start acting weird or breaking. If you don't have primary keys for your tables, consider adding them, they're generally considered a best practice.
The Flourish ORM assumes that all database table and column names are written in underscore_notation
. Below is an example of a correctly implemented database table:
CREATE TABLE users (
first_name VARCHAR(255) NOT NULL,
last_name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL
);
This database table would not be properly detected:
CREATE TABLE Users (
FirstName VARCHAR(255) NOT NULL,
LastName VARCHAR(255) NOT NULL,
Email VARCHAR(255) NOT NULL UNIQUE,
Password VARCHAR(100) NOT NULL
);
With correct underscore_notation
, numbers should be separated from words by an underscore, such as address_2
. If a number is not separated by an underscore, or you are having other notation conversion issues, you man need to customize the notation
conversion using fGrammar.
Methods for fActiveRecord objects use lowerCamelCase
, like the rest of the methods in Flourish. When dealing with database columns, every method will be in the form verbColumnName()
. Below are some example of working with the users
table defined in Schema Notation:
$user->getFirstName();
$user->setLastName($last_name);
HTML forms use underscore_notation
just as the database schema should. The method fActiveRecord::populate() will looks for input names in underscore_notation
when populating an object. Below is an example of a valid HTML form that will work with the users
table defined in Schema Notation:
<form action="" method="post" charset="utf-8">
<p>
<label for="users-first_name">First Name:</label>
<input id="users-first_name" type="text" name="first_name" />
</p>
<p>
<label for="users-last_name">Last Name:</label>
<input id="users-last_name" type="text" name="last_name" />
</p>
<p>
<label for="users-email">Email:</label>
<input id="users-email" type="text" name="email" />
</p>
<p>
<label for="users-password">Password:</label>
<input id="users-password" type="password" name="password" />
</p>
</form>
Database table names should always be plural nouns. A proper database table name would be users
, not user
.
For existing databases, it is possible to configure a class to model a non- plural table name, or a name that is different than the class. The static method fORM::mapClassToTable() accepts a $class
and $table
and will override the default mapping. The fActiveRecord page includes an example of custom mapping.
When two tables are related in a many-to-many relationship the proper way to model the relationship is to use a table consisting of the primary keys from each of the two tables. Flourish uses the term joining table to refer to these.
Currently Flourish only works with single column FOREIGN KEY
constraints, thus these simple tables consist of exactly two columns, each of which have a FOREIGN KEY
constraint. The PRIMARY KEY
of the joining table a multi-column key containing both FOREIGN KEY
columns.
When two database tables are in more than one relationship via FOREIGN KEY
constraints, the Flourish ORM uses the term route to refer to the different ways in which the two tables are related. If two tables only have a single relationship, routes will never need to be specified. Otherwise routes will, and the following rules are used to determine the route name.
FOREIGN KEY
constraintFOREIGN KEY
constraintPostgreSQL, MSSQL, Oracle and DB2 all have the concept of schemas, although in Oracle and DB2 a schema is simply a specific user's set of database objects. Schemas are used for grouping tables, views, functions and other database objects.
With the Flourish ORM (and in raw SQL) a table in the non-default schema (public
for PostgreSQL, dbo
for MSSQL and the username for Oracle) is represented by schema.table
. Anywhere that a table name can be used in the ORM, a schema.table
string can also be used. This includes methods such as fORM::mapClassToTable(), for mapping a class to a table with a different name or in a different schema, and fRecordSet::build(), when specifying a related table in the $where_conditions
.
The Flourish ORM supports using multiple databases, both for vertical partition and master-slave setups. Please see the fORMDatabase documentation for more information.
The Flourish ORM is built on top of the principles of relational database systems including transactions and foreign key constraints. MySQL is built in such a way that multiple storage engines are supported to do the work of actually storing data. Choosing the right storage engine for MySQL is essential for getting the Flourish ORM to work to the best of its abilities.
Unfortunately not all of the MySQL storage engines support the necessary features such as transactions and foreign key constraints. In fact the default storage engine, MyISAM, does not support either of these features as of MySQL
however support the necessary features. Because of these feature limitations, developers should be sure to specify the InnoDB storage engine when creating tables to be used with the Flourish ORM.
Below is an example of creating an InnoDB database table in MySQL, note specifically the ENGINE
parameter after the closing )
of the table definition.
CREATE TABLE users (
customer_id INTEGER AUTO_INCREMENT PRIMARY KEY,
first_name VARCHAR(255) NOT NULL,
last_name VARCHAR(255) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL
) ENGINE=InnoDB;
If the InnoDB engine is not used for tables, foreign key constraints will not be created and Flourish will be unable to automatically detect the relationships between tables. In addition, any operations on multiple records will not be atomic since transactions are not supported. Thus the first record could be successfully changed, but if the second one fails, the first will not be rolled back.
SQLite databases supports the syntax for foreign key constraints, however does not enforce them as of version 3.6.4. In order to enforce the foreign key constraints, triggers must be used instead.
A slightly old, but still relevant, wiki page on the SQLite site explains how triggers can be used to enforce foreign keys. There are a couple of tools mentioned that can automatically generate the appropriate triggers.
In addition, the fDatabase::translatedQuery() method will automatically create appropriate triggers for CREATE TABLE
statements executed through an instance of the fDatabase class. If a CREATE TABLE
statement is executed that includes a FOREIGN KEY
constraint, the clauses will be automatically parsed for the relevant restrictions and the triggers will be created. Both ON UPDATE
and ON DELETE
clauses plus the actions RESTRICT
, NO ACTION
, CASCADE
and SET NULL
are supported:
// The following will create a users table that actually
// enforces the group_id foreign key constraint
$db->translatedQuery("
CREATE TABLE users (
customer_id INTEGER AUTO_INCREMENT PRIMARY KEY,
first_name VARCHAR(255) NOT NULL,
last_name VARCHAR(255) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL,
group_id INTEGER REFERENCES groups(group_id) ON DELETE RESTRICT
)
");
This page is a small collection of some tips to help increase performance of you PHP code. Most of them require that you have enough control of the server to be able to change settings in Apache and load different PHP extensions.
Under normal operation, PHP will load each source code file upon each request. The source code files are first compiled into PHP opcodes and then the opcodes are executed. A number of PHP opcode caching extensions exist that keep the compiled opcodes stored in memory and only regenerate the opcode if the source code changes.
Mike Willbanks has a comparison of APC, eAccelerator and XCache from late 2007.
fLoader will utilize the most performant loading technique based on the current execution environment. If a opcode cache is present, all classes will be included. If not, autoloading will be done via spl_autoload_register()
.
A few of the Flourish classes provide pure PHP implementations of functionality that is also available from extensions that are not installed by default. Below is a list of the classes and the extension that will provide better performance.
Class | Extension |
fJSON | json (included in 5.2 by default) |
fNumber/fMoney | bcmath |
fUTF8 | mbstring |
Flourish is designer to be fully functional on PHP 5.1+. There are, however a number of end-programmer differences between PHP 5.1 and 5.2. This page contains an overview of the major differences.
In both 5.1 and 5.2, callbacks to instance methods of object are formulated as an array with the first element being the object and the second being a string of the method name.
$date = new fDate();
$callback = array($date, '__toString');
Similarly, with both 5.1 and 5.2 static method callbacks can be created with an array containing a string of the class and a string of the static method to call.
$callback = array('fCore', 'backtrace');
In 5.2, however, a second static method callback format was added. The new format consists of a single string with the class name concatenated with ::
and then the method name.
$callback = 'fCore::backtrace';
If you are using callbacks with Flourish, be sure to check out how Flourish provides an intuitive callback syntax.
Many different kinds of objects, especially value objects, will intrinsically have a string representation of their value. In both 5.1 and 5.2 a __toString()
method can be defined for any object. The difference between 5.1 and 5.2 is the automatic conversion of an object to a string.
In 5.1, the __toString()
method is only automatically called when an object is echo
ed or print
ed. However, if the object is concatenated with another string during the echo
or print
statement, the __toString()
method is not called. In PHP 5.2 this behavior was fixed, and the __toString()
method is called whenever a string is expected.
In the context of Flourish, all classes that represent some sort of simple value have a predefined __toString()
methods. Also, any methods that accept strings or objects will automatically check for a __toString()
method on a object and call that. The only thing to keep in mind is that when using objects where strings are expected you will need to explicitly call __toString()
if the code will be run on an environment with 5.1.
A reference object refers to a class that is implemented in such a way that all instances with the same value share properties. Thus changes to one instance of an object representing a user should also show up in another object representing the same user. Reference objects are mutable, in that their state can be changed without creating a new object.
Flourish includes a few different classes that are reference objects:
Here is another page with some info about reference objects:
The opposite of a reference object is a value object.
Security is an important topic when creating web sites. Quite a few techniques exist to compromise web servers, web browsers, or peoples information. This page serves as an overview of the various attacks that Flourish helps to prevent. While technology can help to keep sites secure, the most important part of the equation is that developers know what they are up against.
CSRF attacks consist of malicious websites taking advantage of users being logged in to multiple web sites in a single browser, thereby allowing them to direct users to perform password protected tasks in an unauthorized manner. The fRequest class provides functionality to prevent CSRF attacks.
More info:
Cross-site scripting (or XSS) is an attack that injects malicious code into a normally "safe" page. This usually takes the form of a link containing malicious content which is embedded in a page or by allowing posting of permanent data (such as blog comments) that gets embedded in the page. To prevent this type of attack, all input should be filtered and all output should be escaped. The fRequest::get() method allows for filtering input data with typecasting, while fHTML::escape() ensures that all output is properly escaped for output in HTML.
More info:
Session fixation is when an attacker tricks a user into using a known session id to log into a website and then uses the same session id to access their account. The fSession class helps prevent this by setting the appropriate ini settings so that session ids can only be specified in cookies and not in the query string. The fAuthorization class takes it a step further and regenerates the session identifier whenever any new user information (such as ACLs or authorization level) is set.
More info:
When multiple sites on a single server share the same directory to store their session files, it is possible that a user could log into a legitimate account on one site and then copy and paste the session id into the other sites session id cookie. This type of attack would allow an attacker to gain access to information of the second site as long as the $_SESSION
data is structured the same as the first site.
The easiest way to prevent such an attack is to have each site use a custom directory to store session files by calling the static method fSession::setPath().
More info:
The pseudo-random number generator (PRNG) built into PHP includes a fairly secure seeding system to make sure that the number generated are hard to guess or replicate. Unfortunately the functions srand()
and mt_srand()
allow seeding the rand()
and mt_rand()
functions, and the seeds used are usually very insecure and can be calculated by attackers. It is also possible via a number of different techniques for an attacker to manage to reset the seed to a known value.
The fCryptography class provides functionality where the OS random number generator (/dev/urandom
on BSD/Linux and the CAPICOM object on Windows) is used to seed the PRNG. If neither of those sources is available, a combination of a number of other attributes about the current server and install are used to create a seed value. Because of this functionality, the static method fCrytography::random() should always be used for random numbers and the static method fCryptography::randomString() should always be used for random strings.
More info:
SQL injection is a technique where an attacker passes in a value into a site that includes SQL commands where the application is normally expecting a value to use in a query. Suppose there is a site that accepts a blog id in the query string of the blog page:
http://example.com/blogs.php?blog_id=25
This value would then be used in the SQL query to look up the appropriate blog:
$sql = "SELECT * FROM blogs WHERE blog_id = '" . $_GET['blog_id'] . "'";
which would create the following SQL statement:
SELECT * FROM blogs WHERE blog_id = '2'
If an attacker were to use a different value for the blog id, such as below, they could execute arbitrary SQL statements.
http://example.com/blogs.php?blog_id=2'%3B%20DELETE%20FROM%20blogs%3B
which would end up creating the following SQL statement:
SELECT * FROM blogs WHERE blog_id = '2'; DELETE FROM blogs;
The way to prevent such attacks is to always escape data that is being placed into a SQL query. The fDatabase class is built with this in mind, providing the static method fDatabase::escape() which allows escaping of data based on its type. In addition, each of the four different query methods allow using the same escape syntax.
More info:
Email injection is an attack similar to SQL injection, except that the malicious content must be placed in the $additional_headers
parameter of the mail()
function. Since the From:
header is added to an email using this additional headers parameter, it is possible to an attacker to set additional headers or even inject full messages if user submitted data is included in the From:
header.
Below is an example of code that would be vulnerable to an email injection attack:
$headers = "From: " . $_POST['name'] . " <" . $_POST['email'] . ">";
mail($to, $subject, $body, $headers);
If an attacker were to include line breaks in the name or email fields, they could add additional headers including To:
, Cc:
, Bcc:
, Subject:
, and even more like setting up a mime type. PHP automatically prevents line break characters in the $subject
parameter of mail()
, while the $to
parameter requires a valid email address and the $message
parameter would just display the headers as normal text content.
Both the fValidation and fEmail classes contain features to help prevent email injection attacks. The fValidation class includes methods to check fields for being valid email addresses and to make sure they dont include line breaks. The fEmail class provides full prevention against attacks by validating each email address and name (to, from, reply to, etc) plus the subject to ensure they dont contain vulnerabilities.
More info:
Error messages and unhandled exception messages often include full file or directory paths to documents on your server. During development it is useful to display this information so that debugging is easier. Once a site has moved to its production environment, however, these file and directory paths can be a security risk. While not a risk in an of themselves, the information is useful when combined with other attacks.
Say that a site is on a shared server with a custom session path to prevent cross-site session transfer attacks. Obviously the directory has to be readable and writable by the web server, otherwise the session files would not be saved. If the attacker was able to find the session path via an error or exception message, they could then set their application to use the same session path and execute an attack.
To prevent this information from leaking, error and exception messages should be routed to a log file or an email address once a site is in production. The static methods fCore::enableErrorHandling() and fCore::enableExceptionHandling() provide such functionality, plus more.
More info:
Path traversal is an attack in which a script that uses user input to access a file is fed a path outside of the intended directory. Suppose the following URL allows downloading files from a directory:
http://example.com/download.php?file=image.gif
And that download.php
contained code such as:
$path = '/path/to/download/dir/' . $_GET['file'];
$handle = fopen($path, 'rb');
fpassthru($handle);
fclose($handle);
exit;
If a user specified the following URL, they could access the file /etc/passwd
since they are using the ../
notation for the parent directory:
http://example.com/download.php?file=..%2F..%2F..%2F..%2Fetc%2Fpasswd
The best way to prevent such an attack is to not accept filenames from users, but instead accept an identifer which can be mapped to a filename.
$valid_files = array(
'1' => 'image.gif',
'2' => 'image.jpg',
'3' => 'image.png'
);
$file = $_GET['file'];
if (isset($valid_files[$file])) {
// Handle the error
}
$path = '/path/to/download/dir/' . $file;
Similarly, another option is to create some sort of whitelist of valid files and make sure the file requested is in the whitelist.
If it is not possible to create a whitelist, the function realpath()
will return the canonical form of a path, resolving any ../
paths and following any symlinks. For the example above, realpath()
would have returned /etc/passwd
. Thus, checking the output of realpath()
against the valid file directory can prevent unauthorized filesystem access:
$dir = '/path/to/download/dir/';
$path = realpath($dir . $_GET['file']);
if (strpos($path, $dir) !== 0 || $path == $dir) {
// Handle the error
}
The fFile class can be useful in validating files since it will throw an fValidation exception if the file is not found and will return the output of realpath()
from the method fFile::getPath().
More info:
Request value fixation is an attack that is only effective against script that use the $_REQUEST
superglobal. The $_REQUEST
superglobal pulls in values from $_GET
, $_POST
, and $_COOKIE
, in that order. This means that any value specified in a cookie with the same key as a value passed in by a query string will always resolve to the cookie value.
This type of attack would require that a users cookie be compromised, but could cause serious issues. Because of this vulnerability, the $_REQUEST
superglobal should not be used. The fRequest class provides functionality to retrieve values from both the $_GET
and $_POST
superglobals (in that order) and provides additional useful features.
More info:
Some multibyte character encodings can cause issues when escaping data, especially when creating SQL statements. The issues is that some character encodings use the \
character as a second, third or fourth byte in a multibyte sequence, however that can be combined with other characters to cause escaping to be done incorrectly.
UTF-8, the character encoding that Flourish uses for everything, does not suffer from this type of vulnerability. As a precaution, however, the static method fRequest::get() filters all incoming content through fUTF8::clean() to ensure that no malformed UTF-8 characters are present. In a similar sense, all data imported into the system should be converted to UTF-8, and cleaned to make sure no invalid characters exist.
More info:
File uploads can clearly be a security vulnerability if no filtering is done of user input. Malicious users could upload executables containing viruses, trojans, or any other sort of harmful content. Because of this, it is best practice to limit the accepted file types to those that are suitable for the task at hand.
The $_FILES
superglobal that PHP provides to access file uploads includes a type
key which contains the mime type of the file. This mime type, however, is specified by the user and should not be trusted for filtering nefarious files. The fUpload class, however, includes functionality that filters uploaded files by their mime type, with the mime type checking being done on the server side.
More info:
Users' passwords should never be stored in plain text. As much as there is a desire to provide functionality such as "email me my password," the benefit is not worth the risk of an attacker obtaining a list of users and their passwords. Instead, all user passwords should be hashed and then functionality such as "reset my password" should be implemented.
It is also important to understand that simply hashing a password using md5()
or sha1()
is not enough to provide reasonable security. Before hashing the password, it should be combined with a salt to provide extra security and protect against certain forms of attack. The fCryptography class provides secure password hashing functionality via the static methods fCryptography::hashPassword() and fCryptography::checkPasswordHash().
More info:
Magic quotes and register globals are two features built into PHP that can cause security issues and affect how user input is treated in PHP.
Magic quotes (specifically the ini setting magic_quotes_gpc
) tries to prevent SQL injection attacks by automatically escaping all '
, "
, \
and NULL
characters with a \
. This affects all data in the $_GET
, $_POST
and $_COOKIE
superglobals and is enabled by default in new PHP installations. The fRequest::get() and fCookie::get() static methods will automatically detect if magic_quotes_gpc
is turned on and will remove the extra escape \
characters.
Register globals is a feature where all data from the $_GET
, $_POST
and $_COOKIE
superglobals are exported into the global scope as variables. This can very quickly lead to injection vulnerabilities and this feature should never be enabled. Register globals has been disabled by default since PHP version
More info:
The following web sites and blogs have a wealth of information about web/PHP security and best practices.
After some experience with PHP, developers will often start to notice issues related to character encoding including "weird" characters and multiple characters where there should only be one. Handling character encoding on the web usually means support the UTF-8 character encoding to allow for more than the standard ASCII characters present on US keyboard layouts. This page will try to give a brief overview of the issues and solutions to using UTF-8 with PHP.
UTF-8 is a character encoding, or a way to represent characters in a digital manner. It is an encoding of the of the Unicode standard which is closely related to the Universal Character Set (UCS). There are many different character encodings in the Unicode standard, however UTF-8 has a few properties that make it one of the best and most popular choices for work on the web.
Unicode contains around 100,000 characters, allowing it to represent a majority of the written languages in the world. Other common characters sets in languages with Latin characters include ISO-8859-1 and Windows-1252. Each of these character encoding suffers from issues that they only support 256 different characters, with some common characters not being present (such as curly quotes and the Euro symbol in ISO-8859-1).
Since UTF-8 is an encoding that represents the Unicode standard, character availability is not an issue. However, to be able to represent so many different characters, UTF-8 uses multiple bytes of space for all non-ASCII characters. One of the nice properties of UTF-8 is that it is backwards compatible with ASCII and the first 128 characters in ISO-8859-1 and Windows-1252. In addition, UTF-8 is constructed in such a way that it is possible to tell where character boundaries are even if a parser is started in the middle of a character. Related to that, it is simple for a UTF-8 string to be verified as correctly encoded.
Unfortunately, PHP does not include native support for UTF-8. All of the built- in string functions are designed to work with single-byte encodings only. The mbstring extension exists to provide string functions that are compatible with UTF-8, however it only covers a fraction of the standard string functions, and is not installed by default.
In addition to manipulating strings, the character encoding also affects the HTML that is returned to browsers and text in databases. The HTML standard defines ISO-8859-1 as the default character set to use for HTML when not specified, which will cause "weird" characters when UTF-8 content is returned. In a similar fashion, both MySQL and PostgreSQL default to using ISO-8859-1 as the character encoding unless specified.
MSSQL is much more complicated to work with since the whole server uses a single character encoding. In order to store unicode information, the column data types must be specified as one of the national character types, NVARCHAR
, NCHAR
or NTEXT
. These national columns store data in USC-2 encoding, which contains null bytes for the characters in the ASCII range. Unfortunately none of the PHP extensions support binary data to be returned in string columns, so the national columns require extra work to cast them as binary data and then translated the data once in PHP.
Even though PHP has poor UTF-8 support by default, there are ways to work around it. Since using UTF-8 is the most universal way to handle characters from other languages, Flourish includes code that automatically works the shortcomings of PHP and provides UTF-8 support in all situations. While Flourish can solve many of the PHP issues with UTF-8, it is also important to understand how UTF-8 affects the other aspects of building a web site.
If a database is being used to store text information, it should be created using UTF-8 as the character encoding. This allows the widest range of characters to be properly stored, sorted and manipulated by the database.
For PostgreSQL, the database encoding must be specified when the database is created. If it is not, the database will have to be dumped and re-imported into a new database.
CREATE DATABASE database_name ENCODING = 'UTF-8';
The fDatabase class will also automatically switch the connection encoding of any PostgreSQL database to UTF-8 even if it is not set up with UTF-8 encoding. This ensures that all data coming from PostgreSQL is always UTF-8, however there can be issues with trying to store UTF-8 characters in a database that does not support all of the same characters. Commonly this will be manifested by characters being stored as ?
s.
MySQL allows the character encoding for content to be defined when the database is created, or when a table is created.
-- Setting the default encoding for a database
CREATE DATABASE example CHARACTER SET 'utf8';
-- Setting the encoding on a table
CREATE TABLE examples (
name VARCHAR(255) PRIMARY KEY
) CHARACTER SET utf8;
However, fDatabase will also automatically switch the connection encoding for any MySQL database to UTF-8. This means that all data coming back to PHP will be encoded as UTF-8 regardless of the table encoding. There can, however, be issues if text containing UTF-8 data is stored in a table that can not handle as many characters as UTF-8. Commonly this will be manifested by characters being stored as ?
s.
MSSQL does not support UTF-8 natively like the other database engines, and requires that all databases on a server use the same character encoding. Luckily it also provides the national (or unicode) character data types NVARCHAR
, NCHAR
and NTEXT
that allow storing unicode text.
CREATE TABLE examples (
name NVARCHAR(255) PRIMARY KEY,
description NTEXT NOT NULL
);
If national columns are not used and the data you insert into the database can not be represented by the default database encoding, the characters will be stored as ?
s.
There can be difficulties in dealing with such data types in PHP, however the fDatabase::translatedQuery() method has been developed in such a way that all data from national columns will be returned as UTF-8.
When inserting strings containing UTF-8 into a MSSQL database, the method fDatabase::escape() should be used since it will detect extended characters and escape them properly.
If you are using a linux or BSD server, you are probably access SQL Server through FreeTDS. The following configuration options should be set in your // This will ensure all data coming out of MSSQL is properly converted to UTF-8
$result = $db->translatedQuery("SELECT * FROM examples WHERE name = %s", $name);
// This will ensure all data going into MSSQL is properly stored in NCHAR, NVARCHAR, NTEXT columns
$result = $db->translatedQuery(
"INSERT INTO examples (name, description) VALUES (%s, %s)",
$name
$description
);
freetds.conf
to ensure that everything works properly.
CP1252
) may need to be changed if the Windows machine the server is running on uses a primary language other than English.
Depending on what linux distribution or flavor of BSD you are using, the # This is the version of the protocol to use for SQL Server 2000
# and newer. Versions less than this may cause weird database bugs.
tds version = 8.0
# By default FreeTDS uses ISO-8859-1 which will turn some characters
# into ?s. CP1252 (or Windows-1252) is usually the character encoding
# used by SQL Server for English installs
client charset = CP1252
freetds.conf
may be in a few different directories, including:
It is also possible your operating system may not install a default /etc/
/etc/freetds/
/usr/local/etc/
/usr/local/etc/freetds/
freetds.conf
, but it may provide a freetds.conf.dist
or something similar.
SQLite
SQLite uses UTF-8 for all text storage by default, so no special configuration is needed.
Oracle
Like with the other databases, Oracle has both a database encoding and a client encoding, however, unlike the other databases, the client encoding can not be set at connection time.
In order to full support UTF-8, the Oracle database should be installed with the AL32UTF8
encoding for version 9i+ and UTF8
for version 8. If you are installing Oracle 10g XE, be sure to get the universal
version that support multi-byte encodings.
On the client side, the character encoding is controlled by the NLS_LANG
environmental variable. For US-English installs, this should probably be set to AMERICAN_AMERICA.AL32UTF8
. In addition to the NLS_LANG
environmental variable, the Oracle PHP drivers will also need the ORACLE_HOME
environmental variable set. /connecting.htm#sthref81 Examples for Linux.
DB2
DB2 can be instructed to store UTF-8 as the database default, or on individual tables. UTF-8 is used whenever unicode is specified. To specified unicode as the default for a database, add the USING CODESET
option to the CREATE DATABASE
statement:
The CREATE DATABASE example USING CODESET UTF-8 TERRITORY US;
TERRITORY
should be set to an appropriate locale for your data.
CCSID UNICODE
on individual tables with CREATE TABLE
:
In addition to setting up your server for UTF-8 database, it is required to set the client to use UTF-8 also. Unfortunately this can not be controlled via SQL over the database connection like other database, but must be set as an environmental variable. In a method appropriate for your OS, set the CREATE TABLE example (
-- ....
) CCSID UNICODE;
DB2CODEPAGE
environmental variable to 1208
, which represents UTF-8.
For Windows, this can be set in the Environmental Variables panel under
export DB2CODEPAGE=1208
HTML Encoding
When delivering HTML to browsers, it is important to include the proper character encoding information so that the browser can properly display the characters. The method fHTML::sendHeader() sets the proper Content-type
header for UTF-8 HTML. This method should be called for every page that returns HTML, and should be called before any output is created. Consequently, it will often be called in an init or bootstrapping script:
It is also good practice to include an HTML fHTML::sendHeader();
meta
tag specifying the character set for when HTML is displayed from the filesystem instead of a web server.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
Request Data
As long as the HTML encoding is set to UTF-8, all browsers should obey this setting and submit request data encoded as UTF-8. When retrieving information from GET
and POST
requests, the class fRequest ensures that all values coming in are valid UTF-8 strings, and cleans out those that are not.
// This value will be proper
$name = fRequest::get('name');
String Manipulation
The class fUTF8 is a static class that provides all of the same functionality as the normal PHP string functions, but in a way that is compatible with the multi- byte nature of UTF-8. The class documentation includes a list of all of the PHP string functions and their equivalent methods in fUTF8.
Importing Data
While Flourish is built to ensure that all data coming in through normal web interactions is clean and valid UTF-8, it is necessary to manually convert data coming from an old database or text files. The iconv PHP extension provides functionality to convert text stored in another encoding to UTF-8 using a simple function call.
When importing data from an old database, as long as the fDatabase class is used to connect to the old DB, all data should be automatically converted from the old encoding to UTF-8 on-the-fly. This is accomplished by the fact that fDatabase sets the connection encoding to UTF-8 whenever connecting to a database. This, does not, however mean that fDatabase will work seamlessly with a non UTF-8 database. Most of the default character sets for databases only support a subset of the UTF-8 characters, making conversion to UTF-8 fine, but conversion back nearly impossible.
The term value object refers to a small object that contains a simple value such as a dates, numbers, string or money. They are implemented to be immutable, meaning a new object is created whenever modifications are requested. This property helps to prevent the side affects that come from objects being passed by reference.
There are five classes in Flourish that are implemented as value objects:
Here are some other pages about value objects:
The opposite of a value object is a reference object.
Versioning happens on two different levels with Flourish. On the lowest level, each class that is part of Flourish is versioned using a three-part number. On the library level, Flourish is versioned using the current revision number of the SVN repository.
All classes are versioned using a three-part number in the following format:
{major_version}.{minor_version}.{edit_version}
Below are some examples of valid version numbers:
1.0.0
1.0.25
1.5.1
1.0.0b
The last version, 1.0.0b
signifies that the class is currently in beta. This type of version will only exist before version 1.0.0
, and will not be present once Flourish hits stable.
The version number have the following significance:
Flourish, as a library of related classes, is versioned simply by the current revision of the master SVN repository.