Completed
Push — master ( 26a953...b5f39d )
by Vitaliy
06:17
created

PageTotalsRow::__construct()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 10
ccs 0
cts 9
cp 0
rs 9.4285
cc 3
eloc 7
nc 3
nop 2
crap 12
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 $stopDataCollecting = 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
     * @param array|string[] $operations (optional) keys are column id's and values are operations
69
     *                                   (see PageTotalsRow::OPERATION_* constants) or closures.
70
     * @param string|null $defaultOperation operation that will be used for column
71
     *                                      if operation isn't specified for this column in first argument.
72
     */
73
    public function __construct(array $operations = [], $defaultOperation = null)
74
    {
75
        $this->id = static::ID;
76
        $this->destinationParentId = ManagedList::LIST_CONTAINER_ID;
77
        $this->operations = $operations;
78
        if ($defaultOperation === null) {
79
            $defaultOperation = empty($operations) ? self::OPERATION_SUM : self::OPERATION_IGNORE;
80
        }
81
        $this->defaultOperation = $defaultOperation;
82
    }
83
84
    /**
85
     * @param Compound|Grid $root
86
     * @param bool $prepend
87
     */
88
    public function attachToCompound(Compound $root, $prepend = false)
89
    {
90
        $this->attachToCompoundInternal($root, $prepend);
91
        $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...
92
    }
93
94
    /**
95
     * @return array
96
     */
97
    public function getValuePrefixes()
98
    {
99
        return $this->valuePrefixes;
100
    }
101
102
    public function setValuePrefixes(array $valuePrefixes)
103
    {
104
        $this->valuePrefixes = $valuePrefixes;
105
        return $this;
106
    }
107
108
    /**
109
     * Renders tag and returns output.
110
     *
111
     * @return string
112
     */
113
    public function render()
114
    {
115
        /** @var Grid $grid */
116
        $grid = $this->root;
117
        $this->stopDataCollecting = true;
118
        $tr = $grid->getRecordView();
119
120
        // set total_row as current grid row
121
        $lastRow = $grid->getCurrentRow();
122
        $grid->setCurrentRow($this->totalData);
123
124
        // modify columns
125
        $valueCalculators = [];
126
        $valueFormatters = [];
127
        foreach ($grid->getColumns() as $column) {
128
            $valueCalculators[$column->getId()] = $column->getValueCalculator();
129
            $valueFormatters[$column->getId()] = $prevFormatter = $column->getValueFormatter();
130
            $column->setValueCalculator(null);
131
            $column->setValueFormatter(function ($value) use ($prevFormatter, $column) {
132
                if ($prevFormatter) {
133
                    $value = call_user_func($prevFormatter, $value);
134
                }
135
                $operation = $this->getOperation($column->getId());
136
                if ($value !== null && is_string($operation) && array_key_exists($operation, $this->valuePrefixes)) {
137
                    $value = $this->valuePrefixes[$operation] . '&nbsp;' . $value;
138
                }
139
                return $value;
140
            });
141
        }
142
143
        $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...
144
145
        // restore column value calculators & formatters
146
        foreach ($grid->getColumns() as $column) {
147
            $column->setValueCalculator($valueCalculators[$column->getId()]);
148
            $column->setValueFormatter($valueFormatters[$column->getId()]);
149
        }
150
        // restore last data row
151
        $grid->setCurrentRow($lastRow);
152
        return $output;
153
    }
154
155
    protected function pushData($field, $value)
156
    {
157
        if ($this->totalData === null) {
158
            $this->totalData = new \stdClass();
159
        }
160
        if (!is_numeric($value)) {
161
            return;
162
        }
163
        if (!property_exists($this->totalData, $field)) {
164
            $this->totalData->$field = 0;
165
        }
166
        $operation = $this->getOperation($field);
167
        switch ($operation) {
168
            case self::OPERATION_SUM:
169
                $this->totalData->$field += $value;
170
                break;
171
            case self::OPERATION_COUNT:
172
                $this->totalData->$field = $this->rowsProcessed;
173
                break;
174
            case self::OPERATION_AVG:
175
                $sumFiled = "{$field}_sum_for_totals";
176
                if (!property_exists($this->totalData, $sumFiled)) {
177
                    $this->totalData->$sumFiled = 0;
178
                }
179
                $this->totalData->$sumFiled += $value;
180
                $this->totalData->$field = round(
181
                    $this->totalData->$sumFiled / $this->rowsProcessed,
182
                    2
183
                );
184
                break;
185
            case self::OPERATION_IGNORE:
186
                $this->totalData->$field = null;
187
                break;
188
            default:
189
                if ($operation instanceof Closure) {
190
                    $this->totalData->$field = $operation($this->totalData->$field, $value);
191
                    break;
192
                }
193
                throw new LogicException(
194
                    'PageTotalsRow: Unknown aggregation operation.'
195
                );
196
        }
197
    }
198
199
    /**
200
     * @param string $columnName
201
     * @return string|Closure|null
202
     */
203
    protected function getOperation($columnName)
204
    {
205
        return array_key_exists($columnName, $this->operations)
206
            ? $this->operations[$columnName]
207
            : $this->defaultOperation;
208
    }
209
210
    protected function processCurrentRow()
211
    {
212
        if ($this->stopDataCollecting) {
213
            return;
214
        }
215
        $this->rowsProcessed++;
216
        /** @var Grid $grid */
217
        $grid = $this->root;
218
        foreach ($grid->getColumns() as $column) {
219
            $this->pushData($column->getId(), $column->getCurrentValue());
220
        }
221
    }
222
223
    protected function replaceGridDataInjector(Grid $grid)
224
    {
225
        /** @var CollectionView $collectionView */
226
        $grid->getCollectionView()->setDataInjector(function ($dataRow) use ($grid) {
227
            $grid->setCurrentRow($dataRow);
228
            $this->processCurrentRow();
229
        });
230
    }
231
}
232