- 投稿日:2021-08-29T17:37:28+09:00
【実務で使えるLaravel】バリデーションを実装する方法
実務でよく使うLaravelのバリデーションの実装方法について紹介します。 初級編・応用篇・実務篇と分けて解説していきますね。 開発環境 Docker 20.10.7 PHP 7.4.22 Laravel 8.53.1 mySQL 5.7 データベースのツール phpmyadmin 想定するアプリ 今回想定するのは本を管理するアプリです。 本を登録する際にバリデーションをかけていきます。 登録画面はこんな感じになっています。 関連するソースコードは以下のようになっています。 モデルBook.php <?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Book extends Model { use HasFactory; // モデルに関連付けるテーブル protected $table = 'books'; // テーブルに関連付ける主キー protected $primaryKey = 'book_id'; // 登録・更新可能なカラムの指定 protected $fillable = [ 'book_id', 'user_id', 'category_id', 'book_name', 'created_at', 'updated_at' ]; /** * 登録処理 */ public function InsertBook($request) { // リクエストデータを基に管理マスターユーザーに登録する return $this->create([ 'book_name' => $request->book_name, ]); } } モデルCategory.php <?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Category extends Model { use HasFactory; // モデルに関連付けるテーブル protected $table = 'categories'; // テーブルに関連付ける主キー protected $primaryKey = 'category_id'; // 登録・更新可能なカラムの指定 protected $fillable = [ 'category_id', 'user_id', 'category_name', 'created_at', 'updated_at' ]; /** * カテゴリーテーブルの全件取得 */ public function findCategories() { return $this->all(); } } コントローラーBookController.php <?php namespace App\Http\Controllers; use App\Models\Book; use App\Models\Category; use Illuminate\Http\Request; class BookController extends Controller { public function __construct() { $this->book = new Book(); $this->category = new Category(); } /** * 登録画面 */ public function create(Request $request) { $categories = $this->category->findCategories(); return view('book.create', compact('categories')); } /** * 登録処理 */ public function store(Request $request) { // リクエストされたデータを元に登録処理を実行 $registerBook = $this->book->InsertBook($request); return redirect()->route('book.index'); } } 登録画面create.blade.php @extends('layouts.app') @section('content') <div class="container small"> <h1>本を登録</h1> <form action="{{ route('book.store') }}" method="POST"> @csrf <fieldset> <div class="form-group"> <label for="book_name">{{ __('本の名称') }}<span class="badge badge-danger ml-2">{{ __('必須') }}</span></label> <input type="text" class="form-control" name="book_name" id="book_name"> </div> <!-- カテゴリープルダウン --> <div class="form-group"> <label for="category-id">{{ __('カテゴリー') }}<span class="badge badge-danger ml-2">{{ __('必須') }}</span></label> <select class="form-control" id="category-id" name="category_id"> @foreach ($categories as $category) <option value="{{ $category->category_id }}">{{ $category->category_name }}</option> @endforeach </select> </div> <div class="d-flex justify-content-between pt-3"> <a href="{{ route('book.index') }}" class="btn btn-outline-secondary" role="button"> <i class="fa fa-reply mr-1" aria-hidden="true"></i>{{ __('一覧画面へ') }} </a> <button type="submit" class="btn btn-success"> {{ __('登録') }} </button> </div> </fieldset> </form> </div> @endsection 〜初級編〜コントローラーにベタ書き 最初のステップとして、コントローラーにバリデーションをベタ書きする方法です。 Laravelでバリデーションを実装する入り口としてよく紹介されます。 実装方法はこんな感じ。 /** * 登録処理 */ public function store(Request $request) { // 本の名称を入力必須とするバリデーション $validated = $request->validate([ 'book_name' => 'required' ]); // リクエストされたデータを元に登録処理を実行 $registerBook = $this->book->InsertBook($request); return redirect()->route('book.index'); } laravelに元々用意されているvalidate関数を使うことでバリデーションが実装できます。 このままだとバリデーションメッセージが英語だし、book_nameもバリデーション側で定義していないので定義します。 resoources>lang>validation.phpを開いてください。101行目付近 'required' => ':attributeは必須です', に変更してください。 そして、154行目付近 'attributes' => [ 'book_name' => '本の名称' ], としてください。 あとは画面にバリデーションメッセージを表示させます。 <div class="form-group"> <label for="book_name">{{ __('本の名称') }}<span class="badge badge-danger ml-2">{{ __('必須') }}</span></label> <input type="text" class="form-control {{ $errors->has('book_name') ? 'is-invalid' : '' }}" name="book_name" id="book_name"> @if ($errors->has('book_name')) <span class="invalid-feedback" role="alert"> {{ $errors->first('book_name') }} </span> @endif </div> 三項演算子です。ifの条件分岐を1行でかけます。 {{ $errors->has('book_name') ? 'is-invalid' : '' }} 上記は、以下のif文と同じです。 @if ($errors->has('book_name')) 'is-invalid' @else '' @endif $errors->first('book_name')とすることで、バリデーションに引っかかった最初のメッセージを取り出せます。 ちなみに「本の名称の長さを10文字以内にしたい」というバリデーションを追加でかけたい場合は、以下のようにできます。 /** * 登録処理 */ public function store(Request $request) { // 本の名称を入力必須・名称の長さ10文字以内とするバリデーション $validated = $request->validate([ 'book_name' => 'required|max:10' ]); // リクエストされたデータを元に登録処理を実行 $registerBook = $this->book->InsertBook($request); return redirect()->route('book.index'); } resources>lang>validation.php80行目付近 'max' => [ 'numeric' => 'The :attribute must not be greater than :max.', 'file' => 'The :attribute must not be greater than :max kilobytes.', 'string' => ':attributeは10文字以内で入力してください。', 'array' => 'The :attribute must not have more than :max items.', ], このようにコントローラーにベタ書きでバリデーションは実装できます。 ただ、実務では使えないでしょう。なぜならコントローラーの記述量が膨大になるので、バリデーションはバリデーションで他のファイルに切り分けるからです。 それでは、バリデーションを他のファイルで実装させる方法を紹介します。 〜応用篇〜バリデーションをコントローラーと切り分ける バリデーションをコントローラーと切り分けるには、フォームリクエストを作成します。 イメージとしては、 app>Http>Requestsにフォームリクエストを作成 (ここにバリデーションを書く) コントローラーでそのファイルを読み込む (登録処理の際にバリデーションファイルを読み込んでバリデーション判定させる) こんな感じです。 それでは実際に書いていきます。 フォームリクエストの作成 $ php artisan make:request BookRequestを実行 app>Http>Requests配下にBookRequest.phpが作成されます。 初期状態はこんな感じ。 <?php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class BookRequest extends FormRequest { /** * Determine if the user is authorized to make this request. * * @return bool */ public function authorize() { return false; } /** * Get the validation rules that apply to the request. * * @return array */ public function rules() { return [ // ]; } } 以下のように記述しましょう。 ※authorize()のfalseからtrueに変更することを忘れないでください。 <?php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class BookRequest extends FormRequest { /** * Determine if the user is authorized to make this request. * * @return bool */ public function authorize() { // falseからtrueに変更する return true; } /** * Get the validation rules that apply to the request. * * @return array */ public function rules() { // コントローラーに記述していたバリデーションを移行 return [ 'book_name' => 'required|max:10' ]; } } コントローラーを変更します。 変更点は3つ ・use App\Http\Requests\BookRequest;追加 ・store(BookRequest $request)に変更 ・バリデーションの処理を削除 <?php namespace App\Http\Controllers; use App\Models\Book; use App\Models\Category; use Illuminate\Http\Request; use App\Http\Requests\BookRequest; class BookController extends Controller { public function __construct() { $this->book = new Book(); $this->category = new Category(); } /** * 登録画面 */ public function create(Request $request) { $categories = $this->category->findCategories(); return view('book.create', compact('categories')); } /** * 登録処理 */ public function store(BookRequest $request) { // リクエストされたデータを元に登録処理を実行 $registerBook = $this->book->InsertBook($request); return redirect()->route('book.index'); } } これでよりスマートにバリデーションをかけました。 これでもいいのですが、実務ではバリデーションの内容もより複雑になります。 現在は、requiredやmaxなど基本的なものですが、自作でバリデーション作成したりするので、時にはバリデーション1つで5〜6行使ったり、if文で条件分岐させたり...さまざまです。 なので、私がよく実務で使っている方法を紹介します。 BookRequest.phpを改造します。 public function rules() { $validate = []; $validate += [ 'book_name' => [ 'required', 'max:10' ] ]; return $validate; } ここでやっていることは最終的に$validateという変数にバリデーションを配列で格納しています。 最初に$validateの空の配列を用意します。 バリデーションに引っ掛かれば、$validateにどんどん配列として格納されるってロジックです。 これであればバリデーションのロジックが長くなってもコードを追いやすくなってみやすいです。 public function rules() { $validate = []; $validate += [ 'book_name' => [ 'required', 'max:10' ] ]; $validate += [ 'category_name' => [ 'required', 'max:10' ] ]; return $validate; } みたいな感じです。 今回はここまで! 最後までありがとうございました〜
- 投稿日:2021-08-29T17:37:28+09:00
【実務で使えるLaravel】バリデーションを実装する方法3パターン
実務でよく使うLaravelのバリデーションの実装方法について紹介します。 初級編・応用篇・実務篇と分けて解説していきますね。 開発環境 Docker 20.10.7 PHP 7.4.22 Laravel 8.53.1 mySQL 5.7 データベースのツール phpmyadmin 想定するアプリ 今回想定するのは本を管理するアプリです。 本を登録する際にバリデーションをかけていきます。 登録画面はこんな感じになっています。 今回バリデーションをかけるのは本の名称だけにします。 関連するソースコードは以下のようになっています。 モデルBook.php <?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Book extends Model { use HasFactory; // モデルに関連付けるテーブル protected $table = 'books'; // テーブルに関連付ける主キー protected $primaryKey = 'book_id'; // 登録・更新可能なカラムの指定 protected $fillable = [ 'book_id', 'user_id', 'category_id', 'book_name', 'created_at', 'updated_at' ]; /** * 登録処理 */ public function InsertBook($request) { // リクエストデータを基に管理マスターユーザーに登録する return $this->create([ 'book_name' => $request->book_name, ]); } } モデルCategory.php <?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Category extends Model { use HasFactory; // モデルに関連付けるテーブル protected $table = 'categories'; // テーブルに関連付ける主キー protected $primaryKey = 'category_id'; // 登録・更新可能なカラムの指定 protected $fillable = [ 'category_id', 'user_id', 'category_name', 'created_at', 'updated_at' ]; /** * カテゴリーテーブルの全件取得 */ public function findCategories() { return $this->all(); } } コントローラーBookController.php <?php namespace App\Http\Controllers; use App\Models\Book; use App\Models\Category; use Illuminate\Http\Request; class BookController extends Controller { public function __construct() { $this->book = new Book(); $this->category = new Category(); } /** * 登録画面 */ public function create(Request $request) { $categories = $this->category->findCategories(); return view('book.create', compact('categories')); } /** * 登録処理 */ public function store(Request $request) { // リクエストされたデータを元に登録処理を実行 $registerBook = $this->book->InsertBook($request); return redirect()->route('book.index'); } } 登録画面create.blade.php @extends('layouts.app') @section('content') <div class="container small"> <h1>本を登録</h1> <form action="{{ route('book.store') }}" method="POST"> @csrf <fieldset> <div class="form-group"> <label for="book_name">{{ __('本の名称') }}<span class="badge badge-danger ml-2">{{ __('必須') }}</span></label> <input type="text" class="form-control" name="book_name" id="book_name"> </div> <!-- カテゴリープルダウン --> <div class="form-group"> <label for="category-id">{{ __('カテゴリー') }}<span class="badge badge-danger ml-2">{{ __('必須') }}</span></label> <select class="form-control" id="category-id" name="category_id"> @foreach ($categories as $category) <option value="{{ $category->category_id }}">{{ $category->category_name }}</option> @endforeach </select> </div> <div class="d-flex justify-content-between pt-3"> <a href="{{ route('book.index') }}" class="btn btn-outline-secondary" role="button"> <i class="fa fa-reply mr-1" aria-hidden="true"></i>{{ __('一覧画面へ') }} </a> <button type="submit" class="btn btn-success"> {{ __('登録') }} </button> </div> </fieldset> </form> </div> @endsection 〜初級編〜コントローラーにベタ書き 最初のステップとして、コントローラーにバリデーションをベタ書きする方法です。 Laravelでバリデーションを実装する入り口としてよく紹介されます。 実装方法はこんな感じ。BookController.php public function __construct() { $this->book = new Book(); } /** * 登録処理 */ public function store(Request $request) { // 本の名称を入力必須とするバリデーション $validated = $request->validate([ 'book_name' => 'required' ]); // リクエストされたデータを元に登録処理を実行 $registerBook = $this->book->InsertBook($request); return redirect()->route('book.index'); } laravelに元々用意されているvalidate関数を使うことでバリデーションが実装できます。 このままだとバリデーションメッセージが英語だし、book_nameもバリデーション側で定義していないので定義します。 resoources>lang>validation.phpを開いてください。101行目付近 'required' => ':attributeは必須です', に変更してください。 そして、154行目付近 'attributes' => [ 'book_name' => '本の名称' ], としてください。 あとは画面にバリデーションメッセージを表示させます。 <div class="form-group"> <label for="book_name">{{ __('本の名称') }}<span class="badge badge-danger ml-2">{{ __('必須') }}</span></label> <input type="text" class="form-control {{ $errors->has('book_name') ? 'is-invalid' : '' }}" name="book_name" id="book_name"> @if ($errors->has('book_name')) <span class="invalid-feedback" role="alert"> {{ $errors->first('book_name') }} </span> @endif </div> 三項演算子です。ifの条件分岐を1行でかけます。 {{ $errors->has('book_name') ? 'is-invalid' : '' }} 上記は、以下のif文と同じです。 @if ($errors->has('book_name')) 'is-invalid' @else '' @endif エラーがあった時にis-invalidのクラスを与え、エラーがない場合は何もクラスに追加しないってロジックを表しています。 is-invalidがあると、フォームの枠が赤くなります。 ※ブートストラップの仕様です。各自で導入してください。 $errors->first('book_name')とすることで、バリデーションに引っかかった最初のメッセージを取り出せます。 ちなみに「本の名称の長さを10文字以内にしたい」というバリデーションを追加でかけたい場合は、以下のようにできます。 /** * 登録処理 */ public function store(Request $request) { // 本の名称を入力必須・名称の長さ10文字以内とするバリデーション $validated = $request->validate([ 'book_name' => 'required|max:10' ]); // リクエストされたデータを元に登録処理を実行 $registerBook = $this->book->InsertBook($request); return redirect()->route('book.index'); } resources>lang>validation.php80行目付近 'max' => [ 'numeric' => 'The :attribute must not be greater than :max.', 'file' => 'The :attribute must not be greater than :max kilobytes.', 'string' => ':attributeは10文字以内で入力してください。', 'array' => 'The :attribute must not have more than :max items.', ], このようにコントローラーにベタ書きでバリデーションは実装できます。 ただ、実務では使えないでしょう。なぜならコントローラーの記述量が膨大になるので、バリデーションはバリデーションで他のファイルに切り分けるからです。 それでは、バリデーションを他のファイルで実装させる方法を紹介します。 〜応用篇〜バリデーションをコントローラーと切り分ける バリデーションをコントローラーと切り分けるには、フォームリクエストを作成します。 イメージとしては、 app>Http>Requestsにフォームリクエストを作成 (ここにバリデーションを書く) コントローラーでそのファイルを読み込む (登録処理の際にバリデーションファイルを読み込んでバリデーション判定させる) こんな感じです。 それでは実際に書いていきます。 フォームリクエストの作成 $ php artisan make:request BookRequestを実行 app>Http>Requests配下にBookRequest.phpが作成されます。 初期状態はこんな感じ。 <?php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class BookRequest extends FormRequest { /** * Determine if the user is authorized to make this request. * * @return bool */ public function authorize() { return false; } /** * Get the validation rules that apply to the request. * * @return array */ public function rules() { return [ // ]; } } 以下のように記述しましょう。 ※authorize()のfalseからtrueに変更することを忘れないでください。 <?php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class BookRequest extends FormRequest { /** * Determine if the user is authorized to make this request. * * @return bool */ public function authorize() { // falseからtrueに変更する return true; } /** * Get the validation rules that apply to the request. * * @return array */ public function rules() { // コントローラーに記述していたバリデーションを移行 return [ 'book_name' => 'required|max:10' ]; } } コントローラーを変更します。 変更点は3つ ・use App\Http\Requests\BookRequest;追加 ・store(BookRequest $request)に変更 ・バリデーションの処理を削除 <?php namespace App\Http\Controllers; use App\Models\Book; use App\Models\Category; use Illuminate\Http\Request; use App\Http\Requests\BookRequest; class BookController extends Controller { public function __construct() { $this->book = new Book(); $this->category = new Category(); } /** * 登録画面 */ public function create(Request $request) { $categories = $this->category->findCategories(); return view('book.create', compact('categories')); } /** * 登録処理 */ public function store(BookRequest $request) { // リクエストされたデータを元に登録処理を実行 $registerBook = $this->book->InsertBook($request); return redirect()->route('book.index'); } } これでよりスマートにバリデーションをかけました。 これでもいいのですが、実務ではバリデーションの内容もより複雑になります。 現在は、requiredやmaxなど基本的なものですが、自作でバリデーション作成したりするので、時にはバリデーション1つで5〜6行使ったり、if文で条件分岐させたり...さまざまです。 なので、私がよく実務で使っている方法を紹介します。 〜実務篇〜変数を用意して配列に格納していく BookRequest.phpを改造します。 public function rules() { $validate = []; $validate += [ 'book_name' => [ 'required', 'max:10' ] ]; return $validate; } ここでやっていることは最終的に$validateという変数にバリデーションを配列で格納しています。 最初に$validateの空の配列を用意します。 バリデーションに引っ掛かれば、$validateにどんどん配列として格納されるってロジックです。 これであればバリデーションのロジックが長くなってもコードを追いやすくなってみやすいです。 public function rules() { $validate = []; $validate += [ 'book_name' => [ 'required', 'max:10' ] ]; $validate += [ 'category_name' => [ 'required', 'max:10' ] ]; return $validate; } みたいな感じです。 今回はここまで! 最後までありがとうございました〜
- 投稿日:2021-08-29T14:39:10+09:00
Laravel6 ビューコンポーザ 画面に応じて変数の中身を変える
Qiita投稿 #30 今回は・・・ Laravelのビューコンポーザを使用した時の備忘録です。 SampleProvider.php <?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use Illuminate\Support\Facades\View; class SampleServiceProvider extends ServiceProvider { /** * Register services. * * @return void * */ public function register() { } /** * Bootstrap services. * * @return void */ public function boot() { View::composer(['Sample1','Sample2','Sample3'],'App\Http\ViewComposers\SampleComposer'); } } php artisan make:provider SampleServiceProvider 上記でプロバイダを作成しbootメソッド内にviewを指定します。 Sample1,2,3には同じ変数を使用するが中身は変えたいという場合を想定し、SampleComposerで条件分岐させます。 SampleComposer.php <?php namespace App\Http\ViewComposers; use Illuminate\View\View; class NamingComposer { // 省略 public function compose(View $view) { // 画面に応じて分岐 switch($view->name()){ case 'Sample1': $view->with('success','サンプル1です'); break; case 'Sample2': $view->with('success','サンプル2です'); break; case 'Sample3': $view->with('success','サンプル3です'); break; default: break; } } } laravelで用意されているViewクラスのメソッドを使う場合view()もしくはview->with()で画面や変数指定をする機会が多いと思います。 日本語記事でこれ以外のメソッドが紹介されているのはあまり見かけませんが、laravel APIドキュメントを見ると様々なメソッドが用意されています。 view->name() view->name()は現在のviewの名前を取得します。 今回はこちらのメソッドを使用しSampleComposer内で画面名に応じて処理を書いていきます。 またサービスコンテナに共通ロジックを書いておくことで、「依存度が下がる」「複数のファイルに同じコードを書かなくて済む」「修正する際は1つのファイルのみで済む」「可読性が上がる」など様々なメリットがあります。composerでも独自のサービスコンテナを使用することができますので、積極的にサービスコンテナとビューコンポーザを使っていきたいですね。一人でアプリケーションを作成する時は気にする必要はあまりないですが、チームで開発するときは「共通項はまとめる」という意識が重要かと思います。 参考
- 投稿日:2021-08-29T14:39:10+09:00
Laravel viewに共通変数を使いたいが中身を画面に応じて変えたい
Qiita投稿 #30 今回は・・・ Laravelのビューコンポーザを使用した時の備忘録です。 SampleProvider.php <?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use Illuminate\Support\Facades\View; class SampleServiceProvider extends ServiceProvider { /** * Register services. * * @return void * */ public function register() { } /** * Bootstrap services. * * @return void */ public function boot() { View::composer(['Sample1','Sample2','Sample3'],'App\Http\ViewComposers\SampleComposer'); } } php artisan make:provider SampleServiceProvider 上記でプロバイダを作成しbootメソッド内にviewを指定します。 Sample1,2,3には同じ変数を使用するが中身は変えたいという場合を想定し、SampleComposerで条件分岐させます。 SampleComposer.php <?php namespace App\Http\ViewComposers; use Illuminate\View\View; class NamingComposer { // 省略 public function compose(View $view) { // 画面に応じて分岐 switch($view->name()){ case 'Sample1': $view->with('success','サンプル1です'); break; case 'Sample2': $view->with('success','サンプル2です'); break; case 'Sample3': $view->with('success','サンプル3です'); break; default: break; } } } laravelで用意されているViewクラスのメソッドを使う場合view()もしくはview->with()で画面や変数指定をする機会が多いと思います。 日本語記事でこれ以外のメソッドが紹介されているのはあまり見かけませんが、laravel APIドキュメントを見ると様々なメソッドが用意されています。 view->name() view->name()は現在のviewの名前を取得します。 今回はこちらのメソッドを使用しSampleComposer内で画面名に応じて処理を書いていきます。 またサービスコンテナに共通ロジックを書いておくことで、「依存度が下がる」「複数のファイルに同じコードを書かなくて済む」「修正する際は1つのファイルのみで済む」「可読性が上がる」など様々なメリットがあります。composerでも独自のサービスコンテナを使用することができますので、積極的にサービスコンテナとビューコンポーザを使っていきたいですね。一人でアプリケーションを作成する時は気にする必要はあまりないですが、チームで開発するときは「共通項はまとめる」という意識が重要かと思います。 参考
- 投稿日:2021-08-29T11:44:37+09:00
【初心者向け】バリデーションについて〜Laravel〜
今回は、バリデーションの概要と設定方法、エラーメッセージの表示方法について説明します。 バリデーションとは バリデーションとは、ユーザーが入力した情報がその仕様や文法などに照らして適切な形式で記述されているかをチェックするための仕組みです。 たとえば、数字で入力してもらいたい年齢欄に名前を漢字で入力されたらサービスの提供を適切に行えなくなってしまいますよね。 それを防ぐためにバリデーションを利用します。 バリデーションの設定方法 コントローラーのアクション内に以下のように記述すれば、バリデーションを設定することができます。 〇〇Controller.php $rules([ '連想キー(カラム名)' => 'バリデーションルール', ]); $this->validate($request, $rules); 複数のバリデーションルールを設定したいときは、「|」を使って繋げて記述します(例:'required | max:500')。 バリデーションは、送信された情報が要求した形式に合致していなかったらフォーム画面が再表示される仕様になっているため、このままでは、ユーザーはフォームがちゃんと送信できたのか、今の送信は何がだめだったのかが分かりません。 そこで、ユーザーにも分かるようにエラーメッセージを表示し、再入力しやすいように入力した値はフィールドに残してあげます。 エラーメッセージの表示方法 フォームを用意したテンプレートを以下のように修正していきます。 index.blade.php <div> @error('カラム名') <span class="input-error">{{ $message }}</span> @enderror <input type="text" name="name" value="{{ old('カラム名') }}"> </div> このようにすることで、カラムごとにエラーメッセージを表示してあげることができます。 また、inputタグにvalue="{{ old('カラム名') }}"を入れてあげることで、前回入力した値が再表示されるようになります。 エラーメッセージの日本語化 このままだと、エラーメッセージは英語で表示されてしまうので、日本語に変えたい人は以下の設定が必要になります(やり方は何通りかあります)。 1) config>app.php内に記述されている'locale'=>'en'を'locale'=>'ja'に修正します。 2) resources>lang内に「ja」というディレクトリを作成し、その中にvalidation.phpファイルを作成します。 3) resources>lang/en>validation.phpの中身を2)で作ったvalidation.phpにコピペします。 4) ja>validation.php内の該当するバリデーションルールのThe以降の部分を日本語に書き換えます。 以上で、自分が表示させたい日本語のエラーメッセージが表示されるようになります。 別のやり方は調べたらいろいろ出てくると思うので、ググって自分が1番いいと思う方法で実装してみてください。
- 投稿日:2021-08-29T05:10:32+09:00
laravel メールをまとめてみる(*´∀`*)
まとめサイト。 一切laravelの公式サイトを見ていないという・・・。 laravelでメールを送信する方法 1.ファザード 2.メイラブル 3.ノーティファイ 0.プロジェクトを作ってまとめてみる 0.プロジェクトを作ってまとめてみる まだ最終確認していません。 https://into-the-program.com/laravel-create-contact-form/ 上のサイトを参考にしててまとめてみる。 動作の流れは、お問い合わせフォーム->確認フォーム->送信 となっている。 laravel new mailTutorial テンプレートととしていれておくと便利。 composer require laravel/ui php artisan ui bootstrap php artisan ui bootstrap --auth npm install && npm run dev 作成したテーブル一覧 お馴染み users queue送信のための jobs usersテーブルの友達にメールを送るためのタグ付のためのテーブル friends web.phpのルート一覧 routes\web.php Route::get('/', function () { return view('welcome'); }); Auth::routes(); Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home'); //コンタクトページのためのルート Route::get('contact',[App\Http\Controllers\ContactController::class, 'index'])->name('contact.index'); Route::post('contact/confirm',[App\Http\Controllers\ContactController::class, 'confirm'])->name('contact.confirm'); //コンタクトのインデックスページにリターンするメソッド Route::post('contact/index_return',[App\Http\Controllers\ContactController::class, 'index_return'])->name('contact.index_return'); //メール送信メソッドへのルート // メールfacades送信 Route::post('mail_facades_send',[App\Http\Controllers\MailController::class, 'mail_facades_send'])->name('mail_facades_send'); // メイラブル送信 Route::post('mailable_send',[App\Http\Controllers\MailController::class, 'mailable_send'])->name('mailable_send'); // notification Route::middleware(['auth'])->group(function () { //friends一覧 Route::get('/users/friends',[App\Http\Controllers\UserController::class,'friends_index'])->name('friends.index'); //friends登録form Route::get('/users/friends/register',[App\Http\Controllers\UserController::class,'friends_register_form'])->name('friends.register_form'); //friends登録メソッド Route::post('/users/friends/register',[App\Http\Controllers\UserController::class,'register_friends'])->name('register_friends'); //friendsにnotify送信メソッド Route::post('/friends_mail_send',[App\Http\Controllers\UserController::class,'friends_mail_send'])->name('friends_mail_send'); }); // notification+mailable //入力ホーム Route::get('/users/customer',[App\Http\Controllers\UserController::class,'index'])->name('users.index'); //確認ホーム Route::post('/users/confirm',[App\Http\Controllers\UserController::class,'confirm'])->name('users.confirm'); //送信メソッド Route::post('/users/notify_send',[App\Http\Controllers\UserController::class,'mail_send'])->name('users.mail_send'); リンクを作成する resources\views\welcome.blade.php @if (Route::has('login')) <div class="hidden fixed top-0 right-0 px-6 py-4 sm:block"> @auth <a href="{{ url('/home') }}" class="text-sm text-gray-700 underline">Home</a> <!-- 追記 --> <a href="{{ route('friends.index') }}" class="ml-4 text-sm text-gray-700 underline">friend Page</a> @else <a href="{{ route('login') }}" class="text-sm text-gray-700 underline">Log in</a> @if (Route::has('register')) <a href="{{ route('register') }}" class="ml-4 text-sm text-gray-700 underline">Register</a> @endif @endauth <!-- 追記 --> <a href="{{ route('contact.index') }}" class="ml-4 text-sm text-gray-700 underline">Contact Page</a> <!-- 追記 --> <a href="{{ route('users.index') }}" class="ml-4 text-sm text-gray-700 underline">Customer_sendPage</a> </div> @endif resources\views\layouts\app.blade.php <!-- ドロップダウンメニューに追記 --> <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown"> <!-- 追記 --> <a class="dropdown-item" href="{{ route('friends.index') }}"> frinds </a> <!-- /追記 --> <a class="dropdown-item" href="{{ route('logout') }}" onclick="event.preventDefault(); document.getElementById('logout-form').submit();"> {{ __('Logout') }} </a> <form id="logout-form" action="{{ route('logout') }}" method="POST" class="d-none"> @csrf </form> </div> layouts.app に追記 ステップフォームのスタイルを作成。 resources\views\layouts\app.blade.php <!-- 追記 --> <style> footer { background-color: #555; color: white; padding: 15px; } .step_box { background-color: grey; border-radius: 25%; padding-inline: 25px; display: inline-block; height: 100px; line-height: 100px; color: white; box-sizing: border-box; } .triangle { font-size: 50px; } .active { background-color: tomato; } </style> <!-- 追記 --> @stack('css') ------- <!-- 追記 --> @stack('js') </bodY> メイルファザードとメイラブル送信 入力フォームを作成する。 resources\views\contact\index.blade.php @extends('layouts.app') @push('css') <style> .custom-file { max-width: 20rem; overflow: hidden; } .custom-file-label { white-space: nowrap; } </style> @endpush @section('content') <div class="d-flex justify-content-center align-items-center mt-5"> <div class="step_box active">お問い合わせ</div> <div class="triangle">▶</div> <div class="step_box">確認フォーム</div> <div class="triangle">▶</div> <div class="step_box">送信完了</div> </div> <div class="container mt-5"> <div class="row justify-content-center"> <div class="col-md-8 justify-"> <div class="card"> <div class="card-header">お問い合わせ</div> <div class="card-body"> @if ($errors->any()) <div class="alert alert-danger"> <ul> @foreach ($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif <form id="mail_form" method="POST" enctype="multipart/form-data"> @csrf <label id="email">メールアドレス</label> <input class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" type="text"> @if ($errors->has('email')) <span class="invalid-feedback" role="alert"> <p class="error-message">{{ $errors->first('email') }}</p> </span> @endif <label id="title">タイトル</label> <input class="form-control @error('email') is-invalid @enderror" name="title" value="{{ old('title') }}" type="text"> @if ($errors->has('title')) <span class="invalid-feedback" role="alert"> <p class="error-message">{{ $errors->first('title') }}</p> </span> @endif <label id="body">お問い合わせ内容</label> <textarea class="form-control @error('email') is-invalid @enderror" name="body">{{ old('body') }} </textarea> <!-- errorのほうが便利 --> @error('body') <span class="invalid-feedback" role="alert"> <strong>{{ $message }}</strong> </span> @enderror <div class="form-group mt-3"> <label for="file">ファイル(複数選択可)</label> <div id="file" class="input-group"> <div class="custom-file"> <input type="file" id="customfile" class="custom-file-input @error('file') is-invalid @enderror" name="customfile[]" multiple accept=".png,.jpg,.jpeg,.pdf,.doc,.xlsx" value="{{ old('customfile') }}" /> <label class="custom-file-label" for="customfile" data-browse="参照">画像・ファイルの添付</label> </div> <div class="input-group-append"> <button type="button" class="btn btn-outline-secondary reset">取消</button> </div> @if (old('tmp_img_files')) <div id="preview"> @foreach (explode(',', old('tmp_img_files')) as $tmp_img_file) <div class="d-inline-block mr-1 mt-1"><img class="img-thumbnail" style="height:100px;" src="{{ asset('storage/' . $tmp_img_file) }}" alt=""><div class="small text-muted text-center">{{ $tmp_img_file }}</div></div> @endforeach </div> @endif </div> </div> @if ($errors->has('customfile')) <span class="invalid-feedback" role="alert"> <p class="error-message">{{ $errors->first('file') }}</p> </span> @endif <button class="btn btn-primary btn-sm mt-3" type="submit"> 確認フォームへ </button> @if (old('tmp_files')) @endif </form> </div> </div> </div> </div> </div> @endsection @push('js') <script> //これは遊び $('#mail_form').one('submit', function(event) { event.preventDefault(); $('#mail_form').attr('action', "{{ route('contact.confirm') }}").submit(); }); //https://v4.bootstrap-guide.com/javascript/forms/file-browser $('.custom-file-input').on('change', handleFileSelect); function handleFileSelect(evt) { $('#preview').remove(); // 繰り返し実行時の処理 $(this).parents('.input-group').after('<div id="preview"></div>'); var files = evt.target.files; for (var i = 0, f; f = files[i]; i++) { var reader = new FileReader(); reader.onload = (function(theFile) { return function(e) { if (theFile.type.match('image.*')) { var $html = [ '<div class="d-inline-block mr-1 mt-1"><img class="img-thumbnail" src="', e .target.result, '" title="', escape(theFile.name), '" style="height:100px;" /><div class="small text-muted text-center">', escape(theFile.name), '</div></div>' ].join(''); // 画像では画像のプレビューとファイル名の表示 } else { var $html = ['<div class="d-inline-block mr-1"><span class="small">', escape(theFile .name), '</span></div>'].join(''); //画像以外はファイル名のみの表示 } $('#preview').append($html); }; })(f); reader.readAsDataURL(f); } $(this).next('.custom-file-label').html(+files.length + '個のファイルを選択しました'); } //ファイルの取消 $('.reset').click(function() { $(this).parent().prev().children('.custom-file-label').html('ファイル選択...'); $('#preview').remove(); $('.custom-file-input').val(''); }) </script> @endpush 確認フォーム resources\views\contact\confirm.blade.php @extends('layouts.app') @section('content') <div class="d-flex justify-content-center align-items-center mt-5"> <div class="step_box">お問い合わせ</div> <div class="triangle">▶</div> <div class="step_box active">確認フォーム</div> <div class="triangle">▶</div> <div class="step_box">送信完了</div> </div> <div class="container"> <div class="row justify-content-center mt-5"> <div class="col-8"> <div class="card"> <div class="card-header">確認フォーム</div> <div class="card-body"> <dl class=""> <dt>email</dt> <dd>{{ $request->email }}</dd> <dt>タイトル</dt> <dd>{{ $request->title }}</dd> <dt>お問い合わせ内容</dt> <dd>{{ nl2br($request->body) }}</dd> <dt>画像・ファイル</dt> @isset($tmp_files[$img_pth]) <dd class="d-flex flex-wrap align-content-around"> @forelse ($tmp_files[$img_pth] as $tmp_file) <img class="img-fluid m-1" width="100px" src="{{ asset(Storage::url($tmp_file))}}" alt=""> @empty @endforelse </dd> @endisset </dl> <form id="mail_send" method="POST" enctype="multipart/form-data"> @csrf <input name="email" value="{{ $request->email }}" type="hidden"> <input name="title" value="{{ $request->title }}" type="hidden"> <input name="body" value="{{ $request->body }}" type="hidden"> @isset($tmp_files[$img_pth]) <input name="tmp_img_files" value="{{$img_pth}}" type="hidden"> @endisset <input name="csrf_name" value="confirm" type="hidden"> <div class="d-flex justify-content-end flex-wrap"> <button class="btn btn-danger m-1" type="submit" name="index_return" value="back"> 入力内容修正 </button> <button class="btn btn-primary m-1" type="submit" name="mail_facades_send" value="submit"> ファザード送信する </button> <button class="btn btn-primary m-1" type="submit" name="mailable_send" value="submit"> メイラブル送信する </button> <button class="btn btn-primary m-1" type="submit" name="notify_send" value="submit"> notify送信する。 </button> </div> </form> </div> </div> </div> </div> </div> @endsection @push('js') <script> //1回だけイベントを実行する。永久ループになるため、 $('#mail_send').one('submit', function(e) { e.preventDefault(); //クリックした要素を取得できる。 click_name = $(document.activeElement).attr('name'); //attrではんくpropを使用する。 $('button[type=submit]').prop('disabled', true);; if (click_name == 'index_return') { //atr複数の場合オブジェクトにする $('#mail_send').attr({ 'action': "{{ route('contact.index_return') }}" }); } if (click_name == 'mail_facades_send') { $('#mail_send').attr('action', "{{ route('mail_facades_send') }}"); } if (click_name == 'mailable_send') { $('#mail_send').attr('action', "{{ route('mailable_send') }}"); } $('#mail_send').submit(); }); </script> @endpush 送信完了ページ(contact\thanks.blade.php) resources\views\contact\thanks.blade.php @extends('layouts.app') @section('content') <div class="d-flex justify-content-center align-items-center mt-5"> <div class="step_box">お問い合わせ</div><div class="triangle">▶</div><div class="step_box">確認フォーム</div><div class="triangle">▶</div><div class="step_box active">送信完了</div> </div> <div class="flex-grow-1 d-flex justify-content-center align-items-center"> </div> <div class="d-flex flex-column justify-content-center align-items-center mt-5"> <h1>{{ __('送信完了') }}</h1> <a class="btn btn-sm btn-primary" href="{{ route('contact.index') }}">問い合わせる。</a> </div> @endsection コンタクトコントローラーの作成 php artisan make:controller ContactController app\Http\Controllers\ContactController.php <?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Support\Str; class ContactController extends Controller { public function index() { return view('contact.index'); } public function index_return() { // oldで値を取得することができる。 return redirect()->route('contact.index')->withInput(); } public function confirm(Request $request) { $request->validate([ 'email' => 'required|email', 'title' => 'required|', 'body' => 'required|', 'cutomfile.*' => [ 'file', 'image', 'mimes:jpeg,png,jpg,pdf,doc,xlsx' ], ], [ 'required' => ':attributeは必須やねん', 'email' => ':attributeはメールやねん', ], [ 'email' => 'メール', 'title' => 'タイトル', 'body' => '内容', 'cutomfile'=>'添付ファイル' ]); $tmp_files = []; $img_pth=''; if ($request->has('customfile')) { $img_pth = uniqid("tmp_img_") ."/"; $notImg_pth = uniqid("tmp_notImg_") ."/"; foreach ($request->file('customfile') as $key => $tmp_file) { if ($tmp_file->isValid()) { //画像 か それ以外 if (Str::is('image*', $request->customfile[0]->getMimeType())) { //一時保存pathを作成(一時path + ファイル名) $tmp_files[$img_pth][$key] = $img_pth. Str::of($tmp_file->getClientOriginalName())->replace(' ', '_'); // storage\appからの相対パス 画像を保存している。//取得はsrc="{{ asset('storage/'.$tmp_file) $tmp_file->storeAs("public", $tmp_files[$img_pth][$key]); }else{ $tmp_files[$notImg_pth][$key] = $notImg_pth.Str::of($tmp_file->getClientOriginalName())->replace(' ', '_'); $tmp_file->storeAs("public", $tmp_files[$notImg_pth][$key]); } } } } return view('contact.confirm', compact('request', 'tmp_files','img_pth')); } } メール送信のためのコントローラー 分けたほうがすっきりすると思う app\Http\Controllers\MailController.php php artisan make:controller MailController app\Http\Controllers\MailController.php <?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Http\Requests\MaileRequest; use Illuminate\Support\Facades\Mail; use App\Mail\MailableSend; use Illuminate\Support\Facades\Storage; use App\Jobs\SendHelloEmail; use App\Jobs\DeleteFile; class MailController extends Controller { //メールファザードで送信 public function mail_facades_send(MaileRequest $request) { $tmp_files = Storage::files('public/'.$request->tmp_img_files); //メールをキューを使って送信できない。 Mail::send( // /*html送信*/ 'mail_templates.html_mail'/* 第1引数 viewsPath */, /*text送信*/ ['text' => 'mail_templates.text_mail'/*viewsPath*/], $request->all()/**第2引数 配列[]*/, function ($message/*中身不明dd($message)*/) use ($request, $tmp_files) { // 第3引数にはコールバック関数を指定し、 // その中で、送信先やタイトルの指定を行う. $message ->to($request->email) ->bcc('admin@sample.com') //envより優先される。なければenvが渡る。 ->from('laravel@sample.co.jp', 'Laravel事務局') //storage_pathはstorageディレクトリの直下link等は不要 ->subject("お問い合わせありがとうございます。"); //複数ファイルを添付する。 foreach ($tmp_files as $key => $tmp_file) { $message->attach(public_path(Storage::url($tmp_file))); } } ); // // *** メールファザードで複数送信したい場合。*** // // // $emails = ['tester@blahdomain.com', 'anotheremail@blahdomian.com']; // Mail::send('mail_templates.html_mail', $request->all(), function ($message) use ($request, $emails) // { // $message->from('no-reply@yourdomain.com', 'Joe Smoe'); // // $message->to( $request->input('emails') ); // $message->to( $emails); // //Add a subject // $message->subject("New Email From Your site"); // }); // storage\appからの相対パス // Storage::delete('public/'.$request->tmp_img_files); Storage::deleteDirectory('public/'.$request->tmp_img_files); //2重送信を防止できる。リダイレクト先はExceptionsでhandleする。 $request->session()->regenerateToken(); return view('contact.thanks'); } //メーラブルで送信 public function mailable_send(MaileRequest $request) { ////複数人に送信する場合配列で渡す $to = [$request->email, 'ABC@gmail.com']; //// メールにエイリアスをつけたい場合 // $to = [ // ['email' => 'AAA@text.com','name' => 'Test', ], // ['email' => 'BBB@text.com','name' => 'Test2',], // ]; //でもOK usersテーブルのemail、nameの列が使用される。 // $users = User::all(); $to = $users //ファイルを配列で取得 $tmp_files = Storage::files('public/'.$request->tmp_img_files); //queue送信やjobではrequestオブジェクトでは駄目 $inputs = $request->all(); // //普通に送信 // Mail::to($to)->send(new MailableSend($inputs/*$requestでもOK*/,$tmp_files)); //queue送信 第2引数では$requestオブジェクトをそのまま取得できない // Mail::to($to)->queue(new MailableSend($inputs/*$requestはダメ絶対駄目*/,$tmp_files)); //dispatchでjobファイルを発火 第2引数では$requestオブジェクトをそのまま取得できない SendHelloEmail::dispatch($inputs/*$requestはダメ絶対駄目*/,$tmp_files,$to); // 画像ファイルを削除 // Storage::delete('public/'.$request->tmp_img_files); //ディレクトリを削除 // Storage::deleteDirectory('public/'.$request->tmp_img_files); //queue送信するときは送る前に削除するからjobで実行しないとエラーになる DeleteFile::dispatch($request->tmp_img_files); //2重送信を防止できる。リダイレクト先はExceptionsでhandleする。 $request->session()->regenerateToken(); return view('contact.thanks'); } } mailableクラスを作成する app\Mail\MailableSend.php php artisan make:mail MailableSend app\Mail\MailableSend.php <?php namespace App\Mail; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Mail\Mailable; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Storage; class MailableSend extends Mailable //メイラブルでqueue送信するときはコメントアウトを解除(dispatchはコメントアウト) // implements ShouldQueue { use Queueable, SerializesModels; protected $request; protected $tmp_files //引数で受け取った変数を利用する publicの場合はそのまま利用できる。 public function __construct($request, $tmp_files) { //protectedの場合$thisで受け取る $this->request = $request; $this->tmp_files = $tmp_files; } public function build() { // リクエストの変数展開 extract($this->request); $tmp_files = $this->tmp_files; // //htmlメール $this->view $mail = $this->view('mail_templates.html_mail', compact('email', 'title', 'body','tmp_files')) // //テキストメールもいける。 // $this->text('mail_templates.text_mail',compact('email','title','body')) // //to はコントローラーで指定する。 // // ✗->to($request->email) ->bcc('admin@sample.com') // //envより優先される。なければenvが渡る。 ->from('laravel@sample.co.jp', 'Laravel事務局') ->subject("お問い合わせありがとうございます。"); //添付送信 foreach ($this->tmp_files as $tmp_file) { $mail->attach(public_path(Storage::url($tmp_file))); } return $mail; } } メイルのテンプレートを作成する htmlとtxtでviewsに作成する。 resources\views\mail_templates\html_mail.blade.php お問い合わせ内容を受け付けました。<br> <br> ■メールアドレス<br> {!! $email !!}<br> <br> ■タイトル<br> {!! $title !!}<br> <br> ■お問い合わせ内容<br> {!! nl2br($body) !!}<br> ■添付画像<br> @foreach ($tmp_files as $tmp_file) <img src="{{ asset(Storage::url($tmp_file)) }}" width="200px"> @endforeach resources\views\mail_templates\text_mail.blade.php お問い合わせ内容を受け付けました。 HTMLは書かない。<br>とか ■メールアドレス {{ $email }} ■タイトル {{ $title }} ■お問い合わせ内容 {{ nl2br($body) }} ジョブファイルを作成する メイラブル送信するファイルと 一時画像を削除するファイルを作成する app\Jobs\SendHelloEmail.php app\Jobs\DeleteFile.php php artisan make:job SendHelloEmail php artisan make:job DeleteFile app\Jobs\SendHelloEmail.php <?php namespace App\Jobs; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Mail; use App\Mail\MailableSend; class SendHelloEmail implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; //ジョブがタイムアウトになるまでの秒数 defautは60秒 public $timeout = 120; protected $request; protected $tmp_files; protected $to; public function __construct($request, $tmp_files,$to) { $this->request = $request; $this->tmp_files = $tmp_files; $this->to = $to; } public function handle() { //複数人に送信する場合配列で渡す $to = $this->to; Mail::to($to)->send(new MailableSend($this->request, $this->tmp_files)); } } app\Jobs\DeleteFile.php <?php namespace App\Jobs; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Storage; class DeleteFile implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; protected $tmp_img_files; public function __construct($tmp_img_files) { $this->tmp_img_files = $tmp_img_files; } public function handle() { Storage::deleteDirectory('public/'.$this->tmp_img_files); } } JobとQueueの設定をする #変更 QUEUE_CONNECTION=database php artisan queue:table php artisan migrate 実行するとqueue送信が可能になる。 php artisan queue:work notificationで送信 フレンドに送信させるため、フレンドモデルを作成する。モデルを作成するのはリレーションする必要があるため。1対多のテーブルを作成する。 php artisan make:model Friend -a database\migrations\xxxx_create_friends_table.php Schema::create('friends', function (Blueprint $table) { $table->id(); $table->foreignId('user_id') ->constrained() ->onUpdate('cascade') ->onDelete('cascade'); $table->foreignId('friend_id') ->constrained('users','id') ->onUpdate('cascade') ->onDelete('cascade'); $table->timestamps(); $table->unique(["user_id","friend_id"]); }); Userモデルの編集 public function friends(){ //公式 return $this->hasMany(Comment::class, 'foreign_key', 'local_key'); return $this->hasMany(Friend::class,'user_id','id'); } Friendモデルの編集 app\Models\Friend.php protected $fillable = [ 'user_id', 'friend_id', ]; public function user() { // 公式 return $this->belongsTo(User::class, 'foreign_key', 'owner_key'); return $this->belongsTo(User::class,'friend_id','id'); //公式 return $this->hasOne(Phone::class, 'foreign_key', 'local_key'); //return $this->hasOne(User::class, 'id', 'friend_id'); } 友達一覧画面+送信画面を作成 resources\views\Users\friends\index.blade.php resources\views\Users\friends\index.blade.php @extends('layouts.app') @section('content') <div class="container flex-grow-1 d-flex flex-column"> @if (session()->has('success')) <div class="alert alert-success alert-dismissible fade show" role="alert"> <strong>{{ session('success') }}</strong> <button type="button" class="close" data-dismiss="alert" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> @endif <div class="row flex-grow-1 justify-content-center align-items-center"> <div class="col-md-8"> <div class="card"> <div class="card-header">友達一覧</div> <div class="card-body"> <p>{{ Auth::user()->name }}様</p> <input type="checkbox" name="" id="mail_checbox"><label for="mail_checbox">一斉送信</label> <form method="POST" action="{{ route('friends_mail_send') }}"> @csrf <table> <thead> <th>送信</th> <th>名前</th> </thead> <tbody> @foreach ($friends as $friend) <tr> <td><input type="checkbox" name="friend_ids[][friend_id]" value="{{ $friend->friend_id }}" id="{{ $friend->friend_id }}"></td> <td><label for="{{ $friend->friend_id }}">{{ $friend->user->name }}</label></td> </tr> @endforeach </tbody> </table> <div class="text-right mb-3"> <a class="btn btn-sm btn-secondary" href="{{ route('friends.register_form') }}">友達を登録する</a> </div> <div class="border p-3"> <div class="text-center"><span class="text-secondary">通知内容</span></div> <label>タイトル</label> <input class="form-control @error('email') is-invalid @enderror" name="title" value="{{ old('title') }}" type="text"> @if ($errors->has('title')) <span class="invalid-feedback" role="alert"> <p class="error-message">{{ $errors->first('title') }}</p> </span> @endif <label>内容</label> <textarea rows="5" class="form-control @error('email') is-invalid @enderror" name="body">{{ old('body') }} </textarea> @if ($errors->has('body')) <span class="invalid-feedback" role="alert"> <p class="error-message">{{ $errors->first('body') }}</p> </span> @endif <button class="btn btn-secondary btn-sm mt-3" type="submit" > 友達にメール送信 </button> </div> </form> </div> </div> </div> </div> </div> @endsection @push('js') <script> $('#mail_checbox').on('change',function(){ if($('#mail_checbox').prop('checked')){ $("input[name^='friend_ids']").prop('checked',true) }else{ $("input[name^='friend_ids']").prop('checked',false) } }); </script> @endpush 友達登録画面の作成 resources\views\Users\friends\register_friends.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 d-flex"> <span>お問い合わせ</span> <a class="btn btn-sm btn-secondary w-25 ml-auto" href="{{ route('friends.index') }}">ホーム</a> </div> <div class="card-body"> <p>{{ Auth::user()->name }}様</p> <input type="checkbox" name="" id="register_all"><label for="register_all">登録</label> <input type="checkbox" name="" id="delete_all"><label for="delete_all">登録解除</label> <form id="friends_form" action="{{ route('register_friends') }}" method="POST"> @csrf <table> <thead> <th>登録</th> <th>解除</th> <th>名前</th> </thead> <tbody> @foreach ($users as $user) <tr> @if($friends->contains('friend_id',$user->id)) <td>✔</td> <td><input type="checkbox" name="friend_delete_ids[][friend_id]" value="{{ $user->id }}" id="{{$user->id}}"></td> @else <td><input type="checkbox" name="friend_register_ids[][friend_id]" value="{{ $user->id }}" id="{{$user->id}}"></td> <td></td> @endif <td><label for="{{ $user->id }}">{{ $user->name }}</label></td> </tr> @endforeach <th colspan="2"> <input id="friends_btn" class="btn btn-primary" type="submit" value="友達登録"> </th> </tbody> </table> </form> </div> </div> </div> </div> </div> @endsection @push('js') <script> $('#friends_btn').on('click',function(event){ event.preventDefault(); $('#friends_form').submit(); }); $('#register_all').on('change',function(){ if($('#register_all').prop('checked')){ $("input[name^='friend_register_ids']").prop('checked',true); }else{ $("input[name^='friend_register_ids']").prop('checked',false); } }); $('#delete_all').on('change',function(){ if($('#delete_all').prop('checked')){ $("input[name^='friend_delete_ids']").prop('checked',true); }else{ $("input[name^='friend_delete_ids']").prop('checked',false); } }); </script> @endpush UserControllerを作成する php artisan make:controller UserController app\Http\Controllers\UserController.php app\Http\Controllers\UserController.php //友達一覧 + ここから送信も public function friends_index(){ $user = User::find(Auth::id()); //friendsをgetして、 $friends=$user->friends()->get(); //定番の N対1 対策 $friends->load('user'); return view('Users.friends.index',compact('user','friends')); } // 友達登録//ファンクション名はpathに合わせてみた。 public function friends_register_form(){ $user = User::findOrFail(Auth::id()); $users = User::where('id', '!=', $user->id)->get(); $friends = $user->friends()->get(); return view('users.friends.register_friends', compact('users', 'friends')); } //友達登録メソッド public function register_friends(Request $request) { DB::beginTransaction(); try { $user = User::find(Auth::id()); if ($request->has('friend_register_ids')) { $user->friends()->createMany( $request->friend_register_ids ); } if ($request->has('friend_delete_ids')) { $user->friends()->whereIn('friend_id', $request->friend_delete_ids)->delete(); } DB::commit(); return view('friends.index'); } catch (\Exception $e) { DB::rollback(); return redirect()->route('friends.register_form')->withInput(); } } //友達へ送信メソッド public function friends_mail_send(Request $request){ $friends = User::whereIn('id',$request->friend_ids)->get(); $user = User::find(Auth::id()); Notification::send($friends, new friends_mail_send($user,$request->except('friend_ids'),$friends)); return redirect()->route('friends.index')->with('success','送信しました'); } notificationファイルの作成 php artisan make:notification friends_mail_send app\Notifications\friends_mail_send.php app\Notifications\friends_mail_send.php <?php namespace App\Notifications; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification; class friends_mail_send extends Notification //queue送信するときはコメントアウトを解除したらいいだけ。 //implements ShouldQueue { use Queueable; protected $user; protected $request; protected $friends; public function __construct($user, $request, $friends) { $this->user = $user; $this->request = $request; $this->friends = $friends; } public function via($notifiable) { return ['mail']; } public function toMail($notifiable) { return (new MailMessage) ->markdown('mail_templates.friends_mail_send', [ 'user' => $this->user, 'title' => $this->request['title'], 'body' => $this->request['body'], 'for_friend' => $notifiable, 'friends' => $this->friends, // 'url' => $this->url, // 'token' => $this->token, ]) // ->error()//actionのボタンがdanger ->success()//actionのボタンがsuccess ->line('アクションの前のライン1.') ->line('アクションの前のライン2.') ->line('アクションの前のライン3.') ->action('Notification Action', Url('/')) ->line('アクションの後のライン4') ->line('アクションの後のライン5.') ->subject("{$this->user->name}からあなた宛に通知があります"); } public function toArray($notifiable) { return []; } } 友達へのメールのテンプレート resources\views\mail_templates\friends_mail_send.blade.php @component('mail::layout') {{-- Header logoにリンクがはれるコンポーネント --}} @slot('header') {{-- @component('mail::header', ['url' => $url]) {{ config('app.name') }} @endcomponent --}} @endslot {{-- Body --}} {{$user->name}}さんから連絡です。 - {{ ($for_friend['name']) }}さんへ @component('mail::panel') ## その他<br> @foreach ($friends as $friend) @if ($friend['id'] !== $for_friend['id']) {{$friend['name'] }}<br> @endif @endforeach さん達へ @endcomponent タイトル ---- {{ $title }} # 内容 {{ Illuminate\Mail\Markdown::parse($body) }} {{ $body }} {{-- Intro Lines actionより上のline --}} @foreach ($introLines as $line) {{ $line }} @endforeach {{-- Outro Lines actionより下のline --}} @foreach ($outroLines as $line) {{ $line }} @endforeach {{-- Action Button --}} @isset($actionText) <?php switch ($level) { case 'success': case 'error': $color = $level; break; default: $color = 'primary'; } ?> @component('mail::button', ['url' => $actionUrl, 'color' => $color]) {{ $actionText }} @endcomponent @endisset @component('mail::table') | Laravel | Table | Example | | ------------- |:-------------:| --------:| | Col 2 is | Centered | $10 | | Col 3 is | Right-Aligned | $20 | @endcomponent @component('mail::button', ['url' => 'https://qiita.com/','color'=>'success']) google @endcomponent {{-- Subcopy actionButtonが効かなかった時の予備--}} @isset($actionText) @slot('subcopy') @lang( "If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\n". 'into your web browser:', [ 'actionText' => $actionText, ] ) <span class="break-all">[{{ $displayableActionUrl }}]({{ $actionUrl }})</span> @endslot @endisset {{-- Footer --}} @slot('footer') @component('mail::footer') © {{ date('Y') }} {{ config('app.name') }}. @lang('All rights reserved.') @endcomponent @endslot @endcomponent notification+mailable 今週のお勧めメニューをcustomerに送信する 入力ホーム resources\views\Users\friends\index.blade.php @extends('layouts.app') @push('css') <style> .custom-file { max-width: 20rem; overflow: hidden; } .custom-file-label { white-space: nowrap; } .imagePreview { width: 100%; height: 200px; display: block; border: solid 1px; } </style> @endpush @section('content') <div class="container mt-5"> <div class="row justify-content-center"> <div class="col-md-8 justify-"> <div class="card"> <div class="card-header">入力ホーム</div> <div class="card-body"> @if ($errors->any()) <div class="alert alert-danger"> <ul> @foreach ($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif <form id="mail_send" method="POST" enctype="multipart/form-data" action="{{ route('users.confirm') }}"> @csrf <p>information</p> <div class="card-body border"> <label id="infomation">今週のお知らせ</label> <input class="form-control @error('infomation') is-invalid @enderror" name="infomation" value="{{ old('infomation') }}" type="text"> @if ($errors->has('infomation')) <span class="invalid-feedback" role="alert"> <p class="error-message">{{ $errors->first('infomation') }}</p> </span> @endif <label id="infomation_url">ページのURL</label> <input class="form-control @error('infomation_url') is-invalid @enderror" name="infomation_url" value="{{ old('infomation_url') }}" type="text"> @if ($errors->has('infomation_url')) <span class="invalid-feedback" role="alert"> <p class="error-message">{{ $errors->first('infomation_url') }}</p> </span> @endif </div> <p>メイン</p> <div class="card-body border"> <div class="form-group mt-3"> <label for="file">mainImage</label> <div class="form-row align-items-center no-gutters p-1 m-0 justify-content-center"> <div id="file" class="input-group main col-md-7"> <div class="custom-file"> <input type="file" id="mainImg-file" class="mainImg-file-input @error('file') is-invalid @enderror" name="img-files[]" accept=".png,.jpg,.jpeg" /> <label class="custom-file-label main-lavel" for="mainImg-file" data-browse="参照">mainImage</label> </div> <div class="input-group-append"> <button type="button" class="btn btn-outline-secondary main_reset">取消</button> </div> </div> <div class="imagePreview col-md-5 p-0"> <img class="img-fluid imagePreview-main" src="" alt="メイン画像"> </div> </div> <label id="mainMessage">mainMessage</label> <textarea class="form-control @error('email') is-invalid @enderror" name="mainMessage" value="{{ old('mainMessage') }}" type="text" rows="5"> </textarea> @if ($errors->has('mainMessage')) <span class="invalid-feedback" role="alert"> <p class="error-message">{{ $errors->first('mainMessage') }}</p> </span> @endif </div> </div> <div class="card-body border mt-2"> <div class="form-group mt-3 no-gutters "> <label for="file">first_menus_img</label> <div class="form-row align-items-center p-1 justify-content-center .menus"> <div id="file" class="input-group main col-md-7"> <div class="custom-file"> <input type="file" id="firstImg-file" class="menus_img firstImg-file-input @error('file') is-invalid @enderror" name="img-files[]" accept=".png,.jpg,.jpeg" /> <label class="custom-file-label" for="firstImg-file" data-browse="参照">first_manue_img</label> </div> <div class="input-group-append"> <button type="button" class="btn btn-outline-secondary reset">取消</button> </div> </div> <div class="imagePreview col-md-5 p-0"> <img class="img-fluid imagePreview-menus" src="" alt="ファースト画像"> </div> </div> <label id="first_menus_infomation">first_menus_infomation</label> <input class="form-control @error('first_menus_infomation') is-invalid @enderror" name="first_menus_infomation" type="text"> @if ($errors->has('first_menus_infomation')) <span class="invalid-feedback" role="alert"> <p class="error-message">{{ $errors->first('first_menus_infomation') }}</p> </span> @endif <label id="first_menus_price">first_menus_price</label> <input class="form-control @error('first_menus_price') is-invalid @enderror" name="first_menus_price" type="number"> @if ($errors->has('first_menus_price')) <span class="invalid-feedback" role="alert"> <p class="error-message">{{ $errors->first('first_menus_price') }}</p> </span> @endif </div> </div> <div class="card-body border mt-2"> <div class="form-group mt-3"> <label for="file">second_menus_img</label> <div class="form-row align-items-center no-gutters p-1 justify-content-center .menus"> <div id="file" class="input-group main col-md-7"> <div class="custom-file"> <input type="file" id="secondImg-file" class="menus_img secondImg-file-input @error('file') is-invalid @enderror" name="img-files[]" accept=".png,.jpg,.jpeg" /> <label class="custom-file-label" for="secondImg-file" data-browse="参照">second_manue_img</label> </div> <div class="input-group-append"> <button type="button" class="btn btn-outline-secondary reset">取消</button> </div> </div> <div class="imagePreview col-md-5 p-0"> <img class="img-fluid imagePreview-menus" src="" alt="セカンド画像"> </div> </div> <label id="second_menus_infomation">second_menus_infomation</label> <input class="form-control @error('second_menus_infomation') is-invalid @enderror" name="second_menus_infomation" type="text"> @if ($errors->has('second_menus_infomation')) <span class="invalid-feedback" role="alert"> <p class="error-message">{{ $errors->first('second_menus_infomation') }}</p> </span> @endif <label id="second_menus_price">second_menus_price</label> <input class="form-control @error('second_menus_price') is-invalid @enderror" name="second_menus_price" value="{{ old('second_menus_price') }}" type="number"> @if ($errors->has('second_menus_price')) <span class="invalid-feedback" role="alert"> <p class="error-message">{{ $errors->first('second_menus_price') }}</p> </span> @endif </div> </div> <div class="card-body border mt-2"> <div class="form-group mt-3"> <label for="file">third_menus_img</label> <div class="form-row align-items-center no-gutters p-1 justify-content-center .menus"> <div id="file" class="input-group main col-md-7"> <div class="custom-file"> <input type="file" id="thirdImg-file" class="menus_img thirdImg-file-input @error('file') is-invalid @enderror" name="img-files[]" accept=".png,.jpg,.jpeg" /> <label class="custom-file-label" for="thirdImg-file" data-browse="参照">third_manue_img</label> </div> <div class="input-group-append"> <button type="button" class="btn btn-outline-secondary reset">取消</button> </div> </div> <div class="imagePreview col-md-5 p-0"> <img class="img-fluid imagePreview-menus" src="" alt="サード画像"> </div> </div> <label id="third_menus_infomation">third_menus_infomation</label> <input class="form-control @error('email') is-invalid @enderror" name="third_menus_infomation" type="text"> @if ($errors->has('third_menus_infomation')) <span class="invalid-feedback" role="alert"> <p class="error-message">{{ $errors->first('third_menus_infomation') }}</p> </span> @endif <label id="third_menus_price">third_menus_price</label> <input class="form-control @error('third_menus_price') is-invalid @enderror" name="third_menus_price" type="number"> @if ($errors->has('third_menus_price')) <span class="invalid-feedback" role="alert"> <p class="error-message">{{ $errors->first('third_menus_price') }}</p> </span> @endif </div> </div> <button class="btn btn-primary btn-sm mt-3" type="submit" id="confirm"> 確認フォームへ </button> <button class="btn btn-primary btn-sm mt-3" type="submit" id='mail_send'> メール送信 </button> </form> </div> </div> </div> </div> </div> @endsection @push('js') <script> //1回だけイベントを実行する。永久ループになるため、 $('#mail_send').one('submit', function(e) { e.preventDefault(); //クリックした要素を取得できる。 click_name = $(document.activeElement).attr('id'); //attrではんくpropを使用する。 $('button[type=submit]').prop('disabled', true);; if (click_name == 'mail_send') { //atr複数の場合オブジェクトにする $('#mail_send').attr({ 'action': "{{ route('users.mail_send') }}" }); } if (click_name == 'confirm') { $('#mail_send').attr('action', "{{ route('users.confirm') }}"); } $('#mail_send').submit(); }); // .custom-file-input $('.mainImg-file-input').on('change', handleFileSelect); function handleFileSelect(evt) { $('.imagePreview-main').attr({ src: '', alt: '' }) var files = evt.target.files; var reader = new FileReader(); reader.onload = (function(theFile) { return function(e) { if (theFile.type.match('image.*')) { $('.imagePreview-main').attr({ src: e.target.result, alt: theFile.name }); $.cookie('main', e.target.result); } else { alert('画像ではありません'); } }; })(files[0]); reader.readAsDataURL(files[0]); $(this).next('.custom-file-label').html(files[0].name); } $(document).on('change', '.menus_img', function() { var input = $(this); var files = !!this.files ? this.files : []; input.next('.custom-file-label').html(files[0].name); if (!files.length || !window.FileReader) return; // no file selected, or no FileReader support if (/^image/.test(files[0].type)) { // only image file var reader = new FileReader(); // instance of the FileReader reader.readAsDataURL(files[0]); // read the local file reader.onloadend = function() { input.parent().parent().next('.imagePreview').find('.imagePreview-menus').attr('src', this .result); } } }); //メインファイルの取消 $('.main_reset').click(function() { $('.imagePreview-main').attr({ src: '', alt: 'メイン画像' }); $('.main-lavel').html(''); }) //ファイルの取消 $('.reset').click(function() { $(this).parent().prev().children('.custom-file-label').html('ファイル選択...'); $(this).parent().parent().next('.imagePreview').find('.imagePreview-menus').attr('src', ''); $('.custom-file-input').val(''); }) </script> @endpush UserControllerの編集 app\Http\Controllers\UserController.php //メール本文入力ホーム public function index() { return view('Users.customer.index'); } //確認画面 public function confirm(Request $request) { $tmp_folder="img/".uniqid("tmp_")."/"; $tmp_files =[]; foreach ($request->file('img-files') as $key => $tmp_file) { if ($tmp_file->isValid()) { //画像の$pathを作成 'storage'なら保存/削除 'public'なら取得となるよう作成する。 $tmp_files[$key] = $tmp_folder.Str::of($tmp_file->getClientOriginalName())->replace(' ', '_'); // storage\appからの相対パス 画像を保存している。 $tmp_file->storeAs("public", $tmp_files[$key]); // src="{{ asset('storage/'.$tmp_file) } } return view('mail_templates.html_customer_mail', compact('request','tmp_files')); } //notify送信メソッド public function mail_send(Request $request) { $users = User::all(); $tmp_folder="img/".uniqid("tmp_")."/"; $tmp_files =[]; //画像を保存 foreach ($request->file('img-files') as $key => $tmp_file) { if ($tmp_file->isValid()) { //画像の$pathを作成 'storage'なら保存/削除 'public'なら取得となるよう作成する。 $tmp_files[$key] = $tmp_folder.Str::of($tmp_file->getClientOriginalName())->replace(' ', '_'); // storage\appからの相対パス 画像を保存している。 $tmp_file->storeAs("public", $tmp_files[$key]); // src="{{ asset('storage/'.$tmp_file) } } $inputs = $request->all(); // notification+mailable Notification::send($users, new User_mail_send/*noficationクラス*/($inputs,$tmp_files)); return redirect()->route('users.index'); } notificationファイルの作成 app\Notifications\User_mail_send.php php artisan make:notification User_mail_send app\Notifications\User_mail_send.php <?php namespace App\Notifications; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification; use App\Mail\User_mailable_send; use Illuminate\Support\Facades\Request; class User_mail_send extends Notification //queue送信するときはコメントアウトを解除したらいいだけ。 // implements ShouldQueue { use Queueable; protected $request; protected $tmp_files; public function __construct($request, $tmp_files) { $this->request = $request; $this->tmp_files = $tmp_files; } public function via($notifiable) { return ['mail']; } public function toMail($notifiable) { $request = $this->request; $tmp_files = $this->tmp_files; //mailableで送信 return (new \App\Mail\User_mailable_send()/**空のmailableクラスを作成するだけ*/) ->view('mail_templates.html_customer_mail', compact('request', 'tmp_files')) ->from(config('mail.from.address')) //->to()はここでも指定する必要がある必須。 ->to($notifiable->email)/** Notification::send($users<-コレが$notifiable, new User_mail_send($inputs,$tmp_files)); */ ->subject('今週のおすすめメニュー'); } public function toArray($notifiable) { return [ ]; } } 空のmailableクラスを作成する php artisan make:mail User_mailable_send app\Mail\User_mailable_send.php <?php namespace App\Mail; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Mail\Mailable; use Illuminate\Queue\SerializesModels; class User_mailable_send extends Mailable { use Queueable, SerializesModels; public function __construct() { } public function build() { } } 1.Mailファサードを使ったメール送信 html送信+text送信+多重送信 app\Http\Controllers\MailController.php //メールファザードで送信 public function mail_facades_send(MaileRequest $request) { $tmp_files = Storage::files('public/'.$request->tmp_img_files); //メールファザードではをキューを使って送信できない。 Mail::send( /*html送信*/ 'mail_templates.html_mail',/* 第1引数 viewsPath */ // /*text送信*/ ['text' => 'mail_templates.text_mail'/*viewsPath*/], $request->all()/**第2引数 配列[]*/, function ($message/*中身不明dd($message)*/) use ($request, $tmp_files) { // 第3引数にはコールバック関数を指定し、 // その中で、送信先やタイトルの指定を行う. $message ->to($request->email) ->bcc('admin@sample.com') //envより優先される。なければenvが渡る。 ->from('laravel@sample.co.jp', mb_encode_mimeheader('laravel事務局')) ->subject("お問い合わせありがとうございます。"); //複数ファイルを添付する。 foreach ($tmp_files as $key => $tmp_file) { $message->attach(public_path(Storage::url($tmp_file))); } } ); // // *** メールファザードで複数送信したい場合。*** // // // $emails = ['tester@blahdomain.com', 'anotheremail@blahdomian.com']; // Mail::send('mail_templates.html_mail', $request->all(), function ($message) use ($request, $emails) // { // $message->from('no-reply@yourdomain.com', 'Joe Smoe'); // // $message->to( $request->input('emails') ); // $message->to( $emails); // //Add a subject // $message->subject("New Email From Your site"); // }); //ファイルの削除 // Storage::delete('public/'.$request->tmp_img_files); Storage::deleteDirectory('public/'.$request->tmp_img_files); //2重送信を防止できる。リダイレクト先はExceptionsでhandleする。 $request->session()->regenerateToken(); return view('contact.thanks'); } テンプレートファイル resources\views\mailTemplates\mail.blade.php お問い合わせ内容を受け付けました。<br> <br> ■メールアドレス<br> {{ $email }}<br> <br> ■タイトル<br> {{ $title }}<br> <br> ■お問い合わせ内容<br> {{ nl2br($body) }}<br> テキストメールテンプレートファイル resources\views\mailTemplates\mail_text.blade.php お問い合わせ内容を受け付けました。 HTMLは書かない。<br>とか ■メールアドレス {{ $email }} ■タイトル {{ $title }} ■お問い合わせ内容 {{ nl2br($body) }} 2.Maileableで送信 1.Mailableクラスの作成 コントローラー //メーラブルで送信 public function mailable_send(MaileRequest $request) { ////複数人に送信する場合配列で渡す $to = [$request->email, 'ABC@gmail.com']; //// メールにエイリアスをつけたい場合 // $to = [ // ['email' => 'AAA@text.com','name' => 'Test', ], // ['email' => 'BBB@text.com','name' => 'Test2',], // ]; // $users = User::all(); $to = $users //でもOK usersテーブルのemail、nameの列が使用される。 //ファイルを配列で取得 $tmp_files = Storage::files('public/'.$request->tmp_img_files); $inputs = $request->all(); // //普通に送信 Mail::to($to)->send(new MailableSend($inputs/*$requestでもOK*/,$tmp_files)); //queue送信 引数では$requestオブジェクトをそのまま取得できない // Mail::to($to)->queue(new MailableSend($inputs/*$requestはダメ絶対駄目*/,$tmp_files)); //dispatchでjobファイルを発火 引数では$requestオブジェクトをそのまま取得できない // SendHelloEmail::dispatch($inputs/*$requestはダメ絶対駄目*/,$tmp_files,$to); // 画像を削除 //ファイルを削除 // Storage::delete('public/'.$request->tmp_img_files); //ディレクトリを削除 // Storage::deleteDirectory('public/'.$request->tmp_img_files); //queue送信するときは送る前に削除するからjobで実行しないとエラーになる DeleteFile::dispatch($request->tmp_img_files); //2重送信を防止できる。リダイレクト先はExceptionsでhandleする。 $request->session()->regenerateToken(); return view('contact.thanks'); } メイラブルクラスを作成する。 php artisan make:mail SendTestMail app\Mail\MailableSend.php <?php namespace App\Mail; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Mail\Mailable; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Storage; class MailableSend extends Mailable //普通の時dispatchのときはコメントアウト // implements ShouldQueue { use Queueable, SerializesModels; protected $request; protected $tmp_files; //引数で受け取った変数を利用する publicの場合はそのまま利用できる。 public function __construct($request, $tmp_files) { //protectedの場合$thisで受け取る $this->request = $request; $this->tmp_files = $tmp_files; } public function build() { // リクエストの変数展開 extract($this->request); $tmp_files = $this->tmp_files; // //htmlメール $this->view $mail = $this->view('mail_templates.html_mail', compact('email', 'title', 'body','tmp_files')) // //テキストメールもいける。 // $this->text('mail_templates.text_mail',compact('email','title','body')) // //to はコントローラーで指定する。 // // ✗->to($request->email) ->bcc('admin@sample.com') // //envより優先される。なければenvが渡る。 ->from('laravel@sample.co.jp', 'Laravel事務局') ->subject("お問い合わせありがとうございます。"); //添付送信 foreach ($this->tmp_files as $tmp_file) { $mail->attach(public_path(Storage::url($tmp_file))); } return $mail; } } queue送信するときは implements ShouldQueue を追記してやるだけでmailableでの設定はOK class MailableSend extends Mailable //普通の時dispatchのときはコメントアウト implements ShouldQueue { jobファイルで送信するとき app\Jobs\SendHelloEmail.php app\Jobs\SendHelloEmail.php <?php namespace App\Jobs; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Mail; use App\Mail\MailableSend; class SendHelloEmail implements ShouldQueue //queue送信するときは追記 { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; //ジョブがタイムアウトになるまでの秒数 defautは60秒 public $timeout = 120; protected $request; protected $tmp_files; protected $to; public function __construct($request, $tmp_files,$to) { $this->request = $request; $this->tmp_files = $tmp_files; $this->to = $to; } public function handle() { //複数人に送信する場合配列で渡す $to = $this->to; Mail::to($to)->send(new MailableSend($this->request, $this->tmp_files)); } } 3.notifyで送信 コントローラー public function friends_mail_send(Request $request){ $friends = User::whereIn('id',$request->friend_ids)->get(); $user = User::find(Auth::id()); // $friend = User::find($request->friend_id); // $friend->notify(new friends_mail_send()); //複数人送信する時 Notification::send($friends, new friends_mail_send($user,$request->except('friend_ids'),$friends)); return redirect()->route('friends.index')->with('success','送信しました'); } notificationを作成する(マークダウンを作成して送信) php artisan make:notification friends_mail_send app\Notifications\friends_mail_send.php <?php namespace App\Notifications; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification; class friends_mail_send extends Notification //queue送信するときはコメントアウトを解除したらいいだけ。 implements ShouldQueue { use Queueable; protected $user; protected $request; protected $friends; public function __construct($user, $request, $friends) { $this->user = $user; $this->request = $request; $this->friends = $friends; } public function via($notifiable) { return ['mail']; } public function toMail($notifiable) { return (new MailMessage) ->markdown('mail_templates.friends_mail_send', [ 'user' => $this->user, 'title' => $this->request['title'], 'body' => $this->request['body'], 'for_friend' => $notifiable, 'friends' => $this->friends, // 'url' => $this->url, // 'token' => $this->token, ]) // ->error()//actionのボタンがdanger ->success()//actionのボタンがsuccess ->line('アクションの前のライン1.') ->line('アクションの前のライン2.') ->line('アクションの前のライン3.') ->action('Notification Action', Url('/')) ->line('アクションの後のライン4') ->line('アクションの後のライン5.') ->subject("{$this->user->name}からあなた宛に通知があります"); } public function toArray($notifiable) { return []; } } マークダウンファイルを作成 viewsに作成する resources\views\mail_templates\friends_mail_send.blade.php @component('mail::layout') {{-- logoにリンクがはれるコンポーネント --}} @slot('header') {{-- @component('mail::header', ['url' => $url]) {{ config('app.name') }} @endcomponent --}} @endslot {{-- Body --}} {{$user->name}}さんから連絡です。 - {{ ($for_friend['name']) }}さんへ @component('mail::panel') ## その他<br> @foreach ($friends as $friend) @if ($friend['id'] !== $for_friend['id']) {{$friend['name'] }} @endif @endforeach さん達へ @endcomponent タイトル ---- {{ $title }} # 内容 {{ Illuminate\Mail\Markdown::parse($body) }} {{ $body }} {{-- Intro Lines actionより上のline --}} @foreach ($introLines as $line) {{ $line }} @endforeach {{-- Outro Lines actionより下のline --}} @foreach ($outroLines as $line) {{ $line }} @endforeach {{-- Action Button --}} @isset($actionText) <?php switch ($level) { case 'success': case 'error': $color = $level; break; default: $color = 'primary'; } ?> @component('mail::button', ['url' => $actionUrl, 'color' => $color]) {{ $actionText }} @endcomponent @endisset @component('mail::table') | Laravel | Table | Example | | ------------- |:-------------:| --------:| | Col 2 is | Centered | $10 | | Col 3 is | Right-Aligned | $20 | @endcomponent @component('mail::button', ['url' => 'https://qiita.com/','color'=>'success']) google @endcomponent {{-- Subcopy actionButtonが効かなかった時の予備--}} @isset($actionText) @slot('subcopy') @lang( "If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\n". 'into your web browser:', [ 'actionText' => $actionText, ] ) <span class="break-all">[{{ $displayableActionUrl }}]({{ $actionUrl }})</span> @endslot @endisset {{-- Footer --}} @slot('footer') @component('mail::footer') © {{ date('Y') }} {{ config('app.name') }}. @lang('All rights reserved.') @endcomponent @endslot @endcomponent 既存のマークダウンを編集するときは php artisan vendor:publish --tag=laravel-mail notificationをmailableで送信 コントローラー public function mail_send(Request $request) { $inputs = $request->all(); // notification+mailable Notification::send($users, new User_mail_send/*noficationクラス*/($inputs,$tmp_files)); return redirect()->route('users.index'); } nofiticationsファイル app\Notifications\User_mail_send.php <?php namespace App\Notifications; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification; class User_mail_send extends Notification //queue送信するときはコメントアウトを解除したらいいだけ。 // implements ShouldQueue { use Queueable; protected $request; protected $tmp_files; public function __construct($request, $tmp_files) { $this->request = $request; $this->tmp_files = $tmp_files; } public function via($notifiable) { return ['mail']; } public function toMail($notifiable) { $request = $this->request; $tmp_files = $this->tmp_files; //mailableで送信 return (new \App\Mail\User_mailable_send()/**空のmailableクラスを作成するだけ*/) ->view('mail_templates.html_customer_mail', compact('request', 'tmp_files')) ->from(config('mail.from.address')) //->to()はここでも指定する必要がある必須。 ->to($notifiable->email)/** Notification::send($users<-コレが$notifiable, new User_mail_send($inputs,$tmp_files)); */ ->subject('今週のおすすめメニュー'); } public function toArray($notifiable) { return [ ]; } } 空のmailableクラスを作成するだけ <?php namespace App\Mail; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Mail\Mailable; use Illuminate\Queue\SerializesModels; class User_mailable_send extends Mailable { use Queueable, SerializesModels; public function __construct() { } public function build() { } }
- 投稿日:2021-08-29T01:08:37+09:00
composer require -w tymon/jwt-authがエラー
趣向 composer require -w tymon/jwt-auth を使用するとエラーが出てしまい jwt-authをインストールすることができなかった。 Laravel: 8.40 PHP: 8.0 エラー 下記のエラーがでてしまう。 Your requirements could not be resolved to an installable set of packages. Problem 1 - Root composer.json requires tymon/jwt-auth ^0.5.12 -> satisfiable by tymon/jwt-auth[0.5.12]. - tymon/jwt-auth 0.5.12 requires illuminate/support ~5.0 -> found illuminate/support[v5.0.0, ..., 5.8.x-dev] but these were not loaded, likely because it conflicts with another require. Installation failed, reverting ./composer.json and ./composer.lock to their original content. 解決 以下のコマンドで実行する。 composer require -w tymon/jwt-auth --ignore-platform-reqs ※composer require barryvdh/laravel-cors コマンドも同様でした
- 投稿日:2021-08-29T00:45:55+09:00
Vagrant環境でnpm installができない時に試すべきこと
目次 はじめに 開発環境 前提 生じた問題 対応策 まとめ 参考記事 はじめに この記事では、Vagrantを用いてnpm installを行おうとしたときに生じたエラーについて扱います。 私の開発環境は以下の通りですので、ホストOSがwindows環境の方などは注意してください。 なお、Laravelでの開発を行なっていますが、npmを用いてパッケージ管理をする場合、対処法は共通だと思われます。 開発環境 ハードウェア 項目 内容 OS mac OS Big Sur 11.5 端末種類 MacBook Air (Retina, 13-inch, 2020) プロセッサ 1.1 GHz クアッドコアIntel Core i5 メモリ 16 GB 3733 MHz LPDDR4X グラフィックス Intel Iris Plus Graphics 1536 MB ソフトウェア 項目 ホストOSバージョン ゲストOSバージョン Vagrant 2.2.13 - VirtualBox 6.1.16 - PHP 7.4.22 7.2.26 Laravel 6.20.32 5.5.28 node.js 16.0.0 8.16.2->10.16.2 npm 7.10.0 6.4.1->6.9.0 私の場合は、上記に加えて、チーム開発のためpackage.jsonが既に与えられている状態でした。 前提 Vagrantを用いて仮想マシン構築ができている 今回はvirtualboxを用いています。 仮想マシンに接続することができる vagrant sshで接続してください。 vagrantの使い方に関しては以下リンクを参照してください。 【Laravel超入門】開発環境の構築(VirtualBox + Vagrant + Homestead + Composer) 生じた問題 npm installを行うと、以下のようなエラーが出ました。 vagrant(terminal) npm ERR! path /home/vagrant/code/node_modules/acorn-dynamic-import/node_modules/acorn/package.json.2270336935 npm ERR! code ENOENT npm ERR! errno -2 npm ERR! syscall open npm ERR! enoent ENOENT: no such file or directory, open '/home/vagrant/code/node_modules/acorn-dynamic-import/node_modules/acorn/package.json.2270336935' npm ERR! enoent This is related to npm not being able to find a file. npm ERR! enoent 対応策 まず、Vagrantの共有フォルダ上で'npm install'が動かない場合の対処法およびnpm トラブルシューティングを試してみてください。 私の環境下では、上記方法でも解決しませんでした。そのため、以下の対処を行いました。 vagrantにnvmをインストールした。 nvmのインストール方法はこちら(Vagrantで【nvm】を使用して《node.js》と《npm》を入れる方法)を参照してください。 nvmでなくても、node.jsとnpmのバージョン管理ができるものであれば良いです。 その後、nvmを用いて、node.jsとnpmのバージョンアップを行いました。 用いるべきバージョンは、各人によって異なります。 上記問題の原因は、package.jsonにおいて求められているnode/npmのバージョンが、ゲストOS環境のnode/npmバージョンと一致していないことにあります。 そのため、ホストOSのnode/npmのバージョンがpackage.jsonの必要とするバージョンを満たす場合、上記URLの「ホスト側でnpm installする」ことで解決することになります。 ホストOS, ゲストOSのどちらもがnode/npmのバージョン条件を満たさないために、エラーが残ってしまっていたみたいです。 私の場合は、nodeを10.16.2にアップグレードすることで利用可能になりました。 バージョンを上げすぎてもpackage.json側の要件によってはエラーが生じるため、いくつかバージョンを変えて試してみてください。 まとめ Vagrantの共有フォルダ上で'npm install'が動かない場合の対処法 npm トラブルシューティング node.jsとnpmのバージョンを、package.jsonの条件にしたがってアップデートする。 参考記事 gitにissueがありました。 NPM Install failing on clean homestead box