Created
April 17, 2011 07:54
-
-
Save ajshort/923835 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| diff --git a/core/model/DataList.php b/core/model/DataList.php | |
| index c94250a..4305923 100644 | |
| --- a/core/model/DataList.php | |
| +++ b/core/model/DataList.php | |
| @@ -58,7 +58,16 @@ class DataList extends DataObjectSet { | |
| public function sql() { | |
| return $this->dataQuery->query()->sql(); | |
| } | |
| - | |
| + | |
| + /** | |
| + * @see DataQuery::filter() | |
| + * @returns DataList | |
| + */ | |
| + public function filter($field, $value = null) { | |
| + $this->dataQuery->filter($field, $value); | |
| + return $this; | |
| + } | |
| + | |
| /** | |
| * Add a WHERE clause to the query. | |
| * | |
| diff --git a/core/model/DataQuery.php b/core/model/DataQuery.php | |
| index 3494ab2..85cd18a 100644 | |
| --- a/core/model/DataQuery.php | |
| +++ b/core/model/DataQuery.php | |
| @@ -6,6 +6,23 @@ | |
| * Used extensively by DataList. | |
| */ | |
| class DataQuery { | |
| + | |
| + const DEFAULT_FILTER = 'equals'; | |
| + | |
| + public static $filters = array( | |
| + 'equals' => array('DefaultDataFilters', 'equals'), | |
| + 'in' => array('DefaultDataFilters', 'in'), | |
| + 'like' => array('DefaultDataFilters', 'like'), | |
| + 'startswith' => array('DefaultDataFilters', 'startswith'), | |
| + 'endswith' => array('DefaultDataFilters', 'endswith'), | |
| + 'contains' => array('DefaultDataFilters', 'contains'), | |
| + 'gt' => array('DefaultDataFilters', 'gt'), | |
| + 'gte' => array('DefaultDataFilters', 'gte'), | |
| + 'lt' => array('DefaultDataFilters', 'lt'), | |
| + 'lte' => array('DefaultDataFilters', 'lte'), | |
| + 'between' => array('DefaultDataFilters', 'between'), | |
| + ); | |
| + | |
| protected $dataClass; | |
| protected $query; | |
| @@ -76,8 +93,8 @@ class DataQuery { | |
| // Error checking | |
| if(!$tableClasses) { | |
| - if(!ManifestBuilder::has_been_included()) { | |
| - user_error("DataObjects have been requested before the manifest is loaded. Please ensure you are not querying the database in _config.php.", E_USER_ERROR); | |
| + if(!DB::getConn()) { | |
| + user_error("DataObjects have been requested before the database is connected. Please ensure you are not querying the database in _config.php.", E_USER_ERROR); | |
| } else { | |
| user_error("DataObject::buildSQL: Can't find data classes (classes linked to tables) for $this->dataClass. Please ensure you run dev/build after creating a new DataObject.", E_USER_ERROR); | |
| } | |
| @@ -252,7 +269,109 @@ class DataQuery { | |
| } | |
| } | |
| } | |
| - | |
| + | |
| + /** | |
| + * | |
| + * | |
| + * @param string|array $field | |
| + * @param mixed $value | |
| + * @return DataQuery | |
| + */ | |
| + public function filter($field, $value = null) { | |
| + if (is_array($field)) { | |
| + foreach ($field as $field => $value) { | |
| + $this->filter($field, $value); | |
| + } | |
| + | |
| + return $this; | |
| + } | |
| + | |
| + $sqlFilters = array(); | |
| + $negated = false; | |
| + $notted = false; | |
| + | |
| + // Split the field up into a number of fields, and an optional negation | |
| + // followed by a filter then a not. | |
| + if (!$colPos = strpos($field, ':')) { | |
| + $fields = explode(',', $field); | |
| + $filter = self::DEFAULT_FILTER; | |
| + } else { | |
| + $fields = explode(',', substr($field, 0, $colPos)); | |
| + $filter = substr($field, $colPos + 1); | |
| + $filter = explode(':', $filter); | |
| + | |
| + foreach ($filter as $f) switch ($f) { | |
| + case 'not': $notted = !$notted; break; | |
| + case 'negate': $negated = !$negated; break; | |
| + default: $filter = strtolower($f); break; | |
| + } | |
| + | |
| + if (!$filter) { | |
| + $filter = self::DEFAULT_FILTER; | |
| + } | |
| + } | |
| + | |
| + // If we're only filtering on a single value, then collapse down a not | |
| + // wrapper. | |
| + if (!is_array($value) && $notted) { | |
| + $notted = false; | |
| + $negated = !$negated; | |
| + } | |
| + | |
| + $value = $this->filterEscape($value); | |
| + | |
| + if (!array_key_exists($filter, self::$filters)) { | |
| + throw new Exception("The filter '$filter' does not exist."); | |
| + } | |
| + | |
| + foreach ($fields as $field) { | |
| + $table = $this->applyRelation($field); | |
| + $field = strtok($field, '.'); | |
| + | |
| + if ($field == 'ID') { | |
| + $table = ClassInfo::baseDataClass($table); | |
| + } else { | |
| + while (!singleton($table)->hasOwnTableDatabaseField($field)) { | |
| + if (($table = get_parent_class($table)) == 'DataObject') { | |
| + throw new Exception("Could not find field '$field'."); | |
| + } | |
| + } | |
| + } | |
| + | |
| + $field = "\"$table\".\"$field\""; | |
| + $clause = call_user_func(self::$filters[$filter], $field, $value, $negated, $this); | |
| + | |
| + if ($notted) { | |
| + $clause = "NOT($clause)"; | |
| + } | |
| + | |
| + $sqlFilters[] = $clause; | |
| + } | |
| + | |
| + if (count($sqlFilters) > 1) { | |
| + $this->where('(' . implode(') OR (', $sqlFilters) . ')'); | |
| + } else { | |
| + $this->where($sqlFilters[0]); | |
| + } | |
| + | |
| + return $this; | |
| + } | |
| + | |
| + protected function filterEscape($value) { | |
| + if (is_array($value)) { | |
| + return array_map(array($this, 'filterEscape'), $value); | |
| + } else { | |
| + switch (true) { | |
| + case is_int($value): | |
| + return (string) $value; | |
| + case is_bool($value): | |
| + return $value ? '1' : '0'; | |
| + default: | |
| + return "'" . Convert::raw2sql($value) . "'"; | |
| + } | |
| + } | |
| + } | |
| + | |
| /** | |
| * Set the HAVING clause of this query | |
| */ | |
| diff --git a/core/model/DefaultDataFilters.php b/core/model/DefaultDataFilters.php | |
| new file mode 100644 | |
| index 0000000..d1fa857 | |
| --- /dev/null | |
| +++ b/core/model/DefaultDataFilters.php | |
| @@ -0,0 +1,113 @@ | |
| +<?php | |
| +/** | |
| + * | |
| + * | |
| + * @package sapphire | |
| + * @subpackage model | |
| + */ | |
| +class DefaultDataFilters { | |
| + | |
| + public static function equals($field, $value, $negated) { | |
| + if (is_array($value)) { | |
| + return self::in($field, $value, $negated); | |
| + } else { | |
| + return !$negated ? "$field = $value" : "$field <> $value"; | |
| + } | |
| + } | |
| + | |
| + public static function in($field, $values, $negated) { | |
| + $value = '(' . implode(', ', $values) . ')'; | |
| + $operator = !$negated ? 'IN' : 'NOT IN'; | |
| + | |
| + return "$field $operator $value"; | |
| + } | |
| + | |
| + public static function like($field, $value, $negated) { | |
| + return self::comparison($field, !$negated ? 'LIKE' : 'NOT LIKE', $value); | |
| + } | |
| + | |
| + public static function startswith($field, $value, $negated) { | |
| + if (is_array($value)) { | |
| + foreach ($value as $k => $_value) { | |
| + $value[$k] = substr($_value, 0, -1) . "%'"; | |
| + } | |
| + } else { | |
| + $value = substr($value, 0, -1) . "%'"; | |
| + } | |
| + | |
| + return self::like($field, $value, $negated); | |
| + } | |
| + | |
| + public static function endswith($a, $b, $negated) { | |
| + if (is_array($value)) { | |
| + foreach ($value as $k => $_value) { | |
| + $value[$k] = "'%" . substr($_value, 1); | |
| + } | |
| + } else { | |
| + $value = "'%" . substr($value, 1); | |
| + } | |
| + | |
| + return self::like($field, $value, $negated); | |
| + } | |
| + | |
| + public static function contains($a, $b, $negated) { | |
| + if (is_array($value)) { | |
| + foreach ($value as $k => $_value) { | |
| + $value[$k] = "'%" . substr($_value, 1, -1) . "%'"; | |
| + } | |
| + } else { | |
| + $value = "'%" . substr($value, 1, -1) . "%'"; | |
| + } | |
| + | |
| + return self::like($field, $value, $negated); | |
| + } | |
| + | |
| + public static function gt($field, $value, $negated) { | |
| + return self::comparison($field, !$negated ? '>' : '<=', $value); | |
| + } | |
| + | |
| + public static function gte($field, $value, $negated) { | |
| + return self::comparison($field, !$negated ? '>=' : '<', $value); | |
| + } | |
| + | |
| + public static function lt($field, $value, $negated) { | |
| + return self::comparison($field, !$negated ? '<' : '>=', $value); | |
| + } | |
| + | |
| + public static function lte($field, $value, $negated) { | |
| + return self::comparison($field, !$negated ? '<=' : '>', $value); | |
| + } | |
| + | |
| + public static function between($field, $values, $negated) { | |
| + if (!is_array($values)) { | |
| + throw new Exception('The between filter requires an array of values.'); | |
| + } | |
| + | |
| + if (is_array($values[0])) { | |
| + $value = array(); | |
| + | |
| + foreach ($values as $val) { | |
| + $value[] = "{$val[0]} AND {$val[1]}"; | |
| + } | |
| + } else { | |
| + $value = "{$values[0]} AND {$values[1]}"; | |
| + } | |
| + | |
| + return self::comparison($field, !$negated ? 'BETWEEN' : 'NOT BETWEEN', $value); | |
| + } | |
| + | |
| + protected static function comparison($field, $operator, $values) { | |
| + if (is_array($values)) { | |
| + $ors = array(); | |
| + | |
| + foreach ($values as $value) { | |
| + $ors[] = "$field $operator $value"; | |
| + } | |
| + | |
| + return implode(' OR ', $ors); | |
| + } else { | |
| + return "$field $operator $values"; | |
| + } | |
| + } | |
| + | |
| +} | |
| \ No newline at end of file |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment