├── .prettierignore ├── composer.json ├── LICENSE.md ├── .github └── workflows │ └── tests.yml ├── README.md ├── DOCS.md ├── src └── Number.php └── .php_cs.dist /.prettierignore: -------------------------------------------------------------------------------- 1 | *.md 2 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "louisgab/php-calc", 3 | "description": "Simple fluent float manipulation library", 4 | "type": "library", 5 | "keywords": [ 6 | "php", "number", "float", "fluent" 7 | ], 8 | "homepage": "https://github.com/louisgab/number", 9 | "license": "MIT", 10 | "authors": [ 11 | { 12 | "name": "Louis Gabriel", 13 | "email": "code@louisgab.me" 14 | } 15 | ], 16 | "require": { 17 | "php": "^7.4" 18 | }, 19 | "require-dev": { 20 | "phpunit/phpunit": "^9" 21 | }, 22 | "autoload": { 23 | "psr-4": { 24 | "Louisgab\\Calc\\": "src" 25 | } 26 | }, 27 | "autoload-dev": { 28 | "psr-4": { 29 | "Louisgab\\Calc\\Tests\\": "tests" 30 | } 31 | }, 32 | "scripts": { 33 | "test": "vendor/bin/phpunit --testdox --colors=always", 34 | "test-coverage": "vendor/bin/phpunit --coverage-html coverage" 35 | }, 36 | "config": { 37 | "sort-packages": true 38 | }, 39 | "minimum-stability": "dev", 40 | "prefer-stable": true 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Louis-Gabriel 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 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Setup PHP 17 | uses: shivammathur/setup-php@v2 18 | with: 19 | php-version: "7.4" 20 | tools: composer:v2 21 | coverage: none 22 | 23 | - name: Setup problem matchers 24 | run: | 25 | echo "::add-matcher::${{ runner.tool_cache }}/php.json" 26 | echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" 27 | 28 | - name: Validate composer.json and composer.lock 29 | run: composer validate 30 | 31 | - name: Get composer cache directory 32 | id: composer-cache 33 | run: echo "::set-output name=dir::$(composer config cache-files-dir)" 34 | 35 | - name: Cache Composer packages 36 | uses: actions/cache@v2 37 | with: 38 | path: ${{ steps.composer-cache.outputs.dir }} 39 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 40 | restore-keys: ${{ runner.os }}-composer- 41 | 42 | - name: Install dependencies 43 | run: composer update --prefer-dist --no-progress --no-interaction 44 | 45 | - name: Run test suite 46 | run: composer test 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Calc 2 | ![Tests](https://github.com/louisgab/php-calc/workflows/tests/badge.svg) 3 | [![Packagist Version](https://img.shields.io/packagist/v/louisgab/php-calc.svg?style=flat-square)](https://packagist.org/packages/louisgab/php-calc) 4 | [![Packagist Downloads](https://img.shields.io/packagist/dt/louisgab/php-calc.svg?style=flat-square)](https://packagist.org/packages/louisgab/php-calc) 5 | [![GitHub license](https://img.shields.io/github/license/louisgab/php-calc.svg?style=flat-square)](https://github.com/louisgab/php-calc/blob/master/LICENSE) 6 | 7 | 💯 Simple fluent float manipulation library. 8 | 9 | Calc aims to provide tools for easy and readable calculations, without any dependency. 10 | It comes with `Number` which is an immutable value object that enables fluent float manipulations. 11 | 12 | ## Why 13 | 14 | If you ever worked with a codebase full of that kind of crap: 15 | 16 | ```php 17 | $result = round(($b != 0 ? ((1+$a)/$b) : $c)*0.25, 2) 18 | ``` 19 | 20 | I'm sure you will enjoy that: 21 | 22 | ```php 23 | $result = Number::of($b) 24 | ->when(Number::of($b)->isZero(), 25 | fn($b) => $c 26 | fn($b) => Number::one()->plus($a)->divide($b), 27 | ) 28 | ->multiply(0.25) 29 | ->round(2) 30 | ->value() 31 | ``` 32 | 33 | You may think it's like [brick/math](https://github.com/brick/math), which is a really great package, but Calc serves a different purpose. 34 | If floats are good enough for you - and unless you're dealing with sensible data like accounting or science, it should be - then using GMP or bcmath is overkill. 35 | 36 | That's what Calc is made for, still using floats while enjoying nice readability. 37 | Another good point is that it handles floating point problems (e.g `0.1 + 0.2 == 0.3 // false`) as much as possible, so you don't have to think about it each time (and if you are working with junior developers, it will save them from having problems they didn't even know existed!). 38 | 39 | ## Install 40 | 41 | Via composer: 42 | 43 | ```bash 44 | composer require louisgab/php-calc 45 | ``` 46 | 47 | ## Usage 48 | Simple as: 49 | ```php 50 | use Louisgab\Calc\Number; 51 | 52 | Number::of($anything); 53 | ``` 54 | 55 | And good as : 56 | ```php 57 | public function carsNeeded(Number $people, Number $placesPerCar): int 58 | { 59 | return $people->divide($placesPerCar)->round(0)->toInt(); 60 | } 61 | ``` 62 | 63 | Please see [DOCS](DOCS.md) 64 | 65 | ## Testing 66 | 67 | ```bash 68 | composer test 69 | ``` 70 | 71 | ## Roadmap 72 | 73 | - [x] `Number` 74 | - [ ] `Fraction` 75 | - [ ] `Percentage` 76 | 77 | ## Changelog 78 | 79 | Please see [CHANGELOG](CHANGELOG.md) 80 | 81 | ## Contributing 82 | 83 | Highly welcomed! 84 | 85 | ## License 86 | 87 | Please see [The MIT License (MIT)](LICENSE.md). 88 | -------------------------------------------------------------------------------- /DOCS.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | ## Instanciation 3 | `Number::of(any)` accepts any numeric value like floats, integers and even strings or other Number instances 4 | 5 | e.g: `Number::of("1.5")`, `Number::of(1/3)`, `Number::of(1)`, `Number::of(Number::of(12.8))` are valid and correctly handled 6 | 7 | On the other hand, the strings must be sane 8 | e.g `Number::of('1/3')`, `Number::of('1,2')`, `Number::of('1.5€')` will throw an exception 9 | 10 | Basic instances are already built-in: 11 | - `Number::zero()` alias for Number::of(0) 12 | - `Number::one()` alias for Number::of(1) 13 | - `Number::ten()` alias for Number::of(10) 14 | - `Number::hundred()` alias for Number::of(100) 15 | - `Number::thousand()` alias for Number::of(1000) 16 | 17 | ## Collections 18 | - `Number::sum(...any)` return a Number which values to the sum of the values in the collection 19 | - `Number::max(...any)` return a Number which values to the max value in the collection 20 | - `Number::min(...any)` return a Number which values to the min value in the collection 21 | - `Number::average(...any)` return a Number which values to the average value of the collection 22 | 23 | Those methods accepts a list of valid values (e.g: `Number::sum("2", 4, 1/2)`) as well as an iterable object (e.g: `Number::sum(["2", 4, 1/2])`) 24 | 25 | ## Methods 26 | ### Basic maths methods: 27 | - `plus(any)` returns a Number which values to the sum between the two Numbers 28 | - `minus(any)` returns a Number which values to the difference between the two Numbers 29 | - `multiply(any)` returns a Number which values to the multiplication between the two Numbers 30 | - `divide(any)` returns a Number which values to the division between the two Numbers 31 | - `power(exponent)` returns a Number which values powered to the exponent 32 | - `square()` alias for power(2) 33 | - `cube()` alias for power(3) 34 | - `absolute()` returns a Number which value is positive 35 | - `negate()` returns a Number which value will be positive if was negative, negative if positive 36 | - `inverse()` returns a Number which values to one over the number 37 | - `round(precision, mode)` returns a Number which values will rounded according to parameters, defaults to 15 digits and up 38 | - `round(precision)` returns a Number which decimals will be truncated according to precision without any rounding 39 | 40 | ### Comparison methods: 41 | 42 | - `compare(any)` returns -1 if Number is less, 1 if Number is greater and 0 if Numbers are equal, like strcmp does for strings 43 | - `equals(any)` returns whether the two Numbers are equal. It handles floats epsilon comparison. 44 | - `isDifferent(any)` returns the inverse of equals 45 | - `isGreater(any)` returns the strict superiority comparison result 46 | - `isGreaterOrEqual(any)` returns the superiority comparison result 47 | - `isLess(any)` returns the strict inferiority comparison result 48 | - `isLessOrEqual(any)` returns the inferiority comparison result 49 | 50 | ### Other useful methods: 51 | - `wholePart()` returns a Number which value is the left part of the floating point (e.g 3.52 is 3.0) 52 | - `decimalPart()` returns a Number which value is the right part of the floating point (e.g 3.52 is 0.52) 53 | - `isPositive()` returns whether the Number value is positive or not 54 | - `isNegative()` returns whether the Number value is negative or not 55 | - `isZero()` returns whether the Number value is equal to zero 56 | - `isWhole()` returns whether the Number has a decimal part 57 | - `apply(callback)` returns the callback result, useful for custom functions 58 | - `when(bool, callback)` returns the callback result if the condition is truthy 59 | - `format(locale, decimals, forceDecimals)` returns the display format of the number in the desired locale (not for database storage!) 60 | 61 | 62 | ## Accessors 63 | - `get()` returns the raw internal float value (for debug purposes) 64 | - `value()` returns the float value, preferred over value(), because the format will be right (no strange values like -0.0) 65 | - `sign()` returns `+` or `-`. N.B: Zero will be positive. 66 | 67 | All accessors can be retrieved as properties like `->value` or `->sign`. 68 | 69 | 70 | ## Warning 71 | 72 | While floats you be fine for most usages, please read [What Every Programmer Should Know About Floating-Point Arithmetic](https://floating-point-gui.de/), so you will be aware of its limits. Calc tries to overcome it as much as possible (see the `equals()` method implementation), but still some edge cases can occur. 73 | If so, new tests are welcomed (please PR). 74 | -------------------------------------------------------------------------------- /src/Number.php: -------------------------------------------------------------------------------- 1 | value = $value; 18 | } 19 | 20 | public function __toString(): string 21 | { 22 | return $this->toString(); 23 | } 24 | 25 | public function __get($name) 26 | { 27 | if (in_array($name, ['value', 'sign'], true)) { 28 | return $this->{$name}(); 29 | } 30 | } 31 | 32 | public static function of($value): self 33 | { 34 | if ($value instanceof self) { 35 | return $value; 36 | } 37 | 38 | if (! is_numeric($value)) { 39 | throw new Exception(sprintf('Value "%s" should be castable to float or a Number instance', (string) $value)); 40 | } 41 | 42 | return new self((float) $value); 43 | } 44 | 45 | // Accessors ----------------------------------------------------------------------------- 46 | 47 | /** Raw value, not safe, only for debug */ 48 | public function get(): float 49 | { 50 | return $this->value; 51 | } 52 | 53 | public function value(int $decimals = PHP_FLOAT_DIG): float 54 | { 55 | return (float) number_format($this->value, $decimals, '.', ''); 56 | } 57 | 58 | public function sign(): string 59 | { 60 | return $this->isPositive() ? '+' : '-'; 61 | } 62 | 63 | // Actions ------------------------------------------------------------------------------- 64 | 65 | public function plus($that): self 66 | { 67 | $that = self::of($that); 68 | 69 | if ($that->isZero()) { 70 | return $this; 71 | } 72 | 73 | return self::of($this->value + $that->value); 74 | } 75 | 76 | public function minus($that): self 77 | { 78 | $that = self::of($that); 79 | 80 | if ($that->isZero()) { 81 | return $this; 82 | } 83 | 84 | return self::of($this->value - $that->value); 85 | } 86 | 87 | public function multiply($that): self 88 | { 89 | $that = self::of($that); 90 | 91 | if ($that->isZero()) { 92 | return $that; 93 | } 94 | 95 | return self::of($this->value * $that->value); 96 | } 97 | 98 | public function divide($that): self 99 | { 100 | $that = self::of($that); 101 | 102 | if ($that->isZero()) { 103 | throw new Exception('Division by zero is not possible'); 104 | } 105 | 106 | return self::of($this->value / $that->value); 107 | } 108 | 109 | public function power(int $exponent): self 110 | { 111 | if ($exponent === 0) { 112 | return self::one(); 113 | } 114 | 115 | if ($exponent === 1) { 116 | return $this; 117 | } 118 | 119 | return self::of($this->value ** $exponent); 120 | } 121 | 122 | public function square(): self 123 | { 124 | return $this->power(2); 125 | } 126 | 127 | public function cube(): self 128 | { 129 | return $this->power(3); 130 | } 131 | 132 | /** 133 | * Float equality isn't trivial. 134 | * @link: https://www.php.net/manual/en/language.types.float.php#language.types.float.comparison 135 | * @see: https://floating-point-gui.de/ 136 | */ 137 | public function equals($that): bool 138 | { 139 | $that = self::of($that); 140 | 141 | if ($this->value === $that->value) { 142 | return true; 143 | } 144 | 145 | $diff = abs($this->value - $that->value); 146 | 147 | if ($diff < PHP_FLOAT_EPSILON * 4) { 148 | return true; 149 | } 150 | 151 | $absA = abs($this->value); 152 | $absB = abs($that->value); 153 | 154 | if ($this->value === 0.0 || $that->value === 0.0 || ($absA + $absB < PHP_FLOAT_MIN)) { 155 | return $diff < (PHP_FLOAT_EPSILON * PHP_FLOAT_MIN); 156 | } 157 | 158 | return $diff / min(($absA + $absB), PHP_FLOAT_MAX) < PHP_FLOAT_EPSILON; 159 | } 160 | 161 | public function isDifferent($that): bool 162 | { 163 | return ! $this->equals($that); 164 | } 165 | 166 | public function isGreater($that): bool 167 | { 168 | return $this->compare($that) > 0; 169 | } 170 | 171 | public function isGreaterOrEqual($that): bool 172 | { 173 | return $this->compare($that) >= 0; 174 | } 175 | 176 | public function isLess($that): bool 177 | { 178 | return $this->compare($that) < 0; 179 | } 180 | 181 | public function isLessOrEqual($that): bool 182 | { 183 | return $this->compare($that) <= 0; 184 | } 185 | 186 | public function isNegative(): bool 187 | { 188 | return $this->isLess(self::zero()); 189 | } 190 | 191 | public function isPositive(): bool 192 | { 193 | return ! $this->isNegative(); 194 | } 195 | 196 | public function isZero(): bool 197 | { 198 | return $this->equals(self::zero()); 199 | } 200 | 201 | public function wholePart(): self 202 | { 203 | return self::of((int) $this->toString())->absolute(); 204 | } 205 | 206 | public function decimalPart(): self 207 | { 208 | return $this->absolute()->minus($this->wholePart()); 209 | } 210 | 211 | public function isWhole(): bool 212 | { 213 | return$this->decimalPart()->isZero(); 214 | } 215 | 216 | public function absolute(): self 217 | { 218 | return $this->isNegative() ? $this->negate() : $this; 219 | } 220 | 221 | public function negate(): self 222 | { 223 | return $this->multiply(-1); 224 | } 225 | 226 | public function inverse(): self 227 | { 228 | return self::one()->divide($this); 229 | } 230 | 231 | public function round(int $precision = PHP_FLOAT_DIG, int $mode = PHP_ROUND_HALF_UP): self 232 | { 233 | return self::of(round($this->value, $precision, $mode)); 234 | } 235 | 236 | public function truncate(int $precision): self 237 | { 238 | return self::of($this->sign() . $this->wholePart()->toInt() . '.' . mb_substr((string) $this->decimalPart()->value, 2, $precision)); 239 | } 240 | 241 | public function compare($that): int 242 | { 243 | $that = self::of($that); 244 | 245 | if ($this->equals($that)) { 246 | return 0; 247 | } 248 | 249 | return $this->value <=> $that->value; 250 | } 251 | 252 | public function apply(callable $callback): self 253 | { 254 | return self::of($callback($this)); 255 | } 256 | 257 | public function when(bool $condition, callable $callback, ?callable $default = null): self 258 | { 259 | if ($condition) { 260 | return $this->apply($callback); 261 | } 262 | 263 | if ($default) { 264 | return $this->apply($default); 265 | } 266 | 267 | return $this; 268 | } 269 | 270 | public function format(string $locale = 'en_US', int $decimals = 2, bool $forceDecimals = false): string 271 | { 272 | static $formatter; 273 | 274 | if ($formatter === null) { 275 | $formatter = new NumberFormatter($locale, NumberFormatter::DECIMAL); 276 | } 277 | 278 | if ($forceDecimals) { 279 | $formatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $decimals); 280 | } 281 | 282 | $formatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimals); 283 | 284 | return $formatter->format($this->value); 285 | } 286 | 287 | // Config -------------------------------------------------------------------------------- 288 | 289 | public function toInt(): int 290 | { 291 | if (! $this->isWhole()) { 292 | throw new Exception('This number cant be casted to int without precision loss, use round(0) before'); 293 | } 294 | 295 | return (int) $this->value(); 296 | } 297 | 298 | public function toFloat(): float 299 | { 300 | return $this->value(); 301 | } 302 | 303 | public function toString(): string 304 | { 305 | return (string) $this->value(); 306 | } 307 | 308 | public function toArray(): array 309 | { 310 | return [ 311 | 'value' => $this->value(), 312 | 'sign' => $this->sign(), 313 | 'format' => $this->format(), 314 | ]; 315 | } 316 | 317 | public function toJson(int $options = 0): string 318 | { 319 | return json_encode($this->jsonSerialize(), $options); 320 | } 321 | 322 | public function jsonSerialize(): array 323 | { 324 | return $this->toArray(); 325 | } 326 | 327 | // Helpers ------------------------------------------------------------------------------- 328 | 329 | public static function zero(): self 330 | { 331 | static $zero; 332 | 333 | if ($zero === null) { 334 | $zero = self::of(0); 335 | } 336 | 337 | return $zero; 338 | } 339 | 340 | public static function one(): self 341 | { 342 | static $one; 343 | 344 | if ($one === null) { 345 | $one = self::of(1); 346 | } 347 | 348 | return $one; 349 | } 350 | 351 | public static function ten(): self 352 | { 353 | static $ten; 354 | 355 | if ($ten === null) { 356 | $ten = self::of(10); 357 | } 358 | 359 | return $ten; 360 | } 361 | 362 | public static function hundred(): self 363 | { 364 | static $hundred; 365 | 366 | if ($hundred === null) { 367 | $hundred = self::of(100); 368 | } 369 | 370 | return $hundred; 371 | } 372 | 373 | public static function thousand(): self 374 | { 375 | static $thousand; 376 | 377 | if ($thousand === null) { 378 | $thousand = self::of(1000); 379 | } 380 | 381 | return $thousand; 382 | } 383 | 384 | // Collections --------------------------------------------------------------------------- 385 | 386 | public static function max(...$collection): self 387 | { 388 | $collection = self::collection(...$collection); 389 | 390 | $max = null; 391 | 392 | foreach ($collection as $item) { 393 | if ($max !== null && ! $item->isGreater($max)) { 394 | continue; 395 | } 396 | 397 | $max = $item; 398 | } 399 | 400 | return $max; 401 | } 402 | 403 | public static function min(...$collection): self 404 | { 405 | $collection = self::collection(...$collection); 406 | 407 | $min = null; 408 | 409 | foreach ($collection as $item) { 410 | if ($min !== null && ! $item->isLess($min)) { 411 | continue; 412 | } 413 | 414 | $min = $item; 415 | } 416 | 417 | return $min; 418 | } 419 | 420 | public static function sum(...$collection): self 421 | { 422 | $collection = self::collection(...$collection); 423 | 424 | $sum = null; 425 | 426 | foreach ($collection as $item) { 427 | $sum = $sum === null ? $item : $sum->plus($item); 428 | } 429 | 430 | return $sum; 431 | } 432 | 433 | public static function average(...$collection): self 434 | { 435 | $collection = self::collection(...$collection); 436 | 437 | $sum = self::sum($collection); 438 | 439 | return $sum->divide(count($collection)); 440 | } 441 | 442 | private static function collection($first, ...$items): array 443 | { 444 | $items = is_iterable($first) ? [...$first, ...$items] : [$first, ...$items]; 445 | 446 | if (empty($items)) { 447 | throw new Exception('Collection expects at least one argument'); 448 | } 449 | 450 | foreach ($items as $key => $item) { 451 | $items[$key] = self::of($item); 452 | } 453 | 454 | return $items; 455 | } 456 | } 457 | -------------------------------------------------------------------------------- /.php_cs.dist: -------------------------------------------------------------------------------- 1 | setRiskyAllowed(true) 4 | ->setRules([ 5 | '@PSR1' => true, 6 | '@PSR2' => true, 7 | // Each line of multi-line DocComments must have an asterisk [PSR-5] and must be aligned with the first one. 8 | 'align_multiline_comment' => ['comment_type'=>'all_multiline'], 9 | // Each element of an array must be indented exactly once. 10 | 'array_indentation' => true, 11 | // PHP arrays should be declared using the configured syntax. 12 | 'array_syntax' => ['syntax'=>'short'], 13 | // There MUST be one blank line after the namespace declaration. 14 | 'blank_line_after_namespace' => true, 15 | // Ensure there is no code on the same line as the PHP open tag and it is followed by a blank line. 16 | 'blank_line_after_opening_tag' => true, 17 | // An empty line feed must precede any configured statement. 18 | 'blank_line_before_statement' => true, 19 | // The body of each structure MUST be enclosed by braces. Braces should be properly placed. Body of braces should be properly indented. 20 | 'braces' => ['allow_single_line_closure'=>true], 21 | // A single space or none should be between cast and variable. 22 | 'cast_spaces' => true, 23 | // Class, trait and interface elements must be separated with one blank line. 24 | 'class_attributes_separation' => ['elements'=>['method']], 25 | // Whitespace around the keywords of a class, trait or interfaces definition should be one space. 26 | 'class_definition' => true, 27 | // Using `isset($var) &&` multiple times should be done in one call. 28 | 'combine_consecutive_issets' => true, 29 | // Calling `unset` on multiple items should be done in one call. 30 | 'combine_consecutive_unsets' => true, 31 | // Replace multiple nested calls of `dirname` by only one call with second `$level` parameter. Requires PHP >= 7.0. 32 | 'combine_nested_dirname' => true, 33 | // Remove extra spaces in a nullable typehint. 34 | 'compact_nullable_typehint' => true, 35 | // Concatenation should be spaced according configuration. 36 | 'concat_space' => ['spacing'=>'one'], 37 | // Equal sign in declare statement should be surrounded by spaces or not following configuration. 38 | 'declare_equal_normalize' => true, 39 | // Force strict types declaration in all files. Requires PHP >= 7.0. 40 | 'declare_strict_types' => true, 41 | // Replaces `dirname(__FILE__)` expression with equivalent `__DIR__` constant. 42 | 'dir_constant' => true, 43 | // Add curly braces to indirect variables to make them clear to understand. Requires PHP >= 7.0. 44 | 'explicit_indirect_variable' => true, 45 | // Converts implicit variables into explicit ones in double-quoted strings or heredoc syntax. 46 | 'explicit_string_variable' => true, 47 | // Transforms imported FQCN parameters and return types in function arguments to short version. 48 | 'fully_qualified_strict_types' => true, 49 | // Replace core functions calls returning constants with the constants. 50 | 'function_to_constant' => true, 51 | // Ensure single space between function's argument and its typehint. 52 | 'function_typehint_space' => true, 53 | // Imports or fully qualifies global classes/functions/constants. 54 | 'global_namespace_import' => ['import_classes'=>true,'import_constants'=>true,'import_functions'=>true], 55 | // Include/Require and file path should be divided with a single space. File path should not be placed under brackets. 56 | 'include' => true, 57 | // Pre- or post-increment and decrement operators should be used if possible. 58 | 'increment_style' => ['style'=>'post'], 59 | // Replaces `is_null($var)` expression with `null === $var`. 60 | 'is_null' => ['use_yoda_style'=>false], 61 | // Ensure there is no code on the same line as the PHP open tag. 62 | 'linebreak_after_opening_tag' => true, 63 | // List (`array` destructuring) assignment should be declared using the configured syntax. Requires PHP >= 7.1. 64 | 'list_syntax' => ['syntax'=>'short'], 65 | // Use `&&` and `||` logical operators instead of `and` and `or`. 66 | 'logical_operators' => true, 67 | // Cast should be written in lower case. 68 | 'lowercase_cast' => true, 69 | // Class static references `self`, `static` and `parent` MUST be in lower case. 70 | 'lowercase_static_reference' => true, 71 | // Magic constants should be referred to using the correct casing. 72 | 'magic_constant_casing' => true, 73 | // Magic method definitions and calls must be using the correct casing. 74 | 'magic_method_casing' => true, 75 | // Replace non multibyte-safe functions with corresponding mb function. 76 | 'mb_str_functions' => true, 77 | // In method arguments and method call, there MUST NOT be a space before each comma and there MUST be one space after each comma. Argument lists MAY be split across multiple lines, where each subsequent line is indented once. When doing so, the first item in the list MUST be on the next line, and there MUST be only one argument per line. 78 | 'method_argument_space' => ['on_multiline'=>'ensure_fully_multiline'], 79 | // Method chaining MUST be properly indented. Method chaining with different levels of indentation is not supported. 80 | 'method_chaining_indentation' => true, 81 | // Replaces `intval`, `floatval`, `doubleval`, `strval` and `boolval` function calls with according type casting operator. 82 | 'modernize_types_casting' => true, 83 | // Forbid multi-line whitespace before the closing semicolon or move the semicolon to the new line for chained calls. 84 | 'multiline_whitespace_before_semicolons' => true, 85 | // Function defined by PHP should be called using the correct casing. 86 | 'native_function_casing' => true, 87 | // Native type hints for functions should use the correct case. 88 | 'native_function_type_declaration_casing' => true, 89 | // All instances created with new keyword must be followed by braces. 90 | 'new_with_braces' => true, 91 | // Master functions shall be used instead of aliases. 92 | 'no_alias_functions' => true, 93 | // Replace control structure alternative syntax to use braces. 94 | 'no_alternative_syntax' => true, 95 | // There should be no empty lines after class opening brace. 96 | 'no_blank_lines_after_class_opening' => true, 97 | // There should not be blank lines between docblock and the documented element. 98 | 'no_blank_lines_after_phpdoc' => true, 99 | // There should not be any empty comments. 100 | 'no_empty_comment' => true, 101 | // There should not be empty PHPDoc blocks. 102 | 'no_empty_phpdoc' => true, 103 | // Remove useless semicolon statements. 104 | 'no_empty_statement' => true, 105 | // Removes extra blank lines and/or blank lines following configuration. 106 | 'no_extra_blank_lines' => true, 107 | // Replace accidental usage of homoglyphs (non ascii characters) in names. 108 | 'no_homoglyph_names' => true, 109 | // Remove leading slashes in `use` clauses. 110 | 'no_leading_import_slash' => true, 111 | // The namespace declaration line shouldn't contain leading whitespace. 112 | 'no_leading_namespace_whitespace' => true, 113 | // Either language construct `print` or `echo` should be used. 114 | 'no_mixed_echo_print' => true, 115 | // Operator `=>` should not be surrounded by multi-line whitespaces. 116 | 'no_multiline_whitespace_around_double_arrow' => true, 117 | // Properties MUST not be explicitly initialized with `null` except when they have a type declaration (PHP 7.4). 118 | 'no_null_property_initialization' => true, 119 | // Convert PHP4-style constructors to `__construct`. 120 | 'no_php4_constructor' => true, 121 | // Short cast `bool` using double exclamation mark should not be used. 122 | 'no_short_bool_cast' => true, 123 | // Single-line whitespace before closing semicolon are prohibited. 124 | 'no_singleline_whitespace_before_semicolons' => true, 125 | // There MUST NOT be spaces around offset braces. 126 | 'no_spaces_around_offset' => true, 127 | // Replaces superfluous `elseif` with `if`. 128 | 'no_superfluous_elseif' => true, 129 | // Removes `@param`, `@return` and `@var` tags that don't provide any useful information. 130 | 'no_superfluous_phpdoc_tags' => true, 131 | // Remove trailing commas in list function calls. 132 | 'no_trailing_comma_in_list_call' => true, 133 | // PHP single-line arrays should not have trailing comma. 134 | 'no_trailing_comma_in_singleline_array' => true, 135 | // There MUST be no trailing spaces inside comment or PHPDoc. 136 | 'no_trailing_whitespace_in_comment' => true, 137 | // Removes unneeded parentheses around control statements. 138 | 'no_unneeded_control_parentheses' => true, 139 | // Removes unneeded curly braces that are superfluous and aren't part of a control structure's body. 140 | 'no_unneeded_curly_braces' => true, 141 | // A `final` class must not have `final` methods and `private` methods must not be `final`. 142 | 'no_unneeded_final_method' => true, 143 | // In function arguments there must not be arguments with default values before non-default ones. 144 | 'no_unreachable_default_argument_value' => true, 145 | // Variables must be set `null` instead of using `(unset)` casting. 146 | 'no_unset_cast' => true, 147 | // Unused `use` statements must be removed. 148 | 'no_unused_imports' => true, 149 | // There should not be useless `else` cases. 150 | 'no_useless_else' => true, 151 | // There should not be an empty `return` statement at the end of a function. 152 | 'no_useless_return' => true, 153 | // In array declaration, there MUST NOT be a whitespace before each comma. 154 | 'no_whitespace_before_comma_in_array' => true, 155 | // Remove trailing whitespace at the end of blank lines. 156 | 'no_whitespace_in_blank_line' => true, 157 | // Remove Zero-width space (ZWSP), Non-breaking space (NBSP) and other invisible unicode symbols. 158 | 'non_printable_character' => true, 159 | // Array index should always be written by using square braces. 160 | 'normalize_index_brace' => true, 161 | // Logical NOT operators (`!`) should have one trailing whitespace. 162 | 'not_operator_with_successor_space' => true, 163 | // There should not be space before or after object `T_OBJECT_OPERATOR` `->`. 164 | 'object_operator_without_whitespace' => true, 165 | // Orders the elements of classes/interfaces/traits. 166 | 'ordered_class_elements' => true, 167 | // Ordering `use` statements. 168 | 'ordered_imports' => true, 169 | // Orders the interfaces in an `implements` or `interface extends` clause. 170 | 'ordered_interfaces' => true, 171 | // Docblocks should have the same indentation as the documented subject. 172 | 'phpdoc_indent' => true, 173 | // `@access` annotations should be omitted from PHPDoc. 174 | 'phpdoc_no_access' => true, 175 | // `@package` and `@subpackage` annotations should be omitted from PHPDoc. 176 | 'phpdoc_no_package' => true, 177 | // Classy that does not inherit must not have `@inheritdoc` tags. 178 | 'phpdoc_no_useless_inheritdoc' => true, 179 | // Scalar types should always be written in the same form. `int` not `integer`, `bool` not `boolean`, `float` not `real` or `double`. 180 | 'phpdoc_scalar' => true, 181 | // Single line `@var` PHPDoc should have proper spacing. 182 | 'phpdoc_single_line_var_spacing' => true, 183 | // PHPDoc summary should end in either a full stop, exclamation mark, or question mark. 184 | 'phpdoc_summary' => true, 185 | // Docblocks should only be used on structural elements. 186 | 'phpdoc_to_comment' => true, 187 | // EXPERIMENTAL: Takes `@param` annotations of non-mixed types and adjusts accordingly the function signature. Requires PHP >= 7.0. 188 | 'phpdoc_to_param_type' => true, 189 | // EXPERIMENTAL: Takes `@return` annotation of non-mixed types and adjusts accordingly the function signature. Requires PHP >= 7.0. 190 | 'phpdoc_to_return_type' => true, 191 | // PHPDoc should start and end with content, excluding the very first and last line of the docblocks. 192 | 'phpdoc_trim' => true, 193 | // The correct case must be used for standard PHP types in PHPDoc. 194 | 'phpdoc_types' => true, 195 | // `@var` and `@type` annotations of classy properties should not contain the name. 196 | 'phpdoc_var_without_name' => true, 197 | // Converts `protected` variables and methods to `private` where possible. 198 | 'protected_to_private' => true, 199 | // Class names should match the file name. 200 | 'psr4' => true, 201 | // Local, dynamic and directly referenced variables should not be assigned and directly returned by a function or method. 202 | 'return_assignment' => true, 203 | // There should be one or no space before colon, and one space after it in return type declarations, according to configuration. 204 | 'return_type_declaration' => ['space_before'=>'none'], 205 | // Inside class or interface element `self` should be preferred to the class name itself. 206 | 'self_accessor' => true, 207 | // Instructions must be terminated with a semicolon. 208 | 'semicolon_after_instruction' => true, 209 | // Cast shall be used, not `settype`. 210 | 'set_type_to_cast' => true, 211 | // Cast `(boolean)` and `(integer)` should be written as `(bool)` and `(int)`, `(double)` and `(real)` as `(float)`, `(binary)` as `(string)`. 212 | 'short_scalar_cast' => true, 213 | // A return statement wishing to return `void` should not return `null`. 214 | 'simplified_null_return' => true, 215 | // There should be exactly one blank line before a namespace declaration. 216 | 'single_blank_line_before_namespace' => true, 217 | // Single-line comments and multi-line comments with only one line of actual content should use the `//` syntax. 218 | 'single_line_comment_style' => true, 219 | // Throwing exception must be done in single line. 220 | 'single_line_throw' => true, 221 | // Convert double quotes to single quotes for simple strings. 222 | 'single_quote' => true, 223 | // Each trait `use` must be done as single statement. 224 | 'single_trait_insert_per_statement' => true, 225 | // Fix whitespace after a semicolon. 226 | 'space_after_semicolon' => true, 227 | // Increment and decrement operators should be used if possible. 228 | 'standardize_increment' => true, 229 | // Replace all `<>` with `!=`. 230 | 'standardize_not_equals' => true, 231 | // Comparisons should be strict. 232 | 'strict_comparison' => true, 233 | // Functions should be used with `$strict` param set to `true`. 234 | 'strict_param' => true, 235 | // Standardize spaces around ternary operator. 236 | 'ternary_operator_spaces' => true, 237 | // Use `null` coalescing operator `??` where possible. Requires PHP >= 7.0. 238 | 'ternary_to_null_coalescing' => true, 239 | // PHP multi-line arrays should have a trailing comma. 240 | 'trailing_comma_in_multiline_array' => true, 241 | // Arrays should be formatted like function/method arguments, without leading or trailing single line space. 242 | 'trim_array_spaces' => true, 243 | // Unary operators should be placed adjacent to their operands. 244 | 'unary_operator_spaces' => true, 245 | // Visibility MUST be declared on all properties and methods; `abstract` and `final` MUST be declared before the visibility; `static` MUST be declared after the visibility. 246 | 'visibility_required' => ['elements'=>['property','method','const']], 247 | // Add `void` return type to functions with missing or empty return statements, but priority is given to `@return` annotations. Requires PHP >= 7.1. 248 | 'void_return' => true, 249 | // In array declaration, there MUST be a whitespace after each comma. 250 | 'whitespace_after_comma_in_array' => true, 251 | ]) 252 | ->setFinder(PhpCsFixer\Finder::create() 253 | ->notPath('vendor') 254 | ->in([ 255 | __DIR__ . '/src', 256 | __DIR__ . '/tests', 257 | ]) 258 | ->name('*.php') 259 | ->ignoreDotFiles(true) 260 | ->ignoreVCS(true) 261 | ); 262 | --------------------------------------------------------------------------------