<?php
/* vim: set noexpandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
require_once 'Swat/SwatObject.php';
require_once 'Swat/SwatTableModel.php';
require_once 'SwatDB/SwatDBTransaction.php';
require_once 'SwatDB/SwatDBClassMap.php';
require_once 'SwatDB/SwatDBRecordable.php';
require_once 'SwatDB/exceptions/SwatDBException.php';
require_once 'SwatDB/exceptions/SwatDBNoDatabaseException.php';
require_once 'Swat/exceptions/SwatInvalidClassException.php';
require_once 'Swat/exceptions/SwatInvalidTypeException.php';
/**
* MDB2 recordset wrapper
*
* Used to wrap an MDB2 recordset into a traversable collection of record
* objects. Implements SwatTableModel so it can be used directly as a data
* model for a recordset view. See {@link SwatView}.
*
* Recordsets are iterable and accessible using array access notation. One
* important point about recordsets is that <strong>iteration will always visit
* every record in this recordset</strong>, but if an index field is defined
* for this recordset, <strong>array access notation can only access records
* with their index field set</strong>. This is normally not a problem but
* inconsistencies can arise if records are added to this recordset that do not
* have an index value.
*
* @package SwatDB
* @copyright 2005-2007 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @todo Add lazy instantiation of records.
*/
abstract class SwatDBRecordsetWrapper extends SwatObject
implements Serializable, ArrayAccess, SwatTableModel, SwatDBRecordable
{
// {{{ protected properties
/**
* The name of the row wrapper class to use for this recordset wrapper
*
* @var string
*/
protected $row_wrapper_class;
/**
* The name of the record field to use as an index
*
* This field is used to lookup objects using getIndex(). If unspecified
* by a recordset subclass, the subclass records will not be indexed.
*
* @var string
*/
protected $index_field;
/**
* The database driver to use for this recordset
*
* @var MDB2_Driver_Common
*
* @see SwatDBRecordsetWrapper::setDatabase()
*/
protected $db;
// }}}
// {{{ private properties
/**
* Records contained in this recordset
*
* If this recordset wrapper has a defined $index_field, this array is
* indexed by the index field values of the objects. Otherwise, this array
* is indexed numerically.
*
* @var array
*/
private $objects = array();
/**
* Records contained in this recordset indexed by this recordset's
* index field
*
* If this recordset does not have a defined index field, this array is
* not used.
*
* @var array
*/
private $objects_by_index = array();
/**
* Records removed from this recordset
*
* This array contains records removed from this recordset before this
* recordset is saved. When this recordset is saved, all records contained
* in this array are deleted from the database. This array is indexed
* numerically.
*
* @var array
*/
private $removed_objects = array();
/**
* The current index of the iterator interface
*
* @var integer
*/
private $current_index = 0;
// }}}
// {{{ public function __construct()
/**
* Creates a new recordset wrapper
*
* @param MDB2_Result $recordset optional. The MDB2 recordset to wrap.
*/
public function __construct($recordset = null)
{
$this->init();
if ($recordset === null)
return;
if (MDB2::isError($recordset))
throw new SwatDBException($recordset->getMessage());
$this->setDatabase($recordset->db);
do {
while ($row = $recordset->fetchRow(MDB2_FETCHMODE_OBJECT)) {
$object = $this->instantiateRowWrapperObject($row);
if ($object instanceof SwatDBRecordable)
$object->setDatabase($recordset->db);
$this->objects[] = $object;
if ($this->index_field !== null &&
isset($row->{$this->index_field})) {
$index = $row->{$this->index_field};
$this->objects_by_index[$index] = $object;
}
}
} while ($recordset->nextResult());
}
// }}}
// {{{ public function duplicate()
/**
* Duplicates this record set wrapper
*
* @return SwatDBRecordsetWrapper a duplicate of this object.
* @see SwatDBDataobject::duplicate()
*/
public function duplicate()
{
$class = get_class($this);
$new_wrapper = new $class();
foreach ($this->getArray() as $object) {
$object->setDatabase($this->db);
$duplicate_object = $object->duplicate();
$duplicate_object->setDatabase($this->db);
$new_wrapper->add($duplicate_object);
}
$new_wrapper->setDatabase($this->db);
return $new_wrapper;
}
// }}}
// {{{ protected function instantiateRowWrapperObject()
/**
* Creates a new dataobject
*
* @param stdClass $row the data row to use.
*
* @return stdClass the instantiated data object or the original object if
* no <i>$row_wrapper_class</i> is defined for this
* recordset wrapper.
*/
protected function instantiateRowWrapperObject($row)
{
if ($this->row_wrapper_class === null) {
$object = $row;
} else {
$object = new $this->row_wrapper_class($row);
}
return $object;
}
// }}}
// {{{ protected function init()
/**
* Initializes this recordset wrapper
*
* Subclasses are encoraged to specify a SwatDBDataObject subclass as this
* recordset's row wrapper class. See
* {@link SwatDBRecordsetWrapper::$row_wrapper_class}.
*
* Subclasses are also encoraged to specify an index field here. This
* enables lookup of records in this recordset by the index field value.
* See {@link SwatDBRecordsetWrapper::$index_field}.
*
* Other initialization may be performed here. This method is the first
* thing called in the constructor.
*/
protected function init()
{
}
// }}}
// {{{ protected function checkDB()
protected function checkDB()
{
if ($this->db === null)
throw new SwatDBNoDatabaseException(
sprintf('No database available to this wrapper (%s). '.
'Call the setDatabase method.', get_class($this)));
}
// }}}
// array access
// {{{ public function offsetExists()
/**
* Gets whether or not a value exists for the given offset
*
* @param mixed $offset the offset to check. If this recordset has a
* defined index field, the offset is an index
* value. Otherwise, the offset is an ordinal value.
*
* @return boolean true if this recordset has a value for the given offset
* and false if it does not.
*/
public function offsetExists($offset)
{
if ($this->index_field === null)
return isset($this->objects[$offset]);
return isset($this->objects_by_index[$offset]);
}
// }}}
// {{{ public function offsetGet()
/**
* Gets a record in this recordset by an offset value
*
* @param mixed $offset the offset for which to get the record. If this
* recordset has a defined index field, the offset is
* an index value. Otherwise, the offset is an
* ordinal value.
*
* @return SwatDBDataObject the record at the specified offset.
*
* @throws OutOfBoundsException if no record exists at the specified offset
* in this recordset.
*/
public function offsetGet($offset)
{
if (!isset($this[$offset]))
throw new OutOfBoundsException(sprintf(
'Index %s is out of bounds.',
$offset));
if ($this->index_field === null)
return $this->objects[$offset];
return $this->objects_by_index[$offset];
}
// }}}
// {{{ public function offsetSet()
/**
* Sets a record at a specified offset
*
* @param mixed $offset optional. The offset to set the record at. If this
* recordset has a defined index field, the offset is
* an index value. Otherwise, the offset is an
* ordinal value. If no offset is given, the record
* will be added at the end of this recordset.
*
* @param mixed $value the record to add.
*
* @throws UnexpectedValueException if this recordset has a defined row
* wrapper class and the specified value
* is not an instance of the row wrapper
* class.
* @throws OutOfBoundsException if the specified offset does not exist in
* this recordset. Records can only be added
* to the end of the recordset or replace
* existing records in this recordset.
*/
public function offsetSet($offset, $value)
{
if ($this->row_wrapper_class !== null &&
!($value instanceof $this->row_wrapper_class))
throw new UnexpectedValueException(sprintf(
'Value should be an instance of %s.',
$this->row_wrapper_class));
// add
if ($offset === null) {
$this->objects[] = $value;
// if index field is set, index the object
if ($this->index_field !== null &&
isset($value->{$this->index_field}))
$this->objects_by_index[$value->{$this->index_field}] =
$value;
// replace at offset
} else {
if (!isset($this[$offset]))
throw new OutOfBoundsException(sprintf(
'No record to replace exists at offset %s.',
$offset));
if ($this->index_field === null) {
$this->removed_objects[] = $this->objects[$offset];
$this->objects[$offset] = $value;
} else {
// update object index field value
$value->{$this->index_field} = $offset;
// find and replace ordinally indexed objects
$keys = array_keys($this->objects, $value, true);
foreach ($keys as $key) {
$this->removed_objects[] = $this->objects[$key];
$this->objects[$key] = $value;
}
// add object to indexed array
$this->objects_by_index[$offset] = $value;
}
}
// only set the db on added object if it is set for this recordset
if ($this->db !== null && $value instanceof SwatDBRecordable)
$value->setDatabase($this->db);
// Remove object from removed list if it was on list of removed
// objects. This step needs to happen after adding the new record
// for the case where we replaced an object with itself.
$keys = array_keys($this->removed_objects, $value, true);
foreach ($keys as $key)
unset($this->removed_objects[$key]);
}
// }}}
// {{{ public function offsetUnset()
/**
* Unsets a record in this recordset at the specified offset
*
* This removes the record at the specified offset from this recordset.
* If no such record exists, nothing is done. The record object itself
* still exists if there is an external reference to it elsewhere.
*
* @param mixed $offset the offset for which to unset the record. If this
* recordset has a defined index field, the offset is
* an index value. Otherwise, the offset is an
* ordinal value.
*/
public function offsetUnset($offset)
{
if (isset($this[$offset])) {
if ($this->index_field === null) {
$this->removed_objects[] = $this->objects[$offset];
unset($this->objects[$offset]);
// update iterator index
if ($this->current_index >= $offset && $this->current_index > 0)
$this->current_index--;
} else {
$object = $this->objects_by_index[$offset];
$this->removed_objects[] = $object;
unset($this->objects_by_index[$offset]);
$keys = array_keys($this->objects, $object, true);
foreach ($keys as $key) {
unset($this->objects[$key]);
// update iterator index
if ($this->current_index >= $key &&
$this->current_index > 0)
$this->current_index--;
}
}
// reindex ordinal array of records
$this->objects = array_values($this->objects);
}
}
// }}}
// iteration
// {{{ public function current()
/**
* Returns the current element
*
* @return mixed the current element.
*/
public function current()
{
return $this->objects[$this->current_index];
}
// }}}
// {{{ public function key()
/**
* Returns the key of the current record
*
* If this recordset has an index field defined and the current record has
* an index value, this gets the index value. Otherwise this gets the
* ordinal position of the record in this recordset.
*
* @return integer the key of the current record.
*/
public function key()
{
if ($this->index_field !== null &&
isset($this->current()->{$this->index_field}))
$key = $this->current()->{$this->index_field};
else
$key = $this->current_index;
return $key;
}
// }}}
// {{{ public function next()
/**
* Moves forward to the next element
*/
public function next()
{
$this->current_index++;
}
// }}}
// {{{ public function rewind()
/**
* Rewinds this iterator to the first element
*/
public function rewind()
{
$this->current_index = 0;
}
// }}}
// {{{ public function valid()
/**
* Checks is there is a current element after calls to rewind() and next()
*
* @return boolean true if there is a current element and false if there
* is not.
*/
public function valid()
{
return array_key_exists($this->current_index, $this->objects);
}
// }}}
// counting
// {{{ public function getCount()
/**
* Gets the number of records in this recordset
*
* @return integer the number of records in this recordset.
*
* @deprecated this class now implements Countable. Use count($object)
* instead of $object->getCount().
*/
public function getCount()
{
return count($this);
}
// }}}
// {{{ public function count()
/**
* Gets the number of records in this recordset
*
* This satisfies the Countable interface.
*
* @return integer the number of records in this recordset.
*/
public function count()
{
return count($this->objects);
}
// }}}
// serialization
// {{{ public function serialize()
public function serialize()
{
$data = array();
$private_properties = array(
'row_wrapper_class',
'index_field',
'objects',
'objects_by_index',
);
foreach ($private_properties as $property)
$data[$property] = &$this->$property;
return serialize($data);
}
// }}}
// {{{ public function unserialize()
public function unserialize($data)
{
$data = unserialize($data);
foreach ($data as $property => $value)
$this->$property = $value;
}
// }}}
// manipulating of sub data objects
// {{{ public function getInternalValues()
/**
* Gets the values of an internal property for each record in this set
*
* @param string $name name of the internal property to get.
*
* @return array an array of values.
*
* @throws SwatDBException if records in this recordset do not have an
* internal value with the specified <i>$name</i>.
*
* @see SwatDBDataObject::getInternalValue()
*/
public function getInternalValues($name)
{
$values = array();
if (count($this) > 0) {
if (!$this->getFirst()->hasInternalValue($name)) {
throw new SwatDBException(
"Records in this recordset do not contain an internal ".
"field named '{$name}'.");
}
foreach ($this->objects as $object)
$values[] = $object->getInternalValue($name);
}
return $values;
}
// }}}
// {{{ public function loadAllSubDataObjects()
/**
* Loads all sub-data-objects for an internal property of the data-objects
* in this recordset
*
* This is used to efficiently load sub-objects when there is a one-to-one
* relationship between the objects in this recordset and the sub-objects.
* This is usually the case when there is a foreign key constraint in the
* database table for the objects in this recordset.
*
* @param string $name name of the internal property to load.
* @param MDB2_Driver_Common $db database object.
* @param string $sql SQL to execute with placeholder for the set of
* internal property values. For example:
* <code>select * from Foo where id in (%s)</code>.
* @param string $wrapper the class name of the recordset wrapper to use
* for the sub-data-objects.
* @param string $type optional. The MDB2 datatype of the internal property
* values. If not specified, 'integer' is used.
*
* @return SwatDBRecordsetWrapper an instance of the wrapper, or null.
*/
public function loadAllSubDataObjects($name, MDB2_Driver_Common $db, $sql,
$wrapper, $type = 'integer')
{
$sub_data_objects = null;
$values = $this->getInternalValues($name);
$values = array_filter($values,
create_function('$value', 'return $value !== null;'));
$values = array_unique($values);
if (count($values) > 0) {
$this->checkDB();
$this->db->loadModule('Datatype', null, true);
$quoted_values = $this->db->datatype->implodeArray($values, $type);
$sql = sprintf($sql, $quoted_values);
$sub_data_objects = SwatDB::query($db, $sql, $wrapper);
$this->attachSubDataObjects($name, $sub_data_objects);
}
return $sub_data_objects;
}
// }}}
// {{{ public function attachSubDataObjects()
/**
* Attach existing sub-dataobjects for an internal property of the
* dataobjects in this recordset
*
* @param string $name name of the property to attach to.
* @param SwatDBRecordsetWrapper $sub_data_objects
*/
public function attachSubDataObjects($name,
SwatDBRecordsetWrapper $sub_data_objects)
{
if ($sub_data_objects->index_field === null) {
throw new SwatDBException(sprintf(
'Index field must be specified in the sub-data-object '.
'recordset wrapper class (%s::init()) '.
'in order to attach recordset as sub-dataobjects.',
get_class($sub_data_objects)));
}
foreach ($this->objects as $object) {
$value = $object->getInternalValue($name);
if (isset($sub_data_objects[$value])) {
$object->$name = $sub_data_objects[$value];
}
}
}
// }}}
// manipulating of sub-recordsets
// {{{ public function loadAllSubRecordsets()
/**
* Efficiently loads sub-recordsets for records in this recordset
*
* @param string $name the name of the sub-recordset.
* @param string $wrapper the name of the recordset wrapper class to use
* for the sub-recordsets.
* @param string $table the name of the table containing sub-records.
* @param string $binding_field the name of the binding field in the
* table containing sub-records. This should
* be a field that contains index values from
* this recordset (i.e., a foreign key).
* @param string $where optional additional where clause to apply to the
* sub-records.
* @param string $order_by optional ordering of sub-recordsets.
* @param string $fields optional list of fields to return. By default
* all fields are returned. This can be used to
* optimize tables with large text fields or a lot
* of fields that aren't used in this context.
*
* @throws SwatDBException if this recordset does not define an index
* field.
*
* @return SwatDBRecordsetWrapper a wrapper of the sub-recordsets, or null.
*/
public function loadAllSubRecordsets($name, $wrapper,
$table, $binding_field, $where = '', $order_by = '',
$fields = '*')
{
$this->checkDB();
if ($this->index_field === null) {
throw new SwatDBException(sprintf(
'Index field must be specified in the recordset wrapper '.
'class (%s::init()) in order to attach sub-recordsets.',
get_class($this)));
}
// return empty recordset if this is an empty recordset
if (count($this) === 0) {
$recordset = new $wrapper();
$recordset->setDatabase($this->db);
return $recordset;
}
// get record ids
$record_ids = array();
foreach ($this as $record) {
$record_ids[] = $record->{$this->index_field};
}
// note: this only works when index_field is an integer which is
// currently true anyway
$record_ids = $this->db->implodeArray($record_ids, 'integer');
// build SQL to select all records
$sql = sprintf('select %s from %s
where %s in (%s)',
$fields,
$table, $binding_field, $record_ids);
if ($where != '') {
$sql.= ' and '.$where;
}
$sql.= ' order by '.$binding_field;
if ($order_by != '') {
$sql.= ', '.$order_by;
}
// get all records
$recordset = SwatDB::query($this->db, $sql, $wrapper);
return $this->attachSubRecordset($name, $wrapper, $binding_field,
$recordset);
}
// }}}
// {{{ public function attachSubRecordset()
/**
* Efficiently loads sub-recordsets for records in this recordset
*
* @param string $name the name of the sub-recordset.
* @param string $wrapper the name of the recordset wrapper class to use
* for the sub-recordsets.
* @param string $binding_field the name of the binding field in the
* table containing sub-records. This should
* be a field that contains index values from
* this recordset (i.e., a foreign key).
* @param SwatDBRecordsetWrapper $recordset the recordset to attach
*
* @throws SwatDBException if this recordset does not define an index
* field.
*
* @return SwatDBRecordsetWrapper a wrapper of the sub-recordsets, or null.
*/
public function attachSubRecordset($name, $wrapper, $binding_field,
SwatDBRecordsetWrapper $recordset)
{
$this->checkDB();
// assign empty recordsets for all records in this set
foreach ($this as $record) {
$empty_recordset = new $wrapper();
$record->$name = $empty_recordset;
}
// split records into separate recordsets for records in this set
$current_record_id = null;
$current_recordset = null;
foreach ($recordset as $record) {
$record_id = $record->getInternalValue($binding_field);
if ($record_id !== $current_record_id) {
$current_record_id = $record_id;
$current_recordset = $this[$record_id]->$name;
}
$current_recordset->add($record);
}
return $recordset;
}
// }}}
// manipulating of objects
// {{{ public function getIndexes()
/**
* Gets the index values of the records in this recordset
*
* @return array the index values of the records in this recordset.
*/
public function getIndexes()
{
if ($this->index_field === null) {
throw new SwatDBException(sprintf(
'Index field must be specified in the recordset wrapper '.
'class (%s::init()) in order to get the record indexes.',
get_class($this)));
}
return array_keys($this->objects_by_index);
}
// }}}
// {{{ public function getArray()
/**
* Gets this recordset as an array of objects
*
* @return array this record set as an array. This gets a copy of the
* internal object array (indexed ordinally).
*/
public function getArray()
{
return $this->objects;
}
// }}}
// {{{ public function getFirst()
/**
* Retrieves the first object in this recordset
*
* @return mixed the first object or null if there are no objects in this
* recordset.
*/
public function getFirst()
{
$first = null;
if (count($this->objects) > 0)
$first = reset($this->objects);
return $first;
}
// }}}
// {{{ public function getLast()
/**
* Retrieves the last object in this recordset
*
* @return mixed the last object or null if there are no objects in this
* recordset.
*/
public function getLast()
{
$last = null;
if (count($this->objects) > 0) {
$last = end($this->objects);
}
return $last;
}
// }}}
// {{{ public function getByIndex()
/**
* Retrieves a record in this recordset by index
*
* By default indexes are ordinal numbers unless this class's
* $index_field property is set.
*
* You can use also get records using array acces notation. For example:
* <code>
* $value = (isset($set['index'])) ? $set['index'] : null;
* </code>
*
* @param mixed $index the offset for which to get the record. If this
* recordset has a defined index field, the offset is
* an index value. Otherwise, the offset is an
* ordinal value.
*
* @return mixed the record object or null if not found.
*/
public function getByIndex($index)
{
return (isset($this[$index])) ? $this[$index] : null;
}
// }}}
// {{{ public function add()
/**
* Adds a record to this recordset
*
* You can also add records to this recordset using array access notation.
* For example:
* <code>
* $set[] = $new_record;
* </code>
*
* @param SwatDBDataObject $object the object to add. If this recordset has
* a row wrapper class defined, the object
* must be an instance of that class.
*/
public function add(SwatDBDataObject $object)
{
$this[] = $object;
}
// }}}
// {{{ public function remove()
/**
* Removes a record from this recordset
*
* @param SwatDBDataObject $remove_object the record to remove.
*/
public function remove(SwatDBDataObject $remove_object)
{
if (in_array($remove_object, $this->objects, true)) {
$this->removed_objects[] = $remove_object;
if ($this->index_field !== null) {
$index = $remove_object->{$this->index_field};
unset($this->objects_by_index[$index]);
}
$keys = array_keys($this->objects, $remove_object, true);
foreach ($keys as $key) {
unset($this->objects[$key]);
// update iterator index
if ($this->current_index >= $key && $this->current_index > 0)
$this->current_index--;
}
// reindex ordinal array of records
$this->objects = array_values($this->objects);
}
}
// }}}
// {{{ public function removeByIndex()
/**
* Removes a record from this recordset given the record's index value
*
* You can also remove records from this recordset using array access
* notation. For example:
* <code>
* unset($set[$index]);
* </code>
*
* @param mixed $index the offset of the record to remove. If this
* recordset has a defined index field, the offset is
* an index value. Otherwise, the offset is an
* ordinal value.
*/
public function removeByIndex($index)
{
unset($this[$index]);
}
// }}}
// {{{ public function removeAll()
/**
* Removes all records from this recordset
*/
public function removeAll()
{
$this->removed_objects = array_values($this->objects);
$this->objects = array();
$this->objects_by_index = array();
$this->current_index = 0;
}
// }}}
// {{{ public function reindex()
/**
* Reindexes this recordset
*
* Reindexing is useful when you have added new data-objects to this
* recordset. Reindexing is only done if this recordset has a defined
* index field.
*/
public function reindex()
{
if ($this->index_field !== null) {
$this->objects_by_index = array();
$index_field = $this->index_field;
foreach ($this->objects as $object)
if (isset($object->$index_field))
$this->objects_by_index[$object->$index_field] = $object;
}
}
// }}}
// database loading and saving
// {{{ public function setDatabase()
/**
* Sets the database driver for this recordset
*
* The database is automatically set for all recordable records of this
* recordset.
*
* @param MDB2_Driver_Common $db the database driver to use for this
* recordset.
*/
public function setDatabase(MDB2_Driver_Common $db)
{
$this->db = $db;
foreach ($this->objects as $object)
if ($object instanceof SwatDBRecordable)
$object->setDatabase($db);
}
// }}}
// {{{ public function save()
/**
* Saves this recordset to the database
*
* Saving a recordset works as follows:
* 1. Objects that were removed are deleted from the database.
* 2. Objects that were added are inserted into the database,
* 3. Objects that were modified are updated in the database,
*
* Deleting is performed before adding incase a new row with the same
* values as a deleted row is added. For example, a binding is removed and
* an identical binding is added.
*/
public function save()
{
$this->checkDB();
$transaction = new SwatDBTransaction($this->db);
try {
foreach ($this->removed_objects as $object) {
$object->setDatabase($this->db);
$object->delete();
}
foreach ($this->objects as $object) {
$object->setDatabase($this->db);
$object->save();
}
$transaction->commit();
} catch (Exception $e) {
$transaction->rollback();
throw $e;
}
$this->removed_objects = array();
$this->reindex();
}
// }}}
// {{{ public function load()
/**
* Loads a set of records into this recordset
*
* It is recommended for performance that you use recordset wrappers to
* wrap a MDB2 result set rather than using this load() method. Using this
* method performs N queries where N is the size of the passed array of
* object indexes.
*
* @param array $object_indexes the index field values of the records to
* load into this recordset.
*
* @return boolean true if all records loaded properly and false if one
* or more records could not be loaded. If any records
* fail to load, the recordset state remains unchanged.
*
* @throws SwatInvalidTypeException if the <i>$object_indexes</i> property
* is not an array.
* @throws SwatInvalidClassException if this recordset's
* {@link SwatDBRecordsetWrapper::$row_wrapper_class}
* is not an instance of
* {@link SwatDBRecordable}.
*/
public function load($object_indexes)
{
if (!is_array($object_indexes))
throw new SwatInvalidTypeException(
'The $object_indexes property must be an array.',
0, $object_indexes);
$interfaces = class_implements($this->row_wrapper_class);
if (!in_array('SwatDBRecordable', $interfaces)) {
throw new SwatInvalidClassException(
'The recordset must define a row wrapper class that is an '.
'instance of SwatDBRecordable for recordset loading to work.',
0, $this->row_wrapper_class);
}
$success = true;
// try to load all records
$records = array();
$class_name = $this->row_wrapper_class;
foreach ($object_indexes as $index) {
$record = new $class_name();
$record->setDatabase($this->db);
if ($record->load($index)) {
$records[] = $record;
} else {
$success = false;
break;
}
}
// successfully loaded all records, set this set's records to the
// loaded records
if ($success) {
$this->objects = array();
$this->objects_by_index = array();
$this->removed_objects = array();
foreach($records as $record)
$this[] = $record;
$this->reindex();
}
return $success;
}
// }}}
// {{{ public function delete()
/**
* Deletes this recordset from the database
*
* All records contained in this recordset are removed from this set and
* are deleted from the database.
*/
public function delete()
{
$this->removeAll();
$this->save();
}
// }}}
// {{{ public function isModified()
/**
* Returns true if this recordset has been modified since it was loaded
*
* A recordset is considered modified if any of the contained records have
* been modified or if any records have been removed from this set. Adding
* an unmodified record to this set does not constitute modifying the set.
*
* @return boolean true if this recordset was modified and false if this
* recordset was not modified.
*/
public function isModified()
{
if (count($this->removed_objects) > 0)
return true;
foreach ($this->objects as $name => $object)
if ($object->isModified())
return true;
return false;
}
// }}}
}
?>