php in java -quercus- によるレガシーマイグレーション実例 #jjug_ccc #ccc_r12
TRANSCRIPT
PHP in Java -Quercus- による レガシーマイグレーション
山下 竜司 株式会社アットウェア/Facebook4J
#jjug_ccc #ccc_r12
PHP: Hypertext Preprocessor
http://venturebeat.com/2013/05/17/google-app-engine-finally-supports-php-the-language-that-runs-75-of-the-web/
世界一 広まっている言語
世界一 dis られている言語
Quercus
https://www.flickr.com/photos/justinwkern/6140775849/
Quercus•けっこう昔からあった
> svn log svn://svn.caucho.com/home/svn/svnroot/resin/trunk/modules/quercus
Quercus•PHP (PHP5 相当) を Java 上で動かすことを可能にする Java で実装されたエンジン
Quercus•PHP (PHP5 相当) を Java 上で動かすことを可能にする Java で実装されたエンジン
•オープンソース (ライセンスは GPL)
Quercus•PHP (PHP5 相当) を Java 上で動かすことを可能にする Java で実装されたエンジン
•オープンソース (ライセンスは GPL)
•Caucho Technology 社の商用アプリケーションサーバー Resin の一部
PHP アプリといえば WordPress
WordPress on Quercus デモ
WordPress on Quercus デモ
!
https://gist.github.com/roundrop/4977262
Links•Home
‣ http://quercus.caucho.com/
•Change Log
‣ http://caucho.com/resin-4.0/changes/changes.xtp
•ソースコード
‣ svn://svn.caucho.com/home/svn/svnroot/resin/trunk/modules/quercus
•バグトラッカー
‣ http://bugs.caucho.com/view_all_bug_page.php
見た目に反して 開発はわりとアクティブ
Quercus の構造•実体は Servlet ※Servlet 以外の起動方法もあるが割愛
[web.xml] <servlet> <servlet-name>Quercus Servlet</servlet-name> <servlet-class> com.caucho.quercus.servlet.QuercusServlet </servlet-class> <init-param> : </servlet> <servlet-mapping> <servlet-name>Quercus Servlet</servlet-name> <url-pattern>*.php</url-pattern> </servlet-mapping>
Quercus の構造
QuercusServlet
Tomcat など
xxxx.php
1. phpファイル読込 2. 解析 3. 実行
PHP コードの再現力•Quercus の PHP 再現力はかなりのもの
• (モノによるが) だいたい 90% 以上のコードは動く
•逆に言うとある程度は改修・実装が必要
‣ Quercus で未実装の PHP 関数の自前実装
‣対応していないシンタックスを調整 など
PHP コードの実行速度•速い
•素の PHP より速くなる事例もある
•キャッシュ、コネクションプールなど Java 資産が使える
•プロダクションに投入できるレベル
PHP から Java のコードを呼び出せる
•Java 上での PHP コードの実行だけでなく、PHP コードから Java のコードを呼び出せる
PHP から Java のコードを呼び出せる
•Java 上での PHP コードの実行だけでなく、PHP コードから Java のコードを呼び出せる
<?php import java.lang.System; import java.util.Date; $date = new Date(); System::out->println($date);
PHP から Java のコードを呼び出せる
QuercusServlet
Tomcat など
xxxx.php
Java コード 呼び出し
Java Class
つまり、•PHP アプリを Tomcat 等の上で動かしつつ
つまり、•PHP アプリを Tomcat 等の上で動かしつつ
•これからつくる新機能は Java で開発したり
つまり、•PHP アプリを Tomcat 等の上で動かしつつ
•これからつくる新機能は Java で開発したり
•PHP の処理の一部だけ Java に置き換えたり
つまり、•PHP アプリを Tomcat 等の上で動かしつつ
•これからつくる新機能は Java で開発したり
•PHP の処理の一部だけ Java に置き換えたり
レガシーマイグレーションに使える
Java Application Server (Tomcat など)
PHP
Java Application Server (Tomcat など)
PHPJava
(Spring等)既存機能はPHPのまま 新機能はJava
Java Application Server (Tomcat など)
PHPJava
(Spring等)既存機能はPHPのまま 新機能はJava
性能上のボトルネックだけ Java 化
Java Application Server (Tomcat など)
PHPJava
(Spring等)既存機能はPHPのまま 新機能はJava
あまりにひどい部分だけ Java 化性能上のボトルネックだけ Java 化
Java Application Server (Tomcat など)
PHPJava
(Spring等)既存機能はPHPのまま 新機能はJava
あまりにひどい部分だけ Java 化性能上のボトルネックだけ Java 化
スモールスタートしてから徐々に Java の範囲を 広げていき、最終的には PHP をなくす
まずは PHP をまともに動かす
PHP をまともに動かすまでの障壁•文字化け
•Quercus が解釈できないシンタックス
•Quercus で未対応な PHP 関数
PHP をまともに動かすまでの障壁•文字化け
•Quercus が解釈できないシンタックス
•Quercus で未対応な PHP 関数
文字化け対策•既存 PHP アプリを Quercus 上で動かしてみたところ絶望的に文字化け
文字化け対策•既存 PHP アプリを Quercus 上で動かしてみたところ絶望的に文字化け
•Java でつくる部分は UTF-8 にしたいので、全体的に UTF-8 で統一したい
文字化け対策•文字コードを UTF-8 に揃える
‣ソースコード
‣画面の charset
‣データベース
‣リソースファイル
‣:
文字化け対策•Quercus は ISO-8859-1 前提で動作する
•unicode.semantics=on
‣ on にすることで UTF-8 前提で動作するようになる
‣ php.ini に書いて Quercus に読み込ませる
文字化け対策•QuercusServlet に ini-file パラメータを指定
[WEB-INF/php.ini] unicode.semantics=on ![web.xml] <servlet> <servlet-name>Quercus Servlet</servlet-name> <servlet-class> com.caucho.quercus.servlet.QuercusServlet </servlet-class> <init-param> <param-name>ini-file</param-name> <param-value>WEB-INF/php.ini</param-value> </init-param> :
文字化け対策
QuercusServlet
Tomcat など
xxxx.php
php.iniunicode.semantics=on で動作モードを ISO-8859-1 → UTF-8 に変更
文字化け対策•文字化けの発生する可能性のある箇所
‣画面に記述した日本語の表示
‣GET/POST した日本語パラメータの表示
‣DB に入っている日本語の表示・登録
‣セッションに入れた日本語の表示
‣ファイル入力・出力
‣ログ などなど
文字化け対策•アップロードファイル名
‣最新版でも文字化け
‣ファイル名が重要な場合は commons-fileupload の実装に差し替えるなどの対応が必要
‣ com.caucho.quercus.env.Post#fillPost()
文字化け対策•UTF-8 以外でのレスポンス出力
‣ Excel 前提だから CSV ファイルのダウンロードは SJIS で、といった要件のとき
‣ “SJIS-win” → “Windows-31J”
‣ print じゃなくて echo を使うprintf("%s\r\n", mb_convert_encoding($data, "SJIS-win", "UTF-8")); ↓ echo mb_convert_encoding($data, "Windows-31J", "UTF-8")."\r\n";
PHP をまともに動かすまでの障壁•文字化け
•Quercus が解釈できないシンタックス
•Quercus で未対応な PHP 関数
解釈できないシンタックス•動的に変数名やクラス名を決定するシンタックスは Quercus が解釈できない
!
!
!
!
‣そうしないように修正するしかない
$code = "red"; $color_{$code} = "..."; //$color_red = ..
$name = "Login"; $action = new {$name}Action(); //LoginAction
PHP をまともに動かすまでの障壁•文字化け
•Quercus が解釈できないシンタックス
•Quercus で未対応な PHP 関数
未対応な PHP 関数•Quercus は未実装関数を検知した場合、
UnimplementedException を投げる
•特にマルチバイト系の関数(mb_****) の未実装 or 不備が多い
‣ mb_convert_kana : 未実装
‣ mb_send_mail : 不備
‣ mb_encode_numericentity : 未実装 などなど
未対応な PHP 関数•PHP 関数については同じ名前のメソッドがあるのでわりとわかりやすい
未対応な PHP 関数
•以下のいずれかで対応
‣自力で Quercus を改変してビルド
‣ AOP でひっかけて実装
‣ PHP の呼び出し箇所を Java 化する
PHP と Java を
結合してみる
PHP - Java 結合のポイント•セッション情報
‣ Quercus - JavaEE 間セッション情報共有
‣セッションタイムアウト設定
•PHP ファイルの配置方法
•ビューレイアウト共有
PHP - Java 結合のポイント•セッション情報
‣ Quercus - JavaEE 間セッション情報共有
‣セッションタイムアウト設定
•PHP ファイルの配置方法
•ビューレイアウト共有
Tomcat など
PHP Java
PHP 5.2 Spring など
$_SESSION
• Quercus を使っても、 PHP のセッションと Java のセッションは別空間 になる !
HttpSession
Tomcat など
PHP Java
PHP 5.2 Spring など
$_SESSION
• Quercus を使っても、 PHP のセッションと Java のセッションは別空間 になる • なんらかの方法で、2つの空間を同期する仕組みが必要
HttpSession
Quercus-JavaEE セッション情報共有
•Quercus から HttpSession は参照できる
•PHP の auto_prepend 及び auto_append のしくみが Quercus でも使えるのでこれで同期
‣ auto_prepend‣ Java(HttpSession) → PHP($_SESSION)
‣ auto_append‣ PHP($_SESSION) → Java(HttpSession)
auto_prepend/auto_append で同期
QuercusServlet
Tomcat など
xxxx.php
php.ini
auto_prepend.php
auto_prepend/auto_append で同期
QuercusServlet
Tomcat など
xxxx.php
php.iniauto_append.php
PHP($_SESSION) → Java(HttpSession)
Java(HttpSession) → PHP($_SESSION)
auto_prepend/auto_append で同期
•php.ini に以下のように記述auto_prepend_file=/path/to/before.php auto_append_file=/path/to/after.php
[before.php: Java -> PHP] <?php session_start(); $request = quercus_servlet_request(); $session = $request->getSession(); $keys = $session->getAttributeNames(); while ($keys->hasMoreElements()) { $key = $keys->nextElement(); $value = $session->getAttribute($key); $_SESSION[$key] = $value; }
[after.php: PHP -> Java] <?php session_start(); $request = quercus_servlet_request(); $session = $request->getSession(); foreach ($_SESSION as $key => $value) { $session->setAttribute($key, $value); }
Tomcat など
PHP Java
Quercus Spring など
$_SESSION
• session-config ‣ HttpSession にのみ適用される
!!!!
HttpSession
Tomcat など
PHP Java
Quercus Spring など
$_SESSION
• session-config ‣ HttpSession にのみ適用される
• Quercus 管理の PHP セッション ‣ デフォルト 30 分で変更する手段なし? ‣ リフレクションで変更して Quercus がもっているタイマーを起動すればなんとかできる
HttpSession
セッションタイムアウト•アプリケーション起動時に 1 回 Quercus がもっているセッション管理のタイマーを起動すれば OK
•コードは長いので Gist に書きました
‣ https://gist.github.com/roundrop/96edc5f4d7135e60a2d0
PHP - Java 結合のポイント•セッション情報
‣ Quercus - JavaEE 間セッション情報共有
‣セッションタイムアウト設定
•PHP ファイルの配置方法
•ビューレイアウト共有
PHP ファイルの配置既存 PHP
Apache
htdocs (docルート)
include
機能A
index.phpいろいろ汚いの
機能B
PHP ファイルの配置既存 PHP
Apache
htdocs (docルート)
include
機能A
index.phpいろいろ汚いの
Java アプリ
web ルート
WEB-INF
機能B
PHP ファイルの配置既存 PHP
Apache
htdocs (docルート)
include
機能A
index.phpいろいろ汚いの
Java アプリ
web ルート
WEB-INF
機能B
PHP ファイルの配置既存 PHP
Apache
htdocs (docルート)
include
機能A
index.phpいろいろ汚いの
Java アプリ
web ルート
WEB-INF
機能A
index.phpいろいろ汚いの
機能B 機能B
include
ドキュメントルートをwebルートとして配置
PHP ファイルの配置既存 PHP
Apache
htdocs (docルート)
include
機能A
index.phpいろいろ汚いの
Java アプリ
web ルート
WEB-INF
機能A
index.phpいろいろ汚いの
機能B 機能B
include
ドキュメントルートをwebルートとして配置
PHP ファイルの配置既存 PHP
Apache
htdocs (docルート)
include
機能A
index.phpいろいろ汚いの
Java アプリ
web ルート
WEB-INF
機能A
index.phpいろいろ汚いの
機能B 機能B
include
ドキュメントルートをwebルートとして配置web ルートがきたなくなる 一部階層関係がかわってしまう (この例だと htdocs と include)
PHP ファイルの配置•階層関係重要
•こういうのありがち
!
!
!
!
•階層関係は変えないのが望ましい
htdocs/sub/hoge.php !<?php define('_ROOT', "../.."); define('_INCLUDE', _ROOT."/include"); :
PHP ファイルの配置既存 PHP
Apache
htdocs (docルート)
include
機能A
index.phpいろいろ汚いの
Java アプリ
web ルート
WEB-INF
機能B
PHP ファイルの配置既存 PHP
Apache
htdocs (docルート)
include
機能A
index.phpいろいろ汚いの
Java アプリ
web ルート
WEB-INF
機能B
phphtdocs
機能A
index.phpいろいろ汚いの
機能B
include
1つのディレクトリにそのままの構成で配置
Web サーバーで *.php を /php/htdocs へプロキシーする
PHP ファイルの配置•auto_prepend でサーバー変数を調整$_SERVER['SCRIPT_NAME'] = str_replace("/php/htdocs", "", $_SERVER['SCRIPT_NAME']); $_SERVER['SCRIPT_URL'] = str_replace("/php/htdocs", "", $_SERVER['SCRIPT_URL']); $_SERVER['REQUEST_URI'] = str_replace("/php/htdocs", "", $_SERVER['REQUEST_URI']); $_SERVER['PHP_SELF'] = str_replace("/php/htdocs", "", $_SERVER['PHP_SELF']); $_SERVER['DOCUMENT_ROOT'] = $_SERVER['DOCUMENT_ROOT']."php/htdocs/";
PHP - Java 結合のポイント•セッション情報
‣ Quercus - JavaEE 間セッション情報共有
‣セッションタイムアウト設定
•PHP ファイルの配置方法
•ビューテンプレート共有
ビューテンプレート共有•PHP と Java の両方が動いているとはいえ、サイトとしては 1 つで、デザインも同じ
‣サイトのヘッダーやフッターなどの部品は PHP と Java で共有したい
‣ Java のテンプレートエンジンでデザインして、PHP でもそれを使うのが理想
ビューテンプレート共有<?php : include_once 'header.php'; : [コンテンツ] :
<header> デザインした内容 :
: : <header th:include="header... : [コンテンツ] :
<header> デザインした内容 :
PHP Java (Thymeleaf)
header.php header.html
ビューテンプレート共有<?php : include_once 'header.php'; : [コンテンツ] :
<header> デザインした内容 :
: : <header th:include="header... : [コンテンツ] :
<header> デザインした内容 :
PHP Java (Thymeleaf)
header.php header.html
デザインテンプレートが二重管理 DRY じゃない
ビューテンプレート共有<?php : include_once 'header.php'; : [コンテンツ] :
: : <header th:include="header... : [コンテンツ] :
PHP Java (Thymeleaf)
<header> デザインした内容 :
<?php import aa.bb.XxxReader; print XxxReader::readHeader();
header.php header.html
: : <header th:include="header... : [コンテンツ] :
ビューテンプレート共有<?php : include_once 'header.php'; : [コンテンツ] :
PHP Java (Thymeleaf)
<header> デザインした内容 :
<?php import aa.bb.XxxReader; print XxxReader::readHeader();
header.php header.html
PHP では ・Java テンプレートエンジンの html ファイルを読み込み ・テンプレートエンジン特有のアトリビュート(th: 等)を削除 ・static 変数にキャッシュするなど
その他
その他•PHP の一部を Java に置き換える
•ロギング
•コネクションプーリング
その他•PHP の一部を Java に置き換える
•ロギング
•コネクションプーリング
PHP の一部を Java に置き換える•PHP から Java を扱う
‣ http://quercus.caucho.com/quercus-3.1/doc/quercus.xtp#JavaPHPintegration
• import して使う方法
!
!
•new Java(“…”) する方法
<?php import java.util.Date; $a = new Date(123);
<?php $a = new Java("java.util.Date", 123);
: <?php if ($user->is_premium) { : ?> <div><?php echo $user->name; ?></div> <div>ここに表示項目を追加したい</div> :
PHP の一部を Java に置き換える
PHP の一部を Java に置き換える: <?php if ($user->is_premium) { : ?> <div><?php echo $user->name; ?></div> <?php import com.example.helper.StatusHelper; ?> <div><?php echo StatusHelper::getLabel($user->status); ?></div> :
PHP の一部を Java に置き換える•Java の型 → PHP の型 の変換ルール‣ http://quercus.caucho.com/quercus-3.1/doc/
quercus.xtp#MarshallingPHPtoJavaconversions
‣ 例えば java.util.List は array になる
<?php import com.example.php.ContactLogic; $logic = new ContactLogic(); $recents = $logic->getRecents(); foreach ($recents as $contact) { $from = $contact["from"];
public final class ContactLogic { : public List<Contact> getRecents() { :
その他•PHP の一部を Java に置き換える
•ロギング
•コネクションプーリング
ロギング•Quercus 内部では java.util.logging が使われている
•error_log 関数については、ログタイプ=0 の場合、error_log ディレクティブに “syslog” を指定すると java.util.logging.Logger で出力される
!
!
• jul-to-slf4j を使って SLF4J に集約するとよい
<?php ini_set("error_log", "syslog"); error_log("メッセージ", 0);
その他•PHP の一部を Java に置き換える
•ロギング
•コネクションプーリング
コネクションプーリング•Quercus ならではの強力機能!
•mysql_connect(), pg_connect() や PDO で JNDI から接続をとってきて使用できる
‣ $con = pg_connect(“java:comp/env/jdbc/mydb”);
‣ $pdo = new PDO(“pgsql:java:comp/env/jdbc/mydb”);※PDO の場合は先頭に「データベース名:」が必要なので注意
コネクションプーリング•Web サーバーのContext に jdbc リソースを追加
•web.xml に以下を記述<servlet> <servlet-name>quercusServlet</servlet-name> <servlet-class> com.caucho.quercus.servlet.QuercusServlet </servlet-class> : <init-param> <param-name>database</param-name> <param-value>jdbc/mydb</param-value> </init-param> :
Maven 形式 / JDK 7 でビルド可能な Quercus を GitHub に置いています
https://github.com/roundrop/quercus!
(サンプルコードもそのうち GitHub に置きます)
まとめ•Quercus けっこううまく動く
•でも完璧ではないので、プロダクションレベルで適用するには多少の労力をかける必要はある
•うまくハマれば「段階的に」PHP→Java へ進化させられる
ありがとうございました