フロントエンドとバックエンドにおいては分業が当たり前のこのご時世ですが、僕はちょうどお互い半々で仕事を頂けているので備忘録も兼ねて対応表を作成しました。
普段自分が担当している分野の反対側を扱わなくてはならなくなったときや、一人でフロント・バック両方開発するとき、もしくは暇つぶしにどうぞ。
※目次・各項目に書いてあるコードは JavaScript, PHP の順です。
追加・結合
結合 'hoge'+'fuga'
, 'hoge'.'fuga'
'hoge'+'fuga';
//'hogefuga'
'hoge'.'fuga';
//'hogefuga'
最も基本的かつよく使う文字列を繋げる処理ですが、この時点で JavaScript と PHP の差を感じます。
普段 JavaScript を使用している開発者には違和感を感じる「.」での連結ですが、これは PHP が Perl に影響を受けて開発されたことによるものと思われます。
PHP 公式マニュアル: PHP の歴史
PHP でも数値の加算には「+」を使うのですが、個人的には数値の加算なのか文字列の連結なのかを明示的に指定できるので便利に感じています。そもそも変数の型は安全に把握しておくべきですが……。
なお、JavaScript には concat()
も用意されていますが、MDN でも説明されている通り、パフォーマンス的によろしくないので普通に「+」を使用した方が良いでしょう。
追加 'hoge'+='fuga'
, 'hoge'.='fuga'
let str = 'hoge';
str += 'fuga';
//'hogefuga'
$str = 'hoge';
$str .= 'fuga';
//'hogefuga'
どちらも結合時に使用した演算子に「=」を付けることで追加を行うことができます。
ところで、文字列を追加するときには上記の方法以外にも、
let str = 'hoge';
str = str+'fuga';
$str = 'hoge';
$str = $str.'fuga';
という書き方がありますが、結果は同じでも JavaScript 側のみ、下の方が処理速度が速い傾向にあるようです。
指定した文字数まで特定の文字で埋める(末尾へ追加) 'hoge'.padEnd(10, 'e')
, str_pad('hoge', 10, 'e')
'hoge'.padEnd(10, 'e');
//'hogeeeeeee'
str_pad('hoge', 10, 'e');
//'hogeeeeeee'
どちらも計10文字になるまで 'hoge'
の末尾に 'e'
を追加を追加し、結果 'hogeeeeeee'
を得るコードです。
この後も同じようなコードが沢山登場しますが、PHP 使いからすると、PHP では関数の引数として文字列を仕込むのに対し、JavaScript では文字列にメソッドが生えているのに違和感を感じるところですね。
JavaScript の文字列は大抵「String グローバルオブジェクト」というものを継承しているので、PHP のインスタンスのようにメソッドが生えているのです。
どちらも指定した文字数より元の文字数の方が長い場合、元の文字列がそのまま返されます。文字数までカットされるわけではないので注意しましょう。
ちなみに padEnd()
は IE で動かないので使用する場合は Polyfill をお忘れなく。
MDN: String.prototype.padEnd()
指定した文字数まで特定の文字で埋める(先頭へ追加) 'hoge'.padStart(10, 'h')
, str_pad('hoge', 10, 'h', STR_PAD_RIGHT)
'hoge'.padStart(10, 'e');
//'eeeeeehoge'
str_pad('hoge', 10, 'e', STR_PAD_LEFT);
//'eeeeeehoge'
JavaScript は別のメソッドを使用することになるのに対し、PHP では str_pad()
関数の第四引数に STR_PAD_LEFT
を指定することで先頭への追加を行えます。
先の例では省略されていた str_pad()
関数の第四引数ですが、実はデフォルト値として STR_PAD_RIGHT
定数が定義されているので文字列が末尾に追加されていたのです。
なお、STR_PAD_LEFT
などの定数は PHP が自動的に設定する定数で、特に開発者が定義することなく安心して使えます。
PHP マニュアル: 定義済み定数(文字列)
例によって padStart()
は IE で動かないので使用する場合は Polyfill をお忘れなく。
同じ文字を指定回数結合した文字列を生成 'hoge'.repeat(10)
, str_repeat('hoge', 10)
'hoge'.repeat(10);
//'hogehogehogehogehogehogehogehogehogehoge'
str_repeat('hoge', 10);
//'hogehogehogehogehogehogehogehogehogehoge'
同じ文字列を指定回数結合した文字列を生成する関数です。どちらも指定回数に 0 を指定すると空文字を返します。
例によって repeat()
は IE で動かないので使用する場合は Polyfill をお忘れなく。MDN によると Android でも動かないとのことです。
MDN: String.prototype.repeat()
判定・情報取得
文字数取得 'hoge'.length
, mb_strlen('hoge')
'hoge'.length;
//4
mb_strlen('hoge');
//4
JavaScript 側はクラス風に言えばメソッドではなくプロパティなので ()
は不要です。
PHP で文字列操作をしていると頻繁に登場する「mb_○○○」系の関数ですが、「mb_」が付いていない関数とはマルチバイト文字を考慮するかしないかの違いがあります。
たとえば mb_strlen('ほげ')
は「2」を返すのに対し、 strlen('ほげ')
は「6」を返します。これは strlen('ほげ')
が返すのが、正確には文字数ではなくバイト数なのが原因です。
英語圏では絵文字でも使わない限り特に意識せずにコーディングしても影響なさそうですが、文字数を主眼に操作を行う場合は意識して「mb_○○○」系の関数を使用するようにしましょう。
(追記)
コメントにて @tana97 さんより strlen('ほげ')
が 「6」 を返すのは 文字コードが UTF-8 の場合、という補足を頂きました。
6を返すのは文字コードがUTF-8の場合はですね。
現在はUTF-8を使うことが半ばデフォルトになっていますが、2000年代初期くらいではむしろShift-JISやEUCが使われていることのほうが多かったです。
現在でも昔から使用しているリソースの都合ほか諸々の理由でこれらの文字コードをベースに運用しているシステムもあるでしょう。
この場合マルチバイト文字は1文字あたり基本的に2バイトですので、strlen('ほげ') は4になります。
この項目での主旨ではないかもしれませんが、文字列操作を扱う記事でありマルチバイト文字にも言及していながら、文字コードの違いによるマルチバイト文字のサイズの違いについては言及がなかったのであえて書いてみました。
上記はまさしく、暗黙のものとして UTF-8 しか考慮しておりませんでした。
もし別の文字コード環境でバイト数を扱うようなコードを書く場合にはご注意を!
特定の文字が現れる位置を取得する 'hoge'.indexOf('og')
, strpos('hoge', 'og')
'hoge'.indexOf('og');
//1
strpos('hoge', 'og');
//1
どちらも先頭を「0」として数え始めるので「'hoge'」の中にある「'og'」の位置は「1」となります。
両者の決定的な差は検索結果が無かった場合の返り値で、indexOf()
の場合は -1
、strpos()
の場合は false
です。
これについては次項で詳しく説明します。
特定の文字が含まれているか判定する 'hoge'.indexOf('og') !== -1
, strpos('hoge', 'og') !== false
直感的には、「文字列中に特定の文字列が存在するか」を判定する際には正規表現を使う方法が考え付くと思います。
しかし、JavaScript でも PHP でもこれを判定する最速の方法は前項で説明した関数の返り値を使用する方法です(もちろん、静的な文字列ではなく、正規表現における .
等を表現する場合などは正規表現を使用することになります)。
そのためそれぞれのメソッド・関数が「一致無し」の場合に返す値を利用するのですが indexOf()
の場合は -1
、strpos()
の場合は false
になるので、それを ===
で比較します。
注意すべきは PHP 側で、strpos()
は 0
を返す場合があり、これは緩い比較では false
と同義になってしまう falsy な値であるためしっかり === false
で判定しないと意図しない挙動となってしまいます。
正規表現にマッチするか判定する 'hoge'.match(/^h.+e$/)
, preg_match('\A^h.+e\z/', 'hoge')
'hoge'.match(/^h.+e$/);
//['hoge', index: 0, input: 'hoge']
preg_match('/\A^h.+e\z/', 'hoge');
//1
プログラミングをするなら避けて通れない正規表現ですが、JavaScript ・ PHP の両者にももちろん正規表現を扱うメソッド・関数が用意されています。
ただし、PHP 側では文字列を正規表現として扱うのに対し、JavaScript では RegExp
オブジェクトを使用します。
上記サンプル中、PHP 側の正規表現は文字列なのに対し、JavaScript 側はクオーテーションで囲われていない正規表現を使用している点に注目してください。
これは JavaScript においては /
で括った文字は RegExp
リテラルとして扱われることを表しています。
PHP 側では文字列を表すクォーテーションの中で、さらに正規表現を /
で括っていますが、これは「preg_○○○」系の関数では必須となるデリミタとなります。
JavaScript では /
固定ですが、PHP では /
に限らず @
や {}
なども使用できます。 URL 等を扱う場合はデリミタに /
を使用すると正規表現中の /
をエスケープする手間が発生するため、正規表現中に出現しない文字をデリミタに使用したほうが良いでしょう。
PHP マニュアル: PCRE はじめに
マッチすることを判定するだけなら両者とも if()
の中に入れれば用をなしますが、返り値は全く異なります。
.match()
はマッチした場合に様々な情報が入った配列を返します。
グローバル (g) フラグの有無によって内容が変わる Array を返します。マッチが見つからなかった場合は null を返します。
g フラグがある場合、キャプチャグループを除いた、正規表現にマッチしたすべての結果を返します。
g フラグがない場合、最初のマッチとそれに対するキャプチャグループのみを返します。この場合、返される要素には下記の「追加されるプロパティ」が存在します。
MDN: String.prototype.match()
マッチしなかった場合は null
を返すため、if()
での判定が行えるわけですね。
対して preg_match()
ではマッチした場合は 1
、マッチしなかった場合は 0
を返します(エラー時は false
)。
特定の文字列を抜き出すためにマッチ系のメソッド・関数を使用する場合、.match()
では返り値の配列が使用できますが、上記のとおり preg_match()
の返り値はその用途に使用できません。
この場合は第三引数に変数を仕込むと参照渡しでマッチ内容の配列が挿入されます。
matches を指定した場合、検索結果が代入されます。$matches[0] にはパターン全体にマッチしたテキストが代入され、 $matches[1] には 1 番目のキャプチャ用サブパターンにマッチした 文字列が代入され、といったようになります。
ところで PHP 側で登場している \A
と \z
ですが、何故 ^
と $
を使用していないかというとセキュリティ的に問題があるからです。
正規表現をセキュリティに関わる処理に使用しているかどうかは実装次第ですが、PHP で正規表現を扱う場合は基本的に ^
と $
を使用しないクセを付けておいた方が無難かと思われます。
徳丸浩の日記: 正規表現によるバリデーションでは ^ と $ ではなく \A と \z を使おう
(追記)
コメントにて @tana97 さんより JavaScript 側の test() メソッドについて紹介して頂きました。
JavaScript側はmatch()メソッドを紹介していますが、これはマッチするか判定というよりマッチした内容を返却ですね。
単純にマッチするか判定したいだけの場合はより高速なtest()メソッドがあります。
const s = 'hoge';
console.log(s.match(/^h.+e$/)); // ["hoge", index: 0, input: "hoge", groups: undefined]
console.log(s.match(/^hi.+e$/)); // null
console.log(/^h.+e$/.test(s)); // true
console.log(/^hi.+e$/.test(s)); // false
お恥ずかしながら初見でした……。
というわけで我らが MDN でも該当の記事を読んでみました。
あるパターンがある文字列内で見つかるかどうか調べたいときは、 test() を使用してください。 test() は論理値を返します。これは (一致した場所のインデックス番号、または見つからない場合は -1 を返す) String.prototype.search() メソッドがとは異なります。
より多くの情報を得るためには (実行が遅くなりますが)、 exec() メソッドを使用してください (String.prototype.match() メソッドと同様)。
exec() と同様に (またはその組み合わせで)、 test() は同じグローバル正規表現インスタンスで複数回呼び出されると、前回の一致の先に進むことになります。
勉強になりました!
記事をご覧の皆さんはいざ test()
を使用する際に、match()
が String
に生えているメソッドなのに対し、test()
は RegExp(正規表現)
に生えているメソッドである点にご注意ください。
//match は 'hoge' という文字列リテラルに備わっている、正規表現を引数に取るメソッド
'hoge'.match(/^h.+e$/);
//対して test は /^h.+e$/ という正規表現リテラルに備わっている、文字列を引数に取るメソッド
/^h.+e$/.test('hoge');
置換・削除・整形
両端の空白を削除する ' hoge '.trim()
, trim(' hoge ')
' hoge '.trim();
//'hoge'
trim(' hoge ');
//'hoge'
メソッドなのか関数なのかという違い以外は同じノリで使える両者ですが、実は PHP 側は追加の引数が指定可能です。
trim('ふがふがふがhogeふがふがふが', 'ふが');
//'hoge'
デフォルトでは空白文字を除去する trim()
関数ですが、第二引数を指定した場合はその文字を両端から削除します。便利ですね。
先頭か末尾の空白を削除する ' hoge'.trimStart() or 'hoge '.trimEnd()
, ltrim(' hoge') or rtrim('hoge ')
' hoge'.trimStart();
//'hoge'
'hoge '.trimEnd();
//'hoge'
ltrim(' hoge');
//'hoge'
rtrim('hoge ');
//'hoge'
名前が違いますが、どちらも同じものが用意されています。
例によって PHP 側では第二引数に任意の文字列を指定可能です。
大文字化・小文字化 'HOGE'.toLowerCase() or 'hoge'.toUpperCase()
, strtolower('HOGE') or strtoupper('hoge')
'HOGE'.toLowerCase();
//'hoge'
'hoge'.toUpperCase();
//'HOGE'
strtolower('HOGE');
//'hoge'
strtoupper('hoge');
//'HOGE'
どちらも指定したアルファベットを大文字化、または小文字化します。
日本の制作現場ではレアケースですが、JavaScript 側でトルコ語を扱う場合は注意が必要です。
JavaScriptの文字列を全部小文字/大文字化する
なお、PHP 側には文字列全体ではなく先頭文字だけを変換対象とする lcfirst()
と ucfirst()
が存在します。
さらに日本語圏では中々ユースケースがなさそうですが、文章中の単語の先頭を大文字化する ucwords()
なんて関数も存在します。
特定の文字を置換する 'hoge'.replace('g', 'p')
, str_replace('g', 'p', 'hoge')
'hoge'.replace('g', 'p');
//'hope'
str_replace('g', 'p', 'hoge');
//'hope'
文字列置換と言えば正規表現ですが、静的な文字列を検索して置換する場合、JavaScript では .replace()
で共通なのに対し PHP では str_replace()
を使用します。
後述する preg_replace()
を使用するより遥かに処理速度が速いので積極的にこちらを使用するようにしましょう。
なお、str_replace()
の第一引数には配列が使用できるので、静的な文字列であれば複数でも str_replace()
が使用できます。
str_replace(array('h', 'g'), 'p', 'hoge');
//'pope'
正規表現で置換する 'hoooooooooooge'.replace(/o+/g, 'o')
, preg_replace('/o+/', 'o', 'hoooooooooooge')
'hoooooooooooge'.replace(/o+/g, 'o');
//'hoge'
preg_replace('/o+/', 'o', 'hoooooooooooge');
//'hoge'
.replace()
はそのまま正規表現を仕込めますが、PHP 側では str_replace()
ではなく preg_replace()
を使用することになります。
.replace()
の第二引数には置換する文字列を指定しますが、実は文字列ではなく関数を指定して、更に複雑な置換を行うこともできます。
MDN: String.prototype.replace() 引数としての関数の指定
同じようなことを PHP 側で実現するためには preg_replace()
ではなく preg_replace_callback()
が用意されているのでそちらを使用しましょう。
PHP マニュアル: preg_replace_callback
指定した範囲の文字だけ切り抜く 'hogehugapiyo'.slice(0, 4)
, mb_substr('hogehugapiyo', 0, 4)
'hogehugapiyo'.slice(0, 4);
//'hoge'
mb_substr('hogehugapiyo', 0, 4);
//'hoge'
意外と使う範囲指定の抜き出し処理は両者似たノリで扱うことができます。
どちらも開始位置を指定している引数(上記例では0)を負の数にすると末尾から処理をスタートさせる挙動となります。
変換
文字列中で変数展開する `${hoge}`
, "$hoge"
const hoge = 'ほげ'
`${hoge}`;
//'ほげ'
$hoge = 'ほげ';
"$hoge";
//'ほげ'
JavaScript は ES2015 以降限定ですが、バッククォートで括られた範囲の ${}
内は変数が展開されます。
対して PHP ではダブルクオーテーションで括られた文字列内では自動で変数展開が行われます。
従って、パフォーマンスの観点から PHP で文字列を扱う場合は変数展開の行われないシングルクオーテーションを使用することをお勧めします。
数値を三桁ごとに「,」で区切った文字列にする new Intl.NumberFormat().format(3500)
, number_format(3500)
new Intl.NumberFormat().format(3500)
//'3,500'
number_format(3500);
//'3,500'
存在を知らなかった頃は専用関数を作成していたものですが、多用するからなのか、両者ともに専用のメソッド・関数が存在します。
デフォルトは両者三桁のカンマ区切りですが、フランスでの表記等、独特の区切り方にも対応しているので実装の際はマニュアルを参照してみてください。
MDN: Intl.NumberFormat.prototype.format
PHP マニュアル: number_format
JSON エンコード JSON.stringify({hoge: 'ほげ', fuga: 'ふが', piyo: 'ぴよ'})
, json_encode(array('hoge ' => 'ほげ', 'fuga' => 'ふが', 'piyo' => 'ぴよ'))
JSON.stringify({hoge: 'ほげ', fuga: 'ふが', piyo: 'ぴよ'});
//'{"hoge":"ほげ","fuga":"ふが","piyo":"ぴよ"}'
json_encode(array('hoge ' => 'ほげ', 'fuga' => 'ふが', 'piyo' => 'ぴよ'));
//{"hoge":"\u307b\u3052","fuga":"\u3075\u304c","piyo":"\u3074\u3088"}
API を中心に据えた開発が主流となりつつある現代において、ますます重要になってきている JSON ですが、もちろん両言語でサポートされています。
JavaScript と PHP では連想配列の振る舞いが全く別ですが、JSON に変換する分には同じノリで扱えるので困ることはないでしょう。
もちろんストレートな配列を仕込んでも OK です。
なお、JSON.stringify()
は第二・第三引数を指定して出力内容を人間用に成型して見やすくすることができます。デバックの際に活用しましょう。
GUNMA GIS GEEK: JSON.stringifyの第2引数を使って出力結果(JSON)を整形する
JSON デコード JSON.parse('{"hoge":"ほげ","fuga":"ふが","piyo":"ぴよ"}')
, json_decode('{"hoge":"ほげ","fuga":"ふが","piyo":"ぴよ"}', true)
JSON.parse('{"hoge":"ほげ","fuga":"ふが","piyo":"ぴよ"}');
//{hoge: 'ほげ', fuga: 'ふが', piyo: 'ぴよ'}
json_decode('{"hoge":"ほげ","fuga":"ふが","piyo":"ぴよ"}', true);
//array ( 'hoge' => 'ほげ', 'fuga' => 'ふが', 'piyo' => 'ぴよ', )
json_decode('{"hoge":"ほげ","fuga":"ふが","piyo":"ぴよ"}');
//stdClass::__set_state(array( 'hoge' => 'ほげ', 'fuga' => 'ふが', 'piyo' => 'ぴよ', ))
エンコードがあれば当然デコードがありますが、json_decode()
は第二引数に true
を指定すると連想配列、省略した場合は PHP のデフォルトクラスである stdClass のインスタンスとなります。
個人的には後から操作しやすい連想配列にして使用することが多いですが、メソッドなどを定義する場合は第二引数を省いておいたほうが良いでしょう。
配列との組み合わせ
文字列を特定の文字で区切って配列にする 'hoge,fuga,piyo'.split(',')
, explode(',', 'hoge,fuga,piyo')
hoge,fuga,piyo'.split(',');
//['hoge','fuga','piyo']
explode(',', 'hoge,fuga,piyo');
//array('hoge','fuga','piyo')
URL を /
で分割したりと意外と出番の多い機能ですね。
実は explode()
は引数を逆にしても正常に動作するという特徴があります。これは次項で紹介する implode()
でも一緒なのですが、これには__歴史的な理由__があります。
配列を特定の文字列で区切った文字列にする ['hoge', 'fuga', 'piyo'].join(',')
, implode(',', array('hoge', 'fuga', 'piyo'))
['hoge', 'fuga', 'piyo'].join(',');
//'hoge,fuga,piyo'
implode(',', array('hoge', 'fuga', 'piyo'));
//'hoge,fuga,piyo'
こちらも良く使う機能ですが、両者ともに区切り文字に ''
を指定すると単に文字列を結合する関数として使用できます。
便利なので多用しがちですが、実行速度が単純な結合よりかなり遅くなってしまうので、静的な結合であれば極力演算子による結合を使いましょう。
前項でも紹介した通り、implode()
は引数を逆にしても正常に動作しますが、PHPer には有名な言い回しである「歴史的な理由」が PHP のマニュアルに登場するのがこの implode()
です。
詳しくは以下のスライドが非常に秀逸かつ感動的です。
(追記)
コメントにて @tana97 さんより JavaScript 側の join() メソッドについて補足して頂きました。
JavaScriptの場合、join()メソッドのオプション自体を省略した場合は','がデフォルトで使われますので、単に結合したくて''のつもりで省略しないよう気をつける必要があるかと思います。
まさしくですね。僕も一回間違えたことがあります……。
感想
意外と書き味が共通してて面白かったです。
フロントエンドであってもバックエンドであっても基本的な文字列操作は重要かつ、ベストプラクティスが似通っているのでしょう。
もちろん、url の扱いやバイナリファイルの文字列化、暗号関連等片方にしかない機能も存在するのですが、急な分野外の作業でも基本的なことをする分にはあまり困らないのではないでしょうか。
記事を書いている身としては、両者ともに公式ドキュメントが充実して読みやすいのがなによりの助けでした。著者、翻訳者に感謝。