【PHP入門】PHPの文字列について

## はじめに

こちらは、[PHP5上級試験/準上級試験まとめ](https://qiita.com/chimayu/items/36d6152134464856ac0d) の一環として、PHPの**文字列**について順を追って解説した記事です。
試験対象はPHP5ですが、PHP7くらいまでなら対応できるよう、汎用的な内容を心がけています。内容に間違いがある場合はご指摘ください。
なお2021年秋には [PHP8技術者認定上級試験](https://www.phpexam.jp/2020/12/11/php8/) が始まる予定ですので、最新情報は公式ページよりご確認ください。

## 文字列とは

文字列とは、文字通り（？）文字で構成された値のことですが、PHPで文字列を表現するには ```'シングルクォート（引用符）'``` や ```"ダブルクォート（二重引用符）"``` で文字を囲う必要があります。
例えば ```1``` とだけ書かれた値は**数値**の1を表し、```'1'``` や ```"1"``` と書かれた値は**文字列**としての1を表します。
（ヒアドキュメントやNowdocという機能で文字列を表現することもできますが、これについては後述します。）

```php:数値と文字の表現
echo 1; // 1
echo '1'; // 1
// どちらも出力されるのは 1 だが、内部的には数値の 1 か、文字の 1 かが区別されている
```

文字列内でシングルクォートやダブルクォートを使用したい場合はどうするかと言うと、**エスケープシーケンス**を使用します。エスケープシーケンスとは、プログラムにとって特別な意味を持つ文字の直前に ```\バックスラッシュ``` を書くことによって、その文字のプログラム的な意味を無効化させることを言います。
ちなみにバックスラッシュという文字そのものも、他の文字をエスケープさせる特別な意味があるため、ただの文字として使用するにはバックスラッシュでバックスラッシュをエスケープする必要があります。（使用するOSによってはバックスラッシュが ```¥``` の記号で表される場合があります。）

```php:バックスラッシュをエスケープシーケンスで表現
echo '\\'; // \
```

シングルクォートとダブルクォートでは、エスケープシーケンスの使用できる文字に違いがあります。

#### シングルクォート内で可能なエスケープと使用例
 - バックスラッシュ ```\``` → ```\\```
 - シングルクォート ```'``` → ```\'```

#### ダブルクォート内で可能なエスケープと使用例
 - バックスラッシュ ```\``` → ```\\```
 - ダブルクォート ```"``` → ```\"```
 - ドル記号 ```$``` → ```\$```
 - 改行記号 ```n``` → ```\n```
 - タブ記号 ```t``` → ```\t```

それ以外のエスエープシーケンスについてはPHPマニュアルを参照してください。
[PHPマニュアル - 文字列](https://www.php.net/manual/ja/language.types.string.php)

## 文字列内での変数展開について

シングルクォートやNowdoc内では変数展開（変数のパース、埋め込んだ変数の中身が展開されること）はされませんが、ダブルクォートやヒアドキュメント内では変数展開が行われます。

```php:シングルクォートでは変数展開が行われない
$str = 'test';
echo '$str'; // $str
```

```php:ダブルクォートでは変数展開が行われる
$str = 'test';
echo "$str"; // test
```

ダブルクォートで囲っていても変数名のすぐ後に文字が続くと変数展開ができないので、その場合は ```{}``` で変数の部分だけを囲います。

```php:ダブルクォートで変数展開が失敗する例
$str = 'テスト';
echo "今日は$strです。"; // [エラー] Notice: Undefined variable: strです。 in /sample/index.php on line 3
```

```php:{}を使用して変数展開する例
$str = 'テスト';
echo "今日は{$str}です。"; // 今日はテストです。
```

[PHPマニュアル - 文字列 - 変数のパース](https://www.php.net/manual/ja/language.types.string.php#language.types.string.parsing)

変数展開用の ```{}``` 記号は、下記のように$記号を外に出して書いても同様の結果となります。

```php
$str = 'テスト';
echo "今日は${str}です。"; // 今日はテストです。
```

### ヒアドキュメント

改行されて何行にも渡るような文字列を表すには、ヒアドキュメントという機能を利用するのが便利です。
コード内で ```<<<EOS``` から始まり ```EOS;``` で終わるようなセクションがヒアドキュメントになりますが、```EOS (End of String)``` のような**終端ID**は自由に命名可能です。命名規則は変数や関数と同様になります。

```php:ヒアドキュメントの例
echo <<<EOS
<h1>タイトル</h1>
<p>段落</p>
<p>段落</p>
<p>段落</p>
EOS;
```

ヒアドキュメントの中で使用された変数は展開され、空白文字、改行、シングルクォート、ダブルクォートはそのまま出力されますが、最後の行末の改行だけは無視されます。

```php:変数は展開され、最終行（2回目のテスト）の行末の改行は無視される
$str = 'テスト';
echo '<span>';
echo <<<EOS
$str
$str
EOS;
echo '<span>';
```

```html:出力結果
<span>テスト
テスト<span>
```

終端IDはインデントしたり、同じ行にコメントを書いたりはできません。ヒアドキュメントの終わりに記述できるのは、終端IDとセミコロンだけです。

```php:終端IDをインデントしてみる
<?php
 echo '<span>';
 echo <<<EOS
 テスト
 テスト
 EOS;
 echo '<span>';
```

```php:出力結果（エラー）
Parse error: syntax error, unexpected end of file, expecting variable (T_VARIABLE) or heredoc end (T_END_HEREDOC) or ${ (T_DOLLAR_OPEN_CURLY_BRACES) or {$ (T_CURLY_OPEN) in /sample/index.php on line 8
```

（余談として、自分の環境で試したところPHP 5.4では終端IDをインデントするとエラーになり、PHP 7.3ではならなかったんですが、PHPの公式マニュアルでも「終端IDとセミコロンだけにしてね」と書いてあるので従っておくのが無難かなと思います。）

[PHPマニュアル - 文字列 - ヒアドキュメント](https://www.php.net/manual/ja/language.types.string.php#language.types.string.syntax.heredoc)

### Nowdoc

ヒアドキュメントに似た機能では**Nowdoc**というものがあり、ヒアドキュメントがダブルクォーテーションで文字列を囲ったときの動きに近いとすれば、Nowdocはシングルクォーテーションで文字列を囲ったときの動きに近くなります。PHP 5.3以降ならば利用可能で、Nowdoc開始時の終端IDをシングルクォーテーションで囲うことと、変数展開がされないこと以外は基本的にヒアドキュメントと同じ動きになります。

[PHPマニュアル - 文字列 - Nowdoc](https://www.php.net/manual/ja/language.types.string.php#language.types.string.syntax.nowdoc)

```php:Nowdocで変数の展開は行われない
$str = 'テスト';
echo '<span>';
echo <<<'EOS'
$str
$str
EOS;
echo '<span>';
```

```html:出力結果
<span>$str
$str<span>
```

## echoとprint

その他、今までのコードにも出てきていますが、文字列の出力には ```echo``` ```print``` ```printf``` などを使用します。
厳密に言えば ```echo``` や ```print``` は関数ではなく言語構造であり、引数となる後ろ側の部分を丸カッコで囲っても囲わなくても機能します。

```php
print 'Hello world!'; // Hello world!
print('Hello world!'); // Hello world!
echo 'Hello world!'; // Hello world!
echo('Hello world!'); // Hello world!
```

その他の言語構造についてはPHPマニュアルを参照してください。

[PHPマニュアル - キーワードのリスト](https://www.php.net/manual/ja/reserved.keywords.php)

```echo``` はカンマで区切って複数の値を引数として与えられますが、```print``` ではカンマ区切りの複数の値を与えることはできません。
また、```echo``` を使いカンマ区切りで複数の値を出力するときは、丸カッコで引数を囲うことはできません。
（複数の値を出力するときは、カンマを使わずドットで連結することの方が多いかもしれませんが。）

```php:printにカンマ区切りで複数の値を与える
print 'Hello ', 'world!'; // [エラー] Parse error: syntax error, unexpected ',' in /sample/index.php on line 2
print('Hello ', 'world!'); // [エラー] Parse error: syntax error, unexpected ',' in /sample/index.php on line 2
```

```php:echoにカンマ区切りで複数の値を与える
echo 'Hello ', 'world!'; // Hello world!
echo('Hello ', 'world!'); // [エラー] Parse error: syntax error, unexpected ',' in /sample/index.php on line 2
```

```echo``` と ```print``` の違いとして他には、```print``` には返り値があり、常に数値の ```1``` を返すため式の一部として使うことができますが、 ```echo``` には返り値がありません。

```php:echoを式の一部として使用する
if(echo 'echo'){
 echo 'OK';
}
// [エラー] Parse error: syntax error, unexpected 'echo' (T_ECHO) in /sample/index.php on line 2
true ? echo 'OK' : echo 'NG';
// [エラー] Parse error: syntax error, unexpected 'echo' (T_ECHO) in /sample/index.php on line 3
```

```php:printを式の一部として使用する
if(print 'print'){
 print 'OK';
}
// printOK
true ? print 'OK' : print 'NG';
// OK
```

```echo``` と ```print``` の違いについては別な記事でもまとめているので、興味のある方は読んでみてください。
[PHP : echoとprintの違い](https://qiita.com/chimayu/items/dcc443c3a99f3379b3da)

## printf

```printf()``` は、言語構造ではなく組み込み関数で、**書式文字列**を使って文字列をフォーマットすることができます。
書式文字列は **置換マーク** ```%``` で始まり、**修飾子** ```02``` ```-5``` などは必要に応じて付け、**型指定子** ```d``` ```s``` などで終わります。

```php:printfの構成
printf('[置換マーク][修飾子][型指定子]');
```

```php:置換マーク、修飾子、型指定子の例
printf('%d',9); // 9 // dは10進数のdecimalを表す型指定子
printf('%2d',9); // 9 // 9の直前に空白が置かれて2桁になっている
printf('%02d',9); // 09 // 2桁で出力し、足りない桁数は0で埋める
printf('%f', 3.14); // 3.140000 // fは浮動小数点数のfloatを表す型指定子
printf('%1.2f', 3.14); // 3.14 // 整数1桁.小数2桁
printf('%s','Hello world!'); // Hello world! // sは文字列のstringを表す型指定子
printf('%12s','world!'); // world! // 文字列の前に空白が置かれ12桁になっている
printf('%-12s','Hello'); // Hello // 文字列の後に空白が置かれ12桁になっている
printf('%d%s%d%s', 1, '年', 1, '組'); // 1年1組 // 左から順に対応する引数を出力
printf('%%'); // % // %文字を出力
```

より詳しい ```printf()``` の仕様はマニュアルを確認してください。

[PHPマニュアル - printf — フォーマット済みの文字列を出力する](https://www.php.net/manual/ja/function.printf.php)

## print_r と var_dump

```print_r()``` や ```var_dump()``` を使用すると、変数の値などをデバッグ用にわかりやすく出力してくれます。
パッと見で読みやすいのは ```print_r()``` かもしれませんが、```var_dump()``` の方はデータ型や ```null``` ```false``` もきちんと文字列で出力してくれます。
可読性優先だったら ```print_r()```、より詳細に情報を出力したいときは ```var_dump()``` を使うと良いでしょう。

```php
$array = array( null, false, 'string', 1 );
var_dump($array);
print_r($array);
```

```php:print_rによる出力結果
Array ( [0] => [1] => [2] => string [3] => 1 )
```

```php:var_dumpによる出力結果
array(4) { [0]=> NULL [1]=> bool(false) [2]=> string(6) "string" [3]=> int(1) }
```

## strlenとmb_strlen
```strlen()``` と ```mb_strlen()``` では、文字列の文字数を調べることができます。
日本語の文字はマルチバイト文字といって通常の半角英数字とは異なるので、マルチバイト文字の文字数を調べたいときは ```mb_strlen()``` の方を使います。

```php:strlenの場合
echo strlen('Hello!'); // 6
echo strlen('こんにちは！'); // 18 // strlenで日本語の文字数は数えられない
```

```php:mb_strlenの場合
echo mb_strlen('Hello!'); // 6 // mb_strlenでは半角英数も対応する
echo mb_strlen('こんにちは！'); // 6
```

[PHPマニュアル - strlen — 文字列の長さを得る](https://www.php.net/manual/ja/function.strlen)
[PHPマニュアル - mb_strlen — 文字列の長さを得る](https://www.php.net/manual/ja/function.mb-strlen)

マニュアルにもありますが、```strlen()``` というのは厳密には**文字のバイト数**（大きさ、容量のようなもの）を調べる組み込み関数です。
半角英数字は1文字1バイトなので バイト数＝文字数 と読み替えても不都合はあまりありませんが、日本語のようなマルチバイト文字というのは1文字が3バイトとか4バイトしたりすることもあるので、```strlen()``` でバイト数を調べると文字数よりも大きな数値が出てしまうということになります。

## trim / ltrim / rtrim

```trim()``` ```ltrim()``` ```rtrim()``` のそれぞれの関数は、文字列から特定の文字を削除してくれます。
```trim()``` は文字列の両端から削除、```ltrim()``` は文字列の左側(left)のみ削除、```rtrim()``` は文字列の右側(right)のみ削除します。
第一引数には削除処理を適用したい文字列、第二引数には削除したい文字を指定することができます。第二引数は省略することも可能で、省略した場合は **空白, タブ, リターン, 改行, NULバイト, 垂直タブ** を削除します。

```php:trimで第二引数を省略した場合
echo trim(' Hello '); // Hello // 文字列の左右両側から空白文字が除去される
```

ちなみに削除する文字を指定する際、```trim('Hello','He')``` などとした場合は ```He``` という文字列を削除するのではなく、```H``` と ```e``` の文字を削除しようとします。```trim('Hello','Hel')``` などとした場合は ```Hel``` という文字列ではなく ```H``` と ```e``` と ```l``` の文字を削除しようとするので、次の2つ目の例では ```l``` が2文字とも削除されて ```o``` だけが残っています。

```php:trimで第二引数を指定した場合
echo trim('Hello','He'); // llo
echo trim('Hello','Hel'); // o
```

[PHPマニュアル - trim — 文字列の先頭および末尾にあるホワイトスペースを取り除く](https://www.php.net/manual/ja/function.trim)
[PHPマニュアル - ltrim — 文字列の最初から空白 (もしくはその他の文字) を取り除く](https://www.php.net/manual/ja/function.ltrim.php)
[PHPマニュアル - rtrim — 文字列の最後から空白 (もしくはその他の文字) を取り除く](https://www.php.net/manual/ja/function.rtrim.php)

## strtolower / strtoupper / ucfirst / ucwords

大文字・小文字を変換するには、```strtolower()``` ```strtoupper()``` ```ucfirst()``` ```ucwords()``` などの関数が便利です。
```strtolower()``` は、引数に与えられた文字列の中でアルファベットの部分をすべて小文字にして返します。

```php
echo strtolower('Hello World!'); // hello world!
```

```strtoupper()``` は、引数に与えられた文字列の中でアルファベットの部分をすべて大文字にして返します。

```php
echo strtoupper('Hello World!'); // HELLO WORLD!
```

```ucfirst()``` は、引数に与えられた文字列の最初の文字がアルファベットの場合に、それを大文字にします。

```php
echo ucfirst('hello world!'); // Hello world!
echo ucfirst('こんにちは world!'); // こんにちは world! // 1文字目がアルファベットでないため変化なし
```

```ucwords()``` は、引数に与えられた文字列のアルファベットにおいて、各単語の1文字目を大文字にします。各単語の区切りはデフォルトだと空白文字や改行などですが、第二引数に区切り文字を指定すると、指定の区切り文字の直後が大文字に変換されるようになります。

```php
echo ucwords('hello world!'); // Hello World! // 半角スペースを区切りと認識
echo ucwords("hello \nworld!"); // Hello World! // 改行(\n)を区切りと認識
echo ucwords('hello world!', 'l'); // HelLo worlD! // l(エル)を区切りと認識
```

[PHPマニュアル - strtolower — 文字列を小文字にする](https://www.php.net/manual/ja/function.strtolower)
[PHPマニュアル - strtoupper — 文字列を大文字にする](https://www.php.net/manual/ja/function.strtoupper)
[PHPマニュアル - ucfirst — 文字列の最初の文字を大文字にする](https://www.php.net/manual/ja/function.ucfirst)
[PHPマニュアル - ucwords — 文字列の各単語の最初の文字を大文字にする](https://www.php.net/manual/ja/function.ucwords)

#### 参考URL
[PHPの変数展開は、{$variable}構文を使うのがいいみたいです](http://yinm.hatenadiary.jp/entry/2017/02/08/230339)
[PHP 文字列リテラルにおける変数展開ノ全テ](https://do-aki.hatenablog.jp/entry/20131218/1387335279)
[浮動小数点って何?](https://qiita.com/angel_p_57/items/24078ba4aa5881805ab2)