Laravel基礎（５）Webアプリ開発の実践

Laravelでカレンダーアプリを例に開発の流れを紹介します。

[デモサイト](https://cal435.herokuapp.com/)

#最初にアプリの雛形を作ります

```sh

laravel new cal_lara
cd cal_lara
```
##タイムゾーンやロケールは次のように設定します

```config/app.php '
timezone' => 'Asia/Tokyo', 
'locale' => 'ja',
``` 
##DB接続内容を設定します
編集するファイル：.env

#休日モデルを作成します

アプリが扱うリソースの名前を決めて、その名前でモデルクラスを作成します。カレンダーアプリなので、休日を扱うリソース（テーブル）を持たせます。

```sh

php artisan make:model Holiday 
```

##マイグレーション(DBテーブルの用意)も行っておきます

```sh

php artisan make:migration Holiday 
```

出来上がったファイルの中にholidaysテーブルのスキーマ定義を書きます。 

```database/migrations/YYYY_MM_DD_HHMMSS_holiday.php 
class Holiday extends Migration {  
    public function up() { 
        Schema::create('holidays', function (Blueprint $table) { 
            $table->increments('id'); 
            $table->date('day'); 
            $table->string('description'); 
            $table->timestamps(); 
        }); 
    } 
    public function down() {
        Schema::drop('holidays'); 
    } 
} 
```

##マイグレーション実行します 

```sh

php artisan migrate 
```
マイグレーションをやり直したい場合は次の通りです。

```sh

php artisan migrate:refresh
```

#カレンダーコントローラーを作ります

```sh

php artisan make:controller CalendarController
```

まずは休日の入力と表示のメソッドを作ります。

```app/Http/Controllers/CalendarController.php 

use App\Holiday;
use Illuminate\Http\Request;
class CalendarController extends Controller
{
    public function getHoliday(Request $request)
    {
        // 休日データ取得
        $list = Holiday::all();
        return view('calendar.holiday', ['list' => $list]);        
    }
    public function postHoliday(Request $request)
    {
        // POSTで受信した休日データの登録
        $holiday = new Holiday(); 
        $holiday->day = $request->day;
        $holiday->description = $request->description;        
        $holiday->save();
        // 休日データ取得
        $list = Holiday::all();
        return view('calendar.holiday', ['list' => $list]);
    }
}
```
 Viewは後ほど用意しますが、入力、表示ともに同じViewを使うようにします。

##ルーティング情報を設定します

ルーティングにコントローラークラス@メソッドを定義します。 

```routes/web.php 
Route::get('/holiday','CalendarController@getHoliday');
Route::post('/holiday','CalendarController@postHoliday');
```
 
#ビュー(Blade)を作ります

```sh

mkdir resources/views/calendar
```
Viewではコントローラーに対して日付と説明を送信するフォーム部分と、コントローラーから休日一覧を受け取り、その結果を表示する部分の２つの機能を持たせます。

```resources/views/calendar/holiday.blade.php 
<html>
<head>
    <title>休日データ</title>
</head>
<body>
    <h1>休日</h1>
    <!-- 休日入力フォーム -->
    <form method="POST" action="/holiday"> 
    {{csrf_field()}}     
    日付: <input type="text" name="day"> [YYYY/MM/DD]
    説明: <input type="text" name="description"> 
    <input type="submit"> 
    </form> 
    <!-- 休日一覧表示 -->
    <table>
    <tr>
    <th>日付</th>
    <th>説明</th>
    <th>作成日</th>
    <th>更新日</th>
    </tr>
    @foreach($list as $val)
    <tr>
        <td>{{$val->day}}</td>
        <td>{{$val->description}}</td>
        <td>{{$val->created_at}}</td>
        <td>{{$val->updated_at}}</td>
    </tr>
    @endforeach
    </table>
</body>
</html>
```

ここまで作ったら、動かしてみましょう。休日データが登録できて、一覧取得できるはずです。

```sh

 php artisan serve --port 8080
```
URLアドレス：http://[servername]/holiday

##レイアウトを分割し、ついでにJQuery/Bootstrap導入します

レイアウトファイルを作り、HTMLの共通部分はこのファイルに書くようにします。

```resources/views/layout.blade.php 

<!DOCTYPE HTML>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>@yield('title')</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
    <link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
    <script src="https://code.jquery.com/jquery-1.12.4.js"></script>
    <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
    @yield('content')
</div>
</body>
</html>
```

先ほど作ったBladeファイルを下記のように変更します。

```resources/views/calendar/holiday.blade.php

@extends('layout')
@section('title', '休日設定')
@section('content')
    <h1>休日設定</h1>
    <!-- 休日入力フォーム -->
    <form method="POST" action="/holiday"> 
    <div class="form-group">
    {{csrf_field()}}     
    <label for="day">日付[YYYY/MM/DD] </label>
    <input type="text" name="day" class="form-control" id="day">
    <label for="description">説明</label>
    <input type="text" name="description" class="form-control" id="description"> 
    </div>
    <button type="submit" class="btn btn-primary">登録</button> 
    </form> 
    <!-- 休日一覧表示 -->
    <table class="table">
    <thead>
    <tr>
    <th scope="col">日付</th>
    <th scope="col">説明</th>
    <th scope="col">作成日</th>
    <th scope="col">更新日</th>
    </tr>
    </thead>
    <tbody>
    @foreach($list as $val)
    <tr>
        <th scope="row">{{$val->day}}</th>
        <td>{{$val->description}}</td>
        <td>{{$val->created_at}}</td>
        <td>{{$val->updated_at}}</td>
    </tr>
    @endforeach
    </tbody>
    </table>
@endsection
```
`@extends`で `“layout”` (layout.blade.php) を継承するよう指示しています。

`@section(‘content’) `から `@endsection` までが、layout ビューの `@yield(‘content’) `の箇所に流し込む部分になります。

ここでbootstrapのCSSを使うようにし画面をリッチにします。
参考：
https://getbootstrap.com/docs/4.0/content/tables/





##JQueryで日付入力を便利にします

日付（YYYY/MM/DD）の入力をポップアップカレンダーから選択して日付入力できるようにしてみましょう。
次のコードを書きます。（`<body>`タグの中ならどこでもいいです）

```resources/views/calendar/holiday.blade.php
・・（一部抜粋）・・
<script>
  $( function() {
    $( "#day" ).datepicker({dateFormat: 'yy-mm-dd'});
  } );
</script>
```
そして日付の入力をするフォームのid属性をつけます。（bootstrapの適用の時id="day"と入れたのでそれを使います）
`<input type="text" id="day">`


参考：
http://jqueryui.com/datepicker/

ここまで作ったら、動かしてみましょう。休日一覧が綺麗に表示され、日付入力時にカレンダーから日付が選択できるはずです。

```sh

 php artisan serve --port 8080
```
URLアドレス：http://[servername]/



#カレンダーの表示処理を作ります

カレンダー処理を行うクラスをComposerでオートロードするようにします。
以下のようなクラスを作成します。

```app/Calendar.php

namespace App;
class Calendar
{
    private $html;    
    public function showCalendarTag($m, $y)
    {
        $year = $y;
        $month = $m;
        if ($year == null) {
            // システム日付を取得する。 
            $year = date("Y");
            $month = date("m");
        }
        $firstWeekDay = date("w", mktime(0, 0, 0, $month, 1, $year)); // 1日の曜日(0:日曜日、6:土曜日)
        $lastDay = date("t", mktime(0, 0, 0, $month, 1, $year)); // 指定した月の最終日
        // 日曜日からカレンダーを表示するため前月の余った日付をループの初期値にする
        $day = 1 - $firstWeekDay;
        $this->html = <<< EOS
<h1>{$year}年{$month}月</h1>
<table class="table table-bordered">
<tr>
  <th scope="col">日</th>
  <th scope="col">月</th>
  <th scope="col">火</th>
  <th scope="col">水</th>
  <th scope="col">木</th>
  <th scope="col">金</th>
  <th scope="col">土</th>
</tr>
EOS;
        // カレンダーの日付部分を生成する
        while ($day <= $lastDay) {
            $this->html .= "<tr>";
            // 各週を描画するHTMLソースを生成する
            for ($i = 0; $i < 7; $i++) {
                if ($day <= 0 || $day > $lastDay) {
                    // 先月・来月の日付の場合
                    $this->html .= "<td>&nbsp;</td>";
                } else {
                   $this->html .= "<td>" . $day . "</td>"; 
                }
               $day++;
            }
            
            $this->html .= "</tr>";
        }
        
        return $this->html .= '</table>';
    }
}
```
HTMLタグをコードに埋め込み、指定の年月のカレンダーを表として出力するshowCalendarTagメソッドを作ります。
プロジェクトディレクトリ直下にあるcomposer.jsonの`autoload.files`へ以下のようにこのCalendarクラスのファイル・パスを追加します。

```composer.json
・・（一部抜粋）・・
"autoload": {
    "classmap": [
        ...
    ],
    "psr-4": {
        "App\\": "app/"
    },
    "files": [
        "app/Calendar.php"
    ]
},
```
その後、以下のComposerコマンドを実行します。

```sh

composer dump-autoload
```

これでCalendarクラスが使えるようになりました。
コントローラーでカレンダーの表示のためのCalendarクラスをインスタンス化し、メソッドを呼び出します。

```app/Http/Controllers/CalendarController.php 

use App\Calendar;
・・（一部抜粋）・・
    public function index(Request $request)
    {
        $cal = new Calendar();
        $tag = $cal->showCalendarTag($request->month,$request->year);
        
        return view('calendar.index', ['cal_tag' => $tag]);
    }
```

##ルーティング情報はコントローラークラス@メソッドを定義します
```routes/web.php 

Route::get('/','CalendarController@index');
```

Viewではコントローラーから受け取る結果（カレンダー表）を表示します

```resources/views/calendar/index.blade.php 

@extends('layout')
@section('title', 'カレンダー')
@section('content')
    {!!$cal_tag!!}
@endsection
```

##カレンダーと休日一覧の画面の行き来を行います

先ほど作った休日一覧画面のholiday.blade.phpファイルに下記を追加

```resources/views/calendar/holiday.blade.php

<a href="{{ url('/') }}">カレンダーに戻る</a>
```

先ほど作ったカレンダー画面のindex.blade.phpファイルに下記を追加

```resources/views/calendar/index.blade.php
<a href="{{ url('/holiday') }}">休日設定</a>
```


ここまで作ったら、動かしてみましょう。カレンダーが表示され、休日一覧画面との行き来ができるはずです。

```sh

 php artisan serve --port 8080
```
URLアドレス：http://[servername]/


##カレンダーに休日情報を入れます

Calendarクラスで休日データをもらうようにします。
まずインスタンス変数を作り、コンストラクタで受け取るようにします。

```app/Calendar.php
・・（一部抜粋）・・
    private $holidays; 
    function __construct($holidays) {
        $this->holidays = $holidays;
    }
```
日付を入れる行： `$this->html .= "<td>" . $day . "</td>";` を下記のように書き換え、日付に応じて休日の情報を表示できるようにします。

```php
・・（一部抜粋）・・
                   $this->html .= "<td>" . $day ."&nbsp"; 
                   $target = date("Y-m-d", mktime(0, 0, 0, $month, $day, $year)); 
                   foreach($this->holidays as $val) {
                       if ($val->day == $target) {
                           $this->html .= $val->description; 
                           break;
                       }
                   }
                   $this->html .= "</td>"; 
```

コントローラーのindexでは

```app/Http/Controllers/CalendarController.php
・・（一部抜粋）・・
        $list = Holiday::all();
        $cal = new Calendar($list);
```
とCalendarクラスのインスタンス化の時に休日のデータを渡すようにします。

ここまで作ったら、再度動かしてみましょう。カレンダーが表示され、休日が表示できるはずです。（休日データを登録してくださいね）

```sh

 php artisan serve --port 8080
```
URLアドレス：http://[servername]/


##カレンダーの月めくりを行います

Calendarクラスの中で前月と翌月の年月の計算を行い、ボタンを表示できるようにします。

次のコードを表示すべき\$yearと\$monthが確定したのちの適切な箇所に追加します。

```app/Calendar.php
・・（一部抜粋）・・
        // 前月
        $prev = strtotime('-1 month',mktime(0, 0, 0, $month, 1, $year));
        $prev_year = date("Y",$prev);
        $prev_month = date("m",$prev);
        // 翌月
        $next = strtotime('+1 month',mktime(0, 0, 0, $month, 1, $year));
        $next_year = date("Y",$next);
        $next_month = date("m",$next);
```
そしてHTMLの年月の表示部分のところで、前月と翌月のリンクボタンを表示します。

```app/Calendar.php
・・（一部抜粋）・・
         $this->html = <<< EOS
<h1>
<a class="btn btn-primary" href="/?year={$prev_year}&month={$prev_month}" role="button">&lt;前月</a>
{$year}年{$month}月
<a class="btn btn-primary" href="/?year={$next_year}&month={$next_month}" role="button">翌月&gt;</a>
</h1>
```
ここまで作ったら、再度動かしてみましょう。カレンダーが表示され、月めくりできるはずです。

```sh

 php artisan serve --port 8080
```
URLアドレス：http://[servername]/

##休日の修正ができるようにします

休日一覧の日付をクリックするとそのデータを編集できるようにします。
ルーティング情報で編集のアクションを定義します。 

```routes/web.php 

Route::get('/holiday/{id}','CalendarController@getHolidayId');
```

コントローラーのなかの表示と編集のメソッドを書き換えます。

```app/Http/Controllers/CalendarController.php 
・・（一部抜粋）・・
class CalendarController extends Controller
{
    public function getHoliday(Request $request)
    {   
        // 休日データ取得
        $data = new Holiday();
        $list = Holiday::all();
        return view('calendar.holiday', ['list' => $list,'data' => $data]);
    }
    public function getHolidayId($id)
    {
        // 休日データ取得
        $data = new Holiday();
        if (isset($id)) {
            $data = Holiday::where('id', '=', $id)->first();
        } 
        $list = Holiday::all();
        return view('calendar.holiday', ['list' => $list, 'data' => $data]);
    }
    public function postHoliday(Request $request)
    {
        // POSTで受信した休日データの登録
        if (isset($request->id)) {
            $holiday = Holiday::where('id', '=', $request->id)->first();
            $holiday->day = $request->day;
            $holiday->description = $request->description;        
            $holiday->save();
        } else {
            $holiday = new Holiday(); 
            $holiday->day = $request->day;
            $holiday->description = $request->description;        
            $holiday->save();
        }
        // 休日データ取得
        $data = new Holiday();
        $list = Holiday::all();
        return view('calendar.holiday', ['list' => $list, 'data' => $data]);
    }
}
```

Viewに次のコードを書きます。
編集のためにIDをHiddenで送信できるようにし、各フォームに編集するべき内容を入れるようにします。

```resources/views/calendar/holiday.blade.php
・・（一部抜粋）・・
    <form method="POST" action="/holiday"> 
    <div class="form-group">
    {{csrf_field()}}    
    <label for="day">日付[YYYY/MM/DD] </label>
    <input type="text" name="day" class="form-control" id="day" value="{{$data->day}}">
    <label for="description">説明</label>
    <input type="text" name="description" class="form-control" id="description" value="{{$data->description}}"> 
    </div>
    <button type="submit" class="btn btn-primary">登録</button> 
    <input type="hidden" name="id" value="{{$data->id}}">
    </form> 
・・
    <!-- 日付のリンクをつける -->
    <th scope="row"><a href="{{ url('/holiday/'.$val->id) }}">{{$val->day}}</a></th>

```

ここまで作ったら、再度動かしてみましょう。休日が修正できるはずです。

```sh

 php artisan serve --port 8080
```
URLアドレス：http://[servername]/


##休日の削除ができるようにします

Viewに次のコードを書き削除のためのボタンを追加します。
休日一覧の`<table>`に新しい列を作るため`<td>`のなかに次のフォームを追加します。

```resources/views/calendar/holiday.blade.php
・・（一部抜粋）・・
        <td><form action="/holiday" method="post">
            <input type="hidden" name="id" value="{{$val->id}}">
            {{ method_field('delete') }}
            {{csrf_field()}} 
            <button class="btn btn-default" type="submit">Delete</button>
        </form></td>

```

ルーティング情報で削除のアクションを定義します 

```routes/web.php 

Route::delete('/holiday','CalendarController@deleteHoliday');
``` 

コントローラーのなかに削除のメソッドを書き加えます。

```app/Http/Controllers/CalendarController.php 
・・（一部抜粋）・・
    public function deleteHoliday(Request $request)
    {
        // DELETEで受信した休日データの削除
        if (isset($request->id)) {
            $holiday = Holiday::where('id', '=', $request->id)->first();        
            $holiday->delete();
        }
        // 休日データ取得
        $data = new Holiday();
        $list = Holiday::all();
        return view('calendar.holiday', ['list' => $list, 'data' => $data]);
    }
```
ここまで作ったら、再度動かしてみましょう。休日が削除できるはずです。

```sh

 php artisan serve --port 8080
```
URLアドレス：http://[servername]/



#バリデーションを行います

コントローラーのなかのpostHolidayメソッドの中で次のコードを追加します。

```app/Http/Controllers/CalendarController.php
・・（一部抜粋）・・
    public function postHoliday(Request $request)
    {
        $validatedData = $request->validate([
            'day' => 'required|date_format:Y-m-d',
            'description' => 'required',
        ]);
```

バリデーション結果のメッセージを表示するためにViewに次のコードを書きます。

```resources/views/calendar/holiday.blade.php
・・（一部抜粋）・・
@if ($errors->any())
    <div class="alert alert-danger">
        <ul>
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    </div>
@endif
```
ここまで作ったら、再度動かしてみましょう。休日のフォームで空のときバリデーションが機能し、登録できないはずです。

```sh

 php artisan serve --port 8080
```
URLアドレス：http://[servername]/


#日本語化のため

https://gist.github.com/syokunin/b37725686b5baf09255b

resources/lang/ja/validation.phpを上記URLで取得したvalidation.phpで置き換えます。
フォーム項目名の日本語化は同ファイルのattributesで定義します。

```resources/lang/ja/validation.php
・・（一部抜粋）・・
    'attributes' => [
        'day' => '日付',
        'description' => '説明',
    ],
```


以上、これで完了です。

[ソース Github](https://github.com/taka027/cal_lara)