Composerでオレオレフレームワークを作った話

Composerでオレオレフレームワークを作った話

一覧ページとその詳細ページの2ページのみとても小規模なWebアプリケーションを構築しようとした際にLaravelだと大袈裟過ぎるし、かと言って?id=1のようにパラメータにIDを指定してif文でゴニョゴニョするものうんざりだったので、Composerを使って簡単にオレオレフレームワークを作成しました。

オートローダー

オートローダーはComposerを利用しました。

Composerは実はライブラリの管理機能だけではなく、自分のアプリケーション用のオートローダーとしても使用できます。

composer.jsonに以下のように追加します。

{
    "require": {
        ...
    },
    "autoload": {
        "psr-4": {
            "App\\": "app/"
        }
    }
}

その後、以下のコマンドを実行することによって、appディレクトリを以下を自動で読み込むことができるようになります。

$ composer dump-autoload

ディレクトリ構成

ディレクトリ構成はLaravelっぽいディレクトリ構成にしました。

ルーター

ルーターはSlimなどでも使用されているFastRouteを使用しました。ルーティング結果からControllerを呼び足しています。

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

$base = '/';
$handlers = function (FastRoute\RouteCollector $r) use ($base) {
    $r->addRoute('GET', $base, 'App\Controllers\SampleController@index');
    $r->addRoute('GET', $base . '{post}', 'App\Controllers\SampleController@post');
};

$dispatcher = FastRoute\cachedDispatcher($handlers, [
    'cacheFile' => __DIR__ . '/route.cache',
    'cacheDisabled' => true
]);

$uri = $_SERVER['REQUEST_URI'];
$method = $_SERVER['REQUEST_METHOD'];
$routeInfo = $dispatcher->dispatch($method, $uri);

switch ($routeInfo[0]) {
    case FastRoute\Dispatcher::NOT_FOUND:
        header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found');
        echo "ページが見つかりませんでした。\n";
        break;
    case FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
        $allowedMethods = $routeInfo[1];
        header($_SERVER['SERVER_PROTOCOL'] . ' 405 Method not allowed');
        echo "許可されない HTTP リクエストです。\n";
        break;
    case FastRoute\Dispatcher::FOUND:
        $handler = $routeInfo[1];
        $targets = explode('@', $handler);
        $action = new $targets[0]();
        $vars = $routeInfo[2];
        echo call_user_func_array([$action, $targets[1]], $vars);
        break;
}

ただ、FastRoute単体だけでは、名前付きURLからURLを生成することができないため、別途コレクターを用意して上げる必要があります。

今回は2ページしかないのでコレクターは用意しませんでした。

ViewとController

テンプレートエンジンは、最近良く使用するBabelを使用しました。

<?php

namespace App\Controllers;


use eftec\bladeone\BladeOne;

class Controller
{
    public function blade(string $template, array $variables = []): string
    {
        $dir = dirname(__DIR__, 2);
        $views = $dir . '/resources/views';
        $cache = $dir . '/cache';
        $blade = new BladeOne($views, $cache, BladeOne::MODE_AUTO);
        return $blade->run($template, $variables);
    }
}

Jsonを返すAPIなどもないのでこれだけです。

Model

Modelも特にこだわりもなく最近良く使用するEloquentを使用しました。

<?php

namespace App\Providers;

use App\Helpers\Config;
use Illuminate\Database\Capsule\Manager as Capsule;
use Illuminate\Events\Dispatcher;
use Illuminate\Container\Container;


class DatabaseServiceProvider
{
    public function boot($driver = 'mysql')
    {
        $capsule = new Capsule;

        $capsule->addConnection(Config::get('database.' . $driver));

        $capsule->setEventDispatcher(new Dispatcher(new Container));

        $capsule->setAsGlobal();

        $capsule->bootEloquent();
    }
}

LaravelっぽいMVCフレームワークの完成

これらを組み合わせると殆どコードを書いていないですがLaravelっぽいフレームワークの完成です。つかルーター以外Laravel

要らないものがほどんど入っていないので動作も快適です。

HelperはDBの設定を読み込むためだけにIlluminate\Configを使用できるようにしています。

<?php


namespace App\Helpers;

use Illuminate\Config\Repository;


class Config
{
    private static $instances = [];

    private static function loadFile(string $filename): void
    {
        static::$instances[$filename] = new Repository(require dirname(__DIR__, 2) . '/config/' . $filename . '.php');
    }

    public static function get(string $key)
    {
        $filename = strstr($key,'.', true);
        $key =  mb_substr(strstr($key,'.', false), 1);

        if (!isset(static::$instances[$filename])) {
            self::loadFile($filename);
        }

        return static::$instances[$filename]->get($key);
    }
}

最後に

今回は自作のフレームワークを作成しましたが、小規模なWebアプリケーションを構築する場合には、マイクロフレームワークであるSlimを使用して構築するのが好ましいと思います。