Highway to PSR2
[openemr.git] / portal / patient / fwk / libs / verysimple / Phreeze / Phreezer.php
blobe0c17cd4a6c5167b397214f425a938033d93ffd0
1 <?php
2 /** @package verysimple::Phreeze */
4 /**
5 * import supporting libraries
6 */
7 require_once("Observable.php");
8 require_once("Criteria.php");
9 require_once("DataAdapter.php");
10 require_once("CacheRam.php");
11 require_once("CacheNoCache.php");
12 require_once("verysimple/IO/Includer.php");
14 /**
15 * The Phreezer class is a factory for obtaining and working with Phreezable (persistable)
16 * objects.
17 * The Phreezer is generally the starting point for the application where you
18 * will obtain one or more objects.
20 * @package verysimple::Phreeze
21 * @author VerySimple Inc.
22 * @copyright 1997-2008 VerySimple, Inc.
23 * @license http://www.gnu.org/licenses/lgpl.html LGPL
24 * @version 3.3.8
26 class Phreezer extends Observable
28 /**
29 * An associative array of DataAdapter objects, which can be
30 * specified using SelectAdapter
32 * @var Array
34 public $DataAdapters;
36 /**
37 * The currently selected DataAdapter
39 * @var DataAdapter
41 public $DataAdapter;
43 /**
44 * Render engine can hold any arbitrary object used to render views
46 * @var Smarty
48 public $RenderEngine;
49 public static $Version = '3.3.8 HEAD';
51 /** @var bool set to true to enable compatibility with phreeze 2.0 apps */
52 public static $COMPAT_VERSION_2 = false;
54 /**
56 * @var int expiration time for query & value cache (in seconds) default = 5
57 * The default is a low value which will help only with floods of traffic, but
58 * will prevent stale data from appearing
60 public $ValueCacheTimeout = 5;
62 /**
64 * @var int expiration time for single objects cache (in seconds)
65 * All individual save operations will update the cache so this can be a higher value
66 * as long as other non-phreeze applications are not also editing the database
67 * and you are not doing bulk query updates.
69 public $ObjectCacheTimeout = 300; // 5 minutes
71 /**
73 * @var set to true to save each individual query object in the level-2 cache
74 * this can lead to a lot of save operations on the level-2 cahce that don't
75 * ever get read, so enable only if you know it will improve performance
77 public $CacheQueryObjectLevel2 = false;
79 /**
81 * @var string path used for saving lock files to prevent cache stampedes
83 public $LockFilePath;
85 /**
87 * @var array
89 private $_mapCache;
91 /**
93 * @var ICache
95 private $_level1Cache;
97 /**
99 * @var ICache
101 private $_level2Cache;
104 * If Phreeze is loaded from a .
105 * phar file, return the path of that file
106 * otherwise return empty string
108 * @return string
110 static function PharPath()
112 return class_exists("Phar") ? Phar::running() : '';
116 * Contructor initializes the object.
117 * The database connection is opened only when
118 * a DB call is made.
120 * The ConnectionSetting parameter can be either a single connection setting, or
121 * an associative array. This allows switching among different database connections
122 * which can be referred to by their array key using Phreezer->SelectAdapter.
123 * Multiple connections can be used for example to read from a slave database
124 * and write to a master.
126 * One instantiate the DataAdapter will be set to whichever is the first
127 * connection in the list.
129 * If a single ConnectionSetting is supplied, it will be assigned the key "default"
131 * @access public
132 * @param
133 * ConnectionSetting || Associative Array of ConnectionSetting objects
134 * @param Observable $observer
136 public function __construct($csetting, $observer = null)
138 $this->_mapCache = new CacheRam();
139 $this->_level1Cache = new CacheRam();
140 $this->_level2Cache = new CacheNoCache();
142 if ($observer) {
143 parent::AttachObserver($observer);
146 $this->Observe("Phreeze Instantiated", OBSERVE_DEBUG);
148 $csettings = is_array($csetting) ? $csetting : array (
149 'default' => $csetting
152 $this->DataAdapters = array ();
153 foreach ($csettings as $key => $connection) {
154 $this->DataAdapters [$key] = new DataAdapter($connection, $observer, null, $key);
157 $this->SelectAdapter();
161 * SelectAdapter will change the DataAdapter, allowing the application
162 * to query from multiple data sources.
163 * The connection strings for
164 * each database should be passed in an array during construction of
165 * the Phreezer object.
167 * Once this method is called, all DB calls will be made to this connection
168 * until another adapter is selected.
170 * @param string $key
171 * @return DataAdapter the selected DataAdapter
173 public function SelectAdapter($key = null)
175 if ($key) {
176 $this->Observe("Selecting DataAdapter with key '$key'", OBSERVE_DEBUG);
177 if (! array_key_exists($key, $this->DataAdapters)) {
178 throw new Exception("No DataAdapter with key '$key' is available");
181 $this->DataAdapter = $this->DataAdapters [$key];
182 } else {
183 $this->Observe("Selecting Default DataAdapter", OBSERVE_DEBUG);
184 $adapters = array_values($this->DataAdapters);
185 $this->DataAdapter = $adapters [0];
188 return $this->DataAdapter;
192 * Sets a cache provider for the level 1 cache
194 * @param ICache $cache
196 private function SetLevel1CacheProvider(ICache $cache)
198 $this->_level1Cache = $cache;
202 * Sets a cache provider for the level 1 cache
204 * @param ICache $cache
206 public function SetLevel2CacheProvider(ICache $cache, $lockFilePath = "")
208 $this->_level2Cache = $cache;
209 $this->LockFilePath = $lockFilePath;
213 * ValueCache is a utility method allowing any object or value to
214 * be stored in the cache.
215 * The timout is specified by
216 * ValueCacheTimeout. This
218 * @param string $sql
219 * @param variant $val
220 * @param
221 * int cache timeout (in seconds) default = Phreezer->ValueCacheTimeout. set to zero for no cache
222 * @return bool true if cache was set, false if not
224 public function SetValueCache($key, $val, $timeout = null)
226 if (is_null($timeout)) {
227 $timeout = $this->ValueCacheTimeout;
230 if ($timeout <= 0) {
231 return false;
234 if (strlen($key) > 250) {
235 $key = substr($key, 0, 150) . md5($key);
238 $this->_level1Cache->Set(md5($key), $val, 0, $timeout);
239 return $this->_level2Cache->Set($key, $val, 0, $timeout);
243 * Retreives an object or value that was persisted using SetValueCache
245 * @param string $key
246 * @return variant
248 public function GetValueCache($key)
250 // save the trouble of retrieving the cache if it is not enabled
251 if ($this->ValueCacheTimeout <= 0) {
252 return null;
255 if (strlen($key) > 250) {
256 $key = substr($key, 0, 150) . md5($key);
259 $obj = $this->_level1Cache->Get(md5($key));
260 return $obj ? $obj : $this->_level2Cache->Get($key);
264 * Deletes a value from the cache
266 * @param string $objectclass
267 * @param string $id
269 public function DeleteCache($objectclass, $id)
271 $this->_level1Cache->Delete($objectclass . "_" . $id);
272 $this->_level2Cache->Delete($objectclass . "_" . $id);
273 $this->Observe("Deleted TYPE='$objectclass' ID='$id' from Cache", OBSERVE_DEBUG);
277 * Sets value in the cache
279 * @param string $objectclass
280 * @param string $id
281 * @param Phreezable $val
282 * @param bool $includeCacheLevel2
283 * true = cache both level 1 and 2. false = cache only level 1. (default true)
284 * @param
285 * int optionally override the default cache timeout of Phreezer->ObjectCacheTimeout (in seconds)
287 public function SetCache($objectclass, $id, Phreezable $val, $includeCacheLevel2 = true, $timeout = null)
289 if (is_null($timeout)) {
290 $timeout = $this->ObjectCacheTimeout;
293 if ($val->NoCache() || $timeout <= 0) {
294 return false;
297 // if the object hasn't changed at level 1, then supress the cache update
298 $obj = $this->_level1Cache->Get($objectclass . "_" . $id);
300 if ($obj && $obj->serialize() == $val->serialize()) {
301 $this->Observe("TYPE='$objectclass' ID='$id' level 1 cache has not changed. SetCache was supressed", OBSERVE_DEBUG);
302 return false;
305 $this->_level1Cache->Set($objectclass . "_" . $id, $val, $timeout);
307 // cache level 2 only if specified
308 if ($includeCacheLevel2) {
309 $this->_level2Cache->Set($objectclass . "_" . $id, $val, $timeout);
314 * Retrieves a value from the cache
316 * @param string $objectclass
317 * @param string $id
318 * @return Phreezable
320 public function GetCache($objectclass, $id)
322 if ($this->ObjectCacheTimeout <= 0) {
323 return null;
326 $cachekey = $objectclass . "_" . $id;
328 // include the model so any serialized classes will not throw an exception
329 $this->IncludeModel($objectclass);
331 // see if this object was cached in the level 1 cache
332 $obj = $this->_level1Cache->Get($cachekey);
334 if ($obj) {
335 $this->Observe("Retrieved TYPE='$objectclass' ID='$id' from 1st Level Cache", OBSERVE_DEBUG);
336 $obj->CacheLevel(1);
337 if (! $obj->IsLoaded()) {
338 $obj->Refresh($this);
341 return $obj;
344 // try the level 2 cahce
345 $obj = $this->_level2Cache->Get($cachekey);
347 if ($obj) {
348 $this->Observe("Retrieved TYPE='$objectclass' ID='$id' from 2nd Level Cache", OBSERVE_DEBUG);
349 $obj->Refresh($this);
350 $obj->CacheLevel(2);
352 // we just got this from level 2, but it wasn't in level 1 so let's save it in level 1 for
353 $this->_level1Cache->Set($cachekey, $obj);
355 return $obj;
358 $this->Observe("No L1/L2 Cache for TYPE='$objectclass' ID='$id'", OBSERVE_DEBUG);
359 // $this->Observe("KEYS =" . serialize($this->_level1Cache->GetKeys()) ,OBSERVE_DEBUG);
360 return null;
364 * Override of the base AttachObserver so that when an observer is attached, it
365 * will also be attached to all child objects.
366 * Note that some initialization
367 * messages won't be observed unless you provide it in the Phreezer constructor
369 public function AttachObserver($observer)
371 parent::AttachObserver($observer);
372 foreach ($this->DataAdapters as $adapter) {
373 $adapter->AttachObserver($observer);
378 * Phreezer::Compare is used internally by Phreezer::Sort
380 * @param
381 * object
382 * @param
383 * object
384 * @return bool
386 static function Compare($a, $b)
388 return strcmp($a->ToString(), $b->ToString());
392 * Sort an array of Phreezable objects.
393 * ToString() is used as the sort
394 * key. You must implmement ToString on your sortable objects in order
395 * for Phreezer::Sort to be effective
397 * @param array $objects
398 * array of objects
400 static function Sort(&$objects)
402 usort($objects, array (
403 "Phreezer",
404 "Compare"
409 * Get one instance of an object based on criteria.
410 * If multiple records
411 * are found, only the first is returned. If no matches are found,
412 * an exception is thrown
414 * @access public
415 * @param string $objectclass
416 * the type of object that will be queried
417 * @param Criteria $criteria
418 * a Criteria object to limit results
419 * @param bool $crash_if_multiple_found
420 * default value = true
421 * @param
422 * int cache timeout (in seconds). Default is Phreezer->ValueCacheTimeout. Set to 0 for no cache
423 * @return Phreezable
425 public function GetByCriteria($objectclass, $criteria, $crash_if_multiple_found = true, $cache_timeout = null)
427 if (is_null($cache_timeout)) {
428 $cache_timeout = $this->ValueCacheTimeout;
431 if (strlen($objectclass) < 1) {
432 throw new Exception("\$objectclass argument is required");
435 $obj = null;
436 $objs = $this->Query($objectclass, $criteria, $cache_timeout)->ToObjectArray();
438 if (count($objs) == 0) {
439 require_once("NotFoundException.php");
440 throw new NotFoundException("$objectclass with specified criteria not found");
443 if ($crash_if_multiple_found && count($objs) > 1) {
444 throw new Exception("More than one $objectclass with specified criteria was found");
447 $obj = $objs [0];
449 return $obj;
453 * Query for a specific type of object
455 * @access public
456 * @param string $objectclass
457 * the type of object that your DataSet will contain
458 * @param Criteria $criteria
459 * a Criteria object to limit results
460 * @param
461 * int cache timeout (in seconds). Default is Phreezer->ValueCacheTimeout. Set to 0 for no cache
462 * @return DataSet
464 public function Query($objectclass, $criteria = null, $cache_timeout = null)
466 if (is_null($cache_timeout)) {
467 $cache_timeout = $this->ValueCacheTimeout;
470 if (strlen($objectclass) < 1) {
471 throw new Exception("\$objectclass argument is required");
474 // if criteria is null, then create a generic one
475 if (is_null($criteria)) {
476 $criteria = new Criteria();
479 // see if this object has a custom query designated
480 $custom = $this->GetCustomQuery($objectclass, $criteria);
482 $sql = "";
483 $count_sql = "";
485 if ($custom) {
486 $this->Observe("Using Custom Query", OBSERVE_DEBUG);
487 $sql = $custom;
489 // the counter query may be blank, in which case DataSet will generate one
490 $count_sql = $this->GetCustomCountQuery($objectclass, $criteria);
491 } else {
492 // the first-level fieldmaps should be from the primary table
493 $fms = $this->GetFieldMaps($objectclass);
495 // the query builder will handle creating the SQL for us
496 require_once("QueryBuilder.php");
497 $builder = new QueryBuilder($this);
498 $builder->RecurseFieldMaps($objectclass, $fms);
500 $sql = $builder->GetSQL($criteria);
502 $count_sql = $builder->GetCountSQL($criteria);
505 require_once("DataSet.php");
506 $ds = new DataSet($this, $objectclass, $sql, $cache_timeout);
507 $ds->CountSQL = $count_sql;
508 $ds->UnableToCache = $cache_timeout === 0;
510 return $ds;
514 * Get one instance of an object based on it's primary key value
516 * @access public
517 * @param string $objectclass
518 * to query
519 * @param variant $id
520 * the value of the primary key
521 * @param
522 * int cache timeout (in seconds). Default is Phreezer->ObjectCacheTimeout. Set to 0 for no cache
523 * @return Phreezable
525 public function Get($objectclass, $id, $cache_timeout = null)
527 if (is_null($cache_timeout)) {
528 $cache_timeout = $this->ObjectCacheTimeout;
531 if (strlen($objectclass) < 1) {
532 throw new Exception("\$objectclass argument is required");
535 if (strlen($id) < 1) {
536 throw new Exception("\$id argument is required for $objectclass");
539 // see if this object was cached & if so return it
540 $obj = $cache_timeout == 0 ? null : $this->GetCache($objectclass, $id);
541 if ($obj) {
542 return $obj;
545 $pkm = $this->GetPrimaryKeyMap($objectclass);
547 if (! $pkm) {
548 throw new Exception("Table for '$objectclass' has no primary key");
551 $criteria = new Criteria();
552 $criteria->PrimaryKeyField = "`" . $pkm->TableName . "`.`" . $pkm->ColumnName . "`";
553 $criteria->PrimaryKeyValue = $id;
555 $ds = $this->Query($objectclass, $criteria);
557 // this is cacheable
558 $ds->UnableToCache = false;
560 if (! $obj = $ds->Next()) {
561 require_once("NotFoundException.php");
562 throw new NotFoundException("$objectclass with primary key of $id not found");
565 // cache the object for future use
566 $this->SetCache($objectclass, $id, $obj, $cache_timeout);
568 return $obj;
572 * Persist an object to the data store.
573 * An insert or update will be executed based
574 * on whether the primary key has a value. use $form_insert to override this
575 * in the case of a primary key that is not an auto_increment
577 * @access public
578 * @param Object $obj
579 * the object to persist
580 * @param bool $force_insert
581 * (default = false)
582 * @return int the auto_increment id (insert) or the number of records updated (update)
584 public function Save($obj, $force_insert = false)
586 $objectclass = get_class($obj);
587 $fms = $this->GetFieldMaps($objectclass);
589 $pk = $obj->GetPrimaryKeyName();
590 $id = $obj->$pk;
591 $table = $fms [$pk]->TableName;
592 $pkcol = $fms [$pk]->ColumnName;
593 $returnval = "";
595 $pk_is_auto_insert = strlen($id) == 0;
597 // if there is no value for the primary key, this is an insert
598 $is_insert = $force_insert || $pk_is_auto_insert;
600 // fire the OnSave event in case the object needs to prepare itself
601 // if OnSave returns false, then don't proceed with the save
602 $this->Observe("Firing " . get_class($obj) . "->OnSave($is_insert)", OBSERVE_DEBUG);
603 if (! $obj->OnSave($is_insert)) {
604 $this->Observe("" . get_class($obj) . "->OnSave($is_insert) returned FALSE. Exiting without saving", OBSERVE_WARN);
605 return false;
608 $sql = "";
610 if (! $is_insert) {
611 // this is an update
613 // remove this class from the cache before saving
614 $this->DeleteCache($objectclass, $id);
616 $sql = "update `$table` set ";
617 $delim = "";
618 foreach ($fms as $fm) {
619 if ((! $fm->IsPrimaryKey) && $fm->FieldType != FM_CALCULATION) {
620 $prop = $fm->PropertyName;
621 $val = $obj->$prop;
623 try {
624 $sql .= $delim . "`" . $fm->ColumnName . "` = " . $this->GetQuotedSql($val);
625 } catch (Exception $ex) {
626 throw new Exception("Error escaping property '$prop'. value could not be converted to string");
629 $delim = ", ";
633 $sql .= " where $pkcol = '" . $this->Escape($id) . "'";
635 $returnval = $this->DataAdapter->Execute($sql);
637 $obj->OnUpdate(); // fire OnUpdate event
638 } else {
639 // this is an insert
640 $sql = "insert into `$table` (";
641 $delim = "";
642 foreach ($fms as $fm) {
643 // we don't want to include the primary key if this is an auto-increment table
644 if ((! $fm->IsPrimaryKey) || $force_insert) {
645 // calculated fields are not directly bound to a column and do not get persisted
646 if ($fm->FieldType != FM_CALCULATION) {
647 $prop = $fm->PropertyName;
648 $val = $obj->$prop;
649 $sql .= $delim . "`" . $fm->ColumnName . "`";
650 $delim = ", ";
655 $sql .= ") values (";
657 $delim = "";
658 foreach ($fms as $fm) {
659 // use the save logic inserting values as with the column names above
660 if ((! $fm->IsPrimaryKey) || $force_insert) {
661 if ($fm->FieldType != FM_CALCULATION) {
662 $prop = $fm->PropertyName;
663 $val = $obj->$prop;
665 try {
666 $sql .= $delim . ' ' . $this->GetQuotedSql($val);
667 } catch (Exception $ex) {
668 throw new Exception("Error escaping property '$prop'. value could not be converted to string");
671 $delim = ", ";
676 $sql .= ")";
678 // for the insert we also need to get the insert id of the primary key
679 $returnval = $this->DataAdapter->Execute($sql);
680 if ($pk_is_auto_insert) {
681 $returnval = $this->DataAdapter->GetLastInsertId();
682 $obj->$pk = $returnval;
685 $obj->OnInsert(); // fire OnInsert event
688 return $returnval;
692 * Delete the given object from the data store
694 * @access public
695 * @param Object $obj
696 * the object to delete
698 public function Delete($obj)
700 $objectclass = get_class($obj);
702 if (! $obj->OnBeforeDelete()) {
703 $this->Observe("Delete was cancelled because OnBeforeDelete did not return true");
704 return 0;
707 $fms = $this->GetFieldMaps($objectclass);
709 $pk = $obj->GetPrimaryKeyName();
710 $id = $obj->$pk;
711 $table = $fms [$pk]->TableName;
712 $pkcol = $fms [$pk]->ColumnName;
714 $sql = "delete from `$table` where `$pkcol` = '" . $this->Escape($id) . "'";
715 $returnval = $this->DataAdapter->Execute($sql);
717 // remove from cache
718 $this->DeleteCache($objectclass, $id);
720 $obj->OnDelete(); // fire OnDelete event
722 return $returnval;
726 * Delete all objects from the datastore used by the given object
728 * @access public
729 * @param Object $obj
730 * the object to delete
732 public function DeleteAll($obj)
734 $fms = $this->GetFieldMaps(get_class($obj));
735 $pk = $obj->GetPrimaryKeyName();
736 $table = $fms [$pk]->TableName;
738 $sql = "delete from `$table`";
739 $returnval = $this->DataAdapter->Execute($sql);
740 $obj->OnDelete(); // fire OnDelete event
741 return $returnval;
745 * Returns all FieldMaps for the given object class
747 * @access public
748 * @param string $objectclass
749 * the type of object that your DataSet will contain
750 * @return Array of FieldMap objects
752 public function GetFieldMaps($objectclass)
754 // this is a temporary ram cache
755 $fms = $this->_mapCache->Get($objectclass . "FieldMaps");
756 if ($fms) {
757 return $fms;
760 $this->IncludeModel($objectclass);
762 if (! class_exists($objectclass . "Map")) {
763 throw new Exception($objectclass . " must either implement GetCustomQuery or '" . $objectclass . "Map' class must exist in the include path.");
766 $fms = call_user_func(array (
767 $objectclass . "Map",
768 "GetFieldMaps"
771 $this->_mapCache->Set($objectclass . "FieldMaps", $fms);
772 return $fms;
776 * Returns the custom query for the given object class if it is defined
778 * @access public
779 * @param string $objectclass
780 * the type of object that your DataSet will contain
781 * @return Array of FieldMap objects
783 public function GetCustomQuery($objectclass, $criteria)
785 $this->IncludeModel($objectclass);
786 $sql = call_user_func(array (
787 $objectclass,
788 "GetCustomQuery"
789 ), $criteria);
790 return $sql;
794 * Returns the custom "counter" query for the given object class if it is defined
796 * @access public
797 * @param string $objectclass
798 * the type of object that your DataSet will contain
799 * @return Array of FieldMap objects
801 public function GetCustomCountQuery($objectclass, $criteria)
803 $this->IncludeModel($objectclass);
804 $sql = call_user_func(array (
805 $objectclass,
806 "GetCustomCountQuery"
807 ), $criteria);
808 return $sql;
810 static $cnt = 0; // used for debugging php memory errors due to circular references
813 * Returns all KeyMaps for the given object class
815 * @access public
816 * @param string $objectclass
817 * the type of object
818 * @return Array of KeyMap objects
820 public function GetKeyMaps($objectclass)
822 // TODO: if a php memory error occurs within this method, uncomment this block to debug
824 * if (Phreezer::$cnt++ > 500)
826 * throw new Exception("A sanity limit was exceeded when recursing KeyMaps for `$objectclass`. Please check your Map for circular joins.");
828 * //
831 // this is a temporary ram cache
832 $kms = $this->_mapCache->Get($objectclass . "KeyMaps");
833 if ($kms) {
834 return $kms;
837 $this->IncludeModel($objectclass);
838 if (! class_exists($objectclass . "Map")) {
839 throw new Exception("Class '" . $objectclass . "Map' is not defined.");
842 $kms = call_user_func(array (
843 $objectclass . "Map",
844 "GetKeyMaps"
847 $this->_mapCache->Set($objectclass . "KeyMaps", $kms);
848 return $kms;
852 * Return specific FieldMap for the given object class with the given name
854 * @access public
855 * @param string $objectclass
856 * the type of object
857 * @param string $propertyname
858 * the name of the property
859 * @return Array of FieldMap objects
861 public function GetFieldMap($objectclass, $propertyname)
863 $fms = $this->GetFieldMaps($objectclass);
864 return $fms [$propertyname];
868 * Return specific KeyMap for the given object class with the given name
870 * @access public
871 * @param string $objectclass
872 * the type of object
873 * @param string $keyname
874 * the name of the key
875 * @return Array of KeyMap objects
877 public function GetKeyMap($objectclass, $keyname)
879 $kms = $this->GetKeyMaps($objectclass);
880 return $kms [$keyname];
884 * Returns the name of the DB column associted with the given property
886 * @access public
887 * @param string $objectclass
888 * the type of object
889 * @param string $propertyname
890 * the name of the property
891 * @return string name of the DB Column
893 public function GetColumnName($objectclass, $propertyname)
895 $fm = $this->GetFieldMap($objectclass, $propertyname);
896 return $fm->ColumnName;
900 * Returns the name of the DB Table associted with the given property
902 * @access public
903 * @param string $objectclass
904 * the type of object
905 * @param string $propertyname
906 * the name of the property
907 * @return string name of the DB Column
909 public function GetTableName($objectclass, $propertyname)
911 $fm = $this->GetFieldMap($objectclass, $propertyname);
912 return $fm->TableName;
916 * Return the KeyMap for the primary key for the given object class
918 * @access public
919 * @param string $objectclass
920 * the type of object
921 * @return KeyMap object
923 public function GetPrimaryKeyMap($objectclass)
925 $fms = $this->GetFieldMaps($objectclass);
926 foreach ($fms as $fm) {
927 if ($fm->IsPrimaryKey) {
928 return $fm;
934 * Query for a child objects in a one-to-many relationship
936 * @access public
937 * @param Phreezable $parent
938 * the parent object
939 * @param string $keyname
940 * The name of the key representing the relationship
941 * @return Criteria $criteria a Criteria object to limit the results
943 public function GetOneToMany($parent, $keyname, $criteria)
946 // get the keymap for this child relationship
947 $km = $this->GetKeyMap(get_class($parent), $keyname);
949 // we need the value of the foreign key. (ex. to get all orders for a customer, we need Customer.Id)
950 $parent_prop = $km->KeyProperty;
951 $key_value = $parent->$parent_prop;
953 if (! $criteria) {
954 // if no criteria was specified, then create a generic one. we can specify SQL
955 // code in the constructor, but we have to translate the properties into column names
956 $foreign_table = $this->GetTableName($km->ForeignObject, $km->ForeignKeyProperty);
957 $foreign_column = $this->GetColumnName($km->ForeignObject, $km->ForeignKeyProperty);
958 $criteria = new Criteria("`" . $foreign_table . "`.`" . $foreign_column . "` = '" . $this->Escape($key_value) . "'");
959 } else {
960 // ensure that the criteria passed in will filter correctly by foreign key
961 $foreign_prop = $km->ForeignKeyProperty;
963 // this is only for backwards compatibility with phreeze 2.0 apps
964 if (self::$COMPAT_VERSION_2) {
965 $criteria->$foreign_prop = $key_value;
968 // the current criteria "Equals" format "FieldName_Equals"
969 $foreign_prop .= "_Equals";
970 $criteria->$foreign_prop = $key_value;
972 // if this criteria has any or criterias attached, we need to set the foreign key to these
973 // as well or else we'll get unexpected results
974 foreach ($criteria->GetOrs() as $oc) {
975 $oc->$foreign_prop = $key_value;
979 return $this->Query($km->ForeignObject, $criteria);
983 * Query for a parent object in a many-to-one relationship
985 * @access public
986 * @param Phreezable $parent
987 * the parent object
988 * @param string $keyname
989 * The name of the key representing the relationship
990 * @return Phreezable object an object of the type specified by the KeyMap
992 public function GetManyToOne($parent, $keyname)
994 // get the keymap for this child relationship
995 $km = $this->GetKeyMap(get_class($parent), $keyname);
997 // we need the value of the foreign key. (ex. to get all orders for a customer, we need Customer.Id)
998 // we also need to know the class of the object we're retrieving because if it's cached, we need to
999 // make sure the model file is loaded
1000 $objectclass = $km->ForeignObject;
1001 $parent_prop = $km->KeyProperty;
1002 $key_value = $parent->$parent_prop;
1004 // get this object Get uses caching so we don't need to bother
1005 $obj = $this->Get($km->ForeignObject, $key_value);
1007 return $obj;
1011 * Dynamically override the LoadType for a KeyMap.
1012 * This is useful for
1013 * eager fetching for a particular query. One set, this configuration
1014 * will be used until the end of the page context, or it is changed.
1016 * @access public
1017 * @param string $objectclass
1018 * The name of the object class
1019 * @param string $keyname
1020 * The unique id of the KeyMap in the objects KeyMaps collection
1021 * @param int $load_type
1022 * (optional) KM_LOAD_INNER | KM_LOAD_EAGER | KM_LOAD_LAZY (default is KM_LOAD_EAGER)
1024 public function SetLoadType($objectclass, $keyname, $load_type = KM_LOAD_EAGER)
1026 $this->GetKeyMap($objectclass, $keyname)->LoadType = $load_type;
1030 * Utility method that calls DataAdapter::Escape($val)
1032 * @param variant $val
1033 * to be escaped
1034 * @return string
1036 public function Escape($val)
1038 return DataAdapter::Escape($val);
1042 * Utility method that calls DataAdapter::GetQuotedSql($val)
1044 * @param variant $val
1045 * to be quoted
1046 * @return string
1048 private function GetQuotedSql($val)
1050 return DataAdapter::GetQuotedSql($val);
1054 * If the type is not already defined, attempts to require_once the definition.
1055 * If the Model file cannot be located, an exception is thrown
1057 * @access public
1058 * @param string $objectclass
1059 * The name of the object class
1061 public function IncludeModel($objectclass)
1063 Includer::RequireClass($objectclass, array (
1064 "Model/",
1065 "Reporter/"