diff --git a/docs/book/v3/migration/v2-to-v3.md b/docs/book/v3/migration/v2-to-v3.md index 66f78a78..7b38aa6e 100644 --- a/docs/book/v3/migration/v2-to-v3.md +++ b/docs/book/v3/migration/v2-to-v3.md @@ -48,6 +48,51 @@ The impact of the removal of these aliases will not affect you if you use a FQCN ### Changes to Individual Filters +#### `CamelCaseToDash` + +The following methods have been removed: + +- `setOptions` +- `getOptions` +- `isOptions` +- `setSeparator` +- `getSeparator` + +The constructor now only accepts an associative array of [documented options](../word.md#camelCaseToDash). + +The filter will now treat numbers as a word boundary. +For example `ThisHas4Words` will filter to `This-Has-4-Words` + +#### `CamelCaseToSeparator` + +The following methods have been removed: + +- `setOptions` +- `getOptions` +- `isOptions` +- `setSeparator` +- `getSeparator` + +The constructor now only accepts an associative array of [documented options](../word.md#camelCaseToSeparator). + +The filter will now treat numbers as a word boundary. +For example `ThisHas4Words` with the default separator will filter to `This Has 4 Words` + +#### `CamelCaseToUnderscore` + +The following methods have been removed: + +- `setOptions` +- `getOptions` +- `isOptions` +- `setSeparator` +- `getSeparator` + +The constructor now only accepts an associative array of [documented options](../word.md#camelCaseToUnderscore). + +The filter will now treat numbers as a word boundary. +For example `ThisHas4Words` will filter to `This_Has_4_Words` + #### `DashToSeparator` The following methods have been removed: diff --git a/docs/book/v3/word.md b/docs/book/v3/word.md index 57564a42..cc95b4ad 100644 --- a/docs/book/v3/word.md +++ b/docs/book/v3/word.md @@ -5,6 +5,10 @@ to filtering word strings. ## CamelCaseToDash +TIP: **New Behaviour since Version 3** +The filter will now treat numbers as a word boundary. +For example `ThisHas4Words` will filter to `This-Has-4-Words`. + This filter modifies a given string such that `CamelCaseWords` are converted to `Camel-Case-Words`. ### Supported Options @@ -23,6 +27,10 @@ The above example returns `This-Is-My-Content`. ## CamelCaseToSeparator +TIP: **New Behaviour since Version 3** +The filter will now treat numbers as a word boundary. +For example `ThisHas4Words` with the default separator will filter to `This Has 4 Words` + This filter modifies a given string such that `CamelCaseWords` are converted to `Camel Case Words`. ### Supported Options @@ -35,8 +43,7 @@ The following options are supported for `Laminas\Filter\Word\CamelCaseToSeparato ### Basic Usage ```php -$filter = new Laminas\Filter\Word\CamelCaseToSeparator(':'); -// or new Laminas\Filter\Word\CamelCaseToSeparator(array('separator' => ':')); +$filter = new Laminas\Filter\Word\CamelCaseToSeparator(['separator' => ':']); print $filter->filter('ThisIsMyContent'); ``` @@ -55,6 +62,10 @@ The above example returns `This Is My Content`. ## CamelCaseToUnderscore +TIP: **New Behaviour since Version 3** +The filter will now treat numbers as a word boundary. +For example `ThisHas4Words` will filter to `This_Has_4_Words` + This filter modifies a given string such that `CamelCaseWords` are converted to `Camel_Case_Words`. diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 9e89f811..c58e65d4 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -961,15 +961,15 @@ + - - - - + + + diff --git a/src/Word/CamelCaseToDash.php b/src/Word/CamelCaseToDash.php index 8fb00db5..573338a4 100644 --- a/src/Word/CamelCaseToDash.php +++ b/src/Word/CamelCaseToDash.php @@ -4,18 +4,20 @@ namespace Laminas\Filter\Word; -/** - * @psalm-type Options = array{ - * separator?: string, - * ... - * } - * @template TOptions of Options - * @extends CamelCaseToSeparator - */ -final class CamelCaseToDash extends CamelCaseToSeparator +use Laminas\Filter\FilterInterface; + +/** @implements FilterInterface> */ +final class CamelCaseToDash implements FilterInterface { - public function __construct() + public function filter(mixed $value): mixed + { + $filter = new CamelCaseToSeparator(['separator' => '-']); + + return $filter->filter($value); + } + + public function __invoke(mixed $value): mixed { - parent::__construct('-'); + return $this->filter($value); } } diff --git a/src/Word/CamelCaseToSeparator.php b/src/Word/CamelCaseToSeparator.php index 4b885cf7..d9848959 100644 --- a/src/Word/CamelCaseToSeparator.php +++ b/src/Word/CamelCaseToSeparator.php @@ -4,43 +4,57 @@ namespace Laminas\Filter\Word; -use Closure; -use Laminas\Stdlib\StringUtils; +use Laminas\Filter\FilterInterface; +use Laminas\Filter\ScalarOrArrayFilterCallback; -use function preg_replace; +use function implode; +use function preg_split; + +use const PREG_SPLIT_DELIM_CAPTURE; +use const PREG_SPLIT_NO_EMPTY; /** * @psalm-type Options = array{ * separator?: string, - * ... * } * @template TOptions of Options - * @extends AbstractSeparator + * @implements FilterInterface> */ -class CamelCaseToSeparator extends AbstractSeparator +final class CamelCaseToSeparator implements FilterInterface { - public function filter(mixed $value): mixed + private readonly string $separator; + + /** @param Options $options */ + public function __construct(array $options = []) { - return self::applyFilterOnlyToStringableValuesAndStringableArrayValues( - $value, - Closure::fromCallable([$this, 'filterNormalizedValue']) - ); + $this->separator = $options['separator'] ?? ' '; + } + + public function __invoke(mixed $value): mixed + { + return $this->filter($value); } - /** - * @param string|string[] $value - * @return string|string[] - */ - private function filterNormalizedValue(string|array $value): string|array + public function filter(mixed $value): mixed { - if (StringUtils::hasPcreUnicodeSupport()) { - $pattern = ['#(?<=(?:\p{Lu}))(\p{Lu}\p{Ll})#', '#(?<=(?:\p{Ll}|\p{Nd}))(\p{Lu})#']; - $replacement = [$this->separator . '\1', $this->separator . '\1']; - } else { - $pattern = ['#(?<=(?:[A-Z]))([A-Z]+)([A-Z][a-z])#', '#(?<=(?:[a-z0-9]))([A-Z])#']; - $replacement = ['\1' . $this->separator . '\2', $this->separator . '\1']; - } - - return preg_replace($pattern, $replacement, $value); + $pattern = << implode( + $this->separator, + preg_split($pattern, $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY), + ) + ); } } diff --git a/src/Word/CamelCaseToUnderscore.php b/src/Word/CamelCaseToUnderscore.php index b37876e9..46f0c50c 100644 --- a/src/Word/CamelCaseToUnderscore.php +++ b/src/Word/CamelCaseToUnderscore.php @@ -4,18 +4,20 @@ namespace Laminas\Filter\Word; -/** - * @psalm-type Options = array{ - * separator?: string, - * ... - * } - * @template TOptions of Options - * @extends CamelCaseToSeparator - */ -final class CamelCaseToUnderscore extends CamelCaseToSeparator +use Laminas\Filter\FilterInterface; + +/** @implements FilterInterface> */ +final class CamelCaseToUnderscore implements FilterInterface { - public function __construct() + public function filter(mixed $value): mixed + { + $filter = new CamelCaseToSeparator(['separator' => '_']); + + return $filter->filter($value); + } + + public function __invoke(mixed $value): mixed { - parent::__construct('_'); + return $this->filter($value); } } diff --git a/test/Word/CamelCaseToSeparatorTest.php b/test/Word/CamelCaseToSeparatorTest.php index 1c992983..f518622e 100644 --- a/test/Word/CamelCaseToSeparatorTest.php +++ b/test/Word/CamelCaseToSeparatorTest.php @@ -21,24 +21,53 @@ public function testFilterSeparatesCamelCasedWordsWithSpacesByDefault(): void self::assertSame('Camel Cased Words', $filtered); } - public function testFilterSeparatesCamelCasedWordsWithProvidedSeparator(): void + /** @return list */ + public static function camelCasedWordsProvider(): array { - $string = 'CamelCasedWords'; - $filter = new CamelCaseToSeparatorFilter(':-#'); - $filtered = $filter($string); + return [ + ['SomeCamelCase', 'Some-Camel-Case'], + ['Some12With5Numbers', 'Some-12-With-5-Numbers'], + ['SomePDFInText', 'Some-PDF-In-Text'], + ['123LeadingNumbers', '123-Leading-Numbers'], + ['ItIs2016', 'It-Is-2016'], + ['What-If', 'What---If'], + ['ASingleLetterB', 'A-Single-Letter-B'], + ['some_snake_case', 'some_snake_case'], + ['Title_Snake_Case', 'Title-_-Snake-_-Case'], + ['lower-with-dash', 'lower-with-dash'], + ['FFS!', 'FFS-!'], + ['WithA😃', 'With-A-😃'], + ['PDF123', 'PDF-123'], + ['EmojiInThe🤞Middle', 'Emoji-In-The-🤞-Middle'], + ['12345', '12345'], + ['123A', '123-A'], + ['A123', 'A-123'], + ['War&Peace', 'War-&-Peace'], + ['lowerThenTitleCase', 'lower-Then-Title-Case'], + ['123lower', '123-lower'], + ['lower123', 'lower-123'], + ['ItIsÜber', 'It-Is-Über'], + ['SømeThing', 'Søme-Thing'], + ]; + } - self::assertNotEquals($string, $filtered); - self::assertSame('Camel:-#Cased:-#Words', $filtered); + #[DataProvider('camelCasedWordsProvider')] + public function testFilterSeparatesCamelCasedWordsWithProvidedSeparator(string $input, string $expected): void + { + $filter = new CamelCaseToSeparatorFilter(['separator' => '-']); + $filtered = $filter($input); + + self::assertSame($expected, $filtered); } public function testFilterSeperatesMultipleUppercasedLettersAndUnderscores(): void { $string = 'TheseAre_SOME_CamelCASEDWords'; - $filter = new CamelCaseToSeparatorFilter('_'); + $filter = new CamelCaseToSeparatorFilter(['separator' => '_']); $filtered = $filter($string); self::assertNotEquals($string, $filtered); - self::assertSame('These_Are_SOME_Camel_CASED_Words', $filtered); + self::assertSame('These_Are___SOME___Camel_CASED_Words', $filtered); } public function testFilterSupportArray(): void diff --git a/test/Word/CamelCaseToUnderscoreTest.php b/test/Word/CamelCaseToUnderscoreTest.php index 2d14023e..a77738b3 100644 --- a/test/Word/CamelCaseToUnderscoreTest.php +++ b/test/Word/CamelCaseToUnderscoreTest.php @@ -5,41 +5,28 @@ namespace LaminasTest\Filter\Word; use Laminas\Filter\Word\CamelCaseToUnderscore as CamelCaseToUnderscoreFilter; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class CamelCaseToUnderscoreTest extends TestCase { - public function testFilterSeparatesCamelCasedWordsWithUnderscores(): void + /** @return list */ + public static function camelCasedWordsProvider(): array { - $string = 'CamelCasedWords'; - $filter = new CamelCaseToUnderscoreFilter(); - $filtered = $filter($string); - - self::assertNotEquals($string, $filtered); - self::assertSame('Camel_Cased_Words', $filtered); + return [ + ['CamelCasedWords', 'Camel_Cased_Words'], + ['PaTitle', 'Pa_Title'], + ['Pa2Title', 'Pa_2_Title'], + ['Pa2aTitle', 'Pa_2_a_Title'], + ]; } - public function testFilterSeparatingNumbersToUnderscore(): void + #[DataProvider('camelCasedWordsProvider')] + public function testFilterSeparatesCamelCasedWordsWithUnderscores(string $input, string $expected): void { - $string = 'PaTitle'; - $filter = new CamelCaseToUnderscoreFilter(); - $filtered = $filter($string); - - self::assertNotEquals($string, $filtered); - self::assertSame('Pa_Title', $filtered); - - $string = 'Pa2Title'; - $filter = new CamelCaseToUnderscoreFilter(); - $filtered = $filter($string); - - self::assertNotEquals($string, $filtered); - self::assertSame('Pa2_Title', $filtered); - - $string = 'Pa2aTitle'; $filter = new CamelCaseToUnderscoreFilter(); - $filtered = $filter($string); + $filtered = $filter($input); - self::assertNotEquals($string, $filtered); - self::assertSame('Pa2a_Title', $filtered); + self::assertSame($expected, $filtered); } }