laravelpro_タイムライン機能の実装

11タイムライン機能の実装

続いて、他のユーザーのメモも見れるようなタイムライン機能を実装していきましょう。

要件は以下です。

  • 他のユーザーのメモを見れるようにする
  • 自分のメモのみ編集、削除できるようにする
  • メモにユーザー名を表示させる
  • ヘッダーメニューに「タイムライン」を追加する

イメージ画面

ヘッダーメニューの部分ですが、「トップ」に先ほどまで作成していたメモ追加画面を移動しています。

「トップ」ページ

まずはタイムラインを作成する前にヘッダーメニューに「トップ」を追加し、メモ追加画面が見られるようにしましょう。

メニューを追加したいのですが、ヘッダーメニューの部分はどのページにでも共通です。

例えば、タイムラインの新規ページを作ったとして、タイムラインページとトップページのヘッダーメニューは同じになりますよね?

同じヘッダーメニューなのにトップ・タイムラインページそれぞれでHTMLをべた書きしていた場合、新しくメニューが追加されたり、メニュー名が変更されたときにトップ・タイムラインページの2つのブレードファイルを編集しなければいけなくなります。

これでは面倒ですし保守性の面でもよくありません。

ですので、ブレードファイルで共通になる部分は共通化することができます。

それがLaravelに標準搭載されている「テンプレートエンジンであるblade」というものです。

ブレードファイルでは様々な便利メソッドが用意されています。

早速使ってみましょう。

resources/views/layoutsフォルダの中に(なければ作成してください)base.blade.phpというブレードファイルを作成しましょう。

作成できたら以下をコピー&ペーストしてください。

<!doctype html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>メモ帳</title>
    <link rel="stylesheet" href="{{ asset('css/style.css') }}">
  </head>
  <body>
    <header>
      <div class="container">
        <div class="d-flex header_flex">
          <div class="left_header">
            <h2>メモ共有</h2>
          </div>
          <div class="right_header">
            <ul>
              <li>
                <a href="/">トップ</a>
              </li>
              <li>
                <a href="">タイムライン</a>
              </li>
              <li>
                <form action="{{ route('logout') }}" method="post">@csrf 
                  <input class="logout_btn" type="submit" value="ログアウト">
                </form>
              </li>
              <li>
                <a href="">ユーザー名:{{ Auth::user()->name }}</a>
              </li>
            </ul>
          </div>
        </div>
      </div>
    </header>
    <div class="container">@yield('content') </div>
  </body>
</html>

続いて home.blade.phpを以下のように編集します。

@extends('layouts.base') @section('content') 
<div class="memo_area">
  <div class="memo_form">
    <h2>メモを追加</h2>
    <form action="{{ asset('/add') }}" method="post">@csrf 
      <input class="memo_text" type="text" name="memo_text" id="memo_text">
      <input type="submit" value="追加">
    </form>
    <div class="search_area" style="margin-top: 50px">
      <h2>検索</h2>
      <form action="">@csrf 
        <input class="memo_text" type="text" name="search_word" id="search_word">
        <input type="submit" value="検索">
      </form>
    </div>
  </div>
  <div class="memo_show">@foreach($memo_info as $memo) 
    <div class="memo_item">
      <div class="memo_title">
        <time>{{$memo->created_at}}</time>
        <p>{{$memo->content}}</p>
      </div>
      <div class="btn_area">
        <div class="edit_form">
          <form action="{{ asset('/edit/'.$memo->id) }}" method="get">@csrf 
            <input type="submit" value="編集">
          </form>
        </div>
        <div class="del_area">
          <form action="{{ asset('/delete') }}" method="post">@csrf 
            <input type="hidden" name="delete_id" value="{{$memo->id}}">
            <input type="submit" value="削除">
          </form>
        </div>
      </div>
    </div>@endforeach 
  </div>
</div>@endsection

コードを解説していきます。

base.blade.phpが今回共通化する部分です。

今回共通化しているのは、ヘッドのメタ情報の部分とヘッダーメニューの部分です。

home.blade.phpを見てみましょう。<head>タグの部分や<body>タグの部分がなくなっています。

それらの記述はすべてbase.blade.phpに移動させています。

base.blade.phpの下の方に@yield('content')があります。

この部分がhome.blade.phpの@section(’content’) ~ @endsectionの部分を呼び出しています。

ですので、2つのブレードファイルを使用していますが画面に出力される際には以下の1つのファイルとして出力されます。

<!doctype html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>メモ帳</title>
    <link rel="stylesheet" href="{{ asset('css/style.css') }}">
  </head>
  <body>
    <header>
      <div class="container">
        <div class="d-flex header_flex">
          <div class="left_header">
            <h2>メモ共有</h2>
          </div>
          <div class="right_header">
            <ul>
              <li>
                <a href="/">トップ</a>
              </li>
              <li>
                <a href="">タイムライン</a>
              </li>
              <li>
                <form action="{{ route('logout') }}" method="post">@csrf 
                  <input class="logout_btn" type="submit" value="ログアウト">
                </form>
              </li>
              <li>
                <a href="/user_profile/{{Auth::id()}}">ユーザー名:{{ Auth::user()->name }}</a>
              </li>
            </ul>
          </div>
        </div>
      </div>
    </header>
    <div class="container">
      <div class="memo_area">
        <div class="memo_form">
          <h2>メモを追加</h2>
          <form action="{{ asset('/add') }}" method="post">@csrf 
            <input class="memo_text" type="text" name="memo_text" id="memo_text">
            <input type="submit" value="追加">
          </form>
          <div class="search_area" style="margin-top: 50px">
            <h2>検索</h2>
            <form action="">@csrf 
              <input class="memo_text" type="text" name="search_word" id="search_word">
              <input type="submit" value="検索">
            </form>
          </div>
        </div>
        <div class="memo_show">@foreach($memo_info as $memo) 
          <div class="memo_item">
            <div class="memo_title">
              <time>{{$memo->created_at}}</time>
              <p>{{$memo->content}}</p>
            </div>
            <div class="btn_area">
              <div class="edit_form">
                <form action="{{ asset('/edit/'.$memo->id) }}" method="get">@csrf 
                  <input type="submit" value="編集">
                </form>
              </div>
              <div class="del_area">
                <form action="{{ asset('/delete') }}" method="post">@csrf 
                  <input type="hidden" name="delete_id" value="{{$memo->id}}">
                  <input type="submit" value="削除">
                </form>
              </div>
            </div>
          </div>@endforeach 
        </div>
      </div>
    </div>
  </body>
</html>

また、注意ですが読み込まれる側のファイル(今回だとhome.blade.php)の1行目には必ず@extends('layouts.base')を記述するようにしましょう。

これはlayoutsフォルダの中のbase.blade.phpを読み込みますよという意味になります。

@yieldの使い方ですが、@yield(’名前’)とすると、@section(’名前’) ~ @endsectionの部分が自動的に@yieldの部分に読み込まれます。

※名前の部分は必ず同じである必要があります。

逆に言うと名前を変えると何個であっても@yieldは使うことができます。

よく実務でも使われる例を紹介します。

layouts/base.blade.php

<!doctype html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">@yield('link') 
    <title>Document</title>
  </head>
  <body>@yield('content') </body>
</html>

home.balde.php

@extends('layouts.base') @section('link') 
<link rel="stylesheet" href="{{ asset('css/home.css') }}" >
<script src="{{ asset('js/index.js') }}"></script>@endsection @section('content') 
<div>// ページの中身 
<div>@endsection

home.blade.php を見てみると@sectionが2つありますね。

@section(’content’)は先ほど説明した通り@yields(’content’)の部分に読み込まれます。

注目してほしいのは@yields(’link’)の部分です。

<head>の中に記述されています。これは、ページによって独自のCSS・JSなどのファイルを読み込ませたい時が多くあります。

そこで@yields(’link’)と@section(’link’) ~ @endsectionを組み合わせることで簡単にファイルごとに読み込ませたいものを変えることができます。

この使い方は様々な部署で使われているので覚えておきましょう。

この状態でページを読み込んでみましょう。

ちゃんとページが表示されましたね!

ではいよいよタイムラインページを作成していきます。

新規ページを作成する流れは以下です。

  1. ブレードファイルを作成
  2. URLを決める
  3. コントローラを作成し、ページを表示させるだけのメソッドを作成
  4. web.phpにルーティングを追加し③で作成したメソッドが実行されるようにする
  5. 2で設定したURLに遷移する

ではまず1.ブレードファイルを作成してみましょう。

resources/viewsのフォルダの中に新しく timeline.blade.php を作成しましょう。

@extends('layouts.base') @section('content') 
<div class="memo_area">
  <div class="memo_show">タイムライン </div>
</div>@endsection

タイムラインという文字列のみ表示されるシンプルなページです。

次に2.のURLを設定を行います。

ヘッダーメニューの部分に「タイムライン」メニューを追加しました。その部分にURLを設定しましょう。

base.blade.php

<li>
    <a href="/timeline">タイムライン</a> // hrefに追記
</li>

次に3.コントローラを作成し、ページを表示させるだけのメソッドを作成します。

ターミナルで以下のコマンドを実行しましょう。

php artisan make:controller TimeLineController

TimeLineController.phpが作成されたと思います。

TimeLineController.phpの中に以下のメソッドを追記してください。

public function show() {
    return view('timeline');
}

ブレードファイルを表示させるためだけのシンプルなメソッドです。

では続いて4.web.phpにルーティングを追加し③で作成したメソッドが実行されるようにしましょう。

web.php

Route::get('/timeline', 'App\\Http\\Controllers\\TimeLineController@show');

では最後に⑤②で設定したURLに遷移してみましょう。

ヘッダーメニューのタイムラインをクリックしてください。

上記の様に表示されればOKです。

では機能を足していきましょう。

その前に要件を再度確認します。

  • 他のユーザーのメモを見れるようにする
  • 自分のメモのみ編集、削除できるようにする
  • メモにユーザー名を表示させる
  • ヘッダーメニューに「タイムライン」を追加する

です。

一番の機能としては「他のユーザーのメモを見れるようにする」です。

メモ追加機能では自分のメモのみを取得しましたが今回は他のユーザーも表示させる必要があります。さらに「メモにユーザー名を表示させる」という要件ですが、ユーザー名は「users」テーブルにあり、メモの内容は「memo」テーブルにあります。

つまりページに表示されたい内容が別々のテーブルにあるためテーブルを結合しなければいけません。

SQLのレッスンでもやったjoinを使っていきます。

忘れている場合はSQLのレッスンを見直してみましょう。

memoのuser_idとusersのidを結合して1つのテーブルにして取得します。

これをLaravelのeloquentで書くと以下のようになります。

Memo::select(
    'memo.id',
    'memo.content',
    'memo.created_at',
    'memo.user_id',
    'users.name'
)->join('users', 'memo.user_id', '=', 'users.id')
->where('invalid', 0)
->get();

joinメソッドですが、

join(’結合先テーブル名’, ‘結合元テーブルのキー’, ‘=’, ‘結合元テーブルのキー’)になります。

今回は「memo」テーブルに「users」を結合します。

結合先テーブル名は「users」

結合先テーブルのキーは「memo」テーブルのuser_id

結合元テーブルのキーは「users」テーブルのid

当てはめるとjoin('users', 'memo.user_id', '=', 'users.id')になります。

このままコントローラに書いてもいいですが、少しコード数が多くなってしまったので、この部分はモデルに書いていきましょう。

コントローラはあくまで、情報を受け取って情報を返すだけのファイルです。

本来であればモデルやレポジトリに書くのが適切です。

「Memo.php」に以下のメソッドを追記しましょう。

public static function getUserMemoInfo()
    {
        return Memo::select(
            'memo.id',
            'memo.content',
            'memo.created_at',
            'memo.user_id',
            'users.name'
        )->join('users', 'users.id', '=', 'memo.user_id')
            ->where('invalid', 0)
            ->get();
}

このモデルに書いたメソッドをコントローラで呼び出してみましょう。

TimeLineController.php のshowメソッドを以下のように追記してください。

use App\\Models\\Memo; // 追記
    public function show() {
        $memo_info = Memo::getUserMemoInfo(); // 追記
        return view('timeline')
            ->with('memo_info', $memo_info); // 追記
    }

Memo::getUserMemoInfo();とすることでMemoモデルのgetUserMemoInfoメソッドを呼び出すことができます。

getUserMemoInfoメソッドはstaticメソッドなのでMemo::getUserMemoInfo();で呼び出すことができます。staticがなければMemo::getUserMemoInfo();ではエラーが出てしまいます。

以下のように記述しないようにいけなくなります。

$memo = new Memo();
$memo_info = $memo->getUserMemoInfo();

上記のようにインスタンス化した後に呼び出さなくてはいけません。

ブレードファイルも追記します。

timeline.blade.php

@extends('layouts.base') @section('content') 
<div class="memo_area">
  <div class="memo_show">@foreach($memo_info as $memo) 
    <div class="memo_item">
      <div class="memo_title">
        <time>ユーザー名:{{ $memo->name }} {{$memo->created_at}}</time>
        <p>{{$memo->content}}</p>
      </div>
      <div class="btn_area">
        <div class="edit_form">
          <form action="{{ asset('/edit/'.$memo->id) }}" method="get">@csrf 
            <input type="submit" value="編集">
          </form>
        </div>
        <div class="del_area">
          <form action="{{ asset('/delete') }}" method="post">@csrf 
            <input type="hidden" name="delete_id" value="{{$memo->id}}">
            <input type="submit" value="削除">
          </form>
        </div>
      </div>
    </div>@endforeach 
  </div>
</div>@endsection

ページを読み込んでみましょう。

上記のように、他のユーザーのメモも表示されたらOKです。

ユーザー名も正しいか確認しましょう。

今のままでは他のユーザーのメモも編集できるようになっているので、他のユーザの場合は編集・削除ボタンが非表示になるように修正しましょう。

TimeLineContorller.php の showメソッド

public function show() {
    $memo_info = Memo::getUserMemoInfo();
    $current_user_id = Auth::id(); // 追記
    return view('timeline')
        ->with('current_user_id', $current_user_id) // 追記
        ->with('memo_info', $memo_info);
}

現在ログイン中のユーザーIDを取得しています。

timeline.blade.php

@foreach($memo_info as $memo) 
<div class="memo_item">
  <div class="memo_title">
    <time>ユーザー名:{{ $memo->name }} {{$memo->created_at}}</time>
    <p>{{$memo->content}}</p>
  </div>@if($current_user_id === $memo->user_id) // 追記 
  <div class="btn_area">
    <div class="edit_form">
      <form action="{{ asset('/edit/'.$memo->id) }}" method="get">@csrf 
        <input type="submit" value="編集">
      </form>
    </div>
    <div class="del_area">
      <form action="{{ asset('/delete') }}" method="post">@csrf 
        <input type="hidden" name="delete_id" value="{{$memo->id}}">
        <input type="submit" value="削除">
      </form>
    </div>
  </div>@endif // 追記 
</div>@endforeach

ページを読み込んでみましょう。

無事に編集・削除ボタンが非表示になりました!

@if($current_user_id === $memo->user_id)で現在のユーザーとメモを書いたユーザーを比べています。一致したときだけ表示されます。

これでタイムライン画面が完成しました。


投稿日

カテゴリー:

投稿者:

タグ:

コメント

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です