Listing::setState()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
c 0
b 0
f 0
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
1
<?php
2
/**
3
 * Spiral Framework.
4
 *
5
 * @license   MIT
6
 * @author    Valentin, Anton Titov (Wolfy-J)
7
 */
8
namespace Spiral\Listing;
9
10
11
use Spiral\Listing\Exceptions\InvalidSelectorException;
12
use Spiral\Listing\Exceptions\ListingException;
13
use Spiral\Listing\Exceptions\SorterException;
14
use Spiral\Listing\Traits\SelectorValidationTrait;
15
use Spiral\ODM\Entities\DocumentSelector;
16
use Spiral\ORM\Entities\RecordSelector;
17
use Spiral\Pagination\Paginator;
18
use Spiral\Pagination\PaginatorAwareInterface;
19
20
class Listing implements \IteratorAggregate
21
{
22
    use SelectorValidationTrait;
23
24
    /**
25
     * @var RecordSelector|DocumentSelector
26
     */
27
    private $selector = null;
28
29
    /**
30
     * List of added filters.
31
     *
32
     * @var FilterInterface[]
33
     */
34
    private $filters = [];
35
36
    /**
37
     * List of added sorters.
38
     *
39
     * @var SorterInterface[]
40
     */
41
    private $sorters = [];
42
43
    /**
44
     * Available selection limits
45
     *
46
     * @var array
47
     */
48
    private $limits = [25, 50, 100];
49
50
    /**
51
     * Stores current listing state.
52
     *
53
     * @var StateInterface|null
54
     */
55
    private $state = null;
56
57
    /**
58
     * Default state to be used if primary state is not active
59
     *
60
     * @var StateInterface|null
61
     */
62
    private $defaultState = null;
63
64
    /**
65
     * @param PaginatorAwareInterface|RecordSelector|DocumentSelector $selector
66
     * @param StateInterface                                          $state
67
     *
68
     * @throws InvalidSelectorException
69
     */
70
    public function __construct(
71
        PaginatorAwareInterface $selector = null,
72
        StateInterface $state = null
73
    ) {
74
        if (!empty($selector)) {
75
            $this->validateSelector($selector);
76
            $this->selector = $selector;
0 ignored issues
show
Documentation Bug introduced by
It seems like $selector of type object<Spiral\Pagination\PaginatorAwareInterface> is incompatible with the declared type object<Spiral\ORM\Entiti...ities\DocumentSelector> of property $selector.

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...
77
        }
78
79
        if (!empty($state)) {
80
            $this->setState($state);
81
        }
82
    }
83
84
    /**
85
     * Set active listing selector.
86
     *
87
     * @param RecordSelector|DocumentSelector|PaginatorAwareInterface $selector
88
     *
89
     * @return $this
90
     *
91
     * @throws InvalidSelectorException
92
     */
93
    public function setSelector(PaginatorAwareInterface $selector)
94
    {
95
        $this->validateSelector($selector);
96
        $this->selector = $selector;
0 ignored issues
show
Documentation Bug introduced by
It seems like $selector of type object<Spiral\Pagination\PaginatorAwareInterface> is incompatible with the declared type object<Spiral\ORM\Entiti...ities\DocumentSelector> of property $selector.

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...
97
98
        return $this;
99
    }
100
101
    /**
102
     * Get configured listing selector
103
     *
104
     * @return DocumentSelector|RecordSelector
105
     *
106
     * @throws ListingException
107
     */
108
    public function getSelector()
109
    {
110
        if (empty($this->selector)) {
111
            throw new ListingException("No selector were associated with listing instance");
112
        }
113
114
        return $this->configureSelector(clone $this->selector);
115
    }
116
117
    /**
118
     * @return DocumentSelector|RecordSelector
119
     *
120
     * @throws ListingException
121
     */
122
    public function getIterator()
123
    {
124
        return $this->getSelector();
125
    }
126
127
    /**
128
     * State responsible for listing settings management.
129
     *
130
     * @param StateInterface $state
131
     * @return $this
132
     */
133
    public function setState(StateInterface $state)
134
    {
135
        $this->state = clone $state;
136
137
        return $this;
138
    }
139
140
    /**
141
     * Listing state.
142
     *
143
     * @return StateInterface
144
     */
145
    public function getState()
146
    {
147
        return $this->state;
148
    }
149
150
    /**
151
     * Default state to be used if no active state can be found
152
     *
153
     * @param StateInterface $state
154
     * @return $this
155
     */
156
    public function setDefaultState(StateInterface $state)
157
    {
158
        $this->defaultState = clone $state;
159
160
        return $this;
161
    }
162
163
    /**
164
     * Default state, if any were set.
165
     *
166
     * @return null|StateInterface
167
     */
168
    public function getDefaultState()
169
    {
170
        return $this->defaultState;
171
    }
172
173
    /**
174
     * Active listing state (with fallback to default state)
175
     *
176
     * @return StateInterface
177
     * @throws ListingException
178
     */
179
    public function activeState()
180
    {
181
        if (!empty($this->state) && $this->state->isActive()) {
182
            return $this->getState();
183
        }
184
185
        if (!empty($this->defaultState) && $this->defaultState->isActive()) {
186
            //Falling back to default state
187
            return $this->getDefaultState();
188
        }
189
190
        if (empty($this->defaultState) && !empty($this->state)) {
191
            //No default state, so we have to use state anyway
192
            return $this->getState();
193
        }
194
195
        throw new ListingException("Unable to get active state, no active states are set");
196
    }
197
198
    /**
199
     * @return string
200
     */
201
    public function getNamespace()
202
    {
203
        return $this->state->getNamespace();
204
    }
205
206
    /**
207
     * Set listing isolation namespace
208
     *
209
     * @param string $namespace
210
     *
211
     * @return $this
212
     */
213
    public function setNamespace($namespace)
214
    {
215
        $this->state = $this->state->withNamespace($namespace);
216
217
        return $this;
218
    }
219
220
    /**
221
     * Set allowed pagination limits
222
     *
223
     * @param array $limits
224
     *
225
     * @return $this
226
     */
227
    public function setLimits(array $limits = [])
228
    {
229
        if (empty($limits)) {
230
            throw new ListingException("You must provide at least one limit option");
231
        }
232
233
        $this->limits = array_values($limits);
234
235
        return $this;
236
    }
237
238
    /**
239
     * Available limits.
240
     *
241
     * @return array
242
     */
243
    public function getLimits()
244
    {
245
        return $this->limits;
246
    }
247
248
    /**
249
     * Modify selector
250
     *
251
     * @param PaginatorAwareInterface|RecordSelector|DocumentSelector $selector
252
     *
253
     * @return RecordSelector|DocumentSelector Modified selector
254
     *
255
     * @throws InvalidSelectorException
256
     */
257
    public function configureSelector(PaginatorAwareInterface $selector)
258
    {
259
        if (empty($this->state)) {
260
            throw new ListingException("Unable to pagination without state being set");
261
        }
262
263
        $this->validateSelector($selector);
264
265
        foreach ($this->activeFilters() as $filter) {
266
            $selector = $filter->apply($selector);
0 ignored issues
show
Bug introduced by
It seems like $selector can also be of type object<Spiral\Pagination\PaginatorAwareInterface>; however, Spiral\Listing\FilterInterface::apply() does only seem to accept object<Spiral\ORM\Entiti...ities\DocumentSelector>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
267
        }
268
269
        if (!empty($sorter = $this->activeSorter())) {
270
            $selector = $sorter->apply($selector);
0 ignored issues
show
Bug introduced by
It seems like $selector can also be of type object<Spiral\Pagination\PaginatorAwareInterface>; however, Spiral\Listing\SorterInterface::apply() does only seem to accept object<Spiral\ORM\Entiti...ities\DocumentSelector>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
271
        }
272
273
        //Pagination
274
        $selector->setPaginator($this->createPaginator());
275
276
        return $selector;
277
    }
278
279
    /**
280
     * Add new selection filter
281
     *
282
     * @param string          $name
283
     * @param FilterInterface $filter
284
     *
285
     * @return self
286
     */
287
    final public function addFilter($name, FilterInterface $filter)
288
    {
289
        $this->filters[$name] = $filter;
290
291
        return $this;
292
    }
293
294
    /**
295
     * List of every associated filter.
296
     *
297
     * @return FilterInterface[]
298
     */
299
    public function getFilters()
300
    {
301
        return $this->filters;
302
    }
303
304
    /**
305
     * Attach new sorter to listing
306
     *
307
     * @param string          $name
308
     * @param SorterInterface $sorter
309
     *
310
     * @return self
311
     */
312
    final public function addSorter($name, SorterInterface $sorter)
313
    {
314
        $this->sorters[$name] = $sorter;
315
316
        return $this;
317
    }
318
319
    /**
320
     * Every available sorter
321
     *
322
     * @return SorterInterface[]
323
     */
324
    public function getSorters()
325
    {
326
        return $this->sorters;
327
    }
328
329
    /**
330
     * List of filters to be applied to current selection (named list)
331
     *
332
     * @return FilterInterface[]
333
     *
334
     * @throws ListingException
335
     */
336
    public function activeFilters()
337
    {
338
        $state = $this->activeState();
339
340
        $result = [];
341
        foreach ($state->activeFilters() as $name) {
342
            if (!isset($this->filters[$name])) {
343
                //No such filter
344
                continue;
345
            }
346
347
            $filter = $this->filters[$name];
348
            $value = $state->getValue($name);
349
350
            if ($filter->isApplicable($value)) {
351
                $result[$name] = $filter->withValue($value);
352
            }
353
        }
354
355
        return $result;
356
    }
357
358
    /**
359
     * Looking for active sorter name (normalized).
360
     *
361
     * @return string|null
362
     */
363
    public function getSorter()
364
    {
365
        $state = $this->activeState();
366
367
        if (!isset($this->sorters[$state->activeSorter()])) {
368
            //No such sorter
369
            return null;
370
        }
371
372
        return $state->activeSorter();
373
    }
374
375
    /**
376
     * Active sorter instance (if any)
377
     *
378
     * @return SorterInterface|null
379
     *
380
     * @throws SorterException
381
     */
382
    public function activeSorter()
383
    {
384
        if (empty($sorter = $this->getSorter())) {
385
            return null;
386
        }
387
388
        $sorter = $this->sorters[$sorter];
389
390
        if ($sorter instanceof DirectionalSorterInterface) {
391
            return $sorter->withDirection($this->activeState()->sortDirection());
392
        }
393
394
        return clone $sorter;
395
    }
396
397
    /**
398
     * Get paginator associated with current listing
399
     *
400
     * @return Paginator
401
     */
402
    protected function createPaginator()
403
    {
404
        $paginator = new Paginator($this->getLimit());
405
        $paginator = $paginator->withPage($this->getPage());
406
407
        return $paginator;
408
    }
409
410
    /**
411
     * Get active pagination limit
412
     *
413
     * @return int
414
     */
415
    protected function getLimit()
416
    {
417
        $limit = $this->activeState()->getLimit();
418
        if (!in_array($limit, $this->limits)) {
419
            //We are using lowest limit by default
420
            return $this->limits[0];
421
        }
422
423
        return $limit;
424
    }
425
426
    /**
427
     * Get current page.
428
     *
429
     * @return int
430
     */
431
    protected function getPage()
432
    {
433
        return $this->activeState()->getPage();
434
    }
435
}