実践、レイヤードアーキテクチャによるPHPアプリケーションの構築

PHPの現場で有名な@shin1x1さんのブログに「レイヤードアーキテクチャを意識したPHPアプリケーションの構築」という記事があります。

www.1x1.jp

私の現場では、SlimやDietCakeのようなシンプルなフレームワークを使って、開発しています。

Slim Framework - Slim Framework

DietCake - Fastest MVC framework skeleton for PHP

比較的シンプルなMVC構成で開発していたのですが、どうしてもモデルが肥大化しやすいので、悩んでました。

そこで、レイヤードアーキテクチャを自己流に解釈して取り入れてみたのですが、非常にしっくりきたので、悩みと解決策を合わせて紹介していきます。

悩み モデルが肥大化し、オブジェクト指向が崩壊する

スタンダードなMVC構成を採用した場合、モデルクラスに関数が乱立します。コントローラー間のコードの共有が、モデルクラスで行われるからです。

特によくないなと感じていたのは、ORMのモデルインスタンスとして使われるはずなのに、静的関数ばかり増えてしまったことです。

それにより、中途半端なオブジェクト指向を備えた、よく分からないモデルクラスが出来上がってしまいました。

<?php
class ItemModel
{
    // 大量の静的関数群
    public static function searchOnlyBook() {}
    public static function searchAllItem() {}
    public static function searchMyItem(int $userId) {}

    // あまり使われないインスタンス関数群
    public function calcTax() {}
}

f:id:konosumi:20180130031405j:plain

解決 サービスクラスに静的関数を移動してみた

モデルクラスをモデルインスタンスとして正しく使うために、サービスクラスを導入してみました。

サービスクラスの導入にあたり、以下の2つを条件に設けることとしました。

  1. モデルクラスに静的関数を(極力)置かないこと
  2. サービスクラスには静的関数のみを置くこと

サービスクラスには、状態を持ってはならないという条件を付け加え、静的関数のみを配置することにしました。そして、レイヤードアーキテクチャに沿って、モデルを操作する行動はサービスクラスに置くことにしました。

<?php
class ItemService
{
    // 静的関数しか置かない
    public static function searchOnlyBook() {}
    public static function searchAllItem() {}
    public static function searchMyItem(int $userId) {}

    public static function updateItemName(ItemModel $item, $itemName) {
        $item->itemName = $itemName;
        $item->update();
    }
}

サービスクラスのメリット

端的に申し上げますと、やったことは基本的にこれだけでして、モデルから静的関数を移設しましたという話に尽きます。しかしながら、サービスクラスを厚くする設計は、非常に開発しやすいことが分かりました。

以下のようなメリットがあります。

  1. サービスクラスは状態を持たないため、テストが書きやすい
  2. 小さな静的関数を書いていくことで、UNIX哲学におけるUNIXコマンドのパイプラインのように、静的関数を組み合わせてコードを書くことができる
  3. 静的関数は分割がしやすく、最悪サービスクラスが肥大化したとしても、traitなどを活用して、クラスを分割して管理することができる
<?php
// traitを使う場合
// ItemSearchTrait.php
trait ItemSearchTrait
{
    public static function searchOnlyClockBook() {}
}
// ItemUpdateTrait.php
trait ItemUpdateTrait
{
    public static function updateItemName(ItemModel $item, $itemName) {
        $item->itemName = $itemName;
        $item->update();
    }

}
// ItemService.php
class ItemService
{
    use ItemSearchTrait;
    use ItemUpdateTrait;
}

さいごに

レイヤードアーキテクチャをベースに、コントローラーとモデルの間にサービスクラスを導入したことによって、格段に開発がしやすくなることが分かりました。

  1. 静的関数のないシンプルなオブジェクト指向としてのモデルクラス
  2. モデルを操作するサービスレイヤの静的関数群
  3. サービスレイヤを組み合わせてアプリケーションを組み立てていくコントローラー

この3段構成であれば、基本的にサービスレイヤのテストがきっちりできていれば、コントローラーはサービスレイヤを組み合わせるだけなので品質が安定しやすく、またコードの使い回しや共通化もやりやすくなります。

この記事では触りしか書けてないのですが、もしご興味を持たれた方がいらっしゃいましたら、ぜひ試してみてください!

静的関数のみのサービスクラス群と、インスタンス関数のみのモデルクラス群で分割しただけなのですが、こんなにも開発しやすくなるとは、思ってもみませんでした。