mirror of
https://github.com/linuxserver/Heimdall.git
synced 2026-08-07 07:16:13 +00:00
Upgrade to Laravel 12
- laravel/framework ^11.45 -> ^12.0 (installed 12.63.0) - phpunit/phpunit ^10.5 -> ^11.0; migrate phpunit.xml to the 11.5 schema - graham-campbell/github ^12.5 -> ^13.0 (v12 caps illuminate/support at ^11) - Carbon 3 pulled in by L12; no application code changes required - gitignore /.phpunit.cache Full suite green: 59 tests, 128 assertions (1 skipped).
This commit is contained in:
@@ -29,3 +29,4 @@ yarn-error.log
|
|||||||
storage/app/public/avatars/*
|
storage/app/public/avatars/*
|
||||||
.env
|
.env
|
||||||
.phpunit.result.cache
|
.phpunit.result.cache
|
||||||
|
/.phpunit.cache
|
||||||
|
|||||||
+3
-3
@@ -12,9 +12,9 @@
|
|||||||
"ext-intl": "*",
|
"ext-intl": "*",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
"enshrined/svg-sanitize": "^0.22.0",
|
"enshrined/svg-sanitize": "^0.22.0",
|
||||||
"graham-campbell/github": "^12.5",
|
"graham-campbell/github": "^13.0",
|
||||||
"guzzlehttp/guzzle": "^7.8",
|
"guzzlehttp/guzzle": "^7.8",
|
||||||
"laravel/framework": "^11.45",
|
"laravel/framework": "^12.0",
|
||||||
"laravel/tinker": "^2.9",
|
"laravel/tinker": "^2.9",
|
||||||
"laravel/ui": "^4.4",
|
"laravel/ui": "^4.4",
|
||||||
"league/flysystem-aws-s3-v3": "^3.0",
|
"league/flysystem-aws-s3-v3": "^3.0",
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
"barryvdh/laravel-ide-helper": "^3.0",
|
"barryvdh/laravel-ide-helper": "^3.0",
|
||||||
"filp/whoops": "^2.8",
|
"filp/whoops": "^2.8",
|
||||||
"mockery/mockery": "^1.6",
|
"mockery/mockery": "^1.6",
|
||||||
"phpunit/phpunit": "^10.5",
|
"phpunit/phpunit": "^11.0",
|
||||||
"squizlabs/php_codesniffer": "3.*",
|
"squizlabs/php_codesniffer": "3.*",
|
||||||
"symfony/thanks": "^1.2",
|
"symfony/thanks": "^1.2",
|
||||||
"fakerphp/faker": "^1.23"
|
"fakerphp/faker": "^1.23"
|
||||||
|
|||||||
Generated
+1479
-1075
File diff suppressed because it is too large
Load Diff
+6
-6
@@ -1,10 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" bootstrap="vendor/autoload.php" colors="true">
|
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.5/phpunit.xsd" bootstrap="vendor/autoload.php" colors="true" cacheDirectory=".phpunit.cache">
|
||||||
<coverage processUncoveredFiles="true">
|
|
||||||
<include>
|
|
||||||
<directory suffix=".php">./app</directory>
|
|
||||||
</include>
|
|
||||||
</coverage>
|
|
||||||
<testsuites>
|
<testsuites>
|
||||||
<testsuite name="Unit">
|
<testsuite name="Unit">
|
||||||
<directory suffix="Test.php">./tests/Unit</directory>
|
<directory suffix="Test.php">./tests/Unit</directory>
|
||||||
@@ -25,4 +20,9 @@
|
|||||||
<env name="SESSION_DRIVER" value="array"/>
|
<env name="SESSION_DRIVER" value="array"/>
|
||||||
<env name="TELESCOPE_ENABLED" value="false"/>
|
<env name="TELESCOPE_ENABLED" value="false"/>
|
||||||
</php>
|
</php>
|
||||||
|
<source>
|
||||||
|
<include>
|
||||||
|
<directory suffix=".php">./app</directory>
|
||||||
|
</include>
|
||||||
|
</source>
|
||||||
</phpunit>
|
</phpunit>
|
||||||
|
|||||||
Vendored
+12
-2
@@ -3,8 +3,18 @@
|
|||||||
// autoload.php @generated by Composer
|
// autoload.php @generated by Composer
|
||||||
|
|
||||||
if (PHP_VERSION_ID < 50600) {
|
if (PHP_VERSION_ID < 50600) {
|
||||||
echo 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
|
if (!headers_sent()) {
|
||||||
exit(1);
|
header('HTTP/1.1 500 Internal Server Error');
|
||||||
|
}
|
||||||
|
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
|
||||||
|
if (!ini_get('display_errors')) {
|
||||||
|
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||||
|
fwrite(STDERR, $err);
|
||||||
|
} elseif (!headers_sent()) {
|
||||||
|
echo $err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new RuntimeException($err);
|
||||||
}
|
}
|
||||||
|
|
||||||
require_once __DIR__ . '/composer/autoload_real.php';
|
require_once __DIR__ . '/composer/autoload_real.php';
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
// Share common rules between non-test and test files
|
|
||||||
return [
|
|
||||||
'@PSR12' => true,
|
|
||||||
'blank_line_after_opening_tag' => true,
|
|
||||||
'braces' => [
|
|
||||||
'allow_single_line_anonymous_class_with_empty_body' => true,
|
|
||||||
],
|
|
||||||
'compact_nullable_typehint' => true,
|
|
||||||
'declare_equal_normalize' => true,
|
|
||||||
'lowercase_cast' => true,
|
|
||||||
'lowercase_static_reference' => true,
|
|
||||||
'new_with_braces' => true,
|
|
||||||
'no_blank_lines_after_class_opening' => true,
|
|
||||||
'no_leading_import_slash' => true,
|
|
||||||
'no_whitespace_in_blank_line' => true,
|
|
||||||
'ordered_class_elements' => [
|
|
||||||
'order' => [
|
|
||||||
'use_trait',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'ordered_imports' => [
|
|
||||||
'imports_order' => [
|
|
||||||
'class',
|
|
||||||
'function',
|
|
||||||
'const',
|
|
||||||
],
|
|
||||||
'sort_algorithm' => 'alpha',
|
|
||||||
],
|
|
||||||
'return_type_declaration' => true,
|
|
||||||
'short_scalar_cast' => true,
|
|
||||||
'single_trait_insert_per_statement' => true,
|
|
||||||
'ternary_operator_spaces' => true,
|
|
||||||
'visibility_required' => [
|
|
||||||
'elements' => [
|
|
||||||
'const',
|
|
||||||
'method',
|
|
||||||
'property',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
// Further quality-of-life improvements
|
|
||||||
'array_syntax' => [
|
|
||||||
'syntax' => 'short',
|
|
||||||
],
|
|
||||||
'concat_space' => [
|
|
||||||
'spacing' => 'one',
|
|
||||||
],
|
|
||||||
'fully_qualified_strict_types' => true,
|
|
||||||
'native_function_invocation' => [
|
|
||||||
'include' => [],
|
|
||||||
'strict' => true,
|
|
||||||
],
|
|
||||||
'no_unused_imports' => true,
|
|
||||||
'single_quote' => true,
|
|
||||||
'space_after_semicolon' => true,
|
|
||||||
'trailing_comma_in_multiline' => true,
|
|
||||||
'trim_array_spaces' => true,
|
|
||||||
'unary_operator_spaces' => true,
|
|
||||||
'whitespace_after_comma_in_array' => true,
|
|
||||||
];
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
<?php
|
|
||||||
require __DIR__ . '/vendor/autoload.php';
|
|
||||||
|
|
||||||
$finder = PhpCsFixer\Finder::create()
|
|
||||||
->in(__DIR__)
|
|
||||||
->exclude('tests');
|
|
||||||
|
|
||||||
$config = require __DIR__ . '/.php-cs-fixer.common.php';
|
|
||||||
|
|
||||||
return (new PhpCsFixer\Config())
|
|
||||||
->setFinder($finder)
|
|
||||||
->setRules($config)
|
|
||||||
->setRiskyAllowed(true)
|
|
||||||
->setCacheFile(__DIR__ . '/.php-cs-fixer.cache');
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<?php
|
|
||||||
require __DIR__ . '/vendor/autoload.php';
|
|
||||||
|
|
||||||
$finder = PhpCsFixer\Finder::create()
|
|
||||||
->in(__DIR__ . '/tests')
|
|
||||||
->exclude('__snapshots__');
|
|
||||||
|
|
||||||
$config = require __DIR__ . '/.php-cs-fixer.common.php';
|
|
||||||
|
|
||||||
// Additional rules for tests
|
|
||||||
$config = array_merge(
|
|
||||||
$config,
|
|
||||||
[
|
|
||||||
'declare_strict_types' => true,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (new PhpCsFixer\Config())
|
|
||||||
->setFinder($finder)
|
|
||||||
->setRules($config)
|
|
||||||
->setRiskyAllowed(true)
|
|
||||||
->setCacheFile(__DIR__ . '/.php-cs-fixer.tests.cache');
|
|
||||||
+72
@@ -1,5 +1,77 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v3.6.1 - 2025-12-10
|
||||||
|
|
||||||
|
### What's Changed
|
||||||
|
|
||||||
|
* Fix `methodsto` typo in README by @peterchrjoergensen in https://github.com/barryvdh/laravel-ide-helper/pull/1723
|
||||||
|
* Bump actions/checkout from 4 to 5 in the deps group by @dependabot[bot] in https://github.com/barryvdh/laravel-ide-helper/pull/1731
|
||||||
|
* Fix typos in documentation and code comments by @kei1111 in https://github.com/barryvdh/laravel-ide-helper/pull/1733
|
||||||
|
* Add php 8.5 support by @sergiy-petrov in https://github.com/barryvdh/laravel-ide-helper/pull/1735
|
||||||
|
* Fix alias fake error by @WentTheFox in https://github.com/barryvdh/laravel-ide-helper/pull/1745
|
||||||
|
* Remove calls to PHP 8.5-deprecated `setAccessible` by @jnoordsij in https://github.com/barryvdh/laravel-ide-helper/pull/1744
|
||||||
|
* Bump the deps group across 1 directory with 2 updates by @dependabot[bot] in https://github.com/barryvdh/laravel-ide-helper/pull/1743
|
||||||
|
* Add support for `decimal` column type by @BrainStone in https://github.com/barryvdh/laravel-ide-helper/pull/1739
|
||||||
|
|
||||||
|
### New Contributors
|
||||||
|
|
||||||
|
* @peterchrjoergensen made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1723
|
||||||
|
* @kei1111 made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1733
|
||||||
|
* @WentTheFox made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1745
|
||||||
|
* @BrainStone made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1739
|
||||||
|
|
||||||
|
**Full Changelog**: https://github.com/barryvdh/laravel-ide-helper/compare/v3.6.0...v3.6.1
|
||||||
|
|
||||||
|
## v3.6.0 - 2025-07-18
|
||||||
|
|
||||||
|
### What's Changed
|
||||||
|
|
||||||
|
* fix: Change AsArrayObject cast to be Laravel's ArrayObject by @wsamoht in https://github.com/barryvdh/laravel-ide-helper/pull/1675
|
||||||
|
* Add extends declaration for Macroable classes to fix missing inherited methods by @KentarouTakeda in https://github.com/barryvdh/laravel-ide-helper/pull/1674
|
||||||
|
* fix(meta): ignore aliases in the autoloader (Fixes #1671) by @pataar in https://github.com/barryvdh/laravel-ide-helper/pull/1686
|
||||||
|
* feat(ModelsCommand): add support for the new Scope attribute by @pataar in https://github.com/barryvdh/laravel-ide-helper/pull/1694
|
||||||
|
* fix type change for scope default float parameter by @nivseb in https://github.com/barryvdh/laravel-ide-helper/pull/1697
|
||||||
|
* Revert #1629 - *Allow adding custom Macroable classes* by @erikn69 in https://github.com/barryvdh/laravel-ide-helper/pull/1707
|
||||||
|
* Configurable macro return type defaults by @erikn69 in https://github.com/barryvdh/laravel-ide-helper/pull/1711
|
||||||
|
* docs(readme): add Laravel 12 support information by @SantosVilanculos in https://github.com/barryvdh/laravel-ide-helper/pull/1717
|
||||||
|
* Add multi-level directory support for translation files by @RosiersRobin in https://github.com/barryvdh/laravel-ide-helper/pull/1718
|
||||||
|
* Support `AsCollection::of($map)`, `AsCollection::using($class, $map)` by @erikn69 in https://github.com/barryvdh/laravel-ide-helper/pull/1714
|
||||||
|
* fix: Fixed wrong doc for SoftDeletes `withTrashed` method by @eldair in https://github.com/barryvdh/laravel-ide-helper/pull/1688
|
||||||
|
* Support other OS on tests by @erikn69 in https://github.com/barryvdh/laravel-ide-helper/pull/1715
|
||||||
|
* Fix tests on windows by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1720
|
||||||
|
* Bump stefanzweifel/git-auto-commit-action from 5 to 6 in the deps group by @dependabot[bot] in https://github.com/barryvdh/laravel-ide-helper/pull/1719
|
||||||
|
* Update .gitattributes - avoid all `.php-cs-fixer` files on vendor by @erikn69 in https://github.com/barryvdh/laravel-ide-helper/pull/1708
|
||||||
|
* fix(ModelsCommand): use 'string' as realType for 'encrypted' casts by @pataar in https://github.com/barryvdh/laravel-ide-helper/pull/1698
|
||||||
|
* Trim strings and bump reflection docblock by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1721
|
||||||
|
* Add magic *_exists properties by @erikn69 in https://github.com/barryvdh/laravel-ide-helper/pull/1712
|
||||||
|
|
||||||
|
### New Contributors
|
||||||
|
|
||||||
|
* @wsamoht made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1675
|
||||||
|
* @nivseb made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1697
|
||||||
|
* @SantosVilanculos made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1717
|
||||||
|
* @RosiersRobin made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1718
|
||||||
|
|
||||||
|
**Full Changelog**: https://github.com/barryvdh/laravel-ide-helper/compare/v3.5.5...v3.6.0
|
||||||
|
|
||||||
|
## v3.5.5 - 2025-02-21
|
||||||
|
|
||||||
|
### What's Changed
|
||||||
|
|
||||||
|
* Fix for incorrect config item types in meta file by @eldair in https://github.com/barryvdh/laravel-ide-helper/pull/1662
|
||||||
|
* Prevent generation of incorrect property annotation by @skyler544 in https://github.com/barryvdh/laravel-ide-helper/pull/1665
|
||||||
|
* Fix MorphTo Model Doc Generation by @yparitcher in https://github.com/barryvdh/laravel-ide-helper/pull/1668
|
||||||
|
* Laravel 12 support by @jonnott in https://github.com/barryvdh/laravel-ide-helper/pull/1672
|
||||||
|
|
||||||
|
### New Contributors
|
||||||
|
|
||||||
|
* @eldair made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1662
|
||||||
|
* @skyler544 made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1665
|
||||||
|
* @yparitcher made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1668
|
||||||
|
* @jonnott made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1672
|
||||||
|
|
||||||
|
**Full Changelog**: https://github.com/barryvdh/laravel-ide-helper/compare/v3.5.4...v3.5.5
|
||||||
|
|
||||||
## v3.5.4 - 2025-01-14
|
## v3.5.4 - 2025-01-14
|
||||||
|
|
||||||
### What's Changed
|
### What's Changed
|
||||||
|
|||||||
+5
-7
@@ -11,7 +11,7 @@
|
|||||||
This package generates helper files that enable your IDE to provide accurate autocompletion.
|
This package generates helper files that enable your IDE to provide accurate autocompletion.
|
||||||
Generation is done based on the files in your project, so they are always up-to-date.
|
Generation is done based on the files in your project, so they are always up-to-date.
|
||||||
|
|
||||||
The 3.x branch supports Laravel 10 and 11. For older version, use the 2.x releases.
|
The 3.x branch supports Laravel 10 and later. For older version, use the 2.x releases.
|
||||||
|
|
||||||
- [Installation](#installation)
|
- [Installation](#installation)
|
||||||
- [Usage](#usage)
|
- [Usage](#usage)
|
||||||
@@ -51,7 +51,7 @@ php artisan ide-helper:models -RW
|
|||||||
|
|
||||||
If you don't want the full _ide_helper.php file, you can run add `--write-eloquent-helper` to the model command to generate small version, which is required for the `@mixin \Eloquent` to be able to add the QueryBuilder methods.
|
If you don't want the full _ide_helper.php file, you can run add `--write-eloquent-helper` to the model command to generate small version, which is required for the `@mixin \Eloquent` to be able to add the QueryBuilder methods.
|
||||||
|
|
||||||
If you don't want to add all the phpdocs to your Models directly, you can use `--nowrite` to create a seperate file. The `--write-mixin` option can be used to only add a `@mixin` to your models, but add the generated phpdocs in a seperate file. This avoids having the results marked as duplicate.
|
If you don't want to add all the phpdocs to your Models directly, you can use `--nowrite` to create a separate file. The `--write-mixin` option can be used to only add a `@mixin` to your models, but add the generated phpdocs in a separate file. This avoids having the results marked as duplicate.
|
||||||
|
|
||||||
|
|
||||||
_Check out [this Laracasts video](https://laracasts.com/series/how-to-be-awesome-in-phpstorm/episodes/15) for a quick introduction/explanation!_
|
_Check out [this Laracasts video](https://laracasts.com/series/how-to-be-awesome-in-phpstorm/episodes/15) for a quick introduction/explanation!_
|
||||||
@@ -115,8 +115,6 @@ Str::macro('concat', function(string $str1, string $str2) : string {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
You can add any custom Macroable traits to detect in the `macroable_traits` config option.
|
|
||||||
|
|
||||||
### Automatic PHPDocs for models
|
### Automatic PHPDocs for models
|
||||||
|
|
||||||
If you don't want to write your properties yourself, you can use the command `php artisan ide-helper:models` to generate
|
If you don't want to write your properties yourself, you can use the command `php artisan ide-helper:models` to generate
|
||||||
@@ -212,11 +210,11 @@ Eloquent allows calling `where<Attribute>` on your models, e.g. `Post::whereTitl
|
|||||||
|
|
||||||
If for some reason it's undesired to have them generated (one for each column), you can disable this via config `write_model_magic_where` and setting it to `false`.
|
If for some reason it's undesired to have them generated (one for each column), you can disable this via config `write_model_magic_where` and setting it to `false`.
|
||||||
|
|
||||||
#### Magic `*_count` properties
|
#### Magic `*_count` and `*_exists` properties
|
||||||
|
|
||||||
You may use the [`::withCount`](https://laravel.com/docs/master/eloquent-relationships#counting-related-models) method to count the number results from a relationship without actually loading them. Those results are then placed in attributes following the `<columname>_count` convention.
|
You may use the [`::withCount`](https://laravel.com/docs/master/eloquent-relationships#counting-related-models) and [`::withExists`](https://laravel.com/docs/master/eloquent-relationships#other-aggregate-functions) methods to count the number results from a relationship without actually loading them. Those results are then placed in attributes following the `<columnname>_count` and `<columnname>_exists` convention.
|
||||||
|
|
||||||
By default, these attributes are generated in the phpdoc. You can turn them off by setting the config `write_model_relation_count_properties` to `false`.
|
By default, these attributes are generated in the phpdoc. You can turn them off by setting the config `write_model_relation_count_properties` and `write_model_relation_exists_properties` to `false`.
|
||||||
|
|
||||||
#### Generics annotations
|
#### Generics annotations
|
||||||
|
|
||||||
|
|||||||
+15
-14
@@ -23,23 +23,24 @@
|
|||||||
"require": {
|
"require": {
|
||||||
"php": "^8.2",
|
"php": "^8.2",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
"barryvdh/reflection-docblock": "^2.3",
|
"barryvdh/reflection-docblock": "^2.4",
|
||||||
"composer/class-map-generator": "^1.0",
|
"composer/class-map-generator": "^1.0",
|
||||||
"illuminate/console": "^11.15 || ^12",
|
"illuminate/console": "^11.15 || ^12 || ^13.0",
|
||||||
"illuminate/database": "^11.15 || ^12",
|
"illuminate/database": "^11.15 || ^12 || ^13.0",
|
||||||
"illuminate/filesystem": "^11.15 || ^12",
|
"illuminate/filesystem": "^11.15 || ^12 || ^13.0",
|
||||||
"illuminate/support": "^11.15 || ^12"
|
"illuminate/support": "^11.15 || ^12 || ^13.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"ext-pdo_sqlite": "*",
|
"ext-pdo_sqlite": "*",
|
||||||
"friendsofphp/php-cs-fixer": "^3",
|
"friendsofphp/php-cs-fixer": "^3",
|
||||||
"illuminate/config": "^11.15 || ^12",
|
"illuminate/config": "^11.15 || ^12 || ^13.0",
|
||||||
"illuminate/view": "^11.15 || ^12",
|
"illuminate/view": "^11.15 || ^12 || ^13.0",
|
||||||
|
"larastan/larastan": "^3.1",
|
||||||
"mockery/mockery": "^1.4",
|
"mockery/mockery": "^1.4",
|
||||||
"orchestra/testbench": "^9.2 || ^10",
|
"orchestra/testbench": "^9.2 || ^10 || ^11.0",
|
||||||
"phpunit/phpunit": "^10.5 || ^11.5.3",
|
"phpstan/phpstan-phpunit": "^2.0",
|
||||||
|
"phpunit/phpunit": "^10.5 || ^11.5.3 || ^12.5.12",
|
||||||
"spatie/phpunit-snapshot-assertions": "^4 || ^5",
|
"spatie/phpunit-snapshot-assertions": "^4 || ^5",
|
||||||
"vimeo/psalm": "^5.4",
|
|
||||||
"vlucas/phpdotenv": "^5"
|
"vlucas/phpdotenv": "^5"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
@@ -65,7 +66,7 @@
|
|||||||
},
|
},
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "3.5-dev"
|
"dev-master": "3.6-dev"
|
||||||
},
|
},
|
||||||
"laravel": {
|
"laravel": {
|
||||||
"providers": [
|
"providers": [
|
||||||
@@ -74,7 +75,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"analyze": "psalm",
|
"analyze": "phpstan",
|
||||||
|
"analyze-set-baseline": "phpstan --generate-baseline",
|
||||||
"check-style": [
|
"check-style": [
|
||||||
"php-cs-fixer fix --diff --diff-format=udiff --dry-run",
|
"php-cs-fixer fix --diff --diff-format=udiff --dry-run",
|
||||||
"php-cs-fixer fix --diff --diff-format=udiff --dry-run --config=.php_cs.tests.php"
|
"php-cs-fixer fix --diff --diff-format=udiff --dry-run --config=.php_cs.tests.php"
|
||||||
@@ -83,9 +85,8 @@
|
|||||||
"php-cs-fixer fix",
|
"php-cs-fixer fix",
|
||||||
"php-cs-fixer fix --config=.php-cs-fixer.tests.php"
|
"php-cs-fixer fix --config=.php-cs-fixer.tests.php"
|
||||||
],
|
],
|
||||||
"psalm-set-baseline": "psalm --set-baseline=psalm-baseline.xml",
|
|
||||||
"test": "phpunit",
|
"test": "phpunit",
|
||||||
"test-ci": "phpunit -d --without-creating-snapshots",
|
"test-ci": "phpunit",
|
||||||
"test-regenerate": "phpunit -d --update-snapshots"
|
"test-regenerate": "phpunit -d --update-snapshots"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+44
-22
@@ -49,17 +49,14 @@ return [
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Factory builders
|
| Write model query methods
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| Set to true to generate factory generators for better factory()
|
| Set to false to disable generated docs for the 'query()', 'newQuery()' and 'newModelQuery()' methods.
|
||||||
| method auto-completion.
|
|
||||||
|
|
|
||||||
| Deprecated for Laravel 8 or latest.
|
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'include_factory_builders' => false,
|
'write_query_methods' => true,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@@ -85,14 +82,16 @@ return [
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Write model relation count properties
|
| Write model relation count and exists properties
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| Set to false to disable writing of relation count properties to model DocBlocks.
|
| Set to false to disable writing of relation count and exists properties
|
||||||
|
| to model DocBlocks.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'write_model_relation_count_properties' => true,
|
'write_model_relation_count_properties' => true,
|
||||||
|
'write_model_relation_exists_properties' => false,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@@ -274,6 +273,20 @@ return [
|
|||||||
*/
|
*/
|
||||||
'use_generics_annotations' => true,
|
'use_generics_annotations' => true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Default return types for macros
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Define default return types for macros without explicit return types.
|
||||||
|
| e.g. `\Illuminate\Database\Query\Builder::class => 'static'`,
|
||||||
|
| `\Illuminate\Support\Str::class => 'string'`
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'macro_default_return_types' => [
|
||||||
|
Illuminate\Http\Client\Factory::class => Illuminate\Http\Client\PendingRequest::class,
|
||||||
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Additional relation types
|
| Additional relation types
|
||||||
@@ -323,6 +336,29 @@ return [
|
|||||||
|
|
||||||
'enforce_nullable_relationships' => true,
|
'enforce_nullable_relationships' => true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Make soft deletable relations nullable
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When set to true (default), relationships to models using SoftDeletes trait
|
||||||
|
| will be marked as nullable. This is because soft-deleted records are excluded
|
||||||
|
| from queries by default, meaning even non-nullable foreign keys can return
|
||||||
|
| null when the related model is soft-deleted.
|
||||||
|
|
|
||||||
|
| Default: true
|
||||||
|
| A relationship to a soft-deletable model will include |null in the type:
|
||||||
|
| * @property-read Team|null $team
|
||||||
|
|
|
||||||
|
| Option: false
|
||||||
|
| A relationship to a soft-deletable model will NOT include |null (unless
|
||||||
|
| nullable for other reasons such as nullable foreign key column):
|
||||||
|
| * @property-read Team $team
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'soft_deletes_force_nullable' => true,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Run artisan commands after migrations to generate model helpers
|
| Run artisan commands after migrations to generate model helpers
|
||||||
@@ -335,18 +371,4 @@ return [
|
|||||||
// 'ide-helper:models --nowrite',
|
// 'ide-helper:models --nowrite',
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Macroable Traits
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Define which traits should be considered capable of adding Macro.
|
|
||||||
| You can add any custom trait that behaves like the original Laravel one.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
'macroable_traits' => [
|
|
||||||
Filament\Support\Concerns\Macroable::class,
|
|
||||||
Spatie\Macroable\Macroable::class,
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,53 +1,68 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
function vsCodeGetTranslationsFromFile($file, $path, $namespace)
|
function vsCodeGetTranslationsFromFile(Symfony\Component\Finder\SplFileInfo $file, $path, $namespace)
|
||||||
{
|
{
|
||||||
$key = pathinfo($file, PATHINFO_FILENAME);
|
if ($file->getExtension() !== 'php') {
|
||||||
|
return null;
|
||||||
if ($namespace) {
|
|
||||||
$key = "{$namespace}::{$key}";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$lang = collect(explode(DIRECTORY_SEPARATOR, str_replace($path, '', $file)))
|
$filePath = $file->getRealPath();
|
||||||
->filter()
|
|
||||||
->first();
|
|
||||||
|
|
||||||
$fileLines = Illuminate\Support\Facades\File::lines($file);
|
$relativePath = trim(str_replace($path, '', $file->getPath()), DIRECTORY_SEPARATOR);
|
||||||
|
$lang = explode(DIRECTORY_SEPARATOR, $relativePath)[0] ?? null;
|
||||||
|
|
||||||
|
if (!$lang) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$keyPath = str_replace($path . DIRECTORY_SEPARATOR . $lang . DIRECTORY_SEPARATOR, '', $filePath);
|
||||||
|
$keyWithSlashes = str_replace('.php', '', $keyPath);
|
||||||
|
$baseKey = str_replace(DIRECTORY_SEPARATOR, '.', $keyWithSlashes);
|
||||||
|
|
||||||
|
if ($namespace) {
|
||||||
|
$baseKey = "{$namespace}::{$baseKey}";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$translations = require $filePath;
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_array($translations)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileLines = Illuminate\Support\Facades\File::lines($filePath);
|
||||||
$lines = [];
|
$lines = [];
|
||||||
$inComment = false;
|
$inComment = false;
|
||||||
|
|
||||||
foreach ($fileLines as $index => $line) {
|
foreach ($fileLines as $index => $line) {
|
||||||
$trimmed = trim($line);
|
$trimmed = trim($line);
|
||||||
|
if (str_starts_with($trimmed, '/*')) {
|
||||||
if (substr($trimmed, 0, 2) === '/*') {
|
|
||||||
$inComment = true;
|
$inComment = true;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($inComment) {
|
if ($inComment) {
|
||||||
if (substr($trimmed, -2) !== '*/') {
|
if (str_ends_with($trimmed, '*/')) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$inComment = false;
|
$inComment = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (substr($trimmed, 0, 2) === '//') {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (str_starts_with($trimmed, '//')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$lines[] = [$index + 1, $trimmed];
|
$lines[] = [$index + 1, $trimmed];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'k' => $key,
|
'k' => $baseKey,
|
||||||
'la' => $lang,
|
'la' => $lang,
|
||||||
'vs' => collect(Illuminate\Support\Arr::dot((Illuminate\Support\Arr::wrap(__($key, [], $lang)))))
|
'vs' => collect(Illuminate\Support\Arr::dot($translations))
|
||||||
->map(
|
->map(
|
||||||
fn ($value, $key) => vsCodeTranslationValue(
|
fn ($value, $dotKey) => vsCodeTranslationValue(
|
||||||
$key,
|
$dotKey,
|
||||||
$value,
|
$value,
|
||||||
str_replace(base_path(DIRECTORY_SEPARATOR), '', $file),
|
str_replace(base_path(DIRECTORY_SEPARATOR), '', $filePath),
|
||||||
$lines
|
$lines
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -63,13 +78,12 @@ function vsCodeTranslationValue($key, $value, $file, $lines): ?array
|
|||||||
|
|
||||||
$lineNumber = 1;
|
$lineNumber = 1;
|
||||||
$keys = explode('.', $key);
|
$keys = explode('.', $key);
|
||||||
$index = 0;
|
|
||||||
$currentKey = array_shift($keys);
|
$currentKey = array_shift($keys);
|
||||||
|
|
||||||
foreach ($lines as $index => $line) {
|
foreach ($lines as $line) {
|
||||||
if (
|
if (
|
||||||
strpos($line[1], '"' . $currentKey . '"', 0) !== false ||
|
strpos($line[1], '"' . $currentKey . '"') !== false ||
|
||||||
strpos($line[1], "'" . $currentKey . "'", 0) !== false
|
strpos($line[1], "'" . $currentKey . "'") !== false
|
||||||
) {
|
) {
|
||||||
$lineNumber = $line[0];
|
$lineNumber = $line[0];
|
||||||
$currentKey = array_shift($keys);
|
$currentKey = array_shift($keys);
|
||||||
@@ -98,9 +112,9 @@ function vscodeCollectTranslations(string $path, ?string $namespace = null)
|
|||||||
return collect();
|
return collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
return collect(Illuminate\Support\Facades\File::allFiles($realPath))->map(
|
return collect(Illuminate\Support\Facades\File::allFiles($realPath))
|
||||||
fn ($file) => vsCodeGetTranslationsFromFile($file, $path, $namespace)
|
->map(fn ($file) => vsCodeGetTranslationsFromFile($file, $path, $namespace))
|
||||||
);
|
->filter();
|
||||||
}
|
}
|
||||||
|
|
||||||
$loader = app('translator')->getLoader();
|
$loader = app('translator')->getLoader();
|
||||||
@@ -110,7 +124,6 @@ $reflection = new ReflectionClass($loader);
|
|||||||
$property = $reflection->hasProperty('paths')
|
$property = $reflection->hasProperty('paths')
|
||||||
? $reflection->getProperty('paths')
|
? $reflection->getProperty('paths')
|
||||||
: $reflection->getProperty('path');
|
: $reflection->getProperty('path');
|
||||||
$property->setAccessible(true);
|
|
||||||
|
|
||||||
$paths = Illuminate\Support\Arr::wrap($property->getValue($loader));
|
$paths = Illuminate\Support\Arr::wrap($property->getValue($loader));
|
||||||
|
|
||||||
@@ -125,6 +138,10 @@ $namespaced = collect($namespaces)->flatMap(
|
|||||||
$final = [];
|
$final = [];
|
||||||
|
|
||||||
foreach ($default->merge($namespaced) as $value) {
|
foreach ($default->merge($namespaced) as $value) {
|
||||||
|
if (!isset($value['vs']) || !is_iterable($value['vs'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($value['vs'] as $key => $v) {
|
foreach ($value['vs'] as $key => $v) {
|
||||||
$dotKey = "{$value['k']}.{$key}";
|
$dotKey = "{$value['k']}.{$key}";
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
parameters:
|
||||||
|
ignoreErrors:
|
||||||
|
-
|
||||||
|
message: '#^File ends with a trailing whitespace\. This may cause problems when running the code in the web browser\. Remove the closing \?\> mark or remove the whitespace\.$#'
|
||||||
|
identifier: whitespace.fileEnd
|
||||||
|
count: 1
|
||||||
|
path: resources/views/helper.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Call to an undefined method Illuminate\\Contracts\\Filesystem\\Filesystem\:\:requireOnce\(\)\.$#'
|
||||||
|
identifier: method.notFound
|
||||||
|
count: 1
|
||||||
|
path: src/Console/MetaCommand.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Call to an undefined method Illuminate\\Contracts\\Foundation\\Application\:\:getBindings\(\)\.$#'
|
||||||
|
identifier: method.notFound
|
||||||
|
count: 1
|
||||||
|
path: src/Console/MetaCommand.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Call to an undefined method Barryvdh\\Reflection\\DocBlock\\Tag\:\:getMethodName\(\)\.$#'
|
||||||
|
identifier: method.notFound
|
||||||
|
count: 1
|
||||||
|
path: src/Console/ModelsCommand.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Call to an undefined method Barryvdh\\Reflection\\DocBlock\\Tag\:\:getType\(\)\.$#'
|
||||||
|
identifier: method.notFound
|
||||||
|
count: 1
|
||||||
|
path: src/Console/ModelsCommand.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Call to an undefined method Barryvdh\\Reflection\\DocBlock\\Tag\:\:getVariableName\(\)\.$#'
|
||||||
|
identifier: method.notFound
|
||||||
|
count: 1
|
||||||
|
path: src/Console/ModelsCommand.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Call to an undefined method Illuminate\\Support\\Optional\:\:getNumberOfParameters\(\)\.$#'
|
||||||
|
identifier: method.notFound
|
||||||
|
count: 1
|
||||||
|
path: src/Console/ModelsCommand.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Call to an undefined method Illuminate\\Support\\Optional\:\:getParameters\(\)\.$#'
|
||||||
|
identifier: method.notFound
|
||||||
|
count: 1
|
||||||
|
path: src/Console/ModelsCommand.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Call to an undefined method Illuminate\\Support\\Optional\:\:getReturnType\(\)\.$#'
|
||||||
|
identifier: method.notFound
|
||||||
|
count: 1
|
||||||
|
path: src/Console/ModelsCommand.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Call to an undefined static method Illuminate\\Database\\Eloquent\\Model\:\:newFactory\(\)\.$#'
|
||||||
|
identifier: staticMethod.notFound
|
||||||
|
count: 2
|
||||||
|
path: src/Console/ModelsCommand.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Call to an undefined method ReflectionFunctionAbstract\:\:getDeclaringClass\(\)\.$#'
|
||||||
|
identifier: method.notFound
|
||||||
|
count: 1
|
||||||
|
path: src/Method.php
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
includes:
|
||||||
|
- phpstan-baseline.neon
|
||||||
|
- vendor/phpstan/phpstan-phpunit/extension.neon
|
||||||
|
|
||||||
|
parameters:
|
||||||
|
level: 2
|
||||||
|
paths:
|
||||||
|
- src
|
||||||
|
- resources/views
|
||||||
|
excludePaths:
|
||||||
|
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
/**
|
/**
|
||||||
* @var Barryvdh\LaravelIdeHelper\Alias[][] $namespaces_by_alias_ns
|
* @var Barryvdh\LaravelIdeHelper\Alias[][] $namespaces_by_alias_ns
|
||||||
* @var Barryvdh\LaravelIdeHelper\Alias[][] $namespaces_by_extends_ns
|
* @var Barryvdh\LaravelIdeHelper\Alias[][] $namespaces_by_extends_ns
|
||||||
|
* @var string[] $real_time_facades
|
||||||
* @var bool $include_fluent
|
* @var bool $include_fluent
|
||||||
* @var string $helpers
|
* @var string $helpers
|
||||||
*/
|
*/
|
||||||
@@ -29,7 +30,7 @@ $s3 = $s1 . $s2;
|
|||||||
<?php foreach ($namespaces_by_extends_ns as $namespace => $aliases) : ?>
|
<?php foreach ($namespaces_by_extends_ns as $namespace => $aliases) : ?>
|
||||||
namespace <?= $namespace === '__root' ? '' : trim($namespace, '\\') ?> {
|
namespace <?= $namespace === '__root' ? '' : trim($namespace, '\\') ?> {
|
||||||
<?php foreach ($aliases as $alias) : ?>
|
<?php foreach ($aliases as $alias) : ?>
|
||||||
<?php echo trim($alias->getDocComment($s1)) . "\n{$s1}" . $alias->getClassType() ?> <?= $alias->getExtendsClass() ?> {
|
<?php echo trim($alias->getDocComment($s1)) . "\n{$s1}" . $alias->getClassType() ?> <?= $alias->getExtendsClass() ?><?php if ($alias->shouldExtendParentClass()): ?> extends <?= $alias->getParentClass() ?><?php endif; ?> {
|
||||||
<?php foreach ($alias->getMethods() as $method) : ?>
|
<?php foreach ($alias->getMethods() as $method) : ?>
|
||||||
<?= trim($method->getDocComment($s2)) . "\n{$s2}" ?>public static function <?= $method->getName() ?>(<?= $method->getParamsWithDefault() ?>)
|
<?= trim($method->getDocComment($s2)) . "\n{$s2}" ?>public static function <?= $method->getName() ?>(<?= $method->getParamsWithDefault() ?>)
|
||||||
{<?php if ($method->getDeclaringClass() !== $method->getRoot()) : ?>
|
{<?php if ($method->getDeclaringClass() !== $method->getRoot()) : ?>
|
||||||
@@ -76,7 +77,7 @@ namespace <?= $namespace === '__root' ? '' : trim($namespace, '\\') ?> {
|
|||||||
|
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
|
||||||
<?php foreach($real_time_facades as $name): ?>
|
<?php foreach ($real_time_facades as $name): ?>
|
||||||
<?php $nested = explode('\\', str_replace('\\' . class_basename($name), '', $name)); ?>
|
<?php $nested = explode('\\', str_replace('\\' . class_basename($name), '', $name)); ?>
|
||||||
namespace <?php echo implode('\\', $nested); ?> {
|
namespace <?php echo implode('\\', $nested); ?> {
|
||||||
/**
|
/**
|
||||||
@@ -119,12 +120,3 @@ namespace Illuminate\Support {
|
|||||||
}
|
}
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
<?php foreach ($factories as $factory) : ?>
|
|
||||||
namespace <?=$factory->getNamespaceName()?> {
|
|
||||||
/**
|
|
||||||
* @method \Illuminate\Database\Eloquent\Collection|<?=$factory->getShortName()?>[]|<?=$factory->getShortName()?> create($attributes = [])
|
|
||||||
* @method \Illuminate\Database\Eloquent\Collection|<?=$factory->getShortName()?>[]|<?=$factory->getShortName()?> make($attributes = [])
|
|
||||||
*/
|
|
||||||
class <?=$factory->getShortName()?>FactoryBuilder extends \Illuminate\Database\Eloquent\FactoryBuilder {}
|
|
||||||
}
|
|
||||||
<?php endforeach; ?>
|
|
||||||
|
|||||||
+15
-12
@@ -1,4 +1,16 @@
|
|||||||
<?= '<?php' ?>
|
<?= '<?php' ?>
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @var array $bindings
|
||||||
|
* @var string[] $methods
|
||||||
|
* @var string[] $configMethods
|
||||||
|
* @var Illuminate\Support\Collection $configValues
|
||||||
|
* @var array<string, array> $expectedArgumentSets
|
||||||
|
* @var array $expectedArguments
|
||||||
|
* @var string[] $userMethods
|
||||||
|
* @var string $userModel/
|
||||||
|
* */
|
||||||
|
?>
|
||||||
|
|
||||||
/* @noinspection ALL */
|
/* @noinspection ALL */
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
@@ -35,15 +47,6 @@ namespace PHPSTORM_META {
|
|||||||
]));
|
]));
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
|
||||||
<?php if (count($factories)) : ?>
|
|
||||||
override(\factory(0), map([
|
|
||||||
'' => '@FactoryBuilder',
|
|
||||||
<?php foreach ($factories as $factory) : ?>
|
|
||||||
'<?= $factory->getName() ?>' => \<?= $factory->getName() ?>FactoryBuilder::class,
|
|
||||||
<?php endforeach; ?>
|
|
||||||
]));
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
override(\Illuminate\Foundation\Testing\Concerns\InteractsWithContainer::mock(0), map(["" => "@&\Mockery\MockInterface"]));
|
override(\Illuminate\Foundation\Testing\Concerns\InteractsWithContainer::mock(0), map(["" => "@&\Mockery\MockInterface"]));
|
||||||
override(\Illuminate\Foundation\Testing\Concerns\InteractsWithContainer::partialMock(0), map(["" => "@&\Mockery\MockInterface"]));
|
override(\Illuminate\Foundation\Testing\Concerns\InteractsWithContainer::partialMock(0), map(["" => "@&\Mockery\MockInterface"]));
|
||||||
override(\Illuminate\Foundation\Testing\Concerns\InteractsWithContainer::instance(0), type(1));
|
override(\Illuminate\Foundation\Testing\Concerns\InteractsWithContainer::instance(0), type(1));
|
||||||
@@ -79,15 +82,15 @@ namespace PHPSTORM_META {
|
|||||||
override(\tap(0), type(0));
|
override(\tap(0), type(0));
|
||||||
override(\optional(0), type(0));
|
override(\optional(0), type(0));
|
||||||
|
|
||||||
<?php if (isset($expectedArgumentSets)): ?>
|
<?php if ($expectedArgumentSets): ?>
|
||||||
<?php foreach ($expectedArgumentSets as $name => $argumentsList) : ?>
|
<?php foreach ($expectedArgumentSets as $name => $argumentsList) : ?>
|
||||||
registerArgumentsSet('<?= $name ?>', <?php foreach ($argumentsList as $i => $arg) : ?><?php if($i % 5 == 0) {
|
registerArgumentsSet('<?= $name ?>', <?php foreach ($argumentsList as $i => $arg) : ?><?php if ($i % 5 == 0) {
|
||||||
echo "\n";
|
echo "\n";
|
||||||
} ?><?= var_export($arg, true); ?>,<?php endforeach; ?>);
|
} ?><?= var_export($arg, true); ?>,<?php endforeach; ?>);
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
<?php if (isset($expectedArguments)) : ?>
|
<?php if ($expectedArguments) : ?>
|
||||||
<?php foreach ($expectedArguments as $arguments) : ?>
|
<?php foreach ($expectedArguments as $arguments) : ?>
|
||||||
<?php
|
<?php
|
||||||
$classes = isset($arguments['class']) ? (array) $arguments['class'] : [null];
|
$classes = isset($arguments['class']) ? (array) $arguments['class'] : [null];
|
||||||
|
|||||||
+55
-22
@@ -22,6 +22,7 @@ use Illuminate\Config\Repository as ConfigRepository;
|
|||||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||||
use Illuminate\Database\Query\Builder as QueryBuilder;
|
use Illuminate\Database\Query\Builder as QueryBuilder;
|
||||||
use Illuminate\Support\Facades\Facade;
|
use Illuminate\Support\Facades\Facade;
|
||||||
|
use Illuminate\Support\Traits\Macroable;
|
||||||
use ReflectionClass;
|
use ReflectionClass;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
@@ -36,6 +37,7 @@ class Alias
|
|||||||
protected $classType = 'class';
|
protected $classType = 'class';
|
||||||
protected $short;
|
protected $short;
|
||||||
protected $namespace = '__root';
|
protected $namespace = '__root';
|
||||||
|
protected $parentClass;
|
||||||
protected $root = null;
|
protected $root = null;
|
||||||
protected $classes = [];
|
protected $classes = [];
|
||||||
protected $methods = [];
|
protected $methods = [];
|
||||||
@@ -46,8 +48,6 @@ class Alias
|
|||||||
protected $phpdoc = null;
|
protected $phpdoc = null;
|
||||||
protected $classAliases = [];
|
protected $classAliases = [];
|
||||||
|
|
||||||
protected $isMacroable = false;
|
|
||||||
|
|
||||||
/** @var ConfigRepository */
|
/** @var ConfigRepository */
|
||||||
protected $config;
|
protected $config;
|
||||||
|
|
||||||
@@ -62,13 +62,12 @@ class Alias
|
|||||||
* @param array $magicMethods
|
* @param array $magicMethods
|
||||||
* @param array $interfaces
|
* @param array $interfaces
|
||||||
*/
|
*/
|
||||||
public function __construct($config, $alias, $facade, $magicMethods = [], $interfaces = [], $isMacroable = false)
|
public function __construct($config, $alias, $facade, $magicMethods = [], $interfaces = [])
|
||||||
{
|
{
|
||||||
$this->alias = $alias;
|
$this->alias = $alias;
|
||||||
$this->magicMethods = $magicMethods;
|
$this->magicMethods = $magicMethods;
|
||||||
$this->interfaces = $interfaces;
|
$this->interfaces = $interfaces;
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
$this->isMacroable = $isMacroable;
|
|
||||||
|
|
||||||
// Make the class absolute
|
// Make the class absolute
|
||||||
$facade = '\\' . ltrim($facade, '\\');
|
$facade = '\\' . ltrim($facade, '\\');
|
||||||
@@ -87,6 +86,7 @@ class Alias
|
|||||||
$this->detectNamespace();
|
$this->detectNamespace();
|
||||||
$this->detectClassType();
|
$this->detectClassType();
|
||||||
$this->detectExtendsNamespace();
|
$this->detectExtendsNamespace();
|
||||||
|
$this->detectParentClass();
|
||||||
|
|
||||||
if (!empty($this->namespace)) {
|
if (!empty($this->namespace)) {
|
||||||
try {
|
try {
|
||||||
@@ -101,7 +101,7 @@ class Alias
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($facade === '\Illuminate\Database\Eloquent\Model') {
|
if ($facade === '\Illuminate\Database\Eloquent\Model') {
|
||||||
$this->usedMethods = ['decrement', 'increment'];
|
$this->usedMethods = ['decrement' => true, 'increment' => true];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,6 +171,25 @@ class Alias
|
|||||||
return $this->extendsNamespace;
|
return $this->extendsNamespace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the parent class of the class which this alias extends
|
||||||
|
*
|
||||||
|
* @return null|string
|
||||||
|
*/
|
||||||
|
public function getParentClass()
|
||||||
|
{
|
||||||
|
return $this->parentClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this class should extend the parent class
|
||||||
|
*/
|
||||||
|
public function shouldExtendParentClass()
|
||||||
|
{
|
||||||
|
return $this->parentClass
|
||||||
|
&& !is_subclass_of($this->extends, Facade::class);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the Alias by which this class is called
|
* Get the Alias by which this class is called
|
||||||
*
|
*
|
||||||
@@ -229,11 +248,17 @@ class Alias
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$reflection = new \ReflectionMethod($facade, 'fake');
|
||||||
|
if ($reflection->getNumberOfRequiredParameters() > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$real = $facade::getFacadeRoot();
|
$real = $facade::getFacadeRoot();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$facade::fake();
|
$facade::fake();
|
||||||
$fake = $facade::getFacadeRoot();
|
$fake = $facade::getFacadeRoot();
|
||||||
|
|
||||||
if ($fake !== $real) {
|
if ($fake !== $real) {
|
||||||
$this->addClass(get_class($fake));
|
$this->addClass(get_class($fake));
|
||||||
}
|
}
|
||||||
@@ -268,6 +293,18 @@ class Alias
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect the parent class
|
||||||
|
*/
|
||||||
|
protected function detectParentClass()
|
||||||
|
{
|
||||||
|
$reflection = new ReflectionClass($this->root);
|
||||||
|
|
||||||
|
$parentClass = $reflection->getParentClass();
|
||||||
|
|
||||||
|
$this->parentClass = $parentClass ? '\\' . $parentClass->getName() : null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detect the class type
|
* Detect the class type
|
||||||
*/
|
*/
|
||||||
@@ -288,9 +325,8 @@ class Alias
|
|||||||
/**
|
/**
|
||||||
* Get the real root of a facade
|
* Get the real root of a facade
|
||||||
*
|
*
|
||||||
* @return bool|string
|
|
||||||
*/
|
*/
|
||||||
protected function detectRoot()
|
protected function detectRoot(): void
|
||||||
{
|
{
|
||||||
$facade = $this->facade;
|
$facade = $this->facade;
|
||||||
|
|
||||||
@@ -345,11 +381,10 @@ class Alias
|
|||||||
$method = new \ReflectionMethod($className, $name);
|
$method = new \ReflectionMethod($className, $name);
|
||||||
$class = new ReflectionClass($className);
|
$class = new ReflectionClass($className);
|
||||||
|
|
||||||
if (!in_array($magic, $this->usedMethods)) {
|
if (!isset($this->usedMethods[$magic])) {
|
||||||
if ($class !== $this->root) {
|
if ($class !== $this->root) {
|
||||||
$this->methods[] = new Method(
|
$this->methods[] = new Method(
|
||||||
$method,
|
$method,
|
||||||
$this->alias,
|
|
||||||
$class,
|
$class,
|
||||||
$magic,
|
$magic,
|
||||||
$this->interfaces,
|
$this->interfaces,
|
||||||
@@ -358,7 +393,7 @@ class Alias
|
|||||||
$this->getTemplateNames()
|
$this->getTemplateNames()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
$this->usedMethods[] = $magic;
|
$this->usedMethods[$magic] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -366,7 +401,6 @@ class Alias
|
|||||||
/**
|
/**
|
||||||
* Get the methods for one or multiple classes.
|
* Get the methods for one or multiple classes.
|
||||||
*
|
*
|
||||||
* @return string
|
|
||||||
*/
|
*/
|
||||||
protected function detectMethods()
|
protected function detectMethods()
|
||||||
{
|
{
|
||||||
@@ -376,13 +410,12 @@ class Alias
|
|||||||
$methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC);
|
$methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC);
|
||||||
if ($methods) {
|
if ($methods) {
|
||||||
foreach ($methods as $method) {
|
foreach ($methods as $method) {
|
||||||
if (!in_array($method->name, $this->usedMethods)) {
|
if (!isset($this->usedMethods[$method->name])) {
|
||||||
// Only add the methods to the output when the root is not the same as the class.
|
// Only add the methods to the output when the root is not the same as the class.
|
||||||
// And don't add the __*() methods
|
// And don't add the __*() methods
|
||||||
if ($this->extends !== $class && substr($method->name, 0, 2) !== '__') {
|
if ($this->extends !== $class && substr($method->name, 0, 2) !== '__') {
|
||||||
$this->methods[] = new Method(
|
$this->methods[] = new Method(
|
||||||
$method,
|
$method,
|
||||||
$this->alias,
|
|
||||||
$reflection,
|
$reflection,
|
||||||
$method->name,
|
$method->name,
|
||||||
$this->interfaces,
|
$this->interfaces,
|
||||||
@@ -391,18 +424,18 @@ class Alias
|
|||||||
$this->getTemplateNames(),
|
$this->getTemplateNames(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
$this->usedMethods[] = $method->name;
|
$this->usedMethods[$method->name] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the class is macroable
|
// Check if the class is macroable
|
||||||
// (Eloquent\Builder is also macroable but doesn't use Macroable trait)
|
// (Eloquent\Builder is also macroable but doesn't use Macroable trait)
|
||||||
if ($this->isMacroable || $class === EloquentBuilder::class) {
|
if ($class === EloquentBuilder::class || in_array(Macroable::class, $reflection->getTraitNames())) {
|
||||||
$properties = $reflection->getStaticProperties();
|
$properties = $reflection->getStaticProperties();
|
||||||
$macros = isset($properties['macros']) ? $properties['macros'] : [];
|
$macros = isset($properties['macros']) ? $properties['macros'] : [];
|
||||||
foreach ($macros as $macro_name => $macro_func) {
|
foreach ($macros as $macro_name => $macro_func) {
|
||||||
if (!in_array($macro_name, $this->usedMethods)) {
|
if (!isset($this->usedMethods[$macro_name])) {
|
||||||
try {
|
try {
|
||||||
$method = $this->getMacroFunction($macro_func);
|
$method = $this->getMacroFunction($macro_func);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
@@ -412,14 +445,13 @@ class Alias
|
|||||||
// Add macros
|
// Add macros
|
||||||
$this->methods[] = new Macro(
|
$this->methods[] = new Macro(
|
||||||
$method,
|
$method,
|
||||||
$this->alias,
|
|
||||||
$reflection,
|
$reflection,
|
||||||
$macro_name,
|
$macro_name,
|
||||||
$this->interfaces,
|
$this->interfaces,
|
||||||
$this->classAliases,
|
$this->classAliases,
|
||||||
$this->getReturnTypeNormalizers($reflection)
|
$this->getReturnTypeNormalizers($reflection)
|
||||||
);
|
);
|
||||||
$this->usedMethods[] = $macro_name;
|
$this->usedMethods[$macro_name] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -557,12 +589,13 @@ class Alias
|
|||||||
*/
|
*/
|
||||||
protected function removeDuplicateMethodsFromPhpDoc()
|
protected function removeDuplicateMethodsFromPhpDoc()
|
||||||
{
|
{
|
||||||
$methodNames = array_map(function (Method $method) {
|
$methodNames = [];
|
||||||
return $method->getName();
|
foreach ($this->getMethods() as $method) {
|
||||||
}, $this->getMethods());
|
$methodNames[$method->getName()] = true;
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($this->phpdoc->getTags() as $tag) {
|
foreach ($this->phpdoc->getTags() as $tag) {
|
||||||
if ($tag instanceof MethodTag && in_array($tag->getMethodName(), $methodNames)) {
|
if ($tag instanceof MethodTag && isset($methodNames[$tag->getMethodName()])) {
|
||||||
$this->phpdoc->deleteTag($tag);
|
$this->phpdoc->deleteTag($tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
namespace Barryvdh\LaravelIdeHelper\Console;
|
namespace Barryvdh\LaravelIdeHelper\Console;
|
||||||
|
|
||||||
use Barryvdh\LaravelIdeHelper\Factories;
|
|
||||||
use Dotenv\Parser\Entry;
|
use Dotenv\Parser\Entry;
|
||||||
use Dotenv\Parser\Parser;
|
use Dotenv\Parser\Parser;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
@@ -108,9 +107,6 @@ class MetaCommand extends Command
|
|||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
// Needs to run before exception handler is registered
|
|
||||||
$factories = $this->config->get('ide-helper.include_factory_builders') ? Factories::all() : [];
|
|
||||||
|
|
||||||
$ourAutoloader = $this->registerClassAutoloadExceptions();
|
$ourAutoloader = $this->registerClassAutoloadExceptions();
|
||||||
|
|
||||||
$bindings = [];
|
$bindings = [];
|
||||||
@@ -149,7 +145,6 @@ class MetaCommand extends Command
|
|||||||
$content = $this->view->make('ide-helper::meta', [
|
$content = $this->view->make('ide-helper::meta', [
|
||||||
'bindings' => $bindings,
|
'bindings' => $bindings,
|
||||||
'methods' => $this->methods,
|
'methods' => $this->methods,
|
||||||
'factories' => $factories,
|
|
||||||
'configMethods' => $this->configMethods,
|
'configMethods' => $this->configMethods,
|
||||||
'configValues' => $configValues,
|
'configValues' => $configValues,
|
||||||
'expectedArgumentSets' => $this->getExpectedArgumentSets(),
|
'expectedArgumentSets' => $this->getExpectedArgumentSets(),
|
||||||
@@ -192,10 +187,31 @@ class MetaCommand extends Command
|
|||||||
*/
|
*/
|
||||||
protected function registerClassAutoloadExceptions(): callable
|
protected function registerClassAutoloadExceptions(): callable
|
||||||
{
|
{
|
||||||
$autoloader = function ($class) {
|
$aliases = array_filter([...$this->getAbstracts(), 'config'], fn ($abstract) => !str_contains($abstract, '\\'));
|
||||||
|
|
||||||
|
$autoloader = function ($class) use ($aliases) {
|
||||||
|
// ignore aliases as they're meant to be resolved elsewhere
|
||||||
|
if (in_array($class, $aliases, true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't throw when class existence is being checked via class_exists(),
|
||||||
|
// interface_exists(), trait_exists(), or enum_exists(). These functions
|
||||||
|
// expect the autoloader to return gracefully when the class doesn't exist.
|
||||||
|
// Throwing here would break libraries that use class_exists() to check for
|
||||||
|
// optional dependencies (e.g. Doctrine ORM checking for removed classes).
|
||||||
|
$existsFunctions = ['class_exists', 'interface_exists', 'trait_exists', 'enum_exists'];
|
||||||
|
foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3) as $frame) {
|
||||||
|
if (isset($frame['function']) && in_array($frame['function'], $existsFunctions, true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
throw new \ReflectionException("Class '$class' not found.");
|
throw new \ReflectionException("Class '$class' not found.");
|
||||||
};
|
};
|
||||||
|
|
||||||
spl_autoload_register($autoloader);
|
spl_autoload_register($autoloader);
|
||||||
|
|
||||||
return $autoloader;
|
return $autoloader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+173
-45
@@ -114,6 +114,7 @@ class ModelsCommand extends Command
|
|||||||
|
|
||||||
protected $write_model_magic_where;
|
protected $write_model_magic_where;
|
||||||
protected $write_model_relation_count_properties;
|
protected $write_model_relation_count_properties;
|
||||||
|
protected $write_model_relation_exists_properties;
|
||||||
protected $properties = [];
|
protected $properties = [];
|
||||||
protected $methods = [];
|
protected $methods = [];
|
||||||
protected $write = false;
|
protected $write = false;
|
||||||
@@ -122,6 +123,14 @@ class ModelsCommand extends Command
|
|||||||
protected $reset;
|
protected $reset;
|
||||||
protected $phpstorm_noinspections;
|
protected $phpstorm_noinspections;
|
||||||
protected $write_model_external_builder_methods;
|
protected $write_model_external_builder_methods;
|
||||||
|
/**
|
||||||
|
* @var array<string, \SplFileObject>
|
||||||
|
*/
|
||||||
|
protected $fileCache = [];
|
||||||
|
/**
|
||||||
|
* @var array<string, Context>
|
||||||
|
*/
|
||||||
|
protected $contextCache = [];
|
||||||
/**
|
/**
|
||||||
* @var array<string, true>
|
* @var array<string, true>
|
||||||
*/
|
*/
|
||||||
@@ -173,6 +182,8 @@ class ModelsCommand extends Command
|
|||||||
$this->write_model_external_builder_methods = $this->laravel['config']->get('ide-helper.write_model_external_builder_methods', true);
|
$this->write_model_external_builder_methods = $this->laravel['config']->get('ide-helper.write_model_external_builder_methods', true);
|
||||||
$this->write_model_relation_count_properties =
|
$this->write_model_relation_count_properties =
|
||||||
$this->laravel['config']->get('ide-helper.write_model_relation_count_properties', true);
|
$this->laravel['config']->get('ide-helper.write_model_relation_count_properties', true);
|
||||||
|
$this->write_model_relation_exists_properties =
|
||||||
|
$this->laravel['config']->get('ide-helper.write_model_relation_exists_properties', false);
|
||||||
|
|
||||||
$this->write = $this->write_mixin ? true : $this->write;
|
$this->write = $this->write_mixin ? true : $this->write;
|
||||||
//If filename is default and Write is not specified, ask what to do
|
//If filename is default and Write is not specified, ask what to do
|
||||||
@@ -410,9 +421,6 @@ class ModelsCommand extends Command
|
|||||||
$params = [];
|
$params = [];
|
||||||
|
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
case 'encrypted':
|
|
||||||
$realType = 'mixed';
|
|
||||||
break;
|
|
||||||
case 'boolean':
|
case 'boolean':
|
||||||
case 'bool':
|
case 'bool':
|
||||||
$realType = 'bool';
|
$realType = 'bool';
|
||||||
@@ -420,6 +428,7 @@ class ModelsCommand extends Command
|
|||||||
case 'decimal':
|
case 'decimal':
|
||||||
$realType = 'numeric';
|
$realType = 'numeric';
|
||||||
break;
|
break;
|
||||||
|
case 'encrypted':
|
||||||
case 'string':
|
case 'string':
|
||||||
case 'hashed':
|
case 'hashed':
|
||||||
$realType = 'string';
|
$realType = 'string';
|
||||||
@@ -453,7 +462,7 @@ class ModelsCommand extends Command
|
|||||||
$realType = '\Illuminate\Support\Collection<array-key, mixed>';
|
$realType = '\Illuminate\Support\Collection<array-key, mixed>';
|
||||||
break;
|
break;
|
||||||
case AsArrayObject::class:
|
case AsArrayObject::class:
|
||||||
$realType = '\ArrayObject<array-key, mixed>';
|
$realType = '\Illuminate\Database\Eloquent\Casts\ArrayObject<array-key, mixed>';
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// In case of an optional custom cast parameter , only evaluate
|
// In case of an optional custom cast parameter , only evaluate
|
||||||
@@ -475,7 +484,11 @@ class ModelsCommand extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Str::startsWith($type, AsCollection::class)) {
|
if (Str::startsWith($type, AsCollection::class)) {
|
||||||
$realType = $this->getTypeInModel($model, $params[0] ?? null) ?? '\Illuminate\Support\Collection';
|
$realType = $this->getTypeInModel($model, $params[0] ?? null) ?: '\Illuminate\Support\Collection';
|
||||||
|
$relatedModel = $this->getTypeInModel($model, $params[1] ?? null);
|
||||||
|
if ($relatedModel) {
|
||||||
|
$realType = $this->getCollectionTypeHint($realType, $relatedModel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Str::startsWith($type, AsEnumCollection::class)) {
|
if (Str::startsWith($type, AsEnumCollection::class)) {
|
||||||
@@ -521,7 +534,7 @@ class ModelsCommand extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($isNullable) {
|
if ($isNullable) {
|
||||||
$type .= '|null';
|
$type = $this->wrapIntersectionType($type) . '|null';
|
||||||
} else {
|
} else {
|
||||||
$type = str_replace($nullString, '', $type);
|
$type = str_replace($nullString, '', $type);
|
||||||
}
|
}
|
||||||
@@ -529,6 +542,22 @@ class ModelsCommand extends Command
|
|||||||
return $type;
|
return $type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps a bare intersection type in parentheses for correct DNF syntax.
|
||||||
|
*
|
||||||
|
* For example, `A&B` becomes `(A&B)` so that adding `|null` produces
|
||||||
|
* `(A&B)|null` instead of the ambiguous `A&B|null`.
|
||||||
|
* Types that are already parenthesized or contain union types are returned as-is.
|
||||||
|
*/
|
||||||
|
protected function wrapIntersectionType(string $type): string
|
||||||
|
{
|
||||||
|
if (str_contains($type, '&') && !str_contains($type, '|') && $type[0] !== '(') {
|
||||||
|
return '(' . $type . ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $type;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the override type for the give type.
|
* Returns the override type for the give type.
|
||||||
*
|
*
|
||||||
@@ -579,6 +608,8 @@ class ModelsCommand extends Command
|
|||||||
'float', 'real', 'float4',
|
'float', 'real', 'float4',
|
||||||
'double', 'float8' => 'float',
|
'double', 'float8' => 'float',
|
||||||
|
|
||||||
|
'decimal', 'numeric' => 'numeric',
|
||||||
|
|
||||||
default => 'string',
|
default => 'string',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -669,9 +700,11 @@ class ModelsCommand extends Command
|
|||||||
$comment = $this->getCommentFromDocBlock($reflection);
|
$comment = $this->getCommentFromDocBlock($reflection);
|
||||||
$this->setProperty($name, null, null, true, $comment);
|
$this->setProperty($name, null, null, true, $comment);
|
||||||
}
|
}
|
||||||
} elseif (Str::startsWith($method, 'scope') && $method !== 'scopeQuery' && $method !== 'scope' && $method !== 'scopes') {
|
} elseif (!empty($reflection->getAttributes('Illuminate\Database\Eloquent\Attributes\Scope')) || (Str::startsWith($method, 'scope') && $method !== 'scopeQuery' && $method !== 'scope' && $method !== 'scopes')) {
|
||||||
|
$scopeUsingAttribute = !empty($reflection->getAttributes('Illuminate\Database\Eloquent\Attributes\Scope'));
|
||||||
|
|
||||||
//Magic scope<name>Attribute
|
//Magic scope<name>Attribute
|
||||||
$name = Str::camel(substr($method, 5));
|
$name = $scopeUsingAttribute ? $method : Str::camel(substr($method, 5));
|
||||||
if (!empty($name)) {
|
if (!empty($name)) {
|
||||||
$comment = $this->getCommentFromDocBlock($reflection);
|
$comment = $this->getCommentFromDocBlock($reflection);
|
||||||
$args = $this->getParameters($reflection);
|
$args = $this->getParameters($reflection);
|
||||||
@@ -687,13 +720,16 @@ class ModelsCommand extends Command
|
|||||||
);
|
);
|
||||||
$this->setMethod($name, $builder . '<static>|' . $modelName, $args, $comment);
|
$this->setMethod($name, $builder . '<static>|' . $modelName, $args, $comment);
|
||||||
}
|
}
|
||||||
} elseif (in_array($method, ['query', 'newQuery', 'newModelQuery'])) {
|
} elseif (in_array($method, ['query', 'newQuery', 'newModelQuery'])
|
||||||
|
) {
|
||||||
|
if ($this->laravel['config']->get('ide-helper.write_query_methods', true)) {
|
||||||
$builder = $this->getClassNameInDestinationFile($model, get_class($model->newModelQuery()));
|
$builder = $this->getClassNameInDestinationFile($model, get_class($model->newModelQuery()));
|
||||||
|
|
||||||
$this->setMethod(
|
$this->setMethod(
|
||||||
$method,
|
$method,
|
||||||
$builder . '<static>|' . $this->getClassNameInDestinationFile($model, get_class($model))
|
$builder . '<static>|' . $this->getClassNameInDestinationFile($model, get_class($model))
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->write_model_external_builder_methods) {
|
if ($this->write_model_external_builder_methods) {
|
||||||
$this->writeModelExternalBuilderMethods($model);
|
$this->writeModelExternalBuilderMethods($model);
|
||||||
@@ -712,14 +748,19 @@ class ModelsCommand extends Command
|
|||||||
$type = (string)$this->getReturnTypeFromDocBlock($reflection);
|
$type = (string)$this->getReturnTypeFromDocBlock($reflection);
|
||||||
}
|
}
|
||||||
|
|
||||||
$file = new \SplFileObject($reflection->getFileName());
|
$fileName = $reflection->getFileName();
|
||||||
|
if (!isset($this->fileCache[$fileName])) {
|
||||||
|
$this->fileCache[$fileName] = new \SplFileObject($fileName);
|
||||||
|
}
|
||||||
|
$file = $this->fileCache[$fileName];
|
||||||
$file->seek($reflection->getStartLine() - 1);
|
$file->seek($reflection->getStartLine() - 1);
|
||||||
|
|
||||||
$code = '';
|
$lines = [];
|
||||||
while ($file->key() < $reflection->getEndLine()) {
|
while ($file->key() < $reflection->getEndLine()) {
|
||||||
$code .= $file->current();
|
$lines[] = $file->current();
|
||||||
$file->next();
|
$file->next();
|
||||||
}
|
}
|
||||||
|
$code = implode('', $lines);
|
||||||
$code = trim(preg_replace('/\s\s+/', '', $code));
|
$code = trim(preg_replace('/\s\s+/', '', $code));
|
||||||
$begin = strpos($code, 'function(');
|
$begin = strpos($code, 'function(');
|
||||||
$code = substr($code, $begin, strrpos($code, '}') - $begin + 1);
|
$code = substr($code, $begin, strrpos($code, '}') - $begin + 1);
|
||||||
@@ -816,6 +857,15 @@ class ModelsCommand extends Command
|
|||||||
// What kind of comments should be added to the relation count here?
|
// What kind of comments should be added to the relation count here?
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if ($this->write_model_relation_exists_properties) {
|
||||||
|
$this->setProperty(
|
||||||
|
Str::snake($method) . '_exists',
|
||||||
|
'bool|null',
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
// What kind of comments should be added to the relation count here?
|
||||||
|
);
|
||||||
|
}
|
||||||
} elseif (
|
} elseif (
|
||||||
$relationReturnType === 'morphTo' ||
|
$relationReturnType === 'morphTo' ||
|
||||||
(
|
(
|
||||||
@@ -871,7 +921,6 @@ class ModelsCommand extends Command
|
|||||||
|
|
||||||
if (in_array($relation, ['hasOne', 'hasOneThrough', 'morphOne'], true)) {
|
if (in_array($relation, ['hasOne', 'hasOneThrough', 'morphOne'], true)) {
|
||||||
$defaultProp = $reflectionObj->getProperty('withDefault');
|
$defaultProp = $reflectionObj->getProperty('withDefault');
|
||||||
$defaultProp->setAccessible(true);
|
|
||||||
|
|
||||||
return !$defaultProp->getValue($relationObj);
|
return !$defaultProp->getValue($relationObj);
|
||||||
}
|
}
|
||||||
@@ -881,7 +930,6 @@ class ModelsCommand extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
$fkProp = $reflectionObj->getProperty('foreignKey');
|
$fkProp = $reflectionObj->getProperty('foreignKey');
|
||||||
$fkProp->setAccessible(true);
|
|
||||||
|
|
||||||
$enforceNullableRelation = $this->laravel['config']->get('ide-helper.enforce_nullable_relationships', true);
|
$enforceNullableRelation = $this->laravel['config']->get('ide-helper.enforce_nullable_relationships', true);
|
||||||
|
|
||||||
@@ -895,9 +943,31 @@ class ModelsCommand extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->relatedModelUsesSoftDeletes($relationObj)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the related model uses the SoftDeletes trait
|
||||||
|
*
|
||||||
|
* @param Relation $relationObj
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function relatedModelUsesSoftDeletes(Relation $relationObj): bool
|
||||||
|
{
|
||||||
|
if (!$this->laravel['config']->get('ide-helper.soft_deletes_force_nullable', true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$relatedModel = $relationObj->getRelated();
|
||||||
|
|
||||||
|
return in_array('Illuminate\\Database\\Eloquent\\SoftDeletes', class_uses_recursive($relatedModel));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the morphTo relation is nullable
|
* Check if the morphTo relation is nullable
|
||||||
*
|
*
|
||||||
@@ -914,7 +984,6 @@ class ModelsCommand extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
$fkProp = $reflectionObj->getProperty('foreignKey');
|
$fkProp = $reflectionObj->getProperty('foreignKey');
|
||||||
$fkProp->setAccessible(true);
|
|
||||||
|
|
||||||
foreach (Arr::wrap($fkProp->getValue($relationObj)) as $foreignKey) {
|
foreach (Arr::wrap($fkProp->getValue($relationObj)) as $foreignKey) {
|
||||||
if (isset($this->nullableColumns[$foreignKey])) {
|
if (isset($this->nullableColumns[$foreignKey])) {
|
||||||
@@ -945,7 +1014,7 @@ class ModelsCommand extends Command
|
|||||||
if ($type !== null) {
|
if ($type !== null) {
|
||||||
$newType = $this->getTypeOverride($type);
|
$newType = $this->getTypeOverride($type);
|
||||||
if ($nullable) {
|
if ($nullable) {
|
||||||
$newType .= '|null';
|
$newType = $this->wrapIntersectionType($newType) . '|null';
|
||||||
}
|
}
|
||||||
$this->properties[$name]['type'] = $newType;
|
$this->properties[$name]['type'] = $newType;
|
||||||
}
|
}
|
||||||
@@ -1096,6 +1165,7 @@ class ModelsCommand extends Command
|
|||||||
|
|
||||||
$serializer = new DocBlockSerializer();
|
$serializer = new DocBlockSerializer();
|
||||||
$docComment = $serializer->getDocComment($phpdoc);
|
$docComment = $serializer->getDocComment($phpdoc);
|
||||||
|
$mixinClassName = null;
|
||||||
|
|
||||||
if ($this->write_mixin) {
|
if ($this->write_mixin) {
|
||||||
$phpdocMixin = new DocBlock($reflection, new Context($namespace));
|
$phpdocMixin = new DocBlock($reflection, new Context($namespace));
|
||||||
@@ -1132,6 +1202,14 @@ class ModelsCommand extends Command
|
|||||||
$replace = "{$modelDocComment}\n";
|
$replace = "{$modelDocComment}\n";
|
||||||
$pos = strpos($contents, "final class {$classname}") ?: strpos($contents, "class {$classname}");
|
$pos = strpos($contents, "final class {$classname}") ?: strpos($contents, "class {$classname}");
|
||||||
if ($pos !== false) {
|
if ($pos !== false) {
|
||||||
|
// If PHP 8 attributes (e.g. #[ObservedBy(...)]) precede the class
|
||||||
|
// declaration, insert the docblock before the first attribute so that
|
||||||
|
// the resulting order is: docblock → attributes → class.
|
||||||
|
$before = substr($contents, 0, $pos);
|
||||||
|
if (preg_match('/((?:#\[.+?\]\s*)+)$/s', $before, $matches)) {
|
||||||
|
$pos -= strlen($matches[1]);
|
||||||
|
$replace = "{$modelDocComment}\n";
|
||||||
|
}
|
||||||
$contents = substr_replace($contents, $replace, $pos, 0);
|
$contents = substr_replace($contents, $replace, $pos, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1184,7 +1262,7 @@ class ModelsCommand extends Command
|
|||||||
$default = '[]';
|
$default = '[]';
|
||||||
} elseif (is_null($default)) {
|
} elseif (is_null($default)) {
|
||||||
$default = 'null';
|
$default = 'null';
|
||||||
} elseif (is_int($default)) {
|
} elseif (is_int($default) || is_float($default)) {
|
||||||
//$default = $default;
|
//$default = $default;
|
||||||
} elseif ($default instanceof \UnitEnum) {
|
} elseif ($default instanceof \UnitEnum) {
|
||||||
$default = '\\' . get_class($default) . '::' . $default->name;
|
$default = '\\' . get_class($default) . '::' . $default->name;
|
||||||
@@ -1237,13 +1315,18 @@ class ModelsCommand extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected ?array $cachedRelationTypes = null;
|
||||||
|
protected ?array $cachedRelationReturnTypes = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the available relation types
|
* Returns the available relation types
|
||||||
*/
|
*/
|
||||||
protected function getRelationTypes(): array
|
protected function getRelationTypes(): array
|
||||||
{
|
{
|
||||||
$configuredRelations = $this->laravel['config']->get('ide-helper.additional_relation_types', []);
|
return $this->cachedRelationTypes ??= array_merge(
|
||||||
return array_merge(self::RELATION_TYPES, $configuredRelations);
|
self::RELATION_TYPES,
|
||||||
|
$this->laravel['config']->get('ide-helper.additional_relation_types', [])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1251,7 +1334,7 @@ class ModelsCommand extends Command
|
|||||||
*/
|
*/
|
||||||
protected function getRelationReturnTypes(): array
|
protected function getRelationReturnTypes(): array
|
||||||
{
|
{
|
||||||
return $this->laravel['config']->get('ide-helper.additional_relation_return_types', []);
|
return $this->cachedRelationReturnTypes ??= $this->laravel['config']->get('ide-helper.additional_relation_return_types', []);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1267,9 +1350,6 @@ class ModelsCommand extends Command
|
|||||||
*/
|
*/
|
||||||
protected function getAttributeTypes(Model $model, \ReflectionMethod $reflectionMethod): Collection
|
protected function getAttributeTypes(Model $model, \ReflectionMethod $reflectionMethod): Collection
|
||||||
{
|
{
|
||||||
// Private/protected ReflectionMethods require setAccessible prior to PHP 8.1
|
|
||||||
$reflectionMethod->setAccessible(true);
|
|
||||||
|
|
||||||
/** @var Attribute $attribute */
|
/** @var Attribute $attribute */
|
||||||
$attribute = $reflectionMethod->invoke($model);
|
$attribute = $reflectionMethod->invoke($model);
|
||||||
|
|
||||||
@@ -1329,11 +1409,7 @@ class ModelsCommand extends Command
|
|||||||
*/
|
*/
|
||||||
protected function getCommentFromDocBlock(\ReflectionMethod $reflection)
|
protected function getCommentFromDocBlock(\ReflectionMethod $reflection)
|
||||||
{
|
{
|
||||||
$phpDocContext = (new ContextFactory())->createFromReflector($reflection);
|
$context = $this->getDocBlockContext($reflection);
|
||||||
$context = new Context(
|
|
||||||
$phpDocContext->getNamespace(),
|
|
||||||
$phpDocContext->getNamespaceAliases()
|
|
||||||
);
|
|
||||||
$comment = '';
|
$comment = '';
|
||||||
$phpdoc = new DocBlock($reflection, $context);
|
$phpdoc = new DocBlock($reflection, $context);
|
||||||
|
|
||||||
@@ -1354,11 +1430,7 @@ class ModelsCommand extends Command
|
|||||||
*/
|
*/
|
||||||
protected function getReturnTypeFromDocBlock(\ReflectionMethod $reflection, ?\Reflector $reflectorForContext = null)
|
protected function getReturnTypeFromDocBlock(\ReflectionMethod $reflection, ?\Reflector $reflectorForContext = null)
|
||||||
{
|
{
|
||||||
$phpDocContext = (new ContextFactory())->createFromReflector($reflectorForContext ?? $reflection);
|
$context = $this->getDocBlockContext($reflectorForContext ?? $reflection);
|
||||||
$context = new Context(
|
|
||||||
$phpDocContext->getNamespace(),
|
|
||||||
$phpDocContext->getNamespaceAliases()
|
|
||||||
);
|
|
||||||
$type = null;
|
$type = null;
|
||||||
$phpdoc = new DocBlock($reflection, $context);
|
$phpdoc = new DocBlock($reflection, $context);
|
||||||
|
|
||||||
@@ -1376,6 +1448,31 @@ class ModelsCommand extends Command
|
|||||||
return $type;
|
return $type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getDocBlockContext(\Reflector $reflector): Context
|
||||||
|
{
|
||||||
|
if ($reflector instanceof \ReflectionMethod) {
|
||||||
|
$key = $reflector->getDeclaringClass()->getName();
|
||||||
|
} elseif ($reflector instanceof ReflectionClass) {
|
||||||
|
$key = $reflector->getName();
|
||||||
|
} else {
|
||||||
|
$phpDocContext = (new ContextFactory())->createFromReflector($reflector);
|
||||||
|
return new Context(
|
||||||
|
$phpDocContext->getNamespace(),
|
||||||
|
$phpDocContext->getNamespaceAliases()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($this->contextCache[$key])) {
|
||||||
|
$phpDocContext = (new ContextFactory())->createFromReflector($reflector);
|
||||||
|
$this->contextCache[$key] = new Context(
|
||||||
|
$phpDocContext->getNamespace(),
|
||||||
|
$phpDocContext->getNamespaceAliases()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->contextCache[$key];
|
||||||
|
}
|
||||||
|
|
||||||
protected function getReturnTypeFromReflection(\ReflectionMethod $reflection): ?string
|
protected function getReturnTypeFromReflection(\ReflectionMethod $reflection): ?string
|
||||||
{
|
{
|
||||||
$returnType = $reflection->getReturnType();
|
$returnType = $reflection->getReturnType();
|
||||||
@@ -1405,7 +1502,7 @@ class ModelsCommand extends Command
|
|||||||
if (in_array('Illuminate\\Database\\Eloquent\\SoftDeletes', $traits)) {
|
if (in_array('Illuminate\\Database\\Eloquent\\SoftDeletes', $traits)) {
|
||||||
$modelName = $this->getClassNameInDestinationFile($model, get_class($model));
|
$modelName = $this->getClassNameInDestinationFile($model, get_class($model));
|
||||||
$builder = $this->getClassNameInDestinationFile($model, \Illuminate\Database\Eloquent\Builder::class);
|
$builder = $this->getClassNameInDestinationFile($model, \Illuminate\Database\Eloquent\Builder::class);
|
||||||
$this->setMethod('withTrashed', $builder . '<static>|' . $modelName, []);
|
$this->setMethod('withTrashed', $builder . '<static>|' . $modelName, ['bool $withTrashed = true']);
|
||||||
$this->setMethod('withoutTrashed', $builder . '<static>|' . $modelName, []);
|
$this->setMethod('withoutTrashed', $builder . '<static>|' . $modelName, []);
|
||||||
$this->setMethod('onlyTrashed', $builder . '<static>|' . $modelName, []);
|
$this->setMethod('onlyTrashed', $builder . '<static>|' . $modelName, []);
|
||||||
}
|
}
|
||||||
@@ -1581,9 +1678,10 @@ class ModelsCommand extends Command
|
|||||||
*/
|
*/
|
||||||
protected function getUsedClassNames(ReflectionClass $reflection): array
|
protected function getUsedClassNames(ReflectionClass $reflection): array
|
||||||
{
|
{
|
||||||
|
$context = $this->getDocBlockContext($reflection);
|
||||||
$namespaceAliases = array_flip(array_map(function ($alias) {
|
$namespaceAliases = array_flip(array_map(function ($alias) {
|
||||||
return ltrim($alias, '\\');
|
return ltrim($alias, '\\');
|
||||||
}, (new ContextFactory())->createFromReflector($reflection)->getNamespaceAliases()));
|
}, $context->getNamespaceAliases()));
|
||||||
$namespaceAliases[$reflection->getName()] = $reflection->getShortName();
|
$namespaceAliases[$reflection->getName()] = $reflection->getShortName();
|
||||||
|
|
||||||
return $namespaceAliases;
|
return $namespaceAliases;
|
||||||
@@ -1627,7 +1725,8 @@ class ModelsCommand extends Command
|
|||||||
$type = implode('|', $types);
|
$type = implode('|', $types);
|
||||||
|
|
||||||
if ($paramType->allowsNull()) {
|
if ($paramType->allowsNull()) {
|
||||||
if (count($types) == 1) {
|
// Use ?Type syntax only for single named types, not for intersection types
|
||||||
|
if (count($types) == 1 && !str_starts_with($type, '(')) {
|
||||||
$type = '?' . $type;
|
$type = '?' . $type;
|
||||||
} else {
|
} else {
|
||||||
$type .= '|null';
|
$type .= '|null';
|
||||||
@@ -1645,7 +1744,7 @@ class ModelsCommand extends Command
|
|||||||
|
|
||||||
preg_match(
|
preg_match(
|
||||||
'/@param ((?:(?:[\w?|\\\\<>])+(?:\[])?)+)/',
|
'/@param ((?:(?:[\w?|\\\\<>])+(?:\[])?)+)/',
|
||||||
$docComment ?? '',
|
$docComment,
|
||||||
$matches
|
$matches
|
||||||
);
|
);
|
||||||
$type = $matches[1] ?? '';
|
$type = $matches[1] ?? '';
|
||||||
@@ -1700,24 +1799,53 @@ class ModelsCommand extends Command
|
|||||||
return $type;
|
return $type;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function extractReflectionTypes(ReflectionType $reflection_type)
|
protected function extractReflectionTypes(ReflectionType $reflection_type): array
|
||||||
{
|
{
|
||||||
if ($reflection_type instanceof ReflectionNamedType) {
|
if ($reflection_type instanceof ReflectionNamedType) {
|
||||||
$types[] = $this->getReflectionNamedType($reflection_type);
|
return [$this->getReflectionNamedType($reflection_type)];
|
||||||
} else {
|
|
||||||
$types = [];
|
|
||||||
foreach ($reflection_type->getTypes() as $named_type) {
|
|
||||||
if ($named_type->getName() === 'null') {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$types[] = $this->getReflectionNamedType($named_type);
|
if ($reflection_type instanceof \ReflectionIntersectionType) {
|
||||||
|
return [$this->formatIntersectionType($reflection_type)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($reflection_type instanceof \ReflectionUnionType) {
|
||||||
|
return $this->extractUnionTypes($reflection_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unknown type - return empty array as fallback
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function extractUnionTypes(\ReflectionUnionType $union_type): array
|
||||||
|
{
|
||||||
|
$types = [];
|
||||||
|
|
||||||
|
foreach ($union_type->getTypes() as $inner_type) {
|
||||||
|
if ($inner_type instanceof ReflectionNamedType) {
|
||||||
|
if ($inner_type->getName() === 'null') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$types[] = $this->getReflectionNamedType($inner_type);
|
||||||
|
} elseif ($inner_type instanceof \ReflectionIntersectionType) {
|
||||||
|
$types[] = $this->formatIntersectionType($inner_type);
|
||||||
|
}
|
||||||
|
// ReflectionUnionType cannot be nested per PHP's DNF rules
|
||||||
}
|
}
|
||||||
|
|
||||||
return $types;
|
return $types;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function formatIntersectionType(\ReflectionIntersectionType $intersection_type): string
|
||||||
|
{
|
||||||
|
$parts = [];
|
||||||
|
foreach ($intersection_type->getTypes() as $type) {
|
||||||
|
$parts[] = $this->getReflectionNamedType($type);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '(' . implode('&', $parts) . ')';
|
||||||
|
}
|
||||||
|
|
||||||
protected function getReflectionNamedType(ReflectionNamedType $paramType): string
|
protected function getReflectionNamedType(ReflectionNamedType $paramType): string
|
||||||
{
|
{
|
||||||
$parameterName = $paramType->getName();
|
$parameterName = $paramType->getName();
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Barryvdh\LaravelIdeHelper;
|
|
||||||
|
|
||||||
use Exception;
|
|
||||||
use Illuminate\Database\Eloquent\Factory;
|
|
||||||
use ReflectionClass;
|
|
||||||
|
|
||||||
class Factories
|
|
||||||
{
|
|
||||||
public static function all()
|
|
||||||
{
|
|
||||||
$factories = [];
|
|
||||||
|
|
||||||
if (static::isLaravelSevenOrLower()) {
|
|
||||||
$factory = app(Factory::class);
|
|
||||||
|
|
||||||
$definitions = (new ReflectionClass(Factory::class))->getProperty('definitions');
|
|
||||||
$definitions->setAccessible(true);
|
|
||||||
|
|
||||||
foreach ($definitions->getValue($factory) as $factory_target => $config) {
|
|
||||||
try {
|
|
||||||
$factories[] = new ReflectionClass($factory_target);
|
|
||||||
} catch (Exception $exception) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $factories;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function isLaravelSevenOrLower()
|
|
||||||
{
|
|
||||||
return class_exists('Illuminate\Database\Eloquent\Factory');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+11
-19
@@ -35,7 +35,6 @@ class Generator
|
|||||||
protected $magic = [];
|
protected $magic = [];
|
||||||
protected $interfaces = [];
|
protected $interfaces = [];
|
||||||
protected $helpers;
|
protected $helpers;
|
||||||
protected array $macroableTraits = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \Illuminate\Config\Repository $config
|
* @param \Illuminate\Config\Repository $config
|
||||||
@@ -58,9 +57,13 @@ class Generator
|
|||||||
// Find the drivers to add to the extra/interfaces
|
// Find the drivers to add to the extra/interfaces
|
||||||
$this->detectDrivers();
|
$this->detectDrivers();
|
||||||
|
|
||||||
$this->extra = array_merge($this->extra, $this->config->get('ide-helper.extra'), []);
|
$this->extra = array_merge($this->extra, $this->config->get('ide-helper.extra', []));
|
||||||
$this->magic = array_merge($this->magic, $this->config->get('ide-helper.magic'), []);
|
$this->magic = array_merge($this->magic, $this->config->get('ide-helper.magic', []));
|
||||||
$this->interfaces = array_merge($this->interfaces, $this->config->get('ide-helper.interfaces'), []);
|
$this->interfaces = array_merge($this->interfaces, $this->config->get('ide-helper.interfaces', []));
|
||||||
|
Macro::setDefaultReturnTypes($this->config->get('ide-helper.macro_default_return_types', [
|
||||||
|
\Illuminate\Http\Client\Factory::class => \Illuminate\Http\Client\PendingRequest::class,
|
||||||
|
]));
|
||||||
|
|
||||||
// Make all interface classes absolute
|
// Make all interface classes absolute
|
||||||
foreach ($this->interfaces as &$interface) {
|
foreach ($this->interfaces as &$interface) {
|
||||||
$interface = '\\' . ltrim($interface, '\\');
|
$interface = '\\' . ltrim($interface, '\\');
|
||||||
@@ -71,7 +74,7 @@ class Generator
|
|||||||
/**
|
/**
|
||||||
* Generate the helper file contents;
|
* Generate the helper file contents;
|
||||||
*
|
*
|
||||||
* @return string;
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function generate()
|
public function generate()
|
||||||
{
|
{
|
||||||
@@ -82,7 +85,6 @@ class Generator
|
|||||||
->with('real_time_facades', $this->getRealTimeFacades())
|
->with('real_time_facades', $this->getRealTimeFacades())
|
||||||
->with('helpers', $this->detectHelpers())
|
->with('helpers', $this->detectHelpers())
|
||||||
->with('include_fluent', $this->config->get('ide-helper.include_fluent', true))
|
->with('include_fluent', $this->config->get('ide-helper.include_fluent', true))
|
||||||
->with('factories', $this->config->get('ide-helper.include_factory_builders') ? Factories::all() : [])
|
|
||||||
->render();
|
->render();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,7 +362,7 @@ class Generator
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$aliases[] = new Alias($this->config, $class, $class, [], $this->interfaces, true);
|
$aliases[] = new Alias($this->config, $class, $class, [], $this->interfaces);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,18 +384,8 @@ class Generator
|
|||||||
->filter(function ($class) {
|
->filter(function ($class) {
|
||||||
$traits = class_uses_recursive($class);
|
$traits = class_uses_recursive($class);
|
||||||
|
|
||||||
if (isset($traits[Macroable::class])) {
|
// Filter only classes with the macroable trait
|
||||||
return true;
|
return isset($traits[Macroable::class]);
|
||||||
}
|
|
||||||
|
|
||||||
// Filter only classes with a macroable trait
|
|
||||||
foreach ($this->config->get('ide-helper.macroable_traits', []) as $trait) {
|
|
||||||
if (isset($traits[$trait])) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
})
|
})
|
||||||
->filter(function ($class) use ($aliases) {
|
->filter(function ($class) use ($aliases) {
|
||||||
$class = Str::start($class, '\\');
|
$class = Str::start($class, '\\');
|
||||||
|
|||||||
+46
-16
@@ -5,20 +5,16 @@ namespace Barryvdh\LaravelIdeHelper;
|
|||||||
use Barryvdh\Reflection\DocBlock;
|
use Barryvdh\Reflection\DocBlock;
|
||||||
use Barryvdh\Reflection\DocBlock\Tag;
|
use Barryvdh\Reflection\DocBlock\Tag;
|
||||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||||
use Illuminate\Http\Client\PendingRequest;
|
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
class Macro extends Method
|
class Macro extends Method
|
||||||
{
|
{
|
||||||
protected $macroDefaults = [
|
protected static $macroDefaults = [];
|
||||||
\Illuminate\Http\Client\Factory::class => PendingRequest::class,
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Macro constructor.
|
* Macro constructor.
|
||||||
*
|
*
|
||||||
* @param \ReflectionFunctionAbstract $method
|
* @param \ReflectionFunctionAbstract $method
|
||||||
* @param string $alias
|
|
||||||
* @param \ReflectionClass $class
|
* @param \ReflectionClass $class
|
||||||
* @param null $methodName
|
* @param null $methodName
|
||||||
* @param array $interfaces
|
* @param array $interfaces
|
||||||
@@ -27,16 +23,19 @@ class Macro extends Method
|
|||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
$method,
|
$method,
|
||||||
$alias,
|
|
||||||
$class,
|
$class,
|
||||||
$methodName = null,
|
$methodName = null,
|
||||||
$interfaces = [],
|
$interfaces = [],
|
||||||
$classAliases = [],
|
$classAliases = [],
|
||||||
$returnTypeNormalizers = []
|
$returnTypeNormalizers = []
|
||||||
) {
|
) {
|
||||||
parent::__construct($method, $alias, $class, $methodName, $interfaces, $classAliases, $returnTypeNormalizers);
|
parent::__construct($method, $class, $methodName, $interfaces, $classAliases, $returnTypeNormalizers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function setDefaultReturnTypes(array $map = [])
|
||||||
|
{
|
||||||
|
static::$macroDefaults = array_merge(static::$macroDefaults, $map);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* @param \ReflectionFunctionAbstract $method
|
* @param \ReflectionFunctionAbstract $method
|
||||||
*/
|
*/
|
||||||
@@ -74,8 +73,8 @@ class Macro extends Method
|
|||||||
|
|
||||||
$type = $this->concatReflectionTypes($return);
|
$type = $this->concatReflectionTypes($return);
|
||||||
|
|
||||||
/** @psalm-suppress UndefinedClass */
|
|
||||||
if (!$return instanceof \ReflectionUnionType) {
|
if (!$return instanceof \ReflectionUnionType) {
|
||||||
|
/** @phpstan-ignore method.notFound */
|
||||||
$type .= $this->root === "\\{$builder}" && $return->getName() === $builder ? '|static' : '';
|
$type .= $this->root === "\\{$builder}" && $return->getName() === $builder ? '|static' : '';
|
||||||
$type .= $return->allowsNull() ? '|null' : '';
|
$type .= $return->allowsNull() ? '|null' : '';
|
||||||
}
|
}
|
||||||
@@ -84,25 +83,56 @@ class Macro extends Method
|
|||||||
}
|
}
|
||||||
|
|
||||||
$class = ltrim($this->declaringClassName, '\\');
|
$class = ltrim($this->declaringClassName, '\\');
|
||||||
if (!$this->phpdoc->hasTag('return') && isset($this->macroDefaults[$class])) {
|
if (!$this->phpdoc->hasTag('return') && isset(static::$macroDefaults[$class])) {
|
||||||
$type = $this->macroDefaults[$class];
|
$type = static::$macroDefaults[$class];
|
||||||
$this->phpdoc->appendTag(Tag::createInstance("@return {$type}"));
|
$this->phpdoc->appendTag(Tag::createInstance("@return {$type}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function concatReflectionTypes(?\ReflectionType $type): string
|
protected function concatReflectionTypes(?\ReflectionType $type): string
|
||||||
{
|
{
|
||||||
/** @psalm-suppress UndefinedClass */
|
if ($type instanceof \ReflectionNamedType) {
|
||||||
$returnTypes = $type instanceof \ReflectionUnionType
|
return $type->getName();
|
||||||
? $type->getTypes()
|
}
|
||||||
: [$type];
|
|
||||||
|
|
||||||
return Collection::make($returnTypes)
|
if ($type instanceof \ReflectionIntersectionType) {
|
||||||
|
return $this->formatIntersectionType($type);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($type instanceof \ReflectionUnionType) {
|
||||||
|
return $this->formatUnionType($type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unknown or null type
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function formatUnionType(\ReflectionUnionType $type): string
|
||||||
|
{
|
||||||
|
return Collection::make($type->getTypes())
|
||||||
|
->map(function (\ReflectionType $inner) {
|
||||||
|
if ($inner instanceof \ReflectionNamedType) {
|
||||||
|
return $inner->getName();
|
||||||
|
}
|
||||||
|
if ($inner instanceof \ReflectionIntersectionType) {
|
||||||
|
return $this->formatIntersectionType($inner);
|
||||||
|
}
|
||||||
|
// ReflectionUnionType cannot be nested per PHP's DNF rules
|
||||||
|
return null;
|
||||||
|
})
|
||||||
->filter()
|
->filter()
|
||||||
->map->getName()
|
|
||||||
->implode('|');
|
->implode('|');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function formatIntersectionType(\ReflectionIntersectionType $type): string
|
||||||
|
{
|
||||||
|
$parts = Collection::make($type->getTypes())
|
||||||
|
->map(fn (\ReflectionNamedType $t) => $t->getName())
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
return '(' . implode('&', $parts) . ')';
|
||||||
|
}
|
||||||
|
|
||||||
protected function addLocationToPhpDoc()
|
protected function addLocationToPhpDoc()
|
||||||
{
|
{
|
||||||
if ($this->method->name === '__invoke') {
|
if ($this->method->name === '__invoke') {
|
||||||
|
|||||||
+3
-4
@@ -44,7 +44,6 @@ class Method
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \ReflectionMethod|\ReflectionFunctionAbstract $method
|
* @param \ReflectionMethod|\ReflectionFunctionAbstract $method
|
||||||
* @param string $alias
|
|
||||||
* @param \ReflectionClass $class
|
* @param \ReflectionClass $class
|
||||||
* @param string|null $methodName
|
* @param string|null $methodName
|
||||||
* @param array $interfaces
|
* @param array $interfaces
|
||||||
@@ -52,7 +51,7 @@ class Method
|
|||||||
* @param array $returnTypeNormalizers
|
* @param array $returnTypeNormalizers
|
||||||
* @param string[] $templateNames
|
* @param string[] $templateNames
|
||||||
*/
|
*/
|
||||||
public function __construct($method, $alias, $class, $methodName = null, $interfaces = [], array $classAliases = [], array $returnTypeNormalizers = [], array $templateNames = [])
|
public function __construct($method, $class, $methodName = null, $interfaces = [], array $classAliases = [], array $returnTypeNormalizers = [], array $templateNames = [])
|
||||||
{
|
{
|
||||||
$this->method = $method;
|
$this->method = $method;
|
||||||
$this->interfaces = $interfaces;
|
$this->interfaces = $interfaces;
|
||||||
@@ -178,7 +177,7 @@ class Method
|
|||||||
/**
|
/**
|
||||||
* Get the parameters for this method
|
* Get the parameters for this method
|
||||||
*
|
*
|
||||||
* @param bool $implode Wether to implode the array or not
|
* @param bool $implode Whether to implode the array or not
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getParams($implode = true)
|
public function getParams($implode = true)
|
||||||
@@ -208,7 +207,7 @@ class Method
|
|||||||
/**
|
/**
|
||||||
* Get the parameters for this method including default values
|
* Get the parameters for this method including default values
|
||||||
*
|
*
|
||||||
* @param bool $implode Wether to implode the array or not
|
* @param bool $implode Whether to implode the array or not
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getParamsWithDefault($implode = true)
|
public function getParamsWithDefault($implode = true)
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- "*"
|
- "*"
|
||||||
schedule:
|
|
||||||
- cron: '0 0 * * *'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
php-tests:
|
php-tests:
|
||||||
|
|||||||
+3
-1
@@ -206,8 +206,9 @@ class Serializer
|
|||||||
$text = wordwrap($text, $wrapLength);
|
$text = wordwrap($text, $wrapLength);
|
||||||
}
|
}
|
||||||
$text = str_replace("\n", "\n{$indent} * ", $text);
|
$text = str_replace("\n", "\n{$indent} * ", $text);
|
||||||
|
$text = preg_replace('/^(\s*\*)[ \t]+$/m', '$1', $text);
|
||||||
|
|
||||||
$comment = "{$firstIndent}/**\n{$indent} * {$text}\n{$indent} *\n";
|
$comment = !empty($text)? "{$firstIndent}/**\n{$indent} * {$text}\n{$indent} *\n" : "{$firstIndent}/**\n";
|
||||||
|
|
||||||
$tags = array_values($docblock->getTags());
|
$tags = array_values($docblock->getTags());
|
||||||
|
|
||||||
@@ -220,6 +221,7 @@ class Serializer
|
|||||||
$tagText = wordwrap($tagText, $wrapLength);
|
$tagText = wordwrap($tagText, $wrapLength);
|
||||||
}
|
}
|
||||||
$tagText = str_replace("\n", "\n{$indent} * ", $tagText);
|
$tagText = str_replace("\n", "\n{$indent} * ", $tagText);
|
||||||
|
$tagText = preg_replace('/^(\s*\*)[ \t]+$/m', '$1', $tagText);
|
||||||
|
|
||||||
$comment .= "{$indent} * {$tagText}\n";
|
$comment .= "{$indent} * {$tagText}\n";
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -409,6 +409,6 @@ class Tag implements \Reflector
|
|||||||
*/
|
*/
|
||||||
public function __toString()
|
public function __toString()
|
||||||
{
|
{
|
||||||
return "@{$this->getName()} {$this->getContent()}";
|
return trim("@{$this->getName()} {$this->getContent()}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+2
-3
@@ -112,9 +112,8 @@ if (PHP_VERSION_ID < 80000) {
|
|||||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||||
) {
|
) {
|
||||||
include("phpvfscomposer://" . __DIR__ . '/..'.'/nesbot/carbon/bin/carbon');
|
return include("phpvfscomposer://" . __DIR__ . '/..'.'/nesbot/carbon/bin/carbon');
|
||||||
exit(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
include __DIR__ . '/..'.'/nesbot/carbon/bin/carbon';
|
return include __DIR__ . '/..'.'/nesbot/carbon/bin/carbon';
|
||||||
|
|||||||
Vendored
+2
-3
@@ -112,9 +112,8 @@ if (PHP_VERSION_ID < 80000) {
|
|||||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||||
) {
|
) {
|
||||||
include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/error-handler/Resources/bin/patch-type-declarations');
|
return include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/error-handler/Resources/bin/patch-type-declarations');
|
||||||
exit(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
include __DIR__ . '/..'.'/symfony/error-handler/Resources/bin/patch-type-declarations';
|
return include __DIR__ . '/..'.'/symfony/error-handler/Resources/bin/patch-type-declarations';
|
||||||
|
|||||||
Vendored
+2
-3
@@ -112,9 +112,8 @@ if (PHP_VERSION_ID < 80000) {
|
|||||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||||
) {
|
) {
|
||||||
include("phpvfscomposer://" . __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcbf');
|
return include("phpvfscomposer://" . __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcbf');
|
||||||
exit(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
include __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcbf';
|
return include __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcbf';
|
||||||
|
|||||||
Vendored
+2
-3
@@ -112,9 +112,8 @@ if (PHP_VERSION_ID < 80000) {
|
|||||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||||
) {
|
) {
|
||||||
include("phpvfscomposer://" . __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcs');
|
return include("phpvfscomposer://" . __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcs');
|
||||||
exit(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
include __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcs';
|
return include __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcs';
|
||||||
|
|||||||
Vendored
+2
-3
@@ -115,9 +115,8 @@ if (PHP_VERSION_ID < 80000) {
|
|||||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||||
) {
|
) {
|
||||||
include("phpvfscomposer://" . __DIR__ . '/..'.'/phpunit/phpunit/phpunit');
|
return include("phpvfscomposer://" . __DIR__ . '/..'.'/phpunit/phpunit/phpunit');
|
||||||
exit(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
include __DIR__ . '/..'.'/phpunit/phpunit/phpunit';
|
return include __DIR__ . '/..'.'/phpunit/phpunit/phpunit';
|
||||||
|
|||||||
Vendored
+2
-3
@@ -112,9 +112,8 @@ if (PHP_VERSION_ID < 80000) {
|
|||||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||||
) {
|
) {
|
||||||
include("phpvfscomposer://" . __DIR__ . '/..'.'/psy/psysh/bin/psysh');
|
return include("phpvfscomposer://" . __DIR__ . '/..'.'/psy/psysh/bin/psysh');
|
||||||
exit(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
include __DIR__ . '/..'.'/psy/psysh/bin/psysh';
|
return include __DIR__ . '/..'.'/psy/psysh/bin/psysh';
|
||||||
|
|||||||
Vendored
+2
-3
@@ -112,9 +112,8 @@ if (PHP_VERSION_ID < 80000) {
|
|||||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||||
) {
|
) {
|
||||||
include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server');
|
return include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server');
|
||||||
exit(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
include __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server';
|
return include __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server';
|
||||||
|
|||||||
Vendored
+2
-3
@@ -112,9 +112,8 @@ if (PHP_VERSION_ID < 80000) {
|
|||||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||||
) {
|
) {
|
||||||
include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/yaml/Resources/bin/yaml-lint');
|
return include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/yaml/Resources/bin/yaml-lint');
|
||||||
exit(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
include __DIR__ . '/..'.'/symfony/yaml/Resources/bin/yaml-lint';
|
return include __DIR__ . '/..'.'/symfony/yaml/Resources/bin/yaml-lint';
|
||||||
|
|||||||
Vendored
+156
-1
@@ -2,6 +2,162 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [0.14.8](https://github.com/brick/math/releases/tag/0.14.8) - 2026-02-10
|
||||||
|
|
||||||
|
🗑️ **Deprecations**
|
||||||
|
|
||||||
|
- Method `BigInteger::testBit()` is deprecated, use `isBitSet()` instead
|
||||||
|
|
||||||
|
✨ **New features**
|
||||||
|
|
||||||
|
- New method: `BigInteger::isBitSet()` (replaces `testBit()`)
|
||||||
|
- New method: `BigNumber::toString()` (alias of magic method `__toString()`)
|
||||||
|
|
||||||
|
👌 **Improvements**
|
||||||
|
|
||||||
|
- Performance optimization of `BigRational` comparison methods
|
||||||
|
- More exceptions have been documented with `@throws` annotations
|
||||||
|
|
||||||
|
## [0.14.7](https://github.com/brick/math/releases/tag/0.14.7) - 2026-02-07
|
||||||
|
|
||||||
|
✨ **New features**
|
||||||
|
|
||||||
|
- `clamp()` is now available on the base `BigNumber` class
|
||||||
|
|
||||||
|
👌 **Improvements**
|
||||||
|
|
||||||
|
- Improved `@throws` exception documentation
|
||||||
|
|
||||||
|
## [0.14.6](https://github.com/brick/math/releases/tag/0.14.6) - 2026-02-05
|
||||||
|
|
||||||
|
🗑️ **Deprecations**
|
||||||
|
|
||||||
|
- Not passing a `$scale` to `BigDecimal::dividedBy()` is deprecated; **`$scale` will be required in 0.15**
|
||||||
|
|
||||||
|
👌 **Improvements**
|
||||||
|
|
||||||
|
- `BigRational::toFloat()` never returns `NAN` anymore
|
||||||
|
|
||||||
|
## [0.14.5](https://github.com/brick/math/releases/tag/0.14.5) - 2026-02-03
|
||||||
|
|
||||||
|
🗑️ **Deprecations**
|
||||||
|
|
||||||
|
- Not passing a rounding mode to `BigInteger::sqrt()` and `BigDecimal::sqrt()` triggers a deprecation notice: **the default rounding mode will change from `Down` to `Unnecessary` in 0.15**
|
||||||
|
|
||||||
|
✨ **New features**
|
||||||
|
|
||||||
|
- `BigInteger::sqrt()` and `BigDecimal::sqrt()` now support rounding
|
||||||
|
- `abs()` and `negated()` methods are now available on the base `BigNumber` class
|
||||||
|
|
||||||
|
👌 **Improvements**
|
||||||
|
|
||||||
|
- Alphabet is now checked for duplicate characters in `BigInteger::(from|to)ArbitraryBase()`
|
||||||
|
- `BigNumber::ofNullable()` is now marked as `@pure`
|
||||||
|
|
||||||
|
## [0.14.4](https://github.com/brick/math/releases/tag/0.14.4) - 2026-02-02
|
||||||
|
|
||||||
|
🗑️ **Deprecations**
|
||||||
|
|
||||||
|
- Passing a negative modulus to `BigInteger::mod()` is deprecated to align with Euclidean modulo semantics; it will throw `NegativeNumberException` in 0.15
|
||||||
|
- Method `BigDecimal::stripTrailingZeros()` is deprecated, use `strippedOfTrailingZeros()` instead
|
||||||
|
|
||||||
|
✨ **New features**
|
||||||
|
|
||||||
|
- `BigInteger::modPow()` now accepts negative bases
|
||||||
|
- New method: `BigDecimal::strippedOfTrailingZeros()` (replaces `stripTrailingZeros()`)
|
||||||
|
|
||||||
|
👌 **Improvements**
|
||||||
|
|
||||||
|
- `clamp()` methods are now marked as `@pure`
|
||||||
|
|
||||||
|
## [0.14.3](https://github.com/brick/math/releases/tag/0.14.3) - 2026-02-01
|
||||||
|
|
||||||
|
✨ **New features**
|
||||||
|
|
||||||
|
- New method: `BigInteger::lcm()`
|
||||||
|
- New method: `BigInteger::lcmAll()`
|
||||||
|
- New method: `BigRational::toRepeatingDecimalString()`
|
||||||
|
|
||||||
|
🐛 **Bug fixes**
|
||||||
|
|
||||||
|
- `BigInteger::gcdAll()` / `gcdMultiple()` could return a negative result when used with a single negative number
|
||||||
|
|
||||||
|
## [0.14.2](https://github.com/brick/math/releases/tag/0.14.2) - 2026-01-30
|
||||||
|
|
||||||
|
🗑️ **Deprecations**
|
||||||
|
|
||||||
|
- **Passing `float` values to `of()` or arithmetic methods is deprecated** and will be removed in 0.15; cast to string explicitly to preserve the previous behaviour (#105)
|
||||||
|
- **Accessing `RoundingMode` enum cases through upper snake case (e.g. `HALF_UP`) is deprecated**, use the pascal case version (e.g. `HalfUp`) instead
|
||||||
|
- Method `BigInteger::gcdMultiple()` is deprecated, use `gcdAll()` instead
|
||||||
|
- Method `BigDecimal::exactlyDividedBy()` is deprecated, use `dividedByExact()` instead
|
||||||
|
- Method `BigDecimal::getIntegralPart()` is deprecated (will be removed in 0.15, and re-introduced as returning `BigInteger` in 0.16)
|
||||||
|
- Method `BigDecimal::getFractionalPart()` is deprecated (will be removed in 0.15, and re-introduced as returning `BigDecimal` with a different meaning in 0.16)
|
||||||
|
- Method `BigRational::nd()` is deprecated, use `ofFraction()` instead
|
||||||
|
- Method `BigRational::quotient()` is deprecated, use `getIntegralPart()` instead
|
||||||
|
- Method `BigRational::remainder()` is deprecated, use `$number->getNumerator()->remainder($number->getDenominator())` instead
|
||||||
|
- Method `BigRational::quotientAndRemainder()` is deprecated, use `$number->getNumerator()->quotientAndRemainder($number->getDenominator())` instead
|
||||||
|
|
||||||
|
✨ **New features**
|
||||||
|
|
||||||
|
- New method: `BigInteger::gcdAll()` (replaces `gcdMultiple()`)
|
||||||
|
- New method: `BigRational::clamp()`
|
||||||
|
- New method: `BigRational::ofFraction()` (replaces `nd()`)
|
||||||
|
- New method: `BigRational::getIntegralPart()` (replaces `quotient()`)
|
||||||
|
- New method: `BigRational::getFractionalPart()`
|
||||||
|
|
||||||
|
👌 **Improvements**
|
||||||
|
|
||||||
|
- `BigInteger::modInverse()` now accepts `BigNumber|int|float|string` instead of just `BigInteger`
|
||||||
|
- `BigInteger::gcdMultiple()` now accepts `BigNumber|int|float|string` instead of just `BigInteger`
|
||||||
|
|
||||||
|
🐛 **Bug fixes**
|
||||||
|
|
||||||
|
- `BigInteger::clamp()` and `BigDecimal::clamp()` now throw an exception on inverted bounds, instead of returning an incorrect result
|
||||||
|
|
||||||
|
## [0.14.1](https://github.com/brick/math/releases/tag/0.14.1) - 2025-11-24
|
||||||
|
|
||||||
|
✨ **New features**
|
||||||
|
|
||||||
|
- New method: `BigNumber::ofNullable()` (#94 by @mrkh995)
|
||||||
|
|
||||||
|
✨ **Compatibility fixes**
|
||||||
|
|
||||||
|
- Fixed warnings on PHP 8.5 (#101 and #102 by @julien-boudry)
|
||||||
|
|
||||||
|
## [0.14.0](https://github.com/brick/math/releases/tag/0.14.0) - 2025-08-29
|
||||||
|
|
||||||
|
✨ **New features**
|
||||||
|
|
||||||
|
- New methods: `BigInteger::clamp()` and `BigDecimal::clamp()` (#96 by @JesterIruka)
|
||||||
|
|
||||||
|
✨ **Improvements**
|
||||||
|
|
||||||
|
- All pure methods in `BigNumber` classes are now marked as `@pure` for better static analysis
|
||||||
|
|
||||||
|
💥 **Breaking changes**
|
||||||
|
|
||||||
|
- Minimum PHP version is now 8.2
|
||||||
|
- `BigNumber` classes are now `readonly`
|
||||||
|
- `BigNumber` is now marked as sealed: it must not be extended outside of this package
|
||||||
|
- Exception classes are now `final`
|
||||||
|
|
||||||
|
## [0.13.1](https://github.com/brick/math/releases/tag/0.13.1) - 2025-03-29
|
||||||
|
|
||||||
|
✨ **Improvements**
|
||||||
|
|
||||||
|
- `__toString()` methods of `BigInteger` and `BigDecimal` are now type-hinted as returning `numeric-string` instead of `string` (#90 by @vudaltsov)
|
||||||
|
|
||||||
|
## [0.13.0](https://github.com/brick/math/releases/tag/0.13.0) - 2025-03-03
|
||||||
|
|
||||||
|
💥 **Breaking changes**
|
||||||
|
|
||||||
|
- `BigDecimal::ofUnscaledValue()` no longer throws an exception if the scale is negative
|
||||||
|
- `MathException` now extends `RuntimeException` instead of `Exception`; this reverts the change introduced in version `0.11.0` (#82)
|
||||||
|
|
||||||
|
✨ **New features**
|
||||||
|
|
||||||
|
- `BigDecimal::ofUnscaledValue()` allows a negative scale (and converts the values to create a zero scale number)
|
||||||
|
|
||||||
## [0.12.3](https://github.com/brick/math/releases/tag/0.12.3) - 2025-02-28
|
## [0.12.3](https://github.com/brick/math/releases/tag/0.12.3) - 2025-02-28
|
||||||
|
|
||||||
✨ **New features**
|
✨ **New features**
|
||||||
@@ -476,4 +632,3 @@ Added `BigDecimal::divideAndRemainder()`
|
|||||||
## [0.1.0](https://github.com/brick/math/releases/tag/0.1.0) - 2014-08-31
|
## [0.1.0](https://github.com/brick/math/releases/tag/0.1.0) - 2014-08-31
|
||||||
|
|
||||||
First beta release.
|
First beta release.
|
||||||
|
|
||||||
|
|||||||
Vendored
+3
-3
@@ -19,12 +19,12 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.1"
|
"php": "^8.2"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^10.1",
|
"phpunit/phpunit": "^11.5",
|
||||||
"php-coveralls/php-coveralls": "^2.2",
|
"php-coveralls/php-coveralls": "^2.2",
|
||||||
"vimeo/psalm": "6.8.8"
|
"phpstan/phpstan": "2.1.22"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
|||||||
Vendored
-70
@@ -1,70 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<files psalm-version="6.8.8@1361cd33008feb3ae2b4a93f1860e14e538ec8c2">
|
|
||||||
<file src="src/BigInteger.php">
|
|
||||||
<FalsableReturnStatement>
|
|
||||||
<code><![CDATA[\hex2bin($hex)]]></code>
|
|
||||||
</FalsableReturnStatement>
|
|
||||||
<InvalidFalsableReturnType>
|
|
||||||
<code><![CDATA[string]]></code>
|
|
||||||
</InvalidFalsableReturnType>
|
|
||||||
</file>
|
|
||||||
<file src="src/Exception/DivisionByZeroException.php">
|
|
||||||
<ClassMustBeFinal>
|
|
||||||
<code><![CDATA[DivisionByZeroException]]></code>
|
|
||||||
</ClassMustBeFinal>
|
|
||||||
</file>
|
|
||||||
<file src="src/Exception/IntegerOverflowException.php">
|
|
||||||
<ClassMustBeFinal>
|
|
||||||
<code><![CDATA[IntegerOverflowException]]></code>
|
|
||||||
</ClassMustBeFinal>
|
|
||||||
</file>
|
|
||||||
<file src="src/Exception/NegativeNumberException.php">
|
|
||||||
<ClassMustBeFinal>
|
|
||||||
<code><![CDATA[NegativeNumberException]]></code>
|
|
||||||
</ClassMustBeFinal>
|
|
||||||
</file>
|
|
||||||
<file src="src/Exception/NumberFormatException.php">
|
|
||||||
<ClassMustBeFinal>
|
|
||||||
<code><![CDATA[NumberFormatException]]></code>
|
|
||||||
</ClassMustBeFinal>
|
|
||||||
</file>
|
|
||||||
<file src="src/Exception/RoundingNecessaryException.php">
|
|
||||||
<ClassMustBeFinal>
|
|
||||||
<code><![CDATA[RoundingNecessaryException]]></code>
|
|
||||||
</ClassMustBeFinal>
|
|
||||||
</file>
|
|
||||||
<file src="src/Internal/Calculator/BcMathCalculator.php">
|
|
||||||
<ClassMustBeFinal>
|
|
||||||
<code><![CDATA[BcMathCalculator]]></code>
|
|
||||||
</ClassMustBeFinal>
|
|
||||||
</file>
|
|
||||||
<file src="src/Internal/Calculator/GmpCalculator.php">
|
|
||||||
<ClassMustBeFinal>
|
|
||||||
<code><![CDATA[GmpCalculator]]></code>
|
|
||||||
</ClassMustBeFinal>
|
|
||||||
</file>
|
|
||||||
<file src="src/Internal/Calculator/NativeCalculator.php">
|
|
||||||
<ClassMustBeFinal>
|
|
||||||
<code><![CDATA[NativeCalculator]]></code>
|
|
||||||
</ClassMustBeFinal>
|
|
||||||
<InvalidOperand>
|
|
||||||
<code><![CDATA[$a * $b]]></code>
|
|
||||||
<code><![CDATA[$a * 1]]></code>
|
|
||||||
<code><![CDATA[$a + $b]]></code>
|
|
||||||
<code><![CDATA[$b * 1]]></code>
|
|
||||||
<code><![CDATA[$b * 1]]></code>
|
|
||||||
<code><![CDATA[$blockA * $blockB + $carry]]></code>
|
|
||||||
<code><![CDATA[$blockA + $blockB]]></code>
|
|
||||||
<code><![CDATA[$blockA + $blockB + $carry]]></code>
|
|
||||||
<code><![CDATA[$blockA - $blockB]]></code>
|
|
||||||
<code><![CDATA[$blockA - $blockB - $carry]]></code>
|
|
||||||
<code><![CDATA[$carry]]></code>
|
|
||||||
<code><![CDATA[$mul % $complement]]></code>
|
|
||||||
<code><![CDATA[$mul - $value]]></code>
|
|
||||||
<code><![CDATA[$nb - 1]]></code>
|
|
||||||
<code><![CDATA[$sum += $complement]]></code>
|
|
||||||
<code><![CDATA[($mul - $value) / $complement]]></code>
|
|
||||||
<code><![CDATA[($nb - 1) * 10]]></code>
|
|
||||||
</InvalidOperand>
|
|
||||||
</file>
|
|
||||||
</files>
|
|
||||||
Vendored
+335
-152
@@ -7,15 +7,36 @@ namespace Brick\Math;
|
|||||||
use Brick\Math\Exception\DivisionByZeroException;
|
use Brick\Math\Exception\DivisionByZeroException;
|
||||||
use Brick\Math\Exception\MathException;
|
use Brick\Math\Exception\MathException;
|
||||||
use Brick\Math\Exception\NegativeNumberException;
|
use Brick\Math\Exception\NegativeNumberException;
|
||||||
|
use Brick\Math\Exception\RoundingNecessaryException;
|
||||||
use Brick\Math\Internal\Calculator;
|
use Brick\Math\Internal\Calculator;
|
||||||
|
use Brick\Math\Internal\CalculatorRegistry;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use LogicException;
|
||||||
use Override;
|
use Override;
|
||||||
|
|
||||||
|
use function func_num_args;
|
||||||
|
use function in_array;
|
||||||
|
use function intdiv;
|
||||||
|
use function max;
|
||||||
|
use function rtrim;
|
||||||
|
use function sprintf;
|
||||||
|
use function str_pad;
|
||||||
|
use function str_repeat;
|
||||||
|
use function strlen;
|
||||||
|
use function substr;
|
||||||
|
use function trigger_error;
|
||||||
|
|
||||||
|
use const E_USER_DEPRECATED;
|
||||||
|
use const STR_PAD_LEFT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Immutable, arbitrary-precision signed decimal numbers.
|
* An arbitrarily large decimal number.
|
||||||
*
|
*
|
||||||
* @psalm-immutable
|
* This class is immutable.
|
||||||
|
*
|
||||||
|
* The scale of the number is the number of digits after the decimal point. It is always positive or zero.
|
||||||
*/
|
*/
|
||||||
final class BigDecimal extends BigNumber
|
final readonly class BigDecimal extends BigNumber
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The unscaled value of this decimal number.
|
* The unscaled value of this decimal number.
|
||||||
@@ -24,20 +45,22 @@ final class BigDecimal extends BigNumber
|
|||||||
* No leading zero must be present.
|
* No leading zero must be present.
|
||||||
* No leading minus sign must be present if the value is 0.
|
* No leading minus sign must be present if the value is 0.
|
||||||
*/
|
*/
|
||||||
private readonly string $value;
|
private string $value;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The scale (number of digits after the decimal point) of this decimal number.
|
* The scale (number of digits after the decimal point) of this decimal number.
|
||||||
*
|
*
|
||||||
* This must be zero or more.
|
* This must be zero or more.
|
||||||
*/
|
*/
|
||||||
private readonly int $scale;
|
private int $scale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Protected constructor. Use a factory method to obtain an instance.
|
* Protected constructor. Use a factory method to obtain an instance.
|
||||||
*
|
*
|
||||||
* @param string $value The unscaled value, validated.
|
* @param string $value The unscaled value, validated.
|
||||||
* @param int $scale The scale, validated.
|
* @param int $scale The scale, validated.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
protected function __construct(string $value, int $scale = 0)
|
protected function __construct(string $value, int $scale = 0)
|
||||||
{
|
{
|
||||||
@@ -45,47 +68,45 @@ final class BigDecimal extends BigNumber
|
|||||||
$this->scale = $scale;
|
$this->scale = $scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @psalm-pure
|
|
||||||
*/
|
|
||||||
#[Override]
|
|
||||||
protected static function from(BigNumber $number): static
|
|
||||||
{
|
|
||||||
return $number->toBigDecimal();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a BigDecimal from an unscaled value and a scale.
|
* Creates a BigDecimal from an unscaled value and a scale.
|
||||||
*
|
*
|
||||||
* Example: `(12345, 3)` will result in the BigDecimal `12.345`.
|
* Example: `(12345, 3)` will result in the BigDecimal `12.345`.
|
||||||
*
|
*
|
||||||
|
* A negative scale is normalized to zero by appending zeros to the unscaled value.
|
||||||
|
*
|
||||||
|
* Example: `(12345, -3)` will result in the BigDecimal `12345000`.
|
||||||
|
*
|
||||||
* @param BigNumber|int|float|string $value The unscaled value. Must be convertible to a BigInteger.
|
* @param BigNumber|int|float|string $value The unscaled value. Must be convertible to a BigInteger.
|
||||||
* @param int $scale The scale of the number, positive or zero.
|
* @param int $scale The scale of the number. If negative, the scale will be set to zero
|
||||||
|
* and the unscaled value will be adjusted accordingly.
|
||||||
*
|
*
|
||||||
* @throws \InvalidArgumentException If the scale is negative.
|
* @throws MathException If the value is not valid, or is not convertible to a BigInteger.
|
||||||
*
|
*
|
||||||
* @psalm-pure
|
* @pure
|
||||||
*/
|
*/
|
||||||
public static function ofUnscaledValue(BigNumber|int|float|string $value, int $scale = 0) : BigDecimal
|
public static function ofUnscaledValue(BigNumber|int|float|string $value, int $scale = 0): BigDecimal
|
||||||
{
|
{
|
||||||
|
$value = BigInteger::of($value)->toString();
|
||||||
|
|
||||||
if ($scale < 0) {
|
if ($scale < 0) {
|
||||||
throw new \InvalidArgumentException('The scale cannot be negative.');
|
if ($value !== '0') {
|
||||||
|
$value .= str_repeat('0', -$scale);
|
||||||
|
}
|
||||||
|
$scale = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new BigDecimal((string) BigInteger::of($value), $scale);
|
return new BigDecimal($value, $scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a BigDecimal representing zero, with a scale of zero.
|
* Returns a BigDecimal representing zero, with a scale of zero.
|
||||||
*
|
*
|
||||||
* @psalm-pure
|
* @pure
|
||||||
*/
|
*/
|
||||||
public static function zero() : BigDecimal
|
public static function zero(): BigDecimal
|
||||||
{
|
{
|
||||||
/**
|
/** @var BigDecimal|null $zero */
|
||||||
* @psalm-suppress ImpureStaticVariable
|
|
||||||
* @var BigDecimal|null $zero
|
|
||||||
*/
|
|
||||||
static $zero;
|
static $zero;
|
||||||
|
|
||||||
if ($zero === null) {
|
if ($zero === null) {
|
||||||
@@ -98,14 +119,11 @@ final class BigDecimal extends BigNumber
|
|||||||
/**
|
/**
|
||||||
* Returns a BigDecimal representing one, with a scale of zero.
|
* Returns a BigDecimal representing one, with a scale of zero.
|
||||||
*
|
*
|
||||||
* @psalm-pure
|
* @pure
|
||||||
*/
|
*/
|
||||||
public static function one() : BigDecimal
|
public static function one(): BigDecimal
|
||||||
{
|
{
|
||||||
/**
|
/** @var BigDecimal|null $one */
|
||||||
* @psalm-suppress ImpureStaticVariable
|
|
||||||
* @var BigDecimal|null $one
|
|
||||||
*/
|
|
||||||
static $one;
|
static $one;
|
||||||
|
|
||||||
if ($one === null) {
|
if ($one === null) {
|
||||||
@@ -118,14 +136,11 @@ final class BigDecimal extends BigNumber
|
|||||||
/**
|
/**
|
||||||
* Returns a BigDecimal representing ten, with a scale of zero.
|
* Returns a BigDecimal representing ten, with a scale of zero.
|
||||||
*
|
*
|
||||||
* @psalm-pure
|
* @pure
|
||||||
*/
|
*/
|
||||||
public static function ten() : BigDecimal
|
public static function ten(): BigDecimal
|
||||||
{
|
{
|
||||||
/**
|
/** @var BigDecimal|null $ten */
|
||||||
* @psalm-suppress ImpureStaticVariable
|
|
||||||
* @var BigDecimal|null $ten
|
|
||||||
*/
|
|
||||||
static $ten;
|
static $ten;
|
||||||
|
|
||||||
if ($ten === null) {
|
if ($ten === null) {
|
||||||
@@ -143,8 +158,10 @@ final class BigDecimal extends BigNumber
|
|||||||
* @param BigNumber|int|float|string $that The number to add. Must be convertible to a BigDecimal.
|
* @param BigNumber|int|float|string $that The number to add. Must be convertible to a BigDecimal.
|
||||||
*
|
*
|
||||||
* @throws MathException If the number is not valid, or is not convertible to a BigDecimal.
|
* @throws MathException If the number is not valid, or is not convertible to a BigDecimal.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
public function plus(BigNumber|int|float|string $that) : BigDecimal
|
public function plus(BigNumber|int|float|string $that): BigDecimal
|
||||||
{
|
{
|
||||||
$that = BigDecimal::of($that);
|
$that = BigDecimal::of($that);
|
||||||
|
|
||||||
@@ -158,8 +175,8 @@ final class BigDecimal extends BigNumber
|
|||||||
|
|
||||||
[$a, $b] = $this->scaleValues($this, $that);
|
[$a, $b] = $this->scaleValues($this, $that);
|
||||||
|
|
||||||
$value = Calculator::get()->add($a, $b);
|
$value = CalculatorRegistry::get()->add($a, $b);
|
||||||
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
|
$scale = max($this->scale, $that->scale);
|
||||||
|
|
||||||
return new BigDecimal($value, $scale);
|
return new BigDecimal($value, $scale);
|
||||||
}
|
}
|
||||||
@@ -172,8 +189,10 @@ final class BigDecimal extends BigNumber
|
|||||||
* @param BigNumber|int|float|string $that The number to subtract. Must be convertible to a BigDecimal.
|
* @param BigNumber|int|float|string $that The number to subtract. Must be convertible to a BigDecimal.
|
||||||
*
|
*
|
||||||
* @throws MathException If the number is not valid, or is not convertible to a BigDecimal.
|
* @throws MathException If the number is not valid, or is not convertible to a BigDecimal.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
public function minus(BigNumber|int|float|string $that) : BigDecimal
|
public function minus(BigNumber|int|float|string $that): BigDecimal
|
||||||
{
|
{
|
||||||
$that = BigDecimal::of($that);
|
$that = BigDecimal::of($that);
|
||||||
|
|
||||||
@@ -183,8 +202,8 @@ final class BigDecimal extends BigNumber
|
|||||||
|
|
||||||
[$a, $b] = $this->scaleValues($this, $that);
|
[$a, $b] = $this->scaleValues($this, $that);
|
||||||
|
|
||||||
$value = Calculator::get()->sub($a, $b);
|
$value = CalculatorRegistry::get()->sub($a, $b);
|
||||||
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
|
$scale = max($this->scale, $that->scale);
|
||||||
|
|
||||||
return new BigDecimal($value, $scale);
|
return new BigDecimal($value, $scale);
|
||||||
}
|
}
|
||||||
@@ -196,9 +215,11 @@ final class BigDecimal extends BigNumber
|
|||||||
*
|
*
|
||||||
* @param BigNumber|int|float|string $that The multiplier. Must be convertible to a BigDecimal.
|
* @param BigNumber|int|float|string $that The multiplier. Must be convertible to a BigDecimal.
|
||||||
*
|
*
|
||||||
* @throws MathException If the multiplier is not a valid number, or is not convertible to a BigDecimal.
|
* @throws MathException If the multiplier is not valid, or is not convertible to a BigDecimal.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
public function multipliedBy(BigNumber|int|float|string $that) : BigDecimal
|
public function multipliedBy(BigNumber|int|float|string $that): BigDecimal
|
||||||
{
|
{
|
||||||
$that = BigDecimal::of($that);
|
$that = BigDecimal::of($that);
|
||||||
|
|
||||||
@@ -210,7 +231,7 @@ final class BigDecimal extends BigNumber
|
|||||||
return $that;
|
return $that;
|
||||||
}
|
}
|
||||||
|
|
||||||
$value = Calculator::get()->mul($this->value, $that->value);
|
$value = CalculatorRegistry::get()->mul($this->value, $that->value);
|
||||||
$scale = $this->scale + $that->scale;
|
$scale = $this->scale + $that->scale;
|
||||||
|
|
||||||
return new BigDecimal($value, $scale);
|
return new BigDecimal($value, $scale);
|
||||||
@@ -219,14 +240,19 @@ final class BigDecimal extends BigNumber
|
|||||||
/**
|
/**
|
||||||
* Returns the result of the division of this number by the given one, at the given scale.
|
* Returns the result of the division of this number by the given one, at the given scale.
|
||||||
*
|
*
|
||||||
* @param BigNumber|int|float|string $that The divisor.
|
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
|
||||||
* @param int|null $scale The desired scale, or null to use the scale of this number.
|
* @param int|null $scale The desired scale. Omitting this parameter is deprecated; it will be required in 0.15.
|
||||||
* @param RoundingMode $roundingMode An optional rounding mode, defaults to UNNECESSARY.
|
* @param RoundingMode $roundingMode An optional rounding mode, defaults to Unnecessary.
|
||||||
*
|
*
|
||||||
* @throws \InvalidArgumentException If the scale or rounding mode is invalid.
|
* @throws InvalidArgumentException If the scale is negative.
|
||||||
* @throws MathException If the number is invalid, is zero, or rounding was necessary.
|
* @throws MathException If the divisor is not valid, or is not convertible to a BigDecimal.
|
||||||
|
* @throws DivisionByZeroException If the divisor is zero.
|
||||||
|
* @throws RoundingNecessaryException If RoundingMode::Unnecessary is used and the result cannot be represented
|
||||||
|
* exactly at the given scale.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
public function dividedBy(BigNumber|int|float|string $that, ?int $scale = null, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal
|
public function dividedBy(BigNumber|int|float|string $that, ?int $scale = null, RoundingMode $roundingMode = RoundingMode::Unnecessary): BigDecimal
|
||||||
{
|
{
|
||||||
$that = BigDecimal::of($that);
|
$that = BigDecimal::of($that);
|
||||||
|
|
||||||
@@ -235,9 +261,15 @@ final class BigDecimal extends BigNumber
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($scale === null) {
|
if ($scale === null) {
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
|
trigger_error(
|
||||||
|
'Not passing a $scale to BigDecimal::dividedBy() is deprecated. ' .
|
||||||
|
'Use $a->dividedBy($b, $a->getScale(), $roundingMode) to retain current behavior.',
|
||||||
|
E_USER_DEPRECATED,
|
||||||
|
);
|
||||||
$scale = $this->scale;
|
$scale = $this->scale;
|
||||||
} elseif ($scale < 0) {
|
} elseif ($scale < 0) {
|
||||||
throw new \InvalidArgumentException('Scale cannot be negative.');
|
throw new InvalidArgumentException('Scale must not be negative.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($that->value === '1' && $that->scale === 0 && $scale === $this->scale) {
|
if ($that->value === '1' && $that->scale === 0 && $scale === $this->scale) {
|
||||||
@@ -247,7 +279,7 @@ final class BigDecimal extends BigNumber
|
|||||||
$p = $this->valueWithMinScale($that->scale + $scale);
|
$p = $this->valueWithMinScale($that->scale + $scale);
|
||||||
$q = $that->valueWithMinScale($this->scale - $scale);
|
$q = $that->valueWithMinScale($this->scale - $scale);
|
||||||
|
|
||||||
$result = Calculator::get()->divRound($p, $q, $roundingMode);
|
$result = CalculatorRegistry::get()->divRound($p, $q, $roundingMode);
|
||||||
|
|
||||||
return new BigDecimal($result, $scale);
|
return new BigDecimal($result, $scale);
|
||||||
}
|
}
|
||||||
@@ -257,12 +289,37 @@ final class BigDecimal extends BigNumber
|
|||||||
*
|
*
|
||||||
* The scale of the result is automatically calculated to fit all the fraction digits.
|
* The scale of the result is automatically calculated to fit all the fraction digits.
|
||||||
*
|
*
|
||||||
|
* @deprecated Will be removed in 0.15. Use dividedByExact() instead.
|
||||||
|
*
|
||||||
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
|
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
|
||||||
*
|
*
|
||||||
* @throws MathException If the divisor is not a valid number, is not convertible to a BigDecimal, is zero,
|
* @throws MathException If the divisor is not a valid number, is not convertible to a BigDecimal, is zero,
|
||||||
* or the result yields an infinite number of digits.
|
* or the result yields an infinite number of digits.
|
||||||
*/
|
*/
|
||||||
public function exactlyDividedBy(BigNumber|int|float|string $that) : BigDecimal
|
public function exactlyDividedBy(BigNumber|int|float|string $that): BigDecimal
|
||||||
|
{
|
||||||
|
trigger_error(
|
||||||
|
'BigDecimal::exactlyDividedBy() is deprecated and will be removed in 0.15. Use dividedByExact() instead.',
|
||||||
|
E_USER_DEPRECATED,
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->dividedByExact($that);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the exact result of the division of this number by the given one.
|
||||||
|
*
|
||||||
|
* The scale of the result is automatically calculated to fit all the fraction digits.
|
||||||
|
*
|
||||||
|
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
|
||||||
|
*
|
||||||
|
* @throws MathException If the divisor is not valid, or is not convertible to a BigDecimal.
|
||||||
|
* @throws DivisionByZeroException If the divisor is zero.
|
||||||
|
* @throws RoundingNecessaryException If the result yields an infinite number of digits.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function dividedByExact(BigNumber|int|float|string $that): BigDecimal
|
||||||
{
|
{
|
||||||
$that = BigDecimal::of($that);
|
$that = BigDecimal::of($that);
|
||||||
|
|
||||||
@@ -272,13 +329,13 @@ final class BigDecimal extends BigNumber
|
|||||||
|
|
||||||
[, $b] = $this->scaleValues($this, $that);
|
[, $b] = $this->scaleValues($this, $that);
|
||||||
|
|
||||||
$d = \rtrim($b, '0');
|
$d = rtrim($b, '0');
|
||||||
$scale = \strlen($b) - \strlen($d);
|
$scale = strlen($b) - strlen($d);
|
||||||
|
|
||||||
$calculator = Calculator::get();
|
$calculator = CalculatorRegistry::get();
|
||||||
|
|
||||||
foreach ([5, 2] as $prime) {
|
foreach ([5, 2] as $prime) {
|
||||||
for (;;) {
|
for (; ;) {
|
||||||
$lastDigit = (int) $d[-1];
|
$lastDigit = (int) $d[-1];
|
||||||
|
|
||||||
if ($lastDigit % $prime !== 0) {
|
if ($lastDigit % $prime !== 0) {
|
||||||
@@ -290,7 +347,7 @@ final class BigDecimal extends BigNumber
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->dividedBy($that, $scale)->stripTrailingZeros();
|
return $this->dividedBy($that, $scale)->strippedOfTrailingZeros();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -298,9 +355,11 @@ final class BigDecimal extends BigNumber
|
|||||||
*
|
*
|
||||||
* The result has a scale of `$this->scale * $exponent`.
|
* The result has a scale of `$this->scale * $exponent`.
|
||||||
*
|
*
|
||||||
* @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000.
|
* @throws InvalidArgumentException If the exponent is not in the range 0 to 1,000,000.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
public function power(int $exponent) : BigDecimal
|
public function power(int $exponent): BigDecimal
|
||||||
{
|
{
|
||||||
if ($exponent === 0) {
|
if ($exponent === 0) {
|
||||||
return BigDecimal::one();
|
return BigDecimal::one();
|
||||||
@@ -311,14 +370,14 @@ final class BigDecimal extends BigNumber
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($exponent < 0 || $exponent > Calculator::MAX_POWER) {
|
if ($exponent < 0 || $exponent > Calculator::MAX_POWER) {
|
||||||
throw new \InvalidArgumentException(\sprintf(
|
throw new InvalidArgumentException(sprintf(
|
||||||
'The exponent %d is not in the range 0 to %d.',
|
'The exponent %d is not in the range 0 to %d.',
|
||||||
$exponent,
|
$exponent,
|
||||||
Calculator::MAX_POWER
|
Calculator::MAX_POWER,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new BigDecimal(Calculator::get()->pow($this->value, $exponent), $this->scale * $exponent);
|
return new BigDecimal(CalculatorRegistry::get()->pow($this->value, $exponent), $this->scale * $exponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -326,11 +385,21 @@ final class BigDecimal extends BigNumber
|
|||||||
*
|
*
|
||||||
* The quotient has a scale of `0`.
|
* The quotient has a scale of `0`.
|
||||||
*
|
*
|
||||||
|
* Examples:
|
||||||
|
*
|
||||||
|
* - `7.5` quotient `3` returns `2`
|
||||||
|
* - `7.5` quotient `-3` returns `-2`
|
||||||
|
* - `-7.5` quotient `3` returns `-2`
|
||||||
|
* - `-7.5` quotient `-3` returns `2`
|
||||||
|
*
|
||||||
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
|
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
|
||||||
*
|
*
|
||||||
* @throws MathException If the divisor is not a valid decimal number, or is zero.
|
* @throws MathException If the divisor is not valid, or is not convertible to a BigDecimal.
|
||||||
|
* @throws DivisionByZeroException If the divisor is zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
public function quotient(BigNumber|int|float|string $that) : BigDecimal
|
public function quotient(BigNumber|int|float|string $that): BigDecimal
|
||||||
{
|
{
|
||||||
$that = BigDecimal::of($that);
|
$that = BigDecimal::of($that);
|
||||||
|
|
||||||
@@ -341,7 +410,7 @@ final class BigDecimal extends BigNumber
|
|||||||
$p = $this->valueWithMinScale($that->scale);
|
$p = $this->valueWithMinScale($that->scale);
|
||||||
$q = $that->valueWithMinScale($this->scale);
|
$q = $that->valueWithMinScale($this->scale);
|
||||||
|
|
||||||
$quotient = Calculator::get()->divQ($p, $q);
|
$quotient = CalculatorRegistry::get()->divQ($p, $q);
|
||||||
|
|
||||||
return new BigDecimal($quotient, 0);
|
return new BigDecimal($quotient, 0);
|
||||||
}
|
}
|
||||||
@@ -350,12 +419,23 @@ final class BigDecimal extends BigNumber
|
|||||||
* Returns the remainder of the division of this number by the given one.
|
* Returns the remainder of the division of this number by the given one.
|
||||||
*
|
*
|
||||||
* The remainder has a scale of `max($this->scale, $that->scale)`.
|
* The remainder has a scale of `max($this->scale, $that->scale)`.
|
||||||
|
* The remainder, when non-zero, has the same sign as the dividend.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
*
|
||||||
|
* - `7.5` remainder `3` returns `1.5`
|
||||||
|
* - `7.5` remainder `-3` returns `1.5`
|
||||||
|
* - `-7.5` remainder `3` returns `-1.5`
|
||||||
|
* - `-7.5` remainder `-3` returns `-1.5`
|
||||||
*
|
*
|
||||||
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
|
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
|
||||||
*
|
*
|
||||||
* @throws MathException If the divisor is not a valid decimal number, or is zero.
|
* @throws MathException If the divisor is not valid, or is not convertible to a BigDecimal.
|
||||||
|
* @throws DivisionByZeroException If the divisor is zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
public function remainder(BigNumber|int|float|string $that) : BigDecimal
|
public function remainder(BigNumber|int|float|string $that): BigDecimal
|
||||||
{
|
{
|
||||||
$that = BigDecimal::of($that);
|
$that = BigDecimal::of($that);
|
||||||
|
|
||||||
@@ -366,9 +446,9 @@ final class BigDecimal extends BigNumber
|
|||||||
$p = $this->valueWithMinScale($that->scale);
|
$p = $this->valueWithMinScale($that->scale);
|
||||||
$q = $that->valueWithMinScale($this->scale);
|
$q = $that->valueWithMinScale($this->scale);
|
||||||
|
|
||||||
$remainder = Calculator::get()->divR($p, $q);
|
$remainder = CalculatorRegistry::get()->divR($p, $q);
|
||||||
|
|
||||||
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
|
$scale = max($this->scale, $that->scale);
|
||||||
|
|
||||||
return new BigDecimal($remainder, $scale);
|
return new BigDecimal($remainder, $scale);
|
||||||
}
|
}
|
||||||
@@ -378,15 +458,23 @@ final class BigDecimal extends BigNumber
|
|||||||
*
|
*
|
||||||
* The quotient has a scale of `0`, and the remainder has a scale of `max($this->scale, $that->scale)`.
|
* The quotient has a scale of `0`, and the remainder has a scale of `max($this->scale, $that->scale)`.
|
||||||
*
|
*
|
||||||
|
* Examples:
|
||||||
|
*
|
||||||
|
* - `7.5` quotientAndRemainder `3` returns [`2`, `1.5`]
|
||||||
|
* - `7.5` quotientAndRemainder `-3` returns [`-2`, `1.5`]
|
||||||
|
* - `-7.5` quotientAndRemainder `3` returns [`-2`, `-1.5`]
|
||||||
|
* - `-7.5` quotientAndRemainder `-3` returns [`2`, `-1.5`]
|
||||||
|
*
|
||||||
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
|
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
|
||||||
*
|
*
|
||||||
* @return BigDecimal[] An array containing the quotient and the remainder.
|
* @return array{BigDecimal, BigDecimal} An array containing the quotient and the remainder.
|
||||||
*
|
*
|
||||||
* @psalm-return array{BigDecimal, BigDecimal}
|
* @throws MathException If the divisor is not valid, or is not convertible to a BigDecimal.
|
||||||
|
* @throws DivisionByZeroException If the divisor is zero.
|
||||||
*
|
*
|
||||||
* @throws MathException If the divisor is not a valid decimal number, or is zero.
|
* @pure
|
||||||
*/
|
*/
|
||||||
public function quotientAndRemainder(BigNumber|int|float|string $that) : array
|
public function quotientAndRemainder(BigNumber|int|float|string $that): array
|
||||||
{
|
{
|
||||||
$that = BigDecimal::of($that);
|
$that = BigDecimal::of($that);
|
||||||
|
|
||||||
@@ -397,9 +485,9 @@ final class BigDecimal extends BigNumber
|
|||||||
$p = $this->valueWithMinScale($that->scale);
|
$p = $this->valueWithMinScale($that->scale);
|
||||||
$q = $that->valueWithMinScale($this->scale);
|
$q = $that->valueWithMinScale($this->scale);
|
||||||
|
|
||||||
[$quotient, $remainder] = Calculator::get()->divQR($p, $q);
|
[$quotient, $remainder] = CalculatorRegistry::get()->divQR($p, $q);
|
||||||
|
|
||||||
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
|
$scale = max($this->scale, $that->scale);
|
||||||
|
|
||||||
$quotient = new BigDecimal($quotient, 0);
|
$quotient = new BigDecimal($quotient, 0);
|
||||||
$remainder = new BigDecimal($remainder, $scale);
|
$remainder = new BigDecimal($remainder, $scale);
|
||||||
@@ -408,15 +496,34 @@ final class BigDecimal extends BigNumber
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the square root of this number, rounded down to the given number of decimals.
|
* Returns the square root of this number, rounded to the given scale according to the given rounding mode.
|
||||||
*
|
*
|
||||||
* @throws \InvalidArgumentException If the scale is negative.
|
* @param int $scale The target scale. Must be non-negative.
|
||||||
|
* @param RoundingMode $roundingMode The rounding mode to use, defaults to Down.
|
||||||
|
* ⚠️ WARNING: the default rounding mode was kept as Down for backward
|
||||||
|
* compatibility, but will change to Unnecessary in version 0.15. Pass a rounding
|
||||||
|
* mode explicitly to avoid this upcoming breaking change.
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException If the scale is negative.
|
||||||
* @throws NegativeNumberException If this number is negative.
|
* @throws NegativeNumberException If this number is negative.
|
||||||
|
* @throws RoundingNecessaryException If RoundingMode::Unnecessary is used and the result cannot be represented
|
||||||
|
* exactly at the given scale.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
public function sqrt(int $scale) : BigDecimal
|
public function sqrt(int $scale, RoundingMode $roundingMode = RoundingMode::Down): BigDecimal
|
||||||
{
|
{
|
||||||
|
if (func_num_args() === 1) {
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
|
trigger_error(
|
||||||
|
'The default rounding mode of BigDecimal::sqrt() will change from Down to Unnecessary in version 0.15. ' .
|
||||||
|
'Pass a rounding mode explicitly to avoid this breaking change.',
|
||||||
|
E_USER_DEPRECATED,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if ($scale < 0) {
|
if ($scale < 0) {
|
||||||
throw new \InvalidArgumentException('Scale cannot be negative.');
|
throw new InvalidArgumentException('Scale must not be negative.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->value === '0') {
|
if ($this->value === '0') {
|
||||||
@@ -428,30 +535,48 @@ final class BigDecimal extends BigNumber
|
|||||||
}
|
}
|
||||||
|
|
||||||
$value = $this->value;
|
$value = $this->value;
|
||||||
$addDigits = 2 * $scale - $this->scale;
|
$inputScale = $this->scale;
|
||||||
|
|
||||||
if ($addDigits > 0) {
|
if ($inputScale % 2 !== 0) {
|
||||||
// add zeros
|
$value .= '0';
|
||||||
$value .= \str_repeat('0', $addDigits);
|
$inputScale++;
|
||||||
} elseif ($addDigits < 0) {
|
|
||||||
// trim digits
|
|
||||||
if (-$addDigits >= \strlen($this->value)) {
|
|
||||||
// requesting a scale too low, will always yield a zero result
|
|
||||||
return new BigDecimal('0', $scale);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$value = \substr($value, 0, $addDigits);
|
$calculator = CalculatorRegistry::get();
|
||||||
|
|
||||||
|
// Keep one extra digit for rounding.
|
||||||
|
$intermediateScale = max($scale, intdiv($inputScale, 2)) + 1;
|
||||||
|
$value .= str_repeat('0', 2 * $intermediateScale - $inputScale);
|
||||||
|
|
||||||
|
$sqrt = $calculator->sqrt($value);
|
||||||
|
$isExact = $calculator->mul($sqrt, $sqrt) === $value;
|
||||||
|
|
||||||
|
if (! $isExact) {
|
||||||
|
if ($roundingMode === RoundingMode::Unnecessary) {
|
||||||
|
throw RoundingNecessaryException::roundingNecessary();
|
||||||
}
|
}
|
||||||
|
|
||||||
$value = Calculator::get()->sqrt($value);
|
// Non-perfect-square sqrt is irrational, so the true value is strictly above this sqrt floor.
|
||||||
|
// Add one at the intermediate scale to guarantee Up/Ceiling round up at the target scale.
|
||||||
|
if (in_array($roundingMode, [RoundingMode::Up, RoundingMode::Ceiling], true)) {
|
||||||
|
$sqrt = $calculator->add($sqrt, '1');
|
||||||
|
}
|
||||||
|
|
||||||
return new BigDecimal($value, $scale);
|
// Irrational sqrt cannot land exactly on a midpoint; treat tie-to-down modes as HalfUp.
|
||||||
|
elseif (in_array($roundingMode, [RoundingMode::HalfDown, RoundingMode::HalfEven, RoundingMode::HalfFloor], true)) {
|
||||||
|
$roundingMode = RoundingMode::HalfUp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (new BigDecimal($sqrt, $intermediateScale))->toScale($scale, $roundingMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a copy of this BigDecimal with the decimal point moved $n places to the left.
|
* Returns a copy of this BigDecimal with the decimal point moved to the left by the given number of places.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
public function withPointMovedLeft(int $n) : BigDecimal
|
public function withPointMovedLeft(int $n): BigDecimal
|
||||||
{
|
{
|
||||||
if ($n === 0) {
|
if ($n === 0) {
|
||||||
return $this;
|
return $this;
|
||||||
@@ -465,9 +590,11 @@ final class BigDecimal extends BigNumber
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a copy of this BigDecimal with the decimal point moved $n places to the right.
|
* Returns a copy of this BigDecimal with the decimal point moved to the right by the given number of places.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
public function withPointMovedRight(int $n) : BigDecimal
|
public function withPointMovedRight(int $n): BigDecimal
|
||||||
{
|
{
|
||||||
if ($n === 0) {
|
if ($n === 0) {
|
||||||
return $this;
|
return $this;
|
||||||
@@ -482,7 +609,7 @@ final class BigDecimal extends BigNumber
|
|||||||
|
|
||||||
if ($scale < 0) {
|
if ($scale < 0) {
|
||||||
if ($value !== '0') {
|
if ($value !== '0') {
|
||||||
$value .= \str_repeat('0', -$scale);
|
$value .= str_repeat('0', -$scale);
|
||||||
}
|
}
|
||||||
$scale = 0;
|
$scale = 0;
|
||||||
}
|
}
|
||||||
@@ -492,20 +619,37 @@ final class BigDecimal extends BigNumber
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a copy of this BigDecimal with any trailing zeros removed from the fractional part.
|
* Returns a copy of this BigDecimal with any trailing zeros removed from the fractional part.
|
||||||
|
*
|
||||||
|
* @deprecated Use strippedOfTrailingZeros() instead.
|
||||||
*/
|
*/
|
||||||
public function stripTrailingZeros() : BigDecimal
|
public function stripTrailingZeros(): BigDecimal
|
||||||
|
{
|
||||||
|
trigger_error(
|
||||||
|
'BigDecimal::stripTrailingZeros() is deprecated, use strippedOfTrailingZeros() instead.',
|
||||||
|
E_USER_DEPRECATED,
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->strippedOfTrailingZeros();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a copy of this BigDecimal with any trailing zeros removed from the fractional part.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function strippedOfTrailingZeros(): BigDecimal
|
||||||
{
|
{
|
||||||
if ($this->scale === 0) {
|
if ($this->scale === 0) {
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
$trimmedValue = \rtrim($this->value, '0');
|
$trimmedValue = rtrim($this->value, '0');
|
||||||
|
|
||||||
if ($trimmedValue === '') {
|
if ($trimmedValue === '') {
|
||||||
return BigDecimal::zero();
|
return BigDecimal::zero();
|
||||||
}
|
}
|
||||||
|
|
||||||
$trimmableZeros = \strlen($this->value) - \strlen($trimmedValue);
|
$trimmableZeros = strlen($this->value) - strlen($trimmedValue);
|
||||||
|
|
||||||
if ($trimmableZeros === 0) {
|
if ($trimmableZeros === 0) {
|
||||||
return $this;
|
return $this;
|
||||||
@@ -515,30 +659,20 @@ final class BigDecimal extends BigNumber
|
|||||||
$trimmableZeros = $this->scale;
|
$trimmableZeros = $this->scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
$value = \substr($this->value, 0, -$trimmableZeros);
|
$value = substr($this->value, 0, -$trimmableZeros);
|
||||||
$scale = $this->scale - $trimmableZeros;
|
$scale = $this->scale - $trimmableZeros;
|
||||||
|
|
||||||
return new BigDecimal($value, $scale);
|
return new BigDecimal($value, $scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
#[Override]
|
||||||
* Returns the absolute value of this number.
|
public function negated(): static
|
||||||
*/
|
|
||||||
public function abs() : BigDecimal
|
|
||||||
{
|
{
|
||||||
return $this->isNegative() ? $this->negated() : $this;
|
return new BigDecimal(CalculatorRegistry::get()->neg($this->value), $this->scale);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the negated value of this number.
|
|
||||||
*/
|
|
||||||
public function negated() : BigDecimal
|
|
||||||
{
|
|
||||||
return new BigDecimal(Calculator::get()->neg($this->value), $this->scale);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function compareTo(BigNumber|int|float|string $that) : int
|
public function compareTo(BigNumber|int|float|string $that): int
|
||||||
{
|
{
|
||||||
$that = BigNumber::of($that);
|
$that = BigNumber::of($that);
|
||||||
|
|
||||||
@@ -549,24 +683,30 @@ final class BigDecimal extends BigNumber
|
|||||||
if ($that instanceof BigDecimal) {
|
if ($that instanceof BigDecimal) {
|
||||||
[$a, $b] = $this->scaleValues($this, $that);
|
[$a, $b] = $this->scaleValues($this, $that);
|
||||||
|
|
||||||
return Calculator::get()->cmp($a, $b);
|
return CalculatorRegistry::get()->cmp($a, $b);
|
||||||
}
|
}
|
||||||
|
|
||||||
return - $that->compareTo($this);
|
return -$that->compareTo($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function getSign() : int
|
public function getSign(): int
|
||||||
{
|
{
|
||||||
return ($this->value === '0') ? 0 : (($this->value[0] === '-') ? -1 : 1);
|
return ($this->value === '0') ? 0 : (($this->value[0] === '-') ? -1 : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUnscaledValue() : BigInteger
|
/**
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function getUnscaledValue(): BigInteger
|
||||||
{
|
{
|
||||||
return self::newBigInteger($this->value);
|
return self::newBigInteger($this->value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getScale() : int
|
/**
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function getScale(): int
|
||||||
{
|
{
|
||||||
return $this->scale;
|
return $this->scale;
|
||||||
}
|
}
|
||||||
@@ -584,6 +724,8 @@ final class BigDecimal extends BigNumber
|
|||||||
* 123.456 => 6
|
* 123.456 => 6
|
||||||
* 0.00123 => 3
|
* 0.00123 => 3
|
||||||
* 0.0012300 => 5
|
* 0.0012300 => 5
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
public function getPrecision(): int
|
public function getPrecision(): int
|
||||||
{
|
{
|
||||||
@@ -593,7 +735,7 @@ final class BigDecimal extends BigNumber
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
$length = \strlen($value);
|
$length = strlen($value);
|
||||||
|
|
||||||
return ($value[0] === '-') ? $length - 1 : $length;
|
return ($value[0] === '-') ? $length - 1 : $length;
|
||||||
}
|
}
|
||||||
@@ -602,16 +744,23 @@ final class BigDecimal extends BigNumber
|
|||||||
* Returns a string representing the integral part of this decimal number.
|
* Returns a string representing the integral part of this decimal number.
|
||||||
*
|
*
|
||||||
* Example: `-123.456` => `-123`.
|
* Example: `-123.456` => `-123`.
|
||||||
|
*
|
||||||
|
* @deprecated Will be removed in 0.15 and re-introduced as returning BigInteger in 0.16.
|
||||||
*/
|
*/
|
||||||
public function getIntegralPart() : string
|
public function getIntegralPart(): string
|
||||||
{
|
{
|
||||||
|
trigger_error(
|
||||||
|
'BigDecimal::getIntegralPart() is deprecated and will be removed in 0.15. It will be re-introduced as returning BigInteger in 0.16.',
|
||||||
|
E_USER_DEPRECATED,
|
||||||
|
);
|
||||||
|
|
||||||
if ($this->scale === 0) {
|
if ($this->scale === 0) {
|
||||||
return $this->value;
|
return $this->value;
|
||||||
}
|
}
|
||||||
|
|
||||||
$value = $this->getUnscaledValueWithLeadingZeros();
|
$value = $this->getUnscaledValueWithLeadingZeros();
|
||||||
|
|
||||||
return \substr($value, 0, -$this->scale);
|
return substr($value, 0, -$this->scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -620,28 +769,43 @@ final class BigDecimal extends BigNumber
|
|||||||
* If the scale is zero, an empty string is returned.
|
* If the scale is zero, an empty string is returned.
|
||||||
*
|
*
|
||||||
* Examples: `-123.456` => '456', `123` => ''.
|
* Examples: `-123.456` => '456', `123` => ''.
|
||||||
|
*
|
||||||
|
* @deprecated Will be removed in 0.15 and re-introduced as returning BigDecimal with a different meaning in 0.16.
|
||||||
*/
|
*/
|
||||||
public function getFractionalPart() : string
|
public function getFractionalPart(): string
|
||||||
{
|
{
|
||||||
|
trigger_error(
|
||||||
|
'BigDecimal::getFractionalPart() is deprecated and will be removed in 0.15. It will be re-introduced as returning BigDecimal with a different meaning in 0.16.',
|
||||||
|
E_USER_DEPRECATED,
|
||||||
|
);
|
||||||
|
|
||||||
if ($this->scale === 0) {
|
if ($this->scale === 0) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
$value = $this->getUnscaledValueWithLeadingZeros();
|
$value = $this->getUnscaledValueWithLeadingZeros();
|
||||||
|
|
||||||
return \substr($value, -$this->scale);
|
return substr($value, -$this->scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether this decimal number has a non-zero fractional part.
|
* Returns whether this decimal number has a non-zero fractional part.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
public function hasNonZeroFractionalPart() : bool
|
public function hasNonZeroFractionalPart(): bool
|
||||||
{
|
{
|
||||||
return $this->getFractionalPart() !== \str_repeat('0', $this->scale);
|
if ($this->scale === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = $this->getUnscaledValueWithLeadingZeros();
|
||||||
|
|
||||||
|
return substr($value, -$this->scale) !== str_repeat('0', $this->scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function toBigInteger() : BigInteger
|
public function toBigInteger(): BigInteger
|
||||||
{
|
{
|
||||||
$zeroScaleDecimal = $this->scale === 0 ? $this : $this->dividedBy(1, 0);
|
$zeroScaleDecimal = $this->scale === 0 ? $this : $this->dividedBy(1, 0);
|
||||||
|
|
||||||
@@ -649,22 +813,22 @@ final class BigDecimal extends BigNumber
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function toBigDecimal() : BigDecimal
|
public function toBigDecimal(): BigDecimal
|
||||||
{
|
{
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function toBigRational() : BigRational
|
public function toBigRational(): BigRational
|
||||||
{
|
{
|
||||||
$numerator = self::newBigInteger($this->value);
|
$numerator = self::newBigInteger($this->value);
|
||||||
$denominator = self::newBigInteger('1' . \str_repeat('0', $this->scale));
|
$denominator = self::newBigInteger('1' . str_repeat('0', $this->scale));
|
||||||
|
|
||||||
return self::newBigRational($numerator, $denominator, false);
|
return self::newBigRational($numerator, $denominator, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal
|
public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::Unnecessary): BigDecimal
|
||||||
{
|
{
|
||||||
if ($scale === $this->scale) {
|
if ($scale === $this->scale) {
|
||||||
return $this;
|
return $this;
|
||||||
@@ -674,27 +838,32 @@ final class BigDecimal extends BigNumber
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function toInt() : int
|
public function toInt(): int
|
||||||
{
|
{
|
||||||
return $this->toBigInteger()->toInt();
|
return $this->toBigInteger()->toInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function toFloat() : float
|
public function toFloat(): float
|
||||||
{
|
{
|
||||||
return (float) (string) $this;
|
return (float) $this->toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return numeric-string
|
||||||
|
*/
|
||||||
#[Override]
|
#[Override]
|
||||||
public function __toString() : string
|
public function toString(): string
|
||||||
{
|
{
|
||||||
if ($this->scale === 0) {
|
if ($this->scale === 0) {
|
||||||
|
/** @var numeric-string */
|
||||||
return $this->value;
|
return $this->value;
|
||||||
}
|
}
|
||||||
|
|
||||||
$value = $this->getUnscaledValueWithLeadingZeros();
|
$value = $this->getUnscaledValueWithLeadingZeros();
|
||||||
|
|
||||||
return \substr($value, 0, -$this->scale) . '.' . \substr($value, -$this->scale);
|
/** @phpstan-ignore return.type */
|
||||||
|
return substr($value, 0, -$this->scale) . '.' . substr($value, -$this->scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -713,47 +882,59 @@ final class BigDecimal extends BigNumber
|
|||||||
* This method is only here to allow unserializing the object and cannot be accessed directly.
|
* This method is only here to allow unserializing the object and cannot be accessed directly.
|
||||||
*
|
*
|
||||||
* @internal
|
* @internal
|
||||||
* @psalm-suppress RedundantPropertyInitializationCheck
|
|
||||||
*
|
*
|
||||||
* @param array{value: string, scale: int} $data
|
* @param array{value: string, scale: int} $data
|
||||||
*
|
*
|
||||||
* @throws \LogicException
|
* @throws LogicException
|
||||||
*/
|
*/
|
||||||
public function __unserialize(array $data): void
|
public function __unserialize(array $data): void
|
||||||
{
|
{
|
||||||
|
/** @phpstan-ignore isset.initializedProperty */
|
||||||
if (isset($this->value)) {
|
if (isset($this->value)) {
|
||||||
throw new \LogicException('__unserialize() is an internal function, it must not be called directly.');
|
throw new LogicException('__unserialize() is an internal function, it must not be called directly.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @phpstan-ignore deadCode.unreachable */
|
||||||
$this->value = $data['value'];
|
$this->value = $data['value'];
|
||||||
$this->scale = $data['scale'];
|
$this->scale = $data['scale'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
protected static function from(BigNumber $number): static
|
||||||
|
{
|
||||||
|
return $number->toBigDecimal();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Puts the internal values of the given decimal numbers on the same scale.
|
* Puts the internal values of the given decimal numbers on the same scale.
|
||||||
*
|
*
|
||||||
* @return array{string, string} The scaled integer values of $x and $y.
|
* @return array{string, string} The scaled integer values of $x and $y.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
private function scaleValues(BigDecimal $x, BigDecimal $y) : array
|
private function scaleValues(BigDecimal $x, BigDecimal $y): array
|
||||||
{
|
{
|
||||||
$a = $x->value;
|
$a = $x->value;
|
||||||
$b = $y->value;
|
$b = $y->value;
|
||||||
|
|
||||||
if ($b !== '0' && $x->scale > $y->scale) {
|
if ($b !== '0' && $x->scale > $y->scale) {
|
||||||
$b .= \str_repeat('0', $x->scale - $y->scale);
|
$b .= str_repeat('0', $x->scale - $y->scale);
|
||||||
} elseif ($a !== '0' && $x->scale < $y->scale) {
|
} elseif ($a !== '0' && $x->scale < $y->scale) {
|
||||||
$a .= \str_repeat('0', $y->scale - $x->scale);
|
$a .= str_repeat('0', $y->scale - $x->scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [$a, $b];
|
return [$a, $b];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function valueWithMinScale(int $scale) : string
|
/**
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
private function valueWithMinScale(int $scale): string
|
||||||
{
|
{
|
||||||
$value = $this->value;
|
$value = $this->value;
|
||||||
|
|
||||||
if ($this->value !== '0' && $scale > $this->scale) {
|
if ($this->value !== '0' && $scale > $this->scale) {
|
||||||
$value .= \str_repeat('0', $scale - $this->scale);
|
$value .= str_repeat('0', $scale - $this->scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $value;
|
return $value;
|
||||||
@@ -761,13 +942,15 @@ final class BigDecimal extends BigNumber
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds leading zeros if necessary to the unscaled value to represent the full decimal number.
|
* Adds leading zeros if necessary to the unscaled value to represent the full decimal number.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
private function getUnscaledValueWithLeadingZeros() : string
|
private function getUnscaledValueWithLeadingZeros(): string
|
||||||
{
|
{
|
||||||
$value = $this->value;
|
$value = $this->value;
|
||||||
$targetLength = $this->scale + 1;
|
$targetLength = $this->scale + 1;
|
||||||
$negative = ($value[0] === '-');
|
$negative = ($value[0] === '-');
|
||||||
$length = \strlen($value);
|
$length = strlen($value);
|
||||||
|
|
||||||
if ($negative) {
|
if ($negative) {
|
||||||
$length--;
|
$length--;
|
||||||
@@ -778,10 +961,10 @@ final class BigDecimal extends BigNumber
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($negative) {
|
if ($negative) {
|
||||||
$value = \substr($value, 1);
|
$value = substr($value, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
$value = \str_pad($value, $targetLength, '0', STR_PAD_LEFT);
|
$value = str_pad($value, $targetLength, '0', STR_PAD_LEFT);
|
||||||
|
|
||||||
if ($negative) {
|
if ($negative) {
|
||||||
$value = '-' . $value;
|
$value = '-' . $value;
|
||||||
|
|||||||
Vendored
+493
-204
File diff suppressed because it is too large
Load Diff
Vendored
+535
-338
@@ -8,14 +8,39 @@ use Brick\Math\Exception\DivisionByZeroException;
|
|||||||
use Brick\Math\Exception\MathException;
|
use Brick\Math\Exception\MathException;
|
||||||
use Brick\Math\Exception\NumberFormatException;
|
use Brick\Math\Exception\NumberFormatException;
|
||||||
use Brick\Math\Exception\RoundingNecessaryException;
|
use Brick\Math\Exception\RoundingNecessaryException;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use JsonSerializable;
|
||||||
use Override;
|
use Override;
|
||||||
|
use Stringable;
|
||||||
|
|
||||||
|
use function array_shift;
|
||||||
|
use function assert;
|
||||||
|
use function filter_var;
|
||||||
|
use function is_float;
|
||||||
|
use function is_int;
|
||||||
|
use function is_nan;
|
||||||
|
use function is_null;
|
||||||
|
use function ltrim;
|
||||||
|
use function preg_match;
|
||||||
|
use function str_contains;
|
||||||
|
use function str_repeat;
|
||||||
|
use function strlen;
|
||||||
|
use function substr;
|
||||||
|
use function trigger_error;
|
||||||
|
|
||||||
|
use const E_USER_DEPRECATED;
|
||||||
|
use const FILTER_VALIDATE_INT;
|
||||||
|
use const PREG_UNMATCHED_AS_NULL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Common interface for arbitrary-precision rational numbers.
|
* Base class for arbitrary-precision numbers.
|
||||||
*
|
*
|
||||||
* @psalm-immutable
|
* This class is sealed: it is part of the public API but should not be subclassed in userland.
|
||||||
|
* Protected methods may change in any version.
|
||||||
|
*
|
||||||
|
* @phpstan-sealed BigInteger|BigDecimal|BigRational
|
||||||
*/
|
*/
|
||||||
abstract class BigNumber implements \JsonSerializable
|
abstract readonly class BigNumber implements JsonSerializable, Stringable
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The regular expression used to parse integer or decimal numbers.
|
* The regular expression used to parse integer or decimal numbers.
|
||||||
@@ -36,34 +61,37 @@ abstract class BigNumber implements \JsonSerializable
|
|||||||
'/^' .
|
'/^' .
|
||||||
'(?<sign>[\-\+])?' .
|
'(?<sign>[\-\+])?' .
|
||||||
'(?<numerator>[0-9]+)' .
|
'(?<numerator>[0-9]+)' .
|
||||||
'\/?' .
|
'\/' .
|
||||||
'(?<denominator>[0-9]+)' .
|
'(?<denominator>[0-9]+)' .
|
||||||
'$/';
|
'$/';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a BigNumber of the given value.
|
* Creates a BigNumber of the given value.
|
||||||
*
|
*
|
||||||
* The concrete return type is dependent on the given value, with the following rules:
|
* When of() is called on BigNumber, the concrete return type is dependent on the given value, with the following
|
||||||
|
* rules:
|
||||||
*
|
*
|
||||||
* - BigNumber instances are returned as is
|
* - BigNumber instances are returned as is
|
||||||
* - integer numbers are returned as BigInteger
|
* - integer numbers are returned as BigInteger
|
||||||
* - floating point numbers are converted to a string then parsed as such
|
* - floating point numbers are converted to a string then parsed as such (deprecated, will be removed in 0.15)
|
||||||
* - strings containing a `/` character are returned as BigRational
|
* - strings containing a `/` character are returned as BigRational
|
||||||
* - strings containing a `.` character or using an exponential notation are returned as BigDecimal
|
* - strings containing a `.` character or using an exponential notation are returned as BigDecimal
|
||||||
* - strings containing only digits with an optional leading `+` or `-` sign are returned as BigInteger
|
* - strings containing only digits with an optional leading `+` or `-` sign are returned as BigInteger
|
||||||
*
|
*
|
||||||
|
* When of() is called on BigInteger, BigDecimal, or BigRational, the resulting number is converted to an instance
|
||||||
|
* of the subclass when possible; otherwise a RoundingNecessaryException exception is thrown.
|
||||||
|
*
|
||||||
* @throws NumberFormatException If the format of the number is not valid.
|
* @throws NumberFormatException If the format of the number is not valid.
|
||||||
* @throws DivisionByZeroException If the value represents a rational number with a denominator of zero.
|
* @throws DivisionByZeroException If the value represents a rational number with a denominator of zero.
|
||||||
* @throws RoundingNecessaryException If the value cannot be converted to an instance of the subclass without rounding.
|
* @throws RoundingNecessaryException If the value cannot be converted to an instance of the subclass without rounding.
|
||||||
*
|
*
|
||||||
* @psalm-pure
|
* @pure
|
||||||
*/
|
*/
|
||||||
final public static function of(BigNumber|int|float|string $value) : static
|
final public static function of(BigNumber|int|float|string $value): static
|
||||||
{
|
{
|
||||||
$value = self::_of($value);
|
$value = self::_of($value);
|
||||||
|
|
||||||
if (static::class === BigNumber::class) {
|
if (static::class === BigNumber::class) {
|
||||||
// https://github.com/vimeo/psalm/issues/10309
|
|
||||||
assert($value instanceof static);
|
assert($value instanceof static);
|
||||||
|
|
||||||
return $value;
|
return $value;
|
||||||
@@ -72,29 +100,487 @@ abstract class BigNumber implements \JsonSerializable
|
|||||||
return static::from($value);
|
return static::from($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a BigNumber of the given value, or returns null if the input is null.
|
||||||
|
*
|
||||||
|
* Behaves like of() for non-null values.
|
||||||
|
*
|
||||||
|
* @see BigNumber::of()
|
||||||
|
*
|
||||||
|
* @throws NumberFormatException If the format of the number is not valid.
|
||||||
|
* @throws DivisionByZeroException If the value represents a rational number with a denominator of zero.
|
||||||
|
* @throws RoundingNecessaryException If the value cannot be converted to an instance of the subclass without rounding.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public static function ofNullable(BigNumber|int|float|string|null $value): ?static
|
||||||
|
{
|
||||||
|
if (is_null($value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return static::of($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the minimum of the given values.
|
||||||
|
*
|
||||||
|
* If several values are equal and minimal, the first one is returned.
|
||||||
|
* This can affect the concrete return type when calling this method on BigNumber.
|
||||||
|
*
|
||||||
|
* @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers must be convertible to an
|
||||||
|
* instance of the class this method is called on.
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException If no values are given.
|
||||||
|
* @throws MathException If a number is not valid, or is not convertible to an instance of the class
|
||||||
|
* this method is called on.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public static function min(BigNumber|int|float|string ...$values): static
|
||||||
|
{
|
||||||
|
$min = null;
|
||||||
|
|
||||||
|
foreach ($values as $value) {
|
||||||
|
$value = static::of($value);
|
||||||
|
|
||||||
|
if ($min === null || $value->isLessThan($min)) {
|
||||||
|
$min = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($min === null) {
|
||||||
|
throw new InvalidArgumentException(__METHOD__ . '() expects at least one value.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $min;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the maximum of the given values.
|
||||||
|
*
|
||||||
|
* If several values are equal and maximal, the first one is returned.
|
||||||
|
* This can affect the concrete return type when calling this method on BigNumber.
|
||||||
|
*
|
||||||
|
* @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers must be convertible to an
|
||||||
|
* instance of the class this method is called on.
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException If no values are given.
|
||||||
|
* @throws MathException If a number is not valid, or is not convertible to an instance of the class
|
||||||
|
* this method is called on.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public static function max(BigNumber|int|float|string ...$values): static
|
||||||
|
{
|
||||||
|
$max = null;
|
||||||
|
|
||||||
|
foreach ($values as $value) {
|
||||||
|
$value = static::of($value);
|
||||||
|
|
||||||
|
if ($max === null || $value->isGreaterThan($max)) {
|
||||||
|
$max = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($max === null) {
|
||||||
|
throw new InvalidArgumentException(__METHOD__ . '() expects at least one value.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $max;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the sum of the given values.
|
||||||
|
*
|
||||||
|
* When called on BigNumber, sum() accepts any supported type and returns a result whose type is the widest among
|
||||||
|
* the given values (BigInteger < BigDecimal < BigRational).
|
||||||
|
*
|
||||||
|
* When called on BigInteger, BigDecimal, or BigRational, sum() requires that all values can be converted to that
|
||||||
|
* specific subclass, and returns a result of the same type.
|
||||||
|
*
|
||||||
|
* @param BigNumber|int|float|string ...$values The numbers to add. All the numbers must be convertible to an
|
||||||
|
* instance of the class this method is called on.
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException If no values are given.
|
||||||
|
* @throws MathException If a number is not valid, or is not convertible to an instance of the class
|
||||||
|
* this method is called on.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public static function sum(BigNumber|int|float|string ...$values): static
|
||||||
|
{
|
||||||
|
$first = array_shift($values);
|
||||||
|
|
||||||
|
if ($first === null) {
|
||||||
|
throw new InvalidArgumentException(__METHOD__ . '() expects at least one value.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$sum = static::of($first);
|
||||||
|
|
||||||
|
foreach ($values as $value) {
|
||||||
|
$sum = self::add($sum, static::of($value));
|
||||||
|
}
|
||||||
|
|
||||||
|
assert($sum instanceof static);
|
||||||
|
|
||||||
|
return $sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this number is equal to the given one.
|
||||||
|
*
|
||||||
|
* @throws MathException If the given number is not valid.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function isEqualTo(BigNumber|int|float|string $that): bool
|
||||||
|
{
|
||||||
|
return $this->compareTo($that) === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this number is strictly less than the given one.
|
||||||
|
*
|
||||||
|
* @throws MathException If the given number is not valid.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function isLessThan(BigNumber|int|float|string $that): bool
|
||||||
|
{
|
||||||
|
return $this->compareTo($that) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this number is less than or equal to the given one.
|
||||||
|
*
|
||||||
|
* @throws MathException If the given number is not valid.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function isLessThanOrEqualTo(BigNumber|int|float|string $that): bool
|
||||||
|
{
|
||||||
|
return $this->compareTo($that) <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this number is strictly greater than the given one.
|
||||||
|
*
|
||||||
|
* @throws MathException If the given number is not valid.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function isGreaterThan(BigNumber|int|float|string $that): bool
|
||||||
|
{
|
||||||
|
return $this->compareTo($that) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this number is greater than or equal to the given one.
|
||||||
|
*
|
||||||
|
* @throws MathException If the given number is not valid.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function isGreaterThanOrEqualTo(BigNumber|int|float|string $that): bool
|
||||||
|
{
|
||||||
|
return $this->compareTo($that) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this number equals zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function isZero(): bool
|
||||||
|
{
|
||||||
|
return $this->getSign() === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this number is strictly negative.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function isNegative(): bool
|
||||||
|
{
|
||||||
|
return $this->getSign() < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this number is negative or zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function isNegativeOrZero(): bool
|
||||||
|
{
|
||||||
|
return $this->getSign() <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this number is strictly positive.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function isPositive(): bool
|
||||||
|
{
|
||||||
|
return $this->getSign() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this number is positive or zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function isPositiveOrZero(): bool
|
||||||
|
{
|
||||||
|
return $this->getSign() >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the absolute value of this number.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function abs(): static
|
||||||
|
{
|
||||||
|
return $this->isNegative() ? $this->negated() : $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the negated value of this number.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract public function negated(): static;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the sign of this number.
|
||||||
|
*
|
||||||
|
* Returns -1 if the number is negative, 0 if zero, 1 if positive.
|
||||||
|
*
|
||||||
|
* @return -1|0|1
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract public function getSign(): int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares this number to the given one.
|
||||||
|
*
|
||||||
|
* Returns -1 if `$this` is lower than, 0 if equal to, 1 if greater than `$that`.
|
||||||
|
*
|
||||||
|
* @return -1|0|1
|
||||||
|
*
|
||||||
|
* @throws MathException If the number is not valid.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract public function compareTo(BigNumber|int|float|string $that): int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Limits (clamps) this number between the given minimum and maximum values.
|
||||||
|
*
|
||||||
|
* If the number is lower than $min, returns $min.
|
||||||
|
* If the number is greater than $max, returns $max.
|
||||||
|
* Otherwise, returns this number unchanged.
|
||||||
|
*
|
||||||
|
* @param BigNumber|int|float|string $min The minimum. Must be convertible to an instance of the class this method is called on.
|
||||||
|
* @param BigNumber|int|float|string $max The maximum. Must be convertible to an instance of the class this method is called on.
|
||||||
|
*
|
||||||
|
* @throws MathException If min/max are not convertible to an instance of the class this method is called on.
|
||||||
|
* @throws InvalidArgumentException If min is greater than max.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function clamp(BigNumber|int|float|string $min, BigNumber|int|float|string $max): static
|
||||||
|
{
|
||||||
|
$min = static::of($min);
|
||||||
|
$max = static::of($max);
|
||||||
|
|
||||||
|
if ($min->isGreaterThan($max)) {
|
||||||
|
throw new InvalidArgumentException('Minimum value must be less than or equal to maximum value.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isLessThan($min)) {
|
||||||
|
return $min;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isGreaterThan($max)) {
|
||||||
|
return $max;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts this number to a BigInteger.
|
||||||
|
*
|
||||||
|
* @throws RoundingNecessaryException If this number cannot be converted to a BigInteger without rounding.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract public function toBigInteger(): BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts this number to a BigDecimal.
|
||||||
|
*
|
||||||
|
* @throws RoundingNecessaryException If this number cannot be converted to a BigDecimal without rounding.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract public function toBigDecimal(): BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts this number to a BigRational.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract public function toBigRational(): BigRational;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts this number to a BigDecimal with the given scale, using rounding if necessary.
|
||||||
|
*
|
||||||
|
* @param int $scale The scale of the resulting `BigDecimal`. Must be non-negative.
|
||||||
|
* @param RoundingMode $roundingMode An optional rounding mode, defaults to Unnecessary.
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException If the scale is negative.
|
||||||
|
* @throws RoundingNecessaryException If RoundingMode::Unnecessary is used, and this number cannot be converted to
|
||||||
|
* the given scale without rounding.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::Unnecessary): BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the exact value of this number as a native integer.
|
||||||
|
*
|
||||||
|
* If this number cannot be converted to a native integer without losing precision, an exception is thrown.
|
||||||
|
* Note that the acceptable range for an integer depends on the platform and differs for 32-bit and 64-bit.
|
||||||
|
*
|
||||||
|
* @throws MathException If this number cannot be exactly converted to a native integer.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract public function toInt(): int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an approximation of this number as a floating-point value.
|
||||||
|
*
|
||||||
|
* Note that this method can discard information as the precision of a floating-point value
|
||||||
|
* is inherently limited.
|
||||||
|
*
|
||||||
|
* If the number is greater than the largest representable floating point number, positive infinity is returned.
|
||||||
|
* If the number is less than the smallest representable floating point number, negative infinity is returned.
|
||||||
|
* This method never returns NaN.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract public function toFloat(): float;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string representation of this number.
|
||||||
|
*
|
||||||
|
* The output of this method can be parsed by the `of()` factory method; this will yield an object equal to this
|
||||||
|
* one, but possibly of a different type if instantiated through `BigNumber::of()`.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract public function toString(): string;
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
final public function jsonSerialize(): string
|
||||||
|
{
|
||||||
|
return $this->toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public function __toString(): string
|
||||||
|
{
|
||||||
|
return $this->toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overridden by subclasses to convert a BigNumber to an instance of the subclass.
|
||||||
|
*
|
||||||
|
* @throws RoundingNecessaryException If the value cannot be converted.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
abstract protected static function from(BigNumber $number): static;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy method to access BigInteger's protected constructor from sibling classes.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final protected function newBigInteger(string $value): BigInteger
|
||||||
|
{
|
||||||
|
return new BigInteger($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy method to access BigDecimal's protected constructor from sibling classes.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final protected function newBigDecimal(string $value, int $scale = 0): BigDecimal
|
||||||
|
{
|
||||||
|
return new BigDecimal($value, $scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy method to access BigRational's protected constructor from sibling classes.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final protected function newBigRational(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator): BigRational
|
||||||
|
{
|
||||||
|
return new BigRational($numerator, $denominator, $checkDenominator);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws NumberFormatException If the format of the number is not valid.
|
* @throws NumberFormatException If the format of the number is not valid.
|
||||||
* @throws DivisionByZeroException If the value represents a rational number with a denominator of zero.
|
* @throws DivisionByZeroException If the value represents a rational number with a denominator of zero.
|
||||||
*
|
*
|
||||||
* @psalm-pure
|
* @pure
|
||||||
*/
|
*/
|
||||||
private static function _of(BigNumber|int|float|string $value) : BigNumber
|
private static function _of(BigNumber|int|float|string $value): BigNumber
|
||||||
{
|
{
|
||||||
if ($value instanceof BigNumber) {
|
if ($value instanceof BigNumber) {
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\is_int($value)) {
|
if (is_int($value)) {
|
||||||
return new BigInteger((string) $value);
|
return new BigInteger((string) $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_float($value)) {
|
if (is_float($value)) {
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
|
trigger_error(
|
||||||
|
'Passing floats to BigNumber::of() and arithmetic methods is deprecated and will be removed in 0.15. ' .
|
||||||
|
'Cast the float to string explicitly to preserve the previous behaviour.',
|
||||||
|
E_USER_DEPRECATED,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (is_nan($value)) {
|
||||||
|
$value = 'NAN';
|
||||||
|
} else {
|
||||||
$value = (string) $value;
|
$value = (string) $value;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (str_contains($value, '/')) {
|
if (str_contains($value, '/')) {
|
||||||
// Rational number
|
// Rational number
|
||||||
if (\preg_match(self::PARSE_REGEXP_RATIONAL, $value, $matches, PREG_UNMATCHED_AS_NULL) !== 1) {
|
if (preg_match(self::PARSE_REGEXP_RATIONAL, $value, $matches, PREG_UNMATCHED_AS_NULL) !== 1) {
|
||||||
throw NumberFormatException::invalidFormat($value);
|
throw NumberFormatException::invalidFormat($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,9 +588,6 @@ abstract class BigNumber implements \JsonSerializable
|
|||||||
$numerator = $matches['numerator'];
|
$numerator = $matches['numerator'];
|
||||||
$denominator = $matches['denominator'];
|
$denominator = $matches['denominator'];
|
||||||
|
|
||||||
assert($numerator !== null);
|
|
||||||
assert($denominator !== null);
|
|
||||||
|
|
||||||
$numerator = self::cleanUp($sign, $numerator);
|
$numerator = self::cleanUp($sign, $numerator);
|
||||||
$denominator = self::cleanUp(null, $denominator);
|
$denominator = self::cleanUp(null, $denominator);
|
||||||
|
|
||||||
@@ -115,11 +598,11 @@ abstract class BigNumber implements \JsonSerializable
|
|||||||
return new BigRational(
|
return new BigRational(
|
||||||
new BigInteger($numerator),
|
new BigInteger($numerator),
|
||||||
new BigInteger($denominator),
|
new BigInteger($denominator),
|
||||||
false
|
false,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Integer or decimal number
|
// Integer or decimal number
|
||||||
if (\preg_match(self::PARSE_REGEXP_NUMERICAL, $value, $matches, PREG_UNMATCHED_AS_NULL) !== 1) {
|
if (preg_match(self::PARSE_REGEXP_NUMERICAL, $value, $matches, PREG_UNMATCHED_AS_NULL) !== 1) {
|
||||||
throw NumberFormatException::invalidFormat($value);
|
throw NumberFormatException::invalidFormat($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,20 +621,37 @@ abstract class BigNumber implements \JsonSerializable
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($point !== null || $exponent !== null) {
|
if ($point !== null || $exponent !== null) {
|
||||||
$fractional = ($fractional ?? '');
|
$fractional ??= '';
|
||||||
$exponent = ($exponent !== null) ? (int)$exponent : 0;
|
|
||||||
|
|
||||||
if ($exponent === PHP_INT_MIN || $exponent === PHP_INT_MAX) {
|
if ($exponent !== null) {
|
||||||
|
if ($exponent[0] === '-') {
|
||||||
|
$exponent = ltrim(substr($exponent, 1), '0') ?: '0';
|
||||||
|
$exponent = filter_var($exponent, FILTER_VALIDATE_INT);
|
||||||
|
if ($exponent !== false) {
|
||||||
|
$exponent = -$exponent;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($exponent[0] === '+') {
|
||||||
|
$exponent = substr($exponent, 1);
|
||||||
|
}
|
||||||
|
$exponent = ltrim($exponent, '0') ?: '0';
|
||||||
|
$exponent = filter_var($exponent, FILTER_VALIDATE_INT);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$exponent = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($exponent === false) {
|
||||||
throw new NumberFormatException('Exponent too large.');
|
throw new NumberFormatException('Exponent too large.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$unscaledValue = self::cleanUp($sign, $integral . $fractional);
|
$unscaledValue = self::cleanUp($sign, $integral . $fractional);
|
||||||
|
|
||||||
$scale = \strlen($fractional) - $exponent;
|
$scale = strlen($fractional) - $exponent;
|
||||||
|
|
||||||
if ($scale < 0) {
|
if ($scale < 0) {
|
||||||
if ($unscaledValue !== '0') {
|
if ($unscaledValue !== '0') {
|
||||||
$unscaledValue .= \str_repeat('0', -$scale);
|
$unscaledValue .= str_repeat('0', -$scale);
|
||||||
}
|
}
|
||||||
$scale = 0;
|
$scale = 0;
|
||||||
}
|
}
|
||||||
@@ -166,147 +666,30 @@ abstract class BigNumber implements \JsonSerializable
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overridden by subclasses to convert a BigNumber to an instance of the subclass.
|
* Removes optional leading zeros and applies sign.
|
||||||
*
|
*
|
||||||
* @throws RoundingNecessaryException If the value cannot be converted.
|
* @param string|null $sign The sign, '+' or '-', optional. Null is allowed for convenience and treated as '+'.
|
||||||
|
* @param string $number The number, validated as a string of digits.
|
||||||
*
|
*
|
||||||
* @psalm-pure
|
* @pure
|
||||||
*/
|
*/
|
||||||
abstract protected static function from(BigNumber $number): static;
|
private static function cleanUp(string|null $sign, string $number): string
|
||||||
|
|
||||||
/**
|
|
||||||
* Proxy method to access BigInteger's protected constructor from sibling classes.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
* @psalm-pure
|
|
||||||
*/
|
|
||||||
final protected function newBigInteger(string $value) : BigInteger
|
|
||||||
{
|
{
|
||||||
return new BigInteger($value);
|
$number = ltrim($number, '0');
|
||||||
|
|
||||||
|
if ($number === '') {
|
||||||
|
return '0';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
return $sign === '-' ? '-' . $number : $number;
|
||||||
* Proxy method to access BigDecimal's protected constructor from sibling classes.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
* @psalm-pure
|
|
||||||
*/
|
|
||||||
final protected function newBigDecimal(string $value, int $scale = 0) : BigDecimal
|
|
||||||
{
|
|
||||||
return new BigDecimal($value, $scale);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Proxy method to access BigRational's protected constructor from sibling classes.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
* @psalm-pure
|
|
||||||
*/
|
|
||||||
final protected function newBigRational(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator) : BigRational
|
|
||||||
{
|
|
||||||
return new BigRational($numerator, $denominator, $checkDenominator);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the minimum of the given values.
|
|
||||||
*
|
|
||||||
* @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible
|
|
||||||
* to an instance of the class this method is called on.
|
|
||||||
*
|
|
||||||
* @throws \InvalidArgumentException If no values are given.
|
|
||||||
* @throws MathException If an argument is not valid.
|
|
||||||
*
|
|
||||||
* @psalm-pure
|
|
||||||
*/
|
|
||||||
final public static function min(BigNumber|int|float|string ...$values) : static
|
|
||||||
{
|
|
||||||
$min = null;
|
|
||||||
|
|
||||||
foreach ($values as $value) {
|
|
||||||
$value = static::of($value);
|
|
||||||
|
|
||||||
if ($min === null || $value->isLessThan($min)) {
|
|
||||||
$min = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($min === null) {
|
|
||||||
throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $min;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the maximum of the given values.
|
|
||||||
*
|
|
||||||
* @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible
|
|
||||||
* to an instance of the class this method is called on.
|
|
||||||
*
|
|
||||||
* @throws \InvalidArgumentException If no values are given.
|
|
||||||
* @throws MathException If an argument is not valid.
|
|
||||||
*
|
|
||||||
* @psalm-pure
|
|
||||||
*/
|
|
||||||
final public static function max(BigNumber|int|float|string ...$values) : static
|
|
||||||
{
|
|
||||||
$max = null;
|
|
||||||
|
|
||||||
foreach ($values as $value) {
|
|
||||||
$value = static::of($value);
|
|
||||||
|
|
||||||
if ($max === null || $value->isGreaterThan($max)) {
|
|
||||||
$max = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($max === null) {
|
|
||||||
throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $max;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the sum of the given values.
|
|
||||||
*
|
|
||||||
* @param BigNumber|int|float|string ...$values The numbers to add. All the numbers need to be convertible
|
|
||||||
* to an instance of the class this method is called on.
|
|
||||||
*
|
|
||||||
* @throws \InvalidArgumentException If no values are given.
|
|
||||||
* @throws MathException If an argument is not valid.
|
|
||||||
*
|
|
||||||
* @psalm-pure
|
|
||||||
*/
|
|
||||||
final public static function sum(BigNumber|int|float|string ...$values) : static
|
|
||||||
{
|
|
||||||
/** @var static|null $sum */
|
|
||||||
$sum = null;
|
|
||||||
|
|
||||||
foreach ($values as $value) {
|
|
||||||
$value = static::of($value);
|
|
||||||
|
|
||||||
$sum = $sum === null ? $value : self::add($sum, $value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($sum === null) {
|
|
||||||
throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $sum;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds two BigNumber instances in the correct order to avoid a RoundingNecessaryException.
|
* Adds two BigNumber instances in the correct order to avoid a RoundingNecessaryException.
|
||||||
*
|
*
|
||||||
* @todo This could be better resolved by creating an abstract protected method in BigNumber, and leaving to
|
* @pure
|
||||||
* concrete classes the responsibility to perform the addition themselves or delegate it to the given number,
|
|
||||||
* depending on their ability to perform the operation. This will also require a version bump because we're
|
|
||||||
* potentially breaking custom BigNumber implementations (if any...)
|
|
||||||
*
|
|
||||||
* @psalm-pure
|
|
||||||
*/
|
*/
|
||||||
private static function add(BigNumber $a, BigNumber $b) : BigNumber
|
private static function add(BigNumber $a, BigNumber $b): BigNumber
|
||||||
{
|
{
|
||||||
if ($a instanceof BigRational) {
|
if ($a instanceof BigRational) {
|
||||||
return $a->plus($b);
|
return $a->plus($b);
|
||||||
@@ -324,192 +707,6 @@ abstract class BigNumber implements \JsonSerializable
|
|||||||
return $b->plus($a);
|
return $b->plus($a);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var BigInteger $a */
|
|
||||||
|
|
||||||
return $a->plus($b);
|
return $a->plus($b);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes optional leading zeros and applies sign.
|
|
||||||
*
|
|
||||||
* @param string|null $sign The sign, '+' or '-', optional. Null is allowed for convenience and treated as '+'.
|
|
||||||
* @param string $number The number, validated as a non-empty string of digits.
|
|
||||||
*
|
|
||||||
* @psalm-pure
|
|
||||||
*/
|
|
||||||
private static function cleanUp(string|null $sign, string $number) : string
|
|
||||||
{
|
|
||||||
$number = \ltrim($number, '0');
|
|
||||||
|
|
||||||
if ($number === '') {
|
|
||||||
return '0';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $sign === '-' ? '-' . $number : $number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if this number is equal to the given one.
|
|
||||||
*/
|
|
||||||
final public function isEqualTo(BigNumber|int|float|string $that) : bool
|
|
||||||
{
|
|
||||||
return $this->compareTo($that) === 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if this number is strictly lower than the given one.
|
|
||||||
*/
|
|
||||||
final public function isLessThan(BigNumber|int|float|string $that) : bool
|
|
||||||
{
|
|
||||||
return $this->compareTo($that) < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if this number is lower than or equal to the given one.
|
|
||||||
*/
|
|
||||||
final public function isLessThanOrEqualTo(BigNumber|int|float|string $that) : bool
|
|
||||||
{
|
|
||||||
return $this->compareTo($that) <= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if this number is strictly greater than the given one.
|
|
||||||
*/
|
|
||||||
final public function isGreaterThan(BigNumber|int|float|string $that) : bool
|
|
||||||
{
|
|
||||||
return $this->compareTo($that) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if this number is greater than or equal to the given one.
|
|
||||||
*/
|
|
||||||
final public function isGreaterThanOrEqualTo(BigNumber|int|float|string $that) : bool
|
|
||||||
{
|
|
||||||
return $this->compareTo($that) >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if this number equals zero.
|
|
||||||
*/
|
|
||||||
final public function isZero() : bool
|
|
||||||
{
|
|
||||||
return $this->getSign() === 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if this number is strictly negative.
|
|
||||||
*/
|
|
||||||
final public function isNegative() : bool
|
|
||||||
{
|
|
||||||
return $this->getSign() < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if this number is negative or zero.
|
|
||||||
*/
|
|
||||||
final public function isNegativeOrZero() : bool
|
|
||||||
{
|
|
||||||
return $this->getSign() <= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if this number is strictly positive.
|
|
||||||
*/
|
|
||||||
final public function isPositive() : bool
|
|
||||||
{
|
|
||||||
return $this->getSign() > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if this number is positive or zero.
|
|
||||||
*/
|
|
||||||
final public function isPositiveOrZero() : bool
|
|
||||||
{
|
|
||||||
return $this->getSign() >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the sign of this number.
|
|
||||||
*
|
|
||||||
* @psalm-return -1|0|1
|
|
||||||
*
|
|
||||||
* @return int -1 if the number is negative, 0 if zero, 1 if positive.
|
|
||||||
*/
|
|
||||||
abstract public function getSign() : int;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compares this number to the given one.
|
|
||||||
*
|
|
||||||
* @psalm-return -1|0|1
|
|
||||||
*
|
|
||||||
* @return int -1 if `$this` is lower than, 0 if equal to, 1 if greater than `$that`.
|
|
||||||
*
|
|
||||||
* @throws MathException If the number is not valid.
|
|
||||||
*/
|
|
||||||
abstract public function compareTo(BigNumber|int|float|string $that) : int;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts this number to a BigInteger.
|
|
||||||
*
|
|
||||||
* @throws RoundingNecessaryException If this number cannot be converted to a BigInteger without rounding.
|
|
||||||
*/
|
|
||||||
abstract public function toBigInteger() : BigInteger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts this number to a BigDecimal.
|
|
||||||
*
|
|
||||||
* @throws RoundingNecessaryException If this number cannot be converted to a BigDecimal without rounding.
|
|
||||||
*/
|
|
||||||
abstract public function toBigDecimal() : BigDecimal;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts this number to a BigRational.
|
|
||||||
*/
|
|
||||||
abstract public function toBigRational() : BigRational;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts this number to a BigDecimal with the given scale, using rounding if necessary.
|
|
||||||
*
|
|
||||||
* @param int $scale The scale of the resulting `BigDecimal`.
|
|
||||||
* @param RoundingMode $roundingMode An optional rounding mode, defaults to UNNECESSARY.
|
|
||||||
*
|
|
||||||
* @throws RoundingNecessaryException If this number cannot be converted to the given scale without rounding.
|
|
||||||
* This only applies when RoundingMode::UNNECESSARY is used.
|
|
||||||
*/
|
|
||||||
abstract public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the exact value of this number as a native integer.
|
|
||||||
*
|
|
||||||
* If this number cannot be converted to a native integer without losing precision, an exception is thrown.
|
|
||||||
* Note that the acceptable range for an integer depends on the platform and differs for 32-bit and 64-bit.
|
|
||||||
*
|
|
||||||
* @throws MathException If this number cannot be exactly converted to a native integer.
|
|
||||||
*/
|
|
||||||
abstract public function toInt() : int;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an approximation of this number as a floating-point value.
|
|
||||||
*
|
|
||||||
* Note that this method can discard information as the precision of a floating-point value
|
|
||||||
* is inherently limited.
|
|
||||||
*
|
|
||||||
* If the number is greater than the largest representable floating point number, positive infinity is returned.
|
|
||||||
* If the number is less than the smallest representable floating point number, negative infinity is returned.
|
|
||||||
*/
|
|
||||||
abstract public function toFloat() : float;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a string representation of this number.
|
|
||||||
*
|
|
||||||
* The output of this method can be parsed by the `of()` factory method;
|
|
||||||
* this will yield an object equal to this one, without any information loss.
|
|
||||||
*/
|
|
||||||
abstract public function __toString() : string;
|
|
||||||
|
|
||||||
#[Override]
|
|
||||||
final public function jsonSerialize() : string
|
|
||||||
{
|
|
||||||
return $this->__toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+264
-82
@@ -8,26 +8,38 @@ use Brick\Math\Exception\DivisionByZeroException;
|
|||||||
use Brick\Math\Exception\MathException;
|
use Brick\Math\Exception\MathException;
|
||||||
use Brick\Math\Exception\NumberFormatException;
|
use Brick\Math\Exception\NumberFormatException;
|
||||||
use Brick\Math\Exception\RoundingNecessaryException;
|
use Brick\Math\Exception\RoundingNecessaryException;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use LogicException;
|
||||||
use Override;
|
use Override;
|
||||||
|
|
||||||
|
use function is_finite;
|
||||||
|
use function max;
|
||||||
|
use function min;
|
||||||
|
use function strlen;
|
||||||
|
use function substr;
|
||||||
|
use function trigger_error;
|
||||||
|
|
||||||
|
use const E_USER_DEPRECATED;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An arbitrarily large rational number.
|
* An arbitrarily large rational number.
|
||||||
*
|
*
|
||||||
* This class is immutable.
|
* This class is immutable.
|
||||||
*
|
*
|
||||||
* @psalm-immutable
|
* Fractions are automatically simplified to lowest terms. For example, `2/4` becomes `1/2`.
|
||||||
|
* The denominator is always strictly positive; the sign is carried by the numerator.
|
||||||
*/
|
*/
|
||||||
final class BigRational extends BigNumber
|
final readonly class BigRational extends BigNumber
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The numerator.
|
* The numerator.
|
||||||
*/
|
*/
|
||||||
private readonly BigInteger $numerator;
|
private BigInteger $numerator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The denominator. Always strictly positive.
|
* The denominator. Always strictly positive.
|
||||||
*/
|
*/
|
||||||
private readonly BigInteger $denominator;
|
private BigInteger $denominator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Protected constructor. Use a factory method to obtain an instance.
|
* Protected constructor. Use a factory method to obtain an instance.
|
||||||
@@ -37,6 +49,8 @@ final class BigRational extends BigNumber
|
|||||||
* @param bool $checkDenominator Whether to check the denominator for negative and zero.
|
* @param bool $checkDenominator Whether to check the denominator for negative and zero.
|
||||||
*
|
*
|
||||||
* @throws DivisionByZeroException If the denominator is zero.
|
* @throws DivisionByZeroException If the denominator is zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
protected function __construct(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator)
|
protected function __construct(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator)
|
||||||
{
|
{
|
||||||
@@ -56,12 +70,30 @@ final class BigRational extends BigNumber
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @psalm-pure
|
* Creates a BigRational out of a numerator and a denominator.
|
||||||
|
*
|
||||||
|
* If the denominator is negative, the signs of both the numerator and the denominator
|
||||||
|
* will be inverted to ensure that the denominator is always positive.
|
||||||
|
*
|
||||||
|
* @deprecated Use ofFraction() instead.
|
||||||
|
*
|
||||||
|
* @param BigNumber|int|float|string $numerator The numerator. Must be convertible to a BigInteger.
|
||||||
|
* @param BigNumber|int|float|string $denominator The denominator. Must be convertible to a BigInteger.
|
||||||
|
*
|
||||||
|
* @throws NumberFormatException If an argument does not represent a valid number.
|
||||||
|
* @throws RoundingNecessaryException If an argument represents a non-integer number.
|
||||||
|
* @throws DivisionByZeroException If the denominator is zero.
|
||||||
*/
|
*/
|
||||||
#[Override]
|
public static function nd(
|
||||||
protected static function from(BigNumber $number): static
|
BigNumber|int|float|string $numerator,
|
||||||
{
|
BigNumber|int|float|string $denominator,
|
||||||
return $number->toBigRational();
|
): BigRational {
|
||||||
|
trigger_error(
|
||||||
|
'The BigRational::nd() method is deprecated, use BigRational::ofFraction() instead.',
|
||||||
|
E_USER_DEPRECATED,
|
||||||
|
);
|
||||||
|
|
||||||
|
return self::ofFraction($numerator, $denominator);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,16 +105,15 @@ final class BigRational extends BigNumber
|
|||||||
* @param BigNumber|int|float|string $numerator The numerator. Must be convertible to a BigInteger.
|
* @param BigNumber|int|float|string $numerator The numerator. Must be convertible to a BigInteger.
|
||||||
* @param BigNumber|int|float|string $denominator The denominator. Must be convertible to a BigInteger.
|
* @param BigNumber|int|float|string $denominator The denominator. Must be convertible to a BigInteger.
|
||||||
*
|
*
|
||||||
* @throws NumberFormatException If an argument does not represent a valid number.
|
* @throws MathException If an argument is not valid, or is not convertible to a BigInteger.
|
||||||
* @throws RoundingNecessaryException If an argument represents a non-integer number.
|
|
||||||
* @throws DivisionByZeroException If the denominator is zero.
|
* @throws DivisionByZeroException If the denominator is zero.
|
||||||
*
|
*
|
||||||
* @psalm-pure
|
* @pure
|
||||||
*/
|
*/
|
||||||
public static function nd(
|
public static function ofFraction(
|
||||||
BigNumber|int|float|string $numerator,
|
BigNumber|int|float|string $numerator,
|
||||||
BigNumber|int|float|string $denominator,
|
BigNumber|int|float|string $denominator,
|
||||||
) : BigRational {
|
): BigRational {
|
||||||
$numerator = BigInteger::of($numerator);
|
$numerator = BigInteger::of($numerator);
|
||||||
$denominator = BigInteger::of($denominator);
|
$denominator = BigInteger::of($denominator);
|
||||||
|
|
||||||
@@ -92,14 +123,11 @@ final class BigRational extends BigNumber
|
|||||||
/**
|
/**
|
||||||
* Returns a BigRational representing zero.
|
* Returns a BigRational representing zero.
|
||||||
*
|
*
|
||||||
* @psalm-pure
|
* @pure
|
||||||
*/
|
*/
|
||||||
public static function zero() : BigRational
|
public static function zero(): BigRational
|
||||||
{
|
{
|
||||||
/**
|
/** @var BigRational|null $zero */
|
||||||
* @psalm-suppress ImpureStaticVariable
|
|
||||||
* @var BigRational|null $zero
|
|
||||||
*/
|
|
||||||
static $zero;
|
static $zero;
|
||||||
|
|
||||||
if ($zero === null) {
|
if ($zero === null) {
|
||||||
@@ -112,14 +140,11 @@ final class BigRational extends BigNumber
|
|||||||
/**
|
/**
|
||||||
* Returns a BigRational representing one.
|
* Returns a BigRational representing one.
|
||||||
*
|
*
|
||||||
* @psalm-pure
|
* @pure
|
||||||
*/
|
*/
|
||||||
public static function one() : BigRational
|
public static function one(): BigRational
|
||||||
{
|
{
|
||||||
/**
|
/** @var BigRational|null $one */
|
||||||
* @psalm-suppress ImpureStaticVariable
|
|
||||||
* @var BigRational|null $one
|
|
||||||
*/
|
|
||||||
static $one;
|
static $one;
|
||||||
|
|
||||||
if ($one === null) {
|
if ($one === null) {
|
||||||
@@ -132,14 +157,11 @@ final class BigRational extends BigNumber
|
|||||||
/**
|
/**
|
||||||
* Returns a BigRational representing ten.
|
* Returns a BigRational representing ten.
|
||||||
*
|
*
|
||||||
* @psalm-pure
|
* @pure
|
||||||
*/
|
*/
|
||||||
public static function ten() : BigRational
|
public static function ten(): BigRational
|
||||||
{
|
{
|
||||||
/**
|
/** @var BigRational|null $ten */
|
||||||
* @psalm-suppress ImpureStaticVariable
|
|
||||||
* @var BigRational|null $ten
|
|
||||||
*/
|
|
||||||
static $ten;
|
static $ten;
|
||||||
|
|
||||||
if ($ten === null) {
|
if ($ten === null) {
|
||||||
@@ -149,52 +171,113 @@ final class BigRational extends BigNumber
|
|||||||
return $ten;
|
return $ten;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getNumerator() : BigInteger
|
/**
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function getNumerator(): BigInteger
|
||||||
{
|
{
|
||||||
return $this->numerator;
|
return $this->numerator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDenominator() : BigInteger
|
/**
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function getDenominator(): BigInteger
|
||||||
{
|
{
|
||||||
return $this->denominator;
|
return $this->denominator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the quotient of the division of the numerator by the denominator.
|
* Returns the quotient of the division of the numerator by the denominator.
|
||||||
|
*
|
||||||
|
* @deprecated Will be removed in 0.15. Use getIntegralPart() instead.
|
||||||
*/
|
*/
|
||||||
public function quotient() : BigInteger
|
public function quotient(): BigInteger
|
||||||
{
|
{
|
||||||
|
trigger_error(
|
||||||
|
'BigRational::quotient() is deprecated and will be removed in 0.15. Use getIntegralPart() instead.',
|
||||||
|
E_USER_DEPRECATED,
|
||||||
|
);
|
||||||
|
|
||||||
return $this->numerator->quotient($this->denominator);
|
return $this->numerator->quotient($this->denominator);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the remainder of the division of the numerator by the denominator.
|
* Returns the remainder of the division of the numerator by the denominator.
|
||||||
|
*
|
||||||
|
* @deprecated Will be removed in 0.15. Use `$number->getNumerator()->remainder($number->getDenominator())` instead.
|
||||||
*/
|
*/
|
||||||
public function remainder() : BigInteger
|
public function remainder(): BigInteger
|
||||||
{
|
{
|
||||||
|
trigger_error(
|
||||||
|
'BigRational::remainder() is deprecated and will be removed in 0.15. Use `$number->getNumerator()->remainder($number->getDenominator())` instead.',
|
||||||
|
E_USER_DEPRECATED,
|
||||||
|
);
|
||||||
|
|
||||||
return $this->numerator->remainder($this->denominator);
|
return $this->numerator->remainder($this->denominator);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the quotient and remainder of the division of the numerator by the denominator.
|
* Returns the quotient and remainder of the division of the numerator by the denominator.
|
||||||
*
|
*
|
||||||
* @return BigInteger[]
|
* @deprecated Will be removed in 0.15. Use `$number->getNumerator()->quotientAndRemainder($number->getDenominator())` instead.
|
||||||
*
|
*
|
||||||
* @psalm-return array{BigInteger, BigInteger}
|
* @return array{BigInteger, BigInteger}
|
||||||
*/
|
*/
|
||||||
public function quotientAndRemainder() : array
|
public function quotientAndRemainder(): array
|
||||||
{
|
{
|
||||||
|
trigger_error(
|
||||||
|
'BigRational::quotientAndRemainder() is deprecated and will be removed in 0.15. Use `$number->getNumerator()->quotientAndRemainder($number->getDenominator())` instead.',
|
||||||
|
E_USER_DEPRECATED,
|
||||||
|
);
|
||||||
|
|
||||||
return $this->numerator->quotientAndRemainder($this->denominator);
|
return $this->numerator->quotientAndRemainder($this->denominator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the integral part of this rational number.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
*
|
||||||
|
* - `7/3` returns `2` (since 7/3 = 2 + 1/3)
|
||||||
|
* - `-7/3` returns `-2` (since -7/3 = -2 + (-1/3))
|
||||||
|
*
|
||||||
|
* The following identity holds: `$r->isEqualTo($r->getFractionalPart()->plus($r->getIntegralPart()))`.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function getIntegralPart(): BigInteger
|
||||||
|
{
|
||||||
|
return $this->numerator->quotient($this->denominator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the fractional part of this rational number.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
*
|
||||||
|
* - `7/3` returns `1/3` (since 7/3 = 2 + 1/3)
|
||||||
|
* - `-7/3` returns `-1/3` (since -7/3 = -2 + (-1/3))
|
||||||
|
*
|
||||||
|
* The following identity holds: `$r->isEqualTo($r->getFractionalPart()->plus($r->getIntegralPart()))`.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function getFractionalPart(): BigRational
|
||||||
|
{
|
||||||
|
return new BigRational($this->numerator->remainder($this->denominator), $this->denominator, false);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the sum of this number and the given one.
|
* Returns the sum of this number and the given one.
|
||||||
*
|
*
|
||||||
* @param BigNumber|int|float|string $that The number to add.
|
* @param BigNumber|int|float|string $that The number to add.
|
||||||
*
|
*
|
||||||
* @throws MathException If the number is not valid.
|
* @throws MathException If the number is not valid.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
public function plus(BigNumber|int|float|string $that) : BigRational
|
public function plus(BigNumber|int|float|string $that): BigRational
|
||||||
{
|
{
|
||||||
$that = BigRational::of($that);
|
$that = BigRational::of($that);
|
||||||
|
|
||||||
@@ -211,8 +294,10 @@ final class BigRational extends BigNumber
|
|||||||
* @param BigNumber|int|float|string $that The number to subtract.
|
* @param BigNumber|int|float|string $that The number to subtract.
|
||||||
*
|
*
|
||||||
* @throws MathException If the number is not valid.
|
* @throws MathException If the number is not valid.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
public function minus(BigNumber|int|float|string $that) : BigRational
|
public function minus(BigNumber|int|float|string $that): BigRational
|
||||||
{
|
{
|
||||||
$that = BigRational::of($that);
|
$that = BigRational::of($that);
|
||||||
|
|
||||||
@@ -228,9 +313,11 @@ final class BigRational extends BigNumber
|
|||||||
*
|
*
|
||||||
* @param BigNumber|int|float|string $that The multiplier.
|
* @param BigNumber|int|float|string $that The multiplier.
|
||||||
*
|
*
|
||||||
* @throws MathException If the multiplier is not a valid number.
|
* @throws MathException If the multiplier is not valid.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
public function multipliedBy(BigNumber|int|float|string $that) : BigRational
|
public function multipliedBy(BigNumber|int|float|string $that): BigRational
|
||||||
{
|
{
|
||||||
$that = BigRational::of($that);
|
$that = BigRational::of($that);
|
||||||
|
|
||||||
@@ -245,12 +332,19 @@ final class BigRational extends BigNumber
|
|||||||
*
|
*
|
||||||
* @param BigNumber|int|float|string $that The divisor.
|
* @param BigNumber|int|float|string $that The divisor.
|
||||||
*
|
*
|
||||||
* @throws MathException If the divisor is not a valid number, or is zero.
|
* @throws MathException If the divisor is not valid.
|
||||||
|
* @throws DivisionByZeroException If the divisor is zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
public function dividedBy(BigNumber|int|float|string $that) : BigRational
|
public function dividedBy(BigNumber|int|float|string $that): BigRational
|
||||||
{
|
{
|
||||||
$that = BigRational::of($that);
|
$that = BigRational::of($that);
|
||||||
|
|
||||||
|
if ($that->isZero()) {
|
||||||
|
throw DivisionByZeroException::divisionByZero();
|
||||||
|
}
|
||||||
|
|
||||||
$numerator = $this->numerator->multipliedBy($that->denominator);
|
$numerator = $this->numerator->multipliedBy($that->denominator);
|
||||||
$denominator = $this->denominator->multipliedBy($that->numerator);
|
$denominator = $this->denominator->multipliedBy($that->numerator);
|
||||||
|
|
||||||
@@ -260,14 +354,14 @@ final class BigRational extends BigNumber
|
|||||||
/**
|
/**
|
||||||
* Returns this number exponentiated to the given value.
|
* Returns this number exponentiated to the given value.
|
||||||
*
|
*
|
||||||
* @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000.
|
* @throws InvalidArgumentException If the exponent is not in the range 0 to 1,000,000.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
public function power(int $exponent) : BigRational
|
public function power(int $exponent): BigRational
|
||||||
{
|
{
|
||||||
if ($exponent === 0) {
|
if ($exponent === 0) {
|
||||||
$one = BigInteger::one();
|
return BigRational::one();
|
||||||
|
|
||||||
return new BigRational($one, $one, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($exponent === 1) {
|
if ($exponent === 1) {
|
||||||
@@ -277,7 +371,7 @@ final class BigRational extends BigNumber
|
|||||||
return new BigRational(
|
return new BigRational(
|
||||||
$this->numerator->power($exponent),
|
$this->numerator->power($exponent),
|
||||||
$this->denominator->power($exponent),
|
$this->denominator->power($exponent),
|
||||||
false
|
false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,32 +381,26 @@ final class BigRational extends BigNumber
|
|||||||
* The reciprocal has the numerator and denominator swapped.
|
* The reciprocal has the numerator and denominator swapped.
|
||||||
*
|
*
|
||||||
* @throws DivisionByZeroException If the numerator is zero.
|
* @throws DivisionByZeroException If the numerator is zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
public function reciprocal() : BigRational
|
public function reciprocal(): BigRational
|
||||||
{
|
{
|
||||||
return new BigRational($this->denominator, $this->numerator, true);
|
return new BigRational($this->denominator, $this->numerator, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
#[Override]
|
||||||
* Returns the absolute value of this BigRational.
|
public function negated(): static
|
||||||
*/
|
|
||||||
public function abs() : BigRational
|
|
||||||
{
|
|
||||||
return new BigRational($this->numerator->abs(), $this->denominator, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the negated value of this BigRational.
|
|
||||||
*/
|
|
||||||
public function negated() : BigRational
|
|
||||||
{
|
{
|
||||||
return new BigRational($this->numerator->negated(), $this->denominator, false);
|
return new BigRational($this->numerator->negated(), $this->denominator, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the simplified value of this BigRational.
|
* Returns the simplified value of this BigRational.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
public function simplified() : BigRational
|
public function simplified(): BigRational
|
||||||
{
|
{
|
||||||
$gcd = $this->numerator->gcd($this->denominator);
|
$gcd = $this->numerator->gcd($this->denominator);
|
||||||
|
|
||||||
@@ -323,19 +411,27 @@ final class BigRational extends BigNumber
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function compareTo(BigNumber|int|float|string $that) : int
|
public function compareTo(BigNumber|int|float|string $that): int
|
||||||
{
|
{
|
||||||
return $this->minus($that)->getSign();
|
$that = BigRational::of($that);
|
||||||
|
|
||||||
|
if ($this->denominator->isEqualTo($that->denominator)) {
|
||||||
|
return $this->numerator->compareTo($that->numerator);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->numerator
|
||||||
|
->multipliedBy($that->denominator)
|
||||||
|
->compareTo($that->numerator->multipliedBy($this->denominator));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function getSign() : int
|
public function getSign(): int
|
||||||
{
|
{
|
||||||
return $this->numerator->getSign();
|
return $this->numerator->getSign();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function toBigInteger() : BigInteger
|
public function toBigInteger(): BigInteger
|
||||||
{
|
{
|
||||||
$simplified = $this->simplified();
|
$simplified = $this->simplified();
|
||||||
|
|
||||||
@@ -347,41 +443,59 @@ final class BigRational extends BigNumber
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function toBigDecimal() : BigDecimal
|
public function toBigDecimal(): BigDecimal
|
||||||
{
|
{
|
||||||
return $this->numerator->toBigDecimal()->exactlyDividedBy($this->denominator);
|
return $this->numerator->toBigDecimal()->dividedByExact($this->denominator);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function toBigRational() : BigRational
|
public function toBigRational(): BigRational
|
||||||
{
|
{
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal
|
public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::Unnecessary): BigDecimal
|
||||||
{
|
{
|
||||||
return $this->numerator->toBigDecimal()->dividedBy($this->denominator, $scale, $roundingMode);
|
return $this->numerator->toBigDecimal()->dividedBy($this->denominator, $scale, $roundingMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function toInt() : int
|
public function toInt(): int
|
||||||
{
|
{
|
||||||
return $this->toBigInteger()->toInt();
|
return $this->toBigInteger()->toInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function toFloat() : float
|
public function toFloat(): float
|
||||||
{
|
{
|
||||||
$simplified = $this->simplified();
|
$simplified = $this->simplified();
|
||||||
return $simplified->numerator->toFloat() / $simplified->denominator->toFloat();
|
$numeratorFloat = $simplified->numerator->toFloat();
|
||||||
|
$denominatorFloat = $simplified->denominator->toFloat();
|
||||||
|
|
||||||
|
if (is_finite($numeratorFloat) && is_finite($denominatorFloat)) {
|
||||||
|
return $numeratorFloat / $denominatorFloat;
|
||||||
|
}
|
||||||
|
|
||||||
|
// At least one side overflows to INF; use a decimal approximation instead.
|
||||||
|
// We need ~17 significant digits for double precision (we use 20 for some margin). Since $scale controls
|
||||||
|
// decimal places (not significant digits), we subtract the estimated order of magnitude so that large results
|
||||||
|
// use fewer decimal places and small results use more (to look past leading zeros). Clamped to [0, 350] as
|
||||||
|
// doubles range from e-324 to e308 (350 ≈ 324 + 20 significant digits + margin).
|
||||||
|
$magnitude = strlen($simplified->numerator->abs()->toString()) - strlen($simplified->denominator->toString());
|
||||||
|
$scale = min(350, max(0, 20 - $magnitude));
|
||||||
|
|
||||||
|
return $simplified->numerator
|
||||||
|
->toBigDecimal()
|
||||||
|
->dividedBy($simplified->denominator, $scale, RoundingMode::HalfEven)
|
||||||
|
->toFloat();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function __toString() : string
|
public function toString(): string
|
||||||
{
|
{
|
||||||
$numerator = (string) $this->numerator;
|
$numerator = $this->numerator->toString();
|
||||||
$denominator = (string) $this->denominator;
|
$denominator = $this->denominator->toString();
|
||||||
|
|
||||||
if ($denominator === '1') {
|
if ($denominator === '1') {
|
||||||
return $numerator;
|
return $numerator;
|
||||||
@@ -390,6 +504,67 @@ final class BigRational extends BigNumber
|
|||||||
return $numerator . '/' . $denominator;
|
return $numerator . '/' . $denominator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the decimal representation of this rational number, with repeating decimals in parentheses.
|
||||||
|
*
|
||||||
|
* WARNING: This method is unbounded.
|
||||||
|
* The length of the repeating decimal period can be as large as `denominator - 1`.
|
||||||
|
* For fractions with large denominators, this method can use excessive memory and CPU time.
|
||||||
|
* For example, `1/100019` has a repeating period of 100,018 digits.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
*
|
||||||
|
* - `10/3` returns `3.(3)`
|
||||||
|
* - `171/70` returns `2.4(428571)`
|
||||||
|
* - `1/2` returns `0.5`
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public function toRepeatingDecimalString(): string
|
||||||
|
{
|
||||||
|
if ($this->numerator->isZero()) {
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
$sign = $this->numerator->isNegative() ? '-' : '';
|
||||||
|
$numerator = $this->numerator->abs();
|
||||||
|
$denominator = $this->denominator;
|
||||||
|
|
||||||
|
$integral = $numerator->quotient($denominator);
|
||||||
|
$remainder = $numerator->remainder($denominator);
|
||||||
|
|
||||||
|
$integralString = $integral->toString();
|
||||||
|
|
||||||
|
if ($remainder->isZero()) {
|
||||||
|
return $sign . $integralString;
|
||||||
|
}
|
||||||
|
|
||||||
|
$digits = '';
|
||||||
|
$remainderPositions = [];
|
||||||
|
$index = 0;
|
||||||
|
|
||||||
|
while (! $remainder->isZero()) {
|
||||||
|
$remainderString = $remainder->toString();
|
||||||
|
|
||||||
|
if (isset($remainderPositions[$remainderString])) {
|
||||||
|
$repeatIndex = $remainderPositions[$remainderString];
|
||||||
|
$nonRepeating = substr($digits, 0, $repeatIndex);
|
||||||
|
$repeating = substr($digits, $repeatIndex);
|
||||||
|
|
||||||
|
return $sign . $integralString . '.' . $nonRepeating . '(' . $repeating . ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
$remainderPositions[$remainderString] = $index;
|
||||||
|
$remainder = $remainder->multipliedBy(10);
|
||||||
|
|
||||||
|
$digits .= $remainder->quotient($denominator)->toString();
|
||||||
|
$remainder = $remainder->remainder($denominator);
|
||||||
|
$index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sign . $integralString . '.' . $digits;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is required for serializing the object and SHOULD NOT be accessed directly.
|
* This method is required for serializing the object and SHOULD NOT be accessed directly.
|
||||||
*
|
*
|
||||||
@@ -406,19 +581,26 @@ final class BigRational extends BigNumber
|
|||||||
* This method is only here to allow unserializing the object and cannot be accessed directly.
|
* This method is only here to allow unserializing the object and cannot be accessed directly.
|
||||||
*
|
*
|
||||||
* @internal
|
* @internal
|
||||||
* @psalm-suppress RedundantPropertyInitializationCheck
|
|
||||||
*
|
*
|
||||||
* @param array{numerator: BigInteger, denominator: BigInteger} $data
|
* @param array{numerator: BigInteger, denominator: BigInteger} $data
|
||||||
*
|
*
|
||||||
* @throws \LogicException
|
* @throws LogicException
|
||||||
*/
|
*/
|
||||||
public function __unserialize(array $data): void
|
public function __unserialize(array $data): void
|
||||||
{
|
{
|
||||||
|
/** @phpstan-ignore isset.initializedProperty */
|
||||||
if (isset($this->numerator)) {
|
if (isset($this->numerator)) {
|
||||||
throw new \LogicException('__unserialize() is an internal function, it must not be called directly.');
|
throw new LogicException('__unserialize() is an internal function, it must not be called directly.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @phpstan-ignore deadCode.unreachable */
|
||||||
$this->numerator = $data['numerator'];
|
$this->numerator = $data['numerator'];
|
||||||
$this->denominator = $data['denominator'];
|
$this->denominator = $data['denominator'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
protected static function from(BigNumber $number): static
|
||||||
|
{
|
||||||
|
return $number->toBigRational();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,28 +7,28 @@ namespace Brick\Math\Exception;
|
|||||||
/**
|
/**
|
||||||
* Exception thrown when a division by zero occurs.
|
* Exception thrown when a division by zero occurs.
|
||||||
*/
|
*/
|
||||||
class DivisionByZeroException extends MathException
|
final class DivisionByZeroException extends MathException
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @psalm-pure
|
* @pure
|
||||||
*/
|
*/
|
||||||
public static function divisionByZero() : DivisionByZeroException
|
public static function divisionByZero(): DivisionByZeroException
|
||||||
{
|
{
|
||||||
return new self('Division by zero.');
|
return new self('Division by zero.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @psalm-pure
|
* @pure
|
||||||
*/
|
*/
|
||||||
public static function modulusMustNotBeZero() : DivisionByZeroException
|
public static function modulusMustNotBeZero(): DivisionByZeroException
|
||||||
{
|
{
|
||||||
return new self('The modulus must not be zero.');
|
return new self('The modulus must not be zero.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @psalm-pure
|
* @pure
|
||||||
*/
|
*/
|
||||||
public static function denominatorMustNotBeZero() : DivisionByZeroException
|
public static function denominatorMustNotBeZero(): DivisionByZeroException
|
||||||
{
|
{
|
||||||
return new self('The denominator of a rational number cannot be zero.');
|
return new self('The denominator of a rational number cannot be zero.');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,18 +6,23 @@ namespace Brick\Math\Exception;
|
|||||||
|
|
||||||
use Brick\Math\BigInteger;
|
use Brick\Math\BigInteger;
|
||||||
|
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
|
use const PHP_INT_MAX;
|
||||||
|
use const PHP_INT_MIN;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exception thrown when an integer overflow occurs.
|
* Exception thrown when an integer overflow occurs.
|
||||||
*/
|
*/
|
||||||
class IntegerOverflowException extends MathException
|
final class IntegerOverflowException extends MathException
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @psalm-pure
|
* @pure
|
||||||
*/
|
*/
|
||||||
public static function toIntOverflow(BigInteger $value) : IntegerOverflowException
|
public static function toIntOverflow(BigInteger $value): IntegerOverflowException
|
||||||
{
|
{
|
||||||
$message = '%s is out of range %d to %d and cannot be represented as an integer.';
|
$message = '%s is out of range %d to %d and cannot be represented as an integer.';
|
||||||
|
|
||||||
return new self(\sprintf($message, (string) $value, PHP_INT_MIN, PHP_INT_MAX));
|
return new self(sprintf($message, $value->toString(), PHP_INT_MIN, PHP_INT_MAX));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -4,9 +4,11 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Brick\Math\Exception;
|
namespace Brick\Math\Exception;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for all math exceptions.
|
* Base class for all math exceptions.
|
||||||
*/
|
*/
|
||||||
class MathException extends \Exception
|
class MathException extends RuntimeException
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ namespace Brick\Math\Exception;
|
|||||||
/**
|
/**
|
||||||
* Exception thrown when attempting to perform an unsupported operation, such as a square root, on a negative number.
|
* Exception thrown when attempting to perform an unsupported operation, such as a square root, on a negative number.
|
||||||
*/
|
*/
|
||||||
class NegativeNumberException extends MathException
|
final class NegativeNumberException extends MathException
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-11
@@ -4,14 +4,22 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Brick\Math\Exception;
|
namespace Brick\Math\Exception;
|
||||||
|
|
||||||
|
use function dechex;
|
||||||
|
use function ord;
|
||||||
|
use function sprintf;
|
||||||
|
use function strtoupper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exception thrown when attempting to create a number from a string with an invalid format.
|
* Exception thrown when attempting to create a number from a string with an invalid format.
|
||||||
*/
|
*/
|
||||||
class NumberFormatException extends MathException
|
final class NumberFormatException extends MathException
|
||||||
{
|
{
|
||||||
public static function invalidFormat(string $value) : self
|
/**
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
public static function invalidFormat(string $value): self
|
||||||
{
|
{
|
||||||
return new self(\sprintf(
|
return new self(sprintf(
|
||||||
'The given value "%s" does not represent a valid number.',
|
'The given value "%s" does not represent a valid number.',
|
||||||
$value,
|
$value,
|
||||||
));
|
));
|
||||||
@@ -20,22 +28,33 @@ class NumberFormatException extends MathException
|
|||||||
/**
|
/**
|
||||||
* @param string $char The failing character.
|
* @param string $char The failing character.
|
||||||
*
|
*
|
||||||
* @psalm-pure
|
* @pure
|
||||||
*/
|
*/
|
||||||
public static function charNotInAlphabet(string $char) : self
|
public static function charNotInAlphabet(string $char): self
|
||||||
{
|
{
|
||||||
$ord = \ord($char);
|
return new self(sprintf(
|
||||||
|
'Character %s is not valid in the given alphabet.',
|
||||||
|
self::charToString($char),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
private static function charToString(string $char): string
|
||||||
|
{
|
||||||
|
$ord = ord($char);
|
||||||
|
|
||||||
if ($ord < 32 || $ord > 126) {
|
if ($ord < 32 || $ord > 126) {
|
||||||
$char = \strtoupper(\dechex($ord));
|
$char = strtoupper(dechex($ord));
|
||||||
|
|
||||||
if ($ord < 10) {
|
if ($ord < 16) {
|
||||||
$char = '0' . $char;
|
$char = '0' . $char;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
$char = '"' . $char . '"';
|
return '0x' . $char;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new self(\sprintf('Char %s is not a valid character in the given alphabet.', $char));
|
return '"' . $char . '"';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ namespace Brick\Math\Exception;
|
|||||||
/**
|
/**
|
||||||
* Exception thrown when a number cannot be represented at the requested scale without rounding.
|
* Exception thrown when a number cannot be represented at the requested scale without rounding.
|
||||||
*/
|
*/
|
||||||
class RoundingNecessaryException extends MathException
|
final class RoundingNecessaryException extends MathException
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @psalm-pure
|
* @pure
|
||||||
*/
|
*/
|
||||||
public static function roundingNecessary() : RoundingNecessaryException
|
public static function roundingNecessary(): RoundingNecessaryException
|
||||||
{
|
{
|
||||||
return new self('Rounding is necessary to represent the result of the operation at this scale.');
|
return new self('Rounding is necessary to represent the result of the operation at this scale.');
|
||||||
}
|
}
|
||||||
|
|||||||
+193
-157
@@ -7,6 +7,16 @@ namespace Brick\Math\Internal;
|
|||||||
use Brick\Math\Exception\RoundingNecessaryException;
|
use Brick\Math\Exception\RoundingNecessaryException;
|
||||||
use Brick\Math\RoundingMode;
|
use Brick\Math\RoundingMode;
|
||||||
|
|
||||||
|
use function chr;
|
||||||
|
use function ltrim;
|
||||||
|
use function ord;
|
||||||
|
use function str_repeat;
|
||||||
|
use function strlen;
|
||||||
|
use function strpos;
|
||||||
|
use function strrev;
|
||||||
|
use function strtolower;
|
||||||
|
use function substr;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs basic operations on arbitrary size integers.
|
* Performs basic operations on arbitrary size integers.
|
||||||
*
|
*
|
||||||
@@ -17,10 +27,8 @@ use Brick\Math\RoundingMode;
|
|||||||
* All methods must return strings respecting this format, unless specified otherwise.
|
* All methods must return strings respecting this format, unless specified otherwise.
|
||||||
*
|
*
|
||||||
* @internal
|
* @internal
|
||||||
*
|
|
||||||
* @psalm-immutable
|
|
||||||
*/
|
*/
|
||||||
abstract class Calculator
|
abstract readonly class Calculator
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The maximum exponent value allowed for the pow() method.
|
* The maximum exponent value allowed for the pow() method.
|
||||||
@@ -32,94 +40,29 @@ abstract class Calculator
|
|||||||
*/
|
*/
|
||||||
public const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz';
|
public const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz';
|
||||||
|
|
||||||
/**
|
|
||||||
* The Calculator instance in use.
|
|
||||||
*/
|
|
||||||
private static ?Calculator $instance = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the Calculator instance to use.
|
|
||||||
*
|
|
||||||
* An instance is typically set only in unit tests: the autodetect is usually the best option.
|
|
||||||
*
|
|
||||||
* @param Calculator|null $calculator The calculator instance, or NULL to revert to autodetect.
|
|
||||||
*/
|
|
||||||
final public static function set(?Calculator $calculator) : void
|
|
||||||
{
|
|
||||||
self::$instance = $calculator;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the Calculator instance to use.
|
|
||||||
*
|
|
||||||
* If none has been explicitly set, the fastest available implementation will be returned.
|
|
||||||
*
|
|
||||||
* @psalm-pure
|
|
||||||
* @psalm-suppress ImpureStaticProperty
|
|
||||||
*/
|
|
||||||
final public static function get() : Calculator
|
|
||||||
{
|
|
||||||
if (self::$instance === null) {
|
|
||||||
/** @psalm-suppress ImpureMethodCall */
|
|
||||||
self::$instance = self::detect();
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::$instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the fastest available Calculator implementation.
|
|
||||||
*
|
|
||||||
* @codeCoverageIgnore
|
|
||||||
*/
|
|
||||||
private static function detect() : Calculator
|
|
||||||
{
|
|
||||||
if (\extension_loaded('gmp')) {
|
|
||||||
return new Calculator\GmpCalculator();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (\extension_loaded('bcmath')) {
|
|
||||||
return new Calculator\BcMathCalculator();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Calculator\NativeCalculator();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts the sign & digits of the operands.
|
|
||||||
*
|
|
||||||
* @return array{bool, bool, string, string} Whether $a and $b are negative, followed by their digits.
|
|
||||||
*/
|
|
||||||
final protected function init(string $a, string $b) : array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
$aNeg = ($a[0] === '-'),
|
|
||||||
$bNeg = ($b[0] === '-'),
|
|
||||||
|
|
||||||
$aNeg ? \substr($a, 1) : $a,
|
|
||||||
$bNeg ? \substr($b, 1) : $b,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the absolute value of a number.
|
* Returns the absolute value of a number.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
final public function abs(string $n) : string
|
final public function abs(string $n): string
|
||||||
{
|
{
|
||||||
return ($n[0] === '-') ? \substr($n, 1) : $n;
|
return ($n[0] === '-') ? substr($n, 1) : $n;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Negates a number.
|
* Negates a number.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
final public function neg(string $n) : string
|
final public function neg(string $n): string
|
||||||
{
|
{
|
||||||
if ($n === '0') {
|
if ($n === '0') {
|
||||||
return '0';
|
return '0';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($n[0] === '-') {
|
if ($n[0] === '-') {
|
||||||
return \substr($n, 1);
|
return substr($n, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return '-' . $n;
|
return '-' . $n;
|
||||||
@@ -128,11 +71,13 @@ abstract class Calculator
|
|||||||
/**
|
/**
|
||||||
* Compares two numbers.
|
* Compares two numbers.
|
||||||
*
|
*
|
||||||
* @psalm-return -1|0|1
|
* Returns -1 if the first number is less than, 0 if equal to, 1 if greater than the second number.
|
||||||
*
|
*
|
||||||
* @return int -1 if the first number is less than, 0 if equal to, 1 if greater than the second number.
|
* @return -1|0|1
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
final public function cmp(string $a, string $b) : int
|
final public function cmp(string $a, string $b): int
|
||||||
{
|
{
|
||||||
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
|
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
|
||||||
|
|
||||||
@@ -144,8 +89,8 @@ abstract class Calculator
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
$aLen = \strlen($aDig);
|
$aLen = strlen($aDig);
|
||||||
$bLen = \strlen($bDig);
|
$bLen = strlen($bDig);
|
||||||
|
|
||||||
if ($aLen < $bLen) {
|
if ($aLen < $bLen) {
|
||||||
$result = -1;
|
$result = -1;
|
||||||
@@ -160,18 +105,24 @@ abstract class Calculator
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds two numbers.
|
* Adds two numbers.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
abstract public function add(string $a, string $b) : string;
|
abstract public function add(string $a, string $b): string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subtracts two numbers.
|
* Subtracts two numbers.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
abstract public function sub(string $a, string $b) : string;
|
abstract public function sub(string $a, string $b): string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Multiplies two numbers.
|
* Multiplies two numbers.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
abstract public function mul(string $a, string $b) : string;
|
abstract public function mul(string $a, string $b): string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the quotient of the division of two numbers.
|
* Returns the quotient of the division of two numbers.
|
||||||
@@ -180,8 +131,10 @@ abstract class Calculator
|
|||||||
* @param string $b The divisor, must not be zero.
|
* @param string $b The divisor, must not be zero.
|
||||||
*
|
*
|
||||||
* @return string The quotient.
|
* @return string The quotient.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
abstract public function divQ(string $a, string $b) : string;
|
abstract public function divQ(string $a, string $b): string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the remainder of the division of two numbers.
|
* Returns the remainder of the division of two numbers.
|
||||||
@@ -190,8 +143,10 @@ abstract class Calculator
|
|||||||
* @param string $b The divisor, must not be zero.
|
* @param string $b The divisor, must not be zero.
|
||||||
*
|
*
|
||||||
* @return string The remainder.
|
* @return string The remainder.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
abstract public function divR(string $a, string $b) : string;
|
abstract public function divR(string $a, string $b): string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the quotient and remainder of the division of two numbers.
|
* Returns the quotient and remainder of the division of two numbers.
|
||||||
@@ -200,8 +155,10 @@ abstract class Calculator
|
|||||||
* @param string $b The divisor, must not be zero.
|
* @param string $b The divisor, must not be zero.
|
||||||
*
|
*
|
||||||
* @return array{string, string} An array containing the quotient and remainder.
|
* @return array{string, string} An array containing the quotient and remainder.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
abstract public function divQR(string $a, string $b) : array;
|
abstract public function divQR(string $a, string $b): array;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exponentiates a number.
|
* Exponentiates a number.
|
||||||
@@ -210,13 +167,17 @@ abstract class Calculator
|
|||||||
* @param int $e The exponent, validated as an integer between 0 and MAX_POWER.
|
* @param int $e The exponent, validated as an integer between 0 and MAX_POWER.
|
||||||
*
|
*
|
||||||
* @return string The power.
|
* @return string The power.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
abstract public function pow(string $a, int $e) : string;
|
abstract public function pow(string $a, int $e): string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $b The modulus; must not be zero.
|
* @param string $b The modulus; must not be zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
public function mod(string $a, string $b) : string
|
public function mod(string $a, string $b): string
|
||||||
{
|
{
|
||||||
return $this->divR($this->add($this->divR($a, $b), $b), $b);
|
return $this->divR($this->add($this->divR($a, $b), $b), $b);
|
||||||
}
|
}
|
||||||
@@ -229,8 +190,10 @@ abstract class Calculator
|
|||||||
* This method can be overridden by the concrete implementation if the underlying library has built-in support.
|
* This method can be overridden by the concrete implementation if the underlying library has built-in support.
|
||||||
*
|
*
|
||||||
* @param string $m The modulus; must not be negative or zero.
|
* @param string $m The modulus; must not be negative or zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
public function modInverse(string $x, string $m) : ?string
|
public function modInverse(string $x, string $m): ?string
|
||||||
{
|
{
|
||||||
if ($m === '1') {
|
if ($m === '1') {
|
||||||
return '0';
|
return '0';
|
||||||
@@ -254,11 +217,13 @@ abstract class Calculator
|
|||||||
/**
|
/**
|
||||||
* Raises a number into power with modulo.
|
* Raises a number into power with modulo.
|
||||||
*
|
*
|
||||||
* @param string $base The base number; must be positive or zero.
|
* @param string $base The base number.
|
||||||
* @param string $exp The exponent; must be positive or zero.
|
* @param string $exp The exponent; must be positive or zero.
|
||||||
* @param string $mod The modulus; must be strictly positive.
|
* @param string $mod The modulus; must be strictly positive.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
abstract public function modPow(string $base, string $exp, string $mod) : string;
|
abstract public function modPow(string $base, string $exp, string $mod): string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the greatest common divisor of the two numbers.
|
* Returns the greatest common divisor of the two numbers.
|
||||||
@@ -267,8 +232,10 @@ abstract class Calculator
|
|||||||
* has built-in support for GCD calculations.
|
* has built-in support for GCD calculations.
|
||||||
*
|
*
|
||||||
* @return string The GCD, always positive, or zero if both arguments are zero.
|
* @return string The GCD, always positive, or zero if both arguments are zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
public function gcd(string $a, string $b) : string
|
public function gcd(string $a, string $b): string
|
||||||
{
|
{
|
||||||
if ($a === '0') {
|
if ($a === '0') {
|
||||||
return $this->abs($b);
|
return $this->abs($b);
|
||||||
@@ -282,20 +249,22 @@ abstract class Calculator
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array{string, string, string} GCD, X, Y
|
* Returns the least common multiple of the two numbers.
|
||||||
|
*
|
||||||
|
* This method can be overridden by the concrete implementation if the underlying library
|
||||||
|
* has built-in support for LCM calculations.
|
||||||
|
*
|
||||||
|
* @return string The LCM, always positive, or zero if at least one argument is zero.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
private function gcdExtended(string $a, string $b) : array
|
public function lcm(string $a, string $b): string
|
||||||
{
|
{
|
||||||
if ($a === '0') {
|
if ($a === '0' || $b === '0') {
|
||||||
return [$b, '0', '1'];
|
return '0';
|
||||||
}
|
}
|
||||||
|
|
||||||
[$gcd, $x1, $y1] = $this->gcdExtended($this->mod($b, $a), $a);
|
return $this->divQ($this->abs($this->mul($a, $b)), $this->gcd($a, $b));
|
||||||
|
|
||||||
$x = $this->sub($y1, $this->mul($this->divQ($b, $a), $x1));
|
|
||||||
$y = $x1;
|
|
||||||
|
|
||||||
return [$gcd, $x, $y];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -303,8 +272,10 @@ abstract class Calculator
|
|||||||
*
|
*
|
||||||
* The result is the largest x such that x² ≤ n.
|
* The result is the largest x such that x² ≤ n.
|
||||||
* The input MUST NOT be negative.
|
* The input MUST NOT be negative.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
abstract public function sqrt(string $n) : string;
|
abstract public function sqrt(string $n): string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a number from an arbitrary base.
|
* Converts a number from an arbitrary base.
|
||||||
@@ -316,10 +287,12 @@ abstract class Calculator
|
|||||||
* @param int $base The base of the number, validated from 2 to 36.
|
* @param int $base The base of the number, validated from 2 to 36.
|
||||||
*
|
*
|
||||||
* @return string The converted number, following the Calculator conventions.
|
* @return string The converted number, following the Calculator conventions.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
public function fromBase(string $number, int $base) : string
|
public function fromBase(string $number, int $base): string
|
||||||
{
|
{
|
||||||
return $this->fromArbitraryBase(\strtolower($number), self::ALPHABET, $base);
|
return $this->fromArbitraryBase(strtolower($number), self::ALPHABET, $base);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -332,13 +305,15 @@ abstract class Calculator
|
|||||||
* @param int $base The base to convert to, validated from 2 to 36.
|
* @param int $base The base to convert to, validated from 2 to 36.
|
||||||
*
|
*
|
||||||
* @return string The converted number, lowercase.
|
* @return string The converted number, lowercase.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
public function toBase(string $number, int $base) : string
|
public function toBase(string $number, int $base): string
|
||||||
{
|
{
|
||||||
$negative = ($number[0] === '-');
|
$negative = ($number[0] === '-');
|
||||||
|
|
||||||
if ($negative) {
|
if ($negative) {
|
||||||
$number = \substr($number, 1);
|
$number = substr($number, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
$number = $this->toArbitraryBase($number, self::ALPHABET, $base);
|
$number = $this->toArbitraryBase($number, self::ALPHABET, $base);
|
||||||
@@ -359,11 +334,13 @@ abstract class Calculator
|
|||||||
* @param int $base The base of the number, validated from 2 to alphabet length.
|
* @param int $base The base of the number, validated from 2 to alphabet length.
|
||||||
*
|
*
|
||||||
* @return string The number in base 10, following the Calculator conventions.
|
* @return string The number in base 10, following the Calculator conventions.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
final public function fromArbitraryBase(string $number, string $alphabet, int $base) : string
|
final public function fromArbitraryBase(string $number, string $alphabet, int $base): string
|
||||||
{
|
{
|
||||||
// remove leading "zeros"
|
// remove leading "zeros"
|
||||||
$number = \ltrim($number, $alphabet[0]);
|
$number = ltrim($number, $alphabet[0]);
|
||||||
|
|
||||||
if ($number === '') {
|
if ($number === '') {
|
||||||
return '0';
|
return '0';
|
||||||
@@ -379,13 +356,13 @@ abstract class Calculator
|
|||||||
|
|
||||||
$base = (string) $base;
|
$base = (string) $base;
|
||||||
|
|
||||||
for ($i = \strlen($number) - 1; $i >= 0; $i--) {
|
for ($i = strlen($number) - 1; $i >= 0; $i--) {
|
||||||
$index = \strpos($alphabet, $number[$i]);
|
$index = strpos($alphabet, $number[$i]);
|
||||||
|
|
||||||
if ($index !== 0) {
|
if ($index !== 0) {
|
||||||
$result = $this->add($result, ($index === 1)
|
$result = $this->add(
|
||||||
? $power
|
$result,
|
||||||
: $this->mul($power, (string) $index)
|
($index === 1) ? $power : $this->mul($power, (string) $index),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,8 +382,10 @@ abstract class Calculator
|
|||||||
* @param int $base The base to convert to, validated from 2 to alphabet length.
|
* @param int $base The base to convert to, validated from 2 to alphabet length.
|
||||||
*
|
*
|
||||||
* @return string The converted number in the given alphabet.
|
* @return string The converted number in the given alphabet.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
final public function toArbitraryBase(string $number, string $alphabet, int $base) : string
|
final public function toArbitraryBase(string $number, string $alphabet, int $base): string
|
||||||
{
|
{
|
||||||
if ($number === '0') {
|
if ($number === '0') {
|
||||||
return $alphabet[0];
|
return $alphabet[0];
|
||||||
@@ -422,7 +401,7 @@ abstract class Calculator
|
|||||||
$result .= $alphabet[$remainder];
|
$result .= $alphabet[$remainder];
|
||||||
}
|
}
|
||||||
|
|
||||||
return \strrev($result);
|
return strrev($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -434,19 +413,18 @@ abstract class Calculator
|
|||||||
* @param string $b The divisor, must not be zero.
|
* @param string $b The divisor, must not be zero.
|
||||||
* @param RoundingMode $roundingMode The rounding mode.
|
* @param RoundingMode $roundingMode The rounding mode.
|
||||||
*
|
*
|
||||||
* @throws \InvalidArgumentException If the rounding mode is invalid.
|
* @throws RoundingNecessaryException If RoundingMode::Unnecessary is provided but rounding is necessary.
|
||||||
* @throws RoundingNecessaryException If RoundingMode::UNNECESSARY is provided but rounding is necessary.
|
|
||||||
*
|
*
|
||||||
* @psalm-suppress ImpureFunctionCall
|
* @pure
|
||||||
*/
|
*/
|
||||||
final public function divRound(string $a, string $b, RoundingMode $roundingMode) : string
|
final public function divRound(string $a, string $b, RoundingMode $roundingMode): string
|
||||||
{
|
{
|
||||||
[$quotient, $remainder] = $this->divQR($a, $b);
|
[$quotient, $remainder] = $this->divQR($a, $b);
|
||||||
|
|
||||||
$hasDiscardedFraction = ($remainder !== '0');
|
$hasDiscardedFraction = ($remainder !== '0');
|
||||||
$isPositiveOrZero = ($a[0] === '-') === ($b[0] === '-');
|
$isPositiveOrZero = ($a[0] === '-') === ($b[0] === '-');
|
||||||
|
|
||||||
$discardedFractionSign = function() use ($remainder, $b) : int {
|
$discardedFractionSign = function () use ($remainder, $b): int {
|
||||||
$r = $this->abs($this->mul($remainder, '2'));
|
$r = $this->abs($this->mul($remainder, '2'));
|
||||||
$b = $this->abs($b);
|
$b = $this->abs($b);
|
||||||
|
|
||||||
@@ -456,51 +434,57 @@ abstract class Calculator
|
|||||||
$increment = false;
|
$increment = false;
|
||||||
|
|
||||||
switch ($roundingMode) {
|
switch ($roundingMode) {
|
||||||
case RoundingMode::UNNECESSARY:
|
case RoundingMode::Unnecessary:
|
||||||
if ($hasDiscardedFraction) {
|
if ($hasDiscardedFraction) {
|
||||||
throw RoundingNecessaryException::roundingNecessary();
|
throw RoundingNecessaryException::roundingNecessary();
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RoundingMode::UP:
|
case RoundingMode::Up:
|
||||||
$increment = $hasDiscardedFraction;
|
$increment = $hasDiscardedFraction;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RoundingMode::DOWN:
|
case RoundingMode::Down:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RoundingMode::CEILING:
|
case RoundingMode::Ceiling:
|
||||||
$increment = $hasDiscardedFraction && $isPositiveOrZero;
|
$increment = $hasDiscardedFraction && $isPositiveOrZero;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RoundingMode::FLOOR:
|
case RoundingMode::Floor:
|
||||||
$increment = $hasDiscardedFraction && ! $isPositiveOrZero;
|
$increment = $hasDiscardedFraction && ! $isPositiveOrZero;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RoundingMode::HALF_UP:
|
case RoundingMode::HalfUp:
|
||||||
$increment = $discardedFractionSign() >= 0;
|
$increment = $discardedFractionSign() >= 0;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RoundingMode::HALF_DOWN:
|
case RoundingMode::HalfDown:
|
||||||
$increment = $discardedFractionSign() > 0;
|
$increment = $discardedFractionSign() > 0;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RoundingMode::HALF_CEILING:
|
case RoundingMode::HalfCeiling:
|
||||||
$increment = $isPositiveOrZero ? $discardedFractionSign() >= 0 : $discardedFractionSign() > 0;
|
$increment = $isPositiveOrZero ? $discardedFractionSign() >= 0 : $discardedFractionSign() > 0;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RoundingMode::HALF_FLOOR:
|
case RoundingMode::HalfFloor:
|
||||||
$increment = $isPositiveOrZero ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0;
|
$increment = $isPositiveOrZero ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RoundingMode::HALF_EVEN:
|
case RoundingMode::HalfEven:
|
||||||
$lastDigit = (int) $quotient[-1];
|
$lastDigit = (int) $quotient[-1];
|
||||||
$lastDigitIsEven = ($lastDigit % 2 === 0);
|
$lastDigitIsEven = ($lastDigit % 2 === 0);
|
||||||
$increment = $lastDigitIsEven ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0;
|
$increment = $lastDigitIsEven ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0;
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
break;
|
||||||
throw new \InvalidArgumentException('Invalid rounding mode.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($increment) {
|
if ($increment) {
|
||||||
@@ -515,8 +499,10 @@ abstract class Calculator
|
|||||||
*
|
*
|
||||||
* This method can be overridden by the concrete implementation if the underlying library
|
* This method can be overridden by the concrete implementation if the underlying library
|
||||||
* has built-in support for bitwise operations.
|
* has built-in support for bitwise operations.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
public function and(string $a, string $b) : string
|
public function and(string $a, string $b): string
|
||||||
{
|
{
|
||||||
return $this->bitwise('and', $a, $b);
|
return $this->bitwise('and', $a, $b);
|
||||||
}
|
}
|
||||||
@@ -526,8 +512,10 @@ abstract class Calculator
|
|||||||
*
|
*
|
||||||
* This method can be overridden by the concrete implementation if the underlying library
|
* This method can be overridden by the concrete implementation if the underlying library
|
||||||
* has built-in support for bitwise operations.
|
* has built-in support for bitwise operations.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
public function or(string $a, string $b) : string
|
public function or(string $a, string $b): string
|
||||||
{
|
{
|
||||||
return $this->bitwise('or', $a, $b);
|
return $this->bitwise('or', $a, $b);
|
||||||
}
|
}
|
||||||
@@ -537,33 +525,74 @@ abstract class Calculator
|
|||||||
*
|
*
|
||||||
* This method can be overridden by the concrete implementation if the underlying library
|
* This method can be overridden by the concrete implementation if the underlying library
|
||||||
* has built-in support for bitwise operations.
|
* has built-in support for bitwise operations.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
public function xor(string $a, string $b) : string
|
public function xor(string $a, string $b): string
|
||||||
{
|
{
|
||||||
return $this->bitwise('xor', $a, $b);
|
return $this->bitwise('xor', $a, $b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the sign & digits of the operands.
|
||||||
|
*
|
||||||
|
* @return array{bool, bool, string, string} Whether $a and $b are negative, followed by their digits.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final protected function init(string $a, string $b): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
$aNeg = ($a[0] === '-'),
|
||||||
|
$bNeg = ($b[0] === '-'),
|
||||||
|
|
||||||
|
$aNeg ? substr($a, 1) : $a,
|
||||||
|
$bNeg ? substr($b, 1) : $b,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{string, string, string} GCD, X, Y
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
private function gcdExtended(string $a, string $b): array
|
||||||
|
{
|
||||||
|
if ($a === '0') {
|
||||||
|
return [$b, '0', '1'];
|
||||||
|
}
|
||||||
|
|
||||||
|
[$gcd, $x1, $y1] = $this->gcdExtended($this->mod($b, $a), $a);
|
||||||
|
|
||||||
|
$x = $this->sub($y1, $this->mul($this->divQ($b, $a), $x1));
|
||||||
|
$y = $x1;
|
||||||
|
|
||||||
|
return [$gcd, $x, $y];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs a bitwise operation on a decimal number.
|
* Performs a bitwise operation on a decimal number.
|
||||||
*
|
*
|
||||||
* @param 'and'|'or'|'xor' $operator The operator to use.
|
* @param 'and'|'or'|'xor' $operator The operator to use.
|
||||||
* @param string $a The left operand.
|
* @param string $a The left operand.
|
||||||
* @param string $b The right operand.
|
* @param string $b The right operand.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
private function bitwise(string $operator, string $a, string $b) : string
|
private function bitwise(string $operator, string $a, string $b): string
|
||||||
{
|
{
|
||||||
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
|
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
|
||||||
|
|
||||||
$aBin = $this->toBinary($aDig);
|
$aBin = $this->toBinary($aDig);
|
||||||
$bBin = $this->toBinary($bDig);
|
$bBin = $this->toBinary($bDig);
|
||||||
|
|
||||||
$aLen = \strlen($aBin);
|
$aLen = strlen($aBin);
|
||||||
$bLen = \strlen($bBin);
|
$bLen = strlen($bBin);
|
||||||
|
|
||||||
if ($aLen > $bLen) {
|
if ($aLen > $bLen) {
|
||||||
$bBin = \str_repeat("\x00", $aLen - $bLen) . $bBin;
|
$bBin = str_repeat("\x00", $aLen - $bLen) . $bBin;
|
||||||
} elseif ($bLen > $aLen) {
|
} elseif ($bLen > $aLen) {
|
||||||
$aBin = \str_repeat("\x00", $bLen - $aLen) . $aBin;
|
$aBin = str_repeat("\x00", $bLen - $aLen) . $aBin;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($aNeg) {
|
if ($aNeg) {
|
||||||
@@ -596,18 +625,21 @@ abstract class Calculator
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $number A positive, binary number.
|
* @param string $number A positive, binary number.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
private function twosComplement(string $number) : string
|
private function twosComplement(string $number): string
|
||||||
{
|
{
|
||||||
$xor = \str_repeat("\xff", \strlen($number));
|
$xor = str_repeat("\xff", strlen($number));
|
||||||
|
|
||||||
$number ^= $xor;
|
$number ^= $xor;
|
||||||
|
|
||||||
for ($i = \strlen($number) - 1; $i >= 0; $i--) {
|
for ($i = strlen($number) - 1; $i >= 0; $i--) {
|
||||||
$byte = \ord($number[$i]);
|
$byte = ord($number[$i]);
|
||||||
|
|
||||||
if (++$byte !== 256) {
|
if (++$byte !== 256) {
|
||||||
$number[$i] = \chr($byte);
|
$number[$i] = chr($byte);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -625,36 +657,40 @@ abstract class Calculator
|
|||||||
* Converts a decimal number to a binary string.
|
* Converts a decimal number to a binary string.
|
||||||
*
|
*
|
||||||
* @param string $number The number to convert, positive or zero, only digits.
|
* @param string $number The number to convert, positive or zero, only digits.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
private function toBinary(string $number) : string
|
private function toBinary(string $number): string
|
||||||
{
|
{
|
||||||
$result = '';
|
$result = '';
|
||||||
|
|
||||||
while ($number !== '0') {
|
while ($number !== '0') {
|
||||||
[$number, $remainder] = $this->divQR($number, '256');
|
[$number, $remainder] = $this->divQR($number, '256');
|
||||||
$result .= \chr((int) $remainder);
|
$result .= chr((int) $remainder);
|
||||||
}
|
}
|
||||||
|
|
||||||
return \strrev($result);
|
return strrev($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the positive decimal representation of a binary number.
|
* Returns the positive decimal representation of a binary number.
|
||||||
*
|
*
|
||||||
* @param string $bytes The bytes representing the number.
|
* @param string $bytes The bytes representing the number.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
private function toDecimal(string $bytes) : string
|
private function toDecimal(string $bytes): string
|
||||||
{
|
{
|
||||||
$result = '0';
|
$result = '0';
|
||||||
$power = '1';
|
$power = '1';
|
||||||
|
|
||||||
for ($i = \strlen($bytes) - 1; $i >= 0; $i--) {
|
for ($i = strlen($bytes) - 1; $i >= 0; $i--) {
|
||||||
$index = \ord($bytes[$i]);
|
$index = ord($bytes[$i]);
|
||||||
|
|
||||||
if ($index !== 0) {
|
if ($index !== 0) {
|
||||||
$result = $this->add($result, ($index === 1)
|
$result = $this->add(
|
||||||
? $power
|
$result,
|
||||||
: $this->mul($power, (string) $index)
|
($index === 1) ? $power : $this->mul($power, (string) $index),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+32
-22
@@ -7,69 +7,79 @@ namespace Brick\Math\Internal\Calculator;
|
|||||||
use Brick\Math\Internal\Calculator;
|
use Brick\Math\Internal\Calculator;
|
||||||
use Override;
|
use Override;
|
||||||
|
|
||||||
|
use function bcadd;
|
||||||
|
use function bcdiv;
|
||||||
|
use function bcmod;
|
||||||
|
use function bcmul;
|
||||||
|
use function bcpow;
|
||||||
|
use function bcpowmod;
|
||||||
|
use function bcsqrt;
|
||||||
|
use function bcsub;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculator implementation built around the bcmath library.
|
* Calculator implementation built around the bcmath library.
|
||||||
*
|
*
|
||||||
* @internal
|
* @internal
|
||||||
*
|
|
||||||
* @psalm-immutable
|
|
||||||
*/
|
*/
|
||||||
class BcMathCalculator extends Calculator
|
final readonly class BcMathCalculator extends Calculator
|
||||||
{
|
{
|
||||||
#[Override]
|
#[Override]
|
||||||
public function add(string $a, string $b) : string
|
public function add(string $a, string $b): string
|
||||||
{
|
{
|
||||||
return \bcadd($a, $b, 0);
|
return bcadd($a, $b, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function sub(string $a, string $b) : string
|
public function sub(string $a, string $b): string
|
||||||
{
|
{
|
||||||
return \bcsub($a, $b, 0);
|
return bcsub($a, $b, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function mul(string $a, string $b) : string
|
public function mul(string $a, string $b): string
|
||||||
{
|
{
|
||||||
return \bcmul($a, $b, 0);
|
return bcmul($a, $b, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function divQ(string $a, string $b) : string
|
public function divQ(string $a, string $b): string
|
||||||
{
|
{
|
||||||
return \bcdiv($a, $b, 0);
|
return bcdiv($a, $b, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function divR(string $a, string $b) : string
|
public function divR(string $a, string $b): string
|
||||||
{
|
{
|
||||||
return \bcmod($a, $b, 0);
|
return bcmod($a, $b, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function divQR(string $a, string $b) : array
|
public function divQR(string $a, string $b): array
|
||||||
{
|
{
|
||||||
$q = \bcdiv($a, $b, 0);
|
$q = bcdiv($a, $b, 0);
|
||||||
$r = \bcmod($a, $b, 0);
|
$r = bcmod($a, $b, 0);
|
||||||
|
|
||||||
return [$q, $r];
|
return [$q, $r];
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function pow(string $a, int $e) : string
|
public function pow(string $a, int $e): string
|
||||||
{
|
{
|
||||||
return \bcpow($a, (string) $e, 0);
|
return bcpow($a, (string) $e, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function modPow(string $base, string $exp, string $mod) : string
|
public function modPow(string $base, string $exp, string $mod): string
|
||||||
{
|
{
|
||||||
return \bcpowmod($base, $exp, $mod, 0);
|
// normalize to Euclidean representative so modPow() stays consistent with mod()
|
||||||
|
$base = $this->mod($base, $mod);
|
||||||
|
|
||||||
|
return bcpowmod($base, $exp, $mod, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function sqrt(string $n) : string
|
public function sqrt(string $n): string
|
||||||
{
|
{
|
||||||
return \bcsqrt($n, 0);
|
return bcsqrt($n, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+65
-38
@@ -5,121 +5,148 @@ declare(strict_types=1);
|
|||||||
namespace Brick\Math\Internal\Calculator;
|
namespace Brick\Math\Internal\Calculator;
|
||||||
|
|
||||||
use Brick\Math\Internal\Calculator;
|
use Brick\Math\Internal\Calculator;
|
||||||
|
use GMP;
|
||||||
use Override;
|
use Override;
|
||||||
|
|
||||||
|
use function gmp_add;
|
||||||
|
use function gmp_and;
|
||||||
|
use function gmp_div_q;
|
||||||
|
use function gmp_div_qr;
|
||||||
|
use function gmp_div_r;
|
||||||
|
use function gmp_gcd;
|
||||||
|
use function gmp_init;
|
||||||
|
use function gmp_invert;
|
||||||
|
use function gmp_lcm;
|
||||||
|
use function gmp_mul;
|
||||||
|
use function gmp_or;
|
||||||
|
use function gmp_pow;
|
||||||
|
use function gmp_powm;
|
||||||
|
use function gmp_sqrt;
|
||||||
|
use function gmp_strval;
|
||||||
|
use function gmp_sub;
|
||||||
|
use function gmp_xor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculator implementation built around the GMP library.
|
* Calculator implementation built around the GMP library.
|
||||||
*
|
*
|
||||||
* @internal
|
* @internal
|
||||||
*
|
|
||||||
* @psalm-immutable
|
|
||||||
*/
|
*/
|
||||||
class GmpCalculator extends Calculator
|
final readonly class GmpCalculator extends Calculator
|
||||||
{
|
{
|
||||||
#[Override]
|
#[Override]
|
||||||
public function add(string $a, string $b) : string
|
public function add(string $a, string $b): string
|
||||||
{
|
{
|
||||||
return \gmp_strval(\gmp_add($a, $b));
|
return gmp_strval(gmp_add($a, $b));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function sub(string $a, string $b) : string
|
public function sub(string $a, string $b): string
|
||||||
{
|
{
|
||||||
return \gmp_strval(\gmp_sub($a, $b));
|
return gmp_strval(gmp_sub($a, $b));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function mul(string $a, string $b) : string
|
public function mul(string $a, string $b): string
|
||||||
{
|
{
|
||||||
return \gmp_strval(\gmp_mul($a, $b));
|
return gmp_strval(gmp_mul($a, $b));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function divQ(string $a, string $b) : string
|
public function divQ(string $a, string $b): string
|
||||||
{
|
{
|
||||||
return \gmp_strval(\gmp_div_q($a, $b));
|
return gmp_strval(gmp_div_q($a, $b));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function divR(string $a, string $b) : string
|
public function divR(string $a, string $b): string
|
||||||
{
|
{
|
||||||
return \gmp_strval(\gmp_div_r($a, $b));
|
return gmp_strval(gmp_div_r($a, $b));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function divQR(string $a, string $b) : array
|
public function divQR(string $a, string $b): array
|
||||||
{
|
{
|
||||||
[$q, $r] = \gmp_div_qr($a, $b);
|
[$q, $r] = gmp_div_qr($a, $b);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var GMP $q
|
||||||
|
* @var GMP $r
|
||||||
|
*/
|
||||||
return [
|
return [
|
||||||
\gmp_strval($q),
|
gmp_strval($q),
|
||||||
\gmp_strval($r)
|
gmp_strval($r),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function pow(string $a, int $e) : string
|
public function pow(string $a, int $e): string
|
||||||
{
|
{
|
||||||
return \gmp_strval(\gmp_pow($a, $e));
|
return gmp_strval(gmp_pow($a, $e));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function modInverse(string $x, string $m) : ?string
|
public function modInverse(string $x, string $m): ?string
|
||||||
{
|
{
|
||||||
$result = \gmp_invert($x, $m);
|
$result = gmp_invert($x, $m);
|
||||||
|
|
||||||
if ($result === false) {
|
if ($result === false) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return \gmp_strval($result);
|
return gmp_strval($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function modPow(string $base, string $exp, string $mod) : string
|
public function modPow(string $base, string $exp, string $mod): string
|
||||||
{
|
{
|
||||||
return \gmp_strval(\gmp_powm($base, $exp, $mod));
|
return gmp_strval(gmp_powm($base, $exp, $mod));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function gcd(string $a, string $b) : string
|
public function gcd(string $a, string $b): string
|
||||||
{
|
{
|
||||||
return \gmp_strval(\gmp_gcd($a, $b));
|
return gmp_strval(gmp_gcd($a, $b));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function fromBase(string $number, int $base) : string
|
public function lcm(string $a, string $b): string
|
||||||
{
|
{
|
||||||
return \gmp_strval(\gmp_init($number, $base));
|
return gmp_strval(gmp_lcm($a, $b));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function toBase(string $number, int $base) : string
|
public function fromBase(string $number, int $base): string
|
||||||
{
|
{
|
||||||
return \gmp_strval($number, $base);
|
return gmp_strval(gmp_init($number, $base));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function and(string $a, string $b) : string
|
public function toBase(string $number, int $base): string
|
||||||
{
|
{
|
||||||
return \gmp_strval(\gmp_and($a, $b));
|
return gmp_strval($number, $base);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function or(string $a, string $b) : string
|
public function and(string $a, string $b): string
|
||||||
{
|
{
|
||||||
return \gmp_strval(\gmp_or($a, $b));
|
return gmp_strval(gmp_and($a, $b));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function xor(string $a, string $b) : string
|
public function or(string $a, string $b): string
|
||||||
{
|
{
|
||||||
return \gmp_strval(\gmp_xor($a, $b));
|
return gmp_strval(gmp_or($a, $b));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function sqrt(string $n) : string
|
public function xor(string $a, string $b): string
|
||||||
{
|
{
|
||||||
return \gmp_strval(\gmp_sqrt($n));
|
return gmp_strval(gmp_xor($a, $b));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function sqrt(string $n): string
|
||||||
|
{
|
||||||
|
return gmp_strval(gmp_sqrt($n));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+102
-84
@@ -7,14 +7,26 @@ namespace Brick\Math\Internal\Calculator;
|
|||||||
use Brick\Math\Internal\Calculator;
|
use Brick\Math\Internal\Calculator;
|
||||||
use Override;
|
use Override;
|
||||||
|
|
||||||
|
use function assert;
|
||||||
|
use function in_array;
|
||||||
|
use function intdiv;
|
||||||
|
use function is_int;
|
||||||
|
use function ltrim;
|
||||||
|
use function str_pad;
|
||||||
|
use function str_repeat;
|
||||||
|
use function strcmp;
|
||||||
|
use function strlen;
|
||||||
|
use function substr;
|
||||||
|
|
||||||
|
use const PHP_INT_SIZE;
|
||||||
|
use const STR_PAD_LEFT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculator implementation using only native PHP code.
|
* Calculator implementation using only native PHP code.
|
||||||
*
|
*
|
||||||
* @internal
|
* @internal
|
||||||
*
|
|
||||||
* @psalm-immutable
|
|
||||||
*/
|
*/
|
||||||
class NativeCalculator extends Calculator
|
final readonly class NativeCalculator extends Calculator
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The max number of digits the platform can natively add, subtract, multiply or divide without overflow.
|
* The max number of digits the platform can natively add, subtract, multiply or divide without overflow.
|
||||||
@@ -24,9 +36,11 @@ class NativeCalculator extends Calculator
|
|||||||
* Example: 32-bit: max number 1,999,999,999 (9 digits + carry)
|
* Example: 32-bit: max number 1,999,999,999 (9 digits + carry)
|
||||||
* 64-bit: max number 1,999,999,999,999,999,999 (18 digits + carry)
|
* 64-bit: max number 1,999,999,999,999,999,999 (18 digits + carry)
|
||||||
*/
|
*/
|
||||||
private readonly int $maxDigits;
|
private int $maxDigits;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @pure
|
||||||
|
*
|
||||||
* @codeCoverageIgnore
|
* @codeCoverageIgnore
|
||||||
*/
|
*/
|
||||||
public function __construct()
|
public function __construct()
|
||||||
@@ -34,16 +48,15 @@ class NativeCalculator extends Calculator
|
|||||||
$this->maxDigits = match (PHP_INT_SIZE) {
|
$this->maxDigits = match (PHP_INT_SIZE) {
|
||||||
4 => 9,
|
4 => 9,
|
||||||
8 => 18,
|
8 => 18,
|
||||||
default => throw new \RuntimeException('The platform is not 32-bit or 64-bit as expected.')
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function add(string $a, string $b) : string
|
public function add(string $a, string $b): string
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @psalm-var numeric-string $a
|
* @var numeric-string $a
|
||||||
* @psalm-var numeric-string $b
|
* @var numeric-string $b
|
||||||
*/
|
*/
|
||||||
$result = $a + $b;
|
$result = $a + $b;
|
||||||
|
|
||||||
@@ -71,17 +84,17 @@ class NativeCalculator extends Calculator
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function sub(string $a, string $b) : string
|
public function sub(string $a, string $b): string
|
||||||
{
|
{
|
||||||
return $this->add($a, $this->neg($b));
|
return $this->add($a, $this->neg($b));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function mul(string $a, string $b) : string
|
public function mul(string $a, string $b): string
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @psalm-var numeric-string $a
|
* @var numeric-string $a
|
||||||
* @psalm-var numeric-string $b
|
* @var numeric-string $b
|
||||||
*/
|
*/
|
||||||
$result = $a * $b;
|
$result = $a * $b;
|
||||||
|
|
||||||
@@ -121,7 +134,7 @@ class NativeCalculator extends Calculator
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function divQ(string $a, string $b) : string
|
public function divQ(string $a, string $b): string
|
||||||
{
|
{
|
||||||
return $this->divQR($a, $b)[0];
|
return $this->divQR($a, $b)[0];
|
||||||
}
|
}
|
||||||
@@ -133,7 +146,7 @@ class NativeCalculator extends Calculator
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function divQR(string $a, string $b) : array
|
public function divQR(string $a, string $b): array
|
||||||
{
|
{
|
||||||
if ($a === '0') {
|
if ($a === '0') {
|
||||||
return ['0', '0'];
|
return ['0', '0'];
|
||||||
@@ -151,11 +164,11 @@ class NativeCalculator extends Calculator
|
|||||||
return [$this->neg($a), '0'];
|
return [$this->neg($a), '0'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @psalm-var numeric-string $a */
|
/** @var numeric-string $a */
|
||||||
$na = $a * 1; // cast to number
|
$na = $a * 1; // cast to number
|
||||||
|
|
||||||
if (is_int($na)) {
|
if (is_int($na)) {
|
||||||
/** @psalm-var numeric-string $b */
|
/** @var numeric-string $b */
|
||||||
$nb = $b * 1;
|
$nb = $b * 1;
|
||||||
|
|
||||||
if (is_int($nb)) {
|
if (is_int($nb)) {
|
||||||
@@ -166,7 +179,7 @@ class NativeCalculator extends Calculator
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
(string) $q,
|
(string) $q,
|
||||||
(string) $r
|
(string) $r,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,7 +200,7 @@ class NativeCalculator extends Calculator
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function pow(string $a, int $e) : string
|
public function pow(string $a, int $e): string
|
||||||
{
|
{
|
||||||
if ($e === 0) {
|
if ($e === 0) {
|
||||||
return '1';
|
return '1';
|
||||||
@@ -202,7 +215,6 @@ class NativeCalculator extends Calculator
|
|||||||
|
|
||||||
$aa = $this->mul($a, $a);
|
$aa = $this->mul($a, $a);
|
||||||
|
|
||||||
/** @psalm-suppress PossiblyInvalidArgument We're sure that $e / 2 is an int now */
|
|
||||||
$result = $this->pow($aa, $e / 2);
|
$result = $this->pow($aa, $e / 2);
|
||||||
|
|
||||||
if ($odd === 1) {
|
if ($odd === 1) {
|
||||||
@@ -213,15 +225,13 @@ class NativeCalculator extends Calculator
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Algorithm from: https://www.geeksforgeeks.org/modular-exponentiation-power-in-modular-arithmetic/
|
* Algorithm from: https://www.geeksforgeeks.org/modular-exponentiation-power-in-modular-arithmetic/.
|
||||||
*/
|
*/
|
||||||
#[Override]
|
#[Override]
|
||||||
public function modPow(string $base, string $exp, string $mod) : string
|
public function modPow(string $base, string $exp, string $mod): string
|
||||||
{
|
{
|
||||||
// special case: the algorithm below fails with 0 power 0 mod 1 (returns 1 instead of 0)
|
// normalize to Euclidean representative so modPow() stays consistent with mod()
|
||||||
if ($base === '0' && $exp === '0' && $mod === '1') {
|
$base = $this->mod($base, $mod);
|
||||||
return '0';
|
|
||||||
}
|
|
||||||
|
|
||||||
// special case: the algorithm below fails with power 0 mod 1 (returns 1 instead of 0)
|
// special case: the algorithm below fails with power 0 mod 1 (returns 1 instead of 0)
|
||||||
if ($exp === '0' && $mod === '1') {
|
if ($exp === '0' && $mod === '1') {
|
||||||
@@ -248,21 +258,21 @@ class NativeCalculator extends Calculator
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapted from https://cp-algorithms.com/num_methods/roots_newton.html
|
* Adapted from https://cp-algorithms.com/num_methods/roots_newton.html.
|
||||||
*/
|
*/
|
||||||
#[Override]
|
#[Override]
|
||||||
public function sqrt(string $n) : string
|
public function sqrt(string $n): string
|
||||||
{
|
{
|
||||||
if ($n === '0') {
|
if ($n === '0') {
|
||||||
return '0';
|
return '0';
|
||||||
}
|
}
|
||||||
|
|
||||||
// initial approximation
|
// initial approximation
|
||||||
$x = \str_repeat('9', \intdiv(\strlen($n), 2) ?: 1);
|
$x = str_repeat('9', intdiv(strlen($n), 2) ?: 1);
|
||||||
|
|
||||||
$decreased = false;
|
$decreased = false;
|
||||||
|
|
||||||
for (;;) {
|
for (; ;) {
|
||||||
$nx = $this->divQ($this->add($x, $this->divQ($n, $x)), '2');
|
$nx = $this->divQ($this->add($x, $this->divQ($n, $x)), '2');
|
||||||
|
|
||||||
if ($x === $nx || $this->cmp($nx, $x) > 0 && $decreased) {
|
if ($x === $nx || $this->cmp($nx, $x) > 0 && $decreased) {
|
||||||
@@ -278,38 +288,39 @@ class NativeCalculator extends Calculator
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs the addition of two non-signed large integers.
|
* Performs the addition of two non-signed large integers.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
private function doAdd(string $a, string $b) : string
|
private function doAdd(string $a, string $b): string
|
||||||
{
|
{
|
||||||
[$a, $b, $length] = $this->pad($a, $b);
|
[$a, $b, $length] = $this->pad($a, $b);
|
||||||
|
|
||||||
$carry = 0;
|
$carry = 0;
|
||||||
$result = '';
|
$result = '';
|
||||||
|
|
||||||
for ($i = $length - $this->maxDigits;; $i -= $this->maxDigits) {
|
for ($i = $length - $this->maxDigits; ; $i -= $this->maxDigits) {
|
||||||
$blockLength = $this->maxDigits;
|
$blockLength = $this->maxDigits;
|
||||||
|
|
||||||
if ($i < 0) {
|
if ($i < 0) {
|
||||||
$blockLength += $i;
|
$blockLength += $i;
|
||||||
/** @psalm-suppress LoopInvalidation */
|
|
||||||
$i = 0;
|
$i = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @psalm-var numeric-string $blockA */
|
/** @var numeric-string $blockA */
|
||||||
$blockA = \substr($a, $i, $blockLength);
|
$blockA = substr($a, $i, $blockLength);
|
||||||
|
|
||||||
/** @psalm-var numeric-string $blockB */
|
/** @var numeric-string $blockB */
|
||||||
$blockB = \substr($b, $i, $blockLength);
|
$blockB = substr($b, $i, $blockLength);
|
||||||
|
|
||||||
$sum = (string) ($blockA + $blockB + $carry);
|
$sum = (string) ($blockA + $blockB + $carry);
|
||||||
$sumLength = \strlen($sum);
|
$sumLength = strlen($sum);
|
||||||
|
|
||||||
if ($sumLength > $blockLength) {
|
if ($sumLength > $blockLength) {
|
||||||
$sum = \substr($sum, 1);
|
$sum = substr($sum, 1);
|
||||||
$carry = 1;
|
$carry = 1;
|
||||||
} else {
|
} else {
|
||||||
if ($sumLength < $blockLength) {
|
if ($sumLength < $blockLength) {
|
||||||
$sum = \str_repeat('0', $blockLength - $sumLength) . $sum;
|
$sum = str_repeat('0', $blockLength - $sumLength) . $sum;
|
||||||
}
|
}
|
||||||
$carry = 0;
|
$carry = 0;
|
||||||
}
|
}
|
||||||
@@ -330,8 +341,10 @@ class NativeCalculator extends Calculator
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs the subtraction of two non-signed large integers.
|
* Performs the subtraction of two non-signed large integers.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
private function doSub(string $a, string $b) : string
|
private function doSub(string $a, string $b): string
|
||||||
{
|
{
|
||||||
if ($a === $b) {
|
if ($a === $b) {
|
||||||
return '0';
|
return '0';
|
||||||
@@ -355,20 +368,19 @@ class NativeCalculator extends Calculator
|
|||||||
|
|
||||||
$complement = 10 ** $this->maxDigits;
|
$complement = 10 ** $this->maxDigits;
|
||||||
|
|
||||||
for ($i = $length - $this->maxDigits;; $i -= $this->maxDigits) {
|
for ($i = $length - $this->maxDigits; ; $i -= $this->maxDigits) {
|
||||||
$blockLength = $this->maxDigits;
|
$blockLength = $this->maxDigits;
|
||||||
|
|
||||||
if ($i < 0) {
|
if ($i < 0) {
|
||||||
$blockLength += $i;
|
$blockLength += $i;
|
||||||
/** @psalm-suppress LoopInvalidation */
|
|
||||||
$i = 0;
|
$i = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @psalm-var numeric-string $blockA */
|
/** @var numeric-string $blockA */
|
||||||
$blockA = \substr($a, $i, $blockLength);
|
$blockA = substr($a, $i, $blockLength);
|
||||||
|
|
||||||
/** @psalm-var numeric-string $blockB */
|
/** @var numeric-string $blockB */
|
||||||
$blockB = \substr($b, $i, $blockLength);
|
$blockB = substr($b, $i, $blockLength);
|
||||||
|
|
||||||
$sum = $blockA - $blockB - $carry;
|
$sum = $blockA - $blockB - $carry;
|
||||||
|
|
||||||
@@ -380,10 +392,10 @@ class NativeCalculator extends Calculator
|
|||||||
}
|
}
|
||||||
|
|
||||||
$sum = (string) $sum;
|
$sum = (string) $sum;
|
||||||
$sumLength = \strlen($sum);
|
$sumLength = strlen($sum);
|
||||||
|
|
||||||
if ($sumLength < $blockLength) {
|
if ($sumLength < $blockLength) {
|
||||||
$sum = \str_repeat('0', $blockLength - $sumLength) . $sum;
|
$sum = str_repeat('0', $blockLength - $sumLength) . $sum;
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = $sum . $result;
|
$result = $sum . $result;
|
||||||
@@ -396,7 +408,7 @@ class NativeCalculator extends Calculator
|
|||||||
// Carry cannot be 1 when the loop ends, as a > b
|
// Carry cannot be 1 when the loop ends, as a > b
|
||||||
assert($carry === 0);
|
assert($carry === 0);
|
||||||
|
|
||||||
$result = \ltrim($result, '0');
|
$result = ltrim($result, '0');
|
||||||
|
|
||||||
if ($invert) {
|
if ($invert) {
|
||||||
$result = $this->neg($result);
|
$result = $this->neg($result);
|
||||||
@@ -407,48 +419,48 @@ class NativeCalculator extends Calculator
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs the multiplication of two non-signed large integers.
|
* Performs the multiplication of two non-signed large integers.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
private function doMul(string $a, string $b) : string
|
private function doMul(string $a, string $b): string
|
||||||
{
|
{
|
||||||
$x = \strlen($a);
|
$x = strlen($a);
|
||||||
$y = \strlen($b);
|
$y = strlen($b);
|
||||||
|
|
||||||
$maxDigits = \intdiv($this->maxDigits, 2);
|
$maxDigits = intdiv($this->maxDigits, 2);
|
||||||
$complement = 10 ** $maxDigits;
|
$complement = 10 ** $maxDigits;
|
||||||
|
|
||||||
$result = '0';
|
$result = '0';
|
||||||
|
|
||||||
for ($i = $x - $maxDigits;; $i -= $maxDigits) {
|
for ($i = $x - $maxDigits; ; $i -= $maxDigits) {
|
||||||
$blockALength = $maxDigits;
|
$blockALength = $maxDigits;
|
||||||
|
|
||||||
if ($i < 0) {
|
if ($i < 0) {
|
||||||
$blockALength += $i;
|
$blockALength += $i;
|
||||||
/** @psalm-suppress LoopInvalidation */
|
|
||||||
$i = 0;
|
$i = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
$blockA = (int) \substr($a, $i, $blockALength);
|
$blockA = (int) substr($a, $i, $blockALength);
|
||||||
|
|
||||||
$line = '';
|
$line = '';
|
||||||
$carry = 0;
|
$carry = 0;
|
||||||
|
|
||||||
for ($j = $y - $maxDigits;; $j -= $maxDigits) {
|
for ($j = $y - $maxDigits; ; $j -= $maxDigits) {
|
||||||
$blockBLength = $maxDigits;
|
$blockBLength = $maxDigits;
|
||||||
|
|
||||||
if ($j < 0) {
|
if ($j < 0) {
|
||||||
$blockBLength += $j;
|
$blockBLength += $j;
|
||||||
/** @psalm-suppress LoopInvalidation */
|
|
||||||
$j = 0;
|
$j = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
$blockB = (int) \substr($b, $j, $blockBLength);
|
$blockB = (int) substr($b, $j, $blockBLength);
|
||||||
|
|
||||||
$mul = $blockA * $blockB + $carry;
|
$mul = $blockA * $blockB + $carry;
|
||||||
$value = $mul % $complement;
|
$value = $mul % $complement;
|
||||||
$carry = ($mul - $value) / $complement;
|
$carry = ($mul - $value) / $complement;
|
||||||
|
|
||||||
$value = (string) $value;
|
$value = (string) $value;
|
||||||
$value = \str_pad($value, $maxDigits, '0', STR_PAD_LEFT);
|
$value = str_pad($value, $maxDigits, '0', STR_PAD_LEFT);
|
||||||
|
|
||||||
$line = $value . $line;
|
$line = $value . $line;
|
||||||
|
|
||||||
@@ -461,10 +473,10 @@ class NativeCalculator extends Calculator
|
|||||||
$line = $carry . $line;
|
$line = $carry . $line;
|
||||||
}
|
}
|
||||||
|
|
||||||
$line = \ltrim($line, '0');
|
$line = ltrim($line, '0');
|
||||||
|
|
||||||
if ($line !== '') {
|
if ($line !== '') {
|
||||||
$line .= \str_repeat('0', $x - $blockALength - $i);
|
$line .= str_repeat('0', $x - $blockALength - $i);
|
||||||
$result = $this->add($result, $line);
|
$result = $this->add($result, $line);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,8 +492,10 @@ class NativeCalculator extends Calculator
|
|||||||
* Performs the division of two non-signed large integers.
|
* Performs the division of two non-signed large integers.
|
||||||
*
|
*
|
||||||
* @return string[] The quotient and remainder.
|
* @return string[] The quotient and remainder.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
private function doDiv(string $a, string $b) : array
|
private function doDiv(string $a, string $b): array
|
||||||
{
|
{
|
||||||
$cmp = $this->doCmp($a, $b);
|
$cmp = $this->doCmp($a, $b);
|
||||||
|
|
||||||
@@ -489,8 +503,8 @@ class NativeCalculator extends Calculator
|
|||||||
return ['0', $a];
|
return ['0', $a];
|
||||||
}
|
}
|
||||||
|
|
||||||
$x = \strlen($a);
|
$x = strlen($a);
|
||||||
$y = \strlen($b);
|
$y = strlen($b);
|
||||||
|
|
||||||
// we now know that a >= b && x >= y
|
// we now know that a >= b && x >= y
|
||||||
|
|
||||||
@@ -498,24 +512,24 @@ class NativeCalculator extends Calculator
|
|||||||
$r = $a; // remainder
|
$r = $a; // remainder
|
||||||
$z = $y; // focus length, always $y or $y+1
|
$z = $y; // focus length, always $y or $y+1
|
||||||
|
|
||||||
/** @psalm-var numeric-string $b */
|
/** @var numeric-string $b */
|
||||||
$nb = $b * 1; // cast to number
|
$nb = $b * 1; // cast to number
|
||||||
// performance optimization in cases where the remainder will never cause int overflow
|
// performance optimization in cases where the remainder will never cause int overflow
|
||||||
if (is_int(($nb - 1) * 10 + 9)) {
|
if (is_int(($nb - 1) * 10 + 9)) {
|
||||||
$r = (int) \substr($a, 0, $z - 1);
|
$r = (int) substr($a, 0, $z - 1);
|
||||||
|
|
||||||
for ($i = $z - 1; $i < $x; $i++) {
|
for ($i = $z - 1; $i < $x; $i++) {
|
||||||
$n = $r * 10 + (int) $a[$i];
|
$n = $r * 10 + (int) $a[$i];
|
||||||
/** @psalm-var int $nb */
|
/** @var int $nb */
|
||||||
$q .= \intdiv($n, $nb);
|
$q .= intdiv($n, $nb);
|
||||||
$r = $n % $nb;
|
$r = $n % $nb;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [\ltrim($q, '0') ?: '0', (string) $r];
|
return [ltrim($q, '0') ?: '0', (string) $r];
|
||||||
}
|
}
|
||||||
|
|
||||||
for (;;) {
|
for (; ;) {
|
||||||
$focus = \substr($a, 0, $z);
|
$focus = substr($a, 0, $z);
|
||||||
|
|
||||||
$cmp = $this->doCmp($focus, $b);
|
$cmp = $this->doCmp($focus, $b);
|
||||||
|
|
||||||
@@ -527,7 +541,7 @@ class NativeCalculator extends Calculator
|
|||||||
$z++;
|
$z++;
|
||||||
}
|
}
|
||||||
|
|
||||||
$zeros = \str_repeat('0', $x - $z);
|
$zeros = str_repeat('0', $x - $z);
|
||||||
|
|
||||||
$q = $this->add($q, '1' . $zeros);
|
$q = $this->add($q, '1' . $zeros);
|
||||||
$a = $this->sub($a, $b . $zeros);
|
$a = $this->sub($a, $b . $zeros);
|
||||||
@@ -538,7 +552,7 @@ class NativeCalculator extends Calculator
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$x = \strlen($a);
|
$x = strlen($a);
|
||||||
|
|
||||||
if ($x < $y) { // remainder < dividend
|
if ($x < $y) { // remainder < dividend
|
||||||
break;
|
break;
|
||||||
@@ -553,12 +567,14 @@ class NativeCalculator extends Calculator
|
|||||||
/**
|
/**
|
||||||
* Compares two non-signed large numbers.
|
* Compares two non-signed large numbers.
|
||||||
*
|
*
|
||||||
* @psalm-return -1|0|1
|
* @return -1|0|1
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
private function doCmp(string $a, string $b) : int
|
private function doCmp(string $a, string $b): int
|
||||||
{
|
{
|
||||||
$x = \strlen($a);
|
$x = strlen($a);
|
||||||
$y = \strlen($b);
|
$y = strlen($b);
|
||||||
|
|
||||||
$cmp = $x <=> $y;
|
$cmp = $x <=> $y;
|
||||||
|
|
||||||
@@ -566,7 +582,7 @@ class NativeCalculator extends Calculator
|
|||||||
return $cmp;
|
return $cmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
return \strcmp($a, $b) <=> 0; // enforce -1|0|1
|
return strcmp($a, $b) <=> 0; // enforce -1|0|1
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -575,20 +591,22 @@ class NativeCalculator extends Calculator
|
|||||||
* The numbers must only consist of digits, without leading minus sign.
|
* The numbers must only consist of digits, without leading minus sign.
|
||||||
*
|
*
|
||||||
* @return array{string, string, int}
|
* @return array{string, string, int}
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
*/
|
*/
|
||||||
private function pad(string $a, string $b) : array
|
private function pad(string $a, string $b): array
|
||||||
{
|
{
|
||||||
$x = \strlen($a);
|
$x = strlen($a);
|
||||||
$y = \strlen($b);
|
$y = strlen($b);
|
||||||
|
|
||||||
if ($x > $y) {
|
if ($x > $y) {
|
||||||
$b = \str_repeat('0', $x - $y) . $b;
|
$b = str_repeat('0', $x - $y) . $b;
|
||||||
|
|
||||||
return [$a, $b, $x];
|
return [$a, $b, $x];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($x < $y) {
|
if ($x < $y) {
|
||||||
$a = \str_repeat('0', $y - $x) . $a;
|
$a = str_repeat('0', $y - $x) . $a;
|
||||||
|
|
||||||
return [$a, $b, $y];
|
return [$a, $b, $y];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Brick\Math\Internal;
|
||||||
|
|
||||||
|
use function extension_loaded;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the current Calculator instance used by BigNumber classes.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class CalculatorRegistry
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The Calculator instance in use.
|
||||||
|
*/
|
||||||
|
private static ?Calculator $instance = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the Calculator instance to use.
|
||||||
|
*
|
||||||
|
* An instance is typically set only in unit tests: autodetect is usually the best option.
|
||||||
|
*
|
||||||
|
* @param Calculator|null $calculator The calculator instance, or null to revert to autodetect.
|
||||||
|
*/
|
||||||
|
final public static function set(?Calculator $calculator): void
|
||||||
|
{
|
||||||
|
self::$instance = $calculator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Calculator instance to use.
|
||||||
|
*
|
||||||
|
* If none has been explicitly set, the fastest available implementation will be returned.
|
||||||
|
*
|
||||||
|
* Note: even though this method is not technically pure, it is considered pure when used in a normal context, when
|
||||||
|
* only relying on autodetect.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*/
|
||||||
|
final public static function get(): Calculator
|
||||||
|
{
|
||||||
|
/** @phpstan-ignore impure.staticPropertyAccess */
|
||||||
|
if (self::$instance === null) {
|
||||||
|
/** @phpstan-ignore impure.propertyAssign */
|
||||||
|
self::$instance = self::detect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @phpstan-ignore impure.staticPropertyAccess */
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the fastest available Calculator implementation.
|
||||||
|
*
|
||||||
|
* @pure
|
||||||
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
private static function detect(): Calculator
|
||||||
|
{
|
||||||
|
if (extension_loaded('gmp')) {
|
||||||
|
return new Calculator\GmpCalculator();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extension_loaded('bcmath')) {
|
||||||
|
return new Calculator\BcMathCalculator();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Calculator\NativeCalculator();
|
||||||
|
}
|
||||||
|
}
|
||||||
+70
-25
@@ -5,13 +5,8 @@ declare(strict_types=1);
|
|||||||
namespace Brick\Math;
|
namespace Brick\Math;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies a rounding behavior for numerical operations capable of discarding precision.
|
* Specifies rounding behavior by defining how discarded digits affect the returned result when an exact value cannot
|
||||||
*
|
* be represented at the requested scale.
|
||||||
* Each rounding mode indicates how the least significant returned digit of a rounded result
|
|
||||||
* is to be calculated. If fewer digits are returned than the digits needed to represent the
|
|
||||||
* exact numerical result, the discarded digits will be referred to as the discarded fraction
|
|
||||||
* regardless the digits' contribution to the value of the number. In other words, considered
|
|
||||||
* as a numerical value, the discarded fraction could have an absolute value greater than one.
|
|
||||||
*/
|
*/
|
||||||
enum RoundingMode
|
enum RoundingMode
|
||||||
{
|
{
|
||||||
@@ -21,7 +16,7 @@ enum RoundingMode
|
|||||||
* If this rounding mode is specified on an operation that yields a result that
|
* If this rounding mode is specified on an operation that yields a result that
|
||||||
* cannot be represented at the requested scale, a RoundingNecessaryException is thrown.
|
* cannot be represented at the requested scale, a RoundingNecessaryException is thrown.
|
||||||
*/
|
*/
|
||||||
case UNNECESSARY;
|
case Unnecessary;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rounds away from zero.
|
* Rounds away from zero.
|
||||||
@@ -29,7 +24,7 @@ enum RoundingMode
|
|||||||
* Always increments the digit prior to a nonzero discarded fraction.
|
* Always increments the digit prior to a nonzero discarded fraction.
|
||||||
* Note that this rounding mode never decreases the magnitude of the calculated value.
|
* Note that this rounding mode never decreases the magnitude of the calculated value.
|
||||||
*/
|
*/
|
||||||
case UP;
|
case Up;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rounds towards zero.
|
* Rounds towards zero.
|
||||||
@@ -37,62 +32,112 @@ enum RoundingMode
|
|||||||
* Never increments the digit prior to a discarded fraction (i.e., truncates).
|
* Never increments the digit prior to a discarded fraction (i.e., truncates).
|
||||||
* Note that this rounding mode never increases the magnitude of the calculated value.
|
* Note that this rounding mode never increases the magnitude of the calculated value.
|
||||||
*/
|
*/
|
||||||
case DOWN;
|
case Down;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rounds towards positive infinity.
|
* Rounds towards positive infinity.
|
||||||
*
|
*
|
||||||
* If the result is positive, behaves as for UP; if negative, behaves as for DOWN.
|
* If the result is positive, behaves as for Up; if negative, behaves as for Down.
|
||||||
* Note that this rounding mode never decreases the calculated value.
|
* Note that this rounding mode never decreases the calculated value.
|
||||||
*/
|
*/
|
||||||
case CEILING;
|
case Ceiling;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rounds towards negative infinity.
|
* Rounds towards negative infinity.
|
||||||
*
|
*
|
||||||
* If the result is positive, behave as for DOWN; if negative, behave as for UP.
|
* If the result is positive, behaves as for Down; if negative, behaves as for Up.
|
||||||
* Note that this rounding mode never increases the calculated value.
|
* Note that this rounding mode never increases the calculated value.
|
||||||
*/
|
*/
|
||||||
case FLOOR;
|
case Floor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round up.
|
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round up.
|
||||||
*
|
*
|
||||||
* Behaves as for UP if the discarded fraction is >= 0.5; otherwise, behaves as for DOWN.
|
* Behaves as for Up if the discarded fraction is >= 0.5; otherwise, behaves as for Down.
|
||||||
* Note that this is the rounding mode commonly taught at school.
|
* Note that this is the rounding mode commonly taught at school.
|
||||||
*/
|
*/
|
||||||
case HALF_UP;
|
case HalfUp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round down.
|
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round down.
|
||||||
*
|
*
|
||||||
* Behaves as for UP if the discarded fraction is > 0.5; otherwise, behaves as for DOWN.
|
* Behaves as for Up if the discarded fraction is > 0.5; otherwise, behaves as for Down.
|
||||||
*/
|
*/
|
||||||
case HALF_DOWN;
|
case HalfDown;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round towards positive infinity.
|
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round towards positive infinity.
|
||||||
*
|
*
|
||||||
* If the result is positive, behaves as for HALF_UP; if negative, behaves as for HALF_DOWN.
|
* If the result is positive, behaves as for HalfUp; if negative, behaves as for HalfDown.
|
||||||
*/
|
*/
|
||||||
case HALF_CEILING;
|
case HalfCeiling;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round towards negative infinity.
|
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round towards negative infinity.
|
||||||
*
|
*
|
||||||
* If the result is positive, behaves as for HALF_DOWN; if negative, behaves as for HALF_UP.
|
* If the result is positive, behaves as for HalfDown; if negative, behaves as for HalfUp.
|
||||||
*/
|
*/
|
||||||
case HALF_FLOOR;
|
case HalfFloor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rounds towards the "nearest neighbor" unless both neighbors are equidistant, in which case rounds towards the even neighbor.
|
* Rounds towards the "nearest neighbor" unless both neighbors are equidistant, in which case rounds towards the even neighbor.
|
||||||
*
|
*
|
||||||
* Behaves as for HALF_UP if the digit to the left of the discarded fraction is odd;
|
* Behaves as for HalfUp if the digit to the left of the discarded fraction is odd;
|
||||||
* behaves as for HALF_DOWN if it's even.
|
* behaves as for HalfDown if it's even.
|
||||||
*
|
*
|
||||||
* Note that this is the rounding mode that statistically minimizes
|
* Note that this is the rounding mode that statistically minimizes
|
||||||
* cumulative error when applied repeatedly over a sequence of calculations.
|
* cumulative error when applied repeatedly over a sequence of calculations.
|
||||||
* It is sometimes known as "Banker's rounding", and is chiefly used in the USA.
|
* It is sometimes known as "Banker's rounding", and is chiefly used in the USA.
|
||||||
*/
|
*/
|
||||||
case HALF_EVEN;
|
case HalfEven;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use RoundingMode::Unnecessary instead.
|
||||||
|
*/
|
||||||
|
public const UNNECESSARY = self::Unnecessary;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use RoundingMode::Up instead.
|
||||||
|
*/
|
||||||
|
public const UP = self::Up;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use RoundingMode::Down instead.
|
||||||
|
*/
|
||||||
|
public const DOWN = self::Down;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use RoundingMode::Ceiling instead.
|
||||||
|
*/
|
||||||
|
public const CEILING = self::Ceiling;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use RoundingMode::Floor instead.
|
||||||
|
*/
|
||||||
|
public const FLOOR = self::Floor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use RoundingMode::HalfUp instead.
|
||||||
|
*/
|
||||||
|
public const HALF_UP = self::HalfUp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use RoundingMode::HalfDown instead.
|
||||||
|
*/
|
||||||
|
public const HALF_DOWN = self::HalfDown;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use RoundingMode::HalfCeiling instead.
|
||||||
|
*/
|
||||||
|
public const HALF_CEILING = self::HalfCeiling;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use RoundingMode::HalfFloor instead.
|
||||||
|
*/
|
||||||
|
public const HALF_FLOOR = self::HalfFloor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use RoundingMode::HalfEven instead.
|
||||||
|
*/
|
||||||
|
public const HALF_EVEN = self::HalfEven;
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+58
-51
@@ -42,35 +42,37 @@ namespace Composer\Autoload;
|
|||||||
*/
|
*/
|
||||||
class ClassLoader
|
class ClassLoader
|
||||||
{
|
{
|
||||||
/** @var ?string */
|
/** @var \Closure(string):void */
|
||||||
|
private static $includeFile;
|
||||||
|
|
||||||
|
/** @var string|null */
|
||||||
private $vendorDir;
|
private $vendorDir;
|
||||||
|
|
||||||
// PSR-4
|
// PSR-4
|
||||||
/**
|
/**
|
||||||
* @var array[]
|
* @var array<string, array<string, int>>
|
||||||
* @psalm-var array<string, array<string, int>>
|
|
||||||
*/
|
*/
|
||||||
private $prefixLengthsPsr4 = array();
|
private $prefixLengthsPsr4 = array();
|
||||||
/**
|
/**
|
||||||
* @var array[]
|
* @var array<string, list<string>>
|
||||||
* @psalm-var array<string, array<int, string>>
|
|
||||||
*/
|
*/
|
||||||
private $prefixDirsPsr4 = array();
|
private $prefixDirsPsr4 = array();
|
||||||
/**
|
/**
|
||||||
* @var array[]
|
* @var list<string>
|
||||||
* @psalm-var array<string, string>
|
|
||||||
*/
|
*/
|
||||||
private $fallbackDirsPsr4 = array();
|
private $fallbackDirsPsr4 = array();
|
||||||
|
|
||||||
// PSR-0
|
// PSR-0
|
||||||
/**
|
/**
|
||||||
* @var array[]
|
* List of PSR-0 prefixes
|
||||||
* @psalm-var array<string, array<string, string[]>>
|
*
|
||||||
|
* Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
|
||||||
|
*
|
||||||
|
* @var array<string, array<string, list<string>>>
|
||||||
*/
|
*/
|
||||||
private $prefixesPsr0 = array();
|
private $prefixesPsr0 = array();
|
||||||
/**
|
/**
|
||||||
* @var array[]
|
* @var list<string>
|
||||||
* @psalm-var array<string, string>
|
|
||||||
*/
|
*/
|
||||||
private $fallbackDirsPsr0 = array();
|
private $fallbackDirsPsr0 = array();
|
||||||
|
|
||||||
@@ -78,8 +80,7 @@ class ClassLoader
|
|||||||
private $useIncludePath = false;
|
private $useIncludePath = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string[]
|
* @var array<string, string>
|
||||||
* @psalm-var array<string, string>
|
|
||||||
*/
|
*/
|
||||||
private $classMap = array();
|
private $classMap = array();
|
||||||
|
|
||||||
@@ -87,29 +88,29 @@ class ClassLoader
|
|||||||
private $classMapAuthoritative = false;
|
private $classMapAuthoritative = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var bool[]
|
* @var array<string, bool>
|
||||||
* @psalm-var array<string, bool>
|
|
||||||
*/
|
*/
|
||||||
private $missingClasses = array();
|
private $missingClasses = array();
|
||||||
|
|
||||||
/** @var ?string */
|
/** @var string|null */
|
||||||
private $apcuPrefix;
|
private $apcuPrefix;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var self[]
|
* @var array<string, self>
|
||||||
*/
|
*/
|
||||||
private static $registeredLoaders = array();
|
private static $registeredLoaders = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ?string $vendorDir
|
* @param string|null $vendorDir
|
||||||
*/
|
*/
|
||||||
public function __construct($vendorDir = null)
|
public function __construct($vendorDir = null)
|
||||||
{
|
{
|
||||||
$this->vendorDir = $vendorDir;
|
$this->vendorDir = $vendorDir;
|
||||||
|
self::initializeIncludeClosure();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string[]
|
* @return array<string, list<string>>
|
||||||
*/
|
*/
|
||||||
public function getPrefixes()
|
public function getPrefixes()
|
||||||
{
|
{
|
||||||
@@ -121,8 +122,7 @@ class ClassLoader
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array[]
|
* @return array<string, list<string>>
|
||||||
* @psalm-return array<string, array<int, string>>
|
|
||||||
*/
|
*/
|
||||||
public function getPrefixesPsr4()
|
public function getPrefixesPsr4()
|
||||||
{
|
{
|
||||||
@@ -130,8 +130,7 @@ class ClassLoader
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array[]
|
* @return list<string>
|
||||||
* @psalm-return array<string, string>
|
|
||||||
*/
|
*/
|
||||||
public function getFallbackDirs()
|
public function getFallbackDirs()
|
||||||
{
|
{
|
||||||
@@ -139,8 +138,7 @@ class ClassLoader
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array[]
|
* @return list<string>
|
||||||
* @psalm-return array<string, string>
|
|
||||||
*/
|
*/
|
||||||
public function getFallbackDirsPsr4()
|
public function getFallbackDirsPsr4()
|
||||||
{
|
{
|
||||||
@@ -148,8 +146,7 @@ class ClassLoader
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string[] Array of classname => path
|
* @return array<string, string> Array of classname => path
|
||||||
* @psalm-return array<string, string>
|
|
||||||
*/
|
*/
|
||||||
public function getClassMap()
|
public function getClassMap()
|
||||||
{
|
{
|
||||||
@@ -157,8 +154,7 @@ class ClassLoader
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string[] $classMap Class to filename map
|
* @param array<string, string> $classMap Class to filename map
|
||||||
* @psalm-param array<string, string> $classMap
|
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
@@ -176,23 +172,24 @@ class ClassLoader
|
|||||||
* appending or prepending to the ones previously set for this prefix.
|
* appending or prepending to the ones previously set for this prefix.
|
||||||
*
|
*
|
||||||
* @param string $prefix The prefix
|
* @param string $prefix The prefix
|
||||||
* @param string[]|string $paths The PSR-0 root directories
|
* @param list<string>|string $paths The PSR-0 root directories
|
||||||
* @param bool $prepend Whether to prepend the directories
|
* @param bool $prepend Whether to prepend the directories
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function add($prefix, $paths, $prepend = false)
|
public function add($prefix, $paths, $prepend = false)
|
||||||
{
|
{
|
||||||
|
$paths = (array) $paths;
|
||||||
if (!$prefix) {
|
if (!$prefix) {
|
||||||
if ($prepend) {
|
if ($prepend) {
|
||||||
$this->fallbackDirsPsr0 = array_merge(
|
$this->fallbackDirsPsr0 = array_merge(
|
||||||
(array) $paths,
|
$paths,
|
||||||
$this->fallbackDirsPsr0
|
$this->fallbackDirsPsr0
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$this->fallbackDirsPsr0 = array_merge(
|
$this->fallbackDirsPsr0 = array_merge(
|
||||||
$this->fallbackDirsPsr0,
|
$this->fallbackDirsPsr0,
|
||||||
(array) $paths
|
$paths
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,19 +198,19 @@ class ClassLoader
|
|||||||
|
|
||||||
$first = $prefix[0];
|
$first = $prefix[0];
|
||||||
if (!isset($this->prefixesPsr0[$first][$prefix])) {
|
if (!isset($this->prefixesPsr0[$first][$prefix])) {
|
||||||
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
|
$this->prefixesPsr0[$first][$prefix] = $paths;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($prepend) {
|
if ($prepend) {
|
||||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||||
(array) $paths,
|
$paths,
|
||||||
$this->prefixesPsr0[$first][$prefix]
|
$this->prefixesPsr0[$first][$prefix]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||||
$this->prefixesPsr0[$first][$prefix],
|
$this->prefixesPsr0[$first][$prefix],
|
||||||
(array) $paths
|
$paths
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -223,7 +220,7 @@ class ClassLoader
|
|||||||
* appending or prepending to the ones previously set for this namespace.
|
* appending or prepending to the ones previously set for this namespace.
|
||||||
*
|
*
|
||||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||||
* @param string[]|string $paths The PSR-4 base directories
|
* @param list<string>|string $paths The PSR-4 base directories
|
||||||
* @param bool $prepend Whether to prepend the directories
|
* @param bool $prepend Whether to prepend the directories
|
||||||
*
|
*
|
||||||
* @throws \InvalidArgumentException
|
* @throws \InvalidArgumentException
|
||||||
@@ -232,17 +229,18 @@ class ClassLoader
|
|||||||
*/
|
*/
|
||||||
public function addPsr4($prefix, $paths, $prepend = false)
|
public function addPsr4($prefix, $paths, $prepend = false)
|
||||||
{
|
{
|
||||||
|
$paths = (array) $paths;
|
||||||
if (!$prefix) {
|
if (!$prefix) {
|
||||||
// Register directories for the root namespace.
|
// Register directories for the root namespace.
|
||||||
if ($prepend) {
|
if ($prepend) {
|
||||||
$this->fallbackDirsPsr4 = array_merge(
|
$this->fallbackDirsPsr4 = array_merge(
|
||||||
(array) $paths,
|
$paths,
|
||||||
$this->fallbackDirsPsr4
|
$this->fallbackDirsPsr4
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$this->fallbackDirsPsr4 = array_merge(
|
$this->fallbackDirsPsr4 = array_merge(
|
||||||
$this->fallbackDirsPsr4,
|
$this->fallbackDirsPsr4,
|
||||||
(array) $paths
|
$paths
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
|
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
|
||||||
@@ -252,18 +250,18 @@ class ClassLoader
|
|||||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||||
}
|
}
|
||||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
$this->prefixDirsPsr4[$prefix] = $paths;
|
||||||
} elseif ($prepend) {
|
} elseif ($prepend) {
|
||||||
// Prepend directories for an already registered namespace.
|
// Prepend directories for an already registered namespace.
|
||||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||||
(array) $paths,
|
$paths,
|
||||||
$this->prefixDirsPsr4[$prefix]
|
$this->prefixDirsPsr4[$prefix]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Append directories for an already registered namespace.
|
// Append directories for an already registered namespace.
|
||||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||||
$this->prefixDirsPsr4[$prefix],
|
$this->prefixDirsPsr4[$prefix],
|
||||||
(array) $paths
|
$paths
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -273,7 +271,7 @@ class ClassLoader
|
|||||||
* replacing any others previously set for this prefix.
|
* replacing any others previously set for this prefix.
|
||||||
*
|
*
|
||||||
* @param string $prefix The prefix
|
* @param string $prefix The prefix
|
||||||
* @param string[]|string $paths The PSR-0 base directories
|
* @param list<string>|string $paths The PSR-0 base directories
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
@@ -291,7 +289,7 @@ class ClassLoader
|
|||||||
* replacing any others previously set for this namespace.
|
* replacing any others previously set for this namespace.
|
||||||
*
|
*
|
||||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||||
* @param string[]|string $paths The PSR-4 base directories
|
* @param list<string>|string $paths The PSR-4 base directories
|
||||||
*
|
*
|
||||||
* @throws \InvalidArgumentException
|
* @throws \InvalidArgumentException
|
||||||
*
|
*
|
||||||
@@ -425,7 +423,8 @@ class ClassLoader
|
|||||||
public function loadClass($class)
|
public function loadClass($class)
|
||||||
{
|
{
|
||||||
if ($file = $this->findFile($class)) {
|
if ($file = $this->findFile($class)) {
|
||||||
includeFile($file);
|
$includeFile = self::$includeFile;
|
||||||
|
$includeFile($file);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -476,9 +475,9 @@ class ClassLoader
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the currently registered loaders indexed by their corresponding vendor directories.
|
* Returns the currently registered loaders keyed by their corresponding vendor directories.
|
||||||
*
|
*
|
||||||
* @return self[]
|
* @return array<string, self>
|
||||||
*/
|
*/
|
||||||
public static function getRegisteredLoaders()
|
public static function getRegisteredLoaders()
|
||||||
{
|
{
|
||||||
@@ -555,18 +554,26 @@ class ClassLoader
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private static function initializeIncludeClosure()
|
||||||
|
{
|
||||||
|
if (self::$includeFile !== null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
* Scope isolated include.
|
* Scope isolated include.
|
||||||
*
|
*
|
||||||
* Prevents access to $this/self from included files.
|
* Prevents access to $this/self from included files.
|
||||||
*
|
*
|
||||||
* @param string $file
|
* @param string $file
|
||||||
* @return void
|
* @return void
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
function includeFile($file)
|
self::$includeFile = \Closure::bind(static function($file) {
|
||||||
{
|
|
||||||
include $file;
|
include $file;
|
||||||
|
}, null, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+50
-6
@@ -26,12 +26,23 @@ use Composer\Semver\VersionParser;
|
|||||||
*/
|
*/
|
||||||
class InstalledVersions
|
class InstalledVersions
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
private static $selfDir = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var mixed[]|null
|
* @var mixed[]|null
|
||||||
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
|
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
|
||||||
*/
|
*/
|
||||||
private static $installed;
|
private static $installed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private static $installedIsLocalDir;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var bool|null
|
* @var bool|null
|
||||||
*/
|
*/
|
||||||
@@ -98,7 +109,7 @@ class InstalledVersions
|
|||||||
{
|
{
|
||||||
foreach (self::getInstalled() as $installed) {
|
foreach (self::getInstalled() as $installed) {
|
||||||
if (isset($installed['versions'][$packageName])) {
|
if (isset($installed['versions'][$packageName])) {
|
||||||
return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
|
return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +130,7 @@ class InstalledVersions
|
|||||||
*/
|
*/
|
||||||
public static function satisfies(VersionParser $parser, $packageName, $constraint)
|
public static function satisfies(VersionParser $parser, $packageName, $constraint)
|
||||||
{
|
{
|
||||||
$constraint = $parser->parseConstraints($constraint);
|
$constraint = $parser->parseConstraints((string) $constraint);
|
||||||
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
|
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
|
||||||
|
|
||||||
return $provided->matches($constraint);
|
return $provided->matches($constraint);
|
||||||
@@ -309,6 +320,24 @@ class InstalledVersions
|
|||||||
{
|
{
|
||||||
self::$installed = $data;
|
self::$installed = $data;
|
||||||
self::$installedByVendor = array();
|
self::$installedByVendor = array();
|
||||||
|
|
||||||
|
// when using reload, we disable the duplicate protection to ensure that self::$installed data is
|
||||||
|
// always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
|
||||||
|
// so we have to assume it does not, and that may result in duplicate data being returned when listing
|
||||||
|
// all installed packages for example
|
||||||
|
self::$installedIsLocalDir = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private static function getSelfDir()
|
||||||
|
{
|
||||||
|
if (self::$selfDir === null) {
|
||||||
|
self::$selfDir = strtr(__DIR__, '\\', '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$selfDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -322,17 +351,27 @@ class InstalledVersions
|
|||||||
}
|
}
|
||||||
|
|
||||||
$installed = array();
|
$installed = array();
|
||||||
|
$copiedLocalDir = false;
|
||||||
|
|
||||||
if (self::$canGetVendors) {
|
if (self::$canGetVendors) {
|
||||||
|
$selfDir = self::getSelfDir();
|
||||||
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
|
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
|
||||||
|
$vendorDir = strtr($vendorDir, '\\', '/');
|
||||||
if (isset(self::$installedByVendor[$vendorDir])) {
|
if (isset(self::$installedByVendor[$vendorDir])) {
|
||||||
$installed[] = self::$installedByVendor[$vendorDir];
|
$installed[] = self::$installedByVendor[$vendorDir];
|
||||||
} elseif (is_file($vendorDir.'/composer/installed.php')) {
|
} elseif (is_file($vendorDir.'/composer/installed.php')) {
|
||||||
$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
|
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
|
||||||
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
|
$required = require $vendorDir.'/composer/installed.php';
|
||||||
self::$installed = $installed[count($installed) - 1];
|
self::$installedByVendor[$vendorDir] = $required;
|
||||||
|
$installed[] = $required;
|
||||||
|
if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
|
||||||
|
self::$installed = $required;
|
||||||
|
self::$installedIsLocalDir = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
|
||||||
|
$copiedLocalDir = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,12 +379,17 @@ class InstalledVersions
|
|||||||
// only require the installed.php file if this file is loaded from its dumped location,
|
// only require the installed.php file if this file is loaded from its dumped location,
|
||||||
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
|
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
|
||||||
if (substr(__DIR__, -8, 1) !== 'C') {
|
if (substr(__DIR__, -8, 1) !== 'C') {
|
||||||
self::$installed = require __DIR__ . '/installed.php';
|
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
|
||||||
|
$required = require __DIR__ . '/installed.php';
|
||||||
|
self::$installed = $required;
|
||||||
} else {
|
} else {
|
||||||
self::$installed = array();
|
self::$installed = array();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (self::$installed !== array() && !$copiedLocalDir) {
|
||||||
$installed[] = self::$installed;
|
$installed[] = self::$installed;
|
||||||
|
}
|
||||||
|
|
||||||
return $installed;
|
return $installed;
|
||||||
}
|
}
|
||||||
|
|||||||
+525
-91
File diff suppressed because it is too large
Load Diff
Vendored
+8
-4
@@ -8,8 +8,8 @@ $baseDir = dirname($vendorDir);
|
|||||||
return array(
|
return array(
|
||||||
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
|
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
|
||||||
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
|
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
|
||||||
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
|
|
||||||
'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
|
'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
|
||||||
|
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
|
||||||
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
|
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
|
||||||
'8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php',
|
'8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php',
|
||||||
'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php',
|
'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php',
|
||||||
@@ -18,25 +18,29 @@ return array(
|
|||||||
'7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
|
'7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
|
||||||
'9c67151ae59aff4788964ce8eb2a0f43' => $vendorDir . '/clue/stream-filter/src/functions_include.php',
|
'9c67151ae59aff4788964ce8eb2a0f43' => $vendorDir . '/clue/stream-filter/src/functions_include.php',
|
||||||
'8cff32064859f4559445b89279f3199c' => $vendorDir . '/php-http/message/src/filters.php',
|
'8cff32064859f4559445b89279f3199c' => $vendorDir . '/php-http/message/src/filters.php',
|
||||||
|
'606a39d89246991a373564698c2d8383' => $vendorDir . '/symfony/polyfill-php85/bootstrap.php',
|
||||||
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
|
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
|
||||||
'662a729f963d39afe703c9d9b7ab4a8c' => $vendorDir . '/symfony/polyfill-php83/bootstrap.php',
|
'2203a247e6fda86070a5e4e07aed533a' => $vendorDir . '/symfony/clock/Resources/now.php',
|
||||||
|
'deecf9d6b2672fb73429f9b28fdee35f' => $vendorDir . '/symfony/polyfill-deepclone/bootstrap.php',
|
||||||
|
'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php',
|
||||||
'b067bc7112e384b61c701452d53a14a8' => $vendorDir . '/mtdowling/jmespath.php/src/JmesPath.php',
|
'b067bc7112e384b61c701452d53a14a8' => $vendorDir . '/mtdowling/jmespath.php/src/JmesPath.php',
|
||||||
'35a6ad97d21e794e7e22a17d806652e4' => $vendorDir . '/nunomaduro/termwind/src/Functions.php',
|
'35a6ad97d21e794e7e22a17d806652e4' => $vendorDir . '/nunomaduro/termwind/src/Functions.php',
|
||||||
'3bd81c9b8fcc150b69d8b63b4d2ccf23' => $vendorDir . '/spatie/flare-client-php/src/helpers.php',
|
'3bd81c9b8fcc150b69d8b63b4d2ccf23' => $vendorDir . '/spatie/flare-client-php/src/helpers.php',
|
||||||
'2203a247e6fda86070a5e4e07aed533a' => $vendorDir . '/symfony/clock/Resources/now.php',
|
|
||||||
'09f6b20656683369174dd6fa83b7e5fb' => $vendorDir . '/symfony/polyfill-uuid/bootstrap.php',
|
'09f6b20656683369174dd6fa83b7e5fb' => $vendorDir . '/symfony/polyfill-uuid/bootstrap.php',
|
||||||
'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php',
|
|
||||||
'8a9dc1de0ca7e01f3e08231539562f61' => $vendorDir . '/aws/aws-sdk-php/src/functions.php',
|
'8a9dc1de0ca7e01f3e08231539562f61' => $vendorDir . '/aws/aws-sdk-php/src/functions.php',
|
||||||
'47e1160838b5e5a10346ac4084b58c23' => $vendorDir . '/laravel/prompts/src/helpers.php',
|
'47e1160838b5e5a10346ac4084b58c23' => $vendorDir . '/laravel/prompts/src/helpers.php',
|
||||||
'6124b4c8570aa390c21fafd04a26c69f' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php',
|
'6124b4c8570aa390c21fafd04a26c69f' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php',
|
||||||
'801c31d8ed748cfa537fa45402288c95' => $vendorDir . '/psy/psysh/src/functions.php',
|
'801c31d8ed748cfa537fa45402288c95' => $vendorDir . '/psy/psysh/src/functions.php',
|
||||||
'e39a8b23c42d4e1452234d762b03835a' => $vendorDir . '/ramsey/uuid/src/functions.php',
|
'e39a8b23c42d4e1452234d762b03835a' => $vendorDir . '/ramsey/uuid/src/functions.php',
|
||||||
|
'662a729f963d39afe703c9d9b7ab4a8c' => $vendorDir . '/symfony/polyfill-php83/bootstrap.php',
|
||||||
|
'9d2b9fc6db0f153a0a149fefb182415e' => $vendorDir . '/symfony/polyfill-php84/bootstrap.php',
|
||||||
'476ca15b8d69b04665cd879be9cb4c68' => $vendorDir . '/laravel/framework/src/Illuminate/Collections/functions.php',
|
'476ca15b8d69b04665cd879be9cb4c68' => $vendorDir . '/laravel/framework/src/Illuminate/Collections/functions.php',
|
||||||
'265b4faa2b3a9766332744949e83bf97' => $vendorDir . '/laravel/framework/src/Illuminate/Collections/helpers.php',
|
'265b4faa2b3a9766332744949e83bf97' => $vendorDir . '/laravel/framework/src/Illuminate/Collections/helpers.php',
|
||||||
'c7a3c339e7e14b60e06a2d7fcce9476b' => $vendorDir . '/laravel/framework/src/Illuminate/Events/functions.php',
|
'c7a3c339e7e14b60e06a2d7fcce9476b' => $vendorDir . '/laravel/framework/src/Illuminate/Events/functions.php',
|
||||||
'f57d353b41eb2e234b26064d63d8c5dd' => $vendorDir . '/laravel/framework/src/Illuminate/Filesystem/functions.php',
|
'f57d353b41eb2e234b26064d63d8c5dd' => $vendorDir . '/laravel/framework/src/Illuminate/Filesystem/functions.php',
|
||||||
'f0906e6318348a765ffb6eb24e0d0938' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/helpers.php',
|
'f0906e6318348a765ffb6eb24e0d0938' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/helpers.php',
|
||||||
'7f7ac2ddea9cc3fb4b2cc201d63dbc10' => $vendorDir . '/laravel/framework/src/Illuminate/Log/functions.php',
|
'7f7ac2ddea9cc3fb4b2cc201d63dbc10' => $vendorDir . '/laravel/framework/src/Illuminate/Log/functions.php',
|
||||||
|
'91892b814db86b8442ad76273bb7aec5' => $vendorDir . '/laravel/framework/src/Illuminate/Reflection/helpers.php',
|
||||||
'493c6aea52f6009bab023b26c21a386a' => $vendorDir . '/laravel/framework/src/Illuminate/Support/functions.php',
|
'493c6aea52f6009bab023b26c21a386a' => $vendorDir . '/laravel/framework/src/Illuminate/Support/functions.php',
|
||||||
'58571171fd5812e6e447dce228f52f4d' => $vendorDir . '/laravel/framework/src/Illuminate/Support/helpers.php',
|
'58571171fd5812e6e447dce228f52f4d' => $vendorDir . '/laravel/framework/src/Illuminate/Support/helpers.php',
|
||||||
'c72349b1fe8d0deeedd3a52e8aa814d8' => $vendorDir . '/mockery/mockery/library/helpers.php',
|
'c72349b1fe8d0deeedd3a52e8aa814d8' => $vendorDir . '/mockery/mockery/library/helpers.php',
|
||||||
|
|||||||
Vendored
+8
-5
@@ -9,18 +9,20 @@ return array(
|
|||||||
'voku\\' => array($vendorDir . '/voku/portable-ascii/src/voku'),
|
'voku\\' => array($vendorDir . '/voku/portable-ascii/src/voku'),
|
||||||
'enshrined\\svgSanitize\\' => array($vendorDir . '/enshrined/svg-sanitize/src'),
|
'enshrined\\svgSanitize\\' => array($vendorDir . '/enshrined/svg-sanitize/src'),
|
||||||
'Whoops\\' => array($vendorDir . '/filp/whoops/src/Whoops'),
|
'Whoops\\' => array($vendorDir . '/filp/whoops/src/Whoops'),
|
||||||
'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'),
|
|
||||||
'TijsVerkoyen\\CssToInlineStyles\\' => array($vendorDir . '/tijsverkoyen/css-to-inline-styles/src'),
|
'TijsVerkoyen\\CssToInlineStyles\\' => array($vendorDir . '/tijsverkoyen/css-to-inline-styles/src'),
|
||||||
'Tests\\' => array($baseDir . '/tests'),
|
'Tests\\' => array($baseDir . '/tests'),
|
||||||
'Termwind\\' => array($vendorDir . '/nunomaduro/termwind/src'),
|
'Termwind\\' => array($vendorDir . '/nunomaduro/termwind/src'),
|
||||||
'Symfony\\Thanks\\' => array($vendorDir . '/symfony/thanks/src'),
|
'Symfony\\Thanks\\' => array($vendorDir . '/symfony/thanks/src'),
|
||||||
'Symfony\\Polyfill\\Uuid\\' => array($vendorDir . '/symfony/polyfill-uuid'),
|
'Symfony\\Polyfill\\Uuid\\' => array($vendorDir . '/symfony/polyfill-uuid'),
|
||||||
|
'Symfony\\Polyfill\\Php85\\' => array($vendorDir . '/symfony/polyfill-php85'),
|
||||||
|
'Symfony\\Polyfill\\Php84\\' => array($vendorDir . '/symfony/polyfill-php84'),
|
||||||
'Symfony\\Polyfill\\Php83\\' => array($vendorDir . '/symfony/polyfill-php83'),
|
'Symfony\\Polyfill\\Php83\\' => array($vendorDir . '/symfony/polyfill-php83'),
|
||||||
'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
|
'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
|
||||||
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
|
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
|
||||||
'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'),
|
'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'),
|
||||||
'Symfony\\Polyfill\\Intl\\Idn\\' => array($vendorDir . '/symfony/polyfill-intl-idn'),
|
'Symfony\\Polyfill\\Intl\\Idn\\' => array($vendorDir . '/symfony/polyfill-intl-idn'),
|
||||||
'Symfony\\Polyfill\\Intl\\Grapheme\\' => array($vendorDir . '/symfony/polyfill-intl-grapheme'),
|
'Symfony\\Polyfill\\Intl\\Grapheme\\' => array($vendorDir . '/symfony/polyfill-intl-grapheme'),
|
||||||
|
'Symfony\\Polyfill\\DeepClone\\' => array($vendorDir . '/symfony/polyfill-deepclone'),
|
||||||
'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
|
'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
|
||||||
'Symfony\\Contracts\\Translation\\' => array($vendorDir . '/symfony/translation-contracts'),
|
'Symfony\\Contracts\\Translation\\' => array($vendorDir . '/symfony/translation-contracts'),
|
||||||
'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'),
|
'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'),
|
||||||
@@ -47,8 +49,8 @@ return array(
|
|||||||
'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'),
|
'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'),
|
||||||
'Symfony\\Component\\Clock\\' => array($vendorDir . '/symfony/clock'),
|
'Symfony\\Component\\Clock\\' => array($vendorDir . '/symfony/clock'),
|
||||||
'Symfony\\Component\\Cache\\' => array($vendorDir . '/symfony/cache'),
|
'Symfony\\Component\\Cache\\' => array($vendorDir . '/symfony/cache'),
|
||||||
'Spatie\\LaravelIgnition\\' => array($vendorDir . '/spatie/error-solutions/legacy/laravel-ignition', $vendorDir . '/spatie/laravel-ignition/src'),
|
'Spatie\\LaravelIgnition\\' => array($vendorDir . '/spatie/laravel-ignition/src', $vendorDir . '/spatie/error-solutions/legacy/laravel-ignition'),
|
||||||
'Spatie\\Ignition\\' => array($vendorDir . '/spatie/error-solutions/legacy/ignition', $vendorDir . '/spatie/ignition/src'),
|
'Spatie\\Ignition\\' => array($vendorDir . '/spatie/ignition/src', $vendorDir . '/spatie/error-solutions/legacy/ignition'),
|
||||||
'Spatie\\Html\\' => array($vendorDir . '/spatie/laravel-html/src'),
|
'Spatie\\Html\\' => array($vendorDir . '/spatie/laravel-html/src'),
|
||||||
'Spatie\\FlareClient\\' => array($vendorDir . '/spatie/flare-client-php/src'),
|
'Spatie\\FlareClient\\' => array($vendorDir . '/spatie/flare-client-php/src'),
|
||||||
'Spatie\\ErrorSolutions\\' => array($vendorDir . '/spatie/error-solutions/src'),
|
'Spatie\\ErrorSolutions\\' => array($vendorDir . '/spatie/error-solutions/src'),
|
||||||
@@ -67,6 +69,7 @@ return array(
|
|||||||
'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'),
|
'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'),
|
||||||
'PhpOption\\' => array($vendorDir . '/phpoption/phpoption/src/PhpOption'),
|
'PhpOption\\' => array($vendorDir . '/phpoption/phpoption/src/PhpOption'),
|
||||||
'NunoMaduro\\Collision\\' => array($vendorDir . '/nunomaduro/collision/src'),
|
'NunoMaduro\\Collision\\' => array($vendorDir . '/nunomaduro/collision/src'),
|
||||||
|
'Nette\\' => array($vendorDir . '/nette/schema/src', $vendorDir . '/nette/utils/src'),
|
||||||
'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'),
|
'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'),
|
||||||
'Mockery\\' => array($vendorDir . '/mockery/mockery/library/Mockery'),
|
'Mockery\\' => array($vendorDir . '/mockery/mockery/library/Mockery'),
|
||||||
'League\\Uri\\' => array($vendorDir . '/league/uri', $vendorDir . '/league/uri-interfaces'),
|
'League\\Uri\\' => array($vendorDir . '/league/uri', $vendorDir . '/league/uri-interfaces'),
|
||||||
@@ -82,7 +85,7 @@ return array(
|
|||||||
'Laravel\\SerializableClosure\\' => array($vendorDir . '/laravel/serializable-closure/src'),
|
'Laravel\\SerializableClosure\\' => array($vendorDir . '/laravel/serializable-closure/src'),
|
||||||
'Laravel\\Prompts\\' => array($vendorDir . '/laravel/prompts/src'),
|
'Laravel\\Prompts\\' => array($vendorDir . '/laravel/prompts/src'),
|
||||||
'JmesPath\\' => array($vendorDir . '/mtdowling/jmespath.php/src'),
|
'JmesPath\\' => array($vendorDir . '/mtdowling/jmespath.php/src'),
|
||||||
'Illuminate\\Support\\' => array($vendorDir . '/laravel/framework/src/Illuminate/Macroable', $vendorDir . '/laravel/framework/src/Illuminate/Collections', $vendorDir . '/laravel/framework/src/Illuminate/Conditionable'),
|
'Illuminate\\Support\\' => array($vendorDir . '/laravel/framework/src/Illuminate/Macroable', $vendorDir . '/laravel/framework/src/Illuminate/Collections', $vendorDir . '/laravel/framework/src/Illuminate/Conditionable', $vendorDir . '/laravel/framework/src/Illuminate/Reflection'),
|
||||||
'Illuminate\\Foundation\\Auth\\' => array($vendorDir . '/laravel/ui/auth-backend'),
|
'Illuminate\\Foundation\\Auth\\' => array($vendorDir . '/laravel/ui/auth-backend'),
|
||||||
'Illuminate\\' => array($vendorDir . '/laravel/framework/src/Illuminate'),
|
'Illuminate\\' => array($vendorDir . '/laravel/framework/src/Illuminate'),
|
||||||
'Http\\Promise\\' => array($vendorDir . '/php-http/promise/src'),
|
'Http\\Promise\\' => array($vendorDir . '/php-http/promise/src'),
|
||||||
@@ -105,7 +108,7 @@ return array(
|
|||||||
'Faker\\' => array($vendorDir . '/fakerphp/faker/src/Faker'),
|
'Faker\\' => array($vendorDir . '/fakerphp/faker/src/Faker'),
|
||||||
'Egulias\\EmailValidator\\' => array($vendorDir . '/egulias/email-validator/src'),
|
'Egulias\\EmailValidator\\' => array($vendorDir . '/egulias/email-validator/src'),
|
||||||
'Dotenv\\' => array($vendorDir . '/vlucas/phpdotenv/src'),
|
'Dotenv\\' => array($vendorDir . '/vlucas/phpdotenv/src'),
|
||||||
'Doctrine\\Inflector\\' => array($vendorDir . '/doctrine/inflector/lib/Doctrine/Inflector'),
|
'Doctrine\\Inflector\\' => array($vendorDir . '/doctrine/inflector/src'),
|
||||||
'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/src'),
|
'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/src'),
|
||||||
'Dflydev\\DotAccessData\\' => array($vendorDir . '/dflydev/dot-access-data/src'),
|
'Dflydev\\DotAccessData\\' => array($vendorDir . '/dflydev/dot-access-data/src'),
|
||||||
'DeepCopy\\' => array($vendorDir . '/myclabs/deep-copy/src/DeepCopy'),
|
'DeepCopy\\' => array($vendorDir . '/myclabs/deep-copy/src/DeepCopy'),
|
||||||
|
|||||||
Vendored
+9
-16
@@ -33,25 +33,18 @@ class ComposerAutoloaderInitb2555e5ff7197b9e020da74bbd3b7cfa
|
|||||||
|
|
||||||
$loader->register(true);
|
$loader->register(true);
|
||||||
|
|
||||||
$includeFiles = \Composer\Autoload\ComposerStaticInitb2555e5ff7197b9e020da74bbd3b7cfa::$files;
|
$filesToLoad = \Composer\Autoload\ComposerStaticInitb2555e5ff7197b9e020da74bbd3b7cfa::$files;
|
||||||
foreach ($includeFiles as $fileIdentifier => $file) {
|
$requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
|
||||||
composerRequireb2555e5ff7197b9e020da74bbd3b7cfa($fileIdentifier, $file);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $loader;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $fileIdentifier
|
|
||||||
* @param string $file
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
function composerRequireb2555e5ff7197b9e020da74bbd3b7cfa($fileIdentifier, $file)
|
|
||||||
{
|
|
||||||
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
|
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
|
||||||
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
|
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
|
||||||
|
|
||||||
require $file;
|
require $file;
|
||||||
}
|
}
|
||||||
|
}, null, null);
|
||||||
|
foreach ($filesToLoad as $fileIdentifier => $file) {
|
||||||
|
$requireFile($fileIdentifier, $file);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $loader;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+560
-105
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,97 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
$header = <<<EOF
|
||||||
|
This file is part of Composer.
|
||||||
|
|
||||||
|
(c) Nils Adermann <naderman@naderman.de>
|
||||||
|
Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
|
||||||
|
For the full copyright and license information, please view the LICENSE
|
||||||
|
file that was distributed with this source code.
|
||||||
|
EOF;
|
||||||
|
|
||||||
|
$finder = PhpCsFixer\Finder::create()
|
||||||
|
->files()
|
||||||
|
->in(__DIR__.'/src')
|
||||||
|
->in(__DIR__.'/tests')
|
||||||
|
->name('*.php')
|
||||||
|
->notPath('Fixtures')
|
||||||
|
;
|
||||||
|
|
||||||
|
$config = new PhpCsFixer\Config();
|
||||||
|
return $config
|
||||||
|
->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect())
|
||||||
|
->setRules([
|
||||||
|
'@PSR2' => true,
|
||||||
|
'binary_operator_spaces' => true,
|
||||||
|
'blank_line_before_statement' => ['statements' => ['declare', 'return']],
|
||||||
|
'cast_spaces' => ['space' => 'single'],
|
||||||
|
'header_comment' => ['header' => $header],
|
||||||
|
'statement_indentation' => ['stick_comment_to_next_continuous_control_statement' => true],
|
||||||
|
'include' => true,
|
||||||
|
|
||||||
|
'class_attributes_separation' => ['elements' => ['method' => 'one', 'trait_import' => 'none']],
|
||||||
|
'no_blank_lines_after_class_opening' => true,
|
||||||
|
'no_blank_lines_after_phpdoc' => true,
|
||||||
|
'no_empty_statement' => true,
|
||||||
|
'no_extra_blank_lines' => true,
|
||||||
|
'no_leading_namespace_whitespace' => true,
|
||||||
|
'no_trailing_comma_in_singleline' => true,
|
||||||
|
'no_whitespace_in_blank_line' => true,
|
||||||
|
'object_operator_without_whitespace' => true,
|
||||||
|
//'phpdoc_align' => true,
|
||||||
|
'phpdoc_indent' => true,
|
||||||
|
'no_empty_comment' => true,
|
||||||
|
'no_empty_phpdoc' => true,
|
||||||
|
'phpdoc_no_access' => true,
|
||||||
|
'phpdoc_no_package' => true,
|
||||||
|
//'phpdoc_order' => true,
|
||||||
|
'phpdoc_scalar' => true,
|
||||||
|
'phpdoc_trim' => true,
|
||||||
|
'phpdoc_types' => true,
|
||||||
|
'psr_autoloading' => true,
|
||||||
|
'blank_lines_before_namespace' => true,
|
||||||
|
'standardize_not_equals' => true,
|
||||||
|
'ternary_operator_spaces' => true,
|
||||||
|
'trailing_comma_in_multiline' => ['elements' => ['arrays']],
|
||||||
|
'unary_operator_spaces' => true,
|
||||||
|
|
||||||
|
'native_function_invocation' => [
|
||||||
|
'include' => ['@compiler_optimized'], // Targets functions with special Zend opcodes (e.g., strlen, count)
|
||||||
|
'scope' => 'namespaced', // Only fixes functions inside a namespace
|
||||||
|
'strict' => true, // Removes leading \ if not native
|
||||||
|
],
|
||||||
|
|
||||||
|
// imports
|
||||||
|
'no_unused_imports' => true,
|
||||||
|
'fully_qualified_strict_types' => true,
|
||||||
|
'single_line_after_imports' => true,
|
||||||
|
//'global_namespace_import' => ['import_classes' => true],
|
||||||
|
'no_leading_import_slash' => true,
|
||||||
|
'single_import_per_statement' => true,
|
||||||
|
|
||||||
|
// PHP 7.2 migration
|
||||||
|
'array_syntax' => true,
|
||||||
|
'list_syntax' => true,
|
||||||
|
'regular_callable_call' => true,
|
||||||
|
'static_lambda' => true,
|
||||||
|
'nullable_type_declaration_for_default_null_value' => true,
|
||||||
|
'explicit_indirect_variable' => true,
|
||||||
|
'visibility_required' => ['elements' => ['property', 'method', 'const']],
|
||||||
|
'non_printable_character' => true,
|
||||||
|
'combine_nested_dirname' => true,
|
||||||
|
'random_api_migration' => true,
|
||||||
|
'ternary_to_null_coalescing' => true,
|
||||||
|
'phpdoc_to_param_type' => false,
|
||||||
|
'declare_strict_types' => true,
|
||||||
|
'no_superfluous_phpdoc_tags' => [
|
||||||
|
'allow_mixed' => true,
|
||||||
|
],
|
||||||
|
|
||||||
|
// TODO php 7.4 migration (one day..)
|
||||||
|
// 'phpdoc_to_property_type' => true,
|
||||||
|
])
|
||||||
|
->setUsingCache(true)
|
||||||
|
->setRiskyAllowed(true)
|
||||||
|
->setFinder($finder)
|
||||||
|
;
|
||||||
+1
-2
@@ -3,8 +3,7 @@ composer/class-map-generator
|
|||||||
|
|
||||||
Utilities to generate class maps and scan PHP code.
|
Utilities to generate class maps and scan PHP code.
|
||||||
|
|
||||||
[](https://github.com/composer/class-map-generator/actions)
|
[](https://github.com/composer/class-map-generator/actions)
|
||||||
|
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
|
|||||||
+2
-2
@@ -15,7 +15,7 @@
|
|||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.2 || ^8.0",
|
"php": "^7.2 || ^8.0",
|
||||||
"symfony/finder": "^4.4 || ^5.3 || ^6 || ^7",
|
"symfony/finder": "^4.4 || ^5.3 || ^6 || ^7 || ^8",
|
||||||
"composer/pcre": "^2.1 || ^3.1"
|
"composer/pcre": "^2.1 || ^3.1"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
"phpstan/phpstan-deprecation-rules": "^1 || ^2",
|
"phpstan/phpstan-deprecation-rules": "^1 || ^2",
|
||||||
"phpstan/phpstan-strict-rules": "^1.1 || ^2",
|
"phpstan/phpstan-strict-rules": "^1.1 || ^2",
|
||||||
"phpstan/phpstan-phpunit": "^1 || ^2",
|
"phpstan/phpstan-phpunit": "^1 || ^2",
|
||||||
"symfony/filesystem": "^5.4 || ^6"
|
"symfony/filesystem": "^5.4 || ^6 || ^7 || ^8"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
|||||||
+2
-2
@@ -94,7 +94,7 @@ class ClassMap implements \Countable
|
|||||||
|
|
||||||
$ambiguousClasses = [];
|
$ambiguousClasses = [];
|
||||||
foreach ($this->ambiguousClasses as $class => $paths) {
|
foreach ($this->ambiguousClasses as $class => $paths) {
|
||||||
$paths = array_filter($paths, function ($path) use ($duplicatesFilter) {
|
$paths = array_filter($paths, static function ($path) use ($duplicatesFilter): bool {
|
||||||
return !Preg::isMatch($duplicatesFilter, strtr($path, '\\', '/'));
|
return !Preg::isMatch($duplicatesFilter, strtr($path, '\\', '/'));
|
||||||
});
|
});
|
||||||
if (\count($paths) > 0) {
|
if (\count($paths) > 0) {
|
||||||
@@ -157,7 +157,7 @@ class ClassMap implements \Countable
|
|||||||
$pathPrefix = rtrim(strtr($pathPrefix, '\\', '/'), '/');
|
$pathPrefix = rtrim(strtr($pathPrefix, '\\', '/'), '/');
|
||||||
|
|
||||||
foreach ($this->psrViolations as $path => $violations) {
|
foreach ($this->psrViolations as $path => $violations) {
|
||||||
if ($path === $pathPrefix || 0 === \strpos($path, $pathPrefix.'/')) {
|
if ($path === $pathPrefix || 0 === strpos($path, $pathPrefix.'/')) {
|
||||||
unset($this->psrViolations[$path]);
|
unset($this->psrViolations[$path]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-16
@@ -20,7 +20,6 @@ namespace Composer\ClassMapGenerator;
|
|||||||
|
|
||||||
use Composer\Pcre\Preg;
|
use Composer\Pcre\Preg;
|
||||||
use Symfony\Component\Finder\Finder;
|
use Symfony\Component\Finder\Finder;
|
||||||
use Composer\IO\IOInterface;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ClassMapGenerator
|
* ClassMapGenerator
|
||||||
@@ -57,7 +56,7 @@ class ClassMapGenerator
|
|||||||
{
|
{
|
||||||
$this->extensions = $extensions;
|
$this->extensions = $extensions;
|
||||||
$this->classMap = new ClassMap;
|
$this->classMap = new ClassMap;
|
||||||
$this->streamWrappersRegex = sprintf('{^(?:%s)://}', implode('|', array_map('preg_quote', stream_get_wrappers())));
|
$this->streamWrappersRegex = \sprintf('{^(?:%s)://}', implode('|', array_map('preg_quote', stream_get_wrappers())));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -109,21 +108,21 @@ class ClassMapGenerator
|
|||||||
*/
|
*/
|
||||||
public function scanPaths($path, ?string $excluded = null, string $autoloadType = 'classmap', ?string $namespace = null, array $excludedDirs = []): void
|
public function scanPaths($path, ?string $excluded = null, string $autoloadType = 'classmap', ?string $namespace = null, array $excludedDirs = []): void
|
||||||
{
|
{
|
||||||
if (!in_array($autoloadType, ['psr-0', 'psr-4', 'classmap'], true)) {
|
if (!\in_array($autoloadType, ['psr-0', 'psr-4', 'classmap'], true)) {
|
||||||
throw new \InvalidArgumentException('$autoloadType must be one of: "psr-0", "psr-4" or "classmap"');
|
throw new \InvalidArgumentException('$autoloadType must be one of: "psr-0", "psr-4" or "classmap"');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('classmap' !== $autoloadType) {
|
if ('classmap' !== $autoloadType) {
|
||||||
if (!is_string($path)) {
|
if (!\is_string($path)) {
|
||||||
throw new \InvalidArgumentException('$path must be a string when specifying a psr-0 or psr-4 autoload type');
|
throw new \InvalidArgumentException('$path must be a string when specifying a psr-0 or psr-4 autoload type');
|
||||||
}
|
}
|
||||||
if (!is_string($namespace)) {
|
if (!\is_string($namespace)) {
|
||||||
throw new \InvalidArgumentException('$namespace must be given (even if it is an empty string if you do not want to filter) when specifying a psr-0 or psr-4 autoload type');
|
throw new \InvalidArgumentException('$namespace must be given (even if it is an empty string if you do not want to filter) when specifying a psr-0 or psr-4 autoload type');
|
||||||
}
|
}
|
||||||
$basePath = $path;
|
$basePath = $path;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_string($path)) {
|
if (\is_string($path)) {
|
||||||
if (is_file($path)) {
|
if (is_file($path)) {
|
||||||
$path = [new \SplFileInfo($path)];
|
$path = [new \SplFileInfo($path)];
|
||||||
} elseif (is_dir($path) || strpos($path, '*') !== false) {
|
} elseif (is_dir($path) || strpos($path, '*') !== false) {
|
||||||
@@ -144,7 +143,7 @@ class ClassMapGenerator
|
|||||||
|
|
||||||
foreach ($path as $file) {
|
foreach ($path as $file) {
|
||||||
$filePath = $file->getPathname();
|
$filePath = $file->getPathname();
|
||||||
if (!in_array(pathinfo($filePath, PATHINFO_EXTENSION), $this->extensions, true)) {
|
if (!\in_array(pathinfo($filePath, PATHINFO_EXTENSION), $this->extensions, true)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,13 +223,18 @@ class ClassMapGenerator
|
|||||||
$validClasses = [];
|
$validClasses = [];
|
||||||
$rejectedClasses = [];
|
$rejectedClasses = [];
|
||||||
|
|
||||||
$realSubPath = substr($filePath, strlen($basePath) + 1);
|
$realSubPath = substr($filePath, \strlen($basePath) + 1);
|
||||||
$dotPosition = strrpos($realSubPath, '.');
|
$dotPosition = strrpos($realSubPath, '.');
|
||||||
$realSubPath = substr($realSubPath, 0, $dotPosition === false ? PHP_INT_MAX : $dotPosition);
|
$realSubPath = substr($realSubPath, 0, $dotPosition === false ? PHP_INT_MAX : $dotPosition);
|
||||||
|
|
||||||
foreach ($classes as $class) {
|
foreach ($classes as $class) {
|
||||||
// transform class name to file path and validate
|
// transform class name to file path and validate
|
||||||
if ('psr-0' === $namespaceType) {
|
if ('psr-0' === $namespaceType) {
|
||||||
|
if ('' !== $baseNamespace && !str_starts_with($class, $baseNamespace)) {
|
||||||
|
$rejectedClasses[] = $class;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$namespaceLength = strrpos($class, '\\');
|
$namespaceLength = strrpos($class, '\\');
|
||||||
if (false !== $namespaceLength) {
|
if (false !== $namespaceLength) {
|
||||||
$namespace = substr($class, 0, $namespaceLength + 1);
|
$namespace = substr($class, 0, $namespaceLength + 1);
|
||||||
@@ -241,7 +245,7 @@ class ClassMapGenerator
|
|||||||
$subPath = str_replace('_', DIRECTORY_SEPARATOR, $class);
|
$subPath = str_replace('_', DIRECTORY_SEPARATOR, $class);
|
||||||
}
|
}
|
||||||
} elseif ('psr-4' === $namespaceType) {
|
} elseif ('psr-4' === $namespaceType) {
|
||||||
$subNamespace = ('' !== $baseNamespace) ? substr($class, strlen($baseNamespace)) : $class;
|
$subNamespace = ('' !== $baseNamespace) ? substr($class, \strlen($baseNamespace)) : $class;
|
||||||
$subPath = str_replace('\\', DIRECTORY_SEPARATOR, $subNamespace);
|
$subPath = str_replace('\\', DIRECTORY_SEPARATOR, $subNamespace);
|
||||||
} else {
|
} else {
|
||||||
throw new \InvalidArgumentException('$namespaceType must be "psr-0" or "psr-4"');
|
throw new \InvalidArgumentException('$namespaceType must be "psr-0" or "psr-4"');
|
||||||
@@ -276,11 +280,8 @@ class ClassMapGenerator
|
|||||||
* Checks if the given path is absolute
|
* Checks if the given path is absolute
|
||||||
*
|
*
|
||||||
* @see Composer\Util\Filesystem::isAbsolutePath
|
* @see Composer\Util\Filesystem::isAbsolutePath
|
||||||
*
|
|
||||||
* @param string $path
|
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
private static function isAbsolutePath(string $path)
|
private static function isAbsolutePath(string $path): bool
|
||||||
{
|
{
|
||||||
return strpos($path, '/') === 0 || substr($path, 1, 1) === ':' || strpos($path, '\\\\') === 0;
|
return strpos($path, '/') === 0 || substr($path, 1, 1) === ':' || strpos($path, '\\\\') === 0;
|
||||||
}
|
}
|
||||||
@@ -292,9 +293,8 @@ class ClassMapGenerator
|
|||||||
* @see Composer\Util\Filesystem::normalizePath
|
* @see Composer\Util\Filesystem::normalizePath
|
||||||
*
|
*
|
||||||
* @param string $path Path to the file or directory
|
* @param string $path Path to the file or directory
|
||||||
* @return string
|
|
||||||
*/
|
*/
|
||||||
private static function normalizePath(string $path)
|
private static function normalizePath(string $path): string
|
||||||
{
|
{
|
||||||
$parts = [];
|
$parts = [];
|
||||||
$path = strtr($path, '\\', '/');
|
$path = strtr($path, '\\', '/');
|
||||||
@@ -330,7 +330,7 @@ class ClassMapGenerator
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ensure c: is normalized to C:
|
// ensure c: is normalized to C:
|
||||||
$prefix = Preg::replaceCallback('{(?:^|://)[a-z]:$}i', function (array $m) { return strtoupper((string) $m[0]); }, $prefix);
|
$prefix = Preg::replaceCallback('{(?:^|://)[a-z]:$}i', static function (array $m) { return strtoupper((string) $m[0]); }, $prefix);
|
||||||
|
|
||||||
return $prefix.$absolute.implode('/', $parts);
|
return $prefix.$absolute.implode('/', $parts);
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-26
@@ -24,7 +24,7 @@ class PhpFileCleaner
|
|||||||
private static $typeConfig;
|
private static $typeConfig;
|
||||||
|
|
||||||
/** @var non-empty-string */
|
/** @var non-empty-string */
|
||||||
private static $restPattern;
|
private static $rejectChars;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @readonly
|
* @readonly
|
||||||
@@ -53,14 +53,14 @@ class PhpFileCleaner
|
|||||||
public static function setTypeConfig(array $types): void
|
public static function setTypeConfig(array $types): void
|
||||||
{
|
{
|
||||||
foreach ($types as $type) {
|
foreach ($types as $type) {
|
||||||
self::$typeConfig[$type[0]] = array(
|
self::$typeConfig[$type[0]] = [
|
||||||
'name' => $type,
|
'name' => $type,
|
||||||
'length' => \strlen($type),
|
'length' => \strlen($type),
|
||||||
'pattern' => '{.\b(?<![\$:>])'.$type.'\s++[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+}Ais',
|
'pattern' => '{.\b(?<![\$:>])'.$type.'\s++[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+}Ais',
|
||||||
);
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
self::$restPattern = '{[^?"\'</'.implode('', array_keys(self::$typeConfig)).']+}A';
|
self::$rejectChars = '?"\'</'.implode('', array_keys(self::$typeConfig));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct(string $contents, int $maxMatches)
|
public function __construct(string $contents, int $maxMatches)
|
||||||
@@ -110,6 +110,7 @@ class PhpFileCleaner
|
|||||||
$this->skipToNewline();
|
$this->skipToNewline();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->peek('*')) {
|
if ($this->peek('*')) {
|
||||||
$this->skipComment();
|
$this->skipComment();
|
||||||
continue;
|
continue;
|
||||||
@@ -119,19 +120,18 @@ class PhpFileCleaner
|
|||||||
if ($this->maxMatches === 1 && isset(self::$typeConfig[$char])) {
|
if ($this->maxMatches === 1 && isset(self::$typeConfig[$char])) {
|
||||||
$type = self::$typeConfig[$char];
|
$type = self::$typeConfig[$char];
|
||||||
if (
|
if (
|
||||||
\substr($this->contents, $this->index, $type['length']) === $type['name']
|
substr($this->contents, $this->index, $type['length']) === $type['name']
|
||||||
&& Preg::isMatch($type['pattern'], $this->contents, $match, 0, $this->index - 1)
|
&& Preg::isMatch($type['pattern'], $this->contents, $match, 0, $this->index - 1)
|
||||||
) {
|
) {
|
||||||
$clean .= $match[0];
|
return $clean . $match[0];
|
||||||
|
|
||||||
return $clean;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->index += 1;
|
$this->index += 1;
|
||||||
if ($this->match(self::$restPattern, $match)) {
|
$skip = strcspn($this->contents, self::$rejectChars, $this->index);
|
||||||
$clean .= $char . $match[0];
|
if ($skip > 0) {
|
||||||
$this->index += \strlen($match[0]);
|
$clean .= $char . substr($this->contents, $this->index, $skip);
|
||||||
|
$this->index += $skip;
|
||||||
} else {
|
} else {
|
||||||
$clean .= $char;
|
$clean .= $char;
|
||||||
}
|
}
|
||||||
@@ -155,16 +155,24 @@ class PhpFileCleaner
|
|||||||
|
|
||||||
private function skipString(string $delimiter): void
|
private function skipString(string $delimiter): void
|
||||||
{
|
{
|
||||||
|
$rejectChars = '\\' . $delimiter;
|
||||||
$this->index += 1;
|
$this->index += 1;
|
||||||
while ($this->index < $this->len) {
|
while ($this->index < $this->len) {
|
||||||
|
$this->index += strcspn($this->contents, $rejectChars, $this->index);
|
||||||
|
if ($this->index >= $this->len) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->contents[$this->index] === '\\' && ($this->peek('\\') || $this->peek($delimiter))) {
|
if ($this->contents[$this->index] === '\\' && ($this->peek('\\') || $this->peek($delimiter))) {
|
||||||
$this->index += 2;
|
$this->index += 2;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->contents[$this->index] === $delimiter) {
|
if ($this->contents[$this->index] === $delimiter) {
|
||||||
$this->index += 1;
|
$this->index += 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->index += 1;
|
$this->index += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -173,7 +181,9 @@ class PhpFileCleaner
|
|||||||
{
|
{
|
||||||
$this->index += 2;
|
$this->index += 2;
|
||||||
while ($this->index < $this->len) {
|
while ($this->index < $this->len) {
|
||||||
if ($this->contents[$this->index] === '*' && $this->peek('/')) {
|
$this->index += strcspn($this->contents, '*', $this->index);
|
||||||
|
|
||||||
|
if ($this->peek('/')) {
|
||||||
$this->index += 2;
|
$this->index += 2;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -184,12 +194,7 @@ class PhpFileCleaner
|
|||||||
|
|
||||||
private function skipToNewline(): void
|
private function skipToNewline(): void
|
||||||
{
|
{
|
||||||
while ($this->index < $this->len) {
|
$this->index += strcspn($this->contents, "\r\n", $this->index);
|
||||||
if ($this->contents[$this->index] === "\r" || $this->contents[$this->index] === "\n") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$this->index += 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function skipHeredoc(string $delimiter): void
|
private function skipHeredoc(string $delimiter): void
|
||||||
@@ -207,27 +212,22 @@ class PhpFileCleaner
|
|||||||
continue 2;
|
continue 2;
|
||||||
case $firstDelimiterChar:
|
case $firstDelimiterChar:
|
||||||
if (
|
if (
|
||||||
\substr($this->contents, $this->index, $delimiterLength) === $delimiter
|
substr($this->contents, $this->index, $delimiterLength) === $delimiter
|
||||||
&& $this->match($delimiterPattern)
|
&& $this->match($delimiterPattern)
|
||||||
) {
|
) {
|
||||||
$this->index += $delimiterLength;
|
$this->index += $delimiterLength;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip the rest of the line
|
// skip the rest of the line
|
||||||
while ($this->index < $this->len) {
|
|
||||||
$this->skipToNewline();
|
$this->skipToNewline();
|
||||||
|
|
||||||
// skip newlines
|
// skip newlines
|
||||||
while ($this->index < $this->len && ($this->contents[$this->index] === "\r" || $this->contents[$this->index] === "\n")) {
|
$this->index += strspn($this->contents, "\r\n", $this->index);
|
||||||
$this->index += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+26
-22
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
namespace Composer\ClassMapGenerator;
|
namespace Composer\ClassMapGenerator;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
use Composer\Pcre\Preg;
|
use Composer\Pcre\Preg;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,16 +24,17 @@ class PhpFileParser
|
|||||||
* Extract the classes in the given file
|
* Extract the classes in the given file
|
||||||
*
|
*
|
||||||
* @param string $path The file to check
|
* @param string $path The file to check
|
||||||
* @throws \RuntimeException
|
* @throws RuntimeException
|
||||||
* @return list<class-string> The found classes
|
* @return list<class-string> The found classes
|
||||||
*/
|
*/
|
||||||
public static function findClasses(string $path): array
|
public static function findClasses(string $path): array
|
||||||
{
|
{
|
||||||
$extraTypes = self::getExtraTypes();
|
$extraTypes = self::getExtraTypes();
|
||||||
|
|
||||||
if (!function_exists('php_strip_whitespace')) {
|
if (!\function_exists('php_strip_whitespace')) {
|
||||||
throw new \RuntimeException('Classmap generation relies on the php_strip_whitespace function, but it has been disabled by the disable_functions directive.');
|
throw new RuntimeException('Classmap generation relies on the php_strip_whitespace function, but it has been disabled by the disable_functions directive.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use @ here instead of Silencer to actively suppress 'unhelpful' output
|
// Use @ here instead of Silencer to actively suppress 'unhelpful' output
|
||||||
// @link https://github.com/composer/composer/pull/4886
|
// @link https://github.com/composer/composer/pull/4886
|
||||||
$contents = @php_strip_whitespace($path);
|
$contents = @php_strip_whitespace($path);
|
||||||
@@ -43,24 +45,26 @@ class PhpFileParser
|
|||||||
$message = 'File at "%s" is not readable, check its permissions';
|
$message = 'File at "%s" is not readable, check its permissions';
|
||||||
} elseif ('' === trim((string) file_get_contents($path))) {
|
} elseif ('' === trim((string) file_get_contents($path))) {
|
||||||
// The input file was really empty and thus contains no classes
|
// The input file was really empty and thus contains no classes
|
||||||
return array();
|
return [];
|
||||||
} else {
|
} else {
|
||||||
$message = 'File at "%s" could not be parsed as PHP, it may be binary or corrupted';
|
$message = 'File at "%s" could not be parsed as PHP, it may be binary or corrupted';
|
||||||
}
|
}
|
||||||
|
|
||||||
$error = error_get_last();
|
$error = error_get_last();
|
||||||
if (isset($error['message'])) {
|
if (isset($error['message'])) {
|
||||||
$message .= PHP_EOL . 'The following message may be helpful:' . PHP_EOL . $error['message'];
|
$message .= PHP_EOL . 'The following message may be helpful:' . PHP_EOL . $error['message'];
|
||||||
}
|
}
|
||||||
throw new \RuntimeException(sprintf($message, $path));
|
|
||||||
|
throw new RuntimeException(\sprintf($message, $path));
|
||||||
}
|
}
|
||||||
|
|
||||||
// return early if there is no chance of matching anything in this file
|
// return early if there is no chance of matching anything in this file
|
||||||
Preg::matchAllStrictGroups('{\b(?:class|interface|trait'.$extraTypes.')\s}i', $contents, $matches);
|
Preg::matchAllStrictGroups('{\b(?:class|interface|trait'.$extraTypes.')\s}i', $contents, $matches);
|
||||||
if (0 === \count($matches)) {
|
if ([] === $matches[0]) {
|
||||||
return array();
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$p = new PhpFileCleaner($contents, count($matches[0]));
|
$p = new PhpFileCleaner($contents, \count($matches[0]));
|
||||||
$contents = $p->clean();
|
$contents = $p->clean();
|
||||||
unset($p);
|
unset($p);
|
||||||
|
|
||||||
@@ -71,22 +75,26 @@ class PhpFileParser
|
|||||||
)
|
)
|
||||||
}ix', $contents, $matches);
|
}ix', $contents, $matches);
|
||||||
|
|
||||||
$classes = array();
|
$classes = [];
|
||||||
$namespace = '';
|
$namespace = '';
|
||||||
|
|
||||||
for ($i = 0, $len = count($matches['type']); $i < $len; $i++) {
|
for ($i = 0, $len = \count($matches['type']); $i < $len; ++$i) {
|
||||||
if (isset($matches['ns'][$i]) && $matches['ns'][$i] !== '') {
|
if (isset($matches['ns'][$i]) && $matches['ns'][$i] !== '') {
|
||||||
$namespace = str_replace(array(' ', "\t", "\r", "\n"), '', (string) $matches['nsname'][$i]) . '\\';
|
$namespace = str_replace([' ', "\t", "\r", "\n"], '', (string) $matches['nsname'][$i]) . '\\';
|
||||||
} else {
|
} else {
|
||||||
$name = $matches['name'][$i];
|
$name = $matches['name'][$i];
|
||||||
assert(is_string($name));
|
\assert(\is_string($name));
|
||||||
// skip anon classes extending/implementing
|
// skip anon classes extending/implementing
|
||||||
if ($name === 'extends' || $name === 'implements') {
|
if ($name === 'extends') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if ($name === 'implements') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if ($name[0] === ':') {
|
if ($name[0] === ':') {
|
||||||
// This is an XHP class, https://github.com/facebook/xhp
|
// This is an XHP class, https://github.com/facebook/xhp
|
||||||
$name = 'xhp'.substr(str_replace(array('-', ':'), array('_', '__'), $name), 1);
|
$name = 'xhp'.substr(str_replace(['-', ':'], ['_', '__'], $name), 1);
|
||||||
} elseif (strtolower((string) $matches['type'][$i]) === 'enum') {
|
} elseif (strtolower((string) $matches['type'][$i]) === 'enum') {
|
||||||
// something like:
|
// something like:
|
||||||
// enum Foo: int { HERP = '123'; }
|
// enum Foo: int { HERP = '123'; }
|
||||||
@@ -101,6 +109,7 @@ class PhpFileParser
|
|||||||
$name = substr($name, 0, $colonPos);
|
$name = substr($name, 0, $colonPos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var class-string */
|
/** @var class-string */
|
||||||
$className = ltrim($namespace . $name, '\\');
|
$className = ltrim($namespace . $name, '\\');
|
||||||
$classes[] = $className;
|
$classes[] = $className;
|
||||||
@@ -110,22 +119,18 @@ class PhpFileParser
|
|||||||
return $classes;
|
return $classes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
private static function getExtraTypes(): string
|
private static function getExtraTypes(): string
|
||||||
{
|
{
|
||||||
static $extraTypes = null;
|
static $extraTypes = null;
|
||||||
|
|
||||||
if (null === $extraTypes) {
|
if (null === $extraTypes) {
|
||||||
$extraTypes = '';
|
$extraTypes = '';
|
||||||
if (PHP_VERSION_ID >= 80100 || (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.3', '>='))) {
|
$extraTypesArray = [];
|
||||||
|
if (PHP_VERSION_ID >= 80100 || (\defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.3', '>='))) {
|
||||||
$extraTypes .= '|enum';
|
$extraTypes .= '|enum';
|
||||||
|
$extraTypesArray = ['enum'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$extraTypesArray = array_filter(explode('|', $extraTypes), function (string $type) {
|
|
||||||
return $type !== '';
|
|
||||||
});
|
|
||||||
PhpFileCleaner::setTypeConfig(array_merge(['class', 'interface', 'trait'], $extraTypesArray));
|
PhpFileCleaner::setTypeConfig(array_merge(['class', 'interface', 'trait'], $extraTypesArray));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +145,6 @@ class PhpFileParser
|
|||||||
*
|
*
|
||||||
* @see Composer\Util\Filesystem::isReadable
|
* @see Composer\Util\Filesystem::isReadable
|
||||||
*
|
*
|
||||||
* @param string $path
|
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
private static function isReadable(string $path)
|
private static function isReadable(string $path)
|
||||||
|
|||||||
Vendored
+1582
-1168
File diff suppressed because it is too large
Load Diff
Vendored
+367
-328
File diff suppressed because it is too large
Load Diff
Vendored
+9
-5
@@ -20,12 +20,13 @@
|
|||||||
"php": "^7.4 || ^8.0"
|
"php": "^7.4 || ^8.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^8 || ^9",
|
"phpunit/phpunit": "^9",
|
||||||
"phpstan/phpstan": "^1.12 || ^2",
|
"phpstan/phpstan": "^2",
|
||||||
"phpstan/phpstan-strict-rules": "^1 || ^2"
|
"phpstan/phpstan-strict-rules": "^2",
|
||||||
|
"phpstan/phpstan-deprecation-rules": "^2"
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
"phpstan/phpstan": "<1.11.10"
|
"phpstan/phpstan": "<2.2.2"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
@@ -48,7 +49,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "@php vendor/bin/phpunit",
|
"test": [
|
||||||
|
"@php vendor/bin/phpunit",
|
||||||
|
"@php vendor/bin/phpunit --testsuite phpstan"
|
||||||
|
],
|
||||||
"phpstan": "@php phpstan analyse"
|
"phpstan": "@php phpstan analyse"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,18 +57,38 @@ final class PregMatchTypeSpecifyingExtension implements StaticMethodTypeSpecifyi
|
|||||||
{
|
{
|
||||||
$args = $node->getArgs();
|
$args = $node->getArgs();
|
||||||
$patternArg = $args[0] ?? null;
|
$patternArg = $args[0] ?? null;
|
||||||
|
$subjectArg = $args[1] ?? null;
|
||||||
$matchesArg = $args[2] ?? null;
|
$matchesArg = $args[2] ?? null;
|
||||||
$flagsArg = $args[3] ?? null;
|
$flagsArg = $args[3] ?? null;
|
||||||
|
|
||||||
|
$subjectTypes = new SpecifiedTypes();
|
||||||
|
if ($patternArg === null) {
|
||||||
|
return $subjectTypes;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
$patternArg === null || $matchesArg === null
|
$subjectArg !== null
|
||||||
|
&& $context->true()
|
||||||
|
&& $scope->getType($subjectArg->value)->isString()->yes()
|
||||||
) {
|
) {
|
||||||
return new SpecifiedTypes();
|
$subjectType = $this->regexShapeMatcher->matchSubjectExpr($patternArg->value, $scope);
|
||||||
|
if ($subjectType !== null) {
|
||||||
|
$subjectTypes = $this->typeSpecifier->create(
|
||||||
|
$subjectArg->value,
|
||||||
|
$subjectType,
|
||||||
|
$context,
|
||||||
|
$scope,
|
||||||
|
)->setRootExpr($node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($matchesArg === null) {
|
||||||
|
return $subjectTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
$flagsType = PregMatchFlags::getType($flagsArg, $scope);
|
$flagsType = PregMatchFlags::getType($flagsArg, $scope);
|
||||||
if ($flagsType === null) {
|
if ($flagsType === null) {
|
||||||
return new SpecifiedTypes();
|
return $subjectTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stripos($methodReflection->getName(), 'matchAll') !== false) {
|
if (stripos($methodReflection->getName(), 'matchAll') !== false) {
|
||||||
@@ -78,7 +98,7 @@ final class PregMatchTypeSpecifyingExtension implements StaticMethodTypeSpecifyi
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($matchedType === null) {
|
if ($matchedType === null) {
|
||||||
return new SpecifiedTypes();
|
return $subjectTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -93,27 +113,13 @@ final class PregMatchTypeSpecifyingExtension implements StaticMethodTypeSpecifyi
|
|||||||
$context = $context->negate();
|
$context = $context->negate();
|
||||||
}
|
}
|
||||||
|
|
||||||
// @phpstan-ignore function.alreadyNarrowedType
|
$specifiedTypes = $this->typeSpecifier->create(
|
||||||
if (method_exists('PHPStan\Analyser\SpecifiedTypes', 'setRootExpr')) {
|
|
||||||
$typeSpecifier = $this->typeSpecifier->create(
|
|
||||||
$matchesArg->value,
|
$matchesArg->value,
|
||||||
$matchedType,
|
$matchedType,
|
||||||
$context,
|
$context,
|
||||||
$scope
|
$scope
|
||||||
)->setRootExpr($node);
|
)->setRootExpr($node);
|
||||||
|
|
||||||
return $overwrite ? $typeSpecifier->setAlwaysOverwriteTypes() : $typeSpecifier;
|
return $subjectTypes->unionWith($overwrite ? $specifiedTypes->setAlwaysOverwriteTypes() : $specifiedTypes);
|
||||||
}
|
|
||||||
|
|
||||||
// @phpstan-ignore arguments.count
|
|
||||||
return $this->typeSpecifier->create(
|
|
||||||
$matchesArg->value,
|
|
||||||
$matchedType,
|
|
||||||
$context,
|
|
||||||
// @phpstan-ignore argument.type
|
|
||||||
$overwrite,
|
|
||||||
$scope,
|
|
||||||
$node
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+2
-2
@@ -395,7 +395,7 @@ class Preg
|
|||||||
* @return array<int|string, string>
|
* @return array<int|string, string>
|
||||||
* @throws UnexpectedNullMatchException
|
* @throws UnexpectedNullMatchException
|
||||||
*/
|
*/
|
||||||
private static function enforceNonNullMatches(string $pattern, array $matches, string $variantMethod)
|
private static function enforceNonNullMatches(string $pattern, array $matches, string $variantMethod): array
|
||||||
{
|
{
|
||||||
foreach ($matches as $group => $match) {
|
foreach ($matches as $group => $match) {
|
||||||
if (is_string($match) || (is_array($match) && is_string($match[0]))) {
|
if (is_string($match) || (is_array($match) && is_string($match[0]))) {
|
||||||
@@ -414,7 +414,7 @@ class Preg
|
|||||||
* @return array<int|string, list<string>>
|
* @return array<int|string, list<string>>
|
||||||
* @throws UnexpectedNullMatchException
|
* @throws UnexpectedNullMatchException
|
||||||
*/
|
*/
|
||||||
private static function enforceNonNullMatchAll(string $pattern, array $matches, string $variantMethod)
|
private static function enforceNonNullMatchAll(string $pattern, array $matches, string $variantMethod): array
|
||||||
{
|
{
|
||||||
foreach ($matches as $group => $groupMatches) {
|
foreach ($matches as $group => $groupMatches) {
|
||||||
foreach ($groupMatches as $match) {
|
foreach ($groupMatches as $match) {
|
||||||
|
|||||||
Vendored
+2
-3
@@ -19,8 +19,7 @@ if ($issues) {
|
|||||||
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
|
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
trigger_error(
|
throw new \RuntimeException(
|
||||||
'Composer detected issues in your platform: ' . implode(' ', $issues),
|
'Composer detected issues in your platform: ' . implode(' ', $issues)
|
||||||
E_USER_ERROR
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+44
-18
@@ -1,41 +1,67 @@
|
|||||||
{
|
{
|
||||||
"name": "doctrine/inflector",
|
"name": "doctrine/inflector",
|
||||||
"type": "library",
|
|
||||||
"description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.",
|
"description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.",
|
||||||
"keywords": ["php", "strings", "words", "manipulation", "inflector", "inflection", "uppercase", "lowercase", "singular", "plural"],
|
|
||||||
"homepage": "https://www.doctrine-project.org/projects/inflector.html",
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"authors": [
|
"type": "library",
|
||||||
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
|
"keywords": [
|
||||||
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
|
"php",
|
||||||
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"},
|
"strings",
|
||||||
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"},
|
"words",
|
||||||
{"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"}
|
"manipulation",
|
||||||
|
"inflector",
|
||||||
|
"inflection",
|
||||||
|
"uppercase",
|
||||||
|
"lowercase",
|
||||||
|
"singular",
|
||||||
|
"plural"
|
||||||
],
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Guilherme Blanco",
|
||||||
|
"email": "guilhermeblanco@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Roman Borschel",
|
||||||
|
"email": "roman@code-factory.org"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Benjamin Eberlei",
|
||||||
|
"email": "kontakt@beberlei.de"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jonathan Wage",
|
||||||
|
"email": "jonwage@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Johannes Schmitt",
|
||||||
|
"email": "schmittjoh@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"homepage": "https://www.doctrine-project.org/projects/inflector.html",
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.2 || ^8.0"
|
"php": "^7.2 || ^8.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"doctrine/coding-standard": "^11.0",
|
"doctrine/coding-standard": "^12.0 || ^13.0",
|
||||||
"phpstan/phpstan": "^1.8",
|
"phpstan/phpstan": "^1.12 || ^2.0",
|
||||||
"phpstan/phpstan-phpunit": "^1.1",
|
"phpstan/phpstan-phpunit": "^1.4 || ^2.0",
|
||||||
"phpstan/phpstan-strict-rules": "^1.3",
|
"phpstan/phpstan-strict-rules": "^1.6 || ^2.0",
|
||||||
"phpunit/phpunit": "^8.5 || ^9.5",
|
"phpunit/phpunit": "^8.5 || ^12.2"
|
||||||
"vimeo/psalm": "^4.25 || ^5.4"
|
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Doctrine\\Inflector\\": "lib/Doctrine/Inflector"
|
"Doctrine\\Inflector\\": "src"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload-dev": {
|
"autoload-dev": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Doctrine\\Tests\\Inflector\\": "tests/Doctrine/Tests/Inflector"
|
"Doctrine\\Tests\\Inflector\\": "tests"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"allow-plugins": {
|
"allow-plugins": {
|
||||||
"dealerdirect/phpcodesniffer-composer-installer": true
|
"dealerdirect/phpcodesniffer-composer-installer": true
|
||||||
}
|
},
|
||||||
|
"sort-packages": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -39,6 +39,7 @@ you want to create an inflector for to the ``createForLanguage()`` method:
|
|||||||
The supported languages are as follows:
|
The supported languages are as follows:
|
||||||
|
|
||||||
- ``Language::ENGLISH``
|
- ``Language::ENGLISH``
|
||||||
|
- ``Language::ESPERANTO``
|
||||||
- ``Language::FRENCH``
|
- ``Language::FRENCH``
|
||||||
- ``Language::NORWEGIAN_BOKMAL``
|
- ``Language::NORWEGIAN_BOKMAL``
|
||||||
- ``Language::PORTUGUESE``
|
- ``Language::PORTUGUESE``
|
||||||
|
|||||||
+8
@@ -5,7 +5,9 @@ declare(strict_types=1);
|
|||||||
namespace Doctrine\Inflector;
|
namespace Doctrine\Inflector;
|
||||||
|
|
||||||
use Doctrine\Inflector\Rules\English;
|
use Doctrine\Inflector\Rules\English;
|
||||||
|
use Doctrine\Inflector\Rules\Esperanto;
|
||||||
use Doctrine\Inflector\Rules\French;
|
use Doctrine\Inflector\Rules\French;
|
||||||
|
use Doctrine\Inflector\Rules\Italian;
|
||||||
use Doctrine\Inflector\Rules\NorwegianBokmal;
|
use Doctrine\Inflector\Rules\NorwegianBokmal;
|
||||||
use Doctrine\Inflector\Rules\Portuguese;
|
use Doctrine\Inflector\Rules\Portuguese;
|
||||||
use Doctrine\Inflector\Rules\Spanish;
|
use Doctrine\Inflector\Rules\Spanish;
|
||||||
@@ -27,9 +29,15 @@ final class InflectorFactory
|
|||||||
case Language::ENGLISH:
|
case Language::ENGLISH:
|
||||||
return new English\InflectorFactory();
|
return new English\InflectorFactory();
|
||||||
|
|
||||||
|
case Language::ESPERANTO:
|
||||||
|
return new Esperanto\InflectorFactory();
|
||||||
|
|
||||||
case Language::FRENCH:
|
case Language::FRENCH:
|
||||||
return new French\InflectorFactory();
|
return new French\InflectorFactory();
|
||||||
|
|
||||||
|
case Language::ITALIAN:
|
||||||
|
return new Italian\InflectorFactory();
|
||||||
|
|
||||||
case Language::NORWEGIAN_BOKMAL:
|
case Language::NORWEGIAN_BOKMAL:
|
||||||
return new NorwegianBokmal\InflectorFactory();
|
return new NorwegianBokmal\InflectorFactory();
|
||||||
|
|
||||||
+2
@@ -7,7 +7,9 @@ namespace Doctrine\Inflector;
|
|||||||
final class Language
|
final class Language
|
||||||
{
|
{
|
||||||
public const ENGLISH = 'english';
|
public const ENGLISH = 'english';
|
||||||
|
public const ESPERANTO = 'esperanto';
|
||||||
public const FRENCH = 'french';
|
public const FRENCH = 'french';
|
||||||
|
public const ITALIAN = 'italian';
|
||||||
public const NORWEGIAN_BOKMAL = 'norwegian-bokmal';
|
public const NORWEGIAN_BOKMAL = 'norwegian-bokmal';
|
||||||
public const PORTUGUESE = 'portuguese';
|
public const PORTUGUESE = 'portuguese';
|
||||||
public const SPANISH = 'spanish';
|
public const SPANISH = 'spanish';
|
||||||
+18
-17
@@ -92,28 +92,35 @@ class Inflectible
|
|||||||
/** @return Substitution[] */
|
/** @return Substitution[] */
|
||||||
public static function getIrregular(): iterable
|
public static function getIrregular(): iterable
|
||||||
{
|
{
|
||||||
|
yield new Substitution(new Word('abuse'), new Word('abuses'));
|
||||||
|
yield new Substitution(new Word('alga'), new Word('algae'));
|
||||||
yield new Substitution(new Word('atlas'), new Word('atlases'));
|
yield new Substitution(new Word('atlas'), new Word('atlases'));
|
||||||
|
yield new Substitution(new Word('avalanche'), new Word('avalanches'));
|
||||||
yield new Substitution(new Word('axis'), new Word('axes'));
|
yield new Substitution(new Word('axis'), new Word('axes'));
|
||||||
yield new Substitution(new Word('axe'), new Word('axes'));
|
yield new Substitution(new Word('axe'), new Word('axes'));
|
||||||
yield new Substitution(new Word('beef'), new Word('beefs'));
|
yield new Substitution(new Word('beef'), new Word('beefs'));
|
||||||
yield new Substitution(new Word('blouse'), new Word('blouses'));
|
yield new Substitution(new Word('blouse'), new Word('blouses'));
|
||||||
yield new Substitution(new Word('brother'), new Word('brothers'));
|
yield new Substitution(new Word('brother'), new Word('brothers'));
|
||||||
|
yield new Substitution(new Word('brownie'), new Word('brownies'));
|
||||||
|
yield new Substitution(new Word('cache'), new Word('caches'));
|
||||||
yield new Substitution(new Word('cafe'), new Word('cafes'));
|
yield new Substitution(new Word('cafe'), new Word('cafes'));
|
||||||
|
yield new Substitution(new Word('canvas'), new Word('canvases'));
|
||||||
yield new Substitution(new Word('cave'), new Word('caves'));
|
yield new Substitution(new Word('cave'), new Word('caves'));
|
||||||
yield new Substitution(new Word('chateau'), new Word('chateaux'));
|
yield new Substitution(new Word('chateau'), new Word('chateaux'));
|
||||||
yield new Substitution(new Word('niveau'), new Word('niveaux'));
|
|
||||||
yield new Substitution(new Word('child'), new Word('children'));
|
yield new Substitution(new Word('child'), new Word('children'));
|
||||||
yield new Substitution(new Word('canvas'), new Word('canvases'));
|
|
||||||
yield new Substitution(new Word('cookie'), new Word('cookies'));
|
yield new Substitution(new Word('cookie'), new Word('cookies'));
|
||||||
yield new Substitution(new Word('brownie'), new Word('brownies'));
|
|
||||||
yield new Substitution(new Word('corpus'), new Word('corpuses'));
|
yield new Substitution(new Word('corpus'), new Word('corpuses'));
|
||||||
yield new Substitution(new Word('cow'), new Word('cows'));
|
yield new Substitution(new Word('cow'), new Word('cows'));
|
||||||
yield new Substitution(new Word('criterion'), new Word('criteria'));
|
yield new Substitution(new Word('criterion'), new Word('criteria'));
|
||||||
yield new Substitution(new Word('curriculum'), new Word('curricula'));
|
yield new Substitution(new Word('curriculum'), new Word('curricula'));
|
||||||
|
yield new Substitution(new Word('curve'), new Word('curves'));
|
||||||
yield new Substitution(new Word('demo'), new Word('demos'));
|
yield new Substitution(new Word('demo'), new Word('demos'));
|
||||||
|
yield new Substitution(new Word('die'), new Word('dice'));
|
||||||
yield new Substitution(new Word('domino'), new Word('dominoes'));
|
yield new Substitution(new Word('domino'), new Word('dominoes'));
|
||||||
yield new Substitution(new Word('echo'), new Word('echoes'));
|
yield new Substitution(new Word('echo'), new Word('echoes'));
|
||||||
|
yield new Substitution(new Word('emphasis'), new Word('emphases'));
|
||||||
yield new Substitution(new Word('epoch'), new Word('epochs'));
|
yield new Substitution(new Word('epoch'), new Word('epochs'));
|
||||||
|
yield new Substitution(new Word('foe'), new Word('foes'));
|
||||||
yield new Substitution(new Word('foot'), new Word('feet'));
|
yield new Substitution(new Word('foot'), new Word('feet'));
|
||||||
yield new Substitution(new Word('fungus'), new Word('fungi'));
|
yield new Substitution(new Word('fungus'), new Word('fungi'));
|
||||||
yield new Substitution(new Word('ganglion'), new Word('ganglions'));
|
yield new Substitution(new Word('ganglion'), new Word('ganglions'));
|
||||||
@@ -122,7 +129,9 @@ class Inflectible
|
|||||||
yield new Substitution(new Word('genus'), new Word('genera'));
|
yield new Substitution(new Word('genus'), new Word('genera'));
|
||||||
yield new Substitution(new Word('goose'), new Word('geese'));
|
yield new Substitution(new Word('goose'), new Word('geese'));
|
||||||
yield new Substitution(new Word('graffito'), new Word('graffiti'));
|
yield new Substitution(new Word('graffito'), new Word('graffiti'));
|
||||||
|
yield new Substitution(new Word('grave'), new Word('graves'));
|
||||||
yield new Substitution(new Word('hippopotamus'), new Word('hippopotami'));
|
yield new Substitution(new Word('hippopotamus'), new Word('hippopotami'));
|
||||||
|
yield new Substitution(new Word('hoax'), new Word('hoaxes'));
|
||||||
yield new Substitution(new Word('hoof'), new Word('hoofs'));
|
yield new Substitution(new Word('hoof'), new Word('hoofs'));
|
||||||
yield new Substitution(new Word('human'), new Word('humans'));
|
yield new Substitution(new Word('human'), new Word('humans'));
|
||||||
yield new Substitution(new Word('iris'), new Word('irises'));
|
yield new Substitution(new Word('iris'), new Word('irises'));
|
||||||
@@ -138,9 +147,13 @@ class Inflectible
|
|||||||
yield new Substitution(new Word('motto'), new Word('mottoes'));
|
yield new Substitution(new Word('motto'), new Word('mottoes'));
|
||||||
yield new Substitution(new Word('move'), new Word('moves'));
|
yield new Substitution(new Word('move'), new Word('moves'));
|
||||||
yield new Substitution(new Word('mythos'), new Word('mythoi'));
|
yield new Substitution(new Word('mythos'), new Word('mythoi'));
|
||||||
|
yield new Substitution(new Word('neurosis'), new Word('neuroses'));
|
||||||
yield new Substitution(new Word('niche'), new Word('niches'));
|
yield new Substitution(new Word('niche'), new Word('niches'));
|
||||||
|
yield new Substitution(new Word('niveau'), new Word('niveaux'));
|
||||||
yield new Substitution(new Word('nucleus'), new Word('nuclei'));
|
yield new Substitution(new Word('nucleus'), new Word('nuclei'));
|
||||||
yield new Substitution(new Word('numen'), new Word('numina'));
|
yield new Substitution(new Word('numen'), new Word('numina'));
|
||||||
|
yield new Substitution(new Word('nursery'), new Word('nurseries'));
|
||||||
|
yield new Substitution(new Word('oasis'), new Word('oases'));
|
||||||
yield new Substitution(new Word('occiput'), new Word('occiputs'));
|
yield new Substitution(new Word('occiput'), new Word('occiputs'));
|
||||||
yield new Substitution(new Word('octopus'), new Word('octopuses'));
|
yield new Substitution(new Word('octopus'), new Word('octopuses'));
|
||||||
yield new Substitution(new Word('opus'), new Word('opuses'));
|
yield new Substitution(new Word('opus'), new Word('opuses'));
|
||||||
@@ -151,10 +164,12 @@ class Inflectible
|
|||||||
yield new Substitution(new Word('plateau'), new Word('plateaux'));
|
yield new Substitution(new Word('plateau'), new Word('plateaux'));
|
||||||
yield new Substitution(new Word('runner-up'), new Word('runners-up'));
|
yield new Substitution(new Word('runner-up'), new Word('runners-up'));
|
||||||
yield new Substitution(new Word('safe'), new Word('safes'));
|
yield new Substitution(new Word('safe'), new Word('safes'));
|
||||||
|
yield new Substitution(new Word('save'), new Word('saves'));
|
||||||
yield new Substitution(new Word('sex'), new Word('sexes'));
|
yield new Substitution(new Word('sex'), new Word('sexes'));
|
||||||
yield new Substitution(new Word('sieve'), new Word('sieves'));
|
yield new Substitution(new Word('sieve'), new Word('sieves'));
|
||||||
yield new Substitution(new Word('soliloquy'), new Word('soliloquies'));
|
yield new Substitution(new Word('soliloquy'), new Word('soliloquies'));
|
||||||
yield new Substitution(new Word('son-in-law'), new Word('sons-in-law'));
|
yield new Substitution(new Word('son-in-law'), new Word('sons-in-law'));
|
||||||
|
yield new Substitution(new Word('stadium'), new Word('stadiums'));
|
||||||
yield new Substitution(new Word('syllabus'), new Word('syllabi'));
|
yield new Substitution(new Word('syllabus'), new Word('syllabi'));
|
||||||
yield new Substitution(new Word('testis'), new Word('testes'));
|
yield new Substitution(new Word('testis'), new Word('testes'));
|
||||||
yield new Substitution(new Word('thief'), new Word('thieves'));
|
yield new Substitution(new Word('thief'), new Word('thieves'));
|
||||||
@@ -164,21 +179,7 @@ class Inflectible
|
|||||||
yield new Substitution(new Word('turf'), new Word('turfs'));
|
yield new Substitution(new Word('turf'), new Word('turfs'));
|
||||||
yield new Substitution(new Word('valve'), new Word('valves'));
|
yield new Substitution(new Word('valve'), new Word('valves'));
|
||||||
yield new Substitution(new Word('volcano'), new Word('volcanoes'));
|
yield new Substitution(new Word('volcano'), new Word('volcanoes'));
|
||||||
yield new Substitution(new Word('abuse'), new Word('abuses'));
|
|
||||||
yield new Substitution(new Word('avalanche'), new Word('avalanches'));
|
|
||||||
yield new Substitution(new Word('cache'), new Word('caches'));
|
|
||||||
yield new Substitution(new Word('criterion'), new Word('criteria'));
|
|
||||||
yield new Substitution(new Word('curve'), new Word('curves'));
|
|
||||||
yield new Substitution(new Word('emphasis'), new Word('emphases'));
|
|
||||||
yield new Substitution(new Word('foe'), new Word('foes'));
|
|
||||||
yield new Substitution(new Word('grave'), new Word('graves'));
|
|
||||||
yield new Substitution(new Word('hoax'), new Word('hoaxes'));
|
|
||||||
yield new Substitution(new Word('medium'), new Word('media'));
|
|
||||||
yield new Substitution(new Word('neurosis'), new Word('neuroses'));
|
|
||||||
yield new Substitution(new Word('save'), new Word('saves'));
|
|
||||||
yield new Substitution(new Word('wave'), new Word('waves'));
|
yield new Substitution(new Word('wave'), new Word('waves'));
|
||||||
yield new Substitution(new Word('oasis'), new Word('oases'));
|
|
||||||
yield new Substitution(new Word('valve'), new Word('valves'));
|
|
||||||
yield new Substitution(new Word('zombie'), new Word('zombies'));
|
yield new Substitution(new Word('zombie'), new Word('zombies'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Doctrine\Inflector\Rules\Esperanto;
|
||||||
|
|
||||||
|
use Doctrine\Inflector\Rules\Pattern;
|
||||||
|
use Doctrine\Inflector\Rules\Substitution;
|
||||||
|
use Doctrine\Inflector\Rules\Transformation;
|
||||||
|
use Doctrine\Inflector\Rules\Word;
|
||||||
|
|
||||||
|
class Inflectible
|
||||||
|
{
|
||||||
|
/** @return Transformation[] */
|
||||||
|
public static function getSingular(): iterable
|
||||||
|
{
|
||||||
|
yield new Transformation(new Pattern('oj$'), 'o');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return Transformation[] */
|
||||||
|
public static function getPlural(): iterable
|
||||||
|
{
|
||||||
|
yield new Transformation(new Pattern('o$'), 'oj');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return Substitution[] */
|
||||||
|
public static function getIrregular(): iterable
|
||||||
|
{
|
||||||
|
yield new Substitution(new Word(''), new Word(''));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Doctrine\Inflector\Rules\Esperanto;
|
||||||
|
|
||||||
|
use Doctrine\Inflector\GenericLanguageInflectorFactory;
|
||||||
|
use Doctrine\Inflector\Rules\Ruleset;
|
||||||
|
|
||||||
|
final class InflectorFactory extends GenericLanguageInflectorFactory
|
||||||
|
{
|
||||||
|
protected function getSingularRuleset(): Ruleset
|
||||||
|
{
|
||||||
|
return Rules::getSingularRuleset();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getPluralRuleset(): Ruleset
|
||||||
|
{
|
||||||
|
return Rules::getPluralRuleset();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Doctrine\Inflector\Rules\Esperanto;
|
||||||
|
|
||||||
|
use Doctrine\Inflector\Rules\Patterns;
|
||||||
|
use Doctrine\Inflector\Rules\Ruleset;
|
||||||
|
use Doctrine\Inflector\Rules\Substitutions;
|
||||||
|
use Doctrine\Inflector\Rules\Transformations;
|
||||||
|
|
||||||
|
final class Rules
|
||||||
|
{
|
||||||
|
public static function getSingularRuleset(): Ruleset
|
||||||
|
{
|
||||||
|
return new Ruleset(
|
||||||
|
new Transformations(...Inflectible::getSingular()),
|
||||||
|
new Patterns(...Uninflected::getSingular()),
|
||||||
|
(new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPluralRuleset(): Ruleset
|
||||||
|
{
|
||||||
|
return new Ruleset(
|
||||||
|
new Transformations(...Inflectible::getPlural()),
|
||||||
|
new Patterns(...Uninflected::getPlural()),
|
||||||
|
new Substitutions(...Inflectible::getIrregular())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Doctrine\Inflector\Rules\French;
|
namespace Doctrine\Inflector\Rules\Esperanto;
|
||||||
|
|
||||||
use Doctrine\Inflector\Rules\Pattern;
|
use Doctrine\Inflector\Rules\Pattern;
|
||||||
|
|
||||||
+1
-1
@@ -16,7 +16,7 @@ class Inflectible
|
|||||||
{
|
{
|
||||||
yield new Transformation(new Pattern('/(b|cor|ém|gemm|soupir|trav|vant|vitr)aux$/'), '\1ail');
|
yield new Transformation(new Pattern('/(b|cor|ém|gemm|soupir|trav|vant|vitr)aux$/'), '\1ail');
|
||||||
yield new Transformation(new Pattern('/ails$/'), 'ail');
|
yield new Transformation(new Pattern('/ails$/'), 'ail');
|
||||||
yield new Transformation(new Pattern('/(journ|chev)aux$/'), '\1al');
|
yield new Transformation(new Pattern('/(journ|chev|loc)aux$/'), '\1al');
|
||||||
yield new Transformation(new Pattern('/(bijou|caillou|chou|genou|hibou|joujou|pou|au|eu|eau)x$/'), '\1');
|
yield new Transformation(new Pattern('/(bijou|caillou|chou|genou|hibou|joujou|pou|au|eu|eau)x$/'), '\1');
|
||||||
yield new Transformation(new Pattern('/s$/'), '');
|
yield new Transformation(new Pattern('/s$/'), '');
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Doctrine\Inflector\Rules\French;
|
||||||
|
|
||||||
|
use Doctrine\Inflector\Rules\Pattern;
|
||||||
|
|
||||||
|
final class Uninflected
|
||||||
|
{
|
||||||
|
/** @return Pattern[] */
|
||||||
|
public static function getSingular(): iterable
|
||||||
|
{
|
||||||
|
yield from self::getDefault();
|
||||||
|
|
||||||
|
yield new Pattern('bois');
|
||||||
|
yield new Pattern('mas');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return Pattern[] */
|
||||||
|
public static function getPlural(): iterable
|
||||||
|
{
|
||||||
|
yield from self::getDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return Pattern[] */
|
||||||
|
private static function getDefault(): iterable
|
||||||
|
{
|
||||||
|
yield new Pattern('');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Doctrine\Inflector\Rules\Italian;
|
||||||
|
|
||||||
|
use Doctrine\Inflector\Rules\Pattern;
|
||||||
|
use Doctrine\Inflector\Rules\Substitution;
|
||||||
|
use Doctrine\Inflector\Rules\Transformation;
|
||||||
|
use Doctrine\Inflector\Rules\Word;
|
||||||
|
|
||||||
|
class Inflectible
|
||||||
|
{
|
||||||
|
/** @return iterable<Transformation> */
|
||||||
|
public static function getSingular(): iterable
|
||||||
|
{
|
||||||
|
// Reverse of -sce → -scia (fasce → fascia)
|
||||||
|
yield new Transformation(new Pattern('([aeiou])sce$'), '\\1scia');
|
||||||
|
|
||||||
|
// Reverse of -cie → -cia (farmacia → farmacie)
|
||||||
|
yield new Transformation(new Pattern('cie$'), 'cia');
|
||||||
|
|
||||||
|
// Reverse of -gie → -gia (bugia → bugie)
|
||||||
|
yield new Transformation(new Pattern('gie$'), 'gia');
|
||||||
|
|
||||||
|
// Reverse of -ce → -cia (arance → arancia)
|
||||||
|
yield new Transformation(new Pattern('([^aeiou])ce$'), '\1cia');
|
||||||
|
|
||||||
|
// Reverse of -ge → -gia (valige → valigia)
|
||||||
|
yield new Transformation(new Pattern('([^aeiou])ge$'), '\1gia');
|
||||||
|
|
||||||
|
// Reverse of -chi → -co (bachi → baco)
|
||||||
|
yield new Transformation(new Pattern('([bcdfghjklmnpqrstvwxyz][aeiou])chi$'), '\1co');
|
||||||
|
|
||||||
|
// Reverse of -ghi → -go (laghi → lago)
|
||||||
|
yield new Transformation(new Pattern('([bcdfghjklmnpqrstvwxyz][aeiou])ghi$'), '\1go');
|
||||||
|
|
||||||
|
// Reverse of -ci → -co (medici → medico)
|
||||||
|
yield new Transformation(new Pattern('([aeiou][bcdfghjklmnpqrstvwxyz])ci$'), '\1co');
|
||||||
|
|
||||||
|
// Reverse of -gi → -go (psicologi → psicologo)
|
||||||
|
yield new Transformation(new Pattern('([aeiou][bcdfghjklmnpqrstvwxyz])gi$'), '\1go');
|
||||||
|
|
||||||
|
// Reverse of -i → -io (zii → zio, negozi → negozio)
|
||||||
|
// This is more complex due to Italian's stress patterns, but we'll handle the basic case
|
||||||
|
yield new Transformation(new Pattern('([^aeiou])i$'), '\1io');
|
||||||
|
|
||||||
|
// Handle words that end with -i but should go to -co/-go (amici → amico, not amice)
|
||||||
|
yield new Transformation(new Pattern('([^aeiou])ci$'), '\1co');
|
||||||
|
yield new Transformation(new Pattern('([^aeiou])gi$'), '\1go');
|
||||||
|
|
||||||
|
// Reverse of -a → -e
|
||||||
|
yield new Transformation(new Pattern('e$'), 'a');
|
||||||
|
|
||||||
|
// Reverse of -e → -i
|
||||||
|
yield new Transformation(new Pattern('i$'), 'e');
|
||||||
|
|
||||||
|
// Reverse of -o → -i
|
||||||
|
yield new Transformation(new Pattern('i$'), 'o');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return iterable<Transformation> */
|
||||||
|
public static function getPlural(): iterable
|
||||||
|
{
|
||||||
|
// Words ending in -scia without stress on 'i' become -sce (e.g. fascia → fasce)
|
||||||
|
yield new Transformation(new Pattern('([aeiou])scia$'), '\\1sce');
|
||||||
|
|
||||||
|
// Words ending in -cia/gia with stress on 'i' keep the 'i' in plural
|
||||||
|
yield new Transformation(new Pattern('cia$'), 'cie'); // e.g. farmacia → farmacie
|
||||||
|
yield new Transformation(new Pattern('gia$'), 'gie'); // e.g. bugia → bugie
|
||||||
|
|
||||||
|
// Words ending in -cia/gia without stress on 'i' lose the 'i' in plural
|
||||||
|
yield new Transformation(new Pattern('([^aeiou])cia$'), '\\1ce'); // e.g. arancia → arance
|
||||||
|
yield new Transformation(new Pattern('([^aeiou])gia$'), '\\1ge'); // e.g. valigia → valige
|
||||||
|
|
||||||
|
// Words ending in -co/-go with stress on 'o' become -chi/-ghi
|
||||||
|
yield new Transformation(new Pattern('([bcdfghjklmnpqrstvwxyz][aeiou])co$'), '\\1chi'); // e.g. baco → bachi
|
||||||
|
yield new Transformation(new Pattern('([bcdfghjklmnpqrstvwxyz][aeiou])go$'), '\\1ghi'); // e.g. lago → laghi
|
||||||
|
|
||||||
|
// Words ending in -co/-go with stress on the penultimate syllable become -ci/-gi
|
||||||
|
yield new Transformation(new Pattern('([aeiou][bcdfghjklmnpqrstvwxyz])co$'), '\\1ci'); // e.g. medico → medici
|
||||||
|
yield new Transformation(new Pattern('([aeiou][bcdfghjklmnpqrstvwxyz])go$'), '\\1gi'); // e.g. psicologo → psicologi
|
||||||
|
|
||||||
|
// Words ending in -io with stress on 'i' keep the 'i' in plural
|
||||||
|
yield new Transformation(new Pattern('([^aeiou])io$'), '\\1i'); // e.g. zio → zii
|
||||||
|
|
||||||
|
// Words ending in -io with stress on 'o' lose the 'i' in plural
|
||||||
|
yield new Transformation(new Pattern('([aeiou])io$'), '\\1i'); // e.g. negozio → negozi
|
||||||
|
|
||||||
|
// Standard ending rules
|
||||||
|
yield new Transformation(new Pattern('a$'), 'e'); // -a → -e
|
||||||
|
yield new Transformation(new Pattern('e$'), 'i'); // -e → -i
|
||||||
|
yield new Transformation(new Pattern('o$'), 'i'); // -o → -i
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return iterable<Substitution> */
|
||||||
|
public static function getIrregular(): iterable
|
||||||
|
{
|
||||||
|
// Irregular substitutions (singular => plural)
|
||||||
|
$irregulars = [
|
||||||
|
'ala' => 'ali',
|
||||||
|
'albergo' => 'alberghi',
|
||||||
|
'amica' => 'amiche',
|
||||||
|
'amico' => 'amici',
|
||||||
|
'ampio' => 'ampi',
|
||||||
|
'arancia' => 'arance',
|
||||||
|
'arma' => 'armi',
|
||||||
|
'asparago' => 'asparagi',
|
||||||
|
'banca' => 'banche',
|
||||||
|
'belga' => 'belgi',
|
||||||
|
'braccio' => 'braccia',
|
||||||
|
'budello' => 'budella',
|
||||||
|
'bue' => 'buoi',
|
||||||
|
'caccia' => 'cacce',
|
||||||
|
'calcagno' => 'calcagna',
|
||||||
|
'camicia' => 'camicie',
|
||||||
|
'cane' => 'cani',
|
||||||
|
'capitale' => 'capitali',
|
||||||
|
'carcere' => 'carceri',
|
||||||
|
'casa' => 'case',
|
||||||
|
'cavaliere' => 'cavalieri',
|
||||||
|
'centinaio' => 'centinaia',
|
||||||
|
'cerchio' => 'cerchia',
|
||||||
|
'cervello' => 'cervella',
|
||||||
|
'chiave' => 'chiavi',
|
||||||
|
'chirurgo' => 'chirurgi',
|
||||||
|
'ciglio' => 'ciglia',
|
||||||
|
'città' => 'città',
|
||||||
|
'corno' => 'corna',
|
||||||
|
'corpo' => 'corpi',
|
||||||
|
'crisi' => 'crisi',
|
||||||
|
'dente' => 'denti',
|
||||||
|
'dio' => 'dei',
|
||||||
|
'dito' => 'dita',
|
||||||
|
'dottore' => 'dottori',
|
||||||
|
'fiore' => 'fiori',
|
||||||
|
'fratello' => 'fratelli',
|
||||||
|
'fuoco' => 'fuochi',
|
||||||
|
'gamba' => 'gambe',
|
||||||
|
'ginocchio' => 'ginocchia',
|
||||||
|
'gioco' => 'giochi',
|
||||||
|
'giornale' => 'giornali',
|
||||||
|
'giraffa' => 'giraffe',
|
||||||
|
'labbro' => 'labbra',
|
||||||
|
'lenzuolo' => 'lenzuola',
|
||||||
|
'libro' => 'libri',
|
||||||
|
'madre' => 'madri',
|
||||||
|
'maestro' => 'maestri',
|
||||||
|
'magico' => 'magici',
|
||||||
|
'mago' => 'maghi',
|
||||||
|
'maniaco' => 'maniaci',
|
||||||
|
'manico' => 'manici',
|
||||||
|
'mano' => 'mani',
|
||||||
|
'medico' => 'medici',
|
||||||
|
'membro' => 'membri',
|
||||||
|
'metropoli' => 'metropoli',
|
||||||
|
'migliaio' => 'migliaia',
|
||||||
|
'miglio' => 'miglia',
|
||||||
|
'mille' => 'mila',
|
||||||
|
'mio' => 'miei',
|
||||||
|
'moglie' => 'mogli',
|
||||||
|
'mosaico' => 'mosaici',
|
||||||
|
'muro' => 'muri',
|
||||||
|
'nemico' => 'nemici',
|
||||||
|
'nome' => 'nomi',
|
||||||
|
'occhio' => 'occhi',
|
||||||
|
'orecchio' => 'orecchi',
|
||||||
|
'osso' => 'ossa',
|
||||||
|
'paio' => 'paia',
|
||||||
|
'pane' => 'pani',
|
||||||
|
'papa' => 'papi',
|
||||||
|
'pasta' => 'paste',
|
||||||
|
'penna' => 'penne',
|
||||||
|
'pesce' => 'pesci',
|
||||||
|
'piede' => 'piedi',
|
||||||
|
'pittore' => 'pittori',
|
||||||
|
'poeta' => 'poeti',
|
||||||
|
'porco' => 'porci',
|
||||||
|
'porto' => 'porti',
|
||||||
|
'problema' => 'problemi',
|
||||||
|
'ragazzo' => 'ragazzi',
|
||||||
|
're' => 're',
|
||||||
|
'rene' => 'reni',
|
||||||
|
'riso' => 'risa',
|
||||||
|
'rosa' => 'rosa',
|
||||||
|
'sale' => 'sali',
|
||||||
|
'sarto' => 'sarti',
|
||||||
|
'scuola' => 'scuole',
|
||||||
|
'serie' => 'serie',
|
||||||
|
'serramento' => 'serramenta',
|
||||||
|
'sorella' => 'sorelle',
|
||||||
|
'specie' => 'specie',
|
||||||
|
'staio' => 'staia',
|
||||||
|
'stazione' => 'stazioni',
|
||||||
|
'strido' => 'strida',
|
||||||
|
'strillo' => 'strilla',
|
||||||
|
'studio' => 'studi',
|
||||||
|
'suo' => 'suoi',
|
||||||
|
'superficie' => 'superfici',
|
||||||
|
'tavolo' => 'tavoli',
|
||||||
|
'tempio' => 'templi',
|
||||||
|
'treno' => 'treni',
|
||||||
|
'tuo' => 'tuoi',
|
||||||
|
'uomo' => 'uomini',
|
||||||
|
'uovo' => 'uova',
|
||||||
|
'urlo' => 'urla',
|
||||||
|
'valigia' => 'valigie',
|
||||||
|
'vestigio' => 'vestigia',
|
||||||
|
'vino' => 'vini',
|
||||||
|
'viola' => 'viola',
|
||||||
|
'zio' => 'zii',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($irregulars as $singular => $plural) {
|
||||||
|
yield new Substitution(new Word($singular), new Word($plural));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Doctrine\Inflector\Rules\Italian;
|
||||||
|
|
||||||
|
use Doctrine\Inflector\GenericLanguageInflectorFactory;
|
||||||
|
use Doctrine\Inflector\Rules\Ruleset;
|
||||||
|
|
||||||
|
final class InflectorFactory extends GenericLanguageInflectorFactory
|
||||||
|
{
|
||||||
|
protected function getSingularRuleset(): Ruleset
|
||||||
|
{
|
||||||
|
return Rules::getSingularRuleset();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getPluralRuleset(): Ruleset
|
||||||
|
{
|
||||||
|
return Rules::getPluralRuleset();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Doctrine\Inflector\Rules\Italian;
|
||||||
|
|
||||||
|
use Doctrine\Inflector\Rules\Patterns;
|
||||||
|
use Doctrine\Inflector\Rules\Ruleset;
|
||||||
|
use Doctrine\Inflector\Rules\Substitutions;
|
||||||
|
use Doctrine\Inflector\Rules\Transformations;
|
||||||
|
|
||||||
|
final class Rules
|
||||||
|
{
|
||||||
|
public static function getSingularRuleset(): Ruleset
|
||||||
|
{
|
||||||
|
return new Ruleset(
|
||||||
|
new Transformations(...Inflectible::getSingular()),
|
||||||
|
new Patterns(...Uninflected::getSingular()),
|
||||||
|
(new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPluralRuleset(): Ruleset
|
||||||
|
{
|
||||||
|
return new Ruleset(
|
||||||
|
new Transformations(...Inflectible::getPlural()),
|
||||||
|
new Patterns(...Uninflected::getPlural()),
|
||||||
|
new Substitutions(...Inflectible::getIrregular())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Doctrine\Inflector\Rules\Italian;
|
||||||
|
|
||||||
|
use Doctrine\Inflector\Rules\Pattern;
|
||||||
|
|
||||||
|
final class Uninflected
|
||||||
|
{
|
||||||
|
/** @return iterable<Pattern> */
|
||||||
|
public static function getSingular(): iterable
|
||||||
|
{
|
||||||
|
yield from self::getDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return iterable<Pattern> */
|
||||||
|
public static function getPlural(): iterable
|
||||||
|
{
|
||||||
|
yield from self::getDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return iterable<Pattern> */
|
||||||
|
private static function getDefault(): iterable
|
||||||
|
{
|
||||||
|
// Invariable words (same form in singular and plural)
|
||||||
|
$invariables = [
|
||||||
|
'alpaca',
|
||||||
|
'auto',
|
||||||
|
'bar',
|
||||||
|
'blu',
|
||||||
|
'boia',
|
||||||
|
'boomerang',
|
||||||
|
'brindisi',
|
||||||
|
'campus',
|
||||||
|
'computer',
|
||||||
|
'crisi',
|
||||||
|
'crocevia',
|
||||||
|
'dopocena',
|
||||||
|
'film',
|
||||||
|
'foto',
|
||||||
|
'fuchsia',
|
||||||
|
'gnu',
|
||||||
|
'gorilla',
|
||||||
|
'gru',
|
||||||
|
'iguana',
|
||||||
|
'kamikaze',
|
||||||
|
'karaoke',
|
||||||
|
'koala',
|
||||||
|
'lama',
|
||||||
|
'menu',
|
||||||
|
'metropoli',
|
||||||
|
'moto',
|
||||||
|
'opossum',
|
||||||
|
'panda',
|
||||||
|
'quiz',
|
||||||
|
'radio',
|
||||||
|
're',
|
||||||
|
'scacciapensieri',
|
||||||
|
'serie',
|
||||||
|
'smartphone',
|
||||||
|
'sosia',
|
||||||
|
'sottoscala',
|
||||||
|
'specie',
|
||||||
|
'sport',
|
||||||
|
'tablet',
|
||||||
|
'taxi',
|
||||||
|
'vaglia',
|
||||||
|
'virtù',
|
||||||
|
'virus',
|
||||||
|
'yogurt',
|
||||||
|
'foto',
|
||||||
|
'fuchsia',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($invariables as $word) {
|
||||||
|
yield new Pattern($word);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user