diff --git a/bin/console b/bin/console
new file mode 100755
index 0000000..6fc7cd0
--- /dev/null
+++ b/bin/console
@@ -0,0 +1,22 @@
+#!/usr/bin/env php
+
+ */
+
+declare(strict_types=1);
+
+require __DIR__ . '/../vendor/autoload.php';
+
+use Respect\StringFormatter\DevTools\Commands\LintMixinCommand;
+use Symfony\Component\Console\Application;
+
+return (static function () {
+ $application = new Application('Respect/StringFormatter', '1.0');
+ $application->addCommand(new LintMixinCommand());
+
+ return $application->run();
+})();
diff --git a/composer.json b/composer.json
index 9d7a7a8..3c421d4 100644
--- a/composer.json
+++ b/composer.json
@@ -4,6 +4,7 @@
"description": "A powerful and flexible way of formatting and transforming strings",
"require": {
"php": "^8.5",
+ "respect/fluent": "^1.0",
"respect/stringifier": "^3.0",
"symfony/polyfill-mbstring": "^1.33",
"symfony/translation-contracts": "^3.6"
@@ -17,7 +18,9 @@
"phpstan/phpstan-deprecation-rules": "^2.0",
"phpstan/phpstan-phpunit": "^2.0",
"phpunit/phpunit": "^12.5",
+ "respect/fluentgen": "^1.0",
"respect/coding-standard": "^5.0",
+ "symfony/console": "^6.0|^7.0",
"symfony/translation": "^6.0|^7.0"
},
"license": "ISC",
@@ -30,6 +33,7 @@
"autoload-dev": {
"psr-4": {
"Respect\\StringFormatter\\": ["docs/contributing/templates/php/src"],
+ "Respect\\StringFormatter\\DevTools\\": "src-dev/",
"Respect\\StringFormatter\\Test\\": ["tests/", "docs/contributing/templates/php/tests"]
}
},
diff --git a/composer.lock b/composer.lock
index 527e69e..9d971f2 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,8 +4,61 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "3a55ef562d9ef3da73ee3986e81501d1",
+ "content-hash": "1e091347f6296882da9496e57bb4682d",
"packages": [
+ {
+ "name": "respect/fluent",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Respect/Fluent.git",
+ "reference": "93b2056ebd36648a24a2cea4f1dd2f696f81e510"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Respect/Fluent/zipball/93b2056ebd36648a24a2cea4f1dd2f696f81e510",
+ "reference": "93b2056ebd36648a24a2cea4f1dd2f696f81e510",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.5"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^2.1",
+ "phpstan/phpstan-deprecation-rules": "^2.0",
+ "phpstan/phpstan-phpunit": "^2.0",
+ "phpstan/phpstan-strict-rules": "^2.0",
+ "phpunit/phpunit": "^12.5",
+ "respect/coding-standard": "^5.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Respect\\Fluent\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "ISC"
+ ],
+ "authors": [
+ {
+ "name": "Respect/Fluent Contributors",
+ "homepage": "https://github.com/Respect/Fluent/graphs/contributors"
+ }
+ ],
+ "description": "Namespace-aware fluent class resolution",
+ "keywords": [
+ "builder",
+ "fluent",
+ "respect"
+ ],
+ "support": {
+ "issues": "https://github.com/Respect/Fluent/issues",
+ "source": "https://github.com/Respect/Fluent/tree/1.0.0"
+ },
+ "time": "2026-03-23T20:43:17+00:00"
+ },
{
"name": "respect/stringifier",
"version": "3.0.0",
@@ -444,6 +497,171 @@
],
"time": "2025-08-01T08:46:24+00:00"
},
+ {
+ "name": "nette/php-generator",
+ "version": "v4.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nette/php-generator.git",
+ "reference": "0d7060926f5c3e8c488b9b9ced42d857f12a34b5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nette/php-generator/zipball/0d7060926f5c3e8c488b9b9ced42d857f12a34b5",
+ "reference": "0d7060926f5c3e8c488b9b9ced42d857f12a34b5",
+ "shasum": ""
+ },
+ "require": {
+ "nette/utils": "^4.0.6",
+ "php": "8.1 - 8.5"
+ },
+ "require-dev": {
+ "jetbrains/phpstorm-attributes": "^1.2",
+ "nette/phpstan-rules": "^1.0",
+ "nette/tester": "^2.6",
+ "nikic/php-parser": "^5.0",
+ "phpstan/extension-installer": "^1.4@stable",
+ "phpstan/phpstan": "^2.1.40@stable",
+ "tracy/tracy": "^2.8"
+ },
+ "suggest": {
+ "nikic/php-parser": "to use ClassType::from(withBodies: true) & ClassType::fromCode()"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.2-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Nette\\": "src"
+ },
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause",
+ "GPL-2.0-only",
+ "GPL-3.0-only"
+ ],
+ "authors": [
+ {
+ "name": "David Grudl",
+ "homepage": "https://davidgrudl.com"
+ },
+ {
+ "name": "Nette Community",
+ "homepage": "https://nette.org/contributors"
+ }
+ ],
+ "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.5 features.",
+ "homepage": "https://nette.org",
+ "keywords": [
+ "code",
+ "nette",
+ "php",
+ "scaffolding"
+ ],
+ "support": {
+ "issues": "https://github.com/nette/php-generator/issues",
+ "source": "https://github.com/nette/php-generator/tree/v4.2.2"
+ },
+ "time": "2026-02-26T00:58:33+00:00"
+ },
+ {
+ "name": "nette/utils",
+ "version": "v4.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nette/utils.git",
+ "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nette/utils/zipball/bb3ea637e3d131d72acc033cfc2746ee893349fe",
+ "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe",
+ "shasum": ""
+ },
+ "require": {
+ "php": "8.2 - 8.5"
+ },
+ "conflict": {
+ "nette/finder": "<3",
+ "nette/schema": "<1.2.2"
+ },
+ "require-dev": {
+ "jetbrains/phpstorm-attributes": "^1.2",
+ "nette/phpstan-rules": "^1.0",
+ "nette/tester": "^2.5",
+ "phpstan/extension-installer": "^1.4@stable",
+ "phpstan/phpstan": "^2.1@stable",
+ "tracy/tracy": "^2.9"
+ },
+ "suggest": {
+ "ext-gd": "to use Image",
+ "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()",
+ "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()",
+ "ext-json": "to use Nette\\Utils\\Json",
+ "ext-mbstring": "to use Strings::lower() etc...",
+ "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Nette\\": "src"
+ },
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause",
+ "GPL-2.0-only",
+ "GPL-3.0-only"
+ ],
+ "authors": [
+ {
+ "name": "David Grudl",
+ "homepage": "https://davidgrudl.com"
+ },
+ {
+ "name": "Nette Community",
+ "homepage": "https://nette.org/contributors"
+ }
+ ],
+ "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.",
+ "homepage": "https://nette.org",
+ "keywords": [
+ "array",
+ "core",
+ "datetime",
+ "images",
+ "json",
+ "nette",
+ "paginator",
+ "password",
+ "slugify",
+ "string",
+ "unicode",
+ "utf-8",
+ "utility",
+ "validation"
+ ],
+ "support": {
+ "issues": "https://github.com/nette/utils/issues",
+ "source": "https://github.com/nette/utils/tree/v4.1.3"
+ },
+ "time": "2026-02-13T03:05:33+00:00"
+ },
{
"name": "nikic/php-parser",
"version": "v5.7.0",
@@ -717,11 +935,11 @@
},
{
"name": "phpstan/phpstan",
- "version": "2.1.38",
+ "version": "2.1.42",
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dfaf1f530e1663aa167bc3e52197adb221582629",
- "reference": "dfaf1f530e1663aa167bc3e52197adb221582629",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/1279e1ce86ba768f0780c9d889852b4e02ff40d0",
+ "reference": "1279e1ce86ba768f0780c9d889852b4e02ff40d0",
"shasum": ""
},
"require": {
@@ -766,25 +984,25 @@
"type": "github"
}
],
- "time": "2026-01-30T17:12:46+00:00"
+ "time": "2026-03-17T14:58:32+00:00"
},
{
"name": "phpstan/phpstan-deprecation-rules",
- "version": "2.0.3",
+ "version": "2.0.4",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-deprecation-rules.git",
- "reference": "468e02c9176891cc901143da118f09dc9505fc2f"
+ "reference": "6b5571001a7f04fa0422254c30a0017ec2f2cacc"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/468e02c9176891cc901143da118f09dc9505fc2f",
- "reference": "468e02c9176891cc901143da118f09dc9505fc2f",
+ "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/6b5571001a7f04fa0422254c30a0017ec2f2cacc",
+ "reference": "6b5571001a7f04fa0422254c30a0017ec2f2cacc",
"shasum": ""
},
"require": {
"php": "^7.4 || ^8.0",
- "phpstan/phpstan": "^2.1.15"
+ "phpstan/phpstan": "^2.1.39"
},
"require-dev": {
"php-parallel-lint/php-parallel-lint": "^1.2",
@@ -809,24 +1027,27 @@
"MIT"
],
"description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.",
+ "keywords": [
+ "static analysis"
+ ],
"support": {
"issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues",
- "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/2.0.3"
+ "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/2.0.4"
},
- "time": "2025-05-14T10:56:57+00:00"
+ "time": "2026-02-09T13:21:14+00:00"
},
{
"name": "phpstan/phpstan-phpunit",
- "version": "2.0.12",
+ "version": "2.0.16",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-phpunit.git",
- "reference": "e4c5a22bf43d3d2bd5a780ad261a622ff62c49a4"
+ "reference": "6ab598e1bc106e6827fd346ae4a12b4a5d634c32"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/e4c5a22bf43d3d2bd5a780ad261a622ff62c49a4",
- "reference": "e4c5a22bf43d3d2bd5a780ad261a622ff62c49a4",
+ "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/6ab598e1bc106e6827fd346ae4a12b4a5d634c32",
+ "reference": "6ab598e1bc106e6827fd346ae4a12b4a5d634c32",
"shasum": ""
},
"require": {
@@ -862,11 +1083,14 @@
"MIT"
],
"description": "PHPUnit extensions and rules for PHPStan",
+ "keywords": [
+ "static analysis"
+ ],
"support": {
"issues": "https://github.com/phpstan/phpstan-phpunit/issues",
- "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.12"
+ "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.16"
},
- "time": "2026-01-22T13:40:00+00:00"
+ "time": "2026-02-14T09:05:21+00:00"
},
{
"name": "phpunit/php-code-coverage",
@@ -1216,16 +1440,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "12.5.9",
+ "version": "12.5.14",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "83d4c158526c879b4c5cf7149d27958b6d912373"
+ "reference": "47283cfd98d553edcb1353591f4e255dc1bb61f0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/83d4c158526c879b4c5cf7149d27958b6d912373",
- "reference": "83d4c158526c879b4c5cf7149d27958b6d912373",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/47283cfd98d553edcb1353591f4e255dc1bb61f0",
+ "reference": "47283cfd98d553edcb1353591f4e255dc1bb61f0",
"shasum": ""
},
"require": {
@@ -1239,7 +1463,7 @@
"phar-io/manifest": "^2.0.4",
"phar-io/version": "^3.2.1",
"php": ">=8.3",
- "phpunit/php-code-coverage": "^12.5.2",
+ "phpunit/php-code-coverage": "^12.5.3",
"phpunit/php-file-iterator": "^6.0.1",
"phpunit/php-invoker": "^6.0.0",
"phpunit/php-text-template": "^5.0.0",
@@ -1294,7 +1518,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.9"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.14"
},
"funding": [
{
@@ -1318,7 +1542,60 @@
"type": "tidelift"
}
],
- "time": "2026-02-05T08:01:09+00:00"
+ "time": "2026-02-18T12:38:40+00:00"
+ },
+ {
+ "name": "psr/container",
+ "version": "2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/container.git",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Container\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common Container Interface (PHP FIG PSR-11)",
+ "homepage": "https://github.com/php-fig/container",
+ "keywords": [
+ "PSR-11",
+ "container",
+ "container-interface",
+ "container-interop",
+ "psr"
+ ],
+ "support": {
+ "issues": "https://github.com/php-fig/container/issues",
+ "source": "https://github.com/php-fig/container/tree/2.0.2"
+ },
+ "time": "2021-11-05T16:47:00+00:00"
},
{
"name": "respect/coding-standard",
@@ -1363,6 +1640,64 @@
},
"time": "2026-01-19T10:34:07+00:00"
},
+ {
+ "name": "respect/fluentgen",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Respect/FluentGen.git",
+ "reference": "0e47b54b6748cb445ae34222d3bd0c1d661e6c86"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Respect/FluentGen/zipball/0e47b54b6748cb445ae34222d3bd0c1d661e6c86",
+ "reference": "0e47b54b6748cb445ae34222d3bd0c1d661e6c86",
+ "shasum": ""
+ },
+ "require": {
+ "nette/php-generator": "^4.1",
+ "php": "^8.5"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^2.1",
+ "phpstan/phpstan-deprecation-rules": "^2.0",
+ "phpstan/phpstan-strict-rules": "^2.0",
+ "phpunit/phpunit": "^12.5",
+ "respect/coding-standard": "^5.0",
+ "respect/fluent": "^1.0"
+ },
+ "suggest": {
+ "respect/fluent": "Enables #[Composable] prefix composition support"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Respect\\FluentGen\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "ISC"
+ ],
+ "authors": [
+ {
+ "name": "Respect/FluentGen Contributors",
+ "homepage": "https://github.com/Respect/FluentGen/graphs/contributors"
+ }
+ ],
+ "description": "Code generation for fluent builder interfaces",
+ "keywords": [
+ "fluent",
+ "fluentgen",
+ "mixin",
+ "respect"
+ ],
+ "support": {
+ "issues": "https://github.com/Respect/FluentGen/issues",
+ "source": "https://github.com/Respect/FluentGen/tree/1.0.1"
+ },
+ "time": "2026-03-23T21:57:24+00:00"
+ },
{
"name": "sebastian/cli-parser",
"version": "4.2.0",
@@ -1651,16 +1986,16 @@
},
{
"name": "sebastian/environment",
- "version": "8.0.3",
+ "version": "8.0.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
- "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68"
+ "reference": "7b8842c2d8e85d0c3a5831236bf5869af6ab2a11"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/24a711b5c916efc6d6e62aa65aa2ec98fef77f68",
- "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/7b8842c2d8e85d0c3a5831236bf5869af6ab2a11",
+ "reference": "7b8842c2d8e85d0c3a5831236bf5869af6ab2a11",
"shasum": ""
},
"require": {
@@ -1703,7 +2038,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/environment/issues",
"security": "https://github.com/sebastianbergmann/environment/security/policy",
- "source": "https://github.com/sebastianbergmann/environment/tree/8.0.3"
+ "source": "https://github.com/sebastianbergmann/environment/tree/8.0.4"
},
"funding": [
{
@@ -1723,7 +2058,7 @@
"type": "tidelift"
}
],
- "time": "2025-08-12T14:11:56+00:00"
+ "time": "2026-03-15T07:05:40+00:00"
},
{
"name": "sebastian/exporter",
@@ -2262,32 +2597,32 @@
},
{
"name": "slevomat/coding-standard",
- "version": "8.27.1",
+ "version": "8.28.1",
"source": {
"type": "git",
"url": "https://github.com/slevomat/coding-standard.git",
- "reference": "29bdaee8b65e7ed2b8e702b01852edba8bae1769"
+ "reference": "66151cfbd25b50e8becd9f809fb704f01fd4d6f2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/29bdaee8b65e7ed2b8e702b01852edba8bae1769",
- "reference": "29bdaee8b65e7ed2b8e702b01852edba8bae1769",
+ "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/66151cfbd25b50e8becd9f809fb704f01fd4d6f2",
+ "reference": "66151cfbd25b50e8becd9f809fb704f01fd4d6f2",
"shasum": ""
},
"require": {
"dealerdirect/phpcodesniffer-composer-installer": "^0.7 || ^1.2.0",
"php": "^7.4 || ^8.0",
- "phpstan/phpdoc-parser": "^2.3.1",
+ "phpstan/phpdoc-parser": "^2.3.2",
"squizlabs/php_codesniffer": "^4.0.1"
},
"require-dev": {
- "phing/phing": "3.0.1|3.1.1",
+ "phing/phing": "3.0.1|3.1.2",
"php-parallel-lint/php-parallel-lint": "1.4.0",
- "phpstan/phpstan": "2.1.37",
- "phpstan/phpstan-deprecation-rules": "2.0.3",
- "phpstan/phpstan-phpunit": "2.0.12",
- "phpstan/phpstan-strict-rules": "2.0.7",
- "phpunit/phpunit": "9.6.31|10.5.60|11.4.4|11.5.49|12.5.7"
+ "phpstan/phpstan": "2.1.42",
+ "phpstan/phpstan-deprecation-rules": "2.0.4",
+ "phpstan/phpstan-phpunit": "2.0.16",
+ "phpstan/phpstan-strict-rules": "2.0.10",
+ "phpunit/phpunit": "9.6.34|10.5.63|11.4.4|11.5.50|12.5.14"
},
"type": "phpcodesniffer-standard",
"extra": {
@@ -2311,7 +2646,7 @@
],
"support": {
"issues": "https://github.com/slevomat/coding-standard/issues",
- "source": "https://github.com/slevomat/coding-standard/tree/8.27.1"
+ "source": "https://github.com/slevomat/coding-standard/tree/8.28.1"
},
"funding": [
{
@@ -2323,7 +2658,7 @@
"type": "tidelift"
}
],
- "time": "2026-01-25T15:57:07+00:00"
+ "time": "2026-03-22T17:22:38+00:00"
},
{
"name": "squizlabs/php_codesniffer",
@@ -2457,35 +2792,56 @@
"time": "2024-10-20T05:08:20+00:00"
},
{
- "name": "symfony/deprecation-contracts",
- "version": "v3.6.0",
+ "name": "symfony/console",
+ "version": "v7.4.7",
"source": {
"type": "git",
- "url": "https://github.com/symfony/deprecation-contracts.git",
- "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62"
+ "url": "https://github.com/symfony/console.git",
+ "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62",
- "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62",
+ "url": "https://api.github.com/repos/symfony/console/zipball/e1e6770440fb9c9b0cf725f81d1361ad1835329d",
+ "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d",
"shasum": ""
},
"require": {
- "php": ">=8.1"
+ "php": ">=8.2",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/service-contracts": "^2.5|^3",
+ "symfony/string": "^7.2|^8.0"
},
- "type": "library",
- "extra": {
- "thanks": {
- "url": "https://github.com/symfony/contracts",
- "name": "symfony/contracts"
- },
- "branch-alias": {
- "dev-main": "3.6-dev"
- }
+ "conflict": {
+ "symfony/dependency-injection": "<6.4",
+ "symfony/dotenv": "<6.4",
+ "symfony/event-dispatcher": "<6.4",
+ "symfony/lock": "<6.4",
+ "symfony/process": "<6.4"
+ },
+ "provide": {
+ "psr/log-implementation": "1.0|2.0|3.0"
+ },
+ "require-dev": {
+ "psr/log": "^1|^2|^3",
+ "symfony/config": "^6.4|^7.0|^8.0",
+ "symfony/dependency-injection": "^6.4|^7.0|^8.0",
+ "symfony/event-dispatcher": "^6.4|^7.0|^8.0",
+ "symfony/http-foundation": "^6.4|^7.0|^8.0",
+ "symfony/http-kernel": "^6.4|^7.0|^8.0",
+ "symfony/lock": "^6.4|^7.0|^8.0",
+ "symfony/messenger": "^6.4|^7.0|^8.0",
+ "symfony/process": "^6.4|^7.0|^8.0",
+ "symfony/stopwatch": "^6.4|^7.0|^8.0",
+ "symfony/var-dumper": "^6.4|^7.0|^8.0"
},
+ "type": "library",
"autoload": {
- "files": [
- "function.php"
+ "psr-4": {
+ "Symfony\\Component\\Console\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
@@ -2494,18 +2850,24 @@
],
"authors": [
{
- "name": "Nicolas Grekas",
- "email": "p@tchwork.com"
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
- "description": "A generic function and convention to trigger deprecation notices",
+ "description": "Eases the creation of beautiful and testable command line interfaces",
"homepage": "https://symfony.com",
+ "keywords": [
+ "cli",
+ "command-line",
+ "console",
+ "terminal"
+ ],
"support": {
- "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0"
+ "source": "https://github.com/symfony/console/tree/v7.4.7"
},
"funding": [
{
@@ -2516,25 +2878,523 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2024-09-25T14:21:43+00:00"
+ "time": "2026-03-06T14:06:20+00:00"
+ },
+ {
+ "name": "symfony/deprecation-contracts",
+ "version": "v3.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/deprecation-contracts.git",
+ "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62",
+ "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.6-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "function.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "A generic function and convention to trigger deprecation notices",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-25T14:21:43+00:00"
+ },
+ {
+ "name": "symfony/polyfill-ctype",
+ "version": "v1.33.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-ctype.git",
+ "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
+ "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "provide": {
+ "ext-ctype": "*"
+ },
+ "suggest": {
+ "ext-ctype": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Ctype\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Gert de Pagter",
+ "email": "BackEndTea@gmail.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for ctype functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "ctype",
+ "polyfill",
+ "portable"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-09T11:45:10+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-grapheme",
+ "version": "v1.33.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-grapheme.git",
+ "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70",
+ "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Grapheme\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's grapheme_* functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "grapheme",
+ "intl",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-06-27T09:58:17+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-normalizer",
+ "version": "v1.33.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
+ "reference": "3833d7255cc303546435cb650316bff708a1c75c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c",
+ "reference": "3833d7255cc303546435cb650316bff708a1c75c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Normalizer\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's Normalizer class and related functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "intl",
+ "normalizer",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-09T11:45:10+00:00"
+ },
+ {
+ "name": "symfony/service-contracts",
+ "version": "v3.6.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/service-contracts.git",
+ "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43",
+ "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "psr/container": "^1.1|^2.0",
+ "symfony/deprecation-contracts": "^2.5|^3"
+ },
+ "conflict": {
+ "ext-psr": "<1.1|>=2"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.6-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Service\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Test/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to writing services",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/service-contracts/tree/v3.6.1"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-07-15T11:30:57+00:00"
+ },
+ {
+ "name": "symfony/string",
+ "version": "v8.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/string.git",
+ "reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/string/zipball/6c9e1108041b5dce21a9a4984b531c4923aa9ec4",
+ "reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.4",
+ "symfony/polyfill-ctype": "^1.8",
+ "symfony/polyfill-intl-grapheme": "^1.33",
+ "symfony/polyfill-intl-normalizer": "^1.0",
+ "symfony/polyfill-mbstring": "^1.0"
+ },
+ "conflict": {
+ "symfony/translation-contracts": "<2.5"
+ },
+ "require-dev": {
+ "symfony/emoji": "^7.4|^8.0",
+ "symfony/http-client": "^7.4|^8.0",
+ "symfony/intl": "^7.4|^8.0",
+ "symfony/translation-contracts": "^2.5|^3.0",
+ "symfony/var-exporter": "^7.4|^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "Resources/functions.php"
+ ],
+ "psr-4": {
+ "Symfony\\Component\\String\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "grapheme",
+ "i18n",
+ "string",
+ "unicode",
+ "utf-8",
+ "utf8"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/string/tree/v8.0.6"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-02-09T10:14:57+00:00"
},
{
"name": "symfony/translation",
- "version": "v7.4.4",
+ "version": "v7.4.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
- "reference": "bfde13711f53f549e73b06d27b35a55207528877"
+ "reference": "1888cf064399868af3784b9e043240f1d89d25ce"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/translation/zipball/bfde13711f53f549e73b06d27b35a55207528877",
- "reference": "bfde13711f53f549e73b06d27b35a55207528877",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/1888cf064399868af3784b9e043240f1d89d25ce",
+ "reference": "1888cf064399868af3784b9e043240f1d89d25ce",
"shasum": ""
},
"require": {
@@ -2601,7 +3461,7 @@
"description": "Provides tools to internationalize your application",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/translation/tree/v7.4.4"
+ "source": "https://github.com/symfony/translation/tree/v7.4.6"
},
"funding": [
{
@@ -2621,7 +3481,7 @@
"type": "tidelift"
}
],
- "time": "2026-01-13T10:40:19+00:00"
+ "time": "2026-02-17T07:53:42+00:00"
},
{
"name": "theseer/tokenizer",
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
index 457fe6b..32fc233 100644
--- a/phpcs.xml.dist
+++ b/phpcs.xml.dist
@@ -15,4 +15,10 @@
tests/
+
+
+
+ src/Mixins/
+ tests/feature/
+
diff --git a/src-dev/Commands/LintMixinCommand.php b/src-dev/Commands/LintMixinCommand.php
new file mode 100644
index 0000000..ef65e8f
--- /dev/null
+++ b/src-dev/Commands/LintMixinCommand.php
@@ -0,0 +1,120 @@
+
+ */
+
+declare(strict_types=1);
+
+namespace Respect\StringFormatter\DevTools\Commands;
+
+use Respect\FluentGen\Config;
+use Respect\FluentGen\Fluent\InterfaceConfig;
+use Respect\FluentGen\Fluent\MethodBuilder;
+use Respect\FluentGen\Fluent\MixinGenerator;
+use Respect\FluentGen\NamespaceScanner;
+use Respect\StringFormatter\Formatter;
+use Respect\StringFormatter\FormatterBuilder;
+use Respect\StringFormatter\Mixins\Chain;
+use Symfony\Component\Console\Attribute\AsCommand;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+use function count;
+use function dirname;
+use function file_get_contents;
+use function file_put_contents;
+use function is_file;
+use function is_readable;
+use function sprintf;
+
+#[AsCommand(
+ name: 'lint:mixin',
+ description: 'Apply linters to the generated mixin interfaces',
+)]
+final class LintMixinCommand extends Command
+{
+ protected function configure(): void
+ {
+ $this->addOption(
+ 'fix',
+ null,
+ null,
+ 'Automatically fix files with issues.',
+ );
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ $srcDir = dirname(__DIR__, 2) . '/src';
+
+ $config = new Config(
+ sourceDir: $srcDir,
+ sourceNamespace: 'Respect\\StringFormatter',
+ outputDir: $srcDir . '/Mixins',
+ outputNamespace: 'Respect\\StringFormatter\\Mixins',
+ );
+
+ $scanner = new NamespaceScanner(
+ nodeType: Formatter::class,
+ excludedClassNames: ['FormatterBuilder'],
+ );
+
+ $generator = new MixinGenerator(
+ config: $config,
+ scanner: $scanner,
+ methodBuilder: new MethodBuilder(classSuffix: 'Formatter'),
+ interfaces: [
+ new InterfaceConfig(
+ suffix: 'Builder',
+ returnType: Chain::class,
+ static: true,
+ rootComment: '@mixin FormatterBuilder',
+ rootUses: [FormatterBuilder::class],
+ ),
+ new InterfaceConfig(
+ suffix: 'Chain',
+ returnType: Chain::class,
+ rootExtends: [Formatter::class],
+ ),
+ ],
+ );
+
+ $files = $generator->generate();
+
+ $updatableFiles = [];
+ foreach ($files as $filename => $content) {
+ $existingContent = '';
+ if (is_file($filename) && is_readable($filename)) {
+ $existingContent = file_get_contents($filename) ?: '';
+ }
+
+ if ($content === $existingContent) {
+ continue;
+ }
+
+ $updatableFiles[$filename] = $content;
+ $output->writeln(sprintf('--- a/%s', $filename));
+ $output->writeln(sprintf('+++ b/%s', $filename));
+ }
+
+ if ($updatableFiles === []) {
+ $output->writeln('No changes needed.');
+ } else {
+ $output->writeln(sprintf('Changes needed in %d files.', count($updatableFiles)));
+ }
+
+ if ($updatableFiles !== [] && !$input->getOption('fix')) {
+ return Command::FAILURE;
+ }
+
+ foreach ($updatableFiles as $filename => $content) {
+ file_put_contents($filename, $content);
+ }
+
+ return Command::SUCCESS;
+ }
+}
diff --git a/src/FormatterBuilder.php b/src/FormatterBuilder.php
index 6fa1f2a..c0be9e2 100644
--- a/src/FormatterBuilder.php
+++ b/src/FormatterBuilder.php
@@ -4,60 +4,43 @@
* SPDX-FileCopyrightText: (c) Respect Project Contributors
* SPDX-License-Identifier: ISC
* SPDX-FileContributor: Henrique Moody
+ * SPDX-FileContributor: Alexandre Gomes Gaigalas
*/
declare(strict_types=1);
namespace Respect\StringFormatter;
-use ReflectionClass;
-use Respect\StringFormatter\Mixin\Builder;
+use Respect\Fluent\Attributes\FluentNamespace;
+use Respect\Fluent\Builders\Append;
+use Respect\Fluent\Factories\NamespaceLookup;
+use Respect\Fluent\Resolvers\Suffix;
+use Respect\StringFormatter\Mixins\Builder;
use function array_reduce;
-use function ucfirst;
/** @mixin Builder */
-final readonly class FormatterBuilder implements Formatter
+#[FluentNamespace(new NamespaceLookup(new Suffix('', 'Formatter'), Formatter::class, 'Respect\\StringFormatter'))]
+final readonly class FormatterBuilder extends Append implements Formatter
{
- /** @var array */
- private array $formatters;
-
public function __construct(Formatter ...$formatters)
{
- $this->formatters = $formatters;
- }
-
- public static function create(Formatter ...$formatters): self
- {
- return new self(...$formatters);
+ parent::__construct(static::factoryFromAttribute(), ...$formatters);
}
public function format(string $input): string
{
- if ($this->formatters === []) {
+ /** @var array $formatters */
+ $formatters = $this->getNodes();
+
+ if ($formatters === []) {
throw new InvalidFormatterException('No formatters have been added to the builder');
}
return array_reduce(
- $this->formatters,
+ $formatters,
static fn(string $carry, Formatter $formatter) => $formatter->format($carry),
$input,
);
}
-
- /** @param array $arguments */
- public function __call(string $name, array $arguments): self
- {
- /** @var class-string $class */
- $class = __NAMESPACE__ . '\\' . ucfirst($name) . 'Formatter';
- $reflection = new ReflectionClass($class);
-
- return clone($this, ['formatters' => [...$this->formatters, $reflection->newInstanceArgs($arguments)]]);
- }
-
- /** @param array $arguments */
- public static function __callStatic(string $name, array $arguments): self
- {
- return self::create()->__call($name, $arguments);
- }
}
diff --git a/src/Internal/CompiledPattern.php b/src/Internal/CompiledPattern.php
index 3328097..d395bea 100644
--- a/src/Internal/CompiledPattern.php
+++ b/src/Internal/CompiledPattern.php
@@ -197,7 +197,7 @@ private static function compileQualifier(string $token, int $offset): string
}
preg_match('/^\{(\d*)(?:,(\d*))?\}$/', $token, $m);
- $max = $m[2] ?? $m[1];
+ $max = $m[2] ?? $m[1] ?? '';
return self::$compiledQualifiers[$token] = $max === '' ? '*' : sprintf('{0,%s}', $max);
}
diff --git a/src/Mixin/Builder.php b/src/Mixins/Builder.php
similarity index 76%
rename from src/Mixin/Builder.php
rename to src/Mixins/Builder.php
index abe9ce7..b9f8ffd 100644
--- a/src/Mixin/Builder.php
+++ b/src/Mixins/Builder.php
@@ -9,9 +9,10 @@
declare(strict_types=1);
-namespace Respect\StringFormatter\Mixin;
+namespace Respect\StringFormatter\Mixins;
use Respect\StringFormatter\FormatterBuilder;
+use Respect\StringFormatter\Modifier;
/** @mixin FormatterBuilder */
interface Builder
@@ -36,23 +37,19 @@ public static function mass(string $unit): Chain;
public static function metric(string $unit): Chain;
- public static function number(
- int $decimals = 0,
- string $decimalSeparator = '.',
- string $thousandsSeparator = ',',
- ): Chain;
+ public static function number(int $decimals = 0, string $decimalSeparator = '.', string $thousandsSeparator = ','): Chain;
public static function pattern(string $pattern): Chain;
/** @param array $parameters */
- public static function placeholder(array $parameters): Chain;
+ public static function placeholder(array $parameters, Modifier|null $modifier = null): Chain;
public static function secureCreditCard(string $maskChar = '*'): Chain;
public static function time(string $unit): Chain;
/** @param 'both'|'left'|'right' $side */
- public static function trim(string $side, string|null $characters): Chain;
+ public static function trim(string $side = 'both', string|null $characters = null): Chain;
public static function uppercase(): Chain;
}
diff --git a/src/Mixin/Chain.php b/src/Mixins/Chain.php
similarity index 76%
rename from src/Mixin/Chain.php
rename to src/Mixins/Chain.php
index 6614f5a..208a622 100644
--- a/src/Mixin/Chain.php
+++ b/src/Mixins/Chain.php
@@ -9,9 +9,10 @@
declare(strict_types=1);
-namespace Respect\StringFormatter\Mixin;
+namespace Respect\StringFormatter\Mixins;
use Respect\StringFormatter\Formatter;
+use Respect\StringFormatter\Modifier;
interface Chain extends Formatter
{
@@ -35,23 +36,19 @@ public function mass(string $unit): Chain;
public function metric(string $unit): Chain;
- public function number(
- int $decimals = 0,
- string $decimalSeparator = '.',
- string $thousandsSeparator = ',',
- ): Chain;
+ public function number(int $decimals = 0, string $decimalSeparator = '.', string $thousandsSeparator = ','): Chain;
public function pattern(string $pattern): Chain;
/** @param array $parameters */
- public function placeholder(array $parameters): Chain;
+ public function placeholder(array $parameters, Modifier|null $modifier = null): Chain;
public function secureCreditCard(string $maskChar = '*'): Chain;
public function time(string $unit): Chain;
/** @param 'both'|'left'|'right' $side */
- public function trim(string $side, string|null $characters): Chain;
+ public function trim(string $side = 'both', string|null $characters = null): Chain;
public function uppercase(): Chain;
}
diff --git a/tests/Integration/FormatterBuilderTest.php b/tests/Integration/FormatterBuilderTest.php
index 3e02480..a15ed8a 100644
--- a/tests/Integration/FormatterBuilderTest.php
+++ b/tests/Integration/FormatterBuilderTest.php
@@ -4,6 +4,7 @@
* SPDX-FileCopyrightText: (c) Respect Project Contributors
* SPDX-License-Identifier: ISC
* SPDX-FileContributor: Henrique Moody
+ * SPDX-FileContributor: Alexandre Gomes Gaigalas
*/
declare(strict_types=1);
@@ -11,11 +12,10 @@
namespace Respect\StringFormatter\Test\Integration;
use ArgumentCountError;
-use Error;
+use InvalidArgumentException;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
-use ReflectionException;
use Respect\StringFormatter\FormatterBuilder;
use Respect\StringFormatter\InvalidFormatterException;
use Respect\StringFormatter\MaskFormatter;
@@ -163,8 +163,7 @@ public function itShouldThrowExceptionWhenFormatterIsNotInstantiable(): void
{
$builder = new FormatterBuilder();
- $this->expectException(Error::class);
- $this->expectExceptionMessage('Cannot instantiate interface Respect\StringFormatter\Formatter');
+ $this->expectException(InvalidArgumentException::class);
$builder->__call('', []);
}
@@ -189,8 +188,7 @@ public function itShouldThrowExceptionWhenFormatterDoesNotExist(): void
{
$builder = new FormatterBuilder();
- $this->expectException(ReflectionException::class);
- $this->expectExceptionMessage('Class "Respect\StringFormatter\NonexistentFormatter" does not exist');
+ $this->expectException(InvalidArgumentException::class);
/** @phpstan-ignore method.notFound */
$builder->nonexistent();