Overview

Namespaces

  • Alchemy
    • core
      • query
      • schema
    • dialect
    • engine
    • orm
    • tests
    • util
      • promise
  • PHP

Classes

  • DataMapper
  • DDL
  • ManyToOne
  • OneToMany
  • OneToOne
  • ORMQuery
  • ORMTable
  • ORMTableRef
  • RelatedSet
  • Relationship
  • Session
  • SessionSelect
  • WorkQueue
  • Overview
  • Namespace
  • Class
  • Tree
  1: <?php
  2: 
  3: namespace  Alchemy\orm;
  4: use Alchemy\core\schema\Table;
  5: use Alchemy\core\query;
  6: use Alchemy\util\promise\Promise;
  7: use Alchemy\util\DataTypeLexer;
  8: use Exception;
  9: use ReflectionClass;
 10: 
 11: 
 12: /**
 13:  * Abstract base class for custom ORM models. this class
 14:  * doesn't actually store any data, it merely acts as an
 15:  * interface between Domain objects and the Session (which
 16:  * actually owns all of the DB records).
 17:  */
 18: abstract class DataMapper {
 19: 
 20:     /**
 21:      * Optional: Define the table name here. If left null, the
 22:      * class name will be used
 23:      */
 24:     protected static $table_name = null;
 25:     protected static $schema_args = array();
 26: 
 27:     private static $schema_cache = array();
 28: 
 29:     private $deltas = array();
 30:     private $dependancies = array();
 31:     private $relatedSets = array();
 32:     private $session;
 33:     private $sessionID;
 34:     private $persisting;
 35: 
 36: 
 37:     /**
 38:      * Instantiate a domain object from the session
 39:      *
 40:      * @param Session $session The database session which owns this record
 41:      * @param mixed $sessionID A pointer to the record for this objects data in the session
 42:      */
 43:     public static function from_session(Session $session, $sessionID) {
 44:         $cls = get_called_class();
 45:         $obj = new $cls();
 46:         $obj->setSession($session, $sessionID, true);
 47:         return $obj;
 48:     }
 49: 
 50: 
 51:     /**
 52:      * List all subclasses of DataMapper. Only works for
 53:      * classes already loaded by PHP.
 54:      *
 55:      * @return array
 56:      */
 57:     public static function list_mappers() {
 58:         $result = array();
 59:         foreach (get_declared_classes() as $cls) {
 60:             if (is_subclass_of($cls, __CLASS__)) {
 61:                 $reflection = new ReflectionClass($cls);
 62:                 if (!$reflection->isAbstract()) {
 63:                     $result[] = $cls;
 64:                 }
 65:             }
 66:         }
 67: 
 68:         return $result;
 69:     }
 70: 
 71: 
 72:     /**
 73:      * Call immediately after data mapper definition
 74:      */
 75:     public static function register() {
 76:         static::schema();
 77:     }
 78: 
 79: 
 80:     /**
 81:      * Gen an instance of Table that represents the schema of this
 82:      * domain object.
 83:      *
 84:      * @return Table
 85:      */
 86:     public static function schema() {
 87:         $cls = get_called_class();
 88: 
 89:         if (!array_key_exists($cls, self::$schema_cache)) {
 90:             $name = static::table_name();
 91:             $args = static::$schema_args + array('class' => $cls);
 92: 
 93:             $tablefn = function() use ($name, $args) {
 94:                 return Table::ORM($name, $args);
 95:             };
 96: 
 97:             self::$schema_cache[$cls] = new Promise($tablefn, "Alchemy\orm\ORMTable");
 98:             Table::register_name($name, self::$schema_cache[$cls], true);
 99:             self::$schema_cache[$cls]->register(true);
100:         }
101: 
102:         return self::$schema_cache[$cls];
103:     }
104: 
105: 
106:     /**
107:      * Return a table reference for this model
108:      *
109:      * @return ORMTableRef
110:      */
111:     public static function table() {
112:         return new ORMTableRef(static::schema());
113:     }
114: 
115: 
116:     /**
117:      * Get the table name for this mapper
118:      *
119:      * @return string Table Name
120:      */
121:     public static function table_name() {
122:         $cls = static::$table_name ?: get_called_class();
123:         $cls = preg_replace("/[^A-Za-z0-9]/", "_", $cls);
124:         return $cls;
125:     }
126: 
127: 
128: 
129:     /**
130:      * Object constructor.
131:      */
132:     public function __construct() {
133:         $this->persisting = new Promise();
134:         foreach (static::schema()->listRelationships() as $name => $r) {
135:             $this->relatedSets[$name] = new RelatedSet($this, $r);
136:         }
137:     }
138: 
139: 
140:     /**
141:      * Get the value of a column on this object
142:      *
143:      * @param string $prop
144:      * @return mixed
145:      */
146:     public function __get($prop) {
147:         if (array_key_exists($prop, $this->relatedSets)) {
148:             $set = $this->getRelatedSet($prop);
149:             return $set->isSingleObject() ? $set->first() : $set;
150:         }
151: 
152:         if (!static::schema()->hasColumn($prop)) {
153:             throw new Exception("Property [{$prop}] is not a configured column");
154:         }
155: 
156:         $cls = get_called_class();
157:         if (array_key_exists($prop, $this->deltas)) {
158:             return $this->deltas[$prop];
159:         }
160: 
161:         if (is_object($this->session)) {
162:             return $this->session->getProperty($cls, $this->sessionID, $prop);
163:         }
164: 
165:         return null;
166:     }
167: 
168: 
169:     /**
170:      * Get the value of a column on this object. Nothing is actually written
171:      * back to the session until {@DataMapper::save()} is called.
172:      *
173:      * @param string $prop Column Name
174:      * @param mixed $value
175:      */
176:     public function __set($prop, $value) {
177:         if (array_key_exists($prop, $this->relatedSets)) {
178:             $this->relatedSets[$prop]->set($value);
179:             return;
180:         }
181: 
182:         if (!static::schema()->hasColumn($prop)) {
183:             throw new Exception("Property [{$prop}] is not a configured column");
184:         }
185: 
186:         $this->deltas[$prop] = $value;
187:     }
188: 
189: 
190:     /**
191:      * String repr of object
192:      *
193:      * @return string
194:      */
195:     public function __toString() {
196:         $s = '<' . get_class($this) . ' ';
197:         $s .= implode(', ', $this->getPrimaryKey());
198:         $s = trim($s);
199:         $s .= '>';
200:         return $s;
201:     }
202: 
203: 
204:     /**
205:      * Add an object to the list of objects that must be persisted
206:      * before this object can be persisted.
207:      *
208:      * @param DataMapper $obj
209:      */
210:     protected function addPersistanceDependancy(DataMapper $obj) {
211:         if ($this->isTransient()) {
212:             $this->dependancies[] = $obj;
213:         }
214:     }
215: 
216: 
217:     /**
218:      * Automatically apply properties of this object to another's foreign keys
219:      * according to a Relationship.
220:      *
221:      * @param  DataMapper $child object to affect
222:      * @param  Relationship $rel defines what properties to affect on the child
223:      * @return Promise           resolves when FK is cascaded
224:      */
225:     public function cascadeForeignKey($child, $rel) {
226:         $child->addPersistanceDependancy($this);
227: 
228:         return $this->persisting->then(function($self) use ($child, $rel) {
229:             $child->set($rel->getRemoteColumnMap($self));
230: 
231:             if ($child->getSession()) {
232:                 $child->save(!$child->isTransient());
233:             }
234: 
235:             return $self;
236:         });
237:     }
238: 
239: 
240:     /**
241:      * Return a set of values which represent this object's
242:      * primary key.
243:      *
244:      * @return array
245:      */
246:     public function getPrimaryKey() {
247:         $pk = array();
248:         foreach (static::schema()->getPrimaryKey()->listColumns() as $column) {
249:             $pk[] = $this->{$column->getName()};
250:         }
251: 
252:         return $pk;
253:     }
254: 
255: 
256:     /**
257:      * Return a related set object by relationship name
258:      */
259:     public function getRelatedSet($name) {
260:         return $this->relatedSets[$name];
261:     }
262: 
263: 
264:     /**
265:      * Return the Session of this object
266:      *
267:      * @return Session
268:      */
269:     public function getSession() {
270:         return $this->session;
271:     }
272: 
273: 
274:     /**
275:      * Return the ID used to identify this objects record in the Session
276:      *
277:      * @return string
278:      */
279:     public function getSessionID() {
280:         return $this->sessionID;
281:     }
282: 
283: 
284:     /**
285:      * Return true if this model doesn't yet exist in the database
286:      *
287:      * @return bool
288:      */
289:     public function isTransient() {
290:         foreach ($this->getPrimaryKey() as $value) {
291:             if (is_null($value)) {
292:                 return true;
293:             }
294:         }
295: 
296:         return false;
297:     }
298: 
299: 
300:     /**
301:      * Return a promise resolved when all objects that this object references
302:      * by foreign key have been persisted
303:      *
304:      * @return Promise
305:      */
306:     public function onDependanciesPersisted() {
307:         $promises = array();
308:         foreach ($this->dependancies as $d) {
309:             $promises[] = $d->onPersisted();
310:         }
311: 
312:         return Promise::when($promises);
313:     }
314: 
315: 
316:     /**
317:      * Return a Promise for when this object is actually persisted,
318:      * if it is not already.
319:      *
320:      * @return Promise resolves to $this when this DataMapper is persisted
321:      */
322:     public function onPersisted() {
323:         return $this->persisting;
324:     }
325: 
326: 
327:     /**
328:      * Revert all unsaved changed to this model
329:      */
330:     public function rollback() {
331:         $this->deltas = array();
332:     }
333: 
334: 
335:     /**
336:      * Save this object's data back to the session. By default this will queue
337:      * an UPDATE statement to be run on the server as soon as {@Session::commit()}
338:      * is called.
339:      *
340:      * @param bool $queueUpdate Default's to true
341:      */
342:     public function save($queueUpdate = true) {
343:         if (!$this->session) {
344:             throw new Exception("Can not save this DataMapper because it is not associated with a session");
345:         }
346: 
347:         $cls = get_class($this);
348:         foreach ($this->deltas as $prop => $value) {
349:             $this->session->setProperty($cls, $this->sessionID, $prop, $value);
350:         }
351: 
352:         if ($queueUpdate) {
353:             $this->session->save($cls, $this->sessionID);
354:         }
355: 
356:         $this->deltas = array();
357:     }
358: 
359: 
360:     /**
361:      * Set multiple properties on this object.
362:      *
363:      * @param array $map [Property => Value, ...]
364:      */
365:     public function set(array $map) {
366:         foreach ($map as $key => $value) {
367:             $this->{$key} = $value;
368:         }
369: 
370:         return $this;
371:     }
372: 
373: 
374:     /**
375:      * Set the Session and Session pointer for this object
376:      *
377:      * @param Session $session Session which owns this object
378:      * @param string $sessionID Pointer to data record in $session
379:      */
380:     public function setSession(Session $session, $sessionID, $persisted = false) {
381:         $this->session = $session;
382:         $this->sessionID = $sessionID;
383: 
384:         if ($persisted) {
385:             $this->persisting->resolve($this);
386:             $this->dependancies = array();
387:         }
388:     }
389: }
390: 
API documentation generated by ApiGen 2.8.0