Completed
Push — master ( ca3a89...f348a3 )
by Pavel
07:11 queued 04:12
created

Column   C

Complexity

Total Complexity 72

Size/Duplication

Total Lines 666
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 8

Importance

Changes 14
Bugs 1 Features 4
Metric Value
wmc 72
c 14
b 1
f 4
lcom 3
cbo 8
dl 0
loc 666
rs 5.2082

43 Methods

Rating   Name   Duplication   Size   Complexity  
A render() 0 23 3
A useRenderer() 0 18 4
A setTemplateEscaping() 0 6 1
A isTemplateEscaped() 0 4 1
A setSortable() 0 6 2
A isSortable() 0 4 1
A setSortableResetPagination() 0 6 1
A sortableResetPagination() 0 4 1
A setSortableCallback() 0 6 1
A getSortableCallback() 0 4 1
A getSortingColumn() 0 4 2
A getColumnName() 0 4 1
A getColumnValue() 0 4 1
A getName() 0 4 1
A setReplacement() 0 6 1
A hasReplacements() 0 4 1
A applyReplacements() 0 10 4
B setRenderer() 0 24 5
A setRendererOnCondition() 0 4 1
A getRenderer() 0 4 1
A setTemplate() 0 7 1
A getTemplateVariables() 0 4 1
A hasTemplate() 0 4 1
A getTemplate() 0 4 1
A isSortedBy() 0 4 1
A setSort() 0 6 1
A getSortNext() 0 10 3
A isSortAsc() 0 4 1
A setAlign() 0 6 1
A hasAlign() 0 4 1
A getAlign() 0 4 2
A setEditableCallback() 0 6 1
A getEditableCallback() 0 4 1
A isEditable() 0 4 1
A setEditableInputType() 0 6 1
A getEditableInputType() 0 4 1
A addAttributes() 0 7 1
C getElementPrototype() 0 43 8
A setDefaultHide() 0 10 2
A getDefaultHide() 0 4 1
A createLink() 0 16 3
A getItemParams() 0 10 3
A getColumn() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Column often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Column, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * @copyright   Copyright (c) 2015 ublaboo <[email protected]>
5
 * @author      Pavel Janda <[email protected]>
6
 * @package     Ublaboo
7
 */
8
9
namespace Ublaboo\DataGrid\Column;
10
11
use Nette\InvalidArgumentException;
12
use Ublaboo;
13
use Ublaboo\DataGrid\DataGrid;
14
use Ublaboo\DataGrid\Row;
15
use Ublaboo\DataGrid\Exception\DataGridException;
16
use Ublaboo\DataGrid\Exception\DataGridColumnRendererException;
17
use Ublaboo\DataGrid\Exception\DataGridHasToBeAttachedToPresenterComponentException;
18
use Nette\Utils\Html;
19
20
abstract class Column extends FilterableColumn
21
{
22
23
	/**
24
	 * @var array
25
	 */
26
	protected $replacements = [];
27
28
	/**
29
	 * @var Renderer|NULL
30
	 */
31
	protected $renderer;
32
33
	/**
34
	 * @var string
35
	 */
36
	protected $template;
37
38
	/**
39
	 * @var bool|string
40
	 */
41
	protected $sortable = FALSE;
42
43
	/**
44
	 * @var bool
45
	 */
46
	protected $sortable_reset_pagination = FALSE;
47
48
	/**
49
	 * @var null|callable
50
	 */
51
	protected $sortable_callback = NULL;
52
53
	/**
54
	 * @var array
55
	 */
56
	protected $sort;
57
58
	/**
59
	 * @var bool
60
	 */
61
	protected $template_escaping = TRUE;
62
63
	/**
64
	 * @var string
65
	 */
66
	protected $align;
67
68
	/**
69
	 * @var array
70
	 */
71
	protected $template_variables = [];
72
73
	/**
74
	 * @var callable
75
	 */
76
	protected $editable_callback;
77
78
	/**
79
	 * @var array
80
	 */
81
	protected $editable_element = ['textarea', ['class' => 'form-control']];
82
83
	/**
84
	 * Cached html elements
85
	 * @var array
86
	 */
87
	protected $el_cache = [];
88
89
	/**
90
	 * @var bool
91
	 */
92
	protected $default_hide = FALSE;
93
94
95
	/**
96
	 * Render row item into template
97
	 * @param  Row   $row
98
	 * @return mixed
99
	 */
100
	public function render(Row $row)
101
	{
102
		/**
103
		 * Renderer function may be used
104
		 */
105
		try {
106
			return $this->useRenderer($row);
107
		} catch (DataGridColumnRendererException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
108
			/**
109
			 * Do not use renderer
110
			 */
111
		}
112
113
		/**
114
		 * Or replacements may be applied
115
		 */
116
		list($do_replace, $replaced) = $this->applyReplacements($row);
117
		if ($do_replace) {
118
			return $replaced;
119
		}
120
121
		return $this->getColumnValue($row);
122
	}
123
124
125
	/**
126
	 * Try to render item with custom renderer
127
	 * @param  Row   $row
128
	 * @return mixed
129
	 */
130
	public function useRenderer(Row $row)
131
	{
132
		$renderer = $this->getRenderer();
133
134
		if (!$renderer) {
135
			throw new DataGridColumnRendererException;
136
		}
137
138
		if ($renderer->getConditionCallback()) {
139
			if (!call_user_func_array($renderer->getConditionCallback(), [$row->getItem()])) {
140
				throw new DataGridColumnRendererException;
141
			}
142
143
			return call_user_func_array($renderer->getCallback(), [$row->getItem()]);
144
		}
145
146
		return call_user_func_array($renderer->getCallback(), [$row->getItem()]);
147
	}
148
149
150
	/**
151
	 * Should be column values escaped in latte?
152
	 * @param bool $template_escaping
153
	 */
154
	public function setTemplateEscaping($template_escaping = TRUE)
155
	{
156
		$this->template_escaping = (bool) $template_escaping;
157
158
		return $this;
159
	}
160
161
162
	public function isTemplateEscaped()
163
	{
164
		return $this->template_escaping;
165
	}
166
167
168
	/**
169
	 * Set column sortable or not
170
	 * @param bool|string $sortable
171
	 */
172
	public function setSortable($sortable = TRUE)
173
	{
174
		$this->sortable = is_string($sortable) ? $sortable : (bool) $sortable;
175
176
		return $this;
177
	}
178
179
180
	/**
181
	 * Tell whether column is sortable
182
	 * @return bool
183
	 */
184
	public function isSortable()
185
	{
186
		return (bool) $this->sortable;
187
	}
188
189
190
	/**
191
	 * Shoud be the pagination reseted after sorting?
192
	 * @param bool $sortable_reset_pagination
193
	 * @return static
194
	 */
195
	public function setSortableResetPagination($sortable_reset_pagination = TRUE)
196
	{
197
		$this->sortable_reset_pagination = (bool) $sortable_reset_pagination;
198
199
		return $this;
200
	}
201
202
203
	/**
204
	 * Do reset pagination after sorting?
205
	 * @return bool
206
	 */
207
	public function sortableResetPagination()
208
	{
209
		return $this->sortable_reset_pagination;
210
	}
211
212
213
	/**
214
	 * Set custom ORDER BY clause
215
	 * @param callable $sortable_callback
216
	 * @return static
217
	 */
218
	public function setSortableCallback(callable $sortable_callback)
219
	{
220
		$this->sortable_callback = $sortable_callback;
221
222
		return $this;
223
	}
224
225
226
	/**
227
	 * Get custom ORDER BY clause
228
	 * @return callable|null
229
	 */
230
	public function getSortableCallback()
231
	{
232
		return $this->sortable_callback;
233
	}
234
235
236
	/**
237
	 * Get column to sort by
238
	 * @return string
239
	 */
240
	public function getSortingColumn()
241
	{
242
		return is_string($this->sortable) ? $this->sortable : $this->column;
243
	}
244
245
246
	/**
247
	 * Get column name
248
	 * @return string
249
	 */
250
	public function getColumnName()
251
	{
252
		return $this->column;
253
	}
254
255
256
	/**
257
	 * Get column value of row item
258
	 * @param  Row   $row
259
	 * @return mixed
260
	 */
261
	public function getColumnValue(Row $row)
262
	{
263
		return $row->getValue($this->column);
264
	}
265
266
267
	/**
268
	 * @return string
269
	 */
270
	public function getName()
271
	{
272
		return $this->name;
273
	}
274
275
276
	/**
277
	 * Set column replacements
278
	 * @param  array $replacements
279
	 * @return Column
280
	 */
281
	public function setReplacement(array $replacements)
282
	{
283
		$this->replacements = $replacements;
284
285
		return $this;
286
	}
287
288
289
	/**
290
	 * Tell whether columns has replacements
291
	 * @return bool
292
	 */
293
	public function hasReplacements()
294
	{
295
		return (bool) $this->replacements;
296
	}
297
298
299
	/**
300
	 * Apply replacements
301
	 * @param  Row   $row
302
	 * @return array
303
	 */
304
	public function applyReplacements(Row $row)
305
	{
306
		$value = $row->getValue($this->column);
307
308
		if ((is_scalar($value) || is_null($value)) && isset($this->replacements[$value])) {
309
			return [TRUE, $this->replacements[$value]];
310
		}
311
312
		return [FALSE, NULL];
313
	}
314
315
316
	/**
317
	 * Set renderer callback and (it may be optional - the condition callback will decide)
318
	 * @param callable $renderer
319
	 */
320
	public function setRenderer($renderer, $condition_callback = NULL)
321
	{
322
		if ($this->hasReplacements()) {
323
			throw new DataGridException(
324
				"Use either Column::setReplacement() or Column::setRenderer, not both."
325
			);
326
		}
327
328
		if (!is_callable($renderer)) {
329
			throw new DataGridException(
330
				"Renderer (method Column::setRenderer()) must be callable."
331
			);
332
		}
333
334
		if (NULL != $condition_callback && !is_callable($condition_callback)) {
335
			throw new DataGridException(
336
				"Renderer (method Column::setRenderer()) must be callable."
337
			);
338
		}
339
340
		$this->renderer = new Renderer($renderer, $condition_callback);
341
342
		return $this;
343
	}
344
345
346
	/**
347
	 * Set renderer callback just if condition is truthy
348
	 * @param callable $renderer
349
	 */
350
	public function setRendererOnCondition($renderer, $condition_callback)
351
	{
352
		return $this->setRenderer($renderer, $condition_callback);
353
	}
354
355
356
	/**
357
	 * Return custom renderer callback
358
	 * @return Renderer|null
359
	 */
360
	public function getRenderer()
361
	{
362
		return $this->renderer;
363
	}
364
365
366
	/**
367
	 * Column may have its own template
368
	 * @param string $template
369
	 */
370
	public function setTemplate($template, array $template_variables = [])
371
	{
372
		$this->template = $template;
373
		$this->template_variables = $template_variables;
374
375
		return $this;
376
	}
377
378
379
	/**
380
	 * Column can have variables that will be passed to custom template scope
381
	 * @return array
382
	 */
383
	public function getTemplateVariables()
384
	{
385
		return $this->template_variables;
386
	}
387
388
389
	/**
390
	 * Tell whether column has its owntemplate
391
	 * @return bool
392
	 */
393
	public function hasTemplate()
394
	{
395
		return (bool) $this->template;
396
	}
397
398
399
	/**
400
	 * Get column template path
401
	 * @return string
402
	 */
403
	public function getTemplate()
404
	{
405
		return $this->template;
406
	}
407
408
409
	/**
410
	 * Tell whether data source is sorted by this collumn
411
	 * @return bool
412
	 */
413
	public function isSortedBy()
414
	{
415
		return (bool) $this->sort;
416
	}
417
418
419
	/**
420
	 * Tell column his sorting options
421
	 * @param array $sort
422
	 */
423
	public function setSort(array $sort)
424
	{
425
		$this->sort = $sort[$this->getSortingColumn()];
426
427
		return $this;
428
	}
429
430
431
	/**
432
	 * What sorting will be applied after next click?
433
	 * @return array
434
	 */
435
	public function getSortNext()
436
	{
437
		if ($this->sort == 'ASC') {
438
			return [$this->key => 'DESC'];
439
		} else if ($this->sort == 'DESC') {
440
			return [$this->key => NULL];
441
		}
442
443
		return [$this->key => 'ASC'];
444
	}
445
446
447
	/**
448
	 * Is sorting ascending?
449
	 * @return bool
450
	 */
451
	public function isSortAsc()
452
	{
453
		return $this->sort == 'ASC';
454
	}
455
456
457
	/**
458
	 * Set column alignment
459
	 * @param string $align
460
	 */
461
	public function setAlign($align)
462
	{
463
		$this->align = (string) $align;
464
465
		return $this;
466
	}
467
468
469
	/**
470
	 * Has column some alignment?
471
	 * @return bool [description]
472
	 */
473
	public function hasAlign()
474
	{
475
		return (bool) $this->align;
476
	}
477
478
479
	/**
480
	 * Get column alignment
481
	 * @return string
482
	 */
483
	public function getAlign()
484
	{
485
		return $this->align ?: 'left';
486
	}
487
488
489
	/**
490
	 * Set callback that will be called after inline editing
491
	 * @param callable $editable_callback
492
	 */
493
	public function setEditableCallback(callable $editable_callback)
494
	{
495
		$this->editable_callback = $editable_callback;
496
497
		return $this;
498
	}
499
500
501
	/**
502
	 * Return callback that is used after inline editing
503
	 * @return callable
504
	 */
505
	public function getEditableCallback()
506
	{
507
		return $this->editable_callback;
508
	}
509
510
511
	/**
512
	 * Is column editable?
513
	 * @return bool
514
	 */
515
	public function isEditable()
516
	{
517
		return (bool) $this->getEditableCallback();
518
	}
519
520
521
	/**
522
	 * Element is by default textarea, user can chchge that
523
	 * @param string $el_type
524
	 * @param array  $attrs
525
	 * @return static
526
	 */
527
	public function setEditableInputType($el_type, array $attrs = [])
528
	{
529
		$this->editable_element = [$el_type, $attrs];
530
531
		return $this;
532
	}
533
534
535
	/**
536
	 * [getEditableInputType description]
537
	 * @return array
538
	 */
539
	public function getEditableInputType()
540
	{
541
		return $this->editable_element;
542
	}
543
544
545
	/**
546
	 * Set attributes for both th and td element
547
	 * @param array $attrs
548
	 * @return static
549
	 */
550
	public function addAttributes(array $attrs)
551
	{
552
		$this->getElementPrototype('td')->addAttributes($attrs);
553
		$this->getElementPrototype('th')->addAttributes($attrs);
554
555
		return $this;
556
	}
557
558
559
	/**
560
	 * Get th/td column element
561
	 * @param  string   $tag th|td
562
	 * @param  string   $key
563
	 * @param  Row|NULL $row
564
	 * @return Html
565
	 */
566
	public function getElementPrototype($tag, $key = NULL, Row $row = NULL)
567
	{
568
		/**
569
		 * Get cached element
570
		 */
571
		if (empty($this->el_cache[$tag])) {
572
			$this->el_cache[$tag] = $el = $el = Html::el($tag);
573
		} else {
574
			$el = $this->el_cache[$tag];
575
		}
576
577
		/**
578
		 * If class was set by user via $el->class = '', fix it
579
		 */
580
		if (!empty($el->class) && is_string($el->class)) {
581
			$class = $el->class;
582
			unset($el->class);
583
584
			$el->class[] = $class;
585
		}
586
587
		$el->class[] = "text-{$this->getAlign()}";
588
589
		/**
590
		 * Method called from datagrid template, set appropriate classes and another attributes
591
		 */
592
		if ($key !== NULL && $row !== NULL) {
593
			$el->class[] = "col-{$key}";
594
595
			if ($tag == 'td') {
596
				if ($this->isEditable()) {
597
					$link = $this->grid->link('edit!', ['key' => $key, 'id' => $row->getId()]);
598
599
					$el->data('datagrid-editable-url', $link);
600
601
					$el->data('datagrid-editable-type', $this->editable_element[0]);
602
					$el->data('datagrid-editable-attrs', json_encode($this->editable_element[1]));
603
				}
604
			}
605
		}
606
607
		return $el;
608
	}
609
610
611
	/**
612
	 * @param bool $default_hide
613
	 * @return static
614
	 */
615
	public function setDefaultHide($default_hide = TRUE)
616
	{
617
		$this->default_hide = (bool) $default_hide;
618
619
		if ($default_hide) {
620
			$this->grid->setSomeColumnDefaultHide($default_hide);
621
		}
622
623
		return $this;
624
	}
625
626
627
	public function getDefaultHide()
628
	{
629
		return $this->default_hide;
630
	}
631
632
633
	/**
634
	 * Create link to custom destination
635
	 * @param  string $href
636
	 * @param  array  $params
637
	 * @return string
638
	 * @throws DataGridHasToBeAttachedToPresenterComponentException
639
	 * @throws InvalidArgumentException
640
	 */
641
	protected function createLink($href, $params)
642
	{
643
		try {
644
			$parent = $this->grid->getParent();
645
646
			return $parent->link($href, $params);
647
		} catch (DataGridHasToBeAttachedToPresenterComponentException $e) {
648
			$parent = $this->grid->getPresenter();
649
650
		} catch (InvalidArgumentException $e) {
651
			$parent = $this->grid->getPresenter();
652
653
		}
654
655
		return $parent->link($href, $params);
656
	}
657
658
659
	/**
660
	 * Get row item params (E.g. action may be called id => $item->id, name => $item->name, ...)
661
	 * @param  Row   $row
662
	 * @param  array $params_list
663
	 * @return array
664
	 */
665
	protected function getItemParams(Row $row, array $params_list)
666
	{
667
		$return = [];
668
669
		foreach ($params_list as $param_name => $param) {
670
			$return[is_string($param_name) ? $param_name : $param] = $row->getValue($param);
671
		}
672
673
		return $return;
674
	}
675
676
677
	/**
678
	 * @return string
679
	 */
680
	public function getColumn()
681
	{
682
		return $this->column;
683
	}
684
685
}
686