タグ別アーカイブ: PHP

CodeIgniter 3.x で作ったWebアプリを更に高速化させていく

この記事は CodeIgniter Advent Calendar 2017 の 12月16日 分の投稿です。

CodeIgniter は元々軽量・軽快で高速なフレームワークですが、今時の高速化トレンドに対応して更に速くしていきましょう。

PHP 7に対応する

PHP 7.0 でも 7.1 でも 7.2 でも動きます!
もちろん PHP OPcache も有効にしておきましょう。更に速くなります。

gzip圧縮にする

Googleのアドバイスツールでも指摘してくるやつですね。CodeIgniter、というよりはPHPの標準的な機能になるのですが、以下のコード(冒頭4行)をビューのアウトプットをする前に入れておくだけでPHPの透過的なgzip圧縮が有効になります。簡単。

// 透過的な圧縮を有効にする
if (ini_get('zlib.output_compression') != 'On') {
	ini_set('zlib.output_compression', 'On');
}

$this->load->library('Twig');
$data = [
	'title' => 'XXXXXXX',
	'hoge' => $hoge,
	'foo' => $foo,
	'bar' => $bar
];
$this->output->set_output($this->twig->render('dir1/example', $data));

※このコードではビューの組み立てにTwigを使っています。

注意点として、画像であったりZIPファイルをバイナリで返したりであるような際に透過的な圧縮が有効になっているとファイルが壊れてしまったりします。そういう出力をするURLが有る場合は、全てのURLで常に適用されるような書き方は避けるのが無難です。

http/2対応にする

これはWebサーバ側の話になるのでもちろん CodeIgniter でも対応可能です。http/2 に対応したバージョンの Apache 2.4 or Nginx (h2oとかOpenLightSpeedとかでも)と php-fpm を組み合わせてあげましょう。Apache を http/2 に対応する場合、prefork MPMではなくevent MPM(やworker MPM)にする事が必要になりますので、その辺りも適宜チューニングしていきましょう。

参考: Amazon Linux AMI 2017.09で今こそApacheをhttp/2対応にする手順

APIのバックエンドとして同時に叩かれる

CodeIgniter 3は同じクライアントから同時に複数URLを叩かれるような場合に、同時に処理せず逐次処理のような挙動をします。これはPHPのセッションが有効になっている際の制限なのですが、セッションの機能を使い終わったタイミングでセッションを書き込み・クローズさせてあげる事で並列度が上がります。

// 同ユーザー・セッションからの連続アクセスの為にセッションのロックを解除する。
// これを実行した後はセッション関連の機能が使えない事に注意。
session_write_close();

そもそもAPIでセッションを利用しているのがよくないという話も有りますが、必要な場合にはこの対策をどうぞ。

Redisでキャッシュとかも使っていきたい

Memcachedとか、AWSのElastiCacheとか。CodeIgniterにはそれらを利用するための機能が標準で用意されています。……といっても私自身は php-pecl-redis で 直接 Redis を扱ってしまっているのでCodeIgniter側の機能は使った事が無いのですが。ちょっと重めの外部APIから値を取得しているような場合など、適用できそうな場所はどんどん Redis でキャッシュしましょう。

最新のMySQLやPostgreSQL使いたいよね

MySQLにしてもPostgreSQLにしてもバージョンが上がるたびに様々な改善が有り、高速化が有り、新機能が有ります。MySQL 8.0にも更なる期待が持てるこの頃。

CodeIgniterには重厚なORマッパーが有りません。QueryBuilderは本当にクエリのビルダですし、生のSQLを直接実行する事も容易です。その為、PostgreSQLやMySQL 8.0のウィンドウ関数、CTEであったり、JSON型、配列型など、RDBの強力な機能を積極的に使っていけます。実際、PostgreSQLの配列型をタグの実装として使っていてなかなか便利だったりします。

参考(外部): タグ検索するならPostgreSQLで決まり! – yohgaki’s blog

配列型はCodeIgniterからは「配列っぽい文字列」として扱う事になるので、適当な変換関数を作ってオブジェクトだったり連想配列だったりでやり取り出来るようにすると便利です。

RDBの冗長化はAmazon RDSが便利だよね

といっても一応フェイルオーバーには数十秒かかります。以下の過去記事で「数秒間のDB接続不可に耐えるための方法」をまとめてあります。

参考: CodeigniterのDB接続不可をハンドリングして再接続する

また、CodeIgniterのデータベース接続には「1つ目の接続先が繋がらなかった場合に2つ目の接続先に繋ぐ」という機能が有ります。これはまだ活用した事が無いのですが、MariaDB の Galera Cluster であったり、PostgreSQL の BDR (Bi-Directional Replication) など、これからのマルチマスター時代のフェイルオーバーに容易に対応出来ちゃったりするのでしょうか。出来るのかは分かりませんが、試してみると面白いかもしれません。

終わり

他にもいろいろあるかもしれませんがこの辺で。フロントエンドだと AMP 、 ServiceWorker など色々な話題が有ります(その辺も勉強しないとなぁ)が、CodeIgniterも高速化していきましょう。


CodeIgniter 3.xのログ出力をもっと便利にする

この記事は CodeIgniter Advent Calendar 2017 の 12月15日 分の投稿です。
14日の記事は @NEKOGET さんの「CodeIgniter3のチュートリアルをリファクタリング(1)」でした。

CodeIgniter の良い所はやろうと思った事がほぼ素のPHPの知識だけでも実装してしまう事。誰でもさくっと作れてしまうので、小ネタ的な物はあまり見かけないような気がします。有っても数年前にほぼ同じような事を書いたブログ記事がもう有ったり。でも、ブログはいつか消える物。というわけで今回は CodeIgniter のログ出力をちょっと便利にしてみたいと思います。最新のn番煎じです。

  • ログを出力したクラスと関数名を自動で出力する。
  • おもむろに配列を放り込んでもそのまま中身を出力してくれる。
  • 自分自身のコードから出力したログとフレームワーク自体のログを見分けられるようにする。

ファイル名: exlog_helper.php
Gistへのリンク(こちらの方が読みやすいかと)

<?php
if (! function_exist('logDebug')) {

    function logDebug($message)
    {
        $trace = debug_backtrace();
        $output = 'UserLog: ' . $trace[1]["class"] . '::' . $trace[1]['function'] . ' - ';
        if (is_array($message)) {
            $message = print_r($message, true);
        }
        $output .= $message;
        log_message('debug', $output);
    }
}

if (! function_exists('logError')) {

    function logError($message)
    {
        $trace = debug_backtrace();
        $output = 'UserLog: ' . $trace[1]["class"] . '::' . $trace[1]['function'] . ' - ';
        if (is_array($message)) {
            $message = print_r($message, true);
        }
        $output .= $message;
        log_message('error', $output);
    }
}

debug_backtrace()で取得した結果を混ぜ込んでいるだけのログ出力ヘルパーです。渡された変数が配列だった場合はprint_rで文字列にしています。後はファイルの何行目で実行されたかも出しておきたい場合も添え字 ‘line’ に入っています。これをCodeIgniterのautoload対象helperとしておけば、どこでも利用可能になります。CodeIgniterで開発を始める前にこんな感じの機能を用意しておくと何かと便利です。やっている人はもうやっているのでしょうが、私はこんな風に書いているよーと共有までに。

CodeIgniterとは直接関係ないけども:
久しぶりにCodeIgniter以外のフレームワークを触ってみたら1年で死んでしまった話を書きました。→ https://www.sodo-shed.com/archives/12182


選定した技術が1年で死んだ話

今年の夏頃から、特にサービスとして出すわけではなく、社内で使っているシステムのリプレースを行う事になりました。主な目的はレガシーすぎる設計をある低度モダンにする事、そして他のシステムと連携出来るようにする事、です。

対象のシステム

見積書や請求書などを管理・発行している。機能はそれなりに多いがUI操作はFormベース、テーブルタグで諸情報を表示するシンプルな物。ノンフレームワークで1画面1PHPファイルな古き良き時代のコード。おそらく10年ぐらい?稼働している。当初はPHP 5.1、PostgreSQL 8.x系だったが、現在はPHP 5.6とPostgreSQL 9.6で稼働しています。

その他の社内システム

かつてはノンフレームワークだったり、太古のバージョンのCakePHPだったり、PHPが4系だったりしたが、概ねCodeIgniter 3系最新版 + PHP 5.6~7.1 + PostgreSQL 9.6 / MySQL 5.6 に統一されている(統一する改修を進めてきた)。

対象のシステムをどうするか

これまで通りCodeIgniterでもよかったのですが、そろそろ新しい物を触ってみようかという事で以下のようなことを検討しました。

  • バージョン4以降の Laravel など、Symfonyコンポーネントを活用するフレームワーク、話題が流行っているように見える。PHPカンファレンスなどでも耳にする事が多い。
  • 軽量、軽快なCodeIgniterですら現状フルに機能を活用している訳ではない。URLのルーティング、エラーハンドリング、ログ出力、設定ファイル読み込み、Twigテンプレートエンジンの呼び出し、ORマッパーまでは使わない程度のDB機能、セッション制御、Composer対応が有れば十分に思えた。
  • Laravelは現行LTS期間が残り1年ほどと、すぐに次の更新を考えないといけないタイミングだった。
  • 都合上データベースの構造には大きく手を入れない方向。DBのカラム名が実装にかかわってくるタイプのフレームワークは使用しない。
  • Silexが使うDIコンテナ(もしくはサービスロケータ)の pimple はシンプルで分かりやすかった。
  • Symfonyコンポーネント製、必須機能だけを備え必要な物はSymfonyコンポーネントで追加するスタイル、数年間開発が続いている事、DIの仕組みが分かりやすい、DoctrineDBALでシンプルにDBアクセスが出来る Silex はそこそこ良いのでは。
  • Symfony自身が関わっているのでそれなりにしっかり開発されているように思えた。
  • あの EC-Cubeでもバージョン3で採用している。Web上で見かけた採用理由も近いものが有った。

実際の開発と設計

これまではほぼほぼ自分自身で開発してきましたが、このシステムに関しては社外の協力要員の方に実装して頂きました。私より実力が全然有る方なのでコード品質への懸念はあまりありませんでした。

設計はシステムを Controller, Service, Repository, Entity といったオブジェクトに分割した、ドメイン志向設計的な物になりました。Silexはマイクロフレームワークなので、それこそ1ページ1PHPファイルにしてしまう元とあまり変わらないような実装も出来てしまうのですが、だいぶモダンな設計になりました。これまでのシステムではほぼ不可能だったテストコードも書けるようになりました。

所感

先に述べたように、マイクロフレームワークである分、どうやって設計するかがかなり重要になってきます。設計力の有る方でしたので比較的綺麗な設計になりました。再利用性もそれなりに有るように思います。ただしもし設計がまずい場合、マイクロフレームワークである事がマイナスにしかなっていなかったかもしれません。

Silexがマイクロかどうかについては、普通に使う分には確かにマイクロなのですが、もともと高機能・フルスタックなSymfonyですので、機能を追加するたびにあれよあれよとComposer上の依存パッケージが増えていきます。困ったらコードを読める、かつ読みやすいのはやっぱりCodeIgniterの良い所でした。

突然の死

突然ではなかったのかもしれませんが、11月に Silex のサポート終了(EOL)が発表されました。2018年の6月にサポートが終了との事で、開発開始から1年で移行先の無いフレームワーク終了のお知らせとなってしまいました。

この技術選定はダメだったのか

Q. 素直にLaravelにするべきだったのか?
A. 今回はSymfony 4で互換性を切るという趣旨でのサポート終了であるらしく、Symfonyコンポーネントと関わりのあるLaravelについても、今までの事を考えても今後もそれなりに大きなアップデートを適用していく必要が有ります。もっとも、全く別のフレームワークを使うか、そのままでメンテをし続けていく必要が有る Silex より Laravel の方が未来が明るいです。

Q. マイクロフレームワークがいいならLaravelベースのLumenにすればよかったのでは
A. これは有るかもしれません。

Q. 同じマイクロフレームワークで middleware の仕組みが使える Slim は?
A. Slim は機能面で Silex 以上に薄い、との意見を見かけた事により実装しやすそうな Silex に流れましたが、Slim の方が未来が有った可能性は有ります。ただ、本当にこっちの方が未来が有ったかは今でも分かりません。

Q. 開発効率は良かったのか
A. これも何とも言えません。実装担当の方はLaravelもFuelPHPも経験者であったので、他のフレームワークでもそこまで大きな違いはなかったのではないかと思います。ただ、これは実装者の技術によるところでも有りますが、フレームワーク独自のしきたりをそこまで覚えなくていい、見通しのいい実装する事はできました。

Q. サポート期間が短い?
A. これまで数年に渡って開発されてきたので十分長くサポートされてきたと言えます。ただ、今このタイミングで採用したのは私の見る目が悪かった。もっとも、サービスとして外部に提供するわけでもないシステムなので、これまでがそうであったように使おうと思えば3年でも5年でも使えるでしょう(良くは無いです)。

Q. 目的は達成できたか?
A. クラスの無かったコードは比較的モダンな設計になり、テストコードが書けるようになり、今後ほかのシステムと連携させていく上でも拡張可能な物になりました。

結論

あまりこうだ、と言えることは無いです。長々と読んでもらって申し訳ないですが、どこがどう教訓とも何とも言えないです。Lumenの方が良かったかもしれませんが、結果論です。ひとまず、使える物は作れたので使っていく方向です。CodeIgniter安定!でもよかったのですが、CodeIgniterはドメイン志向設計な書き方にはあまり向いていないように思うので、その辺りは新システムのコードレビューなどでドメイン志向のような物の勉強になりました。

そうですね、「フレームワークの寿命は選ぶ時点ではなかなか分からない」「寿命を気にするならベストだと思う物より、皆が使っているベターを選ぶべき」辺りでしょうか。以上です。