Laravelで投稿機能を作る

# はじめに
Laravelで初めてのアプリケーションを作成中です。
学習しながら作成しているので、私自身の頭の整理をしつつ、超基礎的な流れや頻出コマンドをまとめてみました。
かなり長いですが、この通りにおこなえば簡単にできるはずです。

ちなみに前提として、モデル・コントローラーとは何かを理解しており、環境構築は終わっていることとします。
私はRuby on Railsは簡単なアプリケーションであればサクサク作成できる程度の知識がありますが、PHP、Laravelは学習開始して1週間程度の初学者です。間違えていることなどあれば、ご指摘ください。


# 目次
<!-- タイトルとアンカー名を編集 -->
0. [開発環境](#intro)
1. [マイグレーションファイル・テーブル作成](#Chapter1)
2. [モデル作成](#Chapter2)
3. [コントローラー・ルート作成](#Chapter3)
4. [ビュー作成](#Chapter4)
5. [まとめ](#Chapter5)
6. [参考文献](#reference)

<a id="intro"></a>
# 開発環境
- AWS Cloud9
- PHP 8.0.2
- Laravel 9.21.6
- Composer 2.3.10
- MariaDB 5.5.68

# 手順
ここでは、超シンプルな投稿機能を実装します。
ベースを作成するだけですので、バリデーションやレイアウトは無視していますので、実際にアプリケーションを作成する場合は、これをもとに作りこむ必要があります。

また、名称は、それぞれ下記のようにしました。
テーブル： items
モデル： Item
コントローラー： ItemsController
もしこの記事を見てご自身で作成される場合は、それぞれ名称を変えてくださいね。


<a id="#Chapter1"></a>
### その１　マイグレーションファイル・テーブル作成

最初に、下記コマンドでマイグレーションファイルを作成します。
```
$ php artisan make:migration create_items_table
```

database/migrationsディレクトリの中にマイグレーションファイルが作られるので、テーブルに追加するカラムを記入します。
```php:database/migrations/2022_07_24_062806create_items_table.php
    public function up()
    {
        Schema::create('items', function (Blueprint $table) {
            $table->id();
            // ここから
            $table->string('name');
            $table->text('description');
            $table->integer('price');
            // ここまで追記
            $table->timestamps();
        });
    }
```

追加できたら、下記コマンドを打ってテーブルを作成します。
```
$ php artisan migrate
```

#### 【その１：応用編】Seederで初期データ追加
Seederで初期データを追加してみましょう。
下記コマンドで、database/seeders内にitemsテーブル用のseederファイルを作成します。
```
$ php artisan make:seeder ItemsTableSeeder
```

ファイルを下記のように編集します。内容は何でもOKです。
```php:database/Seeder/ItemsTableSeeder.php
class ItemsTableSeeder extends Seeder
{
    public function run()
    {
    // ここから
        DB::table('items')->insert(
            [
                'name'=>'サンプル',
                'description'=>'サンプルアイテム',
                'price'=>100,
            ]
        );
     // ここまで追記
    }
}
```
データは複数作っても良いです。お好きなだけ、お好きな内容を追加してください。
気が済んだら、ターミナルでコマンドを実行し、内容を反映させます。
```
$ php artisan migrate --seed
```

以上でテーブルが完成しました。


<a id="#Chapter2"></a>
### その２　モデル作成

次に、モデルを作っていきます。
ターミナルで、下記コマンドを実行し、モデルを作成します。
```
$ php artisan make:model Item
```

app/Modelディレクトリ内に「Item.php」というファイルが作られるはずです。
これを開いて、下記のように編集します。

```php:app/Models/Item.php

class Item extends Model
{
    use HasFactory;
    public $guarded = ['id', 'created_at']; //追記部分
}

```

追記部分について説明します。
```php
  $guarded
```
これは、データベースに保存する際の、書き込み禁止のフィールドを設定しています。
この記述をしないと、データベースに保存するときにエラーが発生します。
Eloquentという、データベースとモデルを紐づけるLavarelの機能があり、書き込み可能もしくは書き込み禁止フィールドを指定しなくてはいけないようになっているようです。

例の記述ですと、
```php
    public $guarded = ['id', 'created_at']; 
```
としているため、自動で追加される「id」と「created_at」が書き込み禁止になってます。

書き込み可能を指定することもできます。その場合は、
```php
    public $fillable = ['name', 'description', 'price']; 
```
のように記述します。
どちらでも良いみたいですが、記述禁止フィールドの方が少ないことが多いと思うので、私は$guardedを多用していきたいと思います。


<a id="#Chapter3"></a>
### その３　コントローラー・ルート作成
次に、コントローラーを作っていきます。
コントローラーは、下記のコマンドで作成します。
```
$ php artisan make:controller ItemsController --model=Item --resource
```
`--model=Item`でモデルを関連付けさせています。
また`--resource`を付けることで、投稿機能に必要なアクションを処理するコントローラーが作成できます。

コントローラーはapp/Http/Controllers内に作成されます。

余談ですが、私がPHPを学習し始めて最初に躓いたのが、コントローラーの書き方でした。（resourceコマンドを知らずに一ページずつ作ろうとしていました。）
最初に引っかかったのが、Rubyでいう「new」アクションと「create」アクションです。
Rubyの「new」 => PHPでは「create」
Rubyの「create」 => PHPでは「store」
なのですが、最初に新規投稿ページ作ろ！と思ってnewとcreateアクションを実装しようとするもののよくわからず、ちょっとして分かったときには感動しました。

本題に戻ります。
アクションごとに説明しながら、コントローラーアクションを記述していきます。

まずは、**index**から。
```php:app/Http/Controllers/ItemsController.php
  public function index()
    {
      // データベース内のすべてのItemを取得し、item変数に代入
      $items = Item::all();
      // 'items'フォルダ内の'index'viewファイルを返す。
      // その際にview内で使用する変数を代入します。
      return view('items/index', ['items' => $items]);
    }
```

PHPでは、変数は `$`を使用します。
この後作成するindexのviewで、`$items = Item::all()`で取得したデータを表示するための記述をします。そのために、viewで表示するための変数を代入しています。


次に、**create**アクションです。
Createは新規投稿作成画面を表示するアクションです。
```php:app/Http/Controllers/ItemsController.php
  public function create()
    {
      return view('items/create');
    }
```
viewの表示のみですので、説明は割愛します。


次に、**store**アクションです。
データを作成し、データベースに保存するアクションです。
```php:app/Http/Controllers/ItemsController.php
  public function store(Request $request)
    {
      // 新しい Item を作成
      $item = new Item;
      // フォームから送られてきたデータをそれぞれ代入
      $item->name = $request->name;
      $item->description = $request->description;
      $item->price = $request->price;
      // データベースに保存
      $item->save();
      // indexページへ遷移
      return redirect('/items');
    }
```
`$item->name`は、itemのnameです。これに、`$request->name`（nameフォームから送られてきた内容）をいれています。
Createページから送られてきたフォームのデータが、`$request`に入ってくるイメージだと、わかりやすいかもしれません。
再度申し上げますが、createページ（新規投稿作成画面）を含むviewページは、次のview作成のステップで作成します。


**show**は詳細画面のように、データを一つずつ表示するページです。
```php:app/Http/Controllers/ItemsController.php
  public function show($id)
    {
      $item = Item::find($id); // idでItemを探し出す
      return view('items.show', ['item' => $item]);
    }
```
showアクションは、()内にidと記入することで、findメソッドでidを呼び出せるようにします。これにより、たとえば一覧ページ（index）から、itemの詳細ページ（show）へリンクするときに、idを指定することができます。
`$item = Item::find($id);`は、idに一致するItemを探し出しています。
showページのurlは、/items/1 のように、最後にアイテムのidがつきます。

次に、**edit**と**update**です。
createとstoreに非常に似ているので、特に説明はしません。

```php:app/Http/Controllers/ItemsController.php
  public function edit($id)
    {
      $item = Item::find($id);
      return view('items.edit', ['item' => $item]);
    }

  public function update(Request $request, $id)
    {
      $item = Item::find($id);
      $item->name = $request->name;
      $item->description = $request->description;
      $item->price = $request->price;
      $item->save();
      return redirect('items/'.$id);
    }
```

最後に、**destroy**アクションです。
削除機能に使われます。こちらも非常にシンプルなので、説明は割愛します。
```php:app/Http/Controllers/ItemsController.php
  public function destroy($id)
    {
      $item = Item::find($id);
      $item->delete();
      return redirect('/items');
    }
```


今回作成したコントローラーは、この通りです。
```php:app/Http/Controllers/ItemsController.php

  public function index()
    {
      $items = Item::all();
      return view('items/index', ['items' => $items]);
    }

  public function create()
    {
      return view('items/create');
    }

  public function store(Request $request)
    {
      $item = new Item;
      $item->name = $request->name;
      $item->description = $request->description;
      $item->price = $request->price;
      $item->save();

      return redirect('/items');
    }

  public function show($id)
    {
      $item = Item::find($id);
      return view('items.show', ['item' => $item]);
    }

  public function edit($id)
    {
      $item = Item::find($id);
      return view('items.edit', ['item' => $item]);
    }

  public function update(Request $request, $id)
    {
      $item = Item::find($id);
      $item->name = $request->name;
      $item->description = $request->description;
      $item->price = $request->price;
      $item->save();
      return redirect('items/'.$id);
    }

  public function destroy($id)
    {
      $item = Item::find($id);
      $item->delete();
      return redirect('/items');
    }
```
いくらレイジーでも、コピペはしないでくださいね。


そして、重要な**ルート**を記述します。
これがないと、viewの表示もできません。

ルートは、routes/web.phpというファイルに記述していきます。
```php:routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ItemsController; // 追記

Route::get('/', function () {
    return view('layouts/app');
});

Route::resource('items', ItemsController::class); //追記
```
最初に、下記を追加して、ItemsControllerを読み込みます。
```php:routes/web.php
use App\Http\Controllers\ItemsController;
```
これがないとエラーが起きます。
「逆にルートがない」エラーのほとんどが、この一行で治りました。

次にルートを記述します。
ルートの書き方は様々ありますが、今回はresourceでCRUD機能に必要なアクションをすべて作ったので、ルートもresourceを使用します。
```php:routes/web.php
Route::resource('items', ItemsController::class); 
```
`resource`を使用することで、`ItemsController`内で定義したCRUDのアクションのルートを一括で作成しています。


ルートを確認してみます。ターミナルで下記コマンドを打ってみてください。
```
$ php artisan route:list
```
このようなルートができているはずです。
```
GET|HEAD        items ................................................ items.index › ItemsController@index
POST            items ................................................ items.store › ItemsController@store
GET|HEAD        items/create ....................................... items.create › ItemsController@create
GET|HEAD        items/{item} ........................................... items.show › ItemsController@show
PUT|PATCH       items/{item} ....................................... items.update › ItemsController@update
DELETE          items/{item} ..................................... items.destroy › ItemsController@destroy
GET|HEAD        items/{item}/edit ...................................... items.edit › ItemsController@edit
```
これは、下記のように書き換えもできます。
```php:routes/web.php
Route::get('/items', [ItemController::class, 'index']);
Route::post('/items', [ItemController::class, 'store']);
//　以下省略
```
また、`resource`を使用し、必要のないルート以外、もしくは必要なルートだけ指定することもできます。
```php:routes/web.php
// onlyを付けて、indexとshowだけ
Route::resource('items', ItemsController::class)->only(['index','show']); 
// exceptを付けて、create, store, update, destroy以外　＝ indexとshowだけ
Route::resource('items', ItemsController::class)->except(['create', 'store', 'update', 'destroy]'); 
```
他にもたくさんの記述方法があります。
今回は、ひとまず以上です。


<a id="#Chapter4"></a>
### その４　ビュー作成
次はviewを作成していきますが、今回はレイアウトなどは完全無視で、ベースとなるフォームやデータを表示させる方法についてのみ触れます。レイアウトはご自身で作成してください。

まず、Laravelは、bladeというテンプレートを使用しています。ちなみにview作成コマンドは現時点でありません。なので、ひとつづつ作っていきます。
アクションを定義した際に、viewのディレクトリを指定していますので、その通りになるように、resources/views内に**items**フォルダを作成します。

itemsフォルダ内に、viewファイルを作成します
- create.blade.php
- edit.blade.php
- index.blade.php
- show.blade.php

また、**layouts**フォルダとその中に**app.blade.php**ファイルを作成し、それを親テンプレートとします。
```
root/
　├ resources/
　│　└ views/
　│　　　└ create.blade.php
　│　　　└ edit.blade.php
　│　　　└ index.blade.php
　│　　　└ show.blade.php
　│　└ layouts/
　│　　　└ app.blade.php
```
こんな感じになればOK。

そして、`app.blade.php`を下記のように編集します。
```php:resources/layouts/app.blade.php
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>アプリケーション名</title>
    </head>
    <body class="antialiased">
        <main>
            @yield('content')
        </main>
    </body>
</html>
```
これが親テンプレートとなり、`@yield('content')`の部分に子テンプレート（各ページの内容）が表示される仕組みです。


次に、items用のページを作成します。
子テンプレートとなるページは、この形式に沿って記述します。
```php
@extends('layouts.app') // 親テンプレート'layouts.app'内に表示するための記述

@section('content')
  // 内容
@endsection
```


それぞれの内容は下記を参考にしてください。

### **Createページ**

```php:resources/views/create.blade.php
@extends('layouts.app')

@section('content')
  <div class="create-items">
    <div class="form">
      <form action="/items" method="POST">
        @csrf // 送信されるデータを保護する
        
        <div class="input-form">
          <label for="name">Name</label>
          <input name="name">
        </div>
        <div class="input-form">
          <label for="description">Description</label>
          <textarea name="description"></textarea>
        </div>
        <div class="input-form">
          <label for="price">Price</label>
          <input name="price">
        </div>
        <div class="input-form">
          <input type="submit" value="Submit">
        </div>
      </form>
    </div>
  </div>
@endsection
```
基本はHTMLだけで構成されていますが、下記の記述を追加することを忘れないようにしてください。
```php
   @csrf
   // 以下の記述でもOK
   {{ csrf_field() }}
```
これがないと、データが保護されていません。という表示が出てきて、エラーになってしまいました。

### **Editページ**
※createページとほぼ一緒です。`{{$item->name}}`などを追加することで、既に保存されている情報をフォームに表示します。
```php:resources/views/edit.blade.php
@extends('layouts.app')

@section('content')
  <div class="create-items">
    <div class="form">
      <form action="/items/{{$item->id}}" method="POST">
        @csrf
        @method('PUT')
        <div class="input-form">
          <label for="name">Name</label>
          <input name="name" value="{{$item->name}}">
        </div>
        <div class="input-form">
          <label for="description">Description</label>
          <textarea name="description">{{$item->description}}</textarea>
        </div>
        <div class="input-form">
          <label for="price">Price</label>
          <input name="price" value="{{$item->price}}">
        </div>
        <div class="input-form">
          <input type="submit" value="Update">
        </div>
      </form>
    </div>
  </div>
@endsection

```
メソッドを指定する必要があることだけ忘れないでください。
```
        @method('PUT')
```
この記述により、POSTではなくPUTメソッドであることを指定しています。


### **Indexページ**
```php:resources/views/index.blade.php
@extends('layouts.app')

@section('content')
  <table>
    <tr>
      <th>Item Id</th>
      <th>Name</th>
      <th>Description</th>
      <th>Price</th>
      <td></td>
    </tr>
    @foreach($items as $item)
      <tr>
        <td>{{$item->id}}</td>
        <td>{{$item->name}}</td>
        <td>{{$item->description}}</td>
        <td>{{$item->price}}</td>
        <td><a href="/items/{{$item->id}}">Details</a></td> // showページへのリンク
      </tr>
    @endforeach
  </table>
@endsection
```
seederで初期データを追加している場合は、すでにデータが表示されるはずです。

### **Showページ**
```php:resources/views/show.blade.php
@extends('layouts.app')

@section('content')
  <table>
    <tr>
      <th>Item Id</th>
      <th>Name</th>
      <th>Description</th>
      <th>Price</th>
    </tr>
      <tr>
        <td>{{$item->id}}</td>
        <td>{{$item->name}}</td>
        <td>{{$item->description}}</td>
        <td>{{$item->price}}</td>
      </tr>
  </table>
  <a href="/items/{{$item->id}}/edit">Edit</a>
  
  <form action="/items/{{$item->id}}" method="POST">
    {{ csrf_field() }}
    <input type="hidden" name="_method" value="delete">
    <input type="submit" name="" value="Delete">
  </form>
  
  <a href="/items">Back to index</a>
@endsection
```

これで、下記の事ができるようになるはずです。
- 新規itemの登録（create, store）
- 登録済みitemの一覧表示（index）
- 登録済みitemの詳細表示（show）
- 登録済みitemの編集（edit, update）
- 登録済みitemの削除（destroy）
<a id="#Chapter5"></a>
# まとめ
お疲れ様でした。
長くなりましたが、ここまでお読みいただきありがとうございます。

PHP、Laravelの学習を始めて間もないですが、Laravel 9やPHP 8の情報はまだあまり多くなく、エラーが出た時の解決が簡単ではありませんでした。
ここまでの工程は簡単に作れるようになったので、これからもアプリケーションを作成しながら、新しい機能を作れるようになりたいです。

<a id="#reference"></a>
# 参考文献
- [Laravel 8.X (英語)](https://readouble.com/laravel/8.x/en)
- [僕がLaravelのEloquentに$fillableでなく$guardedを指定する理由](https://qiita.com/toro_ponz/items/b33c757cb7ba5bb48ed4)