Slim Framework 3 MVC Skeleton Application

# はじめに

過去にSlim 1やSlim 2を使っていて、いくつかのサイトに用いて実戦投入しましたが、最近は主にCakePHPなどに代表されるフルスタックなフレームワークを使っていました。ただフロントエンドがReactやVueなどを用いることが多くなって、バックエンド側もAPIサーバで十分という場合も多く、フルスタックフレームワークはスペックオーバーに感じる今日この頃。
そこでシンプルなWEBサイトやAPIサーバ用にマイクロフレームワークのSlim 3を用いてMVCぽい感じでアプリケーションを作成できる雛形を作ってみました。

https://github.com/m92o/slim-mvc-skeleton

ちなみにSlimの名前はテンプレートエンジンのSlimと被っていて、あちらの方がメジャーなのでググラビリティが低いですね。

# 使用コンポーネント

* Eloquent ORM (モデル)
* PHP-View (ビュー)
* Pimple (DIコンテナ)
* Monolog (ログ)
* PHPUnit (テスト)

# インストール

新規にプロジェクトを作るのはComposerを使います。

```shell-session
composer create-project m92o/slim-mvc-skeleton [my-app-name]
```

サンプルコードも含まれているのすぐに動きます。
テストを動かす場合は、[my-app-name]ディレクトリに移動して

```shell-session
composer test
```

ビルトインサーバでアプリを動かす場合は

```shell-session
composer start
```

で、 http://localhost:8080/ にアクセスしてみてください。

# ディレクトリ構成

```shell-session
├── app
│   ├── configs
│   │   ├── dependencies.php
│   │   ├── middleware.php
│   │   ├── routes.php
│   │   └── settings.php
│   ├── controllers
│   │   ├── AppController.php
│   │   └── HomeController.php
│   ├── libs
│   │   ├── Facade.php
│   │   └── Log.php
│   ├── models
│   │   ├── AppModel.php
│   │   └── User.php
│   ├── views
│   │   └── home.phtml
│   ├── bootstrap.php
│   └── environment.php
├── containers
├── db
├── logs
├── public
│   └── index.php
├── tests
│   └── Functional
│       ├── BaseTestCase.php
│       └── HomepageTest.php
├── vendor
├── composer.json
├── docker-compose.yml
└── phpunit.xml
```

Slim公式のslim-skeletonを使ったことある人には見覚えのあるファイルもありますね。/app/configsにあるファイルは、ほぼslim-skeletonのままです。MVCに合わせて models / controllers / views のディレクトリが用意されています。それ以外は libs に入れる感じ。

/containersはDockerコンテナの設定などです。

# 使い方

サンプルコードが含まれているので、それを見ながら説明します。

## 設定

### 環境設定

DB等の設定は/app/configs/settings.phpに書きます。
環境変数SLIM_ENVの値により使用する設定を切り替えるようになっています。SLIM_ENVが未設定の場合はdevelopmentが使用されます。

```settings.php
environments = [
    'production' => [
        'db' => [
            'driver' => 'mysql',
            'host' => 'localhost',
            'database' => 'database',
            'username' => 'user',
            'password' => 'password',
            'charset'   => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix'    => ''
        ]
    ]
];
```

### ルーティング

ルーティングの設定は/app/configs/routes.phpです。

```routes.php
<?php
use Controllers\HomeController;

// Routes
$app->get('/', HomeController::class . ':index');
```

/ にGETメソッドでアクセスが来たら、HomeControllerクラスのindexメソッドを呼び出すという設定になります。ルーティング設定は全て自分で書く必要があります。

ルーティングの詳細はSlimのドキュメントを参照してください。
https://www.slimframework.com/docs/v3/objects/router.html#container-resolution

## コントローラ

コントローラは、AppControllerを継承して書いていきます。routes.phpで設定したメソッドを実装します。

```HomeController.php
<?php
namespace Controllers;

use Slim\Http\Request;
use Slim\Http\Response;
use Controllers\AppController;
use Models\User;
use Libs\Log;

class HomeController extends AppController {
    public function index(Request $request, Response $response, array $args) {
        $user = User::find(1);
        //Log::info('User', ['fullName' => $user->fullName]);
        return $this->view->render($response, 'home.phtml', ['user_name' => $user->fullName]);
    }
}
```

Userはモデルです。ログは`Log::info()`のようにスタティックメソッドとして呼び出し可能です。(LaravelのFacadeを真似てある)

ログの使い方はMonologのドキュメントを参照にしてください。
https://github.com/Seldaek/monolog

`$this->view->render()`でビューを呼び出します。

## モデル

モデルはAppModelを継承して書いていきます。AppModelはEloquent ORMを継承したものです。

```User.php
<?php
namespace Models;

use Models\AppModel;

class User extends AppModel {
}
```

今回は中身が空ですが、ここにビジネスロジックを書く形です。
クラス名(単数形)の複数形がテーブル名になっている必要があります。レールを外れる場合は `protected $table = 'table_name';`のようにクラス変数に記述します。

詳しくはLaravelのドキュメントを参照してください。
https://laravel.com/docs/5.7/eloquent

## ビュー

テンプレートエンジンにはPHP-Viewを使用していますので、ただのPHPファイルです。HTMLにPHPのコードを埋め込みます。

```home.phteml
Hello, <?= $user_name ?>!
```

コントローラで呼び出した`$this->view->render()`の第3引数の連想配列が、ビューに渡された変数になります。(連想配列のuser_nameキーの値が、`$user_name`で取り出せる)

## テスト

テストはBaseTestCaseを継承して書いていきます。PHPUnitです。

```HomepageTest.php
<?php
namespace Tests\Functional;

class HomepageTest extends BaseTestCase {
    /**
     * Test that the index route with optional name argument returns a rendered greeting
     */
    public function testGetHomepageWithGreeting() {
        $response = $this->runApp('GET', '/');

        $this->assertEquals(200, $response->getStatusCode());
        $this->assertEquals('Hello, John Smith!', $response->getBody());
    }
}
```

`$this->runApp()`でURLにアクセスして、戻ってきたレスポンスの中身を確認します。

# まとめ

以上、ざっくりとした説明でした。

マイクロフレームワークを用いて、単純なMVCフレームワークを作ってみました。フルスタックフレームワークの便利さはありませんが、RESTでJSONを返す程度のAPIサーバならこの程度で十分そうです。