Как преобразовать CamelCase в camel_case?

Если бы у меня было:

$string = "CamelCase";

я нуждаюсь

"camel_case"

PHP предлагает функцию для этой цели?

35 ответов

Решение

Попробуйте это для размера:

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

Выход:

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

Это реализует следующие правила:

  1. За последовательностью, начинающейся со строчной буквы, должны следовать строчные буквы и цифры;
  2. Последовательность, начинающаяся с заглавной буквы, может сопровождаться:
    • одна или несколько заглавных букв и цифр (за которыми следует либо конец строки, либо заглавная буква, за которой следует строчная буква или цифра, то есть начало следующей последовательности); или же
    • одна или несколько строчных букв или цифр.

Более короткое решение: похоже на решение редактора с упрощенным регулярным выражением и исправлением проблемы "нижнего подчеркивания":

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

PHP Демо | Regex Demo


Обратите внимание, что такие случаи, как SimpleXML будет преобразован в simple_x_m_l используя вышеуказанное решение. Это также может считаться неправильным использованием обозначения падежа верблюда (правильным будет SimpleXml), а не ошибка алгоритма, поскольку такие случаи всегда неоднозначны - даже путем группировки заглавных символов в одну строку (simple_xml) такой алгоритм всегда будет давать сбой в других крайних случаях, таких как XMLHTMLConverter или однобуквенные слова рядом с аббревиатурами и т. д. Если вы не возражаете против (довольно редких) крайних случаев и хотите разобраться SimpleXML правильно, вы можете использовать немного более сложное решение:

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

PHP Демо | Regex Demo

Краткое решение и может справиться с некоторыми хитрыми вариантами использования:

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

Может справиться со всеми этими случаями:

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

Вы можете проверить эту функцию здесь: http://syframework.alwaysdata.net/decamelize

Компонент Symfony Serializer имеет CamelCaseToSnakeCaseNameConverter, который имеет два метода normalize() а также denormalize(), Они могут быть использованы следующим образом:

$nameConverter = new CamelCaseToSnakeCaseNameConverter();

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

echo $nameConverter->denormalize('snake_case');
// outputs: snakeCase

Портировано из Ruby's String#camelize а также 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); 
}

Один из трюков, которые могли пропустить вышеупомянутые решения, - это модификатор 'e', ​​который вызывает preg_replace оценить замену строки как код PHP.

Большинство решений здесь чувствуются тяжелыми руками. Вот что я использую:

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

"CamelCASE" преобразуется в "camel_case"

  • lcfirst($camelCase) понизит первый символ (избегает преобразованного вывода CamelCASE, чтобы начать с подчеркивания)
  • [A-Z] находит заглавные буквы
  • + будет обрабатывать каждый последовательный верхний регистр как слово (избегает преобразования 'CamelCASE' в camel_C_A_S_E)
  • Второй шаблон и замена для ThoseSPECCases -> those_spec_cases вместо those_speccases
  • strtolower([…]) превращает вывод в нижний регистр

php не предлагает встроенную функцию для этого afaik, но вот что я использую

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

}

разделитель может быть указан в вызове функции, так что вы можете вызвать его так

$camelized="thisStringIsCamelized";
echo uncamelize($camelized,"_");
//echoes "this_string_is_camelized"
echo uncamelize($camelized,"-");
//echoes "this-string-is-camelized"

У меня была аналогичная проблема, но я не смог найти ни одного ответа, который бы удовлетворял преобразованию CamelCase в snake_case, избегая при этом повторяющихся или избыточных подчеркиваний _ для имен с подчеркиванием или сокращений заглавными буквами.

Проблема заключается в следующем:

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

Решение, которое я написал, представляет собой простой вызов двух функций, нижний регистр и поиск и замену последовательных букв нижнего и верхнего регистра:

strtolower(preg_replace("/([a-z])([A-Z])/", "$1_$2", $name));
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_áááá_őőőő

(Принятое "решение" - эпический провал...)

"CamelCase" для "camel_case":

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

или же:

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

Если вы ищете версию PHP 5.4 и позже, ответьте на этот код:

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

} 

Краткое решение:

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

Используйте строку Symfony

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

u($string)->snake()->toString()

Совсем не навороченный, а простой и быстрый, как ад

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

Версию, в которой не используется регулярное выражение, можно найти в источнике 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);
}

Если вы не используете Composer для PHP, вы зря теряете время.

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

Laravel 5.6 предоставляет очень простой способ сделать это:

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

Что он делает: если он видит, что в данной строке есть хотя бы одна заглавная буква, он использует положительный прогноз для поиска любого символа (.) с большой буквы ((?=[A-Z])). Затем он заменяет найденный символ его значением, за которым следует разделитель. _,

https://github.com/danielstjules/Stringy предоставляет метод для преобразования строки из верблюжьего чемодана в змеиный чемодан.

s('TestUCase')->underscored(); // 'test_u_case'

Итак, вот одна строка:

strtolower(preg_replace('/(?|([a-z\d])([A-Z])|([^\^])([A-Z][a-z]))/', '$1_$2', $string));

Вот мой вклад в шестилетний вопрос с бог знает сколько ответов...

Он преобразует все слова в предоставленной строке, которые находятся в camelcase, в snakecase. Например, "SuperSpecialAwesome, а также FizBuzz καιΚάτιΑκόμα" будут преобразованы в "super_special_awesome, а также fizz_buzz και_κάτι_ακόμα".

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

Прямой порт от рельсов (за исключением их специальной обработки для:: или сокращений) будет

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

Зная PHP, это будет быстрее, чем ручной анализ, который происходит в других ответах, приведенных здесь. Недостатком является то, что вы не можете выбрать, что использовать в качестве разделителя между словами, но это не было частью вопроса.

Также проверьте соответствующий исходный код рельсов

Обратите внимание, что это предназначено для использования с идентификаторами ASCII. Если вам нужно сделать это с символами вне диапазона ASCII, используйте модификатор '/u' для preg_matchи использовать mb_strtolower,

Это один из коротких способов:

function camel_to_snake($string)
{
    return strtolower(ltrim(preg_replace('/([A-Z])/', '_\\1', $input), '_'));
}

У Yii2 есть другая функция для создания слова snake_case из 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));
    }

Если вы используете фреймворк Laravel, существует более простой встроенный метод:

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

См. документацию здесь:https://laravel.com/docs/9.x/helpers#method-snake-case .

Есть библиотека, обеспечивающая эту функциональность:

SnakeCaseFormatter::run('CamelCase'); // Output: "camel_case"

Худший ответ здесь был настолько близок к тому, чтобы быть лучшим (используйте фреймворк). НЕТ, просто взгляните на исходный код. увидеть, что использует хорошо зарекомендовавший себя фреймворк, было бы гораздо более надежным подходом (проверенным и опробованным). Zend Framework имеет несколько текстовых фильтров, которые соответствуют вашим потребностям. Источник

Вот пара методов, которые я адаптировал из источника.

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

Если вы используете фреймворк Laravel, вы можете использовать только метод snake_case().

$str = 'FooBarBaz';

return strtolower(preg_replace('~(?<=\\w)([A-Z])~', '_$1', $str)); // foo_bar_baz

Вот что я использую для декамелизации метода:

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

  foreach(str_split($str) as $index => $char) {
     if(ord($char) >= 65 && ord($char) <= 90) {
        $capitals[] = $char;
        $replace[] = ($index > 0 ? $glue : '').strtolower($char);
     }
  }

  if(sizeof($capitals)) return str_replace($capitals, $replace, $str);

  return $str;
}
Другие вопросы по тегам