ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
DatabaseSearcher.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
33
35{
38 protected \ilDBInterface $db;
39
40 public function __construct(
44 ) {
45 $this->ressource_factory = $ressource_factory;
46 $this->paths_parser_factory = $paths_parser_factory;
47 $this->db = $db;
48 }
49
50 public function search(
51 ClauseInterface $clause,
52 ?int $limit,
53 ?int $offset,
54 FilterInterface ...$filters
55 ): \Generator {
56 $paths_parser = $this->paths_parser_factory->forSearch();
57 $where = $this->getClauseForQueryHaving($clause, $paths_parser);
58 $quoted_table_alias = $this->quoteIdentifier($paths_parser->getTableAliasForFilters());
59
60 $query = $paths_parser->getSelectForQuery() . ' GROUP BY ' . $quoted_table_alias . '.rbac_id, ' .
61 $quoted_table_alias . '.obj_id, ' . $quoted_table_alias . '.obj_type HAVING ' . $where .
62 $this->getFiltersForQueryHaving($quoted_table_alias, ...$filters) .
63 ' ORDER BY rbac_id, obj_id, obj_type' . $this->getLimitAndOffsetForQuery($limit, $offset);
64
65 foreach ($this->queryDB($query) as $row) {
66 yield $this->ressource_factory->ressourceID(
67 (int) $row['rbac_id'],
68 (int) $row['obj_id'],
69 (string) $row['obj_type']
70 );
71 }
72 }
73
74 protected function getFiltersForQueryHaving(
75 string $quoted_table_alias,
76 FilterInterface ...$filters
77 ): string {
78 $filter_where = [];
79 foreach ($filters as $filter) {
80 $filter_values = [];
81 if ($val = $this->getFilterValueForCondition($quoted_table_alias, $filter->objID())) {
82 $filter_values[] = $quoted_table_alias . '.rbac_id = ' . $val;
83 }
84 if ($val = $this->getFilterValueForCondition($quoted_table_alias, $filter->subID())) {
85 $filter_values[] = $quoted_table_alias . '.obj_id = ' . $val;
86 }
87 if ($val = $this->getFilterValueForCondition($quoted_table_alias, $filter->type())) {
88 $filter_values[] = $quoted_table_alias . '.obj_type = ' . $val;
89 }
90 if (!empty($filter_values)) {
91 $filter_where[] = '(' . implode(' AND ', $filter_values) . ')';
92 }
93 }
94
95 if (empty($filter_where)) {
96 return '';
97 }
98
99 return ' AND (' . implode(' OR ', $filter_where) . ')';
100 }
101
102 protected function getFilterValueForCondition(
103 string $quoted_table_alias,
104 string|int|Placeholder $value
105 ): string {
106 if (is_int($value)) {
107 return $this->quoteInteger($value);
108 }
109 if (is_string($value)) {
110 return $this->quoteText($value);
111 }
112
113 switch ($value) {
114 case Placeholder::OBJ_ID:
115 return $quoted_table_alias . '.rbac_id';
116
117 case Placeholder::SUB_ID:
118 return $quoted_table_alias . '.obj_id';
119
120 case Placeholder::TYPE:
121 return $quoted_table_alias . '.obj_type';
122
123 case Placeholder::ANY:
124 default:
125 return '';
126 }
127 }
128
129 protected function getLimitAndOffsetForQuery(?int $limit, ?int $offset): string
130 {
131 $query_limit = '';
132 if (!is_null($limit) || !is_null($offset)) {
133 $limit = is_null($limit) ? PHP_INT_MAX : $limit;
134 $query_limit = ' LIMIT ' . $this->quoteInteger($limit);
135 }
136 $query_offset = '';
137 if (!is_null($offset)) {
138 $query_offset = ' OFFSET ' . $this->quoteInteger($offset);
139 }
140 return $query_limit . $query_offset;
141 }
142
143 protected function getClauseForQueryHaving(
144 ClauseInterface $clause,
145 DatabasePathsParserInterface $paths_parser,
146 int $depth = 0
147 ): string {
148 if ($depth > 50) {
149 throw new \ilMDRepositoryException('Search clause is nested to deep.');
150 }
151
152 if (!$clause->isJoin()) {
153 return $this->getBasicClauseForQueryWhere(
154 $clause->basicProperties(),
155 $clause->isNegated(),
156 $paths_parser
157 );
158 }
159
160 $join_props = $clause->joinProperties();
161
162 $sub_clauses_for_query = [];
163 foreach ($join_props->subClauses() as $sub_clause) {
164 $sub_clauses_for_query[] = $this->getClauseForQueryHaving($sub_clause, $paths_parser, $depth + 1);
165 }
166
167 switch ($join_props->operator()) {
168 case Operator::AND:
169 $operator_for_query = 'AND';
170 break;
171
172 case Operator::OR:
173 $operator_for_query = 'OR';
174 break;
175
176 default:
177 throw new \ilMDRepositoryException('Invalid search operator.');
178 }
179
180 $negation = '';
181 if ($clause->isNegated()) {
182 $negation = 'NOT ';
183 }
184
185 return $negation . '(' . implode(' ' . $operator_for_query . ' ', $sub_clauses_for_query) . ')';
186 }
187
188 protected function getBasicClauseForQueryWhere(
189 BasicPropertiesInterface $basic_props,
190 bool $is_clause_negated,
191 DatabasePathsParserInterface $paths_parser
192 ): string {
193 switch ($basic_props->mode()) {
194 case Mode::EQUALS:
195 $comparison = '= ' .
196 $this->quoteText($basic_props->value());
197 break;
198
199 case Mode::CONTAINS:
200 $comparison = 'LIKE ' .
201 $this->quoteText('%' . $basic_props->value() . '%');
202 break;
203
204 case Mode::STARTS_WITH:
205 $comparison = 'LIKE ' .
206 $this->quoteText($basic_props->value() . '%');
207 break;
208
209 case Mode::ENDS_WITH:
210 $comparison = 'LIKE ' .
211 $this->quoteText('%' . $basic_props->value());
212 break;
213
214 default:
215 throw new \ilMDRepositoryException('Invalid search mode.');
216 }
217
218 $mode_negation = '';
219 if ($basic_props->isModeNegated()) {
220 $mode_negation = 'NOT ';
221 }
222 $clause_negation = '';
223 if ($is_clause_negated) {
224 $clause_negation = 'NOT ';
225 }
226
227 $needs_join_to_base_table = $basic_props->value() === '' || $is_clause_negated;
228
229 return $clause_negation . 'COUNT(CASE WHEN ' . $mode_negation .
230 $paths_parser->addPathAndGetColumn($basic_props->path(), $needs_join_to_base_table) .
231 ' ' . $comparison . ' THEN 1 END) > 0';
232 }
233
234 protected function queryDB(string $query): \Generator
235 {
236 $result = $this->db->query($query);
237
238 while ($row = $this->db->fetchAssoc($result)) {
239 yield $row;
240 }
241 }
242
243 protected function quoteIdentifier(string $identifier): string
244 {
245 return $this->db->quoteIdentifier($identifier);
246 }
247
248 protected function quoteText(string $text): string
249 {
250 return $this->db->quote($text, \ilDBConstants::T_TEXT);
251 }
252
253 protected function quoteInteger(int $integer): string
254 {
255 return $this->db->quote($integer, \ilDBConstants::T_INTEGER);
256 }
257}
search(ClauseInterface $clause, ?int $limit, ?int $offset, FilterInterface ... $filters)
__construct(RessourceIDFactoryInterface $ressource_factory, DatabasePathsParserFactoryInterface $paths_parser_factory, \ilDBInterface $db)
getFilterValueForCondition(string $quoted_table_alias, string|int|Placeholder $value)
getClauseForQueryHaving(ClauseInterface $clause, DatabasePathsParserInterface $paths_parser, int $depth=0)
getFiltersForQueryHaving(string $quoted_table_alias, FilterInterface ... $filters)
getBasicClauseForQueryWhere(BasicPropertiesInterface $basic_props, bool $is_clause_negated, DatabasePathsParserInterface $paths_parser)
Interface ilDBInterface.
if(!file_exists('../ilias.ini.php'))