読者です 読者をやめる 読者になる 読者になる

Tech memo

日々学んだ技術のびぼうろく

Riot.js 3.3 と Lumen 5.4 でつくる 初めてのSPA バックエンド編

はじめに

最近 Riot が気になっていたので、
Laravel製軽量フレームワーク Lumen 5.4 と Riot 3.3 で簡単なブログを作ってみることにした。

今回はバックエンド編。
ここでは DB や API をさくっと作っていく。

ダミーテキストはWebtoolsのダミーテキストジェネレータで作成している。
これを使えば日本語・英語・数字混合のダミーテキストが作れる。

他のエントリーはこちら。

www.yjhm214.com

www.yjhm214.com

スポンサーリンク

作るもの

いろいろ頑張って最終的にこんな感じのブログを作る。

  • 記事一覧の表示(カテゴリーの絞り込みあり)
  • 記事詳細の表示
  • 記事の新規作成・更新

記事一覧
f:id:yjhm214:20170325170558p:plain

記事詳細
f:id:yjhm214:20170325170608p:plain

記事編集
f:id:yjhm214:20170325170616p:plain

記事保存 f:id:yjhm214:20170325170621p:plain

前提

  • Lumen 5.4.5
  • Riot.js 3.3.1

手順

  1. ファサードと Eloquent ORM を使えるようにする
  2. テーブルの作成
  3. モデルの作成
  4. ルーティングの作成
  5. コントローラの作成
  6. データの挿入

1. ファサードと Eloquent ORM を使えるようにする

ファサードと Eloquent ORM を使えるようにするため、 bootstarp/app.php

// $app->withFacades();

// $app->withEloquent();

の部分のコメントをはずしておく。

2. テーブルの作成

3つのテーブルを作成する。

  • posts
  • categories
  • posts_categories

まずはマイグレートファイルの作成。
ドキュメントルートに移動してコマンドを実行。

$ php artisan make:migration create_posts_table --create posts
$ php artisan make:migration create_categories_table --create categories
$ php artisan make:migration create_posts_categories_table --create posts_categories

database/migrations ディレクトリ以下に現在日時のマイグレートファイルが作成されるので、
それぞれテーブル定義を書いていく。

posts テーブル

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreatePostsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->increments('id');
            $table->string('title');
            $table->text('text');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('posts');
    }
}

categories テーブル

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateCategoriesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('categories', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name', 100);
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('categories');
    }
}

posts_categories テーブル

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreatePostsCategoriesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('posts_categories', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('post_id');
            $table->integer('category_id');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('posts_categories');
    }
}

テーブル定義ができたらマイグレートを実行してテーブルを作成する。

$ php artisan migrate

3つのテーブルができた!

mysql> desc posts;
+------------+------------------+------+-----+---------+----------------+
| Field      | Type             | Null | Key | Default | Extra          |
+------------+------------------+------+-----+---------+----------------+
| id         | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| title      | varchar(255)     | NO   |     | NULL    |                |
| text       | text             | NO   |     | NULL    |                |
| created_at | timestamp        | YES  |     | NULL    |                |
| updated_at | timestamp        | YES  |     | NULL    |                |
+------------+------------------+------+-----+---------+----------------+

mysql> desc categories;
+------------+------------------+------+-----+---------+----------------+
| Field      | Type             | Null | Key | Default | Extra          |
+------------+------------------+------+-----+---------+----------------+
| id         | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| name       | varchar(100)     | NO   |     | NULL    |                |
| created_at | timestamp        | YES  |     | NULL    |                |
| updated_at | timestamp        | YES  |     | NULL    |                |
+------------+------------------+------+-----+---------+----------------+

mysql> desc posts_categories;
+-------------+------------------+------+-----+---------+----------------+
| Field       | Type             | Null | Key | Default | Extra          |
+-------------+------------------+------+-----+---------+----------------+
| id          | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| post_id     | int(11)          | NO   |     | NULL    |                |
| category_id | int(11)          | NO   |     | NULL    |                |
| created_at  | timestamp        | YES  |     | NULL    |                |
| updated_at  | timestamp        | YES  |     | NULL    |                |
+-------------+------------------+------+-----+---------+----------------+

3. モデルの作成

app の直下にモデルを作成する。

  • app/Post.php
  • app/Category.php
  • app/PostsCategory.php

の3つのモデルを作成。

Posts.php

<?php namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model 
{
    protected $fillable = [
        'title', 'text'
    ];
}

Category.php

<?php namespace App;

use Illuminate\Database\Eloquent\Model;

class Category extends Model 
{
    protected $fillable = [
        'name'
    ];
}

PostsCategory.php

<?php namespace App;

use Illuminate\Database\Eloquent\Model;

class PostsCategory extends Model 
{
    protected $fillable = [
        'post_id', 'category_id'
    ];
}

4. ルーティングの作成

routes/web.php に API のルーティングを追記する。

$app->group(['prefix' => 'api/'. env('API_VERSION', 'v0')], function ($app) {
    $app->get('posts', 'PostController@index');
    $app->get('posts/{id: \d+}', 'PostController@show'); // {param: 正規表現} とすることで正規表現に一致するパラメータのみ許可する。
    $app->post('posts', 'PostController@create');
    $app->put('posts/{id: \d+}', 'PostController@update');
    $app->get('categories', 'CategoryController@index');
});

また、env('API_VERSION', 'v0') のところで API のバージョンを指定しているため、
.envAPI_VERSION の定数を追記しておく。

API_VERSION=v1

5. コントローラの作成

app/Http/Controllers にコントローラを作成する。

  • app/Http/Controllers/PostController.php
  • app/Http/Controllers/CategoryController.php

の2つのコントローラを作成。

PostController.php

<?php

namespace App\Http\Controllers;
use Illuminate\Http\Request;
use DB;
use App\Post;
use App\Category;
use App\PostsCategory;

class PostController extends Controller
{
    /**
     * 記事一覧を取得する。
     *
     * @param  Request $request
     * @return string
     */
    public function index(Request $request)
    {
        $post = Post::orderBy('id', 'desc');

        if (!empty($request['category'])) {
            $category = Category::where('name', $request['category'])->first();
            if (empty($category)) abort(404);
            $postIds = PostsCategory::where('category_id', $category->id)
                        ->get(['post_id'])
                        ->toArray();
            $ids = [];
            foreach ($postIds as $key => $value) {
                array_push($ids, $value['post_id']);
            }
            $post->whereIn('id', $ids);
        }

        $post = $post->get();
        if (empty($post)) abort(404);

        foreach ($post as $key => $value) {
            $post[$key]->categories = $this->getCategories($value->id);
        }

        return response()->json($post);
    }


    /**
     * 特定の記事を取得する。
     *
     * @param  int $id
     * @return string
     */
    public function show($id)
    {
        $post = Post::find($id);
        if (empty($post)) abort(404);

        $post->categories = $this->getCategories($post->id);

        return response()->json($post);
    }


    /**
     * 記事を作成する。
     *
     * @param  Request $request
     * @return string
     */
    public function create(Request $request)
    {
        $post = new Post;
        $post->title = $request->title;
        $post->text  = $request->text;
        if ($post->save()) {
            if (isset($request->categories) && is_array($request->categories)) {
                foreach ($request->categories as $key => $value) {
                    $postsCategory = new PostsCategory;
                    $postsCategory->post_id     = $post->id;
                    $postsCategory->category_id = $value;
                    $postsCategory->save();
                }
            }
            return response()->json(true);
        } else {
            abort(501);
        }
    }


    /**
     * 記事を更新する。
     *
     * @param  Request $request
     * @param  int $id
     * @return string
     */
    public function update(Request $request, $id)
    {
        if (empty($id)) abort(404);

        $post = Post::find($id);
        if (empty($post)) abort(404);

        $post->title = $request->title;
        $post->text  = $request->text;
        if ($post->save()) {
            PostsCategory::where('post_id', $post->id)->delete();
            if (isset($request->categories) && is_array($request->categories)) {
                foreach ($request->categories as $key => $value) {
                    $postsCategory = new PostsCategory;
                    $postsCategory->post_id     = $post->id;
                    $postsCategory->category_id = $value;
                    $postsCategory->save();
                }
            }
            return response()->json(true);

        } else {
            abort(501);
        }
    }


    /**
     * 特定の記事が属しているカテゴリーを取得する。
     *
     * @param  int $post_id
     * @return array $categories
     */
    private function getCategories($post_id)
    {
        $query = 'select pc.post_id, pc.category_id, (select name from categories as c where c.id=pc.category_id) as category_name from posts_categories as pc where pc.post_id=?';
        $rawCategries = DB::select($query, [$post_id]);
        $categories   = [];
        if (!empty($rawCategries)) {
            foreach ($rawCategries as $key => $value) {
                array_push($categories, $value->category_name);
            }
        }
        return $categories;
    }
}

CategoryController.php

<?php

namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Category;

class CategoryController extends Controller
{
    /**
     * カテゴリー一覧を取得する。
     *
     * @return string
     */
    public function index()
    {
        $data = Category::orderBy('id', 'asc')->get();
        if (empty($data)) abort(404);

        return response()->json($data);
    }

}

6. データの挿入

categories テーブルにマスターデータを挿入しておく。

insert into categories (name) values ('Tech'), ('Book'), ('Hobby'), ('Others');

また、posts テーブルと posts_categories テーブルにはダミーデータを入れておくとつなぎこみ時に便利。

insert into posts (title, text) values ('Lorem ipsum dolor si', 'これはダミーテキストです。フォントやレイアウトを確認するために入れています。Lorem ipsum dolor sit amet, consectetur.これはダミーテキストです。フォントやレイアウトを確認するために入れています。Lorem ipsum dolor sit amet, consec'), ('Lorem ipsum dolor si', 'これはダミーテキストです。フォントやレイアウトを確認するために入れています。Lorem ipsum dolor sit amet, consectetur.これはダミーテキストです。フォントやレイアウトを確認するために入れています。Lorem ipsum dolor sit amet, consec'),('Lorem ipsum dolor si', 'これはダミーテキストです。フォントやレイアウトを確認するために入れています。Lorem ipsum dolor sit amet, consectetur.これはダミーテキストです。フォントやレイアウトを確認するために入れています。Lorem ipsum dolor sit amet, consec'),('Lorem ipsum dolor si', 'これはダミーテキストです。フォントやレイアウトを確認するために入れています。Lorem ipsum dolor sit amet, consectetur.これはダミーテキストです。フォントやレイアウトを確認するために入れています。Lorem ipsum dolor sit amet, consec');


insert into posts_categories (post_id, category_id) values (1, 1), (1, 2), (2, 1), (2, 3), (2, 4), (3, 1), (4, 1), (4, 2), (4, 3), (4, 4);

まとめ

Lumen で ブログシステムのバックエンドを作成した。
Lumen は API だけを作るときとかは簡単に作れるので便利。

関連記事

www.yjhm214.com

www.yjhm214.com

スポンサーリンク