【PHP】半角カナで固定長データを作るときの落とし穴

ダンプファイルを作成しようとして固定長データを取得する際に、半角カナ混じりのデータだと引っかかりがちな落とし穴があります。

```PHP:PHP
   $address = "ｵｵｻｶｼｽﾐﾉｴｸ";
   $address2 = "ｻｯﾎﾟﾛｼｱﾂﾍﾞﾂｸ";
```

まずは、この２つの変数の文字数を調べてみましょう。文字数を調べる時は**mb_strlen**を使います。

```PHP:PHP
   echo mb_strlen($address); //10と表示されます
   echo mb_strlen($address2); //濁点、半濁点ともに１文字となるので、12と表示されます。 
```

では、それを踏まえた上で、このデータを40バイトの固定長で値を作成してみます（今回はわかりやすく、空白部分を\*で埋めてみます）。固定長データを作る際には便利な**str_pad**という関数があるのでこれを使ってみます。

すると…

```PHP:PHP
   echo str_pad($address,40,"*"); // ｵｵｻｶｼｽﾐﾉｴｸ*********** と表示される。
   echo str_pad($address2,40,"*"); //ｻｯﾎﾟﾛｼｱﾂﾍﾞﾂｸ**** と表示される。
```

想定していたデータは、`ｵｵｻｶｼｽﾐﾉｴｸ******************************`のはずなのに、明らかに記号が足りません。ｻｯﾎﾟﾛｼｱﾂﾍﾞﾂｸの場合だと、もっと記号が少なくなっています。

## 落とし穴１：UTF-8の半角カナは3バイト
では、バイト数を取得できる**strlen**関数を使って調べてみます。

```PHP:PHP
   echo strlen($address); //30と表示されます。
   echo strlen($address2); //36と表示されます。
```

バイト数が、文字数の3倍になっているのがわかります。このように、まず注意しなければいけないのは、UTF-8において、**半角カナは1文字あたり3バイト**であること、そして**str_pad関数の引数はバイト数で換算しないといけない**、ということです。よって、UTF-8の場合は、半角1文字あたり3バイトなので10\*3の30バイト分消費しているため、残りの記号は10文字分しかパディングされないのです。

そのため、**str_pad**関数を使う場合は、固定長の値も調節が必要になります。半角カナを使うたびに、3バイト消費、言い換えれば**2バイト余分に消費**しているので、2\*半角カナの文字数分、加算しておきます。

```PHP:PHP
   echo str_pad($address,40 + mb_strlen($address)*2,"*"); //ｵｵｻｶｼｽﾐﾉｴｸ******************************
   echo str_pad($address2,40 + mb_strlen($address2)*2,"*") //ｻｯﾎﾟﾛｼｱﾂﾍﾞﾂｸ****************************
```
このように、半角カナ数の2倍分だけ固定長データを加算しておけば、動的な値の変化にも対応できます。

※なお、シフトJISの場合は2バイトになりますので、半角カナの文字分だけ加算してください。

## 落とし穴２：半角スペース混じりの半角カナ
では、先程の変数をこのようにしてみます。

```PHP:PHP
   $address = "ｵｵｻｶｼ ｽﾐﾉｴｸ";
   $address2 = "ｻｯﾎﾟﾛ ｼ ｱﾂﾍﾞﾂ ｸ";
```

この２つの変数の文字数を調べてみましょう。スペースが増えた分だけ増えているはずです。

```PHP:PHP
   echo mb_strlen($address); //11と表示されます
   echo mb_strlen($address2); //15と表示されます。 
```

では、同じようにこのデータを40バイトの固定長で値を作成してみます

すると…

```PHP:PHP
   echo str_pad($address,40,"*"); // ｵｵｻｶｼ ｽﾐﾉｴｸ********* と表示される。
   echo str_pad($address2,40,"*"); // ｻｯﾎﾟﾛ ｼ ｱﾂﾍﾞﾂ ｸ* と表示される。
```

先程と比べるとなにか不可解な結果になっています。

では、バイト数を取得できる**strlen**関数を使って調べてみます。

```PHP:PHP
   echo strlen($address); //31と表示されます。
   echo strlen($address2); //39と表示されます。
```

おわかりでしょうか？半角カナは3バイトなのに対し、**半角スペースは1バイトのまま**です（当たり前ですが）。したがって、先ほどと同じように固定長データを作ろうとすると、想定外の現象に思わずでくわします。

```PHP:PHP
   str_pad($address,40 + mb_strlen($address)*2,"*"); //62バイトの想定に対し、61バイトとなる
   str_pad($address,40 + mb_strlen($address2)*2,"*"); //70バイトの想定に対し、67バイトとなる
```

下手に、半角スペースも含めて半角カナ全部を加算してしまうとこうなってしまい、想定していた固定長が不足してしまいます。したがって、半角スペースも含む場合で固定長バイト数を追加する場合は、**半角カナの文字数だけを加算しないといけない**ので、一度、半角スペースを除外した状態で文字数をカウントする必要があります。

```PHP:PHP
   str_pad($address,40 + mb_strlen(str_replace(" ","",$address))*2,"*"); //40文字62バイト   　　　 
   str_pad($address,40 + mb_strlen(str_replace(" ","",$address2))*2,"*"); //40文字70バイト
```


これで、正しく固定長データを取得することができました。

参考にしたページ

 - [PHPで固定長ファイルを作る](https://qiita.com/zaburo/items/d8a21038b201286f8a98)
 
#落とし穴３：mb_convert_kanaで起きる怪現象
PHPには`mb_convert_kana`という、全角カナを半角カナにしてくれたりする、文字操作に非常に便利な関数があるのですが、これには大きな問題点を抱えており、濁点混じりの文字を処理したときに、以下のようになっている場合があります(自分が起きたケースではExcelをPhpSpreadsheetで半角文字を読み込んだ際に起きた現象です）。

```PHP:PHP
$address = mb_convert_kana($address,"ks");
$address = "ﾅｺﾞ ﾔｼﾃﾝﾊﾟ ｸｸ"; //濁点や半濁点の処理時に半角スペースが空いた！
```

これをこのまま処理しようとすると当然バイト数が狂ってしまうので、正しい固定長バイトを取得できません。なので、この場合は元の文字列も含めて濁点と半濁点に対し、置換処理（濁点、半濁点直後のスペース1個分を削除）を行わないといけなくなります。

```PHP:PHP
   $address = mb_convert_kana($address,"ks");
   $address = "ﾅｺﾞ ﾔｼﾃﾝﾊﾟ ｸｸ"; //濁点や半濁点の処理時に半角スペースが空いた！
   str_pad($address,40 + mb_strlen(str_replace(" ","",$address))*2,"*"); //40文字62バイト   　　　 
   str_pad(str_replace(["ﾞ ","ﾟ "],["ﾞ","ﾟ"],$address),40 + mb_strlen(str_replace(" ","",str_replace(["ﾞ ","ﾟ "],["ﾞ","ﾟ"],$address)))*2,"*"); //元の文字に対しても処理が必要なので注意
```

#落とし穴４：拗音、促音の小文字に注意
拗音とはｷｮｳﾄｼの**ｮ**、促音とはｻｯﾎﾟﾛｼの**ｯ**といった文字の小文字部分です。これを処理するときには固定長ファイルの仕様を再確認する必要もあると思いますが、ｷ**ｮ**ｳﾄｼではなくｷ**ﾖ**ｳﾄｼとしなければならない場合があります（特に金融機関情報とか）。その場合は、str_replace関数を使い、力技でねじ伏せるしかありません。

```PHP:PHP
//このような変換用関数を用意して対応します。
$address = "キョウトシカミギョウク";
$address = mb_convert_kana($address,"ks");
echo $address; // ｷｮｳﾄｼｶﾐｷﾞｮｳｸと表示される
//拗音を変更する
$address = str_replace(["ｧ","ｨ","ｩ","ｪ","ｫ","ｯ","ｬ","ｭ","ｮ"],["ｱ","ｲ","ｳ","ｴ","ｵ","ﾂ","ﾔ","ﾕ","ﾖ"],$address);
echo $address; //ｷﾖｳﾄｼｶﾐｷﾞﾖｳｸと表示される
```