Edited at
SchooDay 20

Laravelでwiki的なものをつくってみる(前編)

More than 1 year has passed since last update.

Schooアドベントカレンダーの20日目の記事です。


はじめに


わたしについて

こんにちは、Schooの@ohidaです。2016年9月にSchooにジョインし、イノベーション推進室及びプロダクト戦略室という部署で、新規事業を推進したりSchooをよりイケてるサービスにするお仕事をしています。会社ではてへぺろちゃんと呼ばれています。

私は開発チーム所属のバリバリのエンジニアではないのでSchooのエンジニアリングの凄みについてはあまり語れません。ので、今回はこれから社内での導入を企んでいるお気に入りフレームワークのLaravelのことについて書きたいと思います。


なんの話をするか

LaravelというPHPのフレームワークを使って簡単なウェブアプリケーションをひとつつくってみようと思います。Laravelの基本的な機能の一部+αでつくる予定です。ウェブアプリをつくるのってこんな感じで楽しいですよというのをお伝えできればと。


  • つくるもの = Laravelを使った簡単なウェブアプリ

  • 対象読者 = Laravelをこれから始める人

  • 言いたいこと = ウェブアプリつくるのたのしいお

ちなみにアドベントカレンダー2日分のノルマを満たすため、前編と後編を予定していますが、何を書くかよりも先に2日分書くということを決めたので、尺が余ったり足りなかったりするかもしれません(ノω・)テヘ


つくるもの

今回は、wiki的な情報保存ツールをつくってみようと思います。

個人的に情報系ツールが好きなのと、シンプルながら一応動くはずなので、つくったあと実際に使ってみながら好きなように改造していく楽しみがあるかなーと思っています。

ひとまずこんな感じのものになる予定です。


こんな感じ


  • キーワード毎にページをつくれる

  • markdown記法に対応

  • 別ページへのリンクがはれる

  • ページの一覧ページがある

  • 検索ができる

いかがでしょうか。

それっぽいものになりそうでドキドキしますね。


開発環境をつくる

さて、前置きはこのあたりにして、さっそく開発にはいっていきます。


Valetで環境構築

開発環境の構築はだいぶラクになってきましたが、仮想マシンを導入したりネットワークの設定を行うのはそれなりに手間がかかったりします。

ValetはLaravel向けに用意されている開発環境のひとつで、非常に簡単に環境を構築することができます。

https://laravel.com/docs/master/valet

こちらの公式ドキュメントを見ながら構築を行いましょう。

Homebrewを使ってインストールをしていきます。


homebrewでPHP7.1をインストール

$ brew install homebrew/php/php71



homebrewでValetをインストール

$ composer global require laravel/valet

(※ このあと ~/.composer/vendor/bin にパスを通しておきます)


Valetを動かす

$ valet install


ここでは~/Codeフォルダをプロジェクト置き場に指定します。


~/Codeフォルダをプロジェクト置き場に指定

$ cd ~/Code

$ valet park

これだけで、~/Code以下に作成したLaravelのプロジェクトにhttp://プロジェクト名.devというURLでアクセスできる準備ができました。hostsファイルとかいじらなくていいんです。ちなみにnginx x php-fpmという環境で動作します。

※ ValetはMacでのみ提供されています

※ ああ?ここまで読んだけどWindowsなんだけどウィン!?という人は怒らず焦らずHomesteadという別の手段をご利用ください


データベース

データの保存先としてデータベースも使いますので、まだ何も入っていなければMariaDBをいれておいてください。


homebrewでMariaDBをインストール

$ brew install mariadb

$ brew services start mariadb


プロジェクトをつくる

開発環境ができたら、次にLaravelの新規プロジェクトを作成しましょう。今回は"wiki"という名前にしてみます。


wikiという名前の新規プロジェクトを作成

$ cd ~/Code

$ laravel new wiki

プロジェクトの作成が完了したら、ブラウザでhttp://wiki.dev/にアクセスしてみます。

(Homesteadの場合はhttp://wiki.app/などに適宜読み替えてください)

できました!(ノω・)テヘ

これでLaravelのプロジェクトとして立派に動くようになりました。すごい。

続いて、今回の目的であるwiki的なものとしてのロジックをつくっていきます。


ルーティングを設定する

まずルーティングの設定を行うことにします。

今は/にアクセスするとデフォルトのウェルカムページが表示されます。この処理は、routes/web.phpというファイルで指定されています。


デフォルトのルーティング

Route::get('/', function () { // "/"にアクセスされたら

return view('welcome'); // welcomeというビューを表示する
});

ルーティングを変えるには、このファイルを編集します。

Laravelには、よくあるCRUDに対応したルーティングを自動で行ってくれる機能があるので、今回はそれを利用してみましょう。


routes.phpにこれを追加

Route::resource('pages', PageController::class);


これによって一気に以下のルートが利用できるようになります。

Verb
URI
Action
Route Name

GET
/pages
index
pages.index

GET
/pages/create
create
pages.create

POST
/pages
store
pages.store

GET
/pages/{page}
show
pages.show

GET
/pages/{page}/edit
edit
pages.edit

PUT/PATCH
/pages/{page}
update
pages.update

DELETE
/pages/{page}
destroy
pages.destroy

ためしにブラウザでhttp://wiki.dev/pagesにアクセスしてみましょう。GET /pagesなのでindexアクションがよばれるはずです。


例外発生!

ReflectionException in Container.php line 749:

Class App\Http\Controllers\PageController does not exist

例外!(ノω・)テヘ

メッセージを見ると、さきほど指定したPageControllerが存在しないと言われています。


コントローラをつくる

そんなわけで、さきほど設定したルーティングには、対応するコントローラが必要となります。

Laravelではコントローラを生成するコマンドが用意されています。CRUDに対応するメソッドをもったコントローラを作成するには、ターミナルから以下のコマンドを使います。


PageControllerを作成する

$ php artisan make:controller PageController --resource


以下のファイルが生成されます


  • app/Http/Controllers/PageController.php

これで再度http://wiki.dev/pagesにアクセスしてみましょう。すると、今度は白紙のページが表示されました。

実際にどのような処理が行われているのか、コントローラの内部を見てみます。前述のルーティングテーブルをみるとGETpagesにアクセスするとindexというアクションがよばれると書かれています。

Verb
URI
Action
Route Name

GET
/pages
index
pages.index

これはPageControllerindex()というメソッドが実行される、ということを意味していますので、該当のメソッドを確認してみます。


app/Http/Controllers/PageController.php

public function index()

{
//
}

何もしていない。

ためしに、以下の文を記述してみましょう。


app/Http/Controllers/PageController.php

public function index()

{
dd(__METHOD__); これを追加
}

ブラウザで確認してみるとメソッド名が表示され、ちゃんとこの処理が呼ばれているということがわかります。

dd()はLaravelのヘルパー関数で変数のダンプに使われます)

このメソッドの役割は「ページの一覧を表示すること」なので、そうさせたいところですが、まだこのアプリはページという概念を持っていませんでした。(ノω・)テヘ


Pageモデルをつくる

ページの概念を扱うため、Pageモデルを作成しましょう。

Laravelでは以下のコマンドでモデルとマイグレーションを一緒に作成できます。

$ php artisan make:model Page --migration

以下のファイルが生成されます


  • app/Page.php

  • database/migrations/2016_12_20_000000_create_pages_table.php


マイグレーション

ページテーブルに「タイトル(title)」と「本文(body)」のカラムを持たせましょう。マイグレーションファイルに次の2行を追加します。


database/migrations/2016_12_20_000000_create_pages_table.php

class CreatePagesTable extends Migration

{
public function up()
{
Schema::create('pages', function (Blueprint $table) {
$table->increments('id');
$table->string('title')->unique(); 追加
$table->text('body'); 追加
$table->timestamps();
});
}


データベースの作成

まだデータベースをつくっていませんでした(ノω・)テヘ

ので、wikiというデータベースを作成します。


mysqlのデータベースをつくる

$ mysql

MariaDB > CREATE DATABASE wiki DEFAULT CHARACTER SET utf8;


.envの編集

Laravelからデータベースに接続するため.envファイルを編集します

DB_CONNECTION=mysql

DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=wiki
DB_USERNAME=root
DB_PASSWORD=


  • 接続情報はご自身の環境に添った内容を記述してください

  • 今回はMariaDB(mysql)を使っていますがSQLiteなどでももちろん大丈夫です


マイグレーション実行

これでデータベースが利用できるようになりましたのでマイグレーションを実行して、ページ用のテーブルを作成します。


レッツマイグレーション!

$ php artisan migrate



Pageモデルのfillable属性を設定

最後にPageモデルのfillable属性を設定します。

これによって、titlebodyについての一括代入(Mass Assignment)が可能となります。


app/Page.php

class Page extends Model

{
protected $fillable = [
'title', 'body',
];

...



Page用のモデルファクトリーをつくる

さて、これでPageモデルが使えるようになりました。

ついでなのでLaravelが持つモデルファクトリーという便利機能を使ってテストデータを投入してみましょう。

モデルファクトリー(factories/ModelFactory.php)はプロジェクト作成時に用意されているので、以下を追加します。

これは、App\Pageモデルのテストデータをつくるときの指定で、titlebodyに任意のダミーデータを代入しています。


factories/ModelFactory.php

$factory->define(App\Page::class, function (Faker\Generator $faker) {

return [
'title' => $faker->word(),
'body' => $faker->paragraphs(3, true),
];
});

※今回テストの話はしませんが、モデルファクトリーはテストの際に大活躍しますのでモデルとセットでつくっておくとよいかと思います。


テストデータを投入

ではモデルファクトリーを使ってテストデータを投入してみましょう。

まずLaravelのREPLを起動します。

$ php artisan tinker

REPLの中でモデルファクトリーを実行します。

これはApp\Pageモデルをのデータを10個つくる、という意味になります。


App\Pageを10個生成

>>> factory(App\Page::class, 10)->create();



確認

>>> App\Page::count(); → 10


※ もしtitleのunique制約でエラーになる場合はもう一度実行してみてください・・・(ノω・)テヘ

※ データベースを初期状態に戻すにはphp artisan migrate:refreshなどを利用できます


ページ一覧を表示


アクションの実装

無事Pageモデルとそのデータができましたので、コントローラの実装に戻りましょう。

PageControllerApp\Pageモデルを使いやすくするため、useで名前空間をインポートします。


app/Http/Controllers/PageController.php

<?php

namespace App\Http\Controllers;

use App\Page; 追加
use Illuminate\Http\Request;


次にPageモデルを使い、ページの一覧を取得します。Page::all();というメソッドで、すべてのページを取得することができます。

ここでも先程のdd()ヘルパー関数を使って中身を覗いてみましょう。


app/Http/Controllers/PageController.php

public function index()

{
$pages = Page::all();

dd($pages);
}


ブラウザで確認すると以下のように10個のデータをもったCollectionオブジェクトであることが確認できると思います。

Collection {#184 ▼

#items: array:10 [▶]
}

:bulb: キーを押しながらをクリックするとネストの全レベルを展開できます。

CollectionはLaravelが用意している、データ配列を便利に扱うクラスで、この例のようにデータベースから一覧を取得した場合などに返されます。iterableなオブジェクトなので通常の配列と同じように扱うことができます。


ビューをつくる

さて、これでページの一覧データが取得できるようになったので、ブラウザに整形して表示してみたくなりますね。うずうず。

テンプレートをつくって表示してみましょう。

LaravelにはBladeというシンプルながら強力なテンプレート機能がついているので、それを利用します。

一覧ページとパーマリンクという異なるページを用意する必要がありそうなので、ベースとなるテンプレートをつくっておいて、それをページ毎に継承するようにしてみます。


ベーステンプレート

アプリケーション共通でベースとなるテンプレートです。


resources/views/app.blade.php

<!DOCTYPE html>

<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ config('app.name') }}<</title>
<link href="/css/app.css" rel="stylesheet">
<script>
window.Laravel = <?php echo json_encode([
'csrfToken' => csrf_token(),
]); ?>
</script>
</head>
<body>
@include('navbar')
<div class="container">
<div class="content">
@yield('content')
</div>
</div>
<script src="/js/app.js"></script>
</body>
</html>


ナビバー部分

navbarの部分も構造を明確にするために切り分けておきます。


resources/views/navbar.blade.php

<nav class="navbar navbar-default">

<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="#">{{ config('app.name') }}<</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="/">Home</a></li>
</ul>
</div>
</div>
</nav>


CSSもか

resources/assets/sass/app.scssの下の方に以下を追加して、gulpコマンドでCSSファイルをコンパイルします。


resources/assets/sass/app.scss

.content {

background: #fff;
padding: 15px;

h1 {
margin-bottom: 30px;
}
}


gulpがうごかない場合は

$ npm install --global gulp-cli

$ npm install

くわしくはドキュメントをご参照ください


ポスト一覧ページ

これが今回使うポスト一覧ページのテンプレートです。

ページの一覧データ$pages@foreachでまわしてリストとして展開します。


resources/views/pages/index.blade.php

@extends('app')

@section('content')
<h1>Home</h1>
<ul>
@foreach ($pages as $page)
<li>
{{ $page->title }}
</li>
@endforeach
</ul>
@endsection



コントローラからビューを呼び出す

先ほどdd()でダンプしていたところを書き換えて、ビューを呼び出すように変更します。


app/Http/Controllers/PageController.php

public function index()

{
$pages = Page::all();

// ↓これを追加
return view('pages.index')->with([
'pages' => $pages,
]);
}


ブラウザで確認してみると・・・

(´;ω;`)でたブワッ


リンクをはる

ページ一覧の表示ができたので、次にページのタイトルにリンクを設定しましょう。パーマリンクのURLが必要となるので、Pageモデルに自分のURLを返すメソッドを追加してみます。

ルーティングのテーブルを確認してみると、ページのパーマリンクはpages.showという名前がついていますので、この名前を使ってURLを生成します。

Verb
URI
Action
Route Name

GET
/pages/{page}
show
pages.show

ルート名からURLをつくるには、route()というヘルパー関数を使います。URLを返すメソッドを追加しましょう。


app/Page.php

public function url()

{
return route('pages.show', $this->title);
}

さらに、Laravelでは属性のかたちで任意の値を取得できる機能がついていますので、それを使ってみます。

例えば$page->urlという属性で先ほどのurl()メソッドの結果を返すようにするにはこのように書きます。


app/Page.php

public function getUrlAttribute()

{
return $this->url();
}

それではこちらを使ってリンクをはってみましょう。ページ一覧のテンプレートのリストの各項目にアンカータグを設定します。


resources/views/pages/index.blade.php

@foreach ($pages as $page)

<li>
<a href="{{ $page->url }}"> ここ
{{ $page->title }}
</a>
</li>
@endforeach

(´;ω;`)できたブワッ


TO BE CONTINUED

というところで、つかれたので続きは後編で書きたいと思います。ていうかなんだろう遠い。


後編の予告


  • パーマリンクを表示するよ

  • ページを作成・編集するよ

  • markdownに対応するよ

  • markdownをカスタマイズするよ

  • 検索するよ

  • なんか終わらなそう


耳寄り情報


明日!SchooでLaravelの授業があるよ

PHPフレームワーク(Laravel)を使った効率的なWebアプリケーション開発


Schooではエンジニアのお友だちを募集しているよ

来年のSchooアドベントカレンダーを一緒に書かないか

(ノω・)テヘ

後編はこちら

ソースコードもあるよ