Highway to PSR2
[openemr.git] / portal / patient / fwk / libs / verysimple / Phreeze / Phreezable.php
blobe5d7264bd4de6627b94a4ff1e2800a902a70b7e2
1 <?php
2 /** @package verysimple::Phreeze */
4 /**
5 * Phreezable Class
7 * Abstract base class for object that are persistable by Phreeze
9 * @package verysimple::Phreeze
10 * @author VerySimple Inc. <noreply@verysimple.com>
11 * @copyright 1997-2005 VerySimple Inc.
12 * @license http://www.gnu.org/licenses/lgpl.html LGPL
13 * @version 1.3
15 abstract class Phreezable implements Serializable
17 private $_cache = array ();
18 protected $_phreezer;
19 protected $_val_errors = array ();
20 protected $_base_validation_complete = false;
21 private $_isLoaded;
22 private $_isPartiallyLoaded;
23 private $_cacheLevel = 0;
24 private $_noCache = false;
26 /** @var these properties will never be cached */
27 private static $NoCacheProperties = array (
28 "_cache",
29 "_phreezer",
30 "_val_errors",
31 "_base_validation_complete"
34 /** @var cache of public properties for each type for improved performance when enumerating */
35 private static $PublicPropCache = array ();
37 /**
38 * Returns true if the current object has been loaded
40 * @access public
41 * @param
42 * bool (optional) if provided will change the value
43 * @return bool
45 public function IsLoaded($value = null)
47 if ($value != null) {
48 $this->_isLoaded = $value;
51 return $this->_isLoaded;
54 /**
55 * Returns true if the current object has been partially loaded
57 * @access public
58 * @param
59 * bool (optional) if provided will change the value
60 * @return bool
62 public function IsPartiallyLoaded($value = null)
64 if ($value != null) {
65 $this->_isPartiallyLoaded = $value;
68 return $this->_isPartiallyLoaded;
71 /**
72 * Returns 0 if this was loaded from the DB, 1 if from 1st level cache and 2 if 2nd level cache
74 * @access public
75 * @param
76 * bool (optional) if provided will change the value
77 * @return bool
79 public function CacheLevel($value = null)
81 if ($value != null) {
82 $this->_cacheLevel = $value;
85 return $this->_cacheLevel;
88 /**
89 * Returns true if the current object should never be cached
91 * @access public
92 * @param
93 * bool (optional) if provided will change the value
94 * @return bool
96 public function NoCache($value = null)
98 if ($value != null) {
99 $this->_noCache = $value;
102 return $this->_noCache;
106 * Returns an array with all public properties, excluding any internal
107 * properties used by the Phreeze framework.
108 * This is cached for performance
109 * when enumerating through large numbers of the same class
111 * @return array
113 public function GetPublicProperties()
115 $className = get_class($this);
117 if (! array_key_exists($className, self::$PublicPropCache)) {
118 $props = array ();
119 $ro = new ReflectionObject($this);
121 foreach ($ro->getProperties() as $rp) {
122 $propname = $rp->getName();
124 if (! in_array($propname, self::$NoCacheProperties)) {
125 if (! ($rp->isPrivate() || $rp->isStatic())) {
126 $props [] = $propname;
131 self::$PublicPropCache [$className] = $props;
134 return self::$PublicPropCache [$className];
138 * When serializing, make sure that we ommit certain properties that
139 * should never be cached or serialized.
141 function serialize()
143 $propvals = array ();
144 $ro = new ReflectionObject($this);
146 foreach ($ro->getProperties() as $rp) {
147 $propname = $rp->getName();
149 if (! in_array($propname, self::$NoCacheProperties)) {
150 if (method_exists($rp, "setAccessible")) {
151 $rp->setAccessible(true);
152 $propvals [$propname] = $rp->getValue($this);
153 } elseif (! $rp->isPrivate()) {
154 // if < php 5.3 we can't serialize private vars
155 $propvals [$propname] = $rp->getValue($this);
160 return serialize($propvals);
165 * @deprecated use ToObject
167 function GetObject($props = null, $camelCase = false)
169 return $this->ToObject(array (
170 'props' => $props,
171 'camelCase' => $camelCase
176 * Return an object with a limited number of properties from this Phreezable object.
177 * This can be used if not all properties are necessary, for example rendering as JSON
179 * This can be overriden per class for custom JSON output. the overridden method may accept
180 * additional option parameters that are not supported by the base Phreezable calss
182 * @param
183 * array assoc array of options. This is passed through from Controller->RenderJSON
184 * props (array) array of props to return (if null then use all public props)
185 * omit (array) array of props to omit
186 * camelCase (bool) if true then first letter of each property is made lowercase
187 * @return stdClass
189 function ToObject($options = null)
191 if ($options === null) {
192 $options = array ();
195 $props = array_key_exists('props', $options) ? $options ['props'] : $this->GetPublicProperties();
196 $omit = array_key_exists('omit', $options) ? $options ['omit'] : array ();
197 $camelCase = array_key_exists('camelCase', $options) ? $options ['camelCase'] : false;
199 $obj = new stdClass();
201 foreach ($props as $prop) {
202 if (! in_array($prop, $omit)) {
203 $newProp = ($camelCase) ? lcfirst($prop) : $prop;
204 $obj->$newProp = $this->$prop;
208 return $obj;
212 * Reload the object when it awakes from serialization
214 * @param
215 * $data
217 function unserialize($data)
219 $propvals = unserialize($data);
220 $ro = new ReflectionObject($this);
222 foreach ($ro->getProperties() as $rp) {
223 $propname = $rp->name;
224 if (array_key_exists($propname, $propvals)) {
225 if (method_exists($rp, "setAccessible")) {
226 $rp->setAccessible(true);
227 $rp->setValue($this, $propvals [$propname]);
228 } elseif (! $rp->isPrivate()) {
229 // if < php 5.3 we can't serialize private vars
230 $rp->setValue($this, $propvals [$propname]);
237 * constructor
239 * @access public
240 * @param Phreezer $phreezer
241 * @param Array $row
243 final function __construct(Phreezer $phreezer, $row = null)
245 $this->_phreezer = $phreezer;
246 $this->_cache = array ();
248 if ($row) {
249 $this->Init();
250 $this->Load($row);
251 } else {
252 $this->LoadDefaults();
253 $this->Init();
258 * Init is called after contruction.
259 * When loading, Init is called prior to Load().
260 * When creating a blank object, Init is called immediately after LoadDefaults()
262 * @access public
264 public function Init()
269 * LoadDefaults is called during construction if this object is not instantiated with
270 * a DB row.
271 * The default values as specified in the fieldmap are loaded
273 * @access public
275 public function LoadDefaults()
277 $fms = $this->_phreezer->GetFieldMaps(get_class($this));
279 foreach ($fms as $fm) {
280 $prop = $fm->PropertyName;
281 $this->$prop = $fm->DefaultValue;
286 * LoadFromObject allows this class to be populated from another class, so long as
287 * the properties are compatible.
288 * This is useful when using reporters so that you
289 * can easily convert them to phreezable objects. Be sure to check that IsLoaded
290 * is true before attempting to save this object.
292 * @access public
293 * @param $src the
294 * object to populate from, which must contain compatible properties
296 public function LoadFromObject($src)
298 $this->IsLoaded(true);
299 $src_cls = get_class($src);
301 foreach (get_object_vars($this) as $key => $val) {
302 if (substr($key, 0, 1) != "_") {
303 if (property_exists($src_cls, $key)) {
304 $this->$key = $src->$key;
305 $this->IsPartiallyLoaded(true);
306 } else {
307 $this->IsLoaded(false);
312 $this->OnLoad();
316 * Validate returns true if the properties all contain valid values.
317 * If not,
318 * use GetValidationErrors to see which fields have invalid values.
320 * @access public
322 public function Validate()
324 // force re-validation
325 $this->ResetValidationErrors();
327 $is_valid = (! $this->HasValidationErrors());
329 // if validation fails, remove this object from the cache otherwise invalid values can
330 // hang around and cause troubles.
331 if (! $is_valid) {
332 $this->_phreezer->DeleteCache(get_class($this), $this->GetPrimaryKeyValue());
335 return $is_valid;
339 * Add a validation error to the error array
341 * @param
342 * string property name
343 * @param
344 * string error message
346 protected function AddValidationError($prop, $msg)
348 $this->_val_errors [$prop] = $msg;
352 * Returns true if this object has validation errors
354 * @return bool
356 protected function HasValidationErrors()
358 $this->_DoBaseValidation();
359 return count($this->_val_errors) > 0;
363 * Returns the error array - containing an array of fields with invalid values.
365 * @access public
366 * @return array
368 public function GetValidationErrors()
370 $this->_DoBaseValidation();
371 return $this->_val_errors;
375 * Clears all previous validation errors
377 protected function ResetValidationErrors()
379 $this->_val_errors = array ();
380 $this->_base_validation_complete = false;
384 * populates the _val_errors array w/ phreezer
386 * @access private
388 private function _DoBaseValidation()
390 $lenfunction = $this->_phreezer->DataAdapter->ConnectionSetting->Multibyte ? 'mb_strlen' : 'strlen';
392 if (! $this->_base_validation_complete) {
393 $fms = $this->_phreezer->GetFieldMaps(get_class($this));
395 foreach ($fms as $fm) {
396 $prop = $fm->PropertyName;
398 if ($fm->FieldType == FM_TYPE_DECIMAL && is_numeric($fm->FieldSize)) {
399 // decimal validation needs to be treated differently than whole numbers
401 $values = explode('.', ( string ) $this->$prop, 2);
402 $right = count($values) > 1 ? strlen(( string ) $values [1]) : 0;
403 $left = strlen(( string ) $values [0]);
405 $limits = explode('.', ( string ) $fm->FieldSize, 2);
406 $limitRight = count($limits) > 1 ? ( int ) $limits [1] : 0;
407 $limitLeft = ( int ) $limits [0] - $limitRight;
409 if ($left > $limitLeft || $right > $limitRight) {
410 $this->AddValidationError($prop, "$prop exceeds the maximum length of " . $fm->FieldSize . "");
412 } elseif (is_numeric($fm->FieldSize) && ($lenfunction ( $this->$prop )-1 > $fm->FieldSize)) {
413 $this->AddValidationError($prop, "$prop exceeds the maximum length of " . $fm->FieldSize . "");
416 if ($this->$prop == "" && ($fm->DefaultValue || $fm->IsAutoInsert)) {
417 // these fields are auto-populated so we don't need to validate them unless
418 // a specific value was provided
419 } else {
420 switch ($fm->FieldType) {
421 case FM_TYPE_INT:
422 case FM_TYPE_SMALLINT:
423 case FM_TYPE_TINYINT:
424 case FM_TYPE_MEDIUMINT:
425 case FM_TYPE_BIGINT:
426 case FM_TYPE_DECIMAL:
427 if (! is_numeric($this->$prop)) {
428 $this->AddValidationError($prop, "$prop is not a valid number");
430 break;
431 case FM_TYPE_DATE:
432 case FM_TYPE_DATETIME:
433 if (strtotime($this->$prop) === '') {
434 $this->AddValidationError($prop, "$prop is not a valid date/time value.");
436 break;
437 case FM_TYPE_ENUM:
438 if (! in_array($this->$prop, $fm->GetEnumValues())) {
439 $this->AddValidationError($prop, "$prop is not valid value. Allowed values: " . implode(', ', $fm->GetEnumValues()));
441 break;
442 default:
443 break;
449 // print_r($this->_val_errors);
451 $this->_base_validation_complete = true;
455 * This static function can be overridden to populate this object with
456 * results of a custom query
458 * @access public
459 * @param Criteria $criteria
460 * @return string or null
462 public static function GetCustomQuery($criteria)
464 return null;
468 * Refresh the object in the event that it has been saved to the session or serialized
470 * @access public
471 * @param Phreezer $phreezer
472 * @param Array $row
474 final function Refresh(&$phreezer, $row = null)
476 $this->_phreezer = $phreezer;
478 // also refresh any children in the cache in case they are accessed
479 foreach ($this->_cache as $child) {
480 if (in_array("Phreezable", class_parents($child))) {
481 $child->Refresh($phreezer, $row);
485 if ($row) {
486 $this->Load($row);
489 $this->OnRefresh();
493 * Serialized string representation of this object.
494 * For sorting
495 * purposes it is recommended to override this method
497 function ToString()
499 return serialize($this);
503 * Returns the name of the primary key property.
504 * TODO: does not support multiple primary keys.
506 * @access public
507 * @return string
509 function GetPrimaryKeyName()
511 $fms = $this->_phreezer->GetFieldMaps(get_class($this));
512 foreach ($fms as $fm) {
513 if ($fm->IsPrimaryKey) {
514 return $fm->PropertyName;
519 * print "<pre>";
520 * $this->Data = "";
521 * $this->_phreezer = null;
522 * $this->_cache = null;
523 * print_r($this);
525 * print_r($fms);
526 * die();
529 throw new Exception("No Primary Key found for " . get_class($this));
533 * Returns the value of the primary key property.
534 * TODO: does not support multiple primary keys.
536 * @access public
537 * @return string
539 function GetPrimaryKeyValue()
541 $prop = $this->GetPrimaryKeyName();
542 return $this->$prop;
546 * Returns this object as an associative array with properties as keys and
547 * values as values
549 * @access public
550 * @return array
552 function GetArray()
554 $fms = $this->_phreezer->GetFieldMaps(get_class($this));
555 $cols = array ();
557 foreach ($fms as $fm) {
558 $prop = $fm->PropertyName;
559 $cols [$fm->ColumnName] = $this->$prop;
562 return $cols;
566 * Persist this object to the data store
568 * @access public
569 * @param bool $force_insert
570 * (default = false)
571 * @return int auto_increment or number of records affected
573 function Save($force_insert = false)
575 return $this->_phreezer->Save($this, $force_insert);
579 * Delete this object from the data store
581 * @access public
582 * @return int number of records affected
584 function Delete()
586 return $this->_phreezer->Delete($this);
590 * Loads the object with data given in the row array.
592 * @access public
593 * @param Array $row
595 function Load(&$row)
597 $fms = $this->_phreezer->GetFieldMaps(get_class($this));
598 $this->_phreezer->Observe("Loading " . get_class($this), OBSERVE_DEBUG);
600 $this->IsLoaded(true); // assume true until fail occurs
601 $this->IsPartiallyLoaded(false); // at least we tried
603 // in order to prevent collisions on fields, QueryBuilder appends __tablename__rand to the
604 // sql statement. We need to strip that out so we can match it up to the property names
605 $rowlocal = array ();
606 foreach ($row as $key => $val) {
607 $info = explode("___", $key);
609 // we prefer to use tablename.colname if we have it, but if not
610 // just use the colname
611 $newkey = isset($info [1]) ? ($info [1] . "." . $info [0]) : $info [0];
612 if (isset($rowlocal [$newkey])) {
613 throw new Exception("The column `$newkey` was selected twice in the same query, causing a data collision");
616 $rowlocal [$newkey] = $val;
619 foreach ($fms as $fm) {
620 if (array_key_exists($fm->TableName . "." . $fm->ColumnName, $rowlocal)) {
621 // first try to locate the field by tablename.colname
622 $prop = $fm->PropertyName;
623 $this->$prop = $rowlocal [$fm->TableName . "." . $fm->ColumnName];
624 } elseif (array_key_exists($fm->ColumnName, $rowlocal)) {
625 // if we can't locate the field by tablename.colname, then just look for colname
626 $prop = $fm->PropertyName;
627 $this->$prop = $rowlocal [$fm->ColumnName];
628 } else {
629 // there is a required column missing from this $row array - mark as partially loaded
630 $this->_phreezer->Observe("Missing column '" . $fm->ColumnName . "' while loading " . get_class($this), OBSERVE_WARN);
631 $this->IsLoaded(false);
632 $this->IsPartiallyLoaded(true);
636 // now look for any eagerly loaded children - their fields should be available in this query
637 $kms = $this->_phreezer->GetKeyMaps(get_class($this));
639 foreach ($kms as $km) {
640 if ($km->LoadType == KM_LOAD_EAGER || $km->LoadType == KM_LOAD_INNER) {
641 // load the child object that was obtained eagerly and cache so we
642 // won't ever grab the same object twice in one page load
643 $this->_phreezer->IncludeModel($km->ForeignObject);
644 $foclass = $km->ForeignObject;
645 $fo = new $foclass ( $this->_phreezer, $row );
647 $this->_phreezer->SetCache($foclass, $fo->GetPrimaryKeyValue(), $fo, $this->_phreezer->CacheQueryObjectLevel2);
651 $this->_phreezer->Observe("Firing " . get_class($this) . "->OnLoad()", OBSERVE_DEBUG);
652 $this->OnLoad();
656 * Returns a value from the local cache
658 * @access public
659 * @deprecated this is handled internally by Phreezer now
660 * @param string $key
661 * @return object
663 public function GetCache($key)
665 return (array_key_exists($key, $this->_cache) ? $this->_cache [$key] : null);
669 * Sets a value from in local cache
671 * @access public
672 * @deprecated this is handled internally by Phreezer now
673 * @param string $key
674 * @param object $obj
676 public function SetCache($key, $obj)
678 $this->_cache [$key] = $obj;
682 * Clears all values in the local cache
684 * @access public
685 * @deprecated this is handled internally by Phreezer now
687 public function ClearCache()
689 $this->_cache = array ();
693 * Called after object is loaded, may be overridden
695 * @access protected
697 protected function OnLoad()
702 * Called by Phreezer prior to saving the object, may be overridden.
703 * If this function returns any non-true value, then the save operation
704 * will be cancelled. This allows you to perform custom insert/update queries
705 * if necessary
707 * @access protected
708 * @param boolean $is_insert
709 * true if Phreezer considers this a new record
710 * @return boolean
712 public function OnSave($is_insert)
714 return true;
718 * Called by Phreezer after object is updated, may be overridden
720 * @access public
722 public function OnUpdate()
727 * Called by Phreezer after object is inserted, may be overridden
729 * @access public
731 public function OnInsert()
736 * Called by Phreezer after object is deleted, may be overridden
738 * @access public
740 public function OnDelete()
745 * Called by Phreezer before object is deleted, may be overridden.
746 * if a true value is not returned, the delete operation will be aborted
748 * @access public
749 * @return bool
751 public function OnBeforeDelete()
753 return true;
757 * Called after object is refreshed, may be overridden
759 * @access public
761 public function OnRefresh()
766 * Throw an exception if an undeclared property is accessed
768 * @access public
769 * @param string $key
770 * @throws Exception
772 public function __get($key)
774 throw new Exception("Unknown property: $key");
778 * Throw an exception if an undeclared property is accessed
780 * @access public
781 * @param string $key
782 * @param string $val
783 * @throws Exception
785 public function __set($key, $val)
787 throw new Exception("Unknown property: $key");