This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * $Id$ |
||
4 | */ |
||
5 | |||
6 | /** |
||
7 | * Copyright (c) 2001-2015, Andrew Aksyonoff |
||
8 | * Copyright (c) 2008-2015, Sphinx Technologies Inc |
||
9 | * All rights reserved |
||
10 | * |
||
11 | * This program is free software; you can redistribute it and/or modify |
||
12 | * it under the terms of the GNU Library General Public License. You should |
||
13 | * have received a copy of the LGPL license along with this program; if you |
||
14 | * did not, you can find it at http://www.gnu.org/ |
||
15 | */ |
||
16 | |||
17 | namespace Sphinx; |
||
18 | |||
19 | /** |
||
20 | * WARNING!!! |
||
21 | * |
||
22 | * As of 2015, we strongly recommend to use either SphinxQL or REST APIs |
||
23 | * rather than the native SphinxAPI. |
||
24 | * |
||
25 | * While both the native SphinxAPI protocol and the existing APIs will |
||
26 | * continue to exist, and perhaps should not even break (too much), exposing |
||
27 | * all the new features via multiple different native API implementations |
||
28 | * is too much of a support complication for us. |
||
29 | * |
||
30 | * That said, you're welcome to overtake the maintenance of any given |
||
31 | * official API, and remove this warning ;) |
||
32 | */ |
||
33 | |||
34 | /** |
||
35 | * Sphinx searchd client class |
||
36 | * PHP version of Sphinx searchd client (PHP API) |
||
37 | */ |
||
38 | class Client |
||
39 | { |
||
40 | /** |
||
41 | * Searchd host |
||
42 | * |
||
43 | * @var string |
||
44 | */ |
||
45 | protected $host = 'localhost'; |
||
46 | |||
47 | /** |
||
48 | * Searchd port |
||
49 | * |
||
50 | * @var int |
||
51 | */ |
||
52 | protected $port = 9312; |
||
53 | |||
54 | /** |
||
55 | * How many records to seek from result-set start |
||
56 | * |
||
57 | * @var int |
||
58 | */ |
||
59 | protected $offset = 0; |
||
60 | |||
61 | /** |
||
62 | * How many records to return from result-set starting at offset |
||
63 | * |
||
64 | * @var int |
||
65 | */ |
||
66 | protected $limit = 20; |
||
67 | |||
68 | /** |
||
69 | * Query matching mode |
||
70 | * |
||
71 | * @var int |
||
72 | */ |
||
73 | protected $mode = self::MATCH_EXTENDED2; |
||
74 | |||
75 | /** |
||
76 | * Per-field weights (default is 1 for all fields) |
||
77 | * |
||
78 | * @var array |
||
79 | */ |
||
80 | protected $weights = array(); |
||
81 | |||
82 | /** |
||
83 | * Match sorting mode |
||
84 | * |
||
85 | * @var int |
||
86 | */ |
||
87 | protected $sort = self::SORT_RELEVANCE; |
||
88 | |||
89 | /** |
||
90 | * Attribute to sort by |
||
91 | * |
||
92 | * @var string |
||
93 | */ |
||
94 | protected $sort_by = ''; |
||
95 | |||
96 | /** |
||
97 | * Min ID to match (0 means no limit) |
||
98 | * |
||
99 | * @var int |
||
100 | */ |
||
101 | protected $min_id = 0; |
||
102 | |||
103 | /** |
||
104 | * Max ID to match (0 means no limit) |
||
105 | * |
||
106 | * @var int |
||
107 | */ |
||
108 | protected $max_id = 0; |
||
109 | |||
110 | /** |
||
111 | * Search filters |
||
112 | * |
||
113 | * @var array |
||
114 | */ |
||
115 | protected $filters = array(); |
||
116 | |||
117 | /** |
||
118 | * Group-by attribute name |
||
119 | * |
||
120 | * @var string |
||
121 | */ |
||
122 | protected $group_by = ''; |
||
123 | |||
124 | /** |
||
125 | * Group-by function (to pre-process group-by attribute value with) |
||
126 | * |
||
127 | * @var int |
||
128 | */ |
||
129 | protected $group_func = self::GROUP_BY_DAY; |
||
130 | |||
131 | /** |
||
132 | * Group-by sorting clause (to sort groups in result set with) |
||
133 | * |
||
134 | * @var string |
||
135 | */ |
||
136 | protected $group_sort = '@group desc'; |
||
137 | |||
138 | /** |
||
139 | * Group-by count-distinct attribute |
||
140 | * |
||
141 | * @var string |
||
142 | */ |
||
143 | protected $group_distinct = ''; |
||
144 | |||
145 | /** |
||
146 | * Max matches to retrieve |
||
147 | * |
||
148 | * @var int |
||
149 | */ |
||
150 | protected $max_matches = 1000; |
||
151 | |||
152 | /** |
||
153 | * Cutoff to stop searching at |
||
154 | * |
||
155 | * @var int |
||
156 | */ |
||
157 | protected $cutoff = 0; |
||
158 | |||
159 | /** |
||
160 | * Distributed retries count |
||
161 | * |
||
162 | * @var int |
||
163 | */ |
||
164 | protected $retry_count = 0; |
||
165 | |||
166 | /** |
||
167 | * Distributed retries delay |
||
168 | * |
||
169 | * @var int |
||
170 | */ |
||
171 | protected $retry_delay = 0; |
||
172 | |||
173 | /** |
||
174 | * Geographical anchor point |
||
175 | * |
||
176 | * @var array |
||
177 | */ |
||
178 | protected $anchor = array(); |
||
179 | |||
180 | /** |
||
181 | * Per-index weights |
||
182 | * |
||
183 | * @var array |
||
184 | */ |
||
185 | protected $index_weights = array(); |
||
186 | |||
187 | /** |
||
188 | * Ranking mode |
||
189 | * |
||
190 | * @var int |
||
191 | */ |
||
192 | protected $ranker = self::RANK_PROXIMITY_BM25; |
||
193 | |||
194 | /** |
||
195 | * Ranking mode expression (for self::RANK_EXPR) |
||
196 | * |
||
197 | * @var string |
||
198 | */ |
||
199 | protected $rank_expr = ''; |
||
200 | |||
201 | /** |
||
202 | * Max query time, milliseconds (0 means no limit) |
||
203 | * |
||
204 | * @var int |
||
205 | */ |
||
206 | protected $max_query_time = 0; |
||
207 | |||
208 | /** |
||
209 | * Per-field-name weights |
||
210 | * |
||
211 | * @var array |
||
212 | */ |
||
213 | protected $field_weights = array(); |
||
214 | |||
215 | /** |
||
216 | * Per-query attribute values overrides |
||
217 | * |
||
218 | * @var array |
||
219 | */ |
||
220 | protected $overrides = array(); |
||
221 | |||
222 | /** |
||
223 | * Select-list (attributes or expressions, with optional aliases) |
||
224 | * |
||
225 | * @var string |
||
226 | */ |
||
227 | protected $select = '*'; |
||
228 | |||
229 | /** |
||
230 | * Per-query various flags |
||
231 | * |
||
232 | * @var int |
||
233 | */ |
||
234 | protected $query_flags = 0; |
||
235 | |||
236 | /** |
||
237 | * Per-query max_predicted_time |
||
238 | * |
||
239 | * @var int |
||
240 | */ |
||
241 | protected $predicted_time = 0; |
||
242 | |||
243 | /** |
||
244 | * Outer match sort by |
||
245 | * |
||
246 | * @var string |
||
247 | */ |
||
248 | protected $outer_order_by = ''; |
||
249 | |||
250 | /** |
||
251 | * Outer offset |
||
252 | * |
||
253 | * @var int |
||
254 | */ |
||
255 | protected $outer_offset = 0; |
||
256 | |||
257 | /** |
||
258 | * Outer limit |
||
259 | * |
||
260 | * @var int |
||
261 | */ |
||
262 | protected $outer_limit = 0; |
||
263 | |||
264 | /** |
||
265 | * @var bool |
||
266 | */ |
||
267 | protected $has_outer = false; |
||
268 | |||
269 | /** |
||
270 | * Last error message |
||
271 | * |
||
272 | * @var string |
||
273 | */ |
||
274 | protected $error = ''; |
||
275 | |||
276 | /** |
||
277 | * Last warning message |
||
278 | * |
||
279 | * @var string |
||
280 | */ |
||
281 | protected $warning = ''; |
||
282 | |||
283 | /** |
||
284 | * Connection error vs remote error flag |
||
285 | * |
||
286 | * @var bool |
||
287 | */ |
||
288 | protected $conn_error = false; |
||
289 | |||
290 | /** |
||
291 | * Requests array for multi-query |
||
292 | * |
||
293 | * @var array |
||
294 | */ |
||
295 | protected $reqs = array(); |
||
296 | |||
297 | /** |
||
298 | * Stored mbstring encoding |
||
299 | * |
||
300 | * @var string |
||
301 | */ |
||
302 | protected $mbenc = ''; |
||
303 | |||
304 | /** |
||
305 | * Whether $result['matches'] should be a hash or an array |
||
306 | * |
||
307 | * @var bool |
||
308 | */ |
||
309 | protected $array_result = false; |
||
310 | |||
311 | /** |
||
312 | * Connect timeout |
||
313 | * |
||
314 | * @var int|float |
||
315 | */ |
||
316 | protected $timeout = 0; |
||
317 | |||
318 | /** |
||
319 | * @var string |
||
320 | */ |
||
321 | protected $path = ''; |
||
322 | |||
323 | /** |
||
324 | * @var resource|bool |
||
325 | */ |
||
326 | protected $socket = false; |
||
327 | |||
328 | // known searchd commands |
||
329 | const SEARCHD_COMMAND_SEARCH = 0; |
||
330 | const SEARCHD_COMMAND_EXCERPT = 1; |
||
331 | const SEARCHD_COMMAND_UPDATE = 2; |
||
332 | const SEARCHD_COMMAND_KEYWORDS = 3; |
||
333 | const SEARCHD_COMMAND_PERSIST = 4; |
||
334 | const SEARCHD_COMMAND_STATUS = 5; |
||
335 | const SEARCHD_COMMAND_FLUSH_ATTRS = 7; |
||
336 | |||
337 | // current client-side command implementation versions |
||
338 | const VER_COMMAND_SEARCH = 0x11E; |
||
339 | const VER_COMMAND_EXCERPT = 0x104; |
||
340 | const VER_COMMAND_UPDATE = 0x103; |
||
341 | const VER_COMMAND_KEYWORDS = 0x100; |
||
342 | const VER_COMMAND_STATUS = 0x101; |
||
343 | const VER_COMMAND_QUERY = 0x100; |
||
344 | const VER_COMMAND_FLUSH_ATTRS = 0x100; |
||
345 | |||
346 | // known searchd status codes |
||
347 | const SEARCHD_OK = 0; |
||
348 | const SEARCHD_ERROR = 1; |
||
349 | const SEARCHD_RETRY = 2; |
||
350 | const SEARCHD_WARNING = 3; |
||
351 | |||
352 | // known match modes |
||
353 | const MATCH_ALL = 0; |
||
354 | const MATCH_ANY = 1; |
||
355 | const MATCH_PHRASE = 2; |
||
356 | const MATCH_BOOLEAN = 3; |
||
357 | const MATCH_EXTENDED = 4; |
||
358 | const MATCH_FULL_SCAN = 5; |
||
359 | const MATCH_EXTENDED2 = 6; // extended engine V2 (TEMPORARY, WILL BE REMOVED) |
||
360 | |||
361 | // known ranking modes (ext2 only) |
||
362 | const RANK_PROXIMITY_BM25 = 0; // default mode, phrase proximity major factor and BM25 minor one |
||
363 | const RANK_BM25 = 1; // statistical mode, BM25 ranking only (faster but worse quality) |
||
364 | const RANK_NONE = 2; // no ranking, all matches get a weight of 1 |
||
365 | const RANK_WORD_COUNT = 3; // simple word-count weighting, rank is a weighted sum of per-field keyword |
||
366 | // occurrence counts |
||
367 | const RANK_PROXIMITY = 4; |
||
368 | const RANK_MATCH_ANY = 5; |
||
369 | const RANK_FIELD_MASK = 6; |
||
370 | const RANK_SPH04 = 7; |
||
371 | const RANK_EXPR = 8; |
||
372 | const RANK_TOTAL = 9; |
||
373 | |||
374 | // known sort modes |
||
375 | const SORT_RELEVANCE = 0; |
||
376 | const SORT_ATTR_DESC = 1; |
||
377 | const SORT_ATTR_ASC = 2; |
||
378 | const SORT_TIME_SEGMENTS = 3; |
||
379 | const SORT_EXTENDED = 4; |
||
380 | const SORT_EXPR = 5; |
||
381 | |||
382 | // known filter types |
||
383 | const FILTER_VALUES = 0; |
||
384 | const FILTER_RANGE = 1; |
||
385 | const FILTER_FLOAT_RANGE = 2; |
||
386 | const FILTER_STRING = 3; |
||
387 | |||
388 | // known attribute types |
||
389 | const ATTR_INTEGER = 1; |
||
390 | const ATTR_TIMESTAMP = 2; |
||
391 | const ATTR_ORDINAL = 3; |
||
392 | const ATTR_BOOL = 4; |
||
393 | const ATTR_FLOAT = 5; |
||
394 | const ATTR_BIGINT = 6; |
||
395 | const ATTR_STRING = 7; |
||
396 | const ATTR_FACTORS = 1001; |
||
397 | const ATTR_MULTI = 0x40000001; |
||
398 | const ATTR_MULTI64 = 0x40000002; |
||
399 | |||
400 | // known grouping functions |
||
401 | const GROUP_BY_DAY = 0; |
||
402 | const GROUP_BY_WEEK = 1; |
||
403 | const GROUP_BY_MONTH = 2; |
||
404 | const GROUP_BY_YEAR = 3; |
||
405 | const GROUP_BY_ATTR = 4; |
||
406 | const GROUP_BY_ATTR_PAIR = 5; |
||
407 | |||
408 | ///////////////////////////////////////////////////////////////////////////// |
||
409 | // common stuff |
||
410 | ///////////////////////////////////////////////////////////////////////////// |
||
411 | |||
412 | 4 | public function __construct() |
|
413 | { |
||
414 | // default idf=tfidf_normalized |
||
415 | 4 | $this->query_flags = setBit(0, 6, true); |
|
416 | 4 | } |
|
417 | |||
418 | public function __destruct() |
||
419 | { |
||
420 | if ($this->socket !== false) { |
||
421 | fclose($this->socket); |
||
422 | } |
||
423 | } |
||
424 | |||
425 | /** |
||
426 | * @return string |
||
427 | */ |
||
428 | 1 | public function getLastError() |
|
429 | { |
||
430 | 1 | return $this->error; |
|
431 | } |
||
432 | |||
433 | /** |
||
434 | * @return string |
||
435 | */ |
||
436 | 1 | public function getLastWarning() |
|
437 | { |
||
438 | 1 | return $this->warning; |
|
439 | } |
||
440 | |||
441 | /** |
||
442 | * Get last error flag (to tell network connection errors from searchd errors or broken responses) |
||
443 | * |
||
444 | * @return bool |
||
445 | */ |
||
446 | 1 | public function isConnectError() |
|
447 | { |
||
448 | 1 | return $this->conn_error; |
|
449 | } |
||
450 | |||
451 | /** |
||
452 | * Set searchd host name and port |
||
453 | * |
||
454 | * @param string $host |
||
455 | * @param int $port |
||
456 | */ |
||
457 | public function setServer($host, $port = 0) |
||
458 | { |
||
459 | assert(is_string($host)); |
||
460 | if ($host[0] == '/') { |
||
461 | $this->path = 'unix://' . $host; |
||
462 | return; |
||
463 | } |
||
464 | if (substr($host, 0, 7) == 'unix://') { |
||
465 | $this->path = $host; |
||
466 | return; |
||
467 | } |
||
468 | |||
469 | $this->host = $host; |
||
470 | $port = intval($port); |
||
471 | assert(0 <= $port && $port < 65536); |
||
472 | $this->port = $port == 0 ? 9312 : $port; |
||
473 | $this->path = ''; |
||
474 | } |
||
475 | |||
476 | /** |
||
477 | * Set server connection timeout (0 to remove) |
||
478 | * |
||
479 | * @param int|float|string $timeout |
||
480 | */ |
||
481 | public function setConnectTimeout($timeout) |
||
482 | { |
||
483 | assert(is_numeric($timeout)); |
||
484 | $this->timeout = $timeout; |
||
0 ignored issues
–
show
|
|||
485 | } |
||
486 | |||
487 | /** |
||
488 | * @param resource $handle |
||
489 | * @param string $data |
||
490 | * @param int $length |
||
491 | * |
||
492 | * @return bool |
||
493 | */ |
||
494 | protected function send($handle, $data, $length) |
||
495 | { |
||
496 | if (feof($handle) || fwrite($handle, $data, $length) !== $length) { |
||
497 | $this->error = 'connection unexpectedly closed (timed out?)'; |
||
498 | $this->conn_error = true; |
||
499 | return false; |
||
500 | } |
||
501 | return true; |
||
502 | } |
||
503 | |||
504 | ///////////////////////////////////////////////////////////////////////////// |
||
505 | |||
506 | /** |
||
507 | * Enter mbstring workaround mode |
||
508 | */ |
||
509 | protected function mbPush() |
||
510 | { |
||
511 | $this->mbenc = ''; |
||
512 | if (ini_get('mbstring.func_overload') & 2) { |
||
513 | $this->mbenc = mb_internal_encoding(); |
||
514 | mb_internal_encoding('latin1'); |
||
515 | } |
||
516 | } |
||
517 | |||
518 | /** |
||
519 | * Leave mbstring workaround mode |
||
520 | */ |
||
521 | protected function mbPop() |
||
522 | { |
||
523 | if ($this->mbenc) { |
||
524 | mb_internal_encoding($this->mbenc); |
||
525 | } |
||
526 | } |
||
527 | |||
528 | /** |
||
529 | * Connect to searchd server |
||
530 | * |
||
531 | * @return bool|resource |
||
532 | */ |
||
533 | protected function connect() |
||
534 | { |
||
535 | if (is_resource($this->socket)) { |
||
536 | // we are in persistent connection mode, so we have a socket |
||
537 | // however, need to check whether it's still alive |
||
538 | if (!feof($this->socket)) { |
||
539 | return $this->socket; |
||
540 | } |
||
541 | |||
542 | // force reopen |
||
543 | $this->socket = false; |
||
544 | } |
||
545 | |||
546 | $errno = 0; |
||
547 | $errstr = ''; |
||
548 | $this->conn_error = false; |
||
549 | |||
550 | if ($this->path) { |
||
551 | $host = $this->path; |
||
552 | $port = 0; |
||
553 | } else { |
||
554 | $host = $this->host; |
||
555 | $port = $this->port; |
||
556 | } |
||
557 | |||
558 | if ($this->timeout <= 0) { |
||
559 | $fp = @fsockopen($host, $port, $errno, $errstr); |
||
560 | } else { |
||
561 | $fp = @fsockopen($host, $port, $errno, $errstr, $this->timeout); |
||
562 | } |
||
563 | |||
564 | if (!is_resource($fp)) { |
||
565 | if ($this->path) { |
||
566 | $location = $this->path; |
||
567 | } else { |
||
568 | $location = "{$this->host}:{$this->port}"; |
||
569 | } |
||
570 | |||
571 | $errstr = trim($errstr); |
||
572 | $this->error = "connection to $location failed (errno=$errno, msg=$errstr)"; |
||
573 | $this->conn_error = true; |
||
574 | return false; |
||
575 | } |
||
576 | |||
577 | // send my version |
||
578 | // this is a subtle part. we must do it before (!) reading back from searchd. |
||
579 | // because otherwise under some conditions (reported on FreeBSD for instance) |
||
580 | // TCP stack could throttle write-write-read pattern because of Nagle. |
||
581 | if (!$this->send($fp, pack('N', 1), 4)) { |
||
582 | fclose($fp); |
||
583 | $this->error = 'failed to send client protocol version'; |
||
584 | return false; |
||
585 | } |
||
586 | |||
587 | // check version |
||
588 | list(, $v) = unpack('N*', fread($fp, 4)); |
||
589 | $v = (int)$v; |
||
590 | if ($v < 1) { |
||
591 | fclose($fp); |
||
592 | $this->error = "expected searchd protocol version 1+, got version '$v'"; |
||
593 | return false; |
||
594 | } |
||
595 | |||
596 | return $fp; |
||
597 | } |
||
598 | |||
599 | /** |
||
600 | * Get and check response packet from searchd server |
||
601 | * |
||
602 | * @param resource $fp |
||
603 | * @param int $client_ver |
||
604 | * |
||
605 | * @return bool|string |
||
606 | */ |
||
607 | protected function getResponse($fp, $client_ver) |
||
608 | { |
||
609 | $ver = 0; |
||
610 | $len = 0; |
||
611 | $status = -1; |
||
612 | $response = ''; |
||
613 | |||
614 | $header = fread($fp, 8); |
||
615 | if (strlen($header) == 8) { |
||
616 | list($status, $ver, $len) = array_values(unpack('n2a/Nb', $header)); |
||
617 | $left = $len; |
||
618 | while ($left > 0 && !feof($fp)) { |
||
619 | $chunk = fread($fp, min(8192, $left)); |
||
620 | if ($chunk) { |
||
621 | $response .= $chunk; |
||
622 | $left -= strlen($chunk); |
||
623 | } |
||
624 | } |
||
625 | } |
||
626 | |||
627 | if ($this->socket === false) { |
||
628 | fclose($fp); |
||
629 | } |
||
630 | |||
631 | // check response |
||
632 | $read = strlen($response); |
||
633 | if (!$response || $read != $len) { |
||
634 | if ($len) { |
||
635 | $this->error = "failed to read searchd response (status=$status, ver=$ver, len=$len, read=$read)"; |
||
636 | } else { |
||
637 | $this->error = 'received zero-sized searchd response'; |
||
638 | } |
||
639 | return false; |
||
640 | } |
||
641 | |||
642 | switch ($status) { |
||
643 | case self::SEARCHD_WARNING: |
||
644 | list(, $wlen) = unpack('N*', substr($response, 0, 4)); |
||
645 | $this->warning = substr($response, 4, $wlen); |
||
646 | return substr($response, 4 + $wlen); |
||
647 | case self::SEARCHD_ERROR: |
||
648 | $this->error = 'searchd error: ' . substr($response, 4); |
||
649 | return false; |
||
650 | case self::SEARCHD_RETRY: |
||
651 | $this->error = 'temporary searchd error: ' . substr($response, 4); |
||
652 | return false; |
||
653 | case self::SEARCHD_OK: |
||
654 | if ($ver < $client_ver) { // check version |
||
655 | $this->warning = sprintf( |
||
656 | 'searchd command v.%d.%d older than client\'s v.%d.%d, some options might not work', |
||
657 | $ver >> 8, |
||
658 | $ver & 0xff, |
||
659 | $client_ver >> 8, |
||
660 | $client_ver & 0xff |
||
661 | ); |
||
662 | } |
||
663 | |||
664 | return $response; |
||
665 | default: |
||
666 | $this->error = "unknown status code '$status'"; |
||
667 | return false; |
||
668 | } |
||
669 | } |
||
670 | |||
671 | ///////////////////////////////////////////////////////////////////////////// |
||
672 | // searching |
||
673 | ///////////////////////////////////////////////////////////////////////////// |
||
674 | |||
675 | /** |
||
676 | * Set offset and count into result set, and optionally set max-matches and cutoff limits |
||
677 | * |
||
678 | * @param int $offset |
||
679 | * @param int $limit |
||
680 | * @param int $max |
||
681 | * @param int $cutoff |
||
682 | */ |
||
683 | public function setLimits($offset, $limit, $max = 0, $cutoff = 0) |
||
684 | { |
||
685 | assert(is_int($offset)); |
||
686 | assert(is_int($limit)); |
||
687 | assert($offset >= 0); |
||
688 | assert($limit > 0); |
||
689 | assert($max >= 0); |
||
690 | $this->offset = $offset; |
||
691 | $this->limit = $limit; |
||
692 | if ($max > 0) { |
||
693 | $this->max_matches = $max; |
||
694 | } |
||
695 | if ($cutoff > 0) { |
||
696 | $this->cutoff = $cutoff; |
||
697 | } |
||
698 | } |
||
699 | |||
700 | /** |
||
701 | * Set maximum query time, in milliseconds, per-index, 0 means 'do not limit' |
||
702 | * |
||
703 | * @param int $max |
||
704 | */ |
||
705 | public function setMaxQueryTime($max) |
||
706 | { |
||
707 | assert(is_int($max)); |
||
708 | assert($max >= 0); |
||
709 | $this->max_query_time = $max; |
||
710 | } |
||
711 | |||
712 | /** |
||
713 | * Set matching mode |
||
714 | * |
||
715 | * @param int $mode |
||
716 | */ |
||
717 | public function setMatchMode($mode) |
||
718 | { |
||
719 | trigger_error( |
||
720 | 'DEPRECATED: Do not call this method or, even better, use SphinxQL instead of an API', |
||
721 | E_USER_DEPRECATED |
||
722 | ); |
||
723 | assert(in_array($mode, array( |
||
724 | self::MATCH_ALL, |
||
725 | self::MATCH_ANY, |
||
726 | self::MATCH_PHRASE, |
||
727 | self::MATCH_BOOLEAN, |
||
728 | self::MATCH_EXTENDED, |
||
729 | self::MATCH_FULL_SCAN, |
||
730 | self::MATCH_EXTENDED2 |
||
731 | ))); |
||
732 | $this->mode = $mode; |
||
733 | } |
||
734 | |||
735 | /** |
||
736 | * Set ranking mode |
||
737 | * |
||
738 | * @param int $ranker |
||
739 | * @param string $rank_expr |
||
740 | */ |
||
741 | public function setRankingMode($ranker, $rank_expr='') |
||
742 | { |
||
743 | assert($ranker === 0 || $ranker >= 1 && $ranker < self::RANK_TOTAL); |
||
744 | assert(is_string($rank_expr)); |
||
745 | $this->ranker = $ranker; |
||
746 | $this->rank_expr = $rank_expr; |
||
747 | } |
||
748 | |||
749 | /** |
||
750 | * Set matches sorting mode |
||
751 | * |
||
752 | * @param int $mode |
||
753 | * @param string $sort_by |
||
754 | */ |
||
755 | public function setSortMode($mode, $sort_by = '') |
||
756 | { |
||
757 | assert(in_array($mode, array( |
||
758 | self::SORT_RELEVANCE, |
||
759 | self::SORT_ATTR_DESC, |
||
760 | self::SORT_ATTR_ASC, |
||
761 | self::SORT_TIME_SEGMENTS, |
||
762 | self::SORT_EXTENDED, |
||
763 | self::SORT_EXPR |
||
764 | ))); |
||
765 | assert(is_string($sort_by)); |
||
766 | assert($mode == self::SORT_RELEVANCE || strlen($sort_by) > 0); |
||
767 | |||
768 | $this->sort = $mode; |
||
769 | $this->sort_by = $sort_by; |
||
770 | } |
||
771 | |||
772 | /** |
||
773 | * Bind per-field weights by order |
||
774 | * |
||
775 | * @deprecated use setFieldWeights() instead |
||
776 | */ |
||
777 | 1 | public function setWeights() |
|
778 | { |
||
779 | 1 | throw new \RuntimeException('This method is now deprecated; please use setFieldWeights instead'); |
|
780 | } |
||
781 | |||
782 | /** |
||
783 | * Bind per-field weights by name |
||
784 | * |
||
785 | * @param array $weights |
||
786 | */ |
||
787 | public function setFieldWeights(array $weights) |
||
788 | { |
||
789 | foreach ($weights as $name => $weight) { |
||
790 | assert(is_string($name)); |
||
791 | assert(is_int($weight)); |
||
792 | } |
||
793 | $this->field_weights = $weights; |
||
794 | } |
||
795 | |||
796 | /** |
||
797 | * Bind per-index weights by name |
||
798 | * |
||
799 | * @param array $weights |
||
800 | */ |
||
801 | public function setIndexWeights(array $weights) |
||
802 | { |
||
803 | foreach ($weights as $index => $weight) { |
||
804 | assert(is_string($index)); |
||
805 | assert(is_int($weight)); |
||
806 | } |
||
807 | $this->index_weights = $weights; |
||
808 | } |
||
809 | |||
810 | /** |
||
811 | * Set IDs range to match. Only match records if document ID is beetwen $min and $max (inclusive) |
||
812 | * |
||
813 | * @param int $min |
||
814 | * @param int $max |
||
815 | */ |
||
816 | public function setIDRange($min, $max) |
||
817 | { |
||
818 | assert(is_numeric($min)); |
||
819 | assert(is_numeric($max)); |
||
820 | assert($min <= $max); |
||
821 | |||
822 | $this->min_id = $min; |
||
0 ignored issues
–
show
It seems like
$min can also be of type double or string . However, the property $min_id is declared as type integer . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||
823 | $this->max_id = $max; |
||
0 ignored issues
–
show
It seems like
$max can also be of type double or string . However, the property $max_id is declared as type integer . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||
824 | } |
||
825 | |||
826 | /** |
||
827 | * Set values set filter. Only match records where $attribute value is in given set |
||
828 | * |
||
829 | * @param string $attribute |
||
830 | * @param array $values |
||
831 | * @param bool $exclude |
||
832 | */ |
||
833 | public function setFilter($attribute, array $values, $exclude = false) |
||
834 | { |
||
835 | assert(is_string($attribute)); |
||
836 | assert(count($values)); |
||
837 | |||
838 | foreach ($values as $value) { |
||
839 | assert(is_numeric($value)); |
||
840 | } |
||
841 | |||
842 | $this->filters[] = array( |
||
843 | 'type' => self::FILTER_VALUES, |
||
844 | 'attr' => $attribute, |
||
845 | 'exclude' => $exclude, |
||
846 | 'values' => $values |
||
847 | ); |
||
848 | } |
||
849 | |||
850 | /** |
||
851 | * Set string filter |
||
852 | * Only match records where $attribute value is equal |
||
853 | * |
||
854 | * @param string $attribute |
||
855 | * @param string $value |
||
856 | * @param bool $exclude |
||
857 | */ |
||
858 | public function setFilterString($attribute, $value, $exclude = false) |
||
859 | { |
||
860 | assert(is_string($attribute)); |
||
861 | assert(is_string($value)); |
||
862 | $this->filters[] = array( |
||
863 | 'type' => self::FILTER_STRING, |
||
864 | 'attr' => $attribute, |
||
865 | 'exclude' => $exclude, |
||
866 | 'value' => $value |
||
867 | ); |
||
868 | } |
||
869 | |||
870 | /** |
||
871 | * Set range filter |
||
872 | * Only match records if $attribute value is beetwen $min and $max (inclusive) |
||
873 | * |
||
874 | * @param string $attribute |
||
875 | * @param int $min |
||
876 | * @param int $max |
||
877 | * @param bool $exclude |
||
878 | */ |
||
879 | public function setFilterRange($attribute, $min, $max, $exclude = false) |
||
880 | { |
||
881 | assert(is_string($attribute)); |
||
882 | assert(is_numeric($min)); |
||
883 | assert(is_numeric($max)); |
||
884 | assert($min <= $max); |
||
885 | |||
886 | $this->filters[] = array( |
||
887 | 'type' => self::FILTER_RANGE, |
||
888 | 'attr' => $attribute, |
||
889 | 'exclude' => $exclude, |
||
890 | 'min' => $min, |
||
891 | 'max' => $max |
||
892 | ); |
||
893 | } |
||
894 | |||
895 | /** |
||
896 | * Set float range filter |
||
897 | * Only match records if $attribute value is beetwen $min and $max (inclusive) |
||
898 | * |
||
899 | * @param string $attribute |
||
900 | * @param float $min |
||
901 | * @param float $max |
||
902 | * @param bool $exclude |
||
903 | */ |
||
904 | public function setFilterFloatRange($attribute, $min, $max, $exclude = false) |
||
905 | { |
||
906 | assert(is_string($attribute)); |
||
907 | assert(is_float($min)); |
||
908 | assert(is_float($max)); |
||
909 | assert($min <= $max); |
||
910 | |||
911 | $this->filters[] = array( |
||
912 | 'type' => self::FILTER_FLOAT_RANGE, |
||
913 | 'attr' => $attribute, |
||
914 | 'exclude' => $exclude, |
||
915 | 'min' => $min, |
||
916 | 'max' => $max |
||
917 | ); |
||
918 | } |
||
919 | |||
920 | /** |
||
921 | * Setup anchor point for geosphere distance calculations |
||
922 | * Required to use @geodist in filters and sorting |
||
923 | * Latitude and longitude must be in radians |
||
924 | * |
||
925 | * @param string $attr_lat |
||
926 | * @param string $attr_long |
||
927 | * @param float $lat |
||
928 | * @param float $long |
||
929 | */ |
||
930 | public function setGeoAnchor($attr_lat, $attr_long, $lat, $long) |
||
931 | { |
||
932 | assert(is_string($attr_lat)); |
||
933 | assert(is_string($attr_long)); |
||
934 | assert(is_float($lat)); |
||
935 | assert(is_float($long)); |
||
936 | |||
937 | $this->anchor = array( |
||
938 | 'attrlat' => $attr_lat, |
||
939 | 'attrlong' => $attr_long, |
||
940 | 'lat' => $lat, |
||
941 | 'long' => $long |
||
942 | ); |
||
943 | } |
||
944 | |||
945 | /** |
||
946 | * Set grouping attribute and function |
||
947 | * |
||
948 | * @param string $attribute |
||
949 | * @param int $func |
||
950 | * @param string $group_sort |
||
951 | */ |
||
952 | public function setGroupBy($attribute, $func, $group_sort = '@group desc') |
||
953 | { |
||
954 | assert(is_string($attribute)); |
||
955 | assert(is_string($group_sort)); |
||
956 | assert(in_array($func, array( |
||
957 | self::GROUP_BY_DAY, |
||
958 | self::GROUP_BY_WEEK, |
||
959 | self::GROUP_BY_MONTH, |
||
960 | self::GROUP_BY_YEAR, |
||
961 | self::GROUP_BY_ATTR, |
||
962 | self::GROUP_BY_ATTR_PAIR |
||
963 | ))); |
||
964 | |||
965 | $this->group_by = $attribute; |
||
966 | $this->group_func = $func; |
||
967 | $this->group_sort = $group_sort; |
||
968 | } |
||
969 | |||
970 | /** |
||
971 | * Set count-distinct attribute for group-by queries |
||
972 | * |
||
973 | * @param string $attribute |
||
974 | */ |
||
975 | public function setGroupDistinct($attribute) |
||
976 | { |
||
977 | assert(is_string($attribute)); |
||
978 | $this->group_distinct = $attribute; |
||
979 | } |
||
980 | |||
981 | /** |
||
982 | * Set distributed retries count and delay |
||
983 | * |
||
984 | * @param int $count |
||
985 | * @param int $delay |
||
986 | */ |
||
987 | public function setRetries($count, $delay = 0) |
||
988 | { |
||
989 | assert(is_int($count) && $count >= 0); |
||
990 | assert(is_int($delay) && $delay >= 0); |
||
991 | $this->retry_count = $count; |
||
992 | $this->retry_delay = $delay; |
||
993 | } |
||
994 | |||
995 | /** |
||
996 | * Set result set format (hash or array; hash by default) |
||
997 | * PHP specific; needed for group-by-MVA result sets that may contain duplicate IDs |
||
998 | * |
||
999 | * @param bool $array_result |
||
1000 | */ |
||
1001 | public function setArrayResult($array_result) |
||
1002 | { |
||
1003 | assert(is_bool($array_result)); |
||
1004 | $this->array_result = $array_result; |
||
1005 | } |
||
1006 | |||
1007 | /** |
||
1008 | * Set attribute values override |
||
1009 | * There can be only one override per attribute |
||
1010 | * $values must be a hash that maps document IDs to attribute values |
||
1011 | * |
||
1012 | * @deprecated Do not call this method. Use SphinxQL REMAP() function instead. |
||
1013 | * |
||
1014 | * @param string $attr_name |
||
1015 | * @param string $attr_type |
||
1016 | * @param array $values |
||
1017 | */ |
||
1018 | public function setOverride($attr_name, $attr_type, array $values) |
||
1019 | { |
||
1020 | trigger_error( |
||
1021 | 'DEPRECATED: Do not call this method. Use SphinxQL REMAP() function instead.', |
||
1022 | E_USER_DEPRECATED |
||
1023 | ); |
||
1024 | assert(is_string($attr_name)); |
||
1025 | assert(in_array($attr_type, array( |
||
1026 | self::ATTR_INTEGER, |
||
1027 | self::ATTR_TIMESTAMP, |
||
1028 | self::ATTR_BOOL, |
||
1029 | self::ATTR_FLOAT, |
||
1030 | self::ATTR_BIGINT |
||
1031 | ))); |
||
1032 | |||
1033 | $this->overrides[$attr_name] = array( |
||
1034 | 'attr' => $attr_name, |
||
1035 | 'type' => $attr_type, |
||
1036 | 'values' => $values |
||
1037 | ); |
||
1038 | } |
||
1039 | |||
1040 | /** |
||
1041 | * Set select-list (attributes or expressions), SQL-like syntax |
||
1042 | * |
||
1043 | * @param string $select |
||
1044 | */ |
||
1045 | public function setSelect($select) |
||
1046 | { |
||
1047 | assert(is_string($select)); |
||
1048 | $this->select = $select; |
||
1049 | } |
||
1050 | |||
1051 | /** |
||
1052 | * @param string $flag_name |
||
1053 | * @param string|int $flag_value |
||
1054 | */ |
||
1055 | public function setQueryFlag($flag_name, $flag_value) |
||
1056 | { |
||
1057 | $known_names = array( |
||
1058 | 'reverse_scan', |
||
1059 | 'sort_method', |
||
1060 | 'max_predicted_time', |
||
1061 | 'boolean_simplify', |
||
1062 | 'idf', |
||
1063 | 'global_idf', |
||
1064 | 'low_priority' |
||
1065 | ); |
||
1066 | $flags = array ( |
||
1067 | 'reverse_scan' => array(0, 1), |
||
1068 | 'sort_method' => array('pq', 'kbuffer'), |
||
1069 | 'max_predicted_time' => array(0), |
||
1070 | 'boolean_simplify' => array(true, false), |
||
1071 | 'idf' => array ('normalized', 'plain', 'tfidf_normalized', 'tfidf_unnormalized'), |
||
1072 | 'global_idf' => array(true, false), |
||
1073 | 'low_priority' => array(true, false) |
||
1074 | ); |
||
1075 | |||
1076 | assert(isset($flag_name, $known_names)); |
||
1077 | assert( |
||
1078 | in_array($flag_value, $flags[$flag_name], true) || |
||
1079 | ($flag_name == 'max_predicted_time' && is_int($flag_value) && $flag_value >= 0) |
||
1080 | ); |
||
1081 | |||
1082 | switch ($flag_name) { |
||
1083 | case 'reverse_scan': |
||
1084 | $this->query_flags = setBit($this->query_flags, 0, $flag_value == 1); |
||
1085 | break; |
||
1086 | case 'sort_method': |
||
1087 | $this->query_flags = setBit($this->query_flags, 1, $flag_value == 'kbuffer'); |
||
1088 | break; |
||
1089 | case 'max_predicted_time': |
||
1090 | $this->query_flags = setBit($this->query_flags, 2, $flag_value > 0); |
||
1091 | $this->predicted_time = (int)$flag_value; |
||
1092 | break; |
||
1093 | case 'boolean_simplify': |
||
1094 | $this->query_flags = setBit($this->query_flags, 3, $flag_value); |
||
1095 | break; |
||
1096 | case 'idf': |
||
1097 | if ($flag_value == 'normalized' || $flag_value == 'plain') { |
||
1098 | $this->query_flags = setBit($this->query_flags, 4, $flag_value == 'plain'); |
||
1099 | } |
||
1100 | if ($flag_value == 'tfidf_normalized' || $flag_value == 'tfidf_unnormalized') { |
||
1101 | $this->query_flags = setBit($this->query_flags, 6, $flag_value == 'tfidf_normalized'); |
||
1102 | } |
||
1103 | break; |
||
1104 | case 'global_idf': |
||
1105 | $this->query_flags = setBit($this->query_flags, 5, $flag_value); |
||
1106 | break; |
||
1107 | case 'low_priority': |
||
1108 | $this->query_flags = setBit($this->query_flags, 8, $flag_value); |
||
1109 | break; |
||
1110 | } |
||
1111 | } |
||
1112 | |||
1113 | /** |
||
1114 | * Set outer order by parameters |
||
1115 | * |
||
1116 | * @param string $order_by |
||
1117 | * @param int $offset |
||
1118 | * @param int $limit |
||
1119 | */ |
||
1120 | public function setOuterSelect($order_by, $offset, $limit) |
||
1121 | { |
||
1122 | assert(is_string($order_by)); |
||
1123 | assert(is_int($offset)); |
||
1124 | assert(is_int($limit)); |
||
1125 | assert($offset >= 0); |
||
1126 | assert($limit > 0); |
||
1127 | |||
1128 | $this->outer_order_by = $order_by; |
||
1129 | $this->outer_offset = $offset; |
||
1130 | $this->outer_limit = $limit; |
||
1131 | $this->has_outer = true; |
||
1132 | } |
||
1133 | |||
1134 | |||
1135 | ////////////////////////////////////////////////////////////////////////////// |
||
1136 | |||
1137 | /** |
||
1138 | * Clear all filters (for multi-queries) |
||
1139 | */ |
||
1140 | public function resetFilters() |
||
1141 | { |
||
1142 | $this->filters = array(); |
||
1143 | $this->anchor = array(); |
||
1144 | } |
||
1145 | |||
1146 | /** |
||
1147 | * Clear groupby settings (for multi-queries) |
||
1148 | */ |
||
1149 | public function resetGroupBy() |
||
1150 | { |
||
1151 | $this->group_by = ''; |
||
1152 | $this->group_func = self::GROUP_BY_DAY; |
||
1153 | $this->group_sort = '@group desc'; |
||
1154 | $this->group_distinct = ''; |
||
1155 | } |
||
1156 | |||
1157 | /** |
||
1158 | * Clear all attribute value overrides (for multi-queries) |
||
1159 | */ |
||
1160 | public function resetOverrides() |
||
1161 | { |
||
1162 | $this->overrides = array(); |
||
1163 | } |
||
1164 | |||
1165 | public function resetQueryFlag() |
||
1166 | { |
||
1167 | $this->query_flags = setBit(0, 6, true); // default idf=tfidf_normalized |
||
1168 | $this->predicted_time = 0; |
||
1169 | } |
||
1170 | |||
1171 | public function resetOuterSelect() |
||
1172 | { |
||
1173 | $this->outer_order_by = ''; |
||
1174 | $this->outer_offset = 0; |
||
1175 | $this->outer_limit = 0; |
||
1176 | $this->has_outer = false; |
||
1177 | } |
||
1178 | |||
1179 | ////////////////////////////////////////////////////////////////////////////// |
||
1180 | |||
1181 | /** |
||
1182 | * Connect to searchd server, run given search query through given indexes, and return the search results |
||
1183 | * |
||
1184 | * @param string $query |
||
1185 | * @param string $index |
||
1186 | * @param string $comment |
||
1187 | * |
||
1188 | * @return bool |
||
1189 | */ |
||
1190 | public function query($query, $index = '*', $comment = '') |
||
1191 | { |
||
1192 | assert(empty($this->reqs)); |
||
1193 | |||
1194 | $this->addQuery($query, $index, $comment); |
||
1195 | $results = $this->runQueries(); |
||
1196 | $this->reqs = array(); // just in case it failed too early |
||
1197 | |||
1198 | if (!is_array($results)) { |
||
1199 | return false; // probably network error; error message should be already filled |
||
1200 | } |
||
1201 | |||
1202 | $this->error = $results[0]['error']; |
||
1203 | $this->warning = $results[0]['warning']; |
||
1204 | |||
1205 | if ($results[0]['status'] == self::SEARCHD_ERROR) { |
||
1206 | return false; |
||
1207 | } else { |
||
1208 | return $results[0]; |
||
1209 | } |
||
1210 | } |
||
1211 | |||
1212 | /** |
||
1213 | * Helper to pack floats in network byte order |
||
1214 | * |
||
1215 | * @param float $float |
||
1216 | * |
||
1217 | * @return string |
||
1218 | */ |
||
1219 | protected function packFloat($float) |
||
1220 | { |
||
1221 | $t1 = pack('f', $float); // machine order |
||
1222 | list(, $t2) = unpack('L*', $t1); // int in machine order |
||
1223 | return pack('N', $t2); |
||
1224 | } |
||
1225 | |||
1226 | /** |
||
1227 | * Add query to multi-query batch |
||
1228 | * Returns index into results array from RunQueries() call |
||
1229 | * |
||
1230 | * @param string $query |
||
1231 | * @param string $index |
||
1232 | * @param string $comment |
||
1233 | * |
||
1234 | * @return int |
||
1235 | */ |
||
1236 | public function addQuery($query, $index = '*', $comment = '') |
||
1237 | { |
||
1238 | // mbstring workaround |
||
1239 | $this->mbPush(); |
||
1240 | |||
1241 | // build request |
||
1242 | $req = pack('NNNNN', $this->query_flags, $this->offset, $this->limit, $this->mode, $this->ranker); |
||
1243 | if ($this->ranker == self::RANK_EXPR) { |
||
1244 | $req .= pack('N', strlen($this->rank_expr)) . $this->rank_expr; |
||
1245 | } |
||
1246 | $req .= pack('N', $this->sort); // (deprecated) sort mode |
||
1247 | $req .= pack('N', strlen($this->sort_by)) . $this->sort_by; |
||
1248 | $req .= pack('N', strlen($query)) . $query; // query itself |
||
1249 | $req .= pack('N', count($this->weights)); // weights |
||
1250 | foreach ($this->weights as $weight) { |
||
1251 | $req .= pack('N', (int)$weight); |
||
1252 | } |
||
1253 | $req .= pack('N', strlen($index)) . $index; // indexes |
||
1254 | $req .= pack('N', 1); // id64 range marker |
||
1255 | $req .= pack64IntUnsigned($this->min_id) . pack64IntUnsigned($this->max_id); // id64 range |
||
1256 | |||
1257 | // filters |
||
1258 | $req .= pack('N', count($this->filters)); |
||
1259 | foreach ($this->filters as $filter) { |
||
1260 | $req .= pack('N', strlen($filter['attr'])) . $filter['attr']; |
||
1261 | $req .= pack('N', $filter['type']); |
||
1262 | switch ($filter['type']) { |
||
1263 | case self::FILTER_VALUES: |
||
1264 | $req .= pack('N', count($filter['values'])); |
||
1265 | foreach ($filter['values'] as $value) { |
||
1266 | $req .= pack64IntSigned($value); |
||
1267 | } |
||
1268 | break; |
||
1269 | case self::FILTER_RANGE: |
||
1270 | $req .= pack64IntSigned($filter['min']) . pack64IntSigned($filter['max']); |
||
1271 | break; |
||
1272 | case self::FILTER_FLOAT_RANGE: |
||
1273 | $req .= $this->packFloat($filter['min']) . $this->packFloat($filter['max']); |
||
1274 | break; |
||
1275 | case self::FILTER_STRING: |
||
1276 | $req .= pack('N', strlen($filter['value'])) . $filter['value']; |
||
1277 | break; |
||
1278 | default: |
||
1279 | assert(0 && 'internal error: unhandled filter type'); |
||
1280 | } |
||
1281 | $req .= pack('N', $filter['exclude']); |
||
1282 | } |
||
1283 | |||
1284 | // group-by clause, max-matches count, group-sort clause, cutoff count |
||
1285 | $req .= pack('NN', $this->group_func, strlen($this->group_by)) . $this->group_by; |
||
1286 | $req .= pack('N', $this->max_matches); |
||
1287 | $req .= pack('N', strlen($this->group_sort)) . $this->group_sort; |
||
1288 | $req .= pack('NNN', $this->cutoff, $this->retry_count, $this->retry_delay); |
||
1289 | $req .= pack('N', strlen($this->group_distinct)) . $this->group_distinct; |
||
1290 | |||
1291 | // anchor point |
||
1292 | if (empty($this->anchor)) { |
||
1293 | $req .= pack('N', 0); |
||
1294 | } else { |
||
1295 | $a =& $this->anchor; |
||
1296 | $req .= pack('N', 1); |
||
1297 | $req .= pack('N', strlen($a['attrlat'])) . $a['attrlat']; |
||
1298 | $req .= pack('N', strlen($a['attrlong'])) . $a['attrlong']; |
||
1299 | $req .= $this->packFloat($a['lat']) . $this->packFloat($a['long']); |
||
1300 | } |
||
1301 | |||
1302 | // per-index weights |
||
1303 | $req .= pack('N', count($this->index_weights)); |
||
1304 | foreach ($this->index_weights as $idx => $weight) { |
||
1305 | $req .= pack('N', strlen($idx)) . $idx . pack('N', $weight); |
||
1306 | } |
||
1307 | |||
1308 | // max query time |
||
1309 | $req .= pack('N', $this->max_query_time); |
||
1310 | |||
1311 | // per-field weights |
||
1312 | $req .= pack('N', count($this->field_weights)); |
||
1313 | foreach ($this->field_weights as $field => $weight) { |
||
1314 | $req .= pack('N', strlen($field)) . $field . pack('N', $weight); |
||
1315 | } |
||
1316 | |||
1317 | // comment |
||
1318 | $req .= pack('N', strlen($comment)) . $comment; |
||
1319 | |||
1320 | // attribute overrides |
||
1321 | $req .= pack('N', count($this->overrides)); |
||
1322 | foreach ($this->overrides as $key => $entry) { |
||
1323 | $req .= pack('N', strlen($entry['attr'])) . $entry['attr']; |
||
1324 | $req .= pack('NN', $entry['type'], count($entry['values'])); |
||
1325 | foreach ($entry['values'] as $id => $val) { |
||
1326 | assert(is_numeric($id)); |
||
1327 | assert(is_numeric($val)); |
||
1328 | |||
1329 | $req .= pack64IntUnsigned($id); |
||
1330 | switch ($entry['type']) { |
||
1331 | case self::ATTR_FLOAT: |
||
1332 | $req .= $this->packFloat($val); |
||
1333 | break; |
||
1334 | case self::ATTR_BIGINT: |
||
1335 | $req .= pack64IntSigned($val); |
||
1336 | break; |
||
1337 | default: |
||
1338 | $req .= pack('N', $val); |
||
1339 | break; |
||
1340 | } |
||
1341 | } |
||
1342 | } |
||
1343 | |||
1344 | // select-list |
||
1345 | $req .= pack('N', strlen($this->select)) . $this->select; |
||
1346 | |||
1347 | // max_predicted_time |
||
1348 | if ($this->predicted_time > 0) { |
||
1349 | $req .= pack('N', (int)$this->predicted_time); |
||
1350 | } |
||
1351 | |||
1352 | $req .= pack('N', strlen($this->outer_order_by)) . $this->outer_order_by; |
||
1353 | $req .= pack('NN', $this->outer_offset, $this->outer_limit); |
||
1354 | if ($this->has_outer) { |
||
1355 | $req .= pack('N', 1); |
||
1356 | } else { |
||
1357 | $req .= pack('N', 0); |
||
1358 | } |
||
1359 | |||
1360 | // mbstring workaround |
||
1361 | $this->mbPop(); |
||
1362 | |||
1363 | // store request to requests array |
||
1364 | $this->reqs[] = $req; |
||
1365 | return count($this->reqs) - 1; |
||
1366 | } |
||
1367 | |||
1368 | /** |
||
1369 | * Connect to searchd, run queries batch, and return an array of result sets |
||
1370 | * |
||
1371 | * @return array|bool |
||
1372 | */ |
||
1373 | public function runQueries() |
||
1374 | { |
||
1375 | if (empty($this->reqs)) { |
||
1376 | $this->error = 'no queries defined, issue AddQuery() first'; |
||
1377 | return false; |
||
1378 | } |
||
1379 | |||
1380 | // mbstring workaround |
||
1381 | $this->mbPush(); |
||
1382 | |||
1383 | if (($fp = $this->connect()) === false) { |
||
1384 | $this->mbPop(); |
||
1385 | return false; |
||
1386 | } |
||
1387 | |||
1388 | // send query, get response |
||
1389 | $nreqs = count($this->reqs); |
||
1390 | $req = join('', $this->reqs); |
||
1391 | $len = 8 + strlen($req); |
||
1392 | // add header |
||
1393 | $req = pack('nnNNN', self::SEARCHD_COMMAND_SEARCH, self::VER_COMMAND_SEARCH, $len, 0, $nreqs) . $req; |
||
1394 | |||
1395 | if (!$this->send($fp, $req, $len + 8) || !($response = $this->getResponse($fp, self::VER_COMMAND_SEARCH))) { |
||
1396 | $this->mbPop(); |
||
1397 | return false; |
||
1398 | } |
||
1399 | |||
1400 | // query sent ok; we can reset reqs now |
||
1401 | $this->reqs = array(); |
||
1402 | |||
1403 | // parse and return response |
||
1404 | return $this->parseSearchResponse($response, $nreqs); |
||
1405 | } |
||
1406 | |||
1407 | /** |
||
1408 | * Parse and return search query (or queries) response |
||
1409 | * |
||
1410 | * @param string $response |
||
1411 | * @param int $nreqs |
||
1412 | * |
||
1413 | * @return array |
||
1414 | */ |
||
1415 | protected function parseSearchResponse($response, $nreqs) |
||
1416 | { |
||
1417 | $p = 0; // current position |
||
1418 | $max = strlen($response); // max position for checks, to protect against broken responses |
||
1419 | |||
1420 | $results = array(); |
||
1421 | for ($ires = 0; $ires < $nreqs && $p < $max; $ires++) { |
||
1422 | $results[] = array(); |
||
1423 | $result =& $results[$ires]; |
||
1424 | |||
1425 | $result['error'] = ''; |
||
1426 | $result['warning'] = ''; |
||
1427 | |||
1428 | // extract status |
||
1429 | list(, $status) = unpack('N*', substr($response, $p, 4)); |
||
1430 | $p += 4; |
||
1431 | $result['status'] = $status; |
||
1432 | if ($status != self::SEARCHD_OK) { |
||
1433 | list(, $len) = unpack('N*', substr($response, $p, 4)); |
||
1434 | $p += 4; |
||
1435 | $message = substr($response, $p, $len); |
||
1436 | $p += $len; |
||
1437 | |||
1438 | if ($status == self::SEARCHD_WARNING) { |
||
1439 | $result['warning'] = $message; |
||
1440 | } else { |
||
1441 | $result['error'] = $message; |
||
1442 | continue; |
||
1443 | } |
||
1444 | } |
||
1445 | |||
1446 | // read schema |
||
1447 | $fields = array(); |
||
1448 | $attrs = array(); |
||
1449 | |||
1450 | list(, $nfields) = unpack('N*', substr($response, $p, 4)); |
||
1451 | $p += 4; |
||
1452 | while ($nfields --> 0 && $p < $max) { |
||
1453 | list(, $len) = unpack('N*', substr($response, $p, 4)); |
||
1454 | $p += 4; |
||
1455 | $fields[] = substr($response, $p, $len); |
||
1456 | $p += $len; |
||
1457 | } |
||
1458 | $result['fields'] = $fields; |
||
1459 | |||
1460 | list(, $n_attrs) = unpack('N*', substr($response, $p, 4)); |
||
1461 | $p += 4; |
||
1462 | while ($n_attrs --> 0 && $p < $max) { |
||
1463 | list(, $len) = unpack('N*', substr($response, $p, 4)); |
||
1464 | $p += 4; |
||
1465 | $attr = substr($response, $p, $len); |
||
1466 | $p += $len; |
||
1467 | list(, $type) = unpack('N*', substr($response, $p, 4)); |
||
1468 | $p += 4; |
||
1469 | $attrs[$attr] = $type; |
||
1470 | } |
||
1471 | $result['attrs'] = $attrs; |
||
1472 | |||
1473 | // read match count |
||
1474 | list(, $count) = unpack('N*', substr($response, $p, 4)); |
||
1475 | $p += 4; |
||
1476 | list(, $id64) = unpack('N*', substr($response, $p, 4)); |
||
1477 | $p += 4; |
||
1478 | |||
1479 | // read matches |
||
1480 | $idx = -1; |
||
1481 | while ($count --> 0 && $p < $max) { |
||
1482 | // index into result array |
||
1483 | $idx++; |
||
1484 | |||
1485 | // parse document id and weight |
||
1486 | if ($id64) { |
||
1487 | $doc = unpack64IntUnsigned(substr($response, $p, 8)); |
||
1488 | $p += 8; |
||
1489 | list(,$weight) = unpack('N*', substr($response, $p, 4)); |
||
1490 | $p += 4; |
||
1491 | } else { |
||
1492 | list($doc, $weight) = array_values(unpack('N*N*', substr($response, $p, 8))); |
||
1493 | $p += 8; |
||
1494 | $doc = fixUInt($doc); |
||
1495 | } |
||
1496 | $weight = sprintf('%u', $weight); |
||
1497 | |||
1498 | // create match entry |
||
1499 | if ($this->array_result) { |
||
1500 | $result['matches'][$idx] = array('id' => $doc, 'weight' => $weight); |
||
1501 | } else { |
||
1502 | $result['matches'][$doc]['weight'] = $weight; |
||
1503 | } |
||
1504 | |||
1505 | // parse and create attributes |
||
1506 | $attr_values = array(); |
||
1507 | foreach ($attrs as $attr => $type) { |
||
1508 | // handle 64bit int |
||
1509 | if ($type == self::ATTR_BIGINT) { |
||
1510 | $attr_values[$attr] = unpack64IntSigned(substr($response, $p, 8)); |
||
1511 | $p += 8; |
||
1512 | continue; |
||
1513 | } |
||
1514 | |||
1515 | // handle floats |
||
1516 | if ($type == self::ATTR_FLOAT) { |
||
1517 | list(, $u_value) = unpack('N*', substr($response, $p, 4)); |
||
1518 | $p += 4; |
||
1519 | list(, $f_value) = unpack('f*', pack('L', $u_value)); |
||
1520 | $attr_values[$attr] = $f_value; |
||
1521 | continue; |
||
1522 | } |
||
1523 | |||
1524 | // handle everything else as unsigned int |
||
1525 | list(, $val) = unpack('N*', substr($response, $p, 4)); |
||
1526 | $p += 4; |
||
1527 | if ($type == self::ATTR_MULTI) { |
||
1528 | $attr_values[$attr] = array(); |
||
1529 | $n_values = $val; |
||
1530 | View Code Duplication | while ($n_values --> 0 && $p < $max) { |
|
1531 | list(, $val) = unpack('N*', substr($response, $p, 4)); |
||
1532 | $p += 4; |
||
1533 | $attr_values[$attr][] = fixUInt($val); |
||
1534 | } |
||
1535 | } elseif ($type == self::ATTR_MULTI64) { |
||
1536 | $attr_values[$attr] = array(); |
||
1537 | $n_values = $val; |
||
1538 | View Code Duplication | while ($n_values > 0 && $p < $max) { |
|
1539 | $attr_values[$attr][] = unpack64IntSigned(substr($response, $p, 8)); |
||
1540 | $p += 8; |
||
1541 | $n_values -= 2; |
||
1542 | } |
||
1543 | } elseif ($type == self::ATTR_STRING) { |
||
1544 | $attr_values[$attr] = substr($response, $p, $val); |
||
1545 | $p += $val; |
||
1546 | } elseif ($type == self::ATTR_FACTORS) { |
||
1547 | $attr_values[$attr] = substr($response, $p, $val - 4); |
||
1548 | $p += $val-4; |
||
1549 | } else { |
||
1550 | $attr_values[$attr] = fixUInt($val); |
||
1551 | } |
||
1552 | } |
||
1553 | |||
1554 | if ($this->array_result) { |
||
1555 | $result['matches'][$idx]['attrs'] = $attr_values; |
||
1556 | } else { |
||
1557 | $result['matches'][$doc]['attrs'] = $attr_values; |
||
1558 | } |
||
1559 | } |
||
1560 | |||
1561 | list($total, $total_found, $msecs, $words) = array_values(unpack('N*N*N*N*', substr($response, $p, 16))); |
||
1562 | $result['total'] = sprintf('%u', $total); |
||
1563 | $result['total_found'] = sprintf('%u', $total_found); |
||
1564 | $result['time'] = sprintf('%.3f', $msecs / 1000); |
||
1565 | $p += 16; |
||
1566 | |||
1567 | while ($words --> 0 && $p < $max) { |
||
1568 | list(, $len) = unpack('N*', substr($response, $p, 4)); |
||
1569 | $p += 4; |
||
1570 | $word = substr($response, $p, $len); |
||
1571 | $p += $len; |
||
1572 | list($docs, $hits) = array_values(unpack('N*N*', substr($response, $p, 8))); |
||
1573 | $p += 8; |
||
1574 | $result['words'][$word] = array ( |
||
1575 | 'docs' => sprintf('%u', $docs), |
||
1576 | 'hits' => sprintf('%u', $hits) |
||
1577 | ); |
||
1578 | } |
||
1579 | } |
||
1580 | |||
1581 | $this->mbPop(); |
||
1582 | return $results; |
||
1583 | } |
||
1584 | |||
1585 | ///////////////////////////////////////////////////////////////////////////// |
||
1586 | // excerpts generation |
||
1587 | ///////////////////////////////////////////////////////////////////////////// |
||
1588 | |||
1589 | /** |
||
1590 | * Connect to searchd server, and generate exceprts (snippets) of given documents for given query. |
||
1591 | * Returns false on failure, an array of snippets on success |
||
1592 | * |
||
1593 | * @param array $docs |
||
1594 | * @param string $index |
||
1595 | * @param string $words |
||
1596 | * @param array $opts |
||
1597 | * |
||
1598 | * @return array|bool |
||
1599 | */ |
||
1600 | public function buildExcerpts(array $docs, $index, $words, array $opts = array()) |
||
1601 | { |
||
1602 | assert(is_string($index)); |
||
1603 | assert(is_string($words)); |
||
1604 | |||
1605 | $this->mbPush(); |
||
1606 | |||
1607 | if (($fp = $this->connect()) === false) { |
||
1608 | $this->mbPop(); |
||
1609 | return false; |
||
1610 | } |
||
1611 | |||
1612 | ///////////////// |
||
1613 | // fixup options |
||
1614 | ///////////////// |
||
1615 | |||
1616 | $opts = array_merge(array( |
||
1617 | 'before_match' => '<b>', |
||
1618 | 'after_match' => '</b>', |
||
1619 | 'chunk_separator' => ' ... ', |
||
1620 | 'limit' => 256, |
||
1621 | 'limit_passages' => 0, |
||
1622 | 'limit_words' => 0, |
||
1623 | 'around' => 5, |
||
1624 | 'exact_phrase' => false, |
||
1625 | 'single_passage' => false, |
||
1626 | 'use_boundaries' => false, |
||
1627 | 'weight_order' => false, |
||
1628 | 'query_mode' => false, |
||
1629 | 'force_all_words' => false, |
||
1630 | 'start_passage_id' => 1, |
||
1631 | 'load_files' => false, |
||
1632 | 'html_strip_mode' => 'index', |
||
1633 | 'allow_empty' => false, |
||
1634 | 'passage_boundary' => 'none', |
||
1635 | 'emit_zones' => false, |
||
1636 | 'load_files_scattered' => false |
||
1637 | ), $opts); |
||
1638 | |||
1639 | ///////////////// |
||
1640 | // build request |
||
1641 | ///////////////// |
||
1642 | |||
1643 | // v.1.2 req |
||
1644 | $flags = 1; // remove spaces |
||
1645 | if ($opts['exact_phrase']) { |
||
1646 | $flags |= 2; |
||
1647 | } |
||
1648 | if ($opts['single_passage']) { |
||
1649 | $flags |= 4; |
||
1650 | } |
||
1651 | if ($opts['use_boundaries']) { |
||
1652 | $flags |= 8; |
||
1653 | } |
||
1654 | if ($opts['weight_order']) { |
||
1655 | $flags |= 16; |
||
1656 | } |
||
1657 | if ($opts['query_mode']) { |
||
1658 | $flags |= 32; |
||
1659 | } |
||
1660 | if ($opts['force_all_words']) { |
||
1661 | $flags |= 64; |
||
1662 | } |
||
1663 | if ($opts['load_files']) { |
||
1664 | $flags |= 128; |
||
1665 | } |
||
1666 | if ($opts['allow_empty']) { |
||
1667 | $flags |= 256; |
||
1668 | } |
||
1669 | if ($opts['emit_zones']) { |
||
1670 | $flags |= 512; |
||
1671 | } |
||
1672 | if ($opts['load_files_scattered']) { |
||
1673 | $flags |= 1024; |
||
1674 | } |
||
1675 | $req = pack('NN', 0, $flags); // mode=0, flags=$flags |
||
1676 | $req .= pack('N', strlen($index)) . $index; // req index |
||
1677 | $req .= pack('N', strlen($words)) . $words; // req words |
||
1678 | |||
1679 | // options |
||
1680 | $req .= pack('N', strlen($opts['before_match'])) . $opts['before_match']; |
||
1681 | $req .= pack('N', strlen($opts['after_match'])) . $opts['after_match']; |
||
1682 | $req .= pack('N', strlen($opts['chunk_separator'])) . $opts['chunk_separator']; |
||
1683 | $req .= pack('NN', (int)$opts['limit'], (int)$opts['around']); |
||
1684 | // v.1.2 |
||
1685 | $req .= pack('NNN', (int)$opts['limit_passages'], (int)$opts['limit_words'], (int)$opts['start_passage_id']); |
||
1686 | $req .= pack('N', strlen($opts['html_strip_mode'])) . $opts['html_strip_mode']; |
||
1687 | $req .= pack('N', strlen($opts['passage_boundary'])) . $opts['passage_boundary']; |
||
1688 | |||
1689 | // documents |
||
1690 | $req .= pack('N', count($docs)); |
||
1691 | foreach ($docs as $doc) { |
||
1692 | assert(is_string($doc)); |
||
1693 | $req .= pack('N', strlen($doc)) . $doc; |
||
1694 | } |
||
1695 | |||
1696 | //////////////////////////// |
||
1697 | // send query, get response |
||
1698 | //////////////////////////// |
||
1699 | |||
1700 | $len = strlen($req); |
||
1701 | $req = pack('nnN', self::SEARCHD_COMMAND_EXCERPT, self::VER_COMMAND_EXCERPT, $len) . $req; // add header |
||
1702 | if (!$this->send($fp, $req, $len + 8) || !($response = $this->getResponse($fp, self::VER_COMMAND_EXCERPT))) { |
||
1703 | $this->mbPop(); |
||
1704 | return false; |
||
1705 | } |
||
1706 | |||
1707 | ////////////////// |
||
1708 | // parse response |
||
1709 | ////////////////// |
||
1710 | |||
1711 | $pos = 0; |
||
1712 | $res = array(); |
||
1713 | $rlen = strlen($response); |
||
1714 | $count = count($docs); |
||
1715 | while ($count--) { |
||
1716 | list(, $len) = unpack('N*', substr($response, $pos, 4)); |
||
1717 | $pos += 4; |
||
1718 | |||
1719 | if ($pos + $len > $rlen) { |
||
1720 | $this->error = 'incomplete reply'; |
||
1721 | $this->mbPop(); |
||
1722 | return false; |
||
1723 | } |
||
1724 | $res[] = $len ? substr($response, $pos, $len) : ''; |
||
1725 | $pos += $len; |
||
1726 | } |
||
1727 | |||
1728 | $this->mbPop(); |
||
1729 | return $res; |
||
1730 | } |
||
1731 | |||
1732 | |||
1733 | ///////////////////////////////////////////////////////////////////////////// |
||
1734 | // keyword generation |
||
1735 | ///////////////////////////////////////////////////////////////////////////// |
||
1736 | |||
1737 | /** |
||
1738 | * Connect to searchd server, and generate keyword list for a given query returns false on failure, |
||
1739 | * an array of words on success |
||
1740 | * |
||
1741 | * @param string $query |
||
1742 | * @param string $index |
||
1743 | * @param bool $hits |
||
1744 | * |
||
1745 | * @return array|bool |
||
1746 | */ |
||
1747 | public function buildKeywords($query, $index, $hits) |
||
1748 | { |
||
1749 | assert(is_string($query)); |
||
1750 | assert(is_string($index)); |
||
1751 | assert(is_bool($hits)); |
||
1752 | |||
1753 | $this->mbPush(); |
||
1754 | |||
1755 | if (($fp = $this->connect()) === false) { |
||
1756 | $this->mbPop(); |
||
1757 | return false; |
||
1758 | } |
||
1759 | |||
1760 | ///////////////// |
||
1761 | // build request |
||
1762 | ///////////////// |
||
1763 | |||
1764 | // v.1.0 req |
||
1765 | $req = pack('N', strlen($query)) . $query; // req query |
||
1766 | $req .= pack('N', strlen($index)) . $index; // req index |
||
1767 | $req .= pack('N', (int)$hits); |
||
1768 | |||
1769 | //////////////////////////// |
||
1770 | // send query, get response |
||
1771 | //////////////////////////// |
||
1772 | |||
1773 | $len = strlen($req); |
||
1774 | $req = pack('nnN', self::SEARCHD_COMMAND_KEYWORDS, self::VER_COMMAND_KEYWORDS, $len) . $req; // add header |
||
1775 | if (!$this->send($fp, $req, $len + 8) || !($response = $this->getResponse($fp, self::VER_COMMAND_KEYWORDS))) { |
||
1776 | $this->mbPop(); |
||
1777 | return false; |
||
1778 | } |
||
1779 | |||
1780 | ////////////////// |
||
1781 | // parse response |
||
1782 | ////////////////// |
||
1783 | |||
1784 | $pos = 0; |
||
1785 | $res = array(); |
||
1786 | $rlen = strlen($response); |
||
1787 | list(, $nwords) = unpack('N*', substr($response, $pos, 4)); |
||
1788 | $pos += 4; |
||
1789 | for ($i = 0; $i < $nwords; $i++) { |
||
1790 | list(, $len) = unpack('N*', substr($response, $pos, 4)); |
||
1791 | $pos += 4; |
||
1792 | $tokenized = $len ? substr($response, $pos, $len) : ''; |
||
1793 | $pos += $len; |
||
1794 | |||
1795 | list(, $len) = unpack('N*', substr($response, $pos, 4)); |
||
1796 | $pos += 4; |
||
1797 | $normalized = $len ? substr($response, $pos, $len) : ''; |
||
1798 | $pos += $len; |
||
1799 | |||
1800 | $res[] = array( |
||
1801 | 'tokenized' => $tokenized, |
||
1802 | 'normalized' => $normalized |
||
1803 | ); |
||
1804 | |||
1805 | if ($hits) { |
||
1806 | list($ndocs, $nhits) = array_values(unpack('N*N*', substr($response, $pos, 8))); |
||
1807 | $pos += 8; |
||
1808 | $res[$i]['docs'] = $ndocs; |
||
1809 | $res[$i]['hits'] = $nhits; |
||
1810 | } |
||
1811 | |||
1812 | if ($pos > $rlen) { |
||
1813 | $this->error = 'incomplete reply'; |
||
1814 | $this->mbPop(); |
||
1815 | return false; |
||
1816 | } |
||
1817 | } |
||
1818 | |||
1819 | $this->mbPop(); |
||
1820 | return $res; |
||
1821 | } |
||
1822 | |||
1823 | /** |
||
1824 | * @param string $string |
||
1825 | * |
||
1826 | * @return string |
||
1827 | */ |
||
1828 | public function escapeString($string) |
||
1829 | { |
||
1830 | $from = array('\\', '(',')','|','-','!','@','~','"','&', '/', '^', '$', '=', '<'); |
||
1831 | $to = array('\\\\', '\(','\)','\|','\-','\!','\@','\~','\"', '\&', '\/', '\^', '\$', '\=', '\<'); |
||
1832 | |||
1833 | return str_replace($from, $to, $string); |
||
1834 | } |
||
1835 | |||
1836 | ///////////////////////////////////////////////////////////////////////////// |
||
1837 | // attribute updates |
||
1838 | ///////////////////////////////////////////////////////////////////////////// |
||
1839 | |||
1840 | /** |
||
1841 | * Batch update given attributes in given rows in given indexes |
||
1842 | * Returns amount of updated documents (0 or more) on success, or -1 on failure |
||
1843 | * |
||
1844 | * @param string $index |
||
1845 | * @param array $attrs |
||
1846 | * @param array $values |
||
1847 | * @param bool $mva |
||
1848 | * @param bool $ignore_non_existent |
||
1849 | * |
||
1850 | * @return int |
||
1851 | */ |
||
1852 | public function updateAttributes($index, array $attrs, array $values, $mva = false, $ignore_non_existent = false) |
||
1853 | { |
||
1854 | // verify everything |
||
1855 | assert(is_string($index)); |
||
1856 | assert(is_bool($mva)); |
||
1857 | assert(is_bool($ignore_non_existent)); |
||
1858 | |||
1859 | foreach ($attrs as $attr) { |
||
1860 | assert(is_string($attr)); |
||
1861 | } |
||
1862 | |||
1863 | foreach ($values as $id => $entry) { |
||
1864 | assert(is_numeric($id)); |
||
1865 | assert(is_array($entry)); |
||
1866 | assert(count($entry) == count($attrs)); |
||
1867 | foreach ($entry as $v) { |
||
1868 | if ($mva) { |
||
1869 | assert(is_array($v)); |
||
1870 | foreach ($v as $vv) { |
||
1871 | assert(is_int($vv)); |
||
1872 | } |
||
1873 | } else { |
||
1874 | assert(is_int($v)); |
||
1875 | } |
||
1876 | } |
||
1877 | } |
||
1878 | |||
1879 | // build request |
||
1880 | $this->mbPush(); |
||
1881 | $req = pack('N', strlen($index)) . $index; |
||
1882 | |||
1883 | $req .= pack('N', count($attrs)); |
||
1884 | $req .= pack('N', $ignore_non_existent ? 1 : 0); |
||
1885 | foreach ($attrs as $attr) { |
||
1886 | $req .= pack('N', strlen($attr)) . $attr; |
||
1887 | $req .= pack('N', $mva ? 1 : 0); |
||
1888 | } |
||
1889 | |||
1890 | $req .= pack('N', count($values)); |
||
1891 | foreach ($values as $id => $entry) { |
||
1892 | $req .= pack64IntUnsigned($id); |
||
1893 | foreach ($entry as $v) { |
||
1894 | $req .= pack('N', $mva ? count($v) : $v); |
||
1895 | if ($mva) { |
||
1896 | foreach ($v as $vv) { |
||
1897 | $req .= pack('N', $vv); |
||
1898 | } |
||
1899 | } |
||
1900 | } |
||
1901 | } |
||
1902 | |||
1903 | // connect, send query, get response |
||
1904 | if (($fp = $this->connect()) === false) { |
||
1905 | $this->mbPop(); |
||
1906 | return -1; |
||
1907 | } |
||
1908 | |||
1909 | $len = strlen($req); |
||
1910 | $req = pack('nnN', self::SEARCHD_COMMAND_UPDATE, self::VER_COMMAND_UPDATE, $len) . $req; // add header |
||
1911 | if (!$this->send($fp, $req, $len + 8)) { |
||
1912 | $this->mbPop(); |
||
1913 | return -1; |
||
1914 | } |
||
1915 | |||
1916 | if (!($response = $this->getResponse($fp, self::VER_COMMAND_UPDATE))) { |
||
1917 | $this->mbPop(); |
||
1918 | return -1; |
||
1919 | } |
||
1920 | |||
1921 | // parse response |
||
1922 | list(, $updated) = unpack('N*', substr($response, 0, 4)); |
||
1923 | $this->mbPop(); |
||
1924 | return $updated; |
||
1925 | } |
||
1926 | |||
1927 | ///////////////////////////////////////////////////////////////////////////// |
||
1928 | // persistent connections |
||
1929 | ///////////////////////////////////////////////////////////////////////////// |
||
1930 | |||
1931 | /** |
||
1932 | * @return bool |
||
1933 | */ |
||
1934 | public function open() |
||
1935 | { |
||
1936 | if ($this->socket !== false) { |
||
1937 | $this->error = 'already connected'; |
||
1938 | return false; |
||
1939 | } |
||
1940 | if (($fp = $this->connect()) === false) |
||
1941 | return false; |
||
1942 | |||
1943 | // command, command version = 0, body length = 4, body = 1 |
||
1944 | $req = pack('nnNN', self::SEARCHD_COMMAND_PERSIST, 0, 4, 1); |
||
1945 | if (!$this->send($fp, $req, 12)) { |
||
1946 | return false; |
||
1947 | } |
||
1948 | |||
1949 | $this->socket = $fp; |
||
1950 | return true; |
||
1951 | } |
||
1952 | |||
1953 | /** |
||
1954 | * @return bool |
||
1955 | */ |
||
1956 | public function close() |
||
1957 | { |
||
1958 | if ($this->socket === false) { |
||
1959 | $this->error = 'not connected'; |
||
1960 | return false; |
||
1961 | } |
||
1962 | |||
1963 | fclose($this->socket); |
||
1964 | $this->socket = false; |
||
1965 | |||
1966 | return true; |
||
1967 | } |
||
1968 | |||
1969 | ////////////////////////////////////////////////////////////////////////// |
||
1970 | // status |
||
1971 | ////////////////////////////////////////////////////////////////////////// |
||
1972 | |||
1973 | /** |
||
1974 | * @param bool $session |
||
1975 | * |
||
1976 | * @return array|bool |
||
1977 | */ |
||
1978 | public function status($session = false) |
||
1979 | { |
||
1980 | assert(is_bool($session)); |
||
1981 | |||
1982 | $this->mbPush(); |
||
1983 | if (($fp = $this->connect()) === false) { |
||
1984 | $this->mbPop(); |
||
1985 | return false; |
||
1986 | } |
||
1987 | |||
1988 | // len=4, body=1 |
||
1989 | $req = pack('nnNN', self::SEARCHD_COMMAND_STATUS, self::VER_COMMAND_STATUS, 4, $session ? 0 : 1); |
||
1990 | if (!$this->send($fp, $req, 12) || !($response = $this->getResponse($fp, self::VER_COMMAND_STATUS))) { |
||
1991 | $this->mbPop(); |
||
1992 | return false; |
||
1993 | } |
||
1994 | |||
1995 | $res = substr($response, 4); // just ignore length, error handling, etc |
||
0 ignored issues
–
show
$res is not used, you could remove the assignment.
This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently. $myVar = 'Value';
$higher = false;
if (rand(1, 6) > 3) {
$higher = true;
} else {
$higher = false;
}
Both the ![]() |
|||
1996 | $p = 0; |
||
1997 | list($rows, $cols) = array_values(unpack('N*N*', substr($response, $p, 8))); |
||
1998 | $p += 8; |
||
1999 | |||
2000 | $res = array(); |
||
2001 | for ($i = 0; $i < $rows; $i++) { |
||
2002 | for ($j = 0; $j < $cols; $j++) { |
||
2003 | list(, $len) = unpack('N*', substr($response, $p, 4)); |
||
2004 | $p += 4; |
||
2005 | $res[$i][] = substr($response, $p, $len); |
||
2006 | $p += $len; |
||
2007 | } |
||
2008 | } |
||
2009 | |||
2010 | $this->mbPop(); |
||
2011 | return $res; |
||
2012 | } |
||
2013 | |||
2014 | ////////////////////////////////////////////////////////////////////////// |
||
2015 | // flush |
||
2016 | ////////////////////////////////////////////////////////////////////////// |
||
2017 | |||
2018 | /** |
||
2019 | * @return int |
||
2020 | */ |
||
2021 | public function flushAttributes() |
||
2022 | { |
||
2023 | $this->mbPush(); |
||
2024 | if (($fp = $this->connect()) === false) { |
||
2025 | $this->mbPop(); |
||
2026 | return -1; |
||
2027 | } |
||
2028 | |||
2029 | $req = pack('nnN', self::SEARCHD_COMMAND_FLUSH_ATTRS, self::VER_COMMAND_FLUSH_ATTRS, 0); // len=0 |
||
2030 | if (!$this->send($fp, $req, 8) || !($response = $this->getResponse($fp, self::VER_COMMAND_FLUSH_ATTRS))) { |
||
2031 | $this->mbPop(); |
||
2032 | return -1; |
||
2033 | } |
||
2034 | |||
2035 | $tag = -1; |
||
2036 | if (strlen($response) == 4) { |
||
2037 | list(, $tag) = unpack('N*', $response); |
||
2038 | } else { |
||
2039 | $this->error = 'unexpected response length'; |
||
2040 | } |
||
2041 | |||
2042 | $this->mbPop(); |
||
2043 | return $tag; |
||
2044 | } |
||
2045 | } |
||
2046 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.