「PHP」カテゴリーアーカイブ

UTF-8の4バイト文字のバリデーション

MySQLの文字セットがutf8の場合、utf-8で符号化すると4バイトになる文字(😁のような絵文字など)をセットすると、SQLモード(sql_mode)が厳密モード(STRICT_ALL_TABLES または STRICT_TRANS_TABLES のいずれかが有効)でない場合、その文字以降が切り捨てられてしまう。(警告は発生する)

4バイトUTF-8文字に対応するためには、CHARACTER SET に utf8mb4(COLLATE に utf8mb4_unicode_520_ci など utf8mb4_xxx) を指定したカラムを使用し、接続文字セットも utf8mb4 を使用する必要がある。

ちなみにRailsでは、MySQLのカラムの文字セットがutf8の場合に4バイトUTF-8文字をセットしようとすると、以下のようなエラーが発生するので、気付かないうちに文字列が切り捨てられてしまうことはない。

An ActiveRecord::StatementInvalid occurred in news#update:

Mysql2::Error: Incorrect string value: '\xF0\x9F\x98\x80\x0D\x0A' for column 'description' at row 1: UPDATE `news` SET `description` = '😀\r\n' WHERE `news`.`id` = 2
app/controllers/news_controller.rb:98:in `update'

これは特に指定していない場合、AbstractMysqlAdapter#configure_connection で、STRICT_ALL_TABLES がセッションのSQL_MODEに追加されているから。(NO_AUTO_VALUE_ON_ZERO も追加される。)

これは以下のようにして確認できる。

  • mysqlクライアントで確認
    mysql> show variables like 'sql_mode';
    +---------------+------------------------+
    | Variable_name | Value                  |
    +---------------+------------------------+
    | sql_mode      | NO_ENGINE_SUBSTITUTION |
    +---------------+------------------------+
    1 row in set (0.00 sec)
    
  • 同じデータベースに対して、rails consoleで確認
    > con = ActiveRecord::Base.connection
    > con.select_all("SHOW VARIABLES LIKE 'sql_mode'")
       (0.8ms)  SHOW VARIABLES LIKE 'sql_mode'
     => #<ActiveRecord::Result:0x00007fc6ca533728 @columns=["Variable_name", "Value"], @rows=[["sql_mode", "NO_AUTO_VALUE_ON_ZERO,STRICT_ALL_TABLES,NO_ENGINE_SUBSTITUTION"]], @hash_rows=nil, @column_types={}>
    

対処方法

カラムの CHARACTER SET を utf8mb4 、COLLATE を utf8mb4_xxx に変換して、接続文字セットに utf8mb4 を使用すればよいが、何らかの事情でカラムを utf8mb4 に変換できない場合は、黙って4バイトUTF-8文字以降が切り捨てられるといろいろとまずいので、4バイトのUTF-8文字をバリデーションではじくことになるだろう。

UTF-8にエンコードすると4バイトになるUnicode文字の範囲は、U+10000からU+10FFFFである。

PHPでの例

if (preg_match('/[\x{10000}-\x{10FFFF}]/u', $s) { /* ... */ }
if (preg_match('/[\xF0-\xF7][\x80-\xBF][\x80-\xBF][\x80-\xBF]/', $s)) { /* ... */ }
preg_match_all('/[\x{10000}-\x{10FFFF}]/u', $s, $matches);
// $matches[0]に4バイトutf-8の文字の配列が格納される。

Rubyでの例

if /[\u{10000}-\u{10FFFF}]/ =~ s
  # ...
end
chars = s.scan(/[\u{10000}-\u{10FFFF}]/)
# charsに4バイトutf-8の文字の配列が格納される。

cachegrind.out.xxxxxx が /private/var/tmp に大量に作成されてディスク容量を圧迫

Mac OS X (Yosemite) で最近ディスクの空き容量がやたら少なくなっているので、調べてみた。

$ sudo du -sh /*

$ sudo du -sh /private/*

$ sudo du -sh /private/var/*

と調べて行くと、/private/var/tmp が200GBくらいある。
/private/var/tmp を見てみると、cachegrind.out.50526 のようなファイルが大量にあった。
これはXdebugのプロファイラーが出力するファイルらしい。(MacPortsのxdebugを使用している。)
Xdebugはエラー時にスタックトレースを表示したり、(Eclipseが必要だが)ステップ実行でデバッグできたりと便利なのだ。

で、この cachegrind.out.xxxxxx を rm で削除しようとしたら、

$ sudo rm cachegrind.out.*
-bash: sudo: Argument list too long

となって削除できない。
ファイルが多すぎるため、シェルが * を展開すると Argument list too long になってしまうのだ。

find -exec で削除した。

$ cd /private/var/tmp
$ sudo find . -name 'cachegrind.out.*' -maxdepth 1 -exec rm {} \;

200GBくらい空いた。

find -exec については、以前書いた以下の記事を参照してください。
findで検索した結果を削除したりgrepする方法

このままではまたどんどん cachegrind.out.xxxx がたまってくるので、xdebugの設定を変更して、プロファイラーが/private/var/tmp でなく、/tmp に出力するようにした。
(ついでにトレースファイル出力先も /tmp にしておいた。)
こうしておけば、再起動時に削除されるだろう。

/opt/local/etc/php53/php.ini
xdebug.profiler_output_dir と xdebug.trace_output_dir を追加。

[xdebug]
xdebug.profiler_enable=On
xdebug.remote_enable=On
xdebug.remote_host="localhost"
xdebug.remote_port=9000
xdebug.remote_handler="dbgp"
xdebug.idekey=ECLIPSE_DBGP
xdebug.profiler_output_dir=/tmp/ ; プロファイラーのファイル出力先
xdebug.trace_output_dir=/tmp/ ; トレースファイル出力先

Xdebug: Documentation.

CentOS 5.7 に PHP 5.3 の mcrypt 拡張とpearをインストール

yumのphp53でPHP5.3がインストールできる。拡張もyumにphp53-XXXXがあるが、php53-pearとphp53-mcryptはない。

mcryptのインストール

CentOS5.7のyumでphp53がインストールされた環境に、PHPのソースからmcrypt拡張をインストールした。
まず必要なmcrypt拡張のビルドに必要なパッケージをインストールしておく。

$ sudo yum install php53-devel libmcrypt-devel

yumでインストールしたphpと同じバージョンのソースを http://jp2.php.net/releases/ などPHPのダウンロードサイトからダウンロード、展開してmcrypt拡張をビルド、インストール。

$ wget http://museum.php.net/php5/php-5.3.3.tar.gz
$ tar xzvf php-5.3.3.tar.gz
$ cd php-5.3.3/ext/mcrypt/
$ phpize
$ aclocal
$ ./configure
$ make
$ sudo make install

php.iniを編集

extension=mcrypt.so

CentOS 5.6 に PHP 5.3 の mcrypt 拡張をインストールする (phpMyAdmin) | MyNotebook

pearのインストールとアップグレード

pearは以下の手順でインストール、アップグレード。

$ sudo yum install php-pear
$ sudo pear upgrade --force Archive_Tar
$ sudo pear upgrade --force Console_Getopt
$ sudo pear upgrade PEAR

MacPortsでphp5−mysqlをインストール

$ sudo port install php5-mysql
Password:
--->  Computing dependencies for php5-mysql
--->  Fetching php5-mysql
--->  Verifying checksum(s) for php5-mysql
--->  Extracting php5-mysql
--->  Configuring php5-mysql
--->  Building php5-mysql
--->  Staging php5-mysql into destroot
--->  Installing php5-mysql @5.3.0_0+mysqlnd
To use mysqlnd with a local MySQL server, edit /opt/local/etc/php5/php.ini and set
mysql.default_socket, mysqli.default_socket and pdo_mysql.default_socket
to /opt/local/var/run/mysql5/mysqld.sock
--->  Activating php5-mysql @5.3.0_0+mysqlnd
--->  Cleaning php5-mysql

 表示されているメッセージにあるように、php.iniにdefault_socketを設定するのを忘れないこと。

To use mysqlnd with a local MySQL server, edit /opt/local/etc/php5/php.ini and set
mysql.default_socket, mysqli.default_socket and pdo_mysql.default_socket
to /opt/local/var/run/mysql5/mysqld.sock

peclでAPCをインストール

peclでAPCをインストールしようとしたら、以下のエラーが発生した。

# pecl install APC
downloading APC-3.0.19.tgz ...
Starting to download APC-3.0.19.tgz (115,735 bytes)
.........done: 115,735 bytes
Fatal error: Allowed memory size of 8388608 bytes exhausted (tried to allocate 7680 bytes) in /usr/share/pear/PEAR/PackageFile/v2.php on line 1917
Fatal error: Allowed memory size of 8388608 bytes exhausted (tried to allocate 76 bytes) in /usr/share/pear/pearcmd.php on line 402

[php-maint] Bug#375070: php-pear: pecl memory limit too low
pecl実行時のmemory_limitが小さすぎるのが原因らしい。
上記サイトを参考にして、peclを変更( -d memory_limit=24Mを追加)したら解決。
また、peclでのAPCのインストールには、phpize、apxsが必要なので、php-devel、httpd-develをyumでインストール。

# yum install php-devel
# yum install httpd-devel

APCをインストール

# pecl install APC

インストール後、php.iniに、

extension=apc.so

を追加する。

macportsでphp5をupgradeしたらactivateでエラー

macportsでphp5をアップグレードした。
php5 @5.2.9_2+apache2+macosx+mysql5+pear+postgresql82+sqlite
 ↓
php5 @5.2.10_0+apache2+macosx+mysql5+pear+postgresql82+sqlite
すると、以下のエラー。
(追記: 後日、php5 @5.2.10_1が出たのでアップグレードしたら何の問題もなかった。)

$ sudo port upgrade php5
--->  Fetching php5
--->  Verifying checksum(s) for php5
--->  Extracting php5
--->  Applying patches to php5
--->  Configuring php5
--->  Building php5
--->  Staging php5 into destroot
Warning: php5 requests to install files outside the common directory structure!
--->  Deactivating php5 @5.2.9_2+apache2+macosx+mysql5+pear+postgresql82+sqlite
--->  Installing php5 @5.2.10_0+apache2+macosx+mysql5+pear+postgresql82+sqlite
--->  Activating php5 @5.2.10_0+apache2+macosx+mysql5+pear+postgresql82+sqlite
Error: Target org.macports.activate returned: Image error: /opt/local/lib/php/.registry/xml_util.reg already exists and does not belong to a registered port.  Unable to activate port php5.

最後のフェーズのactivateで失敗している。(ファイルのコピーでエラー)
-f オプションを付けて、強制的にactivateした。

$ sudo port -f activate php5 @5.2.10_0+apache2+macosx+mysql5+pear+postgresql82+sqlite
Password:
--->  Activating php5 @5.2.10_0+apache2+macosx+mysql5+pear+postgresql82+sqlite
Warning: File /opt/local/lib/php/.registry/xml_util.reg already exists.  Moving to: /opt/local/lib/php/.registry/xml_util.reg.mp_1246068080.
Warning: File /opt/local/lib/php/doc/XML_Util/examples/example.php already exists.  Moving to: /opt/local/lib/php/doc/XML_Util/examples/example.php.mp_1246068080.
Warning: File /opt/local/lib/php/doc/XML_Util/examples/example2.php already exists.  Moving to: /opt/local/lib/php/doc/XML_Util/examples/example2.php.mp_1246068080.
Warning: File /opt/local/lib/php/test/XML_Util/tests/AllTests.php already exists.  Moving to: /opt/local/lib/php/test/XML_Util/tests/AllTests.php.mp_1246068080.
Warning: File /opt/local/lib/php/test/XML_Util/tests/testBasic_apiVersion.phpt already exists.  Moving to: /opt/local/lib/php/test/XML_Util/tests/testBasic_apiVersion.phpt.mp_1246068080.
Warning: File /opt/local/lib/php/test/XML_Util/tests/testBasic_attributesToString.phpt already exists.  Moving to: /opt/local/lib/php/test/XML_Util/tests/testBasic_attributesToString.phpt.mp_1246068080.
Warning: File /opt/local/lib/php/test/XML_Util/tests/testBasic_collapseEmptyTags.phpt already exists.  Moving to: /opt/local/lib/php/test/XML_Util/tests/testBasic_collapseEmptyTags.phpt.mp_1246068080.
Warning: File /opt/local/lib/php/test/XML_Util/tests/testBasic_createCDataSection.phpt already exists.  Moving to: /opt/local/lib/php/test/XML_Util/tests/testBasic_createCDataSection.phpt.mp_1246068080.
Warning: File /opt/local/lib/php/test/XML_Util/tests/testBasic_createComment.phpt already exists.  Moving to: /opt/local/lib/php/test/XML_Util/tests/testBasic_createComment.phpt.mp_1246068080.
Warning: File /opt/local/lib/php/test/XML_Util/tests/testBasic_createEndElement.phpt already exists.  Moving to: /opt/local/lib/php/test/XML_Util/tests/testBasic_createEndElement.phpt.mp_1246068080.
Warning: File /opt/local/lib/php/test/XML_Util/tests/testBasic_createStartElement.phpt already exists.  Moving to: /opt/local/lib/php/test/XML_Util/tests/testBasic_createStartElement.phpt.mp_1246068080.
Warning: File /opt/local/lib/php/test/XML_Util/tests/testBasic_createTag.phpt already exists.  Moving to: /opt/local/lib/php/test/XML_Util/tests/testBasic_createTag.phpt.mp_1246068080.
Warning: File /opt/local/lib/php/test/XML_Util/tests/testBasic_createTagFromArray.phpt already exists.  Moving to: /opt/local/lib/php/test/XML_Util/tests/testBasic_createTagFromArray.phpt.mp_1246068080.
Warning: File /opt/local/lib/php/test/XML_Util/tests/testBasic_getDocTypeDeclaration.phpt already exists.  Moving to: /opt/local/lib/php/test/XML_Util/tests/testBasic_getDocTypeDeclaration.phpt.mp_1246068080.
Warning: File /opt/local/lib/php/test/XML_Util/tests/testBasic_getXmlDeclaration.phpt already exists.  Moving to: /opt/local/lib/php/test/XML_Util/tests/testBasic_getXmlDeclaration.phpt.mp_1246068080.
Warning: File /opt/local/lib/php/test/XML_Util/tests/testBasic_isValidName.phpt already exists.  Moving to: /opt/local/lib/php/test/XML_Util/tests/testBasic_isValidName.phpt.mp_1246068080.
Warning: File /opt/local/lib/php/test/XML_Util/tests/testBasic_raiseError.phpt already exists.  Moving to: /opt/local/lib/php/test/XML_Util/tests/testBasic_raiseError.phpt.mp_1246068080.
Warning: File /opt/local/lib/php/test/XML_Util/tests/testBasic_replaceEntities.phpt already exists.  Moving to: /opt/local/lib/php/test/XML_Util/tests/testBasic_replaceEntities.phpt.mp_1246068080.
Warning: File /opt/local/lib/php/test/XML_Util/tests/testBasic_reverseEntities.phpt already exists.  Moving to: /opt/local/lib/php/test/XML_Util/tests/testBasic_reverseEntities.phpt.mp_1246068080.
Warning: File /opt/local/lib/php/test/XML_Util/tests/testBasic_splitQualifiedName.phpt already exists.  Moving to: /opt/local/lib/php/test/XML_Util/tests/testBasic_splitQualifiedName.phpt.mp_1246068080.
Warning: File /opt/local/lib/php/test/XML_Util/tests/testBug_4950.phpt already exists.  Moving to: /opt/local/lib/php/test/XML_Util/tests/testBug_4950.phpt.mp_1246068080.
Warning: File /opt/local/lib/php/test/XML_Util/tests/testBug_5392.phpt already exists.  Moving to: /opt/local/lib/php/test/XML_Util/tests/testBug_5392.phpt.mp_1246068080.
Warning: File /opt/local/lib/php/XML/Util.php already exists.  Moving to: /opt/local/lib/php/XML/Util.php.mp_1246068080.