気ままなタンス*プログラミングなどのノートブック

プログラミングやRPGツクール、DTM等について、学んだことや備忘録をアウトプットとして残し、情報を必要としている誰かにとって「かゆいところに手が届く」ブログとなることを願いながら記事を書いています。

【備忘録】マイクロフレームワーク「Slim」とEloquentを使って、某小規模企画サイトのバックエンドを作ったので実装の流れを備忘録として残す

スポンサーリンク

こんばんは。 GW後半が始まりましたが、皆様はいかがお過ごしでしょうか。

僕はというと、タイトル通り、某企画サイトのバックエンド側を実装しておりました。 (仕事ではなく、プライベートでの趣味の一環です。とある方から依頼を受けました。)

本番環境の制約

依頼元の本番環境のサーバーがレンタルサーバーということもあり、 利用できる言語はPHPのみで、その中でも利用可能なWebフレームワークが限られていました。

特にサーバーの制約がなければ、比較的慣れている「Django」を 利用していたと思うので、普段使わないものを使うきっかけができて、逆に良かったと思います。

今回の要件

  • ユーザの識別
    • ただしメールアドレス等の個人情報は収集しない
  • 識別したユーザごとの状態管理、ユーザ自身の操作による状態更新

要件の実現方法

  • Twitter OAuth認証の導入
  • ユーザの状態をDBテーブルで管理する
  • ユーザの状態更新のために、然るべきAPIを準備し、HTTPリクエストによって処理を実行する

今回利用した各フレームワーク、ライブラリのバージョン

  • Slim 3.1
  • illuminate/database(Eloquent ORM) 5.5
  • Twig 2.3

開発環境

  • 仮想環境のUbuntu 14.04 - gistで提供されていたVagrantfile及びprovisionシェル

Twitter OAuth認証によるユーザ識別

実装を始める前には、Twitter認証のやり方が全くわからなかったのですが、こちらの記事を参照して なんとか実装することができました。

syncer.jp

大変わかりやすい記事で、本当に助かりました。 誠にありがとうございました。

Slimの導入

ディレクトリ構成の検討

Slimを利用するにあたって、ディレクトリ構成を決める必要があります。 自分で適当に決めることもできるようです。

個人的なアプリ開発ならば適当に決めたものを利用するのですが、 一応「依頼を受けての開発」なので、素直にテンプレート(Slim-Skeleton)を利用することにしました。

github.com

Slim Skeletonプロジェクトの作成

Composerを利用することで、すばやくSlimアプリの作成ができます

$ composer create-project slim/slim-skeleton [appname]

作成後のディレクトリ構成

こんな感じのプロジェクトが作成されます。 public配下のindex.phpには各種設定ファイルの読み込みに関する処理が記述されています。

appname
├── composer.json
├── composer.lock
├── CONTRIBUTING.md
├── docker-compose.yml
├── .gitignore
├── logs/
│ └── README.md
├── phpunit.xml
├── public/
│ ├── .htaccess
│ └── index.php
├── README.md
├── src/
│ ├── dependencies.php
│ ├── middleware.php
│ ├── routes.php
│ └── settings.php
├── templates/
│ └── index.phtml
└── tests/
└── Functional/
├── BaseTestCase.php
└── HomepageTest.php

データベース接続

「Slim DB」で検索してみると、PDO(PHP Data Objects)を利用した データベース接続の解説記事が出てきました。 しかし、「DBテーブルごとに、紐づくモデルクラスを作り、SQLを書く必要がありそうで面倒くさそう」的なことや 「DB接続に利用するコネクションオブジェクトの破棄等をうっかりと忘れてしまいそう」といったことを 考えてしまい、もっと別の方法はないか探すことにしました。

すると、公式ドキュメントに「Eloquent」という文字があったのです!
Using Eloquent with Slim - Slim Framework

Eloquent ORM(オブジェクト関係マッパー)は人気のPHPフレームワーク「Laravel」のORMとして提供されている アクティブレコードによるデータ操作の実装です。

(なお、Eloquentという言葉に反応した理由は下記の記事にもあるとおり、最近Laravelに入門していたからです。) www.rinsymbol.net

フレームワーク「Laravel」を使っていなくても、 データ操作部分に関係するモジュールを導入すれば、Slimでも使えるようになるとのことでした。

Eloquent ORMの導入(と、Twigの導入)

下記のURL先の記述従い、必要な設定を追記していきます。
Using Eloquent with Slim - Slim Framework

  • Eloquentモジュールの導入

リンク先は5.1になっていますが、僕は5.5が使いたかったので、5.5を指定しました。 (5.5を指定した理由ですが、LaravelのLTS(Long Term Support)が5.5らしく、とりあえずそれに合わせといた方が良いのでは、と短絡的に考えたためです)

$ composer require illuminate/database=5.5.*
  • 必要に応じて、twigモジュールの導入

今回、ビュー側のテンプレートエンジンはtwigを利用したかったので、僕はここでtwigも導入しました。

$ composer require twig/twig
  • src/settings.phpファイルへのデータベース設定の追記

settings.phpファイルには、ログやレンダラに関する設定が連想配列で記載されています。 この中に、データベースに関する設定を追加します。('db'から始まる行~timezoneまで)

<?php
'settings' => [
    'displayErrorDetails' => true, // set to false in production
    'addContentLengthHeader' => false, // Allow the web server to send the content-length header
    'determinateRouteBeforeAppMiddleware' => false,
    // Renderer settings
    'renderer' => [
        'template_path' => __DIR__ . '/../templates/',
    ],
    // ここから↓
    'db' => [
        'driver' => 'mysql',
        'host' => 'localhost',
        'database' => 'database_name',
        'username' => 'user_name',
        'password' => 'password',
        'charset' => 'utf8',
        'collation' => 'utf8_unicode_ci',
         'prefix' => '',
        'timezone' => '-09:00'
    ], // ここまで↑
// Monolog settings
    'logger' => [
        'name' => 'appname',
        'path' => isset($_ENV['docker']) ? 'php://stdout' : __DIR__ . '/../logs/app.log',
        'level' => \Monolog\Logger::DEBUG,
    ],
  • src/dependencies.phpファイルにサービスファクトリ情報を追記する
<?php
// DIC configuration

$container = $app->getContainer();

... 中略

// ここから↓
$container['db'] = function($container) {
    $capsule = new \Illuminate\Database\Capsule\Manager;
    $capsule->addConnection($container['settings']['db']);

    $capsule->setAsGlobal();
    $capsule->bootEloquent();

    return $capsule;
};

//コントローラから、$this->container['logger']->debug()といった形で呼び出すため、インスタンス情報を取得する
$container->get('logger');
$container->get('db');

// twigモジュールを入れている場合
$container->get('view');
// ここまで↑

DBテーブルとモデルクラスの作成

DBテーブルを作成し、Eloquentの機能を利用するための、モデルクラスを作成します。

今回は、「users」テーブルと対応するモデルクラス「User」を作成しました。

  • userテーブル
create table users (
  id int(5) auto_increment primary key,
  token varchar(100) not null,
  hash_value varchar(100) not null,
  created_at datetime,
  updated_at datetime
)
  • Userクラス src/models/user.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;

class User extends Model {
    protected $table = 'users';
    protected $dates = ['created_at', 'updated_at'];

    public function getAttribute($key) {
        return parent::getAttribute(\snake_case($key));
    }
    public function setAttribute($key, $value){
        return parent::setAttribute(\snake_case($key), $value);
    }
}

モデルの作成については下記の記事を参考にしました。 ピンポイントで求めていた情報でした!ありがとうございました!

taka512.hatenablog.com

コントローラ作成

リクエストを処理するため、コントローラを作成します。

配置するディレクトリはどこでも良いのですが、僕はsrc/app/controllersディレクトリを作成し、 hoge_controller.phpファイルを作成しました。

  • src/app/controllers/hoge_controller.php
<?php
namespace App\Controllers;
use Psr\Container\ContainerInterface;
use Illuminate\Database\Eloquent\ModelNotFoundException;

require __DIR__ . "/../models/user.php";

class HogeController {
    protected $container;

    public function __construct(ContainerInterface $container) {
        $this->container = $container;
    }

    public function index($request, $response, $args) {
        $users = User::all();
        return $this->container['view']->render($response,
        'index.twig',
        ['users' => $users]);
    }
}

Controllerクラスを作成し、コンストラクタでコンテナ(dependencies.phpで設定したオブジェクト)を 注入(依存性注入:DI?)している感じがしますが、詳細については調べきれていません。

ルーティングの設定がまだなのですが・・・indexにアクセスした場合 ユーザの変数をindex.twigに渡すようにしています。

僕の場合、コントローラでUserモデルクラスをわざわざ読み込んでいるのですが、 おそらく、下記の記事のようにindex.phpで読み込む形式が良いのだと思います。(ブログ投稿にあたって、改めて調べていた時にこの記述を見つけました。Eloquent\Modelを継承したモデルクラスにも適用できるかどうかは試せていません。。) qiita.com

ルーティングの設定

Slim Skeletonのプロジェクトテンプレートを作成した時から存在するsrc/app/routes.phpに設定を追記し、 /indexでアクセスした時に、HogeControllerのindexメソッドが呼ばれるようにマッピングを行います

<?php

use \App\Controllers\HogeController;
$app->get('/index', HogeController::class . ':index');

HogeController::class . ":index"という書き方で、 .(ドット)と,(カンマ)を間違えているのではないかと疑問に思ったのですが、 こちらは.(ドット)であっています。

これで、/indexでアクセスした時、HogeControllerのindexメソッドが呼ばれるようになります。

ビューの設定

今度は、src配下ではなく、appname配下のtemplatesフォルダにtwigファイルを作成します (今まではすべてsrc配下のファイルを更新していました)

  • appname/templates/index.twigファイルを作成する
<!DOCTYPE html>
<html lang="ja">
<head>
<title>index</title>
</head>
<body>
{% for user in users %}
  user.id - user.token
{% endfor %}
</body>
</html>

上記の例では、/indexにアクセスすると、userのid列、token列が表示されます twigで利用できるタグやフィルタについては公式ドキュメントをご参照ください。

Documentation - Twig - The flexible, fast, and secure PHP template engine

静的ファイルの配置

JavaScriptやCSSといった静的ファイルは、src/public配下に配置すると良さそうです。

ちなみに僕は、開発時にはapache2を利用していたのですが、 下記のような構成にしていました。

Apache2の設定 /etc/apache2/sites-enabled/000-default.conf

<VirtualHost *:80>
  DocumentRoot "/var/www"
  ServerName app.dev
  ServerAlias app.dev
  <Directory "/var/www">
    AllowOverride All
    Require all granted
  </Directory>
</VirtualHost>

したがって、開発サーバーでWebアプリに接続する際には下記のような形となっていました。 http://localhost:8000/appname/public/index
(ポートフォワーディングで、ゲスト80番をホストの8000番に設定していることが前提となります)

最後に、その他感想

普段利用しないスキルセットで対応する機会がもらえたり、 TwitterのOAuth等、そもそも知見のないことに挑戦できて、すごく充実した日々でした。