XhtmlTable   B
last analyzed

Complexity

Total Complexity 52

Size/Duplication

Total Lines 245
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 98.28%

Importance

Changes 0
Metric Value
wmc 52
lcom 1
cbo 3
dl 0
loc 245
ccs 114
cts 116
cp 0.9828
rs 7.9487
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A getOptions() 0 4 1
A getOption() 0 4 1
B setOptions() 0 8 5
A setOption() 0 6 2
A defaultOptions() 0 13 1
A asXhtmlTable() 0 4 1
F asSimpleXml() 0 83 17
B xhtmlAddHeader() 0 20 5
D xhtmlAddRow() 0 46 13
B xhtmlAddCellsValues() 0 13 5

How to fix   Complexity   

Complex Class

Complex classes like XhtmlTable 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 XhtmlTable, and based on these observations, apply Extract Interface, too.

1
<?php
2
 declare(strict_types = 1);
3
namespace EngineWorks\Pivot\Formatters;
4
5
use EngineWorks\Pivot\QueryResult;
6
use EngineWorks\Pivot\Result;
7
use EngineWorks\Pivot\Utils;
8
use SimpleXMLElement;
9
10
class XhtmlTable
11
{
12
    /** @var  array */
13
    private $options;
14
15 4
    public function __construct(array $options = [])
16
    {
17 4
        $this->options = $this->defaultOptions();
18 4
        $this->setOptions($options);
19 4
    }
20
21 2
    public function getOptions()
22
    {
23 2
        return $this->options;
24
    }
25
26 2
    public function getOption(string $option) : string
27
    {
28 2
        return $this->options[$option];
29
    }
30
31 4
    public function setOptions(array $options)
32
    {
33 4
        foreach (array_keys($this->options) as $option) {
34 4
            if (is_string($option) && isset($options[$option]) && is_string($options[$option])) {
35 4
                $this->options[$option] = $options[$option];
36
            }
37
        }
38 4
    }
39
40 1
    public function setOption(string $option, string $value)
41
    {
42 1
        if (array_key_exists($option, $this->options)) {
43 1
            $this->options[$option] = $value;
44
        }
45 1
    }
46
47 4
    public function defaultOptions()
48
    {
49
        return [
50 4
            'table-id' => '',
51
            'table-class' => '',
52
            'row-class' => '',
53
            'total-class' => '',
54
            'subtotal-class' => '',
55
            'column-total-caption' => 'Total',
56
            'values-caption' => 'Values',
57
            'row-total-caption' => 'Total',
58
        ];
59
    }
60
61 1
    public function asXhtmlTable(QueryResult $queryResult) : string
62
    {
63 1
        return $this->asSimpleXml($queryResult)->asXML();
64
    }
65
66
    /**
67
     * @param QueryResult $queryResult
68
     * @return SimpleXMLElement
69
     */
70 1
    public function asSimpleXml(QueryResult $queryResult) : SimpleXMLElement
71
    {
72
        /** @var Result $totals */
73 1
        $totals = $queryResult->getTotals();
74
        /** @var Result $details */
75 1
        $details = ($queryResult->hasDetails()) ? $queryResult->getDetails() : null;
76
77
        //
78
        // internal variables
79
        //
80 1
        $aggregates = $queryResult->getAggregates();
81 1
        $valuesCount = count($aggregates);
82 1
        $colsDepth = 0;
83 1
        $colsTree = null;
84
        /* @var $colsLastLevel Result[] */
85 1
        $colsLastLevel = [];
86 1
        $columns = [];
87 1
        if (null !== $details) {
88
            // setup the columns information
89 1
            $columns = $queryResult->getColumns();
90
            // use count of columns instead of calling $details->getDepth(false);
91 1
            $colsDepth = count($columns);
92 1
            $colsTree = $details->copy($colsDepth);
93 1
            $colsLastLevel = $colsTree->getLastChildrenArray();
94
        }
95
96
        // make the table
97
        // use LIBXML_NOXMLDECL to avoid xml declaration on asXml()
98 1
        $table = new SimpleXMLElement('<' . 'table/>', LIBXML_NOXMLDECL);
99 1
        if ($this->getOption('table-class')) {
100 1
            $table->addAttribute('class', $this->getOption('table-class'));
101
        }
102 1
        if ($this->getOption('table-id')) {
103 1
            $table->addAttribute('id', $this->getOption('table-id'));
104
        }
105
106
        // make the thead
107 1
        $thead = $table->addChild('thead');
108 1
        if ($colsDepth) {
109
            // build the rows inside thead
110 1
            foreach ($columns as $column) {
111 1
                $thead->addChild('tr')->addChild('th', Utils::escapeXml($column['caption']));
112
            }
113
            // make header if the column details
114 1
            foreach ($colsTree->children as $item) {
115 1
                $this->xhtmlAddHeader($thead, $item, 1, $valuesCount);
116
            }
117 1
            if (isset($thead->tr) && count($thead->tr) > 0) {
118 1
                $tr = $thead->tr[0];
119 1
                $th = $tr->addChild('th', Utils::escapeXml($this->getOption('column-total-caption')));
120 1
                if ($valuesCount > 1) {
121 1
                    $th->addAttribute('colspan', (string) $valuesCount);
122
                }
123 1
                if ($colsDepth > 0) {
124 1
                    $th->addAttribute('rowspan', (string) $colsDepth);
125
                }
126
            }
127
        }
128
129
        // make the row of the details
130 1
        if (count($aggregates) > 1 || count($columns) == 0) {
131 1
            $tr = $thead->addChild('tr');
132 1
            $tr->addChild('th', Utils::escapeXml($this->getOption('values-caption')));
133 1
            $countColsLastLevel = count($colsLastLevel);
134 1
            for ($i = 0; $i <= $countColsLastLevel; $i++) {
135 1
                foreach ($aggregates as $aggregate) {
136 1
                    $tr->addChild('th', Utils::escapeXml($aggregate['caption']));
137
                }
138
            }
139
        }
140
141
        // tfoot
142 1
        $tfoot = $table->addChild('tfoot');
143 1
        $totals->caption = $this->getOption('row-total-caption');
144 1
        $this->xhtmlAddRow($tfoot, $totals, $aggregates, $details, $colsLastLevel, true);
145
146
        // tbody
147 1
        $tbody = $table->addChild('tbody');
148 1
        foreach ($totals->children as $result) {
149 1
            $this->xhtmlAddRow($tbody, $result, $aggregates, $details, $colsLastLevel, false);
150
        }
151 1
        return $table;
152
    }
153
154
    /**
155
     * @param SimpleXMLElement $thead
156
     * @param Result $item
157
     * @param $level
158
     * @param $valuesCount
159
     */
160 1
    private function xhtmlAddHeader(SimpleXMLElement $thead, Result $item, $level, $valuesCount)
161
    {
162
        // find the current tr according to level
163 1
        $i = 1;
164 1
        $tr = null;
165 1
        foreach ($thead->children() as $tr) {
166 1
            if ($i == $level) {
167 1
                break;
168
            }
169
            $i++;
170
        }
171 1
        if ($tr instanceof SimpleXMLElement) {
172
            // insert the current th
173 1
            $th = $tr->addChild('th', Utils::escapeXml($item->caption));
174 1
            $th->addAttribute('colspan', (string) max(1, $item->getHorizontalDepth() * $valuesCount));
175 1
            foreach ($item->children as $child) {
176
                $this->xhtmlAddHeader($thead, $child, $level + 1, $valuesCount);
177
            }
178
        }
179 1
    }
180
181
    /**
182
     * @param SimpleXMLElement $tbody
183
     * @param Result $result
184
     * @param array $aggregates
185
     * @param Result $details
186
     * @param Result[] $colsLastLevel
187
     * @param bool $istotal
188
     */
189 1
    private function xhtmlAddRow(
190
        SimpleXMLElement $tbody,
191
        Result $result,
192
        array $aggregates,
193
        $details,
194
        $colsLastLevel,
195
        $istotal
196
    ) {
197 1
        $tr = $tbody->addChild('tr');
198 1
        $rowclasses = [];
199 1
        if ($this->getOption('row-class')) {
200 1
            $rowclasses[] = $this->getOption('row-class');
201
        }
202 1
        if ($istotal && $this->getOption('total-class')) {
203 1
            $rowclasses[] = $this->getOption('total-class');
204
        }
205 1
        if (! $istotal && $this->getOption('subtotal-class') && $result->hasChildren()) {
206 1
            $rowclasses[] = $this->getOption('subtotal-class');
207
        }
208 1
        if (count($rowclasses)) {
209 1
            $tr->addAttribute('class', implode(' ', $rowclasses));
210
        }
211 1
        $caption = $tr->addChild('th');
212 1
        $currentDepth = $result->getCurrentDepth() - 1;
213 1
        while ($currentDepth > 0) {
214 1
            $currentDepth--;
215 1
            $caption = $caption->addChild('div');
216
        }
217 1
        $caption->addChild('div', Utils::escapeXml($result->caption));
218 1
        if (null !== $details) {
219 1
            $rowPath = $result->getPath();
220 1
            foreach ($colsLastLevel as $column) {
221 1
                $this->xhtmlAddCellsValues(
222
                    $tr,
223
                    $aggregates,
224 1
                    $details->searchValue(array_merge($column->getPath(), $rowPath))
225
                );
226
            }
227
        }
228 1
        $this->xhtmlAddCellsValues($tr, $aggregates, $result->values);
229 1
        if (! $istotal) {
230 1
            foreach ($result->children as $item) {
231 1
                $this->xhtmlAddRow($tbody, $item, $aggregates, $details, $colsLastLevel, false);
232
            }
233
        }
234 1
    }
235
236
    /**
237
     * @param SimpleXMLElement $tr
238
     * @param array $aggregates
239
     * @param array|null $values
240
     */
241 1
    private function xhtmlAddCellsValues(SimpleXMLElement $tr, array $aggregates, $values)
242
    {
243 1
        if (! is_array($values)) {
244 1
            $values = [];
245
        }
246 1
        foreach ($aggregates as $aggregate) {
247 1
            $value = '';
248 1
            if (isset($values[$aggregate['asname']]) && ! is_null(isset($values[$aggregate['asname']]))) {
249 1
                $value = Utils::escapeXml((string) $values[$aggregate['asname']]);
250
            }
251 1
            $tr->addChild('td', $value);
252
        }
253 1
    }
254
}
255