Case Converter — PHP source
Convert text between UPPER, lower, Title, Sentence, camelCase, PascalCase, snake_case, kebab-case, CONSTANT_CASE and slug. Live, one-click copy.
This is the PHP implementation — the same logic the interactive tool runs, in a shareable, citable form.
<?php
/**
* case-converter — PHP polyglot showcase port.
*
* Pure, deterministic text-case transformations: split any input into word
* tokens and re-emit it in upper / lower / title / sentence / camel / pascal /
* snake / kebab / constant / slug case.
*
* Ported from src/lib/case.ts (the canonical TypeScript that powers the live
* Case Converter tool on dev.cosmolabs.org). Functionally equivalent — same
* inputs produce the same outputs, including edge cases.
*
* This is display source — part of CosmoDev's polyglot tool pages (one tool,
* six language references). stdlib only, no Composer dependencies.
*/
declare(strict_types=1);
namespace CosmoDev\CaseConverter;
/**
* Pure case conversions exposed as static methods.
*
* The static method list mirrors the keys of the TypeScript `caseTransforms`
* object 1:1; {@see CaseConverter::transforms()} returns the callable registry
* that corresponds to that object directly.
*/
final class CaseConverter
{
/**
* Tokenize a string into lowercase-friendly word units.
*
* Mirrors the TypeScript reference:
* 1. Insert a space at every camelCase boundary
* (lowercase-or-digit immediately followed by uppercase).
* 2. Split on runs of whitespace / underscore / hyphen / dot.
* 3. Strip remaining non-alphanumerics and drop empty tokens.
*
* PCRE is used without the /u flag so \w and the character classes are
* ASCII-oriented, matching JavaScript's regex behavior.
*
* @return list<string>
*/
public static function words(string $s): array
{
// preg_replace can return null only on error; fall back to the raw
// input so a PCRE failure can never produce a TypeError here.
$spaced = preg_replace('/([a-z0-9])([A-Z])/', '$1 $2', $s) ?? $s;
$parts = preg_split('/[\s_\-.]+/', $spaced) ?: [];
$out = [];
foreach ($parts as $part) {
$cleaned = preg_replace('/[^a-zA-Z0-9]/', '', $part) ?? '';
if ($cleaned !== '') {
$out[] = $cleaned;
}
}
return $out;
}
/**
* Capitalize the first character and lowercase the rest.
*
* mbstring is used so multi-byte characters are treated as single
* characters (matching JavaScript's unicode-aware toUpperCase/toLowerCase).
*/
public static function cap(string $s): string
{
if ($s === '') {
return '';
}
return mb_strtoupper(mb_substr($s, 0, 1)) . mb_strtolower(mb_substr($s, 1));
}
public static function upper(string $s): string
{
return mb_strtoupper($s);
}
public static function lower(string $s): string
{
return mb_strtolower($s);
}
/**
* Title case: capitalize the first character of each maximal word-run.
*
* \w\S* matches a run that starts with a word character; the callback
* applies {@see cap} to each run. Spacing and punctuation between runs are
* preserved verbatim.
*/
public static function title(string $s): string
{
return preg_replace_callback(
'/\w\S*/',
static fn (array $m): string => self::cap($m[0]),
$s,
) ?? $s;
}
/**
* Sentence case: lowercase + trim the whole string, then uppercase only
* the first character. Empty input yields empty output.
*/
public static function sentence(string $s): string
{
$t = trim(mb_strtolower($s));
if ($t === '') {
return '';
}
return mb_strtoupper(mb_substr($t, 0, 1)) . mb_substr($t, 1);
}
/** camelCase: first word lowercase, every later word capitalized. */
public static function camel(string $s): string
{
$out = '';
foreach (self::words($s) as $i => $w) {
$out .= $i === 0 ? mb_strtolower($w) : self::cap($w);
}
return $out;
}
/** PascalCase: every word capitalized and concatenated. */
public static function pascal(string $s): string
{
$out = '';
foreach (self::words($s) as $w) {
$out .= self::cap($w);
}
return $out;
}
public static function snake(string $s): string
{
return mb_strtolower(implode('_', self::words($s)));
}
public static function kebab(string $s): string
{
return mb_strtolower(implode('-', self::words($s)));
}
public static function constant(string $s): string
{
return mb_strtoupper(implode('_', self::words($s)));
}
public static function slug(string $s): string
{
return mb_strtolower(implode('-', self::words($s)));
}
/**
* The case-transform registry: CaseId => callable.
*
* Mirrors the TypeScript `caseTransforms` object 1:1.
*
* @return array<string, callable(string): string>
*/
public static function transforms(): array
{
return [
'upper' => [self::class, 'upper'],
'lower' => [self::class, 'lower'],
'title' => [self::class, 'title'],
'sentence' => [self::class, 'sentence'],
'camel' => [self::class, 'camel'],
'pascal' => [self::class, 'pascal'],
'snake' => [self::class, 'snake'],
'kebab' => [self::class, 'kebab'],
'constant' => [self::class, 'constant'],
'slug' => [self::class, 'slug'],
];
}
/**
* Apply a named case transform.
*
* Convenience dispatcher over {@see transforms()}; the registry itself
* remains the primary public surface, matching the TypeScript reference.
*
* @throws \InvalidArgumentException if $caseId is not a known case.
*/
public static function convert(string $caseId, string $s): string
{
$map = self::transforms();
if (!isset($map[$caseId])) {
throw new \InvalidArgumentException("Unknown case: {$caseId}");
}
return $map[$caseId]($s);
}
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →