Overview

Packages

  • Rdo

Classes

  • Horde_Rdo
  • Horde_Rdo_Base
  • Horde_Rdo_Exception
  • Horde_Rdo_Iterator
  • Horde_Rdo_List
  • Horde_Rdo_Mapper
  • Horde_Rdo_Query
  • Horde_Rdo_Query_Literal
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * @category Horde
  4:  * @package Rdo
  5:  */
  6: 
  7: /**
  8:  * Horde_Rdo_Base abstract class (Rampage Data Objects). Entity
  9:  * classes extend this baseline.
 10:  *
 11:  * @category Horde
 12:  * @package Rdo
 13:  */
 14: abstract class Horde_Rdo_Base implements IteratorAggregate, ArrayAccess
 15: {
 16:     /**
 17:      * The Horde_Rdo_Mapper instance associated with this Rdo object. The
 18:      * Mapper takes care of all backend access.
 19:      *
 20:      * @see Horde_Rdo_Mapper
 21:      * @var Horde_Rdo_Mapper
 22:      */
 23:     protected $_mapper;
 24: 
 25:     /**
 26:      * This object's fields.
 27:      *
 28:      * @var array
 29:      */
 30:     protected $_fields = array();
 31: 
 32:     /**
 33:      * Constructor. Can be called directly by a programmer, or is
 34:      * called in Horde_Rdo_Mapper::map(). Takes an associative array
 35:      * of initial object values.
 36:      *
 37:      * @param array $fields Initial values for the new object.
 38:      *
 39:      * @see Horde_Rdo_Mapper::map()
 40:      */
 41:     public function __construct($fields = array())
 42:     {
 43:         $this->setFields($fields);
 44:     }
 45: 
 46:     /**
 47:      * When Rdo objects are cloned, unset the unique id that
 48:      * identifies them so that they can be modified and saved to the
 49:      * backend as new objects. If you don't really want a new object,
 50:      * don't clone.
 51:      */
 52:     public function __clone()
 53:     {
 54:         // @TODO Support composite primary keys
 55:         unset($this->{$this->getMapper()->primaryKey});
 56: 
 57:         // @TODO What about associated objects?
 58:     }
 59: 
 60:     /**
 61:      * Fetch fields that haven't yet been loaded. Lazy-loaded fields
 62:      * and lazy-loaded relationships are handled this way. Once a
 63:      * field is retrieved, it is cached in the $_fields array so it
 64:      * doesn't need to be fetched again.
 65:      *
 66:      * @param string $field The name of the field to access.
 67:      *
 68:      * @return mixed The value of $field or null.
 69:      */
 70:     public function __get($field)
 71:     {
 72:         // Honor any explicit getters.
 73:         $fieldMethod = 'get' . ucfirst($field);
 74:         // If an Rdo_Base subclass has a __call() method, is_callable
 75:         // returns true on every method name, so use method_exists
 76:         // instead.
 77:         if (method_exists($this, $fieldMethod)) {
 78:             return call_user_func(array($this, $fieldMethod));
 79:         }
 80: 
 81:         if (isset($this->_fields[$field])) {
 82:             return $this->_fields[$field];
 83:         }
 84: 
 85:         $mapper = $this->getMapper();
 86: 
 87:         // Look for lazy fields first, then relationships.
 88:         if (in_array($field, $mapper->lazyFields)) {
 89:             // @TODO Support composite primary keys
 90:             $query = new Horde_Rdo_Query($mapper);
 91:             $query->setFields($field)
 92:                    ->addTest($mapper->primaryKey, '=', $this->{$mapper->primaryKey});
 93:             list($sql, $params) = $query->getQuery();
 94:             $this->_fields[$field] = $mapper->adapter->selectValue($sql, $params);;
 95:             return $this->_fields[$field];
 96:         } elseif (isset($mapper->lazyRelationships[$field])) {
 97:             $rel = $mapper->lazyRelationships[$field];
 98:         } else {
 99:             return null;
100:         }
101: 
102:         // Try to find the Mapper class for the object the
103:         // relationship is with, and fail if we can't.
104:         if (isset($rel['mapper'])) {
105:             // @TODO - should be getting this instance from somewhere
106:             // else external, and not passing the adapter along
107:             // automatically.
108:             $m = new $rel['mapper']($mapper->adapter);
109:         } else {
110:             $m = $mapper->tableToMapper($field);
111:             if (is_null($m)) {
112:                 return null;
113:             }
114:         }
115: 
116:         // Based on the kind of relationship, fetch the appropriate
117:         // objects and fill the cache.
118:         switch ($rel['type']) {
119:         case Horde_Rdo::ONE_TO_ONE:
120:         case Horde_Rdo::MANY_TO_ONE:
121:             if (isset($rel['query'])) {
122:                 $query = $this->_fillPlaceholders($rel['query']);
123:                 $this->_fields[$field] = $m->findOne($query);
124:             } elseif (!empty($this->{$rel['foreignKey']})) {
125:                 $this->_fields[$field] = $m->findOne($this->{$rel['foreignKey']});
126:                 if (empty($this->_fields[$field])) {
127:                     throw new Horde_Rdo_Exception('The referenced object with key ' . $this->{$rel['foreignKey']} . ' does not exist. Your data is inconsistent');
128:                 }
129:             } else {
130:                 $this->_fields[$field] = null;
131:             }
132:             break;
133: 
134:         case Horde_Rdo::ONE_TO_MANY:
135:             $this->_fields[$field] = $m->find(array($rel['foreignKey'] => $this->{$rel['foreignKey']}));
136:             break;
137: 
138:         case Horde_Rdo::MANY_TO_MANY:
139:             $key = $mapper->primaryKey;
140:             $query = new Horde_Rdo_Query();
141:             $on = isset($rel['on']) ? $rel['on'] : $m->primaryKey;
142:             $query->addRelationship($field, array('mapper' => $mapper,
143:                                                   'table' => $rel['through'],
144:                                                   'type' => Horde_Rdo::MANY_TO_MANY,
145:                                                   'query' => array("$m->table.$on" => new Horde_Rdo_Query_Literal($rel['through'] . '.' . $on), $key => $this->$key)));
146:             $this->_fields[$field] = $m->find($query);
147:             break;
148:         }
149: 
150:         return $this->_fields[$field];
151:     }
152: 
153:     /**
154:      * Implements getter for ArrayAccess interface.
155:      *
156:      * @see __get()
157:      */
158:     public function offsetGet($field)
159:     {
160:         return $this->__get($field);
161:     }
162: 
163:     /**
164:      * Set a field's value.
165:      *
166:      * @param string $field The field to set
167:      * @param mixed $value The field's new value
168:      */
169:     public function __set($field, $value)
170:     {
171:         // Honor any explicit setters.
172:         $fieldMethod = 'set' . ucfirst($field);
173:         // If an Rdo_Base subclass has a __call() method, is_callable
174:         // returns true on every method name, so use method_exists
175:         // instead.
176:         if (method_exists($this, $fieldMethod)) {
177:             return call_user_func(array($this, $fieldMethod), $value);
178:         }
179: 
180:         $this->_fields[$field] = $value;
181:     }
182: 
183:     /**
184:      * Implements setter for ArrayAccess interface.
185:      *
186:      * @see __set()
187:      */
188:     public function offsetSet($field, $value)
189:     {
190:         $this->__set($field, $value);
191:     }
192: 
193:     /**
194:      * Allow using isset($rdo->foo) to check for field or
195:      * relationship presence.
196:      *
197:      * @param string $field The field name to check existence of.
198:      */
199:     public function __isset($field)
200:     {
201:         $m = $this->getMapper();
202:         return isset($this->_fields[$field])
203:             || isset($m->fields[$field])
204:             || isset($m->lazyFields[$field])
205:             || isset($m->relationships[$field])
206:             || isset($m->lazyRelationships[$field]);
207:     }
208: 
209:     /**
210:      * Implements isset() for ArrayAccess interface.
211:      *
212:      * @see __isset()
213:      */
214:     public function offsetExists($field)
215:     {
216:         return $this->__isset($field);
217:     }
218: 
219:     /**
220:      * Allow using unset($rdo->foo) to unset a basic
221:      * field. Relationships cannot be unset in this way.
222:      *
223:      * @param string $field The field name to unset.
224:      */
225:     public function __unset($field)
226:     {
227:         // @TODO Should unsetting a MANY_TO_MANY relationship remove
228:         // the relationship?
229:         unset($this->_fields[$field]);
230:     }
231: 
232:     /**
233:      * Implements unset() for ArrayAccess interface.
234:      *
235:      * @see __unset()
236:      */
237:     public function offsetUnset($field)
238:     {
239:         $this->__unset($field);
240:     }
241: 
242:     /**
243:      * Set field values for the object
244:      *
245:      * @param array $fields Initial values for the new object.
246:      *
247:      * @see Horde_Rdo_Mapper::map()
248:      */
249:     public function setFields($fields = array())
250:     {
251:         $this->_fields = $fields;
252:     }
253: 
254:     /**
255:      * Implement the IteratorAggregate interface. Looping over an Rdo
256:      * object goes through each property of the object in turn.
257:      *
258:      * @return Horde_Rdo_Iterator The Iterator instance.
259:      */
260:     public function getIterator()
261:     {
262:         return new Horde_Rdo_Iterator($this);
263:     }
264: 
265:     /**
266:      * Get a Mapper instance that can be used to manage this
267:      * object. The Mapper instance can come from a few places:
268:      *
269:      * - If the class <RdoClassName>Mapper exists, it will be used
270:      *   automatically.
271:      *
272:      * - Any Rdo instance created with Horde_Rdo_Mapper::map() will have a
273:      *   $mapper object set automatically.
274:      *
275:      * - Subclasses can override getMapper() to return the correct
276:      *   mapper object.
277:      *
278:      * - The programmer can call $rdoObject->setMapper($mapper) to provide a
279:      *   mapper object.
280:      *
281:      * A Horde_Rdo_Exception will be thrown if none of these
282:      * conditions are met.
283:      *
284:      * @return Horde_Rdo_Mapper The Mapper instance managing this object.
285:      */
286:     public function getMapper()
287:     {
288:         if (!$this->_mapper) {
289:             $class = get_class($this) . 'Mapper';
290:             if (class_exists($class)) {
291:                 $this->_mapper = new $class();
292:             } else {
293:                 throw new Horde_Rdo_Exception('No Horde_Rdo_Mapper object found. Override getMapper() or define the ' . get_class($this) . 'Mapper class.');
294:             }
295:         }
296: 
297:         return $this->_mapper;
298:     }
299: 
300:     /**
301:      * Associate this Rdo object with the Mapper instance that will
302:      * manage it. Called automatically by Horde_Rdo_Mapper:map().
303:      *
304:      * @param Horde_Rdo_Mapper $mapper The Mapper to manage this Rdo object.
305:      *
306:      * @see Horde_Rdo_Mapper::map()
307:      */
308:     public function setMapper($mapper)
309:     {
310:         $this->_mapper = $mapper;
311:     }
312: 
313:     /**
314:      * Save any changes to the backend.
315:      *
316:      * @return boolean Success.
317:      */
318:     public function save()
319:     {
320:         return $this->getMapper()->update($this) == 1;
321:     }
322: 
323:     /**
324:      * Delete this object from the backend.
325:      *
326:      * @return boolean Success or failure.
327:      */
328:     public function delete()
329:     {
330:         return $this->getMapper()->delete($this) == 1;
331:     }
332: 
333:     /**
334:      * Take a query array and replace @field@ placeholders with values
335:      * from this object.
336:      *
337:      * @param array $query The query to process placeholders on.
338:      *
339:      * @return array The query with placeholders filled in.
340:      */
341:     protected function _fillPlaceholders($query)
342:     {
343:         foreach (array_keys($query) as $field) {
344:             $value = $query[$field];
345:             if (preg_match('/^@(.*)@$/', $value, $matches)) {
346:                 $query[$field] = $this->{$matches[1]};
347:             }
348:         }
349: 
350:         return $query;
351:     }
352: 
353: }
354: 
API documentation generated by ApiGen