Calculator   A
last analyzed

Complexity

Total Complexity 30

Size/Duplication

Total Lines 258
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 73
c 3
b 0
f 0
dl 0
loc 258
rs 10
wmc 30

21 Methods

Rating   Name   Duplication   Size   Complexity  
A getDefaultOptions() 0 6 1
A getMaxTotal() 0 3 1
A create() 0 3 1
A __construct() 0 11 2
A getOperations() 0 3 1
A setFloatValues() 0 4 1
A getMaxMinWidth() 0 3 1
A setMinWidth() 0 14 2
A setMaxTotal() 0 4 1
A getValues() 0 19 3
A fixOverflow() 0 4 1
A isIntegerMode() 0 3 1
A addColumn() 0 8 1
A getPixelValues() 0 13 2
A getColumns() 0 3 1
A convertToInteger() 0 9 3
A calculate() 0 18 3
A getMinWidth() 0 3 1
A fillMissing() 0 4 1
A fillLeftover() 0 4 1
A removeSurplus() 0 4 1
1
<?php
2
/**
3
 * File containing the {@see Mistralys\WidthsCalculator\Calculator} class.
4
 *
5
 * @package WidthsCalculator
6
 * @see Mistralys\WidthsCalculator\Calculator
7
 */
8
9
declare (strict_types=1);
10
11
namespace Mistralys\WidthsCalculator;
12
13
use AppUtils\Traits_Optionable;
14
use AppUtils\Interface_Optionable;
15
use Mistralys\WidthsCalculator\Calculator\Column;
16
use Mistralys\WidthsCalculator\Calculator\Operations;
17
use Mistralys\WidthsCalculator\Calculator\OverflowFixer;
18
use Mistralys\WidthsCalculator\Calculator\SurplusRemover;
19
use Mistralys\WidthsCalculator\Calculator\MissingFiller;
20
use Mistralys\WidthsCalculator\Calculator\LeftoverFiller;
21
22
/**
23
 * Calculates percent-based column widths given a list of
24
 * column names with user width values.
25
 *
26
 * Columns with 0 width are filled automatically with
27
 * the leftover percent. Values out of bounds are 
28
 * normalized proportionally, allowing the use of an
29
 * arbitrary numbering system to convert to percent.
30
 *
31
 * @package WidthsCalculator
32
 * @author Sebastian Mordziol <[email protected]>
33
 */
34
class Calculator implements Interface_Optionable
35
{
36
    public const ERROR_INVALID_MIN_WIDTH = 61501;
37
    
38
    use Traits_Optionable;
39
    
40
   /**
41
    * @var Column[]
42
    */
43
    private $columns = array();
44
    
45
   /**
46
    * @var boolean
47
    */
48
    private $calculated = false;
49
    
50
   /**
51
    * @var Operations
52
    */
53
    private $operations;
54
    
55
   /**
56
    * @param array<string,float> $columnValues
57
    */
58
    private function __construct(array $columnValues)
59
    {
60
        foreach($columnValues as $name => $value)
61
        {
62
            $this->addColumn(
63
                (string)$name, 
64
                floatval($value)
65
            );
66
        }
67
        
68
        $this->operations = new Operations($this);
69
    }
70
    
71
   /**
72
    * Creates an instance of the calculator.
73
    * 
74
    * @param array<string,float> $columnValues
75
    * @return Calculator
76
    */
77
    public static function create(array $columnValues) : Calculator
78
    {
79
        return new Calculator($columnValues);
80
    }
81
    
82
   /**
83
    * @return array<string,mixed>
84
    */
85
    public function getDefaultOptions(): array
86
    {
87
        return array(
88
            'maxTotal' => 100,
89
            'minPerCol' => 1,
90
            'integerValues' => true
91
        );
92
    }
93
    
94
    public function getMaxTotal() : float
95
    {
96
        return floatval($this->getOption('maxTotal'));
97
    }
98
    
99
    public function setMaxTotal(float $total) : Calculator
100
    {
101
        $this->setOption('maxTotal', $total);
102
        return $this;
103
    }
104
    
105
    public function getOperations() : Operations
106
    {
107
        return $this->operations;
108
    }
109
    
110
    public function setFloatValues(bool $enable=true) : Calculator
111
    {
112
        $this->setOption('integerValues', !$enable);
113
        return $this;
114
    }
115
    
116
   /**
117
    * Sets the minimum width to enforce for columns, 
118
    * when there already are other columns that take
119
    * up all the available width.
120
    * 
121
    * @param float $width
122
    * @return Calculator
123
    */
124
    public function setMinWidth(float $width) : Calculator
125
    {
126
        $max = $this->getMaxMinWidth();
127
        
128
        if($width > $max)
129
        {
130
            throw new \Exception(
131
                sprintf('Minimum width cannot be set above %s.', number_format($max, 4)),
132
                self::ERROR_INVALID_MIN_WIDTH
133
            );
134
        }
135
        
136
        $this->setOption('minPerCol', $width);
137
        return $this;
138
    }
139
    
140
    public function getMaxMinWidth() : float
141
    {
142
        return $this->getMaxTotal() / $this->operations->countColumns();
143
    }
144
    
145
    private function addColumn(string $name, float $value) : void
146
    {
147
        $col = new Column(
148
            $name,
149
            $value
150
        );
151
        
152
        $this->columns[] = $col;
153
    }
154
    
155
   /**
156
    * Retrieves the minimum width for columns, in percent.
157
    * @return float
158
    */
159
    public function getMinWidth() : float
160
    {
161
        return floatval($this->getOption('minPerCol'));
162
    }
163
    
164
    private function calculate() : void
165
    {
166
        if($this->calculated)
167
        {
168
            return;
169
        }
170
        
171
        if($this->operations->calcTotal() > $this->getMaxTotal())
172
        {
173
            $this->fixOverflow();
174
        }
175
        
176
        $this->fillMissing();
177
        $this->removeSurplus();
178
        $this->convertToInteger();
179
        $this->fillLeftover();
180
        
181
        $this->calculated = true;
182
    }
183
    
184
   /**
185
    * Adjusts the individual column values to match
186
    * the expected output format, for example ensuring
187
    * integer values if we are in integer mode.
188
    */
189
    private function convertToInteger() : void
190
    {
191
        // convert all columns to integer values as required
192
        if($this->isIntegerMode())
193
        {
194
            foreach($this->columns as $col)
195
            {
196
                $val = intval(floor($col->getValue()));
197
                $col->setValue(floatval($val));
198
            }
199
        }
200
    }
201
    
202
   /**
203
    * Retrieves the updated list of column values, 
204
    * retaining the original keys.
205
    * 
206
    * @return array<string,int|float>
207
    */
208
    public function getValues() : array
209
    {
210
        $this->calculate();
211
        
212
        $result = array();
213
        
214
        foreach($this->columns as $col)
215
        {
216
            $val = $col->getValue();
217
            
218
            if($this->isIntegerMode())
219
            {
220
                $val = intval($val);
221
            }
222
            
223
            $result[$col->getName()] = $val;
224
        }
225
        
226
        return $result;
227
    }
228
    
229
    public function isIntegerMode() : bool
230
    {
231
        return $this->getBoolOption('integerValues');
232
    }
233
    
234
   /**
235
    * Retrieves the column values as pixel values, based on
236
    * the target available pixel width.
237
    * 
238
    * @param int $targetWidth
239
    * @return array<string,int>
240
    */
241
    public function getPixelValues(int $targetWidth) : array
242
    {
243
        $calc = self::create($this->getValues());
244
        $calc->setMaxTotal($targetWidth);
245
        $values = $calc->getValues();
246
247
        $result = array();
248
        foreach($values as $name => $value)
249
        {
250
            $result[$name] = (int)$value;
251
        }
252
        
253
        return $result;
254
    }
255
    
256
   /**
257
    * @return Column[]
258
    */
259
    public function getColumns() : array 
260
    {
261
        return $this->columns;
262
    }
263
    
264
    private function removeSurplus() : void
265
    {
266
        $surplus = new SurplusRemover($this);
267
        $surplus->remove();
268
    }
269
    
270
   /**
271
    * Detects any leftover percentages that still need
272
    * to be filled, in case we are not at 100% yet. It
273
    * distributes the missing percentages evenly over the
274
    * available columns, from the last one upwards.
275
    */
276
    private function fillLeftover() : void
277
    {
278
        $filler = new LeftoverFiller($this);
279
        $filler->fill();
280
    }
281
    
282
    private function fillMissing() : void
283
    {
284
        $filling = new MissingFiller($this);
285
        $filling->fill();
286
    }
287
    
288
    private function fixOverflow() : void
289
    {
290
        $overflow = new OverflowFixer($this);
291
        $overflow->fix();
292
    }
293
}
294