20210912のlaravelに関する記事は13件です。

Laravel 8でテキストを改行させ、埋め込まれたJSを実行させない方法。

<p>{!! nl2br(e($post->body)) !!}</p> e()で文字実態参照に変換。 nl2br()で改行コードを改行タグに変換。 {!! !!}でbrタグで改行させるようにする。(文字実体参照への変換を無効にする)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel 8 の php artisan down では --allow ではなく --secret を使う

Laravel 8 で php artisan down --allow={俺のIP} って打ったら「--allow なんかねぇ!」って言われたんですが本番環境だったので泣いてしまいました。これに関して日本語の記事が皆無だったのでメモ。 Laravel 7 まで メンテナンスモードにするには php artisan down だが、これでは開発者が何かテストしたい場合にもアクセスできず門前払いになってしまう。そこで、--allow オプションでIPを指定することにより回避することができた。 $ php artisan down --allow=127.0.0.1 Laravel 8 では --allow は消えました。でももっと楽です。 --secret オプション これを利用して、落とすときにパスワードを設定しておく。 $ php artisan down --secret="hogehoge" その後、https://[ドメイン]/hogehoge にアクセスする。すると / にリダイレクトされると同時にメンテナンスモードが解除される。 内部的には、https://[ドメイン]/hogehoge に行った時点でクッキーが保存されて、そのキーによって認証されてる感じらしい。 言うまでもないが、これを使えばいちいちIPを気にする必要もないし複数人での確認も楽勝となる。めっちゃいい機能だけどマジで焦ったんだからね! 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelを使ってチームで開発する前に決めておくべきコーディングルール

はじめに 現在Laravel × Vueを用いてサービス開発をしており、チームとしては実装担当4名でプロジェクトを進めています。開発を進めていく中で、初めのうちに決めておけばよかった!ということがあったので当記事で共有しておきます。 コーディングルールを決めておけば、プログラムの統一性が増し可読性の向上、バグの減少、命名にかける時間の削減に繋がりますので決めておくことをおすすめします。 決めておいたほうがいいこと メソッドや変数の命名規則 各ファイルの役割 ディレクトリ構成 リレーションの命名規則 routeの記述の仕方 コントローラの使い方 リソースコントローラでモデル結合ルート(ルートモデルバインディング)を使うかどうか メソッドや変数の命名規則 命名規則において以下の形式がよく用いられます。 命名規則 書き方の例 キャメルケース userProfile スネークケース user_profile パスカルケース(アッパーキャメル) UserProfile ケバブケース user-profile これはLaravelに限らずどの言語にも言えることですが、クラス名やメソッド名、変数名にそれぞれどの命名規則で記述するか、まず決めておいたほうがいいでしょう。 ちなみに今のプロジェクトではクラス名はパスカルケース、メソッドはキャメルケース、変数名はスネークケースで定義すると決めています(ただし、一部例外あり)。 中にはローカル変数に_(アンダースコア)をつけるというルールをつける場合もあります。 各ファイルの役割 LaravelはMVCのフレームワークのためContollerとModelそして、viewファイルが存在します。 基本的にこの3種類のファイルだけでもアプリケーションは作れますが、これだけだとContollerが肥大して可読性の低いfatControllerが出来上がってしまいます。より責務を分けるためにLaravelではいくつかのファイルが用意されています。 以下にいくつか紹介します。 ファイル種類 役割 Requestクラス リクエストパラメータ関連の処理をまとめる。バリデーションもここで行う MiddleWare Controllerに行く前に行いたい処理をする。IP制限や認証制限などを行える Resourceクラス APIなどでレスポンスパラメータを記述できる また、ビジネスロジックを記述するServiceクラスやUseCaseクラスを定義するパターンもよくあります。 いずれもControllerに書くこともできますがファイルを分けることによって、可読性が増し、また使い回ししやすく開発スピードの向上にもなります。 その際問題となるのがどのファイルにどこまで記述するのかです。 各ファイルをどのようにして使っていくのか、大枠でいいので軽くルール決めしておいたほうがいいでしょう。 あとは開発を進めながらチームでルールを追加修正していく必要が出てきます。 ディレクトリ構成 プロジェクト全体で使うファイル群を格納するディレクトリを決めます。 どのような構成にするかはプロジェクトによって異なってきますが、我々のプロジェクトでは以下の構成で作成しています。 src └ app ├ Http ├ Controllers ├ Api ├ Auth └ Web ├ MiddleWare ├ Requests └ Resources ├ Model ├ User ├ User.php └ UserProfile.php ├ Product ├ UserProduct.php └ UserProductItem.php └ ... ├ Services ├ UserServices.php └ ProductServices.php └ Util └ Price.php ControllerはApiとWebがあるのでそれぞれ分けています。 Modelは分けるか迷いましたが、Modelの数が増えてきたときにわかりにくくなりそうだったので今回は関心事で分けてみました。 ビジネスロジックは基本的にServiceに切り分けServicesディレクトリにまとめています。 また、プロジェクト全体で汎用的に使えるクラスをまとめるためにUtilディレクトリを用意しています。定数管理のクラスをメインにおいてます。 リレーションの命名規則 私達はここで揉めました。皆さんはリレーションをModelに定義するとき命名規則はどうしていますか? User.php // スネークケース public function user_profile() { return $this->hasOne(UserProfile::class); } // パスカルケース public function UserProfile() { return $this->hasOne(UserProfile::class); } // キャメルケース public function userProfile() { return $this->hasOne(UserProfile::class); } // リレーション使用時 public function userProfile() { $user = User::find(1); // スネークケース $address = $user->user_profile->address; // パスカルケース $address = $user->UserProfile->address; // キャメルケース $address = $user->userProfile->address; } スネークケースはカラム名と同じ命名規則でアローでつなげても自然です。ですがカラムとリレーションが混合してリレーションであるとすぐに判断できないかもしれません。 パスカルケースの場合は明確にリレーションだと理解できそうです。 もし他のメソッドがキャメルケースで統一している場合はリレーションの定義もキャメルにしておけば命名規則を合わせることができます。 リレーションはLaravelの主要な機能なので、決めておいたほうがいいでしょう。 routeの記述の仕方 ルーティングを定義するweb.phpとapi.phpにも決めておいたほうがいいことがいろいろあります。 まずはグループ化です。各ルーティングをどのようにまとめるのか(もしくはまとめないか)は決めておいたほうがいいでしょう。基本的に関心事でまとめて置くと探しやすくなります。 以下はUserとAdminで分けた例です。 api.php Route::prefix('user')->name('user.')->group(['middleware' => ['auth.api']], function () { // user関連の各ルーティングをまとめて記述 }); Route::prefix('admin')->name('admin.')->group(['middleware' => ['admin.auth.api']], function () { // admin関連の各ルーティングをまとめて記述 }); 次にControllerの記載方法です。Laravel8からはuseを使って記述できるようになり大変便利になりました。 その際、useを使うか、namespaceをフルで書くかです。 Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home'); Route::get('/home', [HomeController::class, 'index'])->name('home'); 個人的にはuseしたほうが見やすいのでuseしています。 もしUserとAdminで同じController名になってもuseでasを使えば別名で定義することができます。 コントローラの種類 コントローラの種類には「独自メソッド定義コントローラ」「リソースコントローラ」「シングルアクションコントローラ」があります。 make:controllerする際に何もオプションを付けなければ単なる「独自メソッド定義コントローラ」になります。 CRUD処理を記述するのであれば「リソースコントローラ」を使います。 さらに、アクションを一つだけ含むコントローラを定義したい場合「シングルアクションコントローラ」を使います。 どれを使用するかはそのコントローラで何をするかにもよりますが、CRUD処理のコントローラは「リソースコントローラ」を使おう!、あまりアクション数が増えると探すのが大変だから基本的には「シングルアクションコントローラ」にしよう!など、プロジェクトでルールを決めておくと統一性が増すでしょう。 リソースコントローラでモデル結合ルート(ルートモデルバインディング)を使うかどうか 普通にmake:controller UserProfileController -rすると以下のようなファイルが生成されます。 <?php namespace App\Http\Controllers; use Illuminate\Http\Request; class UserProfileController extends Controller { /** * Display a listing of the resource. * * @return \Illuminate\Http\Response */ public function index() { // } /** * Show the form for creating a new resource. * * @return \Illuminate\Http\Response */ public function create() { // } /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(Request $request) { // } /** * Display the specified resource. * * @param int $id * @return \Illuminate\Http\Response */ public function show($id) { // } /** * Show the form for editing the specified resource. * * @param int $id * @return \Illuminate\Http\Response */ public function edit($id) { // } /** * Update the specified resource in storage. * * @param \Illuminate\Http\Request $request * @param int $id * @return \Illuminate\Http\Response */ public function update(Request $request, $id) { // } /** * Remove the specified resource from storage. * * @param int $id * @return \Illuminate\Http\Response */ public function destroy($id) { // } } 注目すべきはshowやeditアクションの引数が$idになっている点です。 ここにはルーティングで指定したパラメータが入ってきます。 基本的には$idはModelのid値が入ってきますが、make時にModelを指定すれば直接Modelを注入した形でも生成できます。 php artisan make:controller UserProfileController --model=UserProfileすると以下のようなファイルが生成されます。 <?php namespace App\Http\Controllers; use App\Models\UserProfile; use Illuminate\Http\Request; class UserProfileController extends Controller { /** * Display a listing of the resource. * * @return \Illuminate\Http\Response */ public function index() { // } /** * Show the form for creating a new resource. * * @return \Illuminate\Http\Response */ public function create() { // } /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(Request $request) { // } /** * Display the specified resource. * * @param \App\Models\UserProfile $userProfile * @return \Illuminate\Http\Response */ public function show(UserProfile $userProfile) { // } /** * Show the form for editing the specified resource. * * @param \App\Models\UserProfile $userProfile * @return \Illuminate\Http\Response */ public function edit(UserProfile $userProfile) { // } /** * Update the specified resource in storage. * * @param \Illuminate\Http\Request $request * @param \App\Models\UserProfile $userProfile * @return \Illuminate\Http\Response */ public function update(Request $request, UserProfile $userProfile) { // } /** * Remove the specified resource from storage. * * @param \App\Models\UserProfile $userProfile * @return \Illuminate\Http\Response */ public function destroy(UserProfile $userProfile) { // } } $idではなくモデルのインスタンスがはいっているのがわかるでしょう。 こちらもチームで$idにするかモデル結合ルートを使うか統一しておいたほうがいいでしょう。 私は$idにする理由はないと思っているので基本的にモデル結合ルートにしています。 まとめ チームで開発する際はなるべくコードを統一できるように工夫していく必要があります。 今回はLaravelプロジェクトにおいて議論した内容を記載してみました。 追加があれば随時追記していこうと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】検索機能を実装する ~複数条件&プルダウンからの選択形式~

作成物 この記事では、「キーワード検索」+「プルダウンより選択形式の検索」による複数条件での検索機能を持つ教材管理アプリを作成します。 テーブル構成 ■教材テーブル ・教材ID ・教材名 ・媒体ID ・カテゴリーID ■媒体テーブル ・媒体ID ・媒体 ■カテゴリーテーブル ・カテゴリーID ・カテゴリー 手順 マイグレーションファイルの作成 サンプルデータの挿入(シーダー) モデルの作成 ビューファイルの作成 コントローラーの作成 ルーティングの設定 1. マイグレーションファイルの作成 テーブル作成 教材テーブル・媒体テーブル・コンテンツテーブルの3つを作成します。 ※MySQLでデータベースを作成し、MySQLとの接続は済んでいるものとします。 $ php artisan make:migration create_TeachingMaterials_table $ php artisan make:migration create_Media_table $ php artisan make:migration create_Categories_table ※媒体:単数形「medium」 / 複数形「media」に注意。 マイグレーションファイルの編集 各マイグレーションファイルのスキーマを構築します。 外部キー参照先のマイグレーションファイルが実行されていない状態で、外部キーを設定したマイグレーションファイルが実行されると、参照するテーブルがないとエラーを返されます。 マイグレーションはファイルを作成した順(ファイル名になっている作成日時順)に実行されます。 よってここでは、MediaテーブルとCategoriesテーブルのマイグレーションファイルの作成日時が、Teaching Materialsテーブルのマイグレーションファイルよりも先でなければいけません。 2021_09_10_222418_create__media_table.php class CreateMediaTable extends Migration { public function up() { Schema::create('media', function (Blueprint $table) { $table->increments('id')->comment('媒体ID'); $table->string('medium')->comment('媒体名(書籍/動画/etc...)'); }); } 2021_09_10_222418_create__categories_table.php class CreateCategoriesTable extends Migration { public function up() { Schema::create('categories', function (Blueprint $table) { $table->increments('id')->comment('コンテンツID'); $table->string('category')->comment('コンテンツ名(Laravel/Git/etc...)'); }); } 2021_09_10_222419_create__teaching_materials_table.php class CreateTeachingMaterialsTable extends Migration { public function up() { Schema::create('teaching_materials', function (Blueprint $table) { $table->increments('id')->comment('教材ID'); $table->string('name')->comment('教材名'); $table->unsignedInteger('medium_id'); $table->unsignedInteger('category_id'); //外部キー制約の設定 $table->foreign('medium_id')->references('id')->on('media')->comment('媒体ID'); $table->foreign('category_id')->references('id')->on('categories')->comment('コンテンツID'); }); } マイグレーションの実行 $ php artisan migrate テーブルを作成することができました。 2. サンプルデータの挿入(シーダー) シーダーファイルの作成 $ php artisan make:seed TeachingMaterialsTableSeeder $ php artisan make:seed MediaTableSeeder $ php artisan make:seed CategoryTableSeeder /アプリ名/database/seedersにシーダーファイルが作成されます。 シーダーファイルの編集 3つのシーダーファイルに、サンプルデータを記述していきます。 TeachingMaterialsTableSeeder.php class TeachingMaterialsTableSeeder extends Seeder { public function run() { \DB::table('teaching_materials')->insert([ 'id' => '1', 'name' => 'Git: もう怖くないGit!チーム開発で必要なGitを完全マスター', 'medium_id' => '1', 'category_id' => '3' ]); \DB::table('teaching_materials')->insert([ 'id' => '2', 'name' => 'PHP+MySQL(MariaDB) Webサーバーサイドプログラミング入門', 'media_id' => '1', 'category' => '4' ]); \DB::table('teaching_materials')->insert([ 'id' => '3', 'name' => '最短・最速で学ぶFirebase Hosting + Vue Todoアプリ実装', 'media_id' => '1', 'category_id' => '7' ]); // 略 } MediaTableSeeder.php class MediaTableSeeder extends Seeder { public function run() { \DB::table('media')->insert([ 'id' => '1', 'medium' => 'Udemy' ]); \DB::table('media')->insert([ 'id' => '2', 'medium' => '書籍' ]); \DB::table('media')->insert([ 'id' => '3', 'medium' => 'Techpit' ]); } } CategoriesTableSeeder.php class CategoriesTableSeeder extends Seeder { public function run() { \DB::table('categories')->insert([ 'id' => '1', 'category' => 'Laravel' ]); \DB::table('categories')->insert([ 'id' => '2', 'category' => 'Web技術' ]); \DB::table('categories')->insert([ 'id' => '3', 'category' => 'Git' ]); // 略 } } DatabaseSeeder.phpの編集 上記で作成したシーダーファイルを大元のシーダーファイル(DatabaseSeeder.php)から呼び出します。 TeachingMaterialsTableにおいてMediaTablesとCategoriesTableを参照しているので、Seederを呼び出す順番は、参照元を先にしなければいけません。 DatabaseSeeder.php class DatabaseSeeder extends Seeder { public function run() { $this->call(MediaTableSeeder::class); $this->call(CategoriesTableSeeder::class); $this->call(TeachingMaterialsTableSeeder::class); } } シーダーの実行 $ php artisan db:seed サンプルデータを作成することができました。 3. モデルの作成 媒体:教材 = 1:多 コンテンツ:教材 = 1:多 の関係になります。 各モデルにリレーションを書いていきます。 TeachingMaterial.php class TeachingMaterial extends Model { use HasFactory; public function medium() { return $this->belongsTo(Medium::class); } public function category() { return $this->belongsTo(Category::class); } } Medium.php class Medium extends Model { use HasFactory; public function teaching_material() { return $this->hasMany(TeachingMaterial::class); } } Category.php class Category extends Model { use HasFactory; public function teaching_material() { return $this->hasMany(TeachingMaterial::class); } } 4. ビューファイルの作成 今回はbladeの機能は使わず、簡易的に1つのビューファイルに記述します。 検索機能は3つです。 ・キーワードを入力し、教材名から部分一致検索 ・媒体をプルダウンより選択して検索 ・カテゴリーをプルダウンより選択して検索 index.blade.php <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Teaaching Materials</title> <link rel="stylesheet" href="{{ url('css/style.css') }}"> </head> <body> //* 検索機能ここから *// <div class="search"> <form action="{{ route('index') }}" method="GET"> @csrf <div class="form-group"> <div> <label for="">キーワード <div> <input type="text" name="keyword" value="{{ $keyword }}"> </div> </label> </div> <div> <label for="">媒体 <div> <select name="medium" data-toggle="select"> <option value="">全て</option> @foreach ($media_list as $media_item) <option value="{{ $media_item->getMedium() }}" @if($medium=='{{ $media_item->getMedium() }}') selected @endif>{{ $media_item->getMedium() }}</option> @endforeach </select> </div> </label> </div> <div> <label for="">カテゴリー <div> <select name="category" data-toggle="select"> <option value="">全て</option> @foreach ($categories_list as $categories_item) <option value="{{ $categories_item->getCategory() }}" @if($category=='{{ $categories_item->getCategory() }}') selected @endif>{{ $categories_item->getCategory() }}</option> @endforeach </select> </div> </label> </div> <div> <input type="submit" class="btn" value="検索"> </div> </div> </form> </div> //* 検索機能ここまで *// <table> <tr> <th>教材名</th> <th>媒体</th> <th>コンテンツ</th> </tr> @foreach ($items as $item) <tr> <td>{{ $item->name }}</td> <td>{{ $item->medium }}</td> <td>{{ $item->category }}</td> //mediaテーブルとcategoriesテーブルを結合しているので、この記述でアクセスできる </tr> @endforeach </table> </body> </html> 5. コントローラーの作成 リレーション先のテーブルのカラムも検索対象になっているので、joinでテーブル結合します。 TeachingMaterialsController.php class TeachingMaterialController extends Controller { public function index(Request $request) { //検索フォームに入力された値を取得 $medium = $request->input('medium'); $category = $request->input('category'); $keyword = $request->input('keyword'); $query = TeachingMaterial::query(); //テーブル結合 $query->join('media', function ($query) use ($request) { $query->on('teaching_materials.medium_id', '=', 'media.id'); })->join('categories', function ($query) use ($request) { $query->on('teaching_materials.category_id', '=', 'categories.id'); }); if(!empty($medium)) { $query->where('medium', 'LIKE', $medium); } if(!empty($category)) { $query->where('category', 'LIKE', $category); } if(!empty($keyword)) { $query->where('name', 'LIKE', "%{$keyword}%"); } $items = $query->get(); $media_list = Medium::all(); $categories_list = Category::all(); return view('index', compact('items', 'keyword', 'medium', 'category', 'media_list', 'categories_list')); } } 6. ルーティングの設定 web.php Route::get('/', [TeachingMaterialController::class, 'index'])->name('index'); 以上です^^
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【個人開発】後悔しない大学選びのために使って欲しいWEBアプリケーションを作りました。

1.はじめに 大学入学後に「進路再考」という理由で休学・退学する学生を数多く見てきました。 入学前に抱いていたイメージとのギャップに苦しむ学生は一定数いるようです。 また、退学とまではいかなくても、4年間「何か違うな。。。」と感じながら大学生活を送るのはあまりにも勿体ないです。 受験生に後悔のない進路選択をして欲しい。 知名度や偏差値以外の判断材料を提供したい。 そんな願いを込めて、今回アプリケーションを開発しました。 使用技術は、LaravelとVue.jsです。 2.何を作ったのか 簡潔に述べると、各大学の定める「ディプロマポリシー」・「カリキュラムポリシー」・「アドミッションポリシー」を検索できるアプリを作りました。 「は?何それ?」と思われた方が大半だと思います。 私も大学関係者になってから知りました。 実はこのポリシーこそが進路選択の際に非常に役に立ちます。 受験生の皆さんにも是非知っていただき、進路選択の判断材料にしていただきたいです。 アプリのトップ画面はこんな感じです。 3.各ポリシーについての説明 ポリシーについて興味を持ってくださった方のために、簡単に説明させていただきます。 文部科学省の「三つのポリシーの策定と運用に係るガイドライン」によると、それぞれ以下のように定義づけられています。 引用元:https://www.mext.go.jp/b_menu/shingi/chukyo/chukyo4/015/attach/1365326.htm ディプロマポリシー 各大学がその教育理念を踏まえ,どのような力を身に付ければ学位を授与するのかを定める基本的な方針であり,学生の学修成果の目標ともなるもの。 卒業するために必要な能力を示しています。 裏を返せば、卒業に必要な単位を揃えることで身に付く能力を示していると言えます。 カリキュラムポリシー ディプロマ・ポリシーの達成のために,どのような教育課程を編成し,どのような教育内容・方法を実施するのかを定める基本的な方針。 各年次でに何を学ぶか、という内容を示しているパターンが多いです。 アドミッションポリシー 各大学が,当該大学・学部等の教育理念,ディプロマ・ポリシー,カリキュラム・ポリシーに基づく教育内容等を踏まえ,入学者を受け入れるための基本的な方針であり,受け入れる学生に求める学習成果(学力の3要素※)を示すもの。 ※(1)知識・技能,(2)思考力・判断力,表現力等の能力,(3)主体性を持って多様な人々と協働して学ぶ態度 簡単に言うと、大学側がどういう人を求めているか、ということを示しています。 4.3つのポリシーを把握するメリット ポリシーを把握しておくことで得られるメリットは以下のとおりです。 ①卒業までに習得できる能力を逆算できる。 ②カリキュラムがどのように進行するか知ることで、進学後の勉強がイメージできる。 ③大学側が求める人物像を知ることで、自身に合っているかの判断ができる。 受験生側が上述の3点を把握しておくことで、大学とのミスマッチを防ぐ効果が期待できます。 5.アプリケーションの機能について 前置きが長くなりました。 当アプリの機能についてご紹介させていただきます。 ポリシーを検索することに特化したアプリとなっておりますので、機能は少なめです。 主な機能をリストアップするとこんな感じです。 機能一覧 ログイン機能(Laravel Auth) 大学の検索機能 ポリシーの表示機能 お気に入り追加機能(Ajax) お気に入り削除機能(Ajax) ご指摘・ご意見等投稿機能(formrun使用) 以下、特にご紹介したい点のみ補足します。 大学検索機能 以下の5つの項目から大学・学部・学科を検索することができます。 ①大学名 ②地方 ③都道府県 ④学びたい分野 ⑤国公立or私立 条件を掛け合わせて検索することも可能です。 ↓↓実際の画面 お気に入り機能 ポリシーの表示画面でハートマークを押すと、お気に入りに追加できます。 お気に入りに追加したデータは、お気に入り画面からいつでも確認することができます。 また、追加したデータは、いつでも削除できます。 ポリシーを比較したい時にも有効です。 ご指摘・ご意見等投稿機能 ゲスト以外でログインした場合に使用することができます。 自作することも考えましたが、簡単かつ安全に利用できる外部のサービスを使用しております。 各学部のポリシーは、学部再編やカリキュラム改訂などで変更になる場合がありますので、 もし気づいた方がいれば、フォームから教えてくださいという願望を込めて設置しております。 もちろん、「この箇所をこうして欲しい!!」といったご意見も大歓迎です。 ↓↓実際の画面 これを無料で使えるのは正直驚きですね。。ありがとうございます。 6.工夫点 ①一部機能を制限したゲスト機能を実装 当アプリケーションは、受験生が使用することを前提に開発いたしました。 高校生に限った話ではないですが、訳のわからんサイトに登録するのは抵抗がありますよね。 私が高校生の頃は架空請求などが流行っていたりして、登録系のサイトは怖くて使用できませんでした。 しかし、サービスは使ってもらえないと価値がないです。 そこで、気軽に使っていただけるよう、ゲストログイン機能を実装しました。 ②スマホで見ることを想定してレスポンシブ化 高校生がスマホで使用することを想定して、レスポンシブデザインにはかなり気を配りました。 勉強の合間とかで使ってくれたらなぁと思います。 ③利用規約の設置 個人的に、利用規約がないサービスは恐ろしくて手を出せません。 また、トラブル発生時に備えて、責任の所在を明確にしておくことは必要不可欠です。 素人が作ったサービスだからこそ、もしもに備えて設置しました。 ④SPA開発 ユーザーの快適さを追求して、SPA開発に挑戦しました。 スイスイ動くので気持ちが良いですね。 ほぼ全ての処理をJavaScript(Vue.js)で書き、SPA対応にしました。 (認証系はLaravelに素晴らしいモノが備わっているため、そちらに任せました。) 7.今後の課題 ①私立大学に対応させる。 現在、国公立大学にしか対応しておりません。 というのも、使えそうなapiがなく、大学のデータを集める作業は全て手作業でやっております。 (おそらくこの作業が一番時間がかかったと思います。) 今後は私立大学も実装していきたい所存です。 ②テストのカバレッジをあげる。 テストコードについて、現在勉強中です。 テストを想定して作っていなかったので、苦戦しています。 テストを想定して開発する重要性を身をもって体感しました。 良い勉強になりました。 8.おわりに 最後までご覧いただきありがとうございました。 当サービスは既にデプロイ済みですので、どなたでもご利用いただけます。 URLを置いておきますので、使っていただけると幸いです。 ご意見などをいただけるととても嬉しいです。 「大学ポリシー検索ツール」 https://univpolicy.net
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

 【個人開発】後悔しない大学選びのために使って欲しいWEBアプリケーションを作りました。

1.はじめに 大学入学後に「進路再考」という理由で休学・退学する学生を数多く見てきました。 入学前に抱いていたイメージとのギャップに苦しむ学生は一定数いるようです。 また、退学とまではいかなくても、4年間「何か違うな。。。」と感じながら大学生活を送るのはあまりにも勿体ないです。 受験生に後悔のない進路選択をして欲しい。 知名度や偏差値以外の判断材料を提供したい。 そんな願いを込めて、今回アプリケーションを開発しました。 使用技術は、LaravelとVue.jsです。 2.何を作ったのか 簡潔に述べると、各大学の定める「ディプロマポリシー」・「カリキュラムポリシー」・「アドミッションポリシー」を検索できるアプリを作りました。 「は?何それ?」と思われた方が大半だと思います。 私も大学関係者になってから知りました。 実はこのポリシーこそが進路選択の際に非常に役に立ちます。 受験生の皆さんにも是非知っていただき、進路選択の判断材料にしていただきたいです。 アプリのトップ画面はこんな感じです。 3.各ポリシーについての説明 ポリシーについて興味を持ってくださった方のために、簡単に説明させていただきます。 文部科学省の「三つのポリシーの策定と運用に係るガイドライン」よると、それぞれ以下のように定義づけられています。 引用元:https://www.mext.go.jp/b_menu/shingi/chukyo/chukyo4/015/attach/1365326.htm ディプロマポリシー 各大学がその教育理念を踏まえ,どのような力を身に付ければ学位を授与するのかを定める基本的な方針であり,学生の学修成果の目標ともなるもの。 卒業するために必要な能力を示しています。 裏を返せば、卒業に必要な単位を揃えることで身に付く能力を示していると言えます。 カリキュラムポリシー ディプロマ・ポリシーの達成のために,どのような教育課程を編成し,どのような教育内容・方法を実施するのかを定める基本的な方針。 各年次でに何を学ぶか、という内容を示しているパターンが多いです。 アドミッションポリシー 各大学が,当該大学・学部等の教育理念,ディプロマ・ポリシー,カリキュラム・ポリシーに基づく教育内容等を踏まえ,入学者を受け入れるための基本的な方針であり,受け入れる学生に求める学習成果(学力の3要素※)を示すもの。 ※(1)知識・技能,(2)思考力・判断力,表現力等の能力,(3)主体性を持って多様な人々と協働して学ぶ態度 簡単に言うと、大学側がどういう人を求めているか、ということを示しています。 4.3つのポリシーを把握するメリット ポリシーを把握しておくことで得られるメリットは以下のとおりです。 ①卒業までに習得できる能力を逆算できる。 ②カリキュラムがどのように進行するか知ることで、進学後の勉強がイメージできる。 ③大学側が求める人物像を知ることで、自身に合っているかの判断ができる。 受験生側が上述の3点を把握しておくことで、大学とのミスマッチを防ぐ効果が期待できます。 5.アプリケーションの機能について 前置きが長くなりました。 当アプリの機能についてご紹介させていただきます。 ポリシーを検索することに特化したアプリとなっておりますので、機能は少なめです。 主な機能をリストアップするとこんな感じです。 機能一覧 ログイン機能(Laravel Auth) 大学の検索機能 ポリシーの表示機能 お気に入り追加機能(Ajax) お気に入り削除機能(Ajax) ご指摘・ご意見等投稿機能(formrun使用) 以下、特にご紹介したい点のみ補足します。 大学検索機能 以下の5つの項目から大学・学部・学科を検索することができます。 ①大学名 ②地方 ③都道府県 ④学びたい分野 ⑤国公立or私立 条件を掛け合わせて検索することも可能です。 ↓↓実際の画面 お気に入り機能 ポリシーの表示画面でハートマークを押すと、お気に入りに追加できます。 お気に入りに追加したデータは、お気に入り画面からいつでも確認することができます。 また、追加したデータは、いつでも削除できます。 ポリシーを比較したい時にも有効です。 ご指摘・ご意見等投稿機能 ゲスト以外でログインした場合に使用することができます。 自作することも考えましたが、簡単かつ安全に利用できる外部のサービスを使用しております。 各学部のポリシーは、学部再編やカリキュラム改訂などで変更になる場合がありますので、 もし気づいた方がいれば、フォームから教えてくださいという願望を込めて設置しております。 もちろん、「この箇所をこうして欲しい!!」といったご意見も大歓迎です。 ↓↓実際の画面 これを無料で使えるのは正直驚きですね。。ありがとうございます。 6.工夫点 ①一部機能を制限したゲスト機能を実装 当アプリケーションは、受験生が使用することを前提に開発いたしました。 高校生に限った話ではないですが、訳のわからんサイトに登録するのは抵抗がありますよね。 私が高校生の頃は架空請求などが流行っていたりして、登録系のサイトは怖くて使用できませんでした。 しかし、サービスは使ってもらえないと価値がないです。 そこで、気軽に使っていただけるよう、ゲストログイン機能を実装しました。 ②スマホで見ることを想定してレスポンシブ化 高校生がスマホで使用することを想定して、レスポンシブデザインにはかなり気を配りました。 勉強の合間とかで使ってくれたらなぁと思います。 ③利用規約の設置 個人的に、利用規約がないサービスは恐ろしくて手を出せません。 また、トラブル発生時に備えて、責任の所在を明確にしておくことは必要不可欠です。 素人が作ったサービスだからこそ、もしもに備えて設置しました。 ④SPA開発 ユーザーの快適さを追求して、SPA開発に挑戦しました。 スイスイ動くので気持ちが良いですね。 ほぼ全ての処理をJavaScript(Vue.js)で書き、SPA対応にしました。 (認証系はLaravelに素晴らしいモノが備わっているため、そちらに任せました。) 7.今後の課題 ①私立大学に対応させる。 現在、国公立大学にしか対応しておりません。 というのも、使えそうなapiがなく、大学のデータを集める作業は全て手作業でやっております。 (おそらくこの作業が一番時間がかかったと思います。) 今後は私立大学も実装していきたい所存です。 ②テストのカバレッジをあげる。 テストコードについて、現在勉強中です。 テストを想定して作っていなかったので、苦戦しています。 テストを想定して開発する重要性を身をもって体感しました。 良い勉強になりました。 8.おわりに 最後までご覧いただきありがとうございました。 当サービスは既にデプロイ済みですので、どなたでもご利用いただけます。 URLを置いておきますので、使っていただけると幸いです。 ご意見などをいただけるととても嬉しいです。 「大学ポリシー検索ツール」 https://univpolicy.net
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelでマイグレーションを使ってテーブルにカラムを追加する

はじめに  phpmyadminを使えばカラムを追加するのは簡単ですが、Laravelにはマイグレーションファイルがあります。マイグレーションファイルを使ったカラムの追加方法を備忘録として残していきます。 追加手順  まずは新たにマイグレーションファイルを作成します。今回はitemsテーブルにimageカラムを追加するという想定でいきましょう。 php artisan make:migration add_image_to_items_table --table=items ターミナルでこのように打ちこむと、新しくマイグレーションファイルが生成されます。 public function up()にカラムの追加の記述を入れましょう。downの方には削除の記述をしておきます。 xxxx_add_image_to_items_table.php class AddImageToItemsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::table('items', function (Blueprint $table) { // $table->string('image'); //カラム追加 }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('items', function (Blueprint $table) { // $table->dropColumn('image'); //カラムの削除 }); } } マイグレーションファイルの編集が終わったら実行しましょう。 php artisan migrate 無事にitemsテーブルにimageカラムを追加することが出来ました!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelでテーブルにカラムを追加する

はじめに  phpmyadminを使えばカラムを追加するのは簡単ですが、Laravelにはマイグレーションファイルがあります。マイグレーションファイルを使ったカラムの追加方法を備忘録として残していきます。 追加手順  まずは新たにマイグレーションファイルを作成します。今回はitemsテーブルにimageカラムを追加するという想定でいきましょう。 php artisan make:migration add_image_to_items_table --table=items ターミナルでこのように打ちこむと、新しくマイグレーションファイルが生成されます。 public function up()にカラムの追加の記述を入れましょう。downの方には削除の記述をしておきます。 xxxx_add_image_to_items_table.php class AddImageToItemsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::table('items', function (Blueprint $table) { // $table->string('image'); //カラム追加 }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('items', function (Blueprint $table) { // $table->dropColumn('image'); //カラムの削除 }); } } マイグレーションファイルの編集が終わったら実行しましょう。 php artisan migrate 無事にitemsテーブルにimageカラムを追加することが出来ました!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【簡単3 steps】Vue.jsサンプルをLaravelアプリに実装する方法

今回の記事は下記の方におすすめです! Laravelの基礎はOK Vue.jsの基礎はOK でもLaravelでVue.jsサンプルを使う方法が不明 自分でゴリゴリコードを書くのもありですが、最初のうちは「Vue.js」のサンプルをLaravelのサイトに埋め込むところから始めるのはありかもしれません。 今回は、「Vue.js」のサンプルが多数紹介されている海外のサイトを紹介しつつ、サンプルを実際の自分のプロジェクトに埋め込む方法について共有いたします! 今回の記事が参考になれば幸いです なお、間違いやご指摘ありましたら、コメント下さると幸いです。 実行環境 PHP 7.4 Laravel 6.2 Vue.js 2.5 ゴール Laravelのアプリ内でvue.jsのサンプルを実装する ちゅうい!! なお、Vue.jsがコンパイルできる環境が構築されている前提で話を進めていきます。 まだの方は、下記の記事の1)~4)を完了させ、「npm run dev」でコンパイルできる状態にしてください。 コンパイルまでの参考記事:【初心者向】Laravel 6系でVue.jsを使用する方法 laravel/uiパッケージをインストール 「composer require laravel/ui:^1.0 --dev」 vue.jsのファイル生成 「php artisan ui vue」 依存パッケージのインストール 「npm install」 js/sassファイルのコンパイル実行 「npm run dev」 ココまで↑は最低限実行しておいてください。詳細は上記記事にて。 Vue.jsサンプルサイト ちなみに下記のサイトではVue.jsを使ったサンプルがまとめられています 「スクロール」や「ポップアップ」など便利な部品が多数まとめられているので、是非一度目を通してみてください。 サンプルまとめサイト:Vue.js Examples ↑こんな感じで、サンプルによっては、Gif動画形式で、挙動を確認できるので、イメージしやすいかと思います。 今回埋め込むサンプル「scroll」 今回は、「スクロール」をLaravelサイトに埋め込んでいこうと思います。画面をスクロールすると、画面上部緑の進捗バーが進捗します。 今回Laravelで使用するサンプル:scroll progress bar ↓緑のバーが進捗 手順 Vue.jsサンプルをインストール Laravelと紐付ける インストールしたVue.jsサンプルをコンパイル 1)Vue.jsサンプルをインストール npm i vue-scroll-progress --save まず、パッケージをインストールします。上記コマンドで「node_modules」ディレクトリ以下に「vue-scroll-progress」がインストールされるかと思います。 この時点でエラーが出た方は、ご自分の開発環境のバージョンとサンプルのバージョンが競合している可能性があるので、競合しなさそうな(エラーにならない)ものをサンプルサイトから探してみてください。 2)Laravelと紐付ける ただインストールしただけでは使用できません。 Laravelのアプリ側からは、見えていないからですね。 そこでLaravelのアプリ側が認識してくれる場所に 「この機能を使いますよー」宣言をします。 具体的に言うと、「resources/js/app.js」ファイルへの追記ですね。 import VueScrollProgress from 'vue-scroll-progress'; Vue.use(VueScrollProgress); 「resources/js/app.js」ファイルに上記の記述を追加してください。 resources/js/app.js // (省略) require('./bootstrap'); window.Vue = require('vue'); // 以下2行追加 import VueScrollProgress from 'vue-scroll-progress'; Vue.use(VueScrollProgress); // (省略) Vue.component('example-component', require('./components/ExampleComponent.vue').default); 「import」の意味がわからない方はこちらの記事を参考にしてみてください。 参考:jsのimportとrequireの違い 参考:JavaScriptのimportとは? 機能ごとに分割したjsファイルを、読み込んで利用する=インポートするイメージです。 3)インストールしたVue.jsサンプルをコンパイル npm run dev 上記コマンドでコンパイルを実行すると... 進捗バーが表示されました! (ちょっとわかりにくいけど、一番上の緑のボーダーですね) 応用事例 これを応用すれば、ユーザーが入力するフォーム画面で 進捗を表示することで、途中での離脱を防ぐ効果なんかも得られそうですね 今回は、「app.js」ファイルに記述しましたが、読み込みたい部分で 部品として使うこともできるようですよ。 <template> <VueScrollProgress></VueScrollProgress> </template> 今回の記事は以上になります お役に立てたなら幸いです!! 参考 たった3ステップ!Laravelでnpmパッケージを使う方法
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

laravel-echoの使い方を整理してみる。(途中)

laravel-echoリファレンスサイト dashboard.pusherの環境設定 滅茶苦茶、丁寧に解説してくれてます。 Pusherのアカウント登録 ↑これを見ながら、下の公式サイトでキーを取得できる。 フロントエンドの選択は何でもいいと思います。 結局みんな、laravel-echo pusher-jsを使ってますし・・・・。 vue.jsを選択しても、laravel-echo pusher-jsは問題なく使える。 だから、なんでもいいと思う。 pusherの公式サイト project start laravel new laravel-echo composer require laravel/ui php artisan ui bootstrap --auth Pusher Channels PHP SDKをインストールと環境構築 laravel8 composer require pusher/pusher-php-server 通信はもうこれで決まり。 npm install --save-dev laravel-echo pusher-js resources\js\bootstrap.js /** * コメントアウトを解除 */ import Echo from 'laravel-echo'; window.Pusher = require('pusher-js'); window.Echo = new Echo({ broadcaster: 'pusher', key: process.env.MIX_PUSHER_APP_KEY, cluster: process.env.MIX_PUSHER_APP_CLUSTER, forceTLS: true }); envのPUSHER_APP_●とBROADCAST_DRIVERに入力 envの編集(keyはenvに既に書かれてある) #(上の方に外れておいてある忘れずにlogからpusherに書き換える。) BROADCAST_DRIVER=pusher PUSHER_APP_ID=5565431 PUSHER_APP_KEY=395bdfe72a63c666dfaee96e PUSHER_APP_SECRET=defaeab0f150493166ed94 PUSHER_APP_CLUSTER=ap3 config/app.phpのコメントアウトを解除 BroadcastServiceProvider::classのコメントを解除する。 BroadcastServiceProviderは2個あるから注意、上は外れている。 config/app.phpの編集 //こっちは外れている Illuminate\Broadcasting\BroadcastServiceProvider::class, /* * Application Service Providers... */ App\Providers\BroadcastServiceProvider::class, // コメントを解除しました とりあえずイベントをつくる イベントを作成して、コントローラー等からイベントを発火させる。 イベントに登録されてあるチャンネルが 同じチャンネルのlaravel-echo-pusher-js を発火させる。 と今は理解している。 php artisan make:event helloWorld app\Events\helloWorld.php class helloWorld /*忘れずにimplements ShouldBroadcast忘れずに*/ implements ShouldBroadcast /*\(^o^)/忘れたら時間を無駄にするで\(^o^)/*/ { use Dispatchable, InteractsWithSockets, SerializesModels; public function __construct() { // } /** * *取り敢えずプライベートチャンネルから *パブリックチャンネルに切り替えて検証 */ public function broadcastOn() { // return new PrivateChannel('channel-name'); return new Channel('hello'); } } イベントを発火するページ(ルート)を作る。 routes\web.php Route::get('/', function () { //イベントを発火 broadcast(new \App\Events\helloWorld()); //welcomeページをリターン return view('welcome'); }); resources\views\welcome.blade.php                 の(/body)の上に追記 resources\views\welcome.blade.php {{-- welcom.blade.phpの</bod>タグの上に追記 --}} <script src="{{ asset('js/app.js') }}"></script> <script> window.Echo.channel('hello') .listen('helloWorld', (e) => { console.log(e) console.log('\(^o^)/'); console.log('(;O;)'); console.log('٩(♡ε♡ )۶'); console.log('(●`ε´●)'); }); </script> </body> ひとまず、検証する。 トップページにアクセス。 あれ・・・何も発火しないが、ネットワークを確認したら、websocketには繋がっていた。 npm run devの結果がうまく反映されていないのではないか。 version()を使うため、asset関数からmix関数へ変更する。 webpack.mix.js mix.js('resources/js/app.js', 'public/js') .sass('resources/sass/app.scss', 'public/css') .version() /*追加 */ .sourceMaps(); resources\views\welcome.blade.php //asset()からmix()へ変更 <script src="{{ mix('js/app.js') }}"></script> npm run dev さいど、検証してみる。 あっ・・・console.log()にチラッとでて遷移されて消える。 laravelのキャッシュ対策のためには、mix関数を優先して使うべき。 これはjsの読み込むタイミングが早いためだと思う。しっかりとイベントが発火して、jsを発火させている。のか、それとも、イベントとは関係なくjsが発火しているのか・・ 確認するため、 chormとedgeで立ち上げて、edgeでアクセスして、chormでjsが発火するか見てみる。 イベントがjsを発火させていた。ヤッターマン\(^o^)/ 今度は、ajaxでイベントを発火させてみる。 routes\web.php Route::get('/', function () { // broadcast(new \App\Events\helloWorld()); //welcomeページをリターン return view('welcome'); }); Route::get('/hello', function () { //ajaxでイベントを発火させる。 broadcast(new \App\Events\helloWorld()); }); resources\views\welcome.blade.php <script src="{{ asset('js/app.js') }}"></script> <script> //ajaxで'/hello'にget送信するだけ。 $.ajax({ type: 'get', datatype: 'json', url: '/laravel-echo1/public/hello', // パス timeout: 3000, }) window.Echo.channel('hello') .listen('helloWorld', (e) => { console.log(e) console.log('\(^o^)/'); console.log('(;O;)'); console.log('٩(♡ε♡ )۶'); console.log('(●`ε´●)'); }); </script> </body> 無事chormもedgeからも、jsが発火している。 broadcastWith()を使用してみる。 broadcastWith()のlaravelの公式サイトの説明 イベントのペイロードとしてpublicプロパティはすべて自動的にシリアライズされます。これによりJavaScriptアプリケーションより、publicデータにアクセスできます。ですから、たとえば、あるイベントにEloquentモデルを含む、publicの$userプロパティがあれば、そのイベントのブロードキャストペイロードは、次のようになります。 なにコレ { "user": { "id": 1, "name": "Patrick Stewart" ... } } しかしながら、ブロードキャストペイロードをより上手くコントロールしたければ、そのイベントへbroadcastWithメソッドを追加してください。このメソッドから、イベントペイロードとしてブロードキャストしたいデータの配列を返してください。 意味不明。 そもそも論、ペイロードってなにやねん。 それをコントロールするってどういうこと。 けど、使えば何となく分かるはず。 取り敢えず、使えるのか確認する。 そういえば、 console.log(e)がカラの配列を返していた。 公式サイトのプロジェクトページ なんのための、eやねん。 取り敢えず、broadcastWith()を使ってみる。 helloWorldイベントにbroadcastWith()を追加 app\Events\helloWorld.php public function broadcastOn() { // return new PrivateChannel('channel-name'); return new Channel('hello'); } /** *追加 */ public function broadcastWith() { return [ 'message' => 'hello world 2', ]; } ●eにdataをペイロードしてしまいましたか?。そういう意味か?。 ●ちょっと違うよな、コントロールするってどういう意味やねんってなる。 次に、あるイベントにEloquentモデルを含む、publicの$userプロパティがあれば、 そのイベントのブロードキャストペイロードは、次のようになります。 とあるので、helloWorldイベントにUserモデルのプロパティを作成する。 データーベースを作成せなあかんめんどくさい。 env DB_CONNECTION=sqlite # DB_HOST=127.0.0.1 # DB_PORT=3306 # DB_DATABASE=laravel_echo2 # DB_USERNAME=root # DB_PASSWORD= win type nul > database\database.sqlite mac touch database/database.sqlite php artisan migrate database\seeders\DatabaseSeeder.php //コメントアウトを解除 \App\Models\User::factory(10)->create(); php artisan db:seed app\Events\helloWorld2.php // + use App\Models\User; class helloWorld2 ------- // + public $users; public function __construct() { // + $this->users = User::all(); } //- /** *動作確認のためコメントアウト */ // public function broadcastWith() // { // return [ // 'message' => 'hello world 2', // ]; // } あっなんか、デフォルトだと、イベントのプロパティのデータを全部返すみたい。 じゃないわ、ペイロードするみたい。これを、細かくコントロールするのが、 broadcastWith()メソッド。 app\Events\helloWorld2.php /** *先程のコメントアウトを解除 */ public function broadcastWith() { return [ 'message' => 'hello world 3', ]; } はい、無事ペイロードのコントロールを確認できました。 \(^o^)/ヤッターマン これは、メッセージを送って相手側でリアルタイム発火することができるんじゃねーか。 検証してみる。 まずはイベントの作成 これがなくては、始まらない。 php artisan make:event MessageSend 作ったら、なにがなんでもこれが最初やろ app\Events\MessageSend.php ------------------------------ class MessageSend implements ShouldBroadcast // + { ------------------------------ } app\Events\MessageSend.php <?php namespace App\Events; use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\PresenceChannel; use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; class MessageSend // + implements ShouldBroadcast { // + public $message; use Dispatchable, InteractsWithSockets, SerializesModels; public function __construct($message) { // + $this->message = $message; } public function broadcastOn() { // + return new Channel('message'); // return new PrivateChannel('channel-name'); } } メッセージを送信するフォームがいるわ。 受け取るのは取り敢えずconsoleでええやろう。 resources\views\layouts\app.blade.php <!-- Scripts これはコメントアウト--> <!-- <script src="{{ asset('js/app.js') }}" defer></script> --> <!-- Styles mixに変更--> <link href="{{ mix('css/app.css') }}" rel="stylesheet"> ---------------------------- <!--+--> <script src="{{ mix('js/app.js') }}"></script> @stack('js') <!--+--> </body> </html> ルートの作成 routes\web.php Route::get('/message_send', [App\Http\Controllers\HomeController::class, 'message_send'])->name('message_send'); Route::post('/message_send', [App\Http\Controllers\HomeController::class, 'message_send_post'])->name('message_send_post'); サーバーの作成(HomeControllerを使い回す) app\Http\Controllers\HomeController.php public function message_send() { return view('message_send'); } public function message_send_post(Request $request) { // 引数を入れて、イベントを発火させる。 broadcast(new \App\Events\MessageSend($request->message)); } フロントの作成(message_send.blade.phpを作成) resources\views\message_send.blade.php @extends('layouts.app') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="col-md-8"> <div class="card"> <div class="card-header">{{ __('Dashboard') }}</div> <div class="card-body"> {{-- @csrf --}} <div class="form-group row"> <label for="message" class="col-md-4 col-form-label text-md-right">メッセージ</label> <div class="col-md-6"> <textarea id="message" class="form-control @error('message') is-invalid @enderror" name="message" value="{{ old('message') }}" required autocomplete="message" autofocus></textarea> @error('message') <span class="invalid-feedback" role="alert"> <strong>{{ $message }}</strong> </span> @enderror </div> </div> <div class="form-group row mb-0"> <div class="col-md-6 offset-md-4 text-right"> <button id="messageBtn" class="btn btn-primary"> 送信する </button> </div> </div> </form> </div> </div> </div> </div> </div> @endsection @push('js') <script> $.ajaxSetup({ timeout: 3000, headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') } }); $(document).on('click', '#messageBtn', function() { var message = $('#message').val(); $.ajax({ type: 'post', datatype: 'json', url: 'message_send', timeout: 3000, data: { message: message } }) .done(function(data, textStatus, jqXHR) { $('#message').val(''); }); }); window.Echo.channel('message') .listen('MessageSend', (e) => { console.log(e) console.log(e.message) }); </script> @endpush エッジから送信してみる。 無事、chormで通信確認。 スマホからも通信してみる。 通信できてるな。\(^o^)/ヤッターマン うーん・・・・。 通信といえば、通信しているが・・・。 やったことは、イベントのプロパティにデータを持たせただけ、 感覚的には、それを表示しているに過ぎない・・・・。 これでいいのか。 なんか、適当なアプリを作って、確認したい。 九保すこひ様のリアルタイムでオンライン通知する機能に挑戦してみる 九保すこひ様のリアルタイム・チャットをつくる ↑データーベースとの連携もあって本当にちょうどよい。 本当に九保すこひ様いつもありがとうございます。 九保やすこひ様のサイトは他のどのサイトよりも安心して挑戦できる。 その他にも、laravelのチートシートが沢山あって、公式サイトなんていらないぐらいだ。 form等は先程のformを使い回すとして、保存するテーブルがいる。 モデルとテーブルを作成する。 php artisan make:model Message -a app\Models\Message.php protected $fillable = [ 'message', ]; database\migrations\xxx_create_messages_table.php public function up() { Schema::create('messages', function (Blueprint $table) { $table->id(); $table->string('message'); $table->timestamps(); }); } php artisan migrate ルートを作成する routes\web.php これは、先程のルートを使い回す。 コントローラーを作成する これも先程のメソッドを使い回して編集する。 app\Http\Controllers\HomeController.php public function message_send(Request $request) { if($request->ajax()){ $messages = Message::get(); return $messages; } return view('message_send'); } public function message_send_post(Request $request) { $message = Message::create([ 'message' => $request->message ]); // 引数を入れて、イベントを発火させる。 // broadcast(new \App\Events\MessageSend($request->message)); event(new MessageCreated($message)); } そして、イベントの作成をする。 php artisan make:event MessageCreated 作成したら、すぐにコレimplements ShouldBroadcast app\Events\MessageCreated.php class MessageCreated // + implements ShouldBroadcast { もう、ここは、チャンネル名を変えてコピペ。 うん、イベントのプロパティにデータを渡してるだけ。 app\Events\MessageCreated.php <?php namespace App\Events; use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\PresenceChannel; use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; class MessageCreated // + implements ShouldBroadcast { use Dispatchable, InteractsWithSockets, SerializesModels; public $message; public function __construct($message) { $this->message = $message; } public function broadcastOn() { return new Channel('kuho_chan_nel'); } } resources\views\message_send.blade.phpを書き換える。 resources\views\message_send.blade.php @extends('layouts.app') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="col-md-8"> <div class="card"> <div class="card-header">{{ __('Dashboard') }}</div> <div class="card-body"> {{-- @csrf --}} <div class="form-group row"> <label for="message" class="col-md-4 col-form-label text-md-right">メッセージ</label> <div class="col-md-6"> <textarea id="message" class="form-control @error('message') is-invalid @enderror" name="message" value="{{ old('message') }}" required autocomplete="message" autofocus></textarea> @error('message') <span class="invalid-feedback" role="alert"> <strong>{{ $message }}</strong> </span> @enderror </div> </div> <div class="form-group row mb-0"> <div class="col-md-6 offset-md-4 text-right"> <button id="messageBtn" class="btn btn-primary"> 送信する </button> </div> </div> </form> </div> </div>{{-- <div class="card"> --}} {{-- 追加 --}} <div class="card mt-5"> <div class="card-header">メッセージ一覧</div> <div class="card-body"> <ul id="message_body"> </ul> </div> </div> {{-- 追加 --}} </div> </div> </div> @endsection @push('js') <script> $.ajaxSetup({ timeout: 3000, headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content'), } }); $(document).on('click', '#messageBtn', function() { var message = $('#message').val(); $.ajax({ type: 'post', datatype: 'json', url: 'message_send', timeout: 3000, data: { message: message } }) .done(function(data, textStatus, jqXHR) { $('#message').val(''); }); }); window.Echo.channel('message') .listen('MessageSend', (e) => { console.log(e.message) }); //追加 getMessage(); window.Echo.channel('kuho_chan_nel') .listen('MessageCreated', (e) => { // this.getMessages(); // 全メッセージを再読込 getMessage(); }); function getMessage() { $.ajax({ type: 'get', datatype: 'json', url: 'message_send', timeout: 3000, }) .done(function(data, textStatus, jqXHR) { $result = $("#message_body"), li = []; $.each(data, function(index, value) { li.push(`<li>id:${value.id} 内容:${value.message}</li>`); }) $result[0].innerHTML = li.join(""); }); } </script> @endpush 無事、通信できている。\(^o^)/ヤッターマン やったことは、イベントのプロパティにデータをもたせただけ。 なんて、簡単なんや。 認証中ユーザーの回避 broadcast関数には->toOthers()メソッドってがあって、 ブロードキャストの受取人から現在のユーザーを除外できる らしい。 取り敢えず、できるか試してみる。 今の状態は エッジから送ってみると、送ったがわにも、ブロードキャストされているのがわかる。 ->toOthers()を追記して検証してみる。 app\Http\Controllers\HomeController.php public function message_send_post(Request $request) { $message = Message::create([ 'message' => $request->message ]); // 引数を入れて、イベントを発火させる。 //->toOthers()メソッドを追加 broadcast(new \App\Events\MessageSend($request->message))->toOthers(); //こっちはコメントアウト // event(new MessageCreated($message)); } resources\views\message_send.blade.php //コメントアウト //追加 // getMessage(); では、試してみる。edgeから送信 受け取ったchormではjsが発火したが、送ったedgeではjsは発火しなかった。 今度は、chormから送信 今度は、逆の関係になった。 これは、\(^o^)/ヤッターマンではないか? と思ったら。 設定のところに VueとAxiosを使用しない場合、JavaScriptアプリケーションでX-Socket-IDヘッダを送信するように、設定する必要があります。 とある。 (゚Д゚)ハァ? 僕が、やったのは、$ajax送信 その上に 。VueとAxiosを使用していれば、X-Socket-IDヘッダとして、送信する全リクエストへ自動的に付加されます。そのため、toOthersメソッドを呼び出す場合、LaravelはヘッダからソケットIDを取り除き、そのソケットIDを使い全接続へブロードキャストしないように、ブロードキャスタに対し指示します。 とある。 これは、どういうこと? 今の所、送信時のheadersはX-CSRF-TOKENだけや。 resources\views\message_send.blade.php $.ajaxSetup({ timeout: 3000, headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content'), } }); 公式サイトの説明では、headersにX-Socket-IDを設定しなければ、 vue axios通信以外では->toOthers()は使えんよとある。 要するに、↓のような設定をする必要があるはずや。 resources\views\message_send.blade.php var socket_id = Echo.socketId(); $.ajaxSetup({ timeout: 3000, headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content'), 'X-Socket-ID': socket_id } }); ↑こんな設定はしておらん。 どういうことや。 いっかいこれでやってみる。 逆もOKや、動作公式サイトどうりに、機能している。 いや違うねん。 俺は見せたかったんや。 'X-Socket-ID': socket_id がある場合とない場合で、 うーん普通にformで送信してみる。 あかん、遷移してまう、めんどくせぇ ajax送信におけるheadersの中身を確認してみる。 普通こっちだわな。 普通に、自動で追記してくれてる・・・・。 結論、ajax送信のときも X-Socket-IDをheaderに自動で追記してくれる。 (laravel8時) あっおれ 5.6の公式サイト 見てる・・・・。 改めてlaravel8を見てみる怖い・・・ グローバルAxiosインスタンスを使用してJavaScriptアプリケーションからHTTPリクエストを作成している場合、ソケットIDはすべての送信リクエストにX-Socket-IDヘッダとして自動的に添付されます。 なに? グローバルAxiosインスタンスを使用して なに?コレ? グローバルAxiosインスタンス?? そういえば、resources\js\bootstrap.jsにそれっぽいのがある。 resources\js\bootstrap.js window.axios = require('axios'); いったん、これを外してみた。 npm run dev あ、あかん。 エラーになって最初から動かん。 ということです。 イベントの方でも->toOthers()メソッドを指定できる。 $this->dontBroadcastToCurrentUser() 試してみる。toOthers()をコメントアウトして、 app\Http\Controllers\HomeController.php broadcast(new \App\Events\MessageSend($request->message))/*->toOthers()*/; イベントに$this->dontBroadcastToCurrentUser();を追記 app\Events\MessageSend.php public function __construct($message) { // + $this->dontBroadcastToCurrentUser(); $this->message = $message; } 期待通り ここで、\(^o^)/ヤッターマン コネクションのカスタマイズ (ここ読む必要ない) 複数のブロードキャスト接続とやりとりしており viaメソッドを使ってどの接続にイベントをプッシュするか指定できます 例として、 broadcast(new OrderShipmentStatusUpdated($update))->via('pusher'); とある。 なんか使い方が、想定と違う。 イベントに複数チャンネルがあって、それを選択できるのかと思ったが、違う。 うーん、 とりあえず、もう一個チャンネルを公式サイトでつくってみる。 あかんつまった。 アプリケーションが複数のブロードキャスト接続とやりとりしており、デフォルト以外のブロードキャスタを使いイベントをブロードキャストしたい場合は、viaメソッドを使ってどの接続にイベントをプッシュするか指定できます。 デフォルトのチャンネルがcomposed-flower-493で、other_messageがデフォルト以外のブロードキャスタと仮定して、viaメソッドを使って切り替えるのかと思ったけど。 そもそも論、デフォルト以外のブロードキャスタをどうやって、登録するのか? サンプルもないし。 ちょっと置いとく、 (ここまで) プライベートチャンネル 概要 プライベートチャンネルをサブスクライブするには、ユーザーがそのチャンネルでリッスンする認証と認可を持っている必要があります。 (´Д`)ハァ…相変わらず意味不明 なんや、認証と認可ってまぁ、 認証と認可が2ついるんってことだけわかったわ。 まぁ、いつものごとく試してみたらわかるんや。 まずは、プライベートイベントの作成やろ・・・と思ったけど、 ここは、サーバー側から把握するよりもフロント側から把握した方が、 理解が早いんとちゃうか。 取り敢えず、先程のMessageSendイベントのchannelをprivateに書き換えてみるか。 resources\views\message_send.blade.php // window.Echo.channel('message') // .listen('MessageSend', (e) => { // console.log(e.message) // }); //channelからprivateに変更しただけ。 window.Echo.private('message') .listen('MessageSend', (e) => { console.log(e.message) }); ん?なんや、これで、アクセスすると、勝手にauthがないとかいってる。 イベントは何も発火させてへんのに、勝手にや、アクセス時や。 これが、認証って意味か? とりあえず、404って言うことはルートがないってことや、これは、xamppから アクセスしてるかやろ。 取り敢えず、ルートを修正してやるか。 承認エンドポイントのカスタマイズってのができるらしい。 公式サイトのコードauthEndpoint: '/custom/endpoint/auth'                   ↑何これ。 resources\js\bootstrap.js window.Echo = new Echo( { broadcaster: 'pusher', key: process.env.MIX_PUSHER_APP_KEY, cluster: process.env.MIX_PUSHER_APP_CLUSTER, forceTLS: true, //+ authEndpoint: '/laravel-echo2/public/broadcasting/auth' }, ); npm run dev 再度、アクセスすると,403になった。 「403 Forbidden」は「認可失敗」に対するコードだが・・・。 ログイン認証に失敗したときでも返ってくることもある。 401(認証)と403(認可)を区別していないとも思えんし・・ routes\channels.php 403は401(認証)の勘違いだと判断して、 認証するにはサーバーサイドの処理も必要になる。 これは、公式サイトによれば、routes\channels.phpでやるみたい。 routes\channels.php Broadcast::channel('message.*'/* *なんでもOK */,function(){ return true; }); フロント側でもサーバー側に認可コードを送ってやる必要がある。 resources\views\message_send.blade.php window.Echo.private("message.1"/*認可コード*/) .listen('MessageSend', (e) => { console.log(e) }); 再度アクセスすると、エラーは表示されず、ソースを確認すると200になっている。 認証が成功しているのが確認できる。 でも、これでメッセージをポストしても動作しない。 200okが帰ってきてるのに。なんでや。 あっ認可か、認可がないからか。 Privateの認可判断するためには、PrivateChannel()を使用する必要がある。 よって、Channel()からPrivateChannel()に変更する。 app\Events\MessageSend.php public function broadcastOn() { // return new Channel('message'); //PrivateChannelに変更して、認可番号をセットする。 return new PrivateChannel('message.1'/*認可コード*/); /* (resources\views\message_send.blade.php) * //認可コードはアクセス時,サーバー側に届け出が行われている。 * //PrivateChannelメソッドは引数と同じ認可コードを持っている * // ユーザーだけにイベントの発火を許可する。 * window.Echo.private("message.1"/*認可コードは最初に届け出る*/) * // .listen('PrivateMessage', (e) => { * .listen('MessageSend', (e) => { * console.log(e) * }); */ } 再度メッセージを送ってやる。無事発火。 このコードでは、すべてのユーザーを認証しているため、 今度は、ログインユーザーを認証するよう変更する。 とりあえず、ログインしているユーザーだけにメッセージを送ってやる。 サーバー側は routes\channels.php 公式サイトでは$userになっているが、わかりにくいので$authに変更した Broadcast::channel('message.{userId}'/* (*)でもOK */,function($auth /*$userから変更*/,$userId/*ここでは不要*/){ /*ログイン認証してなければnull値となりfalseになる*/ return $auth; // return (int) $user->id === (int) $userId; }); app\Events\MessageSend.php public function broadcastOn() { // return new Channel('message'); //('チャンネル.引数') 引数は絶対にいる{id}とか*ではあかん' //プライベートチャンネルでは認証と認可が必ず必要なため。 return new PrivateChannel('message.fugafuga'); } resources\views\message_send.blade.php //"message.fugafuga"これは、認可と同じでないとだめ。 window.Echo.private("message.fugafuga") .listen('MessageSend', (e) => { console.log(e) }); 認証時(アクセス時)、チャンネルの引数が認可コードとして届けられている事がわかる。 画像をとるのを忘れたが、しっかりと、送信できた。 今度は特定のユーザーにだけイベントが発火するようにしたい。 app\Events\MessageSend.php public function broadcastOn() { //本来ならメッセージを送るときにこの番号も一緒に送ってやる $userId = 12; // return new Channel('message'); return new PrivateChannel('message.'.$userId); } resources\views\message_send.blade.php //本来的にはこれはやめたほうがいいらしい。 window.Echo.private("message.{{ Auth::id() }}") .listen('MessageSend', (e) => { console.log(e) }); //色とりどりな書き方をみる。 //https://www.messiahworks.com/archives/19044 //ただ,この書き方が一般的かな。 <script> window.Laravel = {!! json_encode([ 'user_id' => auth()->check() ? auth()->user()->id : null, 'team_id' => auth()->check() ? auth()->user()->team_id : null, ]) !!}; </script> window.Echo.private('kuho_chan_nel.' + window.Laravel.user_id) //他にも //https://github.com/alexeymezenin/laravel-best-practices/blob/master/japanese.md //<input id="article" type="hidden" value='@json($article)'> //let article = $('#article').val(); routes\channels.php //ここのコードが走るのはアクセス時のみ、イベント発火時とうは、関係ない。 Broadcast::channel('message.*',function($user){ return $user; //$idがね相手に対して入力をもとめるpasswordとかやったらね。この処理はね理解できるねん。 //ログインしているかしていないかの処理でこれは違うと思う。今のところ。 //adminユーザーの場合とかguardsの種類もここで、判定できる。 // return (int) $user->id === (int) $id; }); これで、ログインIDが12番のユーザーだけのjsが発火する。 認証と認可を分けたことは、本当によく考えられている。 頭のええ人は違う。 うーんしかし、 認証と認可、わかったようなわからないような。 今回以外だったことは、 認証は認可と常にセットであるといえるが、 認可だけでいいなら、認証は必要がなかったことである。 パブリックチャンネルで特定のユーザにだけ、jsを発火したい場合。 resources\views\message_send.blade.php //privateからchannelに書き換える。認可番号はいる。 window.Echo.channel("message.{{ Auth::id() }}") .listen('MessageSend', (e) => { console.log(e.message) }); app\Events\MessageSend.php //privateからchannelに書き換える。 public function broadcastOn() { $userId = 12; return new Channel('message.'.$userId); // return new PrivateChannel('message.'.$userId); } routes\channels.php 必要ないが、とりあえず、コメントアウト // Broadcast::channel('message.*',function($user){ // return $user; // // return (int) $user->id === (int) $id; // }); 認証のいらないパブリックチャンネルでも認可による制限が機能している。 なにも認証はbroadcastでやる必要はないような気がしてきた・・・。 なによりも、認証と認可を区別してるくせに、認可コードは認証とセットしてるし、 複数の認可コードを設定することもできないので、複数の認可コードをセットしたい時は下のように2つ書く必要がある。 よって、2回認証作業が走る。大した事ではないが・・・。不満だ。 resources\views\message_send.blade.php pusher = window.Echo.private("message.1").listen('MessageSend', (e) => { console.log(e) }); pusher = window.Echo.private("message.3").listen('MessageSend', (e) => { console.log(e) }); ヤッターマンでいいと思う。 認証と認可の区別はできてるし、・・・。 まぁ、動的な認可コードに対応してみる。 ユーザーにチームIDをつける。 同じ、チームIDをもった特定のユーザーに送信してみる。 database\migrations\xxx_create_users_table.php Schema::create('users', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); //+ $table->integer('team_id')->nullable(); $table->rememberToken(); $table->timestamps(); }); app\Http\Controllers\Auth\RegisterController.php protected function create(array $data) { return User::create([ 'name' => $data['name'], 'email' => $data['email'], 'password' => Hash::make($data['password']), //+ 'team_id' => rand(1,3), ]); } database\factories\UserFactory.php public function definition() { return [ 'name' => $this->faker->name(), 'email' => $this->faker->unique()->safeEmail(), 'email_verified_at' => now(), //'password'から'Password'に変更 'password' => '$2y$10$L1XJ1Gqg.PKVdJtfAG7jVuR/vRjmXvFuV4z9e2KJcx5/9NdaP2W0.', 'remember_token' => Str::random(10), //+ 'team_id'=>rand(1,3) ]; } php artisan migrate:refresh php artisan db:seed php artisan tinker User::find(1) => App\Models\User {#4310 id: "1", name: "Garland Carroll I", email: "sample1@test.com", email_verified_at: "2021-09-13 08:28:44", #password: "$2y$10$L1XJ1Gqg.PKVdJtfAG7jVuR/vRjmXvFuV4z9e2KJcx5/9NdaP2W0.", team_id: "1", #remember_token: "uhtKOUMWyu", created_at: "2021-09-13 08:28:44", updated_at: "2021-09-13 08:28:44", } ログインメールは: sample1@test.com password: Password resources\views\message_send.blade.php @extends('layouts.app') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="col-md-8"> <div class="card"> <div class="card-header">{{ __('Dashboard') }}</div> <div class="card-body"> <div class="form-group row"> <label for="message" class="col-md-4 col-form-label text-md-right">送信先ユーザー番号</label> <div class="col-md-6"> <input id="userId" class="form-control @error('user_id') is-invalid @enderror" name="user_id" value="{{ old('user_id') }}"> @error('user_id') <span class="invalid-feedback" role="alert"> <strong>{{ $message }}</strong> </span> @enderror </div> </div> <div class="form-group row"> <label for="message" class="col-md-4 col-form-label text-md-right">メッセージ</label> <div class="col-md-6"> <textarea id="message" class="form-control @error('message') is-invalid @enderror" name="message" value="{{ old('message') }}" required autocomplete="message" autofocus></textarea> @error('message') <span class="invalid-feedback" role="alert"> <strong>{{ $message }}</strong> </span> @enderror </div> </div> <div class="form-group row mb-0"> <div class="col-md-6 offset-md-4 text-right"> <button id="messageBtn" class="btn btn-primary"> 送信する </button> </div> </div> </div> </div>{{-- <div class="card"> --}} {{-- 追加 --}} <div class="card mt-5"> <div class="card-header">メッセージ一覧</div> <div class="card-body"> <ul id="message_body"> </ul> </div> </div> {{-- 追加 --}} </div> </div> </div> @endsection @push('js') <script> window.Laravel = {!! json_encode([ 'user_id' => auth()->check() ? auth()->user()->id : null, 'team_id' => auth()->check() ? auth()->user()->team_id : null, ]) !!}; </script> <script> console.log(window.Laravel); // var socket_id = Echo.socketId(); $.ajaxSetup({ timeout: 3000, headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content'), // 'X-Socket-ID': socket_id, } }); // window.Echo.private("message.{{ Auth::user()->team_id }}") window.Echo.private("message.{{ Auth::user()->team_id }}.{{ Auth::id() }}") .listen('MessageSend', (e) => { console.log(e) }); // var CSRF_TOKEN = $('meta[name="csrf-token"]').attr('content'); $(document).on('click', '#messageBtn', function() { // window.Echo.private("message.1"); // window.Echo.private("message.{{ Auth::id() }}.{{ Auth::user()->team_id }}") var message = $('#message').val(); var userId = $('#userId').val(); $.ajax({ type: 'post', datatype: 'json', url: 'message_send', timeout: 3000, data: { message: message, teamId: {{ Auth::user()->team_id }}, userId: userId, // _token: CSRF_TOKEN, } }) .done(function(data, textStatus, jqXHR) { $('#message').val(''); }); }); // window.Echo.private("message.{{ Auth::id() }}.{{ Auth::user()->team_id }}") // window.Echo.private('kuho_chan_nel.' + window.Laravel.user) // window.Echo.private("chat.{{ Auth::id() }}") //ok // window.Echo.private("message2")/*----1*/ // window.Echo.private("message.{{ Auth::id() }}.{{ Auth::user()->team_id }}") // window.Echo.channel("message.{{ Auth::id() }}") // .listen('MessageSend', (e) => { // console.log(e.message) // }); // //追加 // // getMessage(); // window.Echo.channel('kuho_chan_nel') // .listen('MessageCreated', (e) => { // // this.getMessages(); // 全メッセージを再読込 // getMessage(); // }); function getMessage() { $.ajax({ type: 'get', datatype: 'json', url: 'message_send', timeout: 3000, }) .done(function(data, textStatus, jqXHR) { $result = $("#message_body"), li = []; $.each(data, function(index, value) { li.push(`<li>id:${value.id} 内容:${value.message}</li>`); }) $result[0].innerHTML = li.join(""); }); } </script> @endpush routes\channels.php Broadcast::channel('message.{id}',function($user,$id){ return $user; // return (int) $user->id === (int) $id; }); app\Events\MessageSend.php <?php namespace App\Events; use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\PresenceChannel; use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; class MessageSend // + implements ShouldBroadcast { public $message; public $team_id; protected $user_id; use Dispatchable, InteractsWithSockets, SerializesModels; public function __construct($request) { // + // $this->dontBroadcastToCurrentUser(); $this->message = $request["message"]; $this->team_id = $request["teamId"]; $this->user_id = $request["userId"]; } public function broadcastOn() { return new PrivateChannel('message.' . $this->team_id . ".{$this->user_id}"); } } app\Http\Controllers\HomeController.php <?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Models\Message; use App\Events\MessageCreated; class HomeController extends Controller { /** * Create a new controller instance. * * @return void */ public function __construct() { // $this->middleware('auth'); } /** * Show the application dashboard. * * @return \Illuminate\Contracts\Support\Renderable */ public function index() { return view('home'); } public function message_send(Request $request) { if($request->ajax()){ $messages = Message::get(); return $messages; } return view('message_send'); } public function message_send_post(Request $request) { // $message = Message::create([ // 'message' => $request->message // ]); // プリベートチャンネル // broadcast(new \App\Events\PrivateMessage($request->message))/*->toOthers()*/; //チャンネルイベント broadcast(new \App\Events\MessageSend($request->all()))->toOthers()/**/; // event(new MessageCreated($message)); } } ここで、なにかアプリを作りたい。 めちゃくちゃおもしろいサイトを発見してもうた。 こんなん作れたらワクワクが止まらんよ。 これは、挑戦せなアカン。 キャスレーコンサルティング株式会社の松本様のアプリに挑戦してみる。 あっ、テーブル名が被っとるので、 chatsテーブルに変更する。ついでにモデルとかも作っとく php artisan make:model Chat -a
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】検索機能を実装する

作成物 Book Listの一覧から、著書名もしくは著者名が部分一致するレコードを検索する機能を実装します。 前提 "title" "author" "body" の要素を持つ、Postsテーブル(BoolListの投稿テーブル)を作成済み。 SeederでPostsテーブルへサンプルデータを保存済み。 今回実装する機能の概要 データを全件表示する一覧画面と、検索結果の一覧表示画面は、同じviewファイル・Controllerのメソッドを使います。 検索boxに何か入力された場合は、Controllerのメソッド内のif文を通ってデータを絞り込み、絞り込んだデータのみを一覧画面のviewに返します。検索boxに何も入力されていない場合は、if文を通らず、データ全件をviewへ返します。 migrationファイルの確認 作成しているmigrationファイルは、以下の通りです。 2021_08_28_064931_create_posts_table.php /** 略 **/ class CreatePostsTable extends Migration { public function up() { Schema::create('posts', function (Blueprint $table) { $table->id(); $table->string('title'); $table->string('author'); $table->text('body'); $table->timestamps(); }); } /** 略 **/ Viewの作成 index.blade.php //* 検索機能ここから *// <div> <form action="{{ route('posts.index') }}" method="GET"> <input type="text" name="keyword" value="{{ $keyword }}"> <input type="submit" value="検索"> </form> </div> //*検索機能ここまで*// <h1> <span>My Book List</span> <a href="{{ route('posts.create') }}">[Add]</a> </h1> <table> <tr> <th>著書名</th><th>著者名</th> </tr> //* 保存されているレコードを一覧表示 *// @forelse ($posts as $post) <tr> <td><a href="{{ route('posts.show' , $post) }}">{{ $post->title }}</td></a> <td>{{ $post->author }}</td> </tr> @empty <td>No posts!!</td> @endforelse </table> ルート定義 検索ボタンを押した際のルーティングも、一覧画面の表示と同じPostControllerのindexメソッドに、getメソッドでアクセスします。 web.php Route::get('/', [PostController::class, 'index']) ->name('posts.index'); コントローラーの編集 コントローラーを下記のように編集します。 PostController.php 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('index', compact('posts', 'keyword')); } コントローラーの解説 検索boxに入力された値を受け取り、$keywordに格納します。 $keywordで何かしらの値を受け取った場合は、if文の中で取得するデータを絞りこみます。 whereメソッドでLIKE検索を指定し、$keywordの両側に%をつけることで、部分一致検索を行います。またorWhereメソッドでOR検索を行います。 viewファイルindex.blade.phpへ、posts(一覧表示するデータ)とkeyword(検索ボックスのvalue値)を受け渡します。 以上です^^
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】検索機能を実装する ~基本!キーワード部分一致検索~

作成物 Book Listの一覧から、著書名もしくは著者名が部分一致するレコードを検索する機能を実装します。 前提 "title" "author" "body" の要素を持つ、Postsテーブル(BoolListの投稿テーブル)を作成済み。 SeederでPostsテーブルへサンプルデータを保存済み。 今回実装する機能の概要 データを全件表示する一覧画面と、検索結果の一覧表示画面は、同じviewファイル・Controllerのメソッドを使います。 検索boxに何か入力された場合は、Controllerのメソッド内のif文を通ってデータを絞り込み、絞り込んだデータのみを一覧画面のviewに返します。検索boxに何も入力されていない場合は、if文を通らず、データ全件をviewへ返します。 migrationファイルの確認 作成しているmigrationファイルは、以下の通りです。 2021_08_28_064931_create_posts_table.php /** 略 **/ class CreatePostsTable extends Migration { public function up() { Schema::create('posts', function (Blueprint $table) { $table->id(); $table->string('title'); $table->string('author'); $table->text('body'); $table->timestamps(); }); } /** 略 **/ Viewの作成 index.blade.php //* 検索機能ここから *// <div> <form action="{{ route('posts.index') }}" method="GET"> <input type="text" name="keyword" value="{{ $keyword }}"> <input type="submit" value="検索"> </form> </div> //*検索機能ここまで*// <h1> <span>My Book List</span> <a href="{{ route('posts.create') }}">[Add]</a> </h1> <table> <tr> <th>著書名</th><th>著者名</th> </tr> //* 保存されているレコードを一覧表示 *// @forelse ($posts as $post) <tr> <td><a href="{{ route('posts.show' , $post) }}">{{ $post->title }}</td></a> <td>{{ $post->author }}</td> </tr> @empty <td>No posts!!</td> @endforelse </table> ルート定義 検索ボタンを押した際のルーティングも、一覧画面の表示と同じPostControllerのindexメソッドに、getメソッドでアクセスします。 web.php Route::get('/', [PostController::class, 'index']) ->name('posts.index'); コントローラーの編集 コントローラーを下記のように編集します。 PostController.php 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('index', compact('posts', 'keyword')); } コントローラーの解説 検索boxに入力された値を受け取り、$keywordに格納します。 $keywordで何かしらの値を受け取った場合は、if文の中で取得するデータを絞りこみます。 whereメソッドでLIKE検索を指定し、$keywordの両側に%をつけることで、部分一致検索を行います。またorWhereメソッドでOR検索を行います。 viewファイルindex.blade.phpへ、posts(一覧表示するデータ)とkeyword(検索ボックスのvalue値)を受け渡します。 以上です^^
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

The PutObject operation requires non-empty parameterのエラー対応について

下記の記事を参考にs3へ画像を保存する途中でThe PutObject operation requires non-empty parameterのエラーが出たのでその対処法です。 参考にした記事 確認手順 上記記事を参考にcomposerで必要なものを入れたりバケットを作ったり.envを書き換えます。 その後、 php artisan config:cache もしくは php artisan cache:clear を実行 config/filesystems.phpの設定確認 disk arrayのs3の部分を見る .envと比較して.envをきちんと反映しているか確認 実行してみて下記でデバック MainController.php use Config; 省略---- dd(Config::get('filesystems')); s3の部分が反映されていない場合にはconfig/filesystems.phpに直接記入してみて動けばcacheがうまく消えていない可能性が高いです。 感想 自分は古いcacheがうまく消えていなかったのが問題でした。 php artisan cache:clearだけではうまく消えないらしくphp artisan config:cacheを打つとうまく反映されました
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む