はじめに
こんにちは@kou_hoshです。 健康診断を無事乗り切り、喜び勇んで食事をしていたら早速1kgほど太りました。 食欲だけは夏バテとは無縁のようです。
さて、今回はブログ用のネタではなく、実務でちょっと勉強になったことを紹介したいと思います。
キャメルケースをスネークケースに
今実装している機能でキャメルケースになっている文字列をスネークケースに変換する処理が必要になりました。 正規表現をうまく使えば解決しそうですがぱっと思いつかず、 そういえばCakePHPに変換メソッドあったなと思い出したので確認してみると以下の様な実装になっていました。
/** * Returns the given camelCasedWord as an underscored_word. * * @param string $camelCasedWord Camel-cased word to be "underscorized" * @return string Underscore-syntaxed version of the $camelCasedWord * @link http://book.cakephp.org/2.0/en/core-utility-libraries/inflector.html#Inflector::underscore */ public static function underscore($camelCasedWord) { if (!($result = self::_cache(__FUNCTION__, $camelCasedWord))) { $result = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $camelCasedWord)); self::_cache(__FUNCTION__, $camelCasedWord, $result); } return $result; }
実際変換しているのは以下の部分ですね
strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $camelCasedWord));
…(?<=\w)って何だ??
言明
恥ずかしながら不勉強で知らなかったので、ドキュメントを確認してみると言明(assertion)と言うようですね。
以下引用
言明 (assertion) とは、カレントのマッチング位置の直前・直後の文字に対する テストであり、文字を消費 (consume)〔つまり文字自体にマッチ〕しません。
これだけだと、わかるようなわからないような感じですね。 つまるところマッチ対象には含まれないマッチ条件のようなものでしょうか? 例を挙げてみます。
% php -r "echo preg_replace('/(?<=foo)bar/', 'hoge', 'foobar') . \"\n\";" foohoge % php -r "echo preg_replace('/(?<=foo)bar/', 'hoge', 'fooobar') . \"\n\";" fooobar
bar文字列の前にfoo文字列がある場合にbarが置換されています。 ちなみに上記は言明の中でも戻り読み言明 (lookbehind assertion)と呼ばれるようです。
逆に先読み言明 (lookahead assertion) というものもあり
% php -r "echo preg_replace('/foo(?=bar)/', 'hoge', 'foobar') . \"\n\";" hogebar % php -r "echo preg_replace('/foo(?=bar)/', 'hoge', 'foobaar') . \"\n\";" foobaar
上記の場合、foo文字列の後にbar文字列がある場合にfooが置換されています。
これを踏まえてInflector::underscore()の置換部を見ると、[A-Z]の前に単語構成文字がある場合に置換を行う、 つまり、アッパーキャメルケースの場合、先頭の大文字は置換対象としないといった意味のようですね。
終わりに
自分で書くコードは自分の知識の範囲で書けるコードにどうしてもなってしまうと思います。 世の中にある様々なコードを読み、新たな知識を得て、より良いコードを書いていきたいところです。
ちなみに、今回参考にしたInflectorクラスにはその他にも面白いメソッドがたくさんあります。 特に、Inflector::pluralize()のソースとかとても面白いと思いますので 暇な時にでもぜひ読んでみてください。