Completed
Push — master ( b5f39d...3aa058 )
by Vitaliy
03:15
created

PageTotalsRow::pushData()   C

Complexity

Conditions 12
Paths 44

Size

Total Lines 48
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 156

Importance

Changes 3
Bugs 2 Features 0
Metric Value
c 3
b 2
f 0
dl 0
loc 48
ccs 0
cts 42
cp 0
rs 5.1266
cc 12
eloc 36
nc 44
nop 2
crap 156

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace ViewComponents\Grids\Component;
4
5
use Closure;
6
use LogicException;
7
use Nayjest\Tree\ChildNodeTrait;
8
use ViewComponents\ViewComponents\Base\Compound\PartInterface;
9
use ViewComponents\ViewComponents\Base\Compound\PartTrait;
10
use ViewComponents\ViewComponents\Base\ViewComponentInterface;
11
use ViewComponents\ViewComponents\Component\CollectionView;
12
use ViewComponents\ViewComponents\Component\Compound;
13
use ViewComponents\Grids\Grid;
14
use ViewComponents\ViewComponents\Component\ManagedList;
15
use ViewComponents\ViewComponents\Rendering\ViewTrait;
16
17
/**
18
 * Class PageTotalsRow
19
 */
20
class PageTotalsRow implements PartInterface, ViewComponentInterface
21
{
22
    use PartTrait {
23
        PartTrait::attachToCompound as attachToCompoundInternal;
24
    }
25
    use ChildNodeTrait;
26
    use ViewTrait;
27
28
    const ID = 'page_totals_row';
29
30
    const OPERATION_SUM = 'sum';
31
    const OPERATION_AVG = 'avg';
32
    const OPERATION_COUNT = 'count';
33
    const OPERATION_IGNORE = 'ignore';
34
35
    protected $valuePrefixes = [
36
        self::OPERATION_SUM => 'Σ',
37
        self::OPERATION_AVG => 'Avg.',
38
        self::OPERATION_COUNT => 'Count:'
39
    ];
40
41
    /**
42
     * Keys are column id's and values are operations (see PageTotalsRow::OPERATION_* constants).
43
     *
44
     * @var string[]|array
45
     */
46
    protected $operations;
47
48
    protected $totalData;
49
50
    protected $cellObserver;
51
52
    protected $rowsProcessed = 0;
53
54
    private $isTotalsCalculationFinished = false;
55
56
    /**
57
     * @var string
58
     */
59
    private $defaultOperation;
60
61
    /**
62
     * PageTotalsRow constructor.
63
     *
64
     * Operations passed to first argument ($operations) may contain values
65
     * of PageTotalsRow::OPERATIN_* constants or Closure or null. Keys must be equal to target column id's
66
     * If $operations has no value for column, default operation will be used for that column.
67
     *
68
     * Closure passed to operations can accept
69
     * accumulated value in first argument and current row value in second argument.
70
     *
71
     * @param array|string[] $operations (optional) keys are column id's and values are operations
72
     *                                   (see PageTotalsRow::OPERATION_* constants) or closures.
73
     * @param string|null $defaultOperation operation that will be used for column
74
     *                                      if operation isn't specified for this column in first argument.
75
     */
76
    public function __construct(array $operations = [], $defaultOperation = null)
77
    {
78
        $this->id = static::ID;
79
        $this->destinationParentId = ManagedList::LIST_CONTAINER_ID;
80
        $this->operations = $operations;
81
        if ($defaultOperation === null) {
82
            $defaultOperation = empty($operations) ? self::OPERATION_SUM : self::OPERATION_IGNORE;
83
        }
84
        $this->defaultOperation = $defaultOperation;
85
    }
86
87
    /**
88
     * @param Compound|Grid $root
89
     * @param bool $prepend
90
     */
91
    public function attachToCompound(Compound $root, $prepend = false)
92
    {
93
        $this->attachToCompoundInternal($root, $prepend);
94
        $this->replaceGridDataInjector($root);
0 ignored issues
show
Compatibility introduced by
$root of type object<ViewComponents\Vi...nts\Component\Compound> is not a sub-type of object<ViewComponents\Grids\Grid>. It seems like you assume a child class of the class ViewComponents\ViewComponents\Component\Compound to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
95
    }
96
97
    /**
98
     * Returns string prefixes for data printed in totals row for different operations.
99
     *
100
     * Keys are operations and values are prefixes.
101
     *
102
     * @return string[]
103
     */
104
    public function getValuePrefixes()
105
    {
106
        return $this->valuePrefixes;
107
    }
108
109
    /**
110
     * Sets string prefixes for data printed in totals row for different operations.
111
     *
112
     * @param string[] $valuePrefixes keys are operations and values are prefixes.
113
     * @return $this
114
     */
115
    public function setValuePrefixes(array $valuePrefixes)
116
    {
117
        $this->valuePrefixes = $valuePrefixes;
118
        return $this;
119
    }
120
121
    /**
122
     * Renders tag and returns output.
123
     *
124
     * @return string
125
     */
126
    public function render()
127
    {
128
        /** @var Grid $grid */
129
        $grid = $this->root;
130
        $this->isTotalsCalculationFinished = true;
131
        $tr = $grid->getRecordView();
132
        // set total_row as current grid row
133
        $lastRow = $grid->getCurrentRow();
134
        $grid->setCurrentRow($this->totalData);
135
136
        // modify columns
137
        $valueCalculators = [];
138
        $valueFormatters = [];
139
        foreach ($grid->getColumns() as $column) {
140
            $valueCalculators[$column->getId()] = $column->getValueCalculator();
141
            $valueFormatters[$column->getId()] = $prevFormatter = $column->getValueFormatter();
142
            $column->setValueCalculator(null);
143
            $column->setValueFormatter(function ($value) use ($prevFormatter, $column) {
144
                if ($prevFormatter) {
145
                    $value = call_user_func($prevFormatter, $value);
146
                }
147
                $operation = $this->getOperation($column->getId());
148
                if ($value !== null && is_string($operation) && array_key_exists($operation, $this->valuePrefixes)) {
149
                    $value = $this->valuePrefixes[$operation] . '&nbsp;' . $value;
150
                }
151
                return $value;
152
            });
153
        }
154
155
        $output = $tr->render();
0 ignored issues
show
Bug introduced by
The method render does only exist in ViewComponents\ViewCompo...\ViewComponentInterface, but not in ViewComponents\ViewCompo...\Compound\PartInterface.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
156
157
        // restore column value calculators & formatters
158
        foreach ($grid->getColumns() as $column) {
159
            $column->setValueCalculator($valueCalculators[$column->getId()]);
160
            $column->setValueFormatter($valueFormatters[$column->getId()]);
161
        }
162
        // restore last data row
163
        $grid->setCurrentRow($lastRow);
164
        return $output;
165
    }
166
167
    protected function pushData($field, $value)
168
    {
169
        if ($this->totalData === null) {
170
            $this->totalData = new \stdClass();
171
        }
172
        if (!property_exists($this->totalData, $field)) {
173
            $this->totalData->$field = 0;
174
        }
175
        $operation = $this->getOperation($field);
176
        switch ($operation) {
177
            case self::OPERATION_SUM:
178
                if (!is_numeric($value)) {
179
                    return;
180
                }
181
                $this->totalData->$field += $value;
182
                break;
183
            case self::OPERATION_COUNT:
184
                $this->totalData->$field = $this->rowsProcessed;
185
                break;
186
            case self::OPERATION_AVG:
187
                $sumField = "{$field}_sum_for_totals";
188
                if (!property_exists($this->totalData, $sumField)) {
189
                    $this->totalData->$sumField = 0;
190
                }
191
                if (is_numeric($value)) {
192
                    $this->totalData->$sumField += $value;
193
                }
194
                $this->totalData->$field = round(
195
                    $this->totalData->$sumField / $this->rowsProcessed,
196
                    2
197
                );
198
                break;
199
            case self::OPERATION_IGNORE:
200
                $this->totalData->$field = null;
201
                break;
202
            default:
203
                if ($operation instanceof Closure) {
204
                    if (!property_exists($this->totalData, $field)) {
205
                        $this->totalData->$field = 0;
206
                    }
207
                    $this->totalData->$field = $operation($this->totalData->$field, $value);
208
                    break;
209
                }
210
                throw new LogicException(
211
                    'PageTotalsRow: Unknown aggregation operation.'
212
                );
213
        }
214
    }
215
216
    /**
217
     * @param string $columnName
218
     * @return string|Closure|null
219
     */
220
    protected function getOperation($columnName)
221
    {
222
        return array_key_exists($columnName, $this->operations)
223
            ? $this->operations[$columnName]
224
            : $this->defaultOperation;
225
    }
226
227
    protected function processCurrentRow()
228
    {
229
        if ($this->isTotalsCalculationFinished) {
230
            return;
231
        }
232
        $this->rowsProcessed++;
233
        /** @var Grid $grid */
234
        $grid = $this->root;
235
        foreach ($grid->getColumns() as $column) {
236
            $this->pushData($column->getId(), $column->getCurrentValue());
237
        }
238
    }
239
240
    protected function replaceGridDataInjector(Grid $grid)
241
    {
242
        /** @var CollectionView $collectionView */
243
        $grid->getCollectionView()->setDataInjector(function ($dataRow) use ($grid) {
244
            $grid->setCurrentRow($dataRow);
245
            $this->processCurrentRow();
246
        });
247
    }
248
}
249