From 094f60a89c135c66d236689672dd0377cf497dd2 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Tue, 29 Oct 2024 13:15:26 +0400 Subject: [PATCH 01/22] New Config. --- packages/formatter/UPGRADE.md | 2 + packages/formatter/composer.json | 1 + packages/formatter/defaults/config.php | 138 +----------------- packages/formatter/src/Config/Config.php | 132 +++++++++++++++++ packages/formatter/src/Config/Defaults.php | 22 +++ .../src/Config/Formats/CurrencyConfig.php | 24 +++ .../src/Config/Formats/CurrencyFormat.php | 28 ++++ .../src/Config/Formats/DateTimeConfig.php | 16 ++ .../src/Config/Formats/DateTimeFormat.php | 29 ++++ .../src/Config/Formats/DurationConfig.php | 16 ++ .../src/Config/Formats/DurationFormatIntl.php | 28 ++++ .../Config/Formats/DurationFormatPattern.php | 17 +++ .../src/Config/Formats/FilesizeConfig.php | 16 ++ .../src/Config/Formats/FilesizeFormat.php | 36 +++++ .../src/Config/Formats/NumberConfig.php | 24 +++ .../src/Config/Formats/NumberFormat.php | 32 ++++ .../src/Config/Formats/SecretConfig.php | 16 ++ .../src/Config/Formats/SecretFormat.php | 18 +++ packages/formatter/src/Config/IntlOptions.php | 31 ++++ packages/formatter/src/Config/Locale.php | 24 +++ packages/formatter/src/PackageConfig.php | 22 +++ packages/formatter/src/Provider.php | 2 +- packages/formatter/src/ProviderTest.php | 28 ++++ .../src/ProviderTest~LegacyConfig.php | 56 +++++++ 24 files changed, 623 insertions(+), 135 deletions(-) create mode 100644 packages/formatter/src/Config/Config.php create mode 100644 packages/formatter/src/Config/Defaults.php create mode 100644 packages/formatter/src/Config/Formats/CurrencyConfig.php create mode 100644 packages/formatter/src/Config/Formats/CurrencyFormat.php create mode 100644 packages/formatter/src/Config/Formats/DateTimeConfig.php create mode 100644 packages/formatter/src/Config/Formats/DateTimeFormat.php create mode 100644 packages/formatter/src/Config/Formats/DurationConfig.php create mode 100644 packages/formatter/src/Config/Formats/DurationFormatIntl.php create mode 100644 packages/formatter/src/Config/Formats/DurationFormatPattern.php create mode 100644 packages/formatter/src/Config/Formats/FilesizeConfig.php create mode 100644 packages/formatter/src/Config/Formats/FilesizeFormat.php create mode 100644 packages/formatter/src/Config/Formats/NumberConfig.php create mode 100644 packages/formatter/src/Config/Formats/NumberFormat.php create mode 100644 packages/formatter/src/Config/Formats/SecretConfig.php create mode 100644 packages/formatter/src/Config/Formats/SecretFormat.php create mode 100644 packages/formatter/src/Config/IntlOptions.php create mode 100644 packages/formatter/src/Config/Locale.php create mode 100644 packages/formatter/src/PackageConfig.php create mode 100644 packages/formatter/src/ProviderTest~LegacyConfig.php diff --git a/packages/formatter/UPGRADE.md b/packages/formatter/UPGRADE.md index dd0768110..d4cffb857 100644 --- a/packages/formatter/UPGRADE.md +++ b/packages/formatter/UPGRADE.md @@ -39,6 +39,8 @@ Please also see [changelog](https://github.com/LastDragon-ru/lara-asp/releases) [//]: # (end: preprocess/9679e76379216855) +* [ ] Array-based config is not supported anymore. Please migrate to object-based config. + # Upgrade from v5 [include:file]: ../../docs/Shared/Upgrade/FromV5.md diff --git a/packages/formatter/composer.json b/packages/formatter/composer.json index 825ffc5c7..27d8e7f92 100644 --- a/packages/formatter/composer.json +++ b/packages/formatter/composer.json @@ -28,6 +28,7 @@ "symfony/polyfill-php83": "^1.28" }, "require-dev": { + "illuminate/contracts": "^10.34.0|^11.0.0", "lastdragon-ru/lara-asp-testing": "self.version", "orchestra/testbench": "^8.0.0|^9.0.0", "phpunit/phpunit": "^10.1.0|^11.0.0" diff --git a/packages/formatter/defaults/config.php b/packages/formatter/defaults/config.php index 36216dbdc..37062d7f2 100644 --- a/packages/formatter/defaults/config.php +++ b/packages/formatter/defaults/config.php @@ -1,139 +1,9 @@ [ - /** - * This value has no effect inside the published config. - */ - ConfigMerger::Strict => false, +$config = PackageConfig::getDefaultConfig(); - /** - * Fraction digits for {@link Formatter::decimal()} - */ - // Formatter::Decimal => 2, +// ... - /** - * Default custom time format name, you can also use - * - {@link IntlDateFormatter::SHORT} (default) - * - {@link IntlDateFormatter::FULL} - * - {@link IntlDateFormatter::LONG} - * - {@link IntlDateFormatter::MEDIUM} - */ - // Formatter::Time => 'custom', - - /** - * Default custom duration format name, you can also use - * {@link NumberFormatter::DURATION} for built-in Intl format. - */ - // Formatter::Duration => 'custom', - - /** - * Global Attributes for {@link NumberFormatter::setAttribute} - */ - Formatter::IntlAttributes => [ - NumberFormatter::ROUNDING_MODE => NumberFormatter::ROUND_HALFUP, - ], - - /** - * Global Symbols for {@link NumberFormatter::setSymbol} - */ - // Formatter::IntlSymbols => [ - // // ... - // ], - - /** - * Global Attributes for {@link NumberFormatter::setTextAttribute} - */ - // Formatter::IntlTextAttributes => [ - // // ... - // ], - ], - - /** - * Settings for all locales - * --------------------------------------------------------------------- - * You can define a custom pattern for all locales here. - * - * For date/time please use ICU, see - * https://unicode-org.github.io/icu/userguide/format_parse/datetime/#formatting-dates-and-times - */ - 'all' => [ - /** - * This value has no effect inside the published config. - */ - ConfigMerger::Strict => false, - - /** - * Custom time format for all locales - */ - // Formatter::Time => [ - // 'custom' => 'HH:mm:ss.SSS', - // ], - - /** - * Custom duration format for all locales - * - * @see DurationFormatter - */ - // Formatter::Duration => [ - // 'custom' => 'HH:mm:ss.SSS', - // ], - - /** - * Intl properties for all locales (will be merged with `options`) - */ - // Formatter::Decimal => [ - // Formatter::IntlSymbols => [], - // Formatter::IntlAttributes => [], - // Formatter::IntlTextAttributes => [], - // ], - ], - - /** - * Settings for concrete locales - * --------------------------------------------------------------------- - * For date/time please use ICU, see - * https://unicode-org.github.io/icu/userguide/format_parse/datetime/#formatting-dates-and-times - */ - 'locales' => [ - /** - * This value has no effect inside the published config. - */ - ConfigMerger::Strict => false, - - // 'ru_RU' => [ - // // Custom time format for specific Locale - // Formatter::Time => [ - // 'custom' => 'HH:mm:ss', - // ], - // - // // Custom duration format for specific Locale - // Formatter::Duration => [ - // 'custom' => 'HH:mm:ss', - // ], - // - // // Intl properties for specific Locale (will be merged with all`) - // Formatter::Decimal => [ - // Formatter::IntlSymbols => [], - // Formatter::IntlAttributes => [], - // Formatter::IntlTextAttributes => [], - // ], - // ], - ], -]; +return $config; diff --git a/packages/formatter/src/Config/Config.php b/packages/formatter/src/Config/Config.php new file mode 100644 index 000000000..4789ead4f --- /dev/null +++ b/packages/formatter/src/Config/Config.php @@ -0,0 +1,132 @@ + + */ + public array $locales = [], + ) { + parent::__construct(); + + $this->global->number->attributes += [ + NumberFormatter::ROUNDING_MODE => NumberFormatter::ROUND_HALFUP, + ]; + $this->global->number->formats += [ + Formatter::Integer => new NumberFormat( + style : NumberFormatter::DECIMAL, + attributes: [ + NumberFormatter::FRACTION_DIGITS => 0, + ], + ), + Formatter::Decimal => new NumberFormat( + style : NumberFormatter::DECIMAL, + attributes: [ + NumberFormatter::FRACTION_DIGITS => 2, + ], + ), + Formatter::Scientific => new NumberFormat( + style: NumberFormatter::SCIENTIFIC, + ), + Formatter::Spellout => new NumberFormat( + style: NumberFormatter::SPELLOUT, + ), + Formatter::Ordinal => new NumberFormat( + style: NumberFormatter::ORDINAL, + ), + Formatter::Percent => new NumberFormat( + style : NumberFormatter::PERCENT, + attributes: [ + NumberFormatter::FRACTION_DIGITS => 0, + ], + ), + ]; + $this->global->datetime->formats += [ + Formatter::Time => new DateTimeFormat( + dateType: IntlDateFormatter::NONE, + timeType: IntlDateFormatter::SHORT, + ), + Formatter::Date => new DateTimeFormat( + dateType: IntlDateFormatter::SHORT, + timeType: IntlDateFormatter::NONE, + ), + Formatter::DateTime => new DateTimeFormat( + dateType: IntlDateFormatter::SHORT, + timeType: IntlDateFormatter::SHORT, + ), + ]; + $this->global->duration->formats += [ + Formatter::Default => new DurationFormatPattern('HH:mm:ss.SSS'), + ]; + $this->global->secret->formats += [ + Formatter::Default => new SecretFormat(5), + ]; + $this->global->filesize->formats += [ + Formatter::Disksize => new FileSizeFormat( + base : 1000, + units: [ + ['disksize.B', 'B'], + ['disksize.kB', 'kB'], + ['disksize.MB', 'MB'], + ['disksize.GB', 'GB'], + ['disksize.TB', 'TB'], + ['disksize.PB', 'PB'], + ['disksize.EB', 'EB'], + ['disksize.ZB', 'ZB'], + ['disksize.YB', 'YB'], + ['disksize.RB', 'RB'], + ['disksize.QB', 'QB'], + ], + ), + Formatter::Filesize => new FileSizeFormat( + base : 1024, + units: [ + ['filesize.B', 'B'], + ['filesize.KiB', 'KiB'], + ['filesize.MiB', 'MiB'], + ['filesize.GiB', 'GiB'], + ['filesize.TiB', 'TiB'], + ['filesize.PiB', 'PiB'], + ['filesize.EiB', 'EiB'], + ['filesize.ZiB', 'ZiB'], + ['filesize.YiB', 'YiB'], + ['filesize.RiB', 'RiB'], + ['filesize.QiB', 'QiB'], + ], + ), + ]; + } + + /** + * @deprecated %{VERSION} Array-based config is deprecated. Please migrate to object-based config. + * @inheritDoc + */ + #[Override] + public static function fromArray(array $array): static { + throw new Exception('Array-based config is not supported anymore. Please migrate to object-based config.'); + } +} diff --git a/packages/formatter/src/Config/Defaults.php b/packages/formatter/src/Config/Defaults.php new file mode 100644 index 000000000..46f2c8a43 --- /dev/null +++ b/packages/formatter/src/Config/Defaults.php @@ -0,0 +1,22 @@ + $symbols + * @param array $attributes + * @param array $textAttributes + */ + public function __construct( + /** + * @var array + */ + public array $formats = [], + array $symbols = [], + array $attributes = [], + array $textAttributes = [], + ) { + parent::__construct($symbols, $attributes, $textAttributes); + } +} diff --git a/packages/formatter/src/Config/Formats/CurrencyFormat.php b/packages/formatter/src/Config/Formats/CurrencyFormat.php new file mode 100644 index 000000000..e6dba4c5a --- /dev/null +++ b/packages/formatter/src/Config/Formats/CurrencyFormat.php @@ -0,0 +1,28 @@ + $symbols + * @param array $attributes + * @param array $textAttributes + */ + public function __construct( + /** + * @see NumberFormatter::setPattern() + */ + public ?string $pattern = null, + array $symbols = [], + array $attributes = [], + array $textAttributes = [], + ) { + parent::__construct($symbols, $attributes, $textAttributes); + } +} diff --git a/packages/formatter/src/Config/Formats/DateTimeConfig.php b/packages/formatter/src/Config/Formats/DateTimeConfig.php new file mode 100644 index 000000000..ab0133b50 --- /dev/null +++ b/packages/formatter/src/Config/Formats/DateTimeConfig.php @@ -0,0 +1,16 @@ + + */ + public array $formats = [], + ) { + parent::__construct(); + } +} diff --git a/packages/formatter/src/Config/Formats/DateTimeFormat.php b/packages/formatter/src/Config/Formats/DateTimeFormat.php new file mode 100644 index 000000000..1ea9670a2 --- /dev/null +++ b/packages/formatter/src/Config/Formats/DateTimeFormat.php @@ -0,0 +1,29 @@ + + */ + public array $formats = [], + ) { + parent::__construct(); + } +} diff --git a/packages/formatter/src/Config/Formats/DurationFormatIntl.php b/packages/formatter/src/Config/Formats/DurationFormatIntl.php new file mode 100644 index 000000000..6d651d21e --- /dev/null +++ b/packages/formatter/src/Config/Formats/DurationFormatIntl.php @@ -0,0 +1,28 @@ + $symbols + * @param array $attributes + * @param array $textAttributes + */ + public function __construct( + /** + * @see NumberFormatter::setPattern() + */ + public ?string $pattern = null, + array $symbols = [], + array $attributes = [], + array $textAttributes = [], + ) { + parent::__construct($symbols, $attributes, $textAttributes); + } +} diff --git a/packages/formatter/src/Config/Formats/DurationFormatPattern.php b/packages/formatter/src/Config/Formats/DurationFormatPattern.php new file mode 100644 index 000000000..8c984479f --- /dev/null +++ b/packages/formatter/src/Config/Formats/DurationFormatPattern.php @@ -0,0 +1,17 @@ + + */ + public array $formats = [], + ) { + parent::__construct(); + } +} diff --git a/packages/formatter/src/Config/Formats/FilesizeFormat.php b/packages/formatter/src/Config/Formats/FilesizeFormat.php new file mode 100644 index 000000000..c9ed957f6 --- /dev/null +++ b/packages/formatter/src/Config/Formats/FilesizeFormat.php @@ -0,0 +1,36 @@ +`). + * The last string will be used as a default value if no translation. + * + * @var non-empty-list>|null + */ + public ?array $units = null, + /** + * The number format name that will be used if the display value is + * integer (no fraction part). Default is {@see Formatter::Integer}. + * + * @see Formatter::Integer + */ + public ?string $integerFormat = null, + /** + * The number format name that will be used if the display value is + * float. Default is {@see Formatter::Decimal}. + * + * @see Formatter::Decimal + */ + public ?string $decimalFormat = null, + ) { + parent::__construct(); + } +} diff --git a/packages/formatter/src/Config/Formats/NumberConfig.php b/packages/formatter/src/Config/Formats/NumberConfig.php new file mode 100644 index 000000000..b35b1fdbd --- /dev/null +++ b/packages/formatter/src/Config/Formats/NumberConfig.php @@ -0,0 +1,24 @@ + $symbols + * @param array $attributes + * @param array $textAttributes + */ + public function __construct( + /** + * @var array + */ + public array $formats = [], + array $symbols = [], + array $attributes = [], + array $textAttributes = [], + ) { + parent::__construct($symbols, $attributes, $textAttributes); + } +} diff --git a/packages/formatter/src/Config/Formats/NumberFormat.php b/packages/formatter/src/Config/Formats/NumberFormat.php new file mode 100644 index 000000000..4b0dd1ce5 --- /dev/null +++ b/packages/formatter/src/Config/Formats/NumberFormat.php @@ -0,0 +1,32 @@ + $symbols + * @param array $attributes + * @param array $textAttributes + */ + public function __construct( + /** + * @var NumberFormatter::* + */ + public ?int $style = null, + /** + * @see NumberFormatter::setPattern() + */ + public ?string $pattern = null, + array $symbols = [], + array $attributes = [], + array $textAttributes = [], + ) { + parent::__construct($symbols, $attributes, $textAttributes); + } +} diff --git a/packages/formatter/src/Config/Formats/SecretConfig.php b/packages/formatter/src/Config/Formats/SecretConfig.php new file mode 100644 index 000000000..404e385c3 --- /dev/null +++ b/packages/formatter/src/Config/Formats/SecretConfig.php @@ -0,0 +1,16 @@ + + */ + public array $formats = [], + ) { + parent::__construct(); + } +} diff --git a/packages/formatter/src/Config/Formats/SecretFormat.php b/packages/formatter/src/Config/Formats/SecretFormat.php new file mode 100644 index 000000000..b852ce784 --- /dev/null +++ b/packages/formatter/src/Config/Formats/SecretFormat.php @@ -0,0 +1,18 @@ + + */ + public int $visible, + ) { + parent::__construct(); + } +} diff --git a/packages/formatter/src/Config/IntlOptions.php b/packages/formatter/src/Config/IntlOptions.php new file mode 100644 index 000000000..e960732c5 --- /dev/null +++ b/packages/formatter/src/Config/IntlOptions.php @@ -0,0 +1,31 @@ + + */ + public array $symbols = [], + /** + * @see NumberFormatter::setAttribute() + * + * @var array + */ + public array $attributes = [], + /** + * @see NumberFormatter::setTextAttribute() + * + * @var array + */ + public array $textAttributes = [], + ) { + parent::__construct(); + } +} diff --git a/packages/formatter/src/Config/Locale.php b/packages/formatter/src/Config/Locale.php new file mode 100644 index 000000000..7a7e95b6a --- /dev/null +++ b/packages/formatter/src/Config/Locale.php @@ -0,0 +1,24 @@ + + */ +class PackageConfig extends ConfigurationResolver { + #[Override] + protected static function getName(): string { + return Package::Name; + } + + #[Override] + public static function getDefaultConfig(): Config { + return new Config(); + } +} diff --git a/packages/formatter/src/Provider.php b/packages/formatter/src/Provider.php index d6412c806..51e38c581 100644 --- a/packages/formatter/src/Provider.php +++ b/packages/formatter/src/Provider.php @@ -15,11 +15,11 @@ class Provider extends ServiceProvider { public function register(): void { parent::register(); + $this->registerConfig(PackageConfig::class); $this->app->scopedIf(Formatter::class); } public function boot(): void { - $this->bootConfig(); $this->bootTranslations(); } diff --git a/packages/formatter/src/ProviderTest.php b/packages/formatter/src/ProviderTest.php index dc06cf5c6..e691affd8 100644 --- a/packages/formatter/src/ProviderTest.php +++ b/packages/formatter/src/ProviderTest.php @@ -2,6 +2,8 @@ namespace LastDragon_ru\LaraASP\Formatter; +use Exception; +use Illuminate\Contracts\Config\Repository; use LastDragon_ru\LaraASP\Formatter\Testing\Package\TestCase; use PHPUnit\Framework\Attributes\CoversClass; @@ -16,4 +18,30 @@ public function testRegister(): void { $this->app()->make(Formatter::class), ); } + + public function testConfig(): void { + self::assertConfigurationExportable(PackageConfig::class); + } + + /** + * @deprecated %{VERSION} Array-base config is deprecated. + */ + public function testLegacyConfig(): void { + // Prepare + $app = $this->app(); + $config = $app->make(Repository::class); + $legacy = (array) require self::getTestData()->path('~LegacyConfig.php'); + $package = Package::Name; + + $config->set($package, $legacy); + + self::assertIsArray($config->get($package)); + + self::expectException(Exception::class); + self::expectExceptionMessage( + 'Array-based config is not supported anymore. Please migrate to object-based config.', + ); + + (new Provider($app))->register(); + } } diff --git a/packages/formatter/src/ProviderTest~LegacyConfig.php b/packages/formatter/src/ProviderTest~LegacyConfig.php new file mode 100644 index 000000000..ae1425912 --- /dev/null +++ b/packages/formatter/src/ProviderTest~LegacyConfig.php @@ -0,0 +1,56 @@ + [ + /** + * This value has no effect inside the published config. + */ + ConfigMerger::Strict => false, + ], + + /** + * Settings for all locales + * --------------------------------------------------------------------- + * You can define a custom pattern for all locales here. + * + * For date/time please use ICU, see + * https://unicode-org.github.io/icu/userguide/format_parse/datetime/#formatting-dates-and-times + */ + 'all' => [ + /** + * This value has no effect inside the published config. + */ + ConfigMerger::Strict => false, + ], + + /** + * Settings for concrete locales + * --------------------------------------------------------------------- + * For date/time please use ICU, see + * https://unicode-org.github.io/icu/userguide/format_parse/datetime/#formatting-dates-and-times + */ + 'locales' => [ + /** + * This value has no effect inside the published config. + */ + ConfigMerger::Strict => false, + 'ru_RU' => [ + // ... + ], + ], +]; From 2c336caf03f9912e31f0e33da01870ee620a4ed2 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Tue, 5 Nov 2024 10:22:51 +0400 Subject: [PATCH 02/22] `\LastDragon_ru\LaraASP\Formatter\Formatter` rework. --- packages/formatter/docs/Examples/Config.php | 4 +- .../FailedToCreateCurrencyFormatter.php | 32 + .../FailedToCreateDateFormatter.php | 32 - .../FailedToCreateDateTimeFormatter.php | 26 + .../FailedToCreateDurationFormatter.php | 26 + .../FailedToCreateFilesizeFormatter.php | 26 + .../Exceptions/FailedToCreateFormatter.php | 9 + .../FailedToCreateNumberFormatter.php | 22 +- .../FailedToCreateSecretFormatter.php | 26 + .../src/Exceptions/FailedToFormatCurrency.php | 38 + .../src/Exceptions/FailedToFormatDateTime.php | 32 + .../src/Exceptions/FailedToFormatDuration.php | 32 + .../src/Exceptions/FailedToFormatNumber.php | 32 + .../src/Exceptions/FailedToFormatValue.php | 29 +- packages/formatter/src/Formatter.php | 854 +++++++----------- packages/formatter/src/FormatterTest.php | 183 ++-- 16 files changed, 726 insertions(+), 677 deletions(-) create mode 100644 packages/formatter/src/Exceptions/FailedToCreateCurrencyFormatter.php delete mode 100644 packages/formatter/src/Exceptions/FailedToCreateDateFormatter.php create mode 100644 packages/formatter/src/Exceptions/FailedToCreateDateTimeFormatter.php create mode 100644 packages/formatter/src/Exceptions/FailedToCreateDurationFormatter.php create mode 100644 packages/formatter/src/Exceptions/FailedToCreateFilesizeFormatter.php create mode 100644 packages/formatter/src/Exceptions/FailedToCreateFormatter.php create mode 100644 packages/formatter/src/Exceptions/FailedToCreateSecretFormatter.php create mode 100644 packages/formatter/src/Exceptions/FailedToFormatCurrency.php create mode 100644 packages/formatter/src/Exceptions/FailedToFormatDateTime.php create mode 100644 packages/formatter/src/Exceptions/FailedToFormatDuration.php create mode 100644 packages/formatter/src/Exceptions/FailedToFormatNumber.php diff --git a/packages/formatter/docs/Examples/Config.php b/packages/formatter/docs/Examples/Config.php index c98299cf3..97b2faf3e 100644 --- a/packages/formatter/docs/Examples/Config.php +++ b/packages/formatter/docs/Examples/Config.php @@ -29,6 +29,6 @@ $locale = $default->forLocale('ru_RU'); Example::dump($default->date($datetime)); -Example::dump($default->date($datetime, 'custom')); +Example::dump($default->date($datetime)); +Example::dump($locale->date($datetime)); Example::dump($locale->date($datetime)); -Example::dump($locale->date($datetime, 'custom')); diff --git a/packages/formatter/src/Exceptions/FailedToCreateCurrencyFormatter.php b/packages/formatter/src/Exceptions/FailedToCreateCurrencyFormatter.php new file mode 100644 index 000000000..3c53ccd1b --- /dev/null +++ b/packages/formatter/src/Exceptions/FailedToCreateCurrencyFormatter.php @@ -0,0 +1,32 @@ +getCurrency(), + $this->getFormat(), + ), + $previous, + ); + } + + public function getCurrency(): string { + return $this->currency; + } + + public function getFormat(): string { + return $this->format; + } +} diff --git a/packages/formatter/src/Exceptions/FailedToCreateDateFormatter.php b/packages/formatter/src/Exceptions/FailedToCreateDateFormatter.php deleted file mode 100644 index cba754565..000000000 --- a/packages/formatter/src/Exceptions/FailedToCreateDateFormatter.php +++ /dev/null @@ -1,32 +0,0 @@ -getType(), - $this->getFormat() ?? 'null', - ), $previous); - } - - public function getType(): string { - return $this->type; - } - - public function getFormat(): string|int|null { - return $this->format; - } -} diff --git a/packages/formatter/src/Exceptions/FailedToCreateDateTimeFormatter.php b/packages/formatter/src/Exceptions/FailedToCreateDateTimeFormatter.php new file mode 100644 index 000000000..05c9c16bb --- /dev/null +++ b/packages/formatter/src/Exceptions/FailedToCreateDateTimeFormatter.php @@ -0,0 +1,26 @@ +getFormat(), + ), + $previous, + ); + } + + public function getFormat(): string { + return $this->format; + } +} diff --git a/packages/formatter/src/Exceptions/FailedToCreateDurationFormatter.php b/packages/formatter/src/Exceptions/FailedToCreateDurationFormatter.php new file mode 100644 index 000000000..3d1da15bd --- /dev/null +++ b/packages/formatter/src/Exceptions/FailedToCreateDurationFormatter.php @@ -0,0 +1,26 @@ +getFormat(), + ), + $previous, + ); + } + + public function getFormat(): string { + return $this->format; + } +} diff --git a/packages/formatter/src/Exceptions/FailedToCreateFilesizeFormatter.php b/packages/formatter/src/Exceptions/FailedToCreateFilesizeFormatter.php new file mode 100644 index 000000000..2d4eb8ec4 --- /dev/null +++ b/packages/formatter/src/Exceptions/FailedToCreateFilesizeFormatter.php @@ -0,0 +1,26 @@ +getFormat(), + ), + $previous, + ); + } + + public function getFormat(): string { + return $this->format; + } +} diff --git a/packages/formatter/src/Exceptions/FailedToCreateFormatter.php b/packages/formatter/src/Exceptions/FailedToCreateFormatter.php new file mode 100644 index 000000000..7b8c20a47 --- /dev/null +++ b/packages/formatter/src/Exceptions/FailedToCreateFormatter.php @@ -0,0 +1,9 @@ +getType(), - ), $previous); + parent::__construct( + sprintf( + 'Failed to create Number Formatter for `%s` format.', + $this->getFormat(), + ), + $previous, + ); } - public function getType(): string { - return $this->type; + public function getFormat(): string { + return $this->format; } } diff --git a/packages/formatter/src/Exceptions/FailedToCreateSecretFormatter.php b/packages/formatter/src/Exceptions/FailedToCreateSecretFormatter.php new file mode 100644 index 000000000..ea5bf8fbd --- /dev/null +++ b/packages/formatter/src/Exceptions/FailedToCreateSecretFormatter.php @@ -0,0 +1,26 @@ +getFormat(), + ), + $previous, + ); + } + + public function getFormat(): string { + return $this->format; + } +} diff --git a/packages/formatter/src/Exceptions/FailedToFormatCurrency.php b/packages/formatter/src/Exceptions/FailedToFormatCurrency.php new file mode 100644 index 000000000..4ef171e4b --- /dev/null +++ b/packages/formatter/src/Exceptions/FailedToFormatCurrency.php @@ -0,0 +1,38 @@ +getCurrency(), + $this->getFormat(), + $this->getIntlErrorMessage(), + $this->getIntlErrorCode(), + ), + $intlErrorCode, + $intlErrorMessage, + $previous, + ); + } + + public function getCurrency(): string { + return $this->currency; + } + + public function getFormat(): string { + return $this->format; + } +} diff --git a/packages/formatter/src/Exceptions/FailedToFormatDateTime.php b/packages/formatter/src/Exceptions/FailedToFormatDateTime.php new file mode 100644 index 000000000..8f76c2d4d --- /dev/null +++ b/packages/formatter/src/Exceptions/FailedToFormatDateTime.php @@ -0,0 +1,32 @@ +getFormat(), + $this->getIntlErrorMessage(), + $this->getIntlErrorCode(), + ), + $intlErrorCode, + $intlErrorMessage, + $previous, + ); + } + + public function getFormat(): string { + return $this->format; + } +} diff --git a/packages/formatter/src/Exceptions/FailedToFormatDuration.php b/packages/formatter/src/Exceptions/FailedToFormatDuration.php new file mode 100644 index 000000000..f28d159bb --- /dev/null +++ b/packages/formatter/src/Exceptions/FailedToFormatDuration.php @@ -0,0 +1,32 @@ +getFormat(), + $this->getIntlErrorMessage(), + $this->getIntlErrorCode(), + ), + $intlErrorCode, + $intlErrorMessage, + $previous, + ); + } + + public function getFormat(): string { + return $this->format; + } +} diff --git a/packages/formatter/src/Exceptions/FailedToFormatNumber.php b/packages/formatter/src/Exceptions/FailedToFormatNumber.php new file mode 100644 index 000000000..8c639a953 --- /dev/null +++ b/packages/formatter/src/Exceptions/FailedToFormatNumber.php @@ -0,0 +1,32 @@ +getFormat(), + $this->getIntlErrorMessage(), + $this->getIntlErrorCode(), + ), + $intlErrorCode, + $intlErrorMessage, + $previous, + ); + } + + public function getFormat(): string { + return $this->format; + } +} diff --git a/packages/formatter/src/Exceptions/FailedToFormatValue.php b/packages/formatter/src/Exceptions/FailedToFormatValue.php index 4f6653511..0d05046d1 100644 --- a/packages/formatter/src/Exceptions/FailedToFormatValue.php +++ b/packages/formatter/src/Exceptions/FailedToFormatValue.php @@ -5,32 +5,21 @@ use LastDragon_ru\LaraASP\Formatter\PackageException; use Throwable; -use function sprintf; - -class FailedToFormatValue extends PackageException { +abstract class FailedToFormatValue extends PackageException { public function __construct( - protected string $type, - protected int $errorCode, - protected string $errorMessage, + string $message, + protected int $intlErrorCode, + protected string $intlErrorMessage, ?Throwable $previous = null, ) { - parent::__construct(sprintf( - 'Formatting for type `%s` failed: `%s` (`%s`).', - $this->getType(), - $this->getErrorMessage(), - $this->getErrorCode(), - ), $previous); - } - - public function getType(): string { - return $this->type; + parent::__construct($message, $previous); } - public function getErrorCode(): int { - return $this->errorCode; + public function getIntlErrorCode(): int { + return $this->intlErrorCode; } - public function getErrorMessage(): string { - return $this->errorMessage; + public function getIntlErrorMessage(): string { + return $this->intlErrorMessage; } } diff --git a/packages/formatter/src/Formatter.php b/packages/formatter/src/Formatter.php index 171f04ea0..1b2400c71 100644 --- a/packages/formatter/src/Formatter.php +++ b/packages/formatter/src/Formatter.php @@ -2,32 +2,38 @@ namespace LastDragon_ru\LaraASP\Formatter; -use Closure; use DateInterval; use DateTimeInterface; use DateTimeZone; +use Exception; use Illuminate\Support\Str; use Illuminate\Support\Traits\Macroable; use IntlDateFormatter; use IntlTimeZone; use LastDragon_ru\LaraASP\Core\Application\ApplicationResolver; use LastDragon_ru\LaraASP\Core\Application\ConfigResolver; -use LastDragon_ru\LaraASP\Core\Utils\Cast; -use LastDragon_ru\LaraASP\Formatter\Exceptions\FailedToCreateDateFormatter; +use LastDragon_ru\LaraASP\Formatter\Config\Config; +use LastDragon_ru\LaraASP\Formatter\Config\Formats\DurationFormatIntl; +use LastDragon_ru\LaraASP\Formatter\Config\Formats\DurationFormatPattern; +use LastDragon_ru\LaraASP\Formatter\Config\IntlOptions; +use LastDragon_ru\LaraASP\Formatter\Exceptions\FailedToCreateCurrencyFormatter; +use LastDragon_ru\LaraASP\Formatter\Exceptions\FailedToCreateDateTimeFormatter; +use LastDragon_ru\LaraASP\Formatter\Exceptions\FailedToCreateDurationFormatter; +use LastDragon_ru\LaraASP\Formatter\Exceptions\FailedToCreateFilesizeFormatter; use LastDragon_ru\LaraASP\Formatter\Exceptions\FailedToCreateNumberFormatter; -use LastDragon_ru\LaraASP\Formatter\Exceptions\FailedToFormatValue; +use LastDragon_ru\LaraASP\Formatter\Exceptions\FailedToCreateSecretFormatter; +use LastDragon_ru\LaraASP\Formatter\Exceptions\FailedToFormatCurrency; +use LastDragon_ru\LaraASP\Formatter\Exceptions\FailedToFormatDateTime; +use LastDragon_ru\LaraASP\Formatter\Exceptions\FailedToFormatDuration; +use LastDragon_ru\LaraASP\Formatter\Exceptions\FailedToFormatNumber; use LastDragon_ru\LaraASP\Formatter\Utils\DurationFormatter; use NumberFormatter; use OutOfBoundsException; -use function abs; use function bccomp; use function bcdiv; use function is_float; -use function is_int; use function is_null; -use function is_string; -use function json_encode; use function mb_str_pad; use function mb_strlen; use function mb_substr; @@ -35,185 +41,29 @@ use function str_replace; use function trim; -use const JSON_THROW_ON_ERROR; - class Formatter { use Macroable; - /** - * @see NumberFormatter::setSymbol() - */ - final public const IntlSymbols = 'intl_symbols'; - - /** - * @see NumberFormatter::setAttribute() - */ - final public const IntlAttributes = 'intl_attributes'; - - /** - * @see NumberFormatter::setTextAttribute() - */ - final public const IntlTextAttributes = 'intl_text_attributes'; - - /** - * Options: - * - none - * - * Locales options: - * - {@link Formatter::IntlTextAttributes} - * - {@link Formatter::IntlAttributes} (except {@link NumberFormatter::FRACTION_DIGITS}) - * - {@link Formatter::IntlSymbols} - */ - public const Integer = 'integer'; - - /** - * Options: - * - none - * - * Locales options: - * - {@link Formatter::IntlTextAttributes} - * - {@link Formatter::IntlAttributes} - * - {@link Formatter::IntlSymbols} - */ + public const Default = 'default'; + public const Integer = 'integer'; public const Scientific = 'scientific'; - - /** - * Options: - * - none - * - * Locales options: - * - {@link Formatter::IntlTextAttributes} - * - {@link Formatter::IntlAttributes} - * - {@link Formatter::IntlSymbols} - */ - public const Spellout = 'spellout'; - - /** - * Options: - * - none - * - * Locales options: - * - {@link Formatter::IntlTextAttributes} - * - {@link Formatter::IntlAttributes} - * - {@link Formatter::IntlSymbols} - */ - public const Ordinal = 'ordinal'; - - /** - * Options (one of): - * - `int`: {@link NumberFormatter::DURATION} - * - `string`: the name of custom format - * - * Locales options: - * - {@link Formatter::IntlTextAttributes} - * - {@link Formatter::IntlAttributes} - * - {@link Formatter::IntlSymbols} - * - `string`: available only for custom formats: locale specific pattern - * (key: `locales..duration.` and/or default pattern - * (key: `all.duration.`) - * {@link DurationFormatter} - */ - public const Duration = 'duration'; - - /** - * Options: - * - `int`: fraction digits - * - * Locales options: - * - {@link Formatter::IntlTextAttributes} - * - {@link Formatter::IntlAttributes} (except {@link NumberFormatter::FRACTION_DIGITS}) - * - {@link Formatter::IntlSymbols} - */ - public const Decimal = 'decimal'; - - /** - * Options: - * - `string`: default currency - * - * Locales options: - * - {@link Formatter::IntlTextAttributes} - * - {@link Formatter::IntlAttributes} (except {@link NumberFormatter::FRACTION_DIGITS}) - * - {@link Formatter::IntlSymbols} - */ - public const Currency = 'currency'; - - /** - * Options: - * - `int`: fraction digits - * - * Locales options: - * - {@link Formatter::IntlTextAttributes} - * - {@link Formatter::IntlAttributes} (except {@link NumberFormatter::FRACTION_DIGITS}) - * - {@link Formatter::IntlSymbols} - */ - public const Percent = 'percent'; - - /** - * Options (one of): - * - `int`: {@link IntlDateFormatter::SHORT}, {@link IntlDateFormatter::FULL}, - * {@link IntlDateFormatter::LONG} or {@link IntlDateFormatter::MEDIUM} - * - `string`: the name of custom format - * - * Locales options: - * - `string`: available only for custom formats: locale specific pattern - * (key: `locales..time.` and/or default pattern - * (key: `all.time.`) - */ - public const Time = 'time'; - - /** - * @see Formatter::Time - */ - public const Date = 'date'; - - /** - * @see Formatter::Time - */ - public const DateTime = 'datetime'; - - /** - * Options: - * - `int`: fraction digits - * - * Locales options: - * - none - */ - public const Filesize = 'filesize'; - - /** - * Options: - * - `int`: fraction digits - * - * Locales options: - * - none - */ - public const Disksize = 'disksize'; - - /** - * Options: - * - `int`: how many characters should be shown - * - * Locales options: - * - none - */ - public const Secret = 'secret'; + public const Spellout = 'spellout'; + public const Ordinal = 'ordinal'; + public const Decimal = 'decimal'; + public const Percent = 'percent'; + public const Time = 'time'; + public const Date = 'date'; + public const DateTime = 'datetime'; + public const Filesize = 'filesize'; + public const Disksize = 'disksize'; private ?string $locale = null; private IntlTimeZone|DateTimeZone|string|null $timezone = null; - /** - * @var array - */ - private array $dateFormatters = []; - - /** - * @var array - */ - private array $numbersFormatters = []; - public function __construct( protected readonly ApplicationResolver $application, protected readonly ConfigResolver $config, + protected readonly PackageConfig $configuration, private PackageTranslator $translator, ) { // empty @@ -275,171 +125,73 @@ public function string(?string $value): string { return trim((string) $value); } - public function integer(int|float|null $value): string { - return $this->formatValue(static::Integer, $value); + public function integer(float|int|null $value): string { + return $this->formatNumber(self::Integer, $value); } - public function decimal(float|int|null $value, ?int $decimals = null): string { - return $this->formatValue(static::Decimal, $value, $decimals, function () use ($decimals): int { - return $decimals ?? Cast::toInt($this->getOptions(static::Decimal, 2)); - }); + public function decimal(float|int|null $value): string { + return $this->formatNumber(self::Decimal, $value); } - public function currency(?float $value, ?string $currency = null): string { - $type = static::Currency; - $value = (float) $value; - $currency ??= Cast::toString($this->getOptions($type, 'USD')); - $formatter = $this->getIntlNumberFormatter($type); - $formatted = $formatter->formatCurrency($value, $currency); - - if ($formatted === false) { - throw new FailedToFormatValue($type, $formatter->getErrorCode(), $formatter->getErrorMessage()); - } - - return $formatted; + public function currency(float|int|null $value): string { + return $this->formatCurrency(self::Default, $value); } /** - * @param float|null $value must be between 0-100 + * @param float|int|null $value must be between 0-100 */ - public function percent(?float $value, ?int $decimals = null): string { - return $this->formatValue(static::Percent, (float) $value / 100, $decimals, function () use ($decimals): int { - return $decimals ?? Cast::toInt($this->getOptions(static::Percent, 0)); - }); + public function percent(float|int|null $value): string { + return $this->formatNumber(self::Percent, $value !== null ? $value / 100 : $value); } - public function scientific(?float $value): string { - return $this->formatValue(static::Scientific, $value); + public function scientific(float|int|null $value): string { + return $this->formatNumber(self::Scientific, $value); } - public function spellout(?float $value): string { - return $this->formatValue(static::Spellout, $value); + public function spellout(float|int|null $value): string { + return $this->formatNumber(self::Spellout, $value); } public function ordinal(?int $value): string { - return $this->formatValue(static::Ordinal, $value); + return $this->formatNumber(self::Ordinal, $value); } - public function duration(DateInterval|float|int|null $value, ?string $format = null): string { - $type = static::Duration; - $format ??= $this->getOptions($type); - $format = is_string($format) - ? Cast::toString($this->getLocaleOptions($type, $format)) - : $format; - $format ??= 'HH:mm:ss.SSS'; - $value ??= 0; - $value = $value instanceof DateInterval - ? DurationFormatter::getTimestamp($value) - : $value; - $value = is_string($format) - ? (new DurationFormatter($format))->format($value) - : $this->formatValue($type, $value); - - return $value; + public function duration(DateInterval|float|int|null $value): string { + return $this->formatDuration(self::Default, $value); } - public function time( - ?DateTimeInterface $value, - ?string $format = null, - IntlTimeZone|DateTimeZone|string|null $timezone = null, - ): string { - return $this->formatDateTime(self::Time, $value, $format, $timezone); + public function time(?DateTimeInterface $value): string { + return $this->formatDateTime(self::Time, $value); } - public function date( - ?DateTimeInterface $value, - ?string $format = null, - IntlTimeZone|DateTimeZone|string|null $timezone = null, - ): string { - return $this->formatDateTime(self::Date, $value, $format, $timezone); + public function date(?DateTimeInterface $value): string { + return $this->formatDateTime(self::Date, $value); } - public function datetime( - ?DateTimeInterface $value, - ?string $format = null, - IntlTimeZone|DateTimeZone|string|null $timezone = null, - ): string { - return $this->formatDateTime(self::DateTime, $value, $format, $timezone); + public function datetime(?DateTimeInterface $value): string { + return $this->formatDateTime(self::DateTime, $value); } /** * Formats number of bytes into units based on powers of 2 (kibibyte, mebibyte, etc). * - * @param numeric-string|float|int<0, max>|null $bytes + * @param numeric-string|float|int|null $bytes */ - public function filesize(string|float|int|null $bytes, ?int $decimals = null): string { - return $this->formatFilesize( - $bytes, - $decimals ?? Cast::toInt($this->getOptions(static::Filesize, 2)), - 1024, - [ - $this->getTranslation(['filesize.B', 'B']), - $this->getTranslation(['filesize.KiB', 'KiB']), - $this->getTranslation(['filesize.MiB', 'MiB']), - $this->getTranslation(['filesize.GiB', 'GiB']), - $this->getTranslation(['filesize.TiB', 'TiB']), - $this->getTranslation(['filesize.PiB', 'PiB']), - $this->getTranslation(['filesize.EiB', 'EiB']), - $this->getTranslation(['filesize.ZiB', 'ZiB']), - $this->getTranslation(['filesize.YiB', 'YiB']), - $this->getTranslation(['filesize.RiB', 'RiB']), - $this->getTranslation(['filesize.QiB', 'QiB']), - ], - ); + public function filesize(string|float|int|null $bytes): string { + return $this->formatFilesize(self::Filesize, $bytes); } /** * Formats number of bytes into units based on powers of 10 (kilobyte, megabyte, etc). * - * @param numeric-string|float|int<0, max>|null $bytes + * @param numeric-string|float|int|null $bytes */ - public function disksize(string|float|int|null $bytes, ?int $decimals = null): string { - return $this->formatFilesize( - $bytes, - $decimals ?? Cast::toInt($this->getOptions(static::Disksize, 2)), - 1000, - [ - $this->getTranslation(['disksize.B', 'B']), - $this->getTranslation(['disksize.kB', 'kB']), - $this->getTranslation(['disksize.MB', 'MB']), - $this->getTranslation(['disksize.GB', 'GB']), - $this->getTranslation(['disksize.TB', 'TB']), - $this->getTranslation(['disksize.PB', 'PB']), - $this->getTranslation(['disksize.EB', 'EB']), - $this->getTranslation(['disksize.ZB', 'ZB']), - $this->getTranslation(['disksize.YB', 'YB']), - $this->getTranslation(['disksize.RB', 'RB']), - $this->getTranslation(['disksize.QB', 'QB']), - ], - ); - } - - public function secret(?string $value, ?int $show = null): string { - if (is_null($value)) { - return ''; - } - - $show ??= Cast::toInt($this->getOptions(static::Secret, 5)); - $length = mb_strlen($value); - $hidden = $length - $show; - - if ($length <= $show) { - $value = mb_str_pad('*', $length, '*'); - } elseif ($hidden < $show) { - $value = str_replace( - mb_substr($value, 0, $show), - mb_str_pad('*', $show, '*'), - $value, - ); - } else { - $value = str_replace( - mb_substr($value, 0, $hidden), - mb_str_pad('*', $hidden, '*'), - $value, - ); - } + public function disksize(string|float|int|null $bytes): string { + return $this->formatFilesize(self::Disksize, $bytes); + } - return $value; + public function secret(?string $value): string { + return $this->formatSecret(self::Default, $value); } // @@ -453,24 +205,6 @@ protected function getDefaultTimezone(): IntlTimeZone|DateTimeZone|string|null { return $this->config->getInstance()->get('app.timezone') ?? null; } - protected function getOptions(string $type, mixed $default = null): mixed { - $repository = $this->config->getInstance(); - $package = Package::Name; - $key = "{$package}.options.{$type}"; - - return $repository->get($key, $default); - } - - protected function getLocaleOptions(string $type, string $option): mixed { - $repository = $this->config->getInstance(); - $package = Package::Name; - $locale = $this->getLocale(); - $pattern = $repository->get("{$package}.locales.{$locale}.{$type}.{$option}") - ?? $repository->get("{$package}.all.{$type}.{$option}"); - - return $pattern; - } - /** * @param list|string $key * @param array $replace @@ -479,54 +213,237 @@ protected function getTranslation(array|string $key, array $replace = []): strin return $this->getTranslator()->get($key, $replace, $this->getLocale()); } + protected function formatNumber(string $format, float|int|null $value): string { + // Definition + $config = $this->configuration->getInstance(); + $locale = $this->getLocale(); + $style = $config->global->number->formats[$format]->style + ?? $config->locales[$locale]->number->formats[$format]->style + ?? null; + $pattern = $config->global->number->formats[$format]->pattern + ?? $config->locales[$locale]->number->formats[$format]->pattern + ?? null; + + if ($style === null) { + throw new FailedToCreateNumberFormatter($format); + } + + // Create + try { + $formatter = $this->getNumberFormatter( + $style, + $pattern, + $config->locales[$locale]->number->formats[$format] ?? null, + $config->locales[$locale]->number ?? null, + $config->global->number->formats[$format] ?? null, + $config->global->number, + ); + } catch (Exception $exception) { + throw new FailedToCreateNumberFormatter($format, $exception); + } + + // Format + $formatted = $formatter->format($value ?? 0); + + if ($formatted === false) { + throw new FailedToFormatNumber($format, $formatter->getErrorCode(), $formatter->getErrorMessage()); + } + + return $formatted; + } + /** - * @param Closure(string, ?int):(int|null)|null $closure + * @param non-empty-string|null $currency */ - protected function formatValue( - string $type, - float|int|null $value, - ?int $decimals = null, - ?Closure $closure = null, - ): string { - $value = (float) $value; - $formatter = $this->getIntlNumberFormatter($type, $decimals, $closure); + protected function formatCurrency(string $format, float|int|null $value, ?string $currency = null): string { + // Prepare + $config = $this->configuration->getInstance(); + $locale = $this->getLocale(); + $pattern = $config->global->currency->formats[$format]->pattern + ?? $config->locales[$locale]->currency->formats[$format]->pattern + ?? null; + $currency ??= $config->defaults->currency; + + // Create + try { + $formatter = $this->getNumberFormatter( + NumberFormatter::CURRENCY, + $pattern, + $config->locales[$locale]->currency->formats[$format] ?? null, + $config->locales[$locale]->currency ?? null, + $config->global->currency->formats[$format] ?? null, + $config->global->currency, + ); + } catch (Exception $exception) { + throw new FailedToCreateCurrencyFormatter($currency, $format, $exception); + } + + // Format + $formatted = $formatter->formatCurrency((float) $value, $currency); + + if ($formatted === false) { + throw new FailedToFormatCurrency( + $currency, + $format, + $formatter->getErrorCode(), + $formatter->getErrorMessage(), + ); + } + + // Return + return $formatted; + } + + protected function formatDateTime(string $format, ?DateTimeInterface $value): string { + // Null? + if (is_null($value)) { + return ''; + } + + // Prepare + $config = $this->configuration->getInstance(); + $locale = $this->getLocale(); + $pattern = $config->global->datetime->formats[$format]->pattern + ?? $config->locales[$locale]->datetime->formats[$format]->pattern + ?? null; + $dateType = $config->global->datetime->formats[$format]->dateType + ?? $config->locales[$locale]->datetime->formats[$format]->dateType + ?? null; + $timeType = $config->global->datetime->formats[$format]->timeType + ?? $config->locales[$locale]->datetime->formats[$format]->timeType + ?? null; + + if ($dateType === null || $timeType === null) { + throw new FailedToCreateDateTimeFormatter($format); + } + + // Create + try { + $formatter = $this->getDateTimeFormatter($dateType, $timeType, $pattern); + } catch (Exception $exception) { + throw new FailedToCreateDateTimeFormatter($format, $exception); + } + + // Format $formatted = $formatter->format($value); if ($formatted === false) { - throw new FailedToFormatValue($type, $formatter->getErrorCode(), $formatter->getErrorMessage()); + throw new FailedToFormatDateTime( + $format, + $formatter->getErrorCode(), + $formatter->getErrorMessage(), + ); } + // Return return $formatted; } - protected function formatDateTime( - string $type, - ?DateTimeInterface $value, - ?string $format = null, - IntlTimeZone|DateTimeZone|string|null $timezone = null, - ): string { + protected function formatSecret(string $format, ?string $value): string { + // Null? if (is_null($value)) { return ''; } - $formatter = ($timezone !== null ? $this->forTimezone($timezone) : $this)->getIntlDateFormatter($type, $format); - $value = $formatter->format($value); + // Prepare + $config = $this->configuration->getInstance(); + $locale = $this->getLocale(); + $visible = $config->global->secret->formats[$format]->visible + ?? $config->locales[$locale]->secret->formats[$format]->visible + ?? null; + + if ($visible === null) { + throw new FailedToCreateSecretFormatter($format); + } + + // Format + $length = mb_strlen($value); + $hidden = $length - $visible; + $formatted = match (true) { + $length <= $visible => mb_str_pad('*', $length, '*'), + $hidden < $visible => str_replace(mb_substr($value, 0, $visible), mb_str_pad('*', $visible, '*'), $value), + default => str_replace(mb_substr($value, 0, $hidden), mb_str_pad('*', $hidden, '*'), $value), + }; + + // Return + return $formatted; + } + + protected function formatDuration(string $format, DateInterval|float|int|null $value): string { + $config = $this->configuration->getInstance(); + $locale = $this->getLocale(); + $type = $config->locales[$locale]->duration->formats[$format] + ?? $config->global->duration->formats[$format] + ?? null; + $value = ($value instanceof DateInterval ? DurationFormatter::getTimestamp($value) : $value) ?? 0; + $formatted = match (true) { + $type instanceof DurationFormatPattern => $this->formatDurationPattern($type, $value), + $type instanceof DurationFormatIntl => $this->formatDurationIntl($config, $locale, $format, $value), + default => throw new FailedToCreateDurationFormatter($format), + }; + + return $formatted; + } + + private function formatDurationPattern(DurationFormatPattern $config, float|int $value): string { + return (new DurationFormatter($config->pattern))->format($value); + } + + private function formatDurationIntl(Config $config, string $locale, string $format, float|int $value): string { + // Create + try { + $formatIntl = $config->locales[$locale]->duration->formats[$format] ?? null; + $globalIntl = $config->global->duration->formats[$format] ?? null; + $pattern = $config->global->duration->formats[$format]->pattern + ?? $config->locales[$locale]->duration->formats[$format]->pattern + ?? null; + $formatter = $this->getNumberFormatter( + NumberFormatter::DURATION, + $pattern, + $formatIntl instanceof IntlOptions ? $formatIntl : null, + $globalIntl instanceof IntlOptions ? $globalIntl : null, + ); + } catch (Exception $exception) { + throw new FailedToCreateDurationFormatter($format, $exception); + } + + // Format + $formatted = $formatter->format($value); - if ($value === false) { - throw new FailedToFormatValue($type, $formatter->getErrorCode(), $formatter->getErrorMessage()); + if ($formatted === false) { + throw new FailedToFormatDuration($format, $formatter->getErrorCode(), $formatter->getErrorMessage()); } - return $value; + return $formatted; } /** - * @param numeric-string|float|int<0, max>|null $bytes - * @param array, string> $units + * @param numeric-string|float|int|null $bytes */ - protected function formatFilesize(string|float|int|null $bytes, int $decimals, int $base, array $units): string { + protected function formatFilesize(string $format, string|float|int|null $bytes): string { + // Prepare + $config = $this->configuration->getInstance(); + $locale = $this->getLocale(); + $base = $config->locales[$locale]->filesize->formats[$format]->base + ?? $config->global->filesize->formats[$format]->base + ?? null; + $units = $config->locales[$locale]->filesize->formats[$format]->units + ?? $config->global->filesize->formats[$format]->units + ?? null; + $integerFormat = $config->locales[$locale]->filesize->formats[$format]->integerFormat + ?? $config->global->filesize->formats[$format]->integerFormat + ?? self::Integer; + $decimalFormat = $config->locales[$locale]->filesize->formats[$format]->decimalFormat + ?? $config->global->filesize->formats[$format]->decimalFormat + ?? self::Decimal; + + if ($base === null || $units === null) { + throw new FailedToCreateFileSizeFormatter($format); + } + $unit = 0; $base = (string) $base; - $scale = 2 * $decimals; + $scale = mb_strlen($base) + 1; $bytes = match (true) { is_float($bytes) => sprintf('%0.0f', $bytes), $bytes === null => '0', @@ -542,212 +459,85 @@ protected function formatFilesize(string|float|int|null $bytes, int $decimals, i } // Format - return $unit === 0 - ? $this->integer((int) $bytes)." {$units[$unit]}" - : $this->decimal((float) $bytes, $decimals)." {$units[$unit]}"; + $isInt = $unit === 0; + $bytes = $isInt ? (int) $bytes : (float) $bytes; + $format = $isInt ? $integerFormat : $decimalFormat; + $suffix = $this->getTranslation($units[$unit]); + $formatted = "{$this->formatNumber($format, $bytes)} {$suffix}"; + + return $formatted; } // // // ========================================================================= - private function getIntlDateFormatter(string $type, ?string $format = null): IntlDateFormatter { - $key = json_encode([$type, $format], JSON_THROW_ON_ERROR); - $formatter = $this->dateFormatters[$key] ?? $this->createIntlDateFormatter($type, $format); - - if ($formatter !== null) { - $this->dateFormatters[$key] = $formatter; - } else { - throw new FailedToCreateDateFormatter($type, $format); - } - - return $formatter; - } - - private function createIntlDateFormatter(string $type, ?string $format = null): ?IntlDateFormatter { - $formatter = null; - $pattern = ''; - $default = IntlDateFormatter::SHORT; - $format ??= $this->getOptions($type, $default); - $tz = $this->getTimezone(); - - if (is_string($format)) { - $pattern = Cast::toString($this->getLocaleOptions($type, $format)); - $format = IntlDateFormatter::FULL; - } - - if (!is_int($format)) { - $format = $default; - } - - switch ($type) { - case self::Time: - $formatter = new IntlDateFormatter( - $this->getLocale(), - IntlDateFormatter::NONE, - $format, - $tz, - null, - $pattern, - ); - break; - case self::Date: - $formatter = new IntlDateFormatter( - $this->getLocale(), - $format, - IntlDateFormatter::NONE, - $tz, - null, - $pattern, - ); - break; - case self::DateTime: - $formatter = new IntlDateFormatter( - $this->getLocale(), - $format, - $format, - $tz, - null, - $pattern, - ); - break; - default: - // empty - break; - } + private function getDateTimeFormatter(int $dateType, int $timeType, ?string $pattern): IntlDateFormatter { + $locale = $this->getLocale(); + $timezone = $this->getTimezone(); + $formatter = new IntlDateFormatter($locale, $dateType, $timeType, $timezone, null, $pattern); return $formatter; } - /** - * @param Closure(string, ?int):(int|null)|null $closure - */ - private function getIntlNumberFormatter( - string $type, - ?int $decimals = null, - ?Closure $closure = null, - ): NumberFormatter { - $key = json_encode([$type, $decimals], JSON_THROW_ON_ERROR); - $formatter = $this->numbersFormatters[$key] ?? $this->createIntlNumberFormatter($type, $decimals, $closure); - - if ($formatter !== null) { - $this->numbersFormatters[$key] = $formatter; - } else { - throw new FailedToCreateNumberFormatter($type); - } + private function getNumberFormatter(int $style, ?string $pattern, ?IntlOptions ...$options): NumberFormatter { + // Create + $locale = $this->getLocale(); + $formatter = new NumberFormatter($locale, $style, $pattern); - return $formatter; - } + // Collect Intl options + $textAttributes = []; + $attributes = []; + $symbols = []; - /** - * @param Closure(string, ?int):(int|null)|null $closure - */ - private function createIntlNumberFormatter( - string $type, - ?int $decimals = null, - ?Closure $closure = null, - ): ?NumberFormatter { - $formatter = null; - $attributes = []; - $symbols = []; - $texts = []; - - if ($closure !== null) { - $decimals = $closure($type, $decimals); - } + foreach ($options as $intl) { + if ($intl === null) { + continue; + } - switch ($type) { - case static::Integer: - $formatter = new NumberFormatter($this->getLocale(), NumberFormatter::DECIMAL); - $attributes = $attributes + [ - NumberFormatter::FRACTION_DIGITS => 0, - ]; - break; - case static::Decimal: - $formatter = new NumberFormatter($this->getLocale(), NumberFormatter::DECIMAL); - $attributes = $attributes + [ - NumberFormatter::FRACTION_DIGITS => abs((int) $decimals), - ]; - break; - case static::Currency: - $formatter = new NumberFormatter($this->getLocale(), NumberFormatter::CURRENCY); - break; - case static::Percent: - $formatter = new NumberFormatter($this->getLocale(), NumberFormatter::PERCENT); - $attributes = $attributes + [ - NumberFormatter::FRACTION_DIGITS => abs((int) $decimals), - ]; - break; - case static::Scientific: - $formatter = new NumberFormatter($this->getLocale(), NumberFormatter::SCIENTIFIC); - break; - case static::Spellout: - $formatter = new NumberFormatter($this->getLocale(), NumberFormatter::SPELLOUT); - break; - case static::Ordinal: - $formatter = new NumberFormatter($this->getLocale(), NumberFormatter::ORDINAL); - break; - case static::Duration: - $formatter = new NumberFormatter($this->getLocale(), NumberFormatter::DURATION); - break; - default: - // null - break; + $symbols += $intl->symbols; + $attributes += $intl->attributes; + $textAttributes += $intl->textAttributes; } - if ($formatter !== null) { - $attributes = $attributes - + (array) $this->getLocaleOptions($type, self::IntlAttributes) - + (array) $this->getOptions(self::IntlAttributes); - $symbols = $symbols - + (array) $this->getLocaleOptions($type, self::IntlSymbols) - + (array) $this->getOptions(self::IntlSymbols); - $texts = $texts - + (array) $this->getLocaleOptions($type, self::IntlTextAttributes) - + (array) $this->getOptions(self::IntlTextAttributes); - - foreach ($attributes as $attribute => $value) { - if (!is_int($attribute) || !$formatter->setAttribute($attribute, Cast::toNumber($value))) { - throw new OutOfBoundsException( - sprintf( - '%s::setAttribute() failed: `%s` is unknown/invalid.', - NumberFormatter::class, - $attribute, - ), - ); - } + // Apply + foreach ($attributes as $attribute => $value) { + if (!$formatter->setAttribute($attribute, $value)) { + throw new OutOfBoundsException( + sprintf( + '%s::setAttribute() failed: `%s` is unknown/invalid.', + NumberFormatter::class, + $attribute, + ), + ); } + } - foreach ($symbols as $symbol => $value) { - if (!is_int($symbol) || !$formatter->setSymbol($symbol, Cast::toString($value))) { - throw new OutOfBoundsException( - sprintf( - '%s::setSymbol() failed: `%s` is unknown/invalid.', - NumberFormatter::class, - $symbol, - ), - ); - } + foreach ($symbols as $symbol => $value) { + if (!$formatter->setSymbol($symbol, $value)) { + throw new OutOfBoundsException( + sprintf( + '%s::setSymbol() failed: `%s` is unknown/invalid.', + NumberFormatter::class, + $symbol, + ), + ); } + } - foreach ($texts as $text => $value) { - if (!is_int($text) || !$formatter->setTextAttribute($text, Cast::toString($value))) { - throw new OutOfBoundsException( - sprintf( - '%s::setTextAttribute() failed: `%s` is unknown/invalid.', - NumberFormatter::class, - $text, - ), - ); - } + foreach ($textAttributes as $attribute => $value) { + if (!$formatter->setTextAttribute($attribute, $value)) { + throw new OutOfBoundsException( + sprintf( + '%s::setTextAttribute() failed: `%s` is unknown/invalid.', + NumberFormatter::class, + $attribute, + ), + ); } } + // Return return $formatter; } - - public function __clone(): void { - $this->dateFormatters = []; - $this->numbersFormatters = []; - } // } diff --git a/packages/formatter/src/FormatterTest.php b/packages/formatter/src/FormatterTest.php index ef217308d..17b3bfe16 100644 --- a/packages/formatter/src/FormatterTest.php +++ b/packages/formatter/src/FormatterTest.php @@ -4,6 +4,15 @@ use DateTime; use IntlDateFormatter; +use LastDragon_ru\LaraASP\Core\Utils\Cast; +use LastDragon_ru\LaraASP\Formatter\Config\Config; +use LastDragon_ru\LaraASP\Formatter\Config\Formats\DateTimeFormat; +use LastDragon_ru\LaraASP\Formatter\Config\Formats\DurationFormatIntl; +use LastDragon_ru\LaraASP\Formatter\Config\Formats\DurationFormatPattern; +use LastDragon_ru\LaraASP\Formatter\Config\Formats\NumberConfig; +use LastDragon_ru\LaraASP\Formatter\Config\Formats\NumberFormat; +use LastDragon_ru\LaraASP\Formatter\Config\Formats\SecretFormat; +use LastDragon_ru\LaraASP\Formatter\Config\Locale; use LastDragon_ru\LaraASP\Formatter\Testing\Package\TestCase; use NumberFormatter; use Override; @@ -75,17 +84,32 @@ public function testDecimal(): void { } public function testDecimalConfig(): void { - $this->setConfig([ - Package::Name.'.options.'.Formatter::Decimal => 4, - Package::Name.'.locales.ru_RU.'.Formatter::Decimal.'.'.Formatter::IntlAttributes => [ - NumberFormatter::FRACTION_DIGITS => 9, // should be ignored - NumberFormatter::ROUNDING_MODE => NumberFormatter::ROUND_FLOOR, - ], - ]); + $this->setConfiguration(PackageConfig::class, static function (Config $config): void { + Cast::to(NumberFormat::class, $config->global->number->formats[Formatter::Decimal])->attributes = [ + NumberFormatter::FRACTION_DIGITS => 4, + ]; + + $config->locales['ru_RU'] = new Locale( + number: new NumberConfig( + formats : [ + Formatter::Decimal => new NumberFormat( + style : NumberFormatter::DECIMAL, + attributes: [ + NumberFormatter::FRACTION_DIGITS => 2, + NumberFormatter::ROUNDING_MODE => NumberFormatter::ROUND_FLOOR, + ], + ), + ], + attributes: [ + NumberFormatter::FRACTION_DIGITS => 5, // should be ignored + ], + ), + ); + }); self::assertEquals('1,000.0000', $this->formatter->decimal(1000)); self::assertEquals('1,000.0001', $this->formatter->decimal(1000.000099)); - self::assertEquals("1\u{00A0}000,0000", $this->formatter->forLocale('ru_RU')->decimal(1000.000099)); + self::assertEquals("1\u{00A0}000,00", $this->formatter->forLocale('ru_RU')->decimal(1000.0099)); } public function testOrdinal(): void { @@ -98,10 +122,18 @@ public function testString(): void { } public function testSpellout(): void { + self::assertEquals( + 'ninety-nine', + $this->formatter->spellout(99), + ); self::assertEquals( 'one thousand three hundred twenty-four point two five', $this->formatter->spellout(1324.25), ); + self::assertEquals( + 'девяносто пять', + $this->formatter->forLocale('ru_RU')->spellout(95), + ); self::assertEquals( 'двадцать пять целых пять десятых', $this->formatter->forLocale('ru_RU')->spellout(25.5), @@ -111,14 +143,15 @@ public function testSpellout(): void { public function testPercent(): void { self::assertEquals('10%', $this->formatter->percent(10)); self::assertEquals('25%', $this->formatter->percent(24.59)); - self::assertEquals('24.59%', $this->formatter->percent(24.59, 2)); self::assertEquals("56\u{00A0}%", $this->formatter->forLocale('ru_RU')->percent(56.09)); } public function testPercentConfig(): void { - $this->setConfig([ - Package::Name.'.options.'.Formatter::Percent => 2, - ]); + $this->setConfiguration(PackageConfig::class, static function (Config $config): void { + Cast::to(NumberFormat::class, $config->global->number->formats[Formatter::Percent])->attributes = [ + NumberFormatter::FRACTION_DIGITS => 2, + ]; + }); self::assertEquals('10.99%', $this->formatter->percent(10.99)); } @@ -129,23 +162,20 @@ public function testDuration(): void { } public function testDurationConfig(): void { - $this->setConfig([ - Package::Name.'.options.'.Formatter::Duration => NumberFormatter::DURATION, - ]); + $this->setConfiguration(PackageConfig::class, static function (Config $config): void { + $config->global->duration->formats[Formatter::Default] = new DurationFormatIntl(); + }); self::assertEquals('3:25:45', $this->formatter->duration(12_345)); self::assertEquals("12\u{00A0}345", $this->formatter->forLocale('ru_RU')->duration(12_345)); } public function testDurationCustomFormat(): void { - $this->setConfig([ - Package::Name.'.options.'.Formatter::Duration => 'custom', - Package::Name.'.all.'.Formatter::Duration.'.custom' => 'mm:ss', - Package::Name.'.all.'.Formatter::Duration.'.custom2' => 'H:mm:ss.SSS', - ]); + $this->setConfiguration(PackageConfig::class, static function (Config $config): void { + $config->global->duration->formats[Formatter::Default] = new DurationFormatPattern('mm:ss'); + }); self::assertEquals('02:03', $this->formatter->duration(123.456)); - self::assertEquals('0:02:03.456', $this->formatter->duration(123.456, 'custom2')); } public function testTime(): void { @@ -153,16 +183,13 @@ public function testTime(): void { self::assertIsObject($time); self::assertEquals('11:24 PM', str_replace("\u{202F}", ' ', $this->formatter->time($time))); - self::assertEquals( - '2:24 AM', - str_replace("\u{202F}", ' ', $this->formatter->time($time, null, 'Europe/Moscow')), - ); } public function testTimeConfig(): void { - $this->setConfig([ - Package::Name.'.options.'.Formatter::Time => IntlDateFormatter::MEDIUM, - ]); + $this->setConfiguration(PackageConfig::class, static function (Config $config): void { + $format = Cast::to(DateTimeFormat::class, $config->global->datetime->formats[Formatter::Time]); + $format->timeType = IntlDateFormatter::MEDIUM; + }); $time = DateTime::createFromFormat('H:i:s', '23:24:59'); @@ -171,17 +198,15 @@ public function testTimeConfig(): void { } public function testTimeCustomFormat(): void { - $this->setConfig([ - Package::Name.'.options.'.Formatter::Time => 'custom', - Package::Name.'.all.'.Formatter::Time.'.custom' => 'HH:mm:ss.SSS', - Package::Name.'.all.'.Formatter::Time.'.custom2' => 'HH:mm:ss.SSS', - ]); + $this->setConfiguration(PackageConfig::class, static function (Config $config): void { + $format = Cast::to(DateTimeFormat::class, $config->global->datetime->formats[Formatter::Time]); + $format->pattern = 'HH:mm:ss.SSS'; + }); $time = DateTime::createFromFormat('H:i:s', '23:24:59'); self::assertIsObject($time); self::assertEquals('23:24:59.000', $this->formatter->time($time)); - self::assertEquals('23:24:59.000', $this->formatter->time($time, 'custom2')); } public function testDate(): void { @@ -189,13 +214,13 @@ public function testDate(): void { self::assertIsObject($date); self::assertEquals('5/12/05', $this->formatter->date($date)); - self::assertEquals('5/13/05', $this->formatter->date($date, null, 'Europe/Moscow')); } public function testDateConfig(): void { - $this->setConfig([ - Package::Name.'.options.'.Formatter::Date => IntlDateFormatter::MEDIUM, - ]); + $this->setConfiguration(PackageConfig::class, static function (Config $config): void { + $format = Cast::to(DateTimeFormat::class, $config->global->datetime->formats[Formatter::Date]); + $format->dateType = IntlDateFormatter::MEDIUM; + }); $date = DateTime::createFromFormat('d.m.Y H:i:s', '12.05.2005 23:00:00'); @@ -204,17 +229,15 @@ public function testDateConfig(): void { } public function testDateCustomFormat(): void { - $this->setConfig([ - Package::Name.'.options.'.Formatter::Date => 'custom', - Package::Name.'.all.'.Formatter::Date.'.custom' => 'd MMM YYYY', - Package::Name.'.all.'.Formatter::Date.'.custom2' => 'd MMM YYYY', - ]); + $this->setConfiguration(PackageConfig::class, static function (Config $config): void { + $format = Cast::to(DateTimeFormat::class, $config->global->datetime->formats[Formatter::Date]); + $format->pattern = 'd MMM YYYY'; + }); $date = DateTime::createFromFormat('d.m.Y H:i:s', '12.05.2005 23:00:00'); self::assertIsObject($date); self::assertEquals('12 May 2005', $this->formatter->date($date)); - self::assertEquals('12 May 2005', $this->formatter->date($date, 'custom2')); } public function testDatetime(): void { @@ -222,16 +245,17 @@ public function testDatetime(): void { self::assertIsObject($datetime); self::assertEquals('5/12/05, 11:00 PM', str_replace("\u{202F}", ' ', $this->formatter->datetime($datetime))); - self::assertEquals( - '5/13/05, 3:00 AM', - str_replace("\u{202F}", ' ', $this->formatter->datetime($datetime, null, 'Europe/Moscow')), - ); } public function testDatetimeConfig(): void { - $this->setConfig([ - Package::Name.'.options.'.Formatter::DateTime => IntlDateFormatter::MEDIUM, - ]); + $this->setConfiguration(PackageConfig::class, static function (Config $config): void { + $format = Cast::to( + DateTimeFormat::class, + $config->global->datetime->formats[Formatter::DateTime], + ); + $format->dateType = IntlDateFormatter::MEDIUM; + $format->timeType = IntlDateFormatter::MEDIUM; + }); $datetime = DateTime::createFromFormat('d.m.Y H:i:s', '12.05.2005 23:00:00'); @@ -243,20 +267,19 @@ public function testDatetimeConfig(): void { } public function testDatetimeCustomFormat(): void { - $this->setConfig([ - Package::Name.'.options.'.Formatter::DateTime => 'custom', - Package::Name.'.all.'.Formatter::DateTime.'.custom' => 'd MMM YYYY || HH:mm:ss', - Package::Name.'.all.'.Formatter::DateTime.'.custom2' => 'd MMM YYYY || HH:mm:ss', - ]); + $this->setConfiguration(PackageConfig::class, static function (Config $config): void { + $format = Cast::to(DateTimeFormat::class, $config->global->datetime->formats[Formatter::DateTime]); + $format->pattern = 'd MMM YYYY || HH:mm:ss'; + }); $datetime = DateTime::createFromFormat('d.m.Y H:i:s', '12.05.2005 23:00:00'); self::assertIsObject($datetime); self::assertEquals('12 May 2005 || 23:00:00', $this->formatter->datetime($datetime)); - self::assertEquals('12 May 2005 || 23:00:00', $this->formatter->datetime($datetime, 'custom2')); } public function testScientific(): void { + self::assertEquals('1E1', $this->formatter->scientific(10)); self::assertEquals('1.00324234E1', $this->formatter->scientific(10.0324234)); self::assertEquals('1.00324234E8', $this->formatter->scientific(100_324_234)); self::assertEquals('-1,00324234E8', $this->formatter->forLocale('ru_RU')->scientific(-100_324_234)); @@ -278,9 +301,9 @@ public function testSecret(): void { } public function testSecretConfig(): void { - $this->setConfig([ - Package::Name.'.options.'.Formatter::Secret => 3, - ]); + $this->setConfiguration(PackageConfig::class, static function (Config $config): void { + Cast::to(SecretFormat::class, $config->global->secret->formats[Formatter::Default])->visible = 3; + }); self::assertEquals('', $this->formatter->secret(null)); self::assertEquals('*', $this->formatter->secret('1')); @@ -297,17 +320,17 @@ public function testFilesize(): void { self::assertEquals('0 B', $this->formatter->filesize(null)); self::assertEquals('0 B', $this->formatter->filesize(0)); self::assertEquals('10 B', $this->formatter->filesize(10)); - self::assertEquals('1.00 MiB', $this->formatter->filesize(1023 * 1024, 2)); + self::assertEquals('1.00 MiB', $this->formatter->filesize(1023 * 1024)); self::assertEquals('10.33 MiB', $this->formatter->filesize(10 * 1024 * 1024 + 1024 * 334)); - self::assertEquals('10.00 GiB', $this->formatter->filesize(10 * 1024 * 1024 * 1024, 2)); - self::assertEquals('0.87 EiB', $this->formatter->filesize(999_999_999_999_999_999, 2)); - self::assertEquals('8.00 EiB', $this->formatter->filesize(PHP_INT_MAX, 2)); - self::assertEquals('10.00 QiB', $this->formatter->filesize(10 * pow(2, 100), 2)); - self::assertEquals('10.00 QiB', $this->formatter->filesize('12676506002282294014967032053760', 2)); - self::assertEquals('100.00 QiB', $this->formatter->filesize('126765060022822940149670320537699', 2)); + self::assertEquals('10.00 GiB', $this->formatter->filesize(10 * 1024 * 1024 * 1024)); + self::assertEquals('0.87 EiB', $this->formatter->filesize(999_999_999_999_999_999)); + self::assertEquals('8.00 EiB', $this->formatter->filesize(PHP_INT_MAX)); + self::assertEquals('10.00 QiB', $this->formatter->filesize(10 * pow(2, 100))); + self::assertEquals('10.00 QiB', $this->formatter->filesize('12676506002282294014967032053760')); + self::assertEquals('100.00 QiB', $this->formatter->filesize('126765060022822940149670320537699')); self::assertEquals( '10,000.00 QiB', - $this->formatter->filesize(10 * 1000 * pow(2, 100), 2), + $this->formatter->filesize(10 * 1000 * pow(2, 100)), ); } @@ -315,30 +338,30 @@ public function testDisksize(): void { self::assertEquals('0 B', $this->formatter->disksize(null)); self::assertEquals('0 B', $this->formatter->disksize(0)); self::assertEquals('10 B', $this->formatter->disksize(10)); - self::assertEquals('1.00 MB', $this->formatter->disksize(999 * 1000, 2)); + self::assertEquals('1.00 MB', $this->formatter->disksize(999 * 1000)); self::assertEquals('10.83 MB', $this->formatter->disksize(10 * 1024 * 1024 + 1024 * 334)); - self::assertEquals('10.00 GB', $this->formatter->disksize(10 * 1000 * 1000 * 1000, 2)); - self::assertEquals('9.22 EB', $this->formatter->disksize(PHP_INT_MAX, 2)); - self::assertEquals('10.00 QB', $this->formatter->disksize(10_000_000_000_000_000_000_000_000_000_000, 2)); - self::assertEquals('10.00 QB', $this->formatter->disksize('10000000000000000000000000000000', 2)); - self::assertEquals('100.00 QB', $this->formatter->disksize('99999999999999999999999999999999', 2)); + self::assertEquals('10.00 GB', $this->formatter->disksize(10 * 1000 * 1000 * 1000)); + self::assertEquals('9.22 EB', $this->formatter->disksize(PHP_INT_MAX)); + self::assertEquals('10.00 QB', $this->formatter->disksize(10_000_000_000_000_000_000_000_000_000_000)); + self::assertEquals('10.00 QB', $this->formatter->disksize('10000000000000000000000000000000')); + self::assertEquals('100.00 QB', $this->formatter->disksize('99999999999999999999999999999999')); self::assertEquals( '10,000.00 QB', - $this->formatter->disksize(10_000_000_000_000_000_000_000_000_000_000_000, 2), + $this->formatter->disksize(10_000_000_000_000_000_000_000_000_000_000_000), ); } public function testCurrency(): void { + self::assertEquals('$10.00', $this->formatter->currency(10)); self::assertEquals('$10.03', $this->formatter->currency(10.0324234)); - self::assertEquals("RUB\u{00A0}100,324,234.00", $this->formatter->currency(100_324_234, 'RUB')); - self::assertEquals("100,99\u{00A0}₽", $this->formatter->forLocale('ru_RU')->currency(100.985, 'RUB')); } public function testCurrencyConfig(): void { - $this->setConfig([ - Package::Name.'.options.'.Formatter::Currency => 'RUB', - ]); + $this->setConfiguration(PackageConfig::class, static function (Config $config): void { + $config->defaults->currency = 'RUB'; + }); + self::assertEquals("RUB\u{00A0}10.00", $this->formatter->currency(10)); self::assertEquals("RUB\u{00A0}10.03", $this->formatter->currency(10.0324234)); } // From 678277ee5536d09da2f2e7a93bf662b8882f9951 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Tue, 5 Nov 2024 11:26:14 +0400 Subject: [PATCH 03/22] Removed `\LastDragon_ru\LaraASP\Formatter\Config\Configuration::$defaults` (by default, the locale's default currency will be used). --- packages/formatter/src/Config/Config.php | 4 ---- packages/formatter/src/Config/Defaults.php | 22 ------------------- .../FailedToCreateCurrencyFormatter.php | 4 ++-- packages/formatter/src/Formatter.php | 8 +++---- packages/formatter/src/FormatterTest.php | 14 ++++++------ 5 files changed, 13 insertions(+), 39 deletions(-) delete mode 100644 packages/formatter/src/Config/Defaults.php diff --git a/packages/formatter/src/Config/Config.php b/packages/formatter/src/Config/Config.php index 4789ead4f..d3e52fa32 100644 --- a/packages/formatter/src/Config/Config.php +++ b/packages/formatter/src/Config/Config.php @@ -16,10 +16,6 @@ class Config extends Configuration { public function __construct( - /** - * Default formats. - */ - public Defaults $defaults = new Defaults(), /** * Options and patterns/formats for all locales. */ diff --git a/packages/formatter/src/Config/Defaults.php b/packages/formatter/src/Config/Defaults.php deleted file mode 100644 index 46f2c8a43..000000000 --- a/packages/formatter/src/Config/Defaults.php +++ /dev/null @@ -1,22 +0,0 @@ -currency; } diff --git a/packages/formatter/src/Formatter.php b/packages/formatter/src/Formatter.php index 1b2400c71..0343df3b0 100644 --- a/packages/formatter/src/Formatter.php +++ b/packages/formatter/src/Formatter.php @@ -257,12 +257,11 @@ protected function formatNumber(string $format, float|int|null $value): string { */ protected function formatCurrency(string $format, float|int|null $value, ?string $currency = null): string { // Prepare - $config = $this->configuration->getInstance(); - $locale = $this->getLocale(); - $pattern = $config->global->currency->formats[$format]->pattern + $config = $this->configuration->getInstance(); + $locale = $this->getLocale(); + $pattern = $config->global->currency->formats[$format]->pattern ?? $config->locales[$locale]->currency->formats[$format]->pattern ?? null; - $currency ??= $config->defaults->currency; // Create try { @@ -279,6 +278,7 @@ protected function formatCurrency(string $format, float|int|null $value, ?string } // Format + $currency ??= $formatter->getTextAttribute(NumberFormatter::CURRENCY_CODE); $formatted = $formatter->formatCurrency((float) $value, $currency); if ($formatted === false) { diff --git a/packages/formatter/src/FormatterTest.php b/packages/formatter/src/FormatterTest.php index 17b3bfe16..ada27729c 100644 --- a/packages/formatter/src/FormatterTest.php +++ b/packages/formatter/src/FormatterTest.php @@ -352,17 +352,17 @@ public function testDisksize(): void { } public function testCurrency(): void { - self::assertEquals('$10.00', $this->formatter->currency(10)); - self::assertEquals('$10.03', $this->formatter->currency(10.0324234)); + $formatter = $this->formatter->forLocale('en_US'); + + self::assertEquals('$10.00', $formatter->currency(10)); + self::assertEquals('$10.03', $formatter->currency(10.0324234)); } public function testCurrencyConfig(): void { - $this->setConfiguration(PackageConfig::class, static function (Config $config): void { - $config->defaults->currency = 'RUB'; - }); + $formatter = $this->formatter->forLocale('ru_RU'); - self::assertEquals("RUB\u{00A0}10.00", $this->formatter->currency(10)); - self::assertEquals("RUB\u{00A0}10.03", $this->formatter->currency(10.0324234)); + self::assertEquals("10,00\u{00A0}₽", $formatter->currency(10)); + self::assertEquals("10,03\u{00A0}₽", $formatter->currency(10.0324234)); } // } From 4e398e1afd704934b87de194c48885e7bb67004e Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Wed, 6 Nov 2024 14:13:17 +0400 Subject: [PATCH 04/22] Exceptions simplification. --- .../FailedToCreateDateTimeFormatter.php | 26 ------ .../FailedToCreateDurationFormatter.php | 26 ------ .../FailedToCreateFilesizeFormatter.php | 26 ------ .../FailedToCreateSecretFormatter.php | 26 ------ .../src/Exceptions/FailedToFormatCurrency.php | 38 -------- .../src/Exceptions/FailedToFormatDateTime.php | 32 ------- .../src/Exceptions/FailedToFormatDuration.php | 32 ------- .../src/Exceptions/FailedToFormatNumber.php | 32 ------- .../src/Exceptions/FailedToFormatValue.php | 25 ------ ...teFormatter.php => FormatterException.php} | 2 +- ...p => FormatterFailedToCreateFormatter.php} | 10 ++- ...r.php => FormatterFailedToFormatValue.php} | 18 ++-- packages/formatter/src/Formatter.php | 86 ++++++++++++------- 13 files changed, 76 insertions(+), 303 deletions(-) delete mode 100644 packages/formatter/src/Exceptions/FailedToCreateDateTimeFormatter.php delete mode 100644 packages/formatter/src/Exceptions/FailedToCreateDurationFormatter.php delete mode 100644 packages/formatter/src/Exceptions/FailedToCreateFilesizeFormatter.php delete mode 100644 packages/formatter/src/Exceptions/FailedToCreateSecretFormatter.php delete mode 100644 packages/formatter/src/Exceptions/FailedToFormatCurrency.php delete mode 100644 packages/formatter/src/Exceptions/FailedToFormatDateTime.php delete mode 100644 packages/formatter/src/Exceptions/FailedToFormatDuration.php delete mode 100644 packages/formatter/src/Exceptions/FailedToFormatNumber.php delete mode 100644 packages/formatter/src/Exceptions/FailedToFormatValue.php rename packages/formatter/src/Exceptions/{FailedToCreateFormatter.php => FormatterException.php} (70%) rename packages/formatter/src/Exceptions/{FailedToCreateNumberFormatter.php => FormatterFailedToCreateFormatter.php} (60%) rename packages/formatter/src/Exceptions/{FailedToCreateCurrencyFormatter.php => FormatterFailedToFormatValue.php} (50%) diff --git a/packages/formatter/src/Exceptions/FailedToCreateDateTimeFormatter.php b/packages/formatter/src/Exceptions/FailedToCreateDateTimeFormatter.php deleted file mode 100644 index 05c9c16bb..000000000 --- a/packages/formatter/src/Exceptions/FailedToCreateDateTimeFormatter.php +++ /dev/null @@ -1,26 +0,0 @@ -getFormat(), - ), - $previous, - ); - } - - public function getFormat(): string { - return $this->format; - } -} diff --git a/packages/formatter/src/Exceptions/FailedToCreateDurationFormatter.php b/packages/formatter/src/Exceptions/FailedToCreateDurationFormatter.php deleted file mode 100644 index 3d1da15bd..000000000 --- a/packages/formatter/src/Exceptions/FailedToCreateDurationFormatter.php +++ /dev/null @@ -1,26 +0,0 @@ -getFormat(), - ), - $previous, - ); - } - - public function getFormat(): string { - return $this->format; - } -} diff --git a/packages/formatter/src/Exceptions/FailedToCreateFilesizeFormatter.php b/packages/formatter/src/Exceptions/FailedToCreateFilesizeFormatter.php deleted file mode 100644 index 2d4eb8ec4..000000000 --- a/packages/formatter/src/Exceptions/FailedToCreateFilesizeFormatter.php +++ /dev/null @@ -1,26 +0,0 @@ -getFormat(), - ), - $previous, - ); - } - - public function getFormat(): string { - return $this->format; - } -} diff --git a/packages/formatter/src/Exceptions/FailedToCreateSecretFormatter.php b/packages/formatter/src/Exceptions/FailedToCreateSecretFormatter.php deleted file mode 100644 index ea5bf8fbd..000000000 --- a/packages/formatter/src/Exceptions/FailedToCreateSecretFormatter.php +++ /dev/null @@ -1,26 +0,0 @@ -getFormat(), - ), - $previous, - ); - } - - public function getFormat(): string { - return $this->format; - } -} diff --git a/packages/formatter/src/Exceptions/FailedToFormatCurrency.php b/packages/formatter/src/Exceptions/FailedToFormatCurrency.php deleted file mode 100644 index 4ef171e4b..000000000 --- a/packages/formatter/src/Exceptions/FailedToFormatCurrency.php +++ /dev/null @@ -1,38 +0,0 @@ -getCurrency(), - $this->getFormat(), - $this->getIntlErrorMessage(), - $this->getIntlErrorCode(), - ), - $intlErrorCode, - $intlErrorMessage, - $previous, - ); - } - - public function getCurrency(): string { - return $this->currency; - } - - public function getFormat(): string { - return $this->format; - } -} diff --git a/packages/formatter/src/Exceptions/FailedToFormatDateTime.php b/packages/formatter/src/Exceptions/FailedToFormatDateTime.php deleted file mode 100644 index 8f76c2d4d..000000000 --- a/packages/formatter/src/Exceptions/FailedToFormatDateTime.php +++ /dev/null @@ -1,32 +0,0 @@ -getFormat(), - $this->getIntlErrorMessage(), - $this->getIntlErrorCode(), - ), - $intlErrorCode, - $intlErrorMessage, - $previous, - ); - } - - public function getFormat(): string { - return $this->format; - } -} diff --git a/packages/formatter/src/Exceptions/FailedToFormatDuration.php b/packages/formatter/src/Exceptions/FailedToFormatDuration.php deleted file mode 100644 index f28d159bb..000000000 --- a/packages/formatter/src/Exceptions/FailedToFormatDuration.php +++ /dev/null @@ -1,32 +0,0 @@ -getFormat(), - $this->getIntlErrorMessage(), - $this->getIntlErrorCode(), - ), - $intlErrorCode, - $intlErrorMessage, - $previous, - ); - } - - public function getFormat(): string { - return $this->format; - } -} diff --git a/packages/formatter/src/Exceptions/FailedToFormatNumber.php b/packages/formatter/src/Exceptions/FailedToFormatNumber.php deleted file mode 100644 index 8c639a953..000000000 --- a/packages/formatter/src/Exceptions/FailedToFormatNumber.php +++ /dev/null @@ -1,32 +0,0 @@ -getFormat(), - $this->getIntlErrorMessage(), - $this->getIntlErrorCode(), - ), - $intlErrorCode, - $intlErrorMessage, - $previous, - ); - } - - public function getFormat(): string { - return $this->format; - } -} diff --git a/packages/formatter/src/Exceptions/FailedToFormatValue.php b/packages/formatter/src/Exceptions/FailedToFormatValue.php deleted file mode 100644 index 0d05046d1..000000000 --- a/packages/formatter/src/Exceptions/FailedToFormatValue.php +++ /dev/null @@ -1,25 +0,0 @@ -intlErrorCode; - } - - public function getIntlErrorMessage(): string { - return $this->intlErrorMessage; - } -} diff --git a/packages/formatter/src/Exceptions/FailedToCreateFormatter.php b/packages/formatter/src/Exceptions/FormatterException.php similarity index 70% rename from packages/formatter/src/Exceptions/FailedToCreateFormatter.php rename to packages/formatter/src/Exceptions/FormatterException.php index 7b8c20a47..9e637ad66 100644 --- a/packages/formatter/src/Exceptions/FailedToCreateFormatter.php +++ b/packages/formatter/src/Exceptions/FormatterException.php @@ -4,6 +4,6 @@ use LastDragon_ru\LaraASP\Formatter\PackageException; -abstract class FailedToCreateFormatter extends PackageException { +abstract class FormatterException extends PackageException { // empty } diff --git a/packages/formatter/src/Exceptions/FailedToCreateNumberFormatter.php b/packages/formatter/src/Exceptions/FormatterFailedToCreateFormatter.php similarity index 60% rename from packages/formatter/src/Exceptions/FailedToCreateNumberFormatter.php rename to packages/formatter/src/Exceptions/FormatterFailedToCreateFormatter.php index fb73a0355..cbff5551a 100644 --- a/packages/formatter/src/Exceptions/FailedToCreateNumberFormatter.php +++ b/packages/formatter/src/Exceptions/FormatterFailedToCreateFormatter.php @@ -6,20 +6,26 @@ use function sprintf; -class FailedToCreateNumberFormatter extends FailedToCreateFormatter { +class FormatterFailedToCreateFormatter extends FormatterException { public function __construct( + protected string $formatter, protected string $format, ?Throwable $previous = null, ) { parent::__construct( sprintf( - 'Failed to create Number Formatter for `%s` format.', + 'Failed to create `%s` formatter for `%s` format.', + $this->getFormatter(), $this->getFormat(), ), $previous, ); } + public function getFormatter(): string { + return $this->formatter; + } + public function getFormat(): string { return $this->format; } diff --git a/packages/formatter/src/Exceptions/FailedToCreateCurrencyFormatter.php b/packages/formatter/src/Exceptions/FormatterFailedToFormatValue.php similarity index 50% rename from packages/formatter/src/Exceptions/FailedToCreateCurrencyFormatter.php rename to packages/formatter/src/Exceptions/FormatterFailedToFormatValue.php index e5dc00a7c..492f793a4 100644 --- a/packages/formatter/src/Exceptions/FailedToCreateCurrencyFormatter.php +++ b/packages/formatter/src/Exceptions/FormatterFailedToFormatValue.php @@ -2,31 +2,37 @@ namespace LastDragon_ru\LaraASP\Formatter\Exceptions; +use LastDragon_ru\LaraASP\Formatter\PackageException; use Throwable; use function sprintf; -class FailedToCreateCurrencyFormatter extends FailedToCreateFormatter { +class FormatterFailedToFormatValue extends PackageException { public function __construct( - protected ?string $currency, + protected string $formatter, protected string $format, + protected mixed $value, ?Throwable $previous = null, ) { parent::__construct( sprintf( - 'Failed to create Currency Formatter for `%s` currency and `%s` format.', - $this->getCurrency(), + 'Formatter `%s` failed to format value into `%s` format.', + $this->getFormatter(), $this->getFormat(), ), $previous, ); } - public function getCurrency(): ?string { - return $this->currency; + public function getFormatter(): string { + return $this->formatter; } public function getFormat(): string { return $this->format; } + + public function getValue(): mixed { + return $this->value; + } } diff --git a/packages/formatter/src/Formatter.php b/packages/formatter/src/Formatter.php index 0343df3b0..b46599472 100644 --- a/packages/formatter/src/Formatter.php +++ b/packages/formatter/src/Formatter.php @@ -9,6 +9,7 @@ use Illuminate\Support\Str; use Illuminate\Support\Traits\Macroable; use IntlDateFormatter; +use IntlException; use IntlTimeZone; use LastDragon_ru\LaraASP\Core\Application\ApplicationResolver; use LastDragon_ru\LaraASP\Core\Application\ConfigResolver; @@ -16,16 +17,8 @@ use LastDragon_ru\LaraASP\Formatter\Config\Formats\DurationFormatIntl; use LastDragon_ru\LaraASP\Formatter\Config\Formats\DurationFormatPattern; use LastDragon_ru\LaraASP\Formatter\Config\IntlOptions; -use LastDragon_ru\LaraASP\Formatter\Exceptions\FailedToCreateCurrencyFormatter; -use LastDragon_ru\LaraASP\Formatter\Exceptions\FailedToCreateDateTimeFormatter; -use LastDragon_ru\LaraASP\Formatter\Exceptions\FailedToCreateDurationFormatter; -use LastDragon_ru\LaraASP\Formatter\Exceptions\FailedToCreateFilesizeFormatter; -use LastDragon_ru\LaraASP\Formatter\Exceptions\FailedToCreateNumberFormatter; -use LastDragon_ru\LaraASP\Formatter\Exceptions\FailedToCreateSecretFormatter; -use LastDragon_ru\LaraASP\Formatter\Exceptions\FailedToFormatCurrency; -use LastDragon_ru\LaraASP\Formatter\Exceptions\FailedToFormatDateTime; -use LastDragon_ru\LaraASP\Formatter\Exceptions\FailedToFormatDuration; -use LastDragon_ru\LaraASP\Formatter\Exceptions\FailedToFormatNumber; +use LastDragon_ru\LaraASP\Formatter\Exceptions\FormatterFailedToCreateFormatter; +use LastDragon_ru\LaraASP\Formatter\Exceptions\FormatterFailedToFormatValue; use LastDragon_ru\LaraASP\Formatter\Utils\DurationFormatter; use NumberFormatter; use OutOfBoundsException; @@ -57,6 +50,13 @@ class Formatter { public const Filesize = 'filesize'; public const Disksize = 'disksize'; + private const FormatterNumber = 'Number'; + private const FormatterSecret = 'Secret'; + private const FormatterDateTime = 'DateTime'; + private const FormatterDuration = 'Duration'; + private const FormatterCurrency = 'Currency'; + private const FormatterFilesize = 'Filesize'; + private ?string $locale = null; private IntlTimeZone|DateTimeZone|string|null $timezone = null; @@ -225,7 +225,7 @@ protected function formatNumber(string $format, float|int|null $value): string { ?? null; if ($style === null) { - throw new FailedToCreateNumberFormatter($format); + throw new FormatterFailedToCreateFormatter(self::FormatterNumber, $format); } // Create @@ -239,14 +239,19 @@ protected function formatNumber(string $format, float|int|null $value): string { $config->global->number, ); } catch (Exception $exception) { - throw new FailedToCreateNumberFormatter($format, $exception); + throw new FormatterFailedToCreateFormatter(self::FormatterNumber, $format, $exception); } // Format $formatted = $formatter->format($value ?? 0); if ($formatted === false) { - throw new FailedToFormatNumber($format, $formatter->getErrorCode(), $formatter->getErrorMessage()); + throw new FormatterFailedToFormatValue( + self::FormatterNumber, + $format, + $value, + new IntlException($formatter->getErrorMessage(), $formatter->getErrorCode()), + ); } return $formatted; @@ -274,19 +279,23 @@ protected function formatCurrency(string $format, float|int|null $value, ?string $config->global->currency, ); } catch (Exception $exception) { - throw new FailedToCreateCurrencyFormatter($currency, $format, $exception); + throw new FormatterFailedToCreateFormatter( + self::FormatterCurrency, + "{$format}@".($currency ?? 'NULL'), + $exception, + ); } // Format - $currency ??= $formatter->getTextAttribute(NumberFormatter::CURRENCY_CODE); - $formatted = $formatter->formatCurrency((float) $value, $currency); + $as = $currency ?? $formatter->getTextAttribute(NumberFormatter::CURRENCY_CODE); + $formatted = $formatter->formatCurrency((float) $value, $as); if ($formatted === false) { - throw new FailedToFormatCurrency( - $currency, - $format, - $formatter->getErrorCode(), - $formatter->getErrorMessage(), + throw new FormatterFailedToFormatValue( + self::FormatterCurrency, + "{$format}@".($currency ?? 'NULL')." ({$as})", + $value, + new IntlException($formatter->getErrorMessage(), $formatter->getErrorCode()), ); } @@ -314,24 +323,28 @@ protected function formatDateTime(string $format, ?DateTimeInterface $value): st ?? null; if ($dateType === null || $timeType === null) { - throw new FailedToCreateDateTimeFormatter($format); + throw new FormatterFailedToCreateFormatter(self::FormatterDateTime, $format); } // Create try { $formatter = $this->getDateTimeFormatter($dateType, $timeType, $pattern); } catch (Exception $exception) { - throw new FailedToCreateDateTimeFormatter($format, $exception); + throw new FormatterFailedToCreateFormatter(self::FormatterDateTime, $format, $exception); } // Format $formatted = $formatter->format($value); if ($formatted === false) { - throw new FailedToFormatDateTime( + throw new FormatterFailedToFormatValue( + self::FormatterDateTime, $format, - $formatter->getErrorCode(), - $formatter->getErrorMessage(), + $value, + throw new IntlException( + $formatter->getErrorMessage(), + $formatter->getErrorCode(), + ), ); } @@ -353,7 +366,7 @@ protected function formatSecret(string $format, ?string $value): string { ?? null; if ($visible === null) { - throw new FailedToCreateSecretFormatter($format); + throw new FormatterFailedToCreateFormatter(self::FormatterSecret, $format); } // Format @@ -379,7 +392,10 @@ protected function formatDuration(string $format, DateInterval|float|int|null $v $formatted = match (true) { $type instanceof DurationFormatPattern => $this->formatDurationPattern($type, $value), $type instanceof DurationFormatIntl => $this->formatDurationIntl($config, $locale, $format, $value), - default => throw new FailedToCreateDurationFormatter($format), + default => throw new FormatterFailedToCreateFormatter( + self::FormatterDuration, + $format, + ), }; return $formatted; @@ -404,14 +420,22 @@ private function formatDurationIntl(Config $config, string $locale, string $form $globalIntl instanceof IntlOptions ? $globalIntl : null, ); } catch (Exception $exception) { - throw new FailedToCreateDurationFormatter($format, $exception); + throw new FormatterFailedToCreateFormatter(self::FormatterDuration, $format, $exception); } // Format $formatted = $formatter->format($value); if ($formatted === false) { - throw new FailedToFormatDuration($format, $formatter->getErrorCode(), $formatter->getErrorMessage()); + throw new FormatterFailedToFormatValue( + self::FormatterDuration, + $format, + $value, + throw new IntlException( + $formatter->getErrorMessage(), + $formatter->getErrorCode(), + ), + ); } return $formatted; @@ -438,7 +462,7 @@ protected function formatFilesize(string $format, string|float|int|null $bytes): ?? self::Decimal; if ($base === null || $units === null) { - throw new FailedToCreateFileSizeFormatter($format); + throw new FormatterFailedToCreateFormatter(self::FormatterFilesize, $format); } $unit = 0; From 3aa2f99f52144854e411f9e5093d389d62c9ec6a Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Thu, 7 Nov 2024 10:06:57 +0400 Subject: [PATCH 05/22] `\LastDragon_ru\LaraASP\Formatter\Formatter::getNumberFormatter()` simplification. --- .../src/Config/Formats/CurrencyConfig.php | 6 +- .../src/Config/Formats/CurrencyFormat.php | 11 ++-- .../src/Config/Formats/DurationFormatIntl.php | 11 ++-- .../src/Config/Formats/NumberConfig.php | 16 +++-- .../src/Config/Formats/NumberFormat.php | 17 ++--- ...{IntlOptions.php => IntlNumberOptions.php} | 13 +++- packages/formatter/src/Formatter.php | 66 ++++++++----------- 7 files changed, 65 insertions(+), 75 deletions(-) rename packages/formatter/src/Config/{IntlOptions.php => IntlNumberOptions.php} (71%) diff --git a/packages/formatter/src/Config/Formats/CurrencyConfig.php b/packages/formatter/src/Config/Formats/CurrencyConfig.php index d08b93a4f..84697a577 100644 --- a/packages/formatter/src/Config/Formats/CurrencyConfig.php +++ b/packages/formatter/src/Config/Formats/CurrencyConfig.php @@ -2,9 +2,9 @@ namespace LastDragon_ru\LaraASP\Formatter\Config\Formats; -use LastDragon_ru\LaraASP\Formatter\Config\IntlOptions; +use LastDragon_ru\LaraASP\Formatter\Config\IntlNumberOptions; -class CurrencyConfig extends IntlOptions { +class CurrencyConfig extends IntlNumberOptions { /** * @param array $symbols * @param array $attributes @@ -19,6 +19,6 @@ public function __construct( array $attributes = [], array $textAttributes = [], ) { - parent::__construct($symbols, $attributes, $textAttributes); + parent::__construct(null, null, $symbols, $attributes, $textAttributes); } } diff --git a/packages/formatter/src/Config/Formats/CurrencyFormat.php b/packages/formatter/src/Config/Formats/CurrencyFormat.php index e6dba4c5a..a46f5aef5 100644 --- a/packages/formatter/src/Config/Formats/CurrencyFormat.php +++ b/packages/formatter/src/Config/Formats/CurrencyFormat.php @@ -2,27 +2,24 @@ namespace LastDragon_ru\LaraASP\Formatter\Config\Formats; -use LastDragon_ru\LaraASP\Formatter\Config\IntlOptions; +use LastDragon_ru\LaraASP\Formatter\Config\IntlNumberOptions; use NumberFormatter; /** * @see NumberFormatter::formatCurrency() */ -class CurrencyFormat extends IntlOptions { +class CurrencyFormat extends IntlNumberOptions { /** * @param array $symbols * @param array $attributes * @param array $textAttributes */ public function __construct( - /** - * @see NumberFormatter::setPattern() - */ - public ?string $pattern = null, + ?string $pattern = null, array $symbols = [], array $attributes = [], array $textAttributes = [], ) { - parent::__construct($symbols, $attributes, $textAttributes); + parent::__construct(null, $pattern, $symbols, $attributes, $textAttributes); } } diff --git a/packages/formatter/src/Config/Formats/DurationFormatIntl.php b/packages/formatter/src/Config/Formats/DurationFormatIntl.php index 6d651d21e..f6f2c8f5d 100644 --- a/packages/formatter/src/Config/Formats/DurationFormatIntl.php +++ b/packages/formatter/src/Config/Formats/DurationFormatIntl.php @@ -2,27 +2,24 @@ namespace LastDragon_ru\LaraASP\Formatter\Config\Formats; -use LastDragon_ru\LaraASP\Formatter\Config\IntlOptions; +use LastDragon_ru\LaraASP\Formatter\Config\IntlNumberOptions; use NumberFormatter; /** * @see NumberFormatter */ -class DurationFormatIntl extends IntlOptions { +class DurationFormatIntl extends IntlNumberOptions { /** * @param array $symbols * @param array $attributes * @param array $textAttributes */ public function __construct( - /** - * @see NumberFormatter::setPattern() - */ - public ?string $pattern = null, + ?string $pattern = null, array $symbols = [], array $attributes = [], array $textAttributes = [], ) { - parent::__construct($symbols, $attributes, $textAttributes); + parent::__construct(null, $pattern, $symbols, $attributes, $textAttributes); } } diff --git a/packages/formatter/src/Config/Formats/NumberConfig.php b/packages/formatter/src/Config/Formats/NumberConfig.php index b35b1fdbd..555f1428d 100644 --- a/packages/formatter/src/Config/Formats/NumberConfig.php +++ b/packages/formatter/src/Config/Formats/NumberConfig.php @@ -2,23 +2,27 @@ namespace LastDragon_ru\LaraASP\Formatter\Config\Formats; -use LastDragon_ru\LaraASP\Formatter\Config\IntlOptions; +use LastDragon_ru\LaraASP\Formatter\Config\IntlNumberOptions; +use NumberFormatter; -class NumberConfig extends IntlOptions { +class NumberConfig extends IntlNumberOptions { /** - * @param array $symbols - * @param array $attributes - * @param array $textAttributes + * @param NumberFormatter::*|null $style + * @param array $symbols + * @param array $attributes + * @param array $textAttributes */ public function __construct( /** * @var array */ public array $formats = [], + ?int $style = null, + ?string $pattern = null, array $symbols = [], array $attributes = [], array $textAttributes = [], ) { - parent::__construct($symbols, $attributes, $textAttributes); + parent::__construct($style, $pattern, $symbols, $attributes, $textAttributes); } } diff --git a/packages/formatter/src/Config/Formats/NumberFormat.php b/packages/formatter/src/Config/Formats/NumberFormat.php index 4b0dd1ce5..e0ac71baf 100644 --- a/packages/formatter/src/Config/Formats/NumberFormat.php +++ b/packages/formatter/src/Config/Formats/NumberFormat.php @@ -2,31 +2,26 @@ namespace LastDragon_ru\LaraASP\Formatter\Config\Formats; -use LastDragon_ru\LaraASP\Formatter\Config\IntlOptions; +use LastDragon_ru\LaraASP\Formatter\Config\IntlNumberOptions; use NumberFormatter; /** * @see NumberFormatter */ -class NumberFormat extends IntlOptions { +class NumberFormat extends IntlNumberOptions { /** + * @param ?NumberFormatter::* $style * @param array $symbols * @param array $attributes * @param array $textAttributes */ public function __construct( - /** - * @var NumberFormatter::* - */ - public ?int $style = null, - /** - * @see NumberFormatter::setPattern() - */ - public ?string $pattern = null, + ?int $style = null, + ?string $pattern = null, array $symbols = [], array $attributes = [], array $textAttributes = [], ) { - parent::__construct($symbols, $attributes, $textAttributes); + parent::__construct($style, $pattern, $symbols, $attributes, $textAttributes); } } diff --git a/packages/formatter/src/Config/IntlOptions.php b/packages/formatter/src/Config/IntlNumberOptions.php similarity index 71% rename from packages/formatter/src/Config/IntlOptions.php rename to packages/formatter/src/Config/IntlNumberOptions.php index e960732c5..1ccdbdf86 100644 --- a/packages/formatter/src/Config/IntlOptions.php +++ b/packages/formatter/src/Config/IntlNumberOptions.php @@ -5,8 +5,19 @@ use LastDragon_ru\LaraASP\Core\Application\Configuration\Configuration; use NumberFormatter; -class IntlOptions extends Configuration { +/** + * @see NumberFormatter + */ +class IntlNumberOptions extends Configuration { public function __construct( + /** + * @var NumberFormatter::*|null + */ + public ?int $style = null, + /** + * @see NumberFormatter::setPattern() + */ + public ?string $pattern = null, /** * @see NumberFormatter::setSymbol() * diff --git a/packages/formatter/src/Formatter.php b/packages/formatter/src/Formatter.php index b46599472..3395271da 100644 --- a/packages/formatter/src/Formatter.php +++ b/packages/formatter/src/Formatter.php @@ -11,12 +11,13 @@ use IntlDateFormatter; use IntlException; use IntlTimeZone; +use InvalidArgumentException; use LastDragon_ru\LaraASP\Core\Application\ApplicationResolver; use LastDragon_ru\LaraASP\Core\Application\ConfigResolver; use LastDragon_ru\LaraASP\Formatter\Config\Config; use LastDragon_ru\LaraASP\Formatter\Config\Formats\DurationFormatIntl; use LastDragon_ru\LaraASP\Formatter\Config\Formats\DurationFormatPattern; -use LastDragon_ru\LaraASP\Formatter\Config\IntlOptions; +use LastDragon_ru\LaraASP\Formatter\Config\IntlNumberOptions; use LastDragon_ru\LaraASP\Formatter\Exceptions\FormatterFailedToCreateFormatter; use LastDragon_ru\LaraASP\Formatter\Exceptions\FormatterFailedToFormatValue; use LastDragon_ru\LaraASP\Formatter\Utils\DurationFormatter; @@ -214,25 +215,11 @@ protected function getTranslation(array|string $key, array $replace = []): strin } protected function formatNumber(string $format, float|int|null $value): string { - // Definition - $config = $this->configuration->getInstance(); - $locale = $this->getLocale(); - $style = $config->global->number->formats[$format]->style - ?? $config->locales[$locale]->number->formats[$format]->style - ?? null; - $pattern = $config->global->number->formats[$format]->pattern - ?? $config->locales[$locale]->number->formats[$format]->pattern - ?? null; - - if ($style === null) { - throw new FormatterFailedToCreateFormatter(self::FormatterNumber, $format); - } - // Create try { + $config = $this->configuration->getInstance(); + $locale = $this->getLocale(); $formatter = $this->getNumberFormatter( - $style, - $pattern, $config->locales[$locale]->number->formats[$format] ?? null, $config->locales[$locale]->number ?? null, $config->global->number->formats[$format] ?? null, @@ -261,18 +248,12 @@ protected function formatNumber(string $format, float|int|null $value): string { * @param non-empty-string|null $currency */ protected function formatCurrency(string $format, float|int|null $value, ?string $currency = null): string { - // Prepare - $config = $this->configuration->getInstance(); - $locale = $this->getLocale(); - $pattern = $config->global->currency->formats[$format]->pattern - ?? $config->locales[$locale]->currency->formats[$format]->pattern - ?? null; - // Create try { + $config = $this->configuration->getInstance(); + $locale = $this->getLocale(); $formatter = $this->getNumberFormatter( - NumberFormatter::CURRENCY, - $pattern, + new IntlNumberOptions(NumberFormatter::CURRENCY), $config->locales[$locale]->currency->formats[$format] ?? null, $config->locales[$locale]->currency ?? null, $config->global->currency->formats[$format] ?? null, @@ -410,14 +391,10 @@ private function formatDurationIntl(Config $config, string $locale, string $form try { $formatIntl = $config->locales[$locale]->duration->formats[$format] ?? null; $globalIntl = $config->global->duration->formats[$format] ?? null; - $pattern = $config->global->duration->formats[$format]->pattern - ?? $config->locales[$locale]->duration->formats[$format]->pattern - ?? null; $formatter = $this->getNumberFormatter( - NumberFormatter::DURATION, - $pattern, - $formatIntl instanceof IntlOptions ? $formatIntl : null, - $globalIntl instanceof IntlOptions ? $globalIntl : null, + new IntlNumberOptions(NumberFormatter::DURATION), + $formatIntl instanceof IntlNumberOptions ? $formatIntl : null, + $globalIntl instanceof IntlNumberOptions ? $globalIntl : null, ); } catch (Exception $exception) { throw new FormatterFailedToCreateFormatter(self::FormatterDuration, $format, $exception); @@ -503,26 +480,35 @@ private function getDateTimeFormatter(int $dateType, int $timeType, ?string $pat return $formatter; } - private function getNumberFormatter(int $style, ?string $pattern, ?IntlOptions ...$options): NumberFormatter { - // Create - $locale = $this->getLocale(); - $formatter = new NumberFormatter($locale, $style, $pattern); - + private function getNumberFormatter(?IntlNumberOptions ...$options): NumberFormatter { // Collect Intl options - $textAttributes = []; - $attributes = []; + $style = null; + $pattern = null; $symbols = []; + $attributes = []; + $textAttributes = []; foreach ($options as $intl) { if ($intl === null) { continue; } + $style ??= $intl->style; + $pattern ??= $intl->pattern; $symbols += $intl->symbols; $attributes += $intl->attributes; $textAttributes += $intl->textAttributes; } + // Possible? + if ($style === null) { + throw new InvalidArgumentException('The `$style` in unknown'); + } + + // Create + $locale = $this->getLocale(); + $formatter = new NumberFormatter($locale, $style, $pattern); + // Apply foreach ($attributes as $attribute => $value) { if (!$formatter->setAttribute($attribute, $value)) { From e7b9f2f82d7e7c1c0c30c215af17dc60d6008be7 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Fri, 8 Nov 2024 10:10:56 +0400 Subject: [PATCH 06/22] Number formatting extracted into `\LastDragon_ru\LaraASP\Formatter\Formatters\Number\Formatter`. --- .../src/Config/Formats/CurrencyConfig.php | 4 +- .../src/Config/Formats/CurrencyFormat.php | 4 +- .../src/Config/Formats/DurationFormatIntl.php | 4 +- .../src/Config/Formats/NumberConfig.php | 4 +- .../src/Config/Formats/NumberFormat.php | 4 +- packages/formatter/src/Formatter.php | 145 +++--------------- .../src/Formatters/Number/Formatter.php | 106 +++++++++++++ .../Number/Options.php} | 4 +- 8 files changed, 138 insertions(+), 137 deletions(-) create mode 100644 packages/formatter/src/Formatters/Number/Formatter.php rename packages/formatter/src/{Config/IntlNumberOptions.php => Formatters/Number/Options.php} (90%) diff --git a/packages/formatter/src/Config/Formats/CurrencyConfig.php b/packages/formatter/src/Config/Formats/CurrencyConfig.php index 84697a577..cf4429349 100644 --- a/packages/formatter/src/Config/Formats/CurrencyConfig.php +++ b/packages/formatter/src/Config/Formats/CurrencyConfig.php @@ -2,9 +2,9 @@ namespace LastDragon_ru\LaraASP\Formatter\Config\Formats; -use LastDragon_ru\LaraASP\Formatter\Config\IntlNumberOptions; +use LastDragon_ru\LaraASP\Formatter\Formatters\Number\Options; -class CurrencyConfig extends IntlNumberOptions { +class CurrencyConfig extends Options { /** * @param array $symbols * @param array $attributes diff --git a/packages/formatter/src/Config/Formats/CurrencyFormat.php b/packages/formatter/src/Config/Formats/CurrencyFormat.php index a46f5aef5..756e37a5f 100644 --- a/packages/formatter/src/Config/Formats/CurrencyFormat.php +++ b/packages/formatter/src/Config/Formats/CurrencyFormat.php @@ -2,13 +2,13 @@ namespace LastDragon_ru\LaraASP\Formatter\Config\Formats; -use LastDragon_ru\LaraASP\Formatter\Config\IntlNumberOptions; +use LastDragon_ru\LaraASP\Formatter\Formatters\Number\Options; use NumberFormatter; /** * @see NumberFormatter::formatCurrency() */ -class CurrencyFormat extends IntlNumberOptions { +class CurrencyFormat extends Options { /** * @param array $symbols * @param array $attributes diff --git a/packages/formatter/src/Config/Formats/DurationFormatIntl.php b/packages/formatter/src/Config/Formats/DurationFormatIntl.php index f6f2c8f5d..e20b94569 100644 --- a/packages/formatter/src/Config/Formats/DurationFormatIntl.php +++ b/packages/formatter/src/Config/Formats/DurationFormatIntl.php @@ -2,13 +2,13 @@ namespace LastDragon_ru\LaraASP\Formatter\Config\Formats; -use LastDragon_ru\LaraASP\Formatter\Config\IntlNumberOptions; +use LastDragon_ru\LaraASP\Formatter\Formatters\Number\Options; use NumberFormatter; /** * @see NumberFormatter */ -class DurationFormatIntl extends IntlNumberOptions { +class DurationFormatIntl extends Options { /** * @param array $symbols * @param array $attributes diff --git a/packages/formatter/src/Config/Formats/NumberConfig.php b/packages/formatter/src/Config/Formats/NumberConfig.php index 555f1428d..19257ce43 100644 --- a/packages/formatter/src/Config/Formats/NumberConfig.php +++ b/packages/formatter/src/Config/Formats/NumberConfig.php @@ -2,10 +2,10 @@ namespace LastDragon_ru\LaraASP\Formatter\Config\Formats; -use LastDragon_ru\LaraASP\Formatter\Config\IntlNumberOptions; +use LastDragon_ru\LaraASP\Formatter\Formatters\Number\Options; use NumberFormatter; -class NumberConfig extends IntlNumberOptions { +class NumberConfig extends Options { /** * @param NumberFormatter::*|null $style * @param array $symbols diff --git a/packages/formatter/src/Config/Formats/NumberFormat.php b/packages/formatter/src/Config/Formats/NumberFormat.php index e0ac71baf..3623513bc 100644 --- a/packages/formatter/src/Config/Formats/NumberFormat.php +++ b/packages/formatter/src/Config/Formats/NumberFormat.php @@ -2,13 +2,13 @@ namespace LastDragon_ru\LaraASP\Formatter\Config\Formats; -use LastDragon_ru\LaraASP\Formatter\Config\IntlNumberOptions; +use LastDragon_ru\LaraASP\Formatter\Formatters\Number\Options; use NumberFormatter; /** * @see NumberFormatter */ -class NumberFormat extends IntlNumberOptions { +class NumberFormat extends Options { /** * @param ?NumberFormatter::* $style * @param array $symbols diff --git a/packages/formatter/src/Formatter.php b/packages/formatter/src/Formatter.php index 3395271da..12f92d693 100644 --- a/packages/formatter/src/Formatter.php +++ b/packages/formatter/src/Formatter.php @@ -11,18 +11,17 @@ use IntlDateFormatter; use IntlException; use IntlTimeZone; -use InvalidArgumentException; use LastDragon_ru\LaraASP\Core\Application\ApplicationResolver; use LastDragon_ru\LaraASP\Core\Application\ConfigResolver; use LastDragon_ru\LaraASP\Formatter\Config\Config; use LastDragon_ru\LaraASP\Formatter\Config\Formats\DurationFormatIntl; use LastDragon_ru\LaraASP\Formatter\Config\Formats\DurationFormatPattern; -use LastDragon_ru\LaraASP\Formatter\Config\IntlNumberOptions; use LastDragon_ru\LaraASP\Formatter\Exceptions\FormatterFailedToCreateFormatter; use LastDragon_ru\LaraASP\Formatter\Exceptions\FormatterFailedToFormatValue; +use LastDragon_ru\LaraASP\Formatter\Formatters\Number\Formatter as NumberFormatter; +use LastDragon_ru\LaraASP\Formatter\Formatters\Number\Options; use LastDragon_ru\LaraASP\Formatter\Utils\DurationFormatter; -use NumberFormatter; -use OutOfBoundsException; +use NumberFormatter as IntlNumberFormatter; use function bccomp; use function bcdiv; @@ -219,26 +218,16 @@ protected function formatNumber(string $format, float|int|null $value): string { try { $config = $this->configuration->getInstance(); $locale = $this->getLocale(); - $formatter = $this->getNumberFormatter( + $formatter = new NumberFormatter( + $locale, $config->locales[$locale]->number->formats[$format] ?? null, $config->locales[$locale]->number ?? null, $config->global->number->formats[$format] ?? null, $config->global->number, ); + $formatted = $formatter->formatNumber($value ?? 0); } catch (Exception $exception) { - throw new FormatterFailedToCreateFormatter(self::FormatterNumber, $format, $exception); - } - - // Format - $formatted = $formatter->format($value ?? 0); - - if ($formatted === false) { - throw new FormatterFailedToFormatValue( - self::FormatterNumber, - $format, - $value, - new IntlException($formatter->getErrorMessage(), $formatter->getErrorCode()), - ); + throw new FormatterFailedToFormatValue(self::FormatterNumber, $format, $value, $exception); } return $formatted; @@ -252,31 +241,21 @@ protected function formatCurrency(string $format, float|int|null $value, ?string try { $config = $this->configuration->getInstance(); $locale = $this->getLocale(); - $formatter = $this->getNumberFormatter( - new IntlNumberOptions(NumberFormatter::CURRENCY), + $formatter = new NumberFormatter( + $locale, + new Options(IntlNumberFormatter::CURRENCY), $config->locales[$locale]->currency->formats[$format] ?? null, $config->locales[$locale]->currency ?? null, $config->global->currency->formats[$format] ?? null, $config->global->currency, ); + $formatted = $formatter->formatCurrency($value ?? 0, $currency); } catch (Exception $exception) { - throw new FormatterFailedToCreateFormatter( - self::FormatterCurrency, - "{$format}@".($currency ?? 'NULL'), - $exception, - ); - } - - // Format - $as = $currency ?? $formatter->getTextAttribute(NumberFormatter::CURRENCY_CODE); - $formatted = $formatter->formatCurrency((float) $value, $as); - - if ($formatted === false) { throw new FormatterFailedToFormatValue( self::FormatterCurrency, - "{$format}@".($currency ?? 'NULL')." ({$as})", + "{$format}@".($currency ?? 'NULL'), $value, - new IntlException($formatter->getErrorMessage(), $formatter->getErrorCode()), + $exception, ); } @@ -387,32 +366,18 @@ private function formatDurationPattern(DurationFormatPattern $config, float|int } private function formatDurationIntl(Config $config, string $locale, string $format, float|int $value): string { - // Create try { $formatIntl = $config->locales[$locale]->duration->formats[$format] ?? null; $globalIntl = $config->global->duration->formats[$format] ?? null; - $formatter = $this->getNumberFormatter( - new IntlNumberOptions(NumberFormatter::DURATION), - $formatIntl instanceof IntlNumberOptions ? $formatIntl : null, - $globalIntl instanceof IntlNumberOptions ? $globalIntl : null, + $formatter = new NumberFormatter( + $locale, + new Options(IntlNumberFormatter::DURATION), + $formatIntl instanceof Options ? $formatIntl : null, + $globalIntl instanceof Options ? $globalIntl : null, ); + $formatted = $formatter->formatNumber($value); } catch (Exception $exception) { - throw new FormatterFailedToCreateFormatter(self::FormatterDuration, $format, $exception); - } - - // Format - $formatted = $formatter->format($value); - - if ($formatted === false) { - throw new FormatterFailedToFormatValue( - self::FormatterDuration, - $format, - $value, - throw new IntlException( - $formatter->getErrorMessage(), - $formatter->getErrorCode(), - ), - ); + throw new FormatterFailedToFormatValue(self::FormatterDuration, $format, $value, $exception); } return $formatted; @@ -479,75 +444,5 @@ private function getDateTimeFormatter(int $dateType, int $timeType, ?string $pat return $formatter; } - - private function getNumberFormatter(?IntlNumberOptions ...$options): NumberFormatter { - // Collect Intl options - $style = null; - $pattern = null; - $symbols = []; - $attributes = []; - $textAttributes = []; - - foreach ($options as $intl) { - if ($intl === null) { - continue; - } - - $style ??= $intl->style; - $pattern ??= $intl->pattern; - $symbols += $intl->symbols; - $attributes += $intl->attributes; - $textAttributes += $intl->textAttributes; - } - - // Possible? - if ($style === null) { - throw new InvalidArgumentException('The `$style` in unknown'); - } - - // Create - $locale = $this->getLocale(); - $formatter = new NumberFormatter($locale, $style, $pattern); - - // Apply - foreach ($attributes as $attribute => $value) { - if (!$formatter->setAttribute($attribute, $value)) { - throw new OutOfBoundsException( - sprintf( - '%s::setAttribute() failed: `%s` is unknown/invalid.', - NumberFormatter::class, - $attribute, - ), - ); - } - } - - foreach ($symbols as $symbol => $value) { - if (!$formatter->setSymbol($symbol, $value)) { - throw new OutOfBoundsException( - sprintf( - '%s::setSymbol() failed: `%s` is unknown/invalid.', - NumberFormatter::class, - $symbol, - ), - ); - } - } - - foreach ($textAttributes as $attribute => $value) { - if (!$formatter->setTextAttribute($attribute, $value)) { - throw new OutOfBoundsException( - sprintf( - '%s::setTextAttribute() failed: `%s` is unknown/invalid.', - NumberFormatter::class, - $attribute, - ), - ); - } - } - - // Return - return $formatter; - } // } diff --git a/packages/formatter/src/Formatters/Number/Formatter.php b/packages/formatter/src/Formatters/Number/Formatter.php new file mode 100644 index 000000000..ac5b5caad --- /dev/null +++ b/packages/formatter/src/Formatters/Number/Formatter.php @@ -0,0 +1,106 @@ +style; + $pattern ??= $intl->pattern; + $symbols += $intl->symbols; + $attributes += $intl->attributes; + $textAttributes += $intl->textAttributes; + } + + // Possible? + if ($style === null) { + throw new InvalidArgumentException('The `$style` in unknown.'); + } + + // Create + $pattern = $pattern !== '' ? $pattern : null; + $this->formatter = new NumberFormatter($locale, $style, $pattern); + + // Apply + foreach ($attributes as $attribute => $value) { + if (!$this->formatter->setAttribute($attribute, $value)) { + throw new OutOfBoundsException( + sprintf( + 'Attribute `%s` is unknown/invalid.', + $attribute, + ), + ); + } + } + + foreach ($symbols as $symbol => $value) { + if (!$this->formatter->setSymbol($symbol, $value)) { + throw new OutOfBoundsException( + sprintf( + 'Symbol `%s` is unknown/invalid.', + $symbol, + ), + ); + } + } + + foreach ($textAttributes as $attribute => $value) { + if (!$this->formatter->setTextAttribute($attribute, $value)) { + throw new OutOfBoundsException( + sprintf( + 'TextAttribute `%s` is unknown/invalid.', + $attribute, + ), + ); + } + } + } + + public function formatNumber(float|int $value): string { + $formatted = $this->formatter->format($value); + + if ($formatted === false) { + throw new IntlException($this->formatter->getErrorMessage(), $this->formatter->getErrorCode()); + } + + return $formatted; + } + + public function formatCurrency(float|int $value, ?string $currency = null): string { + $currency ??= $this->formatter->getTextAttribute(NumberFormatter::CURRENCY_CODE); + $formatted = $this->formatter->formatCurrency($value, $currency); + + if ($formatted === false) { + throw new IntlException($this->formatter->getErrorMessage(), $this->formatter->getErrorCode()); + } + + return $formatted; + } +} diff --git a/packages/formatter/src/Config/IntlNumberOptions.php b/packages/formatter/src/Formatters/Number/Options.php similarity index 90% rename from packages/formatter/src/Config/IntlNumberOptions.php rename to packages/formatter/src/Formatters/Number/Options.php index 1ccdbdf86..d21d59191 100644 --- a/packages/formatter/src/Config/IntlNumberOptions.php +++ b/packages/formatter/src/Formatters/Number/Options.php @@ -1,6 +1,6 @@ Date: Fri, 8 Nov 2024 10:30:30 +0400 Subject: [PATCH 07/22] DateTime formatting extracted into `\LastDragon_ru\LaraASP\Formatter\Formatters\DateTime\Formatter`. --- .../src/Config/Formats/DateTimeFormat.php | 21 +------ packages/formatter/src/Formatter.php | 61 ++++-------------- .../src/Formatters/DateTime/Formatter.php | 63 +++++++++++++++++++ .../src/Formatters/DateTime/Options.php | 28 +++++++++ 4 files changed, 107 insertions(+), 66 deletions(-) create mode 100644 packages/formatter/src/Formatters/DateTime/Formatter.php create mode 100644 packages/formatter/src/Formatters/DateTime/Options.php diff --git a/packages/formatter/src/Config/Formats/DateTimeFormat.php b/packages/formatter/src/Config/Formats/DateTimeFormat.php index 1ea9670a2..384c89b4e 100644 --- a/packages/formatter/src/Config/Formats/DateTimeFormat.php +++ b/packages/formatter/src/Config/Formats/DateTimeFormat.php @@ -3,27 +3,12 @@ namespace LastDragon_ru\LaraASP\Formatter\Config\Formats; use IntlDateFormatter; -use LastDragon_ru\LaraASP\Core\Application\Configuration\Configuration; +use LastDragon_ru\LaraASP\Formatter\Formatters\DateTime\Options; /** * @see IntlDateFormatter * @see https://unicode-org.github.io/icu/userguide/format_parse/datetime/#formatting-dates-and-times */ -class DateTimeFormat extends Configuration { - public function __construct( - /** - * @var IntlDateFormatter::* - */ - public ?int $dateType = null, - /** - * @var IntlDateFormatter::* - */ - public ?int $timeType = null, - /** - * @see IntlDateFormatter::setPattern() - */ - public ?string $pattern = null, - ) { - parent::__construct(); - } +class DateTimeFormat extends Options { + // empty } diff --git a/packages/formatter/src/Formatter.php b/packages/formatter/src/Formatter.php index 12f92d693..4a41245d3 100644 --- a/packages/formatter/src/Formatter.php +++ b/packages/formatter/src/Formatter.php @@ -8,8 +8,6 @@ use Exception; use Illuminate\Support\Str; use Illuminate\Support\Traits\Macroable; -use IntlDateFormatter; -use IntlException; use IntlTimeZone; use LastDragon_ru\LaraASP\Core\Application\ApplicationResolver; use LastDragon_ru\LaraASP\Core\Application\ConfigResolver; @@ -18,6 +16,7 @@ use LastDragon_ru\LaraASP\Formatter\Config\Formats\DurationFormatPattern; use LastDragon_ru\LaraASP\Formatter\Exceptions\FormatterFailedToCreateFormatter; use LastDragon_ru\LaraASP\Formatter\Exceptions\FormatterFailedToFormatValue; +use LastDragon_ru\LaraASP\Formatter\Formatters\DateTime\Formatter as DateTimeFormatter; use LastDragon_ru\LaraASP\Formatter\Formatters\Number\Formatter as NumberFormatter; use LastDragon_ru\LaraASP\Formatter\Formatters\Number\Options; use LastDragon_ru\LaraASP\Formatter\Utils\DurationFormatter; @@ -269,43 +268,20 @@ protected function formatDateTime(string $format, ?DateTimeInterface $value): st return ''; } - // Prepare - $config = $this->configuration->getInstance(); - $locale = $this->getLocale(); - $pattern = $config->global->datetime->formats[$format]->pattern - ?? $config->locales[$locale]->datetime->formats[$format]->pattern - ?? null; - $dateType = $config->global->datetime->formats[$format]->dateType - ?? $config->locales[$locale]->datetime->formats[$format]->dateType - ?? null; - $timeType = $config->global->datetime->formats[$format]->timeType - ?? $config->locales[$locale]->datetime->formats[$format]->timeType - ?? null; - - if ($dateType === null || $timeType === null) { - throw new FormatterFailedToCreateFormatter(self::FormatterDateTime, $format); - } - - // Create - try { - $formatter = $this->getDateTimeFormatter($dateType, $timeType, $pattern); - } catch (Exception $exception) { - throw new FormatterFailedToCreateFormatter(self::FormatterDateTime, $format, $exception); - } - // Format - $formatted = $formatter->format($value); - - if ($formatted === false) { - throw new FormatterFailedToFormatValue( - self::FormatterDateTime, - $format, - $value, - throw new IntlException( - $formatter->getErrorMessage(), - $formatter->getErrorCode(), - ), + try { + $config = $this->configuration->getInstance(); + $locale = $this->getLocale(); + $timezone = $this->getTimezone(); + $formatter = new DateTimeFormatter( + $locale, + $timezone, + $config->locales[$locale]->datetime->formats[$format] ?? null, + $config->global->datetime->formats[$format] ?? null, ); + $formatted = $formatter->format($value); + } catch (Exception $exception) { + throw new FormatterFailedToFormatValue(self::FormatterDateTime, $format, $value, $exception); } // Return @@ -434,15 +410,4 @@ protected function formatFilesize(string $format, string|float|int|null $bytes): return $formatted; } // - - // - // ========================================================================= - private function getDateTimeFormatter(int $dateType, int $timeType, ?string $pattern): IntlDateFormatter { - $locale = $this->getLocale(); - $timezone = $this->getTimezone(); - $formatter = new IntlDateFormatter($locale, $dateType, $timeType, $timezone, null, $pattern); - - return $formatter; - } - // } diff --git a/packages/formatter/src/Formatters/DateTime/Formatter.php b/packages/formatter/src/Formatters/DateTime/Formatter.php new file mode 100644 index 000000000..ea27710c0 --- /dev/null +++ b/packages/formatter/src/Formatters/DateTime/Formatter.php @@ -0,0 +1,63 @@ +dateType; + $timeType ??= $intl->timeType; + $pattern ??= $intl->pattern; + } + + // Possible? + if ($dateType === null) { + throw new InvalidArgumentException('The `$dateType` in unknown.'); + } + + if ($timeType === null) { + throw new InvalidArgumentException('The `$timeType` in unknown.'); + } + + // Create + $pattern = $pattern !== '' ? $pattern : null; + $this->formatter = new IntlDateFormatter($locale, $dateType, $timeType, $timezone, null, $pattern); + } + + public function format(DateTimeInterface $value): string { + $formatted = $this->formatter->format($value); + + if ($formatted === false) { + throw new IntlException($this->formatter->getErrorMessage(), $this->formatter->getErrorCode()); + } + + return $formatted; + } +} diff --git a/packages/formatter/src/Formatters/DateTime/Options.php b/packages/formatter/src/Formatters/DateTime/Options.php new file mode 100644 index 000000000..cd3cc48bb --- /dev/null +++ b/packages/formatter/src/Formatters/DateTime/Options.php @@ -0,0 +1,28 @@ + Date: Fri, 8 Nov 2024 11:22:07 +0400 Subject: [PATCH 08/22] Secret formatting extracted into `\LastDragon_ru\LaraASP\Formatter\Formatters\Secret\Formatter`. --- .../src/Config/Formats/SecretFormat.php | 15 ++---- packages/formatter/src/Formatter.php | 33 +++++------- .../src/Formatters/Secret/Formatter.php | 53 +++++++++++++++++++ .../src/Formatters/Secret/Options.php | 18 +++++++ 4 files changed, 86 insertions(+), 33 deletions(-) create mode 100644 packages/formatter/src/Formatters/Secret/Formatter.php create mode 100644 packages/formatter/src/Formatters/Secret/Options.php diff --git a/packages/formatter/src/Config/Formats/SecretFormat.php b/packages/formatter/src/Config/Formats/SecretFormat.php index b852ce784..d5f72b317 100644 --- a/packages/formatter/src/Config/Formats/SecretFormat.php +++ b/packages/formatter/src/Config/Formats/SecretFormat.php @@ -2,17 +2,8 @@ namespace LastDragon_ru\LaraASP\Formatter\Config\Formats; -use LastDragon_ru\LaraASP\Core\Application\Configuration\Configuration; +use LastDragon_ru\LaraASP\Formatter\Formatters\Secret\Options; -class SecretFormat extends Configuration { - public function __construct( - /** - * Number of how many characters should be shown. - * - * @var int<0, max> - */ - public int $visible, - ) { - parent::__construct(); - } +class SecretFormat extends Options { + // empty } diff --git a/packages/formatter/src/Formatter.php b/packages/formatter/src/Formatter.php index 4a41245d3..5a8ddced2 100644 --- a/packages/formatter/src/Formatter.php +++ b/packages/formatter/src/Formatter.php @@ -19,6 +19,7 @@ use LastDragon_ru\LaraASP\Formatter\Formatters\DateTime\Formatter as DateTimeFormatter; use LastDragon_ru\LaraASP\Formatter\Formatters\Number\Formatter as NumberFormatter; use LastDragon_ru\LaraASP\Formatter\Formatters\Number\Options; +use LastDragon_ru\LaraASP\Formatter\Formatters\Secret\Formatter as SecretFormatter; use LastDragon_ru\LaraASP\Formatter\Utils\DurationFormatter; use NumberFormatter as IntlNumberFormatter; @@ -26,11 +27,8 @@ use function bcdiv; use function is_float; use function is_null; -use function mb_str_pad; use function mb_strlen; -use function mb_substr; use function sprintf; -use function str_replace; use function trim; class Formatter { @@ -294,25 +292,18 @@ protected function formatSecret(string $format, ?string $value): string { return ''; } - // Prepare - $config = $this->configuration->getInstance(); - $locale = $this->getLocale(); - $visible = $config->global->secret->formats[$format]->visible - ?? $config->locales[$locale]->secret->formats[$format]->visible - ?? null; - - if ($visible === null) { - throw new FormatterFailedToCreateFormatter(self::FormatterSecret, $format); - } - // Format - $length = mb_strlen($value); - $hidden = $length - $visible; - $formatted = match (true) { - $length <= $visible => mb_str_pad('*', $length, '*'), - $hidden < $visible => str_replace(mb_substr($value, 0, $visible), mb_str_pad('*', $visible, '*'), $value), - default => str_replace(mb_substr($value, 0, $hidden), mb_str_pad('*', $hidden, '*'), $value), - }; + try { + $config = $this->configuration->getInstance(); + $locale = $this->getLocale(); + $formatter = new SecretFormatter( + $config->locales[$locale]->secret->formats[$format] ?? null, + $config->global->secret->formats[$format] ?? null, + ); + $formatted = $formatter->format($value); + } catch (Exception $exception) { + throw new FormatterFailedToFormatValue(self::FormatterSecret, $format, $value, $exception); + } // Return return $formatted; diff --git a/packages/formatter/src/Formatters/Secret/Formatter.php b/packages/formatter/src/Formatters/Secret/Formatter.php new file mode 100644 index 000000000..04e02a8b8 --- /dev/null +++ b/packages/formatter/src/Formatters/Secret/Formatter.php @@ -0,0 +1,53 @@ +visible; + } + + // Possible? + if ($visible === null) { + throw new InvalidArgumentException('The `$visible` in unknown.'); + } + + // Save + $this->visible = $visible; + } + + public function format(string $value): string { + // Format + $visible = $this->visible; + $length = mb_strlen($value); + $hidden = $length - $visible; + $formatted = match (true) { + $length <= $visible => mb_str_pad('*', $length, '*'), + $hidden < $visible => str_replace(mb_substr($value, 0, $visible), mb_str_pad('*', $visible, '*'), $value), + default => str_replace(mb_substr($value, 0, $hidden), mb_str_pad('*', $hidden, '*'), $value), + }; + + // Return + return $formatted; + } +} diff --git a/packages/formatter/src/Formatters/Secret/Options.php b/packages/formatter/src/Formatters/Secret/Options.php new file mode 100644 index 000000000..cc15c35f8 --- /dev/null +++ b/packages/formatter/src/Formatters/Secret/Options.php @@ -0,0 +1,18 @@ + + */ + public int $visible, + ) { + parent::__construct(); + } +} From 2cf209e9816906f2eb4170942f57ae20ca56573d Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Sat, 9 Nov 2024 10:38:32 +0400 Subject: [PATCH 09/22] Duration formatting extracted into `\LastDragon_ru\LaraASP\Formatter\Formatters\Duration\Formatter`. --- .../src/Config/Formats/DurationFormatIntl.php | 18 +----- .../Config/Formats/DurationFormatPattern.php | 14 ++--- packages/formatter/src/Formatter.php | 43 +++---------- .../src/Formatters/Duration/Formatter.php | 62 +++++++++++++++++++ .../src/Formatters/Duration/IntlOptions.php | 9 +++ .../Duration/PatternFormatter.php} | 6 +- .../Duration/PatternFormatterTest.php} | 12 ++-- .../Formatters/Duration/PatternOptions.php | 16 +++++ .../src/Formatters/Number/Formatter.php | 4 ++ 9 files changed, 118 insertions(+), 66 deletions(-) create mode 100644 packages/formatter/src/Formatters/Duration/Formatter.php create mode 100644 packages/formatter/src/Formatters/Duration/IntlOptions.php rename packages/formatter/src/{Utils/DurationFormatter.php => Formatters/Duration/PatternFormatter.php} (95%) rename packages/formatter/src/{Utils/DurationFormatterTest.php => Formatters/Duration/PatternFormatterTest.php} (88%) create mode 100644 packages/formatter/src/Formatters/Duration/PatternOptions.php diff --git a/packages/formatter/src/Config/Formats/DurationFormatIntl.php b/packages/formatter/src/Config/Formats/DurationFormatIntl.php index e20b94569..ee11301d7 100644 --- a/packages/formatter/src/Config/Formats/DurationFormatIntl.php +++ b/packages/formatter/src/Config/Formats/DurationFormatIntl.php @@ -2,24 +2,12 @@ namespace LastDragon_ru\LaraASP\Formatter\Config\Formats; -use LastDragon_ru\LaraASP\Formatter\Formatters\Number\Options; +use LastDragon_ru\LaraASP\Formatter\Formatters\Duration\IntlOptions; use NumberFormatter; /** * @see NumberFormatter */ -class DurationFormatIntl extends Options { - /** - * @param array $symbols - * @param array $attributes - * @param array $textAttributes - */ - public function __construct( - ?string $pattern = null, - array $symbols = [], - array $attributes = [], - array $textAttributes = [], - ) { - parent::__construct(null, $pattern, $symbols, $attributes, $textAttributes); - } +class DurationFormatIntl extends IntlOptions { + // empty } diff --git a/packages/formatter/src/Config/Formats/DurationFormatPattern.php b/packages/formatter/src/Config/Formats/DurationFormatPattern.php index 8c984479f..07ed0bdd8 100644 --- a/packages/formatter/src/Config/Formats/DurationFormatPattern.php +++ b/packages/formatter/src/Config/Formats/DurationFormatPattern.php @@ -2,16 +2,12 @@ namespace LastDragon_ru\LaraASP\Formatter\Config\Formats; -use LastDragon_ru\LaraASP\Core\Application\Configuration\Configuration; -use LastDragon_ru\LaraASP\Formatter\Utils\DurationFormatter; +use LastDragon_ru\LaraASP\Formatter\Formatters\Duration\PatternFormatter; +use LastDragon_ru\LaraASP\Formatter\Formatters\Duration\PatternOptions; /** - * @see DurationFormatter + * @see PatternFormatter */ -class DurationFormatPattern extends Configuration { - public function __construct( - public string $pattern, - ) { - parent::__construct(); - } +class DurationFormatPattern extends PatternOptions { + // empty } diff --git a/packages/formatter/src/Formatter.php b/packages/formatter/src/Formatter.php index 5a8ddced2..b1d436c30 100644 --- a/packages/formatter/src/Formatter.php +++ b/packages/formatter/src/Formatter.php @@ -11,16 +11,13 @@ use IntlTimeZone; use LastDragon_ru\LaraASP\Core\Application\ApplicationResolver; use LastDragon_ru\LaraASP\Core\Application\ConfigResolver; -use LastDragon_ru\LaraASP\Formatter\Config\Config; -use LastDragon_ru\LaraASP\Formatter\Config\Formats\DurationFormatIntl; -use LastDragon_ru\LaraASP\Formatter\Config\Formats\DurationFormatPattern; use LastDragon_ru\LaraASP\Formatter\Exceptions\FormatterFailedToCreateFormatter; use LastDragon_ru\LaraASP\Formatter\Exceptions\FormatterFailedToFormatValue; use LastDragon_ru\LaraASP\Formatter\Formatters\DateTime\Formatter as DateTimeFormatter; +use LastDragon_ru\LaraASP\Formatter\Formatters\Duration\Formatter as DurationFormatter; use LastDragon_ru\LaraASP\Formatter\Formatters\Number\Formatter as NumberFormatter; use LastDragon_ru\LaraASP\Formatter\Formatters\Number\Options; use LastDragon_ru\LaraASP\Formatter\Formatters\Secret\Formatter as SecretFormatter; -use LastDragon_ru\LaraASP\Formatter\Utils\DurationFormatter; use NumberFormatter as IntlNumberFormatter; use function bccomp; @@ -310,43 +307,21 @@ protected function formatSecret(string $format, ?string $value): string { } protected function formatDuration(string $format, DateInterval|float|int|null $value): string { - $config = $this->configuration->getInstance(); - $locale = $this->getLocale(); - $type = $config->locales[$locale]->duration->formats[$format] - ?? $config->global->duration->formats[$format] - ?? null; - $value = ($value instanceof DateInterval ? DurationFormatter::getTimestamp($value) : $value) ?? 0; - $formatted = match (true) { - $type instanceof DurationFormatPattern => $this->formatDurationPattern($type, $value), - $type instanceof DurationFormatIntl => $this->formatDurationIntl($config, $locale, $format, $value), - default => throw new FormatterFailedToCreateFormatter( - self::FormatterDuration, - $format, - ), - }; - - return $formatted; - } - - private function formatDurationPattern(DurationFormatPattern $config, float|int $value): string { - return (new DurationFormatter($config->pattern))->format($value); - } - - private function formatDurationIntl(Config $config, string $locale, string $format, float|int $value): string { + // Format try { - $formatIntl = $config->locales[$locale]->duration->formats[$format] ?? null; - $globalIntl = $config->global->duration->formats[$format] ?? null; - $formatter = new NumberFormatter( + $config = $this->configuration->getInstance(); + $locale = $this->getLocale(); + $formatter = new DurationFormatter( $locale, - new Options(IntlNumberFormatter::DURATION), - $formatIntl instanceof Options ? $formatIntl : null, - $globalIntl instanceof Options ? $globalIntl : null, + $config->locales[$locale]->duration->formats[$format] ?? null, + $config->global->duration->formats[$format] ?? null, ); - $formatted = $formatter->formatNumber($value); + $formatted = $formatter->format($value ?? 0); } catch (Exception $exception) { throw new FormatterFailedToFormatValue(self::FormatterDuration, $format, $value, $exception); } + // Return return $formatted; } diff --git a/packages/formatter/src/Formatters/Duration/Formatter.php b/packages/formatter/src/Formatters/Duration/Formatter.php new file mode 100644 index 000000000..0cf71f542 --- /dev/null +++ b/packages/formatter/src/Formatters/Duration/Formatter.php @@ -0,0 +1,62 @@ +pattern; + + if ($option instanceof IntlOptions) { + $intl[] = $option; + } + } + + // Possible? + if ($isPattern === null) { + throw new InvalidArgumentException('The formatter type in unknown.'); + } + + if ($isPattern === true && $pattern === null) { + throw new InvalidArgumentException('The `$patten` in unknown.'); + } + + // Create + $this->formatter = $isPattern === false + ? new IntlFormatter($locale, ...$intl) + : new PatternFormatter($pattern); + } + + public function format(DateInterval|float|int $value): string { + $value = $value instanceof DateInterval ? PatternFormatter::getTimestamp($value) : $value; + $formatted = $this->formatter->format($value); + + return $formatted; + } +} diff --git a/packages/formatter/src/Formatters/Duration/IntlOptions.php b/packages/formatter/src/Formatters/Duration/IntlOptions.php new file mode 100644 index 000000000..5958edf1a --- /dev/null +++ b/packages/formatter/src/Formatters/Duration/IntlOptions.php @@ -0,0 +1,9 @@ + // ========================================================================= #[DataProvider('dataProviderGetTimestamp')] public function testGetTimestamp(float $expected, DateInterval $interval): void { - self::assertEquals($expected, DurationFormatter::getTimestamp($interval)); + self::assertEquals($expected, PatternFormatter::getTimestamp($interval)); } #[DataProvider('dataProviderFormat')] public function testFormat(string $expected, string $format, float|int $duration): void { - $formatter = new DurationFormatter($format); + $formatter = new PatternFormatter($format); $actual = $formatter->format($duration); self::assertEquals($expected, $actual); @@ -54,7 +54,7 @@ public static function dataProviderGetTimestamp(): array { */ public static function dataProviderFormat(): array { $duration = static function (string $interval): float { - return DurationFormatter::getTimestamp(new DateInterval($interval)); + return PatternFormatter::getTimestamp(new DateInterval($interval)); }; return [ diff --git a/packages/formatter/src/Formatters/Duration/PatternOptions.php b/packages/formatter/src/Formatters/Duration/PatternOptions.php new file mode 100644 index 000000000..7382d4af1 --- /dev/null +++ b/packages/formatter/src/Formatters/Duration/PatternOptions.php @@ -0,0 +1,16 @@ +formatNumber($value); + } + public function formatNumber(float|int $value): string { $formatted = $this->formatter->format($value); From ec03c5adf5f7d20f1f9b44fd63cdd51f5184f3d0 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Wed, 13 Nov 2024 08:56:54 +0400 Subject: [PATCH 10/22] Redesign of value formatters (`\LastDragon_ru\LaraASP\Formatter\Contracts\Format` contract). --- packages/formatter/src/Config/Config.php | 7 ++ packages/formatter/src/Config/Format.php | 29 ++++++++ packages/formatter/src/Contracts/Format.php | 24 +++++++ .../src/Formats/String/StringFormat.php | 23 +++++++ packages/formatter/src/Formatter.php | 68 ++++++++++++++----- 5 files changed, 135 insertions(+), 16 deletions(-) create mode 100644 packages/formatter/src/Config/Format.php create mode 100644 packages/formatter/src/Contracts/Format.php create mode 100644 packages/formatter/src/Formats/String/StringFormat.php diff --git a/packages/formatter/src/Config/Config.php b/packages/formatter/src/Config/Config.php index d3e52fa32..cb0912ae4 100644 --- a/packages/formatter/src/Config/Config.php +++ b/packages/formatter/src/Config/Config.php @@ -10,12 +10,17 @@ use LastDragon_ru\LaraASP\Formatter\Config\Formats\FilesizeFormat; use LastDragon_ru\LaraASP\Formatter\Config\Formats\NumberFormat; use LastDragon_ru\LaraASP\Formatter\Config\Formats\SecretFormat; +use LastDragon_ru\LaraASP\Formatter\Formats\String\StringFormat; use LastDragon_ru\LaraASP\Formatter\Formatter; use NumberFormatter; use Override; class Config extends Configuration { public function __construct( + /** + * @var array|Format> + */ + public array $formats = [], /** * Options and patterns/formats for all locales. */ @@ -29,6 +34,8 @@ public function __construct( ) { parent::__construct(); + $this->formats[Formatter::String] = new Format(StringFormat::class); + $this->global->number->attributes += [ NumberFormatter::ROUNDING_MODE => NumberFormatter::ROUND_HALFUP, ]; diff --git a/packages/formatter/src/Config/Format.php b/packages/formatter/src/Config/Format.php new file mode 100644 index 000000000..a20bbbbda --- /dev/null +++ b/packages/formatter/src/Config/Format.php @@ -0,0 +1,29 @@ +> + */ + public string $class, + /** + * @var TOptions + */ + public ?Configuration $default = null, + /** + * @var array + */ + public array $locales = [], + ) { + parent::__construct(); + } +} diff --git a/packages/formatter/src/Contracts/Format.php b/packages/formatter/src/Contracts/Format.php new file mode 100644 index 000000000..28b8a1c94 --- /dev/null +++ b/packages/formatter/src/Contracts/Format.php @@ -0,0 +1,24 @@ + + */ +class StringFormat implements Format { + public function __construct() { + // empty + } + + #[Override] + public function __invoke(mixed $value): string { + return trim((string) $value); + } +} diff --git a/packages/formatter/src/Formatter.php b/packages/formatter/src/Formatter.php index b1d436c30..feb4f166d 100644 --- a/packages/formatter/src/Formatter.php +++ b/packages/formatter/src/Formatter.php @@ -11,6 +11,8 @@ use IntlTimeZone; use LastDragon_ru\LaraASP\Core\Application\ApplicationResolver; use LastDragon_ru\LaraASP\Core\Application\ConfigResolver; +use LastDragon_ru\LaraASP\Core\Application\Configuration\Configuration; +use LastDragon_ru\LaraASP\Formatter\Contracts\Format; use LastDragon_ru\LaraASP\Formatter\Exceptions\FormatterFailedToCreateFormatter; use LastDragon_ru\LaraASP\Formatter\Exceptions\FormatterFailedToFormatValue; use LastDragon_ru\LaraASP\Formatter\Formatters\DateTime\Formatter as DateTimeFormatter; @@ -19,6 +21,8 @@ use LastDragon_ru\LaraASP\Formatter\Formatters\Number\Options; use LastDragon_ru\LaraASP\Formatter\Formatters\Secret\Formatter as SecretFormatter; use NumberFormatter as IntlNumberFormatter; +use OutOfBoundsException; +use Stringable; use function bccomp; use function bcdiv; @@ -26,12 +30,12 @@ use function is_null; use function mb_strlen; use function sprintf; -use function trim; class Formatter { use Macroable; public const Default = 'default'; + public const String = 'string'; public const Integer = 'integer'; public const Scientific = 'scientific'; public const Spellout = 'spellout'; @@ -57,7 +61,7 @@ class Formatter { public function __construct( protected readonly ApplicationResolver $application, protected readonly ConfigResolver $config, - protected readonly PackageConfig $configuration, + protected readonly PackageConfig $package, private PackageTranslator $translator, ) { // empty @@ -72,7 +76,7 @@ public function forLocale(?string $locale): static { $formatter = $this; if ($this->locale !== $locale) { - $formatter = $this->create(); + $formatter = clone $this; $formatter->locale = $locale; } @@ -86,16 +90,12 @@ public function forTimezone(IntlTimeZone|DateTimeZone|string|null $timezone): st $formatter = $this; if ($this->timezone !== $timezone) { - $formatter = $this->create(); + $formatter = clone $this; $formatter->timezone = $timezone; } return $formatter; } - - protected function create(): static { - return clone $this; - } // // @@ -113,10 +113,46 @@ protected function getTranslator(): PackageTranslator { } // + // + // ========================================================================= + public function format(string $format, mixed $value): string { + try { + return ($this->getFormat($format))($value); + } catch (Exception $exception) { + throw new FormatterFailedToFormatValue($format, $format, $value, $exception); + } + } + + /** + * @return Format|Format + */ + protected function getFormat(string $format): Format { + // Known? + $config = $this->package->getInstance(); + $settings = $config->formats[$format] ?? null; + + if ($settings === null) { + throw new OutOfBoundsException(sprintf('The `%s` format is unknown.', $format)); + } + + // Create + $locale = $this->getLocale(); + $formatter = $this->application->getInstance()->make($settings->class, [ + 'formatter' => $this, + 'options' => [ + $settings->locales[$locale] ?? null, + $settings->default, + ], + ]); + + return $formatter; + } + // + // // ========================================================================= - public function string(?string $value): string { - return trim((string) $value); + public function string(Stringable|string|null $value): string { + return $this->format(self::String, $value); } public function integer(float|int|null $value): string { @@ -210,7 +246,7 @@ protected function getTranslation(array|string $key, array $replace = []): strin protected function formatNumber(string $format, float|int|null $value): string { // Create try { - $config = $this->configuration->getInstance(); + $config = $this->package->getInstance(); $locale = $this->getLocale(); $formatter = new NumberFormatter( $locale, @@ -233,7 +269,7 @@ protected function formatNumber(string $format, float|int|null $value): string { protected function formatCurrency(string $format, float|int|null $value, ?string $currency = null): string { // Create try { - $config = $this->configuration->getInstance(); + $config = $this->package->getInstance(); $locale = $this->getLocale(); $formatter = new NumberFormatter( $locale, @@ -265,7 +301,7 @@ protected function formatDateTime(string $format, ?DateTimeInterface $value): st // Format try { - $config = $this->configuration->getInstance(); + $config = $this->package->getInstance(); $locale = $this->getLocale(); $timezone = $this->getTimezone(); $formatter = new DateTimeFormatter( @@ -291,7 +327,7 @@ protected function formatSecret(string $format, ?string $value): string { // Format try { - $config = $this->configuration->getInstance(); + $config = $this->package->getInstance(); $locale = $this->getLocale(); $formatter = new SecretFormatter( $config->locales[$locale]->secret->formats[$format] ?? null, @@ -309,7 +345,7 @@ protected function formatSecret(string $format, ?string $value): string { protected function formatDuration(string $format, DateInterval|float|int|null $value): string { // Format try { - $config = $this->configuration->getInstance(); + $config = $this->package->getInstance(); $locale = $this->getLocale(); $formatter = new DurationFormatter( $locale, @@ -330,7 +366,7 @@ protected function formatDuration(string $format, DateInterval|float|int|null $v */ protected function formatFilesize(string $format, string|float|int|null $bytes): string { // Prepare - $config = $this->configuration->getInstance(); + $config = $this->package->getInstance(); $locale = $this->getLocale(); $base = $config->locales[$locale]->filesize->formats[$format]->base ?? $config->global->filesize->formats[$format]->base From b921becf28a433a6cdeeade7101381dbd23ea393 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Wed, 13 Nov 2024 11:33:03 +0400 Subject: [PATCH 11/22] New Secret format. --- packages/formatter/src/Config/Config.php | 9 +++--- .../src/Config/Formats/SecretConfig.php | 16 ---------- .../src/Config/Formats/SecretFormat.php | 9 ------ packages/formatter/src/Config/Locale.php | 2 -- .../Secret/SecretFormat.php} | 21 ++++++++++--- .../Secret/SecretOptions.php} | 4 +-- packages/formatter/src/Formatter.php | 31 ++----------------- packages/formatter/src/FormatterTest.php | 13 ++++++-- 8 files changed, 36 insertions(+), 69 deletions(-) delete mode 100644 packages/formatter/src/Config/Formats/SecretConfig.php delete mode 100644 packages/formatter/src/Config/Formats/SecretFormat.php rename packages/formatter/src/{Formatters/Secret/Formatter.php => Formats/Secret/SecretFormat.php} (70%) rename packages/formatter/src/{Formatters/Secret/Options.php => Formats/Secret/SecretOptions.php} (76%) diff --git a/packages/formatter/src/Config/Config.php b/packages/formatter/src/Config/Config.php index cb0912ae4..03530bb24 100644 --- a/packages/formatter/src/Config/Config.php +++ b/packages/formatter/src/Config/Config.php @@ -9,7 +9,8 @@ use LastDragon_ru\LaraASP\Formatter\Config\Formats\DurationFormatPattern; use LastDragon_ru\LaraASP\Formatter\Config\Formats\FilesizeFormat; use LastDragon_ru\LaraASP\Formatter\Config\Formats\NumberFormat; -use LastDragon_ru\LaraASP\Formatter\Config\Formats\SecretFormat; +use LastDragon_ru\LaraASP\Formatter\Formats\Secret\SecretFormat; +use LastDragon_ru\LaraASP\Formatter\Formats\Secret\SecretOptions; use LastDragon_ru\LaraASP\Formatter\Formats\String\StringFormat; use LastDragon_ru\LaraASP\Formatter\Formatter; use NumberFormatter; @@ -18,7 +19,7 @@ class Config extends Configuration { public function __construct( /** - * @var array|Format> + * @var array> */ public array $formats = [], /** @@ -35,6 +36,7 @@ public function __construct( parent::__construct(); $this->formats[Formatter::String] = new Format(StringFormat::class); + $this->formats[Formatter::Secret] = new Format(SecretFormat::class, new SecretOptions(5)); $this->global->number->attributes += [ NumberFormatter::ROUNDING_MODE => NumberFormatter::ROUND_HALFUP, @@ -85,9 +87,6 @@ public function __construct( $this->global->duration->formats += [ Formatter::Default => new DurationFormatPattern('HH:mm:ss.SSS'), ]; - $this->global->secret->formats += [ - Formatter::Default => new SecretFormat(5), - ]; $this->global->filesize->formats += [ Formatter::Disksize => new FileSizeFormat( base : 1000, diff --git a/packages/formatter/src/Config/Formats/SecretConfig.php b/packages/formatter/src/Config/Formats/SecretConfig.php deleted file mode 100644 index 404e385c3..000000000 --- a/packages/formatter/src/Config/Formats/SecretConfig.php +++ /dev/null @@ -1,16 +0,0 @@ - - */ - public array $formats = [], - ) { - parent::__construct(); - } -} diff --git a/packages/formatter/src/Config/Formats/SecretFormat.php b/packages/formatter/src/Config/Formats/SecretFormat.php deleted file mode 100644 index d5f72b317..000000000 --- a/packages/formatter/src/Config/Formats/SecretFormat.php +++ /dev/null @@ -1,9 +0,0 @@ - */ -class Formatter { +class SecretFormat implements Format { protected readonly int $visible; - public function __construct(?Options ...$options) { + /** + * @param list $options + */ + public function __construct(array $options = []) { // Collect options $visible = null; @@ -36,7 +41,13 @@ public function __construct(?Options ...$options) { $this->visible = $visible; } - public function format(string $value): string { + #[Override] + public function __invoke(mixed $value): string { + // Null? + if ($value === null) { + return ''; + } + // Format $visible = $this->visible; $length = mb_strlen($value); diff --git a/packages/formatter/src/Formatters/Secret/Options.php b/packages/formatter/src/Formats/Secret/SecretOptions.php similarity index 76% rename from packages/formatter/src/Formatters/Secret/Options.php rename to packages/formatter/src/Formats/Secret/SecretOptions.php index cc15c35f8..4353f4d17 100644 --- a/packages/formatter/src/Formatters/Secret/Options.php +++ b/packages/formatter/src/Formats/Secret/SecretOptions.php @@ -1,10 +1,10 @@ |Format + * @return Format<*, mixed> */ protected function getFormat(string $format): Format { // Known? @@ -221,7 +219,7 @@ public function disksize(string|float|int|null $bytes): string { } public function secret(?string $value): string { - return $this->formatSecret(self::Default, $value); + return $this->format(self::Secret, $value); } // @@ -319,29 +317,6 @@ protected function formatDateTime(string $format, ?DateTimeInterface $value): st return $formatted; } - protected function formatSecret(string $format, ?string $value): string { - // Null? - if (is_null($value)) { - return ''; - } - - // Format - try { - $config = $this->package->getInstance(); - $locale = $this->getLocale(); - $formatter = new SecretFormatter( - $config->locales[$locale]->secret->formats[$format] ?? null, - $config->global->secret->formats[$format] ?? null, - ); - $formatted = $formatter->format($value); - } catch (Exception $exception) { - throw new FormatterFailedToFormatValue(self::FormatterSecret, $format, $value, $exception); - } - - // Return - return $formatted; - } - protected function formatDuration(string $format, DateInterval|float|int|null $value): string { // Format try { diff --git a/packages/formatter/src/FormatterTest.php b/packages/formatter/src/FormatterTest.php index ada27729c..c9fa3c2d1 100644 --- a/packages/formatter/src/FormatterTest.php +++ b/packages/formatter/src/FormatterTest.php @@ -6,13 +6,15 @@ use IntlDateFormatter; use LastDragon_ru\LaraASP\Core\Utils\Cast; use LastDragon_ru\LaraASP\Formatter\Config\Config; +use LastDragon_ru\LaraASP\Formatter\Config\Format; use LastDragon_ru\LaraASP\Formatter\Config\Formats\DateTimeFormat; use LastDragon_ru\LaraASP\Formatter\Config\Formats\DurationFormatIntl; use LastDragon_ru\LaraASP\Formatter\Config\Formats\DurationFormatPattern; use LastDragon_ru\LaraASP\Formatter\Config\Formats\NumberConfig; use LastDragon_ru\LaraASP\Formatter\Config\Formats\NumberFormat; -use LastDragon_ru\LaraASP\Formatter\Config\Formats\SecretFormat; use LastDragon_ru\LaraASP\Formatter\Config\Locale; +use LastDragon_ru\LaraASP\Formatter\Formats\Secret\SecretFormat; +use LastDragon_ru\LaraASP\Formatter\Formats\Secret\SecretOptions; use LastDragon_ru\LaraASP\Formatter\Testing\Package\TestCase; use NumberFormatter; use Override; @@ -302,7 +304,13 @@ public function testSecret(): void { public function testSecretConfig(): void { $this->setConfiguration(PackageConfig::class, static function (Config $config): void { - Cast::to(SecretFormat::class, $config->global->secret->formats[Formatter::Default])->visible = 3; + $config->formats[Formatter::Secret] = new Format( + class : SecretFormat::class, + default: new SecretOptions(3), + locales: [ + 'ru_RU' => new SecretOptions(2), + ], + ); }); self::assertEquals('', $this->formatter->secret(null)); @@ -314,6 +322,7 @@ public function testSecretConfig(): void { self::assertEquals('***456', $this->formatter->secret('123456')); self::assertEquals('****567', $this->formatter->secret('1234567')); self::assertEquals('*****678', $this->formatter->secret('12345678')); + self::assertEquals('******78', $this->formatter->forLocale('ru_RU')->secret('12345678')); } public function testFilesize(): void { From ff959a340ffa26a5db153fd0c0829be643709b21 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Wed, 13 Nov 2024 14:09:35 +0400 Subject: [PATCH 12/22] New Number/Currency formats. --- packages/formatter/src/Config/Config.php | 77 ++++++++++++++----- .../src/Config/Formats/CurrencyConfig.php | 24 ------ .../src/Config/Formats/CurrencyFormat.php | 25 ------ .../src/Config/Formats/NumberConfig.php | 28 ------- .../src/Config/Formats/NumberFormat.php | 27 ------- packages/formatter/src/Config/Locale.php | 4 - .../Formats/IntlNumber/IntlCurrencyFormat.php | 40 ++++++++++ .../IntlNumber/IntlFormat.php} | 47 ++++------- .../Formats/IntlNumber/IntlNumberFormat.php | 24 ++++++ .../IntlNumber/IntlOptions.php} | 4 +- packages/formatter/src/Formatter.php | 74 +++--------------- packages/formatter/src/FormatterTest.php | 46 +++++------ .../src/Formatters/Duration/Formatter.php | 15 ++-- .../src/Formatters/Duration/IntlOptions.php | 4 +- phpstan-baseline.neon | 5 ++ 15 files changed, 186 insertions(+), 258 deletions(-) delete mode 100644 packages/formatter/src/Config/Formats/CurrencyConfig.php delete mode 100644 packages/formatter/src/Config/Formats/CurrencyFormat.php delete mode 100644 packages/formatter/src/Config/Formats/NumberConfig.php delete mode 100644 packages/formatter/src/Config/Formats/NumberFormat.php create mode 100644 packages/formatter/src/Formats/IntlNumber/IntlCurrencyFormat.php rename packages/formatter/src/{Formatters/Number/Formatter.php => Formats/IntlNumber/IntlFormat.php} (66%) create mode 100644 packages/formatter/src/Formats/IntlNumber/IntlNumberFormat.php rename packages/formatter/src/{Formatters/Number/Options.php => Formats/IntlNumber/IntlOptions.php} (89%) diff --git a/packages/formatter/src/Config/Config.php b/packages/formatter/src/Config/Config.php index 03530bb24..8f85ccaa8 100644 --- a/packages/formatter/src/Config/Config.php +++ b/packages/formatter/src/Config/Config.php @@ -8,7 +8,9 @@ use LastDragon_ru\LaraASP\Formatter\Config\Formats\DateTimeFormat; use LastDragon_ru\LaraASP\Formatter\Config\Formats\DurationFormatPattern; use LastDragon_ru\LaraASP\Formatter\Config\Formats\FilesizeFormat; -use LastDragon_ru\LaraASP\Formatter\Config\Formats\NumberFormat; +use LastDragon_ru\LaraASP\Formatter\Formats\IntlNumber\IntlCurrencyFormat; +use LastDragon_ru\LaraASP\Formatter\Formats\IntlNumber\IntlNumberFormat; +use LastDragon_ru\LaraASP\Formatter\Formats\IntlNumber\IntlOptions; use LastDragon_ru\LaraASP\Formatter\Formats\Secret\SecretFormat; use LastDragon_ru\LaraASP\Formatter\Formats\Secret\SecretOptions; use LastDragon_ru\LaraASP\Formatter\Formats\String\StringFormat; @@ -35,42 +37,75 @@ public function __construct( ) { parent::__construct(); - $this->formats[Formatter::String] = new Format(StringFormat::class); - $this->formats[Formatter::Secret] = new Format(SecretFormat::class, new SecretOptions(5)); - - $this->global->number->attributes += [ - NumberFormatter::ROUNDING_MODE => NumberFormatter::ROUND_HALFUP, - ]; - $this->global->number->formats += [ - Formatter::Integer => new NumberFormat( + $this->formats[Formatter::String] = new Format(StringFormat::class); + $this->formats[Formatter::Secret] = new Format(SecretFormat::class, new SecretOptions(5)); + $this->formats[Formatter::Integer] = new Format( + IntlNumberFormat::class, + new IntlOptions( style : NumberFormatter::DECIMAL, attributes: [ + NumberFormatter::ROUNDING_MODE => NumberFormatter::ROUND_HALFUP, NumberFormatter::FRACTION_DIGITS => 0, ], ), - Formatter::Decimal => new NumberFormat( + ); + $this->formats[Formatter::Decimal] = new Format( + IntlNumberFormat::class, + new IntlOptions( style : NumberFormatter::DECIMAL, attributes: [ + NumberFormatter::ROUNDING_MODE => NumberFormatter::ROUND_HALFUP, NumberFormatter::FRACTION_DIGITS => 2, ], ), - Formatter::Scientific => new NumberFormat( - style: NumberFormatter::SCIENTIFIC, + ); + $this->formats[Formatter::Scientific] = new Format( + IntlNumberFormat::class, + new IntlOptions( + style : NumberFormatter::SCIENTIFIC, + attributes: [ + NumberFormatter::ROUNDING_MODE => NumberFormatter::ROUND_HALFUP, + ], ), - Formatter::Spellout => new NumberFormat( - style: NumberFormatter::SPELLOUT, + ); + $this->formats[Formatter::Spellout] = new Format( + IntlNumberFormat::class, + new IntlOptions( + style : NumberFormatter::SPELLOUT, + attributes: [ + NumberFormatter::ROUNDING_MODE => NumberFormatter::ROUND_HALFUP, + ], ), - Formatter::Ordinal => new NumberFormat( - style: NumberFormatter::ORDINAL, + ); + $this->formats[Formatter::Ordinal] = new Format( + IntlNumberFormat::class, + new IntlOptions( + style : NumberFormatter::ORDINAL, + attributes: [ + NumberFormatter::ROUNDING_MODE => NumberFormatter::ROUND_HALFUP, + ], ), - Formatter::Percent => new NumberFormat( + ); + $this->formats[Formatter::Percent] = new Format( + IntlNumberFormat::class, + new IntlOptions( style : NumberFormatter::PERCENT, attributes: [ + NumberFormatter::ROUNDING_MODE => NumberFormatter::ROUND_HALFUP, NumberFormatter::FRACTION_DIGITS => 0, ], ), - ]; - $this->global->datetime->formats += [ + ); + $this->formats[Formatter::Currency] = new Format( + IntlCurrencyFormat::class, + new IntlOptions( + attributes: [ + NumberFormatter::ROUNDING_MODE => NumberFormatter::ROUND_HALFUP, + ], + ), + ); + + $this->global->datetime->formats += [ Formatter::Time => new DateTimeFormat( dateType: IntlDateFormatter::NONE, timeType: IntlDateFormatter::SHORT, @@ -84,10 +119,10 @@ public function __construct( timeType: IntlDateFormatter::SHORT, ), ]; - $this->global->duration->formats += [ + $this->global->duration->formats += [ Formatter::Default => new DurationFormatPattern('HH:mm:ss.SSS'), ]; - $this->global->filesize->formats += [ + $this->global->filesize->formats += [ Formatter::Disksize => new FileSizeFormat( base : 1000, units: [ diff --git a/packages/formatter/src/Config/Formats/CurrencyConfig.php b/packages/formatter/src/Config/Formats/CurrencyConfig.php deleted file mode 100644 index cf4429349..000000000 --- a/packages/formatter/src/Config/Formats/CurrencyConfig.php +++ /dev/null @@ -1,24 +0,0 @@ - $symbols - * @param array $attributes - * @param array $textAttributes - */ - public function __construct( - /** - * @var array - */ - public array $formats = [], - array $symbols = [], - array $attributes = [], - array $textAttributes = [], - ) { - parent::__construct(null, null, $symbols, $attributes, $textAttributes); - } -} diff --git a/packages/formatter/src/Config/Formats/CurrencyFormat.php b/packages/formatter/src/Config/Formats/CurrencyFormat.php deleted file mode 100644 index 756e37a5f..000000000 --- a/packages/formatter/src/Config/Formats/CurrencyFormat.php +++ /dev/null @@ -1,25 +0,0 @@ - $symbols - * @param array $attributes - * @param array $textAttributes - */ - public function __construct( - ?string $pattern = null, - array $symbols = [], - array $attributes = [], - array $textAttributes = [], - ) { - parent::__construct(null, $pattern, $symbols, $attributes, $textAttributes); - } -} diff --git a/packages/formatter/src/Config/Formats/NumberConfig.php b/packages/formatter/src/Config/Formats/NumberConfig.php deleted file mode 100644 index 19257ce43..000000000 --- a/packages/formatter/src/Config/Formats/NumberConfig.php +++ /dev/null @@ -1,28 +0,0 @@ - $symbols - * @param array $attributes - * @param array $textAttributes - */ - public function __construct( - /** - * @var array - */ - public array $formats = [], - ?int $style = null, - ?string $pattern = null, - array $symbols = [], - array $attributes = [], - array $textAttributes = [], - ) { - parent::__construct($style, $pattern, $symbols, $attributes, $textAttributes); - } -} diff --git a/packages/formatter/src/Config/Formats/NumberFormat.php b/packages/formatter/src/Config/Formats/NumberFormat.php deleted file mode 100644 index 3623513bc..000000000 --- a/packages/formatter/src/Config/Formats/NumberFormat.php +++ /dev/null @@ -1,27 +0,0 @@ - $symbols - * @param array $attributes - * @param array $textAttributes - */ - public function __construct( - ?int $style = null, - ?string $pattern = null, - array $symbols = [], - array $attributes = [], - array $textAttributes = [], - ) { - parent::__construct($style, $pattern, $symbols, $attributes, $textAttributes); - } -} diff --git a/packages/formatter/src/Config/Locale.php b/packages/formatter/src/Config/Locale.php index 8cc4c074f..54cf599ff 100644 --- a/packages/formatter/src/Config/Locale.php +++ b/packages/formatter/src/Config/Locale.php @@ -3,16 +3,12 @@ namespace LastDragon_ru\LaraASP\Formatter\Config; use LastDragon_ru\LaraASP\Core\Application\Configuration\Configuration; -use LastDragon_ru\LaraASP\Formatter\Config\Formats\CurrencyConfig; use LastDragon_ru\LaraASP\Formatter\Config\Formats\DateTimeConfig; use LastDragon_ru\LaraASP\Formatter\Config\Formats\DurationConfig; use LastDragon_ru\LaraASP\Formatter\Config\Formats\FilesizeConfig; -use LastDragon_ru\LaraASP\Formatter\Config\Formats\NumberConfig; class Locale extends Configuration { public function __construct( - public NumberConfig $number = new NumberConfig(), - public CurrencyConfig $currency = new CurrencyConfig(), public DateTimeConfig $datetime = new DateTimeConfig(), public DurationConfig $duration = new DurationConfig(), public FilesizeConfig $filesize = new FilesizeConfig(), diff --git a/packages/formatter/src/Formats/IntlNumber/IntlCurrencyFormat.php b/packages/formatter/src/Formats/IntlNumber/IntlCurrencyFormat.php new file mode 100644 index 000000000..702ea864c --- /dev/null +++ b/packages/formatter/src/Formats/IntlNumber/IntlCurrencyFormat.php @@ -0,0 +1,40 @@ + + */ +class IntlCurrencyFormat extends IntlFormat { + /** + * @param list $options + */ + public function __construct(Formatter $formatter, array $options = []) { + parent::__construct($formatter, [ + new IntlOptions(NumberFormatter::CURRENCY), + ...$options, + ]); + } + + #[Override] + public function __invoke(mixed $value): string { + [$value, $currency] = is_array($value) ? $value : [$value, null]; + $value ??= 0; + $currency ??= $this->formatter->getTextAttribute(NumberFormatter::CURRENCY_CODE); + $formatted = $this->formatter->formatCurrency($value, $currency); + + if ($formatted === false) { + throw new IntlException($this->formatter->getErrorMessage(), $this->formatter->getErrorCode()); + } + + return $formatted; + } +} diff --git a/packages/formatter/src/Formatters/Number/Formatter.php b/packages/formatter/src/Formats/IntlNumber/IntlFormat.php similarity index 66% rename from packages/formatter/src/Formatters/Number/Formatter.php rename to packages/formatter/src/Formats/IntlNumber/IntlFormat.php index f524b4196..de6a28e50 100644 --- a/packages/formatter/src/Formatters/Number/Formatter.php +++ b/packages/formatter/src/Formats/IntlNumber/IntlFormat.php @@ -1,25 +1,30 @@ */ -class Formatter { +abstract class IntlFormat implements Format { protected readonly NumberFormatter $formatter; - public function __construct( - protected readonly string $locale, - ?Options ...$options, - ) { + /** + * @param list $options + */ + public function __construct(Formatter $formatter, array $options = []) { // Collect options $style = null; $pattern = null; @@ -45,6 +50,7 @@ public function __construct( } // Create + $locale = $formatter->getLocale(); $pattern = $pattern !== '' ? $pattern : null; $this->formatter = new NumberFormatter($locale, $style, $pattern); @@ -82,29 +88,4 @@ public function __construct( } } } - - public function format(float|int $value): string { - return $this->formatNumber($value); - } - - public function formatNumber(float|int $value): string { - $formatted = $this->formatter->format($value); - - if ($formatted === false) { - throw new IntlException($this->formatter->getErrorMessage(), $this->formatter->getErrorCode()); - } - - return $formatted; - } - - public function formatCurrency(float|int $value, ?string $currency = null): string { - $currency ??= $this->formatter->getTextAttribute(NumberFormatter::CURRENCY_CODE); - $formatted = $this->formatter->formatCurrency($value, $currency); - - if ($formatted === false) { - throw new IntlException($this->formatter->getErrorMessage(), $this->formatter->getErrorCode()); - } - - return $formatted; - } } diff --git a/packages/formatter/src/Formats/IntlNumber/IntlNumberFormat.php b/packages/formatter/src/Formats/IntlNumber/IntlNumberFormat.php new file mode 100644 index 000000000..f39dd2ef6 --- /dev/null +++ b/packages/formatter/src/Formats/IntlNumber/IntlNumberFormat.php @@ -0,0 +1,24 @@ + + */ +class IntlNumberFormat extends IntlFormat { + #[Override] + public function __invoke(mixed $value): string { + $formatted = $this->formatter->format($value ?? 0); + + if ($formatted === false) { + throw new IntlException($this->formatter->getErrorMessage(), $this->formatter->getErrorCode()); + } + + return $formatted; + } +} diff --git a/packages/formatter/src/Formatters/Number/Options.php b/packages/formatter/src/Formats/IntlNumber/IntlOptions.php similarity index 89% rename from packages/formatter/src/Formatters/Number/Options.php rename to packages/formatter/src/Formats/IntlNumber/IntlOptions.php index d21d59191..738f1cfe3 100644 --- a/packages/formatter/src/Formatters/Number/Options.php +++ b/packages/formatter/src/Formats/IntlNumber/IntlOptions.php @@ -1,6 +1,6 @@ formatNumber(self::Integer, $value); + return $this->format(self::Integer, $value); } public function decimal(float|int|null $value): string { - return $this->formatNumber(self::Decimal, $value); + return $this->format(self::Decimal, $value); } public function currency(float|int|null $value): string { - return $this->formatCurrency(self::Default, $value); + return $this->format(self::Currency, $value); } /** * @param float|int|null $value must be between 0-100 */ public function percent(float|int|null $value): string { - return $this->formatNumber(self::Percent, $value !== null ? $value / 100 : $value); + return $this->format(self::Percent, $value !== null ? $value / 100 : $value); } public function scientific(float|int|null $value): string { - return $this->formatNumber(self::Scientific, $value); + return $this->format(self::Scientific, $value); } public function spellout(float|int|null $value): string { - return $this->formatNumber(self::Spellout, $value); + return $this->format(self::Spellout, $value); } public function ordinal(?int $value): string { - return $this->formatNumber(self::Ordinal, $value); + return $this->format(self::Ordinal, $value); } public function duration(DateInterval|float|int|null $value): string { @@ -241,56 +237,6 @@ protected function getTranslation(array|string $key, array $replace = []): strin return $this->getTranslator()->get($key, $replace, $this->getLocale()); } - protected function formatNumber(string $format, float|int|null $value): string { - // Create - try { - $config = $this->package->getInstance(); - $locale = $this->getLocale(); - $formatter = new NumberFormatter( - $locale, - $config->locales[$locale]->number->formats[$format] ?? null, - $config->locales[$locale]->number ?? null, - $config->global->number->formats[$format] ?? null, - $config->global->number, - ); - $formatted = $formatter->formatNumber($value ?? 0); - } catch (Exception $exception) { - throw new FormatterFailedToFormatValue(self::FormatterNumber, $format, $value, $exception); - } - - return $formatted; - } - - /** - * @param non-empty-string|null $currency - */ - protected function formatCurrency(string $format, float|int|null $value, ?string $currency = null): string { - // Create - try { - $config = $this->package->getInstance(); - $locale = $this->getLocale(); - $formatter = new NumberFormatter( - $locale, - new Options(IntlNumberFormatter::CURRENCY), - $config->locales[$locale]->currency->formats[$format] ?? null, - $config->locales[$locale]->currency ?? null, - $config->global->currency->formats[$format] ?? null, - $config->global->currency, - ); - $formatted = $formatter->formatCurrency($value ?? 0, $currency); - } catch (Exception $exception) { - throw new FormatterFailedToFormatValue( - self::FormatterCurrency, - "{$format}@".($currency ?? 'NULL'), - $value, - $exception, - ); - } - - // Return - return $formatted; - } - protected function formatDateTime(string $format, ?DateTimeInterface $value): string { // Null? if (is_null($value)) { @@ -323,7 +269,7 @@ protected function formatDuration(string $format, DateInterval|float|int|null $v $config = $this->package->getInstance(); $locale = $this->getLocale(); $formatter = new DurationFormatter( - $locale, + $this, $config->locales[$locale]->duration->formats[$format] ?? null, $config->global->duration->formats[$format] ?? null, ); @@ -382,7 +328,7 @@ protected function formatFilesize(string $format, string|float|int|null $bytes): $bytes = $isInt ? (int) $bytes : (float) $bytes; $format = $isInt ? $integerFormat : $decimalFormat; $suffix = $this->getTranslation($units[$unit]); - $formatted = "{$this->formatNumber($format, $bytes)} {$suffix}"; + $formatted = "{$this->format($format, $bytes)} {$suffix}"; return $formatted; } diff --git a/packages/formatter/src/FormatterTest.php b/packages/formatter/src/FormatterTest.php index c9fa3c2d1..54039521f 100644 --- a/packages/formatter/src/FormatterTest.php +++ b/packages/formatter/src/FormatterTest.php @@ -10,9 +10,8 @@ use LastDragon_ru\LaraASP\Formatter\Config\Formats\DateTimeFormat; use LastDragon_ru\LaraASP\Formatter\Config\Formats\DurationFormatIntl; use LastDragon_ru\LaraASP\Formatter\Config\Formats\DurationFormatPattern; -use LastDragon_ru\LaraASP\Formatter\Config\Formats\NumberConfig; -use LastDragon_ru\LaraASP\Formatter\Config\Formats\NumberFormat; -use LastDragon_ru\LaraASP\Formatter\Config\Locale; +use LastDragon_ru\LaraASP\Formatter\Formats\IntlNumber\IntlNumberFormat; +use LastDragon_ru\LaraASP\Formatter\Formats\IntlNumber\IntlOptions; use LastDragon_ru\LaraASP\Formatter\Formats\Secret\SecretFormat; use LastDragon_ru\LaraASP\Formatter\Formats\Secret\SecretOptions; use LastDragon_ru\LaraASP\Formatter\Testing\Package\TestCase; @@ -87,25 +86,22 @@ public function testDecimal(): void { public function testDecimalConfig(): void { $this->setConfiguration(PackageConfig::class, static function (Config $config): void { - Cast::to(NumberFormat::class, $config->global->number->formats[Formatter::Decimal])->attributes = [ - NumberFormatter::FRACTION_DIGITS => 4, - ]; - - $config->locales['ru_RU'] = new Locale( - number: new NumberConfig( - formats : [ - Formatter::Decimal => new NumberFormat( - style : NumberFormatter::DECIMAL, - attributes: [ - NumberFormatter::FRACTION_DIGITS => 2, - NumberFormatter::ROUNDING_MODE => NumberFormatter::ROUND_FLOOR, - ], - ), - ], + $config->formats[Formatter::Decimal] = new Format( + IntlNumberFormat::class, + new IntlOptions( + style : NumberFormatter::DECIMAL, attributes: [ - NumberFormatter::FRACTION_DIGITS => 5, // should be ignored + NumberFormatter::FRACTION_DIGITS => 4, ], ), + [ + 'ru_RU' => new IntlOptions( + attributes: [ + NumberFormatter::FRACTION_DIGITS => 2, + NumberFormatter::ROUNDING_MODE => NumberFormatter::ROUND_FLOOR, + ], + ), + ], ); }); @@ -150,9 +146,15 @@ public function testPercent(): void { public function testPercentConfig(): void { $this->setConfiguration(PackageConfig::class, static function (Config $config): void { - Cast::to(NumberFormat::class, $config->global->number->formats[Formatter::Percent])->attributes = [ - NumberFormatter::FRACTION_DIGITS => 2, - ]; + $config->formats[Formatter::Percent] = new Format( + IntlNumberFormat::class, + new IntlOptions( + style : NumberFormatter::PERCENT, + attributes: [ + NumberFormatter::FRACTION_DIGITS => 2, + ], + ), + ); }); self::assertEquals('10.99%', $this->formatter->percent(10.99)); diff --git a/packages/formatter/src/Formatters/Duration/Formatter.php b/packages/formatter/src/Formatters/Duration/Formatter.php index 0cf71f542..876bac98d 100644 --- a/packages/formatter/src/Formatters/Duration/Formatter.php +++ b/packages/formatter/src/Formatters/Duration/Formatter.php @@ -4,18 +4,19 @@ use DateInterval; use InvalidArgumentException; -use LastDragon_ru\LaraASP\Formatter\Formatters\Number\Formatter as IntlFormatter; -use LastDragon_ru\LaraASP\Formatter\Formatters\Number\Options as IntlOptions; +use LastDragon_ru\LaraASP\Formatter\Formats\IntlNumber\IntlNumberFormat; +use LastDragon_ru\LaraASP\Formatter\Formats\IntlNumber\IntlOptions; +use LastDragon_ru\LaraASP\Formatter\Formatter as GlobalFormatter; use NumberFormatter; /** * @internal */ class Formatter { - protected readonly PatternFormatter|IntlFormatter $formatter; + protected readonly PatternFormatter|IntlNumberFormat $formatter; public function __construct( - protected readonly string $locale, + GlobalFormatter $formatter, IntlOptions|PatternOptions|null ...$options, ) { // Collect options @@ -49,13 +50,15 @@ public function __construct( // Create $this->formatter = $isPattern === false - ? new IntlFormatter($locale, ...$intl) + ? new IntlNumberFormat($formatter, $intl) : new PatternFormatter($pattern); } public function format(DateInterval|float|int $value): string { $value = $value instanceof DateInterval ? PatternFormatter::getTimestamp($value) : $value; - $formatted = $this->formatter->format($value); + $formatted = $this->formatter instanceof PatternFormatter + ? $this->formatter->format($value) + : ($this->formatter)($value); return $formatted; } diff --git a/packages/formatter/src/Formatters/Duration/IntlOptions.php b/packages/formatter/src/Formatters/Duration/IntlOptions.php index 5958edf1a..eadd78c82 100644 --- a/packages/formatter/src/Formatters/Duration/IntlOptions.php +++ b/packages/formatter/src/Formatters/Duration/IntlOptions.php @@ -2,8 +2,8 @@ namespace LastDragon_ru\LaraASP\Formatter\Formatters\Duration; -use LastDragon_ru\LaraASP\Formatter\Formatters\Number\Options; +use LastDragon_ru\LaraASP\Formatter\Formats\IntlNumber\IntlOptions as NumberIntlOptions; -class IntlOptions extends Options { +class IntlOptions extends NumberIntlOptions { // empty } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 766abeb7d..17bafc611 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -25,6 +25,11 @@ parameters: count: 2 path: packages/eloquent/src/Iterators/ChunkedIteratorTest.php + - + message: "#^Variable \\$currency on left side of \\?\\?\\= always exists and is always null\\.$#" + count: 1 + path: packages/formatter/src/Formats/IntlNumber/IntlCurrencyFormat.php + - message: "#^Method LastDragon_ru\\\\LaraASP\\\\Formatter\\\\Formatter\\:\\:getDefaultTimezone\\(\\) should return DateTimeZone\\|IntlTimeZone\\|string\\|null but returns mixed\\.$#" count: 1 From f7bf1cac3518cd5ba4c0059c60b63107647d833c Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Wed, 13 Nov 2024 15:29:09 +0400 Subject: [PATCH 13/22] New Duration formats. --- packages/formatter/src/Config/Config.php | 12 ++-- .../src/Config/Formats/DurationConfig.php | 16 ----- .../src/Config/Formats/DurationFormatIntl.php | 13 ---- .../Config/Formats/DurationFormatPattern.php | 13 ---- packages/formatter/src/Config/Locale.php | 2 - .../Duration/DurationFormat.php} | 67 +++++++++++-------- .../Duration/DurationFormatTest.php} | 50 +++----------- .../Duration/DurationOptions.php} | 7 +- .../Formats/IntlNumber/IntlDurationFormat.php | 39 +++++++++++ .../src/Formats/IntlNumber/IntlFormat.php | 2 +- packages/formatter/src/Formatter.php | 24 +------ packages/formatter/src/FormatterTest.php | 16 +++-- .../src/Formatters/Duration/Formatter.php | 65 ------------------ .../src/Formatters/Duration/IntlOptions.php | 9 --- packages/formatter/src/Utils/Duration.php | 31 +++++++++ packages/formatter/src/Utils/DurationTest.php | 56 ++++++++++++++++ 16 files changed, 200 insertions(+), 222 deletions(-) delete mode 100644 packages/formatter/src/Config/Formats/DurationConfig.php delete mode 100644 packages/formatter/src/Config/Formats/DurationFormatIntl.php delete mode 100644 packages/formatter/src/Config/Formats/DurationFormatPattern.php rename packages/formatter/src/{Formatters/Duration/PatternFormatter.php => Formats/Duration/DurationFormat.php} (73%) rename packages/formatter/src/{Formatters/Duration/PatternFormatterTest.php => Formats/Duration/DurationFormatTest.php} (53%) rename packages/formatter/src/{Formatters/Duration/PatternOptions.php => Formats/Duration/DurationOptions.php} (60%) create mode 100644 packages/formatter/src/Formats/IntlNumber/IntlDurationFormat.php delete mode 100644 packages/formatter/src/Formatters/Duration/Formatter.php delete mode 100644 packages/formatter/src/Formatters/Duration/IntlOptions.php create mode 100644 packages/formatter/src/Utils/Duration.php create mode 100644 packages/formatter/src/Utils/DurationTest.php diff --git a/packages/formatter/src/Config/Config.php b/packages/formatter/src/Config/Config.php index 8f85ccaa8..c8d21145c 100644 --- a/packages/formatter/src/Config/Config.php +++ b/packages/formatter/src/Config/Config.php @@ -6,8 +6,9 @@ use IntlDateFormatter; use LastDragon_ru\LaraASP\Core\Application\Configuration\Configuration; use LastDragon_ru\LaraASP\Formatter\Config\Formats\DateTimeFormat; -use LastDragon_ru\LaraASP\Formatter\Config\Formats\DurationFormatPattern; use LastDragon_ru\LaraASP\Formatter\Config\Formats\FilesizeFormat; +use LastDragon_ru\LaraASP\Formatter\Formats\Duration\DurationFormat; +use LastDragon_ru\LaraASP\Formatter\Formats\Duration\DurationOptions; use LastDragon_ru\LaraASP\Formatter\Formats\IntlNumber\IntlCurrencyFormat; use LastDragon_ru\LaraASP\Formatter\Formats\IntlNumber\IntlNumberFormat; use LastDragon_ru\LaraASP\Formatter\Formats\IntlNumber\IntlOptions; @@ -104,6 +105,12 @@ public function __construct( ], ), ); + $this->formats[Formatter::Duration] = new Format( + DurationFormat::class, + new DurationOptions( + 'HH:mm:ss.SSS', + ), + ); $this->global->datetime->formats += [ Formatter::Time => new DateTimeFormat( @@ -119,9 +126,6 @@ public function __construct( timeType: IntlDateFormatter::SHORT, ), ]; - $this->global->duration->formats += [ - Formatter::Default => new DurationFormatPattern('HH:mm:ss.SSS'), - ]; $this->global->filesize->formats += [ Formatter::Disksize => new FileSizeFormat( base : 1000, diff --git a/packages/formatter/src/Config/Formats/DurationConfig.php b/packages/formatter/src/Config/Formats/DurationConfig.php deleted file mode 100644 index 171899260..000000000 --- a/packages/formatter/src/Config/Formats/DurationConfig.php +++ /dev/null @@ -1,16 +0,0 @@ - - */ - public array $formats = [], - ) { - parent::__construct(); - } -} diff --git a/packages/formatter/src/Config/Formats/DurationFormatIntl.php b/packages/formatter/src/Config/Formats/DurationFormatIntl.php deleted file mode 100644 index ee11301d7..000000000 --- a/packages/formatter/src/Config/Formats/DurationFormatIntl.php +++ /dev/null @@ -1,13 +0,0 @@ - */ -class PatternFormatter { - final protected const SecondsInMinute = 60; - final protected const SecondsInHour = 60 * self::SecondsInMinute; - final protected const SecondsInDay = 24 * self::SecondsInHour; - final protected const SecondsInMonth = 30 * self::SecondsInDay; - final protected const SecondsInYear = 365 * self::SecondsInDay; - - public function __construct( - protected readonly string $pattern, - ) { - // empty - } +class DurationFormat implements Format { + protected readonly string $pattern; + + /** + * @param list $options + */ + public function __construct(array $options = []) { + // Collect options + $pattern = null; + + foreach ($options as $option) { + if ($option === null) { + continue; + } + + $pattern ??= $option->pattern; + } + + // Possible? + if ($pattern === null) { + throw new InvalidArgumentException('The `$patten` in unknown.'); + } - public static function getTimestamp(DateInterval $interval): float { - return ($interval->invert !== 0 ? -1 : 1) * (0 - + $interval->y * self::SecondsInYear - + $interval->m * self::SecondsInMonth - + $interval->d * self::SecondsInDay - + $interval->h * self::SecondsInHour - + $interval->i * self::SecondsInMinute - + $interval->s - + $interval->f); + // Save + $this->pattern = $pattern; } - public function format(float|int $value): string { + #[Override] + public function __invoke(mixed $value): string { $formatted = ''; $tokens = iterator_to_array(new UnicodeDateTimeFormatParser($this->pattern)); + $value = Duration::getTimestamp($value); $units = $this->prepare($tokens, abs($value), [ - 'y' => self::SecondsInYear, - 'M' => self::SecondsInMonth, - 'd' => self::SecondsInDay, - 'H' => self::SecondsInHour, - 'm' => self::SecondsInMinute, + 'y' => Duration::SecondsInYear, + 'M' => Duration::SecondsInMonth, + 'd' => Duration::SecondsInDay, + 'H' => Duration::SecondsInHour, + 'm' => Duration::SecondsInMinute, 's' => 1, 'S' => null, ]); diff --git a/packages/formatter/src/Formatters/Duration/PatternFormatterTest.php b/packages/formatter/src/Formats/Duration/DurationFormatTest.php similarity index 53% rename from packages/formatter/src/Formatters/Duration/PatternFormatterTest.php rename to packages/formatter/src/Formats/Duration/DurationFormatTest.php index 4cce0e725..798c49737 100644 --- a/packages/formatter/src/Formatters/Duration/PatternFormatterTest.php +++ b/packages/formatter/src/Formats/Duration/DurationFormatTest.php @@ -1,9 +1,8 @@ // ========================================================================= - #[DataProvider('dataProviderGetTimestamp')] - public function testGetTimestamp(float $expected, DateInterval $interval): void { - self::assertEquals($expected, PatternFormatter::getTimestamp($interval)); - } - #[DataProvider('dataProviderFormat')] - public function testFormat(string $expected, string $format, float|int $duration): void { - $formatter = new PatternFormatter($format); - $actual = $formatter->format($duration); + public function testFormat(string $expected, string $format, DateInterval|float|int|null $duration): void { + $formatter = new DurationFormat([new DurationOptions($format)]); + $actual = $formatter($duration); self::assertEquals($expected, $actual); } @@ -32,31 +26,9 @@ public function testFormat(string $expected, string $format, float|int $duration // // ========================================================================= /** - * @return array - */ - public static function dataProviderGetTimestamp(): array { - return [ - 'a' => [ - 22 * 365 * 24 * 60 * 60 + 22 * 30 * 24 * 60 * 60 + 22 * 24 * 60 * 60 + 22 * 60 * 60 + 22 * 60 + 22, - new DateInterval('P22Y22M22DT22H22M22S'), - ], - 'b' => [ - -1 * (16 * 24 * 60 * 60 - 0.000484), - (new DateTime('2023-12-27T11:22:45.000121+04:00'))->diff( - new DateTime('2023-12-11T11:22:45.000605+04:00'), - ), - ], - ]; - } - - /** - * @return array + * @return array */ public static function dataProviderFormat(): array { - $duration = static function (string $interval): float { - return PatternFormatter::getTimestamp(new DateInterval($interval)); - }; - return [ 'S' => ['3', 'S', 12.345678], 'SS' => ['35', 'SS', 12.345678], @@ -69,10 +41,10 @@ public static function dataProviderFormat(): array { 'zmm:ss' => ['-03:00', 'zmm:ss', -180], 'H:m:s' => ['5:3:0', 'H:m:s', 5 * 60 * 60 + 180], 'HH:mm:ss' => ['05:03:00', 'HH:mm:ss', 5 * 60 * 60 + 180], - 'y:M:d:H:m:s' => ['1:2:3:1:2:5', 'y:M:d:H:m:s', $duration('P1Y2M3DT1H2M5S')], - 'yyy:MM:dd:HH:mm:ss' => ['001:02:03:01:02:05', 'yyy:MM:dd:HH:mm:ss', $duration('P1Y2M3DT1H2M5S')], - "y:M:d:'H':m:s" => ['1:2:3:H:62:5', "y:M:d:'H':m:s", $duration('P1Y2M3DT1H2M5S')], - "y:'M':d:'H':m:s.SSS" => ['2:M:298:H:62:5.000', "y:'M':d:'H':m:s.SSS", $duration('P1Y22M3DT1H2M5S')], + 'y:M:d:H:m:s' => ['1:2:3:1:2:5', 'y:M:d:H:m:s', new DateInterval('P1Y2M3DT1H2M5S')], + 'yyy:MM:dd:HH:mm:ss' => ['001:02:03:01:02:05', 'yyy:MM:dd:HH:mm:ss', new DateInterval('P1Y2M3DT1H2M5S')], + "y:M:d:'H':m:s" => ['1:2:3:H:62:5', "y:M:d:'H':m:s", new DateInterval('P1Y2M3DT1H2M5S')], + "y:'M':d:'H':m:s.SSS" => ['2:M:298:H:62:5.000', "y:'M':d:'H':m:s.SSS", new DateInterval('P1Y22M3DT1H2M5S')], ]; } // diff --git a/packages/formatter/src/Formatters/Duration/PatternOptions.php b/packages/formatter/src/Formats/Duration/DurationOptions.php similarity index 60% rename from packages/formatter/src/Formatters/Duration/PatternOptions.php rename to packages/formatter/src/Formats/Duration/DurationOptions.php index 7382d4af1..55a48386c 100644 --- a/packages/formatter/src/Formatters/Duration/PatternOptions.php +++ b/packages/formatter/src/Formats/Duration/DurationOptions.php @@ -1,13 +1,10 @@ + */ +class IntlDurationFormat extends IntlFormat { + /** + * @param list $options + */ + public function __construct(Formatter $formatter, array $options = []) { + parent::__construct($formatter, [ + new IntlOptions(NumberFormatter::DURATION), + ...$options, + ]); + } + + #[Override] + public function __invoke(mixed $value): string { + $value = Duration::getTimestamp($value); + $formatted = $this->formatter->format($value); + + if ($formatted === false) { + throw new IntlException($this->formatter->getErrorMessage(), $this->formatter->getErrorCode()); + } + + return $formatted; + } +} diff --git a/packages/formatter/src/Formats/IntlNumber/IntlFormat.php b/packages/formatter/src/Formats/IntlNumber/IntlFormat.php index de6a28e50..58c424297 100644 --- a/packages/formatter/src/Formats/IntlNumber/IntlFormat.php +++ b/packages/formatter/src/Formats/IntlNumber/IntlFormat.php @@ -13,7 +13,7 @@ /** * @see NumberFormatter * - * @template TOptions of IntlOptions + * @template TOptions of IntlOptions|null * @template TValue * * @implements Format diff --git a/packages/formatter/src/Formatter.php b/packages/formatter/src/Formatter.php index 29e3602c0..07ea35ae5 100644 --- a/packages/formatter/src/Formatter.php +++ b/packages/formatter/src/Formatter.php @@ -15,7 +15,6 @@ use LastDragon_ru\LaraASP\Formatter\Exceptions\FormatterFailedToCreateFormatter; use LastDragon_ru\LaraASP\Formatter\Exceptions\FormatterFailedToFormatValue; use LastDragon_ru\LaraASP\Formatter\Formatters\DateTime\Formatter as DateTimeFormatter; -use LastDragon_ru\LaraASP\Formatter\Formatters\Duration\Formatter as DurationFormatter; use OutOfBoundsException; use Stringable; @@ -44,9 +43,9 @@ class Formatter { public const Disksize = 'disksize'; public const Secret = 'secret'; public const Currency = 'currency'; + public const Duration = 'duration'; private const FormatterDateTime = 'DateTime'; - private const FormatterDuration = 'Duration'; private const FormatterFilesize = 'Filesize'; private ?string $locale = null; @@ -181,7 +180,7 @@ public function ordinal(?int $value): string { } public function duration(DateInterval|float|int|null $value): string { - return $this->formatDuration(self::Default, $value); + return $this->format(self::Duration, $value); } public function time(?DateTimeInterface $value): string { @@ -263,25 +262,6 @@ protected function formatDateTime(string $format, ?DateTimeInterface $value): st return $formatted; } - protected function formatDuration(string $format, DateInterval|float|int|null $value): string { - // Format - try { - $config = $this->package->getInstance(); - $locale = $this->getLocale(); - $formatter = new DurationFormatter( - $this, - $config->locales[$locale]->duration->formats[$format] ?? null, - $config->global->duration->formats[$format] ?? null, - ); - $formatted = $formatter->format($value ?? 0); - } catch (Exception $exception) { - throw new FormatterFailedToFormatValue(self::FormatterDuration, $format, $value, $exception); - } - - // Return - return $formatted; - } - /** * @param numeric-string|float|int|null $bytes */ diff --git a/packages/formatter/src/FormatterTest.php b/packages/formatter/src/FormatterTest.php index 54039521f..0fb3b0209 100644 --- a/packages/formatter/src/FormatterTest.php +++ b/packages/formatter/src/FormatterTest.php @@ -8,8 +8,9 @@ use LastDragon_ru\LaraASP\Formatter\Config\Config; use LastDragon_ru\LaraASP\Formatter\Config\Format; use LastDragon_ru\LaraASP\Formatter\Config\Formats\DateTimeFormat; -use LastDragon_ru\LaraASP\Formatter\Config\Formats\DurationFormatIntl; -use LastDragon_ru\LaraASP\Formatter\Config\Formats\DurationFormatPattern; +use LastDragon_ru\LaraASP\Formatter\Formats\Duration\DurationFormat; +use LastDragon_ru\LaraASP\Formatter\Formats\Duration\DurationOptions; +use LastDragon_ru\LaraASP\Formatter\Formats\IntlNumber\IntlDurationFormat; use LastDragon_ru\LaraASP\Formatter\Formats\IntlNumber\IntlNumberFormat; use LastDragon_ru\LaraASP\Formatter\Formats\IntlNumber\IntlOptions; use LastDragon_ru\LaraASP\Formatter\Formats\Secret\SecretFormat; @@ -167,7 +168,9 @@ public function testDuration(): void { public function testDurationConfig(): void { $this->setConfiguration(PackageConfig::class, static function (Config $config): void { - $config->global->duration->formats[Formatter::Default] = new DurationFormatIntl(); + $config->formats[Formatter::Duration] = new Format( + IntlDurationFormat::class, + ); }); self::assertEquals('3:25:45', $this->formatter->duration(12_345)); @@ -176,7 +179,12 @@ public function testDurationConfig(): void { public function testDurationCustomFormat(): void { $this->setConfiguration(PackageConfig::class, static function (Config $config): void { - $config->global->duration->formats[Formatter::Default] = new DurationFormatPattern('mm:ss'); + $config->formats[Formatter::Duration] = new Format( + DurationFormat::class, + new DurationOptions( + 'mm:ss', + ), + ); }); self::assertEquals('02:03', $this->formatter->duration(123.456)); diff --git a/packages/formatter/src/Formatters/Duration/Formatter.php b/packages/formatter/src/Formatters/Duration/Formatter.php deleted file mode 100644 index 876bac98d..000000000 --- a/packages/formatter/src/Formatters/Duration/Formatter.php +++ /dev/null @@ -1,65 +0,0 @@ -pattern; - - if ($option instanceof IntlOptions) { - $intl[] = $option; - } - } - - // Possible? - if ($isPattern === null) { - throw new InvalidArgumentException('The formatter type in unknown.'); - } - - if ($isPattern === true && $pattern === null) { - throw new InvalidArgumentException('The `$patten` in unknown.'); - } - - // Create - $this->formatter = $isPattern === false - ? new IntlNumberFormat($formatter, $intl) - : new PatternFormatter($pattern); - } - - public function format(DateInterval|float|int $value): string { - $value = $value instanceof DateInterval ? PatternFormatter::getTimestamp($value) : $value; - $formatted = $this->formatter instanceof PatternFormatter - ? $this->formatter->format($value) - : ($this->formatter)($value); - - return $formatted; - } -} diff --git a/packages/formatter/src/Formatters/Duration/IntlOptions.php b/packages/formatter/src/Formatters/Duration/IntlOptions.php deleted file mode 100644 index eadd78c82..000000000 --- a/packages/formatter/src/Formatters/Duration/IntlOptions.php +++ /dev/null @@ -1,9 +0,0 @@ - ($interval->invert !== 0 ? -1 : 1) * (0 + + $interval->y * self::SecondsInYear + + $interval->m * self::SecondsInMonth + + $interval->d * self::SecondsInDay + + $interval->h * self::SecondsInHour + + $interval->i * self::SecondsInMinute + + $interval->s + + $interval->f), + $interval === null => 0, + default => $interval, + }; + } +} diff --git a/packages/formatter/src/Utils/DurationTest.php b/packages/formatter/src/Utils/DurationTest.php new file mode 100644 index 000000000..d2e4ee359 --- /dev/null +++ b/packages/formatter/src/Utils/DurationTest.php @@ -0,0 +1,56 @@ + + // ========================================================================= + #[DataProvider('dataProviderGetTimestamp')] + public function testGetTimestamp(float $expected, DateInterval|float|int|null $interval): void { + self::assertEquals($expected, Duration::getTimestamp($interval)); + } + // + + // + // ========================================================================= + /** + * @return array + */ + public static function dataProviderGetTimestamp(): array { + return [ + 'DateInterval' => [ + 22 * 365 * 24 * 60 * 60 + 22 * 30 * 24 * 60 * 60 + 22 * 24 * 60 * 60 + 22 * 60 * 60 + 22 * 60 + 22, + new DateInterval('P22Y22M22DT22H22M22S'), + ], + 'DateInterval (negative)' => [ + -1 * (16 * 24 * 60 * 60 - 0.000484), + (new DateTime('2023-12-27T11:22:45.000121+04:00'))->diff( + new DateTime('2023-12-11T11:22:45.000605+04:00'), + ), + ], + 'float' => [ + 123.45, + 123.45, + ], + 'int' => [ + 123, + 123, + ], + 'null' => [ + 0, + null, + ], + ]; + } + // +} From 7553c20a8217e68c8e0e3d8d023f8cdf54974124 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Thu, 14 Nov 2024 09:54:04 +0400 Subject: [PATCH 14/22] New DateTime formats. --- packages/formatter/src/Config/Config.php | 22 ++++--- .../src/Config/Formats/DateTimeConfig.php | 16 ----- .../src/Config/Formats/DateTimeFormat.php | 14 ----- packages/formatter/src/Config/Locale.php | 2 - .../IntlDateTime/IntlDateTimeFormat.php} | 47 ++++++++------ .../IntlDateTime/IntlDateTimeOptions.php} | 5 +- packages/formatter/src/Formatter.php | 36 +---------- packages/formatter/src/FormatterTest.php | 63 ++++++++++++++----- 8 files changed, 96 insertions(+), 109 deletions(-) delete mode 100644 packages/formatter/src/Config/Formats/DateTimeConfig.php delete mode 100644 packages/formatter/src/Config/Formats/DateTimeFormat.php rename packages/formatter/src/{Formatters/DateTime/Formatter.php => Formats/IntlDateTime/IntlDateTimeFormat.php} (52%) rename packages/formatter/src/{Formatters/DateTime/Options.php => Formats/IntlDateTime/IntlDateTimeOptions.php} (71%) diff --git a/packages/formatter/src/Config/Config.php b/packages/formatter/src/Config/Config.php index c8d21145c..9bf2df103 100644 --- a/packages/formatter/src/Config/Config.php +++ b/packages/formatter/src/Config/Config.php @@ -5,10 +5,11 @@ use Exception; use IntlDateFormatter; use LastDragon_ru\LaraASP\Core\Application\Configuration\Configuration; -use LastDragon_ru\LaraASP\Formatter\Config\Formats\DateTimeFormat; use LastDragon_ru\LaraASP\Formatter\Config\Formats\FilesizeFormat; use LastDragon_ru\LaraASP\Formatter\Formats\Duration\DurationFormat; use LastDragon_ru\LaraASP\Formatter\Formats\Duration\DurationOptions; +use LastDragon_ru\LaraASP\Formatter\Formats\IntlDateTime\IntlDateTimeFormat; +use LastDragon_ru\LaraASP\Formatter\Formats\IntlDateTime\IntlDateTimeOptions; use LastDragon_ru\LaraASP\Formatter\Formats\IntlNumber\IntlCurrencyFormat; use LastDragon_ru\LaraASP\Formatter\Formats\IntlNumber\IntlNumberFormat; use LastDragon_ru\LaraASP\Formatter\Formats\IntlNumber\IntlOptions; @@ -111,21 +112,28 @@ public function __construct( 'HH:mm:ss.SSS', ), ); - - $this->global->datetime->formats += [ - Formatter::Time => new DateTimeFormat( + $this->formats[Formatter::Time] = new Format( + IntlDateTimeFormat::class, + new IntlDateTimeOptions( dateType: IntlDateFormatter::NONE, timeType: IntlDateFormatter::SHORT, ), - Formatter::Date => new DateTimeFormat( + ); + $this->formats[Formatter::Date] = new Format( + IntlDateTimeFormat::class, + new IntlDateTimeOptions( dateType: IntlDateFormatter::SHORT, timeType: IntlDateFormatter::NONE, ), - Formatter::DateTime => new DateTimeFormat( + ); + $this->formats[Formatter::DateTime] = new Format( + IntlDateTimeFormat::class, + new IntlDateTimeOptions( dateType: IntlDateFormatter::SHORT, timeType: IntlDateFormatter::SHORT, ), - ]; + ); + $this->global->filesize->formats += [ Formatter::Disksize => new FileSizeFormat( base : 1000, diff --git a/packages/formatter/src/Config/Formats/DateTimeConfig.php b/packages/formatter/src/Config/Formats/DateTimeConfig.php deleted file mode 100644 index ab0133b50..000000000 --- a/packages/formatter/src/Config/Formats/DateTimeConfig.php +++ /dev/null @@ -1,16 +0,0 @@ - - */ - public array $formats = [], - ) { - parent::__construct(); - } -} diff --git a/packages/formatter/src/Config/Formats/DateTimeFormat.php b/packages/formatter/src/Config/Formats/DateTimeFormat.php deleted file mode 100644 index 384c89b4e..000000000 --- a/packages/formatter/src/Config/Formats/DateTimeFormat.php +++ /dev/null @@ -1,14 +0,0 @@ - */ -class Formatter { +class IntlDateTimeFormat implements Format { protected readonly IntlDateFormatter $formatter; - public function __construct( - protected readonly string $locale, - protected readonly IntlTimeZone|DateTimeZone|string|null $timezone, - ?Options ...$options, - ) { + /** + * @param list $options + */ + public function __construct(Formatter $formatter, array $options = []) { // Collect options $dateType = null; $timeType = null; $pattern = null; - foreach ($options as $intl) { - if ($intl === null) { + foreach ($options as $option) { + if ($option === null) { continue; } - $dateType ??= $intl->dateType; - $timeType ??= $intl->timeType; - $pattern ??= $intl->pattern; + $dateType ??= $option->dateType; + $timeType ??= $option->timeType; + $pattern ??= $option->pattern; } // Possible? @@ -47,11 +49,20 @@ public function __construct( } // Create + $locale = $formatter->getLocale(); $pattern = $pattern !== '' ? $pattern : null; + $timezone = $formatter->getTimezone(); $this->formatter = new IntlDateFormatter($locale, $dateType, $timeType, $timezone, null, $pattern); } - public function format(DateTimeInterface $value): string { + #[Override] + public function __invoke(mixed $value): string { + // Null? + if (is_null($value)) { + return ''; + } + + // Format $formatted = $this->formatter->format($value); if ($formatted === false) { diff --git a/packages/formatter/src/Formatters/DateTime/Options.php b/packages/formatter/src/Formats/IntlDateTime/IntlDateTimeOptions.php similarity index 71% rename from packages/formatter/src/Formatters/DateTime/Options.php rename to packages/formatter/src/Formats/IntlDateTime/IntlDateTimeOptions.php index cd3cc48bb..27aaacb32 100644 --- a/packages/formatter/src/Formatters/DateTime/Options.php +++ b/packages/formatter/src/Formats/IntlDateTime/IntlDateTimeOptions.php @@ -1,14 +1,15 @@ formatDateTime(self::Time, $value); + return $this->format(self::Time, $value); } public function date(?DateTimeInterface $value): string { - return $this->formatDateTime(self::Date, $value); + return $this->format(self::Date, $value); } public function datetime(?DateTimeInterface $value): string { - return $this->formatDateTime(self::DateTime, $value); + return $this->format(self::DateTime, $value); } /** @@ -236,32 +232,6 @@ protected function getTranslation(array|string $key, array $replace = []): strin return $this->getTranslator()->get($key, $replace, $this->getLocale()); } - protected function formatDateTime(string $format, ?DateTimeInterface $value): string { - // Null? - if (is_null($value)) { - return ''; - } - - // Format - try { - $config = $this->package->getInstance(); - $locale = $this->getLocale(); - $timezone = $this->getTimezone(); - $formatter = new DateTimeFormatter( - $locale, - $timezone, - $config->locales[$locale]->datetime->formats[$format] ?? null, - $config->global->datetime->formats[$format] ?? null, - ); - $formatted = $formatter->format($value); - } catch (Exception $exception) { - throw new FormatterFailedToFormatValue(self::FormatterDateTime, $format, $value, $exception); - } - - // Return - return $formatted; - } - /** * @param numeric-string|float|int|null $bytes */ diff --git a/packages/formatter/src/FormatterTest.php b/packages/formatter/src/FormatterTest.php index 0fb3b0209..bdb6e9f86 100644 --- a/packages/formatter/src/FormatterTest.php +++ b/packages/formatter/src/FormatterTest.php @@ -4,12 +4,12 @@ use DateTime; use IntlDateFormatter; -use LastDragon_ru\LaraASP\Core\Utils\Cast; use LastDragon_ru\LaraASP\Formatter\Config\Config; use LastDragon_ru\LaraASP\Formatter\Config\Format; -use LastDragon_ru\LaraASP\Formatter\Config\Formats\DateTimeFormat; use LastDragon_ru\LaraASP\Formatter\Formats\Duration\DurationFormat; use LastDragon_ru\LaraASP\Formatter\Formats\Duration\DurationOptions; +use LastDragon_ru\LaraASP\Formatter\Formats\IntlDateTime\IntlDateTimeFormat; +use LastDragon_ru\LaraASP\Formatter\Formats\IntlDateTime\IntlDateTimeOptions; use LastDragon_ru\LaraASP\Formatter\Formats\IntlNumber\IntlDurationFormat; use LastDragon_ru\LaraASP\Formatter\Formats\IntlNumber\IntlNumberFormat; use LastDragon_ru\LaraASP\Formatter\Formats\IntlNumber\IntlOptions; @@ -199,8 +199,13 @@ public function testTime(): void { public function testTimeConfig(): void { $this->setConfiguration(PackageConfig::class, static function (Config $config): void { - $format = Cast::to(DateTimeFormat::class, $config->global->datetime->formats[Formatter::Time]); - $format->timeType = IntlDateFormatter::MEDIUM; + $config->formats[Formatter::Time] = new Format( + IntlDateTimeFormat::class, + new IntlDateTimeOptions( + dateType: IntlDateFormatter::NONE, + timeType: IntlDateFormatter::MEDIUM, + ), + ); }); $time = DateTime::createFromFormat('H:i:s', '23:24:59'); @@ -211,8 +216,14 @@ public function testTimeConfig(): void { public function testTimeCustomFormat(): void { $this->setConfiguration(PackageConfig::class, static function (Config $config): void { - $format = Cast::to(DateTimeFormat::class, $config->global->datetime->formats[Formatter::Time]); - $format->pattern = 'HH:mm:ss.SSS'; + $config->formats[Formatter::Time] = new Format( + IntlDateTimeFormat::class, + new IntlDateTimeOptions( + dateType: IntlDateFormatter::NONE, + timeType: IntlDateFormatter::SHORT, + pattern : 'HH:mm:ss.SSS', + ), + ); }); $time = DateTime::createFromFormat('H:i:s', '23:24:59'); @@ -230,8 +241,13 @@ public function testDate(): void { public function testDateConfig(): void { $this->setConfiguration(PackageConfig::class, static function (Config $config): void { - $format = Cast::to(DateTimeFormat::class, $config->global->datetime->formats[Formatter::Date]); - $format->dateType = IntlDateFormatter::MEDIUM; + $config->formats[Formatter::Date] = new Format( + IntlDateTimeFormat::class, + new IntlDateTimeOptions( + dateType: IntlDateFormatter::MEDIUM, + timeType: IntlDateFormatter::NONE, + ), + ); }); $date = DateTime::createFromFormat('d.m.Y H:i:s', '12.05.2005 23:00:00'); @@ -242,8 +258,14 @@ public function testDateConfig(): void { public function testDateCustomFormat(): void { $this->setConfiguration(PackageConfig::class, static function (Config $config): void { - $format = Cast::to(DateTimeFormat::class, $config->global->datetime->formats[Formatter::Date]); - $format->pattern = 'd MMM YYYY'; + $config->formats[Formatter::Date] = new Format( + IntlDateTimeFormat::class, + new IntlDateTimeOptions( + dateType: IntlDateFormatter::SHORT, + timeType: IntlDateFormatter::NONE, + pattern : 'd MMM YYYY', + ), + ); }); $date = DateTime::createFromFormat('d.m.Y H:i:s', '12.05.2005 23:00:00'); @@ -261,12 +283,13 @@ public function testDatetime(): void { public function testDatetimeConfig(): void { $this->setConfiguration(PackageConfig::class, static function (Config $config): void { - $format = Cast::to( - DateTimeFormat::class, - $config->global->datetime->formats[Formatter::DateTime], + $config->formats[Formatter::DateTime] = new Format( + IntlDateTimeFormat::class, + new IntlDateTimeOptions( + dateType: IntlDateFormatter::MEDIUM, + timeType: IntlDateFormatter::MEDIUM, + ), ); - $format->dateType = IntlDateFormatter::MEDIUM; - $format->timeType = IntlDateFormatter::MEDIUM; }); $datetime = DateTime::createFromFormat('d.m.Y H:i:s', '12.05.2005 23:00:00'); @@ -280,8 +303,14 @@ public function testDatetimeConfig(): void { public function testDatetimeCustomFormat(): void { $this->setConfiguration(PackageConfig::class, static function (Config $config): void { - $format = Cast::to(DateTimeFormat::class, $config->global->datetime->formats[Formatter::DateTime]); - $format->pattern = 'd MMM YYYY || HH:mm:ss'; + $config->formats[Formatter::DateTime] = new Format( + IntlDateTimeFormat::class, + new IntlDateTimeOptions( + dateType: IntlDateFormatter::MEDIUM, + timeType: IntlDateFormatter::MEDIUM, + pattern : 'd MMM YYYY || HH:mm:ss', + ), + ); }); $datetime = DateTime::createFromFormat('d.m.Y H:i:s', '12.05.2005 23:00:00'); From f06a7d896e467270a3da76eadd5af6d2fd5d564b Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Thu, 14 Nov 2024 10:21:47 +0400 Subject: [PATCH 15/22] New Filesize/Disksize formats. --- packages/formatter/src/Config/Config.php | 26 ++---- .../src/Config/Formats/FilesizeConfig.php | 16 ---- .../src/Config/Formats/FilesizeFormat.php | 36 -------- packages/formatter/src/Config/Locale.php | 14 --- .../src/Formats/Filesize/FilesizeFormat.php | 91 +++++++++++++++++++ .../src/Formats/Filesize/FilesizeOptions.php | 21 +++++ packages/formatter/src/Formatter.php | 76 +--------------- 7 files changed, 124 insertions(+), 156 deletions(-) delete mode 100644 packages/formatter/src/Config/Formats/FilesizeConfig.php delete mode 100644 packages/formatter/src/Config/Formats/FilesizeFormat.php delete mode 100644 packages/formatter/src/Config/Locale.php create mode 100644 packages/formatter/src/Formats/Filesize/FilesizeFormat.php create mode 100644 packages/formatter/src/Formats/Filesize/FilesizeOptions.php diff --git a/packages/formatter/src/Config/Config.php b/packages/formatter/src/Config/Config.php index 9bf2df103..b05c71d69 100644 --- a/packages/formatter/src/Config/Config.php +++ b/packages/formatter/src/Config/Config.php @@ -5,9 +5,10 @@ use Exception; use IntlDateFormatter; use LastDragon_ru\LaraASP\Core\Application\Configuration\Configuration; -use LastDragon_ru\LaraASP\Formatter\Config\Formats\FilesizeFormat; use LastDragon_ru\LaraASP\Formatter\Formats\Duration\DurationFormat; use LastDragon_ru\LaraASP\Formatter\Formats\Duration\DurationOptions; +use LastDragon_ru\LaraASP\Formatter\Formats\Filesize\FilesizeFormat; +use LastDragon_ru\LaraASP\Formatter\Formats\Filesize\FilesizeOptions; use LastDragon_ru\LaraASP\Formatter\Formats\IntlDateTime\IntlDateTimeFormat; use LastDragon_ru\LaraASP\Formatter\Formats\IntlDateTime\IntlDateTimeOptions; use LastDragon_ru\LaraASP\Formatter\Formats\IntlNumber\IntlCurrencyFormat; @@ -26,16 +27,6 @@ public function __construct( * @var array> */ public array $formats = [], - /** - * Options and patterns/formats for all locales. - */ - public Locale $global = new Locale(), - /** - * Options and patterns/formats for concrete locale. - * - * @var array - */ - public array $locales = [], ) { parent::__construct(); @@ -133,9 +124,9 @@ public function __construct( timeType: IntlDateFormatter::SHORT, ), ); - - $this->global->filesize->formats += [ - Formatter::Disksize => new FileSizeFormat( + $this->formats[Formatter::Disksize] = new Format( + FilesizeFormat::class, + new FilesizeOptions( base : 1000, units: [ ['disksize.B', 'B'], @@ -151,7 +142,10 @@ public function __construct( ['disksize.QB', 'QB'], ], ), - Formatter::Filesize => new FileSizeFormat( + ); + $this->formats[Formatter::Filesize] = new Format( + FilesizeFormat::class, + new FilesizeOptions( base : 1024, units: [ ['filesize.B', 'B'], @@ -167,7 +161,7 @@ public function __construct( ['filesize.QiB', 'QiB'], ], ), - ]; + ); } /** diff --git a/packages/formatter/src/Config/Formats/FilesizeConfig.php b/packages/formatter/src/Config/Formats/FilesizeConfig.php deleted file mode 100644 index 39f996e15..000000000 --- a/packages/formatter/src/Config/Formats/FilesizeConfig.php +++ /dev/null @@ -1,16 +0,0 @@ - - */ - public array $formats = [], - ) { - parent::__construct(); - } -} diff --git a/packages/formatter/src/Config/Formats/FilesizeFormat.php b/packages/formatter/src/Config/Formats/FilesizeFormat.php deleted file mode 100644 index c9ed957f6..000000000 --- a/packages/formatter/src/Config/Formats/FilesizeFormat.php +++ /dev/null @@ -1,36 +0,0 @@ -`). - * The last string will be used as a default value if no translation. - * - * @var non-empty-list>|null - */ - public ?array $units = null, - /** - * The number format name that will be used if the display value is - * integer (no fraction part). Default is {@see Formatter::Integer}. - * - * @see Formatter::Integer - */ - public ?string $integerFormat = null, - /** - * The number format name that will be used if the display value is - * float. Default is {@see Formatter::Decimal}. - * - * @see Formatter::Decimal - */ - public ?string $decimalFormat = null, - ) { - parent::__construct(); - } -} diff --git a/packages/formatter/src/Config/Locale.php b/packages/formatter/src/Config/Locale.php deleted file mode 100644 index e7447ca08..000000000 --- a/packages/formatter/src/Config/Locale.php +++ /dev/null @@ -1,14 +0,0 @@ - + */ +class FilesizeFormat implements Format { + protected readonly int $base; + /** + * @var non-empty-list> + */ + protected readonly array $units; + + /** + * @param list $options + */ + public function __construct( + protected PackageTranslator $translator, + protected readonly Formatter $formatter, + array $options = [], + ) { + // Collect options + $base = null; + $units = null; + + foreach ($options as $option) { + if ($option === null) { + continue; + } + + $base ??= $option->base; + $units ??= $option->units; + } + + // Possible? + if ($base === null) { + throw new InvalidArgumentException('The `$base` in unknown.'); + } + + if ($units === null) { + throw new InvalidArgumentException('The `$units` in unknown.'); + } + + // Save + $this->base = $base; + $this->units = $units; + } + + #[Override] + public function __invoke(mixed $value): string { + $unit = 0; + $base = (string) $this->base; + $scale = mb_strlen($base) + 1; + $value = match (true) { + is_float($value) => sprintf('%0.0f', $value), + $value === null => '0', + default => (string) $value, + }; + $length = static function (string $bytes): int { + return mb_strlen(Str::before($bytes, '.')); + }; + + while ((bccomp($value, $base, $scale) >= 0 || $length($value) > 2) && isset($this->units[$unit + 1])) { + $value = bcdiv($value, $base, $scale); + $unit++; + } + + // Format + $isInt = $unit === 0; + $value = $isInt ? (int) $value : (float) $value; + $format = $isInt ? Formatter::Integer : Formatter::Decimal; + $suffix = $this->translator->get($this->units[$unit], [], $this->formatter->getLocale()); + $formatted = "{$this->formatter->format($format, $value)} {$suffix}"; + + return $formatted; + } +} diff --git a/packages/formatter/src/Formats/Filesize/FilesizeOptions.php b/packages/formatter/src/Formats/Filesize/FilesizeOptions.php new file mode 100644 index 000000000..00f4754ab --- /dev/null +++ b/packages/formatter/src/Formats/Filesize/FilesizeOptions.php @@ -0,0 +1,21 @@ +`). + * The last string will be used as a default value if no translation. + * + * @var non-empty-list>|null + */ + public ?array $units = null, + ) { + parent::__construct(); + } +} diff --git a/packages/formatter/src/Formatter.php b/packages/formatter/src/Formatter.php index 7661cba7a..9c6674280 100644 --- a/packages/formatter/src/Formatter.php +++ b/packages/formatter/src/Formatter.php @@ -6,21 +6,15 @@ use DateTimeInterface; use DateTimeZone; use Exception; -use Illuminate\Support\Str; use Illuminate\Support\Traits\Macroable; use IntlTimeZone; use LastDragon_ru\LaraASP\Core\Application\ApplicationResolver; use LastDragon_ru\LaraASP\Core\Application\ConfigResolver; use LastDragon_ru\LaraASP\Formatter\Contracts\Format; -use LastDragon_ru\LaraASP\Formatter\Exceptions\FormatterFailedToCreateFormatter; use LastDragon_ru\LaraASP\Formatter\Exceptions\FormatterFailedToFormatValue; use OutOfBoundsException; use Stringable; -use function bccomp; -use function bcdiv; -use function is_float; -use function mb_strlen; use function sprintf; class Formatter { @@ -42,8 +36,6 @@ class Formatter { public const Currency = 'currency'; public const Duration = 'duration'; - private const FormatterFilesize = 'Filesize'; - private ?string $locale = null; private IntlTimeZone|DateTimeZone|string|null $timezone = null; @@ -51,7 +43,6 @@ public function __construct( protected readonly ApplicationResolver $application, protected readonly ConfigResolver $config, protected readonly PackageConfig $package, - private PackageTranslator $translator, ) { // empty } @@ -96,10 +87,6 @@ public function getLocale(): string { public function getTimezone(): IntlTimeZone|DateTimeZone|string|null { return $this->timezone ?? $this->getDefaultTimezone(); } - - protected function getTranslator(): PackageTranslator { - return $this->translator; - } // // @@ -197,7 +184,7 @@ public function datetime(?DateTimeInterface $value): string { * @param numeric-string|float|int|null $bytes */ public function filesize(string|float|int|null $bytes): string { - return $this->formatFilesize(self::Filesize, $bytes); + return $this->format(self::Filesize, $bytes); } /** @@ -206,7 +193,7 @@ public function filesize(string|float|int|null $bytes): string { * @param numeric-string|float|int|null $bytes */ public function disksize(string|float|int|null $bytes): string { - return $this->formatFilesize(self::Disksize, $bytes); + return $this->format(self::Disksize, $bytes); } public function secret(?string $value): string { @@ -223,64 +210,5 @@ protected function getDefaultLocale(): string { protected function getDefaultTimezone(): IntlTimeZone|DateTimeZone|string|null { return $this->config->getInstance()->get('app.timezone') ?? null; } - - /** - * @param list|string $key - * @param array $replace - */ - protected function getTranslation(array|string $key, array $replace = []): string { - return $this->getTranslator()->get($key, $replace, $this->getLocale()); - } - - /** - * @param numeric-string|float|int|null $bytes - */ - protected function formatFilesize(string $format, string|float|int|null $bytes): string { - // Prepare - $config = $this->package->getInstance(); - $locale = $this->getLocale(); - $base = $config->locales[$locale]->filesize->formats[$format]->base - ?? $config->global->filesize->formats[$format]->base - ?? null; - $units = $config->locales[$locale]->filesize->formats[$format]->units - ?? $config->global->filesize->formats[$format]->units - ?? null; - $integerFormat = $config->locales[$locale]->filesize->formats[$format]->integerFormat - ?? $config->global->filesize->formats[$format]->integerFormat - ?? self::Integer; - $decimalFormat = $config->locales[$locale]->filesize->formats[$format]->decimalFormat - ?? $config->global->filesize->formats[$format]->decimalFormat - ?? self::Decimal; - - if ($base === null || $units === null) { - throw new FormatterFailedToCreateFormatter(self::FormatterFilesize, $format); - } - - $unit = 0; - $base = (string) $base; - $scale = mb_strlen($base) + 1; - $bytes = match (true) { - is_float($bytes) => sprintf('%0.0f', $bytes), - $bytes === null => '0', - default => (string) $bytes, - }; - $length = static function (string $bytes): int { - return mb_strlen(Str::before($bytes, '.')); - }; - - while ((bccomp($bytes, $base, $scale) >= 0 || $length($bytes) > 2) && isset($units[$unit + 1])) { - $bytes = bcdiv($bytes, $base, $scale); - $unit++; - } - - // Format - $isInt = $unit === 0; - $bytes = $isInt ? (int) $bytes : (float) $bytes; - $format = $isInt ? $integerFormat : $decimalFormat; - $suffix = $this->getTranslation($units[$unit]); - $formatted = "{$this->format($format, $bytes)} {$suffix}"; - - return $formatted; - } // } From 048311b87a66aa50acabd4baae146a0113aa83a4 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Thu, 14 Nov 2024 10:54:15 +0400 Subject: [PATCH 16/22] Code cleanup. --- .../FormatterFailedToCreateFormatter.php | 32 ------------------- .../FormatterFailedToFormatValue.php | 8 +---- packages/formatter/src/Formatter.php | 2 +- 3 files changed, 2 insertions(+), 40 deletions(-) delete mode 100644 packages/formatter/src/Exceptions/FormatterFailedToCreateFormatter.php diff --git a/packages/formatter/src/Exceptions/FormatterFailedToCreateFormatter.php b/packages/formatter/src/Exceptions/FormatterFailedToCreateFormatter.php deleted file mode 100644 index cbff5551a..000000000 --- a/packages/formatter/src/Exceptions/FormatterFailedToCreateFormatter.php +++ /dev/null @@ -1,32 +0,0 @@ -getFormatter(), - $this->getFormat(), - ), - $previous, - ); - } - - public function getFormatter(): string { - return $this->formatter; - } - - public function getFormat(): string { - return $this->format; - } -} diff --git a/packages/formatter/src/Exceptions/FormatterFailedToFormatValue.php b/packages/formatter/src/Exceptions/FormatterFailedToFormatValue.php index 492f793a4..fc2914afd 100644 --- a/packages/formatter/src/Exceptions/FormatterFailedToFormatValue.php +++ b/packages/formatter/src/Exceptions/FormatterFailedToFormatValue.php @@ -9,25 +9,19 @@ class FormatterFailedToFormatValue extends PackageException { public function __construct( - protected string $formatter, protected string $format, protected mixed $value, ?Throwable $previous = null, ) { parent::__construct( sprintf( - 'Formatter `%s` failed to format value into `%s` format.', - $this->getFormatter(), + 'Failed to format value into `%s` format.', $this->getFormat(), ), $previous, ); } - public function getFormatter(): string { - return $this->formatter; - } - public function getFormat(): string { return $this->format; } diff --git a/packages/formatter/src/Formatter.php b/packages/formatter/src/Formatter.php index 9c6674280..d2e47d4ff 100644 --- a/packages/formatter/src/Formatter.php +++ b/packages/formatter/src/Formatter.php @@ -95,7 +95,7 @@ public function format(string $format, mixed $value): string { try { return ($this->getFormat($format))($value); } catch (Exception $exception) { - throw new FormatterFailedToFormatValue($format, $format, $value, $exception); + throw new FormatterFailedToFormatValue($format, $value, $exception); } } From 7289deb7443b35f91b222f610a83830d49d945b7 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Thu, 14 Nov 2024 11:07:30 +0400 Subject: [PATCH 17/22] Formats cache. --- packages/formatter/src/Formatter.php | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/formatter/src/Formatter.php b/packages/formatter/src/Formatter.php index d2e47d4ff..6eaa505ee 100644 --- a/packages/formatter/src/Formatter.php +++ b/packages/formatter/src/Formatter.php @@ -36,6 +36,10 @@ class Formatter { public const Currency = 'currency'; public const Duration = 'duration'; + /** + * @var array> + */ + private array $cache = []; private ?string $locale = null; private IntlTimeZone|DateTimeZone|string|null $timezone = null; @@ -103,6 +107,11 @@ public function format(string $format, mixed $value): string { * @return Format<*, mixed> */ protected function getFormat(string $format): Format { + // Cached? + if (isset($this->cache[$format])) { + return $this->cache[$format]; + } + // Known? $config = $this->package->getInstance(); $settings = $config->formats[$format] ?? null; @@ -112,8 +121,8 @@ protected function getFormat(string $format): Format { } // Create - $locale = $this->getLocale(); - $formatter = $this->application->getInstance()->make($settings->class, [ + $locale = $this->getLocale(); + $this->cache[$format] = $this->application->getInstance()->make($settings->class, [ 'formatter' => $this, 'options' => [ $settings->locales[$locale] ?? null, @@ -121,7 +130,7 @@ protected function getFormat(string $format): Format { ], ]); - return $formatter; + return $this->cache[$format]; } // @@ -210,5 +219,9 @@ protected function getDefaultLocale(): string { protected function getDefaultTimezone(): IntlTimeZone|DateTimeZone|string|null { return $this->config->getInstance()->get('app.timezone') ?? null; } + + public function __clone(): void { + $this->cache = []; + } // } From e85b6c30ebd4f996abf253e50e92d2220e6608dc Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Thu, 14 Nov 2024 11:29:56 +0400 Subject: [PATCH 18/22] Global Intl options. --- packages/formatter/src/Config/Config.php | 49 +++++++++---------- packages/formatter/src/Config/Intl.php | 14 ++++++ .../Formats/IntlNumber/IntlCurrencyFormat.php | 11 +++-- .../Formats/IntlNumber/IntlDurationFormat.php | 11 +++-- .../src/Formats/IntlNumber/IntlFormat.php | 20 ++++---- .../Formats/IntlNumber/IntlNumberFormat.php | 2 +- ...{IntlOptions.php => IntlNumberOptions.php} | 2 +- packages/formatter/src/FormatterTest.php | 8 +-- 8 files changed, 65 insertions(+), 52 deletions(-) create mode 100644 packages/formatter/src/Config/Intl.php rename packages/formatter/src/Formats/IntlNumber/{IntlOptions.php => IntlNumberOptions.php} (95%) diff --git a/packages/formatter/src/Config/Config.php b/packages/formatter/src/Config/Config.php index b05c71d69..4abe6d8f8 100644 --- a/packages/formatter/src/Config/Config.php +++ b/packages/formatter/src/Config/Config.php @@ -13,7 +13,7 @@ use LastDragon_ru\LaraASP\Formatter\Formats\IntlDateTime\IntlDateTimeOptions; use LastDragon_ru\LaraASP\Formatter\Formats\IntlNumber\IntlCurrencyFormat; use LastDragon_ru\LaraASP\Formatter\Formats\IntlNumber\IntlNumberFormat; -use LastDragon_ru\LaraASP\Formatter\Formats\IntlNumber\IntlOptions; +use LastDragon_ru\LaraASP\Formatter\Formats\IntlNumber\IntlNumberOptions; use LastDragon_ru\LaraASP\Formatter\Formats\Secret\SecretFormat; use LastDragon_ru\LaraASP\Formatter\Formats\Secret\SecretOptions; use LastDragon_ru\LaraASP\Formatter\Formats\String\StringFormat; @@ -27,75 +27,70 @@ public function __construct( * @var array> */ public array $formats = [], + /** + * Global Intl settings (for all formats/locales). + */ + public ?Intl $intl = null, ) { parent::__construct(); + $this->intl = new Intl( + number: new IntlNumberOptions( + attributes: [ + NumberFormatter::ROUNDING_MODE => NumberFormatter::ROUND_HALFUP, + ], + ), + ); + $this->formats[Formatter::String] = new Format(StringFormat::class); $this->formats[Formatter::Secret] = new Format(SecretFormat::class, new SecretOptions(5)); $this->formats[Formatter::Integer] = new Format( IntlNumberFormat::class, - new IntlOptions( + new IntlNumberOptions( style : NumberFormatter::DECIMAL, attributes: [ - NumberFormatter::ROUNDING_MODE => NumberFormatter::ROUND_HALFUP, NumberFormatter::FRACTION_DIGITS => 0, ], ), ); $this->formats[Formatter::Decimal] = new Format( IntlNumberFormat::class, - new IntlOptions( + new IntlNumberOptions( style : NumberFormatter::DECIMAL, attributes: [ - NumberFormatter::ROUNDING_MODE => NumberFormatter::ROUND_HALFUP, NumberFormatter::FRACTION_DIGITS => 2, ], ), ); $this->formats[Formatter::Scientific] = new Format( IntlNumberFormat::class, - new IntlOptions( - style : NumberFormatter::SCIENTIFIC, - attributes: [ - NumberFormatter::ROUNDING_MODE => NumberFormatter::ROUND_HALFUP, - ], + new IntlNumberOptions( + style: NumberFormatter::SCIENTIFIC, ), ); $this->formats[Formatter::Spellout] = new Format( IntlNumberFormat::class, - new IntlOptions( - style : NumberFormatter::SPELLOUT, - attributes: [ - NumberFormatter::ROUNDING_MODE => NumberFormatter::ROUND_HALFUP, - ], + new IntlNumberOptions( + style: NumberFormatter::SPELLOUT, ), ); $this->formats[Formatter::Ordinal] = new Format( IntlNumberFormat::class, - new IntlOptions( - style : NumberFormatter::ORDINAL, - attributes: [ - NumberFormatter::ROUNDING_MODE => NumberFormatter::ROUND_HALFUP, - ], + new IntlNumberOptions( + style: NumberFormatter::ORDINAL, ), ); $this->formats[Formatter::Percent] = new Format( IntlNumberFormat::class, - new IntlOptions( + new IntlNumberOptions( style : NumberFormatter::PERCENT, attributes: [ - NumberFormatter::ROUNDING_MODE => NumberFormatter::ROUND_HALFUP, NumberFormatter::FRACTION_DIGITS => 0, ], ), ); $this->formats[Formatter::Currency] = new Format( IntlCurrencyFormat::class, - new IntlOptions( - attributes: [ - NumberFormatter::ROUNDING_MODE => NumberFormatter::ROUND_HALFUP, - ], - ), ); $this->formats[Formatter::Duration] = new Format( DurationFormat::class, diff --git a/packages/formatter/src/Config/Intl.php b/packages/formatter/src/Config/Intl.php new file mode 100644 index 000000000..0f89c3cd4 --- /dev/null +++ b/packages/formatter/src/Config/Intl.php @@ -0,0 +1,14 @@ + + * @extends IntlFormat */ class IntlCurrencyFormat extends IntlFormat { /** - * @param list $options + * @param list $options */ - public function __construct(Formatter $formatter, array $options = []) { - parent::__construct($formatter, [ - new IntlOptions(NumberFormatter::CURRENCY), + public function __construct(PackageConfig $config, Formatter $formatter, array $options = []) { + parent::__construct($config, $formatter, [ + new IntlNumberOptions(NumberFormatter::CURRENCY), ...$options, ]); } diff --git a/packages/formatter/src/Formats/IntlNumber/IntlDurationFormat.php b/packages/formatter/src/Formats/IntlNumber/IntlDurationFormat.php index bd2333620..8bdde7b40 100644 --- a/packages/formatter/src/Formats/IntlNumber/IntlDurationFormat.php +++ b/packages/formatter/src/Formats/IntlNumber/IntlDurationFormat.php @@ -5,6 +5,7 @@ use DateInterval; use IntlException; use LastDragon_ru\LaraASP\Formatter\Formatter; +use LastDragon_ru\LaraASP\Formatter\PackageConfig; use LastDragon_ru\LaraASP\Formatter\Utils\Duration; use NumberFormatter; use Override; @@ -12,15 +13,15 @@ /** * @see NumberFormatter * - * @extends IntlFormat + * @extends IntlFormat */ class IntlDurationFormat extends IntlFormat { /** - * @param list $options + * @param list $options */ - public function __construct(Formatter $formatter, array $options = []) { - parent::__construct($formatter, [ - new IntlOptions(NumberFormatter::DURATION), + public function __construct(PackageConfig $config, Formatter $formatter, array $options = []) { + parent::__construct($config, $formatter, [ + new IntlNumberOptions(NumberFormatter::DURATION), ...$options, ]); } diff --git a/packages/formatter/src/Formats/IntlNumber/IntlFormat.php b/packages/formatter/src/Formats/IntlNumber/IntlFormat.php index 58c424297..6fff61f5c 100644 --- a/packages/formatter/src/Formats/IntlNumber/IntlFormat.php +++ b/packages/formatter/src/Formats/IntlNumber/IntlFormat.php @@ -5,6 +5,7 @@ use InvalidArgumentException; use LastDragon_ru\LaraASP\Formatter\Contracts\Format; use LastDragon_ru\LaraASP\Formatter\Formatter; +use LastDragon_ru\LaraASP\Formatter\PackageConfig; use NumberFormatter; use OutOfBoundsException; @@ -13,7 +14,7 @@ /** * @see NumberFormatter * - * @template TOptions of IntlOptions|null + * @template TOptions of IntlNumberOptions|null * @template TValue * * @implements Format @@ -24,24 +25,25 @@ abstract class IntlFormat implements Format { /** * @param list $options */ - public function __construct(Formatter $formatter, array $options = []) { + public function __construct(PackageConfig $config, Formatter $formatter, array $options = []) { // Collect options $style = null; $pattern = null; $symbols = []; $attributes = []; $textAttributes = []; + $options[] = $config->getInstance()->intl->number ?? null; - foreach ($options as $intl) { - if ($intl === null) { + foreach ($options as $option) { + if ($option === null) { continue; } - $style ??= $intl->style; - $pattern ??= $intl->pattern; - $symbols += $intl->symbols; - $attributes += $intl->attributes; - $textAttributes += $intl->textAttributes; + $style ??= $option->style; + $pattern ??= $option->pattern; + $symbols += $option->symbols; + $attributes += $option->attributes; + $textAttributes += $option->textAttributes; } // Possible? diff --git a/packages/formatter/src/Formats/IntlNumber/IntlNumberFormat.php b/packages/formatter/src/Formats/IntlNumber/IntlNumberFormat.php index f39dd2ef6..9fb936dd0 100644 --- a/packages/formatter/src/Formats/IntlNumber/IntlNumberFormat.php +++ b/packages/formatter/src/Formats/IntlNumber/IntlNumberFormat.php @@ -8,7 +8,7 @@ /** * @see NumberFormatter - * @extends IntlFormat + * @extends IntlFormat */ class IntlNumberFormat extends IntlFormat { #[Override] diff --git a/packages/formatter/src/Formats/IntlNumber/IntlOptions.php b/packages/formatter/src/Formats/IntlNumber/IntlNumberOptions.php similarity index 95% rename from packages/formatter/src/Formats/IntlNumber/IntlOptions.php rename to packages/formatter/src/Formats/IntlNumber/IntlNumberOptions.php index 738f1cfe3..6b19e10a4 100644 --- a/packages/formatter/src/Formats/IntlNumber/IntlOptions.php +++ b/packages/formatter/src/Formats/IntlNumber/IntlNumberOptions.php @@ -8,7 +8,7 @@ /** * @see NumberFormatter */ -class IntlOptions extends Configuration { +class IntlNumberOptions extends Configuration { public function __construct( /** * @var NumberFormatter::*|null diff --git a/packages/formatter/src/FormatterTest.php b/packages/formatter/src/FormatterTest.php index bdb6e9f86..32782b4d2 100644 --- a/packages/formatter/src/FormatterTest.php +++ b/packages/formatter/src/FormatterTest.php @@ -12,7 +12,7 @@ use LastDragon_ru\LaraASP\Formatter\Formats\IntlDateTime\IntlDateTimeOptions; use LastDragon_ru\LaraASP\Formatter\Formats\IntlNumber\IntlDurationFormat; use LastDragon_ru\LaraASP\Formatter\Formats\IntlNumber\IntlNumberFormat; -use LastDragon_ru\LaraASP\Formatter\Formats\IntlNumber\IntlOptions; +use LastDragon_ru\LaraASP\Formatter\Formats\IntlNumber\IntlNumberOptions; use LastDragon_ru\LaraASP\Formatter\Formats\Secret\SecretFormat; use LastDragon_ru\LaraASP\Formatter\Formats\Secret\SecretOptions; use LastDragon_ru\LaraASP\Formatter\Testing\Package\TestCase; @@ -89,14 +89,14 @@ public function testDecimalConfig(): void { $this->setConfiguration(PackageConfig::class, static function (Config $config): void { $config->formats[Formatter::Decimal] = new Format( IntlNumberFormat::class, - new IntlOptions( + new IntlNumberOptions( style : NumberFormatter::DECIMAL, attributes: [ NumberFormatter::FRACTION_DIGITS => 4, ], ), [ - 'ru_RU' => new IntlOptions( + 'ru_RU' => new IntlNumberOptions( attributes: [ NumberFormatter::FRACTION_DIGITS => 2, NumberFormatter::ROUNDING_MODE => NumberFormatter::ROUND_FLOOR, @@ -149,7 +149,7 @@ public function testPercentConfig(): void { $this->setConfiguration(PackageConfig::class, static function (Config $config): void { $config->formats[Formatter::Percent] = new Format( IntlNumberFormat::class, - new IntlOptions( + new IntlNumberOptions( style : NumberFormatter::PERCENT, attributes: [ NumberFormatter::FRACTION_DIGITS => 2, From 6eeff33d6e6812f8349eefa72f2609a9bb6499b7 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Thu, 14 Nov 2024 14:58:42 +0400 Subject: [PATCH 19/22] Default currency. --- .../Formats/IntlNumber/IntlCurrencyFormat.php | 27 ++++++++++++++++--- .../IntlNumber/IntlCurrencyOptions.php | 27 +++++++++++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 packages/formatter/src/Formats/IntlNumber/IntlCurrencyOptions.php diff --git a/packages/formatter/src/Formats/IntlNumber/IntlCurrencyFormat.php b/packages/formatter/src/Formats/IntlNumber/IntlCurrencyFormat.php index c57408110..02dd61fb0 100644 --- a/packages/formatter/src/Formats/IntlNumber/IntlCurrencyFormat.php +++ b/packages/formatter/src/Formats/IntlNumber/IntlCurrencyFormat.php @@ -12,24 +12,43 @@ /** * @see NumberFormatter - * @extends IntlFormat + * @extends IntlFormat */ class IntlCurrencyFormat extends IntlFormat { /** - * @param list $options + * @var non-empty-string|null + */ + protected readonly ?string $currency; + + /** + * @param list $options */ public function __construct(PackageConfig $config, Formatter $formatter, array $options = []) { + // Parent parent::__construct($config, $formatter, [ - new IntlNumberOptions(NumberFormatter::CURRENCY), + new IntlCurrencyOptions(NumberFormatter::CURRENCY), ...$options, ]); + + // Currency + $currency = null; + + foreach ($options as $option) { + if ($option === null) { + continue; + } + + $currency ??= $option->currency; + } + + $this->currency = $currency; } #[Override] public function __invoke(mixed $value): string { [$value, $currency] = is_array($value) ? $value : [$value, null]; $value ??= 0; - $currency ??= $this->formatter->getTextAttribute(NumberFormatter::CURRENCY_CODE); + $currency ??= $this->currency ?? $this->formatter->getTextAttribute(NumberFormatter::CURRENCY_CODE); $formatted = $this->formatter->formatCurrency($value, $currency); if ($formatted === false) { diff --git a/packages/formatter/src/Formats/IntlNumber/IntlCurrencyOptions.php b/packages/formatter/src/Formats/IntlNumber/IntlCurrencyOptions.php new file mode 100644 index 000000000..799887ab4 --- /dev/null +++ b/packages/formatter/src/Formats/IntlNumber/IntlCurrencyOptions.php @@ -0,0 +1,27 @@ + $symbols + * @param array $attributes + * @param array $textAttributes + */ + public function __construct( + ?int $style = null, + ?string $pattern = null, + array $symbols = [], + array $attributes = [], + array $textAttributes = [], + /** + * @var non-empty-string|null + */ + public ?string $currency = null, + ) { + parent::__construct($style, $pattern, $symbols, $attributes, $textAttributes); + } +} From bae8740bd1ccda0cba5a3a122ec3a4b6716f7ad8 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Fri, 15 Nov 2024 08:53:37 +0400 Subject: [PATCH 20/22] `\LastDragon_ru\LaraASP\Formatter\Formatter::currency()` will accept `$currency`. --- packages/formatter/src/Formatter.php | 7 +++++-- packages/formatter/src/FormatterTest.php | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/formatter/src/Formatter.php b/packages/formatter/src/Formatter.php index 6eaa505ee..1dac133a6 100644 --- a/packages/formatter/src/Formatter.php +++ b/packages/formatter/src/Formatter.php @@ -148,8 +148,11 @@ public function decimal(float|int|null $value): string { return $this->format(self::Decimal, $value); } - public function currency(float|int|null $value): string { - return $this->format(self::Currency, $value); + /** + * @param non-empty-string|null $currency + */ + public function currency(float|int|null $value, ?string $currency = null): string { + return $this->format(self::Currency, [$value, $currency]); } /** diff --git a/packages/formatter/src/FormatterTest.php b/packages/formatter/src/FormatterTest.php index 32782b4d2..206463b87 100644 --- a/packages/formatter/src/FormatterTest.php +++ b/packages/formatter/src/FormatterTest.php @@ -403,6 +403,7 @@ public function testCurrency(): void { $formatter = $this->formatter->forLocale('en_US'); self::assertEquals('$10.00', $formatter->currency(10)); + self::assertEquals('€10.00', $formatter->currency(10, 'EUR')); self::assertEquals('$10.03', $formatter->currency(10.0324234)); } From fd327adc04d3d1479036835c8cf999c76c2bfbda Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Fri, 15 Nov 2024 09:27:35 +0400 Subject: [PATCH 21/22] Docs update. --- README.md | 2 +- packages/dev/src/App/Example.php | 24 +- packages/formatter/README.md | 281 +++++++++++++++--- packages/formatter/UPGRADE.md | 16 + packages/formatter/docs/Examples/Config.php | 39 ++- packages/formatter/docs/Examples/Currency.php | 30 ++ .../formatter/docs/Examples/DurationIntl.php | 21 ++ .../{Duration.php => DurationPattern.php} | 0 .../formatter/docs/Examples/Uppercase.php | 44 +++ phpstan-baseline-well-known.neon | 1 + 10 files changed, 378 insertions(+), 80 deletions(-) create mode 100644 packages/formatter/docs/Examples/Currency.php create mode 100644 packages/formatter/docs/Examples/DurationIntl.php rename packages/formatter/docs/Examples/{Duration.php => DurationPattern.php} (100%) create mode 100644 packages/formatter/docs/Examples/Uppercase.php diff --git a/README.md b/README.md index 657445acf..0cc353014 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ This package provides highly powerful [`@searchBy`](packages/graphql/docs/Direct ## (Laravel) Intl Formatter -This package provides a customizable wrapper around [Intl](https://www.php.net/manual/en/book.intl) formatters to use it inside Laravel application. +This package provides a customizable wrapper around [Intl](https://www.php.net/manual/en/book.intl) formatters to use it inside Laravel application. And also allows defining own. [Read more](). diff --git a/packages/dev/src/App/Example.php b/packages/dev/src/App/Example.php index 941dcf50e..052bf0b5d 100644 --- a/packages/dev/src/App/Example.php +++ b/packages/dev/src/App/Example.php @@ -2,10 +2,10 @@ namespace LastDragon_ru\LaraASP\Dev\App; -use Illuminate\Contracts\Config\Repository; use Illuminate\Contracts\Foundation\Application; use LastDragon_ru\LaraASP\Core\Application\ApplicationResolver; -use LastDragon_ru\LaraASP\Core\Utils\ConfigMerger; +use LastDragon_ru\LaraASP\Core\Application\Configuration\Configuration; +use LastDragon_ru\LaraASP\Core\Application\Configuration\ConfigurationResolver; use LastDragon_ru\LaraASP\Documentator\Processor\FileSystem\File; use LastDragon_ru\LaraASP\Documentator\Processor\Tasks\Preprocess\Instructions\IncludeExample\Contracts\Runner; use LastDragon_ru\LaraASP\Documentator\Utils\Text; @@ -83,6 +83,8 @@ public function __invoke(File $file): ?string { $result = "{$output}"; } } finally { + self::$app->forgetScopedInstances(); + self::$app = null; self::$file = null; self::$dumper = null; @@ -113,17 +115,15 @@ protected static function app(): Application { } /** - * @param array $settings + * @template T of Configuration + * + * @param class-string> $resolver + * @param callable(T): void|null $callback */ - public static function config(string $root, array $settings): void { - // Update - $repository = self::app()->make(Repository::class); - $config = (array) $repository->get($root, []); - $config = (new ConfigMerger())->merge([ConfigMerger::Strict => false], $config, $settings); - - $repository->set([ - $root => $config, - ]); + public static function config(string $resolver, ?callable $callback): void { + if ($callback !== null) { + $callback(self::app()->make($resolver)->getInstance()); + } } private static function getExpression(string $method): ?string { diff --git a/packages/formatter/README.md b/packages/formatter/README.md index 58f6d9d17..712ec4d60 100644 --- a/packages/formatter/README.md +++ b/packages/formatter/README.md @@ -1,6 +1,6 @@ # (Laravel) Intl Formatter -This package provides a customizable wrapper around [Intl](https://www.php.net/manual/en/book.intl) formatters to use it inside Laravel application. +This package provides a customizable wrapper around [Intl](https://www.php.net/manual/en/book.intl) formatters to use it inside Laravel application. And also allows defining own. [include:artisan]: [//]: # (start: preprocess/78cfc4c7c7c55577) @@ -39,9 +39,21 @@ composer require lastdragon-ru/lara-asp-formatter [//]: # (end: preprocess/8750339286f08805) +# Configuration + +Config can be used to customize formats. Before this, you need to publish it via the following command, and then you can edit `config/lara-asp-formatter.php`. + +```shell +php artisan vendor:publish --provider=LastDragon_ru\\LaraASP\\Formatter\\Provider --tag=config +``` + # Usage -Formatter is very simple to use: +> [!NOTE] +> +> The resolved formats are cached, thus run-time changes in the configuration will not be applied. You can `clone` the formatter instance to reset the internal cache. + +The [`Formatter`][code-links/9fbde97537a14196] is very simple to use. Please also check [`Formatter`][code-links/9fbde97537a14196] to see built-in formats 🤗 [include:example]: ./docs/Examples/Usage.php [//]: # (start: preprocess/4c2bcd97f5d25b12) @@ -81,15 +93,7 @@ The `$locale->decimal(123.454321)` is: [//]: # (end: preprocess/4c2bcd97f5d25b12) -Please check [source code](./src/Formatter.php) to see available methods and [config example](defaults/config.php) to available settings 🤗 - -# Formats - -Some methods like as `date()`/`datetime()`/etc have `$format` argument. The argument specifies not the format but the format name. So you can use the names and do not worry about real formats. It is very important when application big/grow. To specify available names and formats the package config should be published and customized. - -```shell -php artisan vendor:publish --provider=LastDragon_ru\\LaraASP\\Formatter\\Provider --tag=config -``` +You can also define separate setting for each locale: [include:example]: ./docs/Examples/Config.php [//]: # (start: preprocess/e30ad70238f2c282) @@ -100,36 +104,35 @@ php artisan vendor:publish --provider=LastDragon_ru\\LaraASP\\Formatter\\Provide use Illuminate\Support\Facades\Date; use LastDragon_ru\LaraASP\Dev\App\Example; +use LastDragon_ru\LaraASP\Formatter\Config\Config; +use LastDragon_ru\LaraASP\Formatter\Config\Format; +use LastDragon_ru\LaraASP\Formatter\Formats\IntlDateTime\IntlDateTimeFormat; +use LastDragon_ru\LaraASP\Formatter\Formats\IntlDateTime\IntlDateTimeOptions; use LastDragon_ru\LaraASP\Formatter\Formatter; -use LastDragon_ru\LaraASP\Formatter\Package; - -Example::config(Package::Name, [ - 'options' => [ - Formatter::Date => 'default', - ], - 'all' => [ - Formatter::Date => [ - 'default' => 'd MMM yyyy', - 'custom' => 'yyyy/MM/dd', - ], - ], - 'locales' => [ - 'ru_RU' => [ - Formatter::Date => [ - 'custom' => 'dd.MM.yyyy', - ], +use LastDragon_ru\LaraASP\Formatter\PackageConfig; + +Example::config(PackageConfig::class, static function (Config $config): void { + $config->formats[Formatter::Date] = new Format( + IntlDateTimeFormat::class, + new IntlDateTimeOptions( + dateType: IntlDateFormatter::SHORT, + timeType: IntlDateFormatter::NONE, + pattern : 'd MMM yyyy', + ), + [ + 'ru_RU' => new IntlDateTimeOptions( + pattern: 'dd.MM.yyyy', + ), ], - ], -]); + ); +}); $datetime = Date::make('2023-12-30T20:41:40.000018+04:00'); $default = app()->make(Formatter::class); $locale = $default->forLocale('ru_RU'); Example::dump($default->date($datetime)); -Example::dump($default->date($datetime, 'custom')); Example::dump($locale->date($datetime)); -Example::dump($locale->date($datetime, 'custom')); ``` The `$default->date($datetime)` is: @@ -138,32 +141,161 @@ The `$default->date($datetime)` is: "30 Dec 2023" ``` -The `$default->date($datetime, 'custom')` is: +The `$locale->date($datetime)` is: ```plain -"2023/12/30" +"30.12.2023" ``` -The `$locale->date($datetime)` is: +[//]: # (end: preprocess/e30ad70238f2c282) + +# Adding new formats + +You just need to create a class that implements [`Format`][code-links/f729e209367a8080], add into the package config, and add macros to the [`Formatter`][code-links/9fbde97537a14196] class. + +> [!NOTE] +> +> [include:docblock]: ./src/Contracts/Format.php +> [//]: # (start: preprocess/0015746c2d34336b) +> [//]: # (warning: Generated automatically. Do not edit.) +> +> The instance will be created through container with the following additional +> arguments: +> +> * `$formatter`: [`Formatter`][code-links/9fbde97537a14196] - the current formatter instance (can be used to get locale/timezone). +> * `$options` (array) - formatter options defined inside app config (may contain `null`s). +> +> [//]: # (end: preprocess/0015746c2d34336b) +> + +[include:example]: ./docs/Examples/Uppercase.php +[//]: # (start: preprocess/20404ebb04e0776f) +[//]: # (warning: Generated automatically. Do not edit.) + +```php + + */ +class UppercaseFormat implements FormatContract { + public function __construct() { + // empty + } + + #[Override] + public function __invoke(mixed $value): string { + return mb_strtoupper((string) $value); + } +} + +Formatter::macro('uppercase', function (Stringable|string|null $value): string { + return $this->format('uppercase', $value); +}); + +Example::config(PackageConfig::class, static function (Config $config): void { + $config->formats['uppercase'] = new Format( + UppercaseFormat::class, + ); +}); + +// @phpstan-ignore method.notFound +Example::dump(app()->make(Formatter::class)->uppercase('string')); +``` + +The `app()->make(Formatter::class)->uppercase('string')` is: ```plain -"30 дек. 2023" +"STRING" ``` -The `$locale->date($datetime, 'custom')` is: +[//]: # (end: preprocess/20404ebb04e0776f) + +# Notes about built-in formats + +## Currency + +By default, the [`Formatter`][code-links/9fbde97537a14196] use locale currency. You can redefine it globally through config, specify for the call, and/or add a macros for another currency. + +[include:example]: ./docs/Examples/Currency.php +[//]: # (start: preprocess/579d73db05700cf0) +[//]: # (warning: Generated automatically. Do not edit.) + +```php +formats[Formatter::Currency] = new Format( + IntlCurrencyFormat::class, + new IntlCurrencyOptions( + currency: 'USD', + ), + ); +}); + +Formatter::macro('eur', function (float|int|null $value): string { + return $this->format(Formatter::Currency, [$value, 'EUR']); +}); + +$formatter = app()->make(Formatter::class); +$value = 123.45; + +// @phpstan-ignore method.notFound +Example::dump($formatter->eur($value)); // macro +Example::dump($formatter->currency($value)); // locale default +Example::dump($formatter->currency($value, 'EUR')); // as defined +``` + +The `$formatter->eur($value)` is: ```plain -"30.12.2023" +"€123.45" ``` -[//]: # (end: preprocess/e30ad70238f2c282) +The `$formatter->currency($value)` is: + +```plain +"$123.45" +``` + +The `$formatter->currency($value, 'EUR')` is: + +```plain +"€123.45" +``` + +[//]: # (end: preprocess/579d73db05700cf0) -# Duration +## Duration -To format duration you can use built-in Intl formatter, but it doesn't support fraction seconds and have different format between locales (for example, `12345` seconds is `3:25:45` in `en_US` locale, and `12 345` in `ru_RU`). These reasons make difficult to use it in real applications. To make `duration()` more useful, the alternative syntax was added. +To format duration you can use built-in Intl formatter, but it doesn't support fraction seconds and have a different format between locales (for example, `12345` seconds is `3:25:45` in `en_US` locale, and `12 345` in `ru_RU`). These reasons make it difficult to use it in real applications. To make `duration()` more useful, the alternative syntax was added and used by default. -[include:docblock]: ./src/Utils/DurationFormatter.php ({"summary": false}) -[//]: # (start: preprocess/29da251049347125) +[include:docblock]: ./src/Formats/Duration/DurationFormat.php ({"summary": false}) +[//]: # (start: preprocess/ef4289839adfe4ca) [//]: # (warning: Generated automatically. Do not edit.) The syntax is the same as [ICU Date/Time format syntax](https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax). @@ -181,10 +313,10 @@ The syntax is the same as [ICU Date/Time format syntax](https://unicode-org.gith | `'` | escape for text | | `''` | two single quotes produce one | -[//]: # (end: preprocess/29da251049347125) +[//]: # (end: preprocess/ef4289839adfe4ca) -[include:example]: ./docs/Examples/Duration.php -[//]: # (start: preprocess/1bbaf6764d0f3cce) +[include:example]: ./docs/Examples/DurationPattern.php +[//]: # (start: preprocess/75de7a9481771185) [//]: # (warning: Generated automatically. Do not edit.) ```php @@ -219,7 +351,51 @@ The `$locale->duration(1234543)` is: "342:55:43.000" ``` -[//]: # (end: preprocess/1bbaf6764d0f3cce) +[//]: # (end: preprocess/75de7a9481771185) + +To use Intl Formatter, you need to change the duration format in the config: + +[include:example]: ./docs/Examples/DurationIntl.php +[//]: # (start: preprocess/1e573cfe77ba6df3) +[//]: # (warning: Generated automatically. Do not edit.) + +```php +formats[Formatter::Duration] = new Format( + IntlDurationFormat::class, + ); +}); + +$default = app()->make(Formatter::class); // For default app locale +$locale = $default->forLocale('ru_RU'); // For ru_RU locale +$value = 123.4543; + +Example::dump($default->duration($value)); +Example::dump($locale->duration($value)); +``` + +The `$default->duration($value)` is: + +```plain +"2:03" +``` + +The `$locale->duration($value)` is: + +```plain +"123" +``` + +[//]: # (end: preprocess/1e573cfe77ba6df3) # Upgrading @@ -234,3 +410,14 @@ Please follow [Upgrade Guide](UPGRADE.md). This package is the part of Awesome Set of Packages for Laravel. Please use the [main repository](https://github.com/LastDragon-ru/lara-asp) to [report issues](https://github.com/LastDragon-ru/lara-asp/issues), send [pull requests](https://github.com/LastDragon-ru/lara-asp/pulls), or [ask questions](https://github.com/LastDragon-ru/lara-asp/discussions). [//]: # (end: preprocess/c4ba75080f5a48b7) + +[//]: # (start: code-links) +[//]: # (warning: Generated automatically. Do not edit.) + +[code-links/f729e209367a8080]: src/Contracts/Format.php + "\LastDragon_ru\LaraASP\Formatter\Contracts\Format" + +[code-links/9fbde97537a14196]: src/Formatter.php + "\LastDragon_ru\LaraASP\Formatter\Formatter" + +[//]: # (end: code-links) diff --git a/packages/formatter/UPGRADE.md b/packages/formatter/UPGRADE.md index d4cffb857..c54f470ca 100644 --- a/packages/formatter/UPGRADE.md +++ b/packages/formatter/UPGRADE.md @@ -29,6 +29,8 @@ Please also see [changelog](https://github.com/LastDragon-ru/lara-asp/releases) # Upgrade from v6 +This version is the deep refactoring of the [`Formatter`][code-links/9fbde97537a14196] class to make it simple and allow adding new formats easily. All built-in formats are now instances of [`Format`][code-links/f729e209367a8080] interface. Also, the config now is the [`Config`][code-links/d45c59bc79a55ae4] instance instead of an array, and locale-specific settings were moved into format itself. Please check the updated documentation for more details. + [include:file]: ../../docs/Shared/Upgrade/FromV6.md [//]: # (start: preprocess/9679e76379216855) [//]: # (warning: Generated automatically. Do not edit.) @@ -52,3 +54,17 @@ Please also see [changelog](https://github.com/LastDragon-ru/lara-asp/releases) [//]: # (end: preprocess/2e85dad2b0618274) * [ ] If you are passing `\IntlDateFormatter::*` constants as `$format` argument for `Formatter::time()`/`Formatter::date()`/`Formatter::datetime()`, add a new custom format(s) which will refer to `\IntlDateFormatter::*` constant(s). + +[//]: # (start: code-links) +[//]: # (warning: Generated automatically. Do not edit.) + +[code-links/d45c59bc79a55ae4]: src/Config/Config.php + "\LastDragon_ru\LaraASP\Formatter\Config\Config" + +[code-links/f729e209367a8080]: src/Contracts/Format.php + "\LastDragon_ru\LaraASP\Formatter\Contracts\Format" + +[code-links/9fbde97537a14196]: src/Formatter.php + "\LastDragon_ru\LaraASP\Formatter\Formatter" + +[//]: # (end: code-links) diff --git a/packages/formatter/docs/Examples/Config.php b/packages/formatter/docs/Examples/Config.php index 97b2faf3e..f13fc8955 100644 --- a/packages/formatter/docs/Examples/Config.php +++ b/packages/formatter/docs/Examples/Config.php @@ -2,33 +2,32 @@ use Illuminate\Support\Facades\Date; use LastDragon_ru\LaraASP\Dev\App\Example; +use LastDragon_ru\LaraASP\Formatter\Config\Config; +use LastDragon_ru\LaraASP\Formatter\Config\Format; +use LastDragon_ru\LaraASP\Formatter\Formats\IntlDateTime\IntlDateTimeFormat; +use LastDragon_ru\LaraASP\Formatter\Formats\IntlDateTime\IntlDateTimeOptions; use LastDragon_ru\LaraASP\Formatter\Formatter; -use LastDragon_ru\LaraASP\Formatter\Package; +use LastDragon_ru\LaraASP\Formatter\PackageConfig; -Example::config(Package::Name, [ - 'options' => [ - Formatter::Date => 'default', - ], - 'all' => [ - Formatter::Date => [ - 'default' => 'd MMM yyyy', - 'custom' => 'yyyy/MM/dd', +Example::config(PackageConfig::class, static function (Config $config): void { + $config->formats[Formatter::Date] = new Format( + IntlDateTimeFormat::class, + new IntlDateTimeOptions( + dateType: IntlDateFormatter::SHORT, + timeType: IntlDateFormatter::NONE, + pattern : 'd MMM yyyy', + ), + [ + 'ru_RU' => new IntlDateTimeOptions( + pattern: 'dd.MM.yyyy', + ), ], - ], - 'locales' => [ - 'ru_RU' => [ - Formatter::Date => [ - 'custom' => 'dd.MM.yyyy', - ], - ], - ], -]); + ); +}); $datetime = Date::make('2023-12-30T20:41:40.000018+04:00'); $default = app()->make(Formatter::class); $locale = $default->forLocale('ru_RU'); Example::dump($default->date($datetime)); -Example::dump($default->date($datetime)); -Example::dump($locale->date($datetime)); Example::dump($locale->date($datetime)); diff --git a/packages/formatter/docs/Examples/Currency.php b/packages/formatter/docs/Examples/Currency.php new file mode 100644 index 000000000..c4cc6ac2b --- /dev/null +++ b/packages/formatter/docs/Examples/Currency.php @@ -0,0 +1,30 @@ +formats[Formatter::Currency] = new Format( + IntlCurrencyFormat::class, + new IntlCurrencyOptions( + currency: 'USD', + ), + ); +}); + +Formatter::macro('eur', function (float|int|null $value): string { + return $this->format(Formatter::Currency, [$value, 'EUR']); +}); + +$formatter = app()->make(Formatter::class); +$value = 123.45; + +// @phpstan-ignore method.notFound +Example::dump($formatter->eur($value)); // macro +Example::dump($formatter->currency($value)); // locale default +Example::dump($formatter->currency($value, 'EUR')); // as defined diff --git a/packages/formatter/docs/Examples/DurationIntl.php b/packages/formatter/docs/Examples/DurationIntl.php new file mode 100644 index 000000000..a15ea3c62 --- /dev/null +++ b/packages/formatter/docs/Examples/DurationIntl.php @@ -0,0 +1,21 @@ +formats[Formatter::Duration] = new Format( + IntlDurationFormat::class, + ); +}); + +$default = app()->make(Formatter::class); // For default app locale +$locale = $default->forLocale('ru_RU'); // For ru_RU locale +$value = 123.4543; + +Example::dump($default->duration($value)); +Example::dump($locale->duration($value)); diff --git a/packages/formatter/docs/Examples/Duration.php b/packages/formatter/docs/Examples/DurationPattern.php similarity index 100% rename from packages/formatter/docs/Examples/Duration.php rename to packages/formatter/docs/Examples/DurationPattern.php diff --git a/packages/formatter/docs/Examples/Uppercase.php b/packages/formatter/docs/Examples/Uppercase.php new file mode 100644 index 000000000..b4474c997 --- /dev/null +++ b/packages/formatter/docs/Examples/Uppercase.php @@ -0,0 +1,44 @@ + + */ +class UppercaseFormat implements FormatContract { + public function __construct() { + // empty + } + + #[Override] + public function __invoke(mixed $value): string { + return mb_strtoupper((string) $value); + } +} + +Formatter::macro('uppercase', function (Stringable|string|null $value): string { + return $this->format('uppercase', $value); +}); + +Example::config(PackageConfig::class, static function (Config $config): void { + $config->formats['uppercase'] = new Format( + UppercaseFormat::class, + ); +}); + +// @phpstan-ignore method.notFound +Example::dump(app()->make(Formatter::class)->uppercase('string')); diff --git a/phpstan-baseline-well-known.neon b/phpstan-baseline-well-known.neon index cdf7169bb..128244f0a 100644 --- a/phpstan-baseline-well-known.neon +++ b/phpstan-baseline-well-known.neon @@ -17,6 +17,7 @@ parameters: - "#^Dynamic call to static method Illuminate\\\\Testing\\\\TestResponse\\<[^>]+\\>\\:\\:assert[^(]+\\(\\)\\.$#" - "#^Dynamic call to static method Illuminate\\\\Database\\\\Eloquent\\\\Model(\\<[^>]+\\>)?\\:\\:[^(]+\\(\\)\\.$#" - "#^Dynamic call to static method Illuminate\\\\Database\\\\Eloquent\\\\Builder(\\<[^>]+\\>)?\\:\\:[^(]+\\(\\)\\.$#" + - "#^Dynamic call to static method Illuminate\\\\Container\\\\Container::forgetScopedInstances\\(\\)\\.$#" # Sometimes it is needed... # https://github.com/phpstan/phpstan/issues/3296 From 0601312411b66aa559fa4067db4da9e92b0f5183 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Fri, 15 Nov 2024 11:46:54 +0400 Subject: [PATCH 22/22] `composer-require-checker` whitelist fix (still no way to exclude dev/test files :() --- composer-require-checker.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer-require-checker.json b/composer-require-checker.json index 6fcf8e250..c8bc4f172 100644 --- a/composer-require-checker.json +++ b/composer-require-checker.json @@ -8,6 +8,7 @@ "GuzzleHttp\\Psr7\\Response", "Illuminate\\Cache\\Repository", "Illuminate\\Config\\Repository", + "Illuminate\\Contracts\\Config\\Repository", "Illuminate\\Foundation\\Http\\FormRequest", "Illuminate\\Foundation\\Testing\\RefreshDatabase", "Illuminate\\Foundation\\Testing\\RefreshDatabaseState",