20190821のlaravelに関する記事は6件です。

laravelで検索機能を実装する

今回は文字列を入力して部分一致検索、プルダウンメニューから選択して検索を行う機能を実装します。具体的には下記の画面でユーザ名、棋力、好きな戦法による検索を行います。
11fdea620d70330aa57c7896d10a1faa.png

Userモデルは以下のように name(ユーザ名)、stength(棋力)、tactics(好きな戦法)というカラムを定義します。

class User extends Authenticatable
{
    protected $fillable = [
        'name','strength','tactics',
    ];
}

コントローラは以下のようなindexメソッドを定義します。

class SearchController extends Controller
{
    public function index(Request $request){
        $query = User::query();

     //$request->input()で検索時に入力した項目を取得します。
        $search1 = $request->input('strength');
        $search2 = $request->input('tactics');
        $search3 = $request->input('name');

         // プルダウンメニューで指定なし以外を選択した場合、$query->whereで選択した棋力と一致するカラムを取得します
        if ($request->has('strength') && $search1 != ('指定なし')) {
            $query->where('strength', $search1)->get();
        }

         // プルダウンメニューで指定なし以外を選択した場合、$query->whereで選択した好きな戦法と一致するカラムを取得します
        if ($request->has('tactics') && $search2 != ('指定なし')) {
            $query->where('tactics', $search2)->get();
        }

        // ユーザ名入力フォームで入力した文字列を含むカラムを取得します
        if ($request->has('name') && $search3 != '') {
            $query->where('name', 'like', '%'.$search3.'%')->get();
        }

    //ユーザを1ページにつき10件ずつ表示させます
        $data = $query->paginate(10);

        return view('users.search',[
            'data' => $data
        ]);
    }
}

検索ボタンを押したときのルーティングを以下のように記述します。

//検索ボタンを押すとコントローラのindexメソッドを実行します
Route::get('Search','SearchController@index')->name('search');

ユーザ検索ページのビューを以下のように記述します。

    <div class="row">
        <div class="col-sm-4">
            <div class="text-center my-4">
                <h3 class="brown border p-2">ユーザ検索</h3>
            </div>
            {!! Form::open(['route' => 'search', 'method' => 'get']) !!}
                <div class="form-group">
                    {!! Form::label('text', 'ユーザ名:') !!}
                    {!! Form::text('name' ,'', ['class' => 'form-control', 'placeholder' => '指定なし'] ) !!}
                </div>
                <div class="form-group">
                    {!! Form::label('strength', '棋力:') !!}
                    {!! Form::select('strength', ['指定なし' => '指定なし'] + Config::get('strength.kiryoku') ,'指定なし') !!}
                </div>
                <div class="form-group">
                    {!! Form::label('tactics', '好きな戦法:') !!}
                    {!! Form::select('tactics', ['指定なし' => '指定なし'] + Config::get('tactics.senpou') , '指定なし') !!}
                </div>
                {!! Form::submit('検索', ['class' => 'btn btn-primary btn-block']) !!}
            {!! Form::close() !!}
        </div>
        <div class="col-sm-8">
            <div class="text-center my-4">
                <h3 class="brown p-2">ユーザ一覧</h3>
            </div>

            <div class="container">
                <!--検索ボタンが押された時に表示されます-->
                @if(!empty($data))
                    <div class="my-2 p-0">
                        <div class="row  border-bottom text-center">
                            <div class="col-sm-4">
                                <p>ユーザ名</p>
                            </div>
                            <div class="col-sm-4">
                                <p>棋力</p>
                            </div>
                            <div class="col-sm-4">
                                <p>好きな戦法</p>
                            </div>
                        </div>
              //検索条件に一致したユーザを表示します
                        @foreach($data as $item)
                                <div class="row py-2 border-bottom text-center">
                                    <div class="col-sm-4">
                                        <a href="">{{ $item->name }}</a>
                                    </div>
                                    <div class="col-sm-4">
                                        {{ $item->strength }}
                                    </div>
                                    <div class="col-sm-4">
                                        {{ $item->tactics }}
                                    </div>
                                </div>
                        @endforeach
                    </div>
                    {{ $data->appends(request()->input())->render('pagination::bootstrap-4') }}
                @endif
            </div>
        </div>
    </div>

ここでは以下のように、あらかじめconfigディレクトリにstrength.phpを作成して'kiryoku'という配列を定義しています。上記のビューではconfig::get('strength.kiryoku')でその配列を取得して、プルダウンメニューを作成しています。さらに['指定なし' => '指定なし'] + config::get('strength.kiryoku')としてkiryoku配列に「指定なし」という要素を追加しています。

config/strength.php

<?php 
return array(
    'kiryoku' => array(
  '10級' => '10級', 
  '9級' => '9級', 
  '8級' => '8級', 
  '7級' => '7級',
  '6級' => '6級', 
  '5級' => '5級', 
  '4級' => '4級', 
  '3級' => '3級',
  '2級' => '2級', 
  '1級' => '1級', 
  '初段' => '初段', 
  '二段' => '二段',
  '三段' => '三段', 
  '四段' => '四段', 
  '五段' => '五段', 
  '六段' => '六段',
  ),
);
?>

config::get('tactics.senpou')も同様です。
config/tactics.php

<?php 
return array(
    'senpou' => array(
  '角換わり' => '角換わり', 
  '矢倉' => '矢倉', 
  '相掛かり' => '相掛かり', 
  '横歩取り' => '横歩取り',
  '向かい飛車' => '向かい飛車', 
  '三間飛車' => '三間飛車', 
  '四間飛車' => '四間飛車', 
  '中飛車' => '中飛車'
  ),
);
?>

ユーザ名のみで検索すると、以下のように正しく表示されました。
40f910bb147f96bbc5217e4810dd315e.png

また棋力、好きな戦法で検索した場合も、以下のように正しく表示されました。
78290b7b15c518d244dee790a40b7dc0.png

今回の検索機能のポイントは、コントローラの$request->inputメソッドと$query->whereメソッドかなと思いました。あとプルダウンメニューの実装はconfigファイルを使用しましたが、意外と躓きました。プルダウンメニューはほかの方法でより簡単に実装できるのかもしれません。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsのノリでDockerを使ってNginx+Laravelの環境を作ろうとしたらハマった話

概要

DockerでNginx + Laravelの環境を作ろうとしたらハマったのでまとめました。
仕組みを知るって大切ですね。

ハマった経緯

Railsで下記のような構成のアプリケーションを触っていたため、Nginx+Laravelでもいけるだろと思ったらハマった。

Railsの構成(成功)

rails-rp.png

Laravelの構成(失敗)

laravel-failed.png

何故ハマったかの結論

PumaはWebサーバであるが、PHP-FPMはWebサーバではないから静的ファイルを返さなかった!
当たり前の話なんでしょうけど、コピペで動けばいいやでやってるといずれハマるものですね・・・。

じゃあどうするのが正解?

前提

  • Dockerを利用した場合で考えるので、1コンテナ1プロセスとして設計する (Logを収集しやすかったりといろいろと利点がありますよね)
  • 本番環境はKubernetesを利用する

開発環境パターン1(ネット上でよく見るやつ)

NginxとLaravelでソースを共有して静的ファイルを返すパターン
今回ハマってなんで動くのかよく理解ができました・・・!
laravel-success1.png

開発環境パターン2(静的ファイルを分離)

静的ファイルを別サーバで返すようにするパターンです。
LaravelをAPI化してSPAと連携する場合などはこっちの方がよいと思います。(本番でFirebaseとかに投げれるし)
※ LocalではBackendとFrontendを別ディレクトリとしてmountする。
laravel-success2.png

本番環境パターン1(開発環境パターン1に近い形)

1Podに3コンテナはちょっと美しいとは言えないけどFrontとBackをわけていない場合はこれでいいと思います。
laravel-kubernetes1 (1).png

本番環境パターン2(開発環境パターン2に近い形)

開発環境パターン2のようにFrontとBackで分けている場合はこのような構成がいいと思います。
FrontendはFirebaseなどに投げるのもありだと思います。
あとは調査が足りてませんが、頑張ればingress-nginxでfastcgiの対応が可能かもしれません。
laravel-failed (1).png

まとめ

  • PHP-FPMはFastCGIで動作するミドルウェアであり、WebサーバではないためNginxと連携して静的ファイルを返す仕組みが必要になる
  • 上手く連携させるためには2パターンある(サーバを分離しないのも含めると3パターン)
    1. ソースを共有できる場所を用意してNginxでそちらを参照する
    2. 静的ファイルをPHP-FPMサーバから分離する
      • RPサーバに置く事も可能だが、役割を考えると専用にサーバを用意した方がよい

参考文献

Webサーバ、FastCGI、Unixドメインソケット、TCPソケットについて詳しく書かれています
https://qiita.com/kotarella1110/items/634f6fafeb33ae0f51dc

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】任意のSeedを単体実行する。

メモとして残します。
最近物忘れがひどい。

■やり方

全て

database\seeds\DatabaseSeeder.phpに記載した全てのシーダーを実行。

php artisan db:seed

単体

--class=作成したシーダークラス名(classオプションで実行するシーダーを指定する)

php artisan db:seed --class=作成したシーダークラス名
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelで閉包テーブル(Closure Table) を簡単に実現してくれるライブラリ ClosureTable

Laravelで閉包テーブル(Closure Table) を簡単に実現してくれるライブラリ ClosureTable

DBのツリー構造のテーブルにはいくつかありますが、その中に閉包テーブルというものがあります。
この記事にたどり着く人には説明不要だと思うので説明は省きます!!!!

各ツリー構造についてはこちらの記事がわかりやすい気がします!
https://qiita.com/hirashunshun/items/06adf4f42f03a9f3b63d

実際に閉包テーブルを自前で実現しようとすると、CRUDのクエリ投げるときに整合性とれてるのかこれ。。。って部分があり、孫だけ取得とかそのへんのクエリをゴリゴリ書くのは面倒な部分が多く、そこがデメリットな感じもします

本題

ゴリゴリクエリ書くのが面倒だなーというデメリットを解決してくれるのがこれ!
https://github.com/franzose/ClosureTable

テーブル構造のことなんて何も考えず脳死で閉包テーブルが作れます!
めちゃくちゃ良いライブラリだと思います

使い方(Migrationまで

ライブラリが提供しているコマンドでmigrationを作成

php artisan closuretable:make --entity=trees

すると、migration・Model・Interfaceとかが作られる

create 2019_08_19_105450_create_trees_table_migration
create Tree
create TreesInterface
create TreesClosure
create TreesClosureInterface

2019_08_19_105450_create_trees_table_migration
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateTreesTableMigration extends Migration
{
    public function up()
    {
        Schema::create('trees', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('parent_id')->unsigned()->nullable();
            $table->integer('position', false, true);
            $table->integer('real_depth', false, true);
            $table->softDeletes();

            $table->foreign('parent_id')
                ->references('id')
                ->on('trees')
                ->onDelete('set null');
        });

        Schema::create('trees_closure', function (Blueprint $table) {
            $table->increments('closure_id');

            $table->integer('ancestor', false, true);
            $table->integer('descendant', false, true);
            $table->integer('depth', false, true);

            $table->foreign('ancestor')
                ->references('id')
                ->on('trees')
                ->onDelete('cascade');

            $table->foreign('descendant')
                ->references('id')
                ->on('trees')
                ->onDelete('cascade');
        });
    }

    public function down()
    {
        Schema::table('trees_closure', function (Blueprint $table) {
            Schema::dropIfExists('trees_closure');
        });

        Schema::table('trees', function (Blueprint $table) {
            Schema::dropIfExists('trees');
        });
    }
}

最低限の構造を作ってくれます。必要であれば、カラムとか追加可能
基本的にはtrees側に追加すればよいかと

interface抜粋

Treeinterface
use Franzose\ClosureTable\Contracts\EntityInterface;

interface treesInterface extends EntityInterface
{
}

継承しているInterfaceがClosureTable独自のものになっている

モデル

Tree.php
use Franzose\ClosureTable\Models\Entity;

class trees extends Entity implements treesInterface
{
    /**
     * The table associated with the model.
     *
     * @var string
     */
    protected $table = 'trees';

    /**
     * ClosureTable model instance.
     *
     * @var treesClosure
     */
    protected $closure = 'App\treesClosure';
}

継承しているEntityがClosureTable独自のものになっている

この辺のclass名がloweerCaseなのは気持ち悪いので好みで変える…!

rotected $closure = 'App\treesClosure';

実際の使い方

一番上のモデルを作る時

$rootTree = new Tree();
$rootTree->save();

普通にモデル作るのと同じ

子供追加

// 親作る
$rootTree = new Tree();
$rootTree->save();

// 子供作る
$childTree = new Tree();
$rootTree->addChild($childTree);
$childTree->save();

// 子供2作る
$childTree2 = new Tree();
$rootTree->addChild($childTree2);
$childTree2->save();

これもいい感じにできる

削除

// 親作る
$rootTree = new Tree();
$rootTree->save();

// 子供作る
$childTree = new Tree();
$rootTree->addChild($childTree);
$childTree->save();

// 子供2作る
$childTree2 = new Tree();
$rootTree->addChild($childTree2);
$childTree2->save();

// 子供達だけ削除の場合
$rootTree->deleteSubtree();
// 自分を含む子供達を削除する場合
$rootTree->deleteSubtree(true);

取得

$rootTree = Tree::find(いつもの);

// これで子供達とれる(孫まで取れるわけではないので再帰処理とか別メソッドを使う必要がある
$children = $rootTree->getChildren();

こんなかんじ

書くのが辛くなったのでまとめ

使いやすい!

各メソッドはいい感じに抽象化されてて使いやすい!公式みて!!
https://github.com/franzose/ClosureTable#ancestors

使ってみて!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel Blade の component を extends の代わりに使う

良いかどうかはともかく、そういう使い方もできるなと思ったネタ。なお 5.5 を使ってます。

普通に @extends を使えば次のようになりますが、

layout.blade.php

<!doctype html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>Laravel - @yield('title')</title>
</head>
<body>
    <header>
        @section('header')
            <h1>Laravel</h1>
        @show
    </header>
    <main>
        @yield('content')
    </main>
</body>
</html>

home.blade.php

@extends('layout')

@section('title', 'Home')

@section('header')
    @parent
    <h2>Home</h2>
@endsection

@section('content')
    <p>ここは Home のコンテンツです</p>
@endsection

これを @component を使って次のようにします。

layout.blade.php

<!doctype html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>Laravel - {{ $title }}</title>
</head>
<body>
<header>
    {{ $header }}
</header>
<main>
    {{ $content }}
</main>
</body>
</html>

home.blade.php

@component('layout', ['title' => 'Home'])

    @slot('header')
        {{-- @parent--}}
        <h1>Laravel</h1>
        <h2>Home</h2>
    @endslot

    @slot('content')
        <p>ここは Home のコンテンツです</p>
    @endslot

@endcomponent

ほぼ同じことが出来ますが、次のような違いがあります。

  • @extends で使える @parent に相当するものが @component にはない
  • @extends ではビューにアサインした変数がレイアウトテンプレートでも使用できる
    • @component だと明示的指定しないとレイアウトテンプレートに変数が渡されない

前者はあまり使う機会がない(Smarty や Twig にも同じような機能あるけどほとんど使った試しがない)のと、後者は個別のテンプレート用にアサインしたつもりの変数を、全体で共通に使われるレイアウトのテンプレートで使ってしまって、この変数どっから来たんや・・・みたいなのを避けることができてメリットにも感じます。

もし @component ですべての変数を渡したければ次のようにもできそうですし。

home.blade.php

@component('layout', get_defined_vars())

    {{-- snip --}}

@endcomponent

さいごに

たいていのテンプレートエンジンにある includeextends の機能、ビューにアサインされた変数が自動的にすべて引き渡されると includeextends の先のテンプレートでどこから来たのかわかりにくい変数が使われてしまうことがあります。

Blade の @component なら変数は明示的に渡す必要があって間違いが起きにくいので、@include@extends をすべて置き換えてしまっても良いのでは、と思いましたが・・・

@component@extends のように使うのは @component の本来の用途違うだろうので知らずに見ると混乱しそうで余計判りにくいかな。。。実際他の人が @component をこのような使い方をしていて何だこれはと思ったわけですけど(それでこの使い方を知った)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】artisanコマンドまとめ

よく使うartisanコマンドのメモ

https://readouble.com/laravel/5.8/ja/

routeの確認

php artisan route:list

マイグレーション

php artisan make:migration create_users_table

マイグレーションを作成

php artisan migrate

マイグレーションを実行する

php artisan migrate:rollback

マイグレーションをロールバック

Seeder

php artisan make:seeder UsersTableSeeder

seederを作成する

シーダクラスを書き上げたら、Composerのオートローダーを再生成する。

composer dump-autoload

php artisna db:seed

DatabaseSeeder クラスに追加したSeedを実行する

public function run()
{
    $this->call([
        UsersTableSeeder::class,
        PostsTableSeeder::class,
        CommentsTableSeeder::class,
    ]);
}

php artisan db:seed --class=UsersTableSeeder

特定のファイルを個別に実行する

php artisan migrate:refresh --seed

php artisan migrate:refresh でテーブルを再構築し、seederの値を初期値として設定。
本番環境でやるとやばい。とてもやばい。

モデルクラスを作成する

php artisan make:model User

マイグレーションも同時に作成できる。
php artisan make:model Flight --migration
php artisan make:model Flight -m

リクエストクラスを生成する

php artisan make:request UserRegistPost

ページネーションビューのカスタマイズ

php artisan vendor:publish --tag=laravel-pagination

resources/views/vendorディレクトリ以下にページネーションのビューファイルが生成される。
デフォルトではbootstrap-4.blade.phpが使用されている。
https://readouble.com/laravel/5.8/ja/pagination.html#customizing-the-pagination-view

サービスプロバイダを生成

php artisan make:provider RiakServiceProvider

https://readouble.com/laravel/5.8/ja/providers.html#writing-service-providers

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む