Completed
Push — master ( 38af16...127ba1 )
by Peter
02:18
created

SphinxClient::setQueryFlag()   C

Complexity

Conditions 15
Paths 11

Size

Total Lines 57
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 240

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 57
ccs 0
cts 49
cp 0
rs 6.5498
cc 15
eloc 47
nc 11
nop 2
crap 240

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 4
    if ($on) {
405 4
        $flag |= 1 << $bit;
406 4
    } else {
407
        $reset = 16777215 ^ (1 << $bit);
408
        $flag = $flag & $reset;
409
    }
410 4
    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 = 'localhost'; ///< searchd host (default is 'localhost')
418
    protected $port = 9312; ///< searchd port (default is 9312)
419
    protected $offset = 0; ///< how many records to seek from result-set start (default is 0)
420
    protected $limit = 20; ///< how many records to return from result-set starting at offset (default is 20)
421
    protected $mode = SPH_MATCH_EXTENDED2; ///< query matching mode (default is SPH_MATCH_EXTENDED2)
422
    protected $weights = array(); ///< per-field weights (default is 1 for all fields)
423
    protected $sort = SPH_SORT_RELEVANCE; ///< match sorting mode (default is SPH_SORT_RELEVANCE)
424
    protected $sort_by = ''; ///< attribute to sort by (defualt is '')
425
    protected $min_id = 0; ///< min ID to match (default is 0, which means no limit)
426
    protected $max_id = 0; ///< max ID to match (default is 0, which means no limit)
427
    protected $filters = array(); ///< search filters
428
    protected $group_by = ''; ///< group-by attribute name
429
    protected $group_func = SPH_GROUPBY_DAY; ///< group-by function (to pre-process group-by attribute value with)
430
    protected $group_sort = '@group desc'; ///< group-by sorting clause (to sort groups in result set with)
431
    protected $group_distinct = ''; ///< group-by count-distinct attribute
432
    protected $max_matches = 1000; ///< max matches to retrieve
433
    protected $cutoff = 0; ///< cutoff to stop searching at (default is 0)
434
    protected $retry_count = 0; ///< distributed retries count
435
    protected $retry_delay = 0; ///< distributed retries delay
436
    protected $anchor = array(); ///< geographical anchor point
437
    protected $index_weights = array(); ///< per-index weights
438
    protected $ranker = SPH_RANK_PROXIMITY_BM25; ///< ranking mode (default is SPH_RANK_PROXIMITY_BM25)
439
    protected $rank_expr = ''; ///< ranking mode expression (for SPH_RANK_EXPR)
440
    protected $max_query_time = 0; ///< max query time, milliseconds (default is 0, do not limit)
441
    protected $field_weights = array(); ///< per-field-name weights
442
    protected $overrides = array(); ///< per-query attribute values overrides
443
    protected $select = '*'; ///< select-list (attributes or expressions, with optional aliases)
444
    protected $query_flags = 0; ///< per-query various flags
445
    protected $predicted_time = 0; ///< per-query max_predicted_time
446
    protected $outer_order_by = ''; ///< outer match sort by
447
    protected $outer_offset = 0; ///< outer offset
448
    protected $outer_limit = 0; ///< outer limit
449
    protected $has_outer = false;
450
451
    protected $error = ''; ///< last error message
452
    protected $warning = ''; ///< last warning message
453
    protected $conn_error = false; ///< connection error vs remote error flag
454
455
    protected $reqs = array(); ///< requests array for multi-query
456
    protected $mbenc = ''; ///< stored mbstring encoding
457
    protected $array_result = false; ///< whether $result['matches'] should be a hash or an array
458
    protected $timeout = 0; ///< connect timeout
459
460
    protected $path = false;
461
    protected $socket = false;
462
463
    /////////////////////////////////////////////////////////////////////////////
464
    // common stuff
465
    /////////////////////////////////////////////////////////////////////////////
466
467 4
    public function __construct()
468
    {
469 4
        $this->query_flags = sphSetBit(0, 6, true); // default idf=tfidf_normalized
470 4
    }
471
472
    public function __destruct()
473
    {
474
        if ($this->socket !== false) {
475
            fclose($this->socket);
476
        }
477
    }
478
479
    /// get last error message (string)
480 1
    public function getLastError()
481
    {
482 1
        return $this->error;
483
    }
484
485
    /// get last warning message (string)
486 1
    public function getLastWarning()
487
    {
488 1
        return $this->warning;
489
    }
490
491
    /// get last error flag (to tell network connection errors from searchd errors or broken responses)
492 1
    public function isConnectError()
493
    {
494 1
        return $this->conn_error;
495
    }
496
497
    /// set searchd host name (string) and port (integer)
498
    public function setServer($host, $port = 0)
499
    {
500
        assert(is_string($host));
501
        if ($host[0] == '/') {
502
            $this->path = 'unix://' . $host;
0 ignored issues
show
Documentation Bug introduced by
The property $path was declared of type boolean, but 'unix://' . $host is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
503
            return;
504
        }
505
        if (substr($host, 0, 7) == 'unix://') {
506
            $this->path = $host;
0 ignored issues
show
Documentation Bug introduced by
The property $path was declared of type boolean, but $host is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
507
            return;
508
        }
509
510
        $this->host = $host;
511
        $port = intval($port);
512
        assert(0 <= $port && $port < 65536);
513
        $this->port = $port == 0 ? 9312 : $port;
514
        $this->path = '';
0 ignored issues
show
Documentation Bug introduced by
The property $path was declared of type boolean, but '' is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
515
    }
516
517
    /// set server connection timeout (0 to remove)
518
    public function setConnectTimeout($timeout)
519
    {
520
        assert(is_numeric($timeout));
521
        $this->timeout = $timeout;
0 ignored issues
show
Documentation Bug introduced by
It seems like $timeout can also be of type double or string. However, the property $timeout 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 $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account 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.

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;
}
Loading history...
522
    }
523
524
525
    protected function send($handle, $data, $length)
526
    {
527
        if (feof($handle) || fwrite($handle, $data, $length) !== $length) {
528
            $this->error = 'connection unexpectedly closed (timed out?)';
529
            $this->conn_error = true;
530
            return false;
531
        }
532
        return true;
533
    }
534
535
    /////////////////////////////////////////////////////////////////////////////
536
537
    /// enter mbstring workaround mode
538
    protected function mbPush()
539
    {
540
        $this->mbenc = '';
541
        if (ini_get('mbstring.func_overload') & 2) {
542
            $this->mbenc = mb_internal_encoding();
543
            mb_internal_encoding('latin1');
544
        }
545
    }
546
547
    /// leave mbstring workaround mode
548
    protected function mbPop()
549
    {
550
        if ($this->mbenc) {
551
            mb_internal_encoding($this->mbenc);
552
        }
553
    }
554
555
    /// connect to searchd server
556
    protected function connect()
557
    {
558
        if ($this->socket !== false) {
559
            // we are in persistent connection mode, so we have a socket
560
            // however, need to check whether it's still alive
561
            if (!@feof($this->socket)) {
562
                return $this->socket;
563
            }
564
565
            // force reopen
566
            $this->socket = false;
567
        }
568
569
        $errno = 0;
570
        $errstr = '';
571
        $this->conn_error = false;
572
573
        if ($this->path) {
574
            $host = $this->path;
575
            $port = 0;
576
        } else {
577
            $host = $this->host;
578
            $port = $this->port;
579
        }
580
581
        if ($this->timeout <= 0) {
582
            $fp = @fsockopen($host, $port, $errno, $errstr);
583
        } else {
584
            $fp = @fsockopen($host, $port, $errno, $errstr, $this->timeout);
585
        }
586
587
        if (!$fp) {
588
            if ($this->path) {
589
                $location = $this->path;
590
            } else {
591
                $location = "{$this->host}:{$this->port}";
592
            }
593
594
            $errstr = trim($errstr);
595
            $this->error = "connection to $location failed (errno=$errno, msg=$errstr)";
596
            $this->conn_error = true;
597
            return false;
598
        }
599
600
        // send my version
601
        // this is a subtle part. we must do it before (!) reading back from searchd.
602
        // because otherwise under some conditions (reported on FreeBSD for instance)
603
        // TCP stack could throttle write-write-read pattern because of Nagle.
604
        if (!$this->send($fp, pack('N', 1), 4)) {
605
            fclose($fp);
606
            $this->error = 'failed to send client protocol version';
607
            return false;
608
        }
609
610
        // check version
611
        list(, $v) = unpack('N*', fread($fp, 4));
612
        $v = (int)$v;
613
        if ($v < 1) {
614
            fclose($fp);
615
            $this->error = "expected searchd protocol version 1+, got version '$v'";
616
            return false;
617
        }
618
619
        return $fp;
620
    }
621
622
    /// get and check response packet from searchd server
623
    protected function getResponse($fp, $client_ver)
624
    {
625
        $response = '';
626
        $len = 0;
627
628
        $header = fread($fp, 8);
629
        if (strlen($header) == 8) {
630
            list($status, $ver, $len) = array_values(unpack('n2a/Nb', $header));
631
            $left = $len;
632
            while ($left > 0 && !feof($fp)) {
633
                $chunk = fread($fp, min(8192, $left));
634
                if ($chunk) {
635
                    $response .= $chunk;
636
                    $left -= strlen($chunk);
637
                }
638
            }
639
        }
640
        if ($this->socket === false) {
641
            fclose($fp);
642
        }
643
644
        // check response
645
        $read = strlen($response);
646
        if (!$response || $read != $len) {
647
            $this->error = $len
648
                ? "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...
649
                : 'received zero-sized searchd response';
650
            return false;
651
        }
652
653
        // check status
654
        if ($status == SEARCHD_WARNING) {
655
            list(, $wlen) = unpack('N*', substr($response, 0, 4));
656
            $this->warning = substr($response, 4, $wlen);
657
            return substr($response, 4 + $wlen);
658
        }
659
        if ($status == SEARCHD_ERROR) {
660
            $this->error = 'searchd error: ' . substr($response, 4);
661
            return false;
662
        }
663
        if ($status == SEARCHD_RETRY) {
664
            $this->error = 'temporary searchd error: ' . substr($response, 4);
665
            return false;
666
        }
667
        if ($status != SEARCHD_OK) {
668
            $this->error = "unknown status code '$status'";
669
            return false;
670
        }
671
672
        // check version
673
        if ($ver < $client_ver) {
674
            $this->warning = sprintf(
675
                'searchd command v.%d.%d older than client\'s v.%d.%d, some options might not work',
676
                $ver >> 8,
677
                $ver & 0xff,
678
                $client_ver >> 8,
679
                $client_ver & 0xff
680
            );
681
        }
682
683
        return $response;
684
    }
685
686
    /////////////////////////////////////////////////////////////////////////////
687
    // searching
688
    /////////////////////////////////////////////////////////////////////////////
689
690
    /// set offset and count into result set,
691
    /// and optionally set max-matches and cutoff limits
692
    public function setLimits($offset, $limit, $max = 0, $cutoff = 0)
693
    {
694
        assert(is_int($offset));
695
        assert(is_int($limit));
696
        assert($offset >= 0);
697
        assert($limit > 0);
698
        assert($max >= 0);
699
        $this->offset = $offset;
700
        $this->limit = $limit;
701
        if ($max > 0) {
702
            $this->max_matches = $max;
703
        }
704
        if ($cutoff > 0) {
705
            $this->cutoff = $cutoff;
706
        }
707
    }
708
709
    /// set maximum query time, in milliseconds, per-index
710
    /// integer, 0 means 'do not limit'
711
    public function setMaxQueryTime($max)
712
    {
713
        assert(is_int($max));
714
        assert($max >= 0);
715
        $this->max_query_time = $max;
716
    }
717
718
    /// set matching mode
719
    public function setMatchMode($mode)
720
    {
721
        trigger_error(
722
            'DEPRECATED: Do not call this method or, even better, use SphinxQL instead of an API',
723
            E_USER_DEPRECATED
724
        );
725
        assert(
726
            $mode == SPH_MATCH_ALL ||
727
            $mode == SPH_MATCH_ANY ||
728
            $mode == SPH_MATCH_PHRASE ||
729
            $mode == SPH_MATCH_BOOLEAN ||
730
            $mode == SPH_MATCH_EXTENDED ||
731
            $mode == SPH_MATCH_FULLSCAN ||
732
            $mode == SPH_MATCH_EXTENDED2
733
        );
734
        $this->mode = $mode;
735
    }
736
737
    /// set ranking mode
738
    public function setRankingMode($ranker, $rankexpr='')
739
    {
740
        assert($ranker === 0 || $ranker >= 1 && $ranker < SPH_RANK_TOTAL);
741
        assert(is_string($rankexpr));
742
        $this->ranker = $ranker;
743
        $this->rank_expr = $rankexpr;
744
    }
745
746
    /// set matches sorting mode
747
    public function setSortMode($mode, $sortby = '')
748
    {
749
        assert (
750
            $mode == SPH_SORT_RELEVANCE ||
751
            $mode == SPH_SORT_ATTR_DESC ||
752
            $mode == SPH_SORT_ATTR_ASC ||
753
            $mode == SPH_SORT_TIME_SEGMENTS ||
754
            $mode == SPH_SORT_EXTENDED ||
755
            $mode == SPH_SORT_EXPR
756
        );
757
        assert(is_string($sortby));
758
        assert($mode == SPH_SORT_RELEVANCE || strlen($sortby) > 0);
759
760
        $this->sort = $mode;
761
        $this->sort_by = $sortby;
762
    }
763
764
    /**
765
     * Bind per-field weights by order
766
     *
767
     * @deprecated use setFieldWeights() instead
768
     */
769 1
    public function setWeights()
770
    {
771 1
        throw new \RuntimeException('This method is now deprecated; please use setFieldWeights instead');
772
    }
773
774
    /// bind per-field weights by name
775 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...
776
    {
777
        assert(is_array($weights));
778
        foreach ($weights as $name => $weight) {
779
            assert(is_string($name));
780
            assert(is_int($weight));
781
        }
782
        $this->field_weights = $weights;
783
    }
784
785
    /// bind per-index weights by name
786 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...
787
    {
788
        assert(is_array($weights));
789
        foreach ($weights as $index => $weight) {
790
            assert(is_string($index));
791
            assert(is_int($weight));
792
        }
793
        $this->index_weights = $weights;
794
    }
795
796
    /// set IDs range to match
797
    /// only match records if document ID is beetwen $min and $max (inclusive)
798
    public function setIDRange($min, $max)
799
    {
800
        assert(is_numeric($min));
801
        assert(is_numeric($max));
802
        assert($min <= $max);
803
        $this->min_id = $min;
0 ignored issues
show
Documentation Bug introduced by
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 $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account 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.

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;
}
Loading history...
804
        $this->max_id = $max;
0 ignored issues
show
Documentation Bug introduced by
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 $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account 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.

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;
}
Loading history...
805
    }
806
807
    /// set values set filter
808
    /// only match records where $attribute value is in given set
809
    public function setFilter($attribute, $values, $exclude = false)
810
    {
811
        assert(is_string($attribute));
812
        assert(is_array($values));
813
        assert(count($values));
814
815
        if (is_array($values) && count($values)) {
816
            foreach ($values as $value) {
817
                assert(is_numeric($value));
818
            }
819
820
            $this->filters[] = array(
821
                'type' => SPH_FILTER_VALUES,
822
                'attr' => $attribute,
823
                'exclude' => $exclude,
824
                'values' => $values
825
            );
826
        }
827
    }
828
829
    /// set string filter
830
    /// only match records where $attribute value is equal
831
    public function setFilterString($attribute, $value, $exclude = false)
832
    {
833
        assert(is_string($attribute));
834
        assert(is_string($value));
835
        $this->filters[] = array(
836
            'type' => SPH_FILTER_STRING,
837
            'attr' => $attribute,
838
            'exclude' => $exclude,
839
            'value' => $value
840
        );
841
    }    
842
843
    /// set range filter
844
    /// only match records if $attribute value is beetwen $min and $max (inclusive)
845 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...
846
    {
847
        assert(is_string($attribute));
848
        assert(is_numeric($min));
849
        assert(is_numeric($max));
850
        assert($min <= $max);
851
852
        $this->filters[] = array(
853
            'type' => SPH_FILTER_RANGE,
854
            'attr' => $attribute,
855
            'exclude' => $exclude,
856
            'min' => $min,
857
            'max' => $max
858
        );
859
    }
860
861
    /// set float range filter
862
    /// only match records if $attribute value is beetwen $min and $max (inclusive)
863 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...
864
    {
865
        assert(is_string($attribute));
866
        assert(is_float($min));
867
        assert(is_float($max));
868
        assert($min <= $max);
869
870
        $this->filters[] = array(
871
            'type' => SPH_FILTER_FLOATRANGE,
872
            'attr' => $attribute,
873
            'exclude' => $exclude,
874
            'min' => $min,
875
            'max' => $max
876
        );
877
    }
878
879
    /// setup anchor point for geosphere distance calculations
880
    /// required to use @geodist in filters and sorting
881
    /// latitude and longitude must be in radians
882
    public function setGeoAnchor($attrlat, $attrlong, $lat, $long)
883
    {
884
        assert(is_string($attrlat));
885
        assert(is_string($attrlong));
886
        assert(is_float($lat));
887
        assert(is_float($long));
888
889
        $this->anchor = array(
890
            'attrlat' => $attrlat,
891
            'attrlong' => $attrlong,
892
            'lat' => $lat,
893
            'long' => $long
894
        );
895
    }
896
897
    /// set grouping attribute and function
898
    public function setGroupBy($attribute, $func, $groupsort = '@group desc')
899
    {
900
        assert(is_string($attribute));
901
        assert(is_string($groupsort));
902
        assert(
903
            $func == SPH_GROUPBY_DAY ||
904
            $func == SPH_GROUPBY_WEEK ||
905
            $func == SPH_GROUPBY_MONTH ||
906
            $func == SPH_GROUPBY_YEAR ||
907
            $func == SPH_GROUPBY_ATTR ||
908
            $func == SPH_GROUPBY_ATTRPAIR
909
        );
910
911
        $this->group_by = $attribute;
912
        $this->group_func = $func;
913
        $this->group_sort = $groupsort;
914
    }
915
916
    /// set count-distinct attribute for group-by queries
917
    public function setGroupDistinct($attribute)
918
    {
919
        assert(is_string($attribute));
920
        $this->group_distinct = $attribute;
921
    }
922
923
    /// set distributed retries count and delay
924
    public function setRetries($count, $delay = 0)
925
    {
926
        assert(is_int($count) && $count >= 0);
927
        assert(is_int($delay) && $delay >= 0);
928
        $this->retry_count = $count;
929
        $this->retry_delay = $delay;
930
    }
931
932
    /// set result set format (hash or array; hash by default)
933
    /// PHP specific; needed for group-by-MVA result sets that may contain duplicate IDs
934
    public function setArrayResult($arrayresult)
935
    {
936
        assert(is_bool($arrayresult));
937
        $this->array_result = $arrayresult;
938
    }
939
940
    /// set attribute values override
941
    /// there can be only one override per attribute
942
    /// $values must be a hash that maps document IDs to attribute values
943
    public function setOverride($attrname, $attrtype, $values)
944
    {
945
        trigger_error(
946
            'DEPRECATED: Do not call this method. Use SphinxQL REMAP() function instead.',
947
            E_USER_DEPRECATED
948
        );
949
        assert(is_string($attrname));
950
        assert(in_array($attrtype, array(
951
            SPH_ATTR_INTEGER,
952
            SPH_ATTR_TIMESTAMP,
953
            SPH_ATTR_BOOL,
954
            SPH_ATTR_FLOAT,
955
            SPH_ATTR_BIGINT
956
        )));
957
        assert(is_array($values));
958
959
        $this->overrides[$attrname] = array(
960
            'attr' => $attrname,
961
            'type' => $attrtype,
962
            'values' => $values
963
        );
964
    }
965
966
    /// set select-list (attributes or expressions), SQL-like syntax
967
    public function setSelect($select)
968
    {
969
        assert(is_string($select));
970
        $this->select = $select;
971
    }
972
973
    public function setQueryFlag($flag_name, $flag_value)
974
    {
975
        $known_names = array(
976
            'reverse_scan',
977
            'sort_method',
978
            'max_predicted_time',
979
            'boolean_simplify',
980
            'idf',
981
            'global_idf',
982
            'low_priority'
983
        );
984
        $flags = array (
985
            'reverse_scan' => array(0, 1),
986
            'sort_method' => array('pq', 'kbuffer'),
987
            'max_predicted_time' => array(0),
988
            'boolean_simplify' => array(true, false),
989
            'idf' => array ('normalized', 'plain', 'tfidf_normalized', 'tfidf_unnormalized'),
990
            'global_idf' => array(true, false),
991
            'low_priority' => array(true, false)
992
        );
993
994
        assert(isset($flag_name, $known_names));
995
        assert(
996
            in_array($flag_value, $flags[$flag_name], true) ||
997
            ($flag_name == 'max_predicted_time' && is_int($flag_value) && $flag_value >= 0)
998
        );
999
1000
        switch ($flag_name) {
1001
            case 'reverse_scan':
1002
                $this->query_flags = sphSetBit($this->query_flags, 0, $flag_value == 1);
1003
                break;
1004
            case 'sort_method':
1005
                $this->query_flags = sphSetBit($this->query_flags, 1, $flag_value == 'kbuffer');
1006
                break;
1007
            case 'max_predicted_time':
1008
                $this->query_flags = sphSetBit($this->query_flags, 2, $flag_value > 0);
1009
                $this->predicted_time = (int)$flag_value;
1010
                break;
1011
            case 'boolean_simplify':
1012
                $this->query_flags = sphSetBit($this->query_flags, 3, $flag_value);
1013
                break;
1014
            case 'idf':
1015
                if ($flag_value == 'normalized' || $flag_value == 'plain') {
1016
                    $this->query_flags = sphSetBit($this->query_flags, 4, $flag_value == 'plain');
1017
                }
1018
                if ($flag_value == 'tfidf_normalized' || $flag_value == 'tfidf_unnormalized') {
1019
                    $this->query_flags = sphSetBit($this->query_flags, 6, $flag_value == 'tfidf_normalized');
1020
                }
1021
                break;
1022
            case 'global_idf':
1023
                $this->query_flags = sphSetBit($this->query_flags, 5, $flag_value);
1024
                break;
1025
            case 'low_priority':
1026
                $this->query_flags = sphSetBit($this->query_flags, 8, $flag_value);
1027
                break;
1028
        }
1029
    }
1030
1031
    /// set outer order by parameters
1032
    public function setOuterSelect($orderby, $offset, $limit)
1033
    {
1034
        assert(is_string($orderby));
1035
        assert(is_int($offset));
1036
        assert(is_int($limit));
1037
        assert($offset >= 0);
1038
        assert($limit > 0);
1039
1040
        $this->outer_order_by = $orderby;
1041
        $this->outer_offset = $offset;
1042
        $this->outer_limit = $limit;
1043
        $this->has_outer = true;
1044
    }
1045
1046
1047
    //////////////////////////////////////////////////////////////////////////////
1048
1049
    /// clear all filters (for multi-queries)
1050
    public function resetFilters()
1051
    {
1052
        $this->filters = array();
1053
        $this->anchor = array();
1054
    }
1055
1056
    /// clear groupby settings (for multi-queries)
1057
    public function resetGroupBy()
1058
    {
1059
        $this->group_by = '';
1060
        $this->group_func = SPH_GROUPBY_DAY;
1061
        $this->group_sort = '@group desc';
1062
        $this->group_distinct = '';
1063
    }
1064
1065
    /// clear all attribute value overrides (for multi-queries)
1066
    public function resetOverrides()
1067
    {
1068
        $this->overrides = array();
1069
    }
1070
1071
    public function resetQueryFlag()
1072
    {
1073
        $this->query_flags = sphSetBit(0, 6, true); // default idf=tfidf_normalized
1074
        $this->predicted_time = 0;
1075
    }
1076
1077
    public function resetOuterSelect()
1078
    {
1079
        $this->outer_order_by = '';
1080
        $this->outer_offset = 0;
1081
        $this->outer_limit = 0;
1082
        $this->has_outer = false;
1083
    }
1084
1085
    //////////////////////////////////////////////////////////////////////////////
1086
1087
    /// connect to searchd server, run given search query through given indexes,
1088
    /// and return the search results
1089
    public function query($query, $index = '*', $comment = '')
1090
    {
1091
        assert(empty($this->reqs));
1092
1093
        $this->addQuery($query, $index, $comment);
1094
        $results = $this->runQueries();
1095
        $this->reqs = array(); // just in case it failed too early
1096
1097
        if (!is_array($results)) {
1098
            return false; // probably network error; error message should be already filled
1099
        }
1100
1101
        $this->error = $results[0]['error'];
1102
        $this->warning = $results[0]['warning'];
1103
        if ($results[0]['status'] == SEARCHD_ERROR) {
1104
            return false;
1105
        } else {
1106
            return $results[0];
1107
        }
1108
    }
1109
1110
    /// helper to pack floats in network byte order
1111
    protected function packFloat($f)
1112
    {
1113
        $t1 = pack('f', $f); // machine order
1114
        list(, $t2) = unpack('L*', $t1); // int in machine order
1115
        return pack('N', $t2);
1116
    }
1117
1118
    /// add query to multi-query batch
1119
    /// returns index into results array from RunQueries() call
1120
    public function addQuery($query, $index = '*', $comment = '')
1121
    {
1122
        // mbstring workaround
1123
        $this->mbPush();
1124
1125
        // build request
1126
        $req = pack('NNNNN', $this->query_flags, $this->offset, $this->limit, $this->mode, $this->ranker);
1127
        if ($this->ranker == SPH_RANK_EXPR) {
1128
            $req .= pack('N', strlen($this->rank_expr)) . $this->rank_expr;
1129
        }
1130
        $req .= pack('N', $this->sort); // (deprecated) sort mode
1131
        $req .= pack('N', strlen($this->sort_by)) . $this->sort_by;
1132
        $req .= pack('N', strlen($query)) . $query; // query itself
1133
        $req .= pack('N', count($this->weights)); // weights
1134
        foreach ($this->weights as $weight) {
1135
            $req .= pack('N', (int)$weight);
1136
        }
1137
        $req .= pack('N', strlen($index)) . $index; // indexes
1138
        $req .= pack('N', 1); // id64 range marker
1139
        $req .= sphPackU64($this->min_id) . sphPackU64($this->max_id); // id64 range
1140
1141
        // filters
1142
        $req .= pack('N', count($this->filters));
1143
        foreach ($this->filters as $filter) {
1144
            $req .= pack('N', strlen($filter['attr'])) . $filter['attr'];
1145
            $req .= pack('N', $filter['type']);
1146
            switch ($filter['type']) {
1147
                case SPH_FILTER_VALUES:
1148
                    $req .= pack('N', count($filter['values']));
1149
                    foreach ($filter['values'] as $value) {
1150
                        $req .= sphPackI64($value);
1151
                    }
1152
                    break;
1153
                case SPH_FILTER_RANGE:
1154
                    $req .= sphPackI64($filter['min']) . sphPackI64($filter['max']);
1155
                    break;
1156
                case SPH_FILTER_FLOATRANGE:
1157
                    $req .= $this->packFloat($filter['min']) . $this->packFloat($filter['max']);
1158
                    break;
1159
                case SPH_FILTER_STRING:
1160
                    $req .= pack('N', strlen($filter['value'])) . $filter['value'];
1161
                    break;
1162
                default:
1163
                    assert(0 && 'internal error: unhandled filter type');
1164
            }
1165
            $req .= pack('N', $filter['exclude']);
1166
        }
1167
1168
        // group-by clause, max-matches count, group-sort clause, cutoff count
1169
        $req .= pack('NN', $this->group_func, strlen($this->group_by)) . $this->group_by;
1170
        $req .= pack('N', $this->max_matches);
1171
        $req .= pack('N', strlen($this->group_sort)) . $this->group_sort;
1172
        $req .= pack('NNN', $this->cutoff, $this->retry_count, $this->retry_delay);
1173
        $req .= pack('N', strlen($this->group_distinct)) . $this->group_distinct;
1174
1175
        // anchor point
1176
        if (empty($this->anchor)) {
1177
            $req .= pack('N', 0);
1178
        } else {
1179
            $a =& $this->anchor;
1180
            $req .= pack('N', 1);
1181
            $req .= pack('N', strlen($a['attrlat'])) . $a['attrlat'];
1182
            $req .= pack('N', strlen($a['attrlong'])) . $a['attrlong'];
1183
            $req .= $this->packFloat($a['lat']) . $this->packFloat($a['long']);
1184
        }
1185
1186
        // per-index weights
1187
        $req .= pack('N', count($this->index_weights));
1188
        foreach ($this->index_weights as $idx => $weight) {
1189
            $req .= pack('N', strlen($idx)) . $idx . pack('N', $weight);
1190
        }
1191
1192
        // max query time
1193
        $req .= pack('N', $this->max_query_time);
1194
1195
        // per-field weights
1196
        $req .= pack('N', count($this->field_weights));
1197
        foreach ($this->field_weights as $field => $weight) {
1198
            $req .= pack('N', strlen($field)) . $field . pack('N', $weight);
1199
        }
1200
1201
        // comment
1202
        $req .= pack('N', strlen($comment)) . $comment;
1203
1204
        // attribute overrides
1205
        $req .= pack('N', count($this->overrides));
1206
        foreach ($this->overrides as $key => $entry) {
1207
            $req .= pack('N', strlen($entry['attr'])) . $entry['attr'];
1208
            $req .= pack('NN', $entry['type'], count($entry['values']));
1209
            foreach ($entry['values'] as $id => $val) {
1210
                assert(is_numeric($id));
1211
                assert(is_numeric($val));
1212
1213
                $req .= sphPackU64($id);
1214
                switch ($entry['type']) {
1215
                    case SPH_ATTR_FLOAT:
1216
                        $req .= $this->packFloat($val);
1217
                        break;
1218
                    case SPH_ATTR_BIGINT:
1219
                        $req .= sphPackI64($val);
1220
                        break;
1221
                    default:
1222
                        $req .= pack('N', $val);
1223
                        break;
1224
                }
1225
            }
1226
        }
1227
1228
        // select-list
1229
        $req .= pack('N', strlen($this->select)) . $this->select;
1230
1231
        // max_predicted_time
1232
        if ($this->predicted_time > 0) {
1233
            $req .= pack('N', (int)$this->predicted_time);
1234
        }
1235
1236
        $req .= pack('N', strlen($this->outer_order_by)) . $this->outer_order_by;
1237
        $req .= pack('NN', $this->outer_offset, $this->outer_limit);
1238
        if ($this->has_outer) {
1239
            $req .= pack('N', 1);
1240
        } else {
1241
            $req .= pack('N', 0);
1242
        }
1243
1244
        // mbstring workaround
1245
        $this->mbPop();
1246
1247
        // store request to requests array
1248
        $this->reqs[] = $req;
1249
        return count($this->reqs) - 1;
1250
    }
1251
1252
    /// connect to searchd, run queries batch, and return an array of result sets
1253
    public function runQueries()
1254
    {
1255
        if (empty($this->reqs)) {
1256
            $this->error = 'no queries defined, issue AddQuery() first';
1257
            return false;
1258
        }
1259
1260
        // mbstring workaround
1261
        $this->mbPush();
1262
1263
        if (!($fp = $this->connect())) {
1264
            $this->mbPop();
1265
            return false;
1266
        }
1267
1268
        // send query, get response
1269
        $nreqs = count($this->reqs);
1270
        $req = join('', $this->reqs);
1271
        $len = 8 + strlen($req);
1272
        $req = pack('nnNNN', SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, $len, 0, $nreqs) . $req; // add header
1273
1274 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...
1275
            $this->mbPop();
1276
            return false;
1277
        }
1278
1279
        // query sent ok; we can reset reqs now
1280
        $this->reqs = array();
1281
1282
        // parse and return response
1283
        return $this->parseSearchResponse($response, $nreqs);
1284
    }
1285
1286
    /// parse and return search query (or queries) response
1287
    protected function parseSearchResponse($response, $nreqs)
1288
    {
1289
        $p = 0; // current position
1290
        $max = strlen($response); // max position for checks, to protect against broken responses
1291
1292
        $results = array();
1293
        for ($ires = 0; $ires < $nreqs && $p < $max; $ires++) {
1294
            $results[] = array();
1295
            $result =& $results[$ires];
1296
1297
            $result['error'] = '';
1298
            $result['warning'] = '';
1299
1300
            // extract status
1301
            list(, $status) = unpack('N*', substr($response, $p, 4));
1302
            $p += 4;
1303
            $result['status'] = $status;
1304
            if ($status != SEARCHD_OK) {
1305
                list(, $len) = unpack('N*', substr($response, $p, 4));
1306
                $p += 4;
1307
                $message = substr($response, $p, $len);
1308
                $p += $len;
1309
1310
                if ($status == SEARCHD_WARNING) {
1311
                    $result['warning'] = $message;
1312
                } else {
1313
                    $result['error'] = $message;
1314
                    continue;
1315
                }
1316
            }
1317
1318
            // read schema
1319
            $fields = array();
1320
            $attrs = array();
1321
1322
            list(, $nfields) = unpack('N*', substr($response, $p, 4));
1323
            $p += 4;
1324
            while ($nfields --> 0 && $p < $max) {
1325
                list(, $len) = unpack('N*', substr($response, $p, 4));
1326
                $p += 4;
1327
                $fields[] = substr($response, $p, $len);
1328
                $p += $len;
1329
            }
1330
            $result['fields'] = $fields;
1331
1332
            list(, $nattrs) = unpack('N*', substr($response, $p, 4));
1333
            $p += 4;
1334
            while ($nattrs --> 0 && $p < $max) {
1335
                list(, $len) = unpack('N*', substr($response, $p, 4));
1336
                $p += 4;
1337
                $attr = substr($response, $p, $len);
1338
                $p += $len;
1339
                list(, $type) = unpack('N*', substr($response, $p, 4));
1340
                $p += 4;
1341
                $attrs[$attr] = $type;
1342
            }
1343
            $result['attrs'] = $attrs;
1344
1345
            // read match count
1346
            list(, $count) = unpack('N*', substr($response, $p, 4));
1347
            $p += 4;
1348
            list(, $id64) = unpack('N*', substr($response, $p, 4));
1349
            $p += 4;
1350
1351
            // read matches
1352
            $idx = -1;
1353
            while ($count --> 0 && $p < $max) {
1354
                // index into result array
1355
                $idx++;
1356
1357
                // parse document id and weight
1358
                if ($id64) {
1359
                    $doc = sphUnpackU64(substr($response, $p, 8));
1360
                    $p += 8;
1361
                    list(,$weight) = unpack('N*', substr($response, $p, 4));
1362
                    $p += 4;
1363
                } else {
1364
                    list($doc, $weight) = array_values(unpack('N*N*', substr($response, $p, 8)));
1365
                    $p += 8;
1366
                    $doc = sphFixUint($doc);
1367
                }
1368
                $weight = sprintf('%u', $weight);
1369
1370
                // create match entry
1371 View Code Duplication
                if ($this->array_result) {
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...
1372
                    $result['matches'][$idx] = array('id' => $doc, 'weight' => $weight);
1373
                } else {
1374
                    $result['matches'][$doc]['weight'] = $weight;
1375
                }
1376
1377
                // parse and create attributes
1378
                $attrvals = array();
1379
                foreach ($attrs as $attr => $type) {
1380
                    // handle 64bit ints
1381
                    if ($type == SPH_ATTR_BIGINT) {
1382
                        $attrvals[$attr] = sphUnpackI64(substr($response, $p, 8));
1383
                        $p += 8;
1384
                        continue;
1385
                    }
1386
1387
                    // handle floats
1388
                    if ($type == SPH_ATTR_FLOAT) {
1389
                        list(, $uval) = unpack('N*', substr($response, $p, 4));
1390
                        $p += 4;
1391
                        list(, $fval) = unpack('f*', pack('L', $uval));
1392
                        $attrvals[$attr] = $fval;
1393
                        continue;
1394
                    }
1395
1396
                    // handle everything else as unsigned ints
1397
                    list(, $val) = unpack('N*', substr($response, $p, 4));
1398
                    $p += 4;
1399
                    if ($type == SPH_ATTR_MULTI) {
1400
                        $attrvals[$attr] = array();
1401
                        $nvalues = $val;
1402 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...
1403
                            list(, $val) = unpack('N*', substr($response, $p, 4));
1404
                            $p += 4;
1405
                            $attrvals[$attr][] = sphFixUint($val);
1406
                        }
1407
                    } elseif ($type == SPH_ATTR_MULTI64) {
1408
                        $attrvals[$attr] = array();
1409
                        $nvalues = $val;
1410 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...
1411
                            $attrvals[$attr][] = sphUnpackI64(substr($response, $p, 8));
1412
                            $p += 8;
1413
                            $nvalues -= 2;
1414
                        }
1415
                    } elseif ($type == SPH_ATTR_STRING) {
1416
                        $attrvals[$attr] = substr($response, $p, $val);
1417
                        $p += $val;
1418
                    } elseif ($type == SPH_ATTR_FACTORS) {
1419
                        $attrvals[$attr] = substr($response, $p, $val - 4);
1420
                        $p += $val-4;
1421
                    } else {
1422
                        $attrvals[$attr] = sphFixUint($val);
1423
                    }
1424
                }
1425
1426 View Code Duplication
                if ($this->array_result) {
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...
1427
                    $result['matches'][$idx]['attrs'] = $attrvals;
1428
                } else {
1429
                    $result['matches'][$doc]['attrs'] = $attrvals;
1430
                }
1431
            }
1432
1433
            list($total, $total_found, $msecs, $words) = array_values(unpack('N*N*N*N*', substr($response, $p, 16)));
1434
            $result['total'] = sprintf('%u', $total);
1435
            $result['total_found'] = sprintf('%u', $total_found);
1436
            $result['time'] = sprintf('%.3f', $msecs / 1000);
1437
            $p += 16;
1438
1439
            while ($words --> 0 && $p < $max) {
1440
                list(, $len) = unpack('N*', substr($response, $p, 4));
1441
                $p += 4;
1442
                $word = substr($response, $p, $len);
1443
                $p += $len;
1444
                list($docs, $hits) = array_values(unpack('N*N*', substr($response, $p, 8)));
1445
                $p += 8;
1446
                $result['words'][$word] = array (
1447
                    'docs' => sprintf('%u', $docs),
1448
                    'hits' => sprintf('%u', $hits)
1449
                );
1450
            }
1451
        }
1452
1453
        $this->mbPop();
1454
        return $results;
1455
    }
1456
1457
    /////////////////////////////////////////////////////////////////////////////
1458
    // excerpts generation
1459
    /////////////////////////////////////////////////////////////////////////////
1460
1461
    /// connect to searchd server, and generate exceprts (snippets)
1462
    /// of given documents for given query. returns false on failure,
1463
    /// an array of snippets on success
1464
    public function buildExcerpts($docs, $index, $words, $opts = array())
1465
    {
1466
        assert(is_array($docs));
1467
        assert(is_string($index));
1468
        assert(is_string($words));
1469
        assert(is_array($opts));
1470
1471
        $this->mbPush();
1472
1473
        if (!($fp = $this->connect())) {
1474
            $this->mbPop();
1475
            return false;
1476
        }
1477
1478
        /////////////////
1479
        // fixup options
1480
        /////////////////
1481
1482
        if (!isset($opts['before_match'])) {
1483
            $opts['before_match'] = '<b>';
1484
        }
1485
        if (!isset($opts['after_match'])) {
1486
            $opts['after_match'] = '</b>';
1487
        }
1488
        if (!isset($opts['chunk_separator'])) {
1489
            $opts['chunk_separator'] = ' ... ';
1490
        }
1491
        if (!isset($opts['limit'])) {
1492
            $opts['limit'] = 256;
1493
        }
1494
        if (!isset($opts['limit_passages'])) {
1495
            $opts['limit_passages'] = 0;
1496
        }
1497
        if (!isset($opts['limit_words'])) {
1498
            $opts['limit_words'] = 0;
1499
        }
1500
        if (!isset($opts['around'])) {
1501
            $opts['around'] = 5;
1502
        }
1503
        if (!isset($opts['exact_phrase'])) {
1504
            $opts['exact_phrase'] = false;
1505
        }
1506
        if (!isset($opts['single_passage'])) {
1507
            $opts['single_passage'] = false;
1508
        }
1509
        if (!isset($opts['use_boundaries'])) {
1510
            $opts['use_boundaries'] = false;
1511
        }
1512
        if (!isset($opts['weight_order'])) {
1513
            $opts['weight_order'] = false;
1514
        }
1515
        if (!isset($opts['query_mode'])) {
1516
            $opts['query_mode'] = false;
1517
        }
1518
        if (!isset($opts['force_all_words'])) {
1519
            $opts['force_all_words'] = false;
1520
        }
1521
        if (!isset($opts['start_passage_id'])) {
1522
            $opts['start_passage_id'] = 1;
1523
        }
1524
        if (!isset($opts['load_files'])) {
1525
            $opts['load_files'] = false;
1526
        }
1527
        if (!isset($opts['html_strip_mode'])) {
1528
            $opts['html_strip_mode'] = 'index';
1529
        }
1530
        if (!isset($opts['allow_empty'])) {
1531
            $opts['allow_empty'] = false;
1532
        }
1533
        if (!isset($opts['passage_boundary'])) {
1534
            $opts['passage_boundary'] = 'none';
1535
        }
1536
        if (!isset($opts['emit_zones'])) {
1537
            $opts['emit_zones'] = false;
1538
        }
1539
        if (!isset($opts['load_files_scattered'])) {
1540
            $opts['load_files_scattered'] = false;
1541
        }
1542
1543
1544
        /////////////////
1545
        // build request
1546
        /////////////////
1547
1548
        // v.1.2 req
1549
        $flags = 1; // remove spaces
1550
        if ($opts['exact_phrase']) {
1551
            $flags |= 2;
1552
        }
1553
        if ($opts['single_passage']) {
1554
            $flags |= 4;
1555
        }
1556
        if ($opts['use_boundaries']) {
1557
            $flags |= 8;
1558
        }
1559
        if ($opts['weight_order']) {
1560
            $flags |= 16;
1561
        }
1562
        if ($opts['query_mode']) {
1563
            $flags |= 32;
1564
        }
1565
        if ($opts['force_all_words']) {
1566
            $flags |= 64;
1567
        }
1568
        if ($opts['load_files']) {
1569
            $flags |= 128;
1570
        }
1571
        if ($opts['allow_empty']) {
1572
            $flags |= 256;
1573
        }
1574
        if ($opts['emit_zones']) {
1575
            $flags |= 512;
1576
        }
1577
        if ($opts['load_files_scattered']) {
1578
            $flags |= 1024;
1579
        }
1580
        $req = pack('NN', 0, $flags); // mode=0, flags=$flags
1581
        $req .= pack('N', strlen($index)) . $index; // req index
1582
        $req .= pack('N', strlen($words)) . $words; // req words
1583
1584
        // options
1585
        $req .= pack('N', strlen($opts['before_match'])) . $opts['before_match'];
1586
        $req .= pack('N', strlen($opts['after_match'])) . $opts['after_match'];
1587
        $req .= pack('N', strlen($opts['chunk_separator'])) . $opts['chunk_separator'];
1588
        $req .= pack('NN', (int)$opts['limit'], (int)$opts['around']);
1589
        $req .= pack('NNN', (int)$opts['limit_passages'], (int)$opts['limit_words'], (int)$opts['start_passage_id']); // v.1.2
1590
        $req .= pack('N', strlen($opts['html_strip_mode'])) . $opts['html_strip_mode'];
1591
        $req .= pack('N', strlen($opts['passage_boundary'])) . $opts['passage_boundary'];
1592
1593
        // documents
1594
        $req .= pack('N', count($docs));
1595
        foreach ($docs as $doc) {
1596
            assert(is_string($doc));
1597
            $req .= pack('N', strlen($doc)) . $doc;
1598
        }
1599
1600
        ////////////////////////////
1601
        // send query, get response
1602
        ////////////////////////////
1603
1604
        $len = strlen($req);
1605
        $req = pack('nnN', SEARCHD_COMMAND_EXCERPT, VER_COMMAND_EXCERPT, $len) . $req; // add header
1606 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...
1607
            $this->mbPop();
1608
            return false;
1609
        }
1610
1611
        //////////////////
1612
        // parse response
1613
        //////////////////
1614
1615
        $pos = 0;
1616
        $res = array();
1617
        $rlen = strlen($response);
1618
        $count = count($docs);
1619
        while ($count--) {
1620
            list(, $len) = unpack('N*', substr($response, $pos, 4));
1621
            $pos += 4;
1622
1623
            if ($pos + $len > $rlen) {
1624
                $this->error = 'incomplete reply';
1625
                $this->mbPop();
1626
                return false;
1627
            }
1628
            $res[] = $len ? substr($response, $pos, $len) : '';
1629
            $pos += $len;
1630
        }
1631
1632
        $this->mbPop();
1633
        return $res;
1634
    }
1635
1636
1637
    /////////////////////////////////////////////////////////////////////////////
1638
    // keyword generation
1639
    /////////////////////////////////////////////////////////////////////////////
1640
1641
    /// connect to searchd server, and generate keyword list for a given query
1642
    /// returns false on failure,
1643
    /// an array of words on success
1644
    public function buildKeywords($query, $index, $hits)
1645
    {
1646
        assert(is_string($query));
1647
        assert(is_string($index));
1648
        assert(is_bool($hits));
1649
1650
        $this->mbPush();
1651
1652
        if (!($fp = $this->connect())) {
1653
            $this->mbPop();
1654
            return false;
1655
        }
1656
1657
        /////////////////
1658
        // build request
1659
        /////////////////
1660
1661
        // v.1.0 req
1662
        $req  = pack('N', strlen($query)) . $query; // req query
1663
        $req .= pack('N', strlen($index)) . $index; // req index
1664
        $req .= pack('N', (int)$hits);
1665
1666
        ////////////////////////////
1667
        // send query, get response
1668
        ////////////////////////////
1669
1670
        $len = strlen($req);
1671
        $req = pack('nnN', SEARCHD_COMMAND_KEYWORDS, VER_COMMAND_KEYWORDS, $len) . $req; // add header
1672 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...
1673
            $this->mbPop();
1674
            return false;
1675
        }
1676
1677
        //////////////////
1678
        // parse response
1679
        //////////////////
1680
1681
        $pos = 0;
1682
        $res = array();
1683
        $rlen = strlen($response);
1684
        list(, $nwords) = unpack('N*', substr($response, $pos, 4));
1685
        $pos += 4;
1686
        for ($i = 0; $i < $nwords; $i++) {
1687
            list(, $len) = unpack('N*', substr($response, $pos, 4));
1688
            $pos += 4;
1689
            $tokenized = $len ? substr($response, $pos, $len) : '';
1690
            $pos += $len;
1691
1692
            list(, $len) = unpack('N*', substr($response, $pos, 4));
1693
            $pos += 4;
1694
            $normalized = $len ? substr($response, $pos, $len) : '';
1695
            $pos += $len;
1696
1697
            $res[] = array(
1698
                'tokenized' => $tokenized,
1699
                'normalized' => $normalized
1700
            );
1701
1702
            if ($hits) {
1703
                list($ndocs, $nhits) = array_values(unpack('N*N*', substr($response, $pos, 8)));
1704
                $pos += 8;
1705
                $res[$i]['docs'] = $ndocs;
1706
                $res[$i]['hits'] = $nhits;
1707
            }
1708
1709
            if ($pos > $rlen) {
1710
                $this->error = 'incomplete reply';
1711
                $this->mbPop();
1712
                return false;
1713
            }
1714
        }
1715
1716
        $this->mbPop();
1717
        return $res;
1718
    }
1719
1720
    public function escapeString($string)
1721
    {
1722
        $from = array('\\', '(',')','|','-','!','@','~','"','&', '/', '^', '$', '=', '<');
1723
        $to   = array('\\\\', '\(','\)','\|','\-','\!','\@','\~','\"', '\&', '\/', '\^', '\$', '\=', '\<');
1724
1725
        return str_replace($from, $to, $string);
1726
    }
1727
1728
    /////////////////////////////////////////////////////////////////////////////
1729
    // attribute updates
1730
    /////////////////////////////////////////////////////////////////////////////
1731
1732
    /// batch update given attributes in given rows in given indexes
1733
    /// returns amount of updated documents (0 or more) on success, or -1 on failure
1734
    public function updateAttributes($index, $attrs, $values, $mva = false, $ignorenonexistent = false)
1735
    {
1736
        // verify everything
1737
        assert(is_string($index));
1738
        assert(is_bool($mva));
1739
        assert(is_bool($ignorenonexistent));
1740
1741
        assert(is_array($attrs));
1742
        foreach ($attrs as $attr) {
1743
            assert(is_string($attr));
1744
        }
1745
1746
        assert(is_array($values));
1747
        foreach ($values as $id => $entry) {
1748
            assert(is_numeric($id));
1749
            assert(is_array($entry));
1750
            assert(count($entry) == count($attrs));
1751
            foreach ($entry as $v) {
1752
                if ($mva) {
1753
                    assert(is_array($v));
1754
                    foreach ($v as $vv) {
1755
                        assert(is_int($vv));
1756
                    }
1757
                } else {
1758
                    assert(is_int($v));
1759
                }
1760
            }
1761
        }
1762
1763
        // build request
1764
        $this->mbPush();
1765
        $req = pack('N', strlen($index)) . $index;
1766
1767
        $req .= pack('N', count($attrs));
1768
        $req .= pack('N', $ignorenonexistent ? 1 : 0);
1769
        foreach ($attrs as $attr) {
1770
            $req .= pack('N', strlen($attr)) . $attr;
1771
            $req .= pack('N', $mva ? 1 : 0);
1772
        }
1773
1774
        $req .= pack('N', count($values));
1775
        foreach ($values as $id => $entry) {
1776
            $req .= sphPackU64($id);
1777
            foreach ($entry as $v) {
1778
                $req .= pack('N', $mva ? count($v) : $v);
1779
                if ($mva) {
1780
                    foreach ($v as $vv) {
1781
                        $req .= pack('N', $vv);
1782
                    }
1783
                }
1784
            }
1785
        }
1786
1787
        // connect, send query, get response
1788
        if (!($fp = $this->connect())) {
1789
            $this->mbPop();
1790
            return -1;
1791
        }
1792
1793
        $len = strlen($req);
1794
        $req = pack('nnN', SEARCHD_COMMAND_UPDATE, VER_COMMAND_UPDATE, $len) . $req; // add header
1795
        if (!$this->send($fp, $req, $len + 8)) {
1796
            $this->mbPop();
1797
            return -1;
1798
        }
1799
1800
        if (!($response = $this->getResponse($fp, VER_COMMAND_UPDATE))) {
1801
            $this->mbPop();
1802
            return -1;
1803
        }
1804
1805
        // parse response
1806
        list(, $updated) = unpack('N*', substr($response, 0, 4));
1807
        $this->mbPop();
1808
        return $updated;
1809
    }
1810
1811
    /////////////////////////////////////////////////////////////////////////////
1812
    // persistent connections
1813
    /////////////////////////////////////////////////////////////////////////////
1814
1815
    public function open()
1816
    {
1817
        if ($this->socket !== false) {
1818
            $this->error = 'already connected';
1819
            return false;
1820
        }
1821
        if (!($fp = $this->connect()))
1822
            return false;
1823
1824
        // command, command version = 0, body length = 4, body = 1
1825
        $req = pack('nnNN', SEARCHD_COMMAND_PERSIST, 0, 4, 1);
1826
        if (!$this->send($fp, $req, 12)) {
1827
            return false;
1828
        }
1829
1830
        $this->socket = $fp;
1831
        return true;
1832
    }
1833
1834
    public function close()
1835
    {
1836
        if ($this->socket === false) {
1837
            $this->error = 'not connected';
1838
            return false;
1839
        }
1840
1841
        fclose($this->socket);
1842
        $this->socket = false;
1843
1844
        return true;
1845
    }
1846
1847
    //////////////////////////////////////////////////////////////////////////
1848
    // status
1849
    //////////////////////////////////////////////////////////////////////////
1850
1851
    public function status($session = false)
1852
    {
1853
        assert(is_bool($session));
1854
1855
        $this->mbPush();
1856
        if (!($fp = $this->connect())) {
1857
            $this->mbPop();
1858
            return false;
1859
        }
1860
1861
        $req = pack('nnNN', SEARCHD_COMMAND_STATUS, VER_COMMAND_STATUS, 4, $session ? 0 : 1); // len=4, body=1
1862 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...
1863
            $this->mbPop();
1864
            return false;
1865
        }
1866
1867
        $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...
1868
        $p = 0;
1869
        list($rows, $cols) = array_values(unpack('N*N*', substr($response, $p, 8)));
1870
        $p += 8;
1871
1872
        $res = array();
1873
        for ($i = 0; $i < $rows; $i++) {
1874
            for ($j = 0; $j < $cols; $j++) {
1875
                list(, $len) = unpack('N*', substr($response, $p, 4));
1876
                $p += 4;
1877
                $res[$i][] = substr($response, $p, $len);
1878
                $p += $len;
1879
            }
1880
        }
1881
1882
        $this->mbPop();
1883
        return $res;
1884
    }
1885
1886
    //////////////////////////////////////////////////////////////////////////
1887
    // flush
1888
    //////////////////////////////////////////////////////////////////////////
1889
1890
    public function flushAttributes()
1891
    {
1892
        $this->mbPush();
1893
        if (!($fp = $this->connect())) {
1894
            $this->mbPop();
1895
            return -1;
1896
        }
1897
1898
        $req = pack('nnN', SEARCHD_COMMAND_FLUSHATTRS, VER_COMMAND_FLUSHATTRS, 0); // len=0
1899 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...
1900
            $this->mbPop();
1901
            return -1;
1902
        }
1903
1904
        $tag = -1;
1905
        if (strlen($response) == 4) {
1906
            list(, $tag) = unpack('N*', $response);
1907
        } else {
1908
            $this->error = 'unexpected response length';
1909
        }
1910
1911
        $this->mbPop();
1912
        return $tag;
1913
    }
1914
}
1915