Completed
Push — master ( 1765d7...0dda59 )
by Peter
02:32
created

SphinxClient::runQueries()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 32
Code Lines 17

Duplication

Lines 4
Ratio 12.5 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 4
loc 32
ccs 0
cts 22
cp 0
rs 8.439
cc 5
eloc 17
nc 4
nop 0
crap 30
1
<?php
2
3
//
4
// $Id$
5
//
6
7
//
8
// Copyright (c) 2001-2015, Andrew Aksyonoff
9
// Copyright (c) 2008-2015, Sphinx Technologies Inc
10
// All rights reserved
11
//
12
// This program is free software; you can redistribute it and/or modify
13
// it under the terms of the GNU Library General Public License. You should
14
// have received a copy of the LGPL license along with this program; if you
15
// did not, you can find it at http://www.gnu.org/
16
//
17
// WARNING!!!
18
//
19
// As of 2015, we strongly recommend to use either SphinxQL or REST APIs
20
// rather than the native SphinxAPI.
21
//
22
// While both the native SphinxAPI protocol and the existing APIs will
23
// continue to exist, and perhaps should not even break (too much), exposing
24
// all the new features via multiple different native API implementations
25
// is too much of a support complication for us.
26
//
27
// That said, you're welcome to overtake the maintenance of any given
28
// official API, and remove this warning ;)
29
//
30
31
/////////////////////////////////////////////////////////////////////////////
32
// PHP version of Sphinx searchd client (PHP API)
33
/////////////////////////////////////////////////////////////////////////////
34
35
/// known searchd commands
36
define('SEARCHD_COMMAND_SEARCH',     0);
37
define('SEARCHD_COMMAND_EXCERPT',    1);
38
define('SEARCHD_COMMAND_UPDATE',     2);
39
define('SEARCHD_COMMAND_KEYWORDS',   3);
40
define('SEARCHD_COMMAND_PERSIST',    4);
41
define('SEARCHD_COMMAND_STATUS',     5);
42
define('SEARCHD_COMMAND_FLUSHATTRS', 7);
43
44
/// current client-side command implementation versions
45
define('VER_COMMAND_SEARCH',     0x11E);
46
define('VER_COMMAND_EXCERPT',    0x104);
47
define('VER_COMMAND_UPDATE',     0x103);
48
define('VER_COMMAND_KEYWORDS',   0x100);
49
define('VER_COMMAND_STATUS',     0x101);
50
define('VER_COMMAND_QUERY',      0x100);
51
define('VER_COMMAND_FLUSHATTRS', 0x100);
52
53
/// known searchd status codes
54
define('SEARCHD_OK',      0);
55
define('SEARCHD_ERROR',   1);
56
define('SEARCHD_RETRY',   2);
57
define('SEARCHD_WARNING', 3);
58
59
/// known match modes
60
define('SPH_MATCH_ALL',       0);
61
define('SPH_MATCH_ANY',       1);
62
define('SPH_MATCH_PHRASE',    2);
63
define('SPH_MATCH_BOOLEAN',   3);
64
define('SPH_MATCH_EXTENDED',  4);
65
define('SPH_MATCH_FULLSCAN',  5);
66
define('SPH_MATCH_EXTENDED2', 6); // extended engine V2 (TEMPORARY, WILL BE REMOVED)
67
68
/// known ranking modes (ext2 only)
69
define('SPH_RANK_PROXIMITY_BM25', 0); ///< default mode, phrase proximity major factor and BM25 minor one
70
define('SPH_RANK_BM25',           1); ///< statistical mode, BM25 ranking only (faster but worse quality)
71
define('SPH_RANK_NONE',           2); ///< no ranking, all matches get a weight of 1
72
define('SPH_RANK_WORDCOUNT',      3); ///< simple word-count weighting, rank is a weighted sum of per-field keyword occurence counts
73
define('SPH_RANK_PROXIMITY',      4);
74
define('SPH_RANK_MATCHANY',       5);
75
define('SPH_RANK_FIELDMASK',      6);
76
define('SPH_RANK_SPH04',          7);
77
define('SPH_RANK_EXPR',           8);
78
define('SPH_RANK_TOTAL',          9);
79
80
/// known sort modes
81
define('SPH_SORT_RELEVANCE',     0);
82
define('SPH_SORT_ATTR_DESC',     1);
83
define('SPH_SORT_ATTR_ASC',      2);
84
define('SPH_SORT_TIME_SEGMENTS', 3);
85
define('SPH_SORT_EXTENDED',      4);
86
define('SPH_SORT_EXPR',          5);
87
88
/// known filter types
89
define('SPH_FILTER_VALUES',     0);
90
define('SPH_FILTER_RANGE',      1);
91
define('SPH_FILTER_FLOATRANGE', 2);
92
define('SPH_FILTER_STRING',     3);
93
94
/// known attribute types
95
define('SPH_ATTR_INTEGER',   1);
96
define('SPH_ATTR_TIMESTAMP', 2);
97
define('SPH_ATTR_ORDINAL',   3);
98
define('SPH_ATTR_BOOL',      4);
99
define('SPH_ATTR_FLOAT',     5);
100
define('SPH_ATTR_BIGINT',    6);
101
define('SPH_ATTR_STRING',    7);
102
define('SPH_ATTR_FACTORS',   1001);
103
define('SPH_ATTR_MULTI',     0x40000001);
104
define('SPH_ATTR_MULTI64',   0x40000002);
105
106
/// known grouping functions
107
define('SPH_GROUPBY_DAY',      0);
108
define('SPH_GROUPBY_WEEK',     1);
109
define('SPH_GROUPBY_MONTH',    2);
110
define('SPH_GROUPBY_YEAR',     3);
111
define('SPH_GROUPBY_ATTR',     4);
112
define('SPH_GROUPBY_ATTRPAIR', 5);
113
114
// important properties of PHP's integers:
115
//  - always signed (one bit short of PHP_INT_SIZE)
116
//  - conversion from string to int is saturated
117
//  - float is double
118
//  - div converts arguments to floats
119
//  - mod converts arguments to ints
120
121
// the packing code below works as follows:
122
//  - when we got an int, just pack it
123
//    if performance is a problem, this is the branch users should aim for
124
//
125
//  - otherwise, we got a number in string form
126
//    this might be due to different reasons, but we assume that this is
127
//    because it didn't fit into PHP int
128
//
129
//  - factor the string into high and low ints for packing
130
//    - if we have bcmath, then it is used
131
//    - if we don't, we have to do it manually (this is the fun part)
132
//
133
//    - x64 branch does factoring using ints
134
//    - x32 (ab)uses floats, since we can't fit unsigned 32-bit number into an int
135
//
136
// unpacking routines are pretty much the same.
137
//  - return ints if we can
138
//  - otherwise format number into a string
139
140
/// pack 64-bit signed
141
function sphPackI64($v)
142
{
143
    assert(is_numeric($v));
144
145
    // x64
146
    if (PHP_INT_SIZE >= 8) {
147
        $v = (int)$v;
148
        return pack('NN', $v >> 32, $v & 0xFFFFFFFF);
149
    }
150
151
    // x32, int
152
    if (is_int($v)) {
153
        return pack('NN', $v < 0 ? -1 : 0, $v);
154
    }
155
156
    // x32, bcmath
157
    if (function_exists('bcmul')) {
158
        if (bccomp($v, 0) == -1) {
159
            $v = bcadd('18446744073709551616', $v);
160
        }
161
        $h = bcdiv($v, '4294967296', 0);
162
        $l = bcmod($v, '4294967296');
163
        return pack('NN', (float)$h, (float)$l); // conversion to float is intentional; int would lose 31st bit
164
    }
165
166
    // x32, no-bcmath
167
    $p = max(0, strlen($v) - 13);
168
    $lo = abs((float)substr($v, $p));
169
    $hi = abs((float)substr($v, 0, $p));
170
171
    $m = $lo + $hi * 1316134912.0; // (10 ^ 13) % (1 << 32) = 1316134912
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
172
    $q = floor($m / 4294967296.0);
173
    $l = $m - ($q * 4294967296.0);
174
    $h = $hi * 2328.0 + $q; // (10 ^ 13) / (1 << 32) = 2328
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
175
176
    if ($v < 0) {
177
        if ($l == 0) {
178
            $h = 4294967296.0 - $h;
179
        } else {
180
            $h = 4294967295.0 - $h;
181
            $l = 4294967296.0 - $l;
182
        }
183
    }
184
    return pack('NN', $h, $l);
185
}
186
187
/// pack 64-bit unsigned
188
function sphPackU64($v)
189
{
190
    assert(is_numeric($v));
191
192
    // x64
193
    if (PHP_INT_SIZE >= 8) {
194
        assert($v >= 0);
195
196
        // x64, int
197
        if (is_int($v)) {
198
            return pack('NN', $v >> 32, $v & 0xFFFFFFFF);
199
        }
200
201
        // x64, bcmath
202 View Code Duplication
        if (function_exists('bcmul')) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
203
            $h = bcdiv($v, 4294967296, 0);
204
            $l = bcmod($v, 4294967296);
205
            return pack('NN', $h, $l);
206
        }
207
208
        // x64, no-bcmath
209
        $p = max(0, strlen($v) - 13);
210
        $lo = (int)substr($v, $p);
211
        $hi = (int)substr($v, 0, $p);
212
213
        $m = $lo + $hi * 1316134912;
214
        $l = $m % 4294967296;
215
        $h = $hi * 2328 + (int)($m / 4294967296);
216
217
        return pack('NN', $h, $l);
218
    }
219
220
    // x32, int
221
    if (is_int($v)) {
222
        return pack('NN', 0, $v);
223
    }
224
225
    // x32, bcmath
226 View Code Duplication
    if (function_exists('bcmul')) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
227
        $h = bcdiv($v, '4294967296', 0);
228
        $l = bcmod($v, '4294967296');
229
        return pack('NN', (float)$h, (float)$l); // conversion to float is intentional; int would lose 31st bit
230
    }
231
232
    // x32, no-bcmath
233
    $p = max(0, strlen($v) - 13);
234
    $lo = (float)substr($v, $p);
235
    $hi = (float)substr($v, 0, $p);
236
237
    $m = $lo + $hi * 1316134912.0;
238
    $q = floor($m / 4294967296.0);
239
    $l = $m - ($q * 4294967296.0);
240
    $h = $hi * 2328.0 + $q;
241
242
    return pack('NN', $h, $l);
243
}
244
245
// unpack 64-bit unsigned
246
function sphUnpackU64($v)
247
{
248
    list($hi, $lo) = array_values(unpack('N*N*', $v));
249
250
    if (PHP_INT_SIZE >= 8) {
251
        if ($hi < 0) { // because php 5.2.2 to 5.2.5 is totally fucked up again
252
            $hi += 1 << 32;
253
        }
254
        if ($lo < 0) {
255
            $lo += 1 << 32;
256
        }
257
258
        // x64, int
259
        if ($hi <= 2147483647) {
260
            return ($hi << 32) + $lo;
261
        }
262
263
        // x64, bcmath
264
        if (function_exists('bcmul')) {
265
            return bcadd($lo, bcmul($hi, '4294967296'));
266
        }
267
268
        // x64, no-bcmath
269
        $C = 100000;
270
        $h = ((int)($hi / $C) << 32) + (int)($lo / $C);
271
        $l = (($hi % $C) << 32) + ($lo % $C);
272
        if ($l > $C) {
273
            $h += (int)($l / $C);
274
            $l  = $l % $C;
275
        }
276
277
        if ($h == 0) {
278
            return $l;
279
        }
280
        return sprintf('%d%05d', $h, $l);
281
    }
282
283
    // x32, int
284
    if ($hi == 0) {
285
        if ($lo > 0) {
286
            return $lo;
287
        }
288
        return sprintf('%u', $lo);
289
    }
290
291
    $hi = sprintf('%u', $hi);
292
    $lo = sprintf('%u', $lo);
293
294
    // x32, bcmath
295
    if (function_exists('bcmul')) {
296
        return bcadd($lo, bcmul($hi, '4294967296'));
297
    }
298
299
    // x32, no-bcmath
300
    $hi = (float)$hi;
301
    $lo = (float)$lo;
302
303
    $q = floor($hi / 10000000.0);
304
    $r = $hi - $q * 10000000.0;
305
    $m = $lo + $r * 4967296.0;
306
    $mq = floor($m / 10000000.0);
307
    $l = $m - $mq * 10000000.0;
308
    $h = $q * 4294967296.0 + $r * 429.0 + $mq;
309
310
    $h = sprintf('%.0f', $h);
311
    $l = sprintf('%07.0f', $l);
312
    if ($h == '0') {
313
        return sprintf('%.0f', (float)$l);
314
    }
315
    return $h . $l;
316
}
317
318
// unpack 64-bit signed
319
function sphUnpackI64($v)
320
{
321
    list($hi, $lo) = array_values(unpack('N*N*', $v));
322
323
    // x64
324
    if (PHP_INT_SIZE >= 8) {
325
        if ($hi < 0) { // because php 5.2.2 to 5.2.5 is totally fucked up again
326
            $hi += 1 << 32;
327
        }
328
        if ($lo < 0) {
329
            $lo += 1 << 32;
330
        }
331
332
        return ($hi << 32) + $lo;
333
    }
334
335
    if ($hi == 0) { // x32, int
336
        if ($lo > 0) {
337
            return $lo;
338
        }
339
        return sprintf('%u', $lo);
340
    } elseif ($hi == -1) { // x32, int
341
        if ($lo < 0) {
342
            return $lo;
343
        }
344
        return sprintf('%.0f', $lo - 4294967296.0);
345
    }
346
347
    $neg = '';
348
    $c = 0;
349
    if ($hi < 0) {
350
        $hi = ~$hi;
351
        $lo = ~$lo;
352
        $c = 1;
353
        $neg = '-';
354
    }
355
356
    $hi = sprintf('%u', $hi);
357
    $lo = sprintf('%u', $lo);
358
359
    // x32, bcmath
360
    if (function_exists('bcmul')) {
361
        return $neg . bcadd(bcadd($lo, bcmul($hi, '4294967296')), $c);
362
    }
363
364
    // x32, no-bcmath
365
    $hi = (float)$hi;
366
    $lo = (float)$lo;
367
368
    $q = floor($hi / 10000000.0);
369
    $r = $hi - $q * 10000000.0;
370
    $m = $lo + $r * 4967296.0;
371
    $mq = floor($m / 10000000.0);
372
    $l = $m - $mq * 10000000.0 + $c;
373
    $h = $q * 4294967296.0 + $r * 429.0 + $mq;
374
    if ($l == 10000000) {
375
        $l = 0;
376
        $h += 1;
377
    }
378
379
    $h = sprintf('%.0f', $h);
380
    $l = sprintf('%07.0f', $l);
381
    if ($h == '0') {
382
        return $neg . sprintf('%.0f', (float)$l);
383
    }
384
    return $neg . $h . $l;
385
}
386
387
388
function sphFixUint($value)
389
{
390
    if (PHP_INT_SIZE >= 8) {
391
        // x64 route, workaround broken unpack() in 5.2.2+
392
        if ($value < 0) {
393
            $value += 1 << 32;
394
        }
395
        return $value;
396
    } else {
397
        // x32 route, workaround php signed/unsigned braindamage
398
        return sprintf('%u', $value);
399
    }
400
}
401
402
function sphSetBit($flag, $bit, $on)
403
{
404
    if ($on) {
405
        $flag |= 1 << $bit;
406
    } else {
407
        $reset = 16777215 ^ (1 << $bit);
408
        $flag = $flag & $reset;
409
    }
410
    return $flag;
411
}
412
413
414
/// sphinx searchd client class
415
class SphinxClient
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
416
{
417
    protected $host; ///< searchd host (default is 'localhost')
418
    protected $port; ///< searchd port (default is 9312)
419
    protected $offset; ///< how many records to seek from result-set start (default is 0)
420
    protected $limit; ///< how many records to return from result-set starting at offset (default is 20)
421
    protected $mode; ///< query matching mode (default is SPH_MATCH_EXTENDED2)
422
    protected $weights; ///< per-field weights (default is 1 for all fields)
423
    protected $sort; ///< match sorting mode (default is SPH_SORT_RELEVANCE)
424
    protected $sortby; ///< attribute to sort by (defualt is '')
425
    protected $min_id; ///< min ID to match (default is 0, which means no limit)
426
    protected $max_id; ///< max ID to match (default is 0, which means no limit)
427
    protected $filters; ///< search filters
428
    protected $groupby; ///< group-by attribute name
429
    protected $groupfunc; ///< group-by function (to pre-process group-by attribute value with)
430
    protected $groupsort; ///< group-by sorting clause (to sort groups in result set with)
431
    protected $groupdistinct; ///< group-by count-distinct attribute
432
    protected $maxmatches; ///< max matches to retrieve
433
    protected $cutoff; ///< cutoff to stop searching at (default is 0)
434
    protected $retrycount; ///< distributed retries count
435
    protected $retrydelay; ///< distributed retries delay
436
    protected $anchor; ///< geographical anchor point
437
    protected $indexweights; ///< per-index weights
438
    protected $ranker; ///< ranking mode (default is SPH_RANK_PROXIMITY_BM25)
439
    protected $rankexpr; ///< ranking mode expression (for SPH_RANK_EXPR)
440
    protected $maxquerytime; ///< max query time, milliseconds (default is 0, do not limit)
441
    protected $fieldweights; ///< per-field-name weights
442
    protected $overrides; ///< per-query attribute values overrides
443
    protected $select; ///< select-list (attributes or expressions, with optional aliases)
444
    protected $query_flags; ///< per-query various flags
445
    protected $predictedtime; ///< per-query max_predicted_time
446
    protected $outerorderby; ///< outer match sort by
447
    protected $outeroffset; ///< outer offset
448
    protected $outerlimit; ///< outer limit
449
    protected $hasouter;
450
451
    protected $error; ///< last error message
452
    protected $warning; ///< last warning message
453
    protected $connerror; ///< connection error vs remote error flag
454
455
    protected $reqs; ///< requests array for multi-query
456
    protected $mbenc; ///< stored mbstring encoding
457
    protected $arrayresult; ///< whether $result['matches'] should be a hash or an array
458
    protected $timeout; ///< connect timeout
459
460
    /////////////////////////////////////////////////////////////////////////////
461
    // common stuff
462
    /////////////////////////////////////////////////////////////////////////////
463
464
    /// create a new client object and fill defaults
465
    public function __construct()
466
    {
467
        // per-client-object settings
468
        $this->host = 'localhost';
469
        $this->port = 9312;
470
        $this->_path = false;
0 ignored issues
show
Bug introduced by
The property _path does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
471
        $this->_socket = false;
0 ignored issues
show
Bug introduced by
The property _socket does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
472
473
        // per-query settings
474
        $this->offset = 0;
475
        $this->limit = 20;
476
        $this->mode = SPH_MATCH_EXTENDED2;
477
        $this->weights = array();
478
        $this->sort = SPH_SORT_RELEVANCE;
479
        $this->sortby = '';
480
        $this->min_id = 0;
481
        $this->max_id = 0;
482
        $this->filters = array();
483
        $this->groupby = '';
484
        $this->groupfunc = SPH_GROUPBY_DAY;
485
        $this->groupsort = '@group desc';
486
        $this->groupdistinct = '';
487
        $this->maxmatches = 1000;
488
        $this->cutoff = 0;
489
        $this->retrycount = 0;
490
        $this->retrydelay = 0;
491
        $this->anchor = array();
492
        $this->indexweights = array();
493
        $this->ranker = SPH_RANK_PROXIMITY_BM25;
494
        $this->rankexpr = '';
495
        $this->maxquerytime = 0;
496
        $this->fieldweights = array();
497
        $this->overrides = array();
498
        $this->select = '*';
499
        $this->query_flags = sphSetBit(0, 6, true); // default idf=tfidf_normalized
500
        $this->predictedtime = 0;
501
        $this->outerorderby = '';
502
        $this->outeroffset = 0;
503
        $this->outerlimit = 0;
504
        $this->hasouter = false;
505
506
        $this->error = ''; // per-reply fields (for single-query case)
507
        $this->warning = '';
508
        $this->connerror = false;
509
510
        $this->reqs = array();// requests storage (for multi-query case)
511
        $this->mbenc = '';
512
        $this->arrayresult = false;
513
        $this->timeout = 0;
514
    }
515
516
    public function __destruct()
517
    {
518
        if ($this->_socket !== false) {
519
            fclose($this->_socket);
520
        }
521
    }
522
523
    /// get last error message (string)
524
    public function getLastError()
525
    {
526
        return $this->error;
527
    }
528
529
    /// get last warning message (string)
530
    public function getLastWarning()
531
    {
532
        return $this->warning;
533
    }
534
535
    /// get last error flag (to tell network connection errors from searchd errors or broken responses)
536
    public function isConnectError()
537
    {
538
        return $this->connerror;
539
    }
540
541
    /// set searchd host name (string) and port (integer)
542
    public function setServer($host, $port = 0)
543
    {
544
        assert(is_string($host));
545
        if ($host[0] == '/') {
546
            $this->_path = 'unix://' . $host;
547
            return;
548
        }
549
        if (substr($host, 0, 7) == 'unix://') {
550
            $this->_path = $host;
551
            return;
552
        }
553
554
        $this->host = $host;
555
        $port = intval($port);
556
        assert(0 <= $port && $port < 65536);
557
        $this->port = $port == 0 ? 9312 : $port;
558
        $this->_path = '';
559
    }
560
561
    /// set server connection timeout (0 to remove)
562
    public function setConnectTimeout($timeout)
563
    {
564
        assert(is_numeric($timeout));
565
        $this->timeout = $timeout;
566
    }
567
568
569
    protected function send($handle, $data, $length)
570
    {
571
        if (feof($handle) || fwrite($handle, $data, $length) !== $length) {
572
            $this->error = 'connection unexpectedly closed (timed out?)';
573
            $this->connerror = true;
574
            return false;
575
        }
576
        return true;
577
    }
578
579
    /////////////////////////////////////////////////////////////////////////////
580
581
    /// enter mbstring workaround mode
582
    protected function mbPush()
583
    {
584
        $this->mbenc = '';
585
        if (ini_get('mbstring.func_overload') & 2) {
586
            $this->mbenc = mb_internal_encoding();
587
            mb_internal_encoding('latin1');
588
        }
589
    }
590
591
    /// leave mbstring workaround mode
592
    protected function mbPop()
593
    {
594
        if ($this->mbenc) {
595
            mb_internal_encoding($this->mbenc);
596
        }
597
    }
598
599
    /// connect to searchd server
600
    protected function connect()
601
    {
602
        if ($this->_socket !== false) {
603
            // we are in persistent connection mode, so we have a socket
604
            // however, need to check whether it's still alive
605
            if (!@feof($this->_socket)) {
606
                return $this->_socket;
607
            }
608
609
            // force reopen
610
            $this->_socket = false;
611
        }
612
613
        $errno = 0;
614
        $errstr = '';
615
        $this->connerror = false;
616
617
        if ($this->_path) {
618
            $host = $this->_path;
619
            $port = 0;
620
        } else {
621
            $host = $this->host;
622
            $port = $this->port;
623
        }
624
625
        if ($this->timeout <= 0) {
626
            $fp = @fsockopen($host, $port, $errno, $errstr);
627
        } else {
628
            $fp = @fsockopen($host, $port, $errno, $errstr, $this->timeout);
629
        }
630
631
        if (!$fp) {
632
            if ($this->_path) {
633
                $location = $this->_path;
634
            } else {
635
                $location = "{$this->host}:{$this->port}";
636
            }
637
638
            $errstr = trim($errstr);
639
            $this->error = "connection to $location failed (errno=$errno, msg=$errstr)";
640
            $this->connerror = true;
641
            return false;
642
        }
643
644
        // send my version
645
        // this is a subtle part. we must do it before (!) reading back from searchd.
646
        // because otherwise under some conditions (reported on FreeBSD for instance)
647
        // TCP stack could throttle write-write-read pattern because of Nagle.
648
        if (!$this->send($fp, pack('N', 1), 4)) {
649
            fclose($fp);
650
            $this->error = 'failed to send client protocol version';
651
            return false;
652
        }
653
654
        // check version
655
        list(, $v) = unpack('N*', fread($fp, 4));
656
        $v = (int)$v;
657
        if ($v < 1) {
658
            fclose($fp);
659
            $this->error = "expected searchd protocol version 1+, got version '$v'";
660
            return false;
661
        }
662
663
        return $fp;
664
    }
665
666
    /// get and check response packet from searchd server
667
    protected function getResponse($fp, $client_ver)
668
    {
669
        $response = '';
670
        $len = 0;
671
672
        $header = fread($fp, 8);
673
        if (strlen($header) == 8) {
674
            list($status, $ver, $len) = array_values(unpack('n2a/Nb', $header));
675
            $left = $len;
676
            while ($left > 0 && !feof($fp)) {
677
                $chunk = fread($fp, min(8192, $left));
678
                if ($chunk) {
679
                    $response .= $chunk;
680
                    $left -= strlen($chunk);
681
                }
682
            }
683
        }
684
        if ($this->_socket === false) {
685
            fclose($fp);
686
        }
687
688
        // check response
689
        $read = strlen($response);
690
        if (!$response || $read != $len) {
691
            $this->error = $len
692
                ? "failed to read searchd response (status=$status, ver=$ver, len=$len, read=$read)"
0 ignored issues
show
Bug introduced by
The variable $status does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $ver does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
693
                : 'received zero-sized searchd response';
694
            return false;
695
        }
696
697
        // check status
698
        if ($status == SEARCHD_WARNING) {
699
            list(, $wlen) = unpack('N*', substr($response, 0, 4));
700
            $this->warning = substr($response, 4, $wlen);
701
            return substr($response, 4 + $wlen);
702
        }
703
        if ($status == SEARCHD_ERROR) {
704
            $this->error = 'searchd error: ' . substr($response, 4);
705
            return false;
706
        }
707
        if ($status == SEARCHD_RETRY) {
708
            $this->error = 'temporary searchd error: ' . substr($response, 4);
709
            return false;
710
        }
711
        if ($status != SEARCHD_OK) {
712
            $this->error = "unknown status code '$status'";
713
            return false;
714
        }
715
716
        // check version
717
        if ($ver < $client_ver) {
718
            $this->warning = sprintf(
719
                'searchd command v.%d.%d older than client\'s v.%d.%d, some options might not work',
720
                $ver >> 8,
721
                $ver & 0xff,
722
                $client_ver >> 8,
723
                $client_ver & 0xff
724
            );
725
        }
726
727
        return $response;
728
    }
729
730
    /////////////////////////////////////////////////////////////////////////////
731
    // searching
732
    /////////////////////////////////////////////////////////////////////////////
733
734
    /// set offset and count into result set,
735
    /// and optionally set max-matches and cutoff limits
736
    public function setLimits($offset, $limit, $max = 0, $cutoff = 0)
737
    {
738
        assert(is_int($offset));
739
        assert(is_int($limit));
740
        assert($offset >= 0);
741
        assert($limit > 0);
742
        assert($max >= 0);
743
        $this->offset = $offset;
744
        $this->limit = $limit;
745
        if ($max > 0) {
746
            $this->maxmatches = $max;
747
        }
748
        if ($cutoff > 0) {
749
            $this->cutoff = $cutoff;
750
        }
751
    }
752
753
    /// set maximum query time, in milliseconds, per-index
754
    /// integer, 0 means 'do not limit'
755
    public function setMaxQueryTime($max)
756
    {
757
        assert(is_int($max));
758
        assert($max >= 0);
759
        $this->maxquerytime = $max;
760
    }
761
762
    /// set matching mode
763
    public function setMatchMode($mode)
764
    {
765
        trigger_error(
766
            'DEPRECATED: Do not call this method or, even better, use SphinxQL instead of an API',
767
            E_USER_DEPRECATED
768
        );
769
        assert(
770
            $mode == SPH_MATCH_ALL ||
771
            $mode == SPH_MATCH_ANY ||
772
            $mode == SPH_MATCH_PHRASE ||
773
            $mode == SPH_MATCH_BOOLEAN ||
774
            $mode == SPH_MATCH_EXTENDED ||
775
            $mode == SPH_MATCH_FULLSCAN ||
776
            $mode == SPH_MATCH_EXTENDED2
777
        );
778
        $this->mode = $mode;
779
    }
780
781
    /// set ranking mode
782
    public function setRankingMode($ranker, $rankexpr='')
783
    {
784
        assert($ranker === 0 || $ranker >= 1 && $ranker < SPH_RANK_TOTAL);
785
        assert(is_string($rankexpr));
786
        $this->ranker = $ranker;
787
        $this->rankexpr = $rankexpr;
788
    }
789
790
    /// set matches sorting mode
791
    public function setSortMode($mode, $sortby = '')
792
    {
793
        assert (
794
            $mode == SPH_SORT_RELEVANCE ||
795
            $mode == SPH_SORT_ATTR_DESC ||
796
            $mode == SPH_SORT_ATTR_ASC ||
797
            $mode == SPH_SORT_TIME_SEGMENTS ||
798
            $mode == SPH_SORT_EXTENDED ||
799
            $mode == SPH_SORT_EXPR
800
        );
801
        assert(is_string($sortby));
802
        assert($mode == SPH_SORT_RELEVANCE || strlen($sortby) > 0);
803
804
        $this->sort = $mode;
805
        $this->sortby = $sortby;
806
    }
807
808
    /// bind per-field weights by order
809
    /// DEPRECATED; use SetFieldWeights() instead
810
    public function setWeights($weights)
0 ignored issues
show
Unused Code introduced by
The parameter $weights is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
811
    {
812
        exit('This method is now deprecated; please use SetFieldWeights instead');
0 ignored issues
show
Coding Style Compatibility introduced by
The method setWeights() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
813
    }
814
815
    /// bind per-field weights by name
816 View Code Duplication
    public function setFieldWeights($weights)
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
817
    {
818
        assert(is_array($weights));
819
        foreach ($weights as $name => $weight) {
820
            assert(is_string($name));
821
            assert(is_int($weight));
822
        }
823
        $this->fieldweights = $weights;
824
    }
825
826
    /// bind per-index weights by name
827 View Code Duplication
    public function setIndexWeights($weights)
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
828
    {
829
        assert(is_array($weights));
830
        foreach ($weights as $index => $weight) {
831
            assert(is_string($index));
832
            assert(is_int($weight));
833
        }
834
        $this->indexweights = $weights;
835
    }
836
837
    /// set IDs range to match
838
    /// only match records if document ID is beetwen $min and $max (inclusive)
839
    public function setIDRange($min, $max)
840
    {
841
        assert(is_numeric($min));
842
        assert(is_numeric($max));
843
        assert($min <= $max);
844
        $this->min_id = $min;
845
        $this->max_id = $max;
846
    }
847
848
    /// set values set filter
849
    /// only match records where $attribute value is in given set
850
    public function setFilter($attribute, $values, $exclude = false)
851
    {
852
        assert(is_string($attribute));
853
        assert(is_array($values));
854
        assert(count($values));
855
856
        if (is_array($values) && count($values)) {
857
            foreach ($values as $value) {
858
                assert(is_numeric($value));
859
            }
860
861
            $this->filters[] = array(
862
                'type' => SPH_FILTER_VALUES,
863
                'attr' => $attribute,
864
                'exclude' => $exclude,
865
                'values' => $values
866
            );
867
        }
868
    }
869
870
    /// set string filter
871
    /// only match records where $attribute value is equal
872
    public function setFilterString($attribute, $value, $exclude = false)
873
    {
874
        assert(is_string($attribute));
875
        assert(is_string($value));
876
        $this->filters[] = array(
877
            'type' => SPH_FILTER_STRING,
878
            'attr' => $attribute,
879
            'exclude' => $exclude,
880
            'value' => $value
881
        );
882
    }    
883
884
    /// set range filter
885
    /// only match records if $attribute value is beetwen $min and $max (inclusive)
886 View Code Duplication
    public function setFilterRange($attribute, $min, $max, $exclude = false)
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
887
    {
888
        assert(is_string($attribute));
889
        assert(is_numeric($min));
890
        assert(is_numeric($max));
891
        assert($min <= $max);
892
893
        $this->filters[] = array(
894
            'type' => SPH_FILTER_RANGE,
895
            'attr' => $attribute,
896
            'exclude' => $exclude,
897
            'min' => $min,
898
            'max' => $max
899
        );
900
    }
901
902
    /// set float range filter
903
    /// only match records if $attribute value is beetwen $min and $max (inclusive)
904 View Code Duplication
    public function setFilterFloatRange($attribute, $min, $max, $exclude = false)
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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' => SPH_FILTER_FLOATRANGE,
913
            'attr' => $attribute,
914
            'exclude' => $exclude,
915
            'min' => $min,
916
            'max' => $max
917
        );
918
    }
919
920
    /// setup anchor point for geosphere distance calculations
921
    /// required to use @geodist in filters and sorting
922
    /// latitude and longitude must be in radians
923
    public function setGeoAnchor($attrlat, $attrlong, $lat, $long)
924
    {
925
        assert(is_string($attrlat));
926
        assert(is_string($attrlong));
927
        assert(is_float($lat));
928
        assert(is_float($long));
929
930
        $this->anchor = array(
931
            'attrlat' => $attrlat,
932
            'attrlong' => $attrlong,
933
            'lat' => $lat,
934
            'long' => $long
935
        );
936
    }
937
938
    /// set grouping attribute and function
939
    public function setGroupBy($attribute, $func, $groupsort = '@group desc')
940
    {
941
        assert(is_string($attribute));
942
        assert(is_string($groupsort));
943
        assert(
944
            $func == SPH_GROUPBY_DAY ||
945
            $func == SPH_GROUPBY_WEEK ||
946
            $func == SPH_GROUPBY_MONTH ||
947
            $func == SPH_GROUPBY_YEAR ||
948
            $func == SPH_GROUPBY_ATTR ||
949
            $func == SPH_GROUPBY_ATTRPAIR
950
        );
951
952
        $this->groupby = $attribute;
953
        $this->groupfunc = $func;
954
        $this->groupsort = $groupsort;
955
    }
956
957
    /// set count-distinct attribute for group-by queries
958
    public function setGroupDistinct($attribute)
959
    {
960
        assert(is_string($attribute));
961
        $this->groupdistinct = $attribute;
962
    }
963
964
    /// set distributed retries count and delay
965
    public function setRetries($count, $delay = 0)
966
    {
967
        assert(is_int($count) && $count >= 0);
968
        assert(is_int($delay) && $delay >= 0);
969
        $this->retrycount = $count;
970
        $this->retrydelay = $delay;
971
    }
972
973
    /// set result set format (hash or array; hash by default)
974
    /// PHP specific; needed for group-by-MVA result sets that may contain duplicate IDs
975
    public function setArrayResult($arrayresult)
976
    {
977
        assert(is_bool($arrayresult));
978
        $this->arrayresult = $arrayresult;
979
    }
980
981
    /// set attribute values override
982
    /// there can be only one override per attribute
983
    /// $values must be a hash that maps document IDs to attribute values
984
    public function setOverride($attrname, $attrtype, $values)
985
    {
986
        trigger_error(
987
            'DEPRECATED: Do not call this method. Use SphinxQL REMAP() function instead.',
988
            E_USER_DEPRECATED
989
        );
990
        assert(is_string($attrname));
991
        assert(in_array($attrtype, array(
992
            SPH_ATTR_INTEGER,
993
            SPH_ATTR_TIMESTAMP,
994
            SPH_ATTR_BOOL,
995
            SPH_ATTR_FLOAT,
996
            SPH_ATTR_BIGINT
997
        )));
998
        assert(is_array($values));
999
1000
        $this->overrides[$attrname] = array(
1001
            'attr' => $attrname,
1002
            'type' => $attrtype,
1003
            'values' => $values
1004
        );
1005
    }
1006
1007
    /// set select-list (attributes or expressions), SQL-like syntax
1008
    public function setSelect($select)
1009
    {
1010
        assert(is_string($select));
1011
        $this->select = $select;
1012
    }
1013
1014
    public function setQueryFlag($flag_name, $flag_value)
1015
    {
1016
        $known_names = array(
1017
            'reverse_scan',
1018
            'sort_method',
1019
            'max_predicted_time',
1020
            'boolean_simplify',
1021
            'idf',
1022
            'global_idf',
1023
            'low_priority'
1024
        );
1025
        $flags = array (
1026
            'reverse_scan' => array(0, 1),
1027
            'sort_method' => array('pq', 'kbuffer'),
1028
            'max_predicted_time' => array(0),
1029
            'boolean_simplify' => array(true, false),
1030
            'idf' => array ('normalized', 'plain', 'tfidf_normalized', 'tfidf_unnormalized'),
1031
            'global_idf' => array(true, false),
1032
            'low_priority' => array(true, false)
1033
        );
1034
1035
        assert(isset($flag_name, $known_names));
1036
        assert(
1037
            in_array($flag_value, $flags[$flag_name], true) ||
1038
            ($flag_name == 'max_predicted_time' && is_int($flag_value) && $flag_value >= 0)
1039
        );
1040
1041
        if ($flag_name == 'reverse_scan') {
1042
            $this->query_flags = sphSetBit($this->query_flags, 0, $flag_value == 1);
1043
        }
1044
        if ($flag_name == 'sort_method') {
1045
            $this->query_flags = sphSetBit($this->query_flags, 1, $flag_value == 'kbuffer');
1046
        }
1047
        if ($flag_name == 'max_predicted_time') {
1048
            $this->query_flags = sphSetBit($this->query_flags, 2, $flag_value > 0);
1049
            $this->predictedtime = (int)$flag_value;
1050
        }
1051
        if ($flag_name == 'boolean_simplify') {
1052
            $this->query_flags = sphSetBit($this->query_flags, 3, $flag_value);
1053
        }
1054 View Code Duplication
        if ($flag_name == 'idf' && ($flag_value == 'normalized' || $flag_value == 'plain')) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1055
            $this->query_flags = sphSetBit($this->query_flags, 4, $flag_value == 'plain');
1056
        }
1057
        if ($flag_name == 'global_idf') {
1058
            $this->query_flags = sphSetBit($this->query_flags, 5, $flag_value);
1059
        }
1060 View Code Duplication
        if ($flag_name == 'idf' && ($flag_value == 'tfidf_normalized' || $flag_value == 'tfidf_unnormalized')) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1061
            $this->query_flags = sphSetBit($this->query_flags, 6, $flag_value == 'tfidf_normalized');
1062
        }
1063
        if ($flag_name == 'low_priority') {
1064
            $this->query_flags = sphSetBit($this->query_flags, 8, $flag_value);
1065
        }
1066
    }
1067
1068
    /// set outer order by parameters
1069
    public function setOuterSelect($orderby, $offset, $limit)
1070
    {
1071
        assert(is_string($orderby));
1072
        assert(is_int($offset));
1073
        assert(is_int($limit));
1074
        assert($offset >= 0);
1075
        assert($limit > 0);
1076
1077
        $this->outerorderby = $orderby;
1078
        $this->outeroffset = $offset;
1079
        $this->outerlimit = $limit;
1080
        $this->hasouter = true;
1081
    }
1082
1083
1084
    //////////////////////////////////////////////////////////////////////////////
1085
1086
    /// clear all filters (for multi-queries)
1087
    public function resetFilters()
1088
    {
1089
        $this->filters = array();
1090
        $this->anchor = array();
1091
    }
1092
1093
    /// clear groupby settings (for multi-queries)
1094
    public function resetGroupBy()
1095
    {
1096
        $this->groupby = '';
1097
        $this->groupfunc = SPH_GROUPBY_DAY;
1098
        $this->groupsort = '@group desc';
1099
        $this->groupdistinct = '';
1100
    }
1101
1102
    /// clear all attribute value overrides (for multi-queries)
1103
    public function resetOverrides()
1104
    {
1105
        $this->overrides = array();
1106
    }
1107
1108
    public function resetQueryFlag()
1109
    {
1110
        $this->query_flags = sphSetBit(0, 6, true); // default idf=tfidf_normalized
1111
        $this->predictedtime = 0;
1112
    }
1113
1114
    public function resetOuterSelect()
1115
    {
1116
        $this->outerorderby = '';
1117
        $this->outeroffset = 0;
1118
        $this->outerlimit = 0;
1119
        $this->hasouter = false;
1120
    }
1121
1122
    //////////////////////////////////////////////////////////////////////////////
1123
1124
    /// connect to searchd server, run given search query through given indexes,
1125
    /// and return the search results
1126
    public function query($query, $index = '*', $comment = '')
1127
    {
1128
        assert(empty($this->reqs));
1129
1130
        $this->addQuery($query, $index, $comment);
1131
        $results = $this->runQueries();
1132
        $this->reqs = array(); // just in case it failed too early
1133
1134
        if (!is_array($results)) {
1135
            return false; // probably network error; error message should be already filled
1136
        }
1137
1138
        $this->error = $results[0]['error'];
1139
        $this->warning = $results[0]['warning'];
1140
        if ($results[0]['status'] == SEARCHD_ERROR) {
1141
            return false;
1142
        } else {
1143
            return $results[0];
1144
        }
1145
    }
1146
1147
    /// helper to pack floats in network byte order
1148
    protected function packFloat($f)
1149
    {
1150
        $t1 = pack('f', $f); // machine order
1151
        list(, $t2) = unpack('L*', $t1); // int in machine order
1152
        return pack('N', $t2);
1153
    }
1154
1155
    /// add query to multi-query batch
1156
    /// returns index into results array from RunQueries() call
1157
    public function addQuery($query, $index = '*', $comment = '')
1158
    {
1159
        // mbstring workaround
1160
        $this->mbPush();
1161
1162
        // build request
1163
        $req = pack('NNNNN', $this->query_flags, $this->offset, $this->limit, $this->mode, $this->ranker);
1164
        if ($this->ranker == SPH_RANK_EXPR) {
1165
            $req .= pack('N', strlen($this->rankexpr)) . $this->rankexpr;
1166
        }
1167
        $req .= pack('N', $this->sort); // (deprecated) sort mode
1168
        $req .= pack('N', strlen($this->sortby)) . $this->sortby;
1169
        $req .= pack('N', strlen($query)) . $query; // query itself
1170
        $req .= pack('N', count($this->weights)); // weights
1171
        foreach ($this->weights as $weight) {
1172
            $req .= pack('N', (int)$weight);
1173
        }
1174
        $req .= pack('N', strlen($index)) . $index; // indexes
1175
        $req .= pack('N', 1); // id64 range marker
1176
        $req .= sphPackU64($this->min_id) . sphPackU64($this->max_id); // id64 range
1177
1178
        // filters
1179
        $req .= pack('N', count($this->filters));
1180
        foreach ($this->filters as $filter) {
1181
            $req .= pack('N', strlen($filter['attr'])) . $filter['attr'];
1182
            $req .= pack('N', $filter['type']);
1183
            switch ($filter['type']) {
1184
                case SPH_FILTER_VALUES:
1185
                    $req .= pack('N', count($filter['values']));
1186
                    foreach ($filter['values'] as $value) {
1187
                        $req .= sphPackI64($value);
1188
                    }
1189
                    break;
1190
                case SPH_FILTER_RANGE:
1191
                    $req .= sphPackI64($filter['min']) . sphPackI64($filter['max']);
1192
                    break;
1193
                case SPH_FILTER_FLOATRANGE:
1194
                    $req .= $this->packFloat($filter['min']) . $this->packFloat($filter['max']);
1195
                    break;
1196
                case SPH_FILTER_STRING:
1197
                    $req .= pack('N', strlen($filter['value'])) . $filter['value'];
1198
                    break;
1199
                default:
1200
                    assert(0 && 'internal error: unhandled filter type');
1201
            }
1202
            $req .= pack('N', $filter['exclude']);
1203
        }
1204
1205
        // group-by clause, max-matches count, group-sort clause, cutoff count
1206
        $req .= pack('NN', $this->groupfunc, strlen($this->groupby)) . $this->groupby;
1207
        $req .= pack('N', $this->maxmatches);
1208
        $req .= pack('N', strlen($this->groupsort)) . $this->groupsort;
1209
        $req .= pack('NNN', $this->cutoff, $this->retrycount, $this->retrydelay);
1210
        $req .= pack('N', strlen($this->groupdistinct)) . $this->groupdistinct;
1211
1212
        // anchor point
1213
        if (empty($this->anchor)) {
1214
            $req .= pack('N', 0);
1215
        } else {
1216
            $a =& $this->anchor;
1217
            $req .= pack('N', 1);
1218
            $req .= pack('N', strlen($a['attrlat'])) . $a['attrlat'];
1219
            $req .= pack('N', strlen($a['attrlong'])) . $a['attrlong'];
1220
            $req .= $this->packFloat($a['lat']) . $this->packFloat($a['long']);
1221
        }
1222
1223
        // per-index weights
1224
        $req .= pack('N', count($this->indexweights));
1225
        foreach ($this->indexweights as $idx => $weight) {
1226
            $req .= pack('N', strlen($idx)) . $idx . pack('N', $weight);
1227
        }
1228
1229
        // max query time
1230
        $req .= pack('N', $this->maxquerytime);
1231
1232
        // per-field weights
1233
        $req .= pack('N', count($this->fieldweights));
1234
        foreach ($this->fieldweights as $field => $weight) {
1235
            $req .= pack('N', strlen($field)) . $field . pack('N', $weight);
1236
        }
1237
1238
        // comment
1239
        $req .= pack('N', strlen($comment)) . $comment;
1240
1241
        // attribute overrides
1242
        $req .= pack('N', count($this->overrides));
1243
        foreach ($this->overrides as $key => $entry) {
1244
            $req .= pack('N', strlen($entry['attr'])) . $entry['attr'];
1245
            $req .= pack('NN', $entry['type'], count($entry['values']));
1246
            foreach ($entry['values'] as $id => $val) {
1247
                assert(is_numeric($id));
1248
                assert(is_numeric($val));
1249
1250
                $req .= sphPackU64($id);
1251
                switch ($entry['type']) {
1252
                    case SPH_ATTR_FLOAT:
1253
                        $req .= $this->packFloat($val);
1254
                        break;
1255
                    case SPH_ATTR_BIGINT:
1256
                        $req .= sphPackI64($val);
1257
                        break;
1258
                    default:
1259
                        $req .= pack('N', $val);
1260
                        break;
1261
                }
1262
            }
1263
        }
1264
1265
        // select-list
1266
        $req .= pack('N', strlen($this->select)) . $this->select;
1267
1268
        // max_predicted_time
1269
        if ($this->predictedtime > 0) {
1270
            $req .= pack('N', (int)$this->predictedtime);
1271
        }
1272
1273
        $req .= pack('N', strlen($this->outerorderby)) . $this->outerorderby;
1274
        $req .= pack('NN', $this->outeroffset, $this->outerlimit);
1275
        if ($this->hasouter) {
1276
            $req .= pack('N', 1);
1277
        } else {
1278
            $req .= pack('N', 0);
1279
        }
1280
1281
        // mbstring workaround
1282
        $this->mbPop();
1283
1284
        // store request to requests array
1285
        $this->reqs[] = $req;
1286
        return count($this->reqs) - 1;
1287
    }
1288
1289
    /// connect to searchd, run queries batch, and return an array of result sets
1290
    public function runQueries()
1291
    {
1292
        if (empty($this->reqs)) {
1293
            $this->error = 'no queries defined, issue AddQuery() first';
1294
            return false;
1295
        }
1296
1297
        // mbstring workaround
1298
        $this->mbPush();
1299
1300
        if (!($fp = $this->connect())) {
1301
            $this->mbPop();
1302
            return false;
1303
        }
1304
1305
        // send query, get response
1306
        $nreqs = count($this->reqs);
1307
        $req = join('', $this->reqs);
1308
        $len = 8 + strlen($req);
1309
        $req = pack('nnNNN', SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, $len, 0, $nreqs) . $req; // add header
1310
1311 View Code Duplication
        if (!$this->send($fp, $req, $len + 8) || !($response = $this->getResponse($fp, VER_COMMAND_SEARCH))) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1312
            $this->mbPop();
1313
            return false;
1314
        }
1315
1316
        // query sent ok; we can reset reqs now
1317
        $this->reqs = array();
1318
1319
        // parse and return response
1320
        return $this->parseSearchResponse($response, $nreqs);
1321
    }
1322
1323
    /// parse and return search query (or queries) response
1324
    protected function parseSearchResponse($response, $nreqs)
1325
    {
1326
        $p = 0; // current position
1327
        $max = strlen($response); // max position for checks, to protect against broken responses
1328
1329
        $results = array();
1330
        for ($ires = 0; $ires < $nreqs && $p < $max; $ires++) {
1331
            $results[] = array();
1332
            $result =& $results[$ires];
1333
1334
            $result['error'] = '';
1335
            $result['warning'] = '';
1336
1337
            // extract status
1338
            list(, $status) = unpack('N*', substr($response, $p, 4));
1339
            $p += 4;
1340
            $result['status'] = $status;
1341
            if ($status != SEARCHD_OK) {
1342
                list(, $len) = unpack('N*', substr($response, $p, 4));
1343
                $p += 4;
1344
                $message = substr($response, $p, $len);
1345
                $p += $len;
1346
1347
                if ($status == SEARCHD_WARNING) {
1348
                    $result['warning'] = $message;
1349
                } else {
1350
                    $result['error'] = $message;
1351
                    continue;
1352
                }
1353
            }
1354
1355
            // read schema
1356
            $fields = array();
1357
            $attrs = array();
1358
1359
            list(, $nfields) = unpack('N*', substr($response, $p, 4));
1360
            $p += 4;
1361
            while ($nfields --> 0 && $p < $max) {
1362
                list(, $len) = unpack('N*', substr($response, $p, 4));
1363
                $p += 4;
1364
                $fields[] = substr($response, $p, $len);
1365
                $p += $len;
1366
            }
1367
            $result['fields'] = $fields;
1368
1369
            list(, $nattrs) = unpack('N*', substr($response, $p, 4));
1370
            $p += 4;
1371
            while ($nattrs --> 0 && $p < $max) {
1372
                list(, $len) = unpack('N*', substr($response, $p, 4));
1373
                $p += 4;
1374
                $attr = substr($response, $p, $len);
1375
                $p += $len;
1376
                list(, $type) = unpack('N*', substr($response, $p, 4));
1377
                $p += 4;
1378
                $attrs[$attr] = $type;
1379
            }
1380
            $result['attrs'] = $attrs;
1381
1382
            // read match count
1383
            list(, $count) = unpack('N*', substr($response, $p, 4));
1384
            $p += 4;
1385
            list(, $id64) = unpack('N*', substr($response, $p, 4));
1386
            $p += 4;
1387
1388
            // read matches
1389
            $idx = -1;
1390
            while ($count --> 0 && $p < $max) {
1391
                // index into result array
1392
                $idx++;
1393
1394
                // parse document id and weight
1395
                if ($id64) {
1396
                    $doc = sphUnpackU64(substr($response, $p, 8));
1397
                    $p += 8;
1398
                    list(,$weight) = unpack('N*', substr($response, $p, 4));
1399
                    $p += 4;
1400
                } else {
1401
                    list($doc, $weight) = array_values(unpack('N*N*', substr($response, $p, 8)));
1402
                    $p += 8;
1403
                    $doc = sphFixUint($doc);
1404
                }
1405
                $weight = sprintf('%u', $weight);
1406
1407
                // create match entry
1408 View Code Duplication
                if ($this->arrayresult) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1409
                    $result['matches'][$idx] = array('id' => $doc, 'weight' => $weight);
1410
                } else {
1411
                    $result['matches'][$doc]['weight'] = $weight;
1412
                }
1413
1414
                // parse and create attributes
1415
                $attrvals = array();
1416
                foreach ($attrs as $attr => $type) {
1417
                    // handle 64bit ints
1418
                    if ($type == SPH_ATTR_BIGINT) {
1419
                        $attrvals[$attr] = sphUnpackI64(substr($response, $p, 8));
1420
                        $p += 8;
1421
                        continue;
1422
                    }
1423
1424
                    // handle floats
1425
                    if ($type == SPH_ATTR_FLOAT) {
1426
                        list(, $uval) = unpack('N*', substr($response, $p, 4));
1427
                        $p += 4;
1428
                        list(, $fval) = unpack('f*', pack('L', $uval));
1429
                        $attrvals[$attr] = $fval;
1430
                        continue;
1431
                    }
1432
1433
                    // handle everything else as unsigned ints
1434
                    list(, $val) = unpack('N*', substr($response, $p, 4));
1435
                    $p += 4;
1436
                    if ($type == SPH_ATTR_MULTI) {
1437
                        $attrvals[$attr] = array();
1438
                        $nvalues = $val;
1439 View Code Duplication
                        while ($nvalues --> 0 && $p < $max) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1440
                            list(, $val) = unpack('N*', substr($response, $p, 4));
1441
                            $p += 4;
1442
                            $attrvals[$attr][] = sphFixUint($val);
1443
                        }
1444
                    } elseif ($type == SPH_ATTR_MULTI64) {
1445
                        $attrvals[$attr] = array();
1446
                        $nvalues = $val;
1447 View Code Duplication
                        while ($nvalues > 0 && $p < $max) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1448
                            $attrvals[$attr][] = sphUnpackI64(substr($response, $p, 8));
1449
                            $p += 8;
1450
                            $nvalues -= 2;
1451
                        }
1452
                    } elseif ($type == SPH_ATTR_STRING) {
1453
                        $attrvals[$attr] = substr($response, $p, $val);
1454
                        $p += $val;
1455
                    } elseif ($type == SPH_ATTR_FACTORS) {
1456
                        $attrvals[$attr] = substr($response, $p, $val - 4);
1457
                        $p += $val-4;
1458
                    } else {
1459
                        $attrvals[$attr] = sphFixUint($val);
1460
                    }
1461
                }
1462
1463 View Code Duplication
                if ($this->arrayresult) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1464
                    $result['matches'][$idx]['attrs'] = $attrvals;
1465
                } else {
1466
                    $result['matches'][$doc]['attrs'] = $attrvals;
1467
                }
1468
            }
1469
1470
            list($total, $total_found, $msecs, $words) = array_values(unpack('N*N*N*N*', substr($response, $p, 16)));
1471
            $result['total'] = sprintf('%u', $total);
1472
            $result['total_found'] = sprintf('%u', $total_found);
1473
            $result['time'] = sprintf('%.3f', $msecs / 1000);
1474
            $p += 16;
1475
1476
            while ($words --> 0 && $p < $max) {
1477
                list(, $len) = unpack('N*', substr($response, $p, 4));
1478
                $p += 4;
1479
                $word = substr($response, $p, $len);
1480
                $p += $len;
1481
                list($docs, $hits) = array_values(unpack('N*N*', substr($response, $p, 8)));
1482
                $p += 8;
1483
                $result['words'][$word] = array (
1484
                    'docs' => sprintf('%u', $docs),
1485
                    'hits' => sprintf('%u', $hits)
1486
                );
1487
            }
1488
        }
1489
1490
        $this->mbPop();
1491
        return $results;
1492
    }
1493
1494
    /////////////////////////////////////////////////////////////////////////////
1495
    // excerpts generation
1496
    /////////////////////////////////////////////////////////////////////////////
1497
1498
    /// connect to searchd server, and generate exceprts (snippets)
1499
    /// of given documents for given query. returns false on failure,
1500
    /// an array of snippets on success
1501
    public function buildExcerpts($docs, $index, $words, $opts = array())
1502
    {
1503
        assert(is_array($docs));
1504
        assert(is_string($index));
1505
        assert(is_string($words));
1506
        assert(is_array($opts));
1507
1508
        $this->mbPush();
1509
1510
        if (!($fp = $this->connect())) {
1511
            $this->mbPop();
1512
            return false;
1513
        }
1514
1515
        /////////////////
1516
        // fixup options
1517
        /////////////////
1518
1519
        if (!isset($opts['before_match'])) {
1520
            $opts['before_match'] = '<b>';
1521
        }
1522
        if (!isset($opts['after_match'])) {
1523
            $opts['after_match'] = '</b>';
1524
        }
1525
        if (!isset($opts['chunk_separator'])) {
1526
            $opts['chunk_separator'] = ' ... ';
1527
        }
1528
        if (!isset($opts['limit'])) {
1529
            $opts['limit'] = 256;
1530
        }
1531
        if (!isset($opts['limit_passages'])) {
1532
            $opts['limit_passages'] = 0;
1533
        }
1534
        if (!isset($opts['limit_words'])) {
1535
            $opts['limit_words'] = 0;
1536
        }
1537
        if (!isset($opts['around'])) {
1538
            $opts['around'] = 5;
1539
        }
1540
        if (!isset($opts['exact_phrase'])) {
1541
            $opts['exact_phrase'] = false;
1542
        }
1543
        if (!isset($opts['single_passage'])) {
1544
            $opts['single_passage'] = false;
1545
        }
1546
        if (!isset($opts['use_boundaries'])) {
1547
            $opts['use_boundaries'] = false;
1548
        }
1549
        if (!isset($opts['weight_order'])) {
1550
            $opts['weight_order'] = false;
1551
        }
1552
        if (!isset($opts['query_mode'])) {
1553
            $opts['query_mode'] = false;
1554
        }
1555
        if (!isset($opts['force_all_words'])) {
1556
            $opts['force_all_words'] = false;
1557
        }
1558
        if (!isset($opts['start_passage_id'])) {
1559
            $opts['start_passage_id'] = 1;
1560
        }
1561
        if (!isset($opts['load_files'])) {
1562
            $opts['load_files'] = false;
1563
        }
1564
        if (!isset($opts['html_strip_mode'])) {
1565
            $opts['html_strip_mode'] = 'index';
1566
        }
1567
        if (!isset($opts['allow_empty'])) {
1568
            $opts['allow_empty'] = false;
1569
        }
1570
        if (!isset($opts['passage_boundary'])) {
1571
            $opts['passage_boundary'] = 'none';
1572
        }
1573
        if (!isset($opts['emit_zones'])) {
1574
            $opts['emit_zones'] = false;
1575
        }
1576
        if (!isset($opts['load_files_scattered'])) {
1577
            $opts['load_files_scattered'] = false;
1578
        }
1579
1580
1581
        /////////////////
1582
        // build request
1583
        /////////////////
1584
1585
        // v.1.2 req
1586
        $flags = 1; // remove spaces
1587
        if ($opts['exact_phrase']) {
1588
            $flags |= 2;
1589
        }
1590
        if ($opts['single_passage']) {
1591
            $flags |= 4;
1592
        }
1593
        if ($opts['use_boundaries']) {
1594
            $flags |= 8;
1595
        }
1596
        if ($opts['weight_order']) {
1597
            $flags |= 16;
1598
        }
1599
        if ($opts['query_mode']) {
1600
            $flags |= 32;
1601
        }
1602
        if ($opts['force_all_words']) {
1603
            $flags |= 64;
1604
        }
1605
        if ($opts['load_files']) {
1606
            $flags |= 128;
1607
        }
1608
        if ($opts['allow_empty']) {
1609
            $flags |= 256;
1610
        }
1611
        if ($opts['emit_zones']) {
1612
            $flags |= 512;
1613
        }
1614
        if ($opts['load_files_scattered']) {
1615
            $flags |= 1024;
1616
        }
1617
        $req = pack('NN', 0, $flags); // mode=0, flags=$flags
1618
        $req .= pack('N', strlen($index)) . $index; // req index
1619
        $req .= pack('N', strlen($words)) . $words; // req words
1620
1621
        // options
1622
        $req .= pack('N', strlen($opts['before_match'])) . $opts['before_match'];
1623
        $req .= pack('N', strlen($opts['after_match'])) . $opts['after_match'];
1624
        $req .= pack('N', strlen($opts['chunk_separator'])) . $opts['chunk_separator'];
1625
        $req .= pack('NN', (int)$opts['limit'], (int)$opts['around']);
1626
        $req .= pack('NNN', (int)$opts['limit_passages'], (int)$opts['limit_words'], (int)$opts['start_passage_id']); // v.1.2
1627
        $req .= pack('N', strlen($opts['html_strip_mode'])) . $opts['html_strip_mode'];
1628
        $req .= pack('N', strlen($opts['passage_boundary'])) . $opts['passage_boundary'];
1629
1630
        // documents
1631
        $req .= pack('N', count($docs));
1632
        foreach ($docs as $doc) {
1633
            assert(is_string($doc));
1634
            $req .= pack('N', strlen($doc)) . $doc;
1635
        }
1636
1637
        ////////////////////////////
1638
        // send query, get response
1639
        ////////////////////////////
1640
1641
        $len = strlen($req);
1642
        $req = pack('nnN', SEARCHD_COMMAND_EXCERPT, VER_COMMAND_EXCERPT, $len) . $req; // add header
1643 View Code Duplication
        if (!$this->send($fp, $req, $len + 8) || !($response = $this->getResponse($fp, VER_COMMAND_EXCERPT))) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1644
            $this->mbPop();
1645
            return false;
1646
        }
1647
1648
        //////////////////
1649
        // parse response
1650
        //////////////////
1651
1652
        $pos = 0;
1653
        $res = array();
1654
        $rlen = strlen($response);
1655
        for ($i = 0; $i < count($docs); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
1656
            list(, $len) = unpack('N*', substr($response, $pos, 4));
1657
            $pos += 4;
1658
1659
            if ($pos + $len > $rlen) {
1660
                $this->error = 'incomplete reply';
1661
                $this->mbPop();
1662
                return false;
1663
            }
1664
            $res[] = $len ? substr($response, $pos, $len) : '';
1665
            $pos += $len;
1666
        }
1667
1668
        $this->mbPop();
1669
        return $res;
1670
    }
1671
1672
1673
    /////////////////////////////////////////////////////////////////////////////
1674
    // keyword generation
1675
    /////////////////////////////////////////////////////////////////////////////
1676
1677
    /// connect to searchd server, and generate keyword list for a given query
1678
    /// returns false on failure,
1679
    /// an array of words on success
1680
    public function buildKeywords($query, $index, $hits)
1681
    {
1682
        assert(is_string($query));
1683
        assert(is_string($index));
1684
        assert(is_bool($hits));
1685
1686
        $this->mbPush();
1687
1688
        if (!($fp = $this->connect())) {
1689
            $this->mbPop();
1690
            return false;
1691
        }
1692
1693
        /////////////////
1694
        // build request
1695
        /////////////////
1696
1697
        // v.1.0 req
1698
        $req  = pack('N', strlen($query)) . $query; // req query
1699
        $req .= pack('N', strlen($index)) . $index; // req index
1700
        $req .= pack('N', (int)$hits);
1701
1702
        ////////////////////////////
1703
        // send query, get response
1704
        ////////////////////////////
1705
1706
        $len = strlen($req);
1707
        $req = pack('nnN', SEARCHD_COMMAND_KEYWORDS, VER_COMMAND_KEYWORDS, $len) . $req; // add header
1708 View Code Duplication
        if (!$this->send($fp, $req, $len + 8) || !($response = $this->getResponse($fp, VER_COMMAND_KEYWORDS))) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1709
            $this->mbPop();
1710
            return false;
1711
        }
1712
1713
        //////////////////
1714
        // parse response
1715
        //////////////////
1716
1717
        $pos = 0;
1718
        $res = array();
1719
        $rlen = strlen($response);
1720
        list(, $nwords) = unpack('N*', substr($response, $pos, 4));
1721
        $pos += 4;
1722
        for ($i = 0; $i < $nwords; $i++) {
1723
            list(, $len) = unpack('N*', substr($response, $pos, 4));
1724
            $pos += 4;
1725
            $tokenized = $len ? substr($response, $pos, $len) : '';
1726
            $pos += $len;
1727
1728
            list(, $len) = unpack('N*', substr($response, $pos, 4));
1729
            $pos += 4;
1730
            $normalized = $len ? substr($response, $pos, $len) : '';
1731
            $pos += $len;
1732
1733
            $res[] = array(
1734
                'tokenized' => $tokenized,
1735
                'normalized' => $normalized
1736
            );
1737
1738
            if ($hits) {
1739
                list($ndocs, $nhits) = array_values(unpack('N*N*', substr($response, $pos, 8)));
1740
                $pos += 8;
1741
                $res[$i]['docs'] = $ndocs;
1742
                $res[$i]['hits'] = $nhits;
1743
            }
1744
1745
            if ($pos > $rlen) {
1746
                $this->error = 'incomplete reply';
1747
                $this->mbPop();
1748
                return false;
1749
            }
1750
        }
1751
1752
        $this->mbPop();
1753
        return $res;
1754
    }
1755
1756
    public function escapeString($string)
1757
    {
1758
        $from = array('\\', '(',')','|','-','!','@','~','"','&', '/', '^', '$', '=', '<');
1759
        $to   = array('\\\\', '\(','\)','\|','\-','\!','\@','\~','\"', '\&', '\/', '\^', '\$', '\=', '\<');
1760
1761
        return str_replace($from, $to, $string);
1762
    }
1763
1764
    /////////////////////////////////////////////////////////////////////////////
1765
    // attribute updates
1766
    /////////////////////////////////////////////////////////////////////////////
1767
1768
    /// batch update given attributes in given rows in given indexes
1769
    /// returns amount of updated documents (0 or more) on success, or -1 on failure
1770
    public function updateAttributes($index, $attrs, $values, $mva = false, $ignorenonexistent = false)
1771
    {
1772
        // verify everything
1773
        assert(is_string($index));
1774
        assert(is_bool($mva));
1775
        assert(is_bool($ignorenonexistent));
1776
1777
        assert(is_array($attrs));
1778
        foreach ($attrs as $attr) {
1779
            assert(is_string($attr));
1780
        }
1781
1782
        assert(is_array($values));
1783
        foreach ($values as $id => $entry) {
1784
            assert(is_numeric($id));
1785
            assert(is_array($entry));
1786
            assert(count($entry) == count($attrs));
1787
            foreach ($entry as $v) {
1788
                if ($mva) {
1789
                    assert(is_array($v));
1790
                    foreach ($v as $vv) {
1791
                        assert(is_int($vv));
1792
                    }
1793
                } else {
1794
                    assert(is_int($v));
1795
                }
1796
            }
1797
        }
1798
1799
        // build request
1800
        $this->mbPush();
1801
        $req = pack('N', strlen($index)) . $index;
1802
1803
        $req .= pack('N', count($attrs));
1804
        $req .= pack('N', $ignorenonexistent ? 1 : 0);
1805
        foreach ($attrs as $attr) {
1806
            $req .= pack('N', strlen($attr)) . $attr;
1807
            $req .= pack('N', $mva ? 1 : 0);
1808
        }
1809
1810
        $req .= pack('N', count($values));
1811
        foreach ($values as $id => $entry) {
1812
            $req .= sphPackU64($id);
1813
            foreach ($entry as $v) {
1814
                $req .= pack('N', $mva ? count($v) : $v);
1815
                if ($mva) {
1816
                    foreach ($v as $vv) {
1817
                        $req .= pack('N', $vv);
1818
                    }
1819
                }
1820
            }
1821
        }
1822
1823
        // connect, send query, get response
1824
        if (!($fp = $this->connect())) {
1825
            $this->mbPop();
1826
            return -1;
1827
        }
1828
1829
        $len = strlen($req);
1830
        $req = pack('nnN', SEARCHD_COMMAND_UPDATE, VER_COMMAND_UPDATE, $len) . $req; // add header
1831
        if (!$this->send($fp, $req, $len + 8)) {
1832
            $this->mbPop();
1833
            return -1;
1834
        }
1835
1836
        if (!($response = $this->getResponse($fp, VER_COMMAND_UPDATE))) {
1837
            $this->mbPop();
1838
            return -1;
1839
        }
1840
1841
        // parse response
1842
        list(, $updated) = unpack('N*', substr($response, 0, 4));
1843
        $this->mbPop();
1844
        return $updated;
1845
    }
1846
1847
    /////////////////////////////////////////////////////////////////////////////
1848
    // persistent connections
1849
    /////////////////////////////////////////////////////////////////////////////
1850
1851
    public function open()
1852
    {
1853
        if ($this->_socket !== false) {
1854
            $this->error = 'already connected';
1855
            return false;
1856
        }
1857
        if (!($fp = $this->connect()))
1858
            return false;
1859
1860
        // command, command version = 0, body length = 4, body = 1
1861
        $req = pack('nnNN', SEARCHD_COMMAND_PERSIST, 0, 4, 1);
1862
        if (!$this->send($fp, $req, 12)) {
1863
            return false;
1864
        }
1865
1866
        $this->_socket = $fp;
1867
        return true;
1868
    }
1869
1870
    public function close()
1871
    {
1872
        if ($this->_socket === false) {
1873
            $this->error = 'not connected';
1874
            return false;
1875
        }
1876
1877
        fclose($this->_socket);
1878
        $this->_socket = false;
1879
1880
        return true;
1881
    }
1882
1883
    //////////////////////////////////////////////////////////////////////////
1884
    // status
1885
    //////////////////////////////////////////////////////////////////////////
1886
1887
    public function status($session = false)
1888
    {
1889
        assert(is_bool($session));
1890
1891
        $this->mbPush();
1892
        if (!($fp = $this->connect())) {
1893
            $this->mbPop();
1894
            return false;
1895
        }
1896
1897
        $req = pack('nnNN', SEARCHD_COMMAND_STATUS, VER_COMMAND_STATUS, 4, $session ? 0 : 1); // len=4, body=1
1898 View Code Duplication
        if (!$this->send($fp, $req, 12) || !($response = $this->getResponse($fp, VER_COMMAND_STATUS))) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1899
            $this->mbPop();
1900
            return false;
1901
        }
1902
1903
        $res = substr($response, 4); // just ignore length, error handling, etc
0 ignored issues
show
Unused Code introduced by
$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 $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1904
        $p = 0;
1905
        list($rows, $cols) = array_values(unpack('N*N*', substr($response, $p, 8)));
1906
        $p += 8;
1907
1908
        $res = array();
1909
        for ($i = 0; $i < $rows; $i++) {
1910
            for ($j = 0; $j < $cols; $j++) {
1911
                list(, $len) = unpack('N*', substr($response, $p, 4));
1912
                $p += 4;
1913
                $res[$i][] = substr($response, $p, $len);
1914
                $p += $len;
1915
            }
1916
        }
1917
1918
        $this->mbPop();
1919
        return $res;
1920
    }
1921
1922
    //////////////////////////////////////////////////////////////////////////
1923
    // flush
1924
    //////////////////////////////////////////////////////////////////////////
1925
1926
    public function flushAttributes()
1927
    {
1928
        $this->mbPush();
1929
        if (!($fp = $this->connect())) {
1930
            $this->mbPop();
1931
            return -1;
1932
        }
1933
1934
        $req = pack('nnN', SEARCHD_COMMAND_FLUSHATTRS, VER_COMMAND_FLUSHATTRS, 0); // len=0
1935 View Code Duplication
        if (!$this->send($fp, $req, 8) || !($response = $this->getResponse($fp, VER_COMMAND_FLUSHATTRS))) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1936
            $this->mbPop();
1937
            return -1;
1938
        }
1939
1940
        $tag = -1;
1941
        if (strlen($response) == 4) {
1942
            list(, $tag) = unpack('N*', $response);
1943
        } else {
1944
            $this->error = 'unexpected response length';
1945
        }
1946
1947
        $this->mbPop();
1948
        return $tag;
1949
    }
1950
}
1951