Passed
Push — master ( 51be47...3d97d9 )
by Hung
02:14
created

SphinxQL::setType()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Foolz\SphinxQL;
4
5
use Foolz\SphinxQL\Drivers\ConnectionInterface;
6
use Foolz\SphinxQL\Drivers\MultiResultSetInterface;
7
use Foolz\SphinxQL\Drivers\ResultSetInterface;
8
use Foolz\SphinxQL\Exception\ConnectionException;
9
use Foolz\SphinxQL\Exception\DatabaseException;
10
use Foolz\SphinxQL\Exception\SphinxQLException;
11
12
/**
13
 * Query Builder class for SphinxQL statements.
14
 */
15
class SphinxQL
16
{
17
    /**
18
     * A non-static connection for the current SphinxQL object
19
     *
20
     * @var ConnectionInterface
21
     */
22
    protected $connection;
23
24
    /**
25
     * The last result object.
26
     *
27
     * @var array
28
     */
29
    protected $last_result;
30
31
    /**
32
     * The last compiled query.
33
     *
34
     * @var string
35
     */
36
    protected $last_compiled;
37
38
    /**
39
     * The last chosen method (select, insert, replace, update, delete).
40
     *
41
     * @var string
42
     */
43
    protected $type;
44
45
    /**
46
     * An SQL query that is not yet executed or "compiled"
47
     *
48
     * @var string
49
     */
50
    protected $query;
51
52
    /**
53
     * Array of select elements that will be comma separated.
54
     *
55
     * @var array
56
     */
57
    protected $select = array();
58
59
    /**
60
     * From in SphinxQL is the list of indexes that will be used
61
     *
62
     * @var array
63
     */
64
    protected $from = array();
65
66
    /**
67
     * The list of where and parenthesis, must be inserted in order
68
     *
69
     * @var array
70
     */
71
    protected $where = array();
72
73
    /**
74
     * The list of matches for the MATCH function in SphinxQL
75
     *
76
     * @var array
77
     */
78
    protected $match = array();
79
80
    /**
81
     * GROUP BY array to be comma separated
82
     *
83
     * @var array
84
     */
85
    protected $group_by = array();
86
87
    /**
88
     * When not null changes 'GROUP BY' to 'GROUP N BY'
89
     *
90
     * @var null|int
91
     */
92
    protected $group_n_by;
93
94
    /**
95
     * ORDER BY array
96
     *
97
     * @var array
98
     */
99
    protected $within_group_order_by = array();
100
101
    /**
102
     * The list of where and parenthesis, must be inserted in order
103
     *
104
     * @var array
105
     */
106
    protected $having = array();
107
108
    /**
109
     * ORDER BY array
110
     *
111
     * @var array
112
     */
113
    protected $order_by = array();
114
115
    /**
116
     * When not null it adds an offset
117
     *
118
     * @var null|int
119
     */
120
    protected $offset;
121
122
    /**
123
     * When not null it adds a limit
124
     *
125
     * @var null|int
126
     */
127
    protected $limit;
128
129
    /**
130
     * Value of INTO query for INSERT or REPLACE
131
     *
132
     * @var null|string
133
     */
134
    protected $into;
135
136
    /**
137
     * Array of columns for INSERT or REPLACE
138
     *
139
     * @var array
140
     */
141
    protected $columns = array();
142
143
    /**
144
     * Array OF ARRAYS of values for INSERT or REPLACE
145
     *
146
     * @var array
147
     */
148
    protected $values = array();
149
150
    /**
151
     * Array arrays containing column and value for SET in UPDATE
152
     *
153
     * @var array
154
     */
155
    protected $set = array();
156
157
    /**
158
     * Array of OPTION specific to SphinxQL
159
     *
160
     * @var array
161
     */
162
    protected $options = array();
163
164
    /**
165
     * Array of FACETs
166
     *
167
     * @var Facet[]
168
     */
169
    protected $facets = array();
170
171
    /**
172
     * The reference to the object that queued itself and created this object
173
     *
174
     * @var null|SphinxQL
175
     */
176
    protected $queue_prev;
177
178
    /**
179
     * An array of escaped characters for escapeMatch()
180
     * @var array
181
     */
182
    protected $escape_full_chars = array(
183
        '\\' => '\\\\',
184
        '('  => '\(',
185
        ')'  => '\)',
186
        '|'  => '\|',
187
        '-'  => '\-',
188
        '!'  => '\!',
189
        '@'  => '\@',
190
        '~'  => '\~',
191
        '"'  => '\"',
192
        '&'  => '\&',
193
        '/'  => '\/',
194
        '^'  => '\^',
195
        '$'  => '\$',
196
        '='  => '\=',
197
        '<'  => '\<',
198
    );
199
200
    /**
201
     * An array of escaped characters for fullEscapeMatch()
202
     * @var array
203
     */
204
    protected $escape_half_chars = array(
205
        '\\' => '\\\\',
206
        '('  => '\(',
207
        ')'  => '\)',
208
        '!'  => '\!',
209
        '@'  => '\@',
210
        '~'  => '\~',
211
        '&'  => '\&',
212
        '/'  => '\/',
213
        '^'  => '\^',
214
        '$'  => '\$',
215
        '='  => '\=',
216
        '<'  => '\<',
217
    );
218
219
    /**
220
     * @param ConnectionInterface|null $connection
221
     */
222
    public function __construct(ConnectionInterface $connection = null)
223
    {
224
        $this->connection = $connection;
225
    }
226
    
227
    /**
228
     * Sets Query Type
229
     *
230
     */
231
    public function setType(string $type)
232
    {
233
        return $this->type = $type;
234
    }    
235
236
    /**
237
     * Returns the currently attached connection
238
     *
239
     * @returns ConnectionInterface
240
     */
241
    public function getConnection()
242
    {
243
        return $this->connection;
244
    }
245
246
    /**
247
     * Avoids having the expressions escaped
248
     *
249
     * Examples:
250
     *    $query->where('time', '>', SphinxQL::expr('CURRENT_TIMESTAMP'));
251
     *    // WHERE time > CURRENT_TIMESTAMP
252
     *
253
     * @param string $string The string to keep unaltered
254
     *
255
     * @return Expression The new Expression
256
     * @todo make non static
257
     */
258
    public static function expr($string = '')
259
    {
260
        return new Expression($string);
261
    }
262
263
    /**
264
     * Runs the query built
265
     *
266
     * @return ResultSetInterface The result of the query
267
     * @throws DatabaseException
268
     * @throws ConnectionException
269
     * @throws SphinxQLException
270
     */
271
    public function execute()
272
    {
273
        // pass the object so execute compiles it by itself
274
        return $this->last_result = $this->getConnection()->query($this->compile()->getCompiled());
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getConnection()->...mpile()->getCompiled()) of type Foolz\SphinxQL\Drivers\ResultSetInterface is incompatible with the declared type array of property $last_result.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
275
    }
276
277
    /**
278
     * Executes a batch of queued queries
279
     *
280
     * @return MultiResultSetInterface The array of results
281
     * @throws SphinxQLException In case no query is in queue
282
     * @throws Exception\DatabaseException
283
     * @throws ConnectionException
284
     */
285
    public function executeBatch()
286
    {
287
        if (count($this->getQueue()) == 0) {
288
            throw new SphinxQLException('There is no Queue present to execute.');
289
        }
290
291
        $queue = array();
292
293
        foreach ($this->getQueue() as $query) {
294
            $queue[] = $query->compile()->getCompiled();
295
        }
296
297
        return $this->last_result = $this->getConnection()->multiQuery($queue);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getConnection()->multiQuery($queue) of type Foolz\SphinxQL\Drivers\MultiResultSetInterface is incompatible with the declared type array of property $last_result.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
298
    }
299
300
    /**
301
     * Enqueues the current object and returns a new one or the supplied one
302
     *
303
     * @param SphinxQL|null $next
304
     *
305
     * @return SphinxQL A new SphinxQL object with the current object referenced
306
     */
307
    public function enqueue(SphinxQL $next = null)
308
    {
309
        if ($next === null) {
310
            $next = new static($this->getConnection());
311
        }
312
313
        $next->setQueuePrev($this);
314
315
        return $next;
316
    }
317
318
    /**
319
     * Returns the ordered array of enqueued objects
320
     *
321
     * @return SphinxQL[] The ordered array of enqueued objects
322
     */
323
    public function getQueue()
324
    {
325
        $queue = array();
326
        $curr = $this;
327
328
        do {
329
            if ($curr->type != null) {
330
                $queue[] = $curr;
331
            }
332
        } while ($curr = $curr->getQueuePrev());
333
334
        return array_reverse($queue);
335
    }
336
337
    /**
338
     * Gets the enqueued object
339
     *
340
     * @return SphinxQL|null
341
     */
342
    public function getQueuePrev()
343
    {
344
        return $this->queue_prev;
345
    }
346
347
    /**
348
     * Sets the reference to the enqueued object
349
     *
350
     * @param SphinxQL $query The object to set as previous
351
     *
352
     * @return $this
353
     */
354
    public function setQueuePrev($query)
355
    {
356
        $this->queue_prev = $query;
357
358
        return $this;
359
    }
360
361
    /**
362
     * Returns the result of the last query
363
     *
364
     * @return array The result of the last query
365
     */
366
    public function getResult()
367
    {
368
        return $this->last_result;
369
    }
370
371
    /**
372
     * Returns the latest compiled query
373
     *
374
     * @return string The last compiled query
375
     */
376
    public function getCompiled()
377
    {
378
        return $this->last_compiled;
379
    }
380
381
    /**
382
     * Begins transaction
383
     * @throws DatabaseException
384
     * @throws ConnectionException
385
     */
386
    public function transactionBegin()
387
    {
388
        $this->getConnection()->query('BEGIN');
389
    }
390
391
    /**
392
     * Commits transaction
393
     * @throws DatabaseException
394
     * @throws ConnectionException
395
     */
396
    public function transactionCommit()
397
    {
398
        $this->getConnection()->query('COMMIT');
399
    }
400
401
    /**
402
     * Rollbacks transaction
403
     * @throws DatabaseException
404
     * @throws ConnectionException
405
     */
406
    public function transactionRollback()
407
    {
408
        $this->getConnection()->query('ROLLBACK');
409
    }
410
411
    /**
412
     * Runs the compile function
413
     *
414
     * @return $this
415
     * @throws ConnectionException
416
     * @throws DatabaseException
417
     * @throws SphinxQLException
418
     */
419
    public function compile()
420
    {
421
        switch ($this->type) {
422
            case 'select':
423
                $this->compileSelect();
424
                break;
425
            case 'insert':
426
            case 'replace':
427
                $this->compileInsert();
428
                break;
429
            case 'update':
430
                $this->compileUpdate();
431
                break;
432
            case 'delete':
433
                $this->compileDelete();
434
                break;
435
            case 'query':
436
                $this->compileQuery();
437
                break;
438
        }
439
440
        return $this;
441
    }
442
443
    /**
444
     * @return $this
445
     */
446
    public function compileQuery()
447
    {
448
        $this->last_compiled = $this->query;
449
450
        return $this;
451
    }
452
453
    /**
454
     * Compiles the MATCH part of the queries
455
     * Used by: SELECT, DELETE, UPDATE
456
     *
457
     * @return string The compiled MATCH
458
     * @throws Exception\ConnectionException
459
     * @throws Exception\DatabaseException
460
     */
461
    public function compileMatch()
462
    {
463
        $query = '';
464
465
        if (!empty($this->match)) {
466
            $query .= 'WHERE MATCH(';
467
468
            $matched = array();
469
470
            foreach ($this->match as $match) {
471
                $pre = '';
472
                if ($match['column'] instanceof \Closure) {
473
                    $sub = new Match($this);
474
                    call_user_func($match['column'], $sub);
475
                    $pre .= $sub->compile()->getCompiled();
476
                } elseif ($match['column'] instanceof Match) {
477
                    $pre .= $match['column']->compile()->getCompiled();
478
                } elseif (empty($match['column'])) {
479
                    $pre .= '';
480
                } elseif (is_array($match['column'])) {
481
                    $pre .= '@('.implode(',', $match['column']).') ';
482
                } else {
483
                    $pre .= '@'.$match['column'].' ';
484
                }
485
486
                if ($match['half']) {
487
                    $pre .= $this->halfEscapeMatch($match['value']);
488
                } else {
489
                    $pre .= $this->escapeMatch($match['value']);
490
                }
491
492
                if ($pre !== '') {
493
                    $matched[] = '('.$pre.')';
494
                }
495
            }
496
497
            $matched = implode(' ', $matched);
498
            $query .= $this->getConnection()->escape(trim($matched)).') ';
499
        }
500
501
        return $query;
502
    }
503
504
    /**
505
     * Compiles the WHERE part of the queries
506
     * It interacts with the MATCH() and of course isn't usable stand-alone
507
     * Used by: SELECT, DELETE, UPDATE
508
     *
509
     * @return string The compiled WHERE
510
     * @throws ConnectionException
511
     * @throws DatabaseException
512
     */
513
    public function compileWhere()
514
    {
515
        $query = '';
516
517
        if (empty($this->match) && !empty($this->where)) {
518
            $query .= 'WHERE ';
519
        }
520
521
        if (!empty($this->where)) {
522
            foreach ($this->where as $key => $where) {
523
                if ($key > 0 || !empty($this->match)) {
524
                    $query .= 'AND ';
525
                }
526
                $query .= $this->compileFilterCondition($where);
527
            }
528
        }
529
530
        return $query;
531
    }
532
533
    /**
534
     * @param array $filter
535
     *
536
     * @return string
537
     * @throws ConnectionException
538
     * @throws DatabaseException
539
     */
540
    public function compileFilterCondition($filter)
541
    {
542
        $query = '';
543
544
        if (!empty($filter)) {
545
            if (strtoupper($filter['operator']) === 'BETWEEN') {
546
                $query .= $filter['column'];
547
                $query .= ' BETWEEN ';
548
                $query .= $this->getConnection()->quote($filter['value'][0]).' AND '
549
                    .$this->getConnection()->quote($filter['value'][1]).' ';
550
            } else {
551
                // id can't be quoted!
552
                if ($filter['column'] === 'id') {
553
                    $query .= 'id ';
554
                } else {
555
                    $query .= $filter['column'].' ';
556
                }
557
558
                if (in_array(strtoupper($filter['operator']), array('IN', 'NOT IN'), true)) {
559
                    $query .= strtoupper($filter['operator']).' ('.implode(', ', $this->getConnection()->quoteArr($filter['value'])).') ';
560
                } else {
561
                    $query .= $filter['operator'].' '.$this->getConnection()->quote($filter['value']).' ';
562
                }
563
            }
564
        }
565
566
        return $query;
567
    }
568
569
    /**
570
     * Compiles the statements for SELECT
571
     *
572
     * @return $this
573
     * @throws ConnectionException
574
     * @throws DatabaseException
575
     * @throws SphinxQLException
576
     */
577
    public function compileSelect()
578
    {
579
        $query = '';
580
581
        if ($this->type == 'select') {
582
            $query .= 'SELECT ';
583
584
            if (!empty($this->select)) {
585
                $query .= implode(', ', $this->select).' ';
586
            } else {
587
                $query .= '* ';
588
            }
589
        }
590
591
        if (!empty($this->from)) {
592
            if ($this->from instanceof \Closure) {
0 ignored issues
show
introduced by
$this->from is never a sub-type of Closure.
Loading history...
593
                $sub = new static($this->getConnection());
594
                call_user_func($this->from, $sub);
595
                $query .= 'FROM ('.$sub->compile()->getCompiled().') ';
596
            } elseif ($this->from instanceof SphinxQL) {
0 ignored issues
show
introduced by
$this->from is never a sub-type of Foolz\SphinxQL\SphinxQL.
Loading history...
597
                $query .= 'FROM ('.$this->from->compile()->getCompiled().') ';
598
            } else {
599
                $query .= 'FROM '.implode(', ', $this->from).' ';
600
            }
601
        }
602
603
        $query .= $this->compileMatch().$this->compileWhere();
604
605
        if (!empty($this->group_by)) {
606
            $query .= 'GROUP ';
607
            if ($this->group_n_by !== null) {
608
                $query .= $this->group_n_by.' ';
609
            }
610
            $query .= 'BY '.implode(', ', $this->group_by).' ';
611
        }
612
613
        if (!empty($this->within_group_order_by)) {
614
            $query .= 'WITHIN GROUP ORDER BY ';
615
616
            $order_arr = array();
617
618
            foreach ($this->within_group_order_by as $order) {
619
                $order_sub = $order['column'].' ';
620
621
                if ($order['direction'] !== null) {
622
                    $order_sub .= ((strtolower($order['direction']) === 'desc') ? 'DESC' : 'ASC');
623
                }
624
625
                $order_arr[] = $order_sub;
626
            }
627
628
            $query .= implode(', ', $order_arr).' ';
629
        }
630
631
        if (!empty($this->having)) {
632
            $query .= 'HAVING '.$this->compileFilterCondition($this->having);
633
        }
634
635
        if (!empty($this->order_by)) {
636
            $query .= 'ORDER BY ';
637
638
            $order_arr = array();
639
640
            foreach ($this->order_by as $order) {
641
                $order_sub = $order['column'].' ';
642
643
                if ($order['direction'] !== null) {
644
                    $order_sub .= ((strtolower($order['direction']) === 'desc') ? 'DESC' : 'ASC');
645
                }
646
647
                $order_arr[] = $order_sub;
648
            }
649
650
            $query .= implode(', ', $order_arr).' ';
651
        }
652
653
        if ($this->limit !== null || $this->offset !== null) {
654
            if ($this->offset === null) {
655
                $this->offset = 0;
656
            }
657
658
            if ($this->limit === null) {
659
                $this->limit = 9999999999999;
660
            }
661
662
            $query .= 'LIMIT '.((int) $this->offset).', '.((int) $this->limit).' ';
663
        }
664
665
        if (!empty($this->options)) {
666
            $options = array();
667
668
            foreach ($this->options as $option) {
669
                if ($option['value'] instanceof Expression) {
670
                    $option['value'] = $option['value']->value();
671
                } elseif (is_array($option['value'])) {
672
                    array_walk(
673
                        $option['value'],
674
                        function (&$val, $key) {
675
                            $val = $key.'='.$val;
676
                        }
677
                    );
678
                    $option['value'] = '('.implode(', ', $option['value']).')';
679
                } else {
680
                    $option['value'] = $this->getConnection()->quote($option['value']);
681
                }
682
683
                $options[] = $option['name'].' = '.$option['value'];
684
            }
685
686
            $query .= 'OPTION '.implode(', ', $options).' ';
687
        }
688
689
        if (!empty($this->facets)) {
690
            $facets = array();
691
692
            foreach ($this->facets as $facet) {
693
                // dynamically set the own SphinxQL connection if the Facet doesn't own one
694
                if ($facet->getConnection() === null) {
695
                    $facet->setConnection($this->getConnection());
696
                    $facets[] = $facet->getFacet();
697
                    // go back to the status quo for reuse
698
                    $facet->setConnection();
699
                } else {
700
                    $facets[] = $facet->getFacet();
701
                }
702
            }
703
704
            $query .= implode(' ', $facets);
705
        }
706
707
        $query = trim($query);
708
        $this->last_compiled = $query;
709
710
        return $this;
711
    }
712
713
    /**
714
     * Compiles the statements for INSERT or REPLACE
715
     *
716
     * @return $this
717
     * @throws ConnectionException
718
     * @throws DatabaseException
719
     */
720
    public function compileInsert()
721
    {
722
        if ($this->type == 'insert') {
723
            $query = 'INSERT ';
724
        } else {
725
            $query = 'REPLACE ';
726
        }
727
728
        if ($this->into !== null) {
729
            $query .= 'INTO '.$this->into.' ';
730
        }
731
732
        if (!empty($this->columns)) {
733
            $query .= '('.implode(', ', $this->columns).') ';
734
        }
735
736
        if (!empty($this->values)) {
737
            $query .= 'VALUES ';
738
            $query_sub = array();
739
740
            foreach ($this->values as $value) {
741
                $query_sub[] = '('.implode(', ', $this->getConnection()->quoteArr($value)).')';
742
            }
743
744
            $query .= implode(', ', $query_sub);
745
        }
746
747
        $query = trim($query);
748
        $this->last_compiled = $query;
749
750
        return $this;
751
    }
752
753
    /**
754
     * Compiles the statements for UPDATE
755
     *
756
     * @return $this
757
     * @throws ConnectionException
758
     * @throws DatabaseException
759
     */
760
    public function compileUpdate()
761
    {
762
        $query = 'UPDATE ';
763
764
        if ($this->into !== null) {
765
            $query .= $this->into.' ';
766
        }
767
768
        if (!empty($this->set)) {
769
            $query .= 'SET ';
770
771
            $query_sub = array();
772
773
            foreach ($this->set as $column => $value) {
774
                // MVA support
775
                if (is_array($value)) {
776
                    $query_sub[] = $column
777
                        .' = ('.implode(', ', $this->getConnection()->quoteArr($value)).')';
778
                } else {
779
                    $query_sub[] = $column
780
                        .' = '.$this->getConnection()->quote($value);
781
                }
782
            }
783
784
            $query .= implode(', ', $query_sub).' ';
785
        }
786
787
        $query .= $this->compileMatch().$this->compileWhere();
788
789
        $query = trim($query);
790
        $this->last_compiled = $query;
791
792
        return $this;
793
    }
794
795
    /**
796
     * Compiles the statements for DELETE
797
     *
798
     * @return $this
799
     * @throws ConnectionException
800
     * @throws DatabaseException
801
     */
802
    public function compileDelete()
803
    {
804
        $query = 'DELETE ';
805
806
        if (!empty($this->from)) {
807
            $query .= 'FROM '.$this->from[0].' ';
808
        }
809
810
        if (!empty($this->where)) {
811
            $query .= $this->compileWhere();
812
        }
813
814
        $query = trim($query);
815
        $this->last_compiled = $query;
816
817
        return $this;
818
    }
819
820
    /**
821
     * Sets a query to be executed
822
     *
823
     * @param string $sql A SphinxQL query to execute
824
     *
825
     * @return $this
826
     */
827
    public function query($sql)
828
    {
829
        $this->type = 'query';
830
        $this->query = $sql;
831
832
        return $this;
833
    }
834
835
    /**
836
     * Select the columns
837
     *
838
     * Gets the arguments passed as $sphinxql->select('one', 'two')
839
     * Using it without arguments equals to having '*' as argument
840
     * Using it with array maps values as column names
841
     *
842
     * Examples:
843
     *    $query->select('title');
844
     *    // SELECT title
845
     *
846
     *    $query->select('title', 'author', 'date');
847
     *    // SELECT title, author, date
848
     *
849
     *    $query->select(['id', 'title']);
850
     *    // SELECT id, title
851
     *
852
     * @param array|string $columns Array or multiple string arguments containing column names
853
     *
854
     * @return $this
855
     */
856
    public function select($columns = null)
857
    {
858
        $this->reset();
859
        $this->type = 'select';
860
861
        if (is_array($columns)) {
862
            $this->select = $columns;
863
        } else {
864
            $this->select = \func_get_args();
865
        }
866
867
        return $this;
868
    }
869
870
    /**
871
     * Alters which arguments to select
872
     *
873
     * Query is assumed to be in SELECT mode
874
     * See select() for usage
875
     *
876
     * @param array|string $columns Array or multiple string arguments containing column names
877
     *
878
     * @return $this
879
     */
880
    public function setSelect($columns = null)
881
    {
882
        if (is_array($columns)) {
883
            $this->select = $columns;
884
        } else {
885
            $this->select = \func_get_args();
886
        }
887
888
        return $this;
889
    }
890
891
    /**
892
     * Get the columns staged to select
893
     *
894
     * @return array
895
     */
896
    public function getSelect()
897
    {
898
        return $this->select;
899
    }
900
901
    /**
902
     * Activates the INSERT mode
903
     *
904
     * @return $this
905
     */
906
    public function insert()
907
    {
908
        $this->reset();
909
        $this->type = 'insert';
910
911
        return $this;
912
    }
913
914
    /**
915
     * Activates the REPLACE mode
916
     *
917
     * @return $this
918
     */
919
    public function replace()
920
    {
921
        $this->reset();
922
        $this->type = 'replace';
923
924
        return $this;
925
    }
926
927
    /**
928
     * Activates the UPDATE mode
929
     *
930
     * @param string $index The index to update into
931
     *
932
     * @return $this
933
     */
934
    public function update($index)
935
    {
936
        $this->reset();
937
        $this->type = 'update';
938
        $this->into($index);
939
940
        return $this;
941
    }
942
943
    /**
944
     * Activates the DELETE mode
945
     *
946
     * @return $this
947
     */
948
    public function delete()
949
    {
950
        $this->reset();
951
        $this->type = 'delete';
952
953
        return $this;
954
    }
955
956
    /**
957
     * FROM clause (Sphinx-specific since it works with multiple indexes)
958
     * func_get_args()-enabled
959
     *
960
     * @param array $array An array of indexes to use
961
     *
962
     * @return $this
963
     */
964
    public function from($array = null)
965
    {
966
        if (is_string($array)) {
967
            $this->from = \func_get_args();
968
        }
969
970
        if (is_array($array) || $array instanceof \Closure || $array instanceof SphinxQL) {
971
            $this->from = $array;
0 ignored issues
show
Documentation Bug introduced by
It seems like $array can also be of type Closure or Foolz\SphinxQL\SphinxQL. However, the property $from is declared as type array. 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...
972
        }
973
974
        return $this;
975
    }
976
977
    /**
978
     * MATCH clause (Sphinx-specific)
979
     *
980
     * @param mixed  $column The column name (can be array, string, Closure, or Match)
981
     * @param string $value  The value
982
     * @param bool   $half   Exclude ", |, - control characters from being escaped
983
     *
984
     * @return $this
985
     */
986
    public function match($column, $value = null, $half = false)
987
    {
988
        if ($column === '*' || (is_array($column) && in_array('*', $column))) {
989
            $column = array();
990
        }
991
992
        $this->match[] = array('column' => $column, 'value' => $value, 'half' => $half);
993
994
        return $this;
995
    }
996
997
    /**
998
     * WHERE clause
999
     *
1000
     * Examples:
1001
     *    $query->where('column', 'value');
1002
     *    // WHERE column = 'value'
1003
     *
1004
     *    $query->where('column', '=', 'value');
1005
     *    // WHERE column = 'value'
1006
     *
1007
     *    $query->where('column', '>=', 'value')
1008
     *    // WHERE column >= 'value'
1009
     *
1010
     *    $query->where('column', 'IN', array('value1', 'value2', 'value3'));
1011
     *    // WHERE column IN ('value1', 'value2', 'value3')
1012
     *
1013
     *    $query->where('column', 'BETWEEN', array('value1', 'value2'))
1014
     *    // WHERE column BETWEEN 'value1' AND 'value2'
1015
     *    // WHERE example BETWEEN 10 AND 100
1016
     *
1017
     * @param string                                      $column   The column name
1018
     * @param Expression|string|null|bool|array|int|float $operator The operator to use (if value is not null, you can
1019
     *      use only string)
1020
     * @param Expression|string|null|bool|array|int|float $value    The value to check against
1021
     *
1022
     * @return $this
1023
     */
1024
    public function where($column, $operator, $value = null)
1025
    {
1026
        if ($value === null) {
1027
            $value = $operator;
1028
            $operator = '=';
1029
        }
1030
1031
        $this->where[] = array(
1032
            'column'   => $column,
1033
            'operator' => $operator,
1034
            'value'    => $value,
1035
        );
1036
1037
        return $this;
1038
    }
1039
1040
    /**
1041
     * GROUP BY clause
1042
     * Adds to the previously added columns
1043
     *
1044
     * @param string $column A column to group by
1045
     *
1046
     * @return $this
1047
     */
1048
    public function groupBy($column)
1049
    {
1050
        $this->group_by[] = $column;
1051
1052
        return $this;
1053
    }
1054
1055
    /**
1056
     * GROUP N BY clause (SphinxQL-specific)
1057
     * Changes 'GROUP BY' into 'GROUP N BY'
1058
     *
1059
     * @param int $n Number of items per group
1060
     *
1061
     * @return $this
1062
     */
1063
    public function groupNBy($n)
1064
    {
1065
        $this->group_n_by = (int) $n;
1066
1067
        return $this;
1068
    }
1069
1070
    /**
1071
     * WITHIN GROUP ORDER BY clause (SphinxQL-specific)
1072
     * Adds to the previously added columns
1073
     * Works just like a classic ORDER BY
1074
     *
1075
     * @param string $column    The column to group by
1076
     * @param string $direction The group by direction (asc/desc)
1077
     *
1078
     * @return $this
1079
     */
1080
    public function withinGroupOrderBy($column, $direction = null)
1081
    {
1082
        $this->within_group_order_by[] = array('column' => $column, 'direction' => $direction);
1083
1084
        return $this;
1085
    }
1086
1087
    /**
1088
     * HAVING clause
1089
     *
1090
     * Examples:
1091
     *    $sq->having('column', 'value');
1092
     *    // HAVING column = 'value'
1093
     *
1094
     *    $sq->having('column', '=', 'value');
1095
     *    // HAVING column = 'value'
1096
     *
1097
     *    $sq->having('column', '>=', 'value')
1098
     *    // HAVING column >= 'value'
1099
     *
1100
     *    $sq->having('column', 'IN', array('value1', 'value2', 'value3'));
1101
     *    // HAVING column IN ('value1', 'value2', 'value3')
1102
     *
1103
     *    $sq->having('column', 'BETWEEN', array('value1', 'value2'))
1104
     *    // HAVING column BETWEEN 'value1' AND 'value2'
1105
     *    // HAVING example BETWEEN 10 AND 100
1106
     *
1107
     * @param string $column   The column name
1108
     * @param string $operator The operator to use
1109
     * @param string $value    The value to check against
1110
     *
1111
     * @return $this
1112
     */
1113
    public function having($column, $operator, $value = null)
1114
    {
1115
        if ($value === null) {
1116
            $value = $operator;
1117
            $operator = '=';
1118
        }
1119
1120
        $this->having = array(
1121
            'column'   => $column,
1122
            'operator' => $operator,
1123
            'value'    => $value,
1124
        );
1125
1126
        return $this;
1127
    }
1128
1129
    /**
1130
     * ORDER BY clause
1131
     * Adds to the previously added columns
1132
     *
1133
     * @param string $column    The column to order on
1134
     * @param string $direction The ordering direction (asc/desc)
1135
     *
1136
     * @return $this
1137
     */
1138
    public function orderBy($column, $direction = null)
1139
    {
1140
        $this->order_by[] = array('column' => $column, 'direction' => $direction);
1141
1142
        return $this;
1143
    }
1144
1145
    /**
1146
     * LIMIT clause
1147
     * Supports also LIMIT offset, limit
1148
     *
1149
     * @param int      $offset Offset if $limit is specified, else limit
1150
     * @param null|int $limit  The limit to set, null for no limit
1151
     *
1152
     * @return $this
1153
     */
1154
    public function limit($offset, $limit = null)
1155
    {
1156
        if ($limit === null) {
1157
            $this->limit = (int) $offset;
1158
1159
            return $this;
1160
        }
1161
1162
        $this->offset($offset);
1163
        $this->limit = (int) $limit;
1164
1165
        return $this;
1166
    }
1167
1168
    /**
1169
     * OFFSET clause
1170
     *
1171
     * @param int $offset The offset
1172
     *
1173
     * @return $this
1174
     */
1175
    public function offset($offset)
1176
    {
1177
        $this->offset = (int) $offset;
1178
1179
        return $this;
1180
    }
1181
1182
    /**
1183
     * OPTION clause (SphinxQL-specific)
1184
     * Used by: SELECT
1185
     *
1186
     * @param string                                      $name  Option name
1187
     * @param Expression|array|string|int|bool|float|null $value Option value
1188
     *
1189
     * @return $this
1190
     */
1191
    public function option($name, $value)
1192
    {
1193
        $this->options[] = array('name' => $name, 'value' => $value);
1194
1195
        return $this;
1196
    }
1197
1198
    /**
1199
     * INTO clause
1200
     * Used by: INSERT, REPLACE
1201
     *
1202
     * @param string $index The index to insert/replace into
1203
     *
1204
     * @return $this
1205
     */
1206
    public function into($index)
1207
    {
1208
        $this->into = $index;
1209
1210
        return $this;
1211
    }
1212
1213
    /**
1214
     * Set columns
1215
     * Used in: INSERT, REPLACE
1216
     * func_get_args()-enabled
1217
     *
1218
     * @param array $array The array of columns
1219
     *
1220
     * @return $this
1221
     */
1222
    public function columns($array = array())
1223
    {
1224
        if (is_array($array)) {
1225
            $this->columns = $array;
1226
        } else {
1227
            $this->columns = \func_get_args();
1228
        }
1229
1230
        return $this;
1231
    }
1232
1233
    /**
1234
     * Set VALUES
1235
     * Used in: INSERT, REPLACE
1236
     * func_get_args()-enabled
1237
     *
1238
     * @param array $array The array of values matching the columns from $this->columns()
1239
     *
1240
     * @return $this
1241
     */
1242
    public function values($array)
1243
    {
1244
        if (is_array($array)) {
1245
            $this->values[] = $array;
1246
        } else {
1247
            $this->values[] = \func_get_args();
1248
        }
1249
1250
        return $this;
1251
    }
1252
1253
    /**
1254
     * Set column and relative value
1255
     * Used in: INSERT, REPLACE
1256
     *
1257
     * @param string $column The column name
1258
     * @param string $value  The value
1259
     *
1260
     * @return $this
1261
     */
1262
    public function value($column, $value)
1263
    {
1264
        if ($this->type === 'insert' || $this->type === 'replace') {
1265
            $this->columns[] = $column;
1266
            $this->values[0][] = $value;
1267
        } else {
1268
            $this->set[$column] = $value;
1269
        }
1270
1271
        return $this;
1272
    }
1273
1274
    /**
1275
     * Allows passing an array with the key as column and value as value
1276
     * Used in: INSERT, REPLACE, UPDATE
1277
     *
1278
     * @param array $array Array of key-values
1279
     *
1280
     * @return $this
1281
     */
1282
    public function set($array)
1283
    {
1284
        if ($this->columns === array_keys($array)) {
1285
            $this->values($array);
1286
        } else {
1287
            foreach ($array as $key => $item) {
1288
                $this->value($key, $item);
1289
            }
1290
        }
1291
1292
        return $this;
1293
    }
1294
1295
    /**
1296
     * Allows passing an array with the key as column and value as value
1297
     * Used in: INSERT, REPLACE, UPDATE
1298
     *
1299
     * @param Facet $facet
1300
     *
1301
     * @return $this
1302
     */
1303
    public function facet($facet)
1304
    {
1305
        $this->facets[] = $facet;
1306
1307
        return $this;
1308
    }
1309
1310
    /**
1311
     * Sets the characters used for escapeMatch().
1312
     *
1313
     * @param array $array The array of characters to escape
1314
     *
1315
     * @return $this
1316
     */
1317
    public function setFullEscapeChars($array = array())
1318
    {
1319
        if (!empty($array)) {
1320
            $this->escape_full_chars = $this->compileEscapeChars($array);
1321
        }
1322
1323
        return $this;
1324
    }
1325
1326
    /**
1327
     * Sets the characters used for halfEscapeMatch().
1328
     *
1329
     * @param array $array The array of characters to escape
1330
     *
1331
     * @return $this
1332
     */
1333
    public function setHalfEscapeChars($array = array())
1334
    {
1335
        if (!empty($array)) {
1336
            $this->escape_half_chars = $this->compileEscapeChars($array);
1337
        }
1338
1339
        return $this;
1340
    }
1341
1342
    /**
1343
     * Compiles an array containing the characters and escaped characters into a key/value configuration.
1344
     *
1345
     * @param array $array The array of characters to escape
1346
     *
1347
     * @return array An array of the characters and it's escaped counterpart
1348
     */
1349
    public function compileEscapeChars($array = array())
1350
    {
1351
        $result = array();
1352
        foreach ($array as $character) {
1353
            $result[$character] = '\\'.$character;
1354
        }
1355
1356
        return $result;
1357
    }
1358
1359
    /**
1360
     * Escapes the query for the MATCH() function
1361
     *
1362
     * @param string $string The string to escape for the MATCH
1363
     *
1364
     * @return string The escaped string
1365
     */
1366
    public function escapeMatch($string)
1367
    {
1368
        if ($string instanceof Expression) {
0 ignored issues
show
introduced by
$string is never a sub-type of Foolz\SphinxQL\Expression.
Loading history...
1369
            return $string->value();
1370
        }
1371
1372
        return mb_strtolower(str_replace(array_keys($this->escape_full_chars), array_values($this->escape_full_chars), $string), 'utf8');
1373
    }
1374
1375
    /**
1376
     * Escapes the query for the MATCH() function
1377
     * Allows some of the control characters to pass through for use with a search field: -, |, "
1378
     * It also does some tricks to wrap/unwrap within " the string and prevents errors
1379
     *
1380
     * @param string $string The string to escape for the MATCH
1381
     *
1382
     * @return string The escaped string
1383
     */
1384
    public function halfEscapeMatch($string)
1385
    {
1386
        if ($string instanceof Expression) {
0 ignored issues
show
introduced by
$string is never a sub-type of Foolz\SphinxQL\Expression.
Loading history...
1387
            return $string->value();
1388
        }
1389
1390
        $string = str_replace(array_keys($this->escape_half_chars), array_values($this->escape_half_chars), $string);
1391
1392
        // this manages to lower the error rate by a lot
1393
        if (mb_substr_count($string, '"', 'utf8') % 2 !== 0) {
1394
            $string .= '"';
1395
        }
1396
1397
        $string = preg_replace('/-[\s-]*-/u', '-', $string);
1398
1399
        $from_to_preg = array(
1400
            '/([-|])\s*$/u'        => '\\\\\1',
1401
            '/\|[\s|]*\|/u'        => '|',
1402
            '/(\S+)-(\S+)/u'       => '\1\-\2',
1403
            '/(\S+)\s+-\s+(\S+)/u' => '\1 \- \2',
1404
        );
1405
1406
        $string = mb_strtolower(preg_replace(array_keys($from_to_preg), array_values($from_to_preg), $string), 'utf8');
1407
1408
        return $string;
1409
    }
1410
1411
    /**
1412
     * Clears the existing query build for new query when using the same SphinxQL instance.
1413
     *
1414
     * @return $this
1415
     */
1416
    public function reset()
1417
    {
1418
        $this->query = null;
1419
        $this->select = array();
1420
        $this->from = array();
1421
        $this->where = array();
1422
        $this->match = array();
1423
        $this->group_by = array();
1424
        $this->group_n_by = null;
1425
        $this->within_group_order_by = array();
1426
        $this->having = array();
1427
        $this->order_by = array();
1428
        $this->offset = null;
1429
        $this->limit = null;
1430
        $this->into = null;
1431
        $this->columns = array();
1432
        $this->values = array();
1433
        $this->set = array();
1434
        $this->options = array();
1435
1436
        return $this;
1437
    }
1438
1439
    /**
1440
     * @return $this
1441
     */
1442
    public function resetWhere()
1443
    {
1444
        $this->where = array();
1445
1446
        return $this;
1447
    }
1448
1449
    /**
1450
     * @return $this
1451
     */
1452
    public function resetMatch()
1453
    {
1454
        $this->match = array();
1455
1456
        return $this;
1457
    }
1458
1459
    /**
1460
     * @return $this
1461
     */
1462
    public function resetGroupBy()
1463
    {
1464
        $this->group_by = array();
1465
        $this->group_n_by = null;
1466
1467
        return $this;
1468
    }
1469
1470
    /**
1471
     * @return $this
1472
     */
1473
    public function resetWithinGroupOrderBy()
1474
    {
1475
        $this->within_group_order_by = array();
1476
1477
        return $this;
1478
    }
1479
1480
    /**
1481
     * @return $this
1482
     */
1483
    public function resetFacets()
1484
    {
1485
        $this->facets = array();
1486
1487
        return $this;
1488
    }
1489
1490
    /**
1491
     * @return $this
1492
     */
1493
    public function resetHaving()
1494
    {
1495
        $this->having = array();
1496
1497
        return $this;
1498
    }
1499
1500
    /**
1501
     * @return $this
1502
     */
1503
    public function resetOrderBy()
1504
    {
1505
        $this->order_by = array();
1506
1507
        return $this;
1508
    }
1509
1510
    /**
1511
     * @return $this
1512
     */
1513
    public function resetOptions()
1514
    {
1515
        $this->options = array();
1516
1517
        return $this;
1518
    }
1519
}
1520