Passed
Push — master ( 0692ec...4950c9 )
by Sebastian
02:38 queued 11s
created

Calculator::convertToInteger()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 4
c 0
b 0
f 0
nc 3
nop 0
dl 0
loc 9
rs 10
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 percentual 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 numering system to convert to percent.
30
 *
31
 * @package WidthsCalculator
32
 * @author Sebastian Mordziol <[email protected]>
33
 */
34
class Calculator implements Interface_Optionable
35
{
36
    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 getOperations() : Operations
100
    {
101
        return $this->operations;
102
    }
103
    
104
    public function setFloatValues(bool $enable=true) : Calculator
105
    {
106
        $this->setOption('integerValues', !$enable);
107
        return $this;
108
    }
109
    
110
   /**
111
    * Sets the minimum width to enforce for columns, 
112
    * when there already are other columns that take
113
    * up all the available width.
114
    * 
115
    * @param float $width
116
    * @return Calculator
117
    */
118
    public function setMinWidth(float $width) : Calculator
119
    {
120
        $max = $this->getMaxMinWidth();
121
        
122
        if($width > $max)
123
        {
124
            throw new \Exception(
125
                sprintf('Minimum width cannot be set above %s.', number_format($max, 4)),
126
                self::ERROR_INVALID_MIN_WIDTH
127
            );
128
        }
129
        
130
        $this->setOption('minPerCol', $width);
131
        return $this;
132
    }
133
    
134
    public function getMaxMinWidth() : float
135
    {
136
        return $this->getMaxTotal() / $this->operations->countColumns();
137
    }
138
    
139
    private function addColumn(string $name, float $value) : void
140
    {
141
        $col = new Column(
142
            $this,
143
            $name,
144
            $value
145
        );
146
        
147
        $this->columns[] = $col;
148
    }
149
    
150
   /**
151
    * Retrieves the minimum width for columns, in percent.
152
    * @return float
153
    */
154
    public function getMinWidth() : float
155
    {
156
        return floatval($this->getOption('minPerCol'));
157
    }
158
    
159
    private function calculate() : void
160
    {
161
        if($this->calculated)
162
        {
163
            return;
164
        }
165
        
166
        if($this->operations->calcTotal() > $this->getMaxTotal())
167
        {
168
            $this->fixOverflow();
169
        }
170
        
171
        $this->fillMissing();
172
        $this->removeSurplus();
173
        $this->convertToInteger();
174
        $this->fillLeftover();
175
        
176
        $this->calculated = true;
177
    }
178
    
179
   /**
180
    * Adjusts the individual column values to match
181
    * the expected output format, for example ensuring
182
    * integer values if we are in integer mode.
183
    */
184
    private function convertToInteger() : void
185
    {
186
        // convert all columns to integer values as required
187
        if($this->isIntegerMode())
188
        {
189
            foreach($this->columns as $col)
190
            {
191
                $val = intval(floor($col->getValue()));
192
                $col->setValue(floatval($val));
193
            }
194
        }
195
    }
196
    
197
   /**
198
    * Retrieves the updated list of column values, 
199
    * retaining the original keys.
200
    * 
201
    * @return array<string,int|float>
202
    */
203
    public function getValues() : array
204
    {
205
        $this->calculate();
206
        
207
        $result = array();
208
        
209
        foreach($this->columns as $col)
210
        {
211
            $val = $col->getValue();
212
            
213
            if($this->isIntegerMode())
214
            {
215
                $val = intval($val);
216
            }
217
            
218
            $result[$col->getName()] = $val;
219
        }
220
        
221
        return $result;
222
    }
223
    
224
    public function isIntegerMode() : bool
225
    {
226
        return $this->getBoolOption('integerValues');
227
    }
228
    
229
   /**
230
    * @return Column[]
231
    */
232
    public function getColumns() : array 
233
    {
234
        return $this->columns;
235
    }
236
    
237
    private function removeSurplus() : void
238
    {
239
        $surplus = new SurplusRemover($this);
240
        $surplus->remove();
241
    }
242
    
243
   /**
244
    * Detects any leftover percentages that still need
245
    * to be filled, in case we are not at 100% yet. It
246
    * distributes the missing percentages evenly over the
247
    * available columns, from the last one upwards.
248
    */
249
    private function fillLeftover() : void
250
    {
251
        $filler = new LeftoverFiller($this);
252
        $filler->fill();
253
    }
254
    
255
    private function fillMissing() : void
256
    {
257
        $filling = new MissingFiller($this);
258
        $filling->fill();
259
    }
260
    
261
    private function fixOverflow() : void
262
    {
263
        $overflow = new OverflowFixer($this);
264
        $overflow->fix();
265
    }
266
}
267