Completed
Push — master ( 127ba1...126d50 )
by Peter
02:32
created

SphinxClient::setMatchMode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 17
ccs 0
cts 14
cp 0
rs 9.4285
cc 1
eloc 13
nc 1
nop 1
crap 2
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
/**
141
 * Pack 64-bit signed
142
 *
143
 * @param int $value
144
 *
145
 * @return string
146
 */
147
function sphPackI64($value)
148
{
149
    assert(is_numeric($value));
150
151
    // x64
152
    if (PHP_INT_SIZE >= 8) {
153
        $value = (int)$value;
154
        return pack('NN', $value >> 32, $value & 0xFFFFFFFF);
155
    }
156
157
    // x32, int
158
    if (is_int($value)) {
159
        return pack('NN', $value < 0 ? -1 : 0, $value);
160
    }
161
162
    // x32, bcmath
163
    if (function_exists('bcmul')) {
164
        if (bccomp($value, 0) == -1) {
165
            $value = bcadd('18446744073709551616', $value);
166
        }
167
        $h = bcdiv($value, '4294967296', 0);
168
        $l = bcmod($value, '4294967296');
169
        return pack('NN', (float)$h, (float)$l); // conversion to float is intentional; int would lose 31st bit
170
    }
171
172
    // x32, no-bcmath
173
    $p = max(0, strlen($value) - 13);
174
    $lo = abs((float)substr($value, $p));
175
    $hi = abs((float)substr($value, 0, $p));
176
177
    $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...
178
    $q = floor($m / 4294967296.0);
179
    $l = $m - ($q * 4294967296.0);
180
    $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...
181
182
    if ($value < 0) {
183
        if ($l == 0) {
184
            $h = 4294967296.0 - $h;
185
        } else {
186
            $h = 4294967295.0 - $h;
187
            $l = 4294967296.0 - $l;
188
        }
189
    }
190
    return pack('NN', $h, $l);
191
}
192
193
/**
194
 * Ppack 64-bit unsigned
195
 *
196
 * @param int $value
197
 *
198
 * @return string
199
 */
200
function sphPackU64($value)
201
{
202
    assert(is_numeric($value));
203
204
    // x64
205
    if (PHP_INT_SIZE >= 8) {
206
        assert($value >= 0);
207
208
        // x64, int
209
        if (is_int($value)) {
210
            return pack('NN', $value >> 32, $value & 0xFFFFFFFF);
211
        }
212
213
        // x64, bcmath
214 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...
215
            $h = bcdiv($value, 4294967296, 0);
216
            $l = bcmod($value, 4294967296);
217
            return pack('NN', $h, $l);
218
        }
219
220
        // x64, no-bcmath
221
        $p = max(0, strlen($value) - 13);
222
        $lo = (int)substr($value, $p);
223
        $hi = (int)substr($value, 0, $p);
224
225
        $m = $lo + $hi * 1316134912;
226
        $l = $m % 4294967296;
227
        $h = $hi * 2328 + (int)($m / 4294967296);
228
229
        return pack('NN', $h, $l);
230
    }
231
232
    // x32, int
233
    if (is_int($value)) {
234
        return pack('NN', 0, $value);
235
    }
236
237
    // x32, bcmath
238 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...
239
        $h = bcdiv($value, '4294967296', 0);
240
        $l = bcmod($value, '4294967296');
241
        return pack('NN', (float)$h, (float)$l); // conversion to float is intentional; int would lose 31st bit
242
    }
243
244
    // x32, no-bcmath
245
    $p = max(0, strlen($value) - 13);
246
    $lo = (float)substr($value, $p);
247
    $hi = (float)substr($value, 0, $p);
248
249
    $m = $lo + $hi * 1316134912.0;
250
    $q = floor($m / 4294967296.0);
251
    $l = $m - ($q * 4294967296.0);
252
    $h = $hi * 2328.0 + $q;
253
254
    return pack('NN', $h, $l);
255
}
256
257
/**
258
 * Unpack 64-bit unsigned
259
 *
260
 * @param string $value
261
 *
262
 * @return string
263
 */
264
function sphUnpackU64($value)
265
{
266
    list($hi, $lo) = array_values(unpack('N*N*', $value));
267
268
    if (PHP_INT_SIZE >= 8) {
269
        if ($hi < 0) { // because php 5.2.2 to 5.2.5 is totally fucked up again
270
            $hi += 1 << 32;
271
        }
272
        if ($lo < 0) {
273
            $lo += 1 << 32;
274
        }
275
276
        // x64, int
277
        if ($hi <= 2147483647) {
278
            return ($hi << 32) + $lo;
279
        }
280
281
        // x64, bcmath
282
        if (function_exists('bcmul')) {
283
            return bcadd($lo, bcmul($hi, '4294967296'));
284
        }
285
286
        // x64, no-bcmath
287
        $C = 100000;
288
        $h = ((int)($hi / $C) << 32) + (int)($lo / $C);
289
        $l = (($hi % $C) << 32) + ($lo % $C);
290
        if ($l > $C) {
291
            $h += (int)($l / $C);
292
            $l  = $l % $C;
293
        }
294
295
        if ($h == 0) {
296
            return $l;
297
        }
298
        return sprintf('%d%05d', $h, $l);
299
    }
300
301
    // x32, int
302
    if ($hi == 0) {
303
        if ($lo > 0) {
304
            return $lo;
305
        }
306
        return sprintf('%u', $lo);
307
    }
308
309
    $hi = sprintf('%u', $hi);
310
    $lo = sprintf('%u', $lo);
311
312
    // x32, bcmath
313
    if (function_exists('bcmul')) {
314
        return bcadd($lo, bcmul($hi, '4294967296'));
315
    }
316
317
    // x32, no-bcmath
318
    $hi = (float)$hi;
319
    $lo = (float)$lo;
320
321
    $q = floor($hi / 10000000.0);
322
    $r = $hi - $q * 10000000.0;
323
    $m = $lo + $r * 4967296.0;
324
    $mq = floor($m / 10000000.0);
325
    $l = $m - $mq * 10000000.0;
326
    $h = $q * 4294967296.0 + $r * 429.0 + $mq;
327
328
    $h = sprintf('%.0f', $h);
329
    $l = sprintf('%07.0f', $l);
330
    if ($h == '0') {
331
        return sprintf('%.0f', (float)$l);
332
    }
333
    return $h . $l;
334
}
335
336
/**
337
 * Unpack 64-bit signed
338
 *
339
 * @param string $value
340
 *
341
 * @return string
342
 */
343
function sphUnpackI64($value)
344
{
345
    list($hi, $lo) = array_values(unpack('N*N*', $value));
346
347
    // x64
348
    if (PHP_INT_SIZE >= 8) {
349
        if ($hi < 0) { // because php 5.2.2 to 5.2.5 is totally fucked up again
350
            $hi += 1 << 32;
351
        }
352
        if ($lo < 0) {
353
            $lo += 1 << 32;
354
        }
355
356
        return ($hi << 32) + $lo;
357
    }
358
359
    if ($hi == 0) { // x32, int
360
        if ($lo > 0) {
361
            return $lo;
362
        }
363
        return sprintf('%u', $lo);
364
    } elseif ($hi == -1) { // x32, int
365
        if ($lo < 0) {
366
            return $lo;
367
        }
368
        return sprintf('%.0f', $lo - 4294967296.0);
369
    }
370
371
    $neg = '';
372
    $c = 0;
373
    if ($hi < 0) {
374
        $hi = ~$hi;
375
        $lo = ~$lo;
376
        $c = 1;
377
        $neg = '-';
378
    }
379
380
    $hi = sprintf('%u', $hi);
381
    $lo = sprintf('%u', $lo);
382
383
    // x32, bcmath
384
    if (function_exists('bcmul')) {
385
        return $neg . bcadd(bcadd($lo, bcmul($hi, '4294967296')), $c);
386
    }
387
388
    // x32, no-bcmath
389
    $hi = (float)$hi;
390
    $lo = (float)$lo;
391
392
    $q = floor($hi / 10000000.0);
393
    $r = $hi - $q * 10000000.0;
394
    $m = $lo + $r * 4967296.0;
395
    $mq = floor($m / 10000000.0);
396
    $l = $m - $mq * 10000000.0 + $c;
397
    $h = $q * 4294967296.0 + $r * 429.0 + $mq;
398
    if ($l == 10000000) {
399
        $l = 0;
400
        $h += 1;
401
    }
402
403
    $h = sprintf('%.0f', $h);
404
    $l = sprintf('%07.0f', $l);
405
    if ($h == '0') {
406
        return $neg . sprintf('%.0f', (float)$l);
407
    }
408
    return $neg . $h . $l;
409
}
410
411
/**
412
 * @param int $value
413
 *
414
 * @return int|string
415
 */
416
function sphFixUint($value)
417
{
418
    if (PHP_INT_SIZE >= 8) {
419
        // x64 route, workaround broken unpack() in 5.2.2+
420
        if ($value < 0) {
421
            $value += 1 << 32;
422
        }
423
        return $value;
424
    } else {
425
        // x32 route, workaround php signed/unsigned braindamage
426
        return sprintf('%u', $value);
427
    }
428
}
429
430
/**
431
 * @param int $flag
432
 * @param int $bit
433
 * @param bool $on
434
 *
435
 * @return int
436
 */
437
function sphSetBit($flag, $bit, $on)
438
{
439 4
    if ($on) {
440 4
        $flag |= 1 << $bit;
441 4
    } else {
442
        $reset = 16777215 ^ (1 << $bit);
443
        $flag = $flag & $reset;
444
    }
445 4
    return $flag;
446
}
447
448
449
/**
450
 * Sphinx searchd client class
451
 */
452
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...
453
{
454
    /**
455
     * Searchd host
456
     *
457
     * @var string
458
     */
459
    protected $host = 'localhost';
460
461
    /**
462
     * Searchd port
463
     *
464
     * @var int
465
     */
466
    protected $port = 9312;
467
468
    /**
469
     * How many records to seek from result-set start
470
     *
471
     * @var int
472
     */
473
    protected $offset = 0;
474
475
    /**
476
     * How many records to return from result-set starting at offset
477
     *
478
     * @var int
479
     */
480
    protected $limit = 20;
481
482
    /**
483
     * Query matching mode
484
     *
485
     * @var int
486
     */
487
    protected $mode = SPH_MATCH_EXTENDED2;
488
489
    /**
490
     * Per-field weights (default is 1 for all fields)
491
     *
492
     * @var array
493
     */
494
    protected $weights = array();
495
496
    /**
497
     * Match sorting mode
498
     *
499
     * @var int
500
     */
501
    protected $sort = SPH_SORT_RELEVANCE;
502
503
    /**
504
     * Attribute to sort by
505
     *
506
     * @var string
507
     */
508
    protected $sort_by = '';
509
510
    /**
511
     * Min ID to match (0 means no limit)
512
     *
513
     * @var int
514
     */
515
    protected $min_id = 0;
516
517
    /**
518
     * Max ID to match (0 means no limit)
519
     *
520
     * @var int
521
     */
522
    protected $max_id = 0;
523
524
    /**
525
     * Search filters
526
     *
527
     * @var array
528
     */
529
    protected $filters = array();
530
531
    /**
532
     * Group-by attribute name
533
     *
534
     * @var string
535
     */
536
    protected $group_by = '';
537
538
    /**
539
     * Group-by function (to pre-process group-by attribute value with)
540
     *
541
     * @var int
542
     */
543
    protected $group_func = SPH_GROUPBY_DAY;
544
545
    /**
546
     * Group-by sorting clause (to sort groups in result set with)
547
     *
548
     * @var string
549
     */
550
    protected $group_sort = '@group desc';
551
552
    /**
553
     * Group-by count-distinct attribute
554
     *
555
     * @var string
556
     */
557
    protected $group_distinct = '';
558
559
    /**
560
     * Max matches to retrieve
561
     *
562
     * @var int
563
     */
564
    protected $max_matches = 1000;
565
566
    /**
567
     * Cutoff to stop searching at
568
     *
569
     * @var int
570
     */
571
    protected $cutoff = 0;
572
573
    /**
574
     * Distributed retries count
575
     *
576
     * @var int
577
     */
578
    protected $retry_count = 0;
579
580
    /**
581
     * Distributed retries delay
582
     *
583
     * @var int
584
     */
585
    protected $retry_delay = 0;
586
587
    /**
588
     * Geographical anchor point
589
     *
590
     * @var array
591
     */
592
    protected $anchor = array();
593
594
    /**
595
     * Per-index weights
596
     *
597
     * @var array
598
     */
599
    protected $index_weights = array();
600
601
    /**
602
     * Ranking mode
603
     *
604
     * @var int
605
     */
606
    protected $ranker = SPH_RANK_PROXIMITY_BM25;
607
608
    /**
609
     * Ranking mode expression (for SPH_RANK_EXPR)
610
     *
611
     * @var string
612
     */
613
    protected $rank_expr = '';
614
615
    /**
616
     * Max query time, milliseconds (0 means no limit)
617
     *
618
     * @var int
619
     */
620
    protected $max_query_time = 0;
621
622
    /**
623
     * Per-field-name weights
624
     *
625
     * @var array
626
     */
627
    protected $field_weights = array();
628
629
    /**
630
     * Per-query attribute values overrides
631
     *
632
     * @var array
633
     */
634
    protected $overrides = array();
635
636
    /**
637
     * Select-list (attributes or expressions, with optional aliases)
638
     *
639
     * @var string
640
     */
641
    protected $select = '*';
642
643
    /**
644
     * Per-query various flags
645
     *
646
     * @var int
647
     */
648
    protected $query_flags = 0;
649
650
    /**
651
     * Per-query max_predicted_time
652
     *
653
     * @var int
654
     */
655
    protected $predicted_time = 0;
656
657
    /**
658
     * Outer match sort by
659
     *
660
     * @var string
661
     */
662
    protected $outer_order_by = '';
663
664
    /**
665
     * Outer offset
666
     *
667
     * @var int
668
     */
669
    protected $outer_offset = 0;
670
671
    /**
672
     * Outer limit
673
     *
674
     * @var int
675
     */
676
    protected $outer_limit = 0;
677
678
    /**
679
     * @var bool
680
     */
681
    protected $has_outer = false;
682
683
    /**
684
     * Last error message
685
     *
686
     * @var string
687
     */
688
    protected $error = '';
689
690
    /**
691
     * Last warning message
692
     *
693
     * @var string
694
     */
695
    protected $warning = '';
696
697
    /**
698
     * Connection error vs remote error flag
699
     *
700
     * @var bool
701
     */
702
    protected $conn_error = false;
703
704
    /**
705
     * Requests array for multi-query
706
     *
707
     * @var array
708
     */
709
    protected $reqs = array();
710
711
    /**
712
     * Stored mbstring encoding
713
     *
714
     * @var string
715
     */
716
    protected $mbenc = '';
717
718
    /**
719
     * Whether $result['matches'] should be a hash or an array
720
     *
721
     * @var bool
722
     */
723
    protected $array_result = false;
724
725
    /**
726
     * Connect timeout
727
     *
728
     * @var int
729
     */
730
    protected $timeout = 0;
731
732
    /**
733
     * @var bool
734
     */
735
    protected $path = false;
736
737
    /**
738
     * @var resource|bool
739
     */
740
    protected $socket = false;
741
742
    /////////////////////////////////////////////////////////////////////////////
743
    // common stuff
744
    /////////////////////////////////////////////////////////////////////////////
745
746 4
    public function __construct()
747
    {
748
        // default idf=tfidf_normalized
749 4
        $this->query_flags = sphSetBit(0, 6, true);
750 4
    }
751
752
    public function __destruct()
753
    {
754
        if ($this->socket !== false) {
755
            fclose($this->socket);
756
        }
757
    }
758
759
    /**
760
     * @return string
761
     */
762 1
    public function getLastError()
763
    {
764 1
        return $this->error;
765
    }
766
767
    /**
768
     * @return string
769
     */
770 1
    public function getLastWarning()
771
    {
772 1
        return $this->warning;
773
    }
774
775
    /**
776
     * Get last error flag (to tell network connection errors from searchd errors or broken responses)
777
     *
778
     * @return bool
779
     */
780 1
    public function isConnectError()
781
    {
782 1
        return $this->conn_error;
783
    }
784
785
    /**
786
     * Set searchd host name and port
787
     *
788
     * @param string $host
789
     * @param int $port
790
     */
791
    public function setServer($host, $port = 0)
792
    {
793
        assert(is_string($host));
794
        if ($host[0] == '/') {
795
            $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...
796
            return;
797
        }
798
        if (substr($host, 0, 7) == 'unix://') {
799
            $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...
800
            return;
801
        }
802
803
        $this->host = $host;
804
        $port = intval($port);
805
        assert(0 <= $port && $port < 65536);
806
        $this->port = $port == 0 ? 9312 : $port;
807
        $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...
808
    }
809
810
    /**
811
     * Set server connection timeout (0 to remove)
812
     *
813
     * @param int $timeout
814
     */
815
    public function setConnectTimeout($timeout)
816
    {
817
        assert(is_numeric($timeout));
818
        $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...
819
    }
820
821
    /**
822
     * @param resource $handle
823
     * @param string $data
824
     * @param int $length
825
     *
826
     * @return bool
827
     */
828
    protected function send($handle, $data, $length)
829
    {
830
        if (feof($handle) || fwrite($handle, $data, $length) !== $length) {
831
            $this->error = 'connection unexpectedly closed (timed out?)';
832
            $this->conn_error = true;
833
            return false;
834
        }
835
        return true;
836
    }
837
838
    /////////////////////////////////////////////////////////////////////////////
839
840
    /**
841
     * Enter mbstring workaround mode
842
     */
843
    protected function mbPush()
844
    {
845
        $this->mbenc = '';
846
        if (ini_get('mbstring.func_overload') & 2) {
847
            $this->mbenc = mb_internal_encoding();
848
            mb_internal_encoding('latin1');
849
        }
850
    }
851
852
    /**
853
     * Leave mbstring workaround mode
854
     */
855
    protected function mbPop()
856
    {
857
        if ($this->mbenc) {
858
            mb_internal_encoding($this->mbenc);
859
        }
860
    }
861
862
    /**
863
     * Connect to searchd server
864
     *
865
     * @return bool|resource
866
     */
867
    protected function connect()
868
    {
869
        if ($this->socket !== false) {
870
            // we are in persistent connection mode, so we have a socket
871
            // however, need to check whether it's still alive
872
            if (!@feof($this->socket)) {
873
                return $this->socket;
874
            }
875
876
            // force reopen
877
            $this->socket = false;
878
        }
879
880
        $errno = 0;
881
        $errstr = '';
882
        $this->conn_error = false;
883
884
        if ($this->path) {
885
            $host = $this->path;
886
            $port = 0;
887
        } else {
888
            $host = $this->host;
889
            $port = $this->port;
890
        }
891
892
        if ($this->timeout <= 0) {
893
            $fp = @fsockopen($host, $port, $errno, $errstr);
894
        } else {
895
            $fp = @fsockopen($host, $port, $errno, $errstr, $this->timeout);
896
        }
897
898
        if (!$fp) {
899
            if ($this->path) {
900
                $location = $this->path;
901
            } else {
902
                $location = "{$this->host}:{$this->port}";
903
            }
904
905
            $errstr = trim($errstr);
906
            $this->error = "connection to $location failed (errno=$errno, msg=$errstr)";
907
            $this->conn_error = true;
908
            return false;
909
        }
910
911
        // send my version
912
        // this is a subtle part. we must do it before (!) reading back from searchd.
913
        // because otherwise under some conditions (reported on FreeBSD for instance)
914
        // TCP stack could throttle write-write-read pattern because of Nagle.
915
        if (!$this->send($fp, pack('N', 1), 4)) {
916
            fclose($fp);
917
            $this->error = 'failed to send client protocol version';
918
            return false;
919
        }
920
921
        // check version
922
        list(, $v) = unpack('N*', fread($fp, 4));
923
        $v = (int)$v;
924
        if ($v < 1) {
925
            fclose($fp);
926
            $this->error = "expected searchd protocol version 1+, got version '$v'";
927
            return false;
928
        }
929
930
        return $fp;
931
    }
932
933
    /**
934
     * Get and check response packet from searchd server
935
     *
936
     * @param resource $fp
937
     * @param int $client_ver
938
     *
939
     * @return bool|string
940
     */
941
    protected function getResponse($fp, $client_ver)
942
    {
943
        $response = '';
944
        $len = 0;
945
946
        $header = fread($fp, 8);
947
        if (strlen($header) == 8) {
948
            list($status, $ver, $len) = array_values(unpack('n2a/Nb', $header));
949
            $left = $len;
950
            while ($left > 0 && !feof($fp)) {
951
                $chunk = fread($fp, min(8192, $left));
952
                if ($chunk) {
953
                    $response .= $chunk;
954
                    $left -= strlen($chunk);
955
                }
956
            }
957
        }
958
959
        if ($this->socket === false) {
960
            fclose($fp);
961
        }
962
963
        // check response
964
        $read = strlen($response);
965
        if (!$response || $read != $len) {
966
            $this->error = $len
967
                ? "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...
968
                : 'received zero-sized searchd response';
969
            return false;
970
        }
971
972
        // check status
973
        if ($status == SEARCHD_WARNING) {
974
            list(, $wlen) = unpack('N*', substr($response, 0, 4));
975
            $this->warning = substr($response, 4, $wlen);
976
            return substr($response, 4 + $wlen);
977
        }
978
        if ($status == SEARCHD_ERROR) {
979
            $this->error = 'searchd error: ' . substr($response, 4);
980
            return false;
981
        }
982
        if ($status == SEARCHD_RETRY) {
983
            $this->error = 'temporary searchd error: ' . substr($response, 4);
984
            return false;
985
        }
986
        if ($status != SEARCHD_OK) {
987
            $this->error = "unknown status code '$status'";
988
            return false;
989
        }
990
991
        // check version
992
        if ($ver < $client_ver) {
993
            $this->warning = sprintf(
994
                'searchd command v.%d.%d older than client\'s v.%d.%d, some options might not work',
995
                $ver >> 8,
996
                $ver & 0xff,
997
                $client_ver >> 8,
998
                $client_ver & 0xff
999
            );
1000
        }
1001
1002
        return $response;
1003
    }
1004
1005
    /////////////////////////////////////////////////////////////////////////////
1006
    // searching
1007
    /////////////////////////////////////////////////////////////////////////////
1008
1009
    /**
1010
     * Set offset and count into result set, and optionally set max-matches and cutoff limits
1011
     *
1012
     * @param int $offset
1013
     * @param int $limit
1014
     * @param int $max
1015
     * @param int $cutoff
1016
     */
1017
    public function setLimits($offset, $limit, $max = 0, $cutoff = 0)
1018
    {
1019
        assert(is_int($offset));
1020
        assert(is_int($limit));
1021
        assert($offset >= 0);
1022
        assert($limit > 0);
1023
        assert($max >= 0);
1024
        $this->offset = $offset;
1025
        $this->limit = $limit;
1026
        if ($max > 0) {
1027
            $this->max_matches = $max;
1028
        }
1029
        if ($cutoff > 0) {
1030
            $this->cutoff = $cutoff;
1031
        }
1032
    }
1033
1034
    /**
1035
     * Set maximum query time, in milliseconds, per-index, 0 means 'do not limit'
1036
     *
1037
     * @param int $max
1038
     */
1039
    public function setMaxQueryTime($max)
1040
    {
1041
        assert(is_int($max));
1042
        assert($max >= 0);
1043
        $this->max_query_time = $max;
1044
    }
1045
1046
    /**
1047
     * Set matching mode
1048
     *
1049
     * @param int $mode
1050
     */
1051
    public function setMatchMode($mode)
1052
    {
1053
        trigger_error(
1054
            'DEPRECATED: Do not call this method or, even better, use SphinxQL instead of an API',
1055
            E_USER_DEPRECATED
1056
        );
1057
        assert(in_array($mode, array(
1058
            SPH_MATCH_ALL,
1059
            SPH_MATCH_ANY,
1060
            SPH_MATCH_PHRASE,
1061
            SPH_MATCH_BOOLEAN,
1062
            SPH_MATCH_EXTENDED,
1063
            SPH_MATCH_FULLSCAN,
1064
            SPH_MATCH_EXTENDED2
1065
        )));
1066
        $this->mode = $mode;
1067
    }
1068
1069
    /**
1070
     * Set ranking mode
1071
     *
1072
     * @param int $ranker
1073
     * @param string $rank_expr
1074
     */
1075
    public function setRankingMode($ranker, $rank_expr='')
1076
    {
1077
        assert($ranker === 0 || $ranker >= 1 && $ranker < SPH_RANK_TOTAL);
1078
        assert(is_string($rank_expr));
1079
        $this->ranker = $ranker;
1080
        $this->rank_expr = $rank_expr;
1081
    }
1082
1083
    /**
1084
     * Set matches sorting mode
1085
     *
1086
     * @param int $mode
1087
     * @param string $sort_by
1088
     */
1089
    public function setSortMode($mode, $sort_by = '')
1090
    {
1091
        assert(in_array($mode, array(
1092
            SPH_SORT_RELEVANCE,
1093
            SPH_SORT_ATTR_DESC,
1094
            SPH_SORT_ATTR_ASC,
1095
            SPH_SORT_TIME_SEGMENTS,
1096
            SPH_SORT_EXTENDED,
1097
            SPH_SORT_EXPR
1098
        )));
1099
        assert(is_string($sort_by));
1100
        assert($mode == SPH_SORT_RELEVANCE || strlen($sort_by) > 0);
1101
1102
        $this->sort = $mode;
1103
        $this->sort_by = $sort_by;
1104
    }
1105
1106
    /**
1107
     * Bind per-field weights by order
1108
     *
1109
     * @deprecated use setFieldWeights() instead
1110
     */
1111 1
    public function setWeights()
1112
    {
1113 1
        throw new \RuntimeException('This method is now deprecated; please use setFieldWeights instead');
1114
    }
1115
1116
    /**
1117
     * Bind per-field weights by name
1118
     *
1119
     * @param array $weights
1120
     */
1121
    public function setFieldWeights(array $weights)
1122
    {
1123
        foreach ($weights as $name => $weight) {
1124
            assert(is_string($name));
1125
            assert(is_int($weight));
1126
        }
1127
        $this->field_weights = $weights;
1128
    }
1129
1130
    /**
1131
     * Bind per-index weights by name
1132
     *
1133
     * @param array $weights
1134
     */
1135
    public function setIndexWeights(array $weights)
1136
    {
1137
        foreach ($weights as $index => $weight) {
1138
            assert(is_string($index));
1139
            assert(is_int($weight));
1140
        }
1141
        $this->index_weights = $weights;
1142
    }
1143
1144
    /**
1145
     * Set IDs range to match. Only match records if document ID is beetwen $min and $max (inclusive)
1146
     *
1147
     * @param int $min
1148
     * @param int $max
1149
     */
1150
    public function setIDRange($min, $max)
1151
    {
1152
        assert(is_numeric($min));
1153
        assert(is_numeric($max));
1154
        assert($min <= $max);
1155
1156
        $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...
1157
        $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...
1158
    }
1159
1160
    /**
1161
     * Set values set filter. Only match records where $attribute value is in given set
1162
     *
1163
     * @param string $attribute
1164
     * @param array $values
1165
     * @param bool $exclude
1166
     */
1167 View Code Duplication
    public function setFilter($attribute, array $values, $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...
1168
    {
1169
        assert(is_string($attribute));
1170
        assert(count($values));
1171
1172
        foreach ($values as $value) {
1173
            assert(is_numeric($value));
1174
        }
1175
1176
        $this->filters[] = array(
1177
            'type' => SPH_FILTER_VALUES,
1178
            'attr' => $attribute,
1179
            'exclude' => $exclude,
1180
            'values' => $values
1181
        );
1182
    }
1183
1184
    /**
1185
     * Set string filter
1186
     * Only match records where $attribute value is equal
1187
     *
1188
     * @param string $attribute
1189
     * @param string $value
1190
     * @param bool $exclude
1191
     */
1192
    public function setFilterString($attribute, $value, $exclude = false)
1193
    {
1194
        assert(is_string($attribute));
1195
        assert(is_string($value));
1196
        $this->filters[] = array(
1197
            'type' => SPH_FILTER_STRING,
1198
            'attr' => $attribute,
1199
            'exclude' => $exclude,
1200
            'value' => $value
1201
        );
1202
    }    
1203
1204
    /**
1205
     * Set range filter
1206
     * Only match records if $attribute value is beetwen $min and $max (inclusive)
1207
     *
1208
     * @param string $attribute
1209
     * @param int $min
1210
     * @param int $max
1211
     * @param bool $exclude
1212
     */
1213 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...
1214
    {
1215
        assert(is_string($attribute));
1216
        assert(is_numeric($min));
1217
        assert(is_numeric($max));
1218
        assert($min <= $max);
1219
1220
        $this->filters[] = array(
1221
            'type' => SPH_FILTER_RANGE,
1222
            'attr' => $attribute,
1223
            'exclude' => $exclude,
1224
            'min' => $min,
1225
            'max' => $max
1226
        );
1227
    }
1228
1229
    /**
1230
     * Set float range filter
1231
     * Only match records if $attribute value is beetwen $min and $max (inclusive)
1232
     *
1233
     * @param string $attribute
1234
     * @param int $min
1235
     * @param int $max
1236
     * @param bool $exclude
1237
     */
1238 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...
1239
    {
1240
        assert(is_string($attribute));
1241
        assert(is_float($min));
1242
        assert(is_float($max));
1243
        assert($min <= $max);
1244
1245
        $this->filters[] = array(
1246
            'type' => SPH_FILTER_FLOATRANGE,
1247
            'attr' => $attribute,
1248
            'exclude' => $exclude,
1249
            'min' => $min,
1250
            'max' => $max
1251
        );
1252
    }
1253
1254
    /**
1255
     * Setup anchor point for geosphere distance calculations
1256
     * Required to use @geodist in filters and sorting
1257
     * Latitude and longitude must be in radians
1258
     *
1259
     * @param string $attr_lat
1260
     * @param string $attr_long
1261
     * @param float $lat
1262
     * @param float $long
1263
     */
1264
    public function setGeoAnchor($attr_lat, $attr_long, $lat, $long)
1265
    {
1266
        assert(is_string($attr_lat));
1267
        assert(is_string($attr_long));
1268
        assert(is_float($lat));
1269
        assert(is_float($long));
1270
1271
        $this->anchor = array(
1272
            'attrlat' => $attr_lat,
1273
            'attrlong' => $attr_long,
1274
            'lat' => $lat,
1275
            'long' => $long
1276
        );
1277
    }
1278
1279
    /**
1280
     * Set grouping attribute and function
1281
     *
1282
     * @param string $attribute
1283
     * @param string $func
1284
     * @param string $group_sort
1285
     */
1286
    public function setGroupBy($attribute, $func, $group_sort = '@group desc')
1287
    {
1288
        assert(is_string($attribute));
1289
        assert(is_string($group_sort));
1290
        assert(in_array($func, array(
1291
            SPH_GROUPBY_DAY,
1292
            SPH_GROUPBY_WEEK,
1293
            SPH_GROUPBY_MONTH,
1294
            SPH_GROUPBY_YEAR,
1295
            SPH_GROUPBY_ATTR,
1296
            SPH_GROUPBY_ATTRPAIR
1297
        )));
1298
1299
        $this->group_by = $attribute;
1300
        $this->group_func = $func;
0 ignored issues
show
Documentation Bug introduced by
The property $group_func was declared of type integer, but $func 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...
1301
        $this->group_sort = $group_sort;
1302
    }
1303
1304
    /**
1305
     * Set count-distinct attribute for group-by queries
1306
     *
1307
     * @param string $attribute
1308
     */
1309
    public function setGroupDistinct($attribute)
1310
    {
1311
        assert(is_string($attribute));
1312
        $this->group_distinct = $attribute;
1313
    }
1314
1315
    /**
1316
     * Set distributed retries count and delay
1317
     *
1318
     * @param int $count
1319
     * @param int $delay
1320
     */
1321
    public function setRetries($count, $delay = 0)
1322
    {
1323
        assert(is_int($count) && $count >= 0);
1324
        assert(is_int($delay) && $delay >= 0);
1325
        $this->retry_count = $count;
1326
        $this->retry_delay = $delay;
1327
    }
1328
1329
    /**
1330
     * Set result set format (hash or array; hash by default)
1331
     * PHP specific; needed for group-by-MVA result sets that may contain duplicate IDs
1332
     *
1333
     * @param bool $array_result
1334
     */
1335
    public function setArrayResult($array_result)
1336
    {
1337
        assert(is_bool($array_result));
1338
        $this->array_result = $array_result;
1339
    }
1340
1341
    /**
1342
     * Set attribute values override
1343
     * There can be only one override per attribute
1344
     * $values must be a hash that maps document IDs to attribute values
1345
     *
1346
     * @deprecated Do not call this method. Use SphinxQL REMAP() function instead.
1347
     *
1348
     * @param string $attr_name
1349
     * @param string $attr_type
1350
     * @param array $values
1351
     */
1352
    public function setOverride($attr_name, $attr_type, array $values)
1353
    {
1354
        trigger_error(
1355
            'DEPRECATED: Do not call this method. Use SphinxQL REMAP() function instead.',
1356
            E_USER_DEPRECATED
1357
        );
1358
        assert(is_string($attr_name));
1359
        assert(in_array($attr_type, array(
1360
            SPH_ATTR_INTEGER,
1361
            SPH_ATTR_TIMESTAMP,
1362
            SPH_ATTR_BOOL,
1363
            SPH_ATTR_FLOAT,
1364
            SPH_ATTR_BIGINT
1365
        )));
1366
1367
        $this->overrides[$attr_name] = array(
1368
            'attr' => $attr_name,
1369
            'type' => $attr_type,
1370
            'values' => $values
1371
        );
1372
    }
1373
1374
    /**
1375
     * Set select-list (attributes or expressions), SQL-like syntax
1376
     *
1377
     * @param string $select
1378
     */
1379
    public function setSelect($select)
1380
    {
1381
        assert(is_string($select));
1382
        $this->select = $select;
1383
    }
1384
1385
    /**
1386
     * @param string $flag_name
1387
     * @param string|int $flag_value
1388
     */
1389
    public function setQueryFlag($flag_name, $flag_value)
1390
    {
1391
        $known_names = array(
1392
            'reverse_scan',
1393
            'sort_method',
1394
            'max_predicted_time',
1395
            'boolean_simplify',
1396
            'idf',
1397
            'global_idf',
1398
            'low_priority'
1399
        );
1400
        $flags = array (
1401
            'reverse_scan' => array(0, 1),
1402
            'sort_method' => array('pq', 'kbuffer'),
1403
            'max_predicted_time' => array(0),
1404
            'boolean_simplify' => array(true, false),
1405
            'idf' => array ('normalized', 'plain', 'tfidf_normalized', 'tfidf_unnormalized'),
1406
            'global_idf' => array(true, false),
1407
            'low_priority' => array(true, false)
1408
        );
1409
1410
        assert(isset($flag_name, $known_names));
1411
        assert(
1412
            in_array($flag_value, $flags[$flag_name], true) ||
1413
            ($flag_name == 'max_predicted_time' && is_int($flag_value) && $flag_value >= 0)
1414
        );
1415
1416
        switch ($flag_name) {
1417
            case 'reverse_scan':
1418
                $this->query_flags = sphSetBit($this->query_flags, 0, $flag_value == 1);
1419
                break;
1420
            case 'sort_method':
1421
                $this->query_flags = sphSetBit($this->query_flags, 1, $flag_value == 'kbuffer');
1422
                break;
1423
            case 'max_predicted_time':
1424
                $this->query_flags = sphSetBit($this->query_flags, 2, $flag_value > 0);
1425
                $this->predicted_time = (int)$flag_value;
1426
                break;
1427
            case 'boolean_simplify':
1428
                $this->query_flags = sphSetBit($this->query_flags, 3, $flag_value);
0 ignored issues
show
Documentation introduced by
$flag_value is of type string|integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1429
                break;
1430
            case 'idf':
1431
                if ($flag_value == 'normalized' || $flag_value == 'plain') {
1432
                    $this->query_flags = sphSetBit($this->query_flags, 4, $flag_value == 'plain');
1433
                }
1434
                if ($flag_value == 'tfidf_normalized' || $flag_value == 'tfidf_unnormalized') {
1435
                    $this->query_flags = sphSetBit($this->query_flags, 6, $flag_value == 'tfidf_normalized');
1436
                }
1437
                break;
1438
            case 'global_idf':
1439
                $this->query_flags = sphSetBit($this->query_flags, 5, $flag_value);
0 ignored issues
show
Documentation introduced by
$flag_value is of type string|integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1440
                break;
1441
            case 'low_priority':
1442
                $this->query_flags = sphSetBit($this->query_flags, 8, $flag_value);
0 ignored issues
show
Documentation introduced by
$flag_value is of type string|integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1443
                break;
1444
        }
1445
    }
1446
1447
    /**
1448
     * Set outer order by parameters
1449
     *
1450
     * @param string $order_by
1451
     * @param int $offset
1452
     * @param int $limit
1453
     */
1454
    public function setOuterSelect($order_by, $offset, $limit)
1455
    {
1456
        assert(is_string($order_by));
1457
        assert(is_int($offset));
1458
        assert(is_int($limit));
1459
        assert($offset >= 0);
1460
        assert($limit > 0);
1461
1462
        $this->outer_order_by = $order_by;
1463
        $this->outer_offset = $offset;
1464
        $this->outer_limit = $limit;
1465
        $this->has_outer = true;
1466
    }
1467
1468
1469
    //////////////////////////////////////////////////////////////////////////////
1470
1471
    /**
1472
     * Clear all filters (for multi-queries)
1473
     */
1474
    public function resetFilters()
1475
    {
1476
        $this->filters = array();
1477
        $this->anchor = array();
1478
    }
1479
1480
    /**
1481
     * Clear groupby settings (for multi-queries)
1482
     */
1483
    public function resetGroupBy()
1484
    {
1485
        $this->group_by = '';
1486
        $this->group_func = SPH_GROUPBY_DAY;
1487
        $this->group_sort = '@group desc';
1488
        $this->group_distinct = '';
1489
    }
1490
1491
    /**
1492
     * Clear all attribute value overrides (for multi-queries)
1493
     */
1494
    public function resetOverrides()
1495
    {
1496
        $this->overrides = array();
1497
    }
1498
1499
    public function resetQueryFlag()
1500
    {
1501
        $this->query_flags = sphSetBit(0, 6, true); // default idf=tfidf_normalized
1502
        $this->predicted_time = 0;
1503
    }
1504
1505
    public function resetOuterSelect()
1506
    {
1507
        $this->outer_order_by = '';
1508
        $this->outer_offset = 0;
1509
        $this->outer_limit = 0;
1510
        $this->has_outer = false;
1511
    }
1512
1513
    //////////////////////////////////////////////////////////////////////////////
1514
1515
    /**
1516
     * Connect to searchd server, run given search query through given indexes, and return the search results
1517
     *
1518
     * @param string  $query
1519
     * @param string $index
1520
     * @param string $comment
1521
     *
1522
     * @return bool
1523
     */
1524
    public function query($query, $index = '*', $comment = '')
1525
    {
1526
        assert(empty($this->reqs));
1527
1528
        $this->addQuery($query, $index, $comment);
1529
        $results = $this->runQueries();
1530
        $this->reqs = array(); // just in case it failed too early
1531
1532
        if (!is_array($results)) {
1533
            return false; // probably network error; error message should be already filled
1534
        }
1535
1536
        $this->error = $results[0]['error'];
1537
        $this->warning = $results[0]['warning'];
1538
1539
        if ($results[0]['status'] == SEARCHD_ERROR) {
1540
            return false;
1541
        } else {
1542
            return $results[0];
1543
        }
1544
    }
1545
1546
    /**
1547
     * Helper to pack floats in network byte order
1548
     *
1549
     * @param float $float
1550
     *
1551
     * @return string
1552
     */
1553
    protected function packFloat($float)
1554
    {
1555
        $t1 = pack('f', $float); // machine order
1556
        list(, $t2) = unpack('L*', $t1); // int in machine order
1557
        return pack('N', $t2);
1558
    }
1559
1560
    /**
1561
     * Add query to multi-query batch
1562
     * Returns index into results array from RunQueries() call
1563
     *
1564
     * @param string $query
1565
     * @param string $index
1566
     * @param string $comment
1567
     *
1568
     * @return int
1569
     */
1570
    public function addQuery($query, $index = '*', $comment = '')
1571
    {
1572
        // mbstring workaround
1573
        $this->mbPush();
1574
1575
        // build request
1576
        $req = pack('NNNNN', $this->query_flags, $this->offset, $this->limit, $this->mode, $this->ranker);
1577
        if ($this->ranker == SPH_RANK_EXPR) {
1578
            $req .= pack('N', strlen($this->rank_expr)) . $this->rank_expr;
1579
        }
1580
        $req .= pack('N', $this->sort); // (deprecated) sort mode
1581
        $req .= pack('N', strlen($this->sort_by)) . $this->sort_by;
1582
        $req .= pack('N', strlen($query)) . $query; // query itself
1583
        $req .= pack('N', count($this->weights)); // weights
1584
        foreach ($this->weights as $weight) {
1585
            $req .= pack('N', (int)$weight);
1586
        }
1587
        $req .= pack('N', strlen($index)) . $index; // indexes
1588
        $req .= pack('N', 1); // id64 range marker
1589
        $req .= sphPackU64($this->min_id) . sphPackU64($this->max_id); // id64 range
1590
1591
        // filters
1592
        $req .= pack('N', count($this->filters));
1593
        foreach ($this->filters as $filter) {
1594
            $req .= pack('N', strlen($filter['attr'])) . $filter['attr'];
1595
            $req .= pack('N', $filter['type']);
1596
            switch ($filter['type']) {
1597
                case SPH_FILTER_VALUES:
1598
                    $req .= pack('N', count($filter['values']));
1599
                    foreach ($filter['values'] as $value) {
1600
                        $req .= sphPackI64($value);
1601
                    }
1602
                    break;
1603
                case SPH_FILTER_RANGE:
1604
                    $req .= sphPackI64($filter['min']) . sphPackI64($filter['max']);
1605
                    break;
1606
                case SPH_FILTER_FLOATRANGE:
1607
                    $req .= $this->packFloat($filter['min']) . $this->packFloat($filter['max']);
1608
                    break;
1609
                case SPH_FILTER_STRING:
1610
                    $req .= pack('N', strlen($filter['value'])) . $filter['value'];
1611
                    break;
1612
                default:
1613
                    assert(0 && 'internal error: unhandled filter type');
1614
            }
1615
            $req .= pack('N', $filter['exclude']);
1616
        }
1617
1618
        // group-by clause, max-matches count, group-sort clause, cutoff count
1619
        $req .= pack('NN', $this->group_func, strlen($this->group_by)) . $this->group_by;
1620
        $req .= pack('N', $this->max_matches);
1621
        $req .= pack('N', strlen($this->group_sort)) . $this->group_sort;
1622
        $req .= pack('NNN', $this->cutoff, $this->retry_count, $this->retry_delay);
1623
        $req .= pack('N', strlen($this->group_distinct)) . $this->group_distinct;
1624
1625
        // anchor point
1626
        if (empty($this->anchor)) {
1627
            $req .= pack('N', 0);
1628
        } else {
1629
            $a =& $this->anchor;
1630
            $req .= pack('N', 1);
1631
            $req .= pack('N', strlen($a['attrlat'])) . $a['attrlat'];
1632
            $req .= pack('N', strlen($a['attrlong'])) . $a['attrlong'];
1633
            $req .= $this->packFloat($a['lat']) . $this->packFloat($a['long']);
1634
        }
1635
1636
        // per-index weights
1637
        $req .= pack('N', count($this->index_weights));
1638
        foreach ($this->index_weights as $idx => $weight) {
1639
            $req .= pack('N', strlen($idx)) . $idx . pack('N', $weight);
1640
        }
1641
1642
        // max query time
1643
        $req .= pack('N', $this->max_query_time);
1644
1645
        // per-field weights
1646
        $req .= pack('N', count($this->field_weights));
1647
        foreach ($this->field_weights as $field => $weight) {
1648
            $req .= pack('N', strlen($field)) . $field . pack('N', $weight);
1649
        }
1650
1651
        // comment
1652
        $req .= pack('N', strlen($comment)) . $comment;
1653
1654
        // attribute overrides
1655
        $req .= pack('N', count($this->overrides));
1656
        foreach ($this->overrides as $key => $entry) {
1657
            $req .= pack('N', strlen($entry['attr'])) . $entry['attr'];
1658
            $req .= pack('NN', $entry['type'], count($entry['values']));
1659
            foreach ($entry['values'] as $id => $val) {
1660
                assert(is_numeric($id));
1661
                assert(is_numeric($val));
1662
1663
                $req .= sphPackU64($id);
1664
                switch ($entry['type']) {
1665
                    case SPH_ATTR_FLOAT:
1666
                        $req .= $this->packFloat($val);
1667
                        break;
1668
                    case SPH_ATTR_BIGINT:
1669
                        $req .= sphPackI64($val);
1670
                        break;
1671
                    default:
1672
                        $req .= pack('N', $val);
1673
                        break;
1674
                }
1675
            }
1676
        }
1677
1678
        // select-list
1679
        $req .= pack('N', strlen($this->select)) . $this->select;
1680
1681
        // max_predicted_time
1682
        if ($this->predicted_time > 0) {
1683
            $req .= pack('N', (int)$this->predicted_time);
1684
        }
1685
1686
        $req .= pack('N', strlen($this->outer_order_by)) . $this->outer_order_by;
1687
        $req .= pack('NN', $this->outer_offset, $this->outer_limit);
1688
        if ($this->has_outer) {
1689
            $req .= pack('N', 1);
1690
        } else {
1691
            $req .= pack('N', 0);
1692
        }
1693
1694
        // mbstring workaround
1695
        $this->mbPop();
1696
1697
        // store request to requests array
1698
        $this->reqs[] = $req;
1699
        return count($this->reqs) - 1;
1700
    }
1701
1702
    /**
1703
     * Connect to searchd, run queries batch, and return an array of result sets
1704
     *
1705
     * @return array|bool
1706
     */
1707
    public function runQueries()
1708
    {
1709
        if (empty($this->reqs)) {
1710
            $this->error = 'no queries defined, issue AddQuery() first';
1711
            return false;
1712
        }
1713
1714
        // mbstring workaround
1715
        $this->mbPush();
1716
1717
        if (!($fp = $this->connect())) {
1718
            $this->mbPop();
1719
            return false;
1720
        }
1721
1722
        // send query, get response
1723
        $nreqs = count($this->reqs);
1724
        $req = join('', $this->reqs);
1725
        $len = 8 + strlen($req);
1726
        $req = pack('nnNNN', SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, $len, 0, $nreqs) . $req; // add header
1727
1728 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...
Documentation introduced by
$fp is of type boolean, but the function expects a resource.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1729
            $this->mbPop();
1730
            return false;
1731
        }
1732
1733
        // query sent ok; we can reset reqs now
1734
        $this->reqs = array();
1735
1736
        // parse and return response
1737
        return $this->parseSearchResponse($response, $nreqs);
1738
    }
1739
1740
    /**
1741
     * Parse and return search query (or queries) response
1742
     *
1743
     * @param string $response
1744
     * @param int $nreqs
1745
     *
1746
     * @return array
1747
     */
1748
    protected function parseSearchResponse($response, $nreqs)
1749
    {
1750
        $p = 0; // current position
1751
        $max = strlen($response); // max position for checks, to protect against broken responses
1752
1753
        $results = array();
1754
        for ($ires = 0; $ires < $nreqs && $p < $max; $ires++) {
1755
            $results[] = array();
1756
            $result =& $results[$ires];
1757
1758
            $result['error'] = '';
1759
            $result['warning'] = '';
1760
1761
            // extract status
1762
            list(, $status) = unpack('N*', substr($response, $p, 4));
1763
            $p += 4;
1764
            $result['status'] = $status;
1765
            if ($status != SEARCHD_OK) {
1766
                list(, $len) = unpack('N*', substr($response, $p, 4));
1767
                $p += 4;
1768
                $message = substr($response, $p, $len);
1769
                $p += $len;
1770
1771
                if ($status == SEARCHD_WARNING) {
1772
                    $result['warning'] = $message;
1773
                } else {
1774
                    $result['error'] = $message;
1775
                    continue;
1776
                }
1777
            }
1778
1779
            // read schema
1780
            $fields = array();
1781
            $attrs = array();
1782
1783
            list(, $nfields) = unpack('N*', substr($response, $p, 4));
1784
            $p += 4;
1785
            while ($nfields --> 0 && $p < $max) {
1786
                list(, $len) = unpack('N*', substr($response, $p, 4));
1787
                $p += 4;
1788
                $fields[] = substr($response, $p, $len);
1789
                $p += $len;
1790
            }
1791
            $result['fields'] = $fields;
1792
1793
            list(, $nattrs) = unpack('N*', substr($response, $p, 4));
1794
            $p += 4;
1795
            while ($nattrs --> 0 && $p < $max) {
1796
                list(, $len) = unpack('N*', substr($response, $p, 4));
1797
                $p += 4;
1798
                $attr = substr($response, $p, $len);
1799
                $p += $len;
1800
                list(, $type) = unpack('N*', substr($response, $p, 4));
1801
                $p += 4;
1802
                $attrs[$attr] = $type;
1803
            }
1804
            $result['attrs'] = $attrs;
1805
1806
            // read match count
1807
            list(, $count) = unpack('N*', substr($response, $p, 4));
1808
            $p += 4;
1809
            list(, $id64) = unpack('N*', substr($response, $p, 4));
1810
            $p += 4;
1811
1812
            // read matches
1813
            $idx = -1;
1814
            while ($count --> 0 && $p < $max) {
1815
                // index into result array
1816
                $idx++;
1817
1818
                // parse document id and weight
1819
                if ($id64) {
1820
                    $doc = sphUnpackU64(substr($response, $p, 8));
1821
                    $p += 8;
1822
                    list(,$weight) = unpack('N*', substr($response, $p, 4));
1823
                    $p += 4;
1824
                } else {
1825
                    list($doc, $weight) = array_values(unpack('N*N*', substr($response, $p, 8)));
1826
                    $p += 8;
1827
                    $doc = sphFixUint($doc);
1828
                }
1829
                $weight = sprintf('%u', $weight);
1830
1831
                // create match entry
1832 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...
1833
                    $result['matches'][$idx] = array('id' => $doc, 'weight' => $weight);
1834
                } else {
1835
                    $result['matches'][$doc]['weight'] = $weight;
1836
                }
1837
1838
                // parse and create attributes
1839
                $attrvals = array();
1840
                foreach ($attrs as $attr => $type) {
1841
                    // handle 64bit ints
1842
                    if ($type == SPH_ATTR_BIGINT) {
1843
                        $attrvals[$attr] = sphUnpackI64(substr($response, $p, 8));
1844
                        $p += 8;
1845
                        continue;
1846
                    }
1847
1848
                    // handle floats
1849
                    if ($type == SPH_ATTR_FLOAT) {
1850
                        list(, $uval) = unpack('N*', substr($response, $p, 4));
1851
                        $p += 4;
1852
                        list(, $fval) = unpack('f*', pack('L', $uval));
1853
                        $attrvals[$attr] = $fval;
1854
                        continue;
1855
                    }
1856
1857
                    // handle everything else as unsigned ints
1858
                    list(, $val) = unpack('N*', substr($response, $p, 4));
1859
                    $p += 4;
1860
                    if ($type == SPH_ATTR_MULTI) {
1861
                        $attrvals[$attr] = array();
1862
                        $nvalues = $val;
1863 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...
1864
                            list(, $val) = unpack('N*', substr($response, $p, 4));
1865
                            $p += 4;
1866
                            $attrvals[$attr][] = sphFixUint($val);
1867
                        }
1868
                    } elseif ($type == SPH_ATTR_MULTI64) {
1869
                        $attrvals[$attr] = array();
1870
                        $nvalues = $val;
1871 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...
1872
                            $attrvals[$attr][] = sphUnpackI64(substr($response, $p, 8));
1873
                            $p += 8;
1874
                            $nvalues -= 2;
1875
                        }
1876
                    } elseif ($type == SPH_ATTR_STRING) {
1877
                        $attrvals[$attr] = substr($response, $p, $val);
1878
                        $p += $val;
1879
                    } elseif ($type == SPH_ATTR_FACTORS) {
1880
                        $attrvals[$attr] = substr($response, $p, $val - 4);
1881
                        $p += $val-4;
1882
                    } else {
1883
                        $attrvals[$attr] = sphFixUint($val);
1884
                    }
1885
                }
1886
1887 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...
1888
                    $result['matches'][$idx]['attrs'] = $attrvals;
1889
                } else {
1890
                    $result['matches'][$doc]['attrs'] = $attrvals;
1891
                }
1892
            }
1893
1894
            list($total, $total_found, $msecs, $words) = array_values(unpack('N*N*N*N*', substr($response, $p, 16)));
1895
            $result['total'] = sprintf('%u', $total);
1896
            $result['total_found'] = sprintf('%u', $total_found);
1897
            $result['time'] = sprintf('%.3f', $msecs / 1000);
1898
            $p += 16;
1899
1900
            while ($words --> 0 && $p < $max) {
1901
                list(, $len) = unpack('N*', substr($response, $p, 4));
1902
                $p += 4;
1903
                $word = substr($response, $p, $len);
1904
                $p += $len;
1905
                list($docs, $hits) = array_values(unpack('N*N*', substr($response, $p, 8)));
1906
                $p += 8;
1907
                $result['words'][$word] = array (
1908
                    'docs' => sprintf('%u', $docs),
1909
                    'hits' => sprintf('%u', $hits)
1910
                );
1911
            }
1912
        }
1913
1914
        $this->mbPop();
1915
        return $results;
1916
    }
1917
1918
    /////////////////////////////////////////////////////////////////////////////
1919
    // excerpts generation
1920
    /////////////////////////////////////////////////////////////////////////////
1921
1922
    /**
1923
     * Connect to searchd server, and generate exceprts (snippets) of given documents for given query.
1924
     * Returns false on failure, an array of snippets on success
1925
     *
1926
     * @param array $docs
1927
     * @param string $index
1928
     * @param string $words
1929
     * @param array $opts
1930
     *
1931
     * @return array|bool
1932
     */
1933
    public function buildExcerpts(array $docs, $index, $words, array $opts = array())
1934
    {
1935
        assert(is_string($index));
1936
        assert(is_string($words));
1937
1938
        $this->mbPush();
1939
1940
        if (!($fp = $this->connect())) {
1941
            $this->mbPop();
1942
            return false;
1943
        }
1944
1945
        /////////////////
1946
        // fixup options
1947
        /////////////////
1948
1949
        $opts = array_merge(array(
1950
            'before_match' => '<b>',
1951
            'after_match' => '</b>',
1952
            'chunk_separator' => ' ... ',
1953
            'limit' => 256,
1954
            'limit_passages' => 0,
1955
            'limit_words' => 0,
1956
            'around' => 5,
1957
            'exact_phrase' => false,
1958
            'single_passage' => false,
1959
            'use_boundaries' => false,
1960
            'weight_order' => false,
1961
            'query_mode' => false,
1962
            'force_all_words' => false,
1963
            'start_passage_id' => 1,
1964
            'load_files' => false,
1965
            'html_strip_mode' => 'index',
1966
            'allow_empty' => false,
1967
            'passage_boundary' => 'none',
1968
            'emit_zones' => false,
1969
            'load_files_scattered' => false
1970
        ), $opts);
1971
1972
        /////////////////
1973
        // build request
1974
        /////////////////
1975
1976
        // v.1.2 req
1977
        $flags = 1; // remove spaces
1978
        if ($opts['exact_phrase']) {
1979
            $flags |= 2;
1980
        }
1981
        if ($opts['single_passage']) {
1982
            $flags |= 4;
1983
        }
1984
        if ($opts['use_boundaries']) {
1985
            $flags |= 8;
1986
        }
1987
        if ($opts['weight_order']) {
1988
            $flags |= 16;
1989
        }
1990
        if ($opts['query_mode']) {
1991
            $flags |= 32;
1992
        }
1993
        if ($opts['force_all_words']) {
1994
            $flags |= 64;
1995
        }
1996
        if ($opts['load_files']) {
1997
            $flags |= 128;
1998
        }
1999
        if ($opts['allow_empty']) {
2000
            $flags |= 256;
2001
        }
2002
        if ($opts['emit_zones']) {
2003
            $flags |= 512;
2004
        }
2005
        if ($opts['load_files_scattered']) {
2006
            $flags |= 1024;
2007
        }
2008
        $req = pack('NN', 0, $flags); // mode=0, flags=$flags
2009
        $req .= pack('N', strlen($index)) . $index; // req index
2010
        $req .= pack('N', strlen($words)) . $words; // req words
2011
2012
        // options
2013
        $req .= pack('N', strlen($opts['before_match'])) . $opts['before_match'];
2014
        $req .= pack('N', strlen($opts['after_match'])) . $opts['after_match'];
2015
        $req .= pack('N', strlen($opts['chunk_separator'])) . $opts['chunk_separator'];
2016
        $req .= pack('NN', (int)$opts['limit'], (int)$opts['around']);
2017
        $req .= pack('NNN', (int)$opts['limit_passages'], (int)$opts['limit_words'], (int)$opts['start_passage_id']); // v.1.2
2018
        $req .= pack('N', strlen($opts['html_strip_mode'])) . $opts['html_strip_mode'];
2019
        $req .= pack('N', strlen($opts['passage_boundary'])) . $opts['passage_boundary'];
2020
2021
        // documents
2022
        $req .= pack('N', count($docs));
2023
        foreach ($docs as $doc) {
2024
            assert(is_string($doc));
2025
            $req .= pack('N', strlen($doc)) . $doc;
2026
        }
2027
2028
        ////////////////////////////
2029
        // send query, get response
2030
        ////////////////////////////
2031
2032
        $len = strlen($req);
2033
        $req = pack('nnN', SEARCHD_COMMAND_EXCERPT, VER_COMMAND_EXCERPT, $len) . $req; // add header
2034 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...
Documentation introduced by
$fp is of type boolean, but the function expects a resource.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2035
            $this->mbPop();
2036
            return false;
2037
        }
2038
2039
        //////////////////
2040
        // parse response
2041
        //////////////////
2042
2043
        $pos = 0;
2044
        $res = array();
2045
        $rlen = strlen($response);
2046
        $count = count($docs);
2047
        while ($count--) {
2048
            list(, $len) = unpack('N*', substr($response, $pos, 4));
2049
            $pos += 4;
2050
2051
            if ($pos + $len > $rlen) {
2052
                $this->error = 'incomplete reply';
2053
                $this->mbPop();
2054
                return false;
2055
            }
2056
            $res[] = $len ? substr($response, $pos, $len) : '';
2057
            $pos += $len;
2058
        }
2059
2060
        $this->mbPop();
2061
        return $res;
2062
    }
2063
2064
2065
    /////////////////////////////////////////////////////////////////////////////
2066
    // keyword generation
2067
    /////////////////////////////////////////////////////////////////////////////
2068
2069
    /**
2070
     * Connect to searchd server, and generate keyword list for a given query returns false on failure,
2071
     * an array of words on success
2072
     *
2073
     * @param string $query
2074
     * @param string $index
2075
     * @param bool $hits
2076
     *
2077
     * @return array|bool
2078
     */
2079
    public function buildKeywords($query, $index, $hits)
2080
    {
2081
        assert(is_string($query));
2082
        assert(is_string($index));
2083
        assert(is_bool($hits));
2084
2085
        $this->mbPush();
2086
2087
        if (!($fp = $this->connect())) {
2088
            $this->mbPop();
2089
            return false;
2090
        }
2091
2092
        /////////////////
2093
        // build request
2094
        /////////////////
2095
2096
        // v.1.0 req
2097
        $req  = pack('N', strlen($query)) . $query; // req query
2098
        $req .= pack('N', strlen($index)) . $index; // req index
2099
        $req .= pack('N', (int)$hits);
2100
2101
        ////////////////////////////
2102
        // send query, get response
2103
        ////////////////////////////
2104
2105
        $len = strlen($req);
2106
        $req = pack('nnN', SEARCHD_COMMAND_KEYWORDS, VER_COMMAND_KEYWORDS, $len) . $req; // add header
2107 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...
Documentation introduced by
$fp is of type boolean, but the function expects a resource.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2108
            $this->mbPop();
2109
            return false;
2110
        }
2111
2112
        //////////////////
2113
        // parse response
2114
        //////////////////
2115
2116
        $pos = 0;
2117
        $res = array();
2118
        $rlen = strlen($response);
2119
        list(, $nwords) = unpack('N*', substr($response, $pos, 4));
2120
        $pos += 4;
2121
        for ($i = 0; $i < $nwords; $i++) {
2122
            list(, $len) = unpack('N*', substr($response, $pos, 4));
2123
            $pos += 4;
2124
            $tokenized = $len ? substr($response, $pos, $len) : '';
2125
            $pos += $len;
2126
2127
            list(, $len) = unpack('N*', substr($response, $pos, 4));
2128
            $pos += 4;
2129
            $normalized = $len ? substr($response, $pos, $len) : '';
2130
            $pos += $len;
2131
2132
            $res[] = array(
2133
                'tokenized' => $tokenized,
2134
                'normalized' => $normalized
2135
            );
2136
2137
            if ($hits) {
2138
                list($ndocs, $nhits) = array_values(unpack('N*N*', substr($response, $pos, 8)));
2139
                $pos += 8;
2140
                $res[$i]['docs'] = $ndocs;
2141
                $res[$i]['hits'] = $nhits;
2142
            }
2143
2144
            if ($pos > $rlen) {
2145
                $this->error = 'incomplete reply';
2146
                $this->mbPop();
2147
                return false;
2148
            }
2149
        }
2150
2151
        $this->mbPop();
2152
        return $res;
2153
    }
2154
2155
    /**
2156
     * @param string $string
2157
     *
2158
     * @return string
2159
     */
2160
    public function escapeString($string)
2161
    {
2162
        $from = array('\\', '(',')','|','-','!','@','~','"','&', '/', '^', '$', '=', '<');
2163
        $to   = array('\\\\', '\(','\)','\|','\-','\!','\@','\~','\"', '\&', '\/', '\^', '\$', '\=', '\<');
2164
2165
        return str_replace($from, $to, $string);
2166
    }
2167
2168
    /////////////////////////////////////////////////////////////////////////////
2169
    // attribute updates
2170
    /////////////////////////////////////////////////////////////////////////////
2171
2172
    /**
2173
     * Batch update given attributes in given rows in given indexes
2174
     * Returns amount of updated documents (0 or more) on success, or -1 on failure
2175
     *
2176
     * @param string $index
2177
     * @param array $attrs
2178
     * @param array $values
2179
     * @param bool $mva
2180
     * @param bool $ignore_non_existent
2181
     *
2182
     * @return int
2183
     */
2184
    public function updateAttributes($index, array $attrs, array $values, $mva = false, $ignore_non_existent = false)
2185
    {
2186
        // verify everything
2187
        assert(is_string($index));
2188
        assert(is_bool($mva));
2189
        assert(is_bool($ignore_non_existent));
2190
2191
        foreach ($attrs as $attr) {
2192
            assert(is_string($attr));
2193
        }
2194
2195
        foreach ($values as $id => $entry) {
2196
            assert(is_numeric($id));
2197
            assert(is_array($entry));
2198
            assert(count($entry) == count($attrs));
2199
            foreach ($entry as $v) {
2200
                if ($mva) {
2201
                    assert(is_array($v));
2202
                    foreach ($v as $vv) {
2203
                        assert(is_int($vv));
2204
                    }
2205
                } else {
2206
                    assert(is_int($v));
2207
                }
2208
            }
2209
        }
2210
2211
        // build request
2212
        $this->mbPush();
2213
        $req = pack('N', strlen($index)) . $index;
2214
2215
        $req .= pack('N', count($attrs));
2216
        $req .= pack('N', $ignore_non_existent ? 1 : 0);
2217
        foreach ($attrs as $attr) {
2218
            $req .= pack('N', strlen($attr)) . $attr;
2219
            $req .= pack('N', $mva ? 1 : 0);
2220
        }
2221
2222
        $req .= pack('N', count($values));
2223
        foreach ($values as $id => $entry) {
2224
            $req .= sphPackU64($id);
2225
            foreach ($entry as $v) {
2226
                $req .= pack('N', $mva ? count($v) : $v);
2227
                if ($mva) {
2228
                    foreach ($v as $vv) {
2229
                        $req .= pack('N', $vv);
2230
                    }
2231
                }
2232
            }
2233
        }
2234
2235
        // connect, send query, get response
2236
        if (!($fp = $this->connect())) {
2237
            $this->mbPop();
2238
            return -1;
2239
        }
2240
2241
        $len = strlen($req);
2242
        $req = pack('nnN', SEARCHD_COMMAND_UPDATE, VER_COMMAND_UPDATE, $len) . $req; // add header
2243
        if (!$this->send($fp, $req, $len + 8)) {
0 ignored issues
show
Documentation introduced by
$fp is of type boolean, but the function expects a resource.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2244
            $this->mbPop();
2245
            return -1;
2246
        }
2247
2248
        if (!($response = $this->getResponse($fp, VER_COMMAND_UPDATE))) {
0 ignored issues
show
Documentation introduced by
$fp is of type boolean, but the function expects a resource.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2249
            $this->mbPop();
2250
            return -1;
2251
        }
2252
2253
        // parse response
2254
        list(, $updated) = unpack('N*', substr($response, 0, 4));
2255
        $this->mbPop();
2256
        return $updated;
2257
    }
2258
2259
    /////////////////////////////////////////////////////////////////////////////
2260
    // persistent connections
2261
    /////////////////////////////////////////////////////////////////////////////
2262
2263
    /**
2264
     * @return bool
2265
     */
2266
    public function open()
2267
    {
2268
        if ($this->socket !== false) {
2269
            $this->error = 'already connected';
2270
            return false;
2271
        }
2272
        if (!($fp = $this->connect()))
2273
            return false;
2274
2275
        // command, command version = 0, body length = 4, body = 1
2276
        $req = pack('nnNN', SEARCHD_COMMAND_PERSIST, 0, 4, 1);
2277
        if (!$this->send($fp, $req, 12)) {
0 ignored issues
show
Documentation introduced by
$fp is of type boolean, but the function expects a resource.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2278
            return false;
2279
        }
2280
2281
        $this->socket = $fp;
2282
        return true;
2283
    }
2284
2285
    /**
2286
     * @return bool
2287
     */
2288
    public function close()
2289
    {
2290
        if ($this->socket === false) {
2291
            $this->error = 'not connected';
2292
            return false;
2293
        }
2294
2295
        fclose($this->socket);
2296
        $this->socket = false;
2297
2298
        return true;
2299
    }
2300
2301
    //////////////////////////////////////////////////////////////////////////
2302
    // status
2303
    //////////////////////////////////////////////////////////////////////////
2304
2305
    /**
2306
     * @param bool $session
2307
     *
2308
     * @return array|bool
2309
     */
2310
    public function status($session = false)
2311
    {
2312
        assert(is_bool($session));
2313
2314
        $this->mbPush();
2315
        if (!($fp = $this->connect())) {
2316
            $this->mbPop();
2317
            return false;
2318
        }
2319
2320
        $req = pack('nnNN', SEARCHD_COMMAND_STATUS, VER_COMMAND_STATUS, 4, $session ? 0 : 1); // len=4, body=1
2321 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...
Documentation introduced by
$fp is of type boolean, but the function expects a resource.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2322
            $this->mbPop();
2323
            return false;
2324
        }
2325
2326
        $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...
2327
        $p = 0;
2328
        list($rows, $cols) = array_values(unpack('N*N*', substr($response, $p, 8)));
2329
        $p += 8;
2330
2331
        $res = array();
2332
        for ($i = 0; $i < $rows; $i++) {
2333
            for ($j = 0; $j < $cols; $j++) {
2334
                list(, $len) = unpack('N*', substr($response, $p, 4));
2335
                $p += 4;
2336
                $res[$i][] = substr($response, $p, $len);
2337
                $p += $len;
2338
            }
2339
        }
2340
2341
        $this->mbPop();
2342
        return $res;
2343
    }
2344
2345
    //////////////////////////////////////////////////////////////////////////
2346
    // flush
2347
    //////////////////////////////////////////////////////////////////////////
2348
2349
    /**
2350
     * @return int
2351
     */
2352
    public function flushAttributes()
2353
    {
2354
        $this->mbPush();
2355
        if (!($fp = $this->connect())) {
2356
            $this->mbPop();
2357
            return -1;
2358
        }
2359
2360
        $req = pack('nnN', SEARCHD_COMMAND_FLUSHATTRS, VER_COMMAND_FLUSHATTRS, 0); // len=0
2361 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...
Documentation introduced by
$fp is of type boolean, but the function expects a resource.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2362
            $this->mbPop();
2363
            return -1;
2364
        }
2365
2366
        $tag = -1;
2367
        if (strlen($response) == 4) {
2368
            list(, $tag) = unpack('N*', $response);
2369
        } else {
2370
            $this->error = 'unexpected response length';
2371
        }
2372
2373
        $this->mbPop();
2374
        return $tag;
2375
    }
2376
}
2377