Como converter PascalCase para pascal_case?

115

Se eu tivesse:

$string = "PascalCase";

eu preciso

"pascal_case"

O PHP oferece uma função para esse propósito?

rã aberta
fonte
31
Tecnicamente, a primeira string de exemplo é PascalCase.
Robin van Baalen
33
E a segunda string de exemplo é conhecida como snake_case .
Pang

Respostas:

163

Experimente para ver o tamanho:

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

Resultado:

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

Isso implementa as seguintes regras:

  1. Uma sequência que começa com uma letra minúscula deve ser seguida por letras minúsculas e dígitos;
  2. Uma sequência que começa com uma letra maiúscula pode ser seguida por:
    • uma ou mais letras maiúsculas e dígitos (seguidos pelo final da string ou por uma letra maiúscula seguida por uma letra minúscula ou dígito, ou seja, o início da próxima seqüência); ou
    • uma ou mais letras minúsculas ou dígitos.
cletus
fonte
9
Funciona com strings CamelCased (como o openfrog perguntou), mas se você usá-lo com string de entrada por exemplo "r_id" (já "sublinhado"), ele corta o prefixo ("r_"). Boa solução, mas definitivamente não universal.
Martin de
1
Curioso por que você está verificando se a string corresponde a uma string em maiúsculas? Qual é a vantagem de converter apenas o primeiro caractere em minúsculas (em oposição a todos os caracteres)?
Josh
1
Uma solução mais concisa que também pode lidar com todos esses casos de uso: stackoverflow.com/a/35719689/4328383
Syone
156

Uma solução mais curta: semelhante à do editor com uma expressão regular simplificada e corrigindo o problema do "sublinhado à direita":

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

PHP Demo | Regex Demo


Observe que casos como SimpleXMLserão convertidos para simple_x_m_lusar a solução acima. Isso também pode ser considerado um uso incorreto da notação camel case (correto seria SimpleXml) ao invés de um bug do algoritmo, uma vez que tais casos são sempre ambíguos - mesmo agrupando caracteres maiúsculos em uma string ( simple_xml) tal algoritmo sempre falhará em outros casos extremos como XMLHTMLConverterou palavras de uma letra perto de abreviações, etc. Se você não se importa com os casos extremos (bastante raros) e deseja manipular SimpleXMLcorretamente, você pode usar uma solução um pouco mais complexa:

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

PHP Demo | Regex Demo

Jan Jakeš
fonte
Sinta-se à vontade para comentar a resposta de cletus detalhando quais casos de teste você corrigiu.
Mike B,
3
Não estou dizendo que sua solução dê resultados errados. Sua solução é extremamente complicada e ineficaz.
Jan Jakeš
1
Sim, aceitar a resposta é definitivamente um fracasso. A solução de Jan é incrível! Como observação lateral, acho que este (ou uma ligeira variação) é meu novo teste de codificação favorito para desenvolvedores de PHP, porque o número de respostas fornecidas a essa pergunta que na verdade não funcionam é incrível. Seria uma ótima maneira de fazer a filtragem inicial. :-)
JamesG
descobriram que a regex usada nesta solução é muito mais completa: stackoverflow.com/questions/2559759/…
thoroc
2
Boa solução para casos de uso simples e na maioria dos casos usuais é suficiente, mas a solução aceita pode lidar com mais casos de uso, por exemplo, "simpleXML" pode ser convertido para "simple_xml" e não "simple_x_m_l"
Syone
35

Uma solução concisa e pode lidar com alguns casos de uso complicados:

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

Pode lidar com todos esses casos:

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

Você pode testar essa função aqui: http://syframework.alwaysdata.net/decamelize

Syone
fonte
@VivekVardhan qual parte desta regex você não entende?
Syone
Uhm, eu acho que as strings sem caixa-de-camelo em minúsculas são um efeito colateral, caso a sequência não esteja no formato caixa-camelo, a original deve ser retornada. De fato, se você enviar 'simple_Text', você obterá Falha: simple_Test => simple_Test [simple_test]. A string em minúsculas deve ser feita somente e se apenas a string original for uma string real em caixa de camelo. O que você pensa sobre?
guido
24

Transferido do String#camelizee do Ruby 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); 
}

Um truque que as soluções acima podem ter falhado é o modificador 'e' que faz preg_replacecom que a string de substituição seja avaliada como código PHP.

user644783
fonte
10
A eflag para preg_replaceestá sendo descontinuada no PHP 5.5.
cdmckay
A propósito, eles também não estão no Ruby, mas na biblioteca de infletores do Rails - camelize e sublinhado. api.rubyonrails.org/classes/ActiveSupport/Inflector.html
mahemoff
2
Isso falha para "ThisIsATest". Parece que não suporta duas maiúsculas consecutivas.
OnaBai de
Apenas uma observação: você pode usar lcfirst para colocar a primeira letra em minúscula, depois não precisa do ^|ou strlen.
Benubird
decamelize sem depreciação: gist.github.com/scones/e09c30e696246fda14578bcf8ab4910a
scones
23

O Symfony Serializer Component possui um CamelCaseToSnakeCaseNameConverter que possui dois métodos normalize()e denormalize(). Eles podem ser usados ​​da seguinte forma:

$nameConverter = new CamelCaseToSnakeCaseNameConverter();

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

echo $nameConverter->denormalize('snake_case');
// outputs: snakeCase
Mateus
fonte
1
Cuidado! $nameConverter->normalize('CamelCase')saídas _camel_casena versão 3.2 atual do Symfony Serializer Component.
spackmat de
21

A maioria das soluções aqui parecem opressivas. Aqui está o que eu uso:

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

"CamelCASE" é convertido em "camel_case"

  • lcfirst($camelCase) irá diminuir o primeiro caractere (evita que a saída convertida de 'CamelCASE' comece com um sublinhado)
  • [A-Z] encontra letras maiúsculas
  • + tratará todas as maiúsculas consecutivas como uma palavra (evita que 'CamelCASE' seja convertido para camel_C_A_S_E)
  • O segundo padrão e a substituição são para ThoseSPECCases-> em those_spec_casesvez dethose_speccases
  • strtolower([…]) transforma a saída em minúsculas
Buley
fonte
3
Mas também transforma CamelCased em _camel_cased.
acme
1
isso é ótimo - basta adicionar um substr começando no caractere 1 para contornar esse problema.
Oddman,
4
Excelente! Só preciso adicionar a lcfirstfunção a $ camelCase
Edakos
A resposta aceita irá lidar com: TestUPSClass into test_ups_class enquanto isso irá transformá-lo em test_u_p_s_class, algo para manter em mente.
Mazzy de
Uma string de entrada que começa com uma primeira "palavra" allcaps será inesperadamente dividida por esta solução devido à ucfirst()chamada. USADollarSymboltorna-se u_sa_dollar_symbol Demo . Não recomendo esta solução porque ela tem que fazer duas passagens pela string de entrada com regex - um sinal de um padrão não refinado.
mickmackusa
19

php não oferece uma função embutida para este afaik, mas aqui está o que eu uso

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

}

o divisor pode ser especificado na chamada de função, então você pode chamá-lo assim

$camelized="thisStringIsCamelized";
echo uncamelize($camelized,"_");
//echoes "this_string_is_camelized"
echo uncamelize($camelized,"-");
//echoes "this-string-is-camelized"
ekhaled
fonte
2
Isso falha para "ThisIsATest". Parece que não suporta duas maiúsculas consecutivas.
OnaBai de
Certamente você esqueceu algo, pois a segunda substituição não faz nada. Além disso, você pode facilmente torná-lo compatível com Unicode mb_strtolowere a /uopção ativada preg_replace.
bodo
8

Você precisa executar uma regex que corresponda a todas as letras maiúsculas, exceto se estiver no início e substituí-la por sublinhado mais essa letra. Uma solução utf-8 é esta:

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_áááá_őőőő

Se você não tiver certeza de qual é o caso de sua string, é melhor verificar primeiro, porque este código assume que a entrada é em camelCasevez de underscore_Caseou dash-Case, então se os últimos tiverem letras maiúsculas, ele adicionará sublinhados a eles.

A resposta aceita de cletus é imho muito complicada e funciona apenas com caracteres latinos. Acho que é uma solução realmente ruim e me pergunto por que foi aceita. A conversão TEST123Stringem test123_stringnão é necessariamente um requisito válido. Prefiro mantê-lo simples e separado ABCcccem em a_b_ccccvez de ab_ccccporque ele não perde informações dessa forma e a conversão para trás dará exatamente a mesma string com que começamos. Mesmo se você quiser fazer isso de outra maneira, é relativamente fácil escrever um regex para ele com lookbehind positivo (?<!^)\p{Lu}\p{Ll}|(?<=\p{Ll})\p{Lu}ou dois regexes sem lookbehind se você não for um especialista em regex. Não há necessidade de dividi-lo em substrings, para não mencionar decidir entre strtolowere lcfirstonde usar strtolowerseria totalmente adequado.

inf3rno
fonte
Respostas somente de código são de baixo valor no Stackoverflow porque fazem muito pouco para educar / capacitar milhares de futuros pesquisadores.
mickmackusa
@mickmackusa Se os pesquisadores aprenderem a codificar a partir do SO, então temos um problema sério ...
inf3rno
Agora que você eliminou aquele ataque pessoal de seu sistema, melhore sua resposta. Supondo que você saiba como sua solução funciona e por que está usando esses modificadores de padrão, não vejo nenhuma boa razão para reter o conhecimento desta comunidade. Caso você esteja pensando em deixar respostas mais sarcásticas, garanto que elas não me incomodam. No tempo que você levou para comentar, você poderia ter completado sua resposta, poderíamos ter excluído nossos comentários e eu poderia ter ido a outro lugar para ajudar este site.
mickmackusa
Claro, não tenho autoridade para excluir uma postagem com 8 votos positivos. Se desejar, você pode excluir sua resposta, mas não seria muito difícil simplesmente melhorá-la removendo modificadores de padrão desnecessários e adicionando uma explicação. Os ataques pessoais não têm efeito sobre mim.
mickmackusa
@mickmackusa Acho que também não posso excluí-lo. Sinta-se à vontade para editá-lo se desejar.
inf3rno
6

Se você está procurando uma versão do PHP 5.4 e uma resposta posterior, aqui está o código:

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

} 
shacharsol
fonte
camelize produzir "SmsSent" para sms_sent, você precisa de um lcfirst
mik3fly-4steri5k
4

Nada sofisticado, mas simples e rápido como o inferno:

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
Edakos
fonte
++$iem vez de $i++torná-lo um pouco mais rápido também;)
Mathieu Amiot
Respostas somente de código são de baixo valor no Stackoverflow porque fazem muito pouco para educar / capacitar milhares de futuros pesquisadores.
mickmackusa
4

"CamelCase" para "camel_case":

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

ou:

function camelToSnake($camel)
{
    $snake = preg_replace_callback('/[A-Z]/', function ($match){
        return '_' . strtolower($match[0]);
    }, $camel);
    return ltrim($snake, '_');
}
xiaojing
fonte
Obrigado. Usei a primeira abordagem, mas com hífens para gerarthis-kind-of-output
expansão e
3

Uma versão que não usa regex pode ser encontrada na fonte do Alchitect :

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);
}
Darrell Brogdon
fonte
1
A vida seria assim sem regex :-)
ekhaled em
4
Heh, sim. RegEx definitivamente tem suas vantagens. :) Velocidade bruta não é um deles.
Darrell Brogdon
obteve alguns resultados engraçados com este por algum motivo
mr1031011
Não funciona para mim com base nesta string: "CamelCaseTestAAATestAA", deveria ter: "camel_case_test_a_a_a_test_a_a", tem: "" camel_case_test_aest "...
Sybio
3

Então aqui está uma linha:

strtolower(preg_replace('/(?|([a-z\d])([A-Z])|([^\^])([A-Z][a-z]))/', '$1_$2', $string));
seelts
fonte
Legal, mas só converte a primeira aparência, então recomendo adicionar um gmodificador a este regex.
acme
@acme, eu uso sem gele e funciona bem para mim.
setembro
Por alguma razão, no meu caso, tive de adicionar o g. Mas não consigo me lembrar da frase com a qual testei.
acme
3

danielstjules / Stringy fornece um método para converter string de camelcase em snakecase.

s('TestUCase')->underscored(); // 'test_u_case'
Jimmy Ko
fonte
3

O Laravel 5.6 oferece uma maneira muito simples de fazer isso:

 /**
 * 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;
}

O que ele faz: se ele vê que há pelo menos uma letra maiúscula na string dada, ele usa um lookahead positivo para procurar qualquer caractere ( .) seguido por uma letra maiúscula ( (?=[A-Z])). Em seguida, ele substitui o caractere encontrado por seu valor seguido pelo separador _.

Valdrinium
fonte
Esta função agora parece ser chamada de snake_case () e reside no namespace global.
Wotuu
2

A porta direta dos trilhos (sem o tratamento especial para :: ou acrônimos) seria

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, '-', '_'));
}

Conhecendo o PHP, isso será mais rápido do que a análise manual que está acontecendo em outras respostas fornecidas aqui. A desvantagem é que você não escolhe o que usar como separador entre as palavras, mas isso não fazia parte da pergunta.

Verifique também o código-fonte relevante do Rails

Observe que isso se destina ao uso com identificadores ASCII. Se você precisar fazer isso com caracteres fora do intervalo ASCII, use o modificador '/ u' para preg_matche use mb_strtolower.

Pilif
fonte
Você poderia, se simplesmente adicionar um parâmetro que contenha o caractere desejado.
Fleshgrinder
2

Aqui está minha contribuição para uma pergunta de seis anos com sabe Deus quantas respostas ...

Ele irá converter todas as palavras na string fornecida que estão em caixa de camelo em caixa de serpente. Por exemplo, "SuperSpecialAwesome e também FizBuzz καιΚάτιΑκόμα" serão convertidos em "super_special_awesome e também fizz_buzz και_κάτι_ακόμα".

mb_strtolower(
    preg_replace_callback(
        '/(?<!\b|_)\p{Lu}/u',
        function ($a) {
            return "_$a[0]";
        },
        'SuperSpecialAwesome'
    )
);
Loupax
fonte
2

Yii2 tem uma função diferente para fazer a palavra snake_case do 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));
    }
Anil Chaudhari
fonte
2

Solução curta:

$subject = "PascalCase";
echo strtolower(preg_replace('/\B([A-Z])/', '_$1', $subject));
Gevorg Melkumyan
fonte
2

Tive um problema semelhante, mas não consegui encontrar nenhuma resposta que satisfizesse como converter CamelCase para snake_case, evitando sublinhados duplicados ou redundantes _ para nomes com sublinhados ou abreviações todas em maiúsculas.

O problema é o seguinte:

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

A solução que escrevi é uma simples chamada de duas funções, minúsculas e pesquisar e substituir por letras maiúsculas e minúsculas consecutivas:

strtolower(preg_replace("/([a-z])([A-Z])/", "$1_$2", $name));
MMSs
fonte
De longe esta é a solução mais concisa e útil da IMO.
Sr.Shan0,
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);
}
Kurt Zhong
fonte
Você pode acessar os chars diretamente usando $name{$k}(ou $name[$k]), o que tornaria seu código mais longo, mas evita a grande sobrecarga de convertê-lo de e para um array.
bodo
Respostas somente de código são de baixo valor no StackOverflow porque fazem um trabalho pobre de capacitação / educação de futuros pesquisadores. Sua solução, embora evite a graça do regex, é muito pesada e complicada. Você está dividindo em cada personagem e fazendo várias chamadas de função iteradas. Nomear uma string vazia como cola é desnecessário. Eu não consideraria essa solução em um de meus projetos porque não há elegância, baixa legibilidade e n número de chamadas de função desnecessárias.
mickmackusa
1

A pior resposta aqui estava tão perto de ser a melhor (use uma estrutura). NÃO, NÃO, basta dar uma olhada no código-fonte. ver o que um framework bem estabelecido usa seria uma abordagem muito mais confiável (experimentada e testada). O framework Zend possui alguns filtros de palavras que atendem às suas necessidades. Fonte .

aqui estão alguns métodos que adaptei da fonte.

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");
TarranJones
fonte
1

Existe uma biblioteca que fornece esta funcionalidade:

SnakeCaseFormatter::run('CamelCase'); // Output: "camel_case"
Kolyunya
fonte
1
Acho que você quer dizer "Eu criei uma biblioteca que fornece essa funcionalidade". Não há nada de errado com a autopromoção, mas não o esconda.
icc97
1

Se você usa o framework Laravel, você pode usar apenas o método snake_case () .

Marek Skiba
fonte
1

Esta é uma das maneiras mais curtas:

function camel_to_snake($input)
{
    return strtolower(ltrim(preg_replace('/([A-Z])/', '_\\1', $input), '_'));
}
Ayman Gado
fonte
Respostas somente de código são de baixo valor no Stackoverflow porque fazem muito pouco para educar / capacitar milhares de futuros pesquisadores.
mickmackusa
1
@mickmackusa - milhares de pesquisas futuras estarão interessadas em uma frase elegante e se educarão.
Teson
Lamento que você tenha assumido essa postura egoísta. Você certamente poderia ter acrescentado uma explicação no tempo que levou para projetar e digitar aquela resposta sarcástica. Sua resposta faz três chamadas de função, mas outras realizam a tarefa em duas.
mickmackusa
1

Como descamelizar sem usar 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;
}

Uma edição:

Como eu faria isso em 2019:

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

E quando o PHP 7.4 será lançado:

function toSnakeCase($str, $glue = '_') {
    return preg_replace_callback('/[A-Z]/', fn($matches) => $glue . strtolower($matches[0]), $str);
}
baldrs
fonte
1
Respostas somente de código são de baixo valor no StackOverflow porque fazem um trabalho pobre de capacitação / educação de futuros pesquisadores. Fazer de 1 a 3 chamadas de função em cada caractere na string e depois mais duas chamadas de função após o término do loop é muito pesado. Eu não consideraria uma solução com uma economia tão pobre.
mickmackusa
É um exemplo de como isso poderia ser feito sem o uso de expressões regulares, não como deveria ser usado na produção, então não vejo seu ponto de vista além de que você reclama sobre a resposta 5 anos / o que tem um voto positivo e é improvável de ser vista por quaisquer pesquisadores.
baldrs de
Eu dou minha atenção a todas as postagens, não apenas às que tiveram muitos votos positivos ou às recentes. Não estou reclamando, estou fazendo minha crítica para que pesquisadores com menos conhecimento possam entender melhor a diferença entre esta resposta e outras respostas. Você poderia ter explicado em sua postagem que era apenas um desafio acadêmico evitar o regex. Dito isso, há maneiras de tornar esse processo mais eficiente com melhores práticas de codificação.
mickmackusa
0

É fácil usar as classes Filter dos Filtros do Zend Word :

<?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

fácil >>> fácil

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

fácil >>> fácil

HTML >>> html

simpleXML >>> simple_xml

PDFLoad >>> pdf_load

startMIDDLELast >>> start_middle_last

AString >>> a_string

Some4Numbers234 >>> some4_numbers234

TEST123String >>> test123_string

automatix
fonte
0

A biblioteca de código aberto TurboCommons contém um método formatCase () de propósito geral dentro da classe StringUtils, que permite converter uma string em vários formatos de caso comuns, como CamelCase, UpperCamelCase, LowerCamelCase, snake_case, Title Case e muitos mais.

https://github.com/edertone/TurboCommons

Para usá-lo, importe o arquivo phar para o seu projeto e:

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

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

// will output 'camel_Case'
Jaume Mussons Abad
fonte
0
$str = 'FooBarBaz';

return strtolower(preg_replace('~(?<=\\w)([A-Z])~', '_$1', $str)); // foo_bar_baz
Omar Makled
fonte
1
Respostas somente de código são de baixo valor no StackOverflow porque fazem um trabalho pobre de capacitação / educação de futuros pesquisadores.
mickmackusa
-1

SE você pudesse começar com:

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

Então você pode converter para qualquer caso apenas com:

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

Ou qualquer outro caso:

$capitalized = str_replace("_", " ", $string); // Camel Case
$constant = strtoupper($string);               // CAMEL_CASE
$train = str_replace("_", "-", $snake);        // camel-case
Nuno Rafael Figueiredo
fonte