開発ブログ

株式会社Nextatのスタッフがお送りする技術コラムメインのブログ。

電話でのお問合わせ 075-744-6842 ([月]-[金] 10:00〜17:00)

  1. top >
  2. 開発ブログ >
  3. PHP >
  4. PHPフレームワークSlim4の紹介
no-image

PHPフレームワークSlim4の紹介

PHPフレームワークSlim4の紹介


こんにちは。タカギです。

弊社ではサーバーサイドのFWにLaravelを使用することが多いですが、以下のようなことを思ったりすることもあります。

  • 外部APIのコールにGuzzle Httpを使っていると、全体をPSR-7に統一したくなる(LaravelはPSR-7非準拠(LaravelがというよりSymfonyが))
  • FWに提供されている機能をあまり使っていないのでもう少し軽量でいい
  • 軽量と言ってもルーティングとDIコンテナはさすがに必須
  • 単純に別のFWでも遊んでみたい

 

今回そんな欲求を満たしてくれそうなSlim4との出会いがあったのでご紹介します。

※ 前バージョンのSlim3については弊ブログでも紹介しております

Slim4の概要

公式ドキュメントはhttp://www.slimframework.com/から

ドキュメントにある通りですが、 機能としてはHTTPルーター、ミドルウェアと依存性注入のみで、非常に小さいです。
名前の通りですね。

導入

ドキュメントに従って導入していきます。

まずは本体をComposerでインストール。

composer require slim/slim:^4.0

次にPSR-7準拠のHTTP messageライブラリをインストールします。

私の知る限りでは本体と一緒にデフォルトのものが入ってくるのが一般的と思うのですが、Slim4は好きなものを選ばせてくれます。嬉しいですね。

ドキュメントではslimさんが用意してくれているslim/psr7の他に、nyholm, guzzle, laminasのインストール方法が紹介されていますが、
得に拘りはないのでslimのものを入れます。

composer require slim/psr7

あとは以下のコードをpublic/index.phpに貼り付けて完成だそうです。

public/index.php

<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;

require __DIR__ . '/../vendor/autoload.php';

$app = AppFactory::create();

$app->get('/', function (Request $request, Response $response, $args) {
    $response->getBody()->write("Hello world!");
    return $response;
});

$app->run();

ディレクトリはこうなりました。

.
├── public
│   ├── index.php
├── vendor
├── composer.json
└── composer.lock

プロジェクトルートで以下を叩いてサーバーを立ち上げ、ブラウザからアクセスすると"Hello world!"と表示されました。

 php -S localhost:8888 -t public

DIコンテナの導入

ここからは必須ではないですが、個人的には必須なのでDIコンテナを導入していきます。
この辺を参考にしましょう。

先程のHTTP messageと同様、PSR-11に準拠したDIコンテナであれば任意のものを使用できます。

ドキュメントではPHP-DIを推しています。

PHP-DIでももちろんよいのですが、ここでは使い慣れたilluminate/containerを入れてみたいと思います。
そうです。Laravelです。

composer require illuminate/container

index.phpを少し修正し、アプリケーションインスタンスの生成時にコンテナを渡してやるようにします。

use Illuminate\Container\Container;

$container = Container::getInstance();
$app = AppFactory::createFromContainer($container);

// 略

せっかくコンテナを導入しても管理させたいものがなければつまらないので適当にクラスを作っていきます。

ディレクトリ構造としては以下を目指します。

.
├── app
│  ├── Http
│  │  └── Controllers
│  │      └── UserController.php
│  ├── Models
│  │  └── User.php
│  └── Services
│      └── UserService.php
├── composer.json
├── composer.lock
├── public
│  └── index.php
└── vendor

User.php

<?php

declare(strict_types=1);

namespace App\Models;

final class User implements \JsonSerializable
{
    /** @var int $id */
    private $id;

    /** @var string $name */
    private $name;

    public function __construct(int $id, string $name)
    {
        $this->id = $id;
        $this->name = $name;
    }

    /**
     * @return array|mixed
     */
    public function jsonSerialize()
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
        ];
    }
}

UserService.php

<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\User;

class UserService
{
    /** @var User[] $store DBのつもり */
    private $store;

    public function __construct()
    {
        $this->store = [
            (new User(1, 'Satomi')),
            (new User(2, 'Gacky')),
        ];
    }

    /**
     * @return User[]
     */
    public function all(): array
    {
        return $this->store;
    }
}

ついでにいわゆるMVC(2の方)のControllerを作り、それっぽくします。
ルーティングの書き方はこうです。

<?php

declare(strict_types=1);

use Illuminate\Container\Container;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;

require __DIR__ . '/../vendor/autoload.php';

$container = Container::getInstance();
$app = AppFactory::createFromContainer($container);

$app->get('/user', App\Http\Controllers\UserController::class . ':index');

$app->run();

UserController.php

<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Services\UserService;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

class UserController
{
    /** @var UserService $service */
    private $service;

    public function __construct(ContainerInterface $container)
    {
        $this->service = $container->get(UserService::class);
    }

    /**
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return ResponseInterface
     */
    public function index(RequestInterface $request, ResponseInterface $response): ResponseInterface
    {
        $users = $this->service->all();

        $body = $response->getBody();
        $body->write(json_encode($users));
        $response = $response->withBody($body);
        return $response;
    }
}

コンストラクタの引数にContainerInterfaceを受け取るのがポイントです、

ドキュメントで

Slim first looks for an entry of HomeController in the container, if it’s found it will use that instance otherwise it will call it’s constructor with the container as the first argument. Once an instance of the class is created it will then call the specified method using whatever Strategy you have defined.

と言っている通り、コントローラーを手動でコンテナ登録しなかった場合、コンテナのインスタンスが渡されます。

ここまで書いてブラウザから/userにアクセスすると

[{"id":1,"name":"Satomi"},{"id":2,"name":"Gacky"}]

と表示されました。やったね。

だたこのままでは目に見えて問題があります。 UserControllerがDIコンテナ依存しており、これはDependency Injectionではなくサービスロケーターパターンじゃないか?と怖い人に怒られることが想定されます。

コンテナをControllerの外に出してしまいましょう。 方法は簡単で、UserControllerを手動でコンテナ登録するだけです。

index.phpに追記します。

$container->singleton(
    \App\Http\Controllers\UserController::class,
    function (ContainerInterface $c) {
        return new \App\Http\Controllers\UserController($c->get(\App\Services\UserService::class));
    }
);

UserController.phpも合わせて修正します。

/** @var UserService $service */
private $service;

public function __construct(UserService $service)
{
    $this->service = $service;
}

こうすることでUserControllerのコンテナへの依存が消え、DI警察に怒られることはなくなりました。
そしてブラウザを更新すると先程と同様、さとみとガッキーが表示されました。

この調子でアプリケーションを書いていけそうですね!
今回は以上です。

まとめ

以上の通り、Slim4を使うことで冒頭で述べた欲求がある程度満たせることがわかりました。
現実的に開発していく上ではDBとのやりとりのためにORMを導入するなどあるかと思いますが、FWで決められたものではなくその都度好きなライブラリを入れられるのは嬉しいですね。
また、PSR-7準拠のため、HTTP層の実装とFWの結合度が下がったことも大きなポイントだと捉えています。

小さなFWを自分色に染めていくのはとても楽しいので、みなさんも遊んでみてください。

TOPに戻る