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);
}
}