/sapphire/core/model/ComponentSet.php

https://github.com/anathor/SilverStripeCantabileMusic · PHP · 307 lines · 163 code · 36 blank · 108 comment · 41 complexity · 17e30935383a9c5e00f7a6b898923f8c MD5 · raw file

  1. <?php
  2. /**
  3. * This is a special kind of DataObjectSet used to represent the items linked to in a 1-many or many-many
  4. * join. It provides add and remove methods that will update the database.
  5. * @package sapphire
  6. * @subpackage model
  7. */
  8. class ComponentSet extends DataObjectSet {
  9. /**
  10. * Type of relationship (eg '1-1', '1-many').
  11. * @var string
  12. */
  13. protected $type;
  14. /**
  15. * Object that owns this set.
  16. * @var DataObject
  17. */
  18. protected $ownerObj;
  19. /**
  20. * Class of object that owns this set.
  21. * @var string
  22. */
  23. protected $ownerClass;
  24. /**
  25. * Table that holds this relationship.
  26. * @var string
  27. */
  28. protected $tableName;
  29. /**
  30. * Class of child side of the relationship.
  31. * @var string
  32. */
  33. protected $childClass;
  34. /**
  35. * Field to join on.
  36. * @var string
  37. */
  38. protected $joinField;
  39. /**
  40. * Set the ComponentSet specific information.
  41. * @param string $type Type of relationship (eg '1-1', '1-many').
  42. * @param DataObject $ownerObj Object that owns this set.
  43. * @param string $ownerClass Class of object that owns this set.
  44. * @param string $tableName Table that holds this relationship.
  45. * @param string $childClass Class of child side of the relationship.
  46. * @param string $joinField Field to join on.
  47. */
  48. function setComponentInfo($type, $ownerObj, $ownerClass, $tableName, $childClass, $joinField = null) {
  49. $this->type = $type;
  50. $this->ownerObj = $ownerObj;
  51. $this->ownerClass = $ownerClass ? $ownerClass : $ownerObj->class;
  52. $this->tableName = $tableName;
  53. $this->childClass = $childClass;
  54. $this->joinField = $joinField;
  55. }
  56. /**
  57. * Get the ComponentSet specific information
  58. *
  59. * Returns an array on the format array(
  60. * 'type' => <string>,
  61. * 'ownerObj' => <Object>,
  62. * 'ownerClass' => <string>,
  63. * 'tableName' => <string>,
  64. * 'childClass' => <string>,
  65. * 'joinField' => <string>|null );
  66. *
  67. * @return array
  68. */
  69. public function getComponentInfo() {
  70. return array(
  71. 'type' => $this->type,
  72. 'ownerObj' => $this->ownerObj,
  73. 'ownerClass' => $this->ownerClass,
  74. 'tableName' => $this->tableName,
  75. 'childClass' => $this->childClass,
  76. 'joinField' => $this->joinField
  77. );
  78. }
  79. /**
  80. * Get an array of all the IDs in this component set, where the keys are the same as the
  81. * values.
  82. * @return array
  83. */
  84. function getIdList() {
  85. $list = array();
  86. foreach($this->items as $item) {
  87. $list[$item->ID] = $item->ID;
  88. }
  89. return $list;
  90. }
  91. /**
  92. * Add an item to this set.
  93. * @param DataObject|int|string $item Item to add, either as a DataObject or as the ID.
  94. * @param array $extraFields A map of extra fields to add.
  95. */
  96. function add($item, $extraFields = null) {
  97. if(!isset($item)) {
  98. user_error("ComponentSet::add() Not passed an object or ID", E_USER_ERROR);
  99. }
  100. if(is_object($item)) {
  101. if(!is_a($item, $this->childClass)) {
  102. user_error("ComponentSet::add() Tried to add an '{$item->class}' object, but a '{$this->childClass}' object expected", E_USER_ERROR);
  103. }
  104. } else {
  105. if(!$this->childClass) {
  106. user_error("ComponentSet::add() \$this->childClass not set", E_USER_ERROR);
  107. }
  108. $item = DataObject::get_by_id($this->childClass, $item);
  109. if(!$item) return;
  110. }
  111. // If we've already got a database object, then update the database
  112. if($this->ownerObj->ID && is_numeric($this->ownerObj->ID)) {
  113. $this->loadChildIntoDatabase($item, $extraFields);
  114. }
  115. // In either case, add something to $this->items
  116. $this->items[] = $item;
  117. }
  118. /**
  119. * Method to save many-many join data into the database for the given $item.
  120. * Used by add() and write().
  121. * @param DataObject|string|int The item to save, as either a DataObject or the ID.
  122. * @param array $extraFields Map of extra fields.
  123. */
  124. protected function loadChildIntoDatabase($item, $extraFields = null) {
  125. if($this->type == '1-to-many') {
  126. $child = DataObject::get_by_id($this->childClass,$item->ID);
  127. if (!$child) $child = $item;
  128. $joinField = $this->joinField;
  129. $child->$joinField = $this->ownerObj->ID;
  130. $child->write();
  131. } else {
  132. $parentField = $this->ownerClass . 'ID';
  133. $childField = ($this->childClass == $this->ownerClass) ? "ChildID" : ($this->childClass . 'ID');
  134. DB::query( "DELETE FROM \"$this->tableName\" WHERE \"$parentField\" = {$this->ownerObj->ID} AND \"$childField\" = {$item->ID}" );
  135. $extraKeys = $extraValues = '';
  136. if($extraFields) foreach($extraFields as $k => $v) {
  137. $extraKeys .= ", \"$k\"";
  138. $extraValues .= ", '" . DB::getConn()->addslashes($v) . "'";
  139. }
  140. DB::query("INSERT INTO \"$this->tableName\" (\"$parentField\",\"$childField\" $extraKeys) VALUES ({$this->ownerObj->ID}, {$item->ID} $extraValues)");
  141. }
  142. }
  143. /**
  144. * Add a number of items to the component set.
  145. * @param array $items Items to add, as either DataObjects or IDs.
  146. */
  147. function addMany($items) {
  148. foreach($items as $item) {
  149. $this->add($item);
  150. }
  151. }
  152. /**
  153. * Sets the ComponentSet to be the given ID list.
  154. * Records will be added and deleted as appropriate.
  155. * @param array $idList List of IDs.
  156. */
  157. function setByIDList($idList) {
  158. $has = array();
  159. // Index current data
  160. if($this->items) foreach($this->items as $item) {
  161. $has[$item->ID] = true;
  162. }
  163. // Keep track of items to delete
  164. $itemsToDelete = $has;
  165. // add items in the list
  166. // $id is the database ID of the record
  167. if($idList) foreach($idList as $id) {
  168. $itemsToDelete[$id] = false;
  169. if($id && !isset($has[$id])) $this->add($id);
  170. }
  171. // delete items not in the list
  172. $removeList = array();
  173. foreach($itemsToDelete as $id => $actuallyDelete) {
  174. if($actuallyDelete) $removeList[] = $id;
  175. }
  176. $this->removeMany($removeList);
  177. }
  178. /**
  179. * Remove an item from this set.
  180. *
  181. * @param DataObject|string|int $item Item to remove, either as a DataObject or as the ID.
  182. */
  183. function remove($item) {
  184. if(is_object($item)) {
  185. if(!is_a($item, $this->childClass)) {
  186. user_error("ComponentSet::remove() Tried to remove an '{$item->class}' object, but a '{$this->childClass}' object expected", E_USER_ERROR);
  187. }
  188. } else {
  189. $item = DataObject::get_by_id($this->childClass, $item);
  190. }
  191. // Manipulate the database, if it's in there
  192. if($this->ownerObj->ID && is_numeric($this->ownerObj->ID)) {
  193. if($this->type == '1-to-many') {
  194. $child = DataObject::get_by_id($this->childClass,$item->ID);
  195. $joinField = $this->joinField;
  196. if($child->$joinField == $this->ownerObj->ID) {
  197. $child->$joinField = null;
  198. $child->write();
  199. }
  200. } else {
  201. $parentField = $this->ownerClass . 'ID';
  202. $childField = ($this->childClass == $this->ownerClass) ? "ChildID" : ($this->childClass . 'ID');
  203. DB::query("DELETE FROM \"$this->tableName\" WHERE \"$parentField\" = {$this->ownerObj->ID} AND \"$childField\" = {$item->ID}");
  204. }
  205. }
  206. // Manipulate the in-memory array of items
  207. if($this->items) foreach($this->items as $i => $candidateItem) {
  208. if($candidateItem->ID == $item->ID) {
  209. unset($this->items[$i]);
  210. break;
  211. }
  212. }
  213. }
  214. /**
  215. * Remove many items from this set.
  216. * @param array $itemList The items to remove, as a numerical array with IDs or as a DataObjectSet
  217. */
  218. function removeMany($itemList) {
  219. if(!count($itemList)) return false;
  220. if($this->type == '1-to-many') {
  221. foreach($itemList as $item) $this->remove($item);
  222. } else {
  223. $itemCSV = implode(", ", $itemList);
  224. $parentField = $this->ownerClass . 'ID';
  225. $childField = ($this->childClass == $this->ownerClass) ? "ChildID" : ($this->childClass . 'ID');
  226. DB::query("DELETE FROM \"$this->tableName\" WHERE \"$parentField\" = {$this->ownerObj->ID} AND \"$childField\" IN ($itemCSV)");
  227. }
  228. }
  229. /**
  230. * Remove all items in this set.
  231. */
  232. function removeAll() {
  233. if(!empty($this->tableName)) {
  234. $parentField = $this->ownerClass . 'ID';
  235. DB::query("DELETE FROM \"$this->tableName\" WHERE \"$parentField\" = {$this->ownerObj->ID}");
  236. } else {
  237. foreach($this->items as $item) {
  238. $this->remove($item);
  239. }
  240. }
  241. }
  242. /**
  243. * Write this set to the database.
  244. * Called by DataObject::write().
  245. * @param boolean $firstWrite This should be set to true if it the first time the set is being written.
  246. */
  247. function write($firstWrite = false) {
  248. if($firstWrite) {
  249. foreach($this->items as $item) {
  250. $this->loadChildIntoDatabase($item);
  251. }
  252. }
  253. }
  254. /**
  255. * Returns information about this set in HTML format for debugging.
  256. *
  257. * @return string
  258. */
  259. function debug() {
  260. $size = count($this->items);
  261. $output = <<<OUT
  262. <h3>ComponentSet</h3>
  263. <ul>
  264. <li>Type: {$this->type}</li>
  265. <li>Size: $size</li>
  266. </ul>
  267. OUT;
  268. return $output;
  269. }
  270. }
  271. ?>