How to convert PascalCase to snake_case?
Asked Answered
N

35

147

If I had:

$string = "PascalCase";

I need

"pascal_case"

Does PHP offer a function for this purpose?

Nalley answered 3/1, 2010 at 2:20 Comment(3)
Technically, the first example string is PascalCase.Boehmenist
And the second example string is known as snake_case.Ermin
I know this is 8 yrs late but just wanted to point out that OP clearly typed "pascal_case" instead of "snake_case" since it's about converting the string "PascalCase" to snake case, and not to the string "snake_case".Songsongbird
D
177

Try this on for size:

$tests = array(
  'simpleTest' => 'simple_test',
  'easy' => 'easy',
  'HTML' => 'html',
  'simpleXML' => 'simple_xml',
  'PDFLoad' => 'pdf_load',
  'startMIDDLELast' => 'start_middle_last',
  'AString' => 'a_string',
  'Some4Numbers234' => 'some4_numbers234',
  'TEST123String' => 'test123_string',
);

foreach ($tests as $test => $result) {
  $output = from_camel_case($test);
  if ($output === $result) {
    echo "Pass: $test => $result\n";
  } else {
    echo "Fail: $test => $result [$output]\n";
  }
}

function from_camel_case($input) {
  preg_match_all('!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!', $input, $matches);
  $ret = $matches[0];
  foreach ($ret as &$match) {
    $match = $match == strtoupper($match) ? strtolower($match) : lcfirst($match);
  }
  return implode('_', $ret);
}

Output:

Pass: simpleTest => simple_test
Pass: easy => easy
Pass: HTML => html
Pass: simpleXML => simple_xml
Pass: PDFLoad => pdf_load
Pass: startMIDDLELast => start_middle_last
Pass: AString => a_string
Pass: Some4Numbers234 => some4_numbers234
Pass: TEST123String => test123_string

This implements the following rules:

  1. A sequence beginning with a lowercase letter must be followed by lowercase letters and digits;
  2. A sequence beginning with an uppercase letter can be followed by either:
    • one or more uppercase letters and digits (followed by either the end of the string or an uppercase letter followed by a lowercase letter or digit ie the start of the next sequence); or
    • one or more lowercase letters or digits.
Danicadanice answered 3/1, 2010 at 2:45 Comment(3)
It works for CamelCased strings (as openfrog asked), but if you use it with input string for example "r_id" (already "underscored") it trimmes the prefix ("r_"). Good solution, but definitely not universal.Brotherly
Curious why you're checking if string matches all-caps string? What's the benefit of converting just the first character to lowercase (as opposed to all characters)?Turro
A more concise solution that can also handle all these use cases: https://mcmap.net/q/157854/-how-to-convert-pascalcase-to-snake_caseExhibit
S
219

A shorter solution: Similar to the editor's one with a simplified regular expression and fixing the "trailing-underscore" problem:

$output = strtolower(preg_replace('/(?<!^)[A-Z]/', '_$0', $input));

PHP Demo | Regex Demo


Note that cases like SimpleXML will be converted to simple_x_m_l using the above solution. That can also be considered a wrong usage of camel case notation (correct would be SimpleXml) rather than a bug of the algorithm since such cases are always ambiguous - even by grouping uppercase characters to one string (simple_xml) such algorithm will always fail in other edge cases like XMLHTMLConverter or one-letter words near abbreviations, etc. If you don't mind about the (rather rare) edge cases and want to handle SimpleXML correctly, you can use a little more complex solution:

$output = ltrim(strtolower(preg_replace('/[A-Z]([A-Z](?![a-z]))*/', '_$0', $input)), '_');

PHP Demo | Regex Demo

Shahaptian answered 23/10, 2013 at 5:8 Comment(9)
Feel free to comment on cletus' answer detailing which test cases you fixed.Upbuild
I'm not saying his solution gives wrong results. His solution is just extremely complicated and ineffective.Orissa
Yep, the accept answer is definitely a fail. Jan's solution is awesome! As a side note, I think this (or a slight variation) is my new, favourite coding test for PHP developers, because the number of answers provided to this question that don't actually work is incredible. It would be a great way to do the initial filtering. :-)Panthea
found the regex used in this solution much more complete: #2560259Sibell
Nice solution for simple use cases and in most usual cases it's enough but the accepted solution can handle more use cases, for example "simpleXML" may be converted to "simple_xml" and not "simple_x_m_l"Exhibit
Found this suggestion, liked it and took care of to not convert on start and to keep groups of uppercase letters together. So no worries about SimpleXML -> simple_xml then any longer.Sulamith
Thanks for the edit, it's an interesting approach, but it brings new problems. CamelCase notation is used in different ways and some (like SimpleXML) are ambiguous and there's nothing to be done about that (except machine learning or other complicated approach). Your method will not handle cases like ThisIsADonkey (which will result in this_is_adonkey). IMO the simple_x_m_l problem is rather a bad usage of CamelCase - SimpleXml would be correct. Imagine SimpleXMLADonkey... :) I would prefer to keep my original version and add yours below as an alternative if you agree with that.Orissa
@Sulamith Since your solution introduced new bugs (see regex101.com/r/tIlgOF/1) I've put back my original version (preserving your nice negative-lookbehind trick for the trailing underscore), explained its problems, and added a new solution - based on yours but fixing the problems.Orissa
@JanJakeš: Thanks for the notification, you're right all these pesky bugs. Doing this right is harder than thought.Sulamith
D
177

Try this on for size:

$tests = array(
  'simpleTest' => 'simple_test',
  'easy' => 'easy',
  'HTML' => 'html',
  'simpleXML' => 'simple_xml',
  'PDFLoad' => 'pdf_load',
  'startMIDDLELast' => 'start_middle_last',
  'AString' => 'a_string',
  'Some4Numbers234' => 'some4_numbers234',
  'TEST123String' => 'test123_string',
);

foreach ($tests as $test => $result) {
  $output = from_camel_case($test);
  if ($output === $result) {
    echo "Pass: $test => $result\n";
  } else {
    echo "Fail: $test => $result [$output]\n";
  }
}

function from_camel_case($input) {
  preg_match_all('!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!', $input, $matches);
  $ret = $matches[0];
  foreach ($ret as &$match) {
    $match = $match == strtoupper($match) ? strtolower($match) : lcfirst($match);
  }
  return implode('_', $ret);
}

Output:

Pass: simpleTest => simple_test
Pass: easy => easy
Pass: HTML => html
Pass: simpleXML => simple_xml
Pass: PDFLoad => pdf_load
Pass: startMIDDLELast => start_middle_last
Pass: AString => a_string
Pass: Some4Numbers234 => some4_numbers234
Pass: TEST123String => test123_string

This implements the following rules:

  1. A sequence beginning with a lowercase letter must be followed by lowercase letters and digits;
  2. A sequence beginning with an uppercase letter can be followed by either:
    • one or more uppercase letters and digits (followed by either the end of the string or an uppercase letter followed by a lowercase letter or digit ie the start of the next sequence); or
    • one or more lowercase letters or digits.
Danicadanice answered 3/1, 2010 at 2:45 Comment(3)
It works for CamelCased strings (as openfrog asked), but if you use it with input string for example "r_id" (already "underscored") it trimmes the prefix ("r_"). Good solution, but definitely not universal.Brotherly
Curious why you're checking if string matches all-caps string? What's the benefit of converting just the first character to lowercase (as opposed to all characters)?Turro
A more concise solution that can also handle all these use cases: https://mcmap.net/q/157854/-how-to-convert-pascalcase-to-snake_caseExhibit
E
44

A concise solution and can handle some tricky use cases:

function decamelize($string) {
    return strtolower(preg_replace(['/([a-z\d])([A-Z])/', '/([^_])([A-Z][a-z])/'], '$1_$2', $string));
}

Can handle all these cases:

simpleTest => simple_test
easy => easy
HTML => html
simpleXML => simple_xml
PDFLoad => pdf_load
startMIDDLELast => start_middle_last
AString => a_string
Some4Numbers234 => some4_numbers234
TEST123String => test123_string
hello_world => hello_world
hello__world => hello__world
_hello_world_ => _hello_world_
hello_World => hello_world
HelloWorld => hello_world
helloWorldFoo => hello_world_foo
hello-world => hello-world
myHTMLFiLe => my_html_fi_le
aBaBaB => a_ba_ba_b
BaBaBa => ba_ba_ba
libC => lib_c

You can test this function here: http://syframework.alwaysdata.net/decamelize

Exhibit answered 1/3, 2016 at 9:54 Comment(2)
@VivekVardhan which part of this regex you don't understand?Exhibit
Uhm, I think that lowercasing non camelcase strings is a side effect, in case the string is not in camel case format the original one should be returned. Infact if you send 'simple_Text' you get Fail: simple_Test => simple_Test [simple_test]. The lowercasing string should be made only and if only the original string is a real camel case string. What do you think about?Chamonix
A
36

The Symfony Serializer Component has a CamelCaseToSnakeCaseNameConverter that has two methods normalize() and denormalize(). These can be used as follows:

$nameConverter = new CamelCaseToSnakeCaseNameConverter();

echo $nameConverter->normalize('camelCase');
// outputs: camel_case

echo $nameConverter->denormalize('snake_case');
// outputs: snakeCase
Abirritate answered 17/6, 2016 at 12:23 Comment(1)
Beware! $nameConverter->normalize('CamelCase') outputs _camel_case in the current version 3.2 of the Symfony Serializer Component.Cramp
H
25

Ported from Ruby's String#camelize and String#decamelize.

function decamelize($word) {
  return preg_replace(
    '/(^|[a-z])([A-Z])/e', 
    'strtolower(strlen("\\1") ? "\\1_\\2" : "\\2")',
    $word 
  ); 
}

function camelize($word) { 
  return preg_replace('/(^|_)([a-z])/e', 'strtoupper("\\2")', $word); 
}

One trick the above solutions may have missed is the 'e' modifier which causes preg_replace to evaluate the replacement string as PHP code.

Hovel answered 4/3, 2011 at 13:40 Comment(5)
The e flag for preg_replace is being deprecated in PHP 5.5.Dowell
BTW these aren't in Ruby either, but in Rails' inflector library - camelize and underscore. api.rubyonrails.org/classes/ActiveSupport/Inflector.htmlPrivacy
This fails for "ThisIsATest". Seems that does not support two consecutive uppercase.Labdanum
Just a note: you can use lcfirst to get the first letter to lowercase, then you don't need the ^| or strlen.Falconiform
decamelize without deprecation: gist.github.com/scones/e09c30e696246fda14578bcf8ab4910aSwitchblade
D
22

Most solutions here feel heavy handed. Here's what I use:

$underscored = strtolower(
    preg_replace(
        ["/([A-Z]+)/", "/_([A-Z]+)([A-Z][a-z])/"], 
        ["_$1", "_$1_$2"], 
        lcfirst($camelCase)
    )
);

"CamelCASE" is converted to "camel_case"

  • lcfirst($camelCase) will lower the first character (avoids 'CamelCASE' converted output to start with an underscore)
  • [A-Z] finds capital letters
  • + will treat every consecutive uppercase as a word (avoids 'CamelCASE' to be converted to camel_C_A_S_E)
  • Second pattern and replacement are for ThoseSPECCases -> those_spec_cases instead of those_speccases
  • strtolower([…]) turns the output to lowercases
Dermatitis answered 1/5, 2013 at 13:48 Comment(5)
But it also turns CamelCased to _camel_cased.Crimmer
this is great - just add a substr starting at char 1 to get around that issue.Trocar
Excelent! Just need to add lcfirst function to $camelCaseAdulterer
The accepted answer will handle: TestUPSClass into test_ups_class while this will turn it into test_u_p_s_class, something to keep in mind.Gittern
An input string that starts with an allcaps first "word" will be unexpectedly split by this solution because of the ucfirst() call. USADollarSymbol becomes u_sa_dollar_symbol Demo I don't recommend this solution because it has to make two passes through the input string with regex -- a sign of an unrefined pattern.Lachance
H
21

php does not offer a built in function for this afaik, but here is what I use

function uncamelize($camel,$splitter="_") {
    $camel=preg_replace('/(?!^)[[:upper:]][[:lower:]]/', '$0', preg_replace('/(?!^)[[:upper:]]+/', $splitter.'$0', $camel));
    return strtolower($camel);

}

the splitter can be specified in the function call, so you can call it like so

$camelized="thisStringIsCamelized";
echo uncamelize($camelized,"_");
//echoes "this_string_is_camelized"
echo uncamelize($camelized,"-");
//echoes "this-string-is-camelized"
Highway answered 3/1, 2010 at 2:27 Comment(2)
This fails for "ThisIsATest". Seems that does not support two consecutive uppercase.Labdanum
Surely you forgot something as the second replacement does nothing. Apart from this, you can easily make it unicode compatible with mb_strtolower and the /u option on preg_replace.Giblet
Z
10

I had a similar problem but couldn't find any answer that satisfies how to convert CamelCase to snake_case, while avoiding duplicate or redundant underscores _ for names with underscores, or all caps abbreviations.

Th problem is as follows:

CamelCaseClass            => camel_case_class
ClassName_WithUnderscores => class_name_with_underscore
FAQ                       => faq

The solution I wrote is a simple two functions call, lowercase and search and replace for consecutive lowercase-uppercase letters:

strtolower(preg_replace("/([a-z])([A-Z])/", "$1_$2", $name));
Zoometry answered 7/9, 2019 at 10:57 Comment(1)
By far this is the most concise and useful solution IMO.Tody
K
8

"CamelCase" to "camel_case":

function camelToSnake($camel)
{
    $snake = preg_replace('/[A-Z]/', '_$0', $camel);
    $snake = strtolower($snake);
    $snake = ltrim($snake, '_');
    return $snake;
}

or:

function camelToSnake($camel)
{
    $snake = preg_replace_callback('/[A-Z]/', function ($match){
        return '_' . strtolower($match[0]);
    }, $camel);
    return ltrim($snake, '_');
}
Krystakrystal answered 5/12, 2017 at 2:3 Comment(1)
Thank you. I used the first approach, but with hyphens to generate this-kind-of-outputCertifiable
S
7

You need to run a regex through it that matches every uppercase letter except if it is in the beginning and replace it with underscrore plus that letter. An utf-8 solution is this:

header('content-type: text/html; charset=utf-8');
$separated = preg_replace('%(?<!^)\p{Lu}%usD', '_$0', 'AaaaBbbbCcccDdddÁáááŐőőő');
$lower = mb_strtolower($separated, 'utf-8');
echo $lower; //aaaa_bbbb_cccc_dddd_áááá_őőőő

If you are not sure what case your string is, better to check it first, because this code assumes that the input is camelCase instead of underscore_Case or dash-Case, so if the latters have uppercase letters, it will add underscores to them.

The accepted answer from cletus is way too overcomplicated imho and it works only with latin characters. I find it a really bad solution and wonder why it was accepted at all. Converting TEST123String into test123_string is not necessarily a valid requirement. I rather kept it simple and separated ABCccc into a_b_cccc instead of ab_cccc because it does not lose information this way and the backward conversion will give the exact same string we started with. Even if you want to do it the other way it is relative easy to write a regex for it with positive lookbehind (?<!^)\p{Lu}\p{Ll}|(?<=\p{Ll})\p{Lu} or two regexes without lookbehind if you are not a regex expert. There is no need to split it up into substrings not to mention deciding between strtolower and lcfirst where using just strtolower would be completely fine.

Spinet answered 23/5, 2012 at 19:45 Comment(11)
Code-only answers are low value on Stackoverflow because they do very little to educate/empower thousands of future researchers.Lachance
@Lachance If researchers learn how to code from SO, then we have a serious problem...Spinet
Now that you got that personal attack out of your system, please improve your answer. Assuming that you know how your solution works and why you are using those pattern modifiers, I see no good reason to withhold knowledge from this community. In case you are considering leaving more snarky responses, I assure you that they don't bother me. In the time you've taken to comment, you could have completed your answer, we could have deleted our comments, and I could have gone elsewhere to help this site.Lachance
Of course, I have no authority to delete a post with 8 upvotes. If you would like, you may delete your answer, but it wouldn't be very hard to simply improve it by removing unnecessary pattern modifiers and add an explanation. The personal attacks have no effect on me.Lachance
@Lachance I don't think I can delete it either. Feel free to edit it if you want to.Spinet
You have the authority to delete any of your answers that do not wear a green tick, but deleting this answer would be a rash decision. I think it is rich that you insult me twice then invite me to do your work for you. The bottomline is that I don't edit code-only answers because: 1. This would reduce the probability of personal growth for the poster. 2. There is no way for me to know why the posted answer was crafted the way it was crafted. 3. I think it is good natured enough to bring awareness of incomplete answers so that volunteers can defend/improve/complete their answers via edit.Lachance
@Lachance Well, you want it to be edited, not me. Then probably forget about it, because I no longer maintain my answers here. I kept this account only for upvoting other's answers. I use Quora for Q&A nowadays.Spinet
Just as a note, this fails for already 'snake_case' strings. It will convert to `snake__case' (two underscores).Moil
Sorry, I meant 'Upper_Snake_Case' strings will be converted to `upper__snake__case' (two underscores).Moil
@WesleyGonçalves The question was not about how to convert snake case into snake case, you don't need to do anything for that.Spinet
This is the reason why I said "Just as a note" so one that uses it ensures the string is in camel-case.Moil
T
7

If you are looking for a PHP 5.4 version and later answer here is the code:

function decamelize($word) {
      return $word = preg_replace_callback(
        "/(^|[a-z])([A-Z])/",
        function($m) { return strtolower(strlen($m[1]) ? "$m[1]_$m[2]" : "$m[2]"); },
        $word
    );

}
function camelize($word) {
    return $word = preg_replace_callback(
        "/(^|_)([a-z])/",
        function($m) { return strtoupper("$m[2]"); },
        $word
    );

} 
Tentmaker answered 25/2, 2015 at 23:20 Comment(1)
camelize produce "SmsSent" for sms_sent, you need a lcfirstTim
C
5

Short solution:

$subject = "PascalCase";
echo strtolower(preg_replace('/\B([A-Z])/', '_$1', $subject));
Ceolaceorl answered 12/6, 2019 at 11:1 Comment(0)
A
4

Not fancy at all but simple and speedy as hell:

function uncamelize($str) 
{
    $str = lcfirst($str);
    $lc = strtolower($str);
    $result = '';
    $length = strlen($str);
    for ($i = 0; $i < $length; $i++) {
        $result .= ($str[$i] == $lc[$i] ? '' : '_') . $lc[$i];
    }
    return $result;
}

echo uncamelize('HelloAWorld'); //hello_a_world
Adulterer answered 16/12, 2013 at 17:36 Comment(2)
++$i instead of $i++ would make it a little bit speedier as well ;)Allstar
Code-only answers are low value on Stackoverflow because they do very little to educate/empower thousands of future researchers.Lachance
N
4

Use Symfony String

composer require symfony/string
use function Symfony\Component\String\u;

u($string)->snake()->toString()
Noel answered 11/2, 2022 at 17:33 Comment(0)
G
3

A version that doesn't use regex can be found in the Alchitect source:

decamelize($str, $glue='_')
{
    $counter  = 0;
    $uc_chars = '';
    $new_str  = array();
    $str_len  = strlen($str);

    for ($x=0; $x<$str_len; ++$x)
    {
        $ascii_val = ord($str[$x]);

        if ($ascii_val >= 65 && $ascii_val <= 90)
        {
            $uc_chars .= $str[$x];
        }
    }

    $tok = strtok($str, $uc_chars);

    while ($tok !== false)
    {
        $new_char  = chr(ord($uc_chars[$counter]) + 32);
        $new_str[] = $new_char . $tok;
        $tok       = strtok($uc_chars);

        ++$counter;
    }

    return implode($new_str, $glue);
}
Gotten answered 3/1, 2010 at 3:58 Comment(4)
This is what life would be like without regex :-)Highway
Heh, yeah. RegEx definitely has its advantages. :) Raw speed isn't one of them.Gotten
got some funny results with this one for some reasonEmptyhanded
Doesn't work for me based on this string : "CamelCaseTestAAATestAA", should have : "camel_case_test_a_a_a_test_a_a", has : ""camel_case_test_aest" ...Goodbye
B
3

So here is a one-liner:

strtolower(preg_replace('/(?|([a-z\d])([A-Z])|([^\^])([A-Z][a-z]))/', '$1_$2', $string));
Breedlove answered 17/9, 2014 at 17:33 Comment(3)
Nice, but it only converts the first appearance, so I'd recommend to add a g modifier to this regex.Crimmer
@acme, I use it without g and it works fine for me.Breedlove
For some reason in my case I had to add the g. But I can't remember the phrase I tested with.Crimmer
I
3

danielstjules/Stringy provieds a method to convert string from camelcase to snakecase.

s('TestUCase')->underscored(); // 'test_u_case'
Impolite answered 29/11, 2016 at 12:35 Comment(0)
E
3

Laravel 5.6 provides a very simple way of doing this:

 /**
 * Convert a string to snake case.
 *
 * @param  string  $value
 * @param  string  $delimiter
 * @return string
 */
public static function snake($value, $delimiter = '_'): string
{
    if (!ctype_lower($value)) {
        $value = strtolower(preg_replace('/(.)(?=[A-Z])/u', '$1'.$delimiter, $value));
    }

    return $value;
}

What it does: if it sees that there is at least one capital letter in the given string, it uses a positive lookahead to search for any character (.) followed by a capital letter ((?=[A-Z])). It then replaces the found character with it's value followed by the separactor _.

Evidentiary answered 27/6, 2018 at 11:27 Comment(1)
This function now seems to be called snake_case() and lives in the global namespace.Confessional
B
2

The direct port from rails (minus their special handling for :: or acronyms) would be

function underscore($word){
    $word = preg_replace('#([A-Z\d]+)([A-Z][a-z])#','\1_\2', $word);
    $word = preg_replace('#([a-z\d])([A-Z])#', '\1_\2', $word);
    return strtolower(strtr($word, '-', '_'));
}

Knowing PHP, this will be faster than the manual parsing that's happening in other answers given here. The disadvantage is that you don't get to chose what to use as a separator between words, but that wasn't part of the question.

Also check the relevant rails source code

Note that this is intended for use with ASCII identifiers. If you need to do this with characters outside of the ASCII range, use the '/u' modifier for preg_matchand use mb_strtolower.

Boding answered 23/10, 2013 at 19:43 Comment(1)
You could, if you simply add a parameter that contains the desired character.Beneficiary
W
2

Here is my contribution to a six-year-old question with god knows how many answers...

It will convert all words in the provided string that are in camelcase to snakecase. For example "SuperSpecialAwesome and also FizBuzz καιΚάτιΑκόμα" will be converted to "super_special_awesome and also fizz_buzz και_κάτι_ακόμα".

mb_strtolower(
    preg_replace_callback(
        '/(?<!\b|_)\p{Lu}/u',
        function ($a) {
            return "_$a[0]";
        },
        'SuperSpecialAwesome'
    )
);
Webber answered 28/12, 2016 at 15:17 Comment(0)
E
2

Yii2 have the different function to make the word snake_case from CamelCase.

    /**
     * Converts any "CamelCased" into an "underscored_word".
     * @param string $words the word(s) to underscore
     * @return string
     */
    public static function underscore($words)
    {
        return strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $words));
    }
Elwoodelwyn answered 22/8, 2018 at 7:14 Comment(0)
K
2

This is one of shorter ways:

function camel_to_snake($input)
{
    return strtolower(ltrim(preg_replace('/([A-Z])/', '_\\1', $input), '_'));
}
Kahle answered 15/2, 2019 at 13:12 Comment(3)
Code-only answers are low value on Stackoverflow because they do very little to educate/empower thousands of future researchers.Lachance
@Lachance - thousands of future researches will be be interested in an elegnant one-liner, and educate themselfs.Vivisect
I'm sorry that you have taken that selfish stance. You certainly could have added an explanation in the time it took you to design and type that snarky reply. Your answer makes three function calls, but others perform the task in two.Lachance
C
2

If you are not using Composer for PHP you are wasting your time.

composer require doctrine/inflector
use Doctrine\Inflector\InflectorFactory;

// Couple ways to get class name:

// If inside a parent class
$class_name = get_called_class();

// Or just inside the class
$class_name = get_class();

// Or straight get a class name
$class_name = MyCustomClass::class;

// Or, of course, a string
$class_name = 'App\Libs\MyCustomClass';

// Take the name down to the base name:
$class_name = end(explode('\\', $class_name)));

$inflector = InflectorFactory::create()->build();

$inflector->tableize($class_name); // my_custom_class

https://github.com/doctrine/inflector/blob/master/docs/en/index.rst

Claudication answered 28/8, 2020 at 15:36 Comment(0)
J
1

How to de-camelize without using regex:

function decamelize($str, $glue = '_') {
    $capitals = [];
    $replace  = [];

    foreach(str_split($str) as $index => $char) {
        if(!ctype_upper($char)) {
            continue;
        }

        $capitals[] = $char;
        $replace[]  = ($index > 0 ? $glue : '') . strtolower($char);
    }

    if(count($capitals) > 0) {
        return str_replace($capitals, $replace, $str);
    }

    return $str;
}

An edit:

How would I do that in 2019:

PHP 7.3 and before:

function toSnakeCase($str, $glue = '_') {
    return ltrim(
        preg_replace_callback('/[A-Z]/', function ($matches) use ($glue) {
            return $glue . strtolower($matches[0]);
        }, $str),
        $glue
    );

}

And with PHP 7.4+:

function toSnakeCase($str, $glue = '_') {
    return ltrim(preg_replace_callback('/[A-Z]/', fn($matches) => $glue . strtolower($matches[0]), $str), $glue);
}
Jalopy answered 9/4, 2013 at 12:30 Comment(5)
Code-only answers are low value on StackOverflow because they do a poor job of empowering/educating future researchers. Making 1 to 3 function calls on every character in the string then two more function calls after the loop is done is very heavy-handed. I wouldn't entertain a solution with such poor economy.Lachance
It's an example how it could be done without using regular expressions, not how it should be used in production, so I don't see your point besides that you complain about 5y/o answer that has one upvote and is unlikely to be seen by any researchers.Jalopy
I give my attention to all posts, not just highly upvoted ones or recent ones. I am not complaining, I am offering my critique so that researchers with less knowledge can better understand the difference between this answer and other answers. You could have explained in your post that it was merely an academic challenge to avoid regex. That said, there are ways to make this process more efficient with better coding practices.Lachance
This will always start the snake cased string with the glue: _pascal_caseTrabeated
@AngelinCalu good catch! I've edited the answer.Jalopy
E
1
function camel2snake($name) {
    $str_arr = str_split($name);
    foreach ($str_arr as $k => &$v) {
        if (ord($v) >= 64 && ord($v) <= 90) { // A = 64; Z = 90
            $v = strtolower($v);
            $v = ($k != 0) ? '_'.$v : $v;
        }
    }
    return implode('', $str_arr);
}
Elisha answered 5/12, 2013 at 5:1 Comment(2)
You can access chars directly using $name{$k} (or $name[$k]), which would make your code longer, but avoids the large overhead of converting it to and from an array.Giblet
Code-only answers are low value on StackOverflow because they do a poor job of empowering/educating future researchers. Your solution, while avoiding the grace of regex, is very heavy-handed and convoluted. You are splitting on every character and making multiple iterated function calls. Nominating an empty string as glue is unnecessary. I would not entertain this solution in one of my projects because there is no elegance, low readability, and n number of unnecessary function calls.Lachance
F
1

The worst answer on here was so close to being the best(use a framework). NO DON'T, just take a look at the source code. seeing what a well established framework uses would be a far more reliable approach(tried and tested). The Zend framework has some word filters which fit your needs. Source.

here is a couple of methods I adapted from the source.

function CamelCaseToSeparator($value,$separator = ' ')
{
    if (!is_scalar($value) && !is_array($value)) {
        return $value;
    }
    if (defined('PREG_BAD_UTF8_OFFSET_ERROR') && preg_match('/\pL/u', 'a') == 1) {
        $pattern     = ['#(?<=(?:\p{Lu}))(\p{Lu}\p{Ll})#', '#(?<=(?:\p{Ll}|\p{Nd}))(\p{Lu})#'];
        $replacement = [$separator . '\1', $separator . '\1'];
    } else {
        $pattern     = ['#(?<=(?:[A-Z]))([A-Z]+)([A-Z][a-z])#', '#(?<=(?:[a-z0-9]))([A-Z])#'];
        $replacement = ['\1' . $separator . '\2', $separator . '\1'];
    }
    return preg_replace($pattern, $replacement, $value);
}
function CamelCaseToUnderscore($value){
    return CamelCaseToSeparator($value,'_');
}
function CamelCaseToDash($value){
    return CamelCaseToSeparator($value,'-');
}
$string = CamelCaseToUnderscore("CamelCase");
Fail answered 9/11, 2015 at 9:36 Comment(0)
N
1

There is a library providing this functionality:

SnakeCaseFormatter::run('CamelCase'); // Output: "camel_case"
Newsmonger answered 2/4, 2016 at 1:4 Comment(1)
I think you mean "I have created a library providing this functionality". Nothing wrong with self promotion, but don't hide it.Cudbear
V
1

If you use Laravel framework, you can use just snake_case() method.

Vudimir answered 11/11, 2016 at 16:25 Comment(0)
V
1

If you're using the Laravel framework, a simpler built-in method exists:

$converted = Str::snake('fooBar'); // -> foo_bar

See documentation here: https://laravel.com/docs/9.x/helpers#method-snake-case

Verwoerd answered 9/11, 2022 at 6:15 Comment(0)
V
0

The open source TurboCommons library contains a general purpose formatCase() method inside the StringUtils class, which lets you convert a string to lots of common case formats, like CamelCase, UpperCamelCase, LowerCamelCase, snake_case, Title Case, and many more.

https://github.com/edertone/TurboCommons

To use it, import the phar file to your project and:

use org\turbocommons\src\main\php\utils\StringUtils;

echo StringUtils::formatCase('camelCase', StringUtils::FORMAT_SNAKE_CASE);

// will output 'camel_Case'
Vindictive answered 10/3, 2017 at 23:22 Comment(0)
T
0
$str = 'FooBarBaz';

return strtolower(preg_replace('~(?<=\\w)([A-Z])~', '_$1', $str)); // foo_bar_baz
Tong answered 25/10, 2017 at 9:39 Comment(1)
Code-only answers are low value on StackOverflow because they do a poor job of empowering/educating future researchers.Lachance
M
0

I really like the answer posted by syone above as it matched all of my various use cases across all of my projects. However, I also want to use just one regular expression without extra capturing groups.

public function decamelize(string $string): string
{
    return strtolower(preg_replace('/(?<=[a-z0-9])[A-Z]|(?<=[A-Z])[A-Z](?=[a-z])/', '_$0', $string));
}

Regex test

Mcarthur answered 4/1, 2023 at 4:7 Comment(0)
F
0

This is a variation of Jan Jakeš answer:

You can replace ltrim() with lcfirst() for a bit more of peak-performance. So it will become:

$output = strtolower(preg_replace('/[A-Z]([A-Z](?![a-z]))*/', '_$0', lcfirst($text)));

Here is simple performance test

Fairtrade answered 9/1, 2023 at 13:23 Comment(0)
A
-1

It's easy using the Filter classes of the Zend Word Filters:

<?php
namespace MyNamespace\Utility;

use Zend\Filter\Word\CamelCaseToUnderscore;
use Zend\Filter\Word\UnderscoreToCamelCase;

class String
{
    public function test()
    {
        $underscoredStrings = array(
            'simple_test',
            'easy',
            'html',
            'simple_xml',
            'pdf_load',
            'start_middle_last',
            'a_string',
            'some4_numbers234',
            'test123_string',
        );
        $camelCasedStrings = array(
            'simpleTest',
            'easy',
            'HTML',
            'simpleXML',
            'PDFLoad',
            'startMIDDLELast',
            'AString',
            'Some4Numbers234',
            'TEST123String',
        );
        echo PHP_EOL . '-----' . 'underscoreToCamelCase' . '-----' . PHP_EOL;
        foreach ($underscoredStrings as $rawString) {
            $filteredString = $this->underscoreToCamelCase($rawString);
            echo PHP_EOL . $rawString . ' >>> ' . $filteredString . PHP_EOL;
        }
        echo PHP_EOL . '-----' . 'camelCaseToUnderscore' . '-----' . PHP_EOL;
        foreach ($camelCasedStrings as $rawString) {
            $filteredString = $this->camelCaseToUnderscore($rawString);
            echo PHP_EOL . $rawString . ' >>> ' . $filteredString . PHP_EOL;
        }
    }

    public function camelCaseToUnderscore($input)
    {
        $camelCaseToSeparatorFilter = new CamelCaseToUnderscore();
        $result = $camelCaseToSeparatorFilter->filter($input);
        $result = strtolower($result);
        return $result;
    }

    public function underscoreToCamelCase($input)
    {
        $underscoreToCamelCaseFilter = new UnderscoreToCamelCase();
        $result = $underscoreToCamelCaseFilter->filter($input);
        return $result;
    }
}

-----underscoreToCamelCase-----

simple_test >>> SimpleTest

easy >>> Easy

html >>> Html

simple_xml >>> SimpleXml

pdf_load >>> PdfLoad

start_middle_last >>> StartMiddleLast

a_string >>> AString

some4_numbers234 >>> Some4Numbers234

test123_string >>> Test123String

-----camelCaseToUnderscore-----

simpleTest >>> simple_test

easy >>> easy

HTML >>> html

simpleXML >>> simple_xml

PDFLoad >>> pdf_load

startMIDDLELast >>> start_middle_last

AString >>> a_string

Some4Numbers234 >>> some4_numbers234

TEST123String >>> test123_string

Atwood answered 16/12, 2013 at 17:2 Comment(0)
H
-1

IF you could start with:

$string = 'Camel_Case'; // underscore or any other separator...

Then you could convert to either case just with:

$pascal = str_replace("_", "", $string);
$snake = strtolower($string);

Or any other cases:

$capitalized = str_replace("_", " ", $string); // Camel Case
$constant = strtoupper($string);               // CAMEL_CASE
$train = str_replace("_", "-", $snake);        // camel-case
Headcloth answered 28/3, 2015 at 15:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.