Laravel初心者向けの無料チュートリアル③つぶやきの作成と保存と表示

Laravel初心者向けの無料チュートリアル③つぶやきの作成と保存と表示

1048 回閲覧されました

みなさんこんにちは、jonioです。

今回はつぶやきを作成するページでつぶやきを作成してそれを保存して作成したつぶやきの一覧を表示するまでの解説です。

1回目の解説から読んで欲しいのですが読んでない方は↓からどうぞ。

今回から難しく感じるはずですが頑張って下さい。

まずはつぶやきの作成と保存です。

 

つぶやきの作成と保存する時の考え方

あなたがつぶやきを投稿する時をイメージすると考えやすいと思います。

まずつぶやきを投稿するページに移動します。

そしてつぶやきの内容を書いて保存します。

という事は2つの作業(ページへの移動とつぶやきの作成)でルーティング・コントローラー・ビューの処理が必要になります。

  • つぶやきを投稿するページの表示
  • つぶやきの保存

ルーティングに関しては前回の解説でリソースコントローラーを使っている場合はweb.phpの24行目(下記のコード)しか書かなくていいいと説明したので今回は必要ないです。

Route::resource('/post','PostController'); 

だからコントローラーを書きます。

まずはつぶやきを投稿するページを表示するための記述をして次につぶやきの保存の記述をします。

 

つぶやきを作成するページを表示するコントローラー

前回の解説で処理を4つに分けているのがCRUDでLaravelでは細かく分けて7つにしていると解説しました。

  • index : つぶやき一覧の表示
  • create : つぶやきの作成
  • show : 複数あるつぶやきの内の1つの表示
  • store : 作成したつぶやきの保存
  • update : つぶやきを編集した時の更新
  • edit : つぶやきの内容の編集
  • destroy : つぶやきの削除

つぶやきを作成するのは上から2つ目のcreateなのでPostController.phpのcreateアクションに記述します。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class PostController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        return view('post.index');
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        return view('post.create');                        //この行を修正
    }

26行目を修正しています。

「post.create」がなぜこれになるかはターミナルで「php artisan route:list」をすれば分かります。

↓の赤枠です。

青枠はURLで赤枠は表示に使うファイル名です。

この場合はpostフォルダの中のcreate.blade.phpという意味です。

これだけでは意味が分からないと思うので踏み込んで説明します。

前回は説明しませんでしたがリソースコントローラーを使った場合CRUDを細かく分けた7つのアクション名がpost.createcreateやpost.indexindexやpost.storestoreなどに対応します。

対応は下記になります。

  • アクション名がindex : 表示するページはpost.index
  • アクション名がcreate : 表示するページはpost.create
  • アクション名がshow : 表示するページはpost.show
  • アクション名がstore : 表示するページはpost.store
  • アクション名がupdate : 表示するページはpost.update
  • アクション名がedit : 表示するページはpost.edit
  • アクション名がdestroy : vpost.destroy

今回はアクション名がcreateなので表示するページは「post.create」になります。

これでコントローラーはできたので次はビューを作ります。

 

つぶやきを作成するページを作る

既に作成したpostフォルダの下にcreate.blade.phpを作成します。

create.blade.phpを作成したら中身のコードを↓にします。

@extends('layouts.app')
@section('content')
<div class="row">
    <div class="col-md-10 mt-6  ms-auto me-auto">
        <div class="card-body">
            <h1 class="mt4">つぶやきの作成</h1>
            <form>
                <div class="form-group mt-3">
                        <label for="body">つぶやきの内容</label>
                        <textarea name="body" class="form-control" id="body" cols="30" rows="10" placeholder="ここにつぶやきの内容を書く"></textarea>
                </div>
                <button type="submit" class="btn btn-success mt-3">つぶやく</button>
            </form>
        </div>
    </div>
 </div>
 @endsection

そして「http://localhost:8888/post/create」に接続しますが「/post/create」は↓の青枠から判断しました。

青枠の探し方ですがcreateアクションでのURLなので↑の一覧の中からcreateを探して見つけました。

赤枠から青枠を対応させます。

このやり方をすればindexアクションならURLが「post」でdestroyアクションならURLが「post/{post}」と簡単に探す事ができると思います。(ネット上で探し方を教えている説明を見つける事ができなくて最初苦労しました)

話を戻してページに接続して表示が↓になれば大丈夫です。

 

コードの説明

HTMLに関しては説明しないので自分で調べて下さい。

分からないのが1行目・2行目17行目のはずなのでそれの説明をします。

1行目の@extendsは「ファイルをコピーしますよ」という意味になります。

どのファイルをコピーするかですが@extends(‘layouts.app’)のlayouts.appで「layoutsフォルダの中のapp.blade.php」をコピーするという意味です。

.blade.php」は省略します。

今は@extends(layouts.app)ですがもし@extends(‘layouts.kkk’)だったら「layoutsフォルダの中のkkk.blade.php」をコピーするという意味になります。

次は2行目を説明しますが一旦app.blade.phpの中身を見ます。

<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>{{ config('app.name', 'Laravel') }}</title>

    <!-- Scripts -->
    <script src="{{ asset('js/app.js') }}" defer></script>

    <!-- Fonts -->
    <link rel="dns-prefetch" href="//fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">

    <!-- Styles -->
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
    <div id="app">
        <nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
            <div class="container">
                <a class="navbar-brand" href="{{ url('/') }}">
                    {{ config('app.name', 'Laravel') }}
                </a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
                    <span class="navbar-toggler-icon"></span>
                </button>

                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <!-- Left Side Of Navbar -->
                    <ul class="navbar-nav me-auto">

                    </ul>

                    <!-- Right Side Of Navbar -->
                    <ul class="navbar-nav ms-auto">
                        <!-- Authentication Links -->
                        @guest
                            @if (Route::has('login'))
                                <li class="nav-item">
                                    <a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
                                </li>
                            @endif

                            @if (Route::has('register'))
                                <li class="nav-item">
                                    <a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
                                </li>
                            @endif
                        @else
                            <li class="nav-item dropdown">
                                <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
                                    {{ Auth::user()->name }}
                                </a>

                                <div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
                                    <a class="dropdown-item" href="{{ route('logout') }}"
                                       onclick="event.preventDefault();
                                                     document.getElementById('logout-form').submit();">
                                        {{ __('Logout') }}
                                    </a>

                                    <form id="logout-form" action="{{ route('logout') }}" method="POST" class="d-none">
                                        @csrf
                                    </form>
                                </div>
                            </li>
                        @endguest
                    </ul>
                </div>
            </div>
        </nav>

        <main class="py-4">
            @yield('content')
        </main>
    </div>
</body>
</html>

79行目に「@yield(content)」がありますがcontentの所にcreate.blade.phpの@section(‘content’)〜endsectionの間の3行目〜16行目が入るという意味になります。

まとめて説明すると@extends(‘layouts.app’)でapp.blade.phpをコピーして@section(‘content’)〜@endsectionがapp.blade.phpの@yield(‘content’) に入りますよという意味になります。

コピーするけど部分的に内容を変えますよというイメージです。

難しく感じると思うのですが(私も最初は感じました)すぐ慣れるのでとりあえず「そんなもんだ」として見慣れていくか理解したいなら↓の記事を読めば理解が深まるかもしれません。

これでつぶやきを作成するページを表示する為の記述が終わったので次はつぶやきを保存するための処理です。

作成したつぶやきを保存する時はどこのページで保存するかイメージは湧きますか?

つぶやきを作成するページですよね。

だから「つぶやく」ボタンを押すだけでページは必要ないのでコントローラーだけを作成します。

この様に保存するとか削除するとか場合によってはページ(ビュー)が必要ない場合があります。

 

モデルについて

コントローラーを書く前にモデルについて追加の説明をします。

前回の解説でコントローラーがモデルに連絡してモデルがデータベースから情報を持って来ると説明しました。

つぶやきを保存する時はデータベースのテーブルに保存するのですがコントローラーがモデルに連絡してデータベースに保存をします。

だからモデルを作成しないといけません。

モデルを作成するときのターミナルのコマンドは「php artisan make:model モデル名」です。

今回のモデル名はつぶやきの投稿なので「Post」とします。

またデータベースにつぶやきを保存するためのテーブルを作らないといけないのです。

それをマイグレーションファイルでする事ができるのモデルの作成時に一緒に作成します。

↓の赤枠がMAMPのデータベースのカラムです。

カラムが何かを知らない方は調べて下さい。

モデルと一緒にマイグレーションファイルを作成する時のコマンドは「php artisan make:model モデル名 -m」です。

だからコマンドは「php artisan make:model Post -m」とします。

コマンドを叩いたらマイグレーションファイルを編集してカラムを追加しますがbodyカラムを追加します。

マイグレーションファイルは「Laravelのプロジェクト > databaes > migrations > 本日の日付_create_posts_table.php」にあります。

マイグレーションファイルを開いて↓の記述をします。

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreatePostsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->text('body');                                //この行を追加
            $table->timestamps();
        });
    }

18行目を追加しています。

「$table->text(‘body‘);」ですが「text」・「body」以外はそのまま書いていいです。

書き方は「$table->データ型(‘カラム名’)」です。

bodyは追加するカラム名です。

textはざっくりとした説明をすると長い文字列を使うカラムに対して使用します。

逆にタイトルみたいに短い文字列に使う場合はtextではなくstringにします。

とりあえずこれ位の理解でいいです。

18行目の追加が終わったらマイグレーションファイルの内容をデータベースに反映させます。

その為のコマンドは「php artisan migrate」です。

するとMAMPのphpMyAdminのpostsテーブルにbodyカラムが追加されているのが確認できます。

つぶやきを保存する為のコントローラー

それではPostController.phpにつぶやきを保存する為の記述を追加しますが7つのアクションは↓でした。

  • index : つぶやき一覧の表示
  • create : つぶやきの作成
  • show : 複数あるつぶやきの内の1つの表示
  • store : 作成したつぶやきの保存
  • update : つぶやきを編集した時の更新
  • edit : つぶやきの内容の編集
  • destroy : つぶやきの削除

保存するのは上から4つ目のstoreアクションなのでPostController.phpのstoreアクションのコードに記述します。

コードを↓にします。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Post;                                      //この行を追加

class PostController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        return view('post.index');
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        return view('post.create');
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
    
    
        //ここから追加
        $posts = new Post();
        $posts->body = $request->body;
        $posts->save();
        return back();
        //ここまで追加
        
        
    }

41行目の「Post」ですがこれはPostモデルです。

Postモデルを使える様にする為に6行目の「use App\Models\Post; 」があります。(名前空間と言います)

App\Models\Postは階層構造になっていてuseがappフォルダの下にあるModelsフォルダの下にあるPost.phpを使いますという意味です。

意味が分からなかったらモデルを使う時は「use App\Models\モデル名」と書くと覚えて問題ないです。

41行目の「new Post();」でPostモデルのインスタンスを作成しています。

これでデータベースのbodyカラムにデータを追加できる様になります。

「インスタンスの作成って何?」って思った方がいると思います。

とにかくデータベースにデータを保存できるようになると覚えて問題ないです。

42行目の「$posts->body = $request->body;」ですが「$posts->body」がpostテーブルのbodyカラムで「$request->body」がhttp://localhost:8888/post/createで書いたつぶやきの内容(↓の赤枠)です。

だから42行目は「↑の赤枠に書いた内容をpostテーブルのbodyカラムに入れますよ」という意味になります。

43行目で情報の保存をして情報の保存が終わったら44行目でつぶやきを作成するページに戻りますという意味になります。

ひとまずつぶやきを保存する為のコントローラーの記述は終わりました。

つぶやきを作成する為のページ(create.blade.php)にデータベースに情報を保存できる様にする為の記述をしないといけないので追記します。

create.blade.phpを開きます。

そしてコードを↓にします。

@extends('layouts.app')
@section('content')
<div class="row">
    <div class="col-md-10 mt-6  ms-auto me-auto">
        <div class="card-body">
            <h1 class="mt4">つぶやきの作成</h1>
            <form method="post" action="{{route('post.store')}}">              //この行を修正
                @csrf                                                          //この行を追加
                <div class="form-group mt-3">
                        <label for="body">つぶやきの内容</label>
                        <textarea name="body" class="form-control" id="body" cols="30" rows="10" placeholder="ここにつぶやきの内容を書く"></textarea>
                </div>
                <button type="submit" class="btn btn-success mt-3">つぶやく</button>
            </form>
        </div>
    </div>
 </div>
 @endsection

7行目の「method=”post”」のpostですが他にgetがあります。

postのざっくりとした説明はフォームの内容をURLに表示しない場合に使いgetはURLに表示してもいい場合に使います。

つぶやきの内容は個人情報であってURLに表示したくないのでpostを使います。

この説明で納得できない方もいないと思います。

下記画像の赤枠の一番左から二番目の項目(Methodの項目)からも判断できます。

POSTになっていますよね。

また7行目の「action=“{{route(post.store)}}”」ですが保存する為のURLですがターミナルで「php artisan route:list」のコマンドを叩けば見つける事ができます。(下記画像の赤枠です)

{{route(post.store)}}」に「{{}}」がありますがこれはxss(クロスサイトスプリクティング)対策です。

ネットで調べればすぐに見つける事ができるので私は説明しませんがざっくりとした説明としてxssはハッキングです。

8行目の「@csrf」はcsrf(クロスサイトリクエストフォージェリ)対策でformタグを使う場合は必ず書かないといけないです。

書かなかったらエラーになります。

これもネットを探せば見つかりますがハッキングです。

これでつぶやきを作成する画面でつぶやきを作成して保存できるようになったので「http://localhost:8888/post/create」にアクセスして確認して下さい。

つぶやきを保存しても画面が変わらないと思いますがphpMyAdminのpostテーブルを見るとつぶやきを保存されているのが確認できます。

それではつぶやき作成するページでつぶやきを複数作成して下さい。

私は↓にしました。

それではつぶやきを表示します。

ルーティングは書かなくていいのでコントローラーとビューが必要になります。

ビューは前回の解説で使ったindex.blade.phpを使います。

 

つぶやき一覧を表示する為のコントローラー

7つのアクションがありましたが一覧の表示はindexアクションだったのでPostController.phpのindexアクションを修正します。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Post;

class PostController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
    
    
        //ここから修正
        $posts = Post::all();
        return view('post.index',compact('posts'));
        //ここまで修正
        
        
    }

コントローラーからビューにpostsテーブルの情報を渡します。

20行目は「Post::all();」でPostモデルを使ってデータベースに保存した全てのつぶやきを配列で取得します。

これをコレクションといいますがこれでとにかく配列のデータを取得できると思っていいです。

コントローラーからビューにURLを渡しますがその時にモデルが持ってきたpostsテーブルの情報も渡す事ができます。

それが21行目の「compact(posts‘)」になります。

postsは20行目の$postsの$を取ったものです。

こういう書き方をすると覚えていいです。

これでコントローラーは終わったので次はビューです。

 

つぶやき一覧を表示するビュー

index.blade.phpを修正します。

@extends('layouts.app')
@section('content')
@foreach ($posts as $post)
<div class="container-fluid mt-20" style="margin-left:-10px;">
    <div class="row">
        <div class="col-md-12 w-75 ms-auto me-auto">
            <div class="card mb-4">
                <p>{{$post->body}}</p>
            </div>
        </div>
    </div>
</div>
@endforeach
@endsection

配列のデータを表示するときはforeachを使います。

PHPの場合にforeachを使うと下記の書き方をします。

<?php foreach($posts as $post): ?>
<?php endforeach ?>

これがLaravelの場合は下記の書き方になります。

@foreach ($posts as $post)
@endforeach

$postsはコントローラーに書いたのと同じです。

public function index()
{
  $posts = Post::all();          //←これ
  return view('post.index',compact('posts'));    
}

これで「/post」にアクセスすると↓になります。

8行目で「{{$post->body}}」がありますが「$post」がPostモデルの内容で「->body」がbodyカラムになります。

だから「{{$post->body}}」が「Postモデルのつぶやきの内容」という事になります。

今回はここまでにします。

次回はつぶやきの内容の編集と削除の解説をします。

 

今回の解説で難しいと思ったら

今回の内容は1回目・2回目の解説と違ってボリュームが多くて難しく感じたと思います。(私がLaravelを始めたての時は絶対に難しく感じたと思います)

プログラミングは慣れなので1回目に戻って最初からやり直してみて下さい。

そうすれば3回目の解説をまた見た時に難しいと思った印象が少しは薄れると思います。