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