Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
44 / 44 |
|
100.00% |
5 / 5 |
CRAP | |
100.00% |
1 / 1 |
RandomCommand | |
100.00% |
44 / 44 |
|
100.00% |
5 / 5 |
12 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 | |||
getArguments | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
execute | |
100.00% |
29 / 29 |
|
100.00% |
1 / 1 |
8 | |||
getQuery | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getLimit | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | /** |
4 | * Return random records command. |
5 | * |
6 | * PHP version 8 |
7 | * |
8 | * Copyright (C) Villanova University 2010. |
9 | * Copyright (C) The National Library of Finland 2021. |
10 | * |
11 | * This program is free software; you can redistribute it and/or modify |
12 | * it under the terms of the GNU General Public License version 2, |
13 | * as published by the Free Software Foundation. |
14 | * |
15 | * This program is distributed in the hope that it will be useful, |
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
18 | * GNU General Public License for more details. |
19 | * |
20 | * You should have received a copy of the GNU General Public License |
21 | * along with this program; if not, write to the Free Software |
22 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
23 | * |
24 | * @category VuFind |
25 | * @package Search |
26 | * @author Luke O'Sullivan <l.osullivan@swansea.ac.uk> |
27 | * @author Aleksi Peebles <aleksi.peebles@helsinki.fi> |
28 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
29 | * @link https://vufind.org |
30 | */ |
31 | |
32 | namespace VuFindSearch\Command; |
33 | |
34 | use VuFindSearch\Backend\BackendInterface; |
35 | use VuFindSearch\Feature\RandomInterface; |
36 | use VuFindSearch\ParamBag; |
37 | use VuFindSearch\Query\QueryInterface; |
38 | |
39 | use function in_array; |
40 | |
41 | /** |
42 | * Return random records command. |
43 | * |
44 | * @category VuFind |
45 | * @package Search |
46 | * @author Luke O'Sullivan <l.osullivan@swansea.ac.uk> |
47 | * @author Aleksi Peebles <aleksi.peebles@helsinki.fi> |
48 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
49 | * @link https://vufind.org |
50 | */ |
51 | class RandomCommand extends CallMethodCommand |
52 | { |
53 | /** |
54 | * Search query. |
55 | * |
56 | * @var QueryInterface |
57 | */ |
58 | protected $query; |
59 | |
60 | /** |
61 | * Search limit. |
62 | * |
63 | * @var int |
64 | */ |
65 | protected $limit; |
66 | |
67 | /** |
68 | * RandomCommand constructor. |
69 | * |
70 | * @param string $backendId Search backend identifier |
71 | * @param QueryInterface $query Search query |
72 | * @param int $limit Search limit |
73 | * @param ?ParamBag $params Search backend parameters |
74 | */ |
75 | public function __construct( |
76 | string $backendId, |
77 | QueryInterface $query, |
78 | int $limit, |
79 | ?ParamBag $params = null |
80 | ) { |
81 | $this->query = $query; |
82 | $this->limit = $limit; |
83 | parent::__construct( |
84 | $backendId, |
85 | RandomInterface::class, |
86 | 'random', |
87 | $params |
88 | ); |
89 | } |
90 | |
91 | /** |
92 | * Return search backend interface method arguments. |
93 | * |
94 | * @return array |
95 | */ |
96 | public function getArguments(): array |
97 | { |
98 | return [ |
99 | $this->getQuery(), |
100 | $this->getLimit(), |
101 | $this->getSearchParameters(), |
102 | ]; |
103 | } |
104 | |
105 | /** |
106 | * Execute command on backend. |
107 | * |
108 | * @param BackendInterface $backend Backend |
109 | * |
110 | * @return CommandInterface Command instance for method chaining |
111 | */ |
112 | public function execute(BackendInterface $backend): CommandInterface |
113 | { |
114 | // If the backend implements the RetrieveRandomInterface, we can load |
115 | // all the records at once. |
116 | if ($backend instanceof RandomInterface) { |
117 | return parent::execute($backend); |
118 | } |
119 | |
120 | // Otherwise, we need to load them one at a time and aggregate them. |
121 | |
122 | $query = $this->getQuery(); |
123 | $limit = $this->getLimit(); |
124 | |
125 | // offset/limit of 0 - we don't need records, just count |
126 | $results = $backend->search($query, 0, 0, $this->params); |
127 | $total_records = $results->getTotal(); |
128 | |
129 | if (0 === $total_records) { |
130 | // Empty result? Send back as-is: |
131 | $response = $results; |
132 | } elseif ($total_records < $limit) { |
133 | // Result set smaller than limit? Get everything and shuffle: |
134 | $response = $backend->search($query, 0, $limit, $this->params); |
135 | $response->shuffle(); |
136 | } else { |
137 | // Default case: retrieve n random records: |
138 | $response = false; |
139 | $retrievedIndexes = []; |
140 | for ($i = 0; $i < $limit; $i++) { |
141 | $nextIndex = rand(0, $total_records - 1); |
142 | while (in_array($nextIndex, $retrievedIndexes)) { |
143 | // avoid duplicate records |
144 | $nextIndex = rand(0, $total_records - 1); |
145 | } |
146 | $retrievedIndexes[] = $nextIndex; |
147 | $currentBatch = $backend->search( |
148 | $query, |
149 | $nextIndex, |
150 | 1, |
151 | $this->params |
152 | ); |
153 | if (!$response) { |
154 | $response = $currentBatch; |
155 | } elseif ($record = $currentBatch->first()) { |
156 | $response->add($record); |
157 | } |
158 | } |
159 | } |
160 | |
161 | return $this->finalizeExecution($response); |
162 | } |
163 | |
164 | /** |
165 | * Return search query. |
166 | * |
167 | * @return QueryInterface |
168 | */ |
169 | public function getQuery(): QueryInterface |
170 | { |
171 | return $this->query; |
172 | } |
173 | |
174 | /** |
175 | * Return search limit. |
176 | * |
177 | * @return int |
178 | */ |
179 | public function getLimit(): int |
180 | { |
181 | return $this->limit; |
182 | } |
183 | } |