タグ別アーカイブ: CodeIgniter

CodeIgniter 4 RC1 インプレッション

Webアプリケーションフレームワーク Laravel 6 LTSの正式版が2019年9月3日にリリースされましたが、その数日前にCodeIgniter 4.0 もついにRC1となりました。beta1が今年の3月でしたので最初のベータ版からおおよそ6カ月でのRCリリースとなりました。(Larabel 6に関しては記事を書く方が大勢居ると思うので……)まだ本格的なアプリを作っていない状態ではありますがCodeIgniter 4 RC1のパッと見た感じを記事にしたいと思います。個人的にトピックな点に触れていくので細かな違いはドキュメントを参照してみてください。

CodeIgniter公式
https://codeigniter.com/

Composerへの本格対応や名前空間への対応

CodeIgniter 3でもComposer(PHPのパッケージ管理ツール兼オートローダー)で導入されたライブラリや独自定義のクラスをComposerのオートロードで読み込んだり、名前空間の定義されたクラスをアプリ内に含めたりnewしたりする事は出来ていたのですが、CodeIgniter自体をComposerで導入したり、CodeIgniter自体や作っていくコントローラやモデルなどで名前空間を使っていくようになりました。

なおComposerでの導入は必須ではなく、これまで通り圧縮アーカイブでダウンロードして配置して動かす事も出来ます。この辺りの選択肢を残しておくのはCodeIgniterらしくていいですね。また、Composerで導入する場合についても –no-devオプションを付けた場合(= PHPUnitを入れない場合)は数パッケージしか依存関係が有りません。一桁です。少なければ良いという訳ではないですが、フレームワークの全ての機能がそれぞれパッケージのようなSymfonyやLaravelに慣れていると方向性の違いを感じますね。

#導入
composer create-project codeigniter4/appstarter <任意のプロジェクト名> -s rc
# 内臓Webサーバでの実行
cd <今作られた任意のプロジェクト名のディレクトリ>
php spark serve
# →Webブラウザで http://localhost:8080 にアクセスすると仮のTOPページが見られる

JSON/XML形式でのレスポンス返却機能

ビュー(フロントエンド側)とのやりとりをJSONを返すAPIでやり取りする事が多くなった昨今ですが、CodeIgniter 4でもJSON/XML形式でのレスポンス返却が簡単になりました。例としてコントローラ内で

$data = [
    'status' => 'ok',
    'message' => 'json responsed'
]; 
 $this->respond($data, 200); 

と書いてやれば $data をJSONフォーマット化したレスポンスをHTTP 200で返してくれます。渡すのがarrayで良いってのはCodeIgniterっぽいですね。

respondはコンテンツネゴシエーション機能が文脈によってJSONではなくXMLを返したりします(WebブラウザでアクセスするとXML形式で表示されます)。この辺りは常にJSONを返したりなど設定変更可能です。また、他の形式での返却も追加できるようです。
https://codeigniter4.github.io/userguide/outgoing/api_responses.html

ORM的機能の追加

使う場面・使わない場面有るとは思いますが、簡易?ORMが追加されました(ドキュメント上ではORMとはよばれずModel、Entityと記載)。もちろんほぼこれまで通りの直書きSQL+変数の自動エスケープ有りバインディングであったり扱いやすいクエリビルダも利用可能です。

まだ触ってはいないのですが、ドキュメントを読む限りテーブル名やプライマリキー名は手動指定できるタイプ、シンプルなCRUD機能を提供、論理削除対応、バリデーション対応、変更できないプロパティの指定に対応、JSON←→Arrayの自動変換などある程度の機能が揃っているようです。でもテーブル間のJOINやそれに関連する遅延ロード・Eagerロードは見当たらない? どの程度まで使い込めるのかは触っていないので正直まだ分からないのです。

基本は変わらないコントローラ

ルーティング方法がシンプルだからCodeIgniterが好きと言う人は結構いるんじゃないでしょうか(想像)。 クラス名やメソッド名がそのままルーティングになったりディレクトリ階層でパスを分けたりといったあの感覚はCodeIgniter 4でもそのまま利用できます。

Controllerクラスを継承する必要が有ったり、渡された値はInputクラスではなくIncomingRequestから取得するようになっていたりなど差は有るのでその辺は新しい作法にのっとっていく形になります。あとはアプリケーションのエラーを返す際にshow_error関数を呼ぶのではなくコントローラ内で例外をスローするようになっていたりするのも最近のフレームワークっぽいですね。

あとは使いたい人だけ使う形で良いと思いますが、Laravelにも有るようなReutfulなルーティングをまとめて用意する機能が追加されていたりします。APIを数作っていく場合は楽になるかもしれませんね。

神クラスCi_controllerからDI風味へ

CodeIgniter 3まではコントローラ(というかCI_controller)がサービスロケータとして機能しており、更にそれがあらゆる場所から利用できていましたがこれは無くなりました。CodeIgniter 4では代わりに Service という仕組みにより newされたインスタンスを取得するようになります。

基本的にはコントローラ内で必要なインスタンスをServiceとして取得、あとはコンストラクタインジェクションやメソッドインジェクションで注入という方向性のようです。Symfony 4.xほど強力なDIの仕組みではない(Symfony 4.xのDIは本当に強力)ですが、CodeIgniter 3の CI_loaderっぽさも残っていてシンプルなので移行はしやすそうですね。
https://codeigniter4.github.io/userguide/concepts/services.html

/Config/Services.php にクラスの生成を定義。これがサービスプロバイダ的な感じですね。ここでオブジェクトを生成して返します。

上記で生成される仮で作ったライブラリです。

コントローラ内からオブジェクトを取得して利用します。

.envでの設定に対応

.env形式のファイルでフレームワークの設定値を上書きするような事が可能になります。Symfonyなど他のフレームワークで見かける様な物と同様に、環境ごとに変更したい設定が有る場合は.envファイルを用意する事で変更が可能です。

PSR-3準拠のロギング

ログを残す際のレベルがPSR-3に準拠するロガーで見かける様な物(RFC 5424)になりました。引き続きこれまで通りのlog_message関数が利用可能ですが内部的にはPSR-3準拠の関数群を持ったloggerサービスの log() を呼び出しています。DEBUGとERRORの間のレベルが欲しいと思う事が多かったので地味に助かるポイントです。他のPSR-3を実装するロガーへの入れ替えも可能なようです。

所感

CodeIgniter 3との細かい違いはそれなりに有ります。基本的にCodeIgniter 3のコントローラやモデルのコードはそのままでは動作しません。ただ、私がCodeIgniterの方向性だと(個人的に勝手に)思っている「薄く必要最低限で過剰に何かせずに分かりやすい感じ」はそのまま引き継がれているんじゃないかなという印象です。CodeIgniter 3のその部分が好きだったけど流石にもう設計の古さを感じるなという部分(名前空間非対応で有ったり)がアップデートされているので、悪くはない選択肢の一つになれればいいなぁという感想です。

追記 – 2019年9月26日(現地時刻)にCodeIgniter 4.0.0 RC 2が公開されました
https://forum.codeigniter.com/thread-74463.html


「選定した技術が1年で死んだ話」の1年後

1年半前に以下のような記事を書きました。その記事が前提となり、その続きになります。

対応の方向性

2018年末ごろから Symfony 4.2 への移行を始めました。Symfony 4 についての日本語情報はかなり少なかったので、数少ない日本語情報に感謝をささげながら、基本は Symfony 公式のドキュメントを Google翻訳にかけて読みながら対応しました。

※ 得られた知見を本ブログ内別記事にまとめています。

Silex 1.x → Symfony 4.2 への移植の実際

現状の2代目システムの実装としては Silex 1.x での実装となり、依存性注入の仕組みは Pimple というアレイアクセスベースのシンプルなサービスロケータ、クラス構成の主要な部分としては、コントローラ、サービス、リポジトリのインタフェース、リポジトリ、DoctrineDBALで読みだしたレコードを保持したりバリデーションが実装されていたりするレコードオブジェクトという構成になっていました。

コントローラとビュー

まずはコントローラの移植になりますが、基本的にはこの部分が実コードでは一番重いものになりました。Silexのルーティングから Symfony のアノテーションを使った物に置き換えていく作業は主に以下の内容になりました。

  • SilexのクロージャベースのルーティングをSymfonyのアノテーションベースのルーティングに書き換える作業を進めます。どちらか一方でしか出来ない機能という物がなく、淡々と対応すれば完了しました。ただし物量が多い。
  • Requestオブジェクトなど要素要素の機能はもともとSymfonyコンポーネントを使っていたSilexなのもあり、そのまま使い続ける事が出来る。
  • リダイレクトなどは関数名が変わってくるので調整する。
  • ビューはTwigがそのまま使えるのでセッションに関する扱いだけ調整すればそのまま動作する。
  • Pimpleを使っていたDIは全てコンストラクタインジェクションに変更。あとはSymfonyが勝手にサービスを注入してくれる。

ルーティングは書き換えになりますがコントローラ内のアクション処理としては9割そのままコピペで動いた印象です。残りの1割を書き換えていきました。

サービス

ここでいうサービスは所謂ビジネスロジックです。サービスクラスは一部Pimpleのサービスロケータを引き回している部分が有ったので全てコンストラクタインジェクションか、コントローラから呼ばれる際のメソッドインジェクションに変更しました。ログ出力に関しても同様に Psr\Log\LoggerInterface を注入するように変更。それ以外はSilex独自の機能への依存がほとんどなく、ほぼそのまま動作しました。

リポジトリとレコードオブジェクト

リポジトリの実装はDoctrineDBALの存在にがっつり依存していましたが、これはSymfonyでも同様に利用できるので問題になりませんでした。ほとんどの処理がそのまま動作しました。

テスト

PHPUnitで実装されたテストは全体の2~3割程度に調整が必要になりました。この背景として元々使っていた phpunit/dbunit の開発終了が有ります。
https://github.com/sebastianbergmann/dbunit

データベース周りのテストを行う際にレコードの取得結果を比較したり、YAMLファイルで定義したデータをDBに流し込んだりリセットしたりなどの機能を提供しているライブラリなのですが、これが開発終了し新しいPHPUnitに対応していかなくなる事態になりました。

色々方法は考えたのですが、phpunit/dbunit の提供する機能のうち、実際に利用してたものだけについて互換実装を作り込んで使う事にしました。YAMLファイルを読み込んでDBに書き込む機能、欠けた部分のある連想配列を欠けていない部分のみで比較する機能などを関数のインタフェースに互換性を持たせながら実装して、これは1ファイル1クラスのシンプルな互換機能になりました。もちろん PHPUnit 8系で問題なく動作する物になっています。

結果

期間としては大体3か月程度でSymfony 4.2 への移植が完了しました。これは想定よりもだいぶ短い期間でした。併せて PHP 7.1 から 7.2 への移行を行ったり様々な追加機能を載せたりしていますが、現状問題なく運用し続けています。上手く行った要因としては以下が有ったのかと思います。

  • Silex 自体の開発が終了したと言っても Symfony は健在なので、置き換えが必要なクラス、機能が最小限で済んだ。結果論ではあるがSilex採用時の「Symfonyコンポーネントを使ったフレームワークにしよう」という選択に助けられた。
  • もともとサービスクラス、リポジトリ、レコードオブジェクトがほぼSilexに依存しておらず、依存関係も依存性注入を使った作りになっていたので移植コストが低かった。再設計時の設計が(ベストかどうかはともかく)悪くなかった。
  • 初代システム(1ページ1PHPファイル)とは違って2代目システムはテストコードが有ったので、動作確認のコストが抑えられた。テストが書かれていなかった部分やテストは書かれているもののカバーしきれていなかった動作、その他もろもろでバグは出たものの、それでもテストが無い状態よりははるかに楽になった実感が有る。

結論

  • フレームワークの機能を便利に使いつつも 、それに依存しすぎない疎結合な設計になっているとフレームワークが開発終了に見舞われてもコードの大部分をそのまま動かす事が出来る。それまで「疎結合大事って言ってもライブラリ、ユーティリティ的なクラス以外はあんまり再利用したりしなくない? テストが書きやすくなるのは間違いないけど」と思う部分も有ったが、テストが書きやすいだけではないフレームワークに対する疎結合の重要性を体感した。
  • さすがにフレームワークが変わるレベルだと動かなくなるテストコードも少なくないので「元と同じテストが全部通ればOK」とはならないものの、とはいえ有るのと無いのでは全く違う。テストコード大事。

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も高速化していきましょう。