3
1

More than 1 year has passed since last update.

Laravelで投稿機能を作る

Last updated at Posted at 2022-07-24

はじめに

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

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

目次

  1. 開発環境
  2. マイグレーションファイル・テーブル作成
  3. モデル作成
  4. コントローラー・ルート作成
  5. ビュー作成
  6. まとめ
  7. 参考文献

開発環境

  • AWS Cloud9
  • PHP 8.0.2
  • Laravel 9.21.6
  • Composer 2.3.10
  • MariaDB 5.5.68

手順

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

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

その1 マイグレーションファイル・テーブル作成

最初に、下記コマンドでマイグレーションファイルを作成します。

$ php artisan make:migration create_items_table

database/migrationsディレクトリの中にマイグレーションファイルが作られるので、テーブルに追加するカラムを記入します。

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

【その1:応用編】Seederで初期データ追加

Seederで初期データを追加してみましょう。
下記コマンドで、database/seeders内にitemsテーブル用のseederファイルを作成します。

$ php artisan make:seeder ItemsTableSeeder

ファイルを下記のように編集します。内容は何でもOKです。

database/Seeder/ItemsTableSeeder.php
class ItemsTableSeeder extends Seeder
{
    public function run()
    {
    // ここから
        DB::table('items')->insert(
            [
                'name'=>'サンプル',
                'description'=>'サンプルアイテム',
                'price'=>100,
            ]
        );
     // ここまで追記
    }
}

データは複数作っても良いです。お好きなだけ、お好きな内容を追加してください。
気が済んだら、ターミナルでコマンドを実行し、内容を反映させます。

$ php artisan migrate --seed

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

その2 モデル作成

次に、モデルを作っていきます。
ターミナルで、下記コマンドを実行し、モデルを作成します。

$ php artisan make:model Item

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

app/Models/Item.php

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

追記部分について説明します。

  $guarded

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

例の記述ですと、

    public $guarded = ['id', 'created_at']; 

としているため、自動で追加される「id」と「created_at」が書き込み禁止になってます。

書き込み可能を指定することもできます。その場合は、

    public $fillable = ['name', 'description', 'price']; 

のように記述します。
どちらでも良いみたいですが、記述禁止フィールドの方が少ないことが多いと思うので、私は$guardedを多用していきたいと思います。

その3 コントローラー・ルート作成

次に、コントローラーを作っていきます。
コントローラーは、下記のコマンドで作成します。

$ 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から。

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は新規投稿作成画面を表示するアクションです。

app/Http/Controllers/ItemsController.php
  public function create()
    {
      return view('items/create');
    }

viewの表示のみですので、説明は割愛します。

次に、storeアクションです。
データを作成し、データベースに保存するアクションです。

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は詳細画面のように、データを一つずつ表示するページです。

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がつきます。

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

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アクションです。
削除機能に使われます。こちらも非常にシンプルなので、説明は割愛します。

app/Http/Controllers/ItemsController.php
  public function destroy($id)
    {
      $item = Item::find($id);
      $item->delete();
      return redirect('/items');
    }

今回作成したコントローラーは、この通りです。

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というファイルに記述していきます。

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を読み込みます。

routes/web.php
use App\Http\Controllers\ItemsController;

これがないとエラーが起きます。
「逆にルートがない」エラーのほとんどが、この一行で治りました。

次にルートを記述します。
ルートの書き方は様々ありますが、今回はresourceでCRUD機能に必要なアクションをすべて作ったので、ルートもresourceを使用します。

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

これは、下記のように書き換えもできます。

routes/web.php
Route::get('/items', [ItemController::class, 'index']);
Route::post('/items', [ItemController::class, 'store']);
// 以下省略

また、resourceを使用し、必要のないルート以外、もしくは必要なルートだけ指定することもできます。

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]'); 

他にもたくさんの記述方法があります。
今回は、ひとまず以上です。

その4 ビュー作成

次は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を下記のように編集します。

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用のページを作成します。
子テンプレートとなるページは、この形式に沿って記述します。

@extends('layouts.app') // 親テンプレート'layouts.app'内に表示するための記述

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

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

Createページ

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だけで構成されていますが、下記の記述を追加することを忘れないようにしてください。

   @csrf
   // 以下の記述でもOK
   {{ csrf_field() }}

これがないと、データが保護されていません。という表示が出てきて、エラーになってしまいました。

Editページ

※createページとほぼ一緒です。{{$item->name}}などを追加することで、既に保存されている情報をフォームに表示します。

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ページ

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ページ

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)

まとめ

お疲れ様でした。
長くなりましたが、ここまでお読みいただきありがとうございます。

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

参考文献

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1