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を使ってインストールをしていきます。
$ brew install homebrew/php/php71
$ composer global require laravel/valet
(※ このあと ~/.composer/vendor/bin にパスを通しておきます)
$ valet install
ここでは~/Code
フォルダをプロジェクト置き場に指定します。
$ cd ~/Code
$ valet park
これだけで、~/Code
以下に作成したLaravelのプロジェクトにhttp://プロジェクト名.dev
というURLでアクセスできる準備ができました。hostsファイルとかいじらなくていいんです。ちなみにnginx x php-fpmという環境で動作します。
※ ValetはMacでのみ提供されています
※ ああ?ここまで読んだけどWindowsなんだけどウィン!?という人は怒らず焦らずHomesteadという別の手段をご利用ください
データベース
データの保存先としてデータベースも使いますので、まだ何も入っていなければMariaDBをいれておいてください。
$ brew install mariadb
$ brew services start mariadb
プロジェクトをつくる
開発環境ができたら、次にLaravelの新規プロジェクトを作成しましょう。今回は"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に対応したルーティングを自動で行ってくれる機能があるので、今回はそれを利用してみましょう。
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に対応するメソッドをもったコントローラを作成するには、ターミナルから以下のコマンドを使います。
$ php artisan make:controller PageController --resource
以下のファイルが生成されます
app/Http/Controllers/PageController.php
これで再度http://wiki.dev/pages
にアクセスしてみましょう。すると、今度は白紙のページが表示されました。
実際にどのような処理が行われているのか、コントローラの内部を見てみます。前述のルーティングテーブルをみるとGET
でpages
にアクセスするとindex
というアクションがよばれると書かれています。
Verb | URI | Action | Route Name |
---|---|---|---|
GET | /pages | index | pages.index |
これはPageController
のindex()
というメソッドが実行される、ということを意味していますので、該当のメソッドを確認してみます。
public function index()
{
//
}
何もしていない。
ためしに、以下の文を記述してみましょう。
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行を追加します。
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
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
属性を設定します。
これによって、title
とbody
についての一括代入(Mass Assignment)が可能となります。
class Page extends Model
{
protected $fillable = [
'title', 'body',
];
...
Page用のモデルファクトリーをつくる
さて、これでPage
モデルが使えるようになりました。
ついでなのでLaravelが持つモデルファクトリーという便利機能を使ってテストデータを投入してみましょう。
モデルファクトリー(factories/ModelFactory.php
)はプロジェクト作成時に用意されているので、以下を追加します。
これは、App\Page
モデルのテストデータをつくるときの指定で、title
とbody
に任意のダミーデータを代入しています。
$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個つくる、という意味になります。
>>> factory(App\Page::class, 10)->create();
>>> App\Page::count(); → 10
※ もしtitleのunique制約でエラーになる場合はもう一度実行してみてください・・・(ノω・)テヘ
※ データベースを初期状態に戻すにはphp artisan migrate:refresh
などを利用できます
ページ一覧を表示
アクションの実装
無事Page
モデルとそのデータができましたので、コントローラの実装に戻りましょう。
PageController
でApp\Page
モデルを使いやすくするため、use
で名前空間をインポートします。
<?php
namespace App\Http\Controllers;
use App\Page; ← 追加
use Illuminate\Http\Request;
次にPage
モデルを使い、ページの一覧を取得します。Page::all();
というメソッドで、すべてのページを取得することができます。
ここでも先程のdd()
ヘルパー関数を使って中身を覗いてみましょう。
public function index()
{
$pages = Page::all();
dd($pages);
}
ブラウザで確認すると以下のように10個のデータをもったCollectionオブジェクトであることが確認できると思います。
Collection {#184 ▼
#items: array:10 [▶]
}
⌘
キーを押しながら▶
をクリックするとネストの全レベルを展開できます。
CollectionはLaravelが用意している、データ配列を便利に扱うクラスで、この例のようにデータベースから一覧を取得した場合などに返されます。iterable
なオブジェクトなので通常の配列と同じように扱うことができます。
ビューをつくる
さて、これでページの一覧データが取得できるようになったので、ブラウザに整形して表示してみたくなりますね。うずうず。
テンプレートをつくって表示してみましょう。
LaravelにはBladeというシンプルながら強力なテンプレート機能がついているので、それを利用します。
一覧ページとパーマリンクという異なるページを用意する必要がありそうなので、ベースとなるテンプレートをつくっておいて、それをページ毎に継承するようにしてみます。
ベーステンプレート
アプリケーション共通でベースとなるテンプレートです。
<!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の部分も構造を明確にするために切り分けておきます。
<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ファイルをコンパイルします。
.content {
background: #fff;
padding: 15px;
h1 {
margin-bottom: 30px;
}
}
※gulp
がうごかない場合は
$ npm install --global gulp-cli
$ npm install
くわしくはドキュメントをご参照ください
ポスト一覧ページ
これが今回使うポスト一覧ページのテンプレートです。
ページの一覧データ$pages
を@foreach
でまわしてリストとして展開します。
@extends('app')
@section('content')
<h1>Home</h1>
<ul>
@foreach ($pages as $page)
<li>
{{ $page->title }}
</li>
@endforeach
</ul>
@endsection
コントローラからビューを呼び出す
先ほどdd()
でダンプしていたところを書き換えて、ビューを呼び出すように変更します。
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を返すメソッドを追加しましょう。
public function url()
{
return route('pages.show', $this->title);
}
さらに、Laravelでは属性のかたちで任意の値を取得できる機能がついていますので、それを使ってみます。
例えば$page->url
という属性で先ほどのurl()
メソッドの結果を返すようにするにはこのように書きます。
public function getUrlAttribute()
{
return $this->url();
}
それではこちらを使ってリンクをはってみましょう。ページ一覧のテンプレートのリストの各項目にアンカータグを設定します。
@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ではエンジニアのお友だちを募集しているよ
(ノω・)テヘ
後編はこちら
ソースコードもあるよ