こんにちは、PR TIMESの開発本部でインターンをしている三宅です。インターンではレガシーなPHPのコードと向き合い続けながらプロダクト開発とそれを支える技術について学んでいます。今回は、インターン中に取り組んだPR TIMESで使われているSmartyのバージョンアップを行ったことについて書いていきます。
なぜ、Smartyのバージョンアップを行ったのか
当時PR TIMESで使われていたSmartyのバージョンが2系が使用されていました。しかし、Smarty2.xのメンテナンスは終了しており、PR TIMES全体で取り組んでいるPHPのバージョンアップにおいても大きな問題が発生していました。
SmartyはフロントエンドのReactリプレイスなどで置き換わった部分を除いて、PR TIMESほぼ全ての画面で使用されています。しかし、影響範囲が大きいためか長い間バージョンアップが放置されていました。さらに、使途不明の独自プラグインやSmartyのクラスを継承した独自クラスがあるなどメンテナンス生が低下している状態でした。そこで、Smarty周りのコードのリファクタリングとライブラリのバージョンアップを行うことにしました。
Smartyのバージョンアップにどう取り組んだのか?
最終的な影響範囲がPR TIMESのほぼ全画面なので、いきなり全てを修正してしまうとQAの負荷が高まりリリースに失敗する可能性も非常に高まります。また、スタイル崩れなどが起きてしまうとお客様にも迷惑を被ってしまいます。
そこでSmartyのバージョンアップは大きく2つのフェーズに分けて進めていきました。
- Smartyのcomposer管理と独自クラスのリファクタリング
- 使用されていないテンプレートやプラグインの整理
- Smartyのバージョンアップ
phase1: Smarty周りのリファクタリング
Smartyのリファクタリングでは独自クラスの削除とcomposer移行を行いました。元々はSmartyのライブラリのコードがそのまま参照されており、さらに一部コードが変更されている形跡もありました。
Smartyクラスに加えられた差分を確認・置き換える
Smartyをcomposerで管理するため、クラス側に加えられた差分を実態に合わせて、プラグインに置き換えるなどの対応を進めていきました。基本的にはコミットログを遡って、差分をプラグインに追い出すかデッドコードとして処理するかのいずれかでした。しかし、PR TIMESのソースコードはgitでの管理が行われる以前から開発されていたため、ある時点を境に以前の変更はよく分からない状態でした。そのため、社内で使われていたライブラリと同じバージョンのコードを取得し差分を取って変更がないかをチェックしました。結果的に、ロジック部分に差分はなかったので問題ないと判断できました。

Smarty_extを消し込む
Smarty_extはその名の通り、オリジナルのSmartyを継承したクラスです。といいつつ、歴史的な経緯で実態としてはSmartyのエイリアス的な用途で用いられていました。こちらは元のSmartyに置き換えて削除しました。
Smartyのインスタンス作成用のヘルパーを実装する
Smarty_extが継承されていた理由の大半はコンストラクタ内で独自プラグインを登録するためでした。本来であればSmartyのインスタンスを作成してメンバ関数を呼べば良いですが、Smartyインスタンスがさまざまな場所で作成されているためSmartyHelperというヘルパーを実装しました。SmartyHelperでは複数箇所に存在するSmaryのインスタンスに対して同一の設定を反映するインスタンス初期化用の関数と関連処理をまとめたものです。initSmartyという関数でSmartyに対して必要な設定とプラグインの反映を行います。また、独自プラグインもSmartyHelper内に全て移しました。
// SmartyHelper.php
function initSmarty(Smarty &$smarty)
{
$smarty->template_dir = CURRENT_DIR . 'smarty_tmp/';
$smarty->compile_dir = CURRENT_DIR . 'smarty_tmpc/';
$smarty->config_dir = CURRENT_DIR . 'smarty_configs/';
$smarty->cache_dir = dirname(CURRENT_DIR) . '/smarty_cache/';
$smarty->plugins_dir[] = __DIR__. "/prtimes_smarty_plugins";
$smarty->php_handling = SMARTY_PHP_PASSTHRU;
$smarty->left_delimiter = "<{";
$smarty->right_delimiter = "}>";
$smarty->register_function("concat_assign", "smarty_compiler_concat_assign");
$smarty->register_function("ri", "smarty_function_ri");
$smarty->register_modifier("sorturi", "smarty_modifier_sort");
$smarty->register_modifier("_releasedate", "_releasedate");
$smarty->register_modifier("number_format", "number_format");
$smarty->register_function("zip_format", "zip_format");
$smarty->register_modifier("debug_data", "debug");
return $smarty;
これらのリファクタリングが完了し、ライブラリもcomposerに持っていくことで大掃除できました!33352行の削減はびっくりしました(笑)

phase2: 使用されていないプラグイン・テンプレートの削除
Smartyのバージョンアップにあたって、いくつかのプラグインがPHPやSmartyの都合上動作できなくなることを確認していました。これらを闇雲に全てリファクタリングすると工数が増えてしまうので、使われていない物を消し込み影響範囲を小さくしようと試みました。
プラグインは静的解析で明らかに使用されていない物は削除しました。それ以外のデッドコードもリファクタリングしていきました。使われていないテンプレートも静的解析で当てをつけた上でtrigger_errorでE_USER_NOTICEを仕込み、1週間程度ステージングのメトリクスで調査を行なっていきました。

流石に、全てのテンプレートにtrigger_errorを仕込んで利用調査をしようとしたら止められました😅
phase3: Smartyのバージョンアップ
Smartyのバージョンアップでは主に3つの対応を行いました。
<{ if …}>
のような構文先頭のホワイトスペースの消し込み- メソッド名や引数が変わったので置き換える
- プライベート変数をgetter,setterで呼び出すように修正
これらの修正の影響範囲が100ファイル以上に及んでレビュー負荷が高くなるため、少しでもレビュー負荷を低減し、リリース成功率を上げるための工夫をしました。例えば、ライブラリのバージョンを上げなくても修正できる部分は先にリリースをして、ライブラリのバージョンアップに関わる修正範囲が極力小さくなるようにしました。具体的には、ホワイトスペースの削除は元のバージョンのSmartyでも互換がある修正なので、先にこちらの対応を行いリリースを行いました。
リリースの都合などで何度かリリースに失敗しましたが、4ヶ月以上に及ぶ対応の末、ついにリリースに成功しました!

Smartyのバージョンアップで詰まったところ
Smartyのバージョンアップで詰まったところと向き合い方について軽く紹介します。
1. 他の作業とコンフリクトが多発した
Smartyがコアを担う機能だけあって、他の作業の差分とコンフリクトすることが多く修正作業に時間を取られました。特にリファクタリング系のタスクとコンフリクトすることが多く、それが原因で何回かリリースに失敗しました。
当時、影響範囲の大きなリリースがリファクタリングデーにしかすることができなかったため、一度失敗して次の機会に回しても同じような結果になってしまいます。これについては、何度もリリーストライする中でPRをすぐに作れるようになったのでリリース日にPRを作成することでリリースに成功しました。

2. 使っていないと思っていたものが使われていた
Smartyテンプレートやプラグインにもtrigger_errorを仕込んだり静的解析で利用調査を進めていましたが、いくつかのプラグインで確認漏れがありました。静的解析や文字列検索などで使われていなさそうなテンプレートの削除を行ったところ、実は動的に呼び出しファイルが指定されていたためエラーなるなどのトラップに引っかかることがありました。Smartyのプラグインがテンプレート側から呼び出されていたため、静的解析にで引っかからないなど原因は様々でしたが苦労しました。
不要テンプレートやプラグインはデッドコードなので将来的には削除する必要があります。Smartyのバージョンアップ調査を通じて自分が調査したものに関しては、ドキュメントやソースコードに使われている/いないのメモを残すようにしました。その後のリファクタリングで大量のテンプレートが削除されましたが、多少なりとも私のレポーティングが役に立っていたら嬉しいです。
まとめ
今回はPR TIMESのほぼ全箇所で使われているSmartyのバージョンアップを行ったことについて紹介しました。私はこれも含めて幾つかのライブラリのバージョンアップを担当しましたが、Smartyのバージョンアップで、PHPのバージョンアップのブロッキング材料は全てなくなりました。これでPR TIMESをさらに前進させられるようになりました。その仕事の一部を担えて、数年間放置されていたバージョンアップにメスを入れられたのは嬉しかったです。
最後に
レガシーコードのリファクタリングでは「きちんと調査をする」「影響範囲などを考えて筋の良い直し方を考える」「地道に実装する」という基本動作がとても大事だということを改めて学ぶことができました。基本に忠実に地道にやっていけばどれほど大きな作業であってもそれをやり切ってサービスを改善できることを実際に経験することができ、貴重なインターン経験になりました。
これからもレガシーと戦い続けてサービスがさらに成長できるように貢献していきたいと思います。