質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.32%
PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

文字コード

文字コードとは、文字や記号をコンピュータ上で使用するために用いられるバイト表現を指します。

Q&A

解決済

5回答

602閲覧

phpで特定の文字列のエンコードで文字化けさせないようにしたい

myyc

総合スコア3

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

文字コード

文字コードとは、文字や記号をコンピュータ上で使用するために用いられるバイト表現を指します。

0グッド

0クリップ

投稿2025/03/12 09:20

実現したいこと

文字コードを変換する際に特定の文字列だけ文字化けしてしまうので文字化けさせずに文字コードを変換したい。
対象の文字列はSJISかUTF-8で、変換後はUTF-8に統一したい。

発生している問題・分からないこと

№川家
この文字列に対してmb_convert_encodingで文字コード変換する文字化けしてしまうので
文字化けしないようにさせたいです。
SJISへの変換は問題ないですが、UTF-8への変換で文字化けしてしまいます。

該当のソースコード

PHP

1echo mb_convert_encoding("№川家", 'UTF-8', 'utf-8, sjis-win'); 2echo mb_convert_encoding("No.川家", 'UTF-8', 'utf-8, sjis-win'); 3echo mb_convert_encoding("№河家", 'UTF-8', 'utf-8, sjis-win'); 4echo mb_convert_encoding("№川毛", 'UTF-8', 'utf-8, sjis-win');

試したこと・調べたこと

  • teratailやGoogle等で検索した
  • ソースコードを自分なりに変更した
  • 知人に聞いた
  • その他
上記の詳細・結果

貼り付けたソースコードの結果
邃門キ晏ョカ
No.川家
№河家
№川毛
と、1番上の文字列だけ文字化けしてしまいます。
第1引数は変数でUTF-8かSJISが混在しているためエンコードの指定はこのようにしています。

1番上のソースコードを
mb_convert_encoding("№川家", 'sjis, 'utf-8, sjis-win');
このようにSJISに変換しようとすると文字化けせずに動きます。
何故この文字列のときだけSJISなら動くのかわからないです。

補足

phpのバージョンは8.2.12
xamppとVScodeを使っています。
また、phpファイルはUTF-8で保存しています。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yambejp

2025/03/12 10:15

再現できないので、文字化けするという行の「№」の文字をバイナリエディタなどで何が当てられているか確認してみてください
myyc

2025/03/12 10:36

VScodeのHex Editorで開いたところUTF-8で№のところはE2になっていました。 第1引数(№川家)全部だと E2 84 96 E5 B7 9D E5 AE B6 になっていました
melian

2025/03/12 10:40

参考までに、PHP 8.0.30 では問題なしで、PHP 8.1.0 以降で再現します。
TakaiY

2025/03/12 10:56

第三引数に複数指定して**うまく**判定してね、という作りですが、そもそも少ない文字数でミスなく判定するのは難しいということだと思います。文字化けしないようにしたいのであれば、自動判別ではなく対象文字列の文字コードを特定できるようにするしかないのではないかと思います。
guest

回答5

0

これは https://www.php.net/manual/ja/function.mb-detect-encoding.php

文字エンコーディングの一覧を試す順番に指定します。

の説明が正確ではないようです。

https://github.com/php/php-src/blob/3f3ac4de25486fabae99d1d648583d4bd852a592/ext/mbstring/mbstring.c#L3469
https://github.com/php/php-src/blob/3f3ac4de25486fabae99d1d648583d4bd852a592/ext/mbstring/mbstring.c#L3386
https://github.com/php/php-src/blob/3f3ac4de25486fabae99d1d648583d4bd852a592/ext/mbstring/mbstring.c#L3306
https://github.com/php/php-src/blob/3f3ac4de25486fabae99d1d648583d4bd852a592/ext/mbstring/mbstring.c#L3394
と見ていくと、エンコーディングの候補から可能性が残ったもののうちで、demeritsが一番小さいエンコーディングが選ばれるのであって、demeritsが等しい時だけ第2引数の順序が関係するという感じですね。

推測だと、

№川家 をUTF-8でチェックすると、末尾1〜2バイト削ると解釈不能、3バイト削ると解釈可能
邃門キ晏ョカ をShiftJISでチェックすると、末尾1〜2バイト削っても解釈可能、3バイト削ると解釈不能

№河家 をUTF-8でチェックすると、末尾1〜2バイト削ると解釈不能、3バイト削ると解釈可能(これは№川家と同じ)
邃匁イウ螳カ をShiftJISでチェックすると、末尾1バイト削っても解釈可能、2バイト削ると解釈不能、3バイト削ると解釈可能

というあたりでdemeritsが違っているのではないかと感じました。

結論は
UTF-8としてもShiftJISとしても解釈可能なバイト列を与えた時、そのどちらが選ばれるかは決定的ではあるが推測や指定は難しい。

mb_detect_encoding("No.川家", 'UTF-8', true) == 'UTF-8'
mb_detect_encoding("No.川家", 'sjis-win', true) == 'sjis-win'
の両方を試して、いずれがfalseでいずれかがtrueならtrueな方で、両方trueの時はデフォルトをどちらかに決めておいてそちらでmb_convert_encodingするべきかなと思います。

追記ここまで。


https://github.com/php/php-src/issues/16566#issuecomment-2435388893

で議論されていましたが、Not Planned で終わっています。

再追記ここまで。


以下変更タイミングが同じなだけで関係なかったです。

https://ja.stackoverflow.com/questions/100559/

と同じ件のように思います。

https://github.com/php/php-src/issues/17238#issuecomment-2558292993

㎡ is not contain JIS X 0208. This means not contain Shift_JIS and EUC-JP

JIS X 0208の仕様には存在しない文字であるため、UnicodeのコードポイントとJISとの変換表にも存在しません。(昔いわゆる"機種依存文字"と呼ばれていた範囲の文字です)
そのためmb_convert_encodingやmb_detect_encodingが意図通り動作しないのが正しい仕様になったということかと思います。

投稿2025/03/12 15:30

編集2025/03/13 23:21
quickquip

総合スコア11277

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

quickquip

2025/03/13 06:39 編集

"見知っていたこと"を書いただけなので普通にコードや環境がおかしいとか、mb_detect_encodingのバグだといった可能性はあります。そこはphpのコードを読むなり、issueを上げるなり、他の回答者に期待するなりで……(私はphpは使わないので)
myyc

2025/03/13 13:41

ご回答ありがとうございます。 今のところこれが原因の可能性が高そうですが該当のソースコードの3,4行目の通り機種依存文字が含まれていても意図した通りの文字列が取得できるケースもあるのが良くわからない状況です・・・
maisumakun

2025/03/13 14:41

自分の回答に書きましたが、「№」がUTF8で表現されているという状況での問題なので、この文字がSJISに存在しない件とはおそらく無関係です。
quickquip

2025/03/13 15:56

どうもそのようでした。回答を改めました
guest

0

ベストアンサー

PHP8.1 において、mb_convert_encoding() の第3引数の挙動が変更されました。

PHP8.0までは、第3引数に記述した順序で置換を試みる、というものでした。
PHP8.1以降は記述された文字エンコーディングの中で最も可能性が高いものを検出する、というものに変更されています。
このためバージョンによって結果に差異がでます。

'№川家' は e28496e5b79de5aeb6ですから、これを検出させた結果
e284
96e5
b7
9de5
ae
b6
となり、すべての文字がsjis-winとして正しく表現出来るので、この文字列はsjis-winであると判定されているのかと思います。

対策ですが、質問のケースのみであれば、PHP8.0の挙動と同様に、指定した順序でエンコーディングを試みる関数を実装する事で解消はできます。

php

1function convert_encoding_orders(string $string, array $encodings) { 2 foreach ($encodings as $encoding) { 3 if (mb_check_encoding($string, $encoding)) { 4 return mb_convert_encoding($string, 'UTF-8', $encoding); 5 } 6 } 7 8 return false; 9} 10 11$str = '№川家'; 12 13echo convert_encoding_orders($str, ['UTF-8', 'SJIS-win']); 14// №川家 15 16echo convert_encoding_orders($str, ['SJIS-win', 'UTF-8']); 17// 邃門キ晏ョカ

しかしながら、このやり方では、sjis-winの文字列というのが正しいのに、UTF-8と誤判定されてしまう可能性も残ります。
入力の時点で文字エンコーディングを合わせるのが最も良いかと思います。

投稿2025/03/13 17:02

Eggpan

総合スコア3233

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

myyc

2025/03/15 02:03

ご回答ありがとうございます。 入力の時点の文字コードをあわせる手順をふもうと思います。
guest

0

対象の文字列はSJISかUTF-8で、

PHP: Possible modifiers in regex patterns - Pattern Modifiersu modifier を利用してもよいかと思います。

u (PCRE_UTF8)

This modifier turns on additional functionality of PCRE that is incompatible with Perl. Pattern and subject strings are treated as UTF-8. An invalid subject will cause the preg_* function to match nothing;

php

1<?php 2 3function detectUTF8($string) 4{ 5 return preg_match('//u', $string) ? 'UTF-8' : 'sjis-win'; 6} 7 8$strings = [ 9 // UTF-8 SJIS 10 "№川家", "\x87\x82\x90\xec\x89\xc6", 11 "No.川家", "\x4e\x6f\x2e\x90\xec\x89\xc6", 12 "№河家", "\x87\x82\x89\xcd\x89\xc6", 13 "№川毛", "\x87\x82\x90\xec\x96\xd1", 14 "abc", "\x61\x62\x63" // ASCII code 15]; 16foreach ($strings as $str) { 17 echo mb_convert_encoding($str, 'UTF-8', detectUTF8($str)) . PHP_EOL; 18} 19 20# №川家 21# №川家 22# No.川家 23# No.川家 24# №河家 25# №河家 26# №川毛 27# №川毛 28# abc 29# abc

投稿2025/03/13 18:53

melian

総合スコア21038

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

myyc

2025/03/15 02:14

ご回答ありがとうございます。 こちらも試してみます。
guest

0

シンプルな方法ではどうしようもないです。

UTF-8で書かれた「№川家」と、SJISの「邃門キ晏ョカ」は、どちらも「E2 84 96 E5 B7 9D E5 AE B6」という同一のバイト列です(「№」はUTF-8側にしか含まれませんので、SJISかSJIS-WINかという話は無関係です)。

どちらも文字として成立はしているので、このどちらが正しいかを判断するのは一筋縄ではいかないです。

  • どのような文字列が来るかの事前情報があるなら、それと照らし合わせて正しくないエンコードだと判定させる
  • 生成AIに投げてみる

投稿2025/03/13 14:35

maisumakun

総合スコア146452

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

maisumakun

2025/03/13 14:38

もちろん理想的には、「受け渡しする文字コードを確定させる」というのがいちばん確実なのですが、それを困難とする背景(「E2 84 96 E5 B7 9D E5 AE B6」という短いバイト列から文字コードを推定しなければならない事情)はどのようなものなのでしょうか。
myyc

2025/03/15 02:09

ご回答ありがとうございます。 こちらの回答もベストアンサーにしたいくらい腑に落ちました。 後出しになりますが、この文字列自体は元の文字列があり、どこが問題か切り分けるためにできるだけ小さい文字列にしてあります。極力手順を減らして文字コードを変換したいと思い、色々文字列を試している時の出来事でしたが回答を見て文字コードの判断が難しいと思い、入力文字コードを統一する手順を追加することにしました。
guest

0

ざっと試してみた感じ問題なさそうですが以下でソースを確認してみてください

php

1<?PHP 2header('Content-Type: text/html;charset=UTF-8'); 3?> 4<meta http-equiv="Content-Type" content="text/html; charset=UTF8"> 5<?PHP 6mb_internal_encoding("SJIS"); 7$str="№川家"; 8var_dump( mb_convert_encoding($str,'UTF-8', 'utf-8, sjis-win')); 9var_dump( mb_convert_encoding($str,'HTML-ENTITIES', 'utf-8, sjis-win' ) );

追記

「№川家」が「E2 84 96 E5 B7 9D E5 AE B6」で保存されているとありますがSJISであれば「87 82 90 EC 89 C6」になると思います。ご提示の化け方は以下のようにUTF8で保存された文字列をSJISで表示しようとしたときに起きる現象ですので、命題とはやっていることが違うと思います。以下確認ください

php

1<?PHP 2header('Content-Type: text/html;charset=SJIS'); 3?> 4<meta http-equiv="Content-Type" content="text/html; charset=SJIS"> 5<?PHP 6mb_internal_encoding("SJIS"); 7$str="№川家"; 8var_dump( $str);

上記UTF8N(BOMなし)で保存すると文字化けが再現できるかと。

投稿2025/03/12 10:19

編集2025/03/13 00:30
yambejp

総合スコア117400

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

myyc

2025/03/12 10:41

ご回答ありがとうございます。 string(18) "邃門キ晏ョカ" string(48) "邃門キ晏ョカ" 試してみましたが上記の結果になりました。
yambejp

2025/03/13 00:29

なんとなくやっていそうなことは理解しました。追記分ご確認ください
myyc

2025/03/13 13:47

文字化けの再現はできました。 実際には第1引数はUTF-8かSJISのどちらかの文字列が入り、いずれの場合もUTF-8で統一したいというのが今回実装したい内容になりますのでUTF-8の表記で書いてしまいました。 紛らわしくて申し訳ありません。 echo mb_convert_encoding("№川家", 'UTF-8', 'UTF-8, sjis-win'); echo mb_convert_encoding("№川家", 'UTF-8', 'UTF-8'); echo mb_convert_encoding(mb_convert_encoding("№川家",'sjis-win','UTF-8'), 'UTF-8', 'UTF-8, sjis-win'); この場合1行目だけ文字化けしました。 UTF-8上のスクリプトなので第3引数的には1,2行目は同じ結果になりそうなのですが・・・
myyc

2025/03/13 14:22

第1引数を変数にして $s = "№川家"; echo mb_convert_encoding(mb_convert_encoding($s,'sjis-win','sjis-win'), 'UTF-8', 'UTF-8, sjis-win'); echo mb_convert_encoding(mb_convert_encoding($s,'sjis-win','UTF-8'), 'UTF-8', 'UTF-8, sjis-win'); こうした場合、スクリプトがSJISだと上が正しく動いて、UTF-8だと下が正しく動きました。 この変数をmb_detect_encoding($s, "UTF-8, sjis-win")で判定するとスクリプトがSJIS、UTF-8に関わらずSJISが返ってきて更に謎が深まりました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.32%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問