¿Cómo convertir PascalCase a pascal_case?

115

Si tuviera:

$string = "PascalCase";

Necesito

"pascal_case"

¿PHP ofrece una función para este propósito?

rana abierta
fuente
31
Técnicamente, la primera cadena de ejemplo es PascalCase.
Robin van Baalen
33
Y la segunda cadena de ejemplo se conoce como snake_case .
Pang

Respuestas:

163

Pruébate esto para ver el tamaño:

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

Salida:

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

Esto implementa las siguientes reglas:

  1. Una secuencia que comience con una letra minúscula debe ir seguida de letras minúsculas y dígitos;
  2. Una secuencia que comienza con una letra mayúscula puede ir seguida de:
    • una o más letras mayúsculas y dígitos (seguidos del final de la cadena o una letra mayúscula seguida de una letra minúscula o un dígito, es decir, el comienzo de la siguiente secuencia); o
    • una o más letras minúsculas o dígitos.
cletus
fuente
9
Funciona para cadenas CamelCased (como pedía openfrog), pero si lo usa con una cadena de entrada, por ejemplo, "r_id" (ya "subrayado"), recorta el prefijo ("r_"). Buena solución, pero definitivamente no universal.
Martin
1
¿Tiene curiosidad por saber por qué está comprobando si la cadena coincide con la cadena en mayúsculas? ¿Cuál es el beneficio de convertir solo el primer carácter a minúsculas (a diferencia de todos los caracteres)?
Josh
1
Una solución más concisa que también puede manejar todos estos casos de uso: stackoverflow.com/a/35719689/4328383
Syone
156

Una solución más corta: similar a la del editor con una expresión regular simplificada y solucionando el problema de "guión bajo al final":

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

Demostración de PHP | Demostración de expresiones regulares


Tenga en cuenta que los casos como SimpleXMLse convertirán para simple_x_m_lusar la solución anterior. Eso también puede considerarse un uso incorrecto de la notación de mayúsculas y minúsculas (lo correcto sería SimpleXml) en lugar de un error del algoritmo, ya que tales casos son siempre ambiguos, incluso al agrupar caracteres en mayúscula en una cadena ( simple_xml), dicho algoritmo siempre fallará en otros casos de borde. me gusta XMLHTMLConvertero palabras de una letra cerca de abreviaturas, etc. Si no le importan los casos extremos (bastante raros) y desea manejarlos SimpleXMLcorrectamente, puede usar una solución un poco más compleja:

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

Demostración de PHP | Demostración de expresiones regulares

Jan Jakeš
fuente
Siéntase libre de comentar sobre la respuesta de cletus que detalla qué casos de prueba solucionó.
Mike B
3
No digo que su solución dé resultados incorrectos. Su solución es extremadamente complicada e ineficaz.
Jan Jakeš
1
Sí, la respuesta de aceptar es definitivamente un fracaso. ¡La solución de Jan es increíble! Como nota al margen, creo que esta (o una ligera variación) es mi nueva prueba de codificación favorita para los desarrolladores de PHP, porque la cantidad de respuestas proporcionadas a esta pregunta que en realidad no funcionan es increíble. Sería una excelente manera de realizar el filtrado inicial. :-)
JamesG
Encontré la expresión regular utilizada en esta solución mucho más completa: stackoverflow.com/questions/2559759/…
thoroc
2
Buena solución para casos de uso simples y en la mayoría de los casos es suficiente, pero la solución aceptada puede manejar más casos de uso, por ejemplo, "simpleXML" puede convertirse a "simple_xml" y no a "simple_x_m_l"
Syone
35

Una solución concisa que puede manejar algunos casos de uso complicados:

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

Puede manejar todos estos 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

Puede probar esta función aquí: http://syframework.alwaysdata.net/decamelize

Syone
fuente
@VivekVardhan, ¿qué parte de esta expresión regular no entiendes?
Syone
Uhm, creo que las cadenas en minúsculas que no sean camelcase es un efecto secundario, en caso de que la cadena no esté en formato camel case, se debe devolver la original. De hecho, si envía 'simple_Text' obtendrá Fail: simple_Test => simple_Test [simple_test]. La cadena en minúsculas debe hacerse solo y si solo la cadena original es una cadena de caja de camello real. ¿Qué piensa usted acerca de?
guido
24

Portado de Ruby's String#camelizey 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); 
}

Un truco que las soluciones anteriores pueden haber pasado por alto es el modificador 'e' que hace preg_replaceque se evalúe la cadena de reemplazo como código PHP.

usuario644783
fuente
10
La ebandera para preg_replaceestá en desuso en PHP 5.5.
cdmckay
Por cierto, estos tampoco están en Ruby, sino en la biblioteca de inflectores de Rails: camelize y subrayado. api.rubyonrails.org/classes/ActiveSupport/Inflector.html
mahemoff
2
Esto falla para "ThisIsATest". Parece que no admite dos mayúsculas consecutivas.
OnaBai
Solo una nota: puede usar lcfirst para poner la primera letra en minúsculas, entonces no necesita la ^|o strlen.
Benubird
decamelizar sin depreciación: gist.github.com/scones/e09c30e696246fda14578bcf8ab4910a
scones
23

El componente de serializador de Symfony tiene un CamelCaseToSnakeCaseNameConverter que tiene dos métodos normalize()y denormalize(). Estos se pueden utilizar de la siguiente manera:

$nameConverter = new CamelCaseToSnakeCaseNameConverter();

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

echo $nameConverter->denormalize('snake_case');
// outputs: snakeCase
mateo
fuente
1
¡Tener cuidado! $nameConverter->normalize('CamelCase')salidas _camel_caseen la versión 3.2 actual del componente de serializador de Symfony.
spackmat
21

La mayoría de las soluciones aquí se sienten torpes. Esto es lo que uso:

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

"CamelCASE" se convierte en "camel_case"

  • lcfirst($camelCase) bajará el primer carácter (evita que la salida convertida 'CamelCASE' comience con un guión bajo)
  • [A-Z] encuentra letras mayúsculas
  • + tratará cada mayúscula consecutiva como una palabra (evita que 'CamelCASE' se convierta en camel_C_A_S_E)
  • El segundo patrón y reemplazo son para ThoseSPECCases-> en those_spec_caseslugar dethose_speccases
  • strtolower([…]) convierte la salida en minúsculas
Buley
fuente
3
Pero también convierte CamelCased en _camel_cased.
acme
1
esto es genial, simplemente agregue una subcadena que comience en el carácter 1 para solucionar ese problema.
Oddman
4
¡Excelente! Solo necesito agregar una lcfirstfunción a $ camelCase
Edakos
La respuesta aceptada manejará: TestUPSClass en test_ups_class mientras que esto lo convertirá en test_u_p_s_class, algo a tener en cuenta.
Mazzy
Esta solución dividirá inesperadamente una cadena de entrada que comience con una primera "palabra" en mayúsculas debido a la ucfirst()llamada. USADollarSymbolse convierte en u_sa_dollar_symbol demostración No recomiendo esta solución porque tiene que hacer dos pasadas a través de la cadena de entrada con expresiones regulares, un signo de un patrón sin refinar.
mickmackusa
19

php no ofrece una función incorporada para este afaik, pero esto es lo que uso

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

}

el divisor se puede especificar en la llamada a la función, por lo que puede llamarlo así

$camelized="thisStringIsCamelized";
echo uncamelize($camelized,"_");
//echoes "this_string_is_camelized"
echo uncamelize($camelized,"-");
//echoes "this-string-is-camelized"
ekhaled
fuente
2
Esto falla para "ThisIsATest". Parece que no admite dos mayúsculas consecutivas.
OnaBai
Seguro que olvidaste algo ya que el segundo reemplazo no hace nada. Aparte de esto, puede hacer que sea compatible con Unicode mb_strtolowery la /uopción activada preg_replace.
bodo
8

Debe ejecutar una expresión regular que coincida con todas las letras mayúsculas, excepto si está al principio, y reemplazarla con un subrayado más esa letra. Una solución utf-8 es 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_áááá_őőőő

Si no está seguro de qué caso es su cadena, es mejor verificarla primero, porque este código asume que la entrada es en camelCaselugar de underscore_Caseodash-Case , por lo que si las últimas tienen letras mayúsculas, les agregará guiones bajos.

La respuesta aceptada de cletus es demasiado complicada en mi humilde opinión y funciona solo con caracteres latinos. Me parece una solución realmente mala y me pregunto por qué fue aceptada. Convertir TEST123Stringen test123_stringno es necesariamente un requisito válido. Preferí mantenerlo simple y separado ABCcccen a_b_cccclugar de ab_ccccporque no pierde información de esta manera y la conversión hacia atrás dará exactamente la misma cadena con la que comenzamos. Incluso si desea hacerlo de la otra manera, es relativamente fácil escribir una expresión regular con una mirada atrás positiva (?<!^)\p{Lu}\p{Ll}|(?<=\p{Ll})\p{Lu}o dos expresiones regulares sin mirar atrás si no es un experto en expresiones regulares. No hay necesidad de dividirlo en subcadenas sin mencionar decidir entre strtolowery lcfirstdónde usarlo strtolowerestaría completamente bien.

inf3rno
fuente
Las respuestas de solo código tienen poco valor en Stackoverflow porque hacen muy poco para educar / capacitar a miles de futuros investigadores.
mickmackusa
@mickmackusa Si los investigadores aprenden a codificar desde SO, entonces tenemos un problema serio ...
inf3rno
Ahora que eliminó ese ataque personal de su sistema, mejore su respuesta. Suponiendo que sabe cómo funciona su solución y por qué está utilizando esos modificadores de patrones, no veo ninguna buena razón para ocultar su conocimiento a esta comunidad. En caso de que estés pensando en dejar respuestas más sarcásticas, te aseguro que no me molestan. En el tiempo que se tomó para comentar, podría haber completado su respuesta, podríamos haber eliminado nuestros comentarios y podría haber ido a otra parte para ayudar a este sitio.
mickmackusa
Por supuesto, no tengo autoridad para eliminar una publicación con 8 votos a favor. Si lo desea, puede eliminar su respuesta, pero no sería muy difícil simplemente mejorarla eliminando modificadores de patrones innecesarios y agregando una explicación. Los ataques personales no me afectan.
mickmackusa
@mickmackusa No creo que pueda eliminarlo tampoco. Siéntase libre de editarlo si lo desea.
inf3rno
6

Si está buscando una versión de PHP 5.4 y una respuesta posterior, aquí está el 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
fuente
camelize produce "SmsSent" para sms_sent, necesitas un lcfirst
mik3fly-4steri5k
4

No es nada sofisticado, pero es simple y rápido como el infierno:

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
fuente
++$ien lugar de $i++lo haría un poco más rápido también;)
Mathieu Amiot
Las respuestas de solo código tienen poco valor en Stackoverflow porque hacen muy poco para educar / capacitar a miles de futuros investigadores.
mickmackusa
4

"CamelCase" a "camel_case":

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

o:

function camelToSnake($camel)
{
    $snake = preg_replace_callback('/[A-Z]/', function ($match){
        return '_' . strtolower($match[0]);
    }, $camel);
    return ltrim($snake, '_');
}
Xiaojing
fuente
Gracias. this-kind-of-output
Usé
3

Se puede encontrar una versión que no usa expresiones regulares en la fuente de 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
fuente
1
Así sería la vida sin
expresiones
4
Je, sí. La expresión regular definitivamente tiene sus ventajas. :) La velocidad bruta no es uno de ellos.
Darrell Brogdon
obtuve algunos resultados divertidos con este por alguna razón
mr1031011
No me funciona según esta cadena: "CamelCaseTestAAATestAA", debería tener: "camel_case_test_a_a_a_test_a_a", tiene: "" camel_case_test_aest "...
Sybio
3

Así que aquí hay una sola línea:

strtolower(preg_replace('/(?|([a-z\d])([A-Z])|([^\^])([A-Z][a-z]))/', '$1_$2', $string));
seelts
fuente
Bien, pero solo convierte la primera aparición, por lo que recomendaría agregar un gmodificador a esta expresión regular.
acme
@acme, lo uso sin gél y funciona bien para mí.
cierra el
Por alguna razón en mi caso tuve que agregar el g. Pero no recuerdo la frase con la que probé.
acme
3

danielstjules / Stringy proporciona un método para convertir cadenas de camelcase a snakecase.

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

Laravel 5.6 proporciona una forma muy sencilla de hacer esto:

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

Qué hace: si ve que hay al menos una letra mayúscula en la cadena dada, usa una búsqueda anticipada positiva para buscar cualquier carácter ( .) seguido de una letra mayúscula ( (?=[A-Z])). Luego reemplaza el carácter encontrado con su valor seguido por el separador _.

Valdrinio
fuente
Esta función ahora parece llamarse snake_case () y vive en el espacio de nombres global.
Wotuu
2

El puerto directo desde rieles (menos su manejo especial para :: o siglas) sería

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

Conociendo PHP, esto será más rápido que el análisis manual que está sucediendo en otras respuestas dadas aquí. La desventaja es que no puedes elegir qué usar como separador entre palabras, pero eso no era parte de la pregunta.

También verifique el código fuente de rieles relevante

Tenga en cuenta que está diseñado para su uso con identificadores ASCII. Si necesita hacer esto con caracteres fuera del rango ASCII, use el modificador '/ u' para preg_matchy use mb_strtolower.

pilif
fuente
Podría, si simplemente agrega un parámetro que contiene el carácter deseado.
Fleshgrinder
2

Aquí está mi contribución a una pregunta de seis años con Dios sabe cuántas respuestas ...

Convertirá todas las palabras en la cadena proporcionada que están en camelcase a snakecase. Por ejemplo, "SuperSpecialAwesome y también FizBuzz καιΚάτιΑκόμα" se convertirán en "super_special_awesome y también fizz_buzz και_κάτι_ακόμα".

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

Yii2 tiene la función diferente para hacer la palabra snake_case de 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
fuente
2

Solución corta:

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

Tuve un problema similar, pero no pude encontrar ninguna respuesta que satisfaga cómo convertir CamelCase a snake_case, evitando al mismo tiempo guiones bajos duplicados o redundantes _para nombres con guiones bajos o abreviaturas en mayúsculas.

El problema es el siguiente:

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

La solución que escribí es una simple llamada de dos funciones, minúsculas y buscar y reemplazar por letras minúsculas y mayúsculas consecutivas:

strtolower(preg_replace("/([a-z])([A-Z])/", "$1_$2", $name));
MMS
fuente
De lejos, esta es la solución más concisa y útil de la OMI.
Mr.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
fuente
Puede acceder a los caracteres directamente usando $name{$k}(o $name[$k]), lo que alargaría su código, pero evita la gran sobrecarga de convertirlo hacia y desde una matriz.
bodo
Las respuestas de solo código tienen poco valor en StackOverflow porque hacen un mal trabajo a la hora de empoderar / educar a los futuros investigadores. Su solución, mientras evita la gracia de las expresiones regulares, es muy torpe y complicada. Está dividiendo en cada carácter y haciendo múltiples llamadas a funciones iteradas. No es necesario nombrar una cuerda vacía como pegamento. No consideraría esta solución en uno de mis proyectos porque no hay elegancia, baja legibilidad y un número n de llamadas a funciones innecesarias.
mickmackusa
1

La peor respuesta aquí estuvo muy cerca de ser la mejor (use un marco). NO NO, simplemente eche un vistazo al código fuente. ver lo que utiliza un marco bien establecido sería un enfoque mucho más confiable (probado y comprobado). El marco Zend tiene algunos filtros de palabras que se adaptan a sus necesidades. Fuente .

aquí hay un par de métodos que adapté de la fuente.

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
fuente
1

Hay una biblioteca que proporciona esta funcionalidad:

SnakeCaseFormatter::run('CamelCase'); // Output: "camel_case"
Kolyunya
fuente
1
Creo que te refieres a "He creado una biblioteca que ofrece esta funcionalidad". No hay nada de malo en la autopromoción, pero no lo ocultes.
icc97
1

Esta es una de las formas más cortas:

function camel_to_snake($input)
{
    return strtolower(ltrim(preg_replace('/([A-Z])/', '_\\1', $input), '_'));
}
Ayman Gado
fuente
Las respuestas de solo código tienen poco valor en Stackoverflow porque hacen muy poco para educar / capacitar a miles de futuros investigadores.
mickmackusa
1
@mickmackusa: miles de investigaciones futuras estarán interesadas en una frase única y se educarán a sí mismos.
Teson
Lamento que hayas adoptado esa postura egoísta. Ciertamente podría haber agregado una explicación en el tiempo que le tomó diseñar y escribir esa respuesta sarcástica. Su respuesta realiza tres llamadas de función, pero otras realizan la tarea en dos.
mickmackusa
1

Cómo des-camelizar sin usar expresiones regulares:

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

Una edición:

¿Cómo haría eso en 2019?

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

Y cuándo se lanzará PHP 7.4:

function toSnakeCase($str, $glue = '_') {
    return preg_replace_callback('/[A-Z]/', fn($matches) => $glue . strtolower($matches[0]), $str);
}
calvos
fuente
1
Las respuestas de solo código tienen poco valor en StackOverflow porque hacen un mal trabajo a la hora de empoderar / educar a los futuros investigadores. Hacer de 1 a 3 llamadas de función en cada carácter de la cadena y luego dos llamadas de función más después de que el bucle está terminado es muy pesado. No consideraría una solución con una economía tan pobre.
mickmackusa
Es un ejemplo de cómo se podría hacer sin usar expresiones regulares, no cómo debería usarse en producción, así que no veo su punto, además de que se queja de una respuesta de 5 años que tiene un voto a favor y es poco probable que la vea cualquier investigador.
Calvos
Presto atención a todas las publicaciones, no solo a las que han votado a favor o las recientes. No me quejo, estoy ofreciendo mi crítica para que los investigadores con menos conocimiento puedan comprender mejor la diferencia entre esta respuesta y otras respuestas. Podrías haber explicado en tu publicación que era simplemente un desafío académico evitar las expresiones regulares. Dicho esto, hay formas de hacer que este proceso sea más eficiente con mejores prácticas de codificación.
mickmackusa
0

Es fácil usar las clases de filtro de los filtros 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;
    }
}

----- subrayadoToCamelCase -----

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

Some4Numbers234 >>> some4_numbers234

TEST123String >>> test123_string

automatix
fuente
0

La biblioteca TurboCommons de código abierto contiene un método formatCase () de propósito general dentro de la clase StringUtils, que le permite convertir una cadena a muchos formatos de casos comunes, como CamelCase, UpperCamelCase, LowerCamelCase, snake_case, Title Case y muchos más.

https://github.com/edertone/TurboCommons

Para usarlo, importe el archivo phar a su proyecto y:

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

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

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

return strtolower(preg_replace('~(?<=\\w)([A-Z])~', '_$1', $str)); // foo_bar_baz
Omar Makled
fuente
1
Las respuestas de solo código tienen poco valor en StackOverflow porque hacen un mal trabajo a la hora de empoderar / educar a los futuros investigadores.
mickmackusa
-1

SI pudieras comenzar con:

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

Entonces podrías convertir a cualquier caso solo con:

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

O cualquier otro caso:

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