Highway to PSR2
[openemr.git] / portal / patient / fwk / libs / verysimple / Phreeze / Criteria.php
blob9678c98cc5a6bf9d9c1417e7dbce0762d370671f
1 <?php
2 /** @package verysimple::Phreeze */
4 /**
5 * import supporting libraries
6 */
7 require_once("DataAdapter.php");
8 require_once("CriteriaFilter.php");
9 require_once("verysimple/IO/Includer.php");
11 /**
12 * Criteria is a base object that is passed into Phreeze->Query for retreiving
13 * records based on specific criteria
15 * @package verysimple::Phreeze
16 * @author VerySimple Inc.
17 * @copyright 1997-2007 VerySimple, Inc.
18 * @license http://www.gnu.org/licenses/lgpl.html LGPL
19 * @version 2.3
21 class Criteria
23 protected $_join;
24 protected $_where;
25 protected $_where_delim;
26 protected $_order;
27 protected $_order_delim;
28 protected $_is_prepared;
29 protected $_map_object_class;
30 private $_fieldmaps;
31 private $_keymaps;
32 private $_constructor_where;
33 private $_constructor_order;
34 private $_set_order;
35 private $_and = array ();
36 private $_or = array ();
37 public $PrimaryKeyField;
38 public $PrimaryKeyValue;
40 /**
42 * @var $Filters a CriteriaFilter or array of CriteriaFilters to be applied to the query
44 public $Filters;
45 public function __construct($where = "", $order = "")
47 $this->_constructor_where = $where;
48 $this->_constructor_order = $order;
50 $this->_where = $where;
51 $this->_order = $order;
53 $this->Init();
56 /**
57 * Init is called directly after construction and can be overridden.
58 * If the
59 * name of the Criteria class is not ObjectClassCriteria, then this method
60 * must be overriden and _map_object_class should be set to the correct
61 * name of the DAO Map class
63 protected function Init()
65 $this->_map_object_class = str_replace("Criteria", "Map", get_class($this));
68 /**
69 * Add a CriteriaFilter to the criteria for custom filtering of results
71 * @param CriteriaFilter $filter
73 public function AddFilter(CriteriaFilter $filter)
75 if (! $this->Filters) {
76 $this->Filters = array ();
79 $this->Filters [] = $filter;
82 /**
83 * Return an array of CriteriaFilters that have been added to this criteria
85 * @return array
87 public function GetFilters()
89 return $this->Filters;
92 /**
93 * Remove all filters that are currently attached
95 public function ClearFilters()
97 $this->Filters = null;
101 * Adds a criteria to be joined w/ an "and" statement.
102 * Criterias to foreign objects may be added as long as they
103 * have an immediate relationship to the foreign table
105 * @param
106 * Criteria
107 * @param
108 * string [optional] id of the foreign key map. If the same table is joined
109 * multiple times, then you should specify which keymap to use
111 public function AddAnd(Criteria $criteria, $keymap_id = null)
113 $this->_and [] = $criteria;
117 * Return any and criterias that have been added to this criteria
119 * @return array
121 public function GetAnds()
123 return $this->_and;
127 * Escape values for insertion into a SQL query string
129 * @return string
131 public function Escape($val)
133 return DataAdapter::Escape($val);
137 * Returns DataAdapter::GetQuotedSql($val)
139 * @param variant $val
140 * to be quoted
141 * @return string
143 public function GetQuotedSql($val)
145 return DataAdapter::GetQuotedSql($val);
149 * Adds a criteria to be joined w/ an "or" statement.
150 * Criterias to foreign objects may be added as long as they
151 * have an immediate relationship to the foreign table
153 * @param
154 * Criteria
155 * @param
156 * string [optional] id of the foreign key map. If the same table is joined
157 * multiple times, then you should specify which keymap to use
159 public function AddOr(Criteria $criteria, $keymap_id = null)
161 $this->_or [] = $criteria;
165 * Return any 'or' criterias that have been added to this criteria
167 * @return array
169 public function GetOrs()
171 return $this->_or;
175 * Reset the Criteria for re-use.
176 * This is called by querybuilder after the criteria has been used
177 * to generate SQL. It can be called manually as well.
179 public function Reset()
181 $this->_is_prepared = false;
182 $this->_where = $this->_constructor_where;
183 $this->_order = $this->_constructor_order;
187 * Prepare is called just prior to execution and will fire OnPrepare after it completes
188 * If this is a base Criteria class, then we can only do a lookup by PrimaryKeyField or
189 * else raw SQL must be provided during construction.
190 * _Equals, _BeginsWith can only be
191 * used by inherited Criteria classes because we don't know what table this is associated
192 * with, so we can't translate property names to column names.
194 final private function Prepare()
196 if (! $this->_is_prepared) {
197 if (get_class($this) == "Criteria") {
198 if ($this->PrimaryKeyField) {
199 // PrimaryKeyField property was specified. this might be coming from $phreezer->Get
200 $this->_where = " " . $this->PrimaryKeyField . " = '" . $this->Escape($this->PrimaryKeyValue) . "'";
203 // else {raw SQL was likely provided in the constructor. this might be coming from $phreezer->GetOneToMany}
204 } else {
205 // loop through all of the properties and attempt to
206 // build a query based on any values that have been set
207 $this->_where = '';
208 $this->_where_delim = '';
210 $props = get_object_vars($this);
211 foreach ($props as $prop => $val) {
212 // TODO: tighten this up a bit to reduce redundant code
213 if ($prop == "Filters" && isset($val) && (is_array($val) || is_a($val, 'CriteriaFilter'))) {
214 // a filter object will take care of generating it's own where statement
216 // normalize the input to accept either an individual filter or multiple filters
217 $filters = (is_array($val)) ? $val : array (
218 $val
221 foreach ($filters as $filter) {
222 $this->_where .= $this->_where_delim . ' ' . $filter->GetWhere($this);
223 $this->_where_delim = " and";
225 } elseif (substr($prop, - 7) == "_Equals" && strlen($this->$prop)) {
226 $dbfield = $this->GetFieldFromProp(str_replace("_Equals", "", $prop));
227 $this->_where .= $this->_where_delim . " " . $dbfield . " = " . $this->GetQuotedSql($val) . "";
228 $this->_where_delim = " and";
229 } elseif (substr($prop, - 10) == "_NotEquals" && strlen($this->$prop)) {
230 $dbfield = $this->GetFieldFromProp(str_replace("_NotEquals", "", $prop));
231 $this->_where .= $this->_where_delim . " " . $dbfield . " != " . $this->GetQuotedSql($val) . "";
232 $this->_where_delim = " and";
233 } elseif (substr($prop, - 8) == "_IsEmpty" && $this->$prop) {
234 $dbfield = $this->GetFieldFromProp(str_replace("_IsEmpty", "", $prop));
235 $this->_where .= $this->_where_delim . " " . $dbfield . " = ''";
236 $this->_where_delim = " and";
237 } elseif (substr($prop, - 11) == "_IsNotEmpty" && $this->$prop) {
238 $dbfield = $this->GetFieldFromProp(str_replace("_IsNotEmpty", "", $prop));
239 $this->_where .= $this->_where_delim . " " . $dbfield . " != ''";
240 $this->_where_delim = " and";
241 } elseif (substr($prop, - 7) == "_IsLike" && strlen($this->$prop)) {
242 $dbfield = $this->GetFieldFromProp(str_replace("_IsLike", "", $prop));
243 $this->_where .= $this->_where_delim . " " . $dbfield . " like '%" . $this->Escape($val) . "%'";
244 $this->_where_delim = " and";
245 } elseif (substr($prop, - 10) == "_IsNotLike" && strlen($this->$prop)) {
246 $dbfield = $this->GetFieldFromProp(str_replace("_IsNotLike", "", $prop));
247 $this->_where .= $this->_where_delim . " " . $dbfield . " not like '%" . $this->Escape($val) . "%'";
248 $this->_where_delim = " and";
249 } elseif (substr($prop, - 11) == "_BeginsWith" && strlen($this->$prop)) {
250 $dbfield = $this->GetFieldFromProp(str_replace("_BeginsWith", "", $prop));
251 $this->_where .= $this->_where_delim . " " . $dbfield . " like '" . $this->Escape($val) . "%'";
252 $this->_where_delim = " and";
253 } elseif (substr($prop, - 9) == "_EndsWith" && strlen($this->$prop)) {
254 $dbfield = $this->GetFieldFromProp(str_replace("_EndsWith", "", $prop));
255 $this->_where .= $this->_where_delim . " " . $dbfield . " like '%" . $this->Escape($val) . "'";
256 $this->_where_delim = " and";
257 } elseif (substr($prop, - 12) == "_GreaterThan" && strlen($this->$prop)) {
258 $dbfield = $this->GetFieldFromProp(str_replace("_GreaterThan", "", $prop));
259 $this->_where .= $this->_where_delim . " " . $dbfield . " > " . $this->GetQuotedSql($val) . "";
260 $this->_where_delim = " and";
261 } elseif (substr($prop, - 19) == "_GreaterThanOrEqual" && strlen($this->$prop)) {
262 $dbfield = $this->GetFieldFromProp(str_replace("_GreaterThanOrEqual", "", $prop));
263 $this->_where .= $this->_where_delim . " " . $dbfield . " >= " . $this->GetQuotedSql($val) . "";
264 $this->_where_delim = " and";
265 } elseif (substr($prop, - 9) == "_LessThan" && strlen($this->$prop)) {
266 $dbfield = $this->GetFieldFromProp(str_replace("_LessThan", "", $prop));
267 $this->_where .= $this->_where_delim . " " . $dbfield . " < " . $this->GetQuotedSql($val) . "";
268 $this->_where_delim = " and";
269 } elseif (substr($prop, - 16) == "_LessThanOrEqual" && strlen($this->$prop)) {
270 $dbfield = $this->GetFieldFromProp(str_replace("_LessThanOrEqual", "", $prop));
271 $this->_where .= $this->_where_delim . " " . $dbfield . " <= " . $this->GetQuotedSql($val) . "";
272 $this->_where_delim = " and";
273 } elseif (substr($prop, - 10) == "_BitwiseOr" && strlen($this->$prop)) {
274 $dbfield = $this->GetFieldFromProp(str_replace("_BitwiseOr", "", $prop));
275 $this->_where .= $this->_where_delim . " (" . $dbfield . " | '" . $this->Escape($val) . ")";
276 $this->_where_delim = " and";
277 } elseif (substr($prop, - 11) == "_BitwiseAnd" && strlen($this->$prop)) {
278 $dbfield = $this->GetFieldFromProp(str_replace("_BitwiseAnd", "", $prop));
279 $this->_where .= $this->_where_delim . " (" . $dbfield . " & " . $this->Escape($val) . ")";
280 $this->_where_delim = " and";
281 } elseif (substr($prop, - 16) == "_LiteralFunction" && strlen($this->$prop)) {
282 $dbfield = $this->GetFieldFromProp(str_replace("_LiteralFunction", "", $prop));
283 $this->_where .= $this->_where_delim . " (" . $dbfield . " " . $val . ")";
284 $this->_where_delim = " and";
285 } elseif (substr($prop, - 3) == "_In" && isset($val)) {
286 // if a string was passed in then treat it as comma-delimited
287 if (! is_array($val)) {
288 $val = explode(',', $val);
291 // if the count is zero, technically the user is saying that they don't
292 // want any results. the only way to do that is to make the criteria
293 // something that will for sure not match any existing records. we cannot
294 // 100% guarantee this, though, we can choose a highly unlikely value
295 // that will never return a match under ordinary circumstances
296 if (count($val) == 0) {
297 array_push($val, "$prop EMPTY PHREEZE CRITERIA ARRAY");
300 $dbfield = $this->GetFieldFromProp(str_replace("_In", "", $prop));
301 $this->_where .= $this->_where_delim . " " . $dbfield . " in (";
302 $indelim = "";
303 foreach ($val as $n) {
304 $this->_where .= $indelim . "'" . $this->Escape($n) . "'";
305 $indelim = ",";
308 $this->_where .= ")";
309 $this->_where_delim = " and";
310 } elseif (substr($prop, - 6) == "_NotIn" && isset($val)) {
311 // if a string was passed in then treat it as comma-delimited
312 if (! is_array($val)) {
313 $val = explode(',', $val);
316 // if the count is zero, technically the user is saying that they don't
317 // want any results. the only way to do that is to make the criteria
318 // something that will for sure not match any existing records. we cannot
319 // 100% guarantee this, though, we can choose a highly unlikely value
320 // that will never return a match under ordinary circumstances
321 if (count($val) == 0) {
322 array_push($val, "$prop EMPTY PHREEZE CRITERIA ARRAY");
325 $dbfield = $this->GetFieldFromProp(str_replace("_NotIn", "", $prop));
326 $this->_where .= $this->_where_delim . " " . $dbfield . " not in (";
327 $indelim = "";
328 foreach ($val as $n) {
329 $this->_where .= $indelim . "'" . $this->Escape($n) . "'";
330 $indelim = ",";
333 $this->_where .= ")";
334 $this->_where_delim = " and";
339 // prepend the sql so the statement will work correctly
340 if ($this->_where) {
341 $this->_where = " where " . $this->_where;
344 // if the user has called SetOrder then use that for the order
345 if ($this->_set_order) {
346 $this->_order = $this->_set_order;
349 // if any of the filters have an order by then add those
350 if (is_array($this->Filters)) {
351 $orderDelim = $this->_order ? ',' : '';
352 foreach ($this->Filters as $filter) {
353 $filterOrder = $filter->GetOrder($this);
354 if ($filterOrder) {
355 $this->_order .= $orderDelim . $filterOrder;
356 $orderDelim = ', ';
361 if ($this->_order) {
362 $this->_order = " order by " . $this->_order;
365 $this->OnPrepare();
366 $this->_is_prepared = true;
369 public function OnPrepare()
372 final public function GetWhere()
374 $this->Prepare();
375 return $this->_where;
377 final public function GetJoin()
379 $this->Prepare();
380 return $this->_join;
382 final public function GetOrder()
384 $this->Prepare();
385 return $this->_order;
389 * Adds an object property to the order by clause.
390 * If any sorting needs to be done
391 * on foreign tables, then for the moment, you need to override this method and
392 * handle it manually. You can call this method repeatedly to add more than
393 * one property for sorting.
395 * @param string $property
396 * the name of the object property (or '?' for random order)
397 * @param bool $desc
398 * (optional) set to true to sort in descending order (default false)
400 public function SetOrder($property, $desc = false)
402 if (! $property) {
403 // no property was specified.
404 return;
407 $this->_order_delim = ($this->_set_order) ? "," : "";
409 if ($property == '?') {
410 $this->_set_order = "RAND()" . $this->_order_delim . $this->_set_order;
411 } else {
412 $colname = $this->GetFieldFromProp($property);
413 $this->_set_order .= $this->_order_delim . $colname . ($desc ? " desc" : "");
416 private function InitMaps()
418 if (! $this->_fieldmaps) {
419 // we have to open the file to get the fieldmaps
420 $mapname = $this->_map_object_class;
421 $this->IncludeMap($mapname);
423 $this->_fieldmaps = call_user_func(array (
424 $mapname,
425 "GetFieldMaps"
427 $this->_keymaps = call_user_func(array (
428 $mapname,
429 "GetKeyMaps"
435 * If the map class is not already defined, attempts to require_once the definition.
436 * If the Map file cannot be located, an exception is thrown
438 * @access public
439 * @param string $objectclass
440 * The name of the object map class
442 public function IncludeMap($objectclass)
444 try {
445 Includer::RequireClass($objectclass, "Model/DAO/");
446 } catch (IncludeException $ex) {
447 throw new Exception($ex->getMessage() . '. If a map file does not exist then ' . get_class($this) . ' can implement GetFieldFromProp instead.');
450 protected function GetFieldMaps()
452 $this->InitMaps();
453 return $this->_fieldmaps;
455 protected function GetKeyMaps()
457 $this->InitMaps();
458 return $this->_keymaps;
460 public function GetFieldFromProp($propname)
462 if (get_class($this) == "Criteria") {
463 throw new Exception("Phreeze is unable to determine field mapping. The base Criteria class should only be used to query by primary key without sorting");
466 $fms = $this->GetFieldMaps();
468 // make sure this property is defined
469 if (! isset($fms [$propname])) {
470 throw new Exception(get_class($this) . " is unable to determine the database column for the property: '$propname'");
473 // print_r($this->_fieldmaps);
474 $fm = $fms [$propname];
476 return $fm->FieldType == FM_CALCULATION ? "(" . $fm->ColumnName . ")" : "`" . $fm->TableName . "`.`" . $fm->ColumnName . "`";
480 * Throw an exception if an undeclared property is accessed
482 * @access public
483 * @param string $key
484 * @throws Exception
486 public function __get($key)
488 if (! Phreezer::$COMPAT_VERSION_2) {
489 throw new Exception("Unknown property: $key");
494 * Throw an exception if an undeclared property is accessed
496 * @access public
497 * @param string $key
498 * @param string $val
499 * @throws Exception
501 public function __set($key, $val)
503 if (! Phreezer::$COMPAT_VERSION_2) {
504 throw new Exception("Unknown property: $key");