Completed
Push — master ( 75ff44...8004f9 )
by Thibaud
8s
created

RecordQueryBuilder::appendSort()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 11
ccs 6
cts 6
cp 1
rs 9.4285
cc 3
eloc 6
nc 3
nop 1
crap 3
1
<?php
2
3
namespace Alchemy\Phraseanet\Query;
4
5
use Alchemy\Phraseanet\Predicate\PredicateBuilder;
6
use PhraseanetSDK\Entity\DataboxCollection;
7
8
class RecordQueryBuilder
9
{
10
11
    const RECORD_TYPE_AUDIO = 'audio';
12
13
    const RECORD_TYPE_VIDEO = 'video';
14
15
    const RECORD_TYPE_IMAGE = 'image';
16
17
    const RECORD_TYPE_DOCUMENT = 'document';
18
19
    const RECORD_TYPE_FLASH = 'flash';
20
21
    const SEARCH_RECORDS = 0;
22
23
    const SEARCH_STORIES = 1;
24
25
    const SORT_RELEVANCE = 'relevance';
26
27
    const SORT_CREATED_ON = 'created_on';
28
29
    const SORT_RANDOM = 'random';
30
31
    /**
32
     * @var string
33
     */
34
    private $query = '';
35
36
    /**
37
     * @var PredicateBuilder
38
     */
39
    private $conditionBuilder;
40
41
    /**
42
     * @var int[] An array of collection ID's to search
43
     */
44
    private $collections = array();
45
46
    /**
47
     * @var int Offset of the first record to return
48
     */
49
    private $offsetStart = 0;
50
51
    /**
52
     * @var int Number of records to return
53
     */
54
    private $recordsPerPage = 10;
55
56
    /**
57
     * @var string|null One of the RECORD_TYPE_* constant values to restrict the search to a specific document type
58
     */
59
    private $recordType = null;
60
61
    /**
62
     * @var int One of the SEARCH_TYPE_* constant values to select the type of record to fetch
63
     */
64
    private $searchType = self::SEARCH_RECORDS;
65
66
    /**
67
     * @var string|null Name of a date field to use as a date criterion
68
     */
69
    private $dateCriterionField = null;
70
71
    /**
72
     * @var \DateTimeInterface|null
73
     */
74
    private $dateCriterionMin = null;
75
76
    /**
77
     * @var \DateTimeInterface|null
78
     */
79
    private $dateCriterionMax = null;
80
81
    /**
82
     * @var int[] An array of statuses to restrict the search to documents matching the given statuses
83
     */
84
    private $statuses = array();
85
86
    /**
87
     * @var string[] An array of fields names to return in the search results
88
     */
89
    private $fields = array();
90
91
    /**
92
     * @var bool Whether to sort in descending order
93
     */
94
    private $sortDescending = false;
95
96
    /**
97
     * @var string Type of sort to use
98
     */
99
    private $sortType = null;
100
101
    /**
102
     * Sets the record search query string. Format follows the same specification as the Phraseanet search
103
     * engine.
104
     *
105
     * @param $query
106
     * @return $this
107
     */
108 1
    public function setQuery($query)
109
    {
110 1
        $this->query = $query;
111
112 1
        return $this;
113
    }
114
115
    /**
116
     * @return PredicateBuilder
117
     */
118
    public function getConditionBuilder()
119
    {
120
        if ($this->conditionBuilder === null) {
121
            $this->conditionBuilder = new PredicateBuilder();
122
        }
123
124
        return $this->conditionBuilder;
125
    }
126
127
    /**
128
     * Add a collection to the search criteria.
129
     *
130
     * @param DataboxCollection|int $collection A collection or collection ID.
131
     * @return $this
132
     */
133 5
    public function addCollection($collection)
134
    {
135 5
        if ($collection instanceof DataboxCollection) {
136 1
            $collection = $collection->getBaseId();
137
        }
138
139 5
        $this->collections[] = $collection;
140
141 5
        return $this;
142
    }
143
144
    /**
145
     * Adds a list of collections to the search criteria.
146
     *
147
     * @param array $collections An array of DataboxCollection instances or collection ID's.
148
     * @return $this
149
     */
150 3
    public function addCollections(array $collections)
151
    {
152 3
        foreach ($collections as $collection) {
153 3
            $this->addCollection($collection);
154
        }
155
156 3
        return $this;
157
    }
158
159
    /**
160
     * Sets the list of collections in which to search for records.
161
     *
162
     * @param array $collections An array of DataboxCollection instances or collection ID's. An empty array will clear
163
     * the collection restriction.
164
     *
165
     * @return $this
166
     */
167 1
    public function setCollections(array $collections)
168
    {
169 1
        $this->collections = array();
170
171 1
        $this->addCollections($collections);
172
173 1
        return $this;
174
    }
175
176
    /**
177
     * Intersects the current list of collections with the given collections.
178
     *
179
     * @param array $collections
180
     */
181 1
    public function intersectCollections(array $collections)
182
    {
183 1
        $this->collections = array_values(array_intersect($this->collections, $collections));
184 1
    }
185
186
    /**
187
     * Sets the offset of the first record to return.
188
     *
189
     * @param int $offset Index of the first record to return. Defaults to 0 if an invalid value is provided.
190
     *
191
     * @return $this
192
     */
193 1
    public function setOffset($offset)
194
    {
195 1
        $this->offsetStart = max(0, intval($offset));
196
197 1
        return $this;
198
    }
199
200
    /**
201
     * Sets the sort type for the query
202
     *
203
     * @param string $sort One of the SORT_* constant values.
204
     * @param bool $descending True to sort in descending order, false otherwise.
205
     * @return $this
206
     * @throws \InvalidArgumentException when the sort type is not of the SORT_* constant values..
207
     */
208 3
    public function sortBy($sort, $descending = false)
209
    {
210 3
        $this->sortType = (string) $sort;
211 3
        $this->sortDescending = (bool) $descending;
212
213 3
        return $this;
214
    }
215
216
    /**
217
     * Sets the maximum number of records to return.
218
     *
219
     * @param int $limit Maximum number of records to return. Defaults to 1 if an invalid value is provided.
220
     *
221
     * @return $this
222
     */
223 1
    public function setLimit($limit)
224
    {
225 1
        $this->recordsPerPage = max(1, intval($limit));
226
227 1
        return $this;
228
    }
229
230
    /**
231
     * Filters the media type of records to return.
232
     *
233
     * @param string $recordType One of the QueryBuilder::RECORD_TYPE_* constant values.
234
     * @return $this
235
     * @throws \InvalidArgumentException when the record media type is not valid.
236
     */
237 6
    public function setRecordType($recordType)
238
    {
239
        $allowedTypes = array(
240 6
            self::RECORD_TYPE_AUDIO,
241 6
            self::RECORD_TYPE_DOCUMENT,
242 6
            self::RECORD_TYPE_FLASH,
243 6
            self::RECORD_TYPE_IMAGE,
244 6
            self::RECORD_TYPE_VIDEO
245
        );
246
247 6
        if (! in_array($recordType, $allowedTypes, true)) {
248 1
            throw new \InvalidArgumentException(
249 1
                sprintf('Record type must be one of the RECORD_TYPE_* values, %s given.', $recordType)
250
            );
251
        }
252
253 5
        $this->recordType = $recordType;
254
255 5
        return $this;
256
    }
257
258
    /**
259
     * Sets the type of record to search for.
260
     *
261
     * @param int $searchType One of the QueryBuilder::SEARCH_* constant values.
262
     * @return $this
263
     * @throws \InvalidArgumentException when the the search type if not valid.
264
     */
265 2
    public function setSearchType($searchType)
266
    {
267
        $allowedTypes = array(
268 2
            self::SEARCH_RECORDS,
269 2
            self::SEARCH_STORIES
270
        );
271
272 2
        if (! in_array($searchType, $allowedTypes, true)) {
273 1
            throw new \InvalidArgumentException('Search type must be one of the SEARCH_* values');
274
        }
275
276 1
        $this->searchType = $searchType;
277
278 1
        return $this;
279
    }
280
281
    /**
282
     * Sets a date filter on the records to return. At least one of $minDate or $maxDate arguments
283
     * must be specified.
284
     *
285
     * @param string $fieldName The name of the field on which to filter by date.
286
     * @param \DateTimeInterface $minDate The lower date boundary.
287
     * @param \DateTimeInterface $maxDate The upper date boundary.
288
     * @return $this
289
     * @throws \InvalidArgumentException when the field name is an invalid or empty string
290
     * @throws \InvalidArgumentException when both min and max date values are null.
291
     */
292 10
    public function setDateCriterion($fieldName, \DateTimeInterface $minDate = null, \DateTimeInterface $maxDate = null)
293
    {
294 10
        $this->validateFieldName($fieldName, 'Field name is required and must be a non-empty string.');
295
296 4
        if ($minDate == null && $maxDate == null) {
297 1
            throw new \InvalidArgumentException('At least one of min or max date must be provided');
298
        }
299
300 3
        $this->dateCriterionField = $fieldName;
301 3
        $this->dateCriterionMin = $minDate;
302 3
        $this->dateCriterionMax = $maxDate;
303
304 3
        return $this;
305
    }
306
307
    /**
308
     * Adds a status filter to the search
309
     *
310
     * @param mixed $status
311
     * @param $value
312
     * @return $this
313
     */
314 3
    public function addStatus($status, $value)
315
    {
316 3
        $this->statuses[$status] = $value;
317
318 3
        return $this;
319
    }
320
321
    /**
322
     * Adds a list of status filters to the search.
323
     *
324
     * @param array $statuses
325
     * @return $this
326
     */
327 2
    public function addStatuses(array $statuses)
328
    {
329 2
        foreach ($statuses as $status => $value) {
330 2
            $this->addStatus($status, $value);
331
        }
332
333 2
        return $this;
334
    }
335
336
    /**
337
     * Sets the status filter to the given list. An empty list clears the filter.
338
     *
339
     * @param array $statuses
340
     * @return $this
341
     */
342 1
    public function setStatuses(array $statuses)
343
    {
344 1
        $this->statuses = array();
345
346 1
        $this->addStatuses($statuses);
347
348 1
        return $this;
349
    }
350
351
    /**
352
     * Adds a field to the list of requested fields.
353
     *
354
     * @param string $fieldName
355
     * @return $this
356
     * @throws \InvalidArgumentException when the field name is an invalid or empty string
357
     */
358 21
    public function addField($fieldName)
359
    {
360 21
        $this->validateFieldName($fieldName, 'Field name is required and must be a non-empty string.');
361 15
        $this->fields[] = $fieldName;
362
363 15
        return $this;
364
    }
365
366 31
    private function validateFieldName($fieldName, $errorMessage)
367
    {
368 31
        if (! is_string($fieldName) || trim($fieldName) === '') {
369 24
            throw new \InvalidArgumentException($errorMessage);
370
        }
371 19
    }
372
373
    /**
374
     * Adds a list of fields to the list of requested fields.
375
     *
376
     * @param array $fields
377
     * @return $this
378
     * @throws \InvalidArgumentException when one of the field names is an invalid or empty string
379
     */
380 14
    public function addFields(array $fields)
381
    {
382 14
        foreach ($fields as $field) {
383 14
            $this->addField($field);
384
        }
385
386 2
        return $this;
387
    }
388
389
    /**
390
     * Sets the list of requested fields. An empty clears the filter and all fields will be returned.
391
     *
392
     * @param array $fields
393
     * @return $this
394
     * @throws \InvalidArgumentException when one of the field names is an invalid or empty string
395
     */
396 7
    public function setFields(array $fields)
397
    {
398 7
        $this->fields = array();
399
400 7
        $this->addFields($fields);
401
402 1
        return $this;
403
    }
404
405
    /**
406
     * Returns the built query.
407
     *
408
     * @return RecordQuery
409
     */
410 27
    public function getQuery()
411
    {
412
        $query = array(
413 27
            'query' => $this->buildQueryTerm(),
414 27
            'bases' => array_unique($this->collections),
415 27
            'offset_start' => $this->offsetStart,
416 27
            'per_page' => $this->recordsPerPage,
417 27
            'search_type' => $this->searchType
418
        );
419
420 27
        $query = $this->appendRecordType($query);
421 27
        $query = $this->appendDates($query);
422 27
        $query = $this->appendStatuses($query);
423 27
        $query = $this->appendFields($query);
424 27
        $query = $this->appendSort($query);
425
426 27
        return new RecordQuery($query, $this->searchType);
427
    }
428
429 27
    private function buildQueryTerm()
430
    {
431 27
        if (! $this->conditionBuilder) {
432 27
            return $this->query;
433
        }
434
435
        $compiler = new QueryPredicateVisitor();
436
437
        $this->conditionBuilder->endAllGroups();
438
        $this->conditionBuilder->andWhere($this->query);
439
440
        return $compiler->compile($this->conditionBuilder->getPredicate());
441
    }
442
443
    /**
444
     * @param $query
445
     * @return mixed
446
     */
447 27
    private function appendRecordType($query)
448
    {
449 27
        if ($this->recordType !== null) {
450 5
            $query['record_type'] = $this->recordType;
451
452 5
            return $query;
453
        }
454
455 22
        return $query;
456
    }
457
458
    /**
459
     * @param $query
460
     * @return mixed
461
     */
462 27
    private function appendDates($query)
463
    {
464 27
        if ($this->dateCriterionField !== null) {
465 3
            $query['date_field'] = $this->dateCriterionField;
466
467 3
            if ($this->dateCriterionMin) {
468 2
                $query['date_min'] = $this->dateCriterionMin->format('Y/m/d');
469
            }
470
471 3
            if ($this->dateCriterionMax) {
472 2
                $query['date_max'] = $this->dateCriterionMax->format('Y/m/d');
473
474 2
                return $query;
475
            }
476
477 1
            return $query;
478
        }
479
480 24
        return $query;
481
    }
482
483
    /**
484
     * @param $query
485
     * @return mixed
486
     */
487 27
    private function appendStatuses($query)
488
    {
489 27
        if (!empty($this->statuses)) {
490 3
            $query['status'] = $this->statuses;
491
492 3
            return $query;
493
        }
494
495 24
        return $query;
496
    }
497
498
    /**
499
     * @param $query
500
     * @return mixed
501
     */
502 27
    private function appendFields($query)
503
    {
504 27
        if (!empty($this->fields)) {
505 3
            $query['fields'] = $this->fields;
506
507 3
            return $query;
508
        }
509
510 24
        return $query;
511
    }
512
513
    /**
514
     * @param $query
515
     * @return mixed
516
     */
517 27
    private function appendSort($query)
518
    {
519 27
        if ($this->sortType !== null) {
520 3
            $query['sort'] = $this->sortType;
521 3
            $query['ord'] = $this->sortDescending ? 'desc' : 'asc';
522
523 3
            return $query;
524
        }
525
526 24
        return $query;
527
    }
528
}
529