RBO (the RecordBrowser Object wrapper) makes creating, using, and maintaining RecordSets easier. It covers most of RecordBrowser's functionality, but wrapped in an object-oriented interface. Since it's a wrapper around RecordBrowser's static methods, use RecordBrowser directly if you need maximum performance over code readability — but in practice, the overhead is rarely noticeable.
Glossary
Utils/RecordBrowser module, documented on the Utils/RecordBrowser reference page.Every built-in class in this module is prefixed RBO_.
RBO_Recordset
Represents a RecordSet. Acts as a proxy for RecordBrowser's static functions — extend it with your own implementation (details below), or use RBO_RecordsetAccessor instead.
RBO_Record
Represents a single record from a RecordSet.
RBO_RecordsetAccessor
An object wrapper for any existing RecordSet.
RBO_FieldDefinition
The generic field definition class. You can use it directly, but the specific field classes listed below are simpler.
Field definition classes
Wrap any existing RecordSet in an object using RBO_RecordsetAccessor:
$rbo = new RBO_RecordsetAccessor('contact');
echo $rbo->get_records_count();
$record = $rbo->get_record(23); // retrieve contact with id = 23
if ($record != null) {
echo $record->first_name;
$record->first_name = 'Test'; // change first name field
$record->save(); // save record
}
$records = $rbo->get_records(array('first_name' => 'Test'));
To define your own RecordSet, write a single class that extends RBO_Recordset.
First, create a module — see Creating Modules if you haven't already. We'll assume the module lives at EPESI_DIR/modules/Custom/Inventory. The RecordSet class name must combine the module name and file name so Epesi's autoloader can find it; without autoloading, you'll need to include the file manually.
Just a RecordSet class
File modules/Custom/Inventory/Categories.php:
class Custom_Inventory_Categories extends RBO_Recordset {
function table_name() {
return 'custom_inventory_categories';
}
function fields() {
$category_name = new RBO_Field_Text('Name');
$category_name->set_length(24)->set_required()->set_visible();
$description = new RBO_Field_LongText('Description');
$description->set_visible();
return array($category_name, $description);
}
}
Your RecordSet is now ready to use. Install it during your module's install procedure:
class Custom_InventoryInstall extends ModuleInstall {
function install() {
$categories = new Custom_Inventory_Categories();
$success = $categories->install();
return $success;
}
...
}
You can also define a class extending RBO_Record, to add your own methods to each record. Every record your RecordSet returns will then be an instance of that class.
File modules/Custom/Inventory/Category.php:
class Custom_Inventory_Category extends RBO_Record {
function print_summary() {
print $this->name . " - " . $this->description;
}
}
File modules/Custom/Inventory/Categories.php:
class Custom_Inventory_Categories extends RBO_Recordset {
function table_name() {
return 'custom_inventory_categories';
}
function class_name() {
return 'Custom_Inventory_Category';
}
function fields() {
$category_name = new RBO_Field_Text('Name');
$category_name->set_length(24)->set_required()->set_visible();
$description = new RBO_Field_LongText('Description');
$description->set_visible();
return array($category_name, $description);
}
}
Sample usage:
$rb = new Custom_Inventory_Categories();
$rec = $rb->get_record(1);
$rec->print_summary();
Sometimes you need to modify a value before display, or show it differently based on some rule. Magic callbacks make this easy — they're the same QFfield and display callbacks used in Utils/RecordBrowser, but you don't have to register them explicitly in the field definition.
Create a method named display_<field id> or QFfield_<field id>, where field id is the ID returned by Utils_RecordBrowserCommon::get_field_id() — the field's name, lowercased, with every non-alphanumeric character replaced by an underscore (Name → name, Last Name → last_name, Address 1 → address_1). Define these methods on your RecordSet class or your Record class; Epesi discovers and registers them as callbacks automatically during the RecordSet's install process.
File modules/Custom/Inventory/Categories.php:
class Custom_Inventory_Categories extends RBO_Recordset {
function table_name() {
return 'custom_inventory_categories';
}
function fields() {
$category_name = new RBO_Field_Text('Name');
$category_name->set_length(24)->set_required()->set_visible();
$description = new RBO_Field_LongText('Description');
$description->set_visible();
return array($category_name, $description);
}
function display_name($record, $nolink) {
return $record->record_link('my prefix' . $record->name, $nolink);
}
}
As mentioned, you can define these methods on either your RBO_Recordset or RBO_Record subclass. If you define both, the RBO_Recordset version wins.
It's up to you where you put it. Defined on the Record class, $this is the fully-populated record object — but that's one more class to maintain.
Arguments
There are two cases, depending on where the method is defined. Define it on the Record class, and you won't receive the record object as the first argument.
Parameters:
class_name()) — only when the method is defined on the RecordSet class!true = don't create a link).Examples
In the Record class:
class Custom_Inventory_Category extends RBO_Record {
...
function display_name($nolink) {
return $this->record_link('my prefix' . $this->name, $nolink);
}
}
In the RecordSet class:
class Custom_Inventory_Categories extends RBO_Recordset {
...
function display_name($record, $nolink) {
return $record->record_link('my prefix' . $record->name, $nolink);
}
}
Used everywhere the QuickForm library renders a field.
Arguments
first_name, address_1)First Name, Address 1 — same as the name in the field definition)Examples
In the Record class:
class Custom_Inventory_Category extends RBO_Record {
...
function QFfield_name($form, $field, $label, $mode, $default) {
if ($mode == 'view') {
$form->addElement('static', $field, $label, $this->display_name());
} else {
$form->addElement('text', $field, $label);
$form->setDefaults(array($field => $default));
}
}
}
In the RecordSet class:
class Custom_Inventory_Categories extends RBO_Recordset {
...
function QFfield_name($form, $field, $label, $mode, $default) {
if ($mode == 'view') {
$record = $this->record_to_object($rb_obj->record);
$form->addElement('static', $field, $label, $record->display_name());
} else {
$form->addElement('text', $field, $label);
$form->setDefaults(array($field => $default));
}
}
}
This class is abstract — extend it to define your own RecordSet.
Example:
class Custom_Inventory_Categories extends RBO_Recordset {
...
}
Every RBO_Recordset subclass must implement these:
table_name()
Return the table name used to identify this RecordSet.
[a-zA-Z_0-9])We suggest a lower-cased <category>_<module>_<recordset> pattern, or just <category>_<module> when a module has only one recordset — for example:
Example:
function table_name() {
return 'custom_inventory_categories';
}
fields()
Return an array of fields to install on the RecordSet.
Example:
function fields() {
$category_name = new RBO_Field_Text('Name');
$category_name->set_length(24)->set_required()->set_visible();
$description = new RBO_Field_LongText('Description');
$description->set_visible();
return array($category_name, $description);
}
class_name()
Override this to specify a class extending RBO_Record. Records retrieved from this RecordSet will be instances of that class — use it to add custom methods to your records.
Example:
class Custom_Inventory_Item extends RBO_Record {
function is_my_favourite() {
// code here
}
}
class Custom_Inventory_Items extends RBO_Recordset {
function table_name() {
return 'custom_inventory_items';
}
function fields() { /* implementation here */ }
function class_name() {
return 'Custom_Inventory_Item';
}
}
__construct()
You can override the constructor, but must call the parent constructor to initialize the object properly:
function __construct() {
parent::__construct();
// your code goes here
}
Sometimes you already have a record as a plain array and know which RecordSet it belongs to — for example, from a direct Utils_RecordBrowserCommon::get_record() call, or inside a display callback. Use one of the following methods to wrap it.
array_to_object($record_array)
Creates an object from a single record.
// record data in array
$record = Utils_RecordBrowserCommon::get_record('custom_inventory_items', $id);
$rs = new Custom_Inventory_Items();
$object = $rs->array_to_object($record);
array_of_records_to_array_of_objects($array_of_record_arrays)
Creates an array of objects from an array of records stored as arrays.
// get all records
// $records = Utils_RecordBrowserCommon::get_records('custom_inventory_items');
// $records = array(
// array('id' => 1, 'field1' => 'data1'),
// array('id' => 2, 'field1' => 'data2'),
// etc.
// );
$rs = new Custom_Inventory_Items();
$objects = $rs->array_of_records_to_array_of_objects($records);
// $objects = array(
// instance of RBO_Record,
// instance of RBO_Record,
// etc.
// );
create_record_object($recordset_class_name, $record_array)
A protected static helper for creating a record object and binding it to its RecordSet. Use it only from a class that extends RBO_Recordset.
Because of limitations in PHP versions before 5.3 (no late static binding), a parent class can't determine a child's class name inside an inherited static method. Since custom display and QFfield callbacks are always static, there's no generic way to get a record object of the right type in the child class — you have to supply the class name explicitly, via __CLASS__.
You probably don't need this method directly — see Magic callbacks above. Correct usage:
class Custom_Inventory_Items extends RBO_Recordset {
...
function custom_display_callback($record_array, $nolink, $field_desc) {
$object = self::create_record_object(__CLASS__, $record_array);
// ... further code
}
}
RBO_Record represents a single record from any RecordSet. Its methods let you read and manipulate that record directly.
You can also extend this class to add your own methods, or to implement QFfield or display callbacks — in that case, declare a class_name() method on your RecordSet class (see class_name() above).
Accessing data
Data is stored as object properties — every field defined on the RecordSet gets a matching property. You can derive a property name from a field name with Utils_RecordBrowserCommon::get_field_id(): the field name, lowercased, with every non-alphanumeric character replaced by an underscore (Name → name, Last Name → last_name, Address 1 → address_1).
RBO_Record also implements PHP's ArrayAccess interface, so you can use it like an array — this keeps it compatible with plain data arrays from Utils_RecordBrowserCommon functions. Existing code written against RecordBrowser's array data should work unchanged against RBO_Record objects.
Prefer property access over array access — it's faster.
$rs = new RBO_RecordsetAccessor('contact');
$record = $rs->get_record(1);
echo $record->first_name;
echo $record['first_name']; // slower
Creating new records
To create a new record, use RBO_Recordset::new_record() rather than instantiating RBO_Record directly.
Example:
$rs = new RBO_RecordsetAccessor('contact');
$record = $rs->new_record();
$record->first_name = 'Joe';
$record->last_name = 'Doe';
$record->save();
If you already have the record's data in an array, pass it directly to RBO_Recordset::new_record() — it will create the record in the database and return the corresponding object:
$rs = new RBO_RecordsetAccessor('contact');
$data = array('first_name' => 'Joe', 'last_name' => 'Doe');
$joe = $rs->new_record($data);
Special properties
Every record object has four special properties:
id
Read-only. Set to a numeric value on every record retrieved from the database. On a record just created with RBO_Recordset::new_record(), it's null until save() is called.
_active
Whether the record is active (not deleted). Deleted records are simply marked inactive rather than removed. Manage this with delete(), restore(), or set_active($state).
created_by
The ID of the user who created the record. null on unsaved records; set to the current user's ID (Acl::get_user()) once save() is called.
created_on
Date and time, in the format returned by date('Y-m-d H:i:s'). Only populated on records loaded from the database — it isn't set on a freshly-created record after save().
Select/multiselect returned value
A select or multiselect field's value is always the ID of a record in the linked RecordSet. Even though its DB type is integer, it comes back as a string — that's simply how database results are returned.
$contacts = new RBO_RecordsetAccessor('contact');
$record = $contacts->get_record(1);
assert(is_numeric($record->company_name));
assert(is_string($record->company_name));
$companies = new RBO_RecordsetAccessor('company');
$company = $companies->get_record($record->company_name);
$real_company_name = $company->company_name;
About returned types
Some properties — _active, id, and checkbox or integer fields — may come back as strings even though they represent numbers or booleans. Always compare them with the weak equality operator (==), not strict (===).
$rs = new RBO_RecordsetAccessor('contact');
$record = $rs->get_record(13);
assert(is_int($record->id));
assert(is_string($record->_active));
assert($record->_active == true);
// as $record->_active is string "1"
$record = $rs->get_record('13');
assert(is_string($record->id));
Use this class to access any RecordSet by name. Records it returns are RBO_Record objects.
One restriction: you can't call fields() on it — it will trigger an error, since this class can't read an existing RecordSet's field definitions. That's not normally a problem: fields() is only used to install a RecordSet, and since you're accessing one that already exists, you shouldn't be calling install() on it anyway. You can still call uninstall() if you need to.
$rs = new RBO_RecordsetAccessor('company');
// example 1
$all_records = $rs->get_records();
// example 2
$new_record = $rs->new_record(); // returns RBO_Record instance
// fill data
$new_record->save();
// etc ...
The base class for describing a field definition. Use it directly only for custom types — for standard fields, use one of the field definition subclasses below.
Every specific subclass defines a type constant holding the RecordBrowser type string, so with IDE autocompletion you never need to remember the exact string.
Methods
__construct($display_name, $type, $param = null, $extra = false, $required = false, $visible = false, $filter = false, $display_callback = null, $QFfield_callback = null, $position = null)
For some field types, $param is required.
get_definition()
Returns an array with the definition, in the format used by RecordBrowserCommon::new_record_field().
set_extra()
Marks the field as extra. By default, every field described with FieldDefinition is non-extra. Extra fields can be modified by an administrator and appear in a separate tab in view mode, like addons. Returns self.
set_required()
Marks the field as required. By default, every field described with FieldDefinition is optional. Returns self.
set_visible()
Makes the field visible in browse mode — visible fields get their own column in the tabular view. By default, a field is shown only in view mode. Returns self.
set_filter()
Enables filtering by this field. For example, an enabled checkbox filter lets users show only checked or unchecked records; an enabled select filter shows a dropdown of values to filter by. Disabled by default. Returns self.
set_display_callback($callback)
Sets a custom display callback for the field. Must be callable. See Magic callbacks above. Returns self.
set_QFfield_callback($callback)
Sets a custom QFfield callback for the field. Must be callable. See Magic callbacks above. Returns self.
set_position($position)
Sets the field's position. Use this only when adding a field to an existing RecordSet — during install, field order is simply the order of the array returned by fields(). If unset, the field is placed last.
The argument may be:
'First Name'); the new field is placed right after it.Simple example
// used for custom type defined by CRM/Contacts
$contact = new RBO_FieldDefinition(_M('Owner'), 'crm_contact', array('field_type' => 'select'));
$contact->set_visible()->set_required();
// _M('Owner') marks the string "Owner" for translation later, and returns the original string.
RBO_Field_Text($display_name, $length = null)
$length may be null, but then you must call set_length($length) (length in characters) before installing.
RBO_Field_LongText($display_name)
RBO_Field_Integer($display_name)
RBO_Field_Float($display_name)
RBO_Field_Checkbox($display_name)
RBO_Field_Calculated($display_name)
set_db_type($type, $param = null) — sets the field's database representation. $type is a type name or a field instance (if an instance, only its type and param are copied, and this method's $param argument is ignored). $param is the length when $type is 'text', otherwise null.RBO_Field_Date($display_name)
RBO_Field_Timestamp($display_name)
RBO_Field_Currency($display_name)
RBO_Field_Select
from($linked_recordset)fields($field, $_ = null)set_crits_callback($crits_callback)set_advanced_properties_callback($advanced_properties_callback)RBO_Field_MultiSelect
Same as RBO_Field_Select, but lets the user select multiple values.
RBO_Field_CommonData
from($commondata_array_name) — the CommonData array name, as passed to Utils_CommonDataCommon::new_array($name, $values).set_order_by_key() — force ordering by array keys.chained_select($field, $_ = null) — set the preceding field(s) for a chained select.RBO_Field_PageSplit($name)
Adds a new tab to view mode — every field after this one belongs to this tab, until the next page split. $name is the tab's display name.
Examples
// Sometimes there is more than one valid way to define a field. Pick whichever you prefer.
$first_name = new RBO_Field_Text(_M("First Name"), 15);
$first_name->set_required()->set_visible();
$last_name = new RBO_Field_Text(_M("Last Name"));
$last_name->set_length(30)->set_required()->set_visible();
$bio = new RBO_Field_LongText(_M("Biography"));
$siblings = new RBO_Field_Integer(_M("Siblings"));
$weight = new RBO_Field_Float(_M("Weight"));
$likes_tomatoes = new RBO_Field_Checkbox(_M("Likes tomatoes"));
$age = new RBO_Field_Calculated(_M("Age"));
$age->set_visible();
$birth_date = new RBO_Field_Date(_M("Birth date"));
$birth_date->set_required()->set_visible();
$last_visit = new RBO_Field_Date(_M("Last visit"));
$assets = new RBO_Field_Currency(_M("Personal assets"));
1.4.0
First release.
1.5.0
:active property — it's now translated correctly to _activeRBO_Recordset::delete_record_field() to delete a field from a recordset