Completed
Push — master ( 1b341d...d4f9db )
by Thibaud
10:00 queued 07:22
created

RecordQueryBuilder::sortBy()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 7
ccs 4
cts 4
cp 1
rs 9.4285
cc 1
eloc 4
nc 1
nop 2
crap 1
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
     * @param int $pageIndex The 0-based page index
202
     */
203
    public function setPage($pageIndex)
204
    {
205
        $this->setOffset($pageIndex * $this->recordsPerPage);
206
    }
207
208
    /**
209
     * Sets the sort type for the query
210
     *
211
     * @param string $sort One of the SORT_* constant values.
212
     * @param bool $descending True to sort in descending order, false otherwise.
213
     * @return $this
214
     * @throws \InvalidArgumentException when the sort type is not of the SORT_* constant values..
215
     */
216 3
    public function sortBy($sort, $descending = false)
217
    {
218 3
        $this->sortType = (string) $sort;
219 3
        $this->sortDescending = (bool) $descending;
220
221 3
        return $this;
222
    }
223
224
    /**
225
     * Sets the maximum number of records to return.
226
     *
227
     * @param int $limit Maximum number of records to return. Defaults to 1 if an invalid value is provided.
228
     *
229
     * @return $this
230
     */
231 1
    public function setLimit($limit)
232
    {
233 1
        $this->recordsPerPage = max(1, intval($limit));
234
235 1
        return $this;
236
    }
237
238
    /**
239
     * Filters the media type of records to return.
240
     *
241
     * @param string $recordType One of the QueryBuilder::RECORD_TYPE_* constant values.
242
     * @return $this
243
     * @throws \InvalidArgumentException when the record media type is not valid.
244
     */
245 6
    public function setRecordType($recordType)
246
    {
247
        $allowedTypes = array(
248 6
            self::RECORD_TYPE_AUDIO,
249 6
            self::RECORD_TYPE_DOCUMENT,
250 6
            self::RECORD_TYPE_FLASH,
251 6
            self::RECORD_TYPE_IMAGE,
252 6
            self::RECORD_TYPE_VIDEO
253
        );
254
255 6
        if (! in_array($recordType, $allowedTypes, true)) {
256 1
            throw new \InvalidArgumentException(
257 1
                sprintf('Record type must be one of the RECORD_TYPE_* values, %s given.', $recordType)
258
            );
259
        }
260
261 5
        $this->recordType = $recordType;
262
263 5
        return $this;
264
    }
265
266
    /**
267
     * Sets the type of record to search for.
268
     *
269
     * @param int $searchType One of the QueryBuilder::SEARCH_* constant values.
270
     * @return $this
271
     * @throws \InvalidArgumentException when the the search type if not valid.
272
     */
273 2
    public function setSearchType($searchType)
274
    {
275
        $allowedTypes = array(
276 2
            self::SEARCH_RECORDS,
277 2
            self::SEARCH_STORIES
278
        );
279
280 2
        if (! in_array($searchType, $allowedTypes, true)) {
281 1
            throw new \InvalidArgumentException('Search type must be one of the SEARCH_* values');
282
        }
283
284 1
        $this->searchType = $searchType;
285
286 1
        return $this;
287
    }
288
289
    /**
290
     * Sets a date filter on the records to return. At least one of $minDate or $maxDate arguments
291
     * must be specified.
292
     *
293
     * @param string $fieldName The name of the field on which to filter by date.
294
     * @param \DateTimeInterface $minDate The lower date boundary.
295
     * @param \DateTimeInterface $maxDate The upper date boundary.
296
     * @return $this
297
     * @throws \InvalidArgumentException when the field name is an invalid or empty string
298
     * @throws \InvalidArgumentException when both min and max date values are null.
299
     */
300 10
    public function setDateCriterion($fieldName, \DateTimeInterface $minDate = null, \DateTimeInterface $maxDate = null)
301
    {
302 10
        $this->validateFieldName($fieldName, 'Field name is required and must be a non-empty string.');
303
304 4
        if ($minDate == null && $maxDate == null) {
305 1
            throw new \InvalidArgumentException('At least one of min or max date must be provided');
306
        }
307
308 3
        $this->dateCriterionField = $fieldName;
309 3
        $this->dateCriterionMin = $minDate;
310 3
        $this->dateCriterionMax = $maxDate;
311
312 3
        return $this;
313
    }
314
315
    /**
316
     * Adds a status filter to the search
317
     *
318
     * @param mixed $status
319
     * @param $value
320
     * @return $this
321
     */
322 3
    public function addStatus($status, $value)
323
    {
324 3
        $this->statuses[$status] = $value;
325
326 3
        return $this;
327
    }
328
329
    /**
330
     * Adds a list of status filters to the search.
331
     *
332
     * @param array $statuses
333
     * @return $this
334
     */
335 2
    public function addStatuses(array $statuses)
336
    {
337 2
        foreach ($statuses as $status => $value) {
338 2
            $this->addStatus($status, $value);
339
        }
340
341 2
        return $this;
342
    }
343
344
    /**
345
     * Sets the status filter to the given list. An empty list clears the filter.
346
     *
347
     * @param array $statuses
348
     * @return $this
349
     */
350 1
    public function setStatuses(array $statuses)
351
    {
352 1
        $this->statuses = array();
353
354 1
        $this->addStatuses($statuses);
355
356 1
        return $this;
357
    }
358
359
    /**
360
     * Adds a field to the list of requested fields.
361
     *
362
     * @param string $fieldName
363
     * @return $this
364
     * @throws \InvalidArgumentException when the field name is an invalid or empty string
365
     */
366 21
    public function addField($fieldName)
367
    {
368 21
        $this->validateFieldName($fieldName, 'Field name is required and must be a non-empty string.');
369 15
        $this->fields[] = $fieldName;
370
371 15
        return $this;
372
    }
373
374 31
    private function validateFieldName($fieldName, $errorMessage)
375
    {
376 31
        if (! is_string($fieldName) || trim($fieldName) === '') {
377 24
            throw new \InvalidArgumentException($errorMessage);
378
        }
379 19
    }
380
381
    /**
382
     * Adds a list of fields to the list of requested fields.
383
     *
384
     * @param array $fields
385
     * @return $this
386
     * @throws \InvalidArgumentException when one of the field names is an invalid or empty string
387
     */
388 14
    public function addFields(array $fields)
389
    {
390 14
        foreach ($fields as $field) {
391 14
            $this->addField($field);
392
        }
393
394 2
        return $this;
395
    }
396
397
    /**
398
     * Sets the list of requested fields. An empty clears the filter and all fields will be returned.
399
     *
400
     * @param array $fields
401
     * @return $this
402
     * @throws \InvalidArgumentException when one of the field names is an invalid or empty string
403
     */
404 7
    public function setFields(array $fields)
405
    {
406 7
        $this->fields = array();
407
408 7
        $this->addFields($fields);
409
410 1
        return $this;
411
    }
412
413
    /**
414
     * Returns the built query.
415
     *
416
     * @return RecordQuery
417
     */
418 27
    public function getQuery()
419
    {
420
        $query = array(
421 27
            'query' => $this->buildQueryTerm(),
422 27
            'bases' => array_unique($this->collections),
423 27
            'offset_start' => $this->offsetStart,
424 27
            'per_page' => $this->recordsPerPage,
425 27
            'search_type' => $this->searchType
426
        );
427
428 27
        $query = $this->appendRecordType($query);
429 27
        $query = $this->appendDates($query);
430 27
        $query = $this->appendStatuses($query);
431 27
        $query = $this->appendFields($query);
432 27
        $query = $this->appendSort($query);
433
434 27
        return new RecordQuery($query, $this->searchType);
435
    }
436
437 27
    private function buildQueryTerm()
438
    {
439 27
        if (! $this->conditionBuilder) {
440 27
            return $this->query;
441
        }
442
443
        $compiler = new QueryPredicateVisitor();
444
445
        $this->conditionBuilder->endAllGroups();
446
        $this->conditionBuilder->andWhere($this->query);
447
448
        return $compiler->compile($this->conditionBuilder->getPredicate());
449
    }
450
451
    /**
452
     * @param $query
453
     * @return mixed
454
     */
455 27
    private function appendRecordType($query)
456
    {
457 27
        if ($this->recordType !== null) {
458 5
            $query['record_type'] = $this->recordType;
459
460 5
            return $query;
461
        }
462
463 22
        return $query;
464
    }
465
466
    /**
467
     * @param $query
468
     * @return mixed
469
     */
470 27
    private function appendDates($query)
471
    {
472 27
        if ($this->dateCriterionField !== null) {
473 3
            $query['date_field'] = $this->dateCriterionField;
474
475 3
            if ($this->dateCriterionMin) {
476 2
                $query['date_min'] = $this->dateCriterionMin->format('Y/m/d');
477
            }
478
479 3
            if ($this->dateCriterionMax) {
480 2
                $query['date_max'] = $this->dateCriterionMax->format('Y/m/d');
481
482 2
                return $query;
483
            }
484
485 1
            return $query;
486
        }
487
488 24
        return $query;
489
    }
490
491
    /**
492
     * @param $query
493
     * @return mixed
494
     */
495 27
    private function appendStatuses($query)
496
    {
497 27
        if (!empty($this->statuses)) {
498 3
            $query['status'] = $this->statuses;
499
500 3
            return $query;
501
        }
502
503 24
        return $query;
504
    }
505
506
    /**
507
     * @param $query
508
     * @return mixed
509
     */
510 27
    private function appendFields($query)
511
    {
512 27
        if (!empty($this->fields)) {
513 3
            $query['fields'] = $this->fields;
514
515 3
            return $query;
516
        }
517
518 24
        return $query;
519
    }
520
521
    /**
522
     * @param $query
523
     * @return mixed
524
     */
525 27
    private function appendSort($query)
526
    {
527 27
        if ($this->sortType !== null) {
528 3
            $query['sort'] = $this->sortType;
529 3
            $query['ord'] = $this->sortDescending ? 'desc' : 'asc';
530
531 3
            return $query;
532
        }
533
534 24
        return $query;
535
    }
536
}
537