Elgg  Version master
Entities.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Database;
4 
5 use Doctrine\DBAL\Query\Expression\CompositeExpression;
12 
19 class Entities extends Repository {
20 
24  public function count() {
26 
27  $count_expr = $this->options->distinct ? "DISTINCT {$qb->getTableAlias()}.guid" : '*';
28  $qb->select("COUNT({$count_expr}) AS total");
29 
30  $qb = $this->buildQuery($qb);
31 
32  $result = _elgg_services()->db->getDataRow($qb);
33 
34  if (empty($result)) {
35  return 0;
36  }
37 
38  return (int) $result->total;
39  }
40 
51  public function calculate($function, $property, $property_type = null) {
52  if (!in_array(strtolower($function), QueryBuilder::CALCULATIONS)) {
53  throw new DomainException("'{$function}' is not a valid numeric function");
54  }
55 
56  if (!isset($property_type)) {
57  if (in_array($property, \ElggEntity::PRIMARY_ATTR_NAMES)) {
58  $property_type = 'attribute';
59  } else {
60  $property_type = 'metadata';
61  }
62  }
63 
65 
66  switch ($property_type) {
67  case 'attribute':
68  if (!in_array($property, \ElggEntity::PRIMARY_ATTR_NAMES)) {
69  throw new DomainException("'{$property}' is not a valid attribute");
70  }
71 
72  $qb->addSelect("{$function}({$qb->getTableAlias()}.{$property}) AS calculation");
73  break;
74 
75  case 'metadata':
76  $alias = $qb->joinMetadataTable($qb->getTableAlias(), 'guid', $property, 'inner', MetadataTable::DEFAULT_JOIN_ALIAS);
77  $qb->addSelect("{$function}({$alias}.value) AS calculation");
78  break;
79 
80  case 'annotation':
81  $alias = $qb->joinAnnotationTable($qb->getTableAlias(), 'guid', $property, 'inner', AnnotationsTable::DEFAULT_JOIN_ALIAS);
82  $qb->addSelect("{$function}({$alias}.value) AS calculation");
83  break;
84  }
85 
86  $qb = $this->buildQuery($qb);
87 
88  $result = _elgg_services()->db->getDataRow($qb);
89 
90  // do not cast data as calculations could be used as int, float or string
91  return $result->calculation;
92  }
93 
103  public function get($limit = null, $offset = null, $callback = null) {
105 
106  $distinct = $this->options->distinct ? 'DISTINCT ' : '';
107  $qb->select("{$distinct}{$qb->getTableAlias()}.*");
108 
109  $this->expandInto($qb, $qb->getTableAlias());
110 
111  $qb = $this->buildQuery($qb);
112 
113  // add default ordering
114  $original_order = elgg_extract('order_by', $this->options->__original_options);
115  if (empty($this->options->order_by) && $original_order !== false) {
116  $qb->addOrderBy("{$qb->getTableAlias()}.time_created", 'desc');
117  // also add order by guid, to rely less on internals of MySQL fallback ordering
118  $qb->addOrderBy("{$qb->getTableAlias()}.guid", 'desc');
119  }
120 
121  if ($limit > 0) {
122  $qb->setMaxResults((int) $limit);
123  $qb->setFirstResult((int) $offset);
124  }
125 
126  $options = $this->options->getArrayCopy();
127 
128  $options['limit'] = (int) $limit;
129  $options['offset'] = (int) $offset;
130  $options['callback'] = $callback ?: $this->options->callback;
131  if (!isset($options['callback'])) {
132  $options['callback'] = [_elgg_services()->entityTable, 'rowToElggStar'];
133  }
134 
135  unset($options['count']);
136 
137  return _elgg_services()->entityTable->fetch($qb, $options);
138  }
139 
149  public function getDates(): array {
150  $qb = Select::fromTable(EntityTable::TABLE_NAME, EntityTable::DEFAULT_JOIN_ALIAS);
151 
152  $qb->select("DISTINCT EXTRACT(YEAR_MONTH FROM FROM_UNIXTIME({$qb->getTableAlias()}.time_created)) AS yearmonth");
153 
154  $this->expandInto($qb, $qb->getTableAlias());
155 
156  $qb = $this->buildQuery($qb);
157 
158  $options = $this->options->getArrayCopy();
159  unset($options['count']);
160 
161  $options['callback'] = false;
162 
163  $results = _elgg_services()->entityTable->fetch($qb, $options);
164 
165  if (empty($results)) {
166  return [];
167  }
168 
169  return array_map(function ($e) {
170  return $e->yearmonth;
171  }, $results);
172  }
173 
180  public function execute() {
181  if ($this->options->annotation_calculation) {
182  $clauses = $this->options->annotation_name_value_pairs;
183  if (count($clauses) > 1 && $this->options->annotation_name_value_pairs_operator !== 'OR') {
184  throw new LogicException('Annotation calculation can not be performed on multiple annotation name value pairs merged with AND');
185  }
186 
187  $clause = array_shift($clauses);
188 
189  return $this->calculate($this->options->annotation_calculation, $clause->names, 'annotation');
190  } else if ($this->options->metadata_calculation) {
191  $clauses = $this->options->metadata_name_value_pairs;
192  if (count($clauses) > 1 && $this->options->metadata_name_value_pairs_operator !== 'OR') {
193  throw new LogicException('Metadata calculation can not be performed on multiple metadata name value pairs merged with AND');
194  }
195 
196  $clause = array_shift($clauses);
197 
198  return $this->calculate($this->options->metadata_calculation, $clause->names, 'metadata');
199  } else if ($this->options->count) {
200  return $this->count();
201  } else if ($this->options->batch) {
202  return $this->batch($this->options->limit, $this->options->offset, $this->options->callback);
203  } else {
204  return $this->get($this->options->limit, $this->options->offset, $this->options->callback);
205  }
206  }
207 
215  protected function buildQuery(QueryBuilder $qb) {
216  $ands = [];
217 
218  foreach ($this->options->joins as $join) {
219  $join->prepare($qb, $qb->getTableAlias());
220  }
221 
222  foreach ($this->options->wheres as $where) {
223  $ands[] = $where->prepare($qb, $qb->getTableAlias());
224  }
225 
226  $ands[] = $this->buildEntityClause($qb);
227  $ands[] = $this->buildPairedMetadataClause($qb, $this->options->metadata_name_value_pairs, $this->options->metadata_name_value_pairs_operator);
228  $ands[] = $this->buildPairedMetadataClause($qb, $this->options->search_name_value_pairs, 'OR');
229  $ands[] = $this->buildPairedAnnotationClause($qb, $this->options->annotation_name_value_pairs, $this->options->annotation_name_value_pairs_operator);
230  $ands[] = $this->buildPairedRelationshipClause($qb, $this->options->relationship_pairs);
231 
232  $ands = $qb->merge($ands);
233 
234  if (!empty($ands)) {
235  $qb->andWhere($ands);
236  }
237 
238  return $qb;
239  }
240 
249  protected function buildEntityClause(QueryBuilder $qb) {
250  return EntityWhereClause::factory($this->options)->prepare($qb, $qb->getTableAlias());
251  }
252 
263  protected function buildPairedMetadataClause(QueryBuilder $qb, $clauses, $boolean = 'AND') {
264  $parts = [];
265 
266  foreach ($clauses as $clause) {
267  if ($clause instanceof MetadataWhereClause) {
268  if (strtoupper($boolean) === 'OR' || count($clauses) === 1) {
269  $joined_alias = $qb->joinMetadataTable($qb->getTableAlias(), 'guid', null, 'inner', MetadataTable::DEFAULT_JOIN_ALIAS);
270  } else {
271  $joined_alias = $qb->joinMetadataTable($qb->getTableAlias(), 'guid', $clause->names);
272  }
273 
274  $parts[] = $clause->prepare($qb, $joined_alias);
275  }
276  }
277 
278  return $qb->merge($parts, $boolean);
279  }
280 
291  protected function buildPairedAnnotationClause(QueryBuilder $qb, $clauses, $boolean = 'AND') {
292  $parts = [];
293 
294  foreach ($clauses as $clause) {
295  if (strtoupper($boolean) === 'OR' || count($clauses) === 1) {
296  $joined_alias = $qb->joinAnnotationTable($qb->getTableAlias(), 'guid', null, 'inner', AnnotationsTable::DEFAULT_JOIN_ALIAS);
297  } else {
298  $joined_alias = $qb->joinAnnotationTable($qb->getTableAlias(), 'guid', $clause->names);
299  }
300 
301  $parts[] = $clause->prepare($qb, $joined_alias);
302  }
303 
304  return $qb->merge($parts, $boolean);
305  }
306 
316  protected function buildPairedRelationshipClause(QueryBuilder $qb, $clauses, $boolean = 'AND') {
317  $parts = [];
318 
319  foreach ($clauses as $clause) {
320  if (strtoupper($boolean) === 'OR' || count($clauses) === 1) {
321  $joined_alias = $qb->joinRelationshipTable($qb->getTableAlias(), $clause->join_on, null, $clause->inverse, 'inner', RelationshipsTable::DEFAULT_JOIN_ALIAS);
322  } else {
323  $joined_alias = $qb->joinRelationshipTable($qb->getTableAlias(), $clause->join_on, $clause->names, $clause->inverse);
324  }
325 
326  $parts[] = $clause->prepare($qb, $joined_alias);
327  }
328 
329  return $qb->merge($parts, $boolean);
330  }
331 }
const PRIMARY_ATTR_NAMES
Definition: ElggEntity.php:61
Builds queries for matching annotations against their properties.
Builds queries for filtering entities by their properties in the entities table.
Builds clauses for filtering entities by properties in metadata table.
Builds clauses for filtering entities by their properties in entity_relationships table.
Entities repository contains methods for fetching entities from database or performing calculations o...
Definition: Entities.php:19
buildPairedMetadataClause(QueryBuilder $qb, $clauses, $boolean='AND')
Process metadata name value pairs Joins the metadata table on entity guid in the entities table and a...
Definition: Entities.php:263
buildEntityClause(QueryBuilder $qb)
Process entity attribute wheres Applies entity attribute constrains on the selected entities table.
Definition: Entities.php:249
buildPairedRelationshipClause(QueryBuilder $qb, $clauses, $boolean='AND')
Process relationship pairs.
Definition: Entities.php:316
count()
{Count rows.int}
Definition: Entities.php:24
buildPairedAnnotationClause(QueryBuilder $qb, $clauses, $boolean='AND')
Process annotation name value pairs Joins the annotation table on entity guid in the entities table a...
Definition: Entities.php:291
execute()
Execute the query resolving calculation, count and/or batch options.
Definition: Entities.php:180
buildQuery(QueryBuilder $qb)
Build a database query.
Definition: Entities.php:215
getDates()
Returns a list of months in which entities were updated or created.
Definition: Entities.php:149
calculate($function, $property, $property_type=null)
Performs a mathematical calculation on a set of entity properties.
Definition: Entities.php:51
Entity table database service.
Definition: EntityTable.php:24
Database abstraction query builder.
Abstract methods for interfacing with the database.
Definition: Repository.php:16
expandInto(QueryBuilder $qb, $table_alias=null)
Extend query builder with select, group_by, having and order_by clauses from $options.
Definition: Repository.php:250
Query builder for fetching data from the database.
Definition: Select.php:8
static fromTable(string $table, ?string $alias=null)
Returns a QueryBuilder for selecting data from a given table.
Definition: Select.php:18
Exception thrown if a value does not adhere to a defined valid data domain.
Exception that represents error in the program logic.
_elgg_services()
Get the global service provider.
Definition: elgglib.php:353
elgg_extract($key, $array, $default=null, bool $strict=true)
Checks for $array[$key] and returns its value if it exists, else returns $default.
Definition: elgglib.php:256
if(empty($count)) $offset
Definition: pagination.php:26
$limit
Definition: pagination.php:28
$qb
Definition: queue.php:12
$results
Definition: content.php:22