PageRenderTime 25ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/search/filters/SearchFilter.php

http://github.com/silverstripe/sapphire
PHP | 300 lines | 135 code | 30 blank | 135 comment | 12 complexity | 8ef39e10377cd26f2eb77a61b87c5e3c MD5 | raw file
Possible License(s): BSD-3-Clause, MIT, CC-BY-3.0, GPL-2.0, AGPL-1.0, LGPL-2.1
  1. <?php
  2. use SilverStripe\ORM\DataObject;
  3. use SilverStripe\ORM\DataQuery;
  4. /**
  5. * Base class for filtering implementations,
  6. * which work together with {@link SearchContext}
  7. * to create or amend a query for {@link DataObject} instances.
  8. * See {@link SearchContext} for more information.
  9. *
  10. * @package framework
  11. * @subpackage search
  12. */
  13. abstract class SearchFilter extends Object {
  14. /**
  15. * @var string Classname of the inspected {@link DataObject}
  16. */
  17. protected $model;
  18. /**
  19. * @var string
  20. */
  21. protected $name;
  22. /**
  23. * @var string
  24. */
  25. protected $fullName;
  26. /**
  27. * @var mixed
  28. */
  29. protected $value;
  30. /**
  31. * @var array
  32. */
  33. protected $modifiers;
  34. /**
  35. * @var string Name of a has-one, has-many or many-many relation (not the classname).
  36. * Set in the constructor as part of the name in dot-notation, and used in
  37. * {@link applyRelation()}.
  38. */
  39. protected $relation;
  40. /**
  41. * @param string $fullName Determines the name of the field, as well as the searched database
  42. * column. Can contain a relation name in dot notation, which will automatically join
  43. * the necessary tables (e.g. "Comments.Name" to join the "Comments" has-many relationship and
  44. * search the "Name" column when applying this filter to a SiteTree class).
  45. * @param mixed $value
  46. * @param array $modifiers
  47. */
  48. public function __construct($fullName, $value = false, array $modifiers = array()) {
  49. $this->fullName = $fullName;
  50. // sets $this->name and $this->relation
  51. $this->addRelation($fullName);
  52. $this->value = $value;
  53. $this->setModifiers($modifiers);
  54. }
  55. /**
  56. * Called by constructor to convert a string pathname into
  57. * a well defined relationship sequence.
  58. *
  59. * @param string $name
  60. */
  61. protected function addRelation($name) {
  62. if (strstr($name, '.')) {
  63. $parts = explode('.', $name);
  64. $this->name = array_pop($parts);
  65. $this->relation = $parts;
  66. } else {
  67. $this->name = $name;
  68. }
  69. }
  70. /**
  71. * Set the root model class to be selected by this
  72. * search query.
  73. *
  74. * @param string $className
  75. */
  76. public function setModel($className) {
  77. $this->model = $className;
  78. }
  79. /**
  80. * Set the current value(s) to be filtered on.
  81. *
  82. * @param string|array $value
  83. */
  84. public function setValue($value) {
  85. $this->value = $value;
  86. }
  87. /**
  88. * Accessor for the current value to be filtered on.
  89. *
  90. * @return string|array
  91. */
  92. public function getValue() {
  93. return $this->value;
  94. }
  95. /**
  96. * Set the current modifiers to apply to the filter
  97. *
  98. * @param array $modifiers
  99. */
  100. public function setModifiers(array $modifiers) {
  101. $this->modifiers = array_map('strtolower', $modifiers);
  102. }
  103. /**
  104. * Accessor for the current modifiers to apply to the filter.
  105. *
  106. * @return array
  107. */
  108. public function getModifiers() {
  109. return $this->modifiers;
  110. }
  111. /**
  112. * The original name of the field.
  113. *
  114. * @return string
  115. */
  116. public function getName() {
  117. return $this->name;
  118. }
  119. /**
  120. * @param String
  121. */
  122. public function setName($name) {
  123. $this->name = $name;
  124. }
  125. /**
  126. * The full name passed to the constructor,
  127. * including any (optional) relations in dot notation.
  128. *
  129. * @return string
  130. */
  131. public function getFullName() {
  132. return $this->fullName;
  133. }
  134. /**
  135. * @param String
  136. */
  137. public function setFullName($name) {
  138. $this->fullName = $name;
  139. }
  140. /**
  141. * Normalizes the field name to table mapping.
  142. *
  143. * @return string
  144. */
  145. public function getDbName() {
  146. // Special handler for "NULL" relations
  147. if($this->name === "NULL") {
  148. return $this->name;
  149. }
  150. // Ensure that we're dealing with a DataObject.
  151. if (!is_subclass_of($this->model, 'SilverStripe\\ORM\\DataObject')) {
  152. throw new InvalidArgumentException(
  153. "Model supplied to " . get_class($this) . " should be an instance of DataObject."
  154. );
  155. }
  156. // Find table this field belongs to
  157. $table = DataObject::getSchema()->tableForField($this->model, $this->name);
  158. if(!$table) {
  159. // fallback to the provided name in the event of a joined column
  160. // name (as the candidate class doesn't check joined records)
  161. $parts = explode('.', $this->fullName);
  162. return '"' . implode('"."', $parts) . '"';
  163. }
  164. return sprintf('"%s"."%s"', $table, $this->name);
  165. }
  166. /**
  167. * Return the value of the field as processed by the DBField class
  168. *
  169. * @return string
  170. */
  171. public function getDbFormattedValue() {
  172. // SRM: This code finds the table where the field named $this->name lives
  173. // Todo: move to somewhere more appropriate, such as DataMapper, the magical class-to-be?
  174. $candidateClass = $this->model;
  175. $dbField = singleton($this->model)->dbObject($this->name);
  176. $dbField->setValue($this->value);
  177. return $dbField->RAW();
  178. }
  179. /**
  180. * Apply filter criteria to a SQL query.
  181. *
  182. * @param DataQuery $query
  183. * @return DataQuery
  184. */
  185. public function apply(DataQuery $query) {
  186. if(($key = array_search('not', $this->modifiers)) !== false) {
  187. unset($this->modifiers[$key]);
  188. return $this->exclude($query);
  189. }
  190. if(is_array($this->value)) {
  191. return $this->applyMany($query);
  192. } else {
  193. return $this->applyOne($query);
  194. }
  195. }
  196. /**
  197. * Apply filter criteria to a SQL query with a single value.
  198. *
  199. * @param DataQuery $query
  200. * @return DataQuery
  201. */
  202. abstract protected function applyOne(DataQuery $query);
  203. /**
  204. * Apply filter criteria to a SQL query with an array of values.
  205. *
  206. * @param DataQuery $query
  207. * @return DataQuery
  208. */
  209. protected function applyMany(DataQuery $query) {
  210. throw new InvalidArgumentException(get_class($this) . " can't be used to filter by a list of items.");
  211. }
  212. /**
  213. * Exclude filter criteria from a SQL query.
  214. *
  215. * @param DataQuery $query
  216. * @return DataQuery
  217. */
  218. public function exclude(DataQuery $query) {
  219. if(($key = array_search('not', $this->modifiers)) !== false) {
  220. unset($this->modifiers[$key]);
  221. return $this->apply($query);
  222. }
  223. if(is_array($this->value)) {
  224. return $this->excludeMany($query);
  225. } else {
  226. return $this->excludeOne($query);
  227. }
  228. }
  229. /**
  230. * Exclude filter criteria from a SQL query with a single value.
  231. *
  232. * @param DataQuery $query
  233. * @return DataQuery
  234. */
  235. abstract protected function excludeOne(DataQuery $query);
  236. /**
  237. * Exclude filter criteria from a SQL query with an array of values.
  238. *
  239. * @param DataQuery $query
  240. * @return DataQuery
  241. */
  242. protected function excludeMany(DataQuery $query) {
  243. throw new InvalidArgumentException(get_class($this) . " can't be used to filter by a list of items.");
  244. }
  245. /**
  246. * Determines if a field has a value,
  247. * and that the filter should be applied.
  248. * Relies on the field being populated with
  249. * {@link setValue()}
  250. *
  251. * @return boolean
  252. */
  253. public function isEmpty() {
  254. return false;
  255. }
  256. /**
  257. * Determines case sensitivity based on {@link getModifiers()}.
  258. *
  259. * @return Mixed TRUE or FALSE to enforce sensitivity, NULL to use field collation.
  260. */
  261. protected function getCaseSensitive() {
  262. $modifiers = $this->getModifiers();
  263. if(in_array('case', $modifiers)) return true;
  264. else if(in_array('nocase', $modifiers)) return false;
  265. else return null;
  266. }
  267. }