携帯(ガラケー)サイトで、PHP5.2以下から5.3以上へのバージョンアップ時にmb_output_handlerを使った文字コード変換でハマった時のメモ
前回のエントリでも触れたけど、とある携帯(ガラケー)サイトをPHP5.2から5.4にバージョンアップ対応していた時にまたハマった。※文字多めのエントリ
ガラケーといえば、3キャリア対応するためにShift_JISでページを作るのは鉄板。やり方はいろいろあるけど、このサイトはmb_output_handler
で文字コード変換していた。mb_output_handler
はざっくり言うと、ob_start
でバッファリングした出力をmbstring.internal_encoding
で指定した文字コードから、mbstring.http_output
で指定した文字コードによろしく変換してくれるというすぐれものだ。
ただ、このmb_output_handler
には罠があって、
http://php.net/manual/ja/function.mb-output-handler.php
にも書かれている通り、
デフォルトの MIME 型が text/ で始まる
という条件で発動するらしい。
で、今回何にハマったのかというと、
このサイトではapache側でphp_valueを使って、mbstring.internal_encoding
がEUC-JP、mbstring.http_output
がShift_JISで設定されていた。でもってhtmlのテンプレートは基本EUC-JPで書かれていて、それをtext/html
で出力していたので、mb_output_handler
が効いていたために、特に問題なくShift_JISに変換されて表示されていたのだけど、何故か一部のページが文字化けしてしまっていた。PHP5.2の環境だと問題なく表示されるのに。
よくよく調べてみると、文字化けしていたページはなぜかはわからないけどapplication/xhtml+xml
が使われていてShift_JISで書かれていた。(たぶん何かの装飾をしたかったのだろう)
ただ、mb_output_handler
の仕様上、mimeタイプがtext/
でなければ、文字コード変換は行われず、普通にShift_JISとして表示されるはずだし、実際PHP5.2の環境では問題なく表示できていた。
ではなぜ文字化けが起きたのか。
PHP5.3からはmbstring.http_output_conv_mimetypes
という設定が追加されている。
http://php.net/manual/ja/migration53.ini.php
ここでも書かれている通り、
このディレクティブは mb_output_handler() 関数が呼び出された場合に使う Content-Type の正規表現パターンを指定します。
要はmbstring.http_output_conv_mimetypes
にmb_output_handler
で変換対象にしたいContent-Typeの正規表現を書くことで、text/
以外でも変換できるとのこと。
で、mbstring.http_output_conv_mimetypes
のデフォルト値が^(text/|application/xhtml\+xml)
だったために、今まで変換されないはずであったapplication/xhtml+xml
で出力していたShift_JISのページがmb_output_handler
で変換されてしまい文字化けてしまっていたのだった。(Shift_JISのページをEUC-JPとして解釈してShift_JISに変換していた)
対応としては、application/xhtml+xml
のページをEUC-JPにするなどを考えたけど、影響範囲を調査するのが手間だったので、auto_prepend_fileで起動されるbootstrap的なphpでmbstring.http_output_conv_mimetypes
の正規表現をtext/
でのみ文字コード変換されるように変更することにした。
<?php ini_set('mbstring.http_output_conv_mimetypes', 'text/*');
相変わらず設定値にはハマりますね。
ちなみに
http://php.net/manual/ja/migration53.ini.php
ではmbstring.http_output_conv_mimetype
と書かれているけど、実際にはmbstring.http_output_conv_mimetypesのようにs
を付けないと動かない。(もしかしたら5.3では動いたのかもしれない)
PHP5.2以下から5.3以上へのバージョンアップ時にcookie以外の手段でセッションIDを保持する場合のメモ
とある携帯(ガラケー)サイトのPHPを5.2から5.4にアップデートしようとしてハマった。
携帯サイトではcookieを使えない場合もあるので、(セキュリティ的に良いか悪いかは別にして)URLにセッションIDを付ける場合はわりと多いのではないだろうか?
で、試しに5.4で動かしてみたらセッションがうまく引き継がれず、ログイン状態を維持できない場面に遭遇した。PHPのバージョンアップでセッション周りの動きが変わったのかと思い、殆ど触ったことのないプログラムをデバッグしまくって、原因箇所を特定しようと躍起になってみたものの全くわからない。コードを追うのを諦めてphpマニュアルを見ているとそれらしきものを発見。(というかビンゴ)
http://www.php.net/manual/ja/session.configuration.php#ini.session.use-only-cookies
どうやらPHP5.3からsession.user_only_cookies
のデフォルト値がオンになったみたい。すなわちcookie以外でのセッションIDの保持がデフォルトではできなくなってしまったようだ。このせいでURLにくっつけたセッションIDは使われなくなり、アプリケーション側はログオフ状態になってしまっていたようだ。
対策としては
php.ini
で
session.use_only_cookies = 0
とするか
.htaccess
で
php_value session.use_only_cookies Off
とするか phpで
<?php ini_set('session.use_only_cookies', 0);
とすれば良い。
PHPのバージョンアップは、言語の動きというよりも、設定のデフォルト値が変わってしまうことでハマることが多い気がする。
[ssh] ssh経由で他のサーバのスクリプトを実行するときに.bash_profileを読ませる方法
書きなぐりメモ。
ssh経由で何かのスクリプトを実行したい時は
$ ssh <hostname> "/usr/bin/perl /path/to/hoge.pl"
という感じで実行できるけど、~/.bash_profile
とかに書かれた環境変数を使って処理をするスクリプトだと、sshの非対話モードでは~/.bash_profile
を読んでくれないので、困ってしまう。
そんなときは
$ ssh <hostname> "source ~/.bash_profile; /usr/bin/perl /path/to/hoge.pl"
のように明示的に読ませれば良いらしい。
ちなみに
sshdの設定でPermitUserEnvironment=yes
を設定して、~/.ssh/environment
というファイルに環境変数を設定するやり方もあるようだけど、必ずしもssh経由で実行するものではなく、対象のサーバで直接実行したい場合もあるので、~/.bash_profile
と~/.ssh/environment
の両方に設定するのが冗長なので今回は上記のやり方にした。
composerでSmarty2をインストールする
composerでsmartyを入れる時は
{ "require": { "smarty/smarty": "*" } }
とやるとインストールできるのだけど、これだとSmarty3が入ってしまう。でも今の環境ではSmarty2が使われているので2を入れたい(バージョンアップしたらいいがなという話は置いといて)。いろいろ調べてみたけどどうやらSmarty2はcomposerに対応していないようなので、packageを使って個別に入れることにする。
{ "repositories":[ { "type": "package", "package": { "name": "smarty/smarty2", "version": "2.6.28", "dist": { "url": "http://www.smarty.net/files/Smarty-2.6.28.tar.gz", "type": "tar" }, "include-path": [ "libs/" ] } } ], "require": { "smarty/smarty2": "*" } }
nameは適当。あとは
$ php composer.phar install
でインストールし、
<?php require 'vendor/autoload.php'; require 'Smarty.class.php'; $smarty = new Smarty();
という感じで使える。
composer.jsonの中でinclude-path
という項目を設定している。これが何かというと、composerではvendor/autoload.php
をrequireするとcomposerで使うためのinclude_pathを設定してくれる。これはvendor/composer/include_paths.php
の内容が設定されるようだ。composerに対応しているライブラリであれば、このinclude_paths.php
をよろしく編集してくれるので、特にこまらないのだけど、今回のSmarty2のようにpackageを使って入れたりするとinclude_pathを設定してくれないので、そのための設定。ちなみに上記のようにinclude-path
を設定すると
<?php // include_paths.php @generated by Composer $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( $vendorDir . '/smarty/smarty2/libs', );
という感じでinclude_paths.php
を更新してくれる。
Smarty.class.php
はvendor/smarty/smarty2/libs/Smarty.class.php
にいるので、"include-path": ["libs/"]
を書いておくとvendor/smarty/smarty2/libs/
までinclude_pathが通るので、
<?php require 'Smarty.class.php';
で使えるわけだ。
ちなみに、require自体したくない場合は
{ "repositories":[ { "type": "package", "package": { "name": "smarty/smarty2", "version": "2.6.28", "dist": { "url": "http://www.smarty.net/files/Smarty-2.6.28.tar.gz", "type": "tar" }, "autoload": { "files": ["libs/Smarty.class.php"] }, "include-path": [ "libs/" ] } } ], "require": { "smarty/smarty2": "*" } }
という感じでautoload
要素を追加しておけば良い。
composerでライブラリのインストールディレクトリを変える
composerはphpのライブラリ管理ツールだ。改めて説明する必要はないくらいだけど、rubyのbundlerとかperlのcartonみたいなもんだ。
composerのざっくりとしたディレクトリ構成は
current_dir ├ composer.phar ├ composer.json ├ composer.lock └ vendor ├ autoload.php ├ composer ├ ・・・ ├ <ライブラリA> └ <ライブラリB>
こんな感じになっていて
[current_dir]$ php composer.phar install<or update>
などでライブラリをインストールするとvenderディレクトリにインストールされる。
で、このvendorディレクトリを別の名前にしたかったので、いろいろ調べてみたがそれらしい情報に出会えなかったので、composerのソースをみて試してみた。
結論としてcomposer.jsonに以下の設定をしたらインストール先のディレクトリ名を変えれた。
{ "config": { "vendor-dir": "my_library" }, ... }
このようにconfigにvendor-dirに任意のディレクトリ名を設定できるようだ。
他にも
https://github.com/composer/composer/blob/master/src/Composer/Config.php
この辺りのソースを見るとなにやらいろいろ設定できそう。
追記
よくよくcomposerのドキュメントをみるときちんと書いてあって残念な気分になりました。。。ドキュメントはきちんと読みましょう。
perlがスクリプトをどのように解釈しているのかを知るためのB::Deparseなるものを知った
僕のperlの先生monmonさんに今日もまた教えてもらった。
perlがどのようにスクリプトを解釈しているのかを事前に知ることのできるB::Deparseというものがあるらしい。
↓のサイトを参考にさせていただいた。
http://www.g-ishihara.com/perl/compiler01.html
B::Deparseってのは、perlスクリプトをコンパイルして、そこから更にソースコードを生成(デコンパイル?)するらしい。なのでperlがどのようにそのスクリプトを解釈しているのかってのがわかるとか。
ちなみにBモジュールってのはperlのコンパイラ系のモジュールで、Oモジュールってのはそのモジュールを操作するためのインターフェースみたいなものとのこと。
ではやってみる。
$ perl -MO=Deparse -e 'use constant HOGE => 1;print HOGE;' use constant ('HOGE', 1); print 1; -e syntax OK
HOGEが展開されてprint 1となっている。
↓はmonmonさんに教えてもらった内容。ループの中で何かの計算をして$xに値を入れている。ループのたびに計算して効率の悪そうな感じなのだけど、Deparseすると、コンパイル時点で既に計算されているので、そんなに効率は悪くないことがわかる。
$ perl -MO=Deparse -e 'for my $i (0..100){my $x = 60*60*24;}' foreach my $i (0 .. 100) { my $x = 86400; } -e syntax OK
ちなみにこのforはリストコンテキストなので、foreachになっていることもわかった。
あと面白いのが↓
$ perl -MO=Deparse -e 'use constant DEBUG => 1;if(DEBUG){print "debug";} print "done."' use constant ('DEBUG', 1); do { print 'debug' }; print 'done.'; -e syntax OK
よくあるデバッグフラグみたいなものを定数に入れていて、if文で判定してデバッグモードだったらほげほげみたいな処理。
これはif文の判定すらなくなっている。
こんな感じでコンパイル具合がわかるので、これからはちょいちょい使ってみたい。
ORA-12154: TNS: 指定された接続識別子を解決できませんでした
sqlplusなどでoracle clientを使ってoracleにアクセスするときによく出るアレ。これがでるとなんだか憂鬱。 たぶんネットに溢れかえっているであろう内容だけど、備忘録。
$ sqlplus <ユーザーID>/<パスワード>@<SID> SQL*Plus: Release 11.2 Production on 月 9月 30 20:58:17 2013 Copyright (c) 1982, 2010, Oracle. All rights reserved. ERROR: ORA-12154: TNS: 指定された接続識別子を解決できませんでした ユーザー名を入力してください:
今回は、tnsnames.oraを読み込めていないのが原因だった。oracleはtnsnames.oraはどのパスで見つけに行くのか。
とのこと。 今回はtnsnames.oraが
$ORACLE_HOME/lib/network/admin/tnsnames.ora
となっていたのとTNS_ADMINが設定されていなかったのが原因。なぜこのパスになってしまったのかはわからないけど、
とりあえずこのエラーが出た時に少し萎えなくなれた気がする。