silverstripe-framework/core/model/ComponentSet.php

308 lines
8.1 KiB
PHP
Raw Normal View History

<?php
/**
* @package sapphire
* @subpackage core
*/
/**
* This is a special kind of DataObjectSet used to represent the items linked to in a 1-many or many-many
* join. It provides add and remove methods that will update the database.
*/
class ComponentSet extends DataObjectSet {
/**
* Type of relationship (eg '1-1', '1-many').
* @var string
*/
protected $type;
/**
* Object that owns this set.
* @var DataObject
*/
protected $ownerObj;
/**
* Class of object that owns this set.
* @var string
*/
protected $ownerClass;
/**
* Table that holds this relationship.
* @var string
*/
protected $tableName;
/**
* Class of child side of the relationship.
* @var string
*/
protected $childClass;
/**
* Field to join on.
* @var string
*/
protected $joinField;
/**
* Set the ComponentSet specific information.
* @param string $type Type of relationship (eg '1-1', '1-many').
* @param DataObject $ownerObj Object that owns this set.
* @param string $ownerClass Class of object that owns this set.
* @param string $tableName Table that holds this relationship.
* @param string $childClass Class of child side of the relationship.
* @param string $joinField Field to join on.
*/
function setComponentInfo($type, $ownerObj, $ownerClass, $tableName, $childClass, $joinField = null) {
$this->type = $type;
$this->ownerObj = $ownerObj;
$this->ownerClass = $ownerClass ? $ownerClass : $ownerObj->class;
$this->tableName = $tableName;
$this->childClass = $childClass;
$this->joinField = $joinField;
}
/**
* Get an array of all the IDs in this component set, where the keys are the same as the
* values.
* @return array
*/
function getIdList() {
$list = array();
foreach($this->items as $item) {
$list[$item->ID] = $item->ID;
}
return $list;
}
/**
* Add an item to this set.
* @param DataObject|int|string $item Item to add, either as a DataObject or as the ID.
* @param array $extraFields A map of extra fields to add.
*/
function add($item, $extraFields = null) {
if(!isset($item)) {
user_error("ComponentSet::add() Not passed an object or ID", E_USER_ERROR);
}
if(is_object($item)) {
// TODO Should this allow subclasses?
if($item->class != $this->childClass) {
user_error("ComponentSet::add() Tried to add an '{$item->class}' object, but '{$this->childClass}' expected", E_USER_ERROR);
}
} else {
$id = $item;
if(!$this->childClass) {
user_error("ComponentSet::add() \$this->childClass not set", E_USER_ERROR);
}
$item = DataObject::get_by_id($this->childClass, $item);
if(!isset($item)) {
return;
}
}
// If we've already got a database object, then update the database
if($this->ownerObj->ID && is_numeric($this->ownerObj->ID)) {
$this->loadChildIntoDatabase($item, $extraFields);
}
// In either case, add something to $this->items
$this->items[] = $item;
}
/**
* Method to save many-many join data into the database for the given $item.
* Used by add() and write().
* @param DataObject|string|int The item to save, as either a DataObject or the ID.
* @param array $extraFields Map of extra fields.
*/
protected function loadChildIntoDatabase($item, $extraFields = null) {
if($this->type == '1-to-many') {
$child = DataObject::get_by_id($this->childClass,$item->ID);
$joinField = $this->joinField;
$child->$joinField = $this->ownerObj->ID;
$child->write();
} else {
$parentField = $this->ownerClass . 'ID';
$childField = ($this->childClass == $this->ownerClass) ? "ChildID" : ($this->childClass . 'ID');
DB::query( "DELETE FROM `$this->tableName` WHERE $parentField = {$this->ownerObj->ID} AND $childField = {$item->ID}" );
$extraSQL = '';
if($extraFields) foreach($extraFields as $k => $v) {
$extraSQL .= ", $k = '" . addslashes($v) . "'";
}
DB::query("INSERT INTO `$this->tableName` SET $parentField = {$this->ownerObj->ID}, $childField = {$item->ID} $extraSQL");
}
}
/**
* Add a number of items to the component set.
* @param array $items Items to add, as either DataObjects or IDs.
*/
function addMany($items) {
foreach($items as $item) {
$this->add($item);
}
}
/**
* Sets the ComponentSet to be the given ID list.
* Records will be added and deleted as appropriate.
* @param array $idList List of IDs.
*/
function setByIDList($idList) {
$has = array();
// Index current data
if($this->items) {
foreach($this->items as $item) {
$has[$item->ID] = true;
}
}
// Keep track of items to delete
$itemsToDelete = $has;
if($idList){
foreach($idList as $id) {
$itemsToDelete[$id] = false;
if(!isset($has[$id]) && $id) {
$this->add($id);
}
}
}
// Delete any unmentionedItems
if($itemsToDelete) {
$removeList = array();
foreach($itemsToDelete as $id => $actuallyDelete) {
if($actuallyDelete) $removeList[] = $id;
}
if($removeList) {
$this->removeMany($removeList);
}
}
}
/**
* Remove an item from this set.
* @param DataObject|string|int $item Item to remove, either as a DataObject or as the ID.
*/
function remove($item) {
if(is_object($item)) {
if($item->class != $this->childClass && !is_subclass_of($item, $this->childClass)) {
user_error("ComponentSet::remove() Tried to remove an '{$item->class}' object, but '{$this->childClass}' expected", E_USER_ERROR);
}
} else {
$item = DataObject::get_by_id($this->childClass, $item);
}
// Manipulate the database, if it's in there
if($this->ownerObj->ID && is_numeric($this->ownerObj->ID)) {
if($this->type == '1-to-many') {
$child = DataObject::get_by_id($this->childClass,$item->ID);
$joinField = $this->joinField;
if($child->$joinField == $this->ownerObj->ID) {
$child->$joinField = null;
$child->write();
}
} else {
$parentField = $this->ownerClass . 'ID';
$childField = ($this->childClass == $this->ownerClass) ? "ChildID" : ($this->childClass . 'ID');
DB::query("DELETE FROM `$this->tableName` WHERE $parentField = {$this->ownerObj->ID} AND $childField = {$item->ID}");
}
}
// Manipulate the in-memory array of items
if($this->items) foreach($this->items as $i => $candidateItem) {
if($candidateItem->ID == $item->ID) {
unset($this->items[$i]);
break;
}
}
}
/**
* Remove many items from this set.
* @param array $itemList The items to remove.
*/
function removeMany($itemList) {
if($this->type == '1-to-many') {
if(isset($itemList)) {
foreach($itemList as $item) $this->remove($item);
}
} else {
if(isset($itemList)) {
$itemCSV = implode(", ", $itemList);
$parentField = $this->ownerClass . 'ID';
$childField = ($this->childClass == $this->ownerClass) ? "ChildID" : ($this->childClass . 'ID');
DB::query("DELETE FROM `$this->tableName` WHERE $parentField = {$this->ownerObj->ID} AND $childField IN ($itemCSV)");
}
}
}
/**
* Remove all items that match the SQL filter.
* @param string $filter Filter to be inserted into the WHERE clause
*/
function removeByFilter($filter) {
$parentField = $this->ownerClass . 'ID';
DB::query("DELETE FROM `$this->tableName` WHERE $parentField = {$this->ownerObj->ID} AND $filter");
}
/**
* Remove all items in this set.
*/
function removeAll() {
if(!empty($this->tableName)) {
$parentField = $this->ownerClass . 'ID';
DB::query("DELETE FROM `$this->tableName` WHERE $parentField = {$this->ownerObj->ID}");
} else {
foreach($this->items as $item) {
$this->remove($item);
}
}
}
/**
* Write this set to the database.
* Called by DataObject::write().
* @param boolean $firstWrite This should be set to true if it the first time the set is being written.
*/
function write($firstWrite = false) {
if($firstWrite) {
foreach($this->items as $item) {
$this->loadChildIntoDatabase($item);
}
}
}
/**
* Returns information about this set in HTML format for debugging.
* @return string
*/
function debug() {
$size = count($this->items);
$output = <<<OUT
<h3>ComponentSet</h3>
<ul>
<li>Type: {$this->type}</li>
<li>Size: $size</li>
</ul>
OUT;
return $output;
}
}
?>