Completed
Push — master ( a5e29c...3496a4 )
by
unknown
02:21
created

RecordQueryBuilder::addStatuses()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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