UnionQuery.php
1 <?php
2 /**
3  * wCMF - wemove Content Management Framework
4  * Copyright (C) 2005-2020 wemove digital solutions GmbH
5  *
6  * Licensed under the terms of the MIT License.
7  *
8  * See the LICENSE file distributed with this work for
9  * additional information.
10  */
11 namespace wcmf\lib\persistence;
12 
16 
17 /**
18  * UnionQuery combines multiple query results to allow for sorting and paginating
19  * over different queries.
20  *
21  * @author ingo herwig <ingo@wemove.com>
22  */
23 class UnionQuery {
24 
25  /**
26  * Execute the provided queries
27  * @param $queryProvider
28  * @param $buildDepth
29  * @param $orderby
30  * @param Info $pagingInfo
31  * @return Array of PersistentObject instances
32  */
33  public static function execute(UnionQueryProvider $queryProvider, $buildDepth=BuildDepth::SINGLE, $orderby=null, PagingInfo $pagingInfo=null) {
34  $cache = ObjectFactory::getInstance('dynamicCache');
35  $cacheSection = str_replace('\\', '.', __CLASS__);
36 
37  $queryIds = $queryProvider->getIds();
38  $numQueries = sizeof($queryIds);
39 
40  // get cache key for stored offsets of previous page
41  $pagingInfo = $pagingInfo ?: new PagingInfo(PagingInfo::SIZE_INFINITE);
42  $page = $pagingInfo->getPage();
43  $prevPage = $page > 1 ? $page-1 : 1;
44  $prevPagingInfo = new PagingInfo($pagingInfo->getPageSize(), true);
45  $prevPagingInfo->setPage($prevPage);
46  $prevCacheKey = self::getCacheKey($queryIds, $buildDepth, $orderby, $prevPagingInfo);
47 
48  // get offsets
49  if ($page == 1) {
50  $offsets = array_fill(0, $numQueries, 0);
51  }
52  else {
53  if ($cache->exists($cacheSection, $prevCacheKey)) {
54  $offsets = $cache->get($cacheSection, $prevCacheKey);
55  }
56  else {
57  // previous offsets must be generated by loading the pages for pages other than first
58  for ($i=1; $i<$page; $i++) {
59  $tmpPagingInfo = new PagingInfo($pagingInfo->getPageSize(), true);
60  $tmpPagingInfo->setPage($i);
61  $tmpCacheKey = self::getCacheKey($queryIds, $buildDepth, $orderby, $tmpPagingInfo);
62  if (!$cache->exists($cacheSection, $tmpCacheKey)) {
63  self::execute($queryProvider, $buildDepth, $orderby, $tmpPagingInfo);
64  }
65  $offsets = $cache->get($cacheSection, $tmpCacheKey);
66  }
67  }
68  }
69 
70  $tmpResult = [];
71  $total = 0;
72  for ($i=0, $countI=$numQueries; $i<$countI; $i++) {
73  // collect n objects from each query
74  $queryId = $queryIds[$i];
75 
76  // set paging info
77  $tmpPagingInfo = new PagingInfo($pagingInfo->getPageSize(), false);
78  $tmpPagingInfo->setOffset($offsets[$i]);
79 
80  $objects = $queryProvider->execute($queryId, $buildDepth, $orderby, $tmpPagingInfo);
81  foreach ($objects as $object) {
82  $object->setProperty('queryId', $queryId);
83  }
84  $tmpResult = array_merge($tmpResult, $objects);
85  $total += $tmpPagingInfo->getTotalCount();
86  }
87  $pagingInfo->setTotalCount($total);
88 
89  // sort
90  if ($orderby != null) {
91  $comparator = new ObjectComparator($orderby);
92  usort($tmpResult, [$comparator, 'compare']);
93  }
94 
95  // truncate
96  $result = array_slice($tmpResult, 0, $pagingInfo->getPageSize());
97 
98  // update offsets
99  $counts = array_fill_keys($queryIds, 0);
100  for ($i=0, $count=sizeof($result); $i<$count; $i++) {
101  $counts[$result[$i]->getProperty('queryId')]++;
102  }
103  for ($i=0, $count=$numQueries; $i<$count; $i++) {
104  $offsets[$i] += $counts[$queryIds[$i]];
105  }
106  $cacheKey = self::getCacheKey($queryIds, $buildDepth, $orderby, $pagingInfo);
107  $cache->put($cacheSection, $cacheKey, $offsets);
108  return $result;
109  }
110 
111  /**
112  * Get a unique string for the given parameter values
113  * @param $ids
114  * @param $buildDepth
115  * @param $orderby
116  * @param $pagingInfo
117  * @return String
118  */
119  private static function getCacheKey($ids, $buildDepth, $orderby=null, PagingInfo $pagingInfo=null) {
120  $result = is_array($ids) ? join(',', $ids) : $ids;
121  $result .= ','.$buildDepth;
122  if ($orderby != null) {
123  $result .= ','.(is_array($orderby) ? join(',', $orderby) : $orderby);
124  }
125  if ($pagingInfo != null) {
126  $result .= ','.$pagingInfo->getOffset().','.$pagingInfo->getPageSize();
127  }
128  return md5($result);
129  }
130 }
131 ?>
setPage($page)
Set the current page (1-based) (also sets the offset).
Definition: PagingInfo.php:64
execute($queryId, $buildDepth, $orderby, $pagingInfo)
Execute a single query.
getIds()
Get identifiers for the contained queries.
Persistence layer related interfaces and classes.
Definition: namespaces.php:42
setOffset($offset)
Set the current offset (also selects the page).
Definition: PagingInfo.php:97
UnionQueryProvider is used to provide queries to a union query.
static getInstance($name, $dynamicConfiguration=[])
UnionQuery combines multiple query results to allow for sorting and paginating over different queries...
Definition: UnionQuery.php:23
PagingInfo contains information about a paged list.
Definition: PagingInfo.php:18
static execute(UnionQueryProvider $queryProvider, $buildDepth=BuildDepth::SINGLE, $orderby=null, PagingInfo $pagingInfo=null)
Execute the provided queries.
Definition: UnionQuery.php:33
ObjectFactory implements the service locator pattern by wrapping a Factory instance and providing sta...
ObjectComparator is used to compare persistent objects by given criterias.