2 /** @package verysimple::Phreeze */
5 * import supporting libraries
7 require_once("DataAdapter.php");
8 require_once("CriteriaFilter.php");
9 require_once("verysimple/IO/Includer.php");
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
25 protected $_where_delim;
27 protected $_order_delim;
28 protected $_is_prepared;
29 protected $_map_object_class;
32 private $_constructor_where;
33 private $_constructor_order;
35 private $_and = array ();
36 private $_or = array ();
37 public $PrimaryKeyField;
38 public $PrimaryKeyValue;
42 * @var $Filters a CriteriaFilter or array of CriteriaFilters to be applied to the query
45 public function __construct($where = "", $order = "")
47 $this->_constructor_where
= $where;
48 $this->_constructor_order
= $order;
50 $this->_where
= $where;
51 $this->_order
= $order;
57 * Init is called directly after construction and can be overridden.
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));
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;
83 * Return an array of CriteriaFilters that have been added to this criteria
87 public function GetFilters()
89 return $this->Filters
;
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
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
121 public function GetAnds()
127 * Escape values for insertion into a SQL query string
131 public function Escape($val)
133 return DataAdapter
::Escape($val);
137 * Returns DataAdapter::GetQuotedSql($val)
139 * @param variant $val
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
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
169 public function GetOrs()
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}
205 // loop through all of the properties and attempt to
206 // build a query based on any values that have been set
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 (
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 (";
303 foreach ($val as $n) {
304 $this->_where
.= $indelim . "'" . $this->Escape($n) . "'";
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 (";
328 foreach ($val as $n) {
329 $this->_where
.= $indelim . "'" . $this->Escape($n) . "'";
333 $this->_where
.= ")";
334 $this->_where_delim
= " and";
339 // prepend the sql so the statement will work correctly
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);
355 $this->_order
.= $orderDelim . $filterOrder;
362 $this->_order
= " order by " . $this->_order
;
366 $this->_is_prepared
= true;
369 public function OnPrepare()
372 final public function GetWhere()
375 return $this->_where
;
377 final public function GetJoin()
382 final public function GetOrder()
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)
398 * (optional) set to true to sort in descending order (default false)
400 public function SetOrder($property, $desc = false)
403 // no property was specified.
407 $this->_order_delim
= ($this->_set_order
) ?
"," : "";
409 if ($property == '?') {
410 $this->_set_order
= "RAND()" . $this->_order_delim
. $this->_set_order
;
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 (
427 $this->_keymaps
= call_user_func(array (
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
439 * @param string $objectclass
440 * The name of the object map class
442 public function IncludeMap($objectclass)
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()
453 return $this->_fieldmaps
;
455 protected function GetKeyMaps()
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
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
501 public function __set($key, $val)
503 if (! Phreezer
::$COMPAT_VERSION_2) {
504 throw new Exception("Unknown property: $key");