Defining your models
To start using FuzzyRecord, you need to create some model classes. A model is a special type of class that represents one of the data objects in your application. For example, a content-managed website might have a 'Page' model, and a 'User' model.
An example model:
class Page extends FuzzyRecord {
static protected $table = "page";
static protected $properties = array(
"id" => array("primary_key", "auto_increment"),
"url" => array("url", "required"),
"title" => array("min_length" => 2,"max_length" => 64,"required",
"searchable", "like_searchable"),
"body" => array("required", "searchable"),
"section_id" => array("integer"),
"author_id" => array("integer"),
"position" => array("sorter" => array("section")),
"is_live" => array("boolean"),
"last_modified" => array("date_with_time", "order_by_default")
);
static protected $relationships = array(
"belongs_to" => array(
"section" => array("class" => "Section"),
"author" => array("class" => "User")
),
"has_many" => array(
"tags" => array("class" => "Tag", "dependent" => "delete")
)
);
}
All models MUST define at least:
- The name of the database table that this model maps in the $table static variable
- A list of properties (the fields in the database table) in the $properties static variable
Optionally, models may define:
- A list of relationships with other models in the static $relationships variable
- Any other methods and properties you like, just like a normal class.
About Properties
The $properties variable is an associative array, where each key is the name of the property (and also the name of the field in the database table).
The value of a property will be another array, containing a range of keywords and associative arrays that define the behaviour of that property. Any keywords that are not recognised will be ignored. The array can contain a mix of any of the values in the two tables below.
Supported data-types
When specified for a particular property, these keywords define the type of data that will be stored.
Property type | Description | Stored as (MySQL) | Stored as (PostgreSQL) |
---|---|---|---|
integer | Used for storing non-floating point numbers | int | integer |
varchar | Used for storing short strings. This type will also be used if min_length or max_length are specified for this property. | varchar | character varying |
text | Used for storing long strings. This is the default data type, and will be used when the model does not specify one of these data types. | text | text |
boolean | Used for storing boolean values (true or false) | bool / tinyint(1) | boolean |
date | Used for storing dates | date | date |
time | Used for storing times | time | time |
time | Used for storing times | time | time |
date_with_time | Used for storing timestamps. Uses the Date class for its value. | timestamp | timestamp without time zone |
date_with_time_and_timezone | Used for storing timestamps with timezone data intact. Uses the Date class for its value. | varchar | timestamp with time zone |
sorter | A special data type for objects that should be arranged in a particular (generally user-defined) order. See the section on sorter properties for more info. | int | integer |
file | A special data type used for storing data in files on a local disk. The file's name is stored in the database. See the section on file properties for more info. | varchar | character varying |
email_address | The value of this field must be a valid email address, or validation will fail | varchar | character varying |
Property constraints
When specified for a particular property, these keywords control how that property should behave.
Keyword | Description |
---|---|
primary_key | Specifies that this property is the primary key for this model's database table. If this table should use a composite primary key, multiple properties should should contain the primary_key keyword. Every model MUST have at least one property defined as the primary_key. |
auto_increment | Specifies that the field for this database table is of the auto increment type. FuzzyRecord will let the database assign a value for this field when inserting a new record (with write_new()) May only be used on properties that are set as the sole primary key for this model. |
required | The value of this field must not be empty, or validation will fail |
min_length / max_length | Constrains the length of the value of this field in characters. You can use one or both of these for a particular field. Validation will fail if the value does not conform to these parameters. |
unique | The value of this field must be unique - validation will fail if another object of this type has the same value. |
like_searchable | This field should be used for like searches in the form: If you add this keyword to more than one property, FuzzyRecord will return objects that match the query on any of the like_searchable fields. |
searchable | Same as the above, for full-text searches (FT searching doesn't work yet) |
order_by_default | Searches will sort their results by this property when a 'order_by' clause is not specified. Only one property may have this keyword. |
About relationships
The $relationships variable is an associative array that is only used when you want to create a relationship between two objects.
Currently, two types of relationships are supported: has_many and belongs to.
has_many relationships
These are used for describing one to many relationships between database tables. For example, a User has many UserLogins, a BlogEntry has many Comments.
belongs_to relationships
These are the inverse of the has_many relationship. For example, a BlogEntry has one User as the author.
Defining relationships
Generally, has_many and belongs_to are used in conjunction with each other. Let's see a simple example. First, a User model:
class User extends FuzzyRecord {
static protected $table = "user";
static protected $properties = array(
"id" => array("primary_key","auto_increment"),
"email" => array("email_address","required","unique","searchable"),
"password" => array("password","required"),
"name" => array("min_length" => 2,"max_length" => 16,),
);
static protected $relationships = array (
"has_many" => array(
"logins" => array("class" => "UserLogin", "dependent" => "delete")
)
);
}
Now, a UserLogin model:
class UserLogin extends FuzzyRecord {
static protected $table = "user_login";
static protected $properties = array(
"id" => array("primary_key","auto_increment"),
"user_id" => array("integer","required"),
"ip_address" => array("max_length" => 32,"required"),
"date" => array("date_with_time")
);
static protected $relationships = array(
"belongs_to" => array(
"user" => array("class" => "User")
)
);
}
Firstly, look at the $relationships variable in the User model. It defines a single has_many relationship, called 'logins'. It specifies the class of the model that this object is related to (UserLogin). "dependent" => "delete" means that all the UserLogins related to a User should be deleted when the User is deleted.
Now let's look at the UserLogin model. It specifies a single belongs_to relationship, called user, pointing to the User class. Notice that it also contains a 'user_id' field. This is where a reference to the user that logged in is actually stored in the database. The naming of this variable is important. It's name is the name of the belongs_to relationship (user), then an underscore, then the name of the primary key in the User model. It is important that you follow this naming convention in your models so that FuzzyRecord can automatically populate the value of this field, as we'll see below.
Let's see this relationship in action.
$user = new User();
$user->email = "ben@allseeing-i.com";
$user->password = "secret";
$user->name = "Ben";
$user->save();
$login = new UserLogin();
$login->ip_address = "127.0.0.1";
$login->date = "2009-01-01";
$login->user = $user; // <-- This is the key bit!
$login->save();
In this example, we set the 'user' property of the UserLogin instance. But UserLogin doesn't have a 'user' property!
UserLogin does have a 'user' relationship. FuzzyRecord recognises this, and hunts for a foreign key it can use to store the primary key of the user. Since we've named it correctly, it will use the 'user_id' field.
Continuing on from the previous example, we can now do:
echo $login->user_id // Will return the id of the user object we just created
echo $login->user // Will return the user object we just created
echo $login->user->email // Will return ben@allseeing-i.com
Generating your database from your models
Every model can generate the SQL needed to create its table in the database. Simply call:
User::create_table_sql();
A script to generate tables for all your models is included in the source code distribution for your convenience.
Sorter properties
A sorter is a special data type that allows you to easily model objects that have a position in relation to other objects. For example, if we had a TopStory model that was used to describe stories that might appear on the front page of a news website, the TopStory model might define a position attribute that allowed the editors to move TopStories up and down the page depending on which was most newsworthy at a particular time.
A sorter property stores an integer that represents the position of an object in relation to other objects of the same type. When you save a new instance of an object with a sorter property, the sorter property will be set to 1, and all other instances of the class will have their sorter property incremented by 1. You can then use the move_up() and move_down methods to move the objects position:
<?php
class TopStory extends FuzzyRecord {
static protected $table = "top_story";
static protected $properties = array(
"id" => array("primary_key","auto_increment"),
"url" => array("varchar"),
"position" => array("sorter")
);
}
$story1 = new TopStory();
$story1->headline = "Four thousand holes in Blackburn, Lancashire";
$story1->url = "/url-of-this-story";
$story1->save();
$story2 = new TopStory();
$story2->headline = "Elvis seen wandering campsite at night";
$story2->url = "/url-of-this-story";
$story2->save();
$story3 = new TopStory();
$story3->headline = "Starr: Super-smart alien hampsters want revenge";
$story3->url = "/url-of-this-story";
$story3->save();
echo $story1->position; //3
echo $story2->position; //2
echo $story3->position; //1
$story2->move_up();
echo $story1->position; //3
echo $story2->position; //1
echo $story3->position; //2
$story3->move_down();
echo $story1->position; //2
echo $story2->position; //1
echo $story3->position; //3
$story1->delete();
echo $story2->position; //1
echo $story3->position; //2
Specify a constraint for the sorter
If you define a constraint for a sorter property, instances will sort themselves only in relation to other items that match the constraint. For example:
<?php
class TopStory extends FuzzyRecord {
static protected $table = "top_story";
static protected $properties = array(
"id" => array("primary_key","auto_increment"),
"url" => array("varchar"),
"category_id" => array("integer"),
"position" => array("sorter" => array("category"))
);
static protected $relationships = array(
"belongs_to" => array(
"category" => array("class" => "TopStoryCategory")
)
);
}
In the above example, TopStory instances will sort themselves in relation to other TopStories in the same category.
File properties
File properties are used when you want to store data in a file on a local disk, rather than in the database. This is useful for storing binary data (for example, a photo uploaded by a user), or when you have a large amount of data that might slow down your database.
In the example below, we've added a file property (called file) to the Document model. File properties must define a 'save_path', this tells FuzzyRecord where to store the saved file.
<?php
define("DOCUMENT_SAVE_PATH","/home/ben/Sites/fuzzyrecord/htdocs/documents");
class Document extends FuzzyRecord {
static protected $table = "document";
static protected $properties = array(
"id" => array("primary_key","auto_increment"),
"file" => array("file","save_path" => DOCUMENT_SAVE_PATH),
"last_modified" => array("date_with_time","order_by_default")
);
}
$document = new Document();
$document->file = new File("/tmp/myfile.txt");
// File will be saved to:
// /home/ben/Sites/fuzzyrecord/htdocs/documents/myfile.txt
$document->save();
// In this example, assume the user uploaded a file called 'anotherfile.jpg'
$document = new Document();
$document->file = File::file_with_uploaded_file($_FILES["user_uploaded_file"]);
// File will be saved to:
// /home/ben/Sites/fuzzyrecord/htdocs/documents/anotherfile.jpg
$document->save();
We've used a constant for the path that Document files should be saved to. It would be best to define this in your global config file, so that you can change the path later easily.
Specifiying a custom file name for the saved file
To use a custom file name for the saved file, simply define a method named <property name>_file_name in your model. For example:
<?php
define("DOCUMENT_SAVE_PATH","/home/ben/Sites/fuzzyrecord/htdocs/documents");
class Document extends FuzzyRecord {
static protected $table = "document";
static protected $properties = array(
"id" => array("primary_key","auto_increment"),
"file" => array("file","save_path" => DOCUMENT_SAVE_PATH),
"last_modified" => array("date_with_time","order_by_default")
);
public function file_file_name() {
return $this->id.".jpg";
}
}
$document = new Document();
$document->file = File::file_with_uploaded_file($_FILES["user_uploaded_file"]);
// File will be saved to:
// /home/ben/Sites/fuzzyrecord/htdocs/documents/6.jpg
// (where 6 is the id of the Document instance)
$document->save();