├── LICENSE
├── README.md
├── composer.json
├── phpunit.xml.dist
└── src
├── Config
├── AbstractConfig.php
├── Area.php
├── Degree.php
├── Info.php
├── Length.php
├── Money.php
├── Temp.php
├── Time.php
├── Volume.php
└── Weight.php
├── Exception.php
├── Formatter.php
├── Parser.php
└── Type
├── AbstractType.php
├── Area.php
├── Degree.php
├── Info.php
├── Length.php
├── Money.php
├── Temp.php
├── Time.php
├── Volume.php
└── Weight.php
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 JBZoo Content Construction Kit (CCK)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JBZoo / SimpleTypes
2 |
3 | [](https://github.com/JBZoo/SimpleTypes/actions/workflows/main.yml?query=branch%3Amaster) [](https://coveralls.io/github/JBZoo/SimpleTypes?branch=master) [](https://shepherd.dev/github/JBZoo/SimpleTypes) [](https://shepherd.dev/github/JBZoo/SimpleTypes) [](https://www.codefactor.io/repository/github/jbzoo/simpletypes/issues)
4 | [](https://packagist.org/packages/jbzoo/simpletypes/) [](https://packagist.org/packages/jbzoo/simpletypes/stats) [](https://packagist.org/packages/jbzoo/simpletypes/dependents?order_by=downloads) [](https://github.com/JBZoo/SimpleTypes/blob/master/LICENSE)
5 |
6 |
7 | The universal PHP library to convert any values and measures - money, weight, currency coverter, length and what ever you want ;)
8 |
9 |
10 | ## Installation
11 | ```
12 | composer require jbzoo/simpletypes
13 | ```
14 |
15 | ## Examples
16 |
17 | ```php
18 | use JBZoo\SimpleTypes\Config;
19 | use JBZoo\SimpleTypes\Money;
20 | use JBZoo\SimpleTypes\ConfigMoney;
21 |
22 | // Set config object for all Money objects as default
23 | Config::registerDefault('money', new ConfigMoney());
24 |
25 | // Create any object, some different ways
26 | $money = new Money('10 eur');
27 | $weight = new Weight('1000'); // Gram is default in the ConfigWeight class
28 | $length = new Length('500 km');
29 | $money = new Money('100500 usd', new ConfigMoney()); // my custom params only for that object
30 | ```
31 |
32 | ## A lot of types are ready to use
33 |
34 | SimpleTypes has such ready configurations like
35 | * [Area](https://github.com/JBZoo/SimpleTypes/blob/master/src/config/area.php)
36 | * [Degree](https://github.com/JBZoo/SimpleTypes/blob/master/src/config/degree.php) (geometry)
37 | * [Info](https://github.com/JBZoo/SimpleTypes/blob/master/src/config/info.php) (bytes, bits...)
38 | * [Length](https://github.com/JBZoo/SimpleTypes/blob/master/src/config/length.php)
39 | * [Money](https://github.com/JBZoo/SimpleTypes/blob/master/src/config/money.php) (Currency converter)
40 | * [Temperature](https://github.com/JBZoo/SimpleTypes/blob/master/src/config/temp.php) (Kelvin, Fahrenheit, Celsius and etc)
41 | * [Volume](https://github.com/JBZoo/SimpleTypes/blob/master/src/config/volume.php)
42 | * [Weight](https://github.com/JBZoo/SimpleTypes/blob/master/src/config/weight.php)
43 |
44 | You can add your own type. It's really easy. See this page below.
45 |
46 | ### Smart and useful parser
47 |
48 | SimpleTypes has really smart parser for all input values.
49 | It can find number, understand any decimal symbols, trim, letter cases, e.t.c...
50 | and it works really fast!
51 |
52 | ```php
53 | $money = new Money(' - 1 2 3 , 4 5 6 rub '); // Equals -123.456 rubles
54 | $money = new Money('1.0e+18 EUR '); // Really huge number. I'm rich! =)
55 | $money = new Money(' EuR 3,50 ');
56 | $money = new Money('usd'); // Just object with usd rule
57 | ```
58 |
59 | ### Chaining method calls
60 | ```php
61 | $value = (new Money('4.95 usd'))
62 | ->add('10 usd') // $14.95
63 | ->subtract('2 eur') // $10.95
64 | ->negative() // -$10.95
65 | ->getClone() // copy of object is created
66 | ->division(5) // -$2.19
67 | ->multiply(10) // -$21.90
68 | ->convert('eur') // -10.95€ (For easy understanding we use 1 EUR = 2 USD)
69 | ->customFunc(function (Money $value) { // sometimes we would like something more than plus/minus ;)
70 | $value
71 | ->add(new Money('600 rub')) // 1.05€ (1 EUR = 50 RUB)
72 | ->add('-500%'); // -4.2€
73 | })
74 | ->abs(); // 4.2€
75 | ```
76 |
77 | ## Basic arithmetic
78 | Different ways to use basic arithmetic
79 | ```php
80 | // example #1
81 | $usd = new Money('10 usd');
82 | $usd->add(new Money('10 eur'));
83 |
84 | // example #2
85 | $usd = (new Money('10 usd'))->add(new Money('10 eur'));
86 |
87 | // example #3
88 | $usd->add('10 eur');
89 |
90 | // example #4
91 | $usd->add('10'); // eur is default in the ConfigMoney
92 |
93 | // example #5
94 | $usd->add(['10', 'eur']);
95 | ```
96 |
97 | SimpleTypes can
98 | * add
99 | * subtract
100 | * division
101 | * multiply
102 | * use custom functions (Closure)
103 | * negative / positibe / invert sign / abs
104 | * use percent
105 | * setting empty value
106 | * setting another value and rule
107 | * create clone
108 | * converting to any rules (currencies, units)
109 | * rounding
110 | * comparing
111 | * ... and others
112 |
113 | ## Compare values
114 |
115 | ```php
116 | $kg = new Weight('1 kg'); // one kilogram
117 | $lb = new Weight('2 lb'); // two pounds
118 |
119 | var_dump($kg->compare($lb)); // false ("==" by default)
120 | var_dump($kg->compare($lb, '==')); // false
121 | var_dump($kg->compare($lb, '<')); // false
122 | var_dump($kg->compare($lb, '<=')); // false
123 | var_dump($kg->compare($lb, '>')); // true
124 | var_dump($kg->compare($lb, '>=')); // true
125 | ```
126 |
127 | And same examples but we will use smart parser
128 | ```php
129 | $kg = new Weight('1 kg');
130 | $lb = new Weight('2 lb');
131 |
132 | var_dump($kg->compare('1000 g')); // true
133 | var_dump($kg->compare('2 lb', '==')); // false
134 | var_dump($kg->compare('2 lb', '<')); // false
135 | var_dump($kg->compare('2 lb', '<=')); // false
136 | var_dump($kg->compare('2 lb', '>')); // true
137 | var_dump($kg->compare('2 lb', '>=')); // true
138 | ```
139 |
140 | ## Percent method
141 | Simple way for count difference between two values
142 | ```php
143 | $origPrice = new Money('100 usd');
144 | $realPrice = new Money('40 eur');
145 |
146 | $diff = $realPrice->percent($origPrice);
147 | echo $diff->text(); // 80%
148 |
149 | $discount = $realPrice->percent($origPrice, true); // revert flag added
150 | echo $discount->text(); // 20%
151 | ```
152 |
153 | ## PHP magic methods
154 |
155 | Safe serialize/unserialize
156 | ```php
157 | $valBefore = $this->val('500 usd');
158 | $valString = serialize($valBefore);
159 | $valAfter = unserialize($valString)->convert('eur');
160 | $valBefore->compare($valAfter);// true
161 | ```
162 |
163 | __toString() works like text() method
164 | ```php
165 | $val = $this->val('500 usd');
166 | echo $val; // "$500.00"
167 | ```
168 |
169 | __invoke()
170 | ```php
171 | $val = $this->val('10 eur');
172 | // it's converting
173 | $val('usd'); // so object now contains "20 usd" (1 eur = 2 usd)
174 | // set new value and rule
175 | $val('100 rub');
176 | $val('100', 'uah');
177 | ```
178 |
179 | ## Different ways for output and rendering
180 | ### Only text
181 |
182 | ```php
183 | $value = new Money('-50.666666 usd');
184 | echo $value->text(); // "-$50.67"
185 | echo $value->text('rub'); // "-1 266,67 руб." (output without changing inner state)
186 | echo $value->noStyle('rub'); // "-1 266,67" (without symbol)
187 | ```
188 |
189 | ### Simple HTML rendering
190 | ```php
191 | echo (new Money('-50.666666 usd'))->html('rub'); // render HTML, useful for JavaScript
192 | ```
193 | Output (warping added just for clarity)
194 | ```php
195 |
202 | -1 266,67
203 | руб.
204 |
205 | ```
206 |
207 | ### HTML Input type[text]
208 | ```php
209 | echo $value->htmlInput('rub', 'input-name-attr');
210 | ```
211 | Output (warping added just for clarity)
212 | ```html
213 |
224 | ```
225 |
226 | **Notice:** Yes, we added a lot of data-attributes in the HTML code. It will be useful for JavaScript and converting without reload a page.
227 |
228 |
229 | ## Configuration of type
230 |
231 | All configuration classes should be extended from Config class
232 | For example, config for information
233 | ```php
234 | /**
235 | * Class ConfigInfo
236 | * @package JBZoo\SimpleTypes
237 | */
238 | class ConfigInfo extends Config
239 | {
240 | /**
241 | * SimpleTypes uses it for converting and while parsing undefined values
242 | * @var string
243 | */
244 | public $default = 'byte';
245 |
246 | /**
247 | * To collect or not to collect logs for each object (need additional memory a little bit)
248 | * @var bool
249 | */
250 | public $isDebug = true;
251 |
252 | /**
253 | * Array of converting rules and output format
254 | * return array
255 | */
256 | public function getRules()
257 | {
258 | // key of array is alias for parser
259 | return array(
260 | 'byte' => array(
261 | 'rate' => 1 // Because 1 byte to byte is 1 =)))
262 | ),
263 |
264 | 'kb' => array(
265 | 'symbol' => 'KB', // symbol for output (->text(), ->html(), ...)
266 | 'round_type' => Formatter::ROUND_CLASSIC, // classic, float, ceil, none
267 | 'round_value' => Formatter::ROUND_DEFAULT, // Count of valuable number after decimal point for any arithmetic actions
268 | 'num_decimals' => '2', // Sets the number of decimal points
269 | 'decimal_sep' => '.', // Sets the separator for the decimal point.
270 | 'thousands_sep' => ' ', // Sets the thousands separator.
271 | 'format_positive' => '%v %s', // %v - replace to rounded and formated (number_format()) value
272 | 'format_negative' => '-%v %s', // %s - replace to symbol
273 | 'rate' => 1024, // How many bytes (default measure) in the 1 KB ?
274 | ),
275 |
276 | 'mb' => array( // Other params gets from $this->defaultParams variable
277 | 'symbol' => 'MB',
278 | 'rate' => 1024 * 1024,
279 | ),
280 |
281 | 'gb' => array( // Other params gets from $this->defaultParams variable
282 | 'symbol' => 'GB',
283 | 'rate' => 1024 * 1024 * 1024,
284 | ),
285 |
286 | 'bit' => array(
287 | 'symbol' => 'Bit',
288 | 'rate' => function ($value, $to) { // Custom callback function for difficult conversion
289 | if ($to == 'bit') {
290 | return $value * 8;
291 | }
292 | return $value / 8;
293 | },
294 | ),
295 | );
296 | }
297 | }
298 | ```
299 |
300 | Usage example for our information type
301 | ```php
302 | // create config object
303 | $config = new ConfigInfo();
304 |
305 | // you can register default config for all info-objects,
306 | Config::registerDefault('info', $config);
307 | $info1 = new Info('700 MB');
308 | $info2 = new Info('1.4 GB');
309 |
310 | // or add config object manually
311 | $info1 = new Info('700 MB', $config);
312 | $info2 = new Info('1.4 GB', $config);
313 |
314 | // Well... some calculations
315 | echo $info2->subtract($info1)->dump() . PHP_EOL;
316 | echo $info2->convert('mb')->dump() . PHP_EOL;
317 | print_r($info2->logs());
318 | ```
319 |
320 | Output
321 | ```
322 | 0.71640625 gb; id=4
323 | 733.6 mb; id=4
324 | Array
325 | (
326 | [0] => Id=4 has just created; dump="1.4 gb"
327 | [1] => Subtract "700 mb"; New value = "0.71640625 gb"
328 | [2] => Converted "gb"=>"mb"; New value = "733.6 mb"; 1 gb = 1024 mb
329 | )
330 | ```
331 |
332 | ### Debug information
333 | Show list of all actions with object. For example, this is history for chaining code
334 | ```php
335 | print_r($value->logs());
336 |
337 | /**
338 | * Array
339 | * (
340 | * [0] => Id=16 has just created; dump="4.95 usd"
341 | * [1] => Add "10 usd"; New value = "14.95 usd"
342 | * [2] => Subtract "2 eur"; New value = "10.95 usd"
343 | * [3] => Set negative; New value = "-10.95 usd"
344 | * [4] => Cloned from id=16 and created new with id=19; dump=-10.95 usd
345 | * [5] => Division with "5"; New value = "-2.19 usd"
346 | * [6] => Multiply with "10"; New value = "-21.9 usd"
347 | * [7] => Converted "usd"=>"eur"; New value = "-10.95 eur"; 1 usd = 0.5 eur
348 | * [8] => --> Function start
349 | * [9] => Add "600 rub"; New value = "1.05 eur"
350 | * [10] => Add "-500 %"; New value = "-4.2 eur"
351 | * [11] => <-- Function finished; New value = "-4.2 eur"
352 | * [12] => Set positive/abs; New value = "4.2 eur"
353 | * )
354 | */
355 | ```
356 |
357 | Show real inner data without any formating and rounding. ID is unique number for SimpleType objects.
358 | ```php
359 | echo $value->dump(); // "4.2 eur; id=19"
360 | ```
361 |
362 | Get object id
363 | ```php
364 | echo $value->getId(); // "19"
365 | ```
366 | Show current value
367 | ```php
368 | echo $value->val(); // "4.2"
369 | ```
370 |
371 | Show current rule
372 | ```php
373 | echo $value->rule(); // "eur"
374 | ```
375 |
376 | ## License
377 | MIT
378 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name" : "jbzoo/simpletypes",
3 | "type" : "library",
4 | "description" : "The universal PHP library to convert any values and measures",
5 | "license" : "MIT",
6 | "keywords" : [
7 | "converter",
8 | "acceleration",
9 | "area",
10 | "degree",
11 | "info",
12 | "length",
13 | "money",
14 | "number",
15 | "pressure",
16 | "speed",
17 | "temperature",
18 | "time",
19 | "volume",
20 | "weight"
21 | ],
22 | "authors" : [
23 | {
24 | "name" : "Denis Smetannikov",
25 | "email" : "admin@jbzoo.com",
26 | "role" : "lead"
27 | }
28 | ],
29 |
30 | "minimum-stability" : "dev",
31 | "prefer-stable" : true,
32 |
33 | "require" : {
34 | "php" : "^8.1",
35 | "jbzoo/utils" : "^7.1"
36 | },
37 |
38 | "require-dev" : {
39 | "jbzoo/toolbox-dev" : "^7.1"
40 | },
41 |
42 | "autoload" : {
43 | "psr-4" : {"JBZoo\\SimpleTypes\\" : "src"}
44 | },
45 |
46 | "autoload-dev" : {
47 | "psr-4" : {"JBZoo\\PHPUnit\\" : "tests"},
48 | "files" : ["tests/phpunit-functions.php"]
49 | },
50 |
51 | "config" : {
52 | "optimize-autoloader" : true,
53 | "allow-plugins" : {"composer/package-versions-deprecated" : true}
54 | },
55 |
56 | "extra" : {
57 | "branch-alias" : {
58 | "dev-master" : "7.x-dev"
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
16 | tests
17 |
18 |
19 |
20 |
21 |
22 | src
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/Config/AbstractConfig.php:
--------------------------------------------------------------------------------
1 | '',
45 | 'rate' => 1,
46 |
47 | // number format
48 | 'num_decimals' => '2',
49 | 'decimal_sep' => '.',
50 | 'thousands_sep' => ' ',
51 |
52 | // templates
53 | 'format_positive' => '%v %s',
54 | 'format_negative' => '-%v %s',
55 |
56 | // round
57 | 'round_type' => Formatter::ROUND_CLASSIC,
58 | 'round_value' => Formatter::ROUND_DEFAULT,
59 | ];
60 |
61 | /**
62 | * List of rules.
63 | * @return array
64 | */
65 | abstract public function getRules();
66 |
67 | public static function registerDefault(string $type, self $config): void
68 | {
69 | $type = \strtolower(\trim($type));
70 | self::$configs[$type] = $config;
71 | }
72 |
73 | public static function getDefault(string $type): ?self
74 | {
75 | $type = \strtolower(\trim($type));
76 |
77 | return self::$configs[$type] ?? null;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/Config/Area.php:
--------------------------------------------------------------------------------
1 | default = 'm2';
24 | }
25 |
26 | /**
27 | * {@inheritDoc}
28 | */
29 | public function getRules(): array
30 | {
31 | return [
32 | // SI
33 | 'mm2' => [
34 | 'symbol' => 'mm2',
35 | 'rate' => 0.000001,
36 | ],
37 | 'cm2' => [
38 | 'symbol' => 'cm2',
39 | 'rate' => 0.0001,
40 | ],
41 | 'm2' => [
42 | 'symbol' => 'm2',
43 | 'rate' => 1,
44 | ],
45 | 'km2' => [
46 | 'symbol' => 'km2',
47 | 'rate' => 1000000,
48 | ],
49 |
50 | // other
51 | 'ft2' => [
52 | 'symbol' => 'sq ft',
53 | 'rate' => 0.09290341,
54 | ],
55 | 'ch2' => [
56 | 'symbol' => 'sq ch',
57 | 'rate' => 404.6873,
58 | ],
59 | 'acr' => [
60 | 'symbol' => 'Acre',
61 | 'rate' => 4046.873,
62 | ],
63 | 'ar' => [
64 | 'symbol' => 'Ar',
65 | 'rate' => 100,
66 | ],
67 | 'ga' => [
68 | 'symbol' => 'Ga',
69 | 'rate' => 10000,
70 | ],
71 | ];
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Config/Degree.php:
--------------------------------------------------------------------------------
1 | default = 'd';
24 | }
25 |
26 | /**
27 | * {@inheritDoc}
28 | */
29 | public function getRules(): array
30 | {
31 | return [
32 | // degree
33 | 'd' => [
34 | 'format_positive' => '%v%s',
35 | 'format_negative' => '-%v%s',
36 | 'symbol' => '°',
37 | ],
38 |
39 | // radian
40 | 'r' => [
41 | 'symbol' => 'pi',
42 | 'rate' => static function (float $value, string $ruleTo): float {
43 | if ($ruleTo === 'd') {
44 | return $value * 180;
45 | }
46 |
47 | return $value / 180;
48 | },
49 | ],
50 |
51 | // grads
52 | 'g' => [
53 | 'symbol' => 'Grad',
54 | 'rate' => static function (float $value, string $ruleTo): float {
55 | if ($ruleTo === 'd') {
56 | return $value * 0.9;
57 | }
58 |
59 | return $value / 0.9;
60 | },
61 | ],
62 |
63 | // turn (loop)
64 | 't' => [
65 | 'symbol' => 'Turn',
66 | 'rate' => 360,
67 | ],
68 | ];
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Config/Info.php:
--------------------------------------------------------------------------------
1 | default = 'byte';
26 | }
27 |
28 | /**
29 | * {@inheritDoc}
30 | */
31 | public function getRules(): array
32 | {
33 | $base = 1024;
34 |
35 | $this->defaultParams['num_decimals'] = 0;
36 | $this->defaultParams['round_type'] = Formatter::ROUND_NONE;
37 |
38 | return [
39 | 'byte' => [
40 | 'symbol' => 'B',
41 | 'rate' => 1,
42 | ],
43 | 'kb' => [
44 | 'symbol' => 'KB',
45 | 'rate' => $base ** 1,
46 | ],
47 | 'mb' => [
48 | 'symbol' => 'MB',
49 | 'rate' => $base ** 2,
50 | ],
51 | 'gb' => [
52 | 'symbol' => 'GB',
53 | 'rate' => $base ** 3,
54 | ],
55 | 'tb' => [
56 | 'symbol' => 'TB',
57 | 'rate' => $base ** 4,
58 | ],
59 | 'pb' => [
60 | 'symbol' => 'PB',
61 | 'rate' => $base ** 5,
62 | ],
63 | 'eb' => [
64 | 'symbol' => 'EB',
65 | 'rate' => $base ** 6,
66 | ],
67 | 'zb' => [
68 | 'symbol' => 'ZB',
69 | 'rate' => $base ** 7,
70 | ],
71 | 'yb' => [
72 | 'symbol' => 'YB',
73 | 'rate' => $base ** 8,
74 | ],
75 |
76 | 'bit' => [
77 | 'symbol' => 'Bit',
78 | 'rate' => static function (float $value, string $ruleTo) {
79 | if ($ruleTo === 'bit') {
80 | return $value * 8;
81 | }
82 |
83 | return $value / 8;
84 | },
85 | ],
86 | ];
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/Config/Length.php:
--------------------------------------------------------------------------------
1 | default = 'm';
24 | }
25 |
26 | /**
27 | * {@inheritDoc}
28 | */
29 | public function getRules(): array
30 | {
31 | return [
32 | // SI
33 | 'mm' => [
34 | 'symbol' => 'mm',
35 | 'rate' => 0.001,
36 | ],
37 | 'cm' => [
38 | 'symbol' => 'cm',
39 | 'rate' => 0.01,
40 | ],
41 | 'dm' => [
42 | 'symbol' => 'dm',
43 | 'rate' => 0.1,
44 | ],
45 | 'm' => [
46 | 'symbol' => 'm',
47 | 'rate' => 1,
48 | ],
49 | 'km' => [
50 | 'symbol' => 'km',
51 | 'rate' => 1000,
52 | ],
53 |
54 | // others
55 | 'p' => [
56 | 'symbol' => 'Point',
57 | 'rate' => 0.000352777778,
58 | ],
59 | 'li' => [
60 | 'symbol' => 'Link',
61 | 'rate' => 0.2012,
62 | ],
63 | 'in' => [
64 | 'symbol' => 'Inches',
65 | 'rate' => 0.0254,
66 | ],
67 | 'ft' => [
68 | 'symbol' => 'Foot',
69 | 'rate' => 0.3048,
70 | ],
71 | 'yd' => [
72 | 'symbol' => 'Yard',
73 | 'rate' => 0.9144,
74 | ],
75 | 'mi' => [
76 | 'symbol' => 'Mile',
77 | 'rate' => 1609.344,
78 | ],
79 | ];
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/Config/Money.php:
--------------------------------------------------------------------------------
1 | default = 'eur';
26 | }
27 |
28 | /**
29 | * {@inheritDoc}
30 | */
31 | public function getRules(): array
32 | {
33 | $this->defaultParams['num_decimals'] = 2;
34 | $this->defaultParams['round_type'] = Formatter::ROUND_CLASSIC;
35 | $this->defaultParams['decimal_sep'] = '.';
36 | $this->defaultParams['thousands_sep'] = ' ';
37 | $this->defaultParams['format_positive'] = '%v %s';
38 | $this->defaultParams['format_negative'] = '-%v %s';
39 |
40 | return [
41 | 'eur' => [
42 | 'symbol' => '€',
43 | 'rate' => 1,
44 | ],
45 |
46 | 'usd' => [
47 | 'symbol' => '$',
48 | 'format_positive' => '%s%v',
49 | 'format_negative' => '-%s%v',
50 | 'rate' => 0.5,
51 | ],
52 |
53 | 'rub' => [
54 | 'symbol' => 'руб.',
55 | 'decimal_sep' => ',',
56 | 'rate' => 0.02,
57 | ],
58 |
59 | 'uah' => [
60 | 'symbol' => 'грн.',
61 | 'decimal_sep' => ',',
62 | 'rate' => 0.04,
63 | ],
64 |
65 | 'byr' => [
66 | 'symbol' => 'Br',
67 | 'round_type' => Formatter::ROUND_CEIL,
68 | 'round_value' => '-2',
69 | 'num_decimals' => '0',
70 | 'rate' => 0.00005,
71 | ],
72 |
73 | '%' => [
74 | 'symbol' => '%',
75 | 'format_positive' => '%v%s',
76 | 'format_negative' => '-%v%s',
77 | ],
78 | ];
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/Config/Temp.php:
--------------------------------------------------------------------------------
1 | default = 'k';
24 | }
25 |
26 | /**
27 | * {@inheritDoc}
28 | */
29 | public function getRules(): array
30 | {
31 | $this->defaultParams['format_positive'] = '%v%s';
32 | $this->defaultParams['format_negative'] = '-%v%s';
33 |
34 | return [
35 | // Celsius
36 | 'C' => [
37 | 'symbol' => '°C',
38 | 'rate' => static function (float $value, string $ruleTo): float {
39 | if ($ruleTo === 'k') {
40 | $value += 273.15;
41 | } else {
42 | $value -= 273.15;
43 | }
44 |
45 | return $value;
46 | },
47 | ],
48 |
49 | // Fahrenheit
50 | 'F' => [
51 | 'symbol' => '°F',
52 | 'rate' => static function (float $value, string $ruleTo): float {
53 | if ($ruleTo === 'k') {
54 | $value = ($value + 459.67) * (5 / 9);
55 | } else {
56 | $value = $value * (9 / 5) - 459.67;
57 | }
58 |
59 | return $value;
60 | },
61 | ],
62 |
63 | // Rankine
64 | 'R' => [
65 | 'symbol' => '°R',
66 | 'rate' => static function (float $value, string $ruleTo): float {
67 | if ($ruleTo === 'k') {
68 | $value = $value * 5 / 9;
69 | } else {
70 | $value = $value * 9 / 5;
71 | }
72 |
73 | return $value;
74 | },
75 | ],
76 |
77 | // Kelvin
78 | 'K' => [
79 | 'symbol' => 'K',
80 | 'rate' => static fn (float $value): float => $value,
81 | ],
82 | ];
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/Config/Time.php:
--------------------------------------------------------------------------------
1 | default = 's';
24 | }
25 |
26 | /**
27 | * {@inheritDoc}
28 | */
29 | public function getRules(): array
30 | {
31 | return [
32 | 's' => [
33 | 'symbol' => 'Sec',
34 | 'rate' => 1,
35 | ],
36 | 'm' => [
37 | 'symbol' => 'Min',
38 | 'rate' => 60,
39 | ],
40 | 'h' => [
41 | 'symbol' => 'H',
42 | 'rate' => 3600,
43 | ],
44 | 'd' => [
45 | 'symbol' => 'Day',
46 | 'rate' => 86400,
47 | ],
48 | 'w' => [
49 | 'symbol' => 'Week',
50 | 'rate' => 604800,
51 | ],
52 | 'mo' => [
53 | 'symbol' => 'Month', // Only 30 days!
54 | 'rate' => 2592000,
55 | ],
56 | 'q' => [
57 | 'symbol' => 'Quarter', // 3 months
58 | 'rate' => 7776000,
59 | ],
60 | 'y' => [
61 | 'symbol' => 'Year', // 365.25 days
62 | 'rate' => 31557600,
63 | ],
64 | ];
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Config/Volume.php:
--------------------------------------------------------------------------------
1 | default = 'lit';
24 | }
25 |
26 | /**
27 | * {@inheritDoc}
28 | * @see https://en.wikipedia.org/wiki/United_States_customary_units
29 | */
30 | public function getRules(): array
31 | {
32 | return [
33 | // SI
34 | 'ml' => [
35 | 'symbol' => 'mL',
36 | 'rate' => 0.001,
37 | ],
38 | 'cm3' => [
39 | 'symbol' => 'cm3',
40 | 'rate' => 0.1,
41 | ],
42 | 'm3' => [
43 | 'symbol' => 'm3',
44 | 'rate' => 1000,
45 | ],
46 | 'lit' => [
47 | 'symbol' => 'L',
48 | 'rate' => 1,
49 | ],
50 | // other
51 | 'qt' => [
52 | 'symbol' => 'US quart',
53 | 'rate' => 0.946352946,
54 | ],
55 | 'pt' => [
56 | 'symbol' => 'US pint',
57 | 'rate' => 0.56826125,
58 | ],
59 | 'gal' => [
60 | 'symbol' => 'US gallon',
61 | 'rate' => 3.785411784,
62 | ],
63 | 'bbl' => [
64 | 'symbol' => 'Barrel',
65 | 'rate' => 119.240471196,
66 | ],
67 | ];
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Config/Weight.php:
--------------------------------------------------------------------------------
1 | default = 'g';
24 | }
25 |
26 | /**
27 | * {@inheritDoc}
28 | */
29 | public function getRules(): array
30 | {
31 | return [
32 | // SI
33 | 'g' => ['symbol' => 'g', 'rate' => 1],
34 | 'kg' => ['symbol' => 'Kg', 'rate' => 1000],
35 | 'ton' => ['symbol' => 'Tons', 'rate' => 1000000],
36 |
37 | // other
38 | 'gr' => ['symbol' => 'Grains', 'rate' => 0.06479891],
39 | 'dr' => ['symbol' => 'Drams', 'rate' => 1.7718451953125],
40 | 'oz' => ['symbol' => 'Ounces', 'rate' => 28.349523125],
41 | 'lb' => ['symbol' => 'Pounds', 'rate' => 453.59237],
42 | ];
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Exception.php:
--------------------------------------------------------------------------------
1 | type = $type;
35 | $this->default = $default;
36 |
37 | // prepare rules
38 | $this->rules = \array_change_key_case($rules);
39 |
40 | foreach ($this->rules as $key => $item) {
41 | $this->rules[$key] = \array_merge($default, (array)$item);
42 | }
43 | }
44 |
45 | public function get(string $rule): array
46 | {
47 | if (\array_key_exists($rule, $this->rules)) {
48 | return (array)$this->rules[$rule];
49 | }
50 |
51 | throw new Exception("Undefined rule: '{$rule}'");
52 | }
53 |
54 | public function getList(bool $keysOnly = false): array
55 | {
56 | if ($keysOnly) {
57 | $keys = \array_keys($this->rules);
58 | $values = \array_keys($this->rules);
59 |
60 | return \array_combine($keys, $values);
61 | }
62 |
63 | return $this->rules;
64 | }
65 |
66 | public function text(float $value, string $rule, bool $showSymbol = true): string
67 | {
68 | $data = $this->format($value, $rule);
69 | $rData = $this->get($rule);
70 | $symbol = $showSymbol ? $rData['symbol'] : '';
71 |
72 | $result = \str_replace(
73 | ['%v', '%s'],
74 | [$data['value'], $symbol],
75 | (string)$data['template'],
76 | );
77 |
78 | return \trim($result);
79 | }
80 |
81 | public function html(array $current, array $orig, array $params): string
82 | {
83 | $data = $this->format($current['value'], $current['rule']);
84 | $rData = $this->get($current['rule']);
85 |
86 | $result = \str_replace(
87 | ['%v', '%s'],
88 | [
89 | "{$data['value']}",
90 | "{$rData['symbol']}",
91 | ],
92 | (string)$data['template'],
93 | );
94 |
95 | return ' [
97 | 'simpleType',
98 | 'simpleType-block',
99 | "simpleType-{$this->type}",
100 | ],
101 | 'data-simpleType-id' => $params['id'],
102 | 'data-simpleType-value' => $current['value'],
103 | 'data-simpleType-rule' => $current['rule'],
104 | 'data-simpleType-orig-value' => $orig['value'],
105 | 'data-simpleType-orig-rule' => $orig['rule'],
106 | ]) . ">{$result}";
107 | }
108 |
109 | public function htmlInput(array $current, array $orig, array $params): string
110 | {
111 | $inputValue = $params['formatted']
112 | ? $this->text($current['value'], $current['rule'])
113 | : $this->text($current['value'], $current['rule'], false);
114 |
115 | return ' $inputValue,
117 | 'name' => $params['name'],
118 | 'type' => 'text',
119 | 'class' => [
120 | 'simpleType',
121 | "simpleType-{$this->type}",
122 | 'simpleType-input',
123 | ],
124 | 'data-simpleType-id' => $params['id'],
125 | 'data-simpleType-value' => $current['value'],
126 | 'data-simpleType-rule' => $current['rule'],
127 | 'data-simpleType-orig-value' => $orig['value'],
128 | 'data-simpleType-orig-rule' => $orig['rule'],
129 | ]) . ' />';
130 | }
131 |
132 | public function round(float $value, string $rule, array $params = []): float
133 | {
134 | $format = $this->get($rule);
135 |
136 | // prepare params
137 | $params = \array_merge(['roundType' => null, 'roundValue' => null], $params);
138 |
139 | // get vars
140 | $roundType = $params['roundType'];
141 | $roundValue = $params['roundValue'];
142 |
143 | if ($roundType === null) {
144 | $roundType = \array_key_exists('round_type', $format) ? $format['round_type'] : self::ROUND_NONE;
145 | }
146 |
147 | if ($roundValue === null) {
148 | $roundValue = \array_key_exists('round_value', $format) ? $format['round_value'] : self::ROUND_DEFAULT;
149 | }
150 |
151 | $roundValue = (int)$roundValue;
152 |
153 | if ($roundType === self::ROUND_CEIL) {
154 | $base = 10 ** $roundValue;
155 | $value = \ceil($value * $base) / $base;
156 | } elseif ($roundType === self::ROUND_CLASSIC) {
157 | $value = \round($value, $roundValue);
158 | } elseif ($roundType === self::ROUND_FLOOR) {
159 | $base = 10 ** $roundValue;
160 | $value = \floor($value * $base) / $base;
161 | } elseif ($roundType === self::ROUND_NONE) {
162 | $value = \round($value, self::ROUND_DEFAULT); // hack, because 123.400000001 !== 123.4
163 | } else {
164 | throw new Exception("Undefined round mode: '{$roundType}'");
165 | }
166 |
167 | return $value;
168 | }
169 |
170 | public function changeRule(string $rule, array $newFormat): void
171 | {
172 | $oldFormat = $this->get($rule);
173 |
174 | $this->rules[$rule] = \array_merge($oldFormat, $newFormat);
175 | }
176 |
177 | public function addRule(string $rule, array $newFormat = []): void
178 | {
179 | if ($rule === '') {
180 | throw new Exception('Empty rule name');
181 | }
182 |
183 | if (\array_key_exists($rule, $this->rules)) {
184 | throw new Exception("Format '{$rule}' already exists");
185 | }
186 |
187 | $this->rules[$rule] = \array_merge($this->default, $newFormat);
188 | }
189 |
190 | public function removeRule(string $rule): bool
191 | {
192 | if (\array_key_exists($rule, $this->rules)) {
193 | unset($this->rules[$rule]);
194 |
195 | return true;
196 | }
197 |
198 | return false;
199 | }
200 |
201 | public static function htmlAttributes(array $attributes): string
202 | {
203 | $result = '';
204 |
205 | foreach ($attributes as $key => $param) {
206 | $value = \implode(' ', (array)$param);
207 | $value = \htmlspecialchars($value, \ENT_QUOTES, 'UTF-8');
208 | $value = \trim($value);
209 | $result .= " {$key}=\"{$value}\"";
210 | }
211 |
212 | return \trim($result);
213 | }
214 |
215 | /**
216 | * Convert value to money format from config.
217 | */
218 | private function format(float $value, string $rule): array
219 | {
220 | $format = $this->get($rule);
221 |
222 | $roundedValue = $this->round($value, $rule);
223 | $isPositive = ($value >= 0);
224 | $valueStr = \number_format(
225 | \abs($roundedValue),
226 | (int)($format['num_decimals'] ?? 0),
227 | (string)($format['decimal_sep'] ?? '.'),
228 | (string)($format['thousands_sep'] ?? ''),
229 | );
230 |
231 | $template = $isPositive ? $format['format_positive'] : $format['format_negative'];
232 |
233 | return [
234 | 'value' => $valueStr,
235 | 'template' => $template,
236 | 'isPositive' => $isPositive,
237 | ];
238 | }
239 | }
240 |
--------------------------------------------------------------------------------
/src/Parser.php:
--------------------------------------------------------------------------------
1 | \strlen($item2) - \strlen($item1);
29 |
30 | \uksort($ruleList, $sortFunction);
31 |
32 | $this->rules = $ruleList;
33 | $this->default = $default;
34 | }
35 |
36 | public function parse(mixed $data = null, ?string $forceRule = null): array
37 | {
38 | $rule = null;
39 |
40 | if (\is_array($data)) {
41 | $value = $data[0] ?? null;
42 | $rule = $data[1] ?? null;
43 |
44 | return $this->parse($value, $rule);
45 | }
46 |
47 | $value = \strtolower(\trim((string)$data));
48 | $aliases = $this->getCodeList();
49 |
50 | foreach ($aliases as $alias) {
51 | if (\str_contains($value, $alias)) {
52 | $rule = $alias;
53 | $value = \str_ireplace($rule, '', $value);
54 | break;
55 | }
56 | }
57 |
58 | /** @phan-suppress-next-line PhanPartialTypeMismatchArgument */
59 | $value = self::cleanValue($value);
60 | $rule = $this->checkRule($rule);
61 |
62 | if (!isStrEmpty($forceRule)) {
63 | $rule = $forceRule;
64 | }
65 |
66 | return [$value, $rule];
67 | }
68 |
69 | public function getCodeList(): array
70 | {
71 | return \array_keys($this->rules);
72 | }
73 |
74 | public function checkRule(?string $rule): string
75 | {
76 | $cleanRule = self::cleanRule($rule);
77 |
78 | if (isStrEmpty($cleanRule)) {
79 | return $this->default;
80 | }
81 |
82 | if (\array_key_exists($cleanRule, $this->rules)) {
83 | return $cleanRule;
84 | }
85 |
86 | throw new Exception("Undefined rule: {$cleanRule}");
87 | }
88 |
89 | public function addRule(string $newRule): void
90 | {
91 | $this->rules[$newRule] = $newRule;
92 | }
93 |
94 | public function removeRule(string $rule): bool
95 | {
96 | if (\array_key_exists($rule, $this->rules)) {
97 | unset($this->rules[$rule]);
98 |
99 | return true;
100 | }
101 |
102 | return false;
103 | }
104 |
105 | public static function cleanValue(null|float|int|string $value): float
106 | {
107 | $result = \trim((string)$value);
108 |
109 | $result = (string)\preg_replace('#[^0-9-+eE,.]#', '', $result);
110 |
111 | if (\preg_match('#\d[eE][-+]\d#', $result) === 0) { // TODO: Remove exponential format
112 | $result = \str_replace(['e', 'E'], '', $result);
113 | }
114 |
115 | $result = (float)\str_replace(',', '.', $result);
116 |
117 | return \round($result, Formatter::ROUND_DEFAULT);
118 | }
119 |
120 | public static function cleanRule(?string $rule): string
121 | {
122 | return \strtolower(\trim((string)$rule));
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/Type/AbstractType.php:
--------------------------------------------------------------------------------
1 | prepareObject($value, $config);
53 | }
54 |
55 | public function __toString()
56 | {
57 | $this->log('__toString() called');
58 |
59 | return $this->text();
60 | }
61 |
62 | /**
63 | * Serialize.
64 | * @return array
65 | */
66 | public function __sleep()
67 | {
68 | $result = [];
69 | $reflect = new \ReflectionClass($this);
70 | $propList = $reflect->getProperties();
71 |
72 | foreach ($propList as $prop) {
73 | if ($prop->isStatic()) {
74 | continue;
75 | }
76 | $result[] = $prop->name;
77 | }
78 |
79 | $this->log('Serialized');
80 |
81 | return $result;
82 | }
83 |
84 | /**
85 | * Wake up after serialize.
86 | */
87 | public function __wakeup(): void
88 | {
89 | $this->log('--> wakeup start');
90 | $this->prepareObject($this->dump(false));
91 | $this->log('<-- Wakeup finish');
92 | }
93 |
94 | /**
95 | * Clone object.
96 | */
97 | public function __clone()
98 | {
99 | self::$counter++;
100 | $recentId = $this->uniqueId;
101 | $this->uniqueId = self::$counter;
102 |
103 | $this->log(
104 | "Cloned from id='{$recentId}' and created new with id='{$this->uniqueId}'; dump=" . $this->dump(false),
105 | );
106 | }
107 |
108 | /**
109 | * @return float|string
110 | */
111 | public function __get(string $name)
112 | {
113 | $name = \strtolower($name);
114 |
115 | if ($name === 'value') {
116 | return $this->val();
117 | }
118 |
119 | if ($name === 'rule') {
120 | return $this->getRule();
121 | }
122 |
123 | throw new Exception("{$this->type}: Undefined __get() called: '{$name}'");
124 | }
125 |
126 | /**
127 | * @noinspection MagicMethodsValidityInspection
128 | */
129 | public function __set(string $name, mixed $value): void
130 | {
131 | if ($name === 'value') {
132 | $this->set([$value]);
133 | } elseif ($name === 'rule') {
134 | $this->convert($value);
135 | } else {
136 | throw new Exception("{$this->type}: Undefined __set() called: '{$name}' = '{$value}'");
137 | }
138 | }
139 |
140 | /**
141 | * Experimental! Methods aliases.
142 | * @deprecated
143 | */
144 | public function __call(string $name, array $arguments): mixed
145 | {
146 | $name = \strtolower($name);
147 | if ($name === 'value') {
148 | return \call_user_func_array([$this, 'val'], $arguments);
149 | }
150 |
151 | if ($name === 'plus') {
152 | return \call_user_func_array([$this, 'add'], $arguments);
153 | }
154 |
155 | if ($name === 'minus') {
156 | return \call_user_func_array([$this, 'subtract'], $arguments);
157 | }
158 |
159 | throw new Exception("{$this->type}: Called undefined method: '{$name}'");
160 | }
161 |
162 | public function __invoke(): self
163 | {
164 | $args = \func_get_args();
165 | $argsCount = \count($args);
166 | $shortArgList = 1;
167 | $fullArgList = 2;
168 |
169 | if ($argsCount === 0) {
170 | $this->error('Undefined arguments');
171 | } elseif ($argsCount === $shortArgList) {
172 | $rules = $this->formatter->getList();
173 |
174 | if (\array_key_exists($args[0], $rules)) {
175 | return $this->convert($args[0]);
176 | }
177 |
178 | return $this->set($args[0]);
179 | } elseif ($argsCount === $fullArgList) {
180 | return $this->set([$args[0], $args[1]]);
181 | }
182 |
183 | throw new Exception("{$this->type}: Too many arguments");
184 | }
185 |
186 | public function getId(): int
187 | {
188 | return $this->uniqueId;
189 | }
190 |
191 | public function val(?string $rule = null): float
192 | {
193 | $rule = Parser::cleanRule($rule);
194 |
195 | if ($rule !== $this->internalRule && !isStrEmpty($rule)) {
196 | return $this->customConvert($rule);
197 | }
198 |
199 | return $this->internalValue;
200 | }
201 |
202 | public function text(?string $rule = null): string
203 | {
204 | $rule = !isStrEmpty($rule) ? $this->parser->checkRule($rule) : $this->internalRule;
205 | $this->log("Formatted output in '{$rule}' as 'text'");
206 |
207 | return $this->formatter->text($this->val($rule), $rule);
208 | }
209 |
210 | public function noStyle(?string $rule = null): string
211 | {
212 | $rule = !isStrEmpty($rule) ? $this->parser->checkRule($rule) : $this->internalRule;
213 | $this->log("Formatted output in '{$rule}' as 'noStyle'");
214 |
215 | return $this->formatter->text($this->val($rule), $rule, false);
216 | }
217 |
218 | public function html(?string $rule = null): string
219 | {
220 | $rule = !isStrEmpty($rule) ? $this->parser->checkRule($rule) : $this->internalRule;
221 | $this->log("Formatted output in '{$rule}' as 'html'");
222 |
223 | return $this->formatter->html(
224 | ['value' => $this->val($rule), 'rule' => $rule],
225 | ['value' => $this->internalValue, 'rule' => $this->internalRule],
226 | ['id' => $this->uniqueId],
227 | );
228 | }
229 |
230 | public function htmlInput(?string $rule = null, ?string $name = null, bool $formatted = false): string
231 | {
232 | $rule = !isStrEmpty($rule) ? $this->parser->checkRule($rule) : $this->internalRule;
233 | $this->log("Formatted output in '{$rule}' as 'input'");
234 |
235 | return $this->formatter->htmlInput(
236 | ['value' => $this->val($rule), 'rule' => $rule],
237 | ['value' => $this->internalValue, 'rule' => $this->internalRule],
238 | ['id' => $this->uniqueId, 'name' => $name, 'formatted' => $formatted],
239 | );
240 | }
241 |
242 | public function isRule(string $rule): bool
243 | {
244 | $rule = $this->parser->checkRule($rule);
245 |
246 | return $rule === $this->internalRule;
247 | }
248 |
249 | public function getRule(): string
250 | {
251 | return $this->internalRule;
252 | }
253 |
254 | public function isEmpty(): bool
255 | {
256 | return (float)$this->internalValue === 0.0;
257 | }
258 |
259 | public function isPositive(): bool
260 | {
261 | return $this->internalValue > 0;
262 | }
263 |
264 | public function isNegative(): bool
265 | {
266 | return $this->internalValue < 0;
267 | }
268 |
269 | public function getRules(): array
270 | {
271 | return $this->formatter->getList();
272 | }
273 |
274 | public function data(bool $toString = false): array|string
275 | {
276 | $data = [(string)$this->val(), $this->getRule()];
277 |
278 | return $toString ? \implode(' ', $data) : $data;
279 | }
280 |
281 | public function getClone(): self
282 | {
283 | return clone $this;
284 | }
285 |
286 | /**
287 | * @SuppressWarnings(PHPMD.CyclomaticComplexity)
288 | */
289 | public function compare(
290 | null|array|float|int|self|string $value,
291 | string $mode = '==',
292 | int $round = Formatter::ROUND_DEFAULT,
293 | ): bool {
294 | // prepare value
295 | $value = $this->getValidValue($value);
296 |
297 | $mode = \trim($mode);
298 | $mode = \in_array($mode, ['=', '==', '==='], true) ? '==' : $mode;
299 |
300 | $val1 = \round($this->val($this->internalRule), $round);
301 | $val2 = \round($value->val($this->internalRule), $round);
302 |
303 | $this->log(
304 | "Compared '{$this->dump(false)}' {$mode} '{$value->dump(false)}' // {$val1} {$mode} {$val2}, r={$round}",
305 | );
306 |
307 | if ($mode === '==') {
308 | return $val1 === $val2;
309 | }
310 |
311 | if ($mode === '!=' || $mode === '!==') {
312 | return $val1 !== $val2;
313 | }
314 |
315 | if ($mode === '<') {
316 | return $val1 < $val2;
317 | }
318 |
319 | if ($mode === '>') {
320 | return $val1 > $val2;
321 | }
322 |
323 | if ($mode === '<=') {
324 | return $val1 <= $val2;
325 | }
326 |
327 | if ($mode === '>=') {
328 | return $val1 >= $val2;
329 | }
330 |
331 | throw new Exception("{$this->type}: Undefined compare mode: {$mode}");
332 | }
333 |
334 | public function setEmpty(bool $getClone = false): self
335 | {
336 | return $this->modifier(0.0, 'Set empty', $getClone);
337 | }
338 |
339 | public function add(null|array|float|int|self|string $value, bool $getClone = false): self
340 | {
341 | return $this->customAdd($value, $getClone);
342 | }
343 |
344 | public function subtract(null|array|float|int|self|string $value, bool $getClone = false): self
345 | {
346 | return $this->customAdd($value, $getClone, true);
347 | }
348 |
349 | public function convert(string $newRule, bool $getClone = false): self
350 | {
351 | if ($newRule === '') {
352 | $newRule = $this->internalRule;
353 | }
354 |
355 | $newRule = $this->parser->checkRule($newRule);
356 |
357 | $obj = $getClone ? clone $this : $this;
358 |
359 | if ($newRule !== $obj->internalRule) {
360 | $obj->internalValue = $obj->customConvert($newRule, true);
361 | $obj->internalRule = $newRule;
362 | }
363 |
364 | return $obj;
365 | }
366 |
367 | public function invert(bool $getClone = false): self
368 | {
369 | $logMess = 'Invert sign';
370 | if ($this->internalValue > 0) {
371 | $newValue = -1 * $this->internalValue;
372 | } elseif ($this->internalValue < 0) {
373 | $newValue = \abs((float)$this->internalValue);
374 | } else {
375 | $newValue = $this->internalValue;
376 | }
377 |
378 | return $this->modifier($newValue, $logMess, $getClone);
379 | }
380 |
381 | public function positive(bool $getClone = false): self
382 | {
383 | return $this->modifier(\abs((float)$this->internalValue), 'Set positive/abs', $getClone);
384 | }
385 |
386 | public function negative(bool $getClone = false): self
387 | {
388 | return $this->modifier(-1 * \abs((float)$this->internalValue), 'Set negative', $getClone);
389 | }
390 |
391 | public function abs(bool $getClone = false): self
392 | {
393 | return $this->positive($getClone);
394 | }
395 |
396 | public function multiply(float $number, bool $getClone = false): self
397 | {
398 | $multiplier = Parser::cleanValue($number);
399 | $newValue = $multiplier * $this->internalValue;
400 |
401 | return $this->modifier($newValue, "Multiply with '{$multiplier}'", $getClone);
402 | }
403 |
404 | public function division(float $number, bool $getClone = false): self
405 | {
406 | $divider = Parser::cleanValue($number);
407 |
408 | return $this->modifier($this->internalValue / $divider, "Division with '{$divider}'", $getClone);
409 | }
410 |
411 | public function percent(self|string $value, bool $revert = false): self
412 | {
413 | $value = $this->getValidValue($value);
414 |
415 | $percent = 0.0;
416 | if (!$this->isEmpty() && !$value->isEmpty()) {
417 | $percent = ($this->internalValue / $value->val($this->internalRule)) * 100;
418 | }
419 |
420 | if ($revert) {
421 | $percent = 100 - $percent;
422 | }
423 |
424 | $result = $this->getValidValue("{$percent}%");
425 | $this->log("Calculate percent; '{$this->dump(false)}' / {$value->dump(false)} = {$result->dump(false)}");
426 |
427 | return $result;
428 | }
429 |
430 | public function customFunc(\Closure $function, bool $getClone = false): self
431 | {
432 | $this->log('--> Function start');
433 | $function($this);
434 |
435 | return $this->modifier($this->internalValue, '<-- Function finished', $getClone);
436 | }
437 |
438 | public function set(null|array|float|int|self|string $value, bool $getClone = false): self
439 | {
440 | $value = $this->getValidValue($value);
441 |
442 | $this->internalValue = $value->val();
443 | $this->internalRule = $value->getRule();
444 |
445 | return $this->modifier($this->internalValue, "Set new value = '{$this->dump(false)}'", $getClone);
446 | }
447 |
448 | public function round(int $roundValue, string $mode = Formatter::ROUND_CLASSIC): self
449 | {
450 | $oldValue = $this->internalValue;
451 | $newValue = $this->formatter->round($this->internalValue, $this->internalRule, [
452 | 'roundValue' => $roundValue,
453 | 'roundType' => $mode,
454 | ]);
455 |
456 | $this->log("Rounded (size={$roundValue}; type={$mode}) '{$oldValue}' => {$newValue}");
457 |
458 | $this->internalValue = $newValue;
459 |
460 | return $this;
461 | }
462 |
463 | public function getValidValue(null|array|float|int|self|string $value): self
464 | {
465 | if ($value instanceof self) {
466 | $thisClass = \strtolower(static::class);
467 | $valClass = \strtolower($value::class);
468 | if ($thisClass !== $valClass) {
469 | throw new Exception("{$this->type}: No valid object type given: {$valClass}");
470 | }
471 | } else {
472 | /**
473 | * @psalm-suppress UnsafeInstantiation
474 | * @phpstan-ignore-next-line
475 | */
476 | $value = new static($value, $this->getConfig());
477 | }
478 |
479 | return $value;
480 | }
481 |
482 | public function error(string $message): void
483 | {
484 | $this->log($message);
485 | throw new Exception("{$this->type}: {$message}");
486 | }
487 |
488 | public function dump(bool $showId = true): string
489 | {
490 | $uniqueId = $showId ? "; id={$this->uniqueId}" : '';
491 |
492 | return "{$this->internalValue} {$this->internalRule}{$uniqueId}";
493 | }
494 |
495 | public function log(string $message): void
496 | {
497 | if ($this->isDebug) {
498 | $this->logs[] = $message;
499 | }
500 | }
501 |
502 | public function logs(): array
503 | {
504 | return $this->logs;
505 | }
506 |
507 | public function changeRule(string $rule, array $newFormat): self
508 | {
509 | $rule = Parser::cleanRule($rule);
510 | $this->formatter->changeRule($rule, $newFormat);
511 | $this->log("The rule '{$rule}' changed");
512 |
513 | return $this;
514 | }
515 |
516 | public function addRule(string $rule, array $newFormat = []): self
517 | {
518 | $form = $this->formatter;
519 | $rule = Parser::cleanRule($rule);
520 | $form->addRule($rule, $newFormat);
521 | $this->parser->addRule($rule);
522 | $this->log("The rule '{$rule}' added");
523 |
524 | return $this;
525 | }
526 |
527 | public function removeRule(string $rule): self
528 | {
529 | $rule = Parser::cleanRule($rule);
530 | $this->formatter->removeRule($rule);
531 | $this->parser->removeRule($rule);
532 | $this->log("The rule '{$rule}' removed");
533 |
534 | return $this;
535 | }
536 |
537 | public function getRuleData(string $rule): array
538 | {
539 | $rule = Parser::cleanRule($rule);
540 |
541 | return $this->formatter->get($rule);
542 | }
543 |
544 | protected function getConfig(?AbstractConfig $config = null): ?AbstractConfig
545 | {
546 | $defaultConfig = AbstractConfig::getDefault($this->type);
547 |
548 | $config ??= $defaultConfig;
549 |
550 | // Hack for getValidValue method
551 | if ($defaultConfig === null && $config !== null) {
552 | AbstractConfig::registerDefault($this->type, $config);
553 | }
554 |
555 | return $config;
556 | }
557 |
558 | /**
559 | * @SuppressWarnings(PHPMD.CyclomaticComplexity)
560 | */
561 | protected function customConvert(string $rule, bool $addToLog = false): float
562 | {
563 | $from = $this->parser->checkRule($this->internalRule);
564 | $target = $this->parser->checkRule($rule);
565 |
566 | $ruleTo = $this->formatter->get($target);
567 | $ruleFrom = $this->formatter->get($from);
568 | $ruleDef = $this->formatter->get($this->default);
569 |
570 | $log = "'{$from}'=>'{$target}'";
571 |
572 | $result = $this->internalValue;
573 | if ($from !== $target) {
574 | if (\is_callable($ruleTo['rate']) || \is_callable($ruleFrom['rate'])) {
575 | if (\is_callable($ruleFrom['rate'])) {
576 | $defNorm = $ruleFrom['rate']($this->internalValue, $this->default, $from);
577 | } else {
578 | $defNorm = $this->internalValue * $ruleFrom['rate'] * $ruleDef['rate'];
579 | }
580 |
581 | if (\is_callable($ruleTo['rate'])) {
582 | $result = $ruleTo['rate']($defNorm, $target, $this->default);
583 | } else {
584 | $result = $defNorm / $ruleTo['rate'];
585 | }
586 | } else {
587 | $defNorm = $this->internalValue * $ruleFrom['rate'] * $ruleDef['rate'];
588 | $result = $defNorm / $ruleTo['rate'];
589 | }
590 |
591 | if ($this->isDebug && $addToLog) {
592 | $message = [
593 | "Converted {$log};",
594 | "New value = {$result} {$target};",
595 | \is_callable($ruleTo['rate']) ? "func({$from})" : "{$ruleTo['rate']} {$from}",
596 | '=',
597 | \is_callable($ruleFrom['rate']) ? "func({$target})" : "{$ruleFrom['rate']} {$target}",
598 | ];
599 |
600 | $this->log(\implode(' ', $message));
601 | }
602 | }
603 |
604 | return $result;
605 | }
606 |
607 | protected function customAdd(
608 | null|array|float|int|self|string $value,
609 | bool $getClone = false,
610 | bool $isSubtract = false,
611 | ): self {
612 | $value = $this->getValidValue($value);
613 |
614 | $addValue = 0;
615 |
616 | if ($this->internalRule === '%') {
617 | if ($value->getRule() === '%') {
618 | $addValue = $value->val();
619 | } else {
620 | $this->error("Impossible add '{$value->dump(false)}' to '{$this->dump(false)}'");
621 | }
622 | } elseif ($value->getRule() !== '%') {
623 | $addValue = $value->val($this->internalRule);
624 | } else {
625 | $addValue = $this->internalValue * $value->val() / 100;
626 | }
627 |
628 | if ($isSubtract) {
629 | $addValue *= -1;
630 | }
631 |
632 | $newValue = $this->internalValue + $addValue;
633 | $logMess = ($isSubtract ? 'Subtract' : 'Add') . " '{$value->dump(false)}'";
634 |
635 | return $this->modifier($newValue, $logMess, $getClone);
636 | }
637 |
638 | protected function modifier(float $newValue, ?string $logMessage = null, bool $getClone = false): self
639 | {
640 | if ($getClone) {
641 | $clone = $this->getClone();
642 |
643 | $clone->internalValue = $newValue;
644 | $clone->log("{$logMessage}; New value = '{$clone->dump(false)}'");
645 |
646 | return $clone;
647 | }
648 |
649 | $this->internalValue = $newValue;
650 | $this->log("{$logMessage}; New value = '{$this->dump(false)}'");
651 |
652 | return $this;
653 | }
654 |
655 | private function prepareObject(null|array|float|int|string $value = null, ?AbstractConfig $config = null): void
656 | {
657 | // $this->type = Str::class \strtolower(\str_replace(__NAMESPACE__ . '\\', '', static::class));
658 | $this->type = Str::getClassName(static::class, true) ?? 'UndefinedType';
659 |
660 | // get custom or global config
661 | $config = $this->getConfig($config);
662 | if ($config !== null) {
663 | // debug flag (for logging)
664 | $this->isDebug = $config->isDebug;
665 |
666 | // set default rule
667 | $this->default = \strtolower(\trim($config->default));
668 | if (isStrEmpty($this->default)) {
669 | $this->error('Default rule cannot be empty!');
670 | }
671 |
672 | // create formatter helper
673 | $this->formatter = new Formatter($config->getRules(), $config->defaultParams, $this->type);
674 | } else {
675 | $this->formatter = new Formatter();
676 | }
677 |
678 | // check that default rule
679 | $rules = $this->formatter->getList(true);
680 | if (!\array_key_exists($this->default, $rules) || \count($rules) === 0) {
681 | throw new Exception("{$this->type}: Default rule not found!");
682 | }
683 |
684 | // create parser helper
685 | $this->parser = new Parser($this->default, $rules);
686 |
687 | // parse data
688 | [$this->internalValue, $this->internalRule] = $this->parser->parse($value);
689 |
690 | // count unique id
691 | self::$counter++;
692 | $this->uniqueId = self::$counter;
693 |
694 | // success log
695 | $this->log("Id={$this->uniqueId} has just created; dump='{$this->dump(false)}'");
696 | }
697 | }
698 |
--------------------------------------------------------------------------------
/src/Type/Area.php:
--------------------------------------------------------------------------------
1 | isRule('d')) {
25 | $divider = 360;
26 | } elseif ($this->isRule('r')) {
27 | $divider = 2;
28 | } elseif ($this->isRule('g')) {
29 | $divider = 400;
30 | } elseif ($this->isRule('t')) {
31 | $divider = 1;
32 | }
33 |
34 | if ($divider > 0) {
35 | if ($this->internalValue <= (-1 * $divider)) {
36 | $this->internalValue = \fmod($this->internalValue, $divider);
37 | } elseif ($this->internalValue >= $divider) {
38 | $this->internalValue = \fmod($this->internalValue, $divider);
39 | }
40 |
41 | $this->log("Remove circles: {$this->dump(false)}");
42 | }
43 |
44 | return $this;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Type/Info.php:
--------------------------------------------------------------------------------
1 |