Completed
Push — master ( 0ff545...8e26dc )
by Vitaly
04:41 queued 56s
created

Collection::entityHandler()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 7
rs 9.4285
cc 1
eloc 3
nc 1
nop 2
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: egorov
5
 * Date: 26.12.2014
6
 * Time: 16:22
7
 */
8
namespace samsoncms\api;
9
10
use samsonframework\orm\Condition
11
use samsonframework\collection\Paged;
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_USE, expecting ',' or ';'
Loading history...
12
use samsonframework\orm\Relation;
13
use samsonframework\pager\PagerInterface;
14
use samsonframework\core\RenderInterface;
15
use samsonframework\orm\QueryInterface;
16
17
/**
18
 * Collection query builder for filtering
19
 * @package samsonos\cms\collection
20
 * @author Egorov Vitaly <[email protected]>
21
 * @deprecated Use generated Entities and EntityQueries classes.
22
 */
23
class Collection extends Paged
24
{
25
    /** @var string Entity manager instance */
26
    protected $managerEntity = '\samsoncms\api\query\Generic';
27
28
    /** @var array Collection for current filtered material identifiers */
29
    protected $materialIDs = array();
30
31
    /** @var array Collection of navigation filters */
32
    protected $navigation = array();
33
34
    /** @var array Collection of field filters */
35
    protected $field = array();
36
37
    /** @var array Collection of query handlers */
38
    protected $idHandlers = array();
39
40
    /** @var array External material handler and params array */
41
    protected $entityHandlers = array();
42
43
    /** @var array Base material entity handler callbacks array */
44
    protected $baseEntityHandlers = array();
45
46
    /** @var string Collection entities class name */
47
    protected $entityName = 'samson\cms\CMSMaterial';
48
49
    /**
50
     * Generic collection constructor
51
     * @param RenderInterface $renderer View render object
52
     * @param QueryInterface $query Query object
53
     */
54
    public function __construct(RenderInterface $renderer, QueryInterface $query, PagerInterface $pager)
55
    {
56
        // Call parent initialization
57
        parent::__construct($renderer, $query->className('material'), $pager);
58
    }
59
60
    /**
61
     * Render products collection block
62
     * @param string $prefix Prefix for view variables
63
     * @param array $restricted Collection of ignored keys
64
     * @return array Collection key => value
65
     */
66
    public function toView($prefix = null, array $restricted = array())
67
    {
68
        // Render pager and collection
69
        return array(
70
            $prefix.'html' => $this->render(),
71
            $prefix.'pager' => $this->pager->total > 1 ? $this->pager->toHTML() : ''
72
        );
73
    }
74
75
    /**
76
     * Add external identifier filter handler
77
     * @param callback $handler
78
     * @param array $params
79
     * @return self Chaining
80
     */
81
    public function handler($handler, array $params = array())
82
    {
83
        // Add callback with parameters to array
84
        $this->idHandlers[] = array($handler, $params);
85
86
        return $this;
87
    }
88
89
    /**
90
     * Set external entity handler
91
     * @param callback $handler
92
     * @param array $params
93
     * @return self Chaining
94
     */
95
    public function baseEntityHandler($handler, array $params = array())
96
    {
97
        // Add callback with parameters to array
98
        $this->baseEntityHandlers[] = array($handler, $params);
99
100
        return $this;
101
    }
102
103
    /**
104
     * Set external entity handler
105
     * @param callback $handler
106
     * @param array $params
107
     * @return self Chaining
108
     */
109
    public function entityHandler($handler, array $params = array())
110
    {
111
        // Add callback with parameters to array
112
        $this->entityHandlers[] = array($handler, $params);
113
114
        return $this;
115
    }
116
117
    /**
118
     * Set collection sorter parameters
119
     * @param string|integer $field Field identifier or name
120
     * @param string $destination ASC|DESC
121
     * @return void
122
     */
123
    public function sorter($field, $destination = 'ASC')
124
    {
125
        /**@var \samson\activerecord\field $field */
126
        // TODO: Add ability to sort with entity fields
127
        if (in_array($field, \samson\activerecord\material::$_attributes)) {
128
            $this->sorter = array(
129
                'field' => $field,
130
                'name' => $field,
131
                'destination' => $destination
132
            );
133
        } else if ($this->isFieldObject($field)) {
134
            $this->sorter = array(
135
                'entity' => $field,
136
                'name' => $field->Name,
137
                'field' => in_array($field->Type, array(3, 7, 10)) ? 'numeric_value' : 'value',
138
                'destination' => $destination
139
            );
140
        }
141
    }
142
143
    /**
144
     * Filter collection using navigation entity or collection of them.
145
     * If collection of navigation Url or Ids is passed then this group will be
146
     * applied as single navigation filter to retrieve materials.
147
     *
148
     * @param string|integer|array $navigation Navigation URL or identifier for filtering
149
     * @return self Chaining
150
     */
151
    public function navigation($navigation)
152
    {
153
        // Do not allow empty strings
154
        if (!empty($navigation)) {
155
            // Create id or URL condition
156
            $idOrUrl = new Condition('OR');
157
            $idOrUrl->add('StructureID', $navigation)->add('Url', $navigation);
158
159
            /** @var array $navigationIds  */
160
            $navigationIds = null;
161
            if ($this->query->className('structure')->cond($idOrUrl)->fields('StructureID', $navigationIds)) {
162
                // Store all retrieved navigation elements as navigation collection filter
163
                $this->navigation[] = $navigationIds;
164
            }
165
        }
166
167
        // Chaining
168
        return $this;
169
    }
170
171
    /**
172
     * Filter collection using additional field entity.
173
     *
174
     * @param string|integer|Field $field Additional field identifier or name
175
     * @param mixed $value Additional field value for filtering
176
     * @param string $relation Additional field relation for filtering
177
     * @return self Chaining
178
     */
179
    public function field($field, $value, $relation = Relation::EQUAL)
180
    {
181
        // Do not allow empty strings
182
        if ($this->isFieldObject($field)) {
183
            // Get field value column
184
            $valueField = in_array($field->Type, array(3, 7, 10)) ? 'numeric_value' : 'value';
185
			$valueField = $field->Type == 6 ? 'key_value' : $valueField;
186
			
187
            /** @var Condition $condition Ranged condition */
188
            $condition = new Condition('AND');
189
190
            // Add min value for ranged condition
191
            $condition->add($valueField, $value, $relation);
192
193
            // Store retrieved field element and its value as field collection filter
194
            $this->field[] = array($field, $condition);
195
        }
196
197
        // Chaining
198
        return $this;
199
    }
200
201
    /**
202
     * Filter collection using additional field entity values and LIKE relation.
203
     * If this method is called more then once, it will use materials, previously filtered by this method.
204
     *
205
     * @param string $search Search string
206
     * @return self Chaining
207
     */
208
    public function search($search)
209
    {
210
        // If input parameter is a string add it to search string collection
211
        if (isset($search{0})) {
212
            $this->search[] = $search;
213
        }
214
215
        // Chaining
216
        return $this;
217
    }
218
219
    /**
220
     * Filter collection of numeric field in range from min to max values
221
     * @param string|integer|Field $field Additional field identifier or name
222
     * @param integer $minValue Min value for range filter
223
     * @param integer $maxValue Max value for range filter
224
     * @return self Chaining
225
     */
226
    public function ranged($field, $minValue, $maxValue)
227
    {
228
        // Check input parameters and try to find field
229
        if (($minValue <= $maxValue) && $this->isFieldObject($field)) {
230
            // TODO: Remove integers from code, handle else
231
            // Only numeric fields are supported
232
            if (in_array($field->Type, array(3, 7, 10))) {
233
                /** @var Condition $condition Ranged condition */
234
                $condition = new Condition('AND');
235
236
                // Add min value for ranged condition
237
                $condition->add('numeric_value', $minValue, Relation::GREATER_EQ);
238
239
                // Add max value for ranged condition
240
                $condition->add('numeric_value', $maxValue, Relation::LOWER_EQ);
241
242
                // Store created condition
243
                $this->field[] = array($field, $condition);
244
            }
245
        }
246
247
        // Chaining
248
        return $this;
249
    }
250
251
    /**
252
     * Try to find additional field record
253
     * @param string|integer $field Additional field identifier or name
254
     * @return bool True if field record has been found
255
     */
256
    protected function isFieldObject(&$field)
257
    {
258
        // Do not allow empty strings
259
        if (!empty($field)) {
260
            // Create id or URL condition
261
            $idOrUrl = new Condition('OR');
262
            $idOrUrl->add('FieldID', $field)->add('Name', $field);
263
264
            // Perform query
265
            return $this->query->className('field')->cond($idOrUrl)->first($field);
266
        }
267
268
        // Field not found
269
        return false;
270
    }
271
272
    /**
273
     * Try to get all material identifiers filtered by navigation
274
     * if no navigation filtering is set - nothing will happen.
275
     *
276
     * @param array $filteredIds Collection of filtered material identifiers
277
     * @return bool True if ALL navigation filtering succeeded or there was no filtering at all otherwise false
278
     */
279
    protected function applyNavigationFilter(& $filteredIds = array())
280
    {
281
        // Iterate all applied navigation filters
282
        foreach ($this->navigation as $navigation) {
283
            // Create navigation-material query
284
            $this->query->className('structurematerial')
285
286
                ->cond('StructureID', $navigation)
287
                ->cond('Active', 1)
288
                ->group_by('MaterialID')
289
            ;
290
291
            if (isset($filteredIds)) {
292
                $this->query->cond('MaterialID', $filteredIds);
293
            }
294
295
            // Perform request to get next portion of filtered material identifiers
296
            if (!$this->query->fields('MaterialID', $filteredIds)) {
297
                // This filter applying failed
298
                return false;
299
            }
300
        }
301
302
        // We have no navigation collection filters
303
        return true;
304
    }
305
306
    /**
307
     * Try to get all material identifiers filtered by additional field
308
     * if no field filtering is set - nothing will happen.
309
     *
310
     * @param array $filteredIds Collection of filtered material identifiers
311
     * @return bool True if ALL field filtering succeeded or there was no filtering at all otherwise false
312
     */
313
    protected function applyFieldFilter(& $filteredIds = array())
314
    {
315
        // Iterate all applied field filters
316
        foreach ($this->field as $field) {
317
            // Create material-field query
318
            $this->query->className('materialfield')
319
                ->cond('FieldID', $field[0]->id)
320
                ->cond($field[1])
321
                ->group_by('MaterialID')
322
            ;
323
324
            if (isset($filteredIds)) {
325
                $this->query->cond('MaterialID', $filteredIds);
326
            }
327
328
            // Perform request to get next portion of filtered material identifiers
329
            if (!$this->query->fields('MaterialID', $filteredIds)) {
330
                // This filter applying failed
331
                return false;
332
            }
333
        }
334
335
        // We have no field collection filters
336
        return true;
337
    }
338
339
    /**
340
     * Try to find all materials which have fields similar to search strings
341
     *
342
     * @param array $filteredIds Collection of filtered material identifiers
343
     * @return bool True if ALL field filtering succeeded or there was no filtering at all otherwise false
344
     */
345
    protected function applySearchFilter(& $filteredIds = array())
346
    {
347
        /** @var array $fields Variable to store all fields related to set navigation */
348
        $fields = array();
349
        /** @var array $navigationArray Array of set navigation identifiers */
350
        $navigationArray = array();
351
        /** @var array $fieldFilter Array of filtered material identifiers via materialfield table */
352
        $fieldFilter = array();
353
        /** @var array $materialFilter Array of filtered material identifiers via material table */
354
        $materialFilter = array();
355
356
        // If there are at least one search string
357
        if (!empty($this->search)) {
358
            // Create array containing all navigation identifiers
359
            foreach ($this->navigation as $navigation) {
360
                // Navigation hook for searching action
361
                $navigation = is_array($navigation) ? $navigation : array($navigation);
362
                $navigationArray = array_merge($navigationArray, $navigation);
363
            }
364
365
            // Get all related fields
366
            $this->query->className('structurefield')
367
                ->cond('StructureID', $navigationArray)
368
                ->group_by('FieldID')
369
                ->fields('FieldID', $fields);
370
371
            // Iterate over search strings
372
            foreach ($this->search as $searchString) {
373
                // Try to find search value in materialfield table
374
                $this->query->className('materialfield')
375
                    ->cond('FieldID', $fields)
376
                    ->cond('MaterialID', $filteredIds)
377
                    ->cond('Value', '%' . $searchString . '%', Relation::LIKE)
378
                    ->cond('Active', 1)
379
                    ->group_by('MaterialID')
380
                    ->fields('MaterialID', $fieldFilter);
381
382
                // TODO: Add generic support for all native fields or their configuration
383
                // Condition to search in material table by Name and URL
384
                $materialCondition = new Condition('OR');
385
                $materialCondition->add('Name', '%' . $searchString . '%', Relation::LIKE)
386
                    ->add('Url', '%' . $searchString . '%', Relation::LIKE);
387
388
389
                // Try to find search value in material table
390
                $this->query->className('material')
391
                    ->cond($materialCondition)
392
                    ->cond('Active', 1);
393
394
                // If we have not empty collection of filtering identifiers
395
                if (sizeof($filteredIds)) {
396
                    $this->query->cond('MaterialID', $filteredIds);
397
                }
398
399
                $materialFilter = $this->query->fields('MaterialID');
400
401
                // If there are no materials with specified conditions
402
                if (empty($materialFilter) && empty($fieldFilter)) {
403
                    // Filter applying failed
404
                    return false;
405
                } else {// Otherwise set filtered material identifiers
406
                    $filteredIds = array_unique(array_merge($materialFilter, $fieldFilter));
407
                }
408
            }
409
        }
410
411
        // We have no search collection filters
412
        return true;
413
    }
414
415
    /**
416
     * Apply all possible material filters
417
     * @param array $filteredIds Collection of material identifiers
418
     * @return bool True if ALL filtering succeeded or there was no filtering at all otherwise false
419
     */
420
    protected function applyFilter(& $filteredIds = array())
421
    {
422
        return $this->applyNavigationFilter($filteredIds)
423
            && $this->applyFieldFilter($filteredIds)
424
            && $this->applySearchFilter($filteredIds);
425
    }
426
427
    /**
428
     * Perform material identifiers collection sorting
429
     * @param array $materialIDs Variable to return sorted collection
430
     */
431
    protected function applyFieldSorter(&$materialIDs = array())
432
    {
433
        // Check if sorter is configured
434
        if (sizeof($this->sorter)) {
435
            // If we need to sort by entity additional field(column)
436
            if (!in_array($this->sorter['field'], \samson\activerecord\material::$_attributes)) {
437
                // Sort material identifiers by its additional fields
438
                $this->query->className('materialfield')
439
                    ->cond('FieldID', $this->sorter['entity']->id)
440
                    ->order_by($this->sorter['field'], $this->sorter['destination'])
441
                    ->cond('MaterialID', $materialIDs)
442
                    ->fields('MaterialID', $materialIDs);
443
            }
444
        }
445
    }
446
447
    /**
448
     * Call handlers stack
449
     * @param array $handlers Collection of callbacks with their parameters
450
     * @param array $params External parameters to pass to callback at first
451
     * @return bool True if all handlers succeeded
452
     */
453
    protected function callHandlers(& $handlers = array(), $params = array())
454
    {
455
        // Call external handlers
456
        foreach ($handlers as $handler) {
457
            // Call external handlers chain
458
            if (call_user_func_array(
459
                $handler[0],
460
                array_merge($params, $handler[1]) // Merge params and handler params
461
            ) === false) {
462
                // Stop - if one of external handlers has failed
463
                return false;
464
            }
465
        }
466
467
        return true;
468
    }
469
470
    /**
471
     * Perform filtering on base material entity
472
     * @param array $materialIDs Variable to return sorted collection
473
     */
474
    protected function applyBaseEntityFilter(& $materialIDs = array())
475
    {
476
        // TODO: Change this to new OOP approach
477
        $class = $this->entityName;
478
479
        // Configure query to base entity
480
        $this->query->className('samson\activerecord\material');
481
482
        // Call base material entity handlers to prepare query
483
        $this->callHandlers($this->baseEntityHandlers, array(&$this->query));
484
485
        // Check if sorter is configured
486
        if (sizeof($this->sorter)) {
487
            // If we need to sort by entity own field(column)
488
            if (in_array($this->sorter['field'], $class::$_attributes)) {
489
                // Add material entity sorter
490
                $this->query->order_by($this->sorter['field'], $this->sorter['destination']);
491
            }
492
        }
493
494
        // Perform main entity query
495
        $this->materialIDs = $this->query
496
            ->cond('Active', 1) // Remove deleted entities
497
            ->cond('system', 0) // Remove system entities
498
            ->cond($class::$_primary, $materialIDs) // Filter to current set
499
            ->fields($class::$_primary)
500
        ;
501
    }
502
503
    /**
504
     * Perform collection database retrieval using set filters
505
     *
506
     * @return self Chaining
507
     */
508
    public function fill()
509
    {
510
        // Clear current materials identifiers list
511
        $this->materialIDs = null;
512
513
        // TODO: Change this to new OOP approach
514
        $class = $this->entityName;
515
516
        // If no filters is set
517
        if (!sizeof($this->search) && !sizeof($this->navigation) && !sizeof($this->field)) {
518
            // Get all entity records identifiers
519
            $this->materialIDs = $this->query->cond('Active', 1)->cond('system', 0)->fields($class::$_primary);
520
        }
521
522
        // Perform material filtering
523
        if ($this->applyFilter($this->materialIDs)) {
524
            // Now we have all possible material filters applied and final material identifiers collection
525
526
            // Store filtered collection size
527
            $this->count = sizeof($this->materialIDs);
528
529
            // Call material identifier handlers
530
            $this->callHandlers($this->idHandlers, array(&$this->materialIDs));
531
532
            // Perform base entity query for final filtering
533
            $this->applyBaseEntityFilter($this->materialIDs);
534
535
            // Perform sorting
536
            $this->applyFieldSorter($this->materialIDs);
537
538
            // Create count request to count pagination
539
            $this->pager->update(sizeof($this->materialIDs));
540
541
            // Cut only needed materials identifiers from array
542
            $this->materialIDs = array_slice($this->materialIDs, $this->pager->start, $this->pager->end);
543
544
            // Create final material query
545
            $this->query->className($this->entityName)->cond($class::$_primary, $this->materialIDs);
546
547
            // Call material query handlers
548
            $this->callHandlers($this->entityHandlers, array(&$this->query));
549
550
            // Add query sorter for showed page
551
            if (sizeof($this->sorter)) {
552
                $this->query->order_by($this->sorter['name'], $this->sorter['destination']);
553
            }
554
555
            // Return final filtered entity query result
556
            $this->collection = $this->query->exec();
557
558
        } else { // Collection is empty
559
560
            // Clear current materials identifiers list
561
            $this->materialIDs = array();
562
563
            // Updated pagination
564
            $this->pager->update(sizeof($this->materialIDs));
565
        }
566
567
        // Chaining
568
        return $this;
569
    }
570
}
571