効率的に使いまわせるPHPフレームワークを作る為に重要視した事

これは自分の経験に基づいて纏めた内容です。
文章力が無い為、読みづらい箇所があるかもしれませんが、ご了承ください。

自分はこれまでいろんなPHPフレームワークを触ってきました。
Synfony、Cake、Zend、Ethna、Laravel、自社FW、etc...
どれもルールが定まっており、調べやすいし書きやすい印象では有るのですが、
**やはり一長一短を感じざるを得ませんでした。**
なので個人で公開しているサイトでは、自作FWを使って運用しています。
そのFWを作る際に重要視した点を下記に纏めてみました。
ちなみに開発時の**OSはWindows**で**開発環境はLAMP**です。
参考：[LAMP (ソフトウェアバンドル)](https://ja.wikipedia.org/wiki/LAMP_(%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%83%90%E3%83%B3%E3%83%89%E3%83%AB))

# MVC構造必須
参考：[MVCフレームワーク](http://www.objective-php.net/mvc/index/)
言わずもがなですが、MVC構造は必須です。
**Model(モデル)はCakePHPを参考**に、自由度の高い配置にします。
**Controller(コントローラー)はEthnaを参考**に、機能毎にファイルを分けます。
**View(ビュー)はSmarty**を使います。（後記参照）

# 命名規則を統一
当然の話をしますが、命名規則は厳守します。
**コードを書く際に一番時間使うところは名前を決める時**なので、実装する前にルールをFIXしましょう。
個人的な意見ですが、各命名規則は下記が最適かと思ってます。

| 対象  | 命名規則  | 例 | 説明  |
|:-:|:--|:--|:--|
| 変数名&クラス変数名 | スネークケース | $display_mode | ・PHPマニュアルの形に合わせる為(なじみやすい) |
| 引数名 | in_スネークケース | $in_display_mode | ・関数ローカルスコープ変数と被らせない為 |
| 定数名 | 大文字スネークケース | DIR_APP | ・一般的 |
| 関数名 | アッパーキャメルケース | GetDisplayMode | ・PHPの基本関数と被らせない為 |
| クラスメソッド名 | キャメルケース | getDisplayMode | ・上記と被らせない為 |
| クラス名 | 接頭辞(大文字)_用途名(アッパーキャメルケース) | DAO_UInfo | ・**クラスはどんなシステムでも大量に宣言される為、名前が被らないように接頭辞を入れる。**<br>・autoloadを効率良く行う為（後記参照）|
| クラスを宣言するファイル名 | クラス名.php | DAO_UInfo.php |・autoloadを効率良く行う為（後記参照） |
| クラスを宣言しないファイル名 | スネークケース.php | display_mode.php | ・上記と被らせない為 |
| DB名 | スネークケース | qiita_user, qiita_master | ・phpMyAdminを使うと、接頭辞毎に見やすく整理してくれる為<br>・接頭辞にはサービス名称を付け、**ユーザー用かマスター用かしっかり分ける事**で用途をはっきりさせる |
| table名 | スネークケース | u_info | ・接頭辞にDB用途の略称を入れて用途をはっきりさせる |

# autoloadを使いこなす
参考：[クラスのオートローディング](http://php.net/manual/ja/language.oop5.autoload.php)
PHP5以降から使えるようになったこの便利な機能は、書き方次第では実装スピードを格段に早める事が出来ます。
**Laravelでも使われていますが、autoloadで読み込めるクラスを扱う配列が膨大であり、パフォーマンスが悪過ぎます。**
しかし個人で作るフレームワークであれば、上記の命名規則に則って設定すれば格段に実装スピードもパフォーマンスも上がります。

例として、「/app/lib/dao/」のディレクトリ階層を「DAO_UInfo.php」のクラスファイルを作ります。

```php:/app/lib/dao/DAO_UInfo.php
class DAO_UInfo {
...
}
```

次にautoloadに設定を行います。

```php:autoload_define.php
spl_autoload_register(function ($in_class_name) {
    //下記配列は何処かで纏めて設定しておくのが理想
    $load_class_dir = array(
        'DAO' => '/app/lib/dao'
    );
    //$in_class_name名の「接頭辞」の部分を取得し、読み込み先を絞り込む
    $name_exp = explode('_', $in_class_name);
    $prefix = reset($name_exp);
    if(isset($load_class_dir[$prefix])) {
        $load_path = $load_class_dir[$prefix] . '/' . $in_class_name . '.php';
        if(is_file($load_path)) {
            require_once($load_path);
        }
    }
});
```
このように接頭辞を判定して読み込み先を絞れば効率よく読み込みを行う事が可能です。

# interfaceを使いこなす
参考：[オブジェクト インターフェイス](http://php.net/manual/ja/language.oop5.interfaces.php)
オープンソースとして様々なDB管理システムやテンプレートエンジンが提供されています。
**企業毎に推奨してくる環境も様々**です。
よって、それぞれの環境に合わせて機能を切り替えられる仕組みが必要です。
interfaceを使えば、**どの環境でも必要なメソッドの数を統一でき、切り替えも簡単に行う事が可能**です。

# インデントにはtabを使う
**tabサイズをspace4文字分としてインデントを合わせます。**
最近「tab or space」の論争は増えてきてますね。
ツールによってデフォルトのtabサイズが違う為、spaceでインデントを合わせるケースが増えてきました。
しかし自分は、それは**技術者のエゴ**だと思ってます。
実際現場で優先されるのは、**パフォーマンスと実装速度**です。
spaceより、**tabでインデントを合わせる方がアドバンテージは大きい**のです。

- spaceとtabのバイトサイズは同じ為、spaceでインデントを合わせると**tabに比べて4倍記憶領域を消費**する
 - PHPの様なスクリプト言語だとファイルサイズが負荷に直結する為、tabが有利
- tabの方がインデントを合わせるのに**時間は掛からない**
 - サクラエディタや秀丸等、全ツールで一定の実装速度を保てる

# テンプレートエンジンにはSmartyを推奨する
Smartyは非常に便利なテンプレートエンジンです。

- **キャッシュを残せる**

参考：[Smartyマニュアル キャッシュ](http://www.smarty.net/docsv2/ja/caching.tpl)
テンプレート上に書かれたSmartyタグは、**Smartyに読み込まれる事によってPHPの記述に変換されます。**
それだと毎アクセス時に変換処理が入り、高負荷となってしまいます。
**Smartyは変換後のテンプレートをキャッシュする事が可能**なので、しっかり設定しましょう。
テンプレートを修正するたびにキャッシュを削除するのも手間なので、
**キャッシュの生成日時がテンプレートの修正日時よりも以前なら、
キャッシュを再生成する**ように処理を変更しておくと便利です。

- **pluginが便利**

参考：[Smartyマニュアル プラグイン](http://www.smarty.net/docsv2/ja/plugins)
**pluginは必要な時だけ読み込んで使えるので、逐一includeを書いたりせずに処理を挿入できます。**
ゴリゴリのテンプレートでは出来ない芸当です。
modifierやblock等の使い分けもでき、実装面でも機能面でもコスト面でも最高クラスです。

- **prefilterが超便利**

参考：[Smartyマニュアル プリフィルタ/ポストフィルタプラグイン](http://www.smarty.net/docsv2/ja/plugins.prefilters.postfilters.tpl)
**出力するテンプレートソースの特定の箇所を一発置換する時に使えます。**
例えばテンプレート上に書いてあるURLを「http\://example.com/」から「http\://mysite.com/」に置き換えたい時等、
prefilterに纏めて実装しておけば良いわけです。
**更にキャッシュも組み合わせて使うと効果は絶大です。**

- **上記利点を利用して下記実装を施す**

「対象のテンプレを読み込む」
　→「該当のキャッシュがあるか見る」
　　→①「なければ対象テンプレを読む」
　　　→「SmartyタグをPHP処理に変換」
　　　　→「prefilterに通す」
　　　　　→「キャッシュする」
　　　　　　→「出力」
　　→②「有ればキャッシュを読む」
　　　→「出力」

初期プロセスは①の処理を進みますが、次回から②の処理に進むようになり、
**まったくコストにならなくなるのです。**恐ろしい程便利。

# 開発環境毎に別々の設定ファイルを読み込む仕組みを作る
ローカル環境、デバッグ環境、ステージング環境、本番環境と、運用がしっかりしている企業は開発環境が多いです。
そしてそれぞれの環境毎に、読み書きするDBサーバーのIPやドメインは違いますので、
**それぞれの環境にあった設定ファイルを読み込む必要が出てきます。**
ぱっと思いつく方法としては、下記が考えられます。

**- 環境を判断する為のダミーファイルを置く（推奨）**
- アクセスドメインによって環境を判断する（やった事有るけど一長一短）
- 絶対パスによって環境を判断する（結構危険かも）

# FWのメイン処理はtry～catchで囲む
参考：[例外(exceptions)](http://php.net/manual/ja/language.exceptions.php)
**どこからでもthrow出来るように、全体の処理をtry～catchで囲みます。**
システム的に致命的な問題が起きたら即座にthrowし、
テスト環境ならバックトレースを表示し、
本番環境なら「メンテ中」や「システム的な問題が発生したため、サービスを一時中断します」といった表示ができるようにしましょう。

# エラー処理について
**ユーザーからの不正なアクセスで発生したエラーはエラーログに貯めこまないようにしましょう。**
送られてきたパラメータはしっかりバリデートチェックし、多重実行もsession等で防ぎましょう。(防ぎきれませんが)
ツールを使って秒間に数千回サイトにアクセスし攻撃してくる例も少なくありません。
その度にエラーログが蓄積されるとサーバーにとんでもない負荷が掛かってしまいます。

# Unit Testは公開後に書く
参考：[PHPUnit 用のテストの書き方](https://phpunit.de/manual/current/ja/writing-tests-for-phpunit.html)
エンジニア界隈で度々発生する**「開発途中にUnitTest書く書かない問題」**。
これらについての議論は尽きませんが、何に置いても重要な事を棚に上げてる気がします。
エンジニアが現役で上手にソースコードを書ける寿命は35～40歳。
**皆さんは一つのシステムを作るのにどれだけの時間を使いたいのでしょうか。**
2～3年掛けて大きなシステムを作って一生を賄えるのであればそれも良し。
2～3カ月掛けて小さなシステムを作り続け一生を賄えるのであればそれも良し。
しかし**当たるか当たらないかは公開するまで分かりません。**
2カ月掛けて作ったシステムの売上が、2年掛けて作ったシステムの売上を大きく上回る事もあります。
**公開しても1円にもならないシステムに2年も3年も使いたいですか？**
もう結論は出ましたね。

1. **まずは全機能を実装する**
1. 公開前に負荷試験やB&Wボックステストはちゃんとやる
1. **公開後、必要であればリファクタリング&機能追加時に既存に影響が出ないか判断する為のUnit Testを書く**

自分は沢山システムを作って行きたいので、この方針が一番だと思います。
Unit Testは書いてあると確かに便利ですが、
**開発途中に書くと問題が発生する度に修正する必要があり、
実装工数が何倍にも膨れ上がります。**
**開発途中という事は仕様が何度も変更される恐れもあります。**
それなら最後に書いてしまった方が良いでしょう。
開発途中にUnit Testを書いて成功した例は、
相当優秀なプロデューサー、ディレクターに恵まれていると思った方が良いですね。

#最後に
自作FWを作ったのが、この記事を書くより2,3年前なので、まだまだ重要視した点はあったと思いますが、
思い出し次第この記事に追加して行きたいと思います。