1 <?php
2 /**
3 * Apply this interface to any {@link DBField} that doesn't have a 1-1 mapping with a database field.
4 * This includes multi-value fields and transformed fields
5 *
6 * @todo Unittests for loading and saving composite values (see GIS module for existing similiar unittests)
7 *
8 * Example with a combined street name and number:
9 * <code>
10 * class Street extends DBFields implements CompositeDBField() {
11 * protected $streetNumber;
12 * protected $streetName;
13 * protected $isChanged = false;
14 * static $composite_db = return array(
15 * "Number" => "Int",
16 * "Name" => "Text"
17 * );
18 *
19 * function requireField() {
20 * DB::requireField($this->tableName, "{$this->name}Number", 'Int');
21 * DB::requireField($this->tableName, "{$this->name}Name", 'Text');
22 * }
23 *
24 * function writeToManipulation(&$manipulation) {
25 * if($this->getStreetName()) {
26 * $manipulation['fields']["{$this->name}Name"] = $this->prepValueForDB($this->getStreetName());
27 * } else {
28 * $manipulation['fields']["{$this->name}Name"] = DBField::create('Varchar', $this->getStreetName())->nullValue();
29 * }
30 *
31 * if($this->getStreetNumber()) {
32 * $manipulation['fields']["{$this->name}Number"] = $this->prepValueForDB($this->getStreetNumber());
33 * } else {
34 * $manipulation['fields']["{$this->name}Number"] = DBField::create('Int', $this->getStreetNumber())->nullValue();
35 * }
36 * }
37 *
38 * function addToQuery(&$query) {
39 * parent::addToQuery($query);
40 * $query->select[] = "{$this->name}Number";
41 * $query->select[] = "{$this->name}Name";
42 * }
43 *
44 * function setValue($value, $record = null, $markChanged=true) {
45 * if ($value instanceof Street && $value->hasValue()) {
46 * $this->setStreetName($value->getStreetName(), $markChanged);
47 * $this->setStreetNumber($value->getStreetNumber(), $markChanged);
48 * if($markChanged) $this->isChanged = true;
49 * } else if($record && isset($record[$this->name . 'Name']) && isset($record[$this->name . 'Number'])) {
50 * if($record[$this->name . 'Name'] && $record[$this->name . 'Number']) {
51 * $this->setStreetName($record[$this->name . 'Name'], $markChanged);
52 * $this->setStreetNumber($record[$this->name . 'Number'], $markChanged);
53 * }
54 * if($markChanged) $this->isChanged = true;
55 * } else if (is_array($value)) {
56 * if (array_key_exists('Name', $value)) {
57 * $this->setStreetName($value['Name'], $markChanged);
58 * }
59 * if (array_key_exists('Number', $value)) {
60 * $this->setStreetNumber($value['Number'], $markChanged);
61 * }
62 * if($markChanged) $this->isChanged = true;
63 * }
64 * }
65 *
66 * function setStreetNumber($val, $markChanged=true) {
67 * $this->streetNumber = $val;
68 * if($markChanged) $this->isChanged = true;
69 * }
70 *
71 * function setStreetName($val, $markChanged=true) {
72 * $this->streetName = $val;
73 * if($markChanged) $this->isChanged = true;
74 * }
75 *
76 * function getStreetNumber() {
77 * return $this->streetNumber;
78 * }
79 *
80 * function getStreetName() {
81 * return $this->streetName;
82 * }
83 *
84 * function isChanged() {
85 * return $this->isChanged;
86 * }
87 *
88 * function hasValue() {
89 * return ($this->getStreetName() || $this->getStreetNumber());
90 * }
91 * }
92 * </code>
93 *
94 * @package sapphire
95 * @subpackage model
96 */
97 interface CompositeDBField {
98
99 /**
100 * Similiar to {@link DataObject::$db},
101 * holds an array of composite field names.
102 * Don't include the fields "main name",
103 * it will be prefixed in {@link requireField()}.
104 *
105 * @var array $composite_db
106 */
107 //static $composite_db;
108
109 /**
110 * Set the value of this field in various formats.
111 * Used by {@link DataObject->getField()}, {@link DataObject->setCastedField()}
112 * {@link DataObject->dbObject()} and {@link DataObject->write()}.
113 *
114 * As this method is used both for initializing the field after construction,
115 * and actually changing its values, it needs a {@link $markChanged}
116 * parameter.
117 *
118 * @param DBField|array $value
119 * @param array $record Map of values loaded from the database
120 * @param boolean $markChanged Indicate wether this field should be marked changed.
121 * Set to FALSE if you are initializing this field after construction, rather
122 * than setting a new value.
123 */
124 function setValue($value, $record = null, $markChanged = true);
125
126 /**
127 * Used in constructing the database schema.
128 * Add any custom properties defined in {@link $composite_db}.
129 * Should make one or more calls to {@link DB::requireField()}.
130 */
131 //abstract function requireField();
132
133 /**
134 * Add the custom internal values to an INSERT or UPDATE
135 * request passed through the ORM with {@link DataObject->write()}.
136 * Fields are added in $manipulation['fields']. Please ensure
137 * these fields are escaped for database insertion, as no
138 * further processing happens before running the query.
139 * Use {@link DBField->prepValueForDB()}.
140 * Ensure to write NULL or empty values as well to allow
141 * unsetting a previously set field. Use {@link DBField->nullValue()}
142 * for the appropriate type.
143 *
144 * @param array $manipulation
145 */
146 function writeToManipulation(&$manipulation);
147
148 /**
149 * Add all columns which are defined through {@link requireField()}
150 * and {@link $composite_db}, or any additional SQL that is required
151 * to get to these columns. Will mostly just write to the {@link SQLQuery->select}
152 * array.
153 *
154 * @param SQLQuery $query
155 */
156 function addToQuery(&$query);
157
158 /**
159 * Return array in the format of {@link $composite_db}.
160 * Used by {@link DataObject->hasOwnDatabaseField()}.
161 * @return array
162 */
163 function compositeDatabaseFields();
164
165 /**
166 * Determines if the field has been changed since its initialization.
167 * Most likely relies on an internal flag thats changed when calling
168 * {@link setValue()} or any other custom setters on the object.
169 *
170 * @return boolean
171 */
172 function isChanged();
173
174 /**
175 * Determines if any of the properties in this field have a value,
176 * meaning at least one of them is not NULL.
177 *
178 * @return boolean
179 */
180 function hasValue();
181
182 }