- 投稿日:2019-08-21T23:38:06+09:00
laravelで検索機能を実装する
今回は文字列を入力して部分一致検索、プルダウンメニューから選択して検索を行う機能を実装します。具体的には下記の画面でユーザ名、棋力、好きな戦法による検索を行います。
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( '角換わり' => '角換わり', '矢倉' => '矢倉', '相掛かり' => '相掛かり', '横歩取り' => '横歩取り', '向かい飛車' => '向かい飛車', '三間飛車' => '三間飛車', '四間飛車' => '四間飛車', '中飛車' => '中飛車' ), ); ?>ユーザ名のみで検索すると、以下のように正しく表示されました。
また棋力、好きな戦法で検索した場合も、以下のように正しく表示されました。
今回の検索機能のポイントは、コントローラの
$request->input
メソッドと$query->where
メソッドかなと思いました。あとプルダウンメニューの実装はconfigファイルを使用しましたが、意外と躓きました。プルダウンメニューはほかの方法でより簡単に実装できるのかもしれません。
- 投稿日:2019-08-21T20:01:26+09:00
RailsのノリでDockerを使ってNginx+Laravelの環境を作ろうとしたらハマった話
概要
DockerでNginx + Laravelの環境を作ろうとしたらハマったのでまとめました。
仕組みを知るって大切ですね。ハマった経緯
Railsで下記のような構成のアプリケーションを触っていたため、Nginx+Laravelでもいけるだろと思ったらハマった。
Railsの構成(成功)
Laravelの構成(失敗)
何故ハマったかの結論
PumaはWebサーバであるが、PHP-FPMはWebサーバではないから静的ファイルを返さなかった!
当たり前の話なんでしょうけど、コピペで動けばいいやでやってるといずれハマるものですね・・・。じゃあどうするのが正解?
前提
- Dockerを利用した場合で考えるので、1コンテナ1プロセスとして設計する (Logを収集しやすかったりといろいろと利点がありますよね)
- 本番環境はKubernetesを利用する
開発環境パターン1(ネット上でよく見るやつ)
NginxとLaravelでソースを共有して静的ファイルを返すパターン
今回ハマってなんで動くのかよく理解ができました・・・!
開発環境パターン2(静的ファイルを分離)
静的ファイルを別サーバで返すようにするパターンです。
LaravelをAPI化してSPAと連携する場合などはこっちの方がよいと思います。(本番でFirebaseとかに投げれるし)
※ LocalではBackendとFrontendを別ディレクトリとしてmountする。
本番環境パターン1(開発環境パターン1に近い形)
1Podに3コンテナはちょっと美しいとは言えないけどFrontとBackをわけていない場合はこれでいいと思います。
本番環境パターン2(開発環境パターン2に近い形)
開発環境パターン2のようにFrontとBackで分けている場合はこのような構成がいいと思います。
FrontendはFirebaseなどに投げるのもありだと思います。
あとは調査が足りてませんが、頑張ればingress-nginxでfastcgiの対応が可能かもしれません。
まとめ
- PHP-FPMはFastCGIで動作するミドルウェアであり、WebサーバではないためNginxと連携して静的ファイルを返す仕組みが必要になる
- 上手く連携させるためには2パターンある(サーバを分離しないのも含めると3パターン)
- ソースを共有できる場所を用意してNginxでそちらを参照する
- 静的ファイルをPHP-FPMサーバから分離する
- RPサーバに置く事も可能だが、役割を考えると専用にサーバを用意した方がよい
参考文献
Webサーバ、FastCGI、Unixドメインソケット、TCPソケットについて詳しく書かれています
https://qiita.com/kotarella1110/items/634f6fafeb33ae0f51dc
- 投稿日:2019-08-21T15:29:05+09:00
【Laravel】任意のSeedを単体実行する。
- 投稿日:2019-08-21T12:49:09+09:00
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_migrationuse 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抜粋
Treeinterfaceuse Franzose\ClosureTable\Contracts\EntityInterface; interface treesInterface extends EntityInterface { }継承しているInterfaceがClosureTable独自のものになっている
モデル
Tree.phpuse 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使ってみて!!
- 投稿日:2019-08-21T10:30:52+09:00
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さいごに
たいていのテンプレートエンジンにある
include
やextends
の機能、ビューにアサインされた変数が自動的にすべて引き渡されるとinclude
やextends
の先のテンプレートでどこから来たのかわかりにくい変数が使われてしまうことがあります。Blade の
@component
なら変数は明示的に渡す必要があって間違いが起きにくいので、@include
や@extends
をすべて置き換えてしまっても良いのでは、と思いましたが・・・
@component
を@extends
のように使うのは@component
の本来の用途違うだろうので知らずに見ると混乱しそうで余計判りにくいかな。。。実際他の人が@component
をこのような使い方をしていて何だこれはと思ったわけですけど(それでこの使い方を知った)
- 投稿日:2019-08-21T09:52:31+09:00
【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