Introduction

The temporal model is a lot less scary than it sounds.

Usually in a database entities are represented by a row in a table, when this row is updated the old information is overwritten. The temporal model allows data to be referenced in time, it makes it possible to query the state of an entity at a given time.

For example, say you wanted to keep track of changes to products so when an order is placed you know the state of the product without having to duplicate data in the orders table. You can make the products temporal and use the time of the order to reference the state of the ordered products at that time rather than how they currently are, as would happen without using temporal data.

The temporal model could also be used for auditing changes to things like wiki pages. Any changes would be automatically logged without having to use a separate log table.

An "entity" would be any item in your design, for example, a product, a user, a gallery folder, anything that you would use as a Model can be considered an entity. The Temporal Model allows you to track changes to these entities over time by storing changes, each new set of changes is called a "revision". Because each revision is saved along with time (temporal) data it makes it possible to get the state of an entity at any given time.

This implementation of a temporal model stores revisions of entities in the same table as the original data, removing the need for duplicate "log" tables. While the implementation here is not the only way of implementing revisions, it is the most flexible given the limitations of the ORM and the fact that it must work in multiple database environments.

Why do this in the ORM and not just choose a database system that allows it?

Implementing the temporal functionality within the ORM allows the mechanics to be abstracted out of the database level, this allows the use of any database that the ORM supports with temporal models, providing greater flexibility. This model does not replace the ability to use a database that supports temporal information but provides an "out of the box" implementation.

Use

The table must be set up with a compound primary key of the entity ID, just a standard auto-incrementing number (for example), and the timestamps that represent the valid time span of the row.

class Model_MyTemporal extends Orm\Model_Temporal
{
	protected static $_primary_key = array('id', 'temporal_start', 'temporal_end');
	protected static $_temporal = array(
		'start_column' => 'temporal_start', //The name of the column that contains the time this row is valid from
		'end_column' => 'temporal_end', //The name of the column that contains the time this row is valid to
		'mysql_timestamp' => false, //If set to true will use MySQL timestamps rather than unix timestamps
	);
}

Please note that the primary key must be a compound key between the entity id and the temporal timestamps.

Any relations that are defined using temporal models should relate only on the id and not both id and timestamp.

find()

The find method works exactly the same as the regular model's implementation except that a where clause is added to select only the latest revision of an entity.

find_revision($id, $timestamp=null, $relations = array())

This method can be used to query the state of an entity at the given time.

Static Yes
Parameters
Param Default Description
$id required The ID of the entity to search for.
$timestamp
null
The time to reference the entity at. null will return the most current revision.
$relations
array
Names of the relations to load with the entity.
Returns A single subclass of Model_Temporal
Example
Model_Product::find_revision($id, '2012-11-09 12:04:00', array('images', 'reviews'));

Any model fetched through find_revision or find_revisions should be considered read only as it should not be possible to modify the past!.

find_revisions_between($id, $earliestTime = null, $latestTime = null)

This method will return the states of an entity between the given times.

Static Yes
Parameters
Param Default Description
$id required The ID of the entity to search for.
$earliestTime
null
The time of the earliest revision to find or null for an infinite number of previous revisions.
$latestTime
null
,
string
or
integer
The time of the latest revision to find or null for an infinite number of revisions (up to the latest).
Returns An array of subclasses of Model_Temporal
Example
Model_Product::find_revisions_between($id, '2012-11-09 12:04:00', '2012-12-10 19:00:00');

It is currently not possible to select relations at the same time using find_revisions_between.

delete($cascade = null, $use_transaction = false)

This method will delete the object from the database. It works in exactly the same way as the normal Model's delete function except that the information is not removed from the database. This means that you can still reference the object at a given time, but it is no longer valid for the present.

Static No
Example
Model_Product::find(5)->delete();

Cascade delete will delete as normal. Any relations that are not Soft or Temporal will be deleted as normal.

overwrite($cascade = null, $use_transaction = false)

Allows information to be saved without creating a new revision. Works the same as Model::save()

Static No
Example
$product = Model_Product::find(5);
$product->name = 'Super Awesome 12000';
//Product is updated without creating a new row in the database.
$product->overwrite();

restore()

If an entity has been deleted this method restores the entity to the current state. Nothing will happen if the entity has not been deleted.

Static No
Example
Model_Product::find_revision(5, '2012-11-09 12:04:00')->restore();

purge()

Removes all revisions of this entity from the database. This is permanent! It cannot be undone.

Static No
Example
Model_Product::find(5)->purge();

This cannot be undone, it will delete all revisions of the entity from the database. If you do this the data will be destroyed!