Laravel初心者向けの検索機能を実装する方法

Laravel初心者向けの検索機能を実装する方法

3559 回閲覧されました

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

今回はLaravelで検索機能を実装する方法を解説します。

Laravelのバージョン

Laravelのバージョンは8です。

Git

Gitのコードはここから見る事ができますので実装ができなくなったらクローンして使って下さい。

scssのコード

cssではなくscssを使っているのですがコードを記載します。

今回はscssの解説ではないので最低限必要と思う程度にとどめています。

.create-body{
    padding-top: 50px;
    width: 90%;
    margin-left:auto;
    margin-right:auto;
    .title,.author,.body,.submit{
        display: block;
        margin-top: 10px;
    }
    h1{
        font-size: 16px;
    }
}

scssの導入方法が分からない方は下記の記事を読んで下さい。

ページ構成

下記が検索する内容の一覧が表示されているページです。

赤枠をクリックすると本の情報を一覧に追加する事ができます。

下記が本の情報を追加するページです。

赤枠をクリックすると本の情報が表示されます。

本の情報を表示するページは↓みたいに超シンプルにしています。

それでは実装の解説をしますがまずは本の情報を作成するページで本の情報の追加ができる様にします。

 

ルーティング(本の情報を入力)

web.phpを↓にします。

//本の情報を作成するページの表示
Route::get('/create','PostController@create')->name('post.create');

//本の情報を保存
Route::post('/create/store','PostController@store')->name('post.store');

このままだとコントローラーのエラーが出るので「Laravelのプロジェクト > app > Providers > RouteServiceProvider.php」の28行目辺りのコメントアウトを外します。

protected $namespace = 'App\\Http\\Controllers';

次はコントローラーです。

コントローラー とモデル(本の情報を入力)

私は「PostController」を作成しましたがコントローラーを作成します。

私と同じ名前のコントローラー名にするなら「php artisan make:controller PostController」のコマンドを叩いて下さい。

またデータベースに情報を保存しないといけないのでモデルを作成します。

私はPostモデルにしています。

「php artisan make:model Post -m」でモデルを作成してauthorカラム・titleカラム・bodyカラムを作成するために「-m」を付けてカラムを追加する為のマイグレーションファイルも一緒に作成します。

「Laravelのプロジェクト > database > 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->string('author');
            $table->string('title');
            $table->text('body');
            //ここまで追加
            
            
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('posts');
    }
}

それができたら「php artisan migrate」をすればデータベースに authorカラム・titleカラム・bodyカラムが追加されます。

それではPostControllerのコードを下記にします。

<?php

namespace App\Http\Controllers;

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

class PostController extends Controller
{

    
    //ここから追加
    public function create()
    {
        return view('post.create');
    }

    public function store(Request $request)
    {
        $post = new Post();
        $post->title = $request->title;
        $post->author = $request->author;
        $post->body = $request->body;
        $post->save();
        return back();
    }
    //ここまで追加
    
    
}

13行目〜16行目が本の情報を追加するページを表示する為の記述で18行目〜26行目が本の情報を追加するページで入力した情報を保存するための記述です。

21行目が本のタイトルの追加・22行目が著者名の追加・23行目が本の内容の追加をしています。

次は本の情報を入力するページ(ビュー)です。

ビュー(本の情報を入力)

「Laravelのプロジェクト > resources > views」の下にpostフォルダを作ってその下にcreate.blade.phpを作成します。

そして下記の記述をします。

<!doctype html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <link href="{{ asset('css/app.css') }}" rel="stylesheet" type="text/css">
        <title>検索機能</title>
    </head>
    <body class="create-body">
        <h1>本を追加</h1>
        <form action="{{ route('post.store') }}" method="post">
            @csrf
            <label for="title"><input class="title" type="text" name="title" placeholder="本のタイトル"></label>
            <label for="author"><input class="author" type="text" name="author" placeholder="著者"></label>
            <label for="body"><textarea class="body" name="body" cols="30" rows="10" placeholder="本の内容"></textarea></label>
            <button type="submit" class="submit">追加する</button>
        </form>
    </body>
</html>

13行目が本のタイトルの入力で14行目が著者の入力で15行目が本の内容の入力です。

それでは本の情報を表示するためにルーティングとコントローラーの記述を追加します。

またビューも作成します。

ルーティング(本の情報を表示)

web.phpの記述を下記にします。

//本の一覧を表示するページ
Route::get('/index','PostController@index')->name('post.index');              //この行を追加

//本の情報を作成するページの表示
Route::get('/create','PostController@create')->name('post.create');

//本の情報を保存
Route::post('/create/store','PostController@store')->name('post.store');

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

postフォルダの中のindex.blade.phpを表示するので「Laravelのプロジェクト > resources > views > post」の下にindex.blade.phpを作成します。

次はコントローラーです。

 

コントローラー(本の情報を表示)

Postコントローラーの記述を下記にします。

<?php

namespace App\Http\Controllers;

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

class PostController extends Controller
{


    //ここから追加
    public function index(Request $request)
    {
        $keyword = $request->input('keyword');

        if(!empty($keyword)) {
            $posts = Post::where('title', 'LIKE', "%{$keyword}%")->orWhere('author', 'LIKE', "%{$keyword}%")->get()
        }

        return view('post.index', compact('posts', 'keyword'));
    }
    //ここまで追加
    

    public function create()
    {
        return view('post.create');
    }

    public function store(Request $request)
    {
        $post = new Post();
        $post->title = $request->title;
        $post->author = $request->author;
        $post->body = $request->body;
        $post->save();
        return back();
    }
}

13行目〜27行目を追加しています。

15行目の「$request->input(‘keyword’);」でビューのページ(後で記述するindex.blade.php)のname属性がkeyword(name=”keyword”の事)の値(検索フォームの値)を取得します。

19行目〜22行目が検索の部分で19行目の「if(!empty($keyword))」は検索フォームに文字が入力されているならという意味です。

20行目と21行目で検索をしていて20行目・21行目に「%{ }%」がありますが%%をワイルドカードと言って検索フォームに入っている文字を含む情報を全て取得するという意味があります。

具体的な例で説明した方がいいと思うので説明しますが今回のデモだと表示が↓です。

例えば「本」で検索すれば「本その1」と「本その2」が表示されて「あべ」で検索すれば「阿部鬼」が表示されます。

部分的な文字で検索をしていますが「本その1」など書籍名や著者名全文で検索してもヒットします。

話を戻して20行目が本のタイトルに関する検索で21行目が著者に関する検索です。

次は本の一覧を表示するページです。

 

ビュー(本の情報を表示)

index.blade.phpのコードを下記にします。

<!doctype html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <link href="{{ asset('css/app.css') }}" rel="stylesheet" type="text/css">
        <title>検索機能</title>
    </head>
    <body class="index-body">
        <h1><span>本一覧</span><a href="{{ route('post.create') }}">[本を追加する]</a></h1>
        <form action="{{ route('post.index') }}" method="GET">
            <input type="text" name="keyword">
            <input type="submit" value="検索">
        </form>
        <table>
            <tr>
                <th>書籍名</th>
                <th>著者名</th>
            </tr>
            @forelse ($posts as $post)
            <tr>
                <td><a href="{{ route('post.show',['post' => $post]) }}">{{ $post->title }}</a></td>
                <td>{{ $post->author }}</td>
            </tr>
            @empty
            <tr>
                <td>なし</td>
                <td>なし</td>
            </tr>
            @endforelse
        </table>
    </body>
</html>

検索フォームは11行目〜14行目です。

さっき出てきたpostコントローラーの15行目の「keyword」に対応しているのがindex.blade.phpの12行目のkeywordです。

if文を使った時に「@if→@elseif→@endif」と書けますが20行目・25行目・30行目はそれのforelse版で「@forelse→@empty→@endforelse」と書きます。

foreachとの違いはデータがない状態で使うとエラーになるのに対してforelseはデータがなくても@emptyが表示されるのでエラーにならない点です。

22行目の「[post => $post]」ですが「Implicit Binding」という機能を使っています。

知らない方は下記のImplicit Bindingの項目を見て下さい。

これで完成ですが本の中身を表示するページの記述の解説をしていないのでそれのコードだけを記載します。

ここまでの内容が理解できた人はコードを見れば理解できると思います。

下記がルーティングです。

//本の一覧を表示するページ
Route::get('/index','PostController@index')->name('post.index');             

//本の情報を作成するページの表示
Route::get('/create','PostController@create')->name('post.create');

//本の情報を保存
Route::post('/create/store','PostController@store')->name('post.store');

//本の中身を表示するページ
Route::get('/show/{post}','PostController@show')->name('post.show');                   //この行を追加

下記がコントローラーです。

<?php

namespace App\Http\Controllers;

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

class PostController extends Controller
{
    public function index(Request $request)
    {
        $keyword = $request->input('keyword');

        $query = Post::query();

        if(!empty($keyword)) {
            $query->where('title', 'LIKE', "%{$keyword}%")
                ->orWhere('author', 'LIKE', "%{$keyword}%");
        }

        $posts = $query->get();

        return view('post.index', compact('posts', 'keyword'));
    }

    public function create()
    {
        return view('post.create');
    }

    public function store(Request $request)
    {
        $post = new Post();
        $post->title = $request->title;
        $post->author = $request->author;
        $post->body = $request->body;
        $post->save();
        return back();
    }


    //ここから追加
    public function show(Post $post)
    {
        return view('post.show',compact('post'));
    }
    //ここまで追加
    
    
}

下記がビューですがpostフォルダの下にshow.blade.phpを作成して下さい。

{{ $post->body }}