Completed
Push — master ( ddbd68...542252 )
by Peter
02:30
created

Client::isConnectError()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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